BeagleBoneBlack学习之串口通信

时间:2022-06-26 18:52:00

有关Beaglebone的资料请参考官网:BeagleBone资源简介
它的串口资源如下:
BeagleBoneBlack学习之串口通信
一共四个半串口,其中一个串口的rx被系统使用了没引出来,留给用户的还是4个。下面例程以UART1即P9.24/P9.26为例

串口使用方法分为两大步:
1.加载UARTcape
2.访问串口设备

是这样的,BeagleBone官方把所有扩展设备(插在扩展槽上的)都叫cape。无论这个cape是真实的还是虚拟的。本例中只是访问串口资源,就相当于一个虚拟cape(因为用到了官方的扩展功能)

一、加载UARTcape
先说几个相关路径
“/lib/firmware” —— beaglebone 相关设备树文件
“/sys/devices/bone_capemgr.9” —— beaglebone cape配置路径
“/dev” —— linux 设备目录
我们需要在/sys/devices/bone_capemgr.9里加载UART,然后在/dev里就会多出一个ttyO*设备。以UART1为例:
1.先要找到UART1的ID号。
进入/lib/firmware目录,执行如下代码:

ls -l | grep UART

结果如下:
BeagleBoneBlack学习之串口通信
可以看到一堆关于UART的dtbo文件。这些文件就是linux的设备树文件(dts、dtb、dtbo)。更多资料请参考使用BBB的device tree和cape,在此不做多讲。
我们需要从dts(device tree source)文件中找寻UART的编号,但是该文件夹下并没有dts文件。可以用以下命令获取:

dtc -I dtb -O dts ADAFRUIT-UART1-00A0.dtbo > ADAFRUIT-UART1-00A0.dts

该命令是设备树编辑命令,可以正向逆向编辑dts dtbo文件
执行后如下图:
BeagleBoneBlack学习之串口通信
可以看到有dts文件了。
再用cat读取该文件:

cat ADAFRUIT-UART1-00A0.dts

BeagleBoneBlack学习之串口通信
这个part-number就是所要找到UART1的ID号。PS有关设备树请参考使用BBB的device tree和cape

找到UART1的ID号后在/sys/devices/bone_capemgr.9目录下执行:

echo ADAFRUIT-UART1 > slots

那么UART1的设备树驱动便加载完毕,打开/dev目录会发现多了一个ttyO1的设备,该设备便是串口设备。如下图:
BeagleBoneBlack学习之串口通信
如果出现ttyO1则证明配置成功,如果未出现则失败。
PS.ttyO0是系统默认的那个串口,可以通过它以终端方式访问beaglebone

以上是通过bash命令的方式加载UARTcape,下面另附一c语言版本;

#include<stdio.h> 
#include<fcntl.h>
#include<unistd.h>
#define SLOTS "/sys/devices/bone_capemgr.9/slots"

int main()
{
int fd, count;
//mount the Drive of Uart
if ((fd = open(SLOTS, O_WRONLY)) < 0)
{
perror("SLOTS: Failed to open the file. \n");
return -1;
}
if ((count_t = write(fd, "ADAFRUIT-UART1",14))<0)
{
perror("SLOTS:Failed to write to the file\nFailed to mount the UART1");
return -1;
}
close(fd);
//mount successful
return 0;
}

二、访问串口设备

当串口Cape挂载完毕后,便能像普通文件一样访问串口了。有几个地方需要注意下:在linux眼里一切皆是文件,所有的设备都能像文件一样读写,这和单片机、windows里的设备观不一样,需注意。

配置串口有个结构体需要了解下:termios。具体信息执行百度“linux 串口 termios”一大堆资料 在此不再多说
有几个需要注意的地方需留意下:
1.关于串口的读写方式(阻塞和非阻塞)
阻塞模式:读数据时读串口设备没有数据,那么系统会阻塞在这里,一直到串口有数据或者等待超时为止
写数据时,如果缓冲区剩余空间不够,那么系统会阻塞在这里,直到空间足够或等待超时为止
非阻塞模式:读数据如果设备没数据,返回-1,如果有返回读取个数。写数据时如果空间不够,则能写多少写多少,返回写进去的个数
因此推荐使用非阻塞模式,非阻塞模式可通过文件打开方式设置:

fd = open("/dev/ttyO1", O_RDWR | O_NOCTTY | O_NDELAY)

O_RDWR :读写方式打开;
O_NOCTTY :不以终端模式打开,以防键盘对它有影响,比如“ctl+c”,在终端里是结束进程的意思
O_NDELAY : 非阻塞模式打开
2.串口的配置:

