工程准备工作
选择芯片为STM32G431RBT6
1 LED灯
1)因为控制LCD屏幕的引脚包括了PC8~PC15所以避免与LED冲突才设置了锁存器,PD2置高则·会将PC8~PC15的电平状态通到LED灯右边,反之则锁住。
2),由比赛官方的原理图可以知道 PC8~PC15引脚加锁存器控制引脚PD2控制,并且这八个LED另一端都接到了高电平,所以我们要让LED点亮要让LED灯另一端为低电平用到 HAL_GPIO_WritePin(); 函数控制 PC8~PC15 引脚的高低电平并且将PD2置为高电平使能锁存器,从而把PC8-PC15的电平输入到LED另一边,再通过锁存器保存LED右边的电平信号。
CubeMx配置部分
选中PC8~PC15选择GPIO_Output(只是一个完整的工程,所以其他引脚设置,不必在意)
并且将输出电平默认为高
因此代码部分
void LED_DISPLAY(unisgned int LED_STA)
{
//先将灯全部关闭,再输入要更新(设置)的LED状态
HAL_GPIO_WritePin(GPIOC ,GPIO_PIN_ALL ,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC ,(LED_STA << 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_STA = 0x01 则是LED1灯亮其他灭。
2 LCD
1) 由PC0~PC15 、PB8 、PB5、PB9、PA8控制
CubeMax配置将对应的引脚设置为GPIO-Output就行了然后比赛官方会有LCD的驱动程序
在HAL例程中的inc和src中找到 lcd.c 、lcd.h 、fonts.h三个文件就行了,然后复制到自己的工程
3 按键
一共有四个按键 ,分别接到了 PB0 ,PB2 ,PB3 ,PA0 对应B1 ,B2 ,B3 ,B4
在CubeMX中选中对应引脚设置为GPIO_Input模式,其他不用设置了。
这里建议用定时器中断检测按键是否摁下。
如果按键按下则是低电平。
当然别忘记开启定时器中断了
HAL_TIM_Base_Start_IT(&htimx); 如HAL_TIM_Base_Start_IT(&htim15);
选择一个题目没有用到的定时器
我的预分频 Prescaler选择8000 - 1 ,自动重装载寄存器(Counter Period)选择100-1
用于按键的定时器的中断别忘记勾上了
Keil中
创建一个 处理中断的文件 interrupt.c
注释中消抖处理原理是第一次 key_judge = 0 判断按键是否按下,若按下则为1(这是第一次中断做的)
第二次进入中断已经经过了10ms,然后再继续判断按键状态,若还是按下则认为是按键按下,并是key_short = 1(时钟频率是80MHz)
#include "interrupt.h"
struct key keys[4] = {0 ,0 ,0};
//中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM15)
{
//接收保存按键状态
keys[0].key_sta = HAL_GPIO_ReadPin(GPIOB ,GPIO_PIN_0);
keys[1].key_sta = HAL_GPIO_ReadPin(GPIOB ,GPIO_PIN_1);
keys[2].key_sta = HAL_GPIO_ReadPin(GPIOB ,GPIO_PIN_2);
keys[3].key_sta = HAL_GPIO_ReadPin(GPIOA ,GPIO_PIN_0);
for(int i = 0 ;i < 4 ;i++)
{
switch (keys[i].key_judge)
{
//按键是否按下
case 0:
if(keys[i].key_sta == 0)
{
keys[i].key_judge = 1;
}
break;
//消抖处理,若还是低电平,这判断为按键按下
case 1:
if(keys[i].key_sta == 0)
{
keys[i].key_short = 1;
keys[i].key_judge = 2;
}
else
{
keys[i].key_judge = 0;
}
break;
//若还是低电平,则就key_judge不置0,继续等待松手,避免长按造成多次响应
case 2:
if(keys[i].key_sta == 1)
{
keys[i].key_judge = 0;
}
break;
}
}
}
}
对应头文件interrupt.h
#ifndef __INTERRUPT_H
#define __INTERRUPT_H
#include "main.h"
#include "stdbool.h"
struct key
{
/*根据judge值做出相应的判断
0 :判断按键是否为按下
1 :继续判断按键是否按下,若按下则short = 1
2 :松手检测*/
int key_judge;
/*每次进入中断就读取按键的电平状态*/
bool key_sta;
/*按键短按标志位*/
bool key_short;
};
#endif
这里需要根据需求写代码,由于当时刷的题目并没有要求按键长按和按两下的操作,所有我只写了短按的操作
4 ADC模块
原理图中有两个电位器 分别接到了 PB15(R37)和PB12(R38)
这个最简单选择电位器所接到的引脚选择ADCx_INx,然后在ADCx对应的通道上选择single-ended
keil代码部分
这样就返回了电压值0~3.3
#include "MyADC.h"
double GET_ADC(ADC_HandleTypeDef *pin)
{
unsigned int adc;
//开启adc
HAL_ADC_Start(pin);
//调用函数获取adc值
adc = HAL_ADC_GetValue(pin);
return adc*3.3/4096;
}
5 定时器
5.1 定时器生产PWM波
本例用PA7输出PWM
选中PA7并挑选一个CHx,建议用TIM3_CH2 ,带N的是生成互补的PWM考试基本上用不到
根据所选择的定时器和通道配置模式
时钟源选择internal Clock ,在相应通道上选择PWM generation CH2
pwm波的频率 = (时钟源频率)/(预分频数 * 重装载值)
如果我们要设置 PWM波频率为 1000Hz 则 (预分频数 * 重装载值) = 80M/1k =80000
建议重装载值选择 100 这样比较寄存器值就是占空比(这是在题目要求占空比精度为1及以上的时候,偶尔会有带小数的情况)
这是默认初始比较值,可以根据题目需求设置初始比较寄存器值
然后在主函数初始化时开启PWM
HAL_TIM_PWM_Start(&htim3 ,TIM_CHANNEL_2);
5.2 定时器输入捕获
先选定一个定时器通道作为输入捕获的通道,拿十四届蓝桥杯省赛为例,要求PA7引脚作为输入捕获的通道,用于捕获来自PA1的PWM波形.
然后时钟源选择内部时钟,将对应通道选为 input capture direct mode .预分频选小一点,自动重装载值选择默认的最大值
输入捕获通道就选默认的上升沿触发,输入捕获原理是当捕获通道检测到上升沿时,开始计数,当遇到第二个上升沿时触发捕获的中断,然后我们在中断函数里面获取定时器计数的值,然后将定时器时钟频率除以计数值就是所测量的PWM的频率。
代码如下
只需将frq外部应用就行了
uint16_t tim_counter = 0 ,frq = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
//确认定时器
if(htim -> Instance == TIM3)
{
//获取计数值
tim_counter = HAL_TIM_ReadCapturedValue(htim ,TIM_CHANNEL_2);、
//将计数值清零
__HAL_TIM_SetCounter(htim ,0);
//求出频率
frq = (80000000/80)/tim_counter;
//继续开启捕获中断
HAL_TIM_IC_Start(htim ,TIM_CHANNEL_2);
}
}
在main.c初始化中使用HAL_TIM_IC_Start_IT(&htim3 ,TIM_CHANNEL_2);开启捕获中断
6 USART串口
在connectivity中找到对应的串口
这里用USART1
选择Asynchronous ,波特率根据需求选择(题目基本上是9600)
并且开启中断
串口发送
主要使用HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
可以参考我写的方式
char text[30];
sprintf(text ,"your string");
HAL_UART_Transmit(&huart1 ,(uint8_t *)text ,strlen(text) ,50);
串口接收
和按键一样,写在interrupt.c
这里是值接收一个字符的写法
其中 HAL_UART_Receive_IT(&huart1 ,&rx_data ,1);可以写在主函数前作为开启串口中断的函数
uint8_t rx_flag = 0;
uint8_t rx_data;
char rx_da;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(rx_flag == 0)
{
HAL_UART_Receive_IT(&huart1 ,&rx_data ,1);
rx_da = rx_data;
rx_flag = 1;
}
}