1.优劣
优势:RS485的可靠传输距离远,接线简单成为了相对于RS232的最大优势。
不足:RS485总线是一种常规的通信总线,它不能够做总线的自动仲裁,也就是不能够同时发送数据以避免总线竞争,所以整个系统的通信效率必然较低,数据冗余量较大,对于速度要求高的应用场所不适应用RS485总线。同时由于RS485总线上通常只有一台主机,所以这种总线方式是典型的集中—分散型控制系统。一旦主机出现故障,会使整个系统的通信限于瘫痪状态,因此做好主机的在线备份是一个重要措施。
2. 硬件层协议
通讯协议主要是实现两个设备之间的数据交换功能,通讯协议分硬件层协议和软件层协议。硬件层协议决定数据如何传输问题,比如要在设备1向设备2发送0x63,0x63的二进制数为0110 0011,这8个二进制数从设备1传输到设备2,涉及到1怎么传,0怎么传的问题,这就是硬件层要解决的问题。
硬件层协议目前比较多见的有RS-232、RS-485、SPI、IIC等。RS-232规定,线上的电压为x伏都表示传输的是0,y伏传输的则是1。再者,比如要选择多少条线传输数据,选择什么材质的线传输输入,这些也属于硬件层协议约束的。
3.RS-485通讯协议
MCU管脚输出TTL电平,TTL电平的意思是,当MCU管脚输出0电平时,一般情况下电压是0V,当MCU管脚输出1电平时,电压是5V。因TTL电平的是由一条信号线,一条地线产生,信号线上的干扰信号会跟随有效信号传送到接收端,使得有效信号受到干扰,485通讯实际上是把MCU出来的TTL电平通过硬件层的一个转换器芯片进行转换:
把MCU出来的一条的TTL信号经过芯片转换为两根线(线A、线B)上的信号。当MCU给转换器输入低TTL电平时,转换器会使得B的电压比A的电压高,反之,A的电压比B的电压高。
485协议规约两条电平线上差值为多少表示0或者1,电压是通过仪表可以测量得到的,所以说RS-485是硬件层协议。
485协议的接收端可能是另一个MCU,MCU管脚也只接受TTL电平,转换芯片过来的是两条线的电压,所以需要对此两条线差分电压转换为TTL电平。
把TTL转为485,实质是一个集成芯片,其间无任何程序代码,纯粹硬件逻辑。同理,将485电平转为TTL也是如此。现在很多芯片把接收和转换都集成到一块IC,注意,转换器和接收器依旧是没有同时工作的,常见的转换芯片是MAX485。
可以这样理解,硬件层协议是公路,路的目的是为了让车辆能够过去。
4.半双工通讯
首先了解什么是单工通讯,单工通讯是指数据只能朝着一个方向传输的通讯方式。而半双工通讯则是指对于通讯两端,不能同时相对方法发送数据,必须错开时间段发送。
RS-485的通讯线只有2条,且这两条通讯线在一次传输中都需要用到,因此485只可实现半双工通讯。485实现半双工通讯,会遇到一个问题,MCU1向MCU2发数据时,并不知道线上是否正传来MCU2数据,因为没有其他线可用来判断对方的收发状态,那么可能也会导致数据冲突。因此,RS-485要实现半双工通讯,就需要上层的软件协议加以规约,也就是做到”不能你想发数据就发数据”。可以理解,软件层协议就好像交通规则,它能让数据有序传输。
5.基本电路
三种常用电路如下:
5.1 基本的RS485电路
上图是最基本的RS485电路,R/D为低电平时,发送禁止,接收有效,R/D为高电平时,则发送有效,接收截止。上拉电阻R7和下拉电阻R8,用于保证无连接的SP485R芯片处于空闲状态,提供网络失效保护,提高RS485节点与网络的可靠性,R7,R8,R9这三个电阻,需要根据实际应用改变大小,特别是使用120欧或更小的终端电阻时,R9就不需要了,此时R7,R8使用680欧电阻。正常情况下,一般R7=R8=4.7K,R9不要。
图中钳位于6.8V的管V4,V5,V6,都是为了保护RS485总线的,避免受外界干扰,也可以选择集成的总线保护原件。另外图中的L1,L2,C1,C2为可选安装原件,用于提高电路的EMI性能.
5.2 带隔离的RS485电路
5.3 自动切换电路
6.SP3485内部结构图:
图中:
A、B总线接口,用于连接485总线。RO是接收输出端,DI是发送数据收入端,RE是接收使能信号(低电平有效),DE是发送使能信号(高电平有效)。
SP3485硬件连接:
注意:
R55和R56是两个偏置电阻,用来保证总线空闲时,AB之间的电压差都会大约200mV,避免总线空闲时压差不定逻辑混乱。
7. RS485串口编程
7.1 编程思路
使用RS485实现两个MCU之间的通信,把接收到的数据通过串口助手显示在超级终端上。首先对Usart1和Usart2进行初始化,Usart1负责与串口助手通信,Usart2与RS485连接进行两个MCU之间的通信。然后编写发送和接收函数,接收函数在Usart2的中断服务函数中实现。最后把接收到的数据和必要的提示信息发送到超级终端上显示。
7.2 功能模块代码
①串口初始化
void Uart1_Init(void)
{//USART1 初始化GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //开启GPIOA时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //开启USART1时钟//串口1对应引脚复用映射GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1//USART1端口配置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9,GPIOA10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10//USART1 端口配置USART_InitStructure.USART_BaudRate = 115200;USART_InitStructure.USART_WordLength = USART_WordLength_8b;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); USART_Cmd(USART1, ENABLE); //使能串口1USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启相关中断NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3; NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
}void Uart2_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //串口2对应引脚复用映射GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA,&GPIO_InitStructure); //USART2 端口配置USART_InitStructure.USART_BaudRate = 115200;USART_InitStructure.USART_WordLength = USART_WordLength_8b;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(USART2, &USART_InitStructure); USART_Cmd(USART2, ENABLE); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); //Usart2 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
}
②接收数据
void USART2_IRQHandler(void)
{static u32 rx_i=0;if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET){ USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除标志位rx_buf[rx_i++] = USART_ReceiveData(USART2); //rx_buf是在main.c定义的全局变量while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET); }rx_flag = 1;
}
③RS485初始化
(SP3485的RE,DE引脚与MCU的PG8引脚相连接)
void Rs485_Init(void)
{GPIO_InitTypeDef GPIO_InitStruct;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOG, &GPIO_InitStruct); // RS485_TX_EN = 0; //默认为接收模式
}
④主函数
int main(void)
{char *tx_buf = "I believe I can fly!";u8 len;Led_Init();Key_Init();Systick_Init();Uart1_Init();Uart2_Init();Rs485_Init();printf("Usart test succeeded!\r\n");while(1){if(!KEY0) //KEY1按键按下{ delay_ms(10); //消抖动if(!KEY0){while(!KEY0);RS485_TX_EN = 1; //发送模式,RS485_TX_EN是自定义的一个宏,即对PG8进行置位复位len = strlen(tx_buf);while(len--){USART_SendData(USART2, *tx_buf++);while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);}printf("Send data succeeded!\r\n"); //printf函数已经重定义}}if(!KEY1) //KEY1按键按下{delay_ms(10); //消抖动if(!KEY1) //等待按键松开{while(!KEY1);RS485_TX_EN = 0; //接收模式if(rx_flag){rx_flag = 0; //清除标志printf("Receive data: %s\r\n", rx_buf);}}}}
}