STM32PWM--基于HAL库(第十三届蓝桥杯嵌入式模拟题)

前言
一、CubeMX配置(第十三届模拟题完整版)
二、代码相关定义、声明
1.函数声明
2.宏定义
3.变量定义
三、主要函数
1.按键扫描
2.配置模式
3.LCD显示
4.频率检测(TIM2输入捕获中断函数)
5.PWM输出(TIM3)
6.Main函数
四、实验结果
1.输入频率检测
2.R值
3.输出频率
五、源码(转载请注明出处)
总结
前言
相关说明:

开发板:CT117E-M4(STM32G431RB 蓝桥杯嵌入式比赛板)
开发环境: CubeMX+Keil5
涉及题目:第十三届蓝桥杯嵌入式模拟题
题目难点:根据输入的PWM,实时更新输出PWM,R37电压值为0V时如何输出持续的低电平,电压为3.3V时如何输出持续的高电平。

CubeMX配置、主要函数代码及说明:

一、CubeMX配置(第十三届模拟题完整版)
1.使能外部高速时钟:

2.配置时钟树:

3.GPIO:


4.ADC(默认配置即可):
5.TIM2(输入捕获,检测输入信号的频率):
6.TIM3(PWM输出):
7.NVIC(输入捕获中断配置):

二、代码相关定义、声明
1.函数声明
main.c
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);//输入捕获中断函数 计算输入信号频率
void LCD_Init_Show(void);    //LCD初始化显示
void LCD_Refresh(void);        //LCD更新显示 

gpio.h
void KEY_Scan(void);//按键扫描
void LED_AllClose(uint8_t *LED_Close);//LED更新显示
void LED_Change(void);//LED状态改变

adc.h
double ADC_GetValue(void);//获取R37电压值

time.h
void PWM_Out(double R37_V,uint32_t FRQ,uint8_t    R);//PWM输出


2.宏定义
#define LED_GPIO_PORT    GPIOC
#define LED1_GPIO_PIN    GPIO_PIN_8
#define LED2_GPIO_PIN    GPIO_PIN_9
#define LED3_GPIO_PIN    GPIO_PIN_10
#define LED4_GPIO_PIN    GPIO_PIN_11
#define LED5_GPIO_PIN    GPIO_PIN_12
#define LED6_GPIO_PIN    GPIO_PIN_13
#define LED7_GPIO_PIN    GPIO_PIN_14
#define LED8_GPIO_PIN    GPIO_PIN_15

#define ON     GPIO_PIN_RESET
#define OFF GPIO_PIN_SET

#define LED1(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED1_GPIO_PIN,a)
#define LED2(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED2_GPIO_PIN,a)
#define LED3(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED3_GPIO_PIN,a)
#define LED4(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED4_GPIO_PIN,a)

#define KEY1_GPIO_PORT    GPIOB
#define KEY1_GPIO_PIN    GPIO_PIN_0
#define KEY2_GPIO_PORT    GPIOB
#define KEY2_GPIO_PIN    GPIO_PIN_1
#define KEY3_GPIO_PORT    GPIOB
#define KEY3_GPIO_PIN    GPIO_PIN_2
#define KEY4_GPIO_PORT    GPIOA
#define KEY4_GPIO_PIN    GPIO_PIN_0


3.变量定义
char str[30];        //用于组合字符串
uint32_t FRQ;        //输入信号频率
uint32_t TIM_Clock=1000000;    //定时器时钟频率
double R37_V;        //R37电压值
uint8_t R=4;        //R值
uint8_t R_step=2;    //R每次改变值
uint8_t R_max=10;    //R改变上限值
uint8_t R_min=2;    //R改变下限值

uint8_t LED_Close[5]={0,0,1,0,0};    //LED关闭数组 值为1,则下标对应LED关闭
uint8_t Page=0;                        //LCD显示页(数据显示0,数据更改1)

三、主要函数
1.按键扫描
尽量将按键实现的功能封装为独立的函数,降低函数耦合度。

