本文章多以图片代替文字,方便大家直接在软件上实践。
我们先看题目
- 先分析题目: 一共两个LCD界面,外设基本上全部配置,II2读写EEPROM,同时需要用到串口收发指令
数码管和AO1/AO2引脚都在拓展板上。 看似可能比较难,我们一步步往下做。
先打开cubemx按照原理图配置引脚
原理图如下:
初始化配置
基本所有引脚都需要配置,为了快速配置 我们直接按照原理图把所有引脚激活
打开System Core →GPIO 简单的给常用引脚进行一个改名,这里我改了按键的名称和PD2锁存器启用/复位引脚
同样是System Core 打开RCC 设置HES为系统时钟源,这里如果不设置,MCU是跑不到最大频率的
打开顶端 Clock Configuration设置HCLK为170MHZ 回车 ,其他分频器会自动设置
此时G431的频率就是为最高运行频率了,设置完时钟后,可以开始设置TIM计数器。
再读一遍题:
我们一共需要五种不同的定时(注:占空比测量需要独占一个计数器,刷新率可以不管,运行速度够快
这里可以进一步优化 将一个0.2s的定时器做成同时满足1s和2s的,后面程序会讲。
所以,开始设置TIM计数器吧!
首先配置最重要的占空比测量
查询拓展板原理图 PWM2测量引脚为PA7
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210401200405904.png
配置好后 打开左侧列表 Timers 进行TIM3的详细设置
由于只有单引脚输入,所以只能用计数器内部的滤波电路进行一个波形复制,并二次测量波形
具体设置如下:
TIM3CH2的信号映射到CH1上, CH2下降沿触发,CH1上升沿触发,这样可以做到单引脚的输入捕获
TIM3定时器的分频值我设为频率170 000 000 /预分频值 170-1 /ARR自动复位值 50 000 = 20HZ 也就是最低能测出20*3 =60HZ的方波的频率和占空比
然后打开TIM3的NVIC中断,PWM捕捉肯定需要中断的 ,同时配置TIM1更新中断
占空比测量配置好后,我们继续配置TIM,现在需要配置定时0.2s刷新以及其他用于刷新的定时器
频率170 000 000 /预分频值 17000-1 /ARR自动复位值 2000 = 5HZ =0.2s中断一次
配置串口并打开中断
由于需要收发指令,这里要给串口配置为DMA传输
配置AO1/AO2的ADC测量
这里需要打开ADC的循环扫描模式,启用软件触发模式
设置通道顺序
此时以及配置完所有引脚了(注:II2C使用的GPIO模拟信号传输数据,不用单独设置II2C。LCD也是GPIO模拟FSMC)
点击
配置工程
注意编译器和版本的选择,选择.c和.h文件独立生成文件夹
点击右上角
生成代码
Open Project自动使用所对应编译器打开工程
打开工程第一步,先编译一次,然后打开stm32g4xx_it.c配置中断函数,删除自带的USART1中断函数,后面需要自己写一个新的,使用空闲中断DMA传输。
新的中断函数自行添加在main.c文件里 要放在BEGIN和END之间,序号随变,如果不在BEGIN和END之间,使用cubemx重新生成文件时会被清空内容。
1.USART:
串口中断编写
void USART1_IRQHandler(void)
{
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除标记
HAL_UART_DMAStop(&huart1); //停止接受 开始处理
UART_RX_STA = BUFF_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx);
RXbuf[UART_RX_STA] = 0;
// UART_RX_STA |= 0x8000;
// if(UART_RX_STA & 0x8000)
// {
if(strcmp((char *)RXbuf,(char *)RX_ST) == 0) //指令解析
{
memset(UARTTEMP,0,30);
sprintf(UARTTEMP,"$%.2f\r\n",DS18B20_TEMP);
HAL_UART_Transmit(&huart1,(uint8_t *)UARTTEMP,sizeof(UARTTEMP),100 );
}
else if(strcmp((char *)RXbuf,(char *)RX_PARA) == 0)
{
memset(UARTTEMP,0,30);
sprintf(UARTTEMP,"#%d,AO%d\r\n",Tmm,Xmm);
HAL_UART_Transmit(&huart1,(uint8_t *)UARTTEMP,sizeof(UARTTEMP),100);
}
// UART_RX_STA = 0; // 清除标记
// }
HAL_UART_Receive_DMA(&huart1, RXbuf, BUFF_SIZE ); // 重新启动DMA接收
}
}
以下启动函数在main函数内调用
HAL_UART_Receive_DMA(&huart1,RXbuf,BUFF_SIZE); //打开DMA传输
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除中断标记
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //打开UART1空闲中断
串口已经配置好了 还有对应的变量需要设置
static char BUFF_SIZE = 50; //接收长度
static uint8_t RXbuf[50]; //接收缓存
static uint16_t UART_RX_STA = 0; // 第15bit表示一帧数据接收完成,第14~0位表示接收到的数据量
static char UARTTEMP[30];
- USART常用函数:
HAL_UART_Transmit(&huart*,(uint8_t *)字符数组,sizeof(字符数组),等待时间);
发送数据HAL_UART_DMAStop(&huart*)
停止某个串口DMA传输 处理数据时一定要关闭防止数据被挤掉HAL_UART_Receive_DMA(&huart*, 字符数组, 接收大小 );
重新打开DMA传输并指定写入内存
- 串口定时报警程序
static void UART_TempSend()
{
double linshi =0;
linshi = PWM_Zhankongbi*3.3; //根据题目要求:VIN>k×3.3 时,以 1s 为间隔自动上报当前的温度数据
if(ADCRead_Num(1)>linshi || ADCRead_Num(2)> linshi)
{
if(led1!=2 ){ led1 =1;}
if(time4_1S > 4) //定时达到要求 重新上报
{
time4_1S = 0;
memset(UARTTEMP,0,30);
sprintf(UARTTEMP,"$%.2f\r\n",DS18B20_TEMP);
HAL_UART_Transmit(&huart1,(uint8_t *)UARTTEMP,sizeof(UARTTEMP),100);
}
}
else if(led1!=0 ){ led1=5;}
}
static void UART_TN()
{
if( DS18B20_TEMP> Tmm )
{
HAL_TIM_Base_Start_IT(&htim1);
}
else if (DS18B20_TEMP < Tmm )
{
HAL_TIM_Base_Stop_IT(&htim1);
if (tim1_LD8 != 0)tim1_LD8 = 3;
}
}
2.添加重要驱动
我们需要编写温度传感器和II2的驱动文件添加进工程(不知道比赛会不会给,自己写一个也好)
DS18B20驱动
II2C驱动
数码管驱动
把文件添加进工程,顺手添加lcd.c
LCD驱动
3.II2C:
在main.c添加自定义的II2C读写函数(uint8_t)类型
uint16_t x24c02_read(uint8_t address) //I2C读
{
uint16_t val;
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(address);
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
val = I2CReceiveByte();
I2CWaitAck();
I2CStop();
return(val);
}
void x24c02_write(uint8_t address,uint16_t info) //I2C写
{
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(address);
I2CWaitAck();
I2CSendByte(info);
I2CWaitAck();
I2CStop();
}
读写案例:
//从0x00的位置读取一个字
xxx= x24c02_read(0x00);
//向0x00写入一个字
x24c02_write(0x00,(uint8_t) xxx);
4.DS18B20:
温度传感器读取案例:
变量 = ds18b20_read(); //注意这里读取出来的是十六位的数字 要除以16再取整使用
DS18B20_TEMP = ((int)((ds18b20_read()/16.0)*100)) ;
DS18B20_TEMP = DS18B20_TEMP/100;
5.数码管:
直接调用驱动内的函数就好
对应的表如下
// 0 1 2 3 4 5 6 7 8 9 A B C D E F 关
uint8_t Seg7[17] = { 0x3f,0x06,0x5b,0x4f, 0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c, 0x39,0x4f,0x79,0x78,0x00};
SEG_DisplayValue(uint8_t Bit1, uint8_t Bit2, uint8_t Bit3);
- 例:
在这里插入代码片
6.ADC:
接下来编写ADC顺序读取以及按序号取数据的函数,ADC在调用时会读取第一个通道的值,第二次调用读取第二个通道的值。
static double ADCRead(ADC_HandleTypeDef *hadcx)
{
//开启ADC1
HAL_ADC_Start(&hadc2);
//等待ADC转换完成,超时为100ms
HAL_ADC_PollForConversion(&hadc2,100);
//判断ADC是否转换成功
if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc2),HAL_ADC_STATE_REG_EOC))
{
//读取值
HAL_ADC_GetValue(&hadc2);
}
}
static double ADCRead_Num(uint8_t num)
{
ADC_Value[1]= ADCRead(&hadc2); //读取第一个通道的值
HAL_Delay(5);
ADC_Value[2]= ADCRead(&hadc2);//读取第二个通道的值
return ADC_Value[num]; //返回序号的值
}
这时题目的常用函数基本上都写好了,开始编写逻辑类的函数,按键处理以及页面显示,TIM中断类,定时等
7.LED:
LED与LCD复用,所以采用了一个锁存器来储存对LED的控制信号
在启用LE引脚时,可往锁存器写入八位二进制数据,0对应开1关。写满后关闭LE引脚,锁存器可以保持关闭前的八位状态。
- 运行LCD之前先关闭所有LCD,防止LED乱闪
static void LEDClear() //关闭所有LED
{
HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin,GPIO_PIN_SET); //锁存器开
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11|GPIO_PIN_14|GPIO_PIN_10|GPIO_PIN_15|GPIO_PIN_9|GPIO_PIN_8,GPIO_PIN_SET); //LED全部熄灭
HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin,GPIO_PIN_RESET);//锁存器关
}
- 闪灯计时
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) //tim1中断回调
{
if(htim->Instance == TIM1)
{
if (tim1_LD8 == 0) { tim1_LD8 = 1;}
else if(tim1_LD8 == 2) { tim1_LD8 = 4;}
}
}
所有与LED闪烁有关的函数,整体思路如下
- 计时1次后 判断需要闪哪个灯,然后进行一次锁存器复写,完成标记。
- 计时2次 翻转状态。
- 不需要闪灯时,将状态写为不在判定区域的任意数,要用时再写回来即可。
static void Led_bulle()
{
if(tim1_LD8 == 1)
{
HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin,GPIO_PIN_SET); //锁存器开
if(led1 ==2 )
{ HAL_GPIO_WritePin(GPIOC,GPIO_PIN_15|GPIO_PIN_8,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11|GPIO_PIN_14|GPIO_PIN_10|GPIO_PIN_9,GPIO_PIN_SET);
}
else
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_15|GPIO_PIN_8,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11|GPIO_PIN_14|GPIO_PIN_10|GPIO_PIN_9|GPIO_PIN_8,GPIO_PIN_SET);
}
HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin,GPIO_PIN_RESET);//锁存器关
tim1_LD8 = 2;
}
else if (tim1_LD8 == 4)
{
HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin,GPIO_PIN_SET); //锁存器开
if(led1 ==2)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11|GPIO_PIN_14|GPIO_PIN_10|GPIO_PIN_15|GPIO_PIN_9,GPIO_PIN_SET);
}
else{ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11|GPIO_PIN_14|GPIO_PIN_10|GPIO_PIN_15|GPIO_PIN_9|GPIO_PIN_8,GPIO_PIN_SET); }
HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin,GPIO_PIN_RESET);//锁存器关
tim1_LD8 = 0;
}
else if (tim1_LD8 == 3)
{
tim1_LD8 = 0;
HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin,GPIO_PIN_SET); //锁存器开
if(led1 ==2)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11|GPIO_PIN_14|GPIO_PIN_10|GPIO_PIN_15|GPIO_PIN_9,GPIO_PIN_SET);
}
else{ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11|GPIO_PIN_14|GPIO_PIN_10|GPIO_PIN_15|GPIO_PIN_9|GPIO_PIN_8,GPIO_PIN_SET); }
HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin,GPIO_PIN_RESET);//锁存器关
}
if(led1 ==1 )
{
HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin,GPIO_PIN_SET); //锁存器开
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11|GPIO_PIN_14|GPIO_PIN_10|GPIO_PIN_15|GPIO_PIN_9,GPIO_PIN_SET);
HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin,GPIO_PIN_RESET);//锁存器关
led1 = 2;
}
else if (led1 == 5)
{
led1 = 0;
HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin,GPIO_PIN_SET); //锁存器开
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11|GPIO_PIN_14|GPIO_PIN_10|GPIO_PIN_15|GPIO_PIN_9|GPIO_PIN_8,GPIO_PIN_SET);
HAL_GPIO_WritePin(LEDLOCK_GPIO_Port, LEDLOCK_Pin,GPIO_PIN_RESET);//锁存器关
}
}
8.PWM
先看PWM测量 一共要检测2个上升沿,1个下降沿
- 在检测到第一个上升沿时,清空计数值,然后等待一个下降沿
- 下降沿到来时,记录下tim的时间,清空计数值,等待第二个上升沿
- 第二个上升沿来时,记录tim的时间,激活处理数据的函数,计算出占空比
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) //记录等待脉冲中的溢出次数
{
if(htim->Instance == TIM3)
{
if(PWMON == 1) //脉冲检测1阶段
{
PWM_YICHU1++;
}
else if (PWMON == 2) //脉冲检测2阶段
{
PWM_YICHU2++;
}
}
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) // 脉冲捕捉
{
if(htim ->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
if(PWMON == 1 && Setting == 1 )
{
PWMDW1 = __HAL_TIM_GetCounter(&htim3);
__HAL_TIM_SetCounter(&htim3,0);
PWMON = 2;
}
}
else if(htim ->Channel == HAL_TIM_ACTIVE_CHANNEL_2 )
{
if(PWMON == 0)
{
__HAL_TIM_SetCounter(&htim3,0);
PWMON = 1;
}
else if(PWMON == 2)
{
PWMUP2 = __HAL_TIM_GetCounter(&htim3);
__HAL_TIM_SetCounter(&htim3,0);
PWMON = 3;
}
}
}
占空比计算公式 D=PD1/(PD1*频率/PU2) x 100%
频率在初始化tim时就决定了 170MHZ/预分频值170
PU1在我这里默认为0 作为起点
if (PWMON == 3 ) //重新计算占空比
{
PWMDW1 =1000000/ (PWMDW1+50000*PWM_YICHU1); //ARR值为50000,所以真实计数值为 计数值+50000*溢出次数
//PWMUP2 =1000000/ (PWMUP2+50000*PWM_YICHU2);
PWM_Zhankongbi =(100* (PWMDW1 / (PWMDW1+1000000/(PWMUP2+50000*PWM_YICHU2))))+0.5 ; //占空比计算
sprintf(LCDTEMP," PWM2:%d%% ",PWM_Zhankongbi);
LCD_DisplayStringLine(Line5,(uint8_t *)LCDTEMP); //显示占空比
PWMON = 0;
PWM_YICHU1 = 0;
PWM_YICHU2 = 0;
}
9.按键检测:
我并没有采用按键中断的方法,这会干扰PWM计算和LCD的写入,直接用扫描GPIO的方式获得按键值
读题
设置储存按键状态和切换模式的状态值,切换页面时先刷新LCD,防止字符显示错误。
在检测中设上一定的延时,防止多次触发。
static void yemian_JM()
{
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET ) //读取按键状态
{
Setting++;
HAL_TIM_IC_Stop_IT(&htim3, TIM_CHANNEL_1);
if (Setting == 3){ Setting = 1;HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);}
LCD_Clear(White);
while(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET);
}
if(Setting == 2)
{
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)==GPIO_PIN_RESET )//读取按键状态
{
SetOb++;
HAL_Delay(200);
if (SetOb == 3){ SetOb =1;}
}
if(SetOb == 1)
{
if(HAL_GPIO_ReadPin(KEY3_GPIO_Port,KEY3_Pin)==GPIO_PIN_RESET )//读取按键状态
{
Tmm++;
HAL_Delay(500);
if(HAL_GPIO_ReadPin(KEY3_GPIO_Port,KEY3_Pin)==GPIO_PIN_RESET) { Tmm+=9;}
}
else if(HAL_GPIO_ReadPin(KEY4_GPIO_Port,KEY4_Pin)==GPIO_PIN_RESET )//读取按键状态
{
Tmm--;
HAL_Delay(500);
if(HAL_GPIO_ReadPin(KEY4_GPIO_Port,KEY4_Pin)==GPIO_PIN_RESET) { Tmm-=9;}
}
}
else if(SetOb == 2)
{
if(HAL_GPIO_ReadPin(KEY3_GPIO_Port,KEY3_Pin)==RESET )//读取按键状态
{
Xmm++;
HAL_Delay(200);
}
else if(HAL_GPIO_ReadPin(KEY4_GPIO_Port,KEY4_Pin)==RESET )//读取按键状态
{
Xmm++;
HAL_Delay(200);
}
if (Xmm > 2){ Xmm = 1;} //超限设定
else if (Xmm == 0){ Xmm = 1;}
if (Tmm >40){ Tmm = 20;}
else if (Tmm == 0 ){ Tmm = 40;}
}
}
}
然后写LCD页面显示的函数
10.LCD:
LCD_DisplayStringLine(Line1,(uint8_t *)LCDTEMP);
显示一排字符LCD_Init();
初始化LCD_Clear(颜色);
清空并以特定颜色填充屏幕LCD_SetBackColor(颜色);
设置背景颜色LCD_SetTextColor(Blue);
设置字符颜色
static void LCDLive()
{
if(Setting == 1) //主页面
{
LCD_DisplayStringLine(Line0,(uint8_t *)" Main ");
sprintf(LCDTEMP," AO1:%.2lfV ",ADCRead_Num(1));
LCD_DisplayStringLine(Line1,(uint8_t *)LCDTEMP);
sprintf(LCDTEMP," AO2:%.2lfV ",ADCRead_Num(2));
LCD_DisplayStringLine(Line3,(uint8_t *)LCDTEMP);
sprintf(LCDTEMP," TEMP:%.2lf C ",DS18B20_TEMP);
LCD_DisplayStringLine(Line7,(uint8_t *)LCDTEMP);
sprintf(LCDTEMP," N:%d ",N_Num);
LCD_DisplayStringLine(Line9,(uint8_t *)LCDTEMP);
if (PWMON == 3 ) //重新计算占空比
{
PWMDW1 =1000000/ (PWMDW1+50000*PWM_YICHU1);
//PWMUP2 =1000000/ (PWMUP2+50000*PWM_YICHU2);
PWM_Zhankongbi =(100* (PWMDW1 / (PWMDW1+1000000/ (PWMUP2+50000*PWM_YICHU2))))+0.5 ;
sprintf(LCDTEMP," PWM2:%d%% ",PWM_Zhankongbi);
LCD_DisplayStringLine(Line5,(uint8_t *)LCDTEMP);
PWMON = 0;
PWM_YICHU1 = 0;
PWM_YICHU2 = 0;
}
}
else if(Setting == 2) //设置模式
{
if(SetOb == 1)
{
LCD_DisplayStringLine(Line0,(uint8_t *)" Para ");
sprintf(LCDTEMP," T: %d ",Tmm);
LCD_SetTextColor(Red);
LCD_DisplayStringLine(Line2,(uint8_t *)LCDTEMP);
LCD_SetTextColor(Blue);
sprintf(LCDTEMP," X: AO%d ",Xmm);
LCD_DisplayStringLine(Line5,(uint8_t *)LCDTEMP);
}
else if(SetOb == 2)
{
LCD_DisplayStringLine(Line0,(uint8_t *)" Para ");
sprintf(LCDTEMP," T: %d ",Tmm);
LCD_DisplayStringLine(Line2,(uint8_t *)LCDTEMP);
sprintf(LCDTEMP," X: AO%d ",Xmm);
LCD_SetTextColor(Red);
LCD_DisplayStringLine(Line5,(uint8_t *)LCDTEMP);
LCD_SetTextColor(Blue);
}
}
}
11.写完所有函数
整合工程 在主函数循环里调用就行了。
while (1)
{
DS18B20_TEMP = ((int)((ds18b20_read()/16.0)*100)) ;
DS18B20_TEMP = DS18B20_TEMP/100;
if(IIC_huancun != DS18B20_TEMP) //判断是否需要从EEPROM写数据
{
N_Num++;
IIC_huancun = DS18B20_TEMP;
x24c02_write(0x00,N_Num);
}
yemian_JM();
LCDLive();
UART_TempSend();
UART_TN();
Led_bulle();
}
最后
本人也是今年参加蓝桥杯,写这个教程就当复习了,祝大家得个好成绩。
完整工程