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


前言

相关说明:

开发板: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.电压值为0Vb.电压值为3.3Vc.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%,符合预期。在这里插入图片描述

五、源码(转载请注明出处)

在这里插入图片描述


总结

以上就是全部内容,如有错误请批评指正。

猜你喜欢

转载自blog.csdn.net/Octopus1633/article/details/123476310