【STM32】串口和printf

1.数据通信的基本知识

1.串行/并行通信

【STM32】串口和printf_第1张图片

2.单工/半双工/全双工通信

类似于【广播  对讲 电话】

不是有两根线就是全双工,而是输入和输出都有对应的数据线。

【STM32】串口和printf_第2张图片

3.同步/异步通信

区分同步/异步通信的根本:判断是否有时钟信号(时钟线)。如果有时钟线则是同步通信,如果没有时钟线则是异步通信。

【STM32】串口和printf_第3张图片

4.波特率  VS  比特率

【STM32】串口和printf_第4张图片

5.常见的串行通信接口

【STM32】串口和printf_第5张图片

2.串口(RS-232)

1.什么是串口

实际上就是按位来对数据进行发送和接收

【STM32】串口和printf_第6张图片

2.RS232电平和CMOS/TTL电平对比

【STM32】串口和printf_第7张图片

3.设备间的RS232通信示意图

MAX323:将RS232电平---》TTL/CMOS电平

【STM32】串口和printf_第8张图片

 4.STM32串口与电脑USB口通信示意图

USB/串口转换电路CH340C:USB电平---》TTL/CMOS电平

【STM32】串口和printf_第9张图片

5.RS232异步通信协议

注意:异步通信协议中是没有使用到时钟线(SCLK)

【STM32】串口和printf_第10张图片

3.STM32USART

1.STM32USART简介

USART虽然是同步和异步都可以进行使用,但是我们大多数情况下使用的都是异步通信

【STM32】串口和printf_第11张图片

2.STM32USART主要特征

【STM32】串口和printf_第12张图片

3.在选型手册中查看USART/UART对应的引脚定义

【STM32】串口和printf_第13张图片

4.STM32F1的USART框图

1.引脚说明

【STM32】串口和printf_第14张图片

2.接收/发送数据的存放/处理位置

数据要先放在DR寄存器中,然后再通过DR寄存器操作CPU

【STM32】串口和printf_第15张图片

3.波特率的处理

【STM32】串口和printf_第16张图片

5.框图简化版

【STM32】串口和printf_第17张图片

【STM32】串口和printf_第18张图片

4.设置USART/UART波特率

1.计算公式

【STM32】串口和printf_第19张图片

baud:波特率是用户自己定义的,然后求出USARTDIV,然后分为整数部分和小数部分,在存储到USART_BRR寄存器

【STM32】串口和printf_第20张图片

2.波特比率寄存器(BRR)

【STM32】串口和printf_第21张图片

3.举个例子

【STM32】串口和printf_第22张图片

4.公式推导

因为小数部分和整数部分都要往左移动4位,所以将整个值*16

【STM32】串口和printf_第23张图片

5.USART寄存器

1.控制寄存器 1(USART_CR1)

【STM32】串口和printf_第24张图片

【STM32】串口和printf_第25张图片

2.控制寄存器 2(USART_CR2)

【STM32】串口和printf_第26张图片

3. 控制寄存器 3(USART_CR3)

【STM32】串口和printf_第27张图片

【STM32】串口和printf_第28张图片

4. 数据寄存器(USART_DR)

1)具体传输多少位取决于寄存器USART_CR1中的位12M【字长】

2)设置好控制和波特率寄存器后,往该寄存器写入数据即可发送,接收数据则读该寄存器

【STM32】串口和printf_第29张图片

5.状态寄存器(USART_SR)

根据TC位(发送完成位)可以知道能否发数据,根据RXNE位(读数据寄存器非空)知道是否收到数据。

【STM32】串口和printf_第30张图片

【STM32】串口和printf_第31张图片6.需要配置的时序总结

【STM32】串口和printf_第32张图片

6.USART/UART异步通信配置步骤

【STM32】串口和printf_第33张图片

1.HAL_USART_Init

对USART进行初始化

【STM32】串口和printf_第34张图片

【STM32】串口和printf_第35张图片

2.HAL_USART_Receive_IT

USART的中断使能函数

这个函数是非阻塞式的,没有执行完也可以出来

【STM32】串口和printf_第36张图片

3.HAL_USART_Receive

没有开启中断的UASRT

这个函数是阻塞式的,没有执行完不可以出来

【STM32】串口和printf_第37张图片

7.通过串口接收或者发送一个字符

