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

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

文章目录

  • 前言
  • 一、CubeMX配置(第六届蓝桥杯省赛完整版)
  • 二、代码相关定义、声明
    • 1.函数声明
    • 2.变量定义
  • 三、主要函数
    • 1.按键扫描
    • 2.设置时间
    • 3.设置闹钟、闹钟中断函数(中断产生时上报电压值)
    • 4.TIM6中断函数(用于检测串口接收超时)
    • 5.串口接收中断
    • 6.Main函数
  • 四、实验结果
    • 1.定时上报电压(上电状态为为00:00:00)
    • 2.修改k值(发送k0.1\n有效;先发送k0.1再发送\n无效)
    • 3.修改上报时间
  • 五、源码(转载请注明出处)
  • 总结

前言

相关说明:

开发板:CT117E-M4(STM32G431RB 蓝桥杯嵌入式比赛板)
开发环境: CubeMX+Keil5
涉及题目:第六届蓝桥杯嵌入式省赛
题目难点:串口接收中断为每接收一个字节中断一次,如何区分6字节数据同时发送与先发送5字节再发送1字节数据


CubeMX配置、主要函数代码及说明:

一、CubeMX配置(第六届蓝桥杯省赛完整版)

1.使能外部高速时钟:
在这里插入图片描述

2.配置时钟树:在这里插入图片描述

3.GPIO:在这里插入图片描述
4.ADC(采集R37的电压值):在这里插入图片描述

5.RTC:在这里插入图片描述在这里插入图片描述

6.TIM6(2ms中断):在这里插入图片描述

7.TIM7(检测串口接收是否超时,TIM7发生中断则视本次接收数据无效):在这里插入图片描述

8.I2C:在这里插入图片描述
9.USART(题目要求为9600,这里选择115200):在这里插入图片描述

10.NVIC(中断配置):在这里插入图片描述

二、代码相关定义、声明

1.函数声明

void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc);//闹钟中断函数void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);//定时器中断函数void HAL_UART_RxCpltCallback( UART_HandleTypeDef *  huart );//串口接收中断函数void EEPROM_I2C_WriteBuff(uint8_t addr,uint8_t *sendBuff,uint32_t sizeOfSend);//EEPROM写数据void EEPROM_I2C_ReadByte(uint8_t addr,uint8_t *readBuff,uint32_t sizeOfRead);//EEPROM读数据void KEY_Scan(void);//按键扫描void SET_Rtc_Alarm(uint8_t hour,uint8_t min,uint8_t sec);//设置闹钟void Setting_time(void);//设置时间uint16_t getADC(void);//获取ADC的值

2.变量定义

uint8_t LED_flag=1;//LED开启标志位 禁用后无效uint8_t LED_open=1;//LED禁用/启用标志位uint8_t KEY_value=1;//按键值uint8_t Page_change=0;//LCD显示页改变标志位uint8_t Page=1;//当前页uint8_t Alarm_buff[6];//闹钟数组(如00:00:00的六位数字数据)uint8_t Alarm_dex=0;//闹钟数组下标uint8_t Xpos=120;//LCD中X当前坐标(初始对应时间的第一位)uint8_t Ypos=224;//Y当前坐标uint8_t X_step=24;//LCD中一个字符的高度uint8_t Y_step=16;//一个字符的宽度uint8_t Xpos_old=120;//X坐标初始值(用于最后一位数据点击下一个时回到第一位)uint8_t Ypos_old=224;//Y坐标初始值double V=0;//R37的电压值double K=0;//K值uint8_t K_change=1;//K值改变标志位double sendBuff[1]={0};//EEPROM接收数组double readBuff[1]={0};//EEPROM发送数组uint8_t K_Addr=0x00;//EEPROM中储存K值地址uint8_t rx[6]={0};//串口接收数组uint8_t rx_buff;//串口接收一个字节的数据uint8_t rx_dex=0;//串口接收数组下标RTC_DateTypeDef GET_Date; //获取日期RTC_TimeTypeDef GET_Time; //获取时间RTC_AlarmTypeDef SET_Alarm;//设置闹钟char str[30];//用于sprintf函数组合字符串

