笔者有幸,参加了第十三届蓝桥杯嵌入式比赛,由于笔者使用的是旧板子(F103RBT6),所以在编程时用的是Keil473,在比赛过程中遇到了不少问题。
在比赛开始一个半小时后,初始化跟大体框架都写完了,在调试按键时碰到了第一个BUG,KEY1按不动。在之前做往届赛题时根本没出现的BUG,由于之前没碰到过,在赛场上就直接背模板,嘎嘎乱敲,使用结构体数组+定时器扫描的模式。以下是扫描函数。
void TIM3_IRQHandler(void){//KEY //10ms
int i = 0;
key[0].key_sta = GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0);
key[1].key_sta = GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8);
key[2].key_sta = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1);
key[3].key_sta = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2);
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
for(i=0;i<4;++i)
{
switch (key[i].stap)
{
case 0:
{
if(key[i].stap==0)
{
key[i].stap=1;
key[i].key_time=0;
}
}
break;
case 1:
{
if(key[i].key_sta==0)
{
key[i].stap=2;
}
else key[i].stap=0;
}
break;
case 2:
{
if(key[i].key_sta==1)
{
key[i].stap=0;
if(key[i].key_time<70) key[i].short_flag=1;
}
else
{
key[i].key_time++;
if(key[i].key_time>70) key[i].long_flag=1;
}
}
break;
}
}
}
}
在测试的时候发现KEY1不好使,按下无反应,换KEY2有反应,当时人都麻了。
经过后期调试,发现问题存在于数组里,将key[x]中的所有下标增加一位,也就是从1开始,哎,它就好使,从0开始1号按键就不能用,还有一个问题就是结构体,愚以为用结构体将商品的数量、单价、库存用结构体来写比较方便,但是,X商品的数量会无故暴增,即使定义为int,在显示时也是到5亿多,现在我也没解决这些问题,但是,在Keil5中就没有这些问题,不晓得是不是编译器的问题。欢迎各位大哥指正!
不废话了,上干货!
LCD部分
对于这一部分没什么太多需要注意的点,唯一需要注意的就是商品价格界面的价格范围在
1.0---2.0,也就是说当价格大于2.0时,要将价格设置为1.0。
附LCD部分的代码
void LCD_func(void)
{
switch(MODE)
{
case 1:{
sprintf((char*)LCD_str," SHOP ");
LCD_DisplayStringLine(Line2,LCD_str);
sprintf((char*)LCD_str," X:%d ",X_num);
LCD_DisplayStringLine(Line4,LCD_str);
sprintf((char*)LCD_str," Y:%d ",Y_num);
LCD_DisplayStringLine(Line5,LCD_str);
}break;
case 2:{
sprintf((char*)LCD_str," PRICE ");
LCD_DisplayStringLine(Line2,LCD_str);
sprintf((char*)LCD_str," X:%.1f ",(float)price[0]/10);
LCD_DisplayStringLine(Line4,LCD_str);
sprintf((char*)LCD_str," Y:%.1f ",(float)price[1]/10);
LCD_DisplayStringLine(Line5,LCD_str);
}break;
case 3:{
sprintf((char*)LCD_str," REP ");
LCD_DisplayStringLine(Line2,LCD_str);
sprintf((char*)LCD_str," X:%d ",stock[0]);
LCD_DisplayStringLine(Line4,LCD_str);
sprintf((char*)LCD_str," Y:%d ",stock[1]);
LCD_DisplayStringLine(Line5,LCD_str);
}break;
}
}
KEY部分
KEY1
切换界面,在商品购买、商品价格、库存三个界面来回切换,无非就是一个变量的累加,到界限后回滚。
KEY1代码:
if(key[0].short_flag == 1)
{
key[0].short_flag = 0;
MODE ++;
if(MODE == 4) MODE = 1;
}
KEY2
定义为商品X的累加键。
在商品购买界面下,B2单击购买量++,但要注意的是,购买量只能在0到库存的范围内,当购买量大于当前库存时,购买量回滚到0。并且库存发生变化时,购买量的范围也发生相应变化。
在商品价格界面下,B2单击商品X的单价增加0.1,但还是存在范围限制,智能在1.0到2.0之间调整,大于2.0时回滚到1.0。
在库存界面下,B2单击X库存++,但题目并未指出库存上限,为了防止意外,建议库存的变量可以用范围大一点的类型来定义。
KEY2代码:
if(key[1].short_flag == 1)
{
key[1].short_flag = 0;
switch(MODE)
{
case 1:{
X_num += 1;
if(X_num == (stock[0]+1)) X_num = 0;
}break;
case 2:{
price[0] += 1;
if(price[0] == 21) price[0] = 10;
EEPROM_write(price,0x02,2);
Delay_Ms(10);
}break;
case 3:{
stock[0]++;
stock[0] = stock[0] - X_num;
EEPROM_write(stock,0x00,2);
Delay_Ms(10);
}break;
}
}
KEY3
与B2相同,不在赘述。
KEY3代码:
if(key[2].short_flag == 1)
{
key[2].short_flag = 0;
switch(MODE)
{
case 1:{
Y_num += 1;
if(Y_num == (stock[1]+1)) Y_num = 0;
}break;
case 2:{
price[1] += 1;
if(price[1] == 21) price[1] = 10;
EEPROM_write(price,0x02,2);
Delay_Ms(10);
}break;
case 3:{
stock[1]++;
stock[1] = stock[1] - Y_num;
EEPROM_write(stock,0x00,2);
Delay_Ms(10);
}break;
}
}
KEY4
定义为“确认键”。需要注意的是只有在商品购买界面下按下B4才有效,我这里用标志位的方式来进行操作。
KEY4代码:
if(key[3].short_flag == 1)
{
key[3].short_flag = 0;
if(MODE == 1)
{
stock[0] = stock[0]-X_num;
stock[1] = stock[1]-Y_num;
EEPROM_write(stock,0x00,2);
FLAG.S5 = 1;
FLAG.DU30 = 1;
S5_e = 1;
FLAG.USART = 1;
}
}
EEPROM部分
题目要求储存商品的库存数量跟价格,还要求发生变动才存储,无变化不写入,这也就意味着不能采用定时写入的方式,于是乎,我才用了在按键按下后就写入,改变一次就写入一次,但这确实有一个可以抬杠的地方,原本价格是1.3,我按了一圈回来又到1.3,这算不算改变,要不要写入。对此,我的看法是改变了,还改变了不止一次,所以要写入。此处欢迎交流。
还有一点就是上电读取数据。由于不知道官方测试时用的24C02里存的是什么,所以在上电读取时需要判断一下上次写入的是不是想要的数据。对此,我的想法是运用校验位。如果校验位不对,就说明不是我要的数据,所以直接用预定的数据将对应地址的原数据覆盖,并且写入校验位。当检验位匹配正确后,就可以将对应地址的数据读出,处理后直接赋给变量。
数据写入就在按键程序里,往上看看。
下边是上电校验的代码:
EEPROM_read(check_r,0x66,2);
Delay_Ms(10);
if((check_r[0]==6)&&(check_r[1]==6))
{
EEPROM_read(price,0x02,2);
Delay_Ms(10);
EEPROM_read(stock,0x00,2);
Delay_Ms(10);
}
else
{
EEPROM_write(price,0x02,2);
Delay_Ms(10);
EEPROM_write(stock,0x00,2);
Delay_Ms(10);
EEPROM_write(check_w,0x66,2);
Delay_Ms(10);
LED_Control(LED8,ON);
}
串口部分
在购买界面下,B4单击,串口向上位机发送数量跟总价格,格式为:X:2,Y:2,Z:4.0。那么问题又来了,要不要\r\n,此处欢迎交流。
在任意界面下,给单片机发送'?',单片机给上位机返回当前的商品价格。格式为:X:1.0,Y:1.0。这里还需要注意一点,接收到的字符长度。
串口接收“?”代码如下:
void USART_func(void)
{
u8 len;
if(USART_RX_STA&0x8000)
{
len = USART_RX_STA&0x3fff;
USART_RX_STA = 0;
if(len == 1)
{
if(USART_RX_BUF[0] == '?')
printf("X:%.1f,Y:%.1f",((float)price[0]/10),((float)price[1]/10));
}
}
}
B4按下后,向上位机发送数据代码:
if(FLAG.USART == 1)
{
Z = (X_num*((float)price[0]/10)+Y_num*((float)price[1])/10);
printf("X:%d,Y:%d,Z:%.1f",X_num,Y_num,Z);
X_num = 0;
Y_num = 0;
FLAG.USART = 0;
}
LED部分
LED1在B4按下后点亮5s,后熄灭。
LED2在X与Y的库存均为0时才以0.1s间隔闪烁。那么也就是在库存不全为0,LED2为熄灭状态。
LED控制代码:
//LED
void LED_Control(uint16_t LED,uint8_t LED_Status){
uint16_t LED_Buff=0xffff;
uint16_t pinpos = 0x0000,pos = 0x0000,currentpin = 0x0000;
for(pinpos = 0x00;pinpos < 0x08;pinpos ++)
{
pos = ((uint32_t)0x01) << (pinpos + 0x08);
currentpin = (LED) & pos;
if(currentpin == pos)
{
if(LED_Status == OFF){
GPIOC_Buff |= (((uint32_t)0x0001) << (pinpos + 0x08));
}
else if(LED_Status == ON)
{
GPIOC_Buff &=~ (((uint32_t)0x0001) << (pinpos + 0x08));
}
else
{
LED_Buff = GPIOC_Buff;
if(LED_Buff & (((uint32_t)0x0001) << (pinpos + 0x08))) //LED 口为1,表示LED关着, 反转则需要开启
{
GPIOC_Buff &=~ (((uint32_t)0x0001) << (pinpos + 0x08));
}
else{
GPIOC_Buff |= (((uint32_t)0x0001) << (pinpos + 0x08));
}
}
}
}
GPIOC->ODR |= 0Xff00; //清空LCD写入的缓存问题!!!
GPIOC->ODR &= GPIOC_Buff; //只有开的LED才有资格被写入ODR
GPIO_SetBits(GPIOD,GPIO_Pin_2); //高电平允许
GPIO_ResetBits(GPIOD,GPIO_Pin_2); //低电平状态锁存
}
LED2闪烁代码:
if((stock[0]==0)&&(stock[1]==0))
{
S01_e = 1;
if(S01 == 1)
{
LED_Control(LED2,FILP);
S01 = 0;
S01_e = 0;
}
}
else LED_Control(LED2,OFF);
LED1亮5s代码:
if(FLAG.S5 == 1)
{
LED_Control(LED1,ON);
if(S5 == 1)
{
LED_Control(LED1,OFF);
FLAG.S5 = 0;
S5_e = 0;
}
}
PWM部分
在B4按下后,PA1输出频率2KHz,占空比30%的方波,时长为5s,后PA1输出频率2KHz,占空比5%的方波。也就是说,只有B4按下后的5s内,PA1的占空比为30%,其余都是5%。
PWM输出代码:
void PWM_Set(u32 ch1fre,u32 ch1duty,u32 ch2fre,u32 ch2duty)
{
ch1val=1000000/ch1fre;
ch2val=1000000/ch2fre;
ch1dut=ch1val*ch1duty/100;
ch2dut=ch2val*ch2duty/100;
}
void TIM2_IRQHandler(void){ //PWM
u16 capture;
if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2 );
capture = TIM_GetCapture2(TIM2);
if(ch1flag) TIM_SetCompare2(TIM2, capture + ch1dut );
else TIM_SetCompare2(TIM2, capture +ch1val- ch1dut );
ch1flag^=1;
}
if (TIM_GetITStatus(TIM2, TIM_IT_CC3) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_CC3);
capture = TIM_GetCapture3(TIM2);
if(ch2flag) TIM_SetCompare3(TIM2, capture + ch2dut );
else TIM_SetCompare3(TIM2, capture +ch2val- ch2dut );
ch2flag^=1;
}
}
B4按下后以及其他情况下的PWM设置:
if(FLAG.DU30 == 1) PWM_Set(2000,30,2000,100);
else PWM_Set(2000,5,2000,100);
附完整工程:
链接:https://pan.baidu.com/s/1YRrYYJLpPus-Iqx04gsy2g?pwd=LMTX
提取码:LMTX
小生文采拙劣,附几位泰斗的文章。
嵌入式组:第十三届——蓝桥杯嵌入式第二场_WENFGCG的博客-CSDN博客
(97条消息) 第十三届第二场蓝桥杯嵌入式主观题讲解_amkl的博客-CSDN博客
单片机组:蓝桥杯单片机第十三届第二场_XzKaiXinFh的博客-CSDN博客
蓝桥杯十三届第二场程序设计_Namaste795的博客-CSDN博客_第十三届蓝桥杯第二场