STM32开发入门之串口详解

2023-04-14 12:00   393   0  

  一、通信接口


  处理器与外部设备通信的两种方式:


  并行通信:


  -传输原理:数据各个位同时传输。


  -优点:速度快


  -缺点:占用引脚资源多


a4debb00-ac52-11ed-bcd2-b8ca3a6cb5c4.webp


  串行通信:


  -传输原理:数据按位顺序传输。


  -优点:占用引脚资源少


  -缺点:速度相对较慢


a4debb01-ac52-11ed-bcd2-b8ca3a6cb5c4.webp




  串行通信,按照数据传送方向,分为:


  单工:


  数据传输只支持数据在一个方向上传输


a4debb02-ac52-11ed-bcd2-b8ca3a6cb5c4.webp


  半双工:


  允许数据在两个方向上传输,但是,在某一时刻,只允许数


  据在一个方向上传输,它实际上是一种切换方向的单工通信;


a4debb03-ac52-11ed-bcd2-b8ca3a6cb5c4.webp




  全双工:


  允许数据同时在两个方向上传输,因此,全双工通信是两个


  单工通信方式的结合,它要求发送设备和接收设备都有独立


  的接收和发送能力。


a4debb04-ac52-11ed-bcd2-b8ca3a6cb5c4.jpg


  串行通信的通信方式:


  **同步通信:**带时钟同步信号传输。


  如:SPI,IIC通信接口


  **异步通信:**不带时钟同步信号。


  如:UART(通用异步收发器),单总线


  常见的串行通信接口:


a4debb05-ac52-11ed-bcd2-b8ca3a6cb5c4.webp




  二、STM32的串口通信接口


  UART:通用异步收发器(universal asynchronous receiver and transmitter)


  USART:通用同步异步收发器(universal synchronous asynchronous receiver and transmitter)


  其中:


  通用同步异步收发器(USART)


  小容量产品:是指闪存存储器容量在16K至32K字节之间的STM32F101xx、 STM32F102xx和STM32F103xx微控制器。


  中容量产品:是指闪存存储器容量在64K至128K字节之间的STM32F101xx、 STM32F102xx和STM32F103xx微控制器。


  大容量产品:是指闪存存储器容量在256K至512K字节之间的STM32F101xx和STM32F103xx微控制器。


  互联型产品:是指STM32F105xx和STM32F107xx微控制器。


  除非特别说明,本章描述的模块适用于整个STM32F10xxx微控制器系列。


  我使用的是 STM32F105xx,所以是互联型产品,包含3个USART和2个UART。(USART1/USART2/USART3/UART4/UART5)


  三、UART异步通信方式引脚连接方法


  -RXD:数据输入引脚。数据接收。


  -TXD:数据发送引脚。数据发送。


  串口交叉线


a4debb06-ac52-11ed-bcd2-b8ca3a6cb5c4.webp


  串口直通线


a4debb07-ac52-11ed-bcd2-b8ca3a6cb5c4.webp


  四、UART异步通信方式特点


  ● 全双工的,异步通信


  ● NRZ标准格式


  ● 分数波特率发生器系统


  ─ 发送和接收共用的可编程波特率,最高达4.5Mbits/s


  ● 可编程数据字长度(8位或9位)


  ● 可配置的停止位-支持1或2个停止位


  ● LIN主发送同步断开符的能力以及LIN从检测断开符的能力


  ─ 当USART硬件配置成LIN时,生成13位断开符;检测10/11位断开符


  ● 发送方为同步传输提供时钟


  ● IRDA SIR 编码器解码器


  ─ 在正常模式下支持3/16位的持续时间


  ● 智能卡模拟功能


  ─ 智能卡接口支持ISO7816-3标准里定义的异步智能卡协议


  ─ 智能卡用到的0.5和1.5个停止位


  ● 单线半双工通信


  ● 可配置的使用DMA的多缓冲器通信


  ─ 在SRAM里利用集中式DMA缓冲接收/发送字节


  ● 单独的发送器和接收器使能位


  ● 检测标志


  ─ 接收缓冲器满


  ─ 发送缓冲器空


  ─ 传输结束标志


  ● 校验控制


  ─ 发送校验位


  ─ 对接收数据进行校验


  ● 四个错误检测标志


  ─ 溢出错误


  ─ 噪音错误


  ─ 帧错误


  ─ 校验错误


  ● 10个带标志的中断源


  ─ CTS改变


  ─ LIN断开符检测


  ─ 发送数据寄存器空


  ─ 发送完成


  ─ 接收数据寄存器满


  ─ 检测到总线为空闲


  ─ 溢出错误


  ─ 帧错误


  ─ 噪音错误


  ─ 校验错误


  ● 多处理器通信 – 如果地址不匹配,则进入静默模式


  ● 从静默模式中唤醒(通过空闲总线检测或地址标志检测)


  ● 两种唤醒接收器的方式:地址位(MSB,第9位),总线空闲


  五、串口通信过程


