嵌入式Linux开发---RS485通信驱动硬件编程
RS485的使用与UART串口的使用基本相同,差别在于使用485时需要手动切换485芯片的收发引脚模式。实际上,使用RS485通信的本质还是在使用UART串口通信,只是在程序编写时,需要对read、write多一步的处理。即,进行写485操作时,通过控制引脚切换485芯片为发送模式,读操作时,通过控制引脚切换485芯片为接收模式。485处理芯片相对于串口,就是需要将TTL逻辑电平信号转换为RS48
提醒: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
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)