文章目录
- 基于官方提供的串口测试
- 代码部分
- 解析代码部分
- 1. `usage` 函数
- 2. `opt_parsing_err_handle` 函数
- 3. `sig_handle` 函数
- 4. `init_serial` 函数
- 5. `serial_write` 函数
- 6. `serial_read` 函数
- 7. `run_read_mode` 函数
- 8. `run_write_mode` 函数
- 9. `run_loopback_test` 函数
- 进行测试
- 第一步编译
- 第二步发送到板子
- 第三步调试
- 测试前注意
- 先测回环
- 再测写
- 最后测读
- 基于原生Linux串口开发
- 代码
- 测试
- 优化基于Linux原生串口开发
- 代码
- 测试
基于官方提供的串口测试
代码部分
创龙T113-i官方资料包给的代码:
uart_rw.c,功能很高端,支持读取、写入和回环测试三种模式!
/* Copyright 2018 Tronlong Elec. Tech. Co. Ltd. All Rights Reserved. */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/time.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <libgen.h>
#include <signal.h>
#include <getopt.h>
#define NOPASS_CONDITIONS 3
#define INADEQUATE_CONDITIONS 10
enum Mode { READ, WRITE, LOOPBACK };
/* Exit flag */
volatile bool g_quit = false;
/* Short option names */
static const char g_shortopts [] = ":d:s:rwvhl";
/* Option names */
static const struct option g_longopts [] = {
{ "device", required_argument, NULL, 'd' },
{ "read", no_argument, NULL, 'r' },
{ "write", no_argument, NULL, 'w' },
{ "loopback", no_argument, NULL, 'l' },
{ "size", required_argument, NULL, 's' },
{ "version", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
{ 0, 0, 0, 0 }
};
static void usage(FILE *fp, int argc, char **argv) {
fprintf(fp,
"Usage: %s [options]\n\n"
"Options:\n"
" -d | --device Device such as '/dev/ttyS0'\n"
" -r | --read Read\n"
" -w | --write Write\n"
" -l | --loopback loopback test\n"
" -s | --size Read size\n"
" -v | --version Display version information\n"
" -h | --help Show help content\n"
" e.g. %s -d /dev/ttyS1 -r -s 256\n"
" %s -d /dev/ttyS1 -w -s 1024\n"
" %s -d /dev/ttyS1 -l -s 1024\n\n"
"", argv[0], argv[0], argv[0], argv[0]);
}
static void opt_parsing_err_handle(int argc, char **argv, int flag) {
/* Exit if no input parameters are entered */
int state = 0;
if (argc < 2) {
printf("No input parameters are entered, please check the input.\n");
state = -1;
} else {
/* Feedback Error parameter information then exit */
if (optind < argc || flag) {
printf("Error: Parameter parsing failed\n");
if (flag)
printf("\tunrecognized option '%s'\n", argv[optind-1]);
while (optind < argc) {
printf("\tunrecognized option '%s'\n", argv[optind++]);
}
state = -1;
}
}
if (state == -1) {
printf("Tips: '-h' or '--help' to get help\n\n");
exit(2);
}
}
void sig_handle(int arg) {
g_quit = true;
}
int init_serial(int *fd, const char *dev) {
struct termios opt;
/* open serial device */
if ((*fd = open(dev, O_RDWR)) < 0) {
perror("open()");
return -1;
}
/* define termois */
if (tcgetattr(*fd, &opt) < 0) {
perror("tcgetattr()");
return -1;
}
opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
opt.c_oflag &= ~OPOST;
/* Character length, make sure to screen out this bit before setting the data bit */
opt.c_cflag &= ~CSIZE;
/* No hardware flow control */
opt.c_cflag &= ~CRTSCTS;
/* 8-bit data length */
opt.c_cflag |= CS8;
/* 1-bit stop bit */
opt.c_cflag &= ~CSTOPB;
/* No parity bit */
opt.c_iflag |= IGNPAR;
/* Output mode */
opt.c_oflag = 0;
/* No active terminal mode */
opt.c_lflag = 0;
/* Input baud rate */
if (cfsetispeed(&opt, B115200) < 0)
return -1;
/* Output baud rate */
if (cfsetospeed(&opt, B115200) < 0)
return -1;
/* Overflow data can be received, but not read */
if (tcflush(*fd, TCIFLUSH) < 0)
return -1;
if (tcsetattr(*fd, TCSANOW, &opt) < 0)
return -1;
return 0;
}
int serial_write(int *fd, const char *data, size_t size) {
int ret = write(*fd, data, size);
if ( ret < 0 ) {
perror("write");
tcflush(*fd, TCOFLUSH);
}
return ret;
}
int serial_read(int *fd, char *data, size_t size) {
size_t read_left = size;
size_t read_size = 0;
char *read_ptr = data;
struct timeval timeout = {5, 0};
memset(data, 0, size);
fd_set rfds;
while (!g_quit) {
FD_ZERO(&rfds);
FD_SET(*fd, &rfds);
timeout.tv_sec = 5;
timeout.tv_usec = 0;
if (read_left == 0)
break;
switch (select(*fd+1, &rfds, NULL, NULL, &timeout)) {
case -1:
perror("select()");
break;
case 0:
perror("timeout and retry");
break;
default:
if (FD_ISSET(*fd,&rfds)) {
read_size = read(*fd, read_ptr, read_left);
if (read_size == 0)
break;
read_ptr += read_size;
read_left -= read_size;
}
}
}
return read_size;
}
int run_read_mode(char *dev, size_t size) {
char *buf = NULL;
int fd = -1;
int ret = -1;
ret = init_serial(&fd, dev);
if (ret < 0) {
close(fd);
return -1;
}
printf("Mode : read\n");
if (size <= 0) {
printf ("Error : Incorrect size settings\n");
exit(INADEQUATE_CONDITIONS);
}
buf = (char*)malloc(size + 1);
buf[size] = '\0';
ret = serial_read(&fd, buf, size);
printf("recv: %s\nsize: %d\n", buf, ret);
free(buf);
return 0;
}
int run_write_mode(char *dev, size_t size) {
int fd = -1;
int ret = -1;
ret = init_serial(&fd, dev);
if (ret < 0) {
close(fd);
return -1;
}
printf("Mode : write\n");
if (size <= 0) {
printf("Error : Incorrect size settings\n");
exit(INADEQUATE_CONDITIONS);
}
int i = 0;
char context;
size_t write_size = 0;
while (!g_quit) {
if (i > 7)
i = 0;
context = (char)('0' + i);
write_size += serial_write(&fd, &context, sizeof(context));
i ++;
if (size == write_size)
break;
}
printf("send size: %zd\n", write_size);
return 0;
}
int run_loopback_test(char *dev, size_t size) {
int fd;
int ret;
size_t buf_size;
int serial_buf_size;
ret = init_serial(&fd, dev);
if (ret < 0)
return -1;
printf("Start uart loopback testing.\n");
/* Serial port buffer size generally defaults to 2k - 4k */
char *write_buf = (char*)malloc(size);
char *read_buf = (char*)malloc(size);
buf_size = size;
while (buf_size > 0)
{
if(buf_size > 1024) {
serial_buf_size = 1024;
} else {
serial_buf_size = buf_size;
}
// Generate random data to write.
memset(write_buf, rand() % 26 + 65, serial_buf_size);
memset(read_buf, 0, serial_buf_size);
ret = serial_write(&fd, write_buf, serial_buf_size);
/* delay > 1024 / 115200 * 1000000 */
usleep(90000);
ret = serial_read(&fd, read_buf, serial_buf_size);
ret = memcmp(read_buf, write_buf, serial_buf_size);
if (ret != 0) {
printf("Result : Test failed\n");
goto release;
}
buf_size -= 1024;
}
printf("send size: %zd\n", size);
printf("recv size: %zd\n", size);
printf("Result : Test pass\n");
release:
free(write_buf);
free(read_buf);
close (fd);
if (ret != 0) {
return NOPASS_CONDITIONS;
} else {
return 0;
}
}
int main(int argc, char *argv[]) {
int c = 0;
int flag = 0;
int mode = -1;
size_t size = 0;
char *dev = NULL;
int ret = -1;
/* Parsing input parameters */
while ((c = getopt_long(argc, argv, g_shortopts, g_longopts, NULL)) != -1) {
switch (c) {
case 'd':
dev = optarg;
break;
case 'r':
mode = READ;
break;
case 'w':
mode = WRITE;
break;
case 'l':
mode = LOOPBACK;
break;
case 's':
size = atoi(optarg);
break;
case 'v':
/* Display the version */
printf("version : 1.0\n");
exit(0);
case 'h':
usage(stdout, argc, argv);
exit(0);
default :
flag = 1;
break;
}
}
opt_parsing_err_handle(argc, argv, flag);
/* Ctrl+c handler */
signal(SIGINT, sig_handle);
switch (mode) {
case READ:
if(run_read_mode(dev, size) < 0) {
return INADEQUATE_CONDITIONS;
}
break;
case WRITE:
if(run_write_mode(dev, size) < 0) {
return INADEQUATE_CONDITIONS;
}
break;
case LOOPBACK:
ret = run_loopback_test(dev, size);
if(ret < 0) {
return INADEQUATE_CONDITIONS;
} else if(ret == NOPASS_CONDITIONS) {
return NOPASS_CONDITIONS;
}
break;
default:
break;
}
return 0;
}
解析代码部分
不深究可以不看,了解函数啥功能即可。
1. usage
函数
- 作用:打印帮助信息给用户。
- 参数:
FILE *fp
: 输出流,可以是标准输出(stdout)或标准错误(stderr)。argc, argv[]
: 命令行参数的数量和值,用于在示例中显示程序名。
- 行为:当用户请求帮助(
-h
或--help
)时调用,向用户提供如何使用该工具的信息。
2. opt_parsing_err_handle
函数
- 作用:处理命令行选项解析过程中的错误。
- 参数:
argc, argv[]
: 命令行参数的数量和值。flag
: 标记是否有未知选项被识别。
- 行为:如果命令行参数为空或者存在无法识别的选项,则打印错误信息并提示用户使用
-h
获取帮助,然后退出程序。
3. sig_handle
函数
- 作用:信号处理器,用来响应中断信号(如Ctrl+C)。
- 参数:
int arg
,传递给信号处理器的信号编号。 - 行为:设置全局变量
g_quit
为真,通知其他部分停止工作。
4. init_serial
函数
- 作用:初始化串行端口配置。
- 参数:
fd
: 文件描述符指针,将被设置为打开的串行端口。dev
: 设备路径字符串,例如/dev/ttyS0
。
- 行为:根据提供的设备路径打开串行端口,并配置波特率、数据位、停止位等参数。它还会清除终端模式并禁用硬件流控制。
5. serial_write
函数
- 作用:向串行端口写入数据。
- 参数:
fd
: 文件描述符指针。data
: 指向要发送的数据的指针。size
: 要发送的数据大小。
- 行为:尝试将指定数量的字节写入到串行端口,失败时刷新输出缓冲区并报告错误。
6. serial_read
函数
- 作用:从串行端口读取数据。
- 参数:
fd
: 文件描述符指针。data
: 指向存储接收到的数据的缓冲区。size
: 预期接收的数据量。
- 行为:使用
select
函数等待数据到达,然后尽可能多地读取数据直到达到预期大小或超时。如果发生错误或超时,会给出相应的错误信息。
7. run_read_mode
函数
- 作用:执行读取模式操作。
- 参数:
dev
: 设备路径。size
: 期望读取的数据大小。
- 行为:以非阻塞方式读取指定大小的数据,并将其打印出来。如果指定大小无效,则报错退出。
8. run_write_mode
函数
- 作用:执行写入模式操作。
- 参数:
dev
: 设备路径。size
: 要写入的数据大小。
- 行为:循环地写入字符到串行端口,直到写入了指定大小的数据。每次写入后,字符递增,模拟连续的数据流。
9. run_loopback_test
函数
- 作用:执行回环测试。
- 参数:
dev
: 设备路径。size
: 测试过程中使用的数据大小。
- 行为:生成随机数据写入串行端口,然后立即尝试读回相同的数据,比较两者是否一致。如果不一致,则认为测试失败;否则,测试通过。
进行测试
第一步编译
用交叉编译工具进行编译。
不会配置交叉编译链的,请参考:(二)编译原生SDK以及配置交叉编译链中配置交叉编译链的部分。
arm-linux-gnueabi-gcc uart_rw.c -o uart_rw
查看一下可执行文件类型,避免错误。
第二步发送到板子
借助基于ssh的scp工具,不会的可以参考(四)配置有线网口、SSH登陆、文件传输以及运行交叉编译程序测试中SHH登陆以及文件传输部分。
scp ./uart_rw root@192.168.1.101:/root/zhua
出现问题:
根据提示,解决问题,并再次发送:
第三步调试
测试前注意
如何运行可执行文件,查看如何运行
./uart_rw -h
当然直接读代码也是可以的,参考这部分:
注意一下波特率是115200,可以从代码中了解到:
最后查看用的串口4是否开启(可以自行指定),注意创龙的是ttyAS4,跟传统的名称还是有区别的
ls /dev
可以在设备文件夹下看到,说明串口的设备是成功加载到了系统中。如果看不到,请进一步修改设备树,书写设备驱动文件(不会的,请参考后面的章节),再次编译镜像,进行烧录。
先测回环
./uart_rw -d /dev/ttyAS4 -l -s 1024
记得要把串口4的TX和RX短接。另外我开启的是串口4。
再测写
注意这里写的内容就是0-7 来回循环,看代码便知。
./uart_rw --device /dev/ttyAS4 --write --size 16
或者简写参数
./uart_rw -d /dev/ttyAS4 -w -s 16
最后测读
这里我读8个长度,用串口助手发送87654321。
./uart_rw --device /dev/ttyAS4 --read --size 8
或者简写
./uart_rw -d /dev/ttyAS4 -r -s 8
中间提示的,timeout and retry: Success应该是一帧数据的帧检测,可以参考蓝桥杯专栏的(十六)串口UART进行学习。
基于原生Linux串口开发
功能简单,借助线程操作,支持读写同步。
步骤:初始化串口,开启父子线程,父线程读操作,子线程写操作。
代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/time.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <libgen.h>
#include <signal.h>
int init_serial(int *fd, const char *dev) {
struct termios opt;
/* open serial device */
if ((*fd = open(dev, O_RDWR)) < 0) {
perror("open()");
return -1;
}
/* define termois */
if (tcgetattr(*fd, &opt) < 0) {
perror("tcgetattr()");
return -1;
}
opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
opt.c_oflag &= ~OPOST;
/* Character length, make sure to screen out this bit before setting the data bit */
opt.c_cflag &= ~CSIZE;
/* No hardware flow control */
opt.c_cflag &= ~CRTSCTS;
/* 8-bit data length */
opt.c_cflag |= CS8;
/* 1-bit stop bit */
opt.c_cflag &= ~CSTOPB;
/* No parity bit */
opt.c_iflag |= IGNPAR;
/* Output mode */
opt.c_oflag = 0;
/* No active terminal mode */
opt.c_lflag = 0;
/* Input baud rate */
if (cfsetispeed(&opt, B115200) < 0)
return -1;
/* Output baud rate */
if (cfsetospeed(&opt, B115200) < 0)
return -1;
/* Overflow data can be received, but not read */
if (tcflush(*fd, TCIFLUSH) < 0)
return -1;
if (tcsetattr(*fd, TCSANOW, &opt) < 0)
return -1;
return 0;
}
int serial_write(int *fd, const char *data, size_t size) {
int ret = write(*fd, data, size);
if ( ret < 0 ) {
perror("write");
tcflush(*fd, TCOFLUSH);
}
return ret;
}
int serial_read(int *fd, size_t size)
{
int read_size = 0;
char data[128] = {'\0'};
while(1){
read_size = read(*fd, data, size);
if(read_size!=0){
printf("read_size = %d,context = %s\n",read_size,data);
memset(data,'\0',128);
read_size = 0;
}
}
return 0;
}
int main(int argc ,char *argv[])
{
__pid_t pid = 0;
char *writedata = "write from root\n";
int ret= -1;
int fd = -1;
if(argc<2){
printf("tips:./myuart /dev/xxx\n");
printf("argc = %d,argv[0] = %s,,argv[1] = %s\n",argc,argv[0],argv[1]);
}
ret = init_serial(&fd, argv[1]);
if(ret == -1){
return -1;
}
//开启2个线程
pid = fork();
if(pid>0){ //父进程 就是父进程的pid号
serial_read(&fd,128);
}else if(pid==0){ //子进程
while(1){
serial_write(&fd,writedata,strlen(writedata));
sleep(3);
}
}else{ //fork线程错误错误
perror("fork faild\n");
return -1;
}
return 0;
}
测试
执行指令:
./myuart /dev/ttyAS4
注意使用的是串口4,波特率115200。
优化基于Linux原生串口开发
向官方牛x的功能靠近一点点!
实现指定长短的发送,可以双方互不干扰。
这里是开启两个线程,一个是发送线程,一个是接收线程。在原有的Linux原生串口代码的基础上改进,之前使用的fork函数,创建的父子线程有局限性,资源争夺问题,数据共享等问题,所以这里借助pthread_create直接创建两个新的线程。
可以参考(十四)基于Linux的串口开发中基于Linux库的开发,几乎一样。
代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/time.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <libgen.h>
#include <signal.h>
#include <pthread.h>
int fd = -1;
int init_serial(int *fd, const char *dev) {
struct termios opt;
/* open serial device */
if ((*fd = open(dev, O_RDWR)) < 0) {
perror("open()");
return -1;
}
/* define termois */
if (tcgetattr(*fd, &opt) < 0) {
perror("tcgetattr()");
return -1;
}
opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
opt.c_oflag &= ~OPOST;
/* Character length, make sure to screen out this bit before setting the data bit */
opt.c_cflag &= ~CSIZE;
/* No hardware flow control */
opt.c_cflag &= ~CRTSCTS;
/* 8-bit data length */
opt.c_cflag |= CS8;
/* 1-bit stop bit */
opt.c_cflag &= ~CSTOPB;
/* No parity bit */
opt.c_iflag |= IGNPAR;
/* Output mode */
opt.c_oflag = 0;
/* No active terminal mode */
opt.c_lflag = 0;
/* Input baud rate */
if (cfsetispeed(&opt, B115200) < 0)
return -1;
/* Output baud rate */
if (cfsetospeed(&opt, B115200) < 0)
return -1;
/* Overflow data can be received, but not read */
if (tcflush(*fd, TCIFLUSH) < 0)
return -1;
if (tcsetattr(*fd, TCSANOW, &opt) < 0)
return -1;
return 0;
}
int serial_write(int *fd, const char *data, size_t size) {
int ret = write(*fd, data, size);
if ( ret < 0 ) {
perror("write");
tcflush(*fd, TCOFLUSH);
}
return ret;
}
int serial_read(int *fd, size_t size)
{
int read_size = 0;
char data[128] = {'\0'};
while(1){
read_size = read(*fd, data, size);
if(read_size!=0){
printf("read_size = %d,context = %s\n",read_size,data);
memset(data,'\0',128);
read_size = 0;
}
}
return 0;
}
void* recive()
{
printf("recive pthread========ok========\n");
while(1){
serial_read(&fd,128);
}
}
char *writedata = NULL;
int nwrite = 0;
void* send()
{
printf("send pthread========ok========\n");
while(1){
printf("please enter n size what you want to write and press Enter to end \n");
scanf("%d",&nwrite);
writedata = (char *)malloc(nwrite*sizeof(char));
scanf("%s",writedata);
serial_write(&fd,writedata,strlen(writedata));
free(writedata);
writedata = NULL;
}
}
int main(int argc ,char *argv[])
{
pthread_t recivet;
pthread_t sendt;
int ret= -1;
if(argc<2){
printf("tips:./myuart /dev/xxx\n");
printf("argc = %d,argv[0] = %s,,argv[1] = %s\n",argc,argv[0],argv[1]);
}
ret = init_serial(&fd, argv[1]);
if(ret == -1){
return -1;
}
pthread_create(&recivet,NULL,recive,NULL);//接收线程
pthread_create(&sendt,NULL,send,NULL);//发送进程
while(1){
//啥事也不干 3秒睡一次
sleep(3);
}
return 0;
}
测试
注意这里因为用到的pthread_create函数是POSIX线程(pthreads)库的一部分,因此在编译和链接程序时需要链接到这个库,可以通过在编译命令中添加 -lpthread 选项来实现。否则会失败。
必须链接库编译
arm-linux-gnueabi-gcc myuart.c -o myuart -lpthread
输入数据格式一定要注意,因为使用的scanf这个函数,严格遵守它的规范,用回车或者空格断开数据。