tcgetattr(fd, &opt); // get the configuration of the UART
// config UART
opt.c_cflag = B9600 | CS8 | CREAD | CLOCAL;
// 9600 baud, 8-bit, enable receiver, no modem control lines
opt.c_iflag = IGNPAR | ICRNL;
// ignore partity errors, CR -> newline
opt.c_iflag &= ~(IXON | IXOFF | IXANY);
//turn off software stream control
opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
//关闭回显功能,关闭经典输入 改用原始输入
tcflush(fd,TCIOFLUSH); // 清理输入输出缓冲区
tcsetattr(fd, TCSANOW, &opt); // changes occur immmediately

c_cflag 、c_iflag 、c_lflag 都是termios结构体的成员,用以配置串口,具体信息自行百度,在此仅解释用到的内容
tcgetattr(fd,opt)函数:用以获得fd所指向文件的配置参数,保存在opt变量内。
然后修改opt的参数 再调用tcsetattr重设串口参数,如上述代码所示。其中注意串口配置前需要用tcflush()清理下输入输出缓冲区。
关于回显功能:回显就是串口设备接收到什么数据,立马原封不动通过串口发回去,这个咱不需要,关掉就行。

3.输入方式的原始输入和标准输入
标准输入是把串口设备用作终端登录beaglebone时用的,里面有些控制符,比如遇到回车换行read才能读取一次等等。如果该串口不是用以终端登录用的,改成原始模式就是。
原始模式没啥规矩,收到数据就存起来,有read访问时就把数据送出去,没有就存在等着。一般用串口传数据就需要这种方式。

4.关于串口中断啥的
做单片机开发时,串口收数据一般用中断来完成。可能是linux的缘故 并没有在beaglebone上找到有关串口中断的事(linux把一切设备当做文件来处理,文件是没有中断的了)。所以 只能用查询的办法来查看接收串口数据了(1Ghz的主频开个小进程查询串口数据还是小case了)

最后附完整代码如下:
加载UART cape
每5S读取一次串口,如有数据则通过串口传回去,如没有,继续运行。一共循环10次退出

#include<stdio.h> 
#include<fcntl.h>
#include<unistd.h>
#include<termios.h> // using the termios.h library

#define SLOTS "/sys/devices/bone_capemgr.9/slots"

int main()
{
int fd, count_r,count_t,i;
unsigned char buff[100]; // the reading & writing buffer
struct termios opt; //uart confige structure

//mount the Drive of Uart
if ((fd = open(SLOTS, O_WRONLY)) < 0)
{
perror("SLOTS: Failed to open the file. \n");
return -1;
}
if ((count_t = write(fd, "ADAFRUIT-UART1",14))<0)
{
perror("SLOTS:Failed to write to the file\nFailed to mount the UART1");
return -1;
}
close(fd);
//mount successful


//open the UART1: read&write, No block and doesn't serve as an terminal
if ((fd = open("/dev/ttyO1", O_RDWR | O_NOCTTY | O_NDELAY)) < 0)
{
perror("UART: Failed to open the UART device:ttyO1.\n");
return -1;
}


tcgetattr(fd, &opt); // get the configuration of the UART

// config UART

opt.c_cflag = B9600 | CS8 | CREAD | CLOCAL;
// 9600 baud, 8-bit, enable receiver, no modem control lines
opt.c_iflag = IGNPAR | ICRNL;
// ignore partity errors, CR -> newline
opt.c_iflag &= ~(IXON | IXOFF | IXANY);
//turn off software stream control
opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
//关闭回显功能,关闭经典输入 改用原始输入
tcflush(fd,TCIOFLUSH); // 清理输入输出缓冲区

tcsetattr(fd, TCSANOW, &opt); // changes occur immmediately


if ((count_t = write(fd, "Hello BeagleBone\n",18))<0)
{
perror("ERR:Failed to write to the Device:ttyO1\n");
return -1;
}

for(i = 0;i < 10;i++)//查询法 读十次
{
if ((count_r = read(fd,(void*)buff,100))<0)
perror("ERR:No data is ready to be read\n");
else if (count_r == 0)
printf("ERR:No data is ready to be read\n");
else
{
buff[count_r] = 0;
printf("The following was read in [%d]:\n %s\n",count_r,buff);

if ((count_t = write(fd, "DEBUG:",6))<0)
{
perror("ERR:Failed to write to the Device:ttyO1\n");
return -1;
}
if ((count_t = write(fd, buff,count_r))<0)
{
perror("ERR:Failed to write to the Device:ttyO1\n");
return -1;
}
if ((count_t = write(fd, "\n",1))<0)
{
perror("ERR:Failed to write to the Device:ttyO1\n");
return -1;
}
}
usleep(5000000); // 延时 5s
}
close(fd);
return 0;
}

that’s all。据说有select方法查询串口有无数据,下次试试再更。
over