void KEY_Scan()
{
    if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET)//Setting
    {
        HAL_Delay(10);
        if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET)
        {
            while(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET);
            Page=1;//页数更新
            LCD_Refresh();//LCD更新显示
            LED_Change();//LED状态改变
            LED_AllClose(LED_Close);//LED更新显示
            Setting_Mode();//进入设置模式
        }
    }
    
    else if(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET)//ban
    {
        HAL_Delay(10);
        if(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET)
        {
            while(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET);
            LED_ban=!LED_ban;//LED禁用标志位状态翻转
            if(LED_ban)//如果禁用则将LED全部关闭
            {
                LED_Close[1]=1;
                LED_Close[2]=1;
                LED_Close[3]=1;
                LED_Close[4]=1;
            }
            else//否则重新开启
            {
                LED_Close[1]=0;
                LED_Close[2]=0;
                LED_Close[3]=0;
                LED_Close[4]=0;
            }
        }
    }
}

2.配置模式
用到两个函数对数据进行更改:
1.Dat_change(uint16_t mode),参数为数据更改方式(加/减)。
2.Setting_Mode(),按键按下后调用函数即可对数据进行更改操作,更改完后再更新显示。

void Dat_change(uint16_t mode)//数据改变
{
    switch(mode)//数据改变模式(ADD +,SUB -)
    {
        case ADD:
            R+=R_step;
            if(R>R_max)//不可大于上限
                R=R_max;
            break;
        
        case SUB:
            R-=R_step;
            if(R<R_min)//不可小于下限
                R=R_min;
            break;
    }
}

void Setting_Mode()//设置模式
{
    while(1)
    {
        if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET)//save
        {
            HAL_Delay(10);
            if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET)
            {
                while(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET);
                Page=0;//页数更新
                LCD_Refresh();//LCD显示更新
                break;//break;
            }
        }
        
        else if(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET)//++
        {
            HAL_Delay(10);
            if(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET)
            {
                while(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET);
                Dat_change(ADD);//R++
                LCD_Refresh();//LCD显示更新
            }
        }
        
        else if(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET)//--
        {
            HAL_Delay(10);
            if(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET)
            {
                while(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET);
                Dat_change(SUB);//R--
                LCD_Refresh();//LCD显示更新
            }
        }
    }
}


3.LCD显示
共有两个函数:
1.LCD_Init_Show(),在上电启动后对LCD进行初始化显示操作。
2.LCD_Refresh(),LCD更新显示,数据更新后需要实时进行更新显示。

void LCD_Init_Show()//LCD初始化显示
{
    LCD_Clear(Black);
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);
    
    LCD_DisplayStringLine(Line1,(unsigned char*)"       Data           ");
    sprintf(str,"   FRQ:%dHz           ",FRQ);
    LCD_DisplayStringLine(Line3,(unsigned char*)str);
    sprintf(str,"   R37:%.2fV          ",R37_V);
    LCD_DisplayStringLine(Line5,(unsigned char*)str);
}

void LCD_Refresh()//LCD更新显示 
{
    if(Page==0)//数据显示页面
    {
        LCD_DisplayStringLine(Line1,(unsigned char*)"       Data           ");
        sprintf(str,"   FRQ:%dHz           ",FRQ);
        LCD_DisplayStringLine(Line3,(unsigned char*)str);
        sprintf(str,"   R37:%.2fV          ",R37_V);
        LCD_DisplayStringLine(Line5,(unsigned char*)str);
    }
    else if(Page==1)//数据更改页面
    {
        LCD_DisplayStringLine(Line1,(unsigned char*)"       Para           ");
        sprintf(str,"      R:%d            ",R);
        LCD_DisplayStringLine(Line3,(unsigned char*)str);
        LCD_DisplayStringLine(Line5,(unsigned char*)"                      ");
    }
}
4.频率检测(TIM2输入捕获中断函数)
检测输入信号频率分为五步:
1.配置定时器相应通道功能为输入捕获并使能中断,上升沿捕获或下降沿捕获均可。

2.开启输入捕获中断:HAL_TIM_IC_Start_IT(&htimX,TIM_CHANNEL_X);

3.在进入中断函数后,获取定时器的计数值,该计数值/定时器时钟频率即为输入信号周期。

4.频率=1/周期,即频率是周期的倒数,则输入信号频率=定时器时钟频率/计数值。

5.计数值清零。

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//输入捕获中断函数 计算输入信号频率
{
    uint32_t count;
    count=TIM2->CNT;//获取定时器计数值
    FRQ=TIM_Clock/count;//频率=1/周期  所以频率=1/计数值/定时器时钟频率=定时器时钟频率/计数值
    TIM2->CNT=0;//计数值清零
}