三、主要函数

1.按键扫描

void KEY_Scan(){if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==GPIO_PIN_RESET){HAL_Delay(10);if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==GPIO_PIN_RESET){if(LED_flag!=1){HAL_TIM_Base_Start_IT(&htim6);}else{HAL_TIM_Base_Stop_IT(&htim6);TIM6->CNT=0;}LED_open=!LED_open;}while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==GPIO_PIN_RESET);}else if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==GPIO_PIN_RESET){HAL_Delay(10);if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==GPIO_PIN_RESET){KEY_value=2;if(Page!=2){Page=2;Page_change=1;}else if(Page!=1){Page=1;Page_change=1;}}while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==GPIO_PIN_RESET);}else if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==GPIO_PIN_RESET && Page==2){HAL_Delay(10);if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==GPIO_PIN_RESET){KEY_value=3;Setting_time();}while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==GPIO_PIN_RESET);}else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET){HAL_Delay(10);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET){KEY_value=4;}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET);}}

2.设置时间

切换到下一个时需注意下一个为符号,或到达末尾的情况,这里使用的是直接判断是否为符号所在坐标,是的话则Ypos再增加一个长度;末尾同理判断坐标即可;选择的字符颜色高亮实现,先修改LCD颜色,在当前坐标再次显示数字即可(数字加48的原因是LCD_DisplayChar的第三个参数为ASCII码,0+48=48,对应的则是0的ASCII码)

void Setting_time(){LCD_SetBackColor(Blue);//首个颜色翻转LCD_SetTextColor(White);LCD_DisplayChar(Xpos,Ypos,Alarm_buff[Alarm_dex]+48);while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==GPIO_PIN_RESET);while(1){LCD_DisplayChar(Xpos,Ypos,Alarm_buff[Alarm_dex]+48);if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==GPIO_PIN_RESET)//切换到下一个{HAL_Delay(10);if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==GPIO_PIN_RESET){LCD_SetBackColor(White);//当前颜色恢复LCD_SetTextColor(Blue);LCD_DisplayChar(Xpos,Ypos,Alarm_buff[Alarm_dex%6]+48);LCD_SetBackColor(Blue);//下一个颜色翻转LCD_SetTextColor(White);Ypos-=Y_step;if(Ypos==96)//最后一个点击下一个将回到首个 {Ypos=Ypos_old;}if(Ypos==192 || Ypos==144)//非数字跳过{Ypos-=Y_step;}Alarm_dex++;Alarm_dex=Alarm_dex%6;LCD_DisplayChar(Xpos,Ypos,Alarm_buff[Alarm_dex]+48);}while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==GPIO_PIN_RESET);}else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET)//++{HAL_Delay(10);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET){Alarm_buff[Alarm_dex]++;switch(Alarm_dex){case 0:if(Alarm_buff[Alarm_dex]>2)Alarm_buff[Alarm_dex]=0;break;case 1:if(Alarm_buff[Alarm_dex]>3)Alarm_buff[Alarm_dex]=0;break;case 2:if(Alarm_buff[Alarm_dex]>5)Alarm_buff[Alarm_dex]=0;break;case 3:if(Alarm_buff[Alarm_dex]>9)Alarm_buff[Alarm_dex]=0;break;case 4:if(Alarm_buff[Alarm_dex]>5)Alarm_buff[Alarm_dex]=0;break;case 5:if(Alarm_buff[Alarm_dex]>9)Alarm_buff[Alarm_dex]=0;break;}}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET);}else if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==GPIO_PIN_RESET)//更新闹钟{HAL_Delay(10);if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==GPIO_PIN_RESET){SET_Rtc_Alarm(Alarm_buff[0]*10+Alarm_buff[1],Alarm_buff[2]*10+Alarm_buff[3],Alarm_buff[4]*10+Alarm_buff[5]);Alarm_dex=0;Page=1;Ypos=Ypos_old;Xpos=Xpos_old;LCD_SetBackColor(White);//当前颜色恢复LCD_SetTextColor(Blue);break;}while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==GPIO_PIN_RESET);}}}

3.设置闹钟、闹钟中断函数(中断产生时上报电压值)

到达闹钟时间则触发RTC闹钟中断函数,在中断函数中执行上报电压值的操作即可。

void SET_Rtc_Alarm(uint8_t hour,uint8_t min,uint8_t sec){SET_Alarm.AlarmTime.Hours=hour;SET_Alarm.AlarmTime.Minutes=min;SET_Alarm.AlarmTime.Seconds=sec;HAL_RTC_SetAlarm_IT(&hrtc,&SET_Alarm,RTC_FORMAT_BIN);}
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc){printf("%.2f+%.1f+%02d%02d%02d\n",V,K,SET_Alarm.AlarmTime.Hours,SET_Alarm.AlarmTime.Minutes,SET_Alarm.AlarmTime.Seconds);}

