蓝桥杯嵌入式省赛各模块代码(持续更新)

发布于 2024-04-22  60 次阅读


注意

  • PWM输出和ADC均不需要开全局中断
  • 按键定时是有关中断的,应该使用初始化函数HAL_TIM_Base_Start_IT而不是HAL_TIM_Base_Start
  • 修改PWM占空比的函数最好直接记住
    __HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,50);
    //注意__HAL_TIM_SetCompare与__HAL_TIM_GetCompare的区别
  • rx_proc函数最后记得把rx_pointer和rx_data清空,记住memset函数的用法
    memset(rx_data,0,strlen(rx_data));

    1.函数目录

2.1GPIO操作

HAL_GPIO_WritePin//引脚输出高低电平
sscanf(rxdata,"%4s:%4s:%12s",car_type,car_data,car_time);
HAL_UART_Transmit(&huart1,(uint8_t *)temp,strlen(temp),50);//串口发送函数

1.1软件使用

2.1cubemx配置

3.1第一栏引脚初始化(Pinout&Configuration)

image-20231212125814026|700

image-20231212130545047|675

image-20231212130558754

3.2第四栏工具配置

image-20231212130812128

3.3第三栏工程配置Project Manager

image-20231212131248759

第一个选项为加入所有库,第二个选项为仅仅添加必要的库。

image-20231212171925617

第一项为给每一个外设生成一个单独的.c/.h文件,如果不勾选的话生成的所有初始化代码都在main.c文件里

第二项不用管

1.1按键检测与led

2.1LED点亮函数

void LED_Disp(uchar dsLED)
{
    HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOC,dsLED<<8,GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}

2.1按键驱动

//按键检测
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_PIN)
{           
    /*检测是否有按键按下 */
    if(HAL_GPIO_ReadPin(GPIOx,GPIO_PIN) == 0 )  
    {    
        HAL_Delay(20);//消抖
        while(HAL_GPIO_ReadPin(GPIOx,GPIO_PIN) == 0);   /*等待按键释放 */
        return  1;
    }
    else
        return 0;
}

2.2按键控制LED灯点亮

创建变量

__IO uint32_t uwTick_KEY_Point = 0;//控制key_check的执行速度

函数部分

void key_proc(void)
{
    if(uwTick - uwTick_KEY_Point<100) return;//减速函数
    uwTick_KEY_Point = uwTick;

        if(Key_Scan(GPIOB,GPIO_PIN_0))//B1
        {
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_8);//翻转电平
            HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
            HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
        }
        if(Key_Scan(GPIOB,GPIO_PIN_1))//B2
        {
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_9);//翻转电平
            HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
            HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
        }
        if(Key_Scan(GPIOB,GPIO_PIN_2))//B3
        {
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_10);//翻转电平
            HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
            HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
        }
        if(Key_Scan(GPIOA,GPIO_PIN_0))//B4
        {
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_11);//翻转电平
            HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
            HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);

        }
}

1.2LCD程序

找到竞赛包里lcd.h,fonts.h和lcd.c三个文件,直接复制到自己的工程文件夹里,在工程里加入文件

2.1基本函数

下面这段函数在例程里面有的,可以自己加上用

    LCD_Init();
    LCD_Clear(White);//清屏成白色 
    LCD_SetBackColor(White);//设置字的底色
    LCD_SetTextColor(Blue);//设置字的颜色
LCD_DisplayStringLine(Line4, (unsigned char *)"    Hello,world.   ");//指定行打印字符串

2.2创建变量

__IO uint32_t uwTick_LCD_Point = 0;//控制LCD_proc的执行速度
unsigned char i=0;
//LCD显示专用变量
unsigned char LCD_Disp_String[22];//用来存储要放的字符串

2.3打印变化量

    char text[30];//定义字符数组
    uint i=5;
    sprintf(text,"  CNBR:%d          ",i);
    //用sprintf函数将字符串打印给text
    LCD_DisplayStringLine(Line4,(uint8_t *)text);
    //显示在屏幕第4行
