文章目录
前言
相关说明:
开发板:CT117E-M4(STM32G431RB 蓝桥杯嵌入式比赛板)
开发环境: CubeMX+Keil5
涉及题目:第六届蓝桥杯嵌入式省赛
题目难点:RTC秒中断,串口接收判定,在LCD高刷新率下,保证LED以固定频率闪烁。
(重新修改了MX部分配置、代码,代码行数有所减少,函数耦合度降低)
CubeMX配置、主要函数代码及说明:
一、CubeMX配置(第六届蓝桥杯省赛完整版)
1.使能外部高速时钟:
2.配置时钟树:
3.GPIO:
4.ADC(采集R37的电压值):
5.RTC(秒中断):
6.TIM6(在串口接收最后一字节数据5us后进入中断服务函数,函数中判定接收数据长度是否正确):
7.TIM7(LED灯每0.2秒闪烁一次,即每0.1s状态翻转一次):
8.I2C:
9.USART(注意题目要求为9600,这里是选择了115200):
10.NVIC(中断配置):
二、代码相关定义、声明
1.函数声明
main.c
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc); //RTC闹钟中断 时间显示(每秒) 上报电压值
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);//定时器中断函数 TIM6(判定数据长度) TIM7(LED闪烁)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //串口接收中断函数
void LCD_Show_Now_Time(void); //LCD初始化显示
void LCD_Refresh(uint8_t page); //LCD更新显示
void REC_Check(void); //接收数据检测
void LED_Change(void); //LED状态改变
gpio.h
void KEY_Scan(void);//按键扫描
void LED_AllClose(uint8_t *LED_Close);//LED更新显示
adc.h
double ADC_GetValue(void);//获取R37电压值
2.变量定义
main.c
double V; //R37电压值
double K; //K值
uint8_t K_addr=0x00; //K值存储地址
double Send_Buff[8]; //发送数据数组
double Read_Buff[8]; //接收数据数组
char *LED_Statue="ON"; //LED状态(禁用、启用)
uint8_t LED_Blink=1; //LED闪烁标志位
uint8_t LED_Close[2]; //LED关闭数组 值为1则下标对应LED关闭
uint8_t T[3]={0,0,0}; //上报电压时间,T[0]为时 T[1]为分 T[2]为秒
uint8_t mode=0; //当前模式(数据显示、数据设置)
uint32_t recDex=0; //接收数组下标、数据长度
uint8_t recBuff[10]; //串口接收数组
uint8_t recByte; //串口每次接收的一字节数据
uint8_t recCheckFlag=0; //接收数据检测标志位,接收长度无误后进行数据检测
char str[30]; //用于组合字符串
RTC_TimeTypeDef Now_Time;//当前时间
RTC_DateTypeDef Now_Date;//当前日期
gpio.c
const uint16_t Ypos_end=8*16; //初始Y坐标 对应时间第一位数据
const uint16_t Ypos_beg=14*16; //末尾Y坐标 对应时间最后一位数据
const uint8_t Ypos_step=16; //每个字符间Y坐标差值
uint16_t Ypos_now=14*16; //当前Y坐标 初始为第一位数据
const uint8_t hour=Ypos_beg-0*Ypos_step;//小时十位对应Y坐标
const uint8_t min=Ypos_beg-3*Ypos_step; //分钟十位对应Y坐标
const uint8_t sec=Ypos_beg-6*Ypos_step; //秒十位对应Y坐标
三、主要函数
1.按键扫描
尽量将按键实现的功能封装为函数,降低函数耦合度,修改代码的时候比较方便。
void KEY_Scan()
{
static uint8_t ban=0;
if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET)//LED
{
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);
ban=!ban;//禁用标志位翻转
if(ban)
{
LED_Close[1]=1;
LED_Statue="OFF";
HAL_TIM_Base_Stop_IT(&htim7);//关闭LED闪烁
}
else
{
LED_Statue="ON";
TIM7->CNT=0;
HAL_TIM_Base_Start_IT(&htim7);//开启LED闪烁
}
}
}
else if(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET)//setting
{
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);
LCD_Refresh(2);
mode=!mode;
Setting_Mode();
}
}
}
2.设置时间
用到三个函数。
1.Dat_change(uint16_t sel),参数为当前坐标,用于对当前坐标数据进行更改。
2.Sel_change(uint16_t Ypos),参数为当前坐标,用于高亮显示和颜色恢复,函数中根据坐标判断当前数据和上一数据,再对当前数据高亮显示和上一数据恢复显示。
3.Setting_Mode(),简单的按键扫描,实现对应功能时只需要修改或传递当前坐标即可。
void Dat_change(uint16_t sel)//数据改变 参数为当前Y坐标
{
switch(sel)
{
case hour:
T[0]++;
if(T[0]>23)T[0]=0;
break;
case min:
T[1]++;
if(T[1]>59)T[1]=0;
break;
case sec:
T[2]++;
if(T[2]>59)T[2]=0;
break;
}
}
void Sel_change(uint16_t Ypos)//切换选择 参数为当前Y坐标
{
char dex1;
char dex2;
switch(Ypos)
{
case hour:
dex1=0;
dex2=2;
break;
case min:
dex1=1;
dex2=0;
break;
case sec:
dex1=2;
dex2=1;
break;
}
LCD_SetBackColor(Blue);//当前选择高亮
LCD_SetTextColor(White);
LCD_DisplayChar(Line6,Ypos-0*Ypos_step,T[dex1]/10+48);
LCD_DisplayChar(Line6,Ypos-1*Ypos_step,T[dex1]%10+48);
LCD_SetBackColor(White);//上一选择恢复
LCD_SetTextColor(Blue);
if(Ypos==Ypos_beg)//如果当前为小时 则上一个数据为秒
{
LCD_DisplayChar(Line6,Ypos-6*Ypos_step,T[dex2]/10+48);
LCD_DisplayChar(Line6,Ypos-7*Ypos_step,T[dex2]%10+48);
}
else
{
LCD_DisplayChar(Line6,Ypos+3*Ypos_step,T[dex2]/10+48);
LCD_DisplayChar(Line6,Ypos+2*Ypos_step,T[dex2]%10+48);
}
}
void Setting_Mode()//Setting模式
{
while(1)
{
Sel_change(Ypos_now);//更新显示
if(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET)//setting
{
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);
mode=!mode;
break;//break;
}
}
else if(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET)//change
{
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);
Ypos_now-=3*Ypos_step;
if(Ypos_now<Ypos_end)
{
Ypos_now=Ypos_beg;
}
}
}
else if(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET)//++
{
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);
Dat_change(Ypos_now);
}
}
}
}
3.设置闹钟、闹钟中断函数(中断产生时上报电压值)
每次进入闹钟中断函数,都需要做四件事:
1.获取当前时间,即中断发生时间。
2.设定下一秒的闹钟。
3.是否更新显示。
4.判断是否需要上报电压。
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)//RTC闹钟中断 时间显示(每秒) 上报电压值
{
Get_Time();//获取发生中断的时间
Set_Alarm();//设定下一秒的闹钟
if(mode==0)Show_Now_Time();//数据显示页面则更新LCD
if(T[0]==Now_Time.Hours && T[1]==Now_Time.Minutes && T[2]==Now_Time.Seconds)//判断是否为电压上报时间
{
printf("%.2f+%.1f+%02d%02d%02d\n",V,K,T[0],T[1],T[2]);
}
}
下面是如何获取时间以及设定下一秒的闹钟。
void Get_Time() //获取当前时间
{
HAL_RTC_GetTime(&hrtc,&Now_Time,RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc,&Now_Date,RTC_FORMAT_BIN);
}
void Set_Alarm() //设置下一秒的闹钟
{
sAlarm.AlarmTime.Seconds = Now_Time.Seconds+1;
if(sAlarm.AlarmTime.Seconds==60)sAlarm.AlarmTime.Seconds=0;
HAL_RTC_SetAlarm_IT(&hrtc,&sAlarm, RTC_FORMAT_BIN);
}
/*
4.TIM6中断函数(用于判定接收数据长度)
TIM6中断函数中做三件事:
1.进入中断后关闭定时器。
2.判断接收了多少字节数据(下标值),正确则准备数据检测。
3.重置接收数组下标。
if(htim->Instance==TIM6)//数据长度判定
{
HAL_TIM_Base_Stop_IT(&htim6);
if(recDex==6)//数据长度为6
{
recCheckFlag=1;//数据检测标志位置1
}
recDex=0;//重置
HAL_UART_Receive_IT(&huart1,&recByte,1); //重新开启串口接收中断
}
5.串口接收中断
每接收到一个数据时重新开启定时器,在接收到最后一字节数据5us后触发定时器中断,在定时器中断函数中进行数据长度判断。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口接收中断函数
{
recBuff[recDex++]=recByte;//保存当前接收数据
HAL_TIM_Base_Stop_IT(&htim6);//定时器关闭
TIM6->CNT=0;//计数值清零
HAL_TIM_Base_Start_IT(&htim6);//重新开启定时器 接收最后一字节数据5us后进入定时器中断判定数据长度
HAL_UART_Receive_IT(&huart1,&recByte,1);//重新开启串口接收中断
}
6.Main函数
需要注意将定时器中断标志位清零:
TIM6->SR=0;
TIM7->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();
I2CInit();
MX_RTC_Init();
MX_TIM6_Init();
MX_USART1_UART_Init();
MX_TIM7_Init();
/* USER CODE BEGIN 2 */
EEPROM_ReadBuff(K_addr,(void *)Read_Buff,sizeof(Send_Buff[0]));//读取K值
K=Read_Buff[0];//更新K值
Get_Time();//获取当前时间
LCD_Init();//LCD初始化
LCD_Init_Show();//LCD初始化显示
TIM6->SR=0;//TIM6中断标志位清零
TIM7->SR=0;//TIM7中断标志位清零
HAL_TIM_Base_Start_IT(&htim7);//以中断方式开启定时器7
HAL_UART_Receive_IT(&huart1,&recByte,1);//开启串口接收中断
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
KEY_Scan();//按键扫描
V=ADC_GetValue();//更新R37电压值
LCD_Refresh(1);//LCD更新显示
if(recCheckFlag==1)//如果接收检测标志位为1则开始检测数据
{
recCheckFlag=0;//重置
REC_Check();//检测数据
}
if(strcmp(LED_Statue,"OFF")!=0)//如果LED没被禁用
{
LED_Change();//LED状态改变
}
LED_AllClose(LED_Close); //LED更新显示
}
/* USER CODE END 3 */
}
四、实验结果
1.定时上报电压(上电状态为为00:00:00)
2.修改k值
k值改变
3.修改上报时间
五、源码(转载请注明出处)
总结
以上就是全部内容,如有错误请批评指正。