【蓝桥杯】【嵌入式组别】第十二节:USART串口通讯

2023-05-16

USART串口通讯

  • USART:通用同步异步收发器
  • 串口发送程序设计:
    • 如何连续打印helloworld
    • 能不能发送中文?
    • 串口发送printf重定向
  • 串口接收程序设计:
    • 串口接收固定长度数据:
    • 串口接收带帧尾的不定长数据

USART:通用同步异步收发器

所谓异步,意思就是收发设备的时钟不同,是各自用各自的时钟。因此也就决定了它可能发的数据过多的话会导致混乱。所以uart一次最多发8个bit的数据,也就是一个字节。
串行意思就是数据一个一个的发送出去或者接受回来。
发送数据和接收数据时:高电平代表1,低电平代表0。但是我们怎么知道:如果一直是高电平,那么到底代表多少个1呢?其实这个是由波特率控制的。波特率是xxxbit per second,就告诉了机器每过多长时间就代表一个bit的信号了。

而iic和spi都是同步的通信方式。也就是收发设备处于同一个时钟之下工作,所以单词可传送的数据没有特殊限制,不会因为传送的数据多了就混淆掉。可以知道iic和spi再连线时都会有一根时钟线,用这根时钟线就可以保证收发设备处于同一个时钟之下。

USART可以同步也可以异步。区别是同步的时候需要三根线:TXD,RXD和CLK三根线,CLK用来同步收发两端的波特率和时间。
而异步只需要两根线:TXD,RXD就可以了。竞赛开发板上就只有两根线,所以我们只用异步就可以了,
在这里插入图片描述

开发板上的USART管脚是PA9和PA10。

串口发送程序设计:

步骤:

  1. 【模板】作为STM32CUBEMX生成代码的工程;
  2. 配置USART1的PA9、PA10为串口收发管脚
  3. 根据需求,配置USART的波特率、数据位长度、奇偶校验位、停止位和时钟;
  4. 将usart.c 和usart.h移植到【编程工程】
    4.1main.c包含#include “usart.h”
    4.2添加usart.c和stm32g4的HAL库函数到工程中;
    4.3stm32g4xx_hal_confh中启动UART模块;
    4.4时钟初始化,一定要初始化USART1的时钟!
    4.5在主函数调用MX USART1_UART_Init()串口初始化函数;
    4.6调用HAL_UART_Transmit串口发送函数!

在cube里面配置PA9和PA10管脚为异步通信模式:(其他参数可以使用默认给出的,不用更改)
在这里插入图片描述

可以看到时钟树里面又被点亮了一部分,这部分就是控制USART收发频率和波特率的时钟,我们可以选择这四个选项,但此处保持默认选项也是一个不错的选择。然后在移植过程中记得移植时钟部分的代码即可。

在这里插入图片描述
移植操作全部完成之后就可在main函数中编写利用usart发送字符的代码了:

HAL_UART_Transmit(&huart1,(unsigned char*)"helloworld!\r\n",sizeof("helloworld!\r\n"),50);

这一句就是发送helloworld的代码。
在串口监视器里面显示为16进制观察为:
在这里插入图片描述

如何连续打印helloworld

最后的00是停止位,有了00之后,后面再发送什么东西这里都不会有显示了。
再往前面的0D和0A是换行符号。
所以我们如果想要一上电发送两次helloworld,就不能让00出现,不然的话就会出现这样的情况:
代码是这样的:

	HAL_UART_Transmit(&huart1,(unsigned char*)"helloworld!\r\n",sizeof("helloworld!\r\n"),50);
	HAL_UART_Transmit(&huart1,(unsigned char*)"helloworld!\r\n",sizeof("helloworld!\r\n"),50);

就是上电之后会发送两个helloworld,但是运行结果是这样的:
在这里插入图片描述
很明显只发送了一次,因为后面的一次被覆盖掉了。观察其16进制发现:
在这里插入图片描述

确实发了两次,但是由于第一次的最后一位发送了停止位00,所以后面的那一次是无效的,因为默认接收到00之后不论后面有什么都不接受了。

