蓝桥杯嵌入式
代码一定要写在BEGIN和END之间!!(不然用STM32CubeMX重新生成后会不见)
main中记得LCD初始化!!!!!! main文件里记得 #include"headfile.h"
LCD_Init();//不要忘记LCD初始化!!!!!!
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
记得 #include "headfile.h" !!!
目录
- 新建工程
- 点亮LED
- 按键
- LCD
- LED闪烁(定时器中断)
- 长按键与短按键
- LCD高亮显示
- PWM输出
- 输入捕获测量引脚输出PWM波
- 输入捕获测555定时器频率
- ADC测量
- 串口发送与接收
- 利用定时器进行串口不定长数据接收
- eeprom读写
- rtc实时时钟
0.新建工程



- 最后点击右上角的 GENERATE CODE 生成工程就可以啦
1. 点亮LED
-
PCx输入低电平时对应的LED点亮
SN74HC573是锁存器,只有PD2置高电平时PCx的数据才会传到xQ这边 -
点亮LED步骤:
PD2置高->PCx置低->PD2置低(因为LCD引脚与此共用)
STM32CubeMX中的设置
PC8~PC15、PD2设置为输出状态、默认高电平
//LED:PC8~PC15
//PD2是锁存器使能引脚
void led_show(uint8_t led,uint8_t mode)
{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
if(mode)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8<<(led-1),GPIO_PIN_RESET);
else
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8<<(led-1),GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
2.按键
上拉输入
- 按键没有按下时 输入得到高电平
- 按键按下时 输入得到低电平
STM32CubeMX中的设置
uint8_t B1_state,B1_last_state;
uint8_t B2_state,B2_last_state;
uint8_t B3_state,B3_last_state;
uint8_t B4_state,B4_last_state;
//按键:PA0,PB0,PB1,PB2
void key_scan()
{
B1_state=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
B2_state=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
B3_state=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
B4_state=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
if(B1_state==0&&B1_last_state==1)//按键1按下
{
led_show(1,1);
}
if(B2_state==0&&B2_last_state==1)//按键2按下
{
led_show(1,0);
}
if(B3_state==0&&B3_last_state==1)//按键3按下
{
led_show(2,1);
}
if(B4_state==0&&B4_last_state==1)//按键4按下
{
led_show(2,0);
}
B1_last_state=B1_state;
B2_last_state=B2_state;
B3_last_state=B3_state;
B4_last_state=B4_state;
}
3.LCD
- 先把 lcd 的驱动(lcd.c、lcd.h、fouts.h)复制到code中
1)初始化,黑背景白色字体
LCD_Init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
2)显示
char text[20];//因为屏幕一行最多20个字符
void lcd_show()
{
sprintf(text," text ");
LCD_DisplayStringLine(Line0,(uint8_t *)text);
sprintf(text," count:%d ",count);
LCD_DisplayStringLine(Line3,(uint8_t *)text);
}
△ LCD与LED引脚冲突问题:
PC8~PC15是冲突的
解决方法:
在初始化LCD之前将锁存器的使能引脚PD2置低
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
- 在lcd.c文件中所有用到的函数中首尾加这两行代码,保存一下进入这个函数前的GPIOC的状态
评论区方法:
!!! 务必在gpio.h中将PD2设置高的语句后加上
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);//将锁存器的使能引脚PD2置低////
4.LED闪烁(定时器中断)
(记得 NVIC 使能)
-
PSC:自动重装载值 (不能超过2的16次方-1(65535))
-
ARR:预分频器
-
CNT:计数器
-
f(system)= 80MHZ
-
定时器中断要先在main中加上
HAL_TIM_Base_Start_IT(TIM_HandleTypeDef &htim2);使能
//PSC:自动重装载值
//ARR:预分频器
//CNT:计数器
//定时器时钟频率 f = fs / [(ARR+1)(PSC+1)]
//定时器中断周期 T = 1/f (每隔T秒进入一次定时器中断)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)//TIM2中断函数
{
led_mode++;//led闪烁
led_mode = led_mode % 2;
}
}
注意:led_show(1,led_mode);可以放在lcd_show函数中,不要放在中断函数中,防止在PD2使能、GPIOC状态未复原时进入中断,引起led与lcd的引脚冲突。
5.长按键与短按键
- 原理
- STM32CubeMX设置:
- (不需要NVIC 使能)
//按键1
if(B1_state==0&&B1_last_state==1)//按键1按下
{
TIM3->CNT = 0;
}
else if(B1_state == 0 && B1_last_state == 0)//按键1一直按着
{
if(TIM3->CNT >= 10000)//按键1长按1s,0.5s就是5000
{
count++;
}
}
else if(B1_state == 1 && B1_last_state == 0)//按键1松开
{
if(TIM3->CNT < 10000)//按键1短按
{
count+=2;
}
}
- main文件中
HAL_TIM_Base_Start(&htim3);
注意:
uint8_t B1_state,B1_last_state = 1;//设置为1,防止刚启动时count直接为2
uint8_t B2_state,B2_last_state = 1;
uint8_t B3_state,B3_last_state = 1;
uint8_t B4_state,B4_last_state = 1;
6.LCD高亮显示
- 原理:换背景色(
LCD_SetBackColor())
白字黄色高光
// if(lcd_highshow == 0)
// {
// LCD_SetBackColor(Yellow);//第三行高亮
// sprintf(text," count:%d ",count);
// LCD_DisplayStringLine(Line3,(uint8_t *)text);
//
// LCD_SetBackColor(Black);
// sprintf(text," para1 ");
// LCD_DisplayStringLine(Line4,(uint8_t *)text);
//
// sprintf(text," para2 ");
// LCD_DisplayStringLine(Line5,(uint8_t *)text);
// }
// else if(lcd_highshow == 1)
// {
// LCD_SetBackColor(Black);
// sprintf(text," count:%d ",count);
// LCD_DisplayStringLine(Line3,(uint8_t *)text);
//
// LCD_SetBackColor(Yellow);//第四行高亮
// sprintf(text," para1 ");
// LCD_DisplayStringLine(Line4,(uint8_t *)text);
//
// LCD_SetBackColor(Black);
// sprintf(text," para2 ");
// LCD_DisplayStringLine(Line5,(uint8_t *)text);
// }
//
// else if(lcd_highshow == 2)
// {
// sprintf(text," count:%d ",count);
// LCD_DisplayStringLine(Line3,(uint8_t *)text);
//
// sprintf(text," para1 ");
// LCD_DisplayStringLine(Line4,(uint8_t *)text);
//
// LCD_SetBackColor(Yellow);//第五行高亮
// sprintf(text," para2 ");
// LCD_DisplayStringLine(Line5,(uint8_t *)text);
//
// LCD_SetBackColor(Black);
// }
选中时 黑底白字 变 白底黑字
if(lcd_highshow == 0)
{
LCD_SetBackColor(White);//第三行高亮
LCD_SetTextColor(Black);
sprintf(text," count:%d ",count);
LCD_DisplayStringLine(Line3,(uint8_t *)text);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
sprintf(text," para1 ");
LCD_DisplayStringLine(Line4,(uint8_t *)text);
sprintf(text," para2 ");
LCD_DisplayStringLine(Line5,(uint8_t *)text);
}
else if(lcd_highshow == 1)
{
sprintf(text," count:%d ",count);
LCD_DisplayStringLine(Line3,(uint8_t *)text);
LCD_SetBackColor(White);
LCD_SetTextColor(Black);//第四行高亮
sprintf(text," para1 ");
LCD_DisplayStringLine(Line4,(uint8_t *)text);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
sprintf(text," para2 ");
LCD_DisplayStringLine(Line5,(uint8_t *)text);
}
else if(lcd_highshow == 2)
{
sprintf(text," count:%d ",count);
LCD_DisplayStringLine(Line3,(uint8_t *)text);
sprintf(text," para1 ");
LCD_DisplayStringLine(Line4,(uint8_t *)text);
LCD_SetBackColor(White);
LCD_SetTextColor(Black);//第五行高亮
sprintf(text," para2 ");
LCD_DisplayStringLine(Line5,(uint8_t *)text);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
}
7.PWM输出
eg.让PA1输出频率为1000HZ、占空比为50%的方波:
f(system)=80000000
->ARR+1=100,PSC+1=800
->f=1000Hz
代码:(在while(1)外,/* USER CODE BEGIN 2 */和/* USER CODE END 2 */内)
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
TIM2->CCR2 = 50;
8.输入捕获测量引脚输出PWM波
在第一个上升沿的时候CNT=0,测量下一个上升沿时的CNT(即capture_value)
- XL555 : 555定时器(输出一个频率可调的PWM波)
频率是通过R40电位器来调的
方法:
- 用PA1产生PWM波,用PA7测量 (8 讲这个)
- 用555定时器产生PWM波,用PA15或PB4测量 (详见 9)
-
STM32CubeMX里
PA7选TIM17_CH1
PA1选TIM2_CH2
将PSC设置成 80-1
然后点NVIC settings使能中断 -
在main中
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);TIM2->CCR2 = 50;下写上HAL_TIM_IC_Start_IT(&htim17,TIM_CHANNEL_1)使能中断 -
fun.c中:
小细节
HAL_TIM_ReadCapturedValue()函数实际上是读取CCR的值而不是CNT的值,这是因为在捕获到上升沿的时候,会将CNT赋值给CCR
中断回调函数:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM17)
{
capture_value=TIM17->CCR1;//两种写法是一样的
//capture_value = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
TIM17->CNT = 0;
fre = 80000000/(80*capture_value);
}
}
9.输入捕获测555定时器频率
- STM32CubeMX里
- PA15:TIM2_CH1
- PB4:TIM16_CH1
- Timer->
TIM2->CH1:input capture direct mode->PSC=80-1->NVIC settings 中断使能
TIM16->CH1:(上述TIM2CH1相同的操作)
代码:
- main中:
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);//使能中断 HAL_TIM_IC_Start_IT(&htim16,TIM_CHANNEL_1); - fun中 中断回调函数:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM16)//频率输出1 R39
{
capture_value1 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
TIM16->CNT = 0;
fre1 = 80000000/(80*capture_value1);
}
if(htim->Instance == TIM2)//频率输出2 R40
{
capture_value2 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
TIM2->CNT = 0;
fre2 = 80000000/(80*capture_value2);
}
}
不要忘记LCD初始化!!!!!
LCD_Init();//不要忘记LCD初始化!!!!!!
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
10.ADC测量
原理图:
- R37、R38电压在0 ~ 3.3V之间,然后PB15、PB12读取到的adc值在0 ~ 4096之间
->vol = 3.3 * adc_value/4096;
4096怎么来的:2的12次方就是4096
-
STM32CubeMX里
-
PB12选ADC1_IN11
-
PB15选ADC2_IN15
-
ADC1选IN11 Single-ended
-
ADC2勾选IN15 Single-ended
-
fun.c 中
void lcd_show()
{
sprintf(text," text ");
LCD_DisplayStringLine(Line0,(uint8_t *)text);
sprintf(text," R37_VOL:%.2f V ",get_vol(&hadc2));
LCD_DisplayStringLine(Line3,(uint8_t *)text);
sprintf(text," R38_VOL:%.2f V ",get_vol(&hadc1));
LCD_DisplayStringLine(Line5,(uint8_t *)text);
}
double get_vol(ADC_HandleTypeDef *hadc)
{
HAL_ADC_Start(hadc);
uint32_t adc_value = HAL_ADC_GetValue(hadc);
return 3.3*adc_value/4096;
}
11.串口发送与接收
USART1;
PA9 :TGT_TX
PA10 : TGT_RX
发送
- NVIC Setting :enable
代码
- 将文件
stm32g4xx_hal_uart.h中大概1633行处的HAL_UART_Transmit()函数拷贝到main函数中while(1)里 - while(1)中
char text01[20];
sprintf(text01,"Lan Qiao Bei\r\n");
HAL_UART_Transmit(&huart1,(uint8_t *)text01,sizeof(text01),50);//最后的“50”是时间
HAL_Delay(1000);//每隔1s发送一次
- 打开串口调试小助手
波特率设置成与cube中一样的115200Hz
接收
- main.c中
(while(1)外)
HAL_UART_Receive_IT(&huart1,&rec_data,1);//相当于中断使能
- fun.c中
uint8_t rec_data;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
HAL_UART_Transmit(huart,&rec_data,1,50);//input : 指针 指针 大小 时间
HAL_UART_Receive_IT(huart,&rec_data,1);//最后那个“1”为接收数据的字节数
}
}
- fun.h中
extern uint8_t rec_data;//因为要在main函数中使用!!!
12.利用定时器进行串口不定长数据接收
起始位 1 bit,数据位 8 bit,结束位 1 bit
(波特率(Baud Rate)为 9600 bit/s 时)
接收到最后一个数据时会判断t是否大于1.04ms,若大于1.04ms,则说明后面不会再有下一个数据发来了
PSC+1=8000
8000/80000000 = 1/10000
CNT = 15 -> t = 15 * 1/10000 = 0.0015s = 1.5ms
判断 CNT>15 ,也就是说 t>1.5ms
分:**
- main中
while(1)外:
HAL_UART_Receive_IT(&huart1,&rec_data,1);//最后那个“1”为接收数据的字节数
HAL_TIM_Base_Start(&htim4);//使能定时器4;
while(1)中:
uart_data_rec();
- fun.c中
uint8_t rec_data ,count;//count为数组的计数器
uint8_t rec_flag;
uint8_t rec_buff[20];//接收到的数据存储到这里
char send_buff[20];//发送数据的数组
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
//HAL_UART_Transmit(huart,&rec_data,1,50);//input : 指针 指针 大小 时间
TIM4->CNT = 0;//计数器清零
rec_flag=1;//接收数据时置1
rec_buff[count] = rec_data;
count++;
HAL_UART_Receive_IT(huart,&rec_data,1);//最后那个“1”为接收数据的字节数
}
}
//接收函数
void uart_data_rec()
{
if(rec_flag)
{
if(TIM4 -> CNT >15) //数据接收完成
{
//处理数据
if(count == 3 && rec_buff[0] == 'l' && rec_buff[1] == 'a' && rec_buff[2] == 'n') //如果前三个为 “lan” 加上判断长度的条件可以防止前面都对后面加字符的情况
{
sprintf(send_buff,"lan\r\n");//将数据存到send_buff中
HAL_UART_Transmit(&huart1,(uint8_t *)send_buff,sizeof(send_buff),50);//发送
}
if(count == 4 && rec_buff[0] == 'q' && rec_buff[1] == 'i' && rec_buff[2] == 'a'&& rec_buff[3] == 'o') //如果前三个为 “qiao”
{
sprintf(send_buff,"qiao\r\n");//将数据存到send_buff中
HAL_UART_Transmit(&huart1,(uint8_t *)send_buff,sizeof(send_buff),50);//发送
}
if(count == 3 && rec_buff[0] == 'b' && rec_buff[1] == 'e' && rec_buff[2] == 'i') //如果前三个为 “bei”
{
sprintf(send_buff,"bei\r\n");//将数据存到send_buff中
HAL_UART_Transmit(&huart1,(uint8_t *)send_buff,sizeof(send_buff),50);//发送
}
else
{
sprintf(send_buff,"error!\r\n");//将数据存到send_buff中
HAL_UART_Transmit(&huart1,(uint8_t *)send_buff,sizeof(send_buff),50);//发送
}
rec_flag = 0;//代表接收完成
for(int i=0;i<count;i++)//清空数组
rec_buff[i]=0;
count = 0;
}
}
}
}
13.EEPROM 读写
- eeprom是非易失性存储器(掉电后数据不会丢失)
注:
- 主机给从机发信息,从机接到消息后要给主机回应
- 从机给主机发信息,主机接到消息后也要给从机回应 即 接收方 给 发送方 回应
IIC通信协议
AT24C02产品手册p11:

