【蓝桥杯嵌入式·真题解析】第十三届蓝桥杯嵌入式设计与开发项目省赛(第二场)——程序设计试题
摘要:本文为作者对第十三届蓝桥杯嵌入式设计与开发项目省赛(第二场)——程序设计试题的解析。本文包括“题目要求”、“程序设计”、“效果展示”3个部分。供复盘使用。若发现错误之处,请不吝赐教。
摘要:本文为作者对第十三届蓝桥杯嵌入式设计与开发项目省赛(第二场)——程序设计试题的解析。本文包括“题目要求”、“程序设计”、“效果展示”3个部分。供复盘使用。若发现错误之处,请不吝赐教。
链接:蓝桥杯嵌入式方向备赛记录(STM32G431)为作者备赛蓝桥杯嵌入式过程中,整理的学习总结。包括各模块使用要点、各模块程序等,基本搭建好工程框架,给出了各模块处理程序。
写在前面:本道题在EEPROM上稍有难度,其他部分在内容和难度上和第一场差不多。
目 录
一、 题目要求





二、程序设计
(一)思路分析
前期已准备好模块程序、搭建好工程框架,下面结合本道题,实际分析工程思路:
1、涉及考点
按键、LED、EEPROM、定时器、串口、LCD等。
2、几条主线
(1)按键:要完成按键检测+功能处理。“按键检测”使用模块程序(非阻塞+移位消抖)即可。下面分析具体功能:B1:界面切换按键,程序上改变界面状态标志位即可;B2、B3:分别对应商品X、Y,在不同界面下按下按键,改变对应商品的购买数量、单价、库存,通过运算符对变量处理即可;B4:确认按键,处理好变量关系即可。
(2)LED:
![]()
LED指示灯要求 主要是LD1亮5秒,LD2以0.1秒为间隔闪:LD1点亮5秒后熄灭,可以通过Systic秒计数实现;LD2以0.1秒为间隔闪烁,可以通过设置LED处理函数刷新时间为0.1秒来实现(在LED处理函数中,if语句,库存量均为0时,翻转对应引脚。该函数0.1秒刷新一次,即可实现LD2以0.1秒为间隔闪烁)。
(3)定时器:一个脚,两个状态,输出“1路相同频率、不同占空比”的PWM。
![]()
PWM要求 本题,修改占空比(改变CCR值)即可。设置参数如下表所示:
波形 PSC ARR CCR 2KHz 5% 400 100 5 2KHz 30% 400 100 5 (4)串口:通过串口查询单价,显示价格。STM32接收:中断、1位数据,发送:串口重定向printf。
(5)LCD:很常规,不再赘述。
(6)EEPROM:
![]()
EEPROM要求 一般拿到题目,要分析注意以下2点:
- 题目明确初始状态(上电后为默认值、且题目未要求EEPROM)——不要EEPROM,直接变量赋初值就够了;
- 初次上电默认值(掉电保存:第一次上电为默认值,后面需要保存)——要EEPROM,要判断是否第一次上电。
本题就属于第二种情况,注意以下3点,待下文详细说明:
- (题目要求)……发生变动时,写入;无变化不写入;
- (题目要求)设备重新上电,能从EEPROM相应地址载入……须判断设备第一次上电?
- (EEPROM使用)连续读、写,需要延时。
3、注意点
(1)EEPROM的使用;
(2)IIC:硬件和软件,待下文详细说明;
(3)模块化编程:前几篇博客没有说,但实际工程都是模块化的。从目录、程序里可以清晰看到。
(二)程序设计
1、按键
uint8_t ucConfirm = 0;//商品购买界面下,按下B4确认Flag
uint8_t ucLcd[21];//LCD值(\0结束) */ //在写函数内,从0开始按顺序存储到1~3地址
uint8_t ucX_SHOP = 0;//X购买数量
uint8_t ucY_SHOP = 0;//Y购买数量
uint8_t ucX_PRICE=10, ucY_PRICE=10; //商品价格(*10)
uint8_t ucX_REP = 10;//X库存
uint8_t ucY_REP = 10;//Y库存
void KEY_Proc(void) //按键处理函数 注意要清除标志位
{
if(key[0].ucJudgeKeyState == 1) //如果K1短按
{
key[0].ucJudgeKeyState = 0;
if(++ucState == 3)
{
ucState = 0;// 0~2 循环
}
}
if(key[1].ucJudgeKeyState == 1) //如果K2短按
{
key[1].ucJudgeKeyState = 0;
switch(ucState)
{
case 0:
{
if(++ucX_SHOP == ucX_REP + 1)
ucX_SHOP = 0;// 0~库存数量 循环
}
break;
case 1:
{
if(++ucX_PRICE == 21)
ucX_PRICE = 10;// 10~20
ucLcd[0] = ucX_PRICE ;
MEM_Write(ucLcd,2,1); //保存X价格
}
break;
case 2:
{
++ucX_REP ;
ucLcd[0] = ucX_REP ;
MEM_Write(ucLcd,0,1); //保存X库存
}
break;
default : break;
}
}
if(key[2].ucJudgeKeyState == 1) //如果K3短按
{
key[2].ucJudgeKeyState = 0;
switch(ucState)
{
case 0:
{
if(++ucY_SHOP == ucY_REP + 1)
ucY_SHOP = 0; //0~库存数量 循环
}
break;
case 1:
{
if(++ucY_PRICE == 21)
ucY_PRICE = 10;// 10~20 循环
ucLcd[0] = ucY_PRICE ;
MEM_Write(ucLcd,3,1); //保存X价格
}
break;
case 2:
{
++ucY_REP;
ucLcd[0] = ucY_REP ;
MEM_Write(ucLcd,1,1); //保存Y库存
}
break;
default : break;
}
}
if(key[3].ucJudgeKeyState == 1) //如果K4短按
{
key[3].ucJudgeKeyState = 0;
if(ucState == 0)
{
ucConfirm = 1;//确认标志位
ucX_REP -= ucX_SHOP;//新库存量 = 库存量 - 购买量
ucY_REP -= ucY_SHOP;
ucLcd[0] = ucX_REP ; //X库存
ucLcd[1] = ucY_REP ; //Y库存
ucLcd[2] = ucX_PRICE ; //X单价
ucLcd[3] = ucY_PRICE ; //Y单价
MEM_Write(ucLcd,0,2); //保存库存数量
printf("X:%u,Y:%u,Z:%2.1f\r\n", ucX_SHOP, ucY_SHOP,(ucX_SHOP*ucX_PRICE+ucY_SHOP*ucY_PRICE)/10.0); //打印输出总价及购买信息
ucX_SHOP = 0;//购买量清0
ucY_SHOP = 0;
}
ucSec = 0;//B4按下,秒计数清0,重新开始计数
}
}
2、LED
uint8_t ucLed=0;//LED状态
void LED_Proc(void) //LED处理函数 //逻辑/处理/重点
{
//0.1s刷新,刚好下面LD2闪烁,下面不作处理了(实际效果确实可以)
//这也提供了0.1s闪烁的一种思路
if (ucTblk < 100)
return;
ucTblk = 0;
//LD1 SHOP界面按下B4 (ucConfirm)、且在5s内 点亮LD1
if( ucConfirm && (ucSec < 6) ) // 5/6?
ucLed |= 1;//点亮
else
ucLed &=~ 1;//熄灭LD1
//5s时间到 熄灭LD1
if( ucConfirm && (ucSec >5 ) )
{
ucConfirm = 0;
ucLed &=~ 1;//熄灭LD1
}
//LD2 库存均为0,指示灯 LD2 以 0.1 秒为间隔亮、灭闪烁报警
if ((ucX_REP == 0) && (ucY_REP ==0) ) //不能连等判断,否则没用
ucLed ^= 2;//设置LED处理函数0.1s刷新即可
else
ucLed &= ~2;
Led_Disp(ucLed);
}
3、Systic
uint16_t usTms; //ms计数
uint8_t ucSec; //秒计时
uint8_t ucTblk; //LED刷新
uint16_t usTlcd;//LCD刷新
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
extern uint16_t usTms; /* 毫秒计时 */
extern uint8_t ucSec; /* 秒计时 */
extern uint8_t ucTblk; //LED刷新
extern uint16_t usTlcd;//LCD刷新
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
if (++usTms == 1000) /* 1s到 */
{
usTms = 0;
ucSec++; /* 秒加1 */
}
ucTblk++;
usTlcd++;
/* USER CODE END SysTick_IRQn 1 */
}
4、TIM
void TIM_Proc(void) //定时器处理函数
{
if(ucConfirm == 0) //2k 5%
{
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,5);
}
else //2k 30%
{
__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,30);
}
}
5、串口
uint8_t Rxindex;
uint8_t RxDate;
uint8_t RxBuffer[7];//接收缓冲区
int fputc(int ch, FILE *f) //串口重定向
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); //注意,这里使用的是串口1,采用轮询方式发送1字节数据,HAL_MAX_DELAY表示超时时间为无限等待
return ch;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //串口接收中断回调函数
{
if( huart->Instance == USART1 )
{
RxBuffer[Rxindex++] = RxDate; //接收1个:先RxBuffer[0] = RxDate 然后Rxindex = 1 //再接收一个:RxBuffer[1] = RxDate 然后Rxindex = 2 。。。。。。
HAL_UART_Receive_IT(&huart1,&RxDate,1);//使能接收中断
}
}
uint8_t isRxlegal() //判断接收到的数据是否合法 //返回1
{
unsigned char i;
if(Rxindex==0) //未接收到数据
return 0;
if(Rxindex!=1) //接收数据不为1个ASCII时不合法
return 2;
if(RxBuffer[0]=='?') //接收数据为'?'时 //或者这样:RxBuffer[0]=='?'
return 1;
else //接收数据不为'?'时不合法
return 2;
}
void UART_Proc(void) //串口处理函数
{
if(isRxlegal()==1)//接收的数据合法
{
printf("X:%2.1f,Y:%2.1f\r\n", ucX_PRICE/10.0, ucY_PRICE/10.0); 设备返回当前各类商品单价
}
Rxindex = 0; //索引清0
}
这一点切记,血与泪的教训啊!!!
使用串口重定向,一定要勾选“魔术棒”里的:“Use MicroLlB”,否则程序不能运行。
6、EEPROM
要点1:EEPROM的读写
- 说明1:(官方教程1)STM32G431板子上的24C02连接的引脚是PB6、PB7。而CT117E-M4 I2C接口的PB6没有SCL功能,可以用并口仿真实现(即软件IIC:参见竞赛资源包中的底层驱动代码),也可以用硬件I2C实现(PA15-SCL),具体步骤如下:① 拔掉J10和J19上的短路块。② 用短路块连接J10_1(PA15)和J19_1(SCL)。然后程序上:
/* EEPROM读 */ void EEPROM_Read(uint8_t *ucBuf, uint8_t ucAddr, uint8_t ucNum) { HAL_I2C_Mem_Read(&hi2c1, 0xa0, ucAddr, 1, ucBuf, ucNum, 100); } /* EEPROM写 */ void EEPROM_Write(uint8_t *ucBuf, uint8_t ucAddr, uint8_t ucNum) { HAL_I2C_Mem_Write(&hi2c1, 0xa0, ucAddr, 1, ucBuf, ucNum, 100); } /* MCP写 */ void MCP_Write(uint8_t ucVal) { HAL_I2C_Master_Transmit(&hi2c1, 0x5e, &ucVal, 1, 100); }
以上是PPT教程里的说明,但我手头的板子上没有找到J10和J19,所以这种方法无法进行。
说明2:PB6、PB7软件模拟IIC实现,有2套模块程序,使用时按需选择即可,如下:
选择1:
//选择1 //E2PROM读函数:通过IIC通信,读取E2PROM的address位置处的值 uint8_t x24c02_read(uint8_t address) { unsigned char val; I2CStart(); I2CSendByte(0xa0); I2CWaitAck(); I2CSendByte(address); I2CWaitAck(); I2CStart(); I2CSendByte(0xa1); I2CWaitAck(); val = I2CReceiveByte(); I2CWaitAck(); I2CStop(); return(val); } //E2PROM写函数:通过IIC通信,将某值info写进E2PROM内指定的地址address void x24c02_write(unsigned char address,unsigned char info) { I2CStart(); I2CSendByte(0xa0); I2CWaitAck(); I2CSendByte(address); I2CWaitAck(); I2CSendByte(info); I2CWaitAck(); I2CStop(); }选择2:
//选择2 //在i2c_hal.c里添加 /* 存储器读 */ void MEM_Read(unsigned char* ucBuf, unsigned char ucAddr, unsigned char ucNum) { I2CStart(); I2CSendByte(0xa0); I2CWaitAck(); I2CSendByte(ucAddr); I2CWaitAck(); I2CStart(); I2CSendByte(0xa1); I2CWaitAck(); while (ucNum--) { *ucBuf++ = I2CReceiveByte(); if (ucNum) I2CSendAck(); else I2CSendNotAck(); } I2CStop(); } /* 存储器写 */ void MEM_Write(unsigned char* ucBuf, unsigned char ucAddr, unsigned char ucNum) { I2CStart(); I2CSendByte(0xa0); I2CWaitAck(); I2CSendByte(ucAddr); I2CWaitAck(); while (ucNum--) { I2CSendByte(*ucBuf++); I2CWaitAck(); } I2CStop(); delay1(500); } void MCP_Write(unsigned char ucVal) { I2CStart(); I2CSendByte(0x5e); I2CWaitAck(); I2CSendByte(ucVal); I2CWaitAck(); I2CStop(); }要点2:EEPROM连续读写注意点
- 每两个读取或者写入函数之间,必须加5毫秒延时。原因是,EEPROM外设的读取速度是跟不上MCU的运行速度的,需要让MCU停下来等待一会儿。
- 这里参考其他博主文章:蓝桥杯嵌入式——EEPROM避坑指南(干货)。同时里面还提供了判断设备是否第一次上电的另一种方法。
要点3:判断设备是否第一次上电
- 方法:在EEPROM里的某些个地址存储特定的值,每次启动时都读取这这些地址里的值。如果读取出的数值不是自己设定的值,则可判断设备第一次上电,再将特定值写入;否则(说明这些值已经写入),就不是第一次上电,那么就从EEPROM中载入变量值。
- 程序:如下所示。
//在main函数while前
I2CInit();
//设备重新上电,能够从EEPROM相应地址中载入商品库存数量和价格
MEM_Read(ucLcd,0,7);//读出E2PROM存储的商品信息和是否第一次运行标志位
HAL_Delay(100);
if((ucLcd[4]==0x77)&&(ucLcd[5]==0x7A)&&(ucLcd[6]==0x64))//设备不是第一次运行
{
ucX_REP = ucLcd[0];
ucY_REP = ucLcd[1];
ucX_PRICE = ucLcd[2];
ucY_PRICE = ucLcd[3];
}
else//设备第一次运行
{
ucLcd[4]=0x77;
ucLcd[5]=0x7A;
ucLcd[6]=0x64;//自定义的数据
ucLcd[0]=10;
ucLcd[1]=10;
ucLcd[2]=10;
ucLcd[3]=10;//将初始的商品库存和价格按规定的位置写入E2PROM
MEM_Write(ucLcd,0,7);
}
7、LCD
uint8_t ucState=0;//界面状态:0—SHOP界面 1—PRICE界面 2-REP界面
char buf1[20];
void LCD_Proc(void) //LCD处理函数
{
if(usTlcd < 100)
return;
usTlcd = 0;
switch(ucState)
{
case 0:
{
LCD_DisplayStringLine(Line0,(unsigned char*)" ");
LCD_DisplayStringLine(Line1,(unsigned char*)" SHOP ");
LCD_DisplayStringLine(Line2,(unsigned char*)" ");
sprintf(buf1," X:%d ",ucX_SHOP);
LCD_DisplayStringLine(Line3,(unsigned char*)buf1);
sprintf(buf1," Y:%d ",ucY_SHOP);
LCD_DisplayStringLine(Line4,(unsigned char*)buf1);
}
break;
case 1:
{
LCD_DisplayStringLine(Line0,(unsigned char*)" ");
LCD_DisplayStringLine(Line1,(unsigned char*)" PRICE ");
LCD_DisplayStringLine(Line2,(unsigned char*)" ");
sprintf(buf1," X:%2.1f ",ucX_PRICE/10.0);
LCD_DisplayStringLine(Line3,(unsigned char*)buf1);
sprintf(buf1," Y:%2.1f ",ucY_PRICE/10.0);
LCD_DisplayStringLine(Line4,(unsigned char*)buf1);
}
break;
case 2:
{
LCD_DisplayStringLine(Line0,(unsigned char*)" ");
LCD_DisplayStringLine(Line1,(unsigned char*)" REP ");
LCD_DisplayStringLine(Line2,(unsigned char*)" ");
sprintf(buf1," X:%d ",ucX_REP);
LCD_DisplayStringLine(Line3,(unsigned char*)buf1);
sprintf(buf1," X:%d ",ucY_REP);
LCD_DisplayStringLine(Line4,(unsigned char*)buf1);
}
break;
default : break;
}
LCD_DisplayStringLine(Line5,(unsigned char*)" ");
LCD_DisplayStringLine(Line6,(unsigned char*)" ");
LCD_DisplayStringLine(Line7,(unsigned char*)" ");
LCD_DisplayStringLine(Line8,(unsigned char*)" ");
LCD_DisplayStringLine(Line9,(unsigned char*)" ");
}
8、整合
//main里部分
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim4); //按键定时器
HAL_TIM_PWM_Start_IT(&htim2,TIM_CHANNEL_2);//PA1 2kHz 5% : psc400 ARR100 CCR5 ; 2kHz 30% : psc 400 ARR 100 CCR 30
HAL_UART_Receive_IT(&huart1,&RxDate,1);//使能串口接收中断
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
LCD_Init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
I2CInit();
//设备重新上电,能够从EEPROM相应地址中载入商品库存数量和价格
MEM_Read(ucLcd,0,7);//读出E2PROM存储的商品信息和是否第一次运行标志位
HAL_Delay(100);
if((ucLcd[4]==0x77)&&(ucLcd[5]==0x7A)&&(ucLcd[6]==0x64))//设备不是第一次运行
{
ucX_REP = ucLcd[0];
ucY_REP = ucLcd[1];
ucX_PRICE = ucLcd[2];
ucY_PRICE = ucLcd[3];
}
else//设备第一次运行
{
ucLcd[4]=0x77;
ucLcd[5]=0x7A;
ucLcd[6]=0x64;//自定义的信息,
ucLcd[0]=10;
ucLcd[1]=10;
ucLcd[2]=10;
ucLcd[3]=10;//将初始的商品库存和价格按规定的位置写入E2PROM
MEM_Write(ucLcd,0,7);
}
ucLed = 0;
Led_Disp(ucLed);
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
KEY_Proc();
LCD_Proc();
LED_Proc();
TIM_Proc();
UART_Proc();
}
/* USER CODE END 3 */
三、效果展示
视频后续上传。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)