a4debb08-ac52-11ed-bcd2-b8ca3a6cb5c4.webp


  六、STM32串口异步通信需要定义的参数


  起始位


  数据位(8位或者9位)


  奇偶校验位(第9位)


  停止位(1,15,2位)


  波特率设置


a4debb09-ac52-11ed-bcd2-b8ca3a6cb5c4.webp


  七、串口配置


  串口设置的一般步骤可以总结为如下几个步骤:


  1、串口时钟使能,GPIO时钟使能


  2、串口复位


  3、GPIO端口模式设置


  4、串口参数初始化


  5、开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤)


  6、使能串口


  7、编写中断处理函数




  下面, 我们就简单介绍下这几个与串口基本配置直接相关的几个固件库函数。 这些函数和定义主要分布在 stm32f10x_usart.h 和stm32f10x_usart.c 文件中。


  1.串口时钟使能。 串口是挂载在 APB2 下面的外设,所以使能函数为:


  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1);




  2.串口复位。 当外设出现异常的时候可以通过复位设置,实现该外设的复位,然后重新配置这个外设达到让其重新工作的目的。一般在系统刚开始配置外设的时候,都会先执行复位该外设的操作。 复位的是在函数 USART_DeInit()中完成:


  void USART_DeInit(USART_TypeDef* USARTx);//串口复位




  比如我们要复位串口 1,方法为:


  USART_DeInit(USART1); //复位串口 1




  3.串口参数初始化。 串口初始化是通过 USART_Init()函数实现的,


  void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);




  这个函数的第一个入口参数是指定初始化的串口标号,这里选择 USART1。


  第二个入口参数是一个 USART_InitTypeDef 类型的结构体指针, 这个结构体指针的成员变量用来设置串口的一些参数。 一般的实现格式为:




  USART_InitStructure.USART_BaudRate = bound; //波特率设置;


  USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为 8  位数据格式


  USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位


  USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位


  USART_InitStructure.USART_HardwareFlowControl


  = USART_HardwareFlowControl_None; //无硬件数据流控制


  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式


  USART_Init(USART1, &USART_InitStructure); //初始化串口






  从上面的初始化格式可以看出初始化需要设置的参数为:波特率,字长,停止位,奇偶校验位,硬件数据流控制,模式(收,发)。 我们可以根据需要设置这些参数。




  4.数据发送与接收。 STM32 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包含了 TDR 和 RDR。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。




  STM32 库函数操作 USART_DR 寄存器发送数据的函数是:




  void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);


  通过该函数向串口寄存器 USART_DR 写入一个数据。




  STM32 库函数操作 USART_DR 寄存器读取串口接收到的数据的函数是:




  uint16_t USART_ReceiveData(USART_TypeDef* USARTx);


  通过该函数可以读取串口接受到的数据。




  5.串口状态。 串口的状态可以通过状态寄存器 USART_SR 读取。 USART_SR 的各位描述如图 9.1.1 所示:


a4debb0a-ac52-11ed-bcd2-b8ca3a6cb5c4.webp


  这里我们关注一下两个位,第 5、 6 位 RXNE 和 TC。




  RXNE(读数据寄存器非空),当该位被置 1 的时候,就是提示已经有数据被接收到了,并且可以读出来了。这时候我们要做的就是尽快去读取 USART_DR,通过读 USART_DR 可以将该位清零,也可以向该位写 0,直接清除。




  TC(发送完成),当该位被置位的时候,表示 USART_DR 内的数据已经被发送完成了。如果设置了这个位的中断,则会产生中断。该位也有两种清零方式: 1)读 USART_SR,写USART_DR。 2)直接向该位写 0。




  状态寄存器的其他位我们这里就不做过多讲解,大家需要可以查看中文参考手册。




  在我们固件库函数里面,读取串口状态的函数是:




  FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t  USART_FLAG);


  这个函数的第二个入口参数非常关键, 它是标示我们要查看串口的哪种状态, 比如上面讲解的RXNE(读数据寄存器非空)以及 TC(发送完成)。例如我们要判断读寄存器是否非空(RXNE), 操作库函数的方法是:




  USART_GetFlagStatus(USART1, USART_FLAG_RXNE);


  我们要判断发送是否完成(TC),操作库函数的方法是:




  USART_GetFlagStatus(USART1, USART_FLAG_TC);


  这些标识号在 MDK 里面是通过宏定义定义的:




  #define USART_IT_PE ((uint16_t)0x0028)


  #define USART_IT_TXE ((uint16_t)0x0727)


  #define USART_IT_TC ((uint16_t)0x0626)


  #define USART_IT_RXNE ((uint16_t)0x0525)


  #define USART_IT_IDLE ((uint16_t)0x0424)


  #define USART_IT_LBD ((uint16_t)0x0846)


  #define USART_IT_CTS ((uint16_t)0x096A)


  #define USART_IT_ERR ((uint16_t)0x0060)


  #define USART_IT_ORE ((uint16_t)0x0360)


  #define USART_IT_NE ((uint16_t)0x0260)


  #define USART_IT_FE ((uint16_t)0x0160)


  6.串口使能。 串口使能是通过函数 USART_Cmd()来实现的,这个很容易理解,使用方法是:




  USART_Cmd(USART1, ENABLE); //使能串口


  7.开启串口响应中断。 有些时候当我们还需要开启串口中断,那么我们还需要使能串口中断,使能串口中断的函数是:




  void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT,


  FunctionalState NewState)


  这个函数的第二个入口参数是标示使能串口的类型, 也就是使能哪种中断, 因为串口的中断类型有很多种。 比如在接收到数据的时候(RXNE 读数据寄存器非空),我们要产生中断,那么我们开启中断的方法是:




  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断,接收到数据中断


  我们在发送数据结束的时候(TC, 发送完成) 要产生中断,那么方法是:




  USART_ITConfig(USART1, USART_IT_TC, ENABLE);


  8.获取相应中断状态。 当我们使能了某个中断的时候,当该中断发生了,就会设置状态寄存器中的某个标志位。 经常我们在中断处理函数中,要判断该中断是哪种中断,使用的函数是:




  ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)


  比如我们使能了串口发送完成中断,那么当中断发生了, 我们便可以在中断处理函数中调用这个函数来判断到底是否是串口发送完成中断,方法是:




  USART_GetITStatus(USART1, USART_IT_TC)


  返回值是 SET,说明是串口发送完成中断发生。




  八、串口程序完整代码




  参看:USART串口通信配置




   #include "stm32f10x.h"




  u8 Uart1_Get_Flag = 0;




  // 串口初始化函数




  void My_USART1_Init(void)




  {




  GPIO_InitTypeDef GPIO_InitStrue;




  USART_InitTypeDef USART_InitStrue;




  NVIC_InitTypeDef NVIC_InitStrue;




  // 1,使能GPIOA,USART1时钟




  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);




  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);




  // 2,设置PGIO工作模式-PA9 PA10复用为串口1




  GPIO_InitStrue.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出




  GPIO_InitStrue.GPIO_Pin=GPIO_Pin_9;//USART1_TX PA.9




  GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;




  GPIO_Init(GPIOA,&GPIO_InitStrue); //初始化 GPIOA.9




  GPIO_InitStrue.GPIO_Mode=GPIO_Mode_IN_FLOATING;//浮空输入




  GPIO_InitStrue.GPIO_Pin=GPIO_Pin_10;//USART1_RX PA.10




  GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;




  GPIO_Init(GPIOA,&GPIO_InitStrue); //初始化 GPIOA.10




  // 3,串口1初始化配置




  USART_InitStrue.USART_BaudRate=115200;//波特率设置




  USART_InitStrue.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//无硬件数据流控制




  USART_InitStrue.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;//收发模式




  USART_InitStrue.USART_Parity=USART_Parity_No; //无奇偶校验位




  USART_InitStrue.USART_StopBits=USART_StopBits_1; //一个停止位




  USART_InitStrue.USART_WordLength=USART_WordLength_8b;//字长为 8 位




  USART_Init(USART1,&USART_InitStrue);//初始化串口




  // 4,打开串口1




  USART_Cmd(USART1,ENABLE);//使能串口




  // 5,使能串口1中断-接收数据完成中断




  USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启中断




  // 6,设置中断优先级-主函数中设置中断优先级分组




  NVIC_InitStrue.NVIC_IRQChannel=USART1_IRQn;




  NVIC_InitStrue.NVIC_IRQChannelCmd=ENABLE;//IRQ 通道使能




  NVIC_InitStrue.NVIC_IRQChannelPreemptionPriority=1;//抢占优先级 1




  NVIC_InitStrue.NVIC_IRQChannelSubPriority=1;//子优先级 1




  NVIC_Init(&NVIC_InitStrue);//中断优先级初始化




  }




  void USART1_Puts(char * str)




  {




  while(*str)




  {




  USART_SendData(USART1, *str++);




  while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);




  }




  }




  // 中断服务函数




  void USART1_IRQHandler(void)




  {




  u8 res;




  if(USART_GetITStatus(USART1,USART_IT_RXNE))// 接收到数据




  {




  USART_ClearITPendingBit(USART1,USART_IT_RXNE);




  res= USART_ReceiveData(USART1); // 获得串口1接收到的数据




  Uart1_Get_Flag=1;




  }




  }




  // 主函数




  int main(void)




  {




  // 设置中断优先级分组位2 - 2位抢占2位相应




  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);




  // 调用函数 初始化USART1相关引脚配置




  My_USART1_Init();




  if (Uart1_Get_Flag){




  Uart1_Get_Flag = 0;




  USART1_Puts(res);




  }




  return 0;




  }




 }


  九、串口其他需要了解的




  串口相关寄存器




  上面串口部分其实基本上已经讲完了。




  但是我们用的库函数版本是3.5的。在串口配置中还要配置USART 时钟的。




  首先看下串口相关的寄存器:




  USART_SR 状态寄存器


  USART_DR 数据寄存器


  USART_BRR 波特率寄存器


  USART_CR1 控制寄存器


  具体的配置看STM32中文参考手册了解一下,直接用串口库函数就好了。




  十、串口操作相关库函数




  获取状态标志位函数-操作USART_SR寄存器




  // 获取状态标志位


  FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t  USART_FLAG);


  // 清除状态标志位


  void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);


  // 获取中断状态标志位


  ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);


  // 清除中断状态标志位


  void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);


  接收发送数据函数-操作USART_DR寄存器




  // 发送数据到串口(通过写USART_DR寄存器发送数据)


  void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);


  // 接收数据(从USART_DR寄存器读取接收到的数据)


  uint16_t USART_ReceiveData(USART_TypeDef* USARTx);


  串口配置函数




  // 串口初始化:波特率,数据字长,奇偶校验,硬件流控以及收发使能


  void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef*  USART_InitStruct);


  // 使能串口


  void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);


  // 使能相关中断


  void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT,  FunctionalState NewState);


  我们用的库函数V3.5里面还有:


a4debb0b-ac52-11ed-bcd2-b8ca3a6cb5c4.webp


  串口复位




  void USART_DeInit(USART_TypeDef* USARTx)


  串口时钟的初始化:


  void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef*  USART_ClockInitStruct)


  串口通信DMA中断


  void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq,  FunctionalState NewState)


  所以除了上述的串口配置,还可以添加如下的配置:




  USART_InitTypeDef USART_InitStrue;


  USART_ClockInitTypeDef USART_CLK_InitStrue;


  USART_InitStrue.USART_BaudRate = baud_rate;//波特率设置


  USART_InitStrue.USART_WordLength = USART_WordLength_8b;//字长为 8 位


  USART_InitStrue.USART_StopBits = USART_StopBits_1;//一个停止位


  USART_InitStrue.USART_Parity = USART_Parity_No ;//无奇偶校验位


  USART_InitStrue.USART_HardwareFlowControl =  USART_HardwareFlowControl_None;//无硬件数据流控制


  USART_InitStrue.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式


  USART_CLK_InitStrue.USART_Clock = USART_Clock_Disable;//USART 时钟失能


  USART_CLK_InitStrue.USART_CPOL = USART_CPOL_Low;//SCLK引脚上时钟输出的极性


  USART_CLK_InitStrue.USART_CPHA = USART_CPHA_2Edge;//SLCK引脚上时钟输出的相位


  USART_CLK_InitStrue.USART_LastBit = USART_LastBit_Disable;//是否在同步模式下


  USART_Init(USART1,&USART_InitStrue);//初始化串口


  USART_ClockInit(USART1, &USART_CLK_InitStrue);//初始化USART时


  PS:刚开始有点不太理解,为什么要用到串口时钟。




  回顾了一下上面讲到的:




  我使用的是 STM32F105xx,所以是互联型产品,包含3个USART和2个UART。(USART1/USART2/USART3/UART4/UART5)


登录icspec成功后,会自动跳转查看全文
博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。