void LCD_proc(void)
{
    if(uwTick - uwTick_LCD_Point<100) return;//减速函数,实现300ms执行一次
    uwTick_LCD_Point = uwTick;
    if(Key_Scan(GPIOB,GPIO_PIN_0))
    i++;

    sprintf((char *)LCD_Disp_String," i num: %03d        ",(unsigned int)i);
    LCD_DisplayStringLine(Line4, LCD_Disp_String);
}

1.3定时器

2.1按键操作

cube配置

四个按键定义为GPIO_Input,==在配置界面将电平拉高==
Pasted image 20240324211722

在cube里面开一个TIM4定时器,在Mode里面吧时钟源改成外部时钟(Internal Clock),再吧分频改成80-1,计时改成10000-1;此时定时器频率为100HZ,

打开全局中断

在bsp创建一个interrupt.c文件

在stm32g4xx_hal_tim.h文件最下面中找到中断回调函数,直接赋值粘贴

image-20240309105256706

==在interrupt.h文件里==(只有这样才能把变量引入mian.c文件里)定义结构体

struct keys
{
    uchar judge_sta;//判断进行到哪一步了
    uchar key_sta;//识别到按键喊下标志位
    uchar single_flag;//单次按键标志位
    uchar long_flag;//长按按键标志位
    uint key_time;
};

在interrupt.c文件里结构体数组定义变量key[4]

struct keys key[4]={0,0,0};

在mian.c中引入外部变量key[4]

/* Private variables --------------------*/

/* USER CODE BEGIN PV */
extern struct keys key[4];
/* USER CODE END PV */

3.1按键单次检测和长按检测中断函数

struct keys key[4]={0,0,0};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//中断回调函数
{
    if(htim->Instance==TIM3)
    {
        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].judge_sta)//一开始judge_sta为0,
            {
                case 0:
                {
                    if(key[i].key_sta==0)//检测到按键按下,进入状态1
                    {
                        key[i].judge_sta=1;
                        key[i].key_time=0;
                    }
                }
                break;
                case 1:
                {
                    if(key[i].key_sta==0)//检测到按键没有抖动,single_flag置1
                    {
                        key[i].judge_sta=2;
//                      key[i].single_flag=1;//如果放在这里就是按下检测
                    }
                    else
                        key[i].judge_sta=0;
                }
                break;
                case 2:
                {
                    if(key[i].key_sta==1)//检测到按键松开,回到状态0
                    {
                        key[i].judge_sta=0;
                        if(key[i].key_time<70)
                            key[i].single_flag=1;//放在这里就是按键抬起检测
                    }
                    else
                    {
                        key[i].key_time++;
                        if(key[i].key_time>70)
                            key[i].long_flag=1;
                    }
                }
                break;
            }

        }
    }
}

3.初始化

开启定时器4检测按键,具体函数在HAL库定时器头文件下找到

Pasted image 20240324220200

HAL_TIM_Base_Start_IT(&htim4);

3.2按键检测并显示在屏幕上

 while (1)
  {
      if(key[0].single_flag==1)
      {
          sprintf(text,"  key0down    ");
          LCD_DisplayStringLine(Line5,(uint8_t *)text);
          key[0].single_flag=0;
      }
      else if(key[0].long_flag==1)
      {
          sprintf(text,"  long_key0down    ");
          LCD_DisplayStringLine(Line5,(uint8_t *)text);
          key[0].long_flag=0;
      }
        if(key[1].single_flag==1)
      {
          sprintf(text,"  key1down         ");
          LCD_DisplayStringLine(Line5,(uint8_t *)text);
          key[1].single_flag=0;
      }
       if(key[2].single_flag==1)
      {
          sprintf(text,"  key2down         ");
          LCD_DisplayStringLine(Line5,(uint8_t *)text);
          key[2].single_flag=0;
      }

       if(key[3].single_flag==1)
      {
          sprintf(text,"  key3down         ");
          LCD_DisplayStringLine(Line5,(uint8_t *)text);
          key[3].single_flag=0;
      }
  }

3.3通过按键切换屏幕显示

while (1)
  {
      key_proc();
      disp_proc();

  }
