- 1.设备树部分
- 2.驱动部分
- 3.应用程序部分
- 3.1 波特率行规程设置函数
- 3.2 打开串口函数
- 3.3 使能S485函数
- 3.4 主函数如何写
- 步骤一 open 设备节点
- 步骤二 使能 485
- 步骤三 设置波特率行规程
- 步骤四 读写你想要的数据
- 步骤五 结束恢复旧的配置
- 完整代码
- 4.一些函数的解释
- 解读PTR_ERR,ERR_PTR,IS_ERR
- class类总结
- perror()函数的使用
- LINUX 使用tcgetattr与tcsetattr函数控制终端
- printk函数的用法
- read()、write() 相关函数解析
- 结构体(typedef用法、多维结构体、指针、内嵌函数、赋值)
- memset()函数及其作用
- 内核中修饰的函数的__init的含义
- Linux下getopt_long函数的使用
- extern及其对struct的使用
- 有关于fprintf()函数的用法
- atoi()函数用法
- getopt的用法与optarg
- 5.通用modbus接口的设计
1.设备树部分声明:
建议先看韦东山的前五篇入门加上驱动大全的UART子系统,这样会比较好理解下面的文章。
ps:欢迎添加微信进群一起交流嵌入式Linux,一起学习进步。
你既然想使能S485,那么必须要去看S485的硬件原理图。我的是这个:
通过图我们看到他是和串口3连在一起的。所以说在设备树上就要将串口3与S485联系起来,我们打开我们板子对应的设备树文件(我的板子100ask_imx6ull_mini.dtb)。这部分有的厂家已经帮你写好了,有的没有写。没有写的你要学会去写设备树文件(自己查视频资料去学)。我的设备树文件如下,翻到UART3部分。大部分已经配置好了。
2.驱动部分配置完毕以后咱们下一步。
如果只是为了测试,这部分可以不看。因为很多情况是不需要什么修改的。但是深入学习的话还是得理解他。
驱动部分是精华,这部分去看驱动大全之UART子系统-韦东山的视频课。他有很细致的讲解。你需要去了解整个驱动的框架与分层。知道怎么从最下面的设备树开始一步步往上注册相关结构体的。知道上面的函数,比如open/write/如何一步一步向下调用的。学习这些为了是对UART一个比较深入的了解。不然你永远不知道底层怎么运作。
附上UART子系统的框架图(看不懂就去学吧):
UART子系统代码文件如下:
硬件相关: drivers/tty/serial/imx.c 串口核心层: drivers/tty/serial/serial_core.c TTY 层: drivers/tty/tty_io.c3.应用程序部分
3.1 波特率行规程设置函数首先我简单介绍一下我的项目:通过S485要发送一串固定的16进制数据出来。外设接受到了这个数据以后能够实现开关机。以及反馈一些设备的信息。
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop){
//设置行规程
struct termios newtio,oldtio;//定义两个 newtio,oldtio termios 结构体
// Memset 用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为‘ ’或‘/0’;
memset(&oldtio, 0, sizeof(oldtio));
//tcgetattr 函数用于获取与终端相关的参数。参数 fd 为终端的文件描述符,返回的结果保
存在 termios 结构体中,成功返回 0
if ( tcgetattr( fd,&oldtio) != 0) { //获得驱动程序的默认参数放到 oldtio 中,备份下来
//perror ( )用 来 将 上 一 个 函 数 发 生 错 误 的 原 因 输 出 到 标 准 设备 (stderr) 。参数s 所指的字符串会先打印出,后面再加上错误原因字符串。
perror("SetupSerial 1");
return -1;
}
memset(&newtio, 0, sizeof(newtio));
newtio.c_cflag = newtio.c_cflag |= CLOCAL | CREAD;//CLOCAL :忽略 modem 控制线。CREAD :打开接受者
newtio.c_cflag &= ~CSIZE;//CSIZE:字符长度掩码(传送或接收字元时用的位数)。取值为CS5(传送或接收字元时用 5bits), CS6, CS7, 或 CS8。
switch( nBits )//设置数据位的个数:要设置为 8 位
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
switch( nEvent )//设置校验位 注意没有校验位
{
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;//关闭奇偶校验
break;
}
switch( nSpeed )//设置波特率
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if( nStop == 1 )//设置停止位
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VMIN] = 0;
newtio.c_cc[VTIME] = 0;
//tcflush 函数用于清空输入
tcflush(fd,TCIFLUSH);
if((tcsetattr(fd,TCSANOW,&newtio))!=0)//tcsetattr 函数设置行规程 TCSANOW:不等数据传
输完毕就立即改变属性。
{
perror("com set error");
return -1;
}
//printf("set done!n");
return 0;
}
3.2 打开串口函数
int open_port(char *com)
{
int fd;
//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);//打开设备结点,O_NOCTTY
fd = open(com, O_RDWR);
if (-1 == fd){
return(-1);
}
}
3.3 使能S485函数
其实大部分代码是一个串口的程序。区别也就是如下的rs485_enable函数。该函数通过ioctl的方式配置了使能参数。
int rs485_enable(const int fd, const RS485_ENABLE_t enable)
{
struct serial_rs485 rs485conf;
int res;
res = ioctl(fd, TIOCGRS485, &rs485conf);
if (res < 0) {
perror("Ioctl error on getting 485 configure:");
close(fd);
return res;
}
if (enable) { // Enable rs485 mode
rs485conf.flags |= SER_RS485_ENABLED;
rs485conf.flags |= SER_RS485_RTS_ON_SEND;
//rs485conf.flags |= SER_RS485_RTS_AFTER_SEND;
//rs485conf.flags |= SER_RS485_RX_DURING_TX;
} else { // Disable rs485 mode
rs485conf.flags &= ~(SER_RS485_ENABLED);
}
rs485conf.delay_rts_before_send = 0x00000004;
res = ioctl(fd, TIOCSRS485, &rs485conf);
if (res < 0) {
perror("Ioctl error on setting 485 configure:");
close(fd);
}
return res;
}
查看内核485帮助文档,去进行485函数的一些配置。
如内核提示了SER_RS485_RTS等相关的config设置,这些设置应该就是会影响控制信号的控制。
比如增加了如下标志后,发现发送完成后,控制信号被拉低,可以再次接收数据了。
rs485conf.flags |= SER_RS485_RTS_AFTER_SEND;
内核485帮助文档: From user-level, RS485 configuration can be get/set using the previous ioctls. For instance, to set RS485 you can use the following code: #include3.4 主函数如何写 步骤一 open 设备节点#define TIOCGRS485 0x542E #define TIOCSRS485 0x542F int fd = open ("/dev/mydevice", O_RDWR); if (fd < 0) { } struct serial_rs485 rs485conf; rs485conf.flags |= SER_RS485_ENABLED; rs485conf.flags |= SER_RS485_RTS_ON_SEND; rs485conf.flags &= ~(SER_RS485_RTS_ON_SEND); rs485conf.flags |= SER_RS485_RTS_AFTER_SEND; rs485conf.flags &= ~(SER_RS485_RTS_AFTER_SEND); rs485conf.delay_rts_before_send = ...; rs485conf.delay_rts_after_send = ...; rs485conf.flags |= SER_RS485_RX_DURING_TX; if (ioctl (fd, TIOCSRS485, &rs485conf) < 0) { } if (close (fd) < 0) { }
fd = open_port(argv[1]);//打开逆变器设备节点,以读写的方式打开,可读可写。打开成功
会返回 0
if (fd < 0) {//打开失败
perror("open failed");
return -1;
}
步骤二 使能 485
rs485_enable(fd,ENABLE);步骤三 设置波特率行规程
iRet = set_opt(fd, 2400, 8, 'N', 1);//这就是我要重点关注的,设置比特率位 115200,数据位
为 8,没有校验位,停止位的个数是 1。
if (iRet < 0) {
perror("set_port failed");
return -1;
}
步骤四 读写你想要的数据
write(fd, &c, 8);//发给指定串口数据 read(fd ,read_buf ,sizeof(read_buf));//读指定串口数据步骤五 结束恢复旧的配置
tcsetattr(fd, TCSANOW, &oldtio); close(fd);完整代码
我的项目想的完整代码,你可以根据这个去修改成你想要的。
#include4.一些函数的解释#include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef enum {DISABLE = 0, ENABLE} RS485_ENABLE_t; int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop) { //设置行规程 struct termios newtio,oldtio;//定义两个 newtio,oldtio termios 结构体 // Memset 用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初 始化为‘ ’或‘/0’; memset(&oldtio, 0, sizeof(oldtio)); //tcgetattr 函数用于获取与终端相关的参数。参数 fd 为终端的文件描述符,返回的结果保 存在 termios 结构体中,成功返回 0 if ( tcgetattr( fd,&oldtio) != 0) { //获得驱动程序的默认参数放到 oldtio 中,备份下来 //perror ( )用 来 将 上 一 个 函 数 发 生 错 误 的 原 因 输 出 到 标 准 设备 (stderr) 。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。 perror("SetupSerial 1"); return -1; } memset(&newtio, 0, sizeof(newtio)); newtio.c_cflag = newtio.c_cflag |= CLOCAL | CREAD;//CLOCAL :忽略 modem 控制线。 CREAD :打开接受者 newtio.c_cflag &= ~CSIZE;//CSIZE:字符长度掩码(传送或接收字元时用的位数)。取值为 CS5(传送或接收字元时用 5bits), CS6, CS7, 或 CS8。 switch( nBits )//设置数据位的个数:要设置为 8 位 { case 7: newtio.c_cflag |= CS7; break; case 8: newtio.c_cflag |= CS8; break; } switch( nEvent )//设置校验位 注意没有校验位 { case 'O': newtio.c_cflag |= PARENB; newtio.c_cflag |= PARODD; newtio.c_iflag |= (INPCK | ISTRIP); break; case 'E': newtio.c_iflag |= (INPCK | ISTRIP); newtio.c_cflag |= PARENB; newtio.c_cflag &= ~PARODD; break; case 'N': newtio.c_cflag &= ~PARENB;//关闭奇偶校验 break; } switch( nSpeed )//设置波特率 { case 2400: cfsetispeed(&newtio, B2400); cfsetospeed(&newtio, B2400); break; case 4800: cfsetispeed(&newtio, B4800); cfsetospeed(&newtio, B4800); break; case 9600: cfsetispeed(&newtio, B9600); cfsetospeed(&newtio, B9600); break; case 115200: cfsetispeed(&newtio, B115200); cfsetospeed(&newtio, B115200); break; default: cfsetispeed(&newtio, B9600); cfsetospeed(&newtio, B9600); break; } if( nStop == 1 )//设置停止位 newtio.c_cflag &= ~CSTOPB; else if ( nStop == 2 ) newtio.c_cflag |= CSTOPB; newtio.c_cc[VMIN] = 0; newtio.c_cc[VTIME] = 0; //tcflush 函数用于清空输入 tcflush(fd,TCIFLUSH); if((tcsetattr(fd,TCSANOW,&newtio))!=0)//tcsetattr 函数设置行规程 TCSANOW:不等数据传 输完毕就立即改变属性。 { perror("com set error"); return -1; } //printf("set done!n"); return 0; } int open_port(char *com) { int fd; //fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY); fd = open(com, O_RDWR);//打开设备结点,O_NOCTTY if (-1 == fd){ return(-1); } } //控制逆变器 int rs485_enable(const int fd, const RS485_ENABLE_t enable) { struct serial_rs485 rs485conf; int res; res = ioctl(fd, TIOCGRS485, &rs485conf); if (res < 0) { perror("Ioctl error on getting 485 configure:"); close(fd); return res; } if (enable) { // Enable rs485 mode rs485conf.flags |= SER_RS485_ENABLED; rs485conf.flags |= SER_RS485_RTS_ON_SEND; //rs485conf.flags |= SER_RS485_RTS_AFTER_SEND; //rs485conf.flags |= SER_RS485_RX_DURING_TX; } else { // Disable rs485 mode rs485conf.flags &= ~(SER_RS485_ENABLED); } rs485conf.delay_rts_before_send = 0x00000004; res = ioctl(fd, TIOCSRS485, &rs485conf); if (res < 0) { perror("Ioctl error on setting 485 configure:"); close(fd); } return res; } int main(int argc, char *argv[])//两个参数 { char read_buf[1024]; int fd,nread; int iRet; pid_t pid; int next_option; //extern 是一个关键字,它告诉编译器存在着一个变量或者一个函数,如果在当前编译语句的前面中没有找到相应的变量或者函数,也会在当前文件的后面或者其它文件中定义 extern struct termios oldtio; int speed ; char *device; fd = open_port(argv[1]);//打开逆变器设备节点,以读写的方式打开,可读可写。打开成功 会返回 0 if (fd < 0) {//打开失败 perror("open failed"); return -1; } rs485_enable(fd,ENABLE); iRet = set_opt(fd, 2400, 8, 'N', 1);//这就是我要重点关注的,设置比特率位 115200,数据位 为 8,没有校验位,停止位的个数是 1。 if (iRet < 0) { perror("set_port failed"); return -1; } while (1) { int value; printf("请输入命令n 开机为 1n 关机为 0n"); scanf("%d", &value); uint8_t c[1024]; if(value==1){ c[0]=0xAE; c[1]=0x01; c[2]=0x02; c[3]=0x04; c[4]=0x00; c[5]=0x00; c[6]=0x07; c[7]=0xEE; c[8]=' '; } else { c[0]=0xAE; c[1]=0x01; c[2]=0x02; c[3]=0x04; c[4]=0x01; c[5]=0x00; c[6]=0x08; c[7]=0xEE; c[8]=' '; }; //直针 p 初始化为指向 str iRet = write(fd, &c, 8);//发给指定串口数据 nread = read(fd ,read_buf ,sizeof(read_buf));//读指定串口数据 if (nread > 0) { printf("RECV[%3d]: ", nread); for(int i = 0; i < nread; i++) printf("0x%02x ", read_buf[i]); printf("n"); } } tcsetattr(fd, TCSANOW, &oldtio); close(fd); return 0 }
解读PTR_ERR,ERR_PTR,IS_ERR我在这个项目里面遭遇的一些函数罗列一下:
转载-戳这里
class类总结转载-戳这里
perror()函数的使用转载-戳这里
LINUX 使用tcgetattr与tcsetattr函数控制终端转载-戳这里
转载-戳这里
转载-戳这里
read()、write() 相关函数解析转载-戳这里
结构体(typedef用法、多维结构体、指针、内嵌函数、赋值)转载-戳这里
memset()函数及其作用转载-戳这里
内核中修饰的函数的__init的含义转载-戳这里
Linux下getopt_long函数的使用转载-戳这里
extern及其对struct的使用转载-戳这里
有关于fprintf()函数的用法转载-戳这里
atoi()函数用法转载-戳这里
getopt的用法与optarg转载-戳这里
5.通用modbus接口的设计接下来打算做一个标准的modbus接口。正在学习,慢慢更新。



