在蓝桥杯嵌入式竞赛中,LCD显示模块往往是第一个需要攻克的堡垒。记得我第一次参赛时,LCD显示总是出现重影问题,调试了整整两天才发现是刷新策略不当导致的。下面分享几个经过实战检验的核心技巧。
虽然官方提供的lcd.h已经封装了大部分初始化操作,但有几个关键点仍需注意:
c复制// 推荐的主程序初始化顺序
LCD_Init(); // 必须先于任何显示操作
LCD_Clear(Black); // 清屏要放在主循环之前
比赛中常见的整行显示问题,90%的重影都是由于刷新策略不当造成的。我的经验是采用"三明治"刷新法:
c复制// 安全显示示例
char buffer[20];
float voltage = 3.3;
sprintf(buffer, "Voltage:%.2f ", voltage); // 注意末尾空格
LCD_SetBackColor(Yellow);
LCD_SetTextColor(Red);
LCD_DisplayStringLine(Line5, (u8*)buffer);
LCD_SetBackColor(Black); // 立即恢复默认
LCD_SetTextColor(White);
局部高亮是得分亮点,但也是容易失分的地方。经过多次测试,我总结出以下要点:
c复制#define COLUMN_WIDTH 16 // 每列像素宽度
#define ROW_HEIGHT 24 // 每行像素高度
void highlightChar(uint8_t line, uint8_t col, char c) {
uint16_t x_pos = 320 - (col * COLUMN_WIDTH);
LCD_SetBackColor(Cyan);
LCD_SetTextColor(Black);
LCD_DisplayChar(line, x_pos, c);
// 立即恢复防止影响后续显示
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
}
按键模块看似简单,实则是稳定性的关键。在省赛现场,我见过有队伍因为按键抖动问题导致整个系统崩溃。下面分享我的按键处理方案。
CubeMX配置时要注意:
c复制// 定时器配置建议
htim6.Instance = TIM6;
htim6.Init.Prescaler = 79; // 80分频
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 19999; // 20ms中断
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
这是我经过多次优化后的状态机设计,支持单击、长按、双击检测:
c复制typedef enum {
KEY_IDLE,
KEY_PRESS_DETECT,
KEY_DEBOUNCE,
KEY_LONG_PRESS,
KEY_RELEASE
} KeyState;
void keys_scan() {
static KeyState state[4] = {KEY_IDLE};
static uint32_t tick[4] = {0};
for(int i=0; i<4; i++) {
switch(state[i]) {
case KEY_IDLE:
if(!HAL_GPIO_ReadPin(KEY_PORT[i], KEY_PIN[i])) {
state[i] = KEY_PRESS_DETECT;
tick[i] = HAL_GetTick();
}
break;
// 完整状态机实现...
}
}
}
双击检测最容易出现误判,我的解决方案是:
c复制#define DOUBLE_CLICK_TIME 400 // 双击时间窗口(ms)
if(current_state == KEY_RELEASE) {
if((HAL_GetTick() - first_click_time) < DOUBLE_CLICK_TIME) {
double_click_flag = 1;
} else {
first_click_time = HAL_GetTick();
}
}
LED模块虽然简单,但用好了可以极大提升用户体验。在国赛中有队伍因为LED指示不明确被扣分,这些经验值得借鉴。
LED控制本质是位操作游戏,掌握这些技巧可以事半功倍:
c复制#define LED1 0x01
#define LED2 0x02
#define LED3 0x04
// ...其他LED定义
// 翻转LED状态
void toggle_led(uint8_t led_mask) {
static uint8_t led_status = 0;
led_status ^= led_mask; // 异或实现翻转
led_driver(led_status);
}
虽然不是必考项,但流畅的呼吸灯能加分:
c复制void breath_led() {
static uint8_t dir = 0;
static uint16_t duty = 0;
if(dir == 0) {
duty += 50;
if(duty >= 1000) dir = 1;
} else {
duty -= 50;
if(duty == 0) dir = 0;
}
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, duty);
}
最后也是最关键的部分——如何让三个模块和谐共处。在国赛答辩时,评委最看重的就是系统整合能力。
推荐采用时间片轮询架构:
c复制void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim == &htim6) { // 20ms定时器
keys_scan();
led_process();
}
}
void main() {
while(1) {
uint32_t now = HAL_GetTick();
static uint32_t lcd_tick = 0;
if(now - lcd_tick >= 100) {
lcd_proc();
lcd_tick = now;
}
}
}
三个模块间的状态共享要特别注意:
c复制typedef struct {
volatile uint8_t mode;
volatile float temperature;
volatile uint8_t alarm;
} SystemState;
SystemState sys_state;
void keys_func() {
if(double_click_flag) {
__disable_irq(); // 进入临界区
sys_state.mode = (sys_state.mode + 1) % 3;
__enable_irq(); // 退出临界区
}
}
在资源有限的MCU上,这些优化很关键:
c复制// 优化后的LCD显示函数
void safe_display(uint8_t line, char* new_text) {
static char last_text[10][20];
if(strcmp(new_text, last_text[line]) != 0) {
strcpy(last_text[line], new_text);
LCD_DisplayStringLine(line, (u8*)new_text);
}
}