/* USER CODE BEGIN 4 */
void key_proc()
{

    if(key[0].single_flag==1)
      {
          view=!view;
          LCD_Clear(Black);
          key[0].single_flag=0;
      }
}
void disp_proc()
{
    if(view==0)
    {
        sprintf(text,"       Data         ");
        LCD_DisplayStringLine(Line2,(uint8_t *)text);
        sprintf(text,"   CNBR:2          ");
        LCD_DisplayStringLine(Line4,(uint8_t *)text);
        sprintf(text,"   VNBR:4          ");
        LCD_DisplayStringLine(Line6,(uint8_t *)text);
        sprintf(text,"   IDLE:2          ");
        LCD_DisplayStringLine(Line8,(uint8_t *)text);
    }
    if(view==1)
    {
        sprintf(text,"       Para         ");
        LCD_DisplayStringLine(Line2,(uint8_t *)text);
        sprintf(text,"   CNBR:3.50        ");
        LCD_DisplayStringLine(Line4,(uint8_t *)text);
        sprintf(text,"   VNBR:2.00        ");
        LCD_DisplayStringLine(Line6,(uint8_t *)text);
    }
}

/* USER CODE END 4 */

2.2生成频率占空比可调的PWM波

生成频率和占空比均可调的脉冲波
[蓝桥杯嵌入式]hal库 stm32 PWM的使用(随时修改占空比,随时修改频率)-CSDN博客

3.1cube配置

把PA6和PA7引脚分别改成TIM16_CH1和TIM17_CH1;

cubemx Timers进行如下配置

image-20240312002851539

分频为4000,定时为100,占空比为20(后期代码可调),总共开了两个PWM波,都是通道一的

3.2初始化

PWM初始化

    HAL_TIM_PWM_Start(&htim16,TIM_CHANNEL_1);//PWM初始化
    HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_2);
    HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,pa6_duty);//设置初始pwm频率
    __HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,pa7_duty);

3.3按键过程函数

实现按下按键PWM波占空比增大,前面定义了两个新变量pa6_duty和pa7_duty,到90后重新变成10

uchar pa6_duty=10;
uchar pa7_duty=10;

void key_proc()
{
    if(key[0].single_flag==1)
      {
          view=!view;
          key[0].single_flag=0;
          LCD_Clear(Black);
      }
    if(key[1].single_flag==1)
      {
          if(pa6_duty>=90) pa6_duty=10;
          else
              pa6_duty+=10;
          __HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,pa6_duty);
          key[1].single_flag=0;
      }
    if(key[2].single_flag==1)
      {
          if(pa7_duty>=90) pa7_duty=10;
          else
              pa7_duty+=10;
          __HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,pa7_duty);//直接设置占空比
          key[2].single_flag=0;
      }
}

3.4显示过程函数

讲pa6_duty和pa7_duty的值显示在屏幕上

char text[30];

void disp_proc()
{
    if(view==0)
    {
        sprintf(text,"       Data         ");
        LCD_DisplayStringLine(Line2,(uint8_t *)text);
        sprintf(text,"   CNBR:2          ");
        LCD_DisplayStringLine(Line4,(uint8_t *)text);
        sprintf(text,"   VNBR:4          ");
        LCD_DisplayStringLine(Line6,(uint8_t *)text);
        sprintf(text,"   IDLE:2          ");
        LCD_DisplayStringLine(Line8,(uint8_t *)text);
    }
    if(view==1)
    {
        sprintf(text,"       Para         ");
        LCD_DisplayStringLine(Line1,(uint8_t *)text);
        sprintf(text,"    PA6:%d          ",pa6_duty);
        LCD_DisplayStringLine(Line3,(uint8_t *)text);
        sprintf(text,"    PA7:%d          ",pa7_duty);
        LCD_DisplayStringLine(Line5,(uint8_t *)text);
    }
}

2.3占空比和频率捕捉

3.1Cube配置

把引脚PA15和PB4分别定义成TIM2_CH1和TIM3_CH1;

Mode下Channel1改成直接捕获模式Input Capture direct mode,Channe2改成间接捕获模式Input Capture indirect mode

Channel1用来测频率,Channel2用来测占空比

进行如下配置,Channel2用来检测下降沿

image-20240312220928921

3.2初始化

HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);//输入捕获初始化
    HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);
    HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_2);
    HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2);

3.3频率与占空比检测函数

写在bsp的interrupt.c文件中

