第十届 蓝桥杯 嵌入式 省赛
一、分析
这届的真题,有点像第七届的液位检测。
这届的题目开始,貌似比赛描述的功能,逻辑上变得更好梳理了。一开始就把大致的功能给你说明一遍,不像之前都是一块一块的说明。
1. 基本功能
1)测量竞赛板上电位器 R37 输出的模拟电压信号,并通过 LCD 显示
2)通过 LED 指示灯实现超出上限、低于下限的提醒功能
3)通过按键实现阈值范围和输出提醒指示灯的设置功能
4)通过 E2PROM 实现参数的断电存储功能
2. 显示功能
显示界面有两个:数据显示界面和参数配置界面
1)数据显示界面
- 显示 界面名称、采集的实时电压数据 和 状态
- 电压单位为 V,保留小数点后两位
- 状态(Status):超出上限(Upper)、低于下限(Lower)和正常(Normal)
2)参数配置界面
- 显示:界面名称、电压上限、电压下限;电压超出上限 LD1,电压低于下限 LD2 指示灯。
- 电压上下限:0~3.3V,设备应具备错误设置的保护功能
伪代码
typedef enum{
Data_Disp,
Set_Pama
}Lcd_State;
Lcd_State L_state = Data_Disp;
typedef enum{
Upper,
Lower,
Normal
} Volt_Status;
Volt_Status Status = Normal;
float R37_Volt_Sum, R37_Volt, R37_Volt_AVE;
unsigned int choose_pama = 1;
float Get_Volt(){
//均值滤波
for(int i = 0; i < 9; i++){
R37_Volt = ADC_Get1();
R37_Volt_Sum += R37_Volt;
}
//16位精度,分4096
R37_Volt_Sum *= 3.3;
R37_Volt_Sum /= 4096;
R37_Volt_AVE = R37_Volt_AVE/10;
return R37_Volt_AVE;
}
void Lcd_Proc(void){
if(L_state == Data_Disp){
R37_Volt_AVE = Get_Volt();
//显示 界面、电压、状态
Lcd_Disp(Line0, "Main");
Lcd_Disp(Line2, "Volt: %fV", R37_Volt_AVE);
Lcd_Disp(Line3, "%s", Status);
}
else if(L_state == Set_Pama){
i2c_read(Save_Volt_Max_Min_Led, 0, 4);
Lcd_Disp(Line0, "Setting");
if(choose_pama == 1)
LCD_SetBackColor(Green);
Lcd_Disp(Line1, "Max Volt: %f", Save_Volt_Max_Min_Led[0]);
LCD_SetBackColor(White);
Lcd_Disp(Line2, "Min Volt: %f", Save_Volt_Max_Min_Led[1]);
Lcd_Disp(Line3, "Upper:LD%d",Save_Volt_Max_Min_Led[2]);
Lcd_Disp(Line4, "Lower:LD%d",Save_Volt_Max_Min_Led[3]);
}
}
3. 按键功能
1)B1:设置按键,用来切换2个显示界面的。退出参数设置界面时,应将参数保存到 E2PROM
2)B2:选择按键,被选中的需要高亮
3)B3:加 按键,电压增加 0.3V,LED序号+1
4)B3:减 按键,电压减少 0.3V,LED序号-1
LED 范围为:1~8.
伪代码
void Key_Proc(){
if(L_state == Data_Disp){
if(Key_down == 1){
L_state = Set_Pama;
}
}
else if(L_state == Set_Pama){
switch (Key_down)
{
case 1:
LCD_Clear(White);
i2c_write(Save_Volt_Max_Min_Led, 0 , 4);
L_state = Data_Disp;
break;
case 2:
if(++choose_pama == 5)
choose_pama = 1;
break;
case 3:
switch (choose_pama)
{
case 1:
Save_Volt_Max_Min_Led[0]+=3;
if(Save_Volt_Max_Min_Led[0] >=33 && Save_Volt_Max_Min_Led[0] <40)
Save_Volt_Max_Min_Led[0] = 33;
break;
case 2:
if((Save_Volt_Max_Min_Led[1]+3)<Save_Volt_Max_Min_Led[0])
Save_Volt_Max_Min_Led[1] += 3;
break;
case 3:
//到了8以后,再加就原地不动
if(++Save_Volt_Max_Min_Led[2] >= 8)
Save_Volt_Max_Min_Led[2] = 8;
//如果相同
if(Save_Volt_Max_Min_Led[2] == Save_Volt_Max_Min_Led[3]){
if(Save_Volt_Max_Min_Led[3] == 8){
Save_Volt_Max_Min_Led[2]=7;
}
else {
Save_Volt_Max_Min_Led[2] += 1;
}
}
break;
case 4:
if(++Save_Volt_Max_Min_Led[3] >= 8)
Save_Volt_Max_Min_Led[2] = 8;
if(Save_Volt_Max_Min_Led[2] == Save_Volt_Max_Min_Led[3]){
if(Save_Volt_Max_Min_Led[2] == 8){
Save_Volt_Max_Min_Led[3]=7;
}
else {
Save_Volt_Max_Min_Led[3] += 1;
}
}
break;
}
break;
case 4:
switch (choose_pama)
{
case 1:
if((Save_Volt_Max_Min_Led[1]+3) < Save_Volt_Max_Min_Led[0])
Save_Volt_Max_Min_Led[0] -= 3;
break;
case 2:
Save_Volt_Max_Min_Led[1] -= 3;
if(Save_Volt_Max_Min_Led[1] >= 200)
//补码
Save_Volt_Max_Min_Led[1] = 0;
break;
case 3:
if(--Save_Volt_Max_Min_Led[2] == 0)
Save_Volt_Max_Min_Led[2] = 1;
if(Save_Volt_Max_Min_Led[2] == Save_Volt_Max_Min_Led[3])
{
if(Save_Volt_Max_Min_Led[3] == 1)
{
Save_Volt_Max_Min_Led[2] = 2;
}
else {
Save_Volt_Max_Min_Led[2] -= 1;
}
}
break;
case 4:
if(--Save_Volt_Max_Min_Led[3] == 0)
Save_Volt_Max_Min_Led[3] = 1;
if(Save_Volt_Max_Min_Led[3] == Save_Volt_Max_Min_Led[2])
{
if(Save_Volt_Max_Min_Led[2] == 1)
{
Save_Volt_Max_Min_Led[3] = 2;
}
else {
Save_Volt_Max_Min_Led[3] -= 1;
}
}
break;
}
}
}
}
4. LED 指示灯
1)R37 输出电压值超过电压上限值时,上限提醒指示灯以 0.2 秒闪烁,下限指示灯熄灭。
2)R37 输出电压值低于电压下限值时,下限提醒指示灯以 0.2 秒闪烁,上限指示灯熄灭。
3)R37 输出电压值介于上限和下限电压之间时,全灭。
伪代码
ucled = 0x0;
void Led_Proc(void){
if(R37_Volt_AVE*10 >= Save_Volt_Max_Min_Led[0])
{ ucled ^= (0x01 << (Save_Volt_Max_Min_Led[2]-1));
Status = Upper;
}
else if(R37_Volt_AVE*10 <= Save_Volt_Max_Min_Led[1])
{
ucled ^= (0x01 << (Save_Volt_Max_Min_Led[3]-1));
Status = Lower;
}
else if( Save_Volt_Max_Min_Led[1] <= R37_Volt_AVE <= Save_Volt_Max_Min_Led[0])
{
Led_Disp(0x00);
Status = Normal;
}
}
5. 初始状态
1)重新上电,从 E2PROM 载入各个参数。
2)默认指示灯 LD1 和 LD2
3)默认阈值 2.4V 和 1.2V
unsigned int Save_Volt_Max_Min_Led[4] = {24,12,1,2};
二、CubeMx
这次只用配置 Key、LED、ADC即可。打开赛点资源包,按照原理图配置。
1. LED
2. Key
3. ADC
三、模版编写
1. LED
void Led_Disp(uint8_t ucled){
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10
|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC, ucled << 8, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
2. Key
uint8_t Key_Scan(){
uint8_t Key_val;
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0)==GPIO_PIN_RESET)
Key_val = 1;
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)==GPIO_PIN_RESET)
Key_val = 2;
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2)==GPIO_PIN_RESET)
Key_val = 3;
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)==GPIO_PIN_RESET)
Key_val = 4;
return Key_val;
}
3. ADC
uint16_t Get_ADC2(){
uint16_t adc = 0;
HAL_ADC_Start(&hadc2);
adc = HAL_ADC_GetValue(&hadc2);
return adc;
}
4. I2C
void i2c_read(uint8_t* buf, uint8_t addr, uint8_t num){
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
while(num--){
*buf++ = I2CReceiveByte();
if(num)
I2CSendAck();
else
I2CSendNotAck();
}
I2CStop();
}
void i2c_write(uint8_t* buf, uint8_t addr, uint8_t num){
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
while(num--){
I2CSendByte(*buf++);
I2CWaitAck();
}
I2CStop();
HAL_Delay(500);
}
四、完整代码编写
1. 全局变量
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "i2c_hal.h"
#include "lcd.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/*两个界面*/
typedef enum{
Data_Disp,
Set_Pama
}Lcd_State;
Lcd_State L_state = Data_Disp;
/*状态*/
typedef enum{
Upper,
Lower,
Normal
} Volt_Status;
const char* StatusStrings[] = {
"Upper",
"Lower",
"Nomal"
};
Volt_Status Status = Normal;
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
float R37_Volt, R37_Volt_Sum, R37_Volt_AVE;
uint8_t Lcd_Str_Disp[21];
uint8_t Save_Volt_Max_Min_Led[4] = {24,12,1,2};
uint8_t Key_val, Key_up, Key_down, Key_old;
uint8_t choose_pama = 1;
__IO uint32_t uwTick_Set_Lcd = 0;
__IO uint32_t uwTick_Set_Key = 0;
__IO uint32_t uwTick_Set_Led = 0;
uint8_t ucled = 0x0;
/* USER CODE END PD */
2. 主函数
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_ADC2_Init();
/* USER CODE BEGIN 2 */
LCD_Init();
LCD_Clear(White);
LCD_SetBackColor(White);
LCD_SetBackColor(Blue);
I2CInit();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
Lcd_Proc();
Key_Proc();
Led_Proc();
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
3. 显示功能
void Lcd_Proc(){
if((uwTick - uwTick_Set_Lcd) < 100)
return;
uwTick_Set_Lcd = uwTick;
if(L_state == Data_Disp){
//R37_Volt_AVE = Get_Volt();
R37_Volt_AVE = (((float)Get_ADC2())/4096)*3.3;
//显示:界面、电压、状态
sprintf((char*) Lcd_Str_Disp, "Main");
LCD_DisplayStringLine(Line0, Lcd_Str_Disp);
sprintf((char*) Lcd_Str_Disp, "Volt: %4.2fV", R37_Volt_AVE);
LCD_DisplayStringLine(Line2, Lcd_Str_Disp);
sprintf((char*) Lcd_Str_Disp, "Status:%s", StatusStrings[Status]);
LCD_DisplayStringLine(Line3, Lcd_Str_Disp);
}
else if(L_state == Set_Pama){
//i2c_read(Save_Volt_Max_Min_Led, 0, 4);
//Volt_Max_Min[0] = Save_Volt_Max_Min_Led[0];
//Volt_Max_Min[1] = Save_Volt_Max_Min_Led[1];
//ucled_Max = Save_Volt_Max_Min_Led[2];
//ucled_Min = Save_Volt_Max_Min_Led[3];
sprintf((char*) Lcd_Str_Disp, "Setting");
LCD_DisplayStringLine(Line0, Lcd_Str_Disp);
sprintf((char*) Lcd_Str_Disp, "Max Volt: %3.2f", (float)Save_Volt_Max_Min_Led[0]/10.0);
if(choose_pama == 1) LCD_SetBackColor(Green);
LCD_DisplayStringLine(Line1, Lcd_Str_Disp);
LCD_SetBackColor(White);
sprintf((char*) Lcd_Str_Disp, "Min Volt: %3.2f", (float)Save_Volt_Max_Min_Led[1]/10.0);
if(choose_pama == 2) LCD_SetBackColor(Blue);
LCD_DisplayStringLine(Line2, Lcd_Str_Disp);
LCD_SetBackColor(White);
sprintf((char*) Lcd_Str_Disp, "Upper:LD%1d", (uint8_t)Save_Volt_Max_Min_Led[2]);
if(choose_pama == 3) LCD_SetBackColor(Blue);
LCD_DisplayStringLine(Line3, Lcd_Str_Disp);
LCD_SetBackColor(White);
sprintf((char*) Lcd_Str_Disp, "Lower:LD%1d", (uint8_t)Save_Volt_Max_Min_Led[3]);
if(choose_pama == 4) LCD_SetBackColor(Blue);
LCD_DisplayStringLine(Line4, Lcd_Str_Disp);
LCD_SetBackColor(White);
}
}
4. 按键功能
void Key_Proc(){
if((uwTick - uwTick_Set_Key) < 100)
return;
uwTick_Set_Key = uwTick;
Key_val = Key_Scan();
Key_down = Key_val & (Key_val ^ Key_old);
Key_up = ~Key_val & (Key_val ^ Key_old);
Key_old = Key_val;
if(L_state == Data_Disp){
if(Key_down == 1){
LCD_Clear(White);
L_state = Set_Pama;
}
}
else if(L_state == Set_Pama){
switch (Key_down)
{
case 1:
LCD_Clear(White);
i2c_write(Save_Volt_Max_Min_Led, 0 , 4);
L_state = Data_Disp;
break;
case 2:
if(++choose_pama == 5)
choose_pama = 1;
break;
case 3:
switch(choose_pama){
case 1:
Save_Volt_Max_Min_Led[0]+=3;
if(Save_Volt_Max_Min_Led[0] >=33 && Save_Volt_Max_Min_Led[0] <40)
Save_Volt_Max_Min_Led[0] = 33;
break;
case 2:
if((Save_Volt_Max_Min_Led[1]+3)<Save_Volt_Max_Min_Led[0]){
Save_Volt_Max_Min_Led[1] += 3;
}
break;
case 3:
//到了8以后,再加就原地不动
if(++Save_Volt_Max_Min_Led[2] >= 8)
Save_Volt_Max_Min_Led[2] = 8;
//如果相同
if(Save_Volt_Max_Min_Led[2] == Save_Volt_Max_Min_Led[3]){
if(Save_Volt_Max_Min_Led[3] == 8){
Save_Volt_Max_Min_Led[2]=7;
}
else {
Save_Volt_Max_Min_Led[2] += 1;
}
}
break;
case 4:
if(++Save_Volt_Max_Min_Led[3] >= 8)
Save_Volt_Max_Min_Led[2] = 8;
if(Save_Volt_Max_Min_Led[2] == Save_Volt_Max_Min_Led[3]){
if(Save_Volt_Max_Min_Led[2] == 8){
Save_Volt_Max_Min_Led[3]=7;
}
else {
Save_Volt_Max_Min_Led[3] += 1;
}
}
break;
}
break;
case 4:
switch(choose_pama){
case 1:
if((Save_Volt_Max_Min_Led[1]+3) < Save_Volt_Max_Min_Led[0])
Save_Volt_Max_Min_Led[0] -= 3;
break;
case 2:
Save_Volt_Max_Min_Led[1] -= 3;
if(Save_Volt_Max_Min_Led[1] >= 200)
//补码
Save_Volt_Max_Min_Led[1] = 0;
break;
case 3:
if(--Save_Volt_Max_Min_Led[2] == 0){
Save_Volt_Max_Min_Led[2] = 1;
}
if(Save_Volt_Max_Min_Led[2] == Save_Volt_Max_Min_Led[3])
{
if(Save_Volt_Max_Min_Led[3] == 1)
{
Save_Volt_Max_Min_Led[2] = 2;
}
else {
Save_Volt_Max_Min_Led[2] -= 1;
}
}
break;
case 4:
if(--Save_Volt_Max_Min_Led[3] == 0){
Save_Volt_Max_Min_Led[3] = 1;
}
if(Save_Volt_Max_Min_Led[3] == Save_Volt_Max_Min_Led[2])
{
if(Save_Volt_Max_Min_Led[2] == 1)
{
Save_Volt_Max_Min_Led[3] = 2;
}
else {
Save_Volt_Max_Min_Led[3] -= 1;
}
}
break;
}
break;
}
}
}
5. LED 指示灯
void Led_Proc(){
//ucled_Max = Save_Volt_Max_Min_Led[2];
//ucled_Min = Save_Volt_Max_Min_Led[3];
if((uwTick - uwTick_Set_Led) < 200)
return;
uwTick_Set_Led = uwTick;
if(R37_Volt_AVE*10 >= Save_Volt_Max_Min_Led[0]){
ucled ^= (0x01 << (Save_Volt_Max_Min_Led[2]-1));
Status = Upper;
}
else if(R37_Volt_AVE*10 <= Save_Volt_Max_Min_Led[1])
{
ucled ^= (0x01 << (Save_Volt_Max_Min_Led[3]-1));
Status = Lower;
}
else if( (Save_Volt_Max_Min_Led[1] <= R37_Volt_AVE*10) && (R37_Volt_AVE*10 <= Save_Volt_Max_Min_Led[0]))
{
ucled = 0x00;
Status = Normal;
}
Led_Disp(ucled);
}
小结
这届比赛,需要在数值边界部分要考虑考虑。本届难度一般。