第31章 STM32H7的USART应用之RS485
本章教程为大家讲解USART应用之485总线。虽然这几年无线网络的使用率有所上升,有线的串行网络仍然提供最有力、最可靠的通信,特别是在恶劣的环境中。在需要抗噪、抗静电、抗电压故障的工业,建筑自动化领域仍然是有线通信的天下。
31.1 初学者重要提示
31.2 RS485基础知识
31.3 RS485硬件设计
31.4 RS485驱动设计
31.5 RS485板级支持包(bsp_uart_fifo.c)
31.6 RS485驱动移植和使用
31.7 使用例程设计框架
31.8 实验例程说明(MDK)
31.9 实验例程说明(IAR)
31.10 总结
31.1 初学者重要提示
学习本章节前,务必优先学习第30章,RS485用到的串口FIFO也是建立在30章的基础上。
了解了本章31.2和31.3小节的基础知识后,强烈推荐看此贴的两个文档,对RS485讲解的比较透彻,中文版:http://www.armbbs.cn/forum.php?mod=viewthread&tid=90753 。
STM32H7支持RS485的硬件流控制,即有一个专门的引脚来控制485 PHY的收发状态切换。V7开发板用的USART3,需要用PD12来控制,而这个引脚要用于FMC,所以用的是一个通用IO。
经常会有网友咨询为什么程序里面收发切换没有做延迟处理,这里就涉及到一个关键的知识点TXE发送空中断和TC发送完成中断的区别,详细看教程中说明即可。
31.2 RS485的基础知识
背景知识(了解即可)
智能仪表是随着80年代初单片机技术的成熟而发展起来的,现在世界仪表市场基本被智能仪表所垄断。究其原因就是企业信息化的需要,企业在仪表选型时其中的一个必要条件就是要具有联网通讯接口。最初是数据模拟信号输出简单过程量,后来仪表接口是RS232接口,这种接口可以实现点对点的通信方式,但这种方式不能实现联网功能。随后出现的RS485解决了这个问题。
EIA-485(过去叫做RS-485或者RS485)是隶属于OSI模型物理层的电气特性规定为2线、半双工、平衡传输线多点通信的标准,是由电信行业协会(TIA)及电子工业联盟(EIA)联合发布的标准。实现此标准的数字通信网可以在有电子噪声的环境下进行长距离有效率的通信。在线性多点总线的配置下,可以在一个网络上有多个接收器。因此适用在工业环境中。
EIA一开始将RS(Recommended Standard)做为标准的前缀,不过后来为了便于识别标准的来源,已将RS改为EIA/TIA。电子工业联盟(EIA)已结束运作,此标准目前是电信行业协会(TIA)维护,名称为TIA-485,但工程师仍继续用RS-485来称呼此协议。
RS485电气特性
RS-485的数据最高传输速率为10Mbsp。
RS-485接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗干扰噪声性好。
RS-485最大的通信距离约为1219m,最高传输速率为10Mbsp,传输速率与传输距离成反比,在100Kb/S的传输速率下,才可以达到最大的通信距离,如果需传输更长的距离,需要加485中继器。RS-485总线一般最大支持32个节点,如果使用特制的485芯片,可以达到128个或者256个节点,最大的可以支持到400个节点。
关于RS485的逻辑状态,不同厂家的芯片的定义可能不同,但不影响正常的数据收发,这里以TI的为例做个说明,TI的定义方式如下:
A表示非反向输出non-inverting output,B表示反向输出inverting output。
当VA > VB 的时候表示逻辑状态0,被称为ON。
当VA < VB 的时候表示逻辑状态1,被称为OFF。
对应到实际芯片框图上就是下面这样(DE发送使能,D是发送数据端,RE是接收使能,R是接收数据端):
当用户在D(Driver)引脚输入逻辑高电平时,将在485总线上实现逻辑状态0,即ON状态。接收端R(Receiver)将收到逻辑高电平。
当用户在D(Driver)引脚输入逻辑低电平时,将在485总线上实现逻辑状态1,即OFF状态。接收端R(Receiver)将收到逻辑低电平。
发送状态下,大于|±1.5V |可以有效表示逻辑状态1和逻辑状态0:
接收状态下,大于|±200mv|可以有效表示逻辑状态1和逻辑状态0:
31.3 RS485硬件设计
STM32H743XIH6最多可以支持8个独立的串口。其中串口4和串口5和SDIO的GPIO是共用的,也就是说,如果要用到SD卡,那么串口4和串口5将不能使用。串口7和SPI3共用,串口8和RGB硬件接口共用。串口功能可以分配到不同的GPIO。我们常用的管脚分配如下:
串口USART1 TX = PA9, RX = PA10
串口USART2 TX = PA2, RX = PA3
串口USART3 TX = PB10, RX = PB11
串口UART4 TX = PC10, RX = PC11 (和SDIO共用)
串口UART5 TX = PC12, RX = PD2 (和SDIO共用)
串口USART6 TX = PG14, RX = PC7
串口UART7 TX = PB4, RX = PB3 (和SPI1/3共用)
串口UART8 TX = PJ8, RX =PJ9 (和RGB硬件接口共用)
STM32-V7开发板使用了4个串口设备。
串口1用于RS232接口,很多例子的pritnf结果就是输出到串口1
串口2用于GPS
串口3用于RS485接口
串口6 用于TTL串口插座,板子上有GPRS插座和串口WIFI插座。
下面是相关的原理图:
串口3,RS485
关于485的PHY芯片SP3485E要注意以下几个问题:
SP3485E允许在同一总线上连接32个收发器。
PB2(RS485_TX_EN)引脚控制收发状态,高电平表示使能发送,低电平表示用于接收。
由于 485的RO是输出引脚,如果软件上将PB2配置为输出就会冲突(PB2输出高,RO输出低就会短路)。加电阻的话,PB2就可以做其他用途。主要是开发板才这样弄,正式产品不加。
电阻R15和R165的作用是避免CPU复位期间,TX为高阻时影响总线数据。
电阻R4和R2是保证空闲时处于确定的逻辑状态,提供可靠性。
电阻R3是终端电阻。使用终端电阻是为了阻抗匹配,防止不匹配引起的噪声干扰。一般在300米以下,19200bps不需要接终端电阻。终端电阻要接在传输总线的两端。
31.4 RS485驱动设计
RS485的驱动实现是建立在第31章讲解的串口FIFO基础上,关键的知识点已经在第31章节做了详细讲解,这里把485驱动涉及到的两个关键地方做个说明。
31.4.1 RS485驱动初始化
RS485驱动的初始化要对收发控制引脚进行配置,这点要注意,对应的代码如下:
/**********************************************************************************************************
* 函 数 名: bsp_InitUart
* 功能说明: 初始化串口硬件,并对全局变量赋初值.
* 形 参: 无
* 返 回 值: 无
**********************************************************************************************************/
void bsp_InitUart(void)
{
UartVarInit();/*必须先初始化全局变量,再配置硬件*/InitHardUart();/*配置串口的硬件参数(波特率等)*/RS485_InitTXE();/*配置RS485芯片的发送使能硬件,配置为推挽输出*/}/**********************************************************************************************************
* 函 数 名: RS485_InitTXE
* 功能说明: 配置RS485发送使能口线 TXE
* 形 参: 无
* 返 回 值: 无
**********************************************************************************************************/
void RS485_InitTXE(void)
{
GPIO_InitTypeDef gpio_init;/*打开GPIO时钟*/RS485_TXEN_GPIO_CLK_ENABLE();/*配置引脚为推挽输出*/gpio_init.Mode= GPIO_MODE_OUTPUT_PP; /*推挽输出*/gpio_init.Pull= GPIO_NOPULL; /*上下拉电阻不使能*/gpio_init.Speed= GPIO_SPEED_FREQ_VERY_HIGH; /*GPIO速度等级*/gpio_init.Pin=RS485_TXEN_PIN;
HAL_GPIO_Init(RS485_TXEN_GPIO_PORT,&gpio_init);
}
31.4.2 RS485驱动回调函数初始化
由于RS485是半双工,收发要做切换,初始化的时候专门配套了回调函数:
/**********************************************************************************************************
* 函 数 名: UartVarInit
* 功能说明: 初始化串口相关的变量
* 形 参: 无
* 返 回 值: 无
**********************************************************************************************************/
static void UartVarInit(void)
{#if UART3_FIFO_EN == 1g_tUart3.uart= USART3; /*STM32 串口设备*/g_tUart3.pTxBuf= g_TxBuf3; /*发送缓冲区指针*/g_tUart3.pRxBuf= g_RxBuf3; /*接收缓冲区指针*/g_tUart3.usTxBufSize= UART3_TX_BUF_SIZE; /*发送缓冲区大小*/g_tUart3.usRxBufSize= UART3_RX_BUF_SIZE; /*接收缓冲区大小*/g_tUart3.usTxWrite= 0; /*发送FIFO写索引*/g_tUart3.usTxRead= 0; /*发送FIFO读索引*/g_tUart3.usRxWrite= 0; /*接收FIFO写索引*/g_tUart3.usRxRead= 0; /*接收FIFO读索引*/g_tUart3.usRxCount= 0; /*接收到的新数据个数*/g_tUart3.usTxCount= 0; /*待发送的数据个数*/g_tUart3.SendBefor= RS485_SendBefor; /* 发送数据前的回调函数 */g_tUart3.SendOver = RS485_SendOver; /* 发送完毕后的回调函数 */
g_tUart3.ReciveNew = RS485_ReciveNew; /*接收到新数据后的回调函数*/g_tUart3.Sending= 0; /*正在发送中标志*/
#endif}
上面代码中置红的部分是专用于485总线的,对应的代码如下:
/**********************************************************************************************************
* 函 数 名: RS485_SendBefor
* 功能说明: 发送数据前的准备工作。对于RS485通信,请设置RS485芯片为发送状态,
* 并修改 UartVarInit()中的函数指针等于本函数名,比如 g_tUart2.SendBefor = RS485_SendBefor
* 形 参: 无
* 返 回 值: 无
**********************************************************************************************************/
void RS485_SendBefor(void)
{
RS485_TX_EN();/*切换RS485收发芯片为发送模式*/}/**********************************************************************************************************
* 函 数 名: RS485_SendOver
* 功能说明: 发送一串数据结束后的善后处理。对于RS485通信,请设置RS485芯片为接收状态,
* 并修改 UartVarInit()中的函数指针等于本函数名,比如 g_tUart2.SendOver = RS485_SendOver
* 形 参: 无
* 返 回 值: 无
**********************************************************************************************************/
void RS485_SendOver(void)
{
RS485_RX_EN();/*切换RS485收发芯片为接收模式*/}/**********************************************************************************************************
* 函 数 名: RS485_ReciveNew
* 功能说明: 接收到新的数据
* 形 参: _byte 接收到的新数据
* 返 回 值: 无
**********************************************************************************************************/
//extern void MODH_ReciveNew(uint8_t _byte);
voidRS485_ReciveNew(uint8_t _byte)
{//MODH_ReciveNew(_byte);
}
函数RS485_SendBefor
发送数据前的准备工作。对于RS485通信,请设置RS485芯片为发送状态。
函数RS485_SendOver
发送一串数据结束后的善后处理。对于RS485通信,请设置RS485芯片为接收状态。
函数RS485_ReciveNew
接收到新的数据,用于Modbus通信协议。这里未用到Modbus,所以将对应的内容注释掉了。
31.4.3 RS485发送处理
串口数据的发送主要涉及到下面四个函数,调用关系是如下:
RS485_SendStr –> RS485_SendBuf –> comSendBuf -> UartSend
实际应用中,大家调用函数RS485_SendStr&#x