扩展板中的数码管讲解
蓝桥杯嵌入式扩展板有三个共阴数码管,用3个74LS595来控制。74LS595是串/并转换,在时钟信号的作用下,将串口输入的8个高低电平锁存到芯片中并并行输出。如同单片机中的LED点阵。其电路图为
相应的跳线帽链接如图所示
其P3口和P4口的1、2、3要用跳线帽相连。真实实物连接方法如下
数码管的初始化代码为
void STM3210B_SEG_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
将PA1、PA2、PA3设置为上拉输出。在比赛时可以根据电路图确定需要设置的是哪个I/O口。
数码管显示代码为
void SEG_DisplayValue(u8 Bit1, u8 Bit2, u8 Bit3)
{
u8 i = 0; //
u8 code_tmp = 0;
code_tmp = Seg7[Bit3];
for(i=0;i<8;i++)
{
if(code_tmp & 0x80)SER_H;
else SER_L;
SCK_H;
code_tmp = code_tmp << 1;
SCK_L;
}
code_tmp = Seg7[Bit2];
for(i=0;i<8;i++)
{
if(code_tmp & 0x80)SER_H;
else SER_L;
SCK_H;
code_tmp = code_tmp << 1;
SCK_L;
}
code_tmp = Seg7[Bit1];
for(i=0;i<8;i++)
{
if(code_tmp & 0x80)SER_H;
else SER_L;
SCK_H;
code_tmp = code_tmp << 1;
SCK_L;
}
RCLK_H;
RCLK_L;
}
其中SER_H、SER_L、SCK_H、SCK_L、RCLK_H、RCLK_L的定义是
#define RCLK_H GPIO_SetBits(GPIOA,GPIO_Pin_2)
#define RCLK_L GPIO_ResetBits(GPIOA,GPIO_Pin_2)
#define SER_H GPIO_SetBits(GPIOA,GPIO_Pin_1)
#define SER_L GPIO_ResetBits(GPIOA,GPIO_Pin_1)
#define SCK_H GPIO_SetBits(GPIOA,GPIO_Pin_3)
#define SCK_L GPIO_ResetBits(GPIOA,GPIO_Pin_3)
在相应的.h文件中定义。
函数中的参数决定数码管显示的内容,第一个参数决定第一个数码管的显示,第二个参数决定第二个数码管的显示......因为三个74LS595是通过级联的方式连接,即第一个的串行输出口QH‘与第二个74LS595的串口输入端SER相连。所以需要先将第三个的参数进行传递,再传递第二个参数,最后传递第一个参数。
变量code_tmp 的值是由参数根据数组Seg7确定的。数组Seg7保存的是数码管显示各种字符或数字的十六进制数。数组Seg7的定义为
uc8 Seg7[17] = { 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x4f,0x79,0x78,0x00};
这个需要自己提前推导。
语句if(code_tmp & 0x80)是判断变量code_tmp的最高位是否为1,如果为1,则将串口输入的电平拉高,否则拉低。由时钟信号的下降沿(由高电平跳变为低电平)锁存并输出到并行端口。其中为了让下降沿变得明显,所以在高低电平变化中添加变量code_tmp的移位命令,使时钟信号的高电平时间长一点,让芯片能够很好的捕捉到时钟信号的下降沿。也可以用延时来增加高电平的时间,但是最后还得需要对变量的移位code_tmp。变量code_tmp的移位是将每一位移到最高位,需要注意的是移到最高位需要左移函数。
ADC按键讲解
ADC按键的电路图为
ADC采集的电压为电容C的电压,当有不同的按键按下的话会有不同的电压值,对应电压值的不同计算出哪个按键按下。
对应的跳线帽为
ADC按键初始化代码为
void ADC_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// ADC1 工作模式配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //单次转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 5;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1, ADC_SampleTime_13Cycles5);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
/* Check the end of ADC1 reset calibration register */
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
/* Check the end of ADC1 calibration */
while(ADC_GetCalibrationStatus(ADC1));
}
与普通ADC初始化的区别在于结构体ADC_InitStructure的元素ADC_NbrOfChannel的值为5。GPIO口为PA5。所选通道为5,即ADC_Channel_5。其它代码都与普通ADC的一样。
ADC的采集
因为ADC的值是时刻在发生变化的,所以需要多次采集ADC的值,其中间的值为ADC采集的值。本例为采集50次。代码为
u16 Read_Btn(void)
{
u16 tmp;
u8 i = 0,j = 0;
for(i=0; i<BTN_BUFF_LEN; i++)
{
btn_buff[i] = Read_ADC();
}
for(i=0; i<=BTN_BUFF_LEN/2; i++)
{
for(j=0; j< BTN_BUFF_LEN-i-1; j++)
{
if(btn_buff[j+1] < btn_buff[j])
{
tmp = btn_buff[j+1];
btn_buff[j+1] = btn_buff[j];
btn_buff[j] = tmp;
}
}
}
if(BTN_BUFF_LEN % 2 == 0)
{
return(btn_buff[BTN_BUFF_LEN/2-1] + btn_buff[BTN_BUFF_LEN/2])/2;
}
else
{
return(btn_buff[BTN_BUFF_LEN/2]);
}
}
将采取的值放到一个数组btn_buff里,数组btn_buff的定义为
static u16 btn_buff[BTN_BUFF_LEN];
ADC值读取函数为
u16 Read_ADC(void)
{
u16 ADC_VALUE = 0;
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
ADC_VALUE = ADC_GetConversionValue(ADC1);
ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
return ADC_VALUE;
}
与普通ADC的区别在于读取的是2个字节的整型值,并且没有公式的转换。在软件打开后需要判断ADC的标志量的值,最后也要对标志量进行清除。
前一个for循环是读取50个ADC采样值,第二个for循环是对采样后的值进行从小到大排序,最后判断采集的值是否为偶数个。若是,则取中间两个值的平均数,否则取中间那个数。
常量BTN_BUFF_LEN的值是可以变化的,但是不能太小,否则读取到的采样值误差太大;也不能太多,否则按键读取速度慢。在这里变量BTN_BUFF_LEN的定义为
#define BTN_BUFF_LEN 50
按键转换函数
在读取完ADC的样值后需要转换成按键值,代码为
u8 Scan_Btn(void)
{
u16 btn_tmp = 0;
btn_tmp = Read_Btn();
if(btn_tmp <= 0x0020)
{
return 1;
}
else if((btn_tmp >= 0x00B0) && (btn_tmp <= 0x0100))
{
return 2;
}
else if((btn_tmp >= 0x0240) && (btn_tmp <= 0x0300))
{
return 3;
}
else if((btn_tmp >= 0x03B0) && (btn_tmp <= 0x0450))
{
return 4;
}
else if((btn_tmp >= 0x0500) && (btn_tmp <= 0x0600))
{
return 5;
}
else if((btn_tmp >= 0x0700) && (btn_tmp <= 0x0800))
{
return 6;
}
else if((btn_tmp >= 0x0840) && (btn_tmp <= 0x0940))
{
return 7;
}
else if(btn_tmp <= 0x0B50)
{
return 8;
}
else
{
return 0; //error status & no key
}
}
样值在某一区间的话,则被判为某一按键被按下。类似于ADC的抽样、量化、编码步骤。