> 技术文档 > 蓝桥杯备赛笔记_大学生蓝桥杯大赛 内容stm32

蓝桥杯备赛笔记_大学生蓝桥杯大赛 内容stm32


2025蓝桥杯嵌入式备赛笔记

  • 🏆 比赛着重点
  • 🌟 STM32CubeMX基本配置
      • STM32CubeMX 工程配置
  • 💡 LED 控制
      • LED 配置
      • 1. **📚 库函数实现**
      • 2. **🔧 寄存器实现**
      • 3. **🔄 使用静态变量存储上一个LED状态**
  • 📺 LCD 控制
        • **LCD 配置⚙️**
  • 🖲️ 按键控制
      • 1. **🔑 按键配置**
    • 🧩 结构体示例
      • 1. **💬 匿名结构体**
      • 2. **✍️ 带 `typedef` 的结构体**
      • 3. **🔖 带名称的结构体**
    • ⌨️ 按键状态机实现:
  • 🧭TIMER
    • 相关寄存器:
    • 定时器中断:
      • 🔔 TIM 中断类型详解
        • 1. ✅ **`TIM1 Break Interrupt & TIM15 Global Interrupt`**
        • 2. 🔄 **`TIM1 Update Interrupt & TIM16 Global Interrupt`**
        • 3. ⚡ **`TIM1 Trigger and Commutation Interrupts & TIM17 Global Interrupt`**
        • 4. ⏱️ **`TIM1 Capture Compare Interrupt`**
      • 🌐 **TIM15 / TIM16 / TIM17 Global Interrupts**
    • 📐 定时器计时机制与计算公式
      • 🧮 关键公式
    • 🕒 单位换算基础
      • 🔁 周期 & 次数 & 频率关系
    • ⚡ PWM 输出控制:
    • 🎯 输入捕获
  • ADC
  • IIC
  • 📡UART通信
    • 实现基本的串口收发
    • 实现不定长数据的串口收发
    • 字符串处理
    • STM32CubeMX配置:
    • 编写:

📅 记录于 2025 年蓝桥杯备赛期间

🏆 比赛着重点


  1. 🖥️ 注意默认显示界面要求
    确保显示界面符合预设要求。

  2. 优化响应时间
    保证及时响应,避免延时。

  3. ⚠️ 避免重定义
    防止变量或函数冲突。

  4. 🚫 避免数组越界
    检查数组边界,防止内存损坏。

  5. 📏 打印时多加空格
    在变量对应的格式转换说明符后面多打几个空格。

  6. 🔢 行数从0开始
    注意行数索引是从0开始的。


🌟 STM32CubeMX基本配置

STM32CubeMX 工程配置

配置项 设置要求 系统配置 ✅ 勾选 Serial Write RCC 配置 选择 HSE Crystal / Ceramic Resonator 输入频率 设置为 24 MHz HSE ✅ 勾选 HSE PLLCLK ✅ 勾选 PLLCLK HCLK 设置为 80 MHz

💡 LED 控制

LED 配置

引脚 配置要求 初始状态 输出模式 PC8-PC15 设置为 输出模式 高电平 推挽输出 PD2 设置为 输出模式 (锁存器) 低电平 推挽输出

1. 📚 库函数实现

void LED_Disp(uchar LED){ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_All, GPIO_PIN_SET); // 设置PC8-PC15为高电平 HAL_GPIO_WritePin(GPIOC, LED << 8, GPIO_PIN_RESET); // 根据传入的LED值控制相应的LED HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); // 锁存器控制 HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); // 锁存器控制}

2. 🔧 寄存器实现

使用寄存器直接控制 LED 灯。

uint16_t LED = 0xff00; // 定义初始LED状态void LED_SET(void){ GPIOC->ODR = (uint32_t)LED; // 设置PC端口的输出数据寄存器 HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); // 锁存器控制 HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); // 锁存器控制}// 要打开某个LED灯LED &= ~(0x01ff); LED_SET();// 要关闭某个LED灯LED |= 0x0100; LED_SET();

