STM32串口通信--基于HAL库(第六届蓝桥杯嵌入式省赛)


前言

相关说明:

开发板: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.修改上报时间

在这里插入图片描述
在这里插入图片描述

五、源码(转载请注明出处)

在这里插入图片描述


总结

以上就是全部内容,如有错误请批评指正。

  • 11
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AゞOctopus๊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值