所以如果想要实现连续两次接收helloworld,就要把00删除掉,方法就是让发送的数据的size减一,就把末位删除掉了。
代码如下:

	HAL_UART_Transmit(&huart1,(unsigned char*)"helloworld!\r\n",sizeof("helloworld!\r\n")-1,50);
	HAL_UART_Transmit(&huart1,(unsigned char*)"helloworld!\r\n",sizeof("helloworld!\r\n")-1,50);

在这里插入图片描述
可以看到很完美的打印了两次。

能不能发送中文?

答案是可以发送中文。我们可以编写如下代码:
在main函数以外编写一个全局变量:

u8 tx_buf[]={"您好\r\n"};

在main函数中:

	HAL_UART_Transmit(&huart1,(unsigned char*)tx_buf,sizeof(tx_buf)-1,50);

最后实现的结果如下;
在这里插入图片描述
如果我们开启16进制观察,可以发现是这样的:
在这里插入图片描述
其实是keil自动把中文翻译为了16进制字符,每一个中文对应两个16进制字符:
“您”对应“C4 FA”
“好”对应“BA C3”
后面的“0D 0A“表示的是换行符

串口发送printf重定向

我们可以使用重定向之后的printf函数作为串口输出的函数。关于如何将printf重定向:只需要在usart.c文件中添加这样的代码即可:

int fputc(int ch, FILE *f) 
{
  HAL_UART_Transmit(&huart1,(unsigned char*)&ch,1,50);
  return ch;
}

这个函数我们可以在keil的help中查询到,步骤如下:
点击help的第一项,进入help页面
在这里插入图片描述

然后点击搜索:搜索”retarget“,第三项就是我们要找的printf的重定向函数
在这里插入图片描述
依照他的要求,我们把#include <stdio.h>(标准的输入输出库)加到usart.c文件中,然后复制下面的函数到usart.c文件中:

int fputc(int ch, FILE *f) 
{
  /* Your implementation of fputc(). */
  return ch;
}

/* Your implementation of fputc(). */这里换成hal库的usart发送函数即可。完整的函数如下所示:

int fputc(int ch, FILE *f) 
{
  HAL_UART_Transmit(&huart1,(unsigned char*)&ch,1,50);
  return ch;
}

添加好之后我们就可以在main.c文件中直接写printf来进行usart的发送了,如:

 printf("%s \n","你好!");
 printf("您输入的数字是%d",2);

效果如下:
在这里插入图片描述
记得要勾选上微库才能正常使用printf!在这里插入图片描述

串口接收程序设计:

步骤:

  1. 【模板】作为STM32CUBEMX生成代码的工程
  2. 配置USART1的PA9、PA10为串口收发管脚;
  3. 根据需求,配置USART的波特率、数据位长度、奇偶校验位、停止位和时钟;
  4. 勾选NVIC Settings中的使能USART1的中断;
  5. 将usart.c 和usart,h移植到【编程工程】
    5.1 USART初始化部分,需要移植NVIC初始化内容
    5.2 stm32g4xx it.c中,处理USART1 IRQHandler中断服务函数;
    5.3在初始化部分,启动HAL UART_ReceiveIT(&huart1,rx_buf,1);
    5.4在回调函数HAL_UART_RxCpltCallback实现串口接收缓存处理,并再次使能HAL_UART_Receive_IT;

先打开模板工程,然后配置USART的中断向量控制:
在这里插入图片描述
勾选启用NVIC。
后面的:Preemption Priority是抢占优先级的意思。比如程序目前在主函数中运行,遇到了Systick的中断触发,就会到Systick中断处理函数中执行程序,如果在这期间又遇到了更高级别的优先级的中断,就会再进入更高级别的中断处理函数中,也就是一个中断嵌套。这就是抢占优先级要处理的问题。
而Sub Priority是响应优先级,解决的是当程序运行主函数时,同时来了两个中断触发,那么选择哪一个先响应的问题。
我们需要点击到NVIC页面来配置相应的抢占优先级和响应优先级:

在这里插入图片描述
一般配置成如图所示的4bit抢占优先级和0bit响应优先级。
4个bit 就是4位的2进制数,范围是从0000-1111,也就是从0到15一共有16种优先级。
一般来说本竞赛不涉及中断嵌套,所以下面配置各个外设的抢占优先级的时候可以都设置为0,也就是不施行抢断,就是哪个先来就响应哪个。如果实在希望设置为不一样的优先级的话,可以把USART设置为高优先级,也就是0,其他的可以稍微低优先级一点,也就是设置为大于0的某个数即可。我们此处保持默认即可。

