提醒:RS485是串口中的一种,它与常用的TTL-UART的使用方式基本相同,差别在于使用485时需要手动切换485芯片的收发引脚状态模式。Linux 驱动RS485通信的程序源码Demo见文末。

1、RS485基础铺垫

        智能仪表随着80年代初单片机技术的成熟而发展起来,世界仪表市场基本被智能仪表所垄断,这归结于企业信息化的需要,而企业在仪表选型时其中的一个必要条件就是要具有联网通信接口。最初是数据模拟信号输出简单过程量,后来仪表接口是RS-232接口,这种接口可以实现点对点的通信方式,但这种方式不能实现联网功能,随后出现的RS-485解决了这个问题。RS-485又名TIA-485-A, ANSI/TIA/EIA-485或TIA/EIA-485。

RS-485两线制四线制两种接线,四线制只能实现点对点的通信方式,现很少采用,多采用的是两线制接线方式,这种接线方式为总线式拓扑结构,在同一总线上最多可以挂接32个节点

在RS-485通信网络中一般采用的是主从通信方式,即一个主机带多个从机。

RS-485能够进行远距离传输主要得益于使用差分信号进行传输,当有噪声干扰时仍可以使用线路上两者差值进行判断,使传输数据不受噪声干扰

差分传输是一种信号传输的技术,区别于传统的一根信号线一根地线的做法,差分传输在这两根线上都传输信号,这两个信号的振幅相等,相位相反。在这两根线上传输的信号就是差分信号,也称差模信号。

差分信号又称差模信号,是相对共模信号而言的。

共模信号是信号线对地的电压,差模信号是信号线之间的电压。放大电路是一个双口网络,每个端口有两个端子。当两个输入端子的输入信号分别为U1和U2时,两信号的差值称为差模信号,而两信号的算术平均值称为共模信号。

RS-485差分线路包括以下2个信号:

  • A:非反向(non-inverting)信号
  • B:反向(inverting)信号

为避免信号反射,当线缆长度很长时数据传输线必须有终点,并且分支长度尽可能的。正确的终端需要终端电阻RT匹配,其值为传输线的特性阻抗Z0。

RS-485标准建议线缆的Z0=120Ω。线缆干线通常终端匹配120Ω的电阻,线缆的末尾处各一个。

此外,在RS485的基础上,还衍生了Modbus等通信标准。

2、RS485硬件说明

