十二届蓝桥杯嵌入式(省赛篇)
十二届蓝桥杯嵌入式(省赛篇)
十二届蓝桥杯嵌入式省赛题目,以下给出每个功能到代码,具体功能描述可以参考省赛真题。下面只给出系统框图。
端口配置 stm32Cube

根据功能描述,下面我们配置一下stm32Cube。
以上配置了,按键模块PB0、PB1、PB2、PA0,USART1串口通信,锁存器PD2(注意:千万要打开,因为LED和LCD的端口复用,锁存器用于保存led模块的电平,防止LCD复用篡改电平),还有PA7定时器功能用于输出固定频率和占空比的脉冲信号。
代码目录

注意以下代码,bsp_system.h文件用于保存所有的外部变量以及文件
bsp_system.h
#ifndef __BSP_SYSTEM_H
#define __BSP_SYSTEM_H
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "string.h"
#include "stdio.h"
#define Data 0 // 数据界面
#define Para 1 // 参数界面
#include "key_app.h"
#include "lcd_app.h"
#include "led_app.h"
#include "usart_app.h"
#include "gpio_init_app.h"
#include "tim_init_app.h"
#include "system_init.h"
extern _Bool lcd_state; //屏幕状态
extern u8 CNBR_CNT; // 初始两种类型车为0辆
extern u8 VNBR_CNT;
extern u8 IDLE_CNT; // 初始空闲车辆为8辆
extern float CNBR_Price; // 两种车辆计费价格
extern float VNBR_Price;
extern u8 ctr_state;
extern u8 rec_buff[2]; // 串口接收缓冲区
#endif
系统初始化
上电时候,led需要保持关闭状态
system_init.c
#include "system_init.h"
#include "bsp_system.h"
/**
@ 系统初始化
**/
void system_init(void)
{
// 清除高八位
GPIOC->ODR &= 0x00ff;
// 将高八位拉高
GPIOC->ODR |= ~(0x00<<8);
// 锁存器 打开
GPIOD->BSRR |= (0x01<<2);
// 锁存器 关闭
GPIOD->BRR |= (0x01<<2);
}
system_init.h
#ifndef __SYSTEM_INIT_H
#define __SYSTEM_INIT_H
void system_init(void);
#endif
LCD界面
lcd_app.c
#include "lcd_app.h"
#include "bsp_system.h"
#define Data 0 // 数据界面
#define Para 1 // 参数界面
_Bool lcd_state = 0;
u8 CNBR_CNT = 0; // 初始两种类型车为0辆
u8 VNBR_CNT = 0;
u8 IDLE_CNT = 8; // 初始空闲车辆为8辆
float CNBR_Price = 3.50; // 两种车辆计费价格
float VNBR_Price = 2.00;
void LCD_Process(void)
{
//u8 display_buff[15];
if(lcd_state == Data)
{
u8 display_buff[15];
sprintf((char *)display_buff," Data ");
LCD_DisplayStringLine(Line1,display_buff);
sprintf((char *)display_buff," CNBR:%d ",CNBR_CNT);
LCD_DisplayStringLine(Line3,display_buff);
sprintf((char *)display_buff," VNBR:%d ",VNBR_CNT);
LCD_DisplayStringLine(Line5,display_buff);
sprintf((char *)display_buff," IDLE:%d ",IDLE_CNT);
LCD_DisplayStringLine(Line7,display_buff);
}
if(lcd_state == Para)
{
u8 display_buff[15];
sprintf((char *)display_buff," Para ");
LCD_DisplayStringLine(Line1,display_buff);
sprintf((char *)display_buff," CNBR:%-5.2f ",CNBR_Price);
LCD_DisplayStringLine(Line3,display_buff);
sprintf((char *)display_buff," VNBR:%-5.2f ",VNBR_Price);
LCD_DisplayStringLine(Line5,display_buff);
LCD_ClearLine(Line7);
}
}
lcd_app.h
#ifndef _LCD_APP_H
#define _LCD_APP_H
void LCD_Process(void);
#endif
以下是实验现象
上述代码中定义了两个界面状态参数,Para和Data,当按键按下时候会自动切换界面,按键模块下面部分会给出。