然后生成代码,进行移植。
移植完成后我们就可以编写代码了:
在编写代码之前先了解一下这个中断是如何进行的,以及我们需要编写哪些函数:
在主函数的初始化代码部分我们需要先调用“开启串口中断”的函数:

  HAL_UART_Receive_IT(&huart1,uart_buf,1);//开启串口接收中断

第二个参数是一个接受字符的数组,我们发送给USART的数据都会存储在这个数组里面,第三个参数是意味着我们接收到几个数据就出发中断。我们事先可以定义:

u8 uart_buf[2]

我们设置第三个参数为1,意思是我们希望接收到一个数据就触发中断,而不是接收好几个数据才触发。
所以上面句代码意味着我们接收到一个字节的数据就应该触发中断,然后进入到USART的中断处理函数中。
那么USART的中断处理函数在哪里呢?在stm32g4xx_it.c文件中的下面这个函数部分:

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

其中HAL_UART_IRQHandler(&huart1);这一句函数就是中断处理函数。
进入HAL_UART_IRQHandler(&huart1);函数的定义中(节选了一部分):

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
  uint32_t isrflags   = READ_REG(huart->Instance->ISR);
  uint32_t cr1its     = READ_REG(huart->Instance->CR1);
  uint32_t cr3its     = READ_REG(huart->Instance->CR3);

  uint32_t errorflags;
  uint32_t errorcode;

  /* If no error occurs */
  errorflags = (isrflags & (uint32_t)(USART_ISR_PE | USART_ISR_FE | USART_ISR_ORE | USART_ISR_NE));
  if (errorflags == 0U)
  {
    /* UART in mode Receiver ---------------------------------------------------*/
    if (((isrflags & USART_ISR_RXNE_RXFNE) != 0U)
        && (((cr1its & USART_CR1_RXNEIE_RXFNEIE) != 0U)
            || ((cr3its & USART_CR3_RXFTIE) != 0U)))
    {
      if (huart->RxISR != NULL)
      {
        huart->RxISR(huart);
      }
      return;
    }
  }

这部分的意思就是如果没有任何错误发生的话,就会进行一系列接收标志位的查验,然后调用RxISR(huart);函数。
点击进入RxISR(huart);函数的定义可以看到:

void (*RxISR)(struct __UART_HandleTypeDef *huart); /*!< Function pointer on Rx IRQ handler   */

它本身是一个函数指针。所以RxISR(huart);函数指向哪个函数,就意味着在USART中断处理中会调用哪个函数。
我们可以点击进入 HAL_UART_Receive_IT(&huart1,uart_buf,1);//开启串口接收中断这个函数的定义:

 if ((huart->FifoMode == UART_FIFOMODE_ENABLE) && (Size >= huart->NbRxDataToProcess))
    {
      /* Set the Rx ISR function pointer according to the data word length */
      if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
      {
        huart->RxISR = UART_RxISR_16BIT_FIFOEN;
      }
      else
      {
        huart->RxISR = UART_RxISR_8BIT_FIFOEN;
      }

可以看到上面这几行的代码,由于我们设置的是8bit数据位,所以执行的是UART_RxISR_8BIT_FIFOEN而不是UART_RxISR_16BIT_FIFOEN(关于这一点可以仿真然后在此处设置一个断点看是否会停留在这个函数这里,就可以知道)。
所以我们知道RxISR其实是指向了UART_RxISR_8BIT_FIFOEN这个函数。
我们在仿真时候打个断点可以最终得知,RxISR会通过UART_RxISR_8BIT_FIFOEN最终执行这个函数里面的HAL_UART_RxCpltCallback(huart);函数。这个函数上面的注释是:

/*Call legacy weak Rx complete callback*/

说明这是一个弱定义函数,就是只有一个函数体,没有里面的内容,他是长这个样子:

__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);

  /* NOTE : This function should not be modified, when the callback is needed,
            the HAL_UART_RxCpltCallback can be implemented in the user file.
   */
}

