蓝桥杯嵌入式赛题17届省赛重构
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
此文档将带来本人完整的编写过程和思路,首先声明不保证满分,但是已经测试功能完全符合题目要求。
注:本文是根据固定模板的基础上配置和编写的(模板其实解释对于lcd、led和按键的初始化,也可以参照lz的第一篇文章)
那么话不多说开始写代码(结尾附带完整代码供参考)。
一、首先查看系统框图对需要的引脚进行配置

对需要用到的引脚根据原理图进行配置:
a. 压力监测功能
R37->PB15->ADC2_IN15
b. 流量监测功能
R39->PB4->TIM16_CH1
输入捕获模式,PSC->80-1,其他默认
这么配置的原因是:提高了定时器的计数时钟频率(时间分辨率),从而减小周期测量的量化误差。
c. 脉冲输出功能
PA1->TIM2_CH2
PWM输出通道二,PSC->80-1,ARR->100-1, 其他默认
这么配置的原因是:3.6需要输出频率固定为 10kHz。
F=f/(PSC+1)*(ARR+1)=10kHz
此时duty=5/(ARR+1)=5%.方便计算
二、然后对数据进行处理
1.压力监测的编写:
a.实时压力值(Main_P)=(采集的电压值 -零点偏值电压)/(最大的采集电压值 -零点偏值电压)*最大压力值
b.零点校准功能:就是按键长按两秒后将(adc_value)的值直接存入(main_V_off)(*此功能需在按键代码实现)
具体实现代码为:
// ==================== 用到的参数 ====================
float main_V_off = 0.0f; // 零点偏置电压(V)
float adc_value = 0.0f; // ADC电压(V)
float main_P = 0.0f; // 压力(Bar)
// ==================== 压力采集 ====================
static float adc_voltage(void)
{
HAL_ADC_Start(&hadc2);
uint32_t val = HAL_ADC_GetValue(&hadc2);
return (val / 4096.0f) * 3.3f;
}
// ==================== 数据更新 ====================
void data_proc(void)
{
// 压力采集与变换
adc_value = adc_voltage();
if (adc_value <= main_V_off)
main_P = 0.0f;
else {
float range = 3.3f - main_V_off;
main_P = (adc_value - main_V_off) / range * 10.0f;
if (main_P > 10.0f) main_P = 10.0f;
}
}
2.流量监测的编写
a.回调函数编写
// ==================== 用到的参数 ====================
uint32_t f = 0; // 脉冲频率(Hz)
// ==================== TIM16输入捕获中断(测频率) ====================
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim ->Instance == TIM16)
{
uint32_t capture = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
__HAL_TIM_SET_COUNTER( &htim16 ,0);
f = 1000000 / capture;
}
}
b.瞬时流量和累计流量的编写

