通用同步/异步收发器(USART)
STM32F103xC、 STM32F103xD和STM32F103xE增强型系列产品中,内置了3个通用同步/异步收发器(USART1、 USART2和USART3),和2个通用异步收发器(UART4和UART5)。这5个接口提供异步通信、支持IrDA SIR ENDEC传输编解码、多处理器通信模式、单线半双工通信模式和LIN主/从功能。USART1接口通信速率可达4.5兆位/秒,其他接口的通信速率可达2.25兆位/秒。USART1、 USART2和USART3接口具有硬件的CTS和RTS信号管理、兼容ISO7816的智能卡模式和类SPI通信模式,除了UART5之外所有其他接口都可以使用DMA操作。
1.USART主要特性
1.1.全双工的,异步通信
1.2.NRZ标准格式
1.3.分数波特率发生器系统
1.3.1发送和接收共用的可编程波特率,最高达4.5Mbits/s
1.4.可编程数据字长度(8位或9位)
1.5.可配置的停止位-支持1或2个停止位
1.6.LIN主发送同步断开符的能力以及LIN从检测断开符的能力
1.6.1当USART硬件配置成LIN时,生成13位断开符;检测10/11位断开符
1.7.发送方为同步传输提供时钟
1.8.IRDA SIR 编码器解码器
1.8.1.在正常模式下支持3/16位的持续时间
1.9.智能卡模拟功能
1.9.1.智能卡接口支持ISO7816-3标准里定义的异步智能卡协议
1.9.2.智能卡用到的0.5和1.5个停止位
1.10.单线半双工通信
1.11.可配置的使用DMA的多缓冲器通信
1.11.1.在SRAM里利用集中式DMA缓冲接收/发送字节
1.12.单独的发送器和接收器使能位
1.13.检测标志
1.13.1.接收缓冲器满
1.13.2.发送缓冲器空
1.13.3.传输结束标志
1.14.校验控制
1.14.1.发送校验位
1.14.2.对接收数据进行校验
1.15.四个错误检测标志
1.15.1.溢出错误
1.15.2.噪音错误
1.15.3.帧错误
1.15.4.校验错误
1.16.10个带标志的中断源
1.16.1.CTS改变
1.16.2.LIN断开符检测
1.16.3.发送数据寄存器空
1.16.4.发送完成
1.16.5.接收数据寄存器满
1.16.6.检测到总线为空闲
1.16.7.溢出错误
1.16.8.帧错误
1.16.9.噪音错误
1.16.10.校验错误
1.17.多处理器通信 -- 如果地址不匹配,则进入静默模式
1.18.从静默模式中唤醒(通过空闲总线检测或地址标志检测)
1.19.两种唤醒接收器的方式:地址位(MSB,第9位),总线空闲
2.USART功能概述
接口通过三个引脚与其他设备连接在一起(见下图)。任何USART双向通信至少需要两个脚:接收数据输入(RX)和发送数据输出(TX)。
RX:接收数据输入。通过过采样技术来区别数据和噪音,从而恢复数据。
TX:发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发送和接收。
2.1.总线在发送或接收前应处于空闲状态
2.2.一个起始位
2.3.一个数据字(8或9位),最低有效位在前
2.4.0.5,1,1.5, 2个的停止位,由此表明数据帧的结束
2.5.使用分数波特率发生器 —— 12位整数和4位小数的表示方法。
2.6.一个状态寄存器(USART_SR)
2.7.数据寄存器(USART_DR)
2.8.一个波特率寄存器(USART_BRR), 12位的整数和4位小数
2.9.一个智能卡模式下的保护时间寄存器(USART_GTPR)
在同步模式中需要下列引脚:
CK:发送器时钟输出。此引脚输出用于同步传输的 时钟, (在Start位和Stop位上没有时钟脉冲,软件可选地,可以在最后一个数据位送出一个时钟脉冲)。数据可以在RX上同步被接收。这可以用来控制带有移位寄存器的外部设备(例如LCD驱动器)。时钟相位和极性都是软件可编程的。在智能卡模式里,CK可以为智能卡提供时钟。
在IrDA模式里需要下列引脚:
IrDA_RDI: IrDA模式下的数据输入;
IrDA_TDO: IrDA模式下的数据输出。
下列引脚在硬件流控模式中需要:
nCTS: 清除发送,若是高电平,在当前数据传输结束时阻断下一次的数据发送;
nRTS: 发送请求,若是低电平,表明USART准备好接收数据。
下面以STM32F103ZET6为例,通过库函数结合寄存器配置来介绍一下通用同步异步收发器的异步收发器使用(项目验证运行稳定)。
串口设置的一般(常规)步骤可以总结为如下几个步骤:
1) 串口时钟使能, GPIO 时钟使能;
2) 串口复位 (可省略);
3) GPIO 端口模式设置;
4) 串口参数初始化;
5) 开启中断并且初始化 NVIC(如果需要开启中断才需要这个步骤);
6) 使能串口;
7) 编写中断处理函数。
除了上述常规的配置方法,USART还可以利用DMA连续通信。 Rx缓冲器和Tx缓冲器的DMA请求是分别产生的。
利用DMA发送
使用DMA进行发送,可以通过设置USART_CR3寄存器上的DMAT位激活。当TXE位被置为’1’时, DMA就从指定的SRAM区传送数据到USART_DR寄存器。为USART的发送分配一个DMA通道的步骤如下(x表示通道号):
1. 在DMA控制寄存器上将USART_DR寄存器地址配置成DMA传输的目的地址。在每个TXE事件后,数据将被传送到这个地址。
2. 在DMA控制寄存器上将存储器地址配置成DMA传输的源地址。在每个TXE事件后,将从此存储器区读出数据并传送到USART_DR寄存器。
3. 在DMA控制寄存器中配置要传输的总的字节数。
4. 在DMA寄存器上配置通道优先级。
5. 根据应用程序的要求,配置在传输完成一半还是全部完成时产生DMA中断。
6. 在DMA寄存器上激活该通道。
当传输完成DMA控制器指定的数据量时, DMA控制器在该DMA通道的中断向量上产生一中断。
在发送模式下,当DMA传输完所有要发送的数据时, DMA控制器设置DMA_ISR寄存器的TCIF标志;监视USART_SR寄存器的TC标志可以确认USART通信是否结束,这样可以在关闭USART或进入停机模式之前避免破坏最后一次传输的数据;软件需要先等待TXE=1,再等待TC=1。
利用DMA接收
可以通过设置USART_CR3寄存器的DMAR位激活使用DMA进行接收,每次接收到一个字节,DMA控制器就就把数据从USART_DR寄存器传送到指定的SRAM区(参考DMA相关说明)。为USART的接收分配一个DMA通道的步骤如下(x表示通道号):
1. 通过DMA控制寄存器把USART_DR寄存器地址配置成传输的源地址。在每个RXNE事件后,将从此地址读出数据并传输到存储器。
2. 通过DMA控制寄存器把存储器地址配置成传输的目的地址。在每个RXNE事件后,数据将从USART_DR传输到此存储器区。
3. 在DMA控制寄存器中配置要传输的总的字节数。
4. 在DMA寄存器上配置通道优先级。
5. 根据应用程序的要求配置在传输完成一半还是全部完成时产生DMA中断。
6. 在DMA控制寄存器上激活该通道。
当接收完成DMA控制器指定的传输量时, DMA控制器在该DMA通道的中断矢量上产生一中断。
多缓冲器通信中的错误标志和中断产生
在多缓冲器通信的情况下,通信期间如果发生任何错误,在当前字节传输后将置起错误标志。如果中断使能位被设置,将产生中断。在单个字节接收的情况下,和RXNE一起被置起的帧错误、溢出错误和噪音标志,有单独的错误标志中断使能位;如果设置了,会在当前字节传输结束后,产生中断。
代码如下:
1.定义接收和发送数据缓存变量
#define COMx_RXBUFFER_SIZE 255 //串口接收缓存器长度typedef struct COMx_RXBUFFER
{
u8 buffer[COMx_RXBUFFER_SIZE]; //接收缓存器
u8 RxFlag; //接收数据标志 1:接收到新数据 0:无新数据被接收
u16 RxLen; //接收到数据长度
}COMx_RXBUFFER;
#define UART1_RX_LEN COMx_RXBUFFER_SIZE //USART1 DMA接收缓存器长度
uint8_t Uart1_Tx[UART1_TX_LEN] = {0}; //串口1发送DMA缓存 uint8_t Uart1_Rx[UART1_RX_LEN] = {0}; //串口1接收DMA缓存 //串口1接收DMA缓存COMx_RXBUFFER Uart1_RxBuffer;
2.配置串口
static void BSP_USART1_Init(u32 baud){ USART_InitTypeDef bsp_usart_init; GPIO_InitTypeDef bsp_usartpin_init; //使能USART1时钟、使能USART1引脚时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); //初始化USART1引脚PA9 TX bsp_usartpin_init.GPIO_Pin = GPIO_Pin_9; bsp_usartpin_init.GPIO_Speed = GPIO_Speed_50MHz; bsp_usartpin_init.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &bsp_usartpin_init); //初始化USART1引脚PA10 RX bsp_usartpin_init.GPIO_Pin = GPIO_Pin_10; bsp_usartpin_init.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &bsp_usartpin_init); //初始化USART1参数 bsp_usart_init.USART_BaudRate = baud; bsp_usart_init.USART_WordLength = USART_WordLength_8b; bsp_usart_init.USART_StopBits = USART_StopBits_1; bsp_usart_init.USART_Parity = USART_Parity_No; bsp_usart_init.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; bsp_usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init(USART1, &bsp_usart_init); //初始化中断 BSP_NVIC_Init(USART1_IRQn, 1, 1); //开启USART1总线空闲中断 USART_ITConfig(USART1,USART_IT_TC,DISABLE); USART_ITConfig(USART1,USART_IT_RXNE,DISABLE); USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); //串口接收DMA配置 BSP_DMAUsar1Rx_Init(); //串口发送DMA配置 BSP_DMAUsar1Tx_Init(); USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //采用DMA方式接收 //开启USART1 USART_Cmd(USART1, ENABLE);}
分析上面代码:
1.串口时钟使能。 串口是挂载在 APB2 下面的外设,所以使能函数为:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1);
2. 串口复位。 当外设出现异常的时候可以通过复位设置,实现该外设的复位,然后重新配置这个外设达到让其重新工作的目的。一般在系统刚开始配置外设的时候,都会先执行复位该外
设的操作(此处省略);
3.GPIO 端口模式设置 。按照下图配置引脚,这里用的USART1 对应引脚PA9 (TX) PA10(RX);
4.串口参数配置。自定义波特率,1位起始位,1位停止位,8位数据位,无校验,无流控。使能接收和发送数据功能。
5.开启总线空闲中断,并初始化中断。关于中断参考相关内容
初始化中断代码如下:
/*********************************************************************************************************** BSP_NVIC_Init()** Description : 中断优先级初始化函数,用于给中断设定优先级** Argument(s) : IRQChannel 中断通道.* PreemptionPrio 中断抢占优先级* SubPrio 中断子优先级 ** Return(s) : none.** Caller(s) : Application.** Note(s) : none.**********************************************************************************************************/void BSP_NVIC_Init(u8 IRQChannel, u8 PreemptionPrio, u8 SubPrio){ NVIC_InitTypeDef bsp_nvic_init; bsp_nvic_init.NVIC_IRQChannel = IRQChannel; bsp_nvic_init.NVIC_IRQChannelPreemptionPriority = PreemptionPrio; bsp_nvic_init.NVIC_IRQChannelSubPriority = SubPrio; bsp_nvic_init.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&bsp_nvic_init); }
6.配置DMA方式接收串口数据。关于DMA参见相关内容。
代码如下:
//USART1接收DMA配置static void BSP_DMAUsar1Rx_Init(void){ DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //启动DMA时钟 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR); //外设地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Uart1_Rx; //内存地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //dma传输方向单向 DMA_InitStructure.DMA_BufferSize = UART1_RX_LEN; //设置DMA在传输时缓冲区的长度 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //禁止DMA的外设递增模式,一个外设 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //设置DMA的内存递增模式 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据字长 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //内存数据字长 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //设置DMA的传输模式 DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //设置DMA的优先级别 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //禁止DMA的2个memory中的变量互相访问 DMA_Init(DMA1_Channel5,&DMA_InitStructure); DMA_Cmd(DMA1_Channel5,ENABLE); //使能通道5 }
7.配置DMA方式发送串口数据。代码如下:
//USART1发送DMA配置static void BSP_DMAUsar1Tx_Init(void){ DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //启动DMA时钟 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR); //DMA外设基地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Uart1_Tx; //DMA内存基地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设 DMA_InitStructure.DMA_BufferSize = UART1_TX_LEN; //DMA通道的DMA缓存的大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输 DMA_Init(DMA1_Channel4, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器 }
8.使能DMA方式接收数据,开启串口功能。
9.中断处理函数如下:
//USART1中断函数。void USART1_IRQHandler(void){ uint16_t i = 0; OSIntEnter(); if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) { //1.清除USART1接收完成中断 //USART_ClearFlag(USART1,USART_IT_IDLE); USART1->SR; USART1->DR; //清USART_IT_IDLE标志 //2.存储收到的数据内容、长度、标志位 DMA_Cmd(DMA1_Channel5,DISABLE); //关闭DMA if(!Uart1_RxBuffer.RxFlag) //判断是否有数据包在处理 { Uart1_RxBuffer.RxLen = UART1_RX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5); //计算接收数据包长度 for (i = 0;i < Uart1_RxBuffer.RxLen;i++) //将接收到的数据包缓存 { Uart1_RxBuffer.buffer[i] = Uart1_Rx[i]; } Uart1_RxBuffer.RxFlag = 1; //置位接收状态标志位 } DMA_SetCurrDataCounter(DMA1_Channel5,UART1_RX_LEN); //设置传输数据长度 DMA_Cmd(DMA1_Channel5,ENABLE); //打开DMA } // 3. Send message to task_command_process OSMboxPost(MboxCmdSet, &Uart1_RxBuffer.buffer[0]); // Inform App_TaskCommandProcess() that one command has arrived. OSIntExit(); }
因为项目基于ucos-ii系统开发的,所以中断函数里含有ucos-ii系统相关函数。中断处理步骤如下:
9.1.判断中断类型是否是总线空闲中断。否,不作任何处理退出中断;是,继续一下处理;
9.2.清除中断标志;
9.3.关闭DMA接收功能,获取接收数据长度;
9.4.根据接收数据长度从DMA接收缓存区提取接收到的数据;
9.5.重设DMA传输数据长度并打开DMA接收功能;
9.6.退出中断处理函数。
由于串口接收数据的时间点是未知的,所以采用中断来处理接收的数据。但是只有接收没有放松明显不行,下面介绍一下发送数据函数。代码如下:
//USART1 DMA发送指定长度的数据//str:要发送的数据首地址//cndtr:数据传输量 void BSP_DMAUsart1Puts(unsigned char *str,u8 cndtr){ memcpy(Uart1_Tx, str, cndtr); USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送 DMA_Cmd(DMA1_Channel4, DISABLE ); //关闭USART1 TX DMA1 所指示的通道 DMA_SetCurrDataCounter(DMA1_Channel4,cndtr); //DMA通道的DMA缓存的大小 DMA_Cmd(DMA1_Channel4, ENABLE); //使能USART1 TX DMA1 所指示的通道 }
通过串口发送数据的时刻是可预知的,所以采用普通的方式发送串口数据即可。步骤如下:
1.将要发送的数据传送至DMA发送缓存区中;
2.关闭DMA串口发送数据功能;
3.设置发送数据size;
4.使能DMA串口发送功能,将数据发送出去。
到此,串口通信的内容就介绍完了。想要获取接收的数据,只要查看变量Uart1_RxBuffer.buffer[]里的数据;至于发送数据,调用下面的函数即可。
void BSP_DMAUsart1Puts(unsigned char *str,u8 cndtr);