ADC采集
- CubeMX配置
比如题目要求使用R37来调节输出的占空比(14th省赛),那就需要用到ADC采集。
- 代码
double getADC(ADC_HandleTypeDef *hadc)
{
unsigned int value = 0;
//开启转换ADC并且获取值
HAL_ADC_Start(hadc);
value = HAL_ADC_GetValue(hadc);
//ADC值的转换 3.3V是电压 4096是ADC的精度为12位也就是2^12=4096
return value*3.3/4096;
}
ADC的hal库函数在 stm32g4xx_hal_adc.h
拉到最后。
输入捕获
- CubeMX配置
开启定时器某一个通道的“输入直接比较”模式。
- 代码
比如题目要求“通过PA7来测量输入信号的频率”(14th省赛)。
/* 定时器回调函数*/
uint16_t f = 0;
// 保存TIMx_CCR的值
uint32_t cclValue = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
// // 保存TIMx_CCR的值
// uint32_t cclValue = 0;
// 定时器3时执行该段
if(htim->Instance == TIM3)
{
cclValue = __HAL_TIM_GET_COUNTER(&htim3);
__HAL_TIM_SetCounter(&htim3, 0);
f = 1000000 / cclValue;
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2); // 这里使用中断,而非轮询方法
}
}
- 定时功能
插播一下,比如题目要求“保持时间超过2s的输入信号才纳入统计”,那需要定时器来实现2s定时。
使用刚才那个定时器3,把它的内部时钟源打开。
在stm32g4xx_it.c 中找到 tim3的句柄。
void TIM3_IRQHandler(void)
{
/* USER CODE BEGIN TIM3_IRQn 0 */
/* USER CODE END TIM3_IRQn 0 */
HAL_TIM_IRQHandler(&htim3);
/* USER CODE BEGIN TIM3_IRQn 1 */
if(sysCount[2] < 2000) // 既然tim3已经配置好了是1Hz,那就计数2k次来表示2s
sysCount[2]++;
/* USER CODE END TIM3_IRQn 1 */
}
eeprom读写(IIC通信)
EEPROM可以简单理解为是一个掉电不丢失的存储模块,相对于g431,它是一个从机,所以呢,需要用软件IIC来进行主从机的通信。
- CubeMX配置
先将官方资源包里的“i2c - hal.c”和“i2c - hal.h”复制到我们的工程目录下,可以看到里面已经对 PB6 和PB7 做好了初始化,那就将PB6、PB7配置为输出(相当于IIC通信中的SCL和SDA线)。
另外,题目要求“电压参数写入E2PROM内部地址0,频率参数写入E2PROM内部地址1”(13th国赛),根据E2PROM手册对每一位对应解读,其bit0为R/W,bit3-1为从机地址。 - 代码
/***************************************
* 函数功能:读取eeprom相应位置的值
* 函数参数:unsigned char ucAddr:读取的地址
* 函数返回值:ucRes:读取到的值
***************************************/
unsigned char readEepromByBit(unsigned char ucAddr)
{
unsigned char ucRes = 0;
//发送起始信号
I2CStart();
//发送设备地址
I2CSendByte(0xa0);
//等待应答
I2CWaitAck();
//发送读取地址
I2CSendByte(ucAddr);
//等待应答
I2CWaitAck();
//发送停止信号
I2CStop();
//发送起始信号
I2CStart();
//发送读取数据命令
I2CSendByte(0xa1);
//等待应答
I2CWaitAck();
//接收数据
ucRes = I2CReceiveByte();
//发送应答
I2CSendNotAck();
//发送停止信号
I2CStop();
return ucRes;
}
/***************************************
* 函数功能:向eeprom对应地址写入数据
* 函数参数:unsigned char ucAddr:写入的地址
* unsigned char ucData:写入的数据
* 函数返回值:无
***************************************/
void writeEepromByBit(unsigned char ucAddr,unsigned char ucData)
{
//发送起始信号
I2CStart();
//发送设备地址
I2CSendByte(0xa0);
//等待应答
I2CWaitAck();
//发送写入地址
I2CSendByte(ucAddr);
//发送应答
I2CSendAck();
//发送写入数据
I2CSendByte(ucData);
//等待应答
I2CWaitAck();
//发送停止信号
I2CStop();
}
uart串口通信
题目要求“使用USB转串口功能完成查询功能:PC端通过串口调试助手向设备发送字符‘X’返回当前频率参数,串口通信波特率设置9600”(13th国赛)。除了要安装串口调试助手,我们还要在CubeMX中打开任意一个USART。
- CubeMX配置
使用USART1,将Mode设置为异步,下面的参数设置将波特率改为题目要求的9600。
NVIC Settings 打开串口接收中断。 - 代码
(1)串口发送
/* 串口发送函数原型 */
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
/* 举个使用例子 */
char usartTemp[10];
sprintf(usartTemp, "X:%d\r\n", freq);
HAL_UART_Transmit(&huart1,(uint8_t*)usartTemp,sizeof(char)*strlen(usartTemp),10); //第二个参数强制转换uint8_t*
/* 这样串口助手就可以收到G431发送过来的信息了,显示在接收区。 */
(2)串口接收
/* 串口接收中断函数原型 */
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
/* 使用实例 */
/* 初始化打开接收中断*/
HAL_UART_Receive_IT(&huart1,(uint8_t *)&_ucRxbuff,sizeof(_ucRxbuff));
/* 逻辑处理函数 */
void uart_process(void)
{
if(ucRxbuff[0] == 'X')
{
sprintf(usartTemp, "X:%d\r\n", freq);
}
}
/* LCD显示 */
sprintf(displayTemp[1]," X=%d ", freq);
LCD_DisplayStringLine(Line3,(uint8_t*)displayTemp[1]);
按键消抖、长短按
在之前的文章《软硬件对按键消抖》中有提到两种消抖方法:逻辑运算消抖和定时器中断消抖。二者的区别以及优劣势也都做了分析。
这里再附一种状态机思想,可以同时实现按键消抖、长短按以及双击单击(当然,要搭配定时器中断来使用)。
/* .h */
// 按键结果结构体 需要注意的是任意俩者不能够同时为1
struct keys{
//按键单次按下 0-无效 1-按键按下
int flag;
//双击按键的标志 0-无效 1-双次按下
int doubleFlag;
//长按按键的标志 0-无效 1-长按
int longFlag;
};
//按键状态判断的结构体
struct keyState{
//记录按键状态机的状态
unsigned char judgeSate;
//记录按键的状态
unsigned char keyState;
//记录按键是否双击 双击就会计时
unsigned char doubleClickTimerFlag;
//记录按键按下的时间
int keyTime;
//记录按键双击的时间
unsigned char doubleClickTime;
};
/*@copyright 博主:黑心萝卜三条杠
*/
//定义一个按键结构体数组变量 其中包含按键是否按下 是否长按 是否双击
struct keys key[4] = {0,0,0,0};
/****************************************************************************************************
* 函数功能:按键扫描函数 注意此函数放在定时器中断(10ms)中的使用效果最佳 否则双击与长按会出现问题
* 函数参数:无
* 函数返回值:无
*****************************************************************************************************/
void scanKeyUseStructAndTime(void)
{
static struct keyState _key[4];
//获取按键的最新状态
_key[0].keyState = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
_key[1].keyState = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
_key[2].keyState = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
_key[3].keyState = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
//处理按键的最新状态
for(int i=0;i<4;i++){
switch (_key[i].judgeSate){
//按键第一次按下
case 0:
if(_key[i].keyState == 0){
//跳转按键的状态
_key[i].judgeSate=1;
//清空按键时间
_key[i].keyTime=0;
}
break;
//按键第二次按下 两次相隔10ms可以起到消抖作用
case 1:
//按键再次按下 跳转按键状态
if(_key[i].keyState == 0)
_key[i].judgeSate=2;
//上一次按键按下是抖动按下 属于无效状态 应该退回最开始的状态
else
_key[i].judgeSate=0;
break;
//确定按键按下后的处理过程
case 2:
//等待松开过程,且非长按键
if((_key[i].keyState==1) && _key[i].keyTime<30){
//可能双击按键的第一次,进入计时
if(_key[i].doubleClickTimerFlag == 0) {
_key[i].doubleClickTimerFlag = 1;
_key[i].doubleClickTime = 0;
}
//在计时范围内又按了一次
else{
key[i].doubleFlag=1;//双击情况
_key[i].doubleClickTimerFlag = 0;
}
_key[i].judgeSate = 0;
}
//松开且是长按键
else if(_key[i].keyState==1 && _key[i].keyTime>=30)
{
_key[i].judgeSate = 0;
key[i].longFlag = 1;
}
//按下 且为长按键
else
_key[i].keyTime++;
break;
}
//按键单次按下
if(_key[i].doubleClickTimerFlag == 1 && _key[i].doubleClickTime >= 25) {
key[i].flag = 1;
_key[i].doubleClickTimerFlag = 0;
}
//按键双击 双击计时
else if(_key[i].doubleClickTimerFlag == 1){
_key[i].doubleClickTime++;
}
}
}