所以我们确定了我们要编写的就是void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)这个函数的函数体。
那么我们把他复制出来到main.c文件中编写他的函数本体:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	LED_Control(uart_buf[0]);
	HAL_UART_Receive_IT(&huart1,uart_buf,1);//开启串口接收中断
}

第一行是我们自己想实现的目标,第二行是必须要做的,要重新开启定时器中断,因为一方面他执行完一次中断后会清楚标志位,另一方面我们需要这句代码把buffer清除掉,方便下一次串口接收。
上面这个回调函数能实现的功能就是将我们发送给单片机的第一个数据作为一个指令指导led灯的亮灭。
此处还要明确的一点是:
我们设置的串口发送与接收的位数都是1个字节。1个字节就是8个bit,8个bit就是8位二进制数。而4位二进制数可以表示的最大值是15,也就是4位二进制数等效于1位十六进制数。而1个字节有8位二进制数,就等效于2位16进制数。
所以我们可以发送两位十六进制数给单片机,就相当于1个bit的数据。根据我们设置的HAL_UART_Receive_IT函数的参数,单片机接收到1个bit的数据之后就会进入中断,并且把这1个bit的数据存储到uart_buf里面。
所以我们如果发送十六进制55,就相当于是0x55,此时进入USART中断,然后执行这个回调函数,uart_buf[0]被赋值为0x55,然后就会间隔点亮led灯。

并且要注意的是:虽然我们这里定义的是uart_buf[2],也就是这个数组有三位可以存储数据,也就是能接受3个bit的数据,然是实际上只能用到1位,就是uart_buf[0]这一位。每次发送来的数据都会更新uart_buf[0]位,而不会对后面的位有任何影响和操作。

串口接收固定长度数据:

我们如果想接收多位数据,然后让不同位的数据执行不同的操作,应该怎么做呢?
uart_buf[]只能接收1位数据,后面来的数据会把前面的数据顶掉。所以我们应该重新定义一个数组,用来存储我们需要的位数的数据。
比如说我们需要接收3个数据,第一个数据写入到eeprom里面,第二个数据用来控制led灯的点亮,第三个数据用来设置dac的值。那么我们此处定义一个10位的数组(大于3即可)和一个计数单位:

u8 rx_buf[10];
u8 rx_cnt=0;

按照如下方法编写回调函数(即:每进入一次回调函数,把uart_buf[]里面的数据赋值给我们自定义的数据存储数组rx_buf,由于我们一共希望要三个数据,所以我们当rx_cnt==3的时候就可以退出,重新置位,这样就得到了三个数据)

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	rx_buf[rx_cnt++]=uart_buf[0];
	if(rx_cnt==3)
	{
		rx_cnt=0;
		//第一位数据写入eeprom。。。省略代码
		LED_Control(rx_buf[1]);
		//第三位数据用作dac、、、省略代码
	}
	HAL_UART_Receive_IT(&huart1,uart_buf,1);//开启串口接收中断
}

但是此时有一个问题是:
假设接收3个字节固定长度数据:

  1. 用户先发送了1个字节数据(错误的命令,正确的命令应该是3个字节);
  2. 用户发现发错数据了,又发送了3个字节的正确字节数据
  3. 结果,rx_buf会缓存1个字节的错误数据

比如:用户本来想发的是:11 55 22
但是一开始只发了一个数据:11
后来想起来应该发三个数据,所以又发了11 55 22
那么此时rx_buf的前三位是:11 11 55,也就是多存储了错误的那个命令。
这种问题应该如何避免呢?
由于我们的比特率通常都很高,所以如果连发三个数据,这三个数据之间的间隔应该是小于50ms的,但与前面的错误数据的时间间隔就会很大了,因为这是我们人为导致的。这就是我们区分错误数据和正确数据的依据。所以我们只需判断数据间的间隔时间就可以了:

//usart
u8 tx_buf[]={"您好\r\n"};
u8 uart_buf[2];
u8 rx_buf[10];
u8 rx_cnt=0;
__IO uint32_t usartTick =0;
void RxIdle_Process()
{
	if(uwTick-usartTick<50)return;
	usartTick=uwTick;
	//50ms执行一次清空rx_buf
	rx_cnt=0;
	memset(rx_buf,'\0',sizeof(rx_buf));
}
//串口接收回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	usartTick=uwTick;//重新开始计时50ms
	rx_buf[rx_cnt++]=uart_buf[0];
	if(rx_cnt==3)
	{
		rx_cnt=0;
		LED_Control(rx_buf[0]);
	}
	HAL_UART_Receive_IT(&huart1,uart_buf,1);//开启串口接收中断
}