E1、E2、E3都是接地的->A2、A1、A0都是0
最后一位的 R/W 是 read 和 write (分别是 1 和 0 )
代码部分:
- 将资源包里的
i2c_hal.c和i2c_hal.h复制到code文件夹中(记得去include一下) - 在
headfile.h中#include"i2c_hal.h"
i2c_hal.c中最后加上:
void eeprom_write(uint8_t addr,uint8_t dat)
{
I2CStart();//开启I2C
I2CSendByte(0xa0);//发送写地址
I2CWaitAck();//等待回应
I2CSendByte(addr);//发送内地址(实际写入AT24C02的地址 0~ 255)
I2CWaitAck();//等待回应
I2CSendByte(dat);//写数据
I2CWaitAck();//等待回应
I2CStop();//结束
}
uint8_t eeprom_read(uint8_t addr)
{
I2CStart();//开启I2C
I2CSendByte(0xa0);//发送写地址(因为第一步要先发送读取数据的地址)
I2CWaitAck();//等待回应
I2CSendByte(addr);//发送内地址(实际读取AT24C02的地址)
I2CWaitAck();//等待回应
I2CStop();//先停止
I2CStart();//开启I2C
I2CSendByte(0xa1);//发送读地址
I2CWaitAck();//等待回应
uint8_t dat = I2CReceiveByte();
I2CSendNotAck();//注意!!!此时主机不回应,如果回应就会发送下一个数据
I2CStop();
return dat;
}
记得在 i2c_hal.h 中加入定义:void eeprom_write(uint8_t addr,uint8_t dat); uint8_t eeprom_read(uint8_t addr);
读取AT24C02第0个地址的数据:
- i2C的初始化(可放在LCD初始化之后)
- 使用
eeprom_read(0)函数
main函数中
uint8_t dat = eeprom_read(0);
char text[20];
while(1)中:
sprintf(text," %d ",dat);
LCD_DisplayStringLine(Line0,(uint8_t *)text);
14.RTC实时时钟
主要功能实现:
- 设置时间和日期
- 读取时间和日期
- 设置一个闹钟
STM32CubeMX中的设置:





实现时间流动
Keil中:

char text[20];
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
void lcd_show()
{
HAL_RTC_GetTime(&hrtc,&sTime,RTC_FORMAT_BIN);//获取时间 第一个参数是句柄 &hrtc ,第二个是变量,第三个是 格式,例如 RTC_FORMAT_BIN 、RTC_FORMAT_BCD(和cubemx一样)
HAL_RTC_GetDate(&hrtc,&sDate,RTC_FORMAT_BIN);//获取日期
sprintf(text," text ");
LCD_DisplayStringLine(Line0,(uint8_t *)text);
sprintf(text," %02d:%02d:%02d ",sTime.Hours,sTime.Minutes,sTime.Seconds);
LCD_DisplayStringLine(Line3,(uint8_t *)text);
sprintf(text," %d-%d-%d-%d ",sDate.Year,sDate.Month,sDate.Date,sDate.WeekDay);
LCD_DisplayStringLine(Line5,(uint8_t *)text);
}
注意: 这里即使你不需要获取日期也要将获取日期的函数添上,不然的话时间不会流动!!!!
闹钟中断:
将大约832行处的中断回调函数void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)拷贝到fun.c中
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)//中断回调函数
{
led_mode = 1;
}
再在void lcd_show 函数中最后加上led_show(1,mode);
2025.02.24晚上21:35更新至此