// ==================== 用到的参数 ====================
uint32_t f = 0; // 脉冲频率(Hz)
float main_F = 0.0f; // 瞬时流量(L/min)
static float flow_accum = 0.0f; //流量累计
// ==================== 瞬时流量累加(每100ms调用) ====================
void accumulate_flow(void)
{
if (f < 800) main_F = 0.0f;
else if (f > 8000) main_F = 40.0f;
else main_F = f / 200.0f;
flow_accum += main_F / 600.0f; // 因为mian_F的单位是分钟需要转换,1min=60s,当前为0.1s的周期,所以需要除以600
if (flow_accum >= 1.0f) {
uint32_t inc = (uint32_t)flow_accum;
main_Q += inc; //整数部分
if (main_Q > 999999999) main_Q = 999999999;
flow_accum -= inc; //小数部分
}
}
// ==================== TIM3 1ms中断 ====================
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM3)
{
static uint8_t cnt_100ms = 0;
if (++cnt_100ms >=100) //确保每100ms调用一次
{
cnt_100ms=0;
accumulate_flow();
}
}
}
因为此处的(main_F)是浮点数,在累计流量计算中,需要整数部分和小数部分是为了精确累加并避免浮点误差累积,同时满足“每满1升更新一次累计流量”的要求。
3.运行模式与输出控制逻辑的编写
- 手动模式
题目说的是使用按键B3,B4对于界面二的duty的加减,然后当切换到下个界面时,配置开始生效,同时LD4点亮,当界面一的PWM 输出状态(D)达到目标值后熄灭 - 自动模式
代码编写使用TIM4确保每秒刷新,下面时对TIM4的cubemx配置:
// ==================== 用到的参数 ====================
uint8_t currentDuty = 5; // 当前PWM占空比(%)
uint8_t targetDuty_manual = 5; // 手动模式目标占空比(%)
float targetPressure_auto = 5.0f; // 自动模式目标压力(Bar)
uint8_t dutyTransitionActive = 0; // 平滑过渡标志
float main_P = 0.0f; // 压力(Bar)
// ==================== 占空比平滑调节(每秒调用) ====================
void adjust_duty(void)
{
uint8_t new_duty = currentDuty;
uint8_t reach_target = 0;
if (main_flag == 1) { // 手动模式
if (currentDuty < targetDuty_manual) new_duty = currentDuty + 1;
else if (currentDuty > targetDuty_manual) new_duty = currentDuty - 1;
else reach_target = 1;
} else { // 自动模式
if (main_P < targetPressure_auto - 0.5f) new_duty = currentDuty + 1;
else if (main_P > targetPressure_auto + 0.5f) new_duty = currentDuty - 1;
else reach_target = 1;
}
if (new_duty < 5) new_duty = 5;
if (new_duty > 95) new_duty = 95;
if (new_duty != currentDuty) {
currentDuty = new_duty;
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, currentDuty);
dutyTransitionActive = 1;
} else if (reach_target) {
dutyTransitionActive = 0;
}
// LD4指示
led_state[3] = dutyTransitionActive ? 1 : 0;
}
// ==================== TIM4 1s中断 ====================
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM4)
{
adjust_duty();
}
}
三、显示功能
要注意不要显示错行数和在主函数包含头文件以及初始化。
// ==================== LCD显示 ====================
void lcd_show(void)
{
uint16_t temp=GPIOC ->ODR;
if (lcd_mode >2)return;
if (lcd_mode == 0)
{
sprintf(text," MAIN ");
LCD_DisplayStringLine(Line1 ,(uint8_t *)text);
if (main_flag==0)
sprintf(text," M=AUTO ");
else
sprintf(text," M=MAN ");
LCD_DisplayStringLine(Line3 ,(uint8_t *)text);
sprintf(text," P=%.1fBAR ",main_P);
LCD_DisplayStringLine(Line4 ,(uint8_t *)text);
sprintf(text," F=%.1fL/M ",main_F);
LCD_DisplayStringLine(Line5 ,(uint8_t *)text);
sprintf(text," Q=%dL ",main_Q);
LCD_DisplayStringLine(Line6 ,(uint8_t *)text);
sprintf(text," D=%d%% ",currentDuty);
LCD_DisplayStringLine(Line7 ,(uint8_t *)text);
sprintf(text," V=%.1fV ",main_V_off);
LCD_DisplayStringLine(Line8 ,(uint8_t *)text);
}
if (lcd_mode ==1)
{
sprintf(text," OUTP ");
LCD_DisplayStringLine(Line1 ,(uint8_t *)text);
sprintf(text," TarD=%d ",outp_tard);
LCD_DisplayStringLine(Line3 ,(uint8_t *)text);
sprintf(text," TarP=%.1fBAR ",outp_tarp);
LCD_DisplayStringLine(Line4 ,(uint8_t *)text);
LCD_ClearLine(Line5);LCD_ClearLine(Line6);LCD_ClearLine(Line7);LCD_ClearLine(Line8);
}
if(lcd_mode ==2)
{
sprintf(text," PARA ");
LCD_DisplayStringLine(Line1 ,(uint8_t *)text);
sprintf(text," FH=%.1fL/M ",para_fh);
LCD_DisplayStringLine(Line3 ,(uint8_t *)text);
sprintf(text," FL=%.1fL/M ",para_fl);
LCD_DisplayStringLine(Line4 ,(uint8_t *)text);
sprintf(text," PL=%.1fBAR ",para_pl);
LCD_DisplayStringLine(Line5 ,(uint8_t *)text);
sprintf(text," DH=%d%% ",para_dh);
LCD_DisplayStringLine(Line6 ,(uint8_t *)text);
LCD_ClearLine(Line7);LCD_ClearLine(Line8);
}
GPIOC ->ODR =temp;
}
其中,uint16_t temp = GPIOC->ODR;在操作 LCD 之前,先读取 GPIOC 端口的输出数据寄存器(ODR),保存当前所有引脚的高低电平状态到临时变量 temp。
GPIOC->ODR = temp;在 LCD 显示完成后,将之前保存的状态写回 GPIOC 的 ODR,恢复所有引脚原来的输出电平。
这是一种简易的临界区保护,确保 LCD 操作不会干扰同时使用同一端口其他引脚的功能。
四、按键功能
// ==================== 按键扫描 ====================
void key_scan(void)
{
// 读取当前状态到cur变量
B1cur_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
B2cur_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
B3cur_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
B4cur_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
uint32_t tick = HAL_GetTick();
// B1: 界面切换
if (B1cur_state == 0 && B1last_state == 1) {
HAL_Delay(10);
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == 0) //消抖后再次确认按下
{
uint8_t old_mode = lcd_mode;
lcd_mode = (lcd_mode + 1) % 3;//界面的切换
// 离开输出配置界面 -> 保存临时参数到实际目标
if (old_mode == 1) {
if (main_flag == 1) targetDuty_manual = outp_tard;
else targetPressure_auto = outp_tarp;
dutyTransitionActive = 1; // 开启平滑过渡
}
// 离开运行参数界面 -> 保存临时参数到实际阈值
else if (old_mode == 2) {
actual_FH = para_fh;
actual_FL = para_fl;
actual_PL = para_pl;
actual_DH = para_dh;
}
// 进入输出配置界面时,临时变量同步当前目标值
if (lcd_mode == 1) {
outp_tard = targetDuty_manual;
outp_tarp = targetPressure_auto;
outp_flag = 0;
}
// 进入运行参数界面时,临时变量同步实际阈值
if (lcd_mode == 2) {
para_fh = actual_FH;
para_fl = actual_FL;
para_pl = actual_PL;
para_dh = actual_DH;
para_flag = 0;
}
}
}
// B2: 功能切换
if (B2cur_state == 0 && B2last_state == 1) {
HAL_Delay(10);
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == 0) {
if (lcd_mode == 0) main_flag = !main_flag;
else if (lcd_mode == 1) outp_flag = !outp_flag;
else if (lcd_mode == 2) para_flag = (para_flag + 1) % 4;
}
}
// B3: 加/长按校准
if (B3cur_state == 0 && B3last_state == 1) { // 下降沿
B3_press_start = tick;
B3_long_pending = 0;
}
else if (B3cur_state == 0 && B3last_state == 0 && B3_long_pending == 0) {
if (tick - B3_press_start >= 2000) { // 长按2秒
B3_long_pending = 1;
if (lcd_mode == 0) { // 监控界面长按校准零点
main_V_off = adc_value;
if (main_V_off > 3.3f) main_V_off = 3.3f;
}
}
}
else if (B3cur_state == 1 && B3last_state == 0) { // 上升沿
if (B3_long_pending == 0 && (tick - B3_press_start) < 2000) {
// 短按:参数界面调节
if (lcd_mode == 1) {
if (outp_flag == 0) {
outp_tard += 5;
if (outp_tard < 5) outp_tard = 5;
if (outp_tard > 95) outp_tard = 95;
} else {
outp_tarp += 0.5f;
if (outp_tarp < 1.0f) outp_tarp = 1.0f;
if (outp_tarp > 9.5f) outp_tarp = 9.5f;
}
}
else if (lcd_mode == 2) {
switch (para_flag) {
case 0: para_fh += 1.0f; if (para_fh < 4.0f) para_fh = 4.0f; if (para_fh > 40.0f) para_fh = 40.0f; break;
case 1: para_fl += 1.0f; if (para_fl < 4.0f) para_fl = 4.0f; if (para_fl > 40.0f) para_fl = 40.0f; break;
case 2: para_pl += 0.5f; if (para_pl < 1.0f) para_pl = 1.0f; if (para_pl > 9.5f) para_pl = 9.5f; break;
case 3: para_dh += 10; if (para_dh < 5) para_dh = 5; if (para_dh > 95) para_dh = 95; break;
}
}
}
B3_long_pending = 0;
}
// B4: 减/长按清零累计流量
if (B4cur_state == 0 && B4last_state == 1) {
B4_press_start = tick;
B4_long_pending = 0;
}
else if (B4cur_state == 0 && B4last_state == 0 && B4_long_pending == 0) {
if (tick - B4_press_start >= 2000) {
B4_long_pending = 1;
if (lcd_mode == 0) main_Q = 0;
}
}
else if (B4cur_state == 1 && B4last_state == 0) {
if (B4_long_pending == 0 && (tick - B4_press_start) < 2000) {
if (lcd_mode == 1) {
if (outp_flag == 0) {
outp_tard -= 5;
if (outp_tard < 5) outp_tard = 5;
if (outp_tard > 95) outp_tard = 95;
} else {
outp_tarp -= 0.5f;
if (outp_tarp < 1.0f) outp_tarp = 1.0f;
if (outp_tarp > 9.5f) outp_tarp = 9.5f;
}
}
else if (lcd_mode == 2) {
switch (para_flag) {
case 0: para_fh -= 1.0f; if (para_fh < 4.0f) para_fh = 4.0f; if (para_fh > 40.0f) para_fh = 40.0f; break;
case 1: para_fl -= 1.0f; if (para_fl < 4.0f) para_fl = 4.0f; if (para_fl > 40.0f) para_fl = 40.0f; break;
case 2: para_pl -= 0.5f; if (para_pl < 1.0f) para_pl = 1.0f; if (para_pl > 9.5f) para_pl = 9.5f; break;
case 3: para_dh -= 10; if (para_dh < 5) para_dh = 5; if (para_dh > 95) para_dh = 95; break;
}
}
}
B4_long_pending = 0;
}
// 更新last状态
B1last_state = B1cur_state;
B2last_state = B2cur_state;
B3last_state = B3cur_state;
B4last_state = B4cur_state;
}
五、LED 指示灯功能
// ==================== 用到的参数 ====================
uint32_t f = 0; // 脉冲频率(Hz)
float main_F = 0.0f; // 瞬时流量(L/min)
float actual_PL = 1.0f; // 压力低阈值(Bar)
float actual_FH = 20.0f; // 流量高阈值(L/min)
float actual_FL = 10.0f; // 流量低阈值(L/min)
uint8_t actual_DH = 65; // 占空比高阈值(%)
uint8_t currentDuty = 5; // 当前PWM占空比(%)
static uint16_t block_timer = 0; // 堵塞计时(100ms)
static uint16_t leak_timer = 0; // 泄漏计时(100ms)
// ==================== 预警处理(每100ms调用) ====================
void update_warnings(void)
{
// LD1: 频率超限
led_state[0] = (f > 8000) ? 1 : 0;
// LD2: 堵塞预警
if (main_F < actual_FL && currentDuty > actual_DH) {
if (block_timer < 20) block_timer++;
led_state[1] = (block_timer >= 20) ? 1 : 0;
} else {
block_timer = 0;
led_state[1] = 0;
}
// LD3: 泄漏预警
if (main_F > actual_FH && main_P < actual_PL) {
if (leak_timer < 20) leak_timer++;
led_state[2] = (leak_timer >= 20) ? 1 : 0;
} else {
leak_timer = 0;
led_state[2] = 0;
}
}
// ==================== TIM3 1ms中断 ====================
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM3)
{
static uint8_t cnt_100ms = 0;
if (++cnt_100ms >=100) //确保每100ms调用一次
{
cnt_100ms=0;
update_warnings();
}
}
}
五、MAIN函数的关键配置
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc2 ,ADC_SINGLE_ENDED);
HAL_TIM_IC_Start_IT(&htim16 ,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2 , TIM_CHANNEL_2);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 5);
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_Base_Start_IT(&htim4);
LCD_Init();
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2, GPIO_PIN_RESET);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
while (1)
{
key_scan();
lcd_show();
data_proc();
led_update();
HAL_Delay(10);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
完整代码在本人GitHub
总结
以上就是我(lz)针对17届蓝桥杯嵌入式竞赛的完整配置流程、核心思路以及实操细节的全部分享啦!
由于本人水平有限,分享过程中难免会有疏漏、不足或者表述不够精准的地方,也恳请各位技术大佬们不吝赐教,多多指出问题——无论是配置步骤中的优化空间,还是思路逻辑上的可改进之处,每一条建议都是我成长的动力,也能帮助后续看到这篇分享的小伙伴少走弯路。
如果这篇分享能够给正在备战蓝桥杯嵌入式竞赛、或是刚接触嵌入式开发的你,带来哪怕一点点启发,解决你遇到的某个小困惑,麻烦动一动小手,点一个免费的赞支持一下~ 你的认可,就是我持续分享技术、整理干货的最大底气!
更多推荐
所有评论(0)