3. 🔄 使用静态变量存储上一个LED状态

通过静态变量存储上一个LED状态,避免多次修改时对LED状态的影响。

void LED_Proc(uint8_t led, GPIO_PinState state){ static uint8_t led_state = 0x00; if (state == GPIO_PIN_RESET) led_state |= led; // 开启LED else led_state &= ~led; // 关闭LED HAL_GPIO_WritePin(GPIOC, 0xff00, GPIO_PIN_SET); // 设置PC端口的输出数据寄存器 HAL_GPIO_WritePin(GPIOC, LED << 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); // 锁存器控制}

📺 LCD 控制

LCD 配置⚙️
引脚 配置要求 初始状态 输出模式 PB9,8,5 输出模式 低电平 推挽输出 PA8 输出模式 低电平 推挽输出 PC0-PC7 输出模式 低电平 推挽输出

🖲️ 按键控制

1. 🔑 按键配置

PB0, PB1, PB2, PA1 设置为 输入模式(output) 并启用 上拉电阻(Pull Up)。


🧩 结构体示例

1. 💬 匿名结构体

struct { // 无字段的匿名结构体} keys; // 声明结构体变量 keys

2. ✍️ 带 typedef 的结构体

typedef struct { // 可扩展字段} keys; // 定义结构体类型别名keys myKey; // 声明结构体变量 myKey

3. 🔖 带名称的结构体

struct keys { // 可扩展字段}; // 定义名为 keys 的结构体类型struct keys myKey; // 声明结构体变量 myKey

⌨️ 按键状态机实现:

//.htypedef struct {uint8_t juage_sta;bool key_sta;bool single_flag;bool long_flag;bool double_flag;int key_times;}keys;//.ckeys key[4]={0,0,0,0};void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){if(htim->Instance==TIM4){key[0].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);key[1].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);key[2].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);key[3].key_sta=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);for(int i=0;i<4;i++){switch(key[i].juage_sta){case 0:if(key[i].key_sta==0){key[i].key_times=0;key[i].juage_sta=1;}break;case 1:if(key[i].key_sta==0){key[i].juage_sta=2;}else{key[i].juage_sta=0;}break;case 2:if(key[i].key_sta==1){if(key[i].key_times<70){key[i].single_flag=1;}else{key[i].long_flag=1;} // 重置状态机和计数 key[i].juage_sta = 0; key[i].key_times = 0;}else{key[i].key_times++;}break;case 3:break;}}}}

🧭TIMER

相关寄存器:

🧱 初始化设置类PSC → 分频ARR → 周期CNT → 当前计数值⏱️ 比较捕获类(PWM/捕获)CCR1~CCR4 → 比较值CCMR1/2 → 模式选择CCER → 输出使能🧠 控制状态类CR1 → 启停、方向、重装等DIER → 中断使能SR → 中断标志EGR → 手动触发更新

🧩 举个例子

定时器中断:

🔔 TIM 中断类型详解

1. ✅ TIM1 Break Interrupt & TIM15 Global Interrupt
  • Break Interrupt:当定时器遇到“断路”事件(如外部故障输入)时触发中断,常用于电机控制中的过流、过压保护。
2. 🔄 TIM1 Update Interrupt & TIM16 Global Interrupt
  • Update Interrupt:定时器1计数器溢出或更新事件时触发中断,常用于周期性任务。
3. ⚡ TIM1 Trigger and Commutation Interrupts & TIM17 Global Interrupt
  • Trigger Interrupt:定时器1的触发事件(如外部或内部信号)时触发,用于同步启动其他模块。
  • Commutation Interrupt:在电机控制中,特别是BLDC换向时,发生换向事件触发中断。
4. ⏱️ TIM1 Capture Compare Interrupt
  • 输入捕获 & 输出比较中断:用于测量外部信号频率、周期或脉宽,以及当计数器达到特定值时触发某个操作。

🌐 TIM15 / TIM16 / TIM17 Global Interrupts

中断名 描述 TIM15 Global Interrupt 定时器15的任意事件(如更新、溢出等)触发中断。 TIM16 Global Interrupt 定时器16的全局中断。 TIM17 Global Interrupt 定时器17的全局中断,功能类似TIM16。

📐 定时器计时机制与计算公式

🧮 关键公式

定时器时钟频率 = 系统时钟频率 ÷ (PSC + 1)周期 (Period) = 定时器时钟频率 ÷ (ARR + 1)
  • ⚠️ 定时器在 CNT 计数器达到 ARR 值 时发生溢出,并触发 更新中断

🕒 单位换算基础

表达式 含义 1 秒 = 1000 毫秒 (ms) 时间单位 1 Hz = 每秒 1 次事件 频率单位 10 Hz = 每秒 10 次事件,周期 = 100 ms 100 Hz = 每秒 100 次事件,周期 = 10 ms

🔁 周期 & 次数 & 频率关系

次数(每秒事件数) 周期(每次事件间隔) 频率 1 次 1000 ms 1 Hz 10 次 100 ms 10 Hz 100 次 10 ms 100 Hz

📌 例子说明:

  • 若每秒发生 100 次事件,则:

    • 每次事件间隔为 10ms
    • 频率为 100Hz
  • 若每秒发生 10 次事件,则:

    • 每次事件间隔为 100ms
    • 频率为 10Hz

⚡ PWM 输出控制:

⚠️ 注意 :设置PWM输出的时候,一定不能让占空比为0,否则就是持续低电平,没有频率输出;
启动PWM

//寄存器TIM2_CR1 |= TIM_CR1_CEN;// 设置 CEN 位来启动定时器 HAL_TIM_PWM_Start(htimx, Channex); //库函数 启动 PWM 输出 

修改分频系数

//寄存器TIMx->PSC = prescaler;__HAL_TIM_SET_PRESCALER(htim, prescaler);//库函数

修改重装载值(周期):

//寄存器TIMX->ARR = period;__HAL_TIM_SET_AUTORELOAD(htim, period);//库函数

修改占空比(CCR):

//寄存器TIMx->CCRx = pulse_value;__HAL_TIM_SET_COMPARE(htim, Channel, pulse_value);//库函数

停止PWM:

//寄存器TIM2_CR1 &= ~TIM_CR1_CEN; // 清除 CEN 位来停止定时器HAL_TIM_PWM_Stop(htim, Channel); // 停止 PWM 输出

🎯 输入捕获

配置PA15和PB4为定时器通道并设置相应通道为输入捕获
配置定时器尽量选择TIM3和TIM2的TIM_CHANNEL_1

TIM16输入捕获为0,暂时没有解决这个问题

输入捕获是读取通道的计时值

需要用到的的函数:

frqA_temp=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);//读取通道的计时值 hal_tim.h 2517__HAL_TIM_SetCounter(htim,0);//通道计时值清零

或者直接对寄存器操作

frqA_temp=TIM2->CNT//读取通道的计时值TIM2->CNT=0;//通道计时值清零

🧩 回调示例:

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){if(htim->Instance==TIM2){//frqA_temp=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);//__HAL_TIM_SetCounter(htim,0);frqA_temp=TIM2->CNT;TIM2->CNT=0;frqA_dat=(80000000/80)/frqA_temp;HAL_TIM_IC_Start(htim,TIM_CHANNEL_1);}}

ADC

adc电压值获取:

float getADC(ADC_HandleTypeDef *pin){uint16_t adc;HAL_ADC_Start(pin);adc = HAL_ADC_GetValue(pin);return adc*3.3/4096;}//或者float getADC(void){uint16_t adc;HAL_ADC_Start(&hadc2);adc = HAL_ADC_GetValue(&hadc2);return adc*3.3/4096;}

IIC

引脚PB6,PB7设置为输出模式即可配置完成;
IIC读取函数

//开启->请求写入->写入地址->关闭->重启->请求读取->读取数据->发送非应答信号->关闭uint8_t I2C_Read(uint8_t addr){uint8_t data;I2CStart();I2CSendByte(0xa0);//发起写入请求信号I2CWaitAck(); I2CSendByte(addr);//发送要读取的地址I2CWaitAck();I2CStop();I2CStart();I2CSendByte(0xa1);//发起读取请求信号I2CWaitAck(); data=I2CReceiveByte();//读取数据I2CSendNotAck(); //发送读取完成应答信号I2CStop();return data;}

IIC写入函数

//开启->请求写入->写入地址->写入数据>关闭void I2C_Write(uint8_t addr,uint8_t dat){I2CStart();I2CSendByte(0xa0);//发起写入请求信号I2CWaitAck(); I2CSendByte(addr);//发送要读取的地址I2CWaitAck();I2CSendByte(dat);//写入数据I2CWaitAck();I2CStop();}

📌 设备地址为 0xA0,0为写入,1为读取,需查阅数据手册确认写/读模式。
⚠️ IIC一个地址仅能存八位数据,若遇到十六位数据需要高低位分开存储;
✅ I2CInit();不要放入初始化中

📡UART通信

实现基本的串口收发

//寄存器TIM2_CR1 &= ~TIM_CR1_CEN; // 清除 CEN 位来停止定时器HAL_TIM_PWM_Stop(htim, Channel); // 停止 PWM 输出

实现不定长数据的串口收发

STM32CubeMX在串口基础上开启DMA并添加两条通道即可;

实现不定长数据接收主要是利用IDLE寄存器,即空闲中断,来实现不定长数据接收;

  • 普通串口+IDLE实现不完美
  • 采用DMA+IDLE实现

DMA和普通中断使用大致相同,需要在初始化部分先开启一次接收,之后的接收都是在中断中开启

//需要用到的库函数HAL_UARTEx_ReceiveToIdle_DMA();//DMA空闲中断接收void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size);//位于hal_uart.h中1661行

HAL_UART_RxCpltCallback 和 HAL_UARTEx_RxEventCallback 区别:

  1. HAL_UART_RxCpltCallback
    接收完成回调函数,适合数据量小或者单次发送
  2. HAL_UARTEx_RxEventCallback
    是 扩展版的接收事件回调,它可以在 DMA(直接内存访问)接收模式 或 其他特殊接收模式 下使用,适合数据量较大或需要更加灵活处理接收事件时使用。
#define BUFF_LEN 100uint8_t buff_Rx_temp[BUFF_LEN]={\'\\0\'};uint8_t buff_Tx_temp[BUFF_LEN]={\'\\0\'};bool buff_Rx_flag=0;void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){if(huart->Instance==USART1){buff_Rx_flag=1;HAL_UARTEx_ReceiveToIdle_DMA(&huart1,buff_Rx_temp,BUFF_LEN);}} if(buff_Rx_flag==1) {sprintf((char*)buff_Tx_temp,\"buff_Tx_temp=%s\\r\\n\",buff_Rx_temp);HAL_UART_Transmit_DMA(&huart1,buff_Tx_temp,strlen((char *)buff_Tx_temp));//memset(buff_Rx_temp,\'\\0\',BUFF_LEN);buff_Rx_flag=0; }

缓冲区可以使用memset(buff_Rx_temp,\'\\0\',BUFF_LEN);清空,谨慎使用

字符串处理

⏰ RTC 实时时钟
✅ STM32CubeMX 配置

STM32CubeMX配置:

RTC
✅Activate Clock Source
✅Activate Calendar
勾选唯一的中断

编写:

相关函数:

学习中可能会遇到的问题:
1.数组越界产生不可预测的错误,在数组定义的时候一定要指定长度,或者给指定长度个初值。
蓝桥杯备赛笔记_大学生蓝桥杯大赛 内容stm32