就可以完美解决这样的问题了。

串口接收带帧尾的不定长数据

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	usartTick=uwTick;//重新开始计时50ms
	rx_buf[rx_cnt++]=uart_buf[0];
//	if(rx_cnt==3)
//	{
//		rx_cnt=0;
//		LED_Control(rx_buf[0]);
//	}
		if(uart_buf[0]!='\n')  //接收到换行符
	{
		rx_cnt=0;
		LED_Control(rx_buf[0]);
	}
	HAL_UART_Receive_IT(&huart1,uart_buf,1);//开启串口接收中断
}

最终的main.c如下:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
#include "led.h"
#include "key.h"
#include "i2c.h"
#include "dac.h"
#include "rtc.h"
#include "usart.h"
#include "string.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

//Led执行程序
__IO uint32_t ledTick =0,keyTick=0;
u8 led_ctrl=0xff;
void LED_Process(void)
{
	if(uwTick-ledTick<500)return;
	ledTick=uwTick;
	LED_Control(led_ctrl);
	led_ctrl=~led_ctrl;
}

//KEY
void KEY_Process(void)
{
	if(uwTick-keyTick<10)return;
	keyTick=uwTick;
	Key_Read();
//	if(Trg&0x01)
//	{
//	LED_Control(0x01);
//	}
	if(Trg)
	{
		LED_Control(Trg);
	}
}

//EEPROM
u8 val_24c02=0;

//DAC
u16 dac_ch1_val,dac_ch2_val;
void DAC_Process(void)
{
	  dac_ch1_val=(1.1f/3.3f*4095);//输出1.1V
	  dac_ch2_val=(2.1f/3.3f*4095);//输出2.2V
	
    HAL_DAC_SetValue(&hdac1,DAC_CHANNEL_1,DAC_ALIGN_12B_R,dac_ch1_val);//0->0V;4095->3.3V
		HAL_DAC_Start(&hdac1,DAC_CHANNEL_1);
	
	  HAL_DAC_SetValue(&hdac1,DAC_CHANNEL_2,DAC_ALIGN_12B_R,dac_ch2_val);//0->0V;4095->3.3V
		HAL_DAC_Start(&hdac1,DAC_CHANNEL_2);
}

//RTC
RTC_TimeTypeDef rtc_time;
RTC_DateTypeDef rtc_date;
void RTC_Process(void)
{
	HAL_RTC_GetTime(&hrtc,&rtc_time,RTC_FORMAT_BIN);
	HAL_RTC_GetDate(&hrtc,&rtc_date,RTC_FORMAT_BIN);
}

//LCD
void LCD_Process(void)
{
	u8 display_buf[20];
	
	sprintf((char*)display_buf,"%02d-%02d-%02d",rtc_time.Hours,rtc_time.Minutes,rtc_time.Seconds);
	LCD_DisplayStringLine(Line0,display_buf);
	
	sprintf((char*)display_buf,"%04d-%02d-%02d",rtc_date.Year,rtc_date.Month,rtc_date.Date);
	LCD_DisplayStringLine(Line1,display_buf);
	
	sprintf((char*)display_buf,"EEPROM:%d",val_24c02);
  LCD_DisplayStringLine(Line2,display_buf);//输出百分号:%
	
}