4.TIM6中断函数(用于检测串口接收超时)

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){if(htim->Instance==TIM6){if(LED_open){HAL_GPIO_WritePin(GPIOC,GPIO_PIN_9,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_10,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_11,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_12,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_14,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_15,GPIO_PIN_SET);HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_8);HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);}}if(htim->Instance==TIM7){rx_dex=0;//超过一定时间 更新接收数组HAL_TIM_Base_Start_IT(&htim7);}}

5.串口接收中断

每次进入中断都开启定时器用于检测数据是否为一次性发送,若定时器溢出(溢出时间以一次性发送六字节数据所需时间为准),则忽略本次接收的数据,下一次重新接收。这样就能达到发送k0.1\n有效,先发送k0.1再发送\n无效。

void HAL_UART_RxCpltCallback( UART_HandleTypeDef *  huart ) {while(HAL_UART_GetState(&huart1)==HAL_UART_STATE_BUSY_RX);if(rx_dex==0)HAL_TIM_Base_Start_IT(&htim7);//开启定时器确保指令是一次性发送的 超过一定时间将重新读取rx[rx_dex++]=rx_buff;if(rx_dex>=6){if(rx[0]=='k' && rx[1]=='0' && rx[2]=='.' && rx[3]>='1' && rx[3]<='9' && rx[4]=='\\' && rx[5]=='n'){sprintf(str,"%c.%c",rx[1],rx[3]); //组合数值sendBuff[0]=atof(str);//字符串转浮点型EEPROM_I2C_WriteBuff(K_Addr,(void *)sendBuff,sizeof(sendBuff));HAL_TIM_Base_Stop_IT(&htim7);//关闭定时器printf("ok\n");}TIM7->CNT=0;//重置定时器值rx_dex=0;}HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx_buff, 1);}

6.Main函数

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_USART1_UART_Init();  MX_TIM6_Init();  MX_TIM7_Init();  MX_RTC_Init();  /* USER CODE BEGIN 2 */LCD_Init();//LCD初始化HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx_buff, 1);//开启串口接收中断HAL_ADC_Start(&hadc2);//开启adcAlarm_buff[0]=SET_Alarm.AlarmTime.Hours/10;Alarm_buff[1]=SET_Alarm.AlarmTime.Hours%10;Alarm_buff[2]=SET_Alarm.AlarmTime.Minutes/10;Alarm_buff[3]=SET_Alarm.AlarmTime.Minutes%10;Alarm_buff[4]=SET_Alarm.AlarmTime.Seconds/10;Alarm_buff[5]=SET_Alarm.AlarmTime.Seconds%10;//初始化闹钟结构体,将EEPROM中的时间拆解后储存在数组中SET_Alarm.AlarmTime.SubSeconds = 0x0;SET_Alarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY;SET_Alarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;SET_Alarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;SET_Alarm.AlarmDateWeekDay = 0x1;SET_Alarm.Alarm = RTC_ALARM_A;//新闹钟结构体//lcdLCD_Clear(Blue);LCD_SetBackColor(Blue);LCD_DisplayStringLine(Line0,(unsigned char *)"      ");LCD_DisplayStringLine(Line1,(unsigned char *)"      ");LCD_SetBackColor(White);LCD_DisplayStringLine(Line2,(unsigned char *)"      ");LCD_DisplayStringLine(Line3,(unsigned char *)"      ");LCD_DisplayStringLine(Line4,(unsigned char *)"      ");LCD_DisplayStringLine(Line5,(unsigned char *)"      ");LCD_DisplayStringLine(Line6,(unsigned char *)"      ");LCD_DisplayStringLine(Line7,(unsigned char *)"      ");LCD_SetBackColor(Blue);LCD_SetTextColor(Yellow);LCD_DisplayStringLine(Line8,(unsigned char *)"      ");LCD_DisplayStringLine(Line9,(unsigned char *)"    1 ");  /* USER CODE END 2 */  /* Infinite loop */  /* USER CODE BEGIN WHILE */LCD_SetBackColor(White);LCD_SetTextColor(Blue);  while (1)  {    /* USER CODE END WHILE */    /* USER CODE BEGIN 3 */KEY_Scan();if(Page==2){if(Page_change){Page_change=0;LCD_SetBackColor(Blue);LCD_SetTextColor(Yellow);LCD_DisplayStringLine(Line9,(unsigned char *)"    2 ");LCD_SetBackColor(White);LCD_SetTextColor(Blue);}//setLCD_DisplayStringLine(Line3,(unsigned char *)"      Setting   ");LCD_DisplayStringLine(Line4,(unsigned char *)"      ");//timesprintf(str,"      %02d-%02d-%02d ",SET_Alarm.AlarmTime.Hours,SET_Alarm.AlarmTime.Hours,SET_Alarm.AlarmTime.Seconds);LCD_DisplayStringLine(Line5,(unsigned char *)str );LCD_DisplayStringLine(Line6,(unsigned char *)"      ");}else if(Page==1){if(Page_change){Page_change=0;LCD_SetBackColor(Blue);LCD_SetTextColor(Yellow);LCD_DisplayStringLine(Line9,(unsigned char *)"    1 ");LCD_SetBackColor(White);LCD_SetTextColor(Blue);}//show VV=getADC()*3.3/4096;sprintf(str,"   V1 :%.2fV     ",V);LCD_DisplayStringLine(Line3,(unsigned char *)str);if(V>3.3*K){HAL_TIM_Base_Start_IT      (&htim6);}else{HAL_TIM_Base_Stop_IT      (&htim6);}//show KEEPROM_I2C_ReadByte(K_Addr,(void*)&K,sizeof(sendBuff));sprintf(str,"   K  :%.2F  ",K);LCD_DisplayStringLine(Line4,(unsigned char *)str);//show LEDif(LED_open==1){LCD_DisplayStringLine(Line5,(unsigned char *)"   LED:ON    ");}else{LCD_DisplayStringLine(Line5,(unsigned char *)"   LED:OFF   ");}//show T HAL_RTC_GetTime(&hrtc,&GET_Time,RTC_FORMAT_BIN);HAL_RTC_GetDate(&hrtc,&GET_Date,RTC_FORMAT_BIN);sprintf(str,"   T  :%02d-%02d-%02d ",GET_Time.Hours,GET_Time.Minutes,GET_Time.Seconds);LCD_DisplayStringLine(Line6,(unsigned char *)str);//if(Tim)}HAL_Delay(300);  }  /* USER CODE END 3 */}

四、实验结果

1.定时上报电压(上电状态为为00:00:00)

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

2.修改k值(发送k0.1\n有效;先发送k0.1再发送\n无效)

在这里插入图片描述
k值改变在这里插入图片描述

3.修改上报时间

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

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

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


总结

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