蓝桥杯全国软件和信息技术专业人才大赛是国内最具影响力的IT类学科竞赛之一,其嵌入式设计与开发赛道主要考察参赛者在STM32平台上的嵌入式系统开发能力。第十二届省赛第二阶段试题延续了往届"基于真实工业场景命题"的特点,要求选手在限定时间内完成具有完整功能的嵌入式系统开发。
这个赛题的核心在于考察三个维度的能力:
提示:省赛第二阶段通常会在基础功能外设置2-3个难度递增的"隐藏得分点",这些往往涉及外设的复合使用或异常处理机制
开发平台采用官方指定的CT117E-M4竞赛板(基于STM32F103RBT6),其关键外设配置如下:
| 外设类型 | 具体功能 | 对应引脚 | 竞赛使用场景 |
|---|---|---|---|
| GPIO | 按键输入 | PA0-PA3 | 用户交互控制 |
| ADC | 电位器模拟量采集 | PA4 | 参数调节输入 |
| TIM | PWM输出 | PA6-PA7 | LED亮度/电机控制 |
| USART | 调试信息输出 | PA9-PA10 | 状态监控与故障诊断 |
| I2C | EEPROM数据存储 | PB6-PB7 | 参数持久化存储 |
竞赛板采用AMS1117-3.3V稳压芯片,在实际开发中需特别注意:
c复制// 电源初始化示例代码
void Power_Config(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
PWR_BackupAccessCmd(ENABLE); // 启用备份域访问
RCC_LSEConfig(RCC_LSE_ON); // 开启低速外部时钟
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待时钟稳定
}
赛题要求实现电位器输入的0-3.3V模拟量采集,并转换为0-100%的百分比值。推荐采用DMA+定时器触发的方式提高采样效率:
c复制// ADC配置代码片段
void ADC1_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
// DMA配置
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 1;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
// ADC配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_CC1;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// 校准与使能
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_Cmd(ADC1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
}
利用TIM1的CH1(PA8)和CH2(PA9)输出两路PWM,通过改变CCR值实现呼吸灯效果时需注意:
c复制// PWM渐变算法实现
void LED_Effect_Update(void)
{
static uint16_t pwmVal = 0;
static int8_t dir = 1;
// 使用查表法优化计算效率
const uint16_t sinTable[64] = {0, 79, 158, 237, 314, 391, 466, 539,
610, 679, 744, 807, 866, 921, 973, 1020,
1063, 1101, 1134, 1162, 1185, 1202, 1214, 1220,
1220, 1214, 1202, 1185, 1162, 1134, 1101, 1063,
1020, 973, 921, 866, 807, 744, 679, 610,
539, 466, 391, 314, 237, 158, 79, 0};
static uint8_t index = 0;
TIM1->CCR1 = sinTable[index];
TIM1->CCR2 = sinTable[(index+16)%64];
index = (index + 1) % 64;
}
根据赛题要求,系统需实现至少三种工作模式的循环切换:
c复制// 状态枚举定义
typedef enum {
MODE_MANUAL = 0,
MODE_AUTO,
MODE_CALIB,
MODE_MAX
} SystemMode_t;
// 状态转换处理函数
void Mode_Handler(SystemMode_t currMode)
{
static uint32_t modeEnterTime = 0;
switch(currMode) {
case MODE_MANUAL:
if(HAL_GetTick() - modeEnterTime > 5000) {
// 5秒无操作自动切换到自动模式
currMode = MODE_AUTO;
modeEnterTime = HAL_GetTick();
}
break;
case MODE_AUTO:
Auto_Run_Algorithm();
if(Key_Read() == KEY_MODE) {
currMode = MODE_CALIB;
modeEnterTime = HAL_GetTick();
}
break;
case MODE_CALIB:
if(Calib_Finish_Check()) {
currMode = MODE_MANUAL;
EEPROM_Save_Params();
}
break;
}
}
使用4个独立按键(PA0-PA3)和LCD12864显示屏构建人机界面:
c复制// 按键状态机实现
typedef enum {
KEY_IDLE = 0,
KEY_DEBOUNCE,
KEY_PRESSED,
KEY_RELEASE
} KeyState_t;
KeyState_t Key_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
static KeyState_t state = KEY_IDLE;
static uint32_t tick = 0;
switch(state) {
case KEY_IDLE:
if(HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET) {
state = KEY_DEBOUNCE;
tick = HAL_GetTick();
}
break;
case KEY_DEBOUNCE:
if(HAL_GetTick() - tick > 15) { // 15ms消抖
if(HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET) {
state = KEY_PRESSED;
return KEY_PRESSED;
} else {
state = KEY_IDLE;
}
}
break;
case KEY_PRESSED:
if(HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_SET) {
state = KEY_RELEASE;
tick = HAL_GetTick();
}
break;
case KEY_RELEASE:
if(HAL_GetTick() - tick > 15) {
state = KEY_IDLE;
return KEY_RELEASE;
}
break;
}
return KEY_IDLE;
}
通过USART1输出调试信息时,建议采用DMA+环形缓冲区方案避免阻塞:
c复制// 非阻塞式串口打印实现
#define DEBUG_BUF_SIZE 1024
typedef struct {
uint8_t buf[DEBUG_BUF_SIZE];
uint16_t head;
uint16_t tail;
} DebugRingBuf_t;
void USART_Printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int len = vsnprintf((char*)&debugBuf.buf[debugBuf.head],
DEBUG_BUF_SIZE - debugBuf.head, fmt, args);
if(len > 0) {
debugBuf.head = (debugBuf.head + len) % DEBUG_BUF_SIZE;
// 触发DMA传输
if(!DMA_GetCmdStatus(DMA1_Channel4)) {
uint16_t size = (debugBuf.head >= debugBuf.tail) ?
(debugBuf.head - debugBuf.tail) :
(DEBUG_BUF_SIZE - debugBuf.tail + debugBuf.head);
DMA_SetCurrDataCounter(DMA1_Channel4, size);
DMA_Cmd(DMA1_Channel4, ENABLE);
}
}
va_end(args);
}
为满足低功耗评分项,可实施以下优化:
动态时钟调整:运行中根据负载切换系统时钟
外设按需供电:
c复制void Peripheral_Power_Manage(SystemState_t state)
{
switch(state) {
case STATE_RUNNING:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ALL, ENABLE);
break;
case STATE_LOW_POWER:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR |
RCC_APB1Periph_BKP, ENABLE);
break;
}
}
睡眠模式唤醒配置:
c复制void Enter_Stop_Mode(void)
{
// 配置唤醒源
PWR_WakeUpPinCmd(ENABLE);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 进入停止模式
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
// 唤醒后恢复时钟
SystemInit();
}
推荐的项目目录结构:
code复制/Project
├── /CMSIS // 内核支持文件
├── /Drivers
│ ├── /STM32F10x_StdPeriph_Driver // 标准外设库
│ └── /ThirdParty // 第三方驱动
├── /Middlewares // 中间件层
├── /User
│ ├── /app // 应用逻辑
│ ├── /bsp // 板级支持包
│ ├── /config // 系统配置
│ └── /utils // 通用工具
└── /MDK-ARM // Keil工程文件
参数有效性检查:
c复制void PWM_SetDuty(TIM_TypeDef* TIMx, uint8_t ch, uint16_t duty)
{
// 检查TIMx是否有效
assert_param(IS_TIM_ALL_PERIPH(TIMx));
// 限制占空比范围
duty = (duty > 1000) ? 1000 : duty;
switch(ch) {
case 1: TIMx->CCR1 = duty; break;
case 2: TIMx->CCR2 = duty; break;
default:
Error_Handler(__FILE__, __LINE__);
}
}
运行时错误处理机制:
c复制void Error_Handler(const char *file, uint32_t line)
{
__disable_irq();
USART_Printf("[ERROR] %s:%lu\r\n", file, line);
while(1) {
LED_Error_Blink();
HAL_Delay(200);
}
}
看门狗集成方案:
c复制void IWDG_Config(uint8_t timeout_s)
{
uint16_t reload = 0;
// 计算重载值(LSI=40kHz)
if(timeout_s <= 2) {
IWDG->PR = 0x04; // 分频系数64
reload = timeout_s * 625; // 40000/64=625Hz
} else {
IWDG->PR = 0x06; // 分频系数256
reload = timeout_s * 156; // 40000/256=156Hz
}
IWDG->KR = 0x5555;
IWDG->RLR = reload;
IWDG->KR = 0xAAAA;
IWDG->KR = 0xCCCC;
}