double ccr1_val1a=0,ccr1_val2a=0;//用来读取定时器的数值
uint ccr1_val1b=0,ccr1_val2b=0;
uint frq1=0,frq2=0;//频率
float duty1=0,duty2=0;//占空比
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance==TIM2)
    {
        if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1)//中断消息来源 选择直接输入的通道
        {
            ccr1_val1a=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);//直接
            ccr1_val1b=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);//间接
            __HAL_TIM_SetCounter(htim,0);
            frq1=(80000000/80)/ccr1_val1a;
            duty1=(ccr1_val1b/ccr1_val1a)*100;
            HAL_TIM_IC_Start(htim,TIM_CHANNEL_1);
            HAL_TIM_IC_Start(htim,TIM_CHANNEL_2);
        }
    }
    if(htim->Instance==TIM3)
    {
        if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1)//中断消息来源 选择直接输入的通道
        {
            ccr1_val2a=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
            ccr1_val2b=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);
            __HAL_TIM_SetCounter(htim,0);
            frq2=(80000000/80)/ccr1_val2a;
            duty2=(ccr1_val2b/ccr1_val2a)*100;
            HAL_TIM_IC_Start(htim,TIM_CHANNEL_1);
            HAL_TIM_IC_Start(htim,TIM_CHANNEL_2);
        }
    }
}

3.4显示函数

    if(view==0)
    {
        sprintf(text,"       Data         ");
        LCD_DisplayStringLine(Line2,(uint8_t *)text);
        sprintf(text,"     FRQ1=%d         ",frq1);
        LCD_DisplayStringLine(Line4,(uint8_t *)text);
        sprintf(text,"     duty1=%.3f      ",duty1);
        LCD_DisplayStringLine(Line5, (uint8_t *)text);
        sprintf(text,"     FRQ2=%d         ",frq2);
        LCD_DisplayStringLine(Line6,(uint8_t *)text);
        sprintf(text,"     duty2=%.3f      ",duty2);
        LCD_DisplayStringLine(Line7, (uint8_t *)text);

    }

1.4ADC

2.1cube配置

在cube中打开对应ADC的引脚PB12和PB15

image-20240313195244628

然后点击Analog,选中对应ADC的通道IN15和IN11

2.2初始化

HAL_ADCEx_Calibration_Start(&hadc1,ADC_SINGLE_ENDED);//ADC校准函数 使用后测量更准确
    HAL_ADCEx_Calibration_Start(&hadc2,ADC_SINGLE_ENDED);//本函数视频中未提及

2.3电压读取函数

新建一个bsp_adc.c文件,注意到ADC是一个12位的

#include "bsp_adc.h"

double getADC(ADC_HandleTypeDef *pin)
{
    uint adc;
    HAL_ADC_Start(pin);//记住,这里不需要地址
    adc = HAL_ADC_GetValue(pin);
    return adc*3.3/4096;
}

2.4显示函数

直接调用函数在屏幕上显示出来

        sprintf(text,"     V:%.2f      ",getADC(&hadc1));
        LCD_DisplayStringLine(Line8, (uint8_t *)text);
        sprintf(text,"     V:%.2f      ",getADC(&hadc2));
        LCD_DisplayStringLine(Line9, (uint8_t *)text);

1.5IIC

eeprom

原理

image-20240318172846786

2.1板子硬件

板子通过IIC连接了两个芯片,本次使用IIC读取EPPROM

image-20240315180605826

2.1Cube配置

本次使用的是软件IIC,故只需将PB6和PB7引脚配置为GPIO_Output

image-20240315180818744

注:截图截错了

2.2比赛代码移植

2.3eeprom读函数

//eeprom_read
uchar eeprom_read(uchar addr)
{
    //开启联系芯片
    uchar dat;
    I2CStart();
    I2CSendByte(0xa0);//第一步要先写,告诉eeprom要读取哪个数
    I2CWaitAck();//I2C等待确认信号
    I2CSendByte(addr);//发送要读取的·地址
    I2CWaitAck();
    I2CStop();

    I2CStart();
    I2CSendByte(0xa1);//开始读模式
    I2CWaitAck();
    dat=I2CReceiveByte();
    I2CSendNotAck();//单片机不发送确认信号,防止eeprom继续发送信号
    I2CStop();

    return dat;
}

