基于ZYNQ的嵌入式学习笔记四(矩阵键盘实现电子琴)

本次要实现的是利用ZYNQ-7000板子上自带的16个矩阵键盘来实现每按一次发出一个音符的声音,从而实现电子琴的效果。

一、原理部分:

矩阵键盘:用8位来存储4*4的矩阵键盘的信息,初始化的值为00001111;

先将高四位设为输出,低四位设为输入,当第一行有按键按下时 ,第四位相应的位值变为0,但此时只能判定是哪一行的按键被按下,并不能判断具体是哪一个。 

将高四位设为输入,低四位设为输出,对应的那一列的值变为1,从而获取按下的按键值。

扬声器发生原理:计算机中发声普遍使用的是MIDI音乐,MIDI不是具体发声的部件,只是编码来驱动相应发声器件发声的。

本实验中发声的端口是DR6,DR6输出高电平会按照最高频率发声,按照固定频率输出高低电平会发出固定频率的声音。

为了得到准确的频率就需要能够准确的定时得到T/2的时间间隔,DR6在每次定时时间到之后将输出反向,即可得到准确的方波脉冲也就可以得到准确的发声频率。

音乐基本理论:

(1)音调:

音阶为等比数列,即下一个音阶高八度的频率是上一个音阶频率的两倍。国际标准音规定,钢琴的a1的频率是为440Hz;
又规定每相邻半音的频率比值为2^1/12≈1.059463,根据这规定,就可以得出钢琴上每一个琴键音的频率。简谱中的1、2、3、4、5、6、7对应钢琴琴键C、D、E、F、G、A、B由于每个音阶是12个半音所以这七个音调之间不全是半音关系即频率倍数为1.059463,只有E和F以及B和C1之间是半音关系即1.059463倍频率。C、D、E之间会有两个半音标记为#C和#DF、G、A、B之间会有三个半音标记为#F、#G和#A。

(2)节拍:在MIDI演奏中小节可以不作为参数考虑进程序中,而一拍的时间长度由演奏速度决定,如每分钟60拍则代表每拍时长1s。采用定时器进行精确定时用以产生对应频率的声音根据节奏和演奏速度决定某个频率的声音要演奏的时间长度每一个演奏的频率不能占100%的时长应该有间隔用来模仿人演奏时的音符间隔。

二、VIvado部分:

块设计部分:用一个一位的AXI GPIO来接发声的口(设为输出),用一个8位的AXI GPIO的IP来连接矩阵键盘(双向的),允许中断连接到PS上->创建HDL Wrapper->综合->配置管脚约束->生成bit流->export hardware->启动SDK。

I/O ports 在初始化设定pull type时,pullup 默认值为1,pulldown默认值为0,根据我们的设定,我们要将初始化值设为相反的即不输出信号的值。

三、代码实现部分(详解):

钢琴琴键:

#include "xil_exception.h"
//System Timer Tick
#define TIMER_FRQ 333333333          //计数频率同时也是1s的周期数
//钢琴琴键10倍频率,每秒中断间隔次数的5倍
#define KEY_A2   275
#define KEY_SA2  291
#define KEY_B2   309
#define KEY_C1   327
#define KEY_SC1  346
#define KEY_D1   367
#define KEY_SD1  389
#define KEY_E1   412
#define KEY_F1   437
#define KEY_SF1  462
#define KEY_G1   490
#define KEY_SG1  519
#define KEY_A1   550
#define KEY_SA1  583
#define KEY_B1   617
#define KEY_C    654
#define KEY_SC   693
#define KEY_D    734
#define KEY_SD   778
#define KEY_E    824
#define KEY_F    873
#define KEY_SF   925
#define KEY_G    980
#define KEY_SG   1038
#define KEY_A    1100
#define KEY_SA   1165
#define KEY_B    1235
#define KEY_c    1308
#define KEY_Sc   1386
#define KEY_d    1468
#define KEY_Sd   1556
#define KEY_e    1648
#define KEY_f    1746
#define KEY_Sf   1850
#define KEY_g    1960
#define KEY_Sg   2077
#define KEY_a    2200
#define KEY_Sa   2331
#define KEY_b    2469
#define KEY_c1   2616      //中音哆
#define KEY_Sc1  2772
#define KEY_d1   2937
#define KEY_Sd1  3111
#define KEY_e1   3296
#define KEY_f1   3492
#define KEY_Sf1  3700
#define KEY_g1   3920
#define KEY_Sg1  4153
#define KEY_a1   4400
#define KEY_Sa1  4662
#define KEY_b1   4939
#define KEY_c2   5233
#define KEY_Sc2  5544
#define KEY_d2   5873
#define KEY_Sd2  6223
#define KEY_e2   6593
#define KEY_f2   6985
#define KEY_Sf2  7400
#define KEY_g2   7840
#define KEY_Sg2  8306
#define KEY_a2   8800
#define KEY_Sa2  9323
#define KEY_b2   9878
#define KEY_c3   10470
#define KEY_Sc3  11090
#define KEY_d3   11750
#define KEY_Sd3  12450
#define KEY_e3   13190
#define KEY_f3   13970
#define KEY_Sf3  14800
#define KEY_g3   15680
#define KEY_Sg3  16610
#define KEY_a3   17600
#define KEY_Sa3  18650
#define KEY_b3   19760
#define KEY_c4   20930
#define KEY_Sc4  22170
#define KEY_d4   23490
#define KEY_Sd4  24890
#define KEY_e4   26370
#define KEY_f4   27940
#define KEY_Sf4  29600
#define KEY_g4   31360
#define KEY_Sg4  33220
#define KEY_a4   35200
#define KEY_Sa4  37290
#define KEY_b4   39510
#define KEY_c5   41860
#define KEY_NOP  0
/*
 * //几分音符////////////////////////////////////////*/
