RS485通信

RS485——RS485通信基础理论与STM32测试

口袋里のInit 2018-05-04 21:30:52  11872  收藏 97
分类专栏: 基础知识 stm32 通信、总线协议 文章标签: RS485 STM32
版权

基础知识
同时被 3 个专栏收录
42 篇文章16 订阅
订阅专栏

stm32
6 篇文章0 订阅
订阅专栏

通信、总线协议
13 篇文章8 订阅
订阅专栏
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电路

根本原理与基本电路的原理相似。使用DC-DC器件可以产生1组与微处理器电路完全隔离的电源输出,用于向RS485收发器提供+5V电源。电路中的光耦器件速率会影响RS485电路的通信速率。上图中选用了NEC的光耦PS2501,受其影响,该电路的通讯速率控制在19200bps下。
 
5.3 自动切换电路

 
上图中,TX,RX引脚均需要上拉电阻,这一点特别重要。
接收:默认没有数据时,TX为高电平,三极管导通,RE为低电平使能,RO收数据有效,MAX485为接收态。
发送:发送数据1时,TX为高电平时,三极管导通,DE为低电平,此时收发器处于接收状态,驱动器就变成了高阻态,也就是发送端与A\B断开了,此时A\B之间的电压就取决于A\B的上下拉电阻了,A为高电平、B为低电平,也就成为了逻辑1了。
            发送数据0时,TX为低电平,三极管截止,DE为高电平,驱动器使能,此时正好DI是接地的,也就是低电平,驱动器也就会驱动输出B为1,A为0,也就是所谓的逻辑0了。
          理解自收发的作用,关键是要理解RE和DE的作用,尤其是DE为0时,驱动器与A\B之间就是高阻态,也就是断开状态,而且A\B都要有上下拉电阻。然后就有了逻辑0-1之间的切换了。所以很巧妙,但是这里也有一个很明显的bug,也就是只适用于“半双工”,如果是全双工,就不行了,因为TX为1时,接收使能,此时从机如果回复数据,那么也就乱了。
     基本原理了解了,除了使用三极管实现,还可以使用施密特触发器,也就是所谓的“非”门,来显现,如下图所示:
     
   基本原理与三极管相同,TX为1时,经过施密特触发器进行“非”运算,DE为0,则接收使能,驱动器呈高阻态,此时A\B的电平就是上下拉电阻的电平,也就是逻辑1。TX为0时,DE为1,发送使能,由于DI接地,也就是0,A\B输出也是0.
 

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复用为USART1
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);    //GPIOA10复用为USART1
 
    //USART1端口配置
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;     //GPIOA9,GPIOA10
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;                //复用功能
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;           //速度50MHz
    GPIO_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);  //使能串口1
 
    USART_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);
                }
            }
        }
    }
}
 
————————————————
版权声明:本文为CSDN博主「口袋里のInit」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wangguchao/article/details/80200402

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值