目录
前言:
2.用于定时器中断溢出函数的定时器启用函数(没错,在CubeMX设定了之后还要在函数里初始化)
5.负责PWM波的定时器的赋值使用(分HAL库操作及汇编操作)
前言:
此版是本人使用CUbeMx的创建库函数的练手之作,仅仅涉及到了adc,pwm,频率,LED,LCD,回访记录,按键等操作。
题目:14届蓝桥杯嵌入式国赛
硬件框图:
总体框架较为简单,这个题目考到了DS18B20、单路PWM捕获、ADC采集、PWM生成等模块。细节上东西比较多,处理记录波形和产生波形的时序、温度的温定读取、双按键长按、时间准确控制等,很多都可以在哔站的网站看速成——B站推荐速成总结。
14届国赛省赛都在PWM生成上作文章,控制好变化时间还是可以很丝滑的,这题目就是记录电压与PWM波,频率的时候有点怪,其他都非常简单!
由于一些设备原因,本人没有做DS18B20的温度读取(写了一个壳子),其他均符合题目要求。
1.使用CUbeMX进行基础初始化配置
(1)选则芯片与基本初始化
这个操作不多赘述,想了解的可以看看这个博主的博客。嵌入式开发--CubeMX使用入门教程-CSDN博客,值得注意的是我们所要用到芯片主频是80,这个帧率配置过程B站的视频教程里面有。
(2)LED配置
在STM32G431RBTx的芯片图上,LED1~LED8对应的是PC8~15
所以在CUbeMX中,我们要对PC8~15进行配置,
(3)按键配置
同样的,在STM32G431RBTx的芯片图上,B1~B4对应的是PB0~2和PA0。
所以要将所用的引脚配置成输入(GPIO_Input)模式,并设置成上拉输入。
(4)定时器和PWM以及频率
题目要求用PA1进行频率采集,同PA7进行频率输出,我看了一下使用PA1采集PWM就要使用TIM2这个定时器的CH2通道,而PA7用定时器17就可以了。所以定时器的开启也是要注意看题目要求,不要占用定时器。
这个是我配置定时器的设定,通道一采集上升沿,通道二采集下降沿
我们配置的时候修改PSC与Counter,他们遵循公式
工作频率=外部总线频率/(PSC+1)
定时频率=定时器工作频率/(counter+1)=外部总线频率/(PSC+1)*(counter+1)
由于我设置的外部总线频率为80MHZ(80*10^6HZ),所以可以计算得我设置的TIM2频率为100HZ,0.01秒触发一次中断,注意要设置中段哦!一定要勾选Enabled!
接着是PA7的PWM输出定时器TIM17,这个定时器像我这么设置就可以了,注意输出PWM波的话就不要设置中断,会出问题的!
接下来是其他TIM定时器,这是我代码中所用到的所有定时器。
(5)ADC电压检测
ADC的话直接在Analog里面去选择ADC1,然后再IN11里像我一样配置就好了,其他直接默认就行了。
2.各外设使用代码。
(1)LED灯
#include "led.h"
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);
}这个是本次使用的点亮LED灯的代码,但因为不知道什么原因,只能亮灭0~2的LED灯,所以我是把它当LED初始化全灭代码。
(2)按键代码(长短按都包含)
struct keys key[4]={0,0,0};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance ==TIM4){
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){
case 0:
{
if(key[i].key_sta==0) key[i].judge_sta=1;
key[i].key_time= 0;
}
break;
case 1:
{
if(key[i].key_sta==0)
{
key[i].judge_sta=2;
}else {
key[i].judge_sta=0;
// key[i].key_time =0;
}
}
break;
case 2:
{
if(key[i].key_sta==1)
{
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;
}
}
}}
struct keys
{
uchar judge_sta;
bool key_sta;
bool single_flag;
bool long_flag;
uint key_time;
};
按键用swicth进行了消抖操作,并把按键函数设里在了定时器溢出函数里面,只要TIM定时器溢出中断触发,就会检测查看有没有按键被按下,由于函数溢出时间非常快,处理中断时间非常短,可以看成按键一直在检测,且由于是中断,所以不耽误主进程。
(3)定时器
定时器有基本,通用,高级定时器,我们主要了解如何去用这TIM,CUbeMX去配置初始化就不用多说,前文有描述,这里主要是如何在代码中使用TIM。
1.定时器中断溢出函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance ==TIM4){//通过这个代码,可以判断是否是想要的TIM定时器溢出中断
}
}
2.用于定时器中断溢出函数的定时器启用函数(没错,在CubeMX设定了之后还要在函数里初始化)
HAL_TIM_Base_Start_IT(&htim4);
3.输入捕获回调函数
HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance ==TIM2){
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_2)
//这里是查看所用的通道有没有信号,毕竟捕获是数据输入,如果没有数据输入就不触发。
{}
}
}
3.用于输入捕获回调函数的定时器启用函数
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_2);
4. 负责PWM波的定时器的开启
HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);//负责PWM波的定时器开启代码
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,pa7_duty);
5.负责PWM波的定时器的赋值使用(分HAL库操作及汇编操作)
HAL库版
set_pwm_param(TIM_HandleTypeDef htim, uint32_t Channel, uint32_t freq, uint16_t duty);
该函数可直接调整占空比和频率
汇编语言版
TIM17->ARR=1e6/freq_r[cnt_index]*FP-1;//直接调TIM17的ARR
TIM17->CCR1=duty_r[cnt_index]*(1e6/freq_r[cnt_index]*FP)/100.0f;//直接调TIM17的RCC
(4)ADC转移(可测电压)直接使用型代码。
#include "dadc.h"
double getADC(ADC_HandleTypeDef *pin)
{
uint adc;
HAL_ADC_Start(pin);
adc =HAL_ADC_GetValue(pin);
return adc*3.3/4096;
}这里的4096是2^12的值,通过这一串带码,实现adc的读取
使用方式:getADC(&hadc1);//这里的&hadc1通过CubeMX开启
(5)LCD显示屏
LCD显示屏蓝桥杯官方已经公布了.c与.h文件,也可以去我的bsp文件里面找,可以直接拿来用。
我这里主要是讲LCD的代码初始化,以及如何使用。
LCD_Init();//LCD初始化
LCD_Clear(Black);//黑色清屏
LCD_SetBackColor(Black);//黑色背景颜色
LCD_SetTextColor(White);//白色字体
char text[30];//需要数组转存
sprintf(text," change:FH ");
LCD_DisplayStringLine(Line8, (uint8_t *)text);操作显示在多少行。
3.题目逻辑代码实现
关于逻辑代码这一块,非常感谢GHHHHHH!-CSDN博客大佬,我看了他的处理可谓是茅塞顿开,用一个数加加去进行定时处理,非常给力,在处理下图板块的时候,通过一个int数加加,进行判断,存储,再输出的时候,继续进行数的加加,非常巧妙。
下面是我看了大佬的博客之后的函数逻辑处理
if(flag_Record==1){//记录标志位
if(++cnt_Record>10*TT)
{
cnt_Record=0;
flag_Record=0;//控制LED灯的信号
cnt_index=0;
flag_jilu=1;//释放频率与占空比的通过信号
return ;
}
if(cnt_Record%10==0)//0.1s
{
freq_r[cnt_index]=frq1;//记录频率
duty_r[cnt_index]=duty1;//记录占空比
if(getADC(&hadc1)>=VP)//记录频率电压
duty_Adc[cnt_index]=90.0f/(3.3f-VP)*(getADC(&hadc1)-3.3f)+100.0f;
else
duty_Adc[cnt_index]=10;
cnt_index++;
}
if(++led_cnt==10){
led_cnt=0;
LED_Disp(0x01);
}else LED_Disp(0x00);
}
4.总实现照片
界面一和界面二
界面三和界面四
总结
作为一个从标准库转HAL库的程序猿,确实是觉得HAL库比标准库方便很多,主要是有CubeMx这个工具的存在,同时,现在一些芯片只能识别HAL库函数,而不能识别标准库,这更体现了HAl库的优势。但这并不意味着标准库就不行,且不说网上资料还是标准库的多,而且标准库可以更好的帮我们了解单片机的原理,打好基础。
希望我的这篇文章能帮助到大家!
实现代码
通过百度网盘分享的文件:14jie4.zip
链接:https://pan.baidu.com/s/1tzceKMszDdTQTxK8tLkfUA
提取码:fwwz