#define Beat_1Q1   1
#define Beat_1Q2   2
#define Beat_1Q4   4
#define Beat_1Q8   8
#define Beat_1Q16 16
//Musical note
typedef struct Music_Note
{
	u32 Note;
	u8  Beat;
	float Dotted;
} Muc_N;

初始化两个GPIO的配置以及初始化定时器的代码解析部分在这里就省略了。

当有键按下时允许全局中断,并且将flag值设为1.

void GpioHandler(void *CallbackRef)
{
XGpio *GpioPtr = (XGpio *)CallbackRef;
XGpio_InterruptGlobalDisable(&Gpio_BTN);
XGpio_InterruptClear(&Gpio_BTN,INTR_MASK);
ReadData=XGpio_DiscreteRead(&Gpio_BTN, LEDS_CHANNEL)&0xF;
if((ReadData&0xF)!=0xF)
{
IntrFlag=1;//全局变量
}
else
{
IntrFlag=0;
XGpio_InterruptGlobalEnable(&Gpio_BTN);
}
}

按下矩阵键盘,获取按下键的值:


XGpio_SetDataDirection(&Gpio_BTN, SEG7_CHANNEL, LOW4_MASK); //将独立按键的4位引脚设置为输入
XGpio_SetDataDirection(&Gpio_BTN, LEDS_CHANNEL, LOW4_MASK); //将矩阵键盘的8位分为高四位输出低四位输入
XGpio_DiscreteWrite(&Gpio_BTN, LEDS_CHANNEL, LOW4_MASK); //在高四位输出'0'
for(int i=0;i<Delay_Time;i++);//延迟再读一次
u8 GpioData=XGpio_DiscreteRead(&Gpio_BTN, LEDS_CHANNEL)&0xF;
u8 Leds_Group;
if(GpioData==ReadData) //如果两次读的一样,就说明是按键而不是抖动产生的,如果不相等则要滤掉
{
XGpio_SetDataDirection(&Gpio_BTN, LEDS_CHANNEL, 0XF0); //将矩阵键盘的8位分为高四位输入低四位输出
XGpio_DiscreteWrite(&Gpio_BTN, LEDS_CHANNEL, 0XF0); //在低四位输出'1'
GpioData=XGpio_DiscreteRead(&Gpio_BTN,LEDS_CHANNEL)&0xF0;

 针对每一个按键发出一个音符:

switch(ReadData& 0x0F)
{
case 0x0E:
switch(GpioData& 0xF0)
{
case 0xE0:
LoadIntTime(KEY_c2,Beat_1Q4,0.9);//根据音符设定相应的定时器频率
XScuTimer_LoadTimer(&TimerInstance, ReloadTime);//重装定时器
XScuTimer_Start(&TimerInstance);//启动定时器
/*后面的十五种情况省略*/
}
}

根据设定的音符和节拍,计算出定时器中断的频率以及停止时间: 

void LoadIntTime(u32 Note_Play,u8 Beat_Play,float Dotted_Play)
{
	ReloadTime=CalReloadTime(Note_Play,Beat_Play,Dotted_Play);
	TimerExpired=CalIntTime(ReloadTime,Beat_Play,Dotted_Play);
	//ReloadTime=0xFFFFFFFF-100;
}

static u32 CalReloadTime(u32 Note_Play,u8 Beat_Play,float Dotted_Play)
{

	if(Note_Play!=KEY_NOP)
	{
		return TIMER_FRQ/Note_Play*5*Dotted_Play;
	}
	else
	{
		return TIMER_FRQ/Beat_Play/PLAY_SPEED*MUS_BEAT*60*Dotted_Play;//

	}

}
static u32 CalIntTime(u32 Note_Play,u8 Beat_Play,float Dotted_Play)
{
	return CalReloadTime(0,Beat_Play,Dotted_Play)/Note_Play;
}

定时器处理函数,并输出扬声器电平,当TimerExpired减为0时将定时器停下来: 

static void TimerIntrHandler(void *CallbackRef)
{
XScuTimer *TimerInstancePtr = (XScuTimer *) CallbackRef;
if (XScuTimer_IsExpired(TimerInstancePtr))
{
XScuTimer_ClearInterruptStatus(TimerInstancePtr);
TimerExpired--;
		WriteData=~WriteData;
		if(TimerExpired==0)//该音符已经演奏完毕
		{
			XScuTimer_Stop(&TimerInstance);
			WriteData=0;
		}
		XGpio_DiscreteWrite(&Gpio_DEV, 1, WriteData);//输出扬声器电平

}
}

猜你喜欢

转载自blog.csdn.net/weixin_39626452/article/details/84956894