2.4eeprom写函数

//eeprom_write
void eeprom_write(uchar addr,uchar dat)
{
    //开启联系芯片
    I2CStart();
    I2CSendByte(0xa0);//2进制表示为1010_0000,=000表示硬件地址,最后一位0表示写入,如果是1表示读取
    I2CWaitAck();//I2C等待确认信号
    I2CSendByte(addr);//发送存储的地址
    I2CWaitAck();

    I2CSendByte(dat);//发送数据
    I2CWaitAck();
    I2CStop();
    HAL_Delay(10);//这里一定要有,eeprom写数据需要时间
}

2.5主函数测试

按下按键4将通道1频率值存入eeprom中,然后再用读取函数显示在屏幕上;

/* USER CODE BEGIN 4 */
void key_proc()
{ 
      if(key[3].single_flag==1)
      {
          uchar frq_h=frq1>>8;//eeprom只能存8位数据,uint是16位数据,故需要取高8位和低8位分别存入
          uchar frq_l=frq2&0xff;
          eeprom_write(1,frq_h);
          HAL_Delay(10);//写入需要时间,不延时可能写不进;
          eeprom_write(2,frq_l);
          key[3].single_flag=0;
      }
}
void disp_proc()
{
    if(view==1)
    {
        uint eep_temp=(eeprom_read(1)<<8)+eeprom_read(2);
        sprintf(text,"     eeprom=%d     ",eep_temp);
        LCD_DisplayStringLine(Line7, (uint8_t *)text);
    }
}

MCP4017可编程电阻

电阻的最大阻值为100K,可调范围为0到127;寄存器每增加一个数,电阻增加787.402欧

原理

这里要记住,它的地址永远是0101111X,0x5f(读模式)或0x5e(写模式)
Pasted image 20240411185327

PB14接入adc检测,配置和Cube一样
Pasted image 20240411185339

代码

void write_resistor(uint8_t value)
{
    I2CStart();
    I2CSendByte(0x5E);
    I2CWaitAck();

    I2CSendByte(value);
    I2CWaitAck();   
    I2CStop();  
}
uint8_t read_resistor(void)
{
    uint8_t value;
    I2CStart();
    I2CSendByte(0x5F);
    I2CWaitAck();

    value = I2CReceiveByte();
    I2CSendNotAck();    
    I2CStop();  

    return value;
}

1.6串口USART

2.1Cube配置

image-20240318181915718

配置PA9和PA10两个引脚

image-20240318182934955

将USART1的Mode改为异步模式

image-20240318183246351

修改波特率为9600

image-20240318183307415

NVIC全局中断勾上
Pasted image 20240326200009

2.2初始化

在变量创建区创建一个串口专用unsigned char变量rxdat
在main函数中初始化

HAL_UART_Receive_IT(&huart1, &rxdat, 1);

第二个变量填入变量rxdat的地址,这样串口接收到的数据会实时赋值给rxdat

2.3串口接收

串口发送函数,在hal库中找
Pasted image 20240326201258

中断回调函数

char rxdata[30];
uint8_t rxdat;
uchar rx_pointer;

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    rxdata[rx_pointer++]=rxdat;
    HAL_UART_Receive_IT(&huart1,&rxdat,1);
}

串口接收检测

if(rx_pointer!=0)
        {
            int temp=rx_pointer;
            HAL_Delay(1);
            if (temp==rx_pointer)uart_rx_proc();//完成接收
        }

串口接收过程函数

void usart_proc()
{
    if(strcmp(rxdata,"R37")==0)
    {
        sprintf(text,"R37:%d,%d,%.1f%%",R37_ALL_count,R37_Pass_count,R37_Hegelv);
        HAL_UART_Transmit(&huart1,(u8 *)text,strlen(text),50);
    }
    if(strcmp(rxdata,"R38")==0)
    {
        sprintf(text,"R38:%d,%d,%.1f%%",R38_ALL_count,R38_Pass_count,R38_Hegelv);
        HAL_UART_Transmit(&huart1,(u8 *)text,strlen(text),50);
    }
    rx_pointer=0;
    memset(rxdata,0,strlen(rxdata));
}
最后更新于 2025-03-03