OrangePi Zero 3学习笔记(Android篇)3 - 串口
目录
1. 找到串口号
2. 修改串口权限
3. 串口类
3.1 serialport.hpp
3.2 serialport.cpp
3.2.1 构造函数
3.2.2 Open函数
3.2.3 Close函数
3.2.4 Write函数
3.2.5 Read函数
3.2.6 SetFlowCtrl函数
4. 测试程序
5. 编译
6. 运行验证
除了默认的UART用于shell,Zero 3的24pin接口还有一路UART5:
将这个脚用线短路起来测试,即红色排针(2个5V接口)那排排针往下数4-5脚短路。
1. 找到串口号
可以在shell中运行命令:
1|apollo-p2:/ $ ls -l /dev/tty*
查看系统本身的串口信息:
crw-rw-rw- 1 root root 5, 0 1970-01-01 08:00 /dev/tty
crw------- 1 root root 247, 0 1970-01-01 08:00 /dev/ttyAS0
crw-rw---- 1 bluetooth net_bt_admin 247, 1 1970-01-01 08:00 /dev/ttyAS1
crw------- 1 root root 247, 5 1970-01-01 08:00 /dev/ttyAS5
crw-rw---- 1 bluetooth net_bt_admin 236, 0 2023-08-10 23:01 /dev/ttyBT0
crw------- 1 root root 236, 1 1970-01-01 08:00 /dev/ttyBT1
crw------- 1 root root 4, 64 1970-01-01 08:00 /dev/ttyS0
crw------- 1 root root 4, 65 1970-01-01 08:00 /dev/ttyS1
crw------- 1 root root 4, 66 1970-01-01 08:00 /dev/ttyS2
crw------- 1 root root 4, 67 1970-01-01 08:00 /dev/ttyS3
从原理图上看,AS1对应UART1,标识也是bluetooth
猜测ttyAS5对应UART5,不过尝试读写ttyAS5提示权限不够。
apollo-p2:/ $ whoami
shell
apollo-p2:/ $ echo hello >/dev/ttyAS5
/system/bin/sh: can't create /dev/ttyAS5: Permission denied
1|apollo-p2:/ $ echo hello >/dev/tty
hello
修改权限也不行
127|apollo-p2:/ $ chmod 666 /dev/ttyAS5
chmod: chmod '/dev/ttyAS5' to 0666: Operation not permitted
2. 修改串口权限
找到文件longan/kernel/linux-5.4/scripts/dtc/include-prefixes/arm64/sunxi/sun50iw9.dtsi
serial0 = &uart0;serial1 = &uart1;serial2 = &uart2;serial3 = &uart3;serial4 = &uart4;serial5 = &uart5;
可以看到实际配置了6个Uart。说明不是这里配置。
进入Ubuntu文件系统,进入longan/kernel/linux-5.4文件夹,执行
make menuconfig
在Device Drivers找一圈也没有找到配置。
尝试SELinux方式修改权限。
在device/softwinner/apollo/common/sepolicy/public/file_contexts添加:
/dev/ttyAS5 u:object_r:ttyAS5_device:s0
这样改也不行。
在device/softwinner/apollo/common/system/init.sun50iw9p1.rc中添加修改权限命令:
on post-fs-data# create file for audio dump datamkdir /data/vendor/hardware/audio_d 0777 audio audiomkdir /data/audio_d 0777 media mediachown system system /dev/nsichmod 0660 /dev/nsichmod 0755 /product/bin/HelloWorldchmod 0666 /dev/ttyAS5
apollo-p2:/ $ ls -l /dev/tty*
crw-rw-rw- 1 root root 5, 0 1970-01-01 08:00 /dev/tty
crw------- 1 root root 247, 0 1970-01-01 08:00 /dev/ttyAS0
crw-rw---- 1 bluetooth net_bt_admin 247, 1 1970-01-01 08:00 /dev/ttyAS1
crw-rw-rw- 1 root root 247, 5 1970-01-01 08:00 /dev/ttyAS5
crw-rw---- 1 bluetooth net_bt_admin 236, 0 2023-08-10 23:01 /dev/ttyBT0
crw------- 1 root root 236, 1 1970-01-01 08:00 /dev/ttyBT1
crw------- 1 root root 4, 64 1970-01-01 08:00 /dev/ttyS0
crw------- 1 root root 4, 65 1970-01-01 08:00 /dev/ttyS1
crw------- 1 root root 4, 66 1970-01-01 08:00 /dev/ttyS2
crw------- 1 root root 4, 67 1970-01-01 08:00 /dev/ttyS3
可以看到已经改动了。
apollo-p2:/ $ echo hello </dev/ttyAS5
hello
3. 串口类
参考上一节helloworld的方式(device/softwinner/apollo/apollo-p2)添加SerialPort的文件夹,在这个文件夹里面新增2个文件:serialport.cpp和serialport.hpp。
3.1 serialport.hpp
需要包含的头文件:
#include <stdio.h> /*标准输入输出定义*/
#include <stdlib.h> /*标准函数库定义*/
#include <unistd.h> /*Unix 标准函数定义*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> /*文件控制定义*/
#include <errno.h> /*错误号定义*/
#include <termios.h> /*PPSIX 终端控制定义*/
#include <string.h>
增加类:
using namespace std;
class CSerialport
{private:int fd;const int baudrateSetting[14] = { B1000000, B576000, B500000, B460800, B230400, B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300,};const int baudrate[14] = {1000000, 576000, 500000, 460800, 230400, 115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300, };public:CSerialport();bool Open(const char *dev); bool Open(const char *dev, int baud);bool Open(const char *dev, int baud, int databits, int stopbits, char parity);void Close(void);int Write(unsigned char *buf, int len); int Read(unsigned char *buf, int len); bool SetFlowCtrl(bool enable);
};
- fd:串口的句柄。
- baudrateSetting :底层设置波特率的值,这些参数是底层驱动固定的值。
- baudrate:app层设置波特率的范围,和实际值一致。
- Open:打开串口设备,重载了3个函数,返回值都是true或false,参数的含义分别是
- dev:设备名,例如打开"/dev/ttyAS1", Open("/dev/ttyAS1")即可。
- baud:波特率,有效值看数组baudrate
- databits:数据位长度,有效值7或8,默认8。
- stopbits:停止位长度,有效值1或2,默认1。
- parity:校验方式,有效值'O'(奇校验)、'E'(偶校验)、'N'(无校验),默认'N'。
- Close:关闭串口设备
- Write:写数据到串口,buf表示缓冲,写出的数据,len表示数据长度,返回值为实际写出的数据长度,为-1时表示写错误发生。
- Read:从串口读数据,buf表示缓冲,读入的数据,len表示缓冲长度,返回值为实际读入的数据长度,为0则表示无数据。
- SetFlowCtrl:设置流控功能是否使能,参数enable为true时打开流控功能。返回true或false。
3.2 serialport.cpp
在cpp中实现类的成员函数。
需要包含的头文件:
#include "serialport.hpp"
添加命名空间:
using namespace std;
3.2.1 构造函数
CSerialport::CSerialport()
{fd = -1;
}
初始化串口句柄为-1。
3.2.2 Open函数
这里重载了3个Open函数。
第一个只是打开,没有设置串口的其他关键参数。
bool CSerialport::Open(const char *dev)
{char* _dev=new char[256];strcpy(_dev, dev);fd = open(_dev, O_RDWR | O_NOCTTY | O_NDELAY);if (-1 == fd) { perror("Can't Open Serial Port\n");return false; }return true;
}
第二个是设置了波特率。
bool CSerialport::Open(const char *dev, int baud)
{struct termios options;int i;if(Open(dev) == false)return false; if (tcgetattr(fd, &options) != 0){perror("SetupSerial fail\n");return false;}//设置串口输入波特率和输出波特率 for (i = 0; i < (int)(sizeof(baudrate) / sizeof(int)); i++) {if (baud == baudrate[i]) {cfsetispeed(&options, baudrateSetting[i]); cfsetospeed(&options, baudrateSetting[i]); tcflush(fd, TCIFLUSH);if ((tcsetattr(fd, TCSANOW, &options)) != 0){perror("Serialport set error\n");return false;} return true; }}perror("Open Serial fail: baudrate is invalid\n");return false;
}
第三个函数包括设置串口的常用参数。
bool CSerialport::Open(const char *dev, int baud, int databits, int stopbits, char parity)
{struct termios options;int i;if (Open(dev) == false)return false; if (tcgetattr(fd, &options) != 0){perror("SetupSerial fail\n");return false;}bzero(&options, sizeof(options));for (i = 0; i < (int)(sizeof(baudrate) / sizeof(int)); i++) {if (baud == baudrate[i]) {cfsetispeed(&options, baudrateSetting[i]);cfsetospeed(&options, baudrateSetting[i]);break;}}options.c_cflag |= CLOCAL | CREAD;options.c_cflag &= ~CSIZE;switch (databits){case 7:options.c_cflag |= CS7;break;case 8:default:options.c_cflag |= CS8;break;}switch (parity){case 'O': //奇校验options.c_cflag |= PARENB;options.c_cflag |= PARODD;options.c_iflag |= (INPCK | ISTRIP);//printf("parity is Odd\n");break;case 'E': //偶校验options.c_iflag |= (INPCK | ISTRIP);options.c_cflag |= PARENB;options.c_cflag &= ~PARODD;//printf("parity is Even\n");break;case 'N': //无校验default:options.c_cflag &= ~PARENB;//printf("parity is None\n");break;}if (stopbits == 2){options.c_cflag |= CSTOPB;}else{options.c_cflag &= ~CSTOPB;}options.c_cc[VTIME] = 0;options.c_cc[VMIN] = 0;tcflush(fd, TCIFLUSH);if ((tcsetattr(fd, TCSANOW, &options)) != 0){perror("Serialport set error\n");return false;}return true;
}
3.2.3 Close函数
void CSerialport::Close(void)
{if (-1 == fd){return;}close(fd);
}
3.2.4 Write函数
int CSerialport::Write(unsigned char *buf, int len)
{ssize_t ret;ret = write(fd, buf, len);if(ret == -1)perror("Serialport send fail\n");return ret;
}
3.2.5 Read函数
int CSerialport::Read(unsigned char *buf, int len)
{#define TimeOut 10 //if no data in 10ms, returnint retval;fd_set rfds;struct timeval tv;int ret, pos;tv.tv_sec = TimeOut / 1000; //set the rcv wait time tv.tv_usec = TimeOut % 1000 * 1000; //100000us = 0.1spos = 0;while (1){FD_ZERO(&rfds);FD_SET(fd, &rfds);retval = select(fd + 1, &rfds, NULL, NULL, &tv);if (retval == -1){perror("Serialport no data\n");break;}else if (retval){ret = read(fd, buf + pos, 1);if (-1 == ret){printf("Serialport read no data\n");break;}pos++;if (len <= pos){break;}}else{//printf("retval:%d\n", retval);break;}}return pos;
}
读函数是在10ms内判断设备是否有数据,如果没数据就返回,如果有就继续读。宏定义TimeOut就是定义时间间隔,需要自己根据实际情况设定,一般情况10ms应该就够了。
3.2.6 SetFlowCtrl函数
bool CSerialport::SetFlowCtrl(bool enable)
{struct termios options;if (tcgetattr(fd, &options) != 0){perror("SetupSerial fail\n");return false;}if(enable == true){options.c_cflag |= CRTSCTS;}else{options.c_cflag &= ~CRTSCTS;}tcflush(fd, TCIFLUSH);if ((tcsetattr(fd, TCSANOW, &options)) != 0){perror("Serialport set error\n");return false;}return true;
}
4. 测试程序
新建文件main.cpp
包含的头文件
#include "serialport.hpp"
添加main函数
int main(int argc, char **argv)
{}
定义一个串口类实体
CSerialport sp1;
把main参数打印出来
int i = 0;
unsigned char buf[256];
int len = 0;
printf("The num of parameter:%d\n", argc);
for (i = 0; i < argc; i++)
{printf("%s\n", argv[i]);
}
根据不同的参数个数调用Open函数
if(argc == 1)
{printf("Open default serial port: tty\n");sp1.Open("/dev/tty");
}
else if(argc == 2)
{printf("Open serial port:%s\n", argv[1]);sp1.Open(argv[1]);
}
else if(argc == 3)
{printf("Open serial port:%s, baudrate:%d\n", argv[1], atoi(argv[2]));sp1.Open(argv[1], atoi(argv[2]));
}
else if(argc == 6)
{printf("Open serial port:%s, baudrate:%d, data bits:%d, stop bits:%d, parity:%s\n", argv[1], atoi(argv[2]), atoi(argv[3]), atoi(argv[4]), argv[5]);sp1.Open(argv[1], atoi(argv[2]), atoi(argv[3]), atoi(argv[4]), argv[5]);
}
写字符串
const char *wrData = "Serial Port Write String\n";
sp1.Write((unsigned char *)(wrData), strlen(wrData));
因为此时串口的TxD和RxD是短路的, 此时可以读到写出去的字符串
usleep(50000);
len = sp1.Read(buf, 256);
if(len > 0)
{//buf[len] = 0;printf("Serial Read String:%s", buf);
}
elseprintf("Serial Read String Fail\n");
注意需要delay一段时间再去读。
最后关闭设备
sp1.Close();
return 0;
5. 编译
新建一个Android.bp文件。
cc_binary {name: "SerialPort",srcs: ["main.cpp","serialport.cpp"],product_specific: true
}
在apollo_p2.mk里面添加SerialPort:
PRODUCT_PACKAGES += SerialPort
在Ubuntun中运行lunch后执行:
mmm device/softwinner/apollo/apollo-p2/serialport/
6. 运行验证
通过adb把生成的执行文件push到目标板中。
首先连接目标板:
adb connect 192.168.3.81:5555
IP地址根据自己的板子地址修改。
将文件push到目标板:
adb root
adb remount
adb push out/target/product/apollo-p2/product/bin/SerialPort /product/bin
然后adb shell,进入目标板的product/bin下运行:
apollo-p2:/product/bin # ./SerialPort /dev/ttyAS5 115200
The num of parameter:3
./SerialPort
/dev/ttyAS5
115200
Open serial port:/dev/ttyAS5, baudrate:115200
Serial Read String:Serial Port Write StringSerial Port Write String
将RxD和TxD断开短路,接到一个USB转串口板子上验证。
apollo-p2:/ # ./product/bin/SerialPort /dev/ttyAS5 115200 8 1 N
The num of parameter:6
./product/bin/SerialPort
/dev/ttyAS5
115200
8
1
N
Open serial port:/dev/ttyAS5, baudrate:115200, data bits:8, stop bits:1, parity:N
Serial Read String Fail
电脑上接收到字符串,由于没有发送数据到串口,所以显示Serial Read String Fail。