蓝桥杯备赛笔记_大学生蓝桥杯大赛 内容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 年蓝桥杯备赛期间
🏆 比赛着重点
-
🖥️ 注意默认显示界面要求
确保显示界面符合预设要求。 -
⏳ 优化响应时间
保证及时响应,避免延时。 -
⚠️ 避免重定义
防止变量或函数冲突。 -
🚫 避免数组越界
检查数组边界,防止内存损坏。 -
📏 打印时多加空格
在变量对应的格式转换说明符后面多打几个空格。 -
🔢 行数从0开始
注意行数索引是从0开始的。
🌟 STM32CubeMX基本配置
STM32CubeMX 工程配置
💡 LED 控制
LED 配置
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 配置⚙️
🖲️ 按键控制
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
TIM16 Global Interrupt
TIM17 Global Interrupt
📐 定时器计时机制与计算公式
🧮 关键公式
定时器时钟频率 = 系统时钟频率 ÷ (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 区别:
- HAL_UART_RxCpltCallback
接收完成回调函数,适合数据量小或者单次发送 - 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.数组越界产生不可预测的错误,在数组定义的时候一定要指定长度,或者给指定长度个初值。