(如果看不懂可以跳过,确定RS485的控制GPIO是哪个就行

Linux开发板的RS485部分的原理图

SP3485芯片内部逻辑电路图

通过开发板的RS485部分的原理图和SP3485的芯片内部逻辑图可知:

GPIO_RX485_CTL引脚为高电平时,RS485为发送模式,

GPIO_RX485_CTL引脚为低电平时,RS485为接收模式。

SP3485芯片原厂说明手册,建议多查阅厂商手册,对于理解各类技术文档资料和开发能力的提升有非常大的帮助。

3、RS485通信功能实现

实际上,使用RS485通信的本质还是在使用UART串口通信,只是在程序编写时,需要对read、write多一步的处理。即,进行写485操作时,通过控制引脚切换485芯片为发送模式,读操作时,通过控制引脚切换485芯片为接收模式。485处理芯片相对于串口,就是需要将TTL逻辑电平信号转换为RS485差分电平信号。

如果觉得在代码中切换485芯片操作不方便,甚至可以直接使用TTL串口转485等模块,以串口方式驱动硬件设备。

本文中使用的是RS485转USB模块,根据实际情况连接设备,不能接错线。

连接开发板和硬件设备的RS485引脚A、B(A线接A线,B线接B线)

查看开发板串口3对应的tty设备号及RS485控制端口引脚对应的编号

 ttymxc 是指 i.MX 系列芯片上的一个串口设备。其它Linux开发板根据板子实际情况选择。

编写RS485测试程序,交叉编译到Linux开发板中运行

开发板运行RS485程序,接收及发送数据运行效果图

4、Linux RS485通信程序源码

        main.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "linux-rs485.h"



int main(int argc, char **argv)
{
    int fd_rs485;
    int ret;
    ret = rs485_init_for_linux(&fd_rs485, B115200);
    if(ret < 0)
    {
        return -1;
    }
    printf("RS485 test Starting...\n");
    char buf[1024] = {0};
    while(1)
    {
        rs485_set_gpio_value(RS485_CTRL_GPIO, RS485_SEND_MODE);
        write(fd_rs485,"hello rs485\r\n", strlen("hello rs485\r\n"));
        sleep(1);
        rs485_set_gpio_value(RS485_CTRL_GPIO, RS485_RECV_MODE);
        read(fd_rs485,buf,1024);
        printf("recv:%s\r\n", buf);
        memset(buf, 0, 1024);
        sleep(1);
    }

    return 0;
}

        linux-rs485.c

#include "linux-rs485.h"

/**
  * @brief  linux开发板RS485通信初始化
  * @param  fd:打开RS485通信文件描述符
  * @param  baudrate:485通信波特率
  * @retval 成功返回0,失败返回-1
  */
int rs485_init_for_linux(int *fd, speed_t baudrate)
{
    //0、打开串口 /dev/ttymxc2
    *fd = open("/dev/ttymxc2", O_RDWR); //具体的tty设备号,需根据开发板的实际情况修改
    if(*fd == -1)
    {
        perror("open /dev/ttymxc2 failed!");
        return -1;
    }
    //1.定义串口结构体
    struct termios old_cfg, new_cfg;
    //2.获取当前串口的属性配置
    if(tcgetattr(*fd, &old_cfg) != 0)
    {
        perror("tcgetattr");
        close(*fd);
        return -1;
    }
    //3.设置串口的通信模式
    bzero(&new_cfg, sizeof(new_cfg));
    new_cfg = old_cfg;
    cfmakeraw(&new_cfg);
    //4.配置串口信息
    cfsetispeed(&new_cfg, baudrate);
    cfsetospeed(&new_cfg, baudrate);
    new_cfg.c_cflag |=  CLOCAL | CREAD;
    //设置数据位:8位
    new_cfg.c_cflag &= ~CSIZE;
    new_cfg.c_cflag |=  CS8;
    //设置校验位
    new_cfg.c_cflag &= ~PARENB;
    //设置停止位
    new_cfg.c_cflag &= ~CSTOPB;
    //清空缓冲区
    new_cfg.c_cc[VTIME] = 0;
    new_cfg.c_cc[VMIN]  = 4;
    tcflush(*fd, TCIFLUSH);

    //5.将配置信息同步到系统中
    if((tcsetattr(*fd, TCSANOW, &new_cfg)) != 0)
    {
        perror("tcsetattr");
        close(*fd);
        return -1;
    }

    //6、配置485通信控制IO
    rs485_set_gpio_export(RS485_CTRL_GPIO);
    rs485_set_gpio_direction(RS485_CTRL_GPIO, GPIO_OUT_MODE);
    
    return 0;
}

/**
  * @brief  设置RS485引脚export
  * @param  gpio:端口引脚号
  * @retval 成功返回0,失败返回-1
  */
int rs485_set_gpio_export(unsigned int gpio)
{
    int fd, len;
    char buf[128];
    fd = open( "/sys/class/gpio/export", O_WRONLY);
    if (fd < 0) 
    {
        perror("gpio/export");
        return -1;
    }
 
    len = snprintf(buf, sizeof(buf), "%d", gpio);//从数字变换为字符串,即1 变为”1“
    write(fd, buf, len);//将需要导出的GPIO引脚编号进行写入
    close(fd);
 
    return 0;
}

/**
  * @brief  设置RS485引脚unexport
  * @param  gpio:端口引脚号
  * @retval 成功返回0,失败返回-1
  */
int rs485_set_gpio_unexport(unsigned int gpio)
{
    int fd, len;
    char buf[128];
    fd = open("/sys/class/gpio/unexport", O_WRONLY);
    if (fd < 0)
    {
        perror("gpio/export");
        return -1;
    }
 
    len = snprintf(buf, sizeof(buf), "%d", gpio);//从数字变换为字符串,即1 变为”1“
    write(fd, buf, len);//将需要取消导出的GPIO引脚编号进行写入
    close(fd);
    return 0;
}

/**
  * @brief  设置RS485引脚方向
  * @param  gpio:端口引脚号
  * @param  io_flag:引脚IO的状态
  * @retval 成功返回0,失败返回-1
  */
int rs485_set_gpio_direction(unsigned int gpio, unsigned int io_flag)
{
    int fd, len;
    char buf[128];
    len = snprintf(buf, sizeof(buf), "/sys/class/gpio"  "/gpio%d/direction", gpio);
 
    fd = open(buf, O_WRONLY);
    if (fd < 0) 
    {
        perror(buf);
        return -1;
    }
    
    if (io_flag)//为1,则写入“out",即设置为输出
        write(fd, "out", 4);
    else//为0,则写入“in",即设置为输入
        write(fd, "in", 3);
 
    close(fd);
    return 0;
}

/**
  * @brief  设置RS485引脚电平值
  * @param  gpio:端口引脚号
  * @param  value:电平值(高电平、低电平)
  * @retval 成功返回0,失败返回-1
  */
int rs485_set_gpio_value(unsigned int gpio, unsigned int value)
{
    int fd, len;
    char buf[128];
    len = snprintf(buf, sizeof(buf), "/sys/class/gpio" "/gpio%d/value", gpio);
 
    fd = open(buf, O_WRONLY);
    if (fd < 0) 
    {
        perror(buf);
        return -1;
    }
 
    if (value)//为1,则写入“1",即设置为输出高电平
        write(fd, "1", 2);
    else//为0,则写入“0",即设置为输出低电平
        write(fd, "0", 2);
 
    close(fd);
    return 0;
}

/**
  * @brief  设置RS485引脚方向
  * @param  gpio:端口引脚号
  * @param  value:获取的引脚值
  * @retval 成功返回0,失败返回-1
  */
int rs485_get_gpio_value(unsigned int gpio, unsigned int *value)
{
    int fd, len;
    char buf[128];
    char ch;
    len = snprintf(buf, sizeof(buf), "/sys/class/gpio" "/gpio%d/value", gpio);
    
    fd = open(buf, O_RDONLY);
    if (fd < 0) 
    {
        perror("gpio/get-value");
        return fd;
    }
    
    read(fd, &ch, 1);//读取外部输入电平

    if (ch != '0') //为'1',则设置为1,即输入为高电平
    {
        *value = 1;
    } else {       //为'0',则设置为0,即输入为低电平
        *value = 0;
    }
    
    close(fd);
    return 0;
}

        rs485-linux.h

#ifndef __LINUX_RS485_H
#define __LINUX_RS485_H

#ifdef __cplusplus
extern "C"{
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>

#define RS485_CTRL_GPIO  128 //RS485具体的控制引脚需根据开发板实际情况修改
#define GPIO_OUT_MODE    1
#define GPIO_IN_MODE     0
#define RS485_RECV_MODE  0
#define RS485_SEND_MODE  1

int rs485_init_for_linux(int *fd, speed_t baudrate);
int rs485_set_gpio_export(unsigned int gpio);
int rs485_set_gpio_unexport(unsigned int gpio);
int rs485_set_gpio_direction(unsigned int gpio, unsigned int io_flag);
int rs485_set_gpio_value(unsigned int gpio, unsigned int value);
int rs485_get_gpio_value(unsigned int gpio, unsigned int *value);

#ifdef __cplusplus
}
#endif

#endif

嵌入式Linux开发---UART串口通信驱动硬件编程_嵌入式linux串口编程-CSDN博客

嵌入式Linux开发---Socket CAN通信驱动硬件编程_linux can源码-CSDN博客

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