【STM32】串口和printf_第38张图片

1.连接注意点

【STM32】串口和printf_第39张图片

【STM32】串口和printf_第40张图片

2.原理图分析

【STM32】串口和printf_第41张图片

3.代码编写

【STM32】串口和printf_第42张图片

usart_init()

相关的设置参数和开启usart的中断

【STM32】串口和printf_第43张图片

【STM32】串口和printf_第44张图片

hal_usart_mspinit()

1)使能USART1和对应的IO时钟

2)初始化IO

3)使能USART1中断,设置NVIC优先级

【STM32】串口和printf_第45张图片

usart1_IRQHandler

【STM32】串口和printf_第46张图片

中断回调服务函数

2)HAL_UART_iRQHandler()会清除中断

【STM32】串口和printf_第47张图片

HAL_USART_RxCpltCallback

串口接收数据的回调函数

【STM32】串口和printf_第48张图片

1.CubeMX和HAL库的串口实战

1、CubeMX中打开并设置串口

1.设置对应的RCC

【STM32】串口和printf_第49张图片

2.查看原理图上对应的RX和TX

注意点:我们的串口对应的应该是RX--》TX,TX--》RX

我们要先查看原理图中是否已经帮我们接反了,如果没有则需要我们自己手动接反。

【STM32】串口和printf_第50张图片

【STM32】串口和printf_第51张图片

我们使用的STM32F103C8xx直接接入USB口即可

由上面分析可知,我们使能PA9和PA10即可

【STM32】串口和printf_第52张图片

3.设置相关的USART

【STM32】串口和printf_第53张图片

4.设置相关的时钟

【STM32】串口和printf_第54张图片

2、串口操作

(1)阻塞模式串口发送:CPU不做其他事情

MCU的CPU一个字节一个字节的将要发送的内容丢给串口模块,然后看着串口模块将这个字节发送出去,然后CPU再去拿下一个字节来丢给串口模块。直到本次要发送的所有字节全部发完,CPU才会去做其他事。

(2)中断模式串口发送:CPU轮询式查询

MCU的CPU向串口模块丢一个字节,然后串口模块慢慢发,CPU丢完这个字节后会跳出去做其他事情,等串口模块发完这个字节后会生成一个中断,中断会通知CPU过来继续丢下一个字节。

2.源码分析和串口发送的实现

1、阻塞式串口发送/接收

1.1 阻塞式发送:HAL_UART_Transmit(常用)

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint16_t *tmp;
  uint32_t tickstart = 0U;

  /* Check that a Tx process is not already ongoing */
  //查看状态
  if (huart->gState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return  HAL_ERROR;
    }

    /* Process Locked */
	//将串口模块锁住了,将变量的值设置为"HAL_LOCKED"
    __HAL_LOCK(huart);

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;

    /* Init tickstart for timeout managment */
	//获取当前的时候,用于判断是否超时
    tickstart = HAL_GetTick();

    huart->TxXferSize = Size;//表示要发送的个数
    huart->TxXferCount = Size;//还要发送的个数=总个数-已经发送
    while (huart->TxXferCount > 0U)
    {
      huart->TxXferCount--;
      if (huart->Init.WordLength == UART_WORDLENGTH_9B)
      {
     	 //等待标志发生,查看是否超时
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;//超时
        }
        tmp = (uint16_t *) pData;
        huart->Instance->DR = (*tmp & (uint16_t)0x01FF);
        if (huart->Init.Parity == UART_PARITY_NONE)
        {
          pData += 2U;
        }
        else
        {
          pData += 1U;
        }
      }
      else
      {
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;
        }
		//真正执行操作的代码
        huart->Instance->DR = (*pData++ & (uint8_t)0xFF);
      }
    }
	//查看发送是否已经完成【阻塞等待串口发完】
    if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
    {
      return HAL_TIMEOUT;
    }

    /* At end of Tx process, restore huart->gState to Ready */
    huart->gState = HAL_UART_STATE_READY;

    /* Process Unlocked */
	//解锁
    __HAL_UNLOCK(huart);

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

【STM32】串口和printf_第55张图片

【STM32】串口和printf_第56张图片

1.2 阻塞式接收:HAL_UART_Receive(不常用)

HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint16_t *tmp;
  uint32_t tickstart = 0U;

  /* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return  HAL_ERROR;
    }

    /* Process Locked */
    __HAL_LOCK(huart);

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->RxState = HAL_UART_STATE_BUSY_RX;

    /* Init tickstart for timeout managment */
    tickstart = HAL_GetTick();

    huart->RxXferSize = Size;
    huart->RxXferCount = Size;

    /* Check the remain data to be received */
    while (huart->RxXferCount > 0U)
    {
      huart->RxXferCount--;
      if (huart->Init.WordLength == UART_WORDLENGTH_9B)
      {
      //检查UART_FLAG_RXNE是否为空,如果为非空,表示接收到数据
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;
        }
        tmp = (uint16_t *) pData;
        if (huart->Init.Parity == UART_PARITY_NONE)
        {
          *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
          pData += 2U;
        }
        else
        {
          *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
          pData += 1U;
        }

      }
      else
      {
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;
        }
        if (huart->Init.Parity == UART_PARITY_NONE)
        {
          *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
        }
        else
        {
          *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
        }

      }
    }

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    return HAL_OK;
  }

3.延时时间的设置

如果我们设置的延时时间太长,则我们可能会在延时的时候错过一些字符的发送和接收。

【STM32】串口和printf_第57张图片

【STM32】串口和printf_第58张图片

 【STM32】串口和printf_第59张图片

4.总结

int main(void)
{
  //定义一个要进行发送的数据
  uint8_t sbuf[8]="stm32";
  //定义一个要进行接收的数据
  uint8_t rbuf[20]="";
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  while (1)
  {
		//第一个参数要传入的是地址
		//size:要发送的大小
		//0x0000ffff:超时时间
	//	HAL_UART_Transmit(&huart1,sbuf,5,0x0000ffff);
	//	HAL_Delay(1000);
		
		//阻塞式的接收:接收一个后马上接着下一个接收
		//此方法最好一次发送一个bit
		HAL_UART_Receive(&huart1,rbuf,1,0x0000ffff);
		//将接收到的数值发送出来
		HAL_UART_Transmit(&huart1,rbuf,1,0x0000ffff);
		//这个延时时间不能太久,要不然可能会在延时的时候接收了一个字符,从而错过这个
		//HAL_Delay(100);
  }
}

阻塞式的发送实际用的很多,因为编程简单。缺陷是浪费高速CPU的部分性能,没有追求到串口发送和整个系统性能的最高。

2、非阻塞式(中断)串口发送

1.HAL_UART_Transmit_IT

这个函数没有超时时间,因为我们不用等。

注意点::要打开中断

HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  /* Check that a Tx process is not already ongoing */
  if (huart->gState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return HAL_ERROR;
    }

    /* Process Locked */
    __HAL_LOCK(huart);

//因为我们使用中断方式,则需要在串口内部定义一个buf,使得buf指向data的地址
    huart->pTxBuffPtr = pData;
    huart->TxXferSize = Size;
    huart->TxXferCount = Size;

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    /* Enable the UART Transmit data register empty Interrupt */
	//使能UART发送数据寄存器空中断
    __HAL_UART_ENABLE_IT(huart, UART_IT_TXE);

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

2.注意点 

我们使用这个中断式的应该打开Usart的中断

【STM32】串口和printf_第60张图片

【STM32】串口和printf_第61张图片

int main(void)
{
	//定义一个要进行发送的数据
	uint8_t sbuf[8]="stm32";
	//定义一个要进行接收的数据
	uint8_t rbuf[20]="";
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  while (1)
  {
		/**
			中断式发送和接收
		*/
		HAL_UART_Transmit_IT(&huart1,sbuf,5);
		HAL_Delay(100);
  }
}

3.printf的实现

printf的实现其实就是重载fputc函数

1.原始代码 

#ifdef __GNUC__
	#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
	#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
  return ch;
}

2.注意点1: 

一定一定要勾选User MicroLIB

【STM32】串口和printf_第62张图片

3.注意点2:

我们使用到了fputc,其中FILE中是定义在中的宏定义

定义在usart.h中

【STM32】串口和printf_第63张图片

【STM32】串口和printf_第64张图片

int main(void)
{
  /* USER CODE BEGIN 1 */
	//定义一个要进行发送的数据
	uint8_t sbuf[8]="stm32";
	//定义一个要进行接收的数据
	uint8_t rbuf[20]="";
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  while (1)
  {
	float a=4.532;
	printf("a=%f\n",a);
	printf("test\r");
  }
}