5.PWM输出(TIM3)
PWM输出的代码看似很长,但起始有一大段是GPIO结构体的配置,该题配置PWM输出共分为四步:
1.根据R37电压值的不同,将PA7输出分为三种方式:a.电压值为0V;b.电压值为3.3V;c.0V<电压值<3.3V;ab两种情况对应持续的低电平和高电平,c对应PWM输出。

2.ab两种情况时先关闭PWM,并将GPIO引脚输出方式更改为通用推挽输出(否则PA7无法正常输出持续的高低电平),重新初始化GPIO后调用HAL_GPIO_WritePin即可正常输出。

3.c则根据输入信号频率以及R37电压值来配置PWM参数。首先要知道两条公式:
PWM输出频率=定时器时钟频率/重装载值。
占空比=Pulse/重装载值*100%。
现在知道的数据是输出信号频率(输入信号频率/R值)和占空比(R37电压值/3.3V),则可以得出:
重装载值=(定时器时钟频率/输出信号频率)-1。
Pulse=R37电压值/3.3V×重装载值。

4.根据计算得出的数值直接对寄存器进行配置:
TIM3->ARR=Period;TIM3->CCR2=Pulse;
最后再重启启动PWM即可。

void PWM_Out(double R37_V,uint32_t FRQ,uint8_t R) //PWM输出
{
    uint32_t Period;//配置重装载值
    uint32_t Pulse;//配置占空比
    GPIO_InitTypeDef GPIO_InitStruct;//重新配置GPIO 默认为PWM输出 
    GPIO_InitStruct.Pin = GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    if(R37_V<0.01)//如果R37电压值为0V
    {
        HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_2);//PWM关闭
        GPIO_InitStruct.Mode =GPIO_MODE_OUTPUT_PP;//配置为通用推挽输出 如不配置 无法正常输出高低电平
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);    //重新初始化
        HAL_GPIO_WritePin(GPIOA,GPIO_PIN_7,GPIO_PIN_RESET);//输出低电平
    }
    else if(R37_V>3.29)//如果R37电压值为3.3V
    {
        HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_2);//PWM关闭
        GPIO_InitStruct.Mode =GPIO_MODE_OUTPUT_PP;//配置为通用推挽输出
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);    //重新初始化
        HAL_GPIO_WritePin(GPIOA,GPIO_PIN_7,GPIO_PIN_SET);//输出低电平
    }
    else if(R37_V>0 && R37_V<3.3)//输入PWM
    {
        Period=TIM_Clock/(FRQ/R)-1;//输出PWM的重装载值=(定时器时钟频率/目标频率)-1
        Pulse=R37_V/3.3*Period-1;//输出PWM的Pulse=(R37电压值/3.3*重装载值)-1
        TIM3->ARR=Period;//直接对寄存器进行配置
        TIM3->CCR2=Pulse;
        HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);//开启PWM
    }
}

6.Main函数
注意起始时要将TIM2中断标志位清零:TIM2->SR=0;

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC2_Init();
  MX_TIM2_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
    LCD_Init();//LCD初始化
    LCD_Init_Show();//LCD初始化显示
    TIM2->SR=0;//TIM2中断标志位清零
    HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_2);//打开TIM2输入捕获中断
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
        KEY_Scan();//按键扫描
        R37_V=ADC_GetValue();//更新R37电压值
        LCD_Refresh();//LCD更新显示
        LED_Change();//LED状态改变
        LED_AllClose(LED_Close);//LED更新显示
        PWM_Out(R37_V,FRQ,R); //PWM输出配置
        FRQ=0;//FRQ清零
  }
  /* USER CODE END 3 */
}


四、实验结果
1.输入频率检测
此处用的是另一块开发板为该板提供信号,频率为1KHz;并调节R37使其电压为1.65V,预期占空比为1.65/3.3=50%。


2.R值
默认R值为4,作用为将输入信号进行分频处理;预期输出频率为1K/4=250Hz。


3.输出频率
这里直接用示波器检测输出PWM,频率为250Hz,占空比为50%,符合预期。

五、源码(转载请注明出处)
————————————————
版权声明:本文为CSDN博主「AゞOctopus๊」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Octopus1633/article/details/123476310

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值