栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

项目总结:i.mx6ull基于S485控制外设

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

项目总结:i.mx6ull基于S485控制外设

文章目录
  • 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接口的设计

声明:
建议先看韦东山的前五篇入门加上驱动大全的UART子系统,这样会比较好理解下面的文章。
ps:欢迎添加微信进群一起交流嵌入式Linux,一起学习进步。

1.设备树部分

你既然想使能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.c
3.应用程序部分

首先我简单介绍一下我的项目:通过S485要发送一串固定的16进制数据出来。外设接受到了这个数据以后能够实现开关机。以及反馈一些设备的信息。

3.1 波特率行规程设置函数
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:
 
	#include 
 
	
	#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) {
		
	}
3.4 主函数如何写 步骤一 open 设备节点
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);
完整代码

我的项目想的完整代码,你可以根据这个去修改成你想要的。

#include 
#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
}
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接口的设计

接下来打算做一个标准的modbus接口。正在学习,慢慢更新。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/939332.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号