4.串口接收编程实战

1、阻塞式串口接收

CPU等着过来。所以当CPU临时去处理其他事情时,可能会错过一些串口的输出。所以我们基本上不用。如果真的要使用,则要协调好延时和串口的接收和发送。

2、中断式串口接收

使用中断的方式向串口发送数据并且输出

1.先开启uart中断

【STM32】串口和printf_第65张图片

【STM32】串口和printf_第66张图片

2.写中断处理函数:HAL_UART_RxCpltCallback

在main函数中

//rc用来暂存和处理串口接收到的字节内容的
uint8_t receive_char;

//这个就是HAL库对接的中断处理程序
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	//判断是否为usart1
	if(huart->Instance == USART1)
	{
		//这里就是真正的中断处理代码
		//我们这里的处理就是接收到一个字节后原封不动的发回去
		HAL_UART_Transmit(&huart1, &receive_char, 1,0xFF);//发送字符
		//等待本次接收完毕,同时开启下一次接收【更新receive_char】
		//HAL_UART_Receive_IT:开启中断处理流程
		while(HAL_UART_Receive_IT(&huart1, &receive_char, 1) != HAL_OK);
	}

}

【STM32】串口和printf_第67张图片

【STM32】串口和printf_第68张图片

3.注册中断

由上面分析可以知道,我们在进入中断处理函数中,是将接收到的字符发送出去,所以我们需要在外部先接收到字符【触发中断】,才可以发送。所以我们需要在其他模块初始化的位置先进入一次中断,接收串口发送的数据。

【STM32】串口和printf_第69张图片

3.测试代码

此代码:在我们不向串口发送字符时,每3s发送一次a,当我们向串口发送数据时,会马上在串口输出。

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	//从此处进入中断处理程序
  HAL_UART_Receive_IT(&huart1, &receive_char, 1);
  while (1)
  {
	float a=4.532;
	printf("a=%f\n",a);
	HAL_Delay(3000);
  }
}

//这个就是HAL库对接的中断处理程序
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	//判断是否为usart1
	if(huart->Instance == USART1)
	{
		//这里就是真正的中断处理代码
		//我们这里的处理就是接收到一个字节后原封不动的发回去
		HAL_UART_Transmit(&huart1, &receive_char, 1,0xFF);//发送字符
		//等待本次接收完毕,同时开启下一次接收【更新receive_char】
		//HAL_UART_Receive_IT:开启中断处理流程
		while(HAL_UART_Receive_IT(&huart1, &receive_char, 1) != HAL_OK);
	}

}

3.处理串口中断的流程

【STM32】串口和printf_第70张图片

【STM32】串口和printf_第71张图片

【STM32】串口和printf_第72张图片

在初始化部分已经将中断连接起来,到时候产生中断则直接进入【HAL_UART_RxCpltCallback】这个函数

【STM32】串口和printf_第73张图片

5.案例1:基于串口的命令shell实现

命令shell:发送一个命令返回一个回应

1.协议自定义

(1)指令集:指令1:add    指令2:sub
(2)指令结束符:';'【定义普通指令中不可能出现的】
(3)指令中遇到回车和空格、Tab等特殊字符怎么办

2.实现思路分析

1.定义一个缓冲区,存储接到的数据

【STM32】串口和printf_第74张图片

2.定义一个索引值

值存储再buf中的第几个字节

3.代码编写

【STM32】串口和printf_第75张图片

【STM32】串口和printf_第76张图片

/* USER CODE BEGIN 4 */
//这个就是HAL库对接的中断处理程序
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	//判断是否为usart1
	if(huart->Instance == USART1)
	{
		//将receive_char的数值存放再buf中
		rev_buf[tindex]=receive_char;//暂存
		tindex++;//指向下一个
		while(HAL_UART_Receive_IT(&huart1, &receive_char, 1) != HAL_OK);
	}

}

4.main函数编写

1)判断指令

2)buf和index要记得清空

6.串口实验

1.串口接收数据过程

【STM32】串口和printf_第77张图片

2.串口发送数据过程

【STM32】串口和printf_第78张图片

你可能感兴趣的:(STM32,stm32,嵌入式硬件,单片机)