按键模块
key_app.c
此模块会写出四个按键具体的功能,使用滴答定时器用于按键消抖
其中B4按键功能为使PA7端口输出频率为2000HZ占空比为20%的脉冲信号和低电平之间来回切换
#include "key_app.h"
#include "bsp_system.h"
#define KB1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KB2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KB3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KB4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
#define KEYPORT KB1 | (KB2<<1) | (KB3<<2) | (KB4<<3)| 0xf0
unsigned char Trg; // 单次触发
unsigned char Con; // 防止长按
void KEY_Read(void)
{
unsigned char ReadData = (KEYPORT)^0xff;
Trg = ReadData&(ReadData^Con);
Con = ReadData;
}
u32 key_tick = 0;
u8 ctr_state = 0;
void KEY_Process(void)
{
if(uwTick - key_tick < 20) return;
key_tick = uwTick;
KEY_Read();
if(Trg & 0x01) // B1键
{
lcd_state = !lcd_state;
//lcd_state = Para;
}
if(Trg & 0x02)
{
if(lcd_state == Para)
{
CNBR_Price+=0.5f;
VNBR_Price+=0.5f;
}
}
if(Trg & 0x04)
{
if(lcd_state == Para)
{
if(CNBR_Price >=0.5f && VNBR_Price >= 0.5f)
{
CNBR_Price-=0.5f;
VNBR_Price-=0.5f;
}
}
}
if(Trg & 0x08)
{
if(ctr_state == 0)
{
TIM_Init();
ctr_state = 1;
}else if(ctr_state == 1)
{
GPIO_Init();
ctr_state = 0;
}
}
}
key_app.h
#ifndef __KEY_APP_H
#define __KEY_APP_H
#include "bsp_system.h"
void KEY_Read(void);
void KEY_Process(void);
#endif
切换PA7输出脉冲信号和低电平功能
gpio_init_app.c
初始化PA7端口为低电平,不过第一行需要停止PWM的输出,因为在主函数中使能了PWM输出,所以在按键切换时,首先就要把它停止,在初始化写入低电平。
#include "gpio_init_app.h"
#include "bsp_system.h"
void GPIO_Init(void)
{
// 停止PA7的PWM信号输出
HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_2);
// 初始化GPIO端口 配置为输出模式
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
// 将PA7写入低电平
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_7,GPIO_PIN_RESET);
}
gpio_init_app.h
#ifndef __GPIO_INIT_APP_H
#define __GPIO_INIT_APP_H
void GPIO_Init(void);
#endif
tim_init_app.c
初始化定时器,配置为频率为2000HZ,占空比为20%的脉冲信号
#include "tim_init_app.h"
#include "bsp_system.h"
TIM_OC_InitTypeDef sConfigOC = {0};
void TIM_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
htim3.Instance = TIM3;
htim3.Init.Prescaler = 80 - 1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 500-1;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTOMATICOUTPUT_ENABLE;
HAL_TIM_PWM_Init(&htim3);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 100-1;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim3,&sConfigOC,TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);
}
tim_init_app.h
#ifndef __TIM_INIT_APP_H
#define __TIM_INIT_APP_H
void TIM_Init(void);
#endif
LED模块
led_app.c
这个功能没有什么太多,按照描述启动停止就行。
#include "led_app.h"
void LED_Ctr(u8 ctr)
{
HAL_GPIO_WritePin(GPIOC,0xff00,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,ctr<<8,GPIO_PIN_RESET); //左移八位,因为led端口是高八位的
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
u8 led_ctr = 0x00;
void LED_Process(void)
{
if(IDLE_CNT != 0)
{
led_ctr|=0x01; // led1点亮
}else
{
led_ctr&=~0x01;
}
if(ctr_state == 1)
{
led_ctr|=0x02; //led2点亮
}else{
led_ctr&=~0x02;
}
LED_Ctr(led_ctr);
}
led_app.h
#ifndef __LED_APP_H
#define __LED_APP_H
#include "bsp_system.h"
void LED_Ctr(u8 ctr);
void LED_Process(void);
#endif
USART串口功能模块
这个功能模块,我觉得是所以功能中最难的,以下是具体代码,注释也很详细,可以对照参考。
usart_app.c
#include "usart_app.h"
u8 rx_buf[23];
u8 rx_cnt = 0;
u32 uart_tick = 0;
u8 tem_code[5]; // 记录车辆编号
u8 car_out_pos = 0;
u8 car_IDLE_pos = 0;;
long fee_time_sec; // 停留时间 秒
int fee_hour;
_Bool validata_command_format(u8* str);
u8 check_entry_leave(u8* str);
u8 check_IDLE_Pos(void); // 查找空闲位置
typedef struct{
u8 now_Year;
u8 now_Month;
u8 now_Day;
u8 now_Hour;
u8 now_Minute;
u8 now_Second;
}time_data; // 记录时间结构体
typedef struct{
u8 type[5];
u8 code[5];
u8 in_year;
u8 in_month;
u8 in_day;
u8 in_hour;
u8 in_minute;
u8 in_second;
u8 pos;
}Type_Car_INTO;// 车辆信息
Type_Car_INTO car_into[8];//8个位置
time_data store_time; // 时间结构体
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
uart_tick = uwTick;
rx_buf[rx_cnt++] = rec_buff[0]; // 接收的数据存入字符串数组
HAL_UART_Receive_IT(&huart1,rec_buff,1);
}
void UART_Process(void)
{
if(rx_cnt == 0) return ; // 如果缓冲区无数据就直接返回,有数据时候才回去解析
if(uwTick - uart_tick < 50) return;
uart_tick = uwTick;
rx_buf[rx_cnt] = '\0';
rx_cnt = 0; // 索引归0,字符串数组暂时接收完成
_Bool format_state = validata_command_format(rx_buf);
if(format_state == 1) // 格式正确
{
tem_code[0] = rx_buf[5]; tem_code[1] = rx_buf[6]; tem_code[2] = rx_buf[7];tem_code[3] = rx_buf[8];
car_out_pos = check_entry_leave(tem_code);
printf("%d\r\n",car_out_pos);
if(car_out_pos == 0) // 有空闲
{
printf("car in");
// 查找空闲车位
car_IDLE_pos = check_IDLE_Pos();
if(car_IDLE_pos == 0)
{
// 位置已经满了
printf("ERROR\r\n");
return ;
}
car_into[car_IDLE_pos-1].type[0] = rx_buf[0]; car_into[car_IDLE_pos-1].type[1] = rx_buf[1]; car_into[car_IDLE_pos-1].type[2] = rx_buf[2]; car_into[car_IDLE_pos-1].type[3] = rx_buf[3];
car_into[car_IDLE_pos-1].code[0] = tem_code[0];car_into[car_IDLE_pos-1].code[1] = tem_code[1];car_into[car_IDLE_pos-1].code[2] = tem_code[2];car_into[car_IDLE_pos-1].code[3] = tem_code[3];
car_into[car_IDLE_pos-1].in_year = store_time.now_Year;
car_into[car_IDLE_pos-1].in_month = store_time.now_Month;
car_into[car_IDLE_pos-1].in_day = store_time.now_Day;
car_into[car_IDLE_pos-1].in_hour = store_time.now_Hour;
car_into[car_IDLE_pos-1].in_minute = store_time.now_Minute;
car_into[car_IDLE_pos-1].in_second = store_time.now_Second;
car_into[car_IDLE_pos-1].pos = 1;
if(rx_buf[0] == 'C')
{
printf("car in");
CNBR_CNT++;
IDLE_CNT--;
}
if(rx_buf[0] == 'V')
{
VNBR_CNT++;
IDLE_CNT--;
}
}else // 离开
{
if(rx_buf[0] != car_into[car_out_pos-1].type[0])
{
printf("Error");
return ;
}
printf("car_out");
fee_time_sec = (store_time.now_Year - car_into[car_out_pos-1].in_year)*365*24*60*60+
(store_time.now_Month - car_into[car_out_pos-1].in_month)*30*24*60*60+
(store_time.now_Day - car_into[car_out_pos-1].in_day)*24*60*60 +
(store_time.now_Hour - car_into[car_out_pos-1].in_hour)*60*60+
(store_time.now_Minute - car_into[car_out_pos-1].in_minute)*60+
(store_time.now_Second - car_into[car_out_pos-1].in_second);
if(fee_time_sec < 0)
{
printf("Error\r\n");
return ;
}
fee_hour = (fee_time_sec + 3599) / 3600;
if(rx_buf[0] == 'C')
{
CNBR_CNT--;
IDLE_CNT++;
float CNBR_fee = fee_hour*CNBR_Price;
}
if(rx_buf[0] == 'V')
{
VNBR_CNT--;
IDLE_CNT++;
float CNBR_fee = fee_hour*VNBR_Price;
}
memset(&car_into[car_out_pos-1],'\0',sizeof(car_into[car_out_pos-1]));
}
}else{
printf("Error\r\n"); //格式错误输出ERROR 然后返回掉
return ;
}
}
// CNBR:A392:200202120000
_Bool validata_command_format(u8* str)
{
printf("%s\r\n",str);
if(strlen((char *)str)!=22 || str[4] != ':' || str[9] != ':')
{
printf("Error1\r\n");
return 0;
}
if(strncmp((char *)str,"CNBR",4)!=0 && strncmp((char *)str,"VNBR",4) !=0)
{
printf("Error\r\n");
return 0;
}
for(int i = 10;i<=21;i++)
{
if(str[i] < '0' || str[i]>'9')
{
return 0;
}
}
store_time.now_Year = (str[10] - '0') *10 + str[11] - '0';
store_time.now_Month = (str[12] - '0') * 10 + str[13] - '0';
store_time.now_Day = (str[14] - '0') * 10 + str[15] - '0';
store_time.now_Hour = (str[16] - '0') * 10 + str[17] - '0';
store_time.now_Minute = (str[18] - '0') * 10 + str[19] - '0';
store_time.now_Second = (str[20] - '0') * 10 + str[21] - '0';
printf("OK\r\n");
return 1;
}
u8 check_entry_leave(u8* str)
{
for(int i = 0;i<8;i++)
{
if(car_into[i].code[0] == str[0] && car_into[i].code[1] == str[1] && car_into[i].code[2] == str[2] &&car_into[i].code[3] == str[3])
{
return (i+1);
}
}
return 0; // 找到空闲位置
}
u8 check_IDLE_Pos(void)
{
for(int i = 0;i<8;i++)
{
if(car_into[i].pos == 0)
{
return (i+1);
}
}
return 0;
}
usart_app.h
#ifndef __USART_APP_H
#define __USART_APP_H
#include "bsp_system.h"
_Bool validata_command_format(u8* str);
void UART_Process(void);
#endif
main函数
这里只给出,main中的代码,上面定义了几个变量,上述代码中都有提到,比如串口中断接收缓冲区,报错的化自行定义就可。
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM3_Init();
MX_USART1_UART_Init();
LCD_Init();
system_init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
HAL_UART_Receive_IT(&huart1,rec_buff,1);
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);
GPIO_INIT(); // 上电初始化 PA7输出低电平
while (1)
{
LCD_Process();
KEY_Process();
LED_Process();
UART_Process();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
完结
WhoIam
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)