//usart
u8 tx_buf[]={"您好\r\n"};
u8 uart_buf[2];
u8 rx_buf[10];
u8 rx_cnt=0;
__IO uint32_t usartTick =0;
void RxIdle_Process()
{
	if(uwTick-usartTick<50)return;
	usartTick=uwTick;
	//50ms执行一次清空rx_buf
	rx_cnt=0;
	memset(rx_buf,'\0',sizeof(rx_buf));
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */

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();
  /* USER CODE BEGIN 2 */
	
	LCD_Init();
	LED_Control(0x00);
	MX_DAC1_Init();
	MX_RTC_Init();
	I2CInit();
	MX_USART1_UART_Init();
  HAL_UART_Receive_IT(&huart1,uart_buf,1);//开启串口接收中断
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
	
	LCD_Clear(Blue);
	LCD_SetBackColor(Blue);
	LCD_SetTextColor(White);
	EEPROM_Write(0x10,0x55);
	val_24c02=EEPROM_Read(0x10);
//	HAL_UART_Transmit(&huart1,(unsigned char*)"helloworld!\r\n",sizeof("helloworld!\r\n")-1,50);
//	HAL_UART_Transmit(&huart1,(unsigned char*)"helloworld!\r\n",sizeof("helloworld!\r\n")-1,50);
//	HAL_UART_Transmit(&huart1,(unsigned char*)tx_buf,sizeof(tx_buf)-1,50);
  printf("%s \n","你好!");
	printf("您输入的数字是%d",2);
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    //LED_Process();
		KEY_Process();
		DAC_Process();
		RTC_Process();
		LCD_Process();
		RxIdle_Process();
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
  /** Configure the main internal regulator output voltage
  */
  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the CPU, AHB and APB busses clocks
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV2;
  RCC_OscInitStruct.PLL.PLLN = 20;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
  {
    Error_Handler();
  }
	
 /** Initializes the peripherals clocks
  */
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC|RCC_PERIPHCLK_USART1
                              |RCC_PERIPHCLK_ADC12;
  PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
  PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_SYSCLK;
  PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;

  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
//串口接收回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	usartTick=uwTick;//重新开始计时50ms
	rx_buf[rx_cnt++]=uart_buf[0];
//	if(rx_cnt==3)
//	{
//		rx_cnt=0;
//		LED_Control(rx_buf[0]);
//	}
		if(uart_buf[0]!='\n')  //接收到换行符
	{
		rx_cnt=0;
		LED_Control(rx_buf[0]);
	}
	HAL_UART_Receive_IT(&huart1,uart_buf,1);//开启串口接收中断
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【蓝桥杯】【嵌入式组别】第十二节:USART串口通讯 的相关文章

  • Ubuntu配置无线路由器笔记记录

    参考文章 xff1a linux 开启制作无线路由器 ubuntu 1404 linux zhu的博客 CSDN博客 hostapd实现WIFI 热点 xff08 AP xff09 自由枫 的博客 CSDN博客 hostapd 终端get一
  • C++STL的使用心得汇总(vector,string,map,list)

    文章目录 find 函数vector的findstring的findmap的find count 函数vector的countstring的countmap的count vectorstringmap的各种排序方法转换相关 待完善 find
  • Qt 之 样式表的使用——设置样式的方法

    一 简述 我们通常在使用Qt开发的过程中都会使用样式表来美化我们的界面 xff0c 关于如何使用样式表的资料也很多 xff0c 样式表的使用方法也千变万化 为了搭建一个漂亮的界面那么必须学会如何使用样式表 xff0c Qt帮助文档中提供了非
  • 如何使QGraphicsItem不随QGraphicsView放大缩小而改变大小

    一 简述 在使用QGraphicsView过程中 xff0c 有时候我们需要对view进行缩放 xff0c 但是对于一般正常的加入view中的item都会随着view的大小变化而变化 xff0c 但是如果我们想让某些item不随view的缩
  • 【linux系统如何查看内核版本、操作系统版本等信息】

    有时候需要查看linux系统的内核版本 xff0c 可以有多种方法 xff0c 方法如下 xff1a xff08 下面以优麒麟系统为例 xff09 方法1 xff1a 打开mate终端 xff0c 在命令行输入以下命令 xff1a unam
  • 【linux系统如何安装arm交叉编译工具链】

    文章目录 前言一 arm交叉编译器介绍命名规则具体编译器 二 Arm GNU Toolchain安装总结 前言 本文简要介绍arm交叉编译器及工具链的安装方法 一 arm交叉编译器介绍 命名规则 交叉编译工具链的命名规则为 xff1a ar
  • 比较冒泡排序、选择排序和快速排序的时间(C语言实现)

    文章目录 前言代码设计代码实现运行结果结果分析稳定性测试 总结 前言 本文主要比较冒泡排序 快速排序 选择排序的时间 冒泡排序和快速排序的思想可以参考我转载的以下博文 xff1a https blog csdn net gogo0707 a
  • freertos应用程序常见错误排查

    freertos系统应用程序常见问题 对一些比较常见的问题 xff0c 下面简要的以 FAQ 问答 的形式给出可能的原因和解决方法 问题现象 xff1a 在一个 Demo 应用程序中增加了一个简单的任务 xff0c 导致应用程序崩溃 任务创
  • keil5编译工程常见问题汇总

    简介 我们在编译keil工程的时候总是遇到很多问题 xff0c 我把一些常见的问题和解决方案汇总下来 xff0c 仅供大家参考 问题汇总 问题1 问题描述 选择arm v6版本编译器 xff0c 编译keil5工程 xff0c 报错 xff
  • mdk arm debug配置

    简述 本文简要讲述启动调试之前如何配置debug 点击魔术棒 xff0c 进入debug选项界面 xff0c 如下图 xff1a 我们可以选择软件仿真 xff0c 也可以选择硬件仿真 xff08 软件仿真不需要接开发板和仿真器 xff09
  • stm32高级定时器实现pwm互补输出

    简介 stm32设备一般都有很多类型的定时器 xff0c 常见的有systick timer 基本定时器 通用定时器 高级定时器 看门狗定时器 RTC等等 xff0c 本文简单介绍高级定时器是如何实现pwm互补输出 详细 我这里使用的dev
  • shell脚本基础知识(入门)

    简介 本文会全面介绍shell脚本的基础知识 脚本格式 要把shell命令放到一个 脚本 当中 xff0c 有一个要求 xff1a 脚本的第一行必须写成类似这样的格式 xff1a bin bash bash是一个shell解释器 xff0c
  • 记ADB shell for循环踩坑

    abd 里面的shell的电脑Linux的shell有点不太一样 以下这些案例均不能执行 xff1a for i 61 1 i lt 61 100 i 43 43 do echo i done for i in 1 100 do echo
  • linux线程调度策略简述

    简述 linux系统调度执行的最小单位是线程 xff0c 线程的调度策略有以下三种 xff1a xff08 1 xff09 SCHED FIFO 其静态优先级必须设置为1 99 xff0c 这将意味着一旦线程处于就绪态 xff0c 他就能立
  • stm32串口发送接口

    简介 本文记录一下stm32标准库实现串口发送功能和接收功能的接口函数 轮询方式发送串口数据 1 标准库实现 span class token comment 61 61 61 61 61 61 61 61 61 61 61 61 61 6
  • linux系统线程池

    简述 一个进程中的线程就好比是一家公司里的员工 xff0c 员工的数目应该根据公司的业务多少来定 xff0c 太少了忙不过来 xff0c 但是太多了也浪费资源 最理想的情况是让进程有一些初始数目的线程 xff08 线程池 xff09 xff
  • STM32串口环形缓冲区

    目录 1 xff1a 概述 2 xff1a 代码 1 xff1a 概述 1 1 xff1a 本篇实现串口驱动 xff0c 实现printf函数的重定向 xff0c 实现串口的中断接受和发送 xff0c 效仿modbus协议中的3 5T超时机
  • 天地飞6通道遥控器解码

    在做四轴 xff0c 想到要改造一下遥控器 我用的是天地飞6通道2 4G版 改造的思路是这样的 xff1a 用M8单片机读取PPM信号 xff0c 用液晶显示出6个通道的解码 xff0c 当然这个解码还需要根据飞控板进行一下 校准 xff0
  • 如何计算网络协议校验和以及为什么这么计算

    一 校验和是如何计算的 xff1f 这个问题牵扯到计算机最底层最神秘的部分 二进制运算 说实话 xff0c 从学计组计统起 xff0c 我就比较讨厌思考二进制的相关运算 但毕竟是学这个的 xff0c 只好硬着头皮想了 首先发送方校验和的计算
  • JAVA又臭又长,是一门垃圾语言,早晚会被淘汰

    1 JAVA又臭又长 xff0c 是一门垃圾语言 xff0c 早晚会被淘汰 2 JAVA能做的 xff0c python 等上层解释类语言大部分都可以坐到 3 JAVA为了面向对象而面向对象 xff0c 导致程序冗长 xff0c 效率低下

随机推荐