蓝桥杯嵌入式2025备赛B站教程视频笔记总结

445 阅读13分钟

蓝桥杯嵌入式

代码一定要写在BEGIN和END之间!!(不然用STM32CubeMX重新生成后会不见)

image.png

main中记得LCD初始化!!!!!! main文件里记得 #include"headfile.h"

    LCD_Init();//不要忘记LCD初始化!!!!!!
    LCD_Clear(Black);
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);

记得 #include "headfile.h" !!!

目录

  1. 新建工程
  2. 点亮LED
  3. 按键
  4. LCD
  5. LED闪烁(定时器中断)
  6. 长按键与短按键
  7. LCD高亮显示
  8. PWM输出
  9. 输入捕获测量引脚输出PWM波
  10. 输入捕获测555定时器频率
  11. ADC测量
  12. 串口发送与接收
  13. 利用定时器进行串口不定长数据接收
  14. eeprom读写
  15. rtc实时时钟

0.新建工程

image.png image.png

image.png image.png

image.png

image.png

  • 最后点击右上角的 GENERATE CODE 生成工程就可以啦

1. 点亮LED

image.png

  • PCx输入低电平时对应的LED点亮
    SN74HC573是锁存器,只有PD2置高电平时PCx的数据才会传到xQ这边

  • 点亮LED步骤:
    PD2置高->PCx置低->PD2置低(因为LCD引脚与此共用)

STM32CubeMX中的设置 image.png 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.按键

image.png

上拉输入

  • 按键没有按下时 输入得到高电平
  • 按键按下时 输入得到低电平
    STM32CubeMX中的设置 image.png
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引脚冲突问题:

image.png

image.png

PC8~PC15是冲突的
解决方法:
在初始化LCD之前将锁存器的使能引脚PD2置低

HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);

image.png

  • 在lcd.c文件中所有用到的函数中首尾加这两行代码,保存一下进入这个函数前的GPIOC的状态

评论区方法: !!! 务必在gpio.h中将PD2设置高的语句后加上
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);//将锁存器的使能引脚PD2置低////

image.png

image.png

image.png

image.png

4.LED闪烁(定时器中断)

(记得 NVIC 使能) image.png

  • PSC:自动重装载值 (不能超过2的16次方-1(65535))

  • ARR:预分频器

  • CNT:计数器
    image.png

  • 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.长按键与短按键

  • 原理 image.png

image.png

  • STM32CubeMX设置:
  • (不需要NVIC 使能) image.png
 //按键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输出

image.png

eg.让PA1输出频率为1000HZ、占空比为50%的方波: f(system)=80000000
->ARR+1=100,PSC+1=800
->f=1000Hz

image.png

代码:(在while(1)外,/* USER CODE BEGIN 2 *//* USER CODE END 2 */内

  HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
  TIM2->CCR2 = 50;

8.输入捕获测量引脚输出PWM波

image.png

在第一个上升沿的时候CNT=0,测量下一个上升沿时的CNT(即capture_value)

image.png

image.png

  • XL555 : 555定时器(输出一个频率可调的PWM波)
    频率是通过R40电位器来调的

方法:

  1. 用PA1产生PWM波,用PA7测量 (8 讲这个)
  2. 用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测量

原理图: image.png

  • R37、R38电压在0 ~ 3.3V之间,然后PB15、PB12读取到的adc值在0 ~ 4096之间
    ->vol = 3.3 * adc_value/4096; image.png

4096怎么来的:2的12次方就是4096

  • STM32CubeMX里

  • PB12选ADC1_IN11

  • PB15选ADC2_IN15 image.png

  • ADC1选IN11 Single-ended image.png

  • ADC2勾选IN15 Single-ended image.png

  • 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

发送

image.png

image.png

  • NVIC Setting :enable

代码

  1. 将文件 stm32g4xx_hal_uart.h 中大概1633行处的HAL_UART_Transmit()函数拷贝到main函数中while(1)
  2. 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发送一次
  1. 打开串口调试小助手

image.png

波特率设置成与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.利用定时器进行串口不定长数据接收

image.png 起始位 1 bit,数据位 8 bit,结束位 1 bit

image.png

(波特率(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

image.png

image.png 分:**

  • 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;
        }
        
    }

}

}

a25a5f271e401ea09bece681acd6c06.png


13.EEPROM 读写

  • eeprom是非易失性存储器(掉电后数据不会丢失)

注:

  1. 主机给从机发信息,从机接到消息后要给主机回应
  2. 从机给主机发信息,主机接到消息后也要给从机回应 即 接收方 给 发送方 回应

IIC通信协议

image.png

image.png AT24C02产品手册p11:
image.png

E1、E2、E3都是接地的->A2、A1、A0都是0
最后一位的 R/Wreadwrite (分别是 1 和 0 )

代码部分:

  1. 将资源包里的i2c_hal.ci2c_hal.h复制到code文件夹中(记得去include一下)
  2. 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个地址的数据:

  1. i2C的初始化(可放在LCD初始化之后)
  2. 使用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);

image.png

image.png

14.RTC实时时钟

主要功能实现:

  1. 设置时间和日期
  2. 读取时间和日期
  3. 设置一个闹钟

STM32CubeMX中的设置:

image.png

image.png

image.png

image.png

cee9d97d56fcccc71b808d4964f27f9.jpg

实现时间流动

Keil中:
image.png

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更新至此