相关说明:
开发板:CT117E-M4(STM32G431RB 蓝桥杯嵌入式比赛板)
开发环境: CubeMX+Keil5
涉及题目:第十三届蓝桥杯嵌入式模拟题
题目难点:根据输入的PWM,实时更新输出PWM,R37电压值为0V时如何输出持续的低电平,电压为3.3V时如何输出持续的高电平。
CubeMX配置、主要函数代码及说明:
4.ADC(默认配置即可):
5.TIM2(输入捕获,检测输入信号的频率):
6.TIM3(PWM输出):
7.NVIC(输入捕获中断配置):
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输出
#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
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)
尽量将按键实现的功能封装为独立的函数,降低函数耦合度。
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;
}
}
}
}
用到两个函数对数据进行更改:
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显示更新
}
}
}
}
共有两个函数:
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*)" ");
}
}
检测输入信号频率分为五步:
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;//计数值清零
}
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
}
}
注意起始时要将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 */
}
此处用的是另一块开发板为该板提供信号,频率为1KHz;并调节R37使其电压为1.65V,预期占空比为1.65/3.3=50%。
默认R值为4,作用为将输入信号进行分频处理;预期输出频率为1K/4=250Hz。
这里直接用示波器检测输出PWM,频率为250Hz,占空比为50%,符合预期。
以上就是全部内容,如有错误请批评指正。