提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

此文档将带来本人完整的编写过程和思路,首先声明不保证满分,但是已经测试功能完全符合题目要求。
注:本文是根据固定模板的基础上配置和编写的(模板其实解释对于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.瞬时流量和累计流量的编写
TIM3

// ==================== 用到的参数 ====================
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.运行模式与输出控制逻辑的编写

  1. 手动模式
    题目说的是使用按键B3,B4对于界面二的duty的加减,然后当切换到下个界面时,配置开始生效,同时LD4点亮,当界面一的PWM 输出状态(D)达到目标值后熄灭
  2. 自动模式
    代码编写使用TIM4确保每秒刷新,下面时对TIM4的cubemx配置:TIM4
// ==================== 用到的参数 ====================
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届蓝桥杯嵌入式竞赛的完整配置流程、核心思路以及实操细节的全部分享啦!
由于本人水平有限,分享过程中难免会有疏漏、不足或者表述不够精准的地方,也恳请各位技术大佬们不吝赐教,多多指出问题——无论是配置步骤中的优化空间,还是思路逻辑上的可改进之处,每一条建议都是我成长的动力,也能帮助后续看到这篇分享的小伙伴少走弯路。
如果这篇分享能够给正在备战蓝桥杯嵌入式竞赛、或是刚接触嵌入式开发的你,带来哪怕一点点启发,解决你遇到的某个小困惑,麻烦动一动小手,点一个免费的赞支持一下~ 你的认可,就是我持续分享技术、整理干货的最大底气!

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