Rockchip UART (Universal Asynchronous Receiver/Transmitter) 基于16550A串口标准。内核里面使用8250串口通用驱动,因此能够支持Linux下的标准串口编程。
板上引出四组通用串口,还有一路调试串口,建议不要把调试串口用于其他用途。
这四组通用串口的序号分别是1、6、7、9,在Linux下对应的设备节点分别是/dev/ttyS1 /dev/ttyS6 /dev/ttyS7 /dev/ttyS9.下面以ttyS9为例介绍其用法,其他几路串口也是类似的。
使用TTL电平串口时。按下图所示接线。在ttyS9外接一个TTL电平的串口调试板,来进行收发测试,接线如下
使用232电平串口时,按下图所示接线,在ttyS9外接一个TTL电平转232电平的转接板,然后再接一根USB转232的线来进行收发测试,接线如下
"/dev/ttyS*"的节点都需要system或者root权限才能操作,使用命令行或者android studio编译出来的程序操作串口都需要system权限。如果是使用adb命令行的,先执行如下命令获取system权限。
su system
查看串口信息
busybox stty -F /dev/ttyS9
设置串口波特率为115200
busybox stty -F /dev/ttyS9 speed 115200
设置串口八位数据位 无校验 一位停止位 无回显
busybox stty -F /dev/ttyS9 cs8 -parenb -cstopb -echo
下面进行收发测试
后台接收数据,前台执行发送
cat /dev/ttyS9 &
echo -e "12345\n" > /dev/ttyS9
执行结果如下,可以看到收到PC端发送的内容,同时PC也可以收到YY3588发送的内容
android 编程时由于配置串口属性需要用到ioctl,可以使用C语言来实现,然后封装一个jni接口,由java来调用。C语言实现如下
int speed_arr[] = {
B921600, B460800, B230400, B115200, B57600, B38400, B19200,
B9600, B4800, B2400, B1200, B300,
};
int name_arr[] = {
921600, 460800, 230400, 115200, 57600, 38400, 19200,
9600, 4800, 2400, 1200, 300,
};
int user_uart_open(int uart_num)
{
int fd = -1;
char str[40] = {0};
snprintf(str, sizeof(str) - 1, "/dev/ttyS%d", uart_num);
if (access(str, F_OK) != 0) {
printf("%s do not exist \n", str);
return -1;
}
fd = open(str, O_RDWR | O_NONBLOCK);
if (fd < 0) {
printf("open %s fail \n", str);
return -1;
}
return fd;
}
int user_uart_set_property(int fd, int speed, int databits, int stopbits, int parity)
{
struct termios options;
if (tcgetattr(fd, &options) != 0) {
return -1;
}
tcflush(fd, TCIOFLUSH);
for (unsigned char i = 0; i < sizeof(speed_arr) / sizeof(int); i++) {
if (speed == name_arr[i]) {
cfsetispeed(&options, speed_arr[i]);
cfsetospeed(&options, speed_arr[i]);
}
}
options.c_cflag &= ~CSIZE;
switch (databits) /*设置数据位数*/ {
case 7:
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
return -1;
}
switch (parity) {
case 'n':
case 'N':
options.c_cflag &= ~PARENB; /* Clear parity enable */
options.c_iflag &= ~INPCK; /* Enable parity checking */
break;
case 'o':
case 'O':
options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'e':
case 'E':
options.c_cflag |= PARENB; /* Enable parity */
options.c_cflag &= ~PARODD; /* 转换为偶效验*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'S':
case 's': /*as no parity*/
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
break;
default:
return -1;
}
/* 设置停止位*/
switch (stopbits) {
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
return -1;
}
/* Set input parity option */
if (parity != 'n')
options.c_iflag |= INPCK;
options.c_cc[VTIME] = 150; // 15 seconds
options.c_cc[VMIN] = 1;
options.c_iflag |= ICRNL; // cr -> nl
options.c_lflag &= ~(ECHO | ICANON);
tcflush(fd, TCIOFLUSH); /* Update the options and do it NOW */
if (tcsetattr(fd, TCSANOW, &options) != 0) {
return -1;
}
return 0;
}
这里封装了user_uart_open用于打开指定编号的串口,user_uart_set_property用于设置串口属性,关闭使用Linux原始的close即可
设置串口属性的JNI接口代码如下
extern "C" JNIEXPORT jint JNICALL
Java_com_example_testdemo_MainActivity_UARTSetProperty(JNIEnv *env, jobject /* this */,jint uart_num, jint speed, jint databits, jint stopbits, jint parity)
{
int fd = -1;
int ret = 0;
fd = user_uart_open(uart_num);
if (fd < 0) {
LOGE("user_uart_open %d fail \n",uart_num);
return -1;
}
ret = user_uart_set_property(fd, speed, databits, stopbits, parity);
if (ret < 0) {
LOGE("user_uart_open %d fail \n",uart_num);
}
close(fd);
return ret;
}
同时在 MainActivity 声明上面定义的JNI接口
public native int UARTSetProperty(int uart_num, int speed, int databits, int stopbits, int parity);
在上一章节创建的app基础上,先新增一个class来实现串口收发逻辑,这里的逻辑是,创建一个线程,一直接收数据,然后将收到的数据发送。源码如下
package com.example.testdemo;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class serialTest {
private FileInputStream mInputStream;
private FileOutputStream mOutputStream;
private ReadThread mReadThread;
private volatile boolean mIsRunning = false;
// 串口数据接收回调接口
public interface OnDataReceivedListener {
void onDataReceived(byte[] data, int size);
}
private OnDataReceivedListener mListener;
/**
* 启动串口通信线程
* @param devicePath 串口设备节点路径(如"/dev/ttyS1")
* @return true表示启动成功,false表示失败
*/
public boolean start(String devicePath,OnDataReceivedListener listener) {
if (mIsRunning) {
return false;
}
try {
// 创建输入输出流
mInputStream = new FileInputStream(devicePath);
mOutputStream = new FileOutputStream(devicePath);
mListener = listener;
// 创建并启动读取线程
mIsRunning = true;
mReadThread = new ReadThread();
mReadThread.start();
return true;
} catch (IOException e) {
e.printStackTrace();
closeResources();
return false;
}
}
/**
* 停止串口通信线程并关闭资源
*/
public void stop() {
mIsRunning = false;
mListener =null;
if (mReadThread != null) {
mReadThread.interrupt();
try {
mReadThread.join(100); // 等待线程结束
} catch (InterruptedException e) {
e.printStackTrace();
}
mReadThread = null;
}
closeResources();
}
/**
* 发送数据到串口
* @param data 要发送的数据
*/
public void sendData(byte[] data) {
if (mOutputStream != null && data != null) {
try {
mOutputStream.write(data);
mOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 串口读取线程
*/
private class ReadThread extends Thread {
@Override
public void run() {
super.run();
while (mIsRunning && !isInterrupted()) {
try {
// 非阻塞方式检查是否有数据可读
int available = mInputStream.available();
if (available > 0) {
byte[] buffer = new byte[available];
int size = mInputStream.read(buffer);
if (size > 0) {
// 将收到的数据立即发送回去
sendData(buffer);
// 回调通知数据接收
if (mListener != null) {
mListener.onDataReceived(buffer, size);
}
}
} else {
// 没有数据时短暂休眠
Thread.sleep(10);
}
} catch (IOException e) {
if (mIsRunning) {
e.printStackTrace();
}
break;
} catch (InterruptedException e) {
break;
}
}
}
}
/**
* 关闭串口资源
*/
private void closeResources() {
try {
if (mInputStream != null) {
mInputStream.close();
mInputStream = null;
}
if (mOutputStream != null) {
mOutputStream.close();
mOutputStream = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 检查是否正在运行
*/
public boolean isRunning() {
return mIsRunning;
}
}
然后在 MainActivity 增加一个按钮和一个文本框,按下按钮则执行 serialTest的start 方法,再次按下则调用 stop方法。这里使用mTextLinewriter.writeLine向文本框打印信息
button1.setText("start uart");
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(!isUARTTesting) {
isUARTTesting = true;
mTextLinewriter.writeLine(" start uart test... \n");
UARTSetProperty(9,115200, 8, 1, 'n');
mSerialTest.start("/dev/ttyS9",(data, size) -> {
String received = new String(data, 0, size);
mTextLinewriter.writeLine("recv: " + received + "\n");
});
button1.setText("stop uart");
} else {
mTextLinewriter.writeLine(" stop uart test... \n");
isUARTTesting = false;
mSerialTest.stop();
button1.setText("start uart");
}
}
});
测试时按下 START UART 的按钮可以看到如下效果
串口测试工具发送的数据和收到的一致
232的测试程序和TTL测试程序一样,使用232转接板测试的结果如下