关于串口也是之前学习过很多次了,详见:
香橙派提供了两路串口,第一路就是在刷机时串口连接的引脚(对应驱动ttyS0),第二路就是物理引脚8和10(对应驱动ttyS5):
![]()
此处要请出老朋友CH340,这次连接物理引脚8和10的第二路串口:

在使用串口连接香橙派的时候,使用的Mobaxterm就可以视为一个串口助手,但Moba更多的是提供一个基于指令交互的平台,所以串口助手的使用还是选择之前用过的AI Thinker:

在实际应用中,单片机作为比较简单的芯片,可以去负责数据的采集,然后通过串口接到相对高级的香橙派或其他芯片,香橙派读取数据并进行复杂的数据分析或开发,包括人工智能,UI,网络等在单片机中难以实现的功能,同时通过串口给单片机发送各种指令。
关于串口的代码,wiringPI库同样提供了demo代码:
![]()
cp一份过来:
![]()
(也可以使用SourceInsight来读代码!)
首先,发现打开默认的demo打开的是串口2的驱动,所以此处要改成串口5的驱动:
![]()
然后编译运行:(显示的就是串口助手中发来的字符的ASCII码形式)

串口助手中记得勾选HEX显示:(发送的就是16进制的0到256)

可以使用之前学习的线程相关概念来优化这个demo,关于线程的知识之前也学过,详见:
- #include
- #include
- #include
-
- #include
- #include
-
- #include
- #include
-
- void *read_serial(void *arg)
- {
- char *sendbuf;
- sendbuf = (char *)malloc(32*sizeof(char));
- char *p = sendbuf;
-
- while(1){
- memset(sendbuf,'\0',32*sizeof(char));
- fgets(sendbuf,sizeof(sendbuf),stdin);
- //scanf("%s",sendbuf);
- while(*sendbuf != '\0'){
- serialPutchar (*((int *)arg), *sendbuf) ; //串口打印数据的函数 serialPutchar()
- sendbuf++;
- }
- sendbuf = p;
- }
-
- pthread_exit(NULL);
-
- }
-
-
-
- void *write_serial(void *arg)
- {
- while(1){
- while(serialDataAvail (*((int *)arg))){ //当串口有数据的时候进入while
- printf ("%c", serialGetchar (*((int *)arg))) ; //串口接收数据的函数serialGetchar()
- fflush (stdout) ;
- }
- }
-
- pthread_exit(NULL);
- }
-
-
-
- int main ()
- {
- int fd ;
- int ret;
- pthread_t read_thread;
- pthread_t write_thread;
-
- if ((fd = serialOpen ("/dev/ttyS5", 115200)) < 0) //打开驱动文件,配置波特率
- {
- fprintf (stderr, "Unable to open serial device: %s\n", strerror (errno)) ;
- return 1 ;
- }
-
- if (wiringPiSetup () == -1)
- {
- fprintf (stdout, "Unable to start wiringPi: %s\n", strerror (errno)) ;
- return 1 ;
- }
-
- ret = pthread_create(&read_thread,NULL,read_serial,(void *)&fd);
- if(ret != 0){
- printf("read_serial create error\n");
- return 1;
- }
- ret = pthread_create(&write_thread,NULL,write_serial,(void *)&fd);
- if(ret != 0){
- printf("write_serial create error\n");
- return 1;
- }
-
- pthread_join(read_thread,NULL);
- pthread_join(write_thread,NULL);
-
- return 0 ;
- }
发送数据:

接收数据:

也可以一边发一边接,因为经过优化,接和发被封装在了不同的线程中!
通过sourceinsight查看跳转wiringPI库实现的串口代码,就会发现函数的实现并不困难,所以可以尝试不使用wiringPI库,自己通过Linux封装函数实现串口的通讯。
首先观察wiringPi库,其对于串口最核心的就是三个函数,serialOpen();serialPutchar();serialGetchar(),所以我就自己写一个C文件来实现这三个函数(其实所谓的自己实现就是根据sourceinsight跳转这三个函数,然后删去一些我认为在使用中不必要的代码,与其说是自己实现,更不如说是对这三个函数进行一个删减,精简化),然后创建一个关于它的h文件,最后在串口通讯的函数里添加这个我写的h文件,使用我自己实现的这三个函数来完成串口的通讯。
步骤为:编写mjm_uart_tool.c -> 编写mjm_uart_tool.h -> 编写serial_mjm_test.c调用mjm_uart_tool.h来实现和刚刚使用wiringPI相同的效果。
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include "wiringSerial.h"
-
- int myserialOpen (const char *device, const int baud)
- {
- struct termios options ;
- speed_t myBaud ;
- int status, fd ;
- switch (baud){
- case 9600: myBaud = B9600 ; break ;
- case 115200: myBaud = B115200 ; break ;
- }
- if ((fd = open (device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK)) == -1)
- return -1 ;
- fcntl (fd, F_SETFL, O_RDWR) ;
- // Get and modify current options:
- tcgetattr (fd, &options) ;
- cfmakeraw (&options) ;
- cfsetispeed (&options, myBaud) ;
- cfsetospeed (&options, myBaud) ;
- options.c_cflag |= (CLOCAL | CREAD) ;
- options.c_cflag &= ~PARENB ;
- options.c_cflag &= ~CSTOPB ;
- options.c_cflag &= ~CSIZE ;
- options.c_cflag |= CS8 ;
- options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG) ;
- options.c_oflag &= ~OPOST ;
- options.c_cc [VMIN] = 0 ;
- options.c_cc [VTIME] = 100 ; // Ten seconds (100 deciseconds)
- tcsetattr (fd, TCSANOW, &options) ;
- ioctl (fd, TIOCMGET, &status);
- status |= TIOCM_DTR ;
- status |= TIOCM_RTS ;
- ioctl (fd, TIOCMSET, &status);
- usleep (10000) ; // 10mS
- return fd ;
- }
-
- void serialSendstring (const int fd, const char *s)
- {
- int ret;
- ret = write (fd, s, strlen (s));
- if (ret < 0)
- printf("Serial Puts Error\n");
- }
-
- int serialGetstring (const int fd, char *buffer)
- {
- int n_read;
- n_read = read(fd, buffer,32);
- return n_read;
- }
-
- int serialDataAvail (const int fd) //用来判断串口有无数据的函数,直接复制黏贴过来的
- {
- int result ;
-
- if (ioctl (fd, FIONREAD, &result) == -1)
- return -1 ;
-
- return result ;
- }
- int myserialOpen (const char *device, const int baud);
- void serialSendstring (const int fd, const char *s);
- int serialGetstring (const int fd, char *buffer);
- int serialDataAvail (const int fd);
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include "mjm_uart_tool.h"
-
- void *read_serial(void *arg)
- {
- char *sendbuf;
- sendbuf = (char *)malloc(32*sizeof(char));
-
- while(1){
- memset(sendbuf,'\0',32*sizeof(char));
- fgets(sendbuf,sizeof(sendbuf),stdin);
- serialSendstring (*((int *)arg), sendbuf) ;
- }
-
- pthread_exit(NULL);
-
- }
-
-
- void *write_serial(void *arg)
- {
- char readbuf[32] = {'\0'};
- while(1){
- while(serialDataAvail (*((int *)arg))){
- serialGetstring (*((int *)arg),readbuf) ;
- printf("-> %s\n",readbuf);
- memset(readbuf,'\0',32);
- }
- }
-
- pthread_exit(NULL);
- }
-
-
-
- int main ()
- {
- int fd ;
-
- int ret;
- pthread_t read_thread;
- pthread_t write_thread;
-
- if ((fd = myserialOpen ("/dev/ttyS5", 115200)) < 0) //打开驱动文件,配置波特率
- {
- fprintf (stderr, "Unable to open serial device: %s\n", strerror (errno)) ;
- return 1 ;
- }
-
- /* if (wiringPiSetup () == -1)
- {
- fprintf (stdout, "Unable to start wiringPi: %s\n", strerror (errno)) ;
- return 1 ;
- }*/
-
- ret = pthread_create(&read_thread,NULL,read_serial,(void *)&fd);
- if(ret != 0){
- printf("read_serial create error\n");
- return 1;
- }
- ret = pthread_create(&write_thread,NULL,write_serial,(void *)&fd);
- if(ret != 0){
- printf("write_serial create error\n");
- return 1;
- }
-
- pthread_join(read_thread,NULL);
- pthread_join(write_thread,NULL);
-
- return 0 ;
- }
注意,由于这里没有使用wiringPI库,所以编译不需要使用之前的build.sh脚本,直接使用gcc就可以,但是要记得链线程的库:
gcc serial_mjm_test.c mjm_uart_tool.c -lpthread
发送数据:

接收数据:
![]()
可见,此时,我将serialOpen();serialPutchar();serialGetchar(),替换成了自己的myserialOpen();serialSendstring();serialGetstring();(还原封不动照搬了serialDataAvail函数),然后实现了和刚刚类似的效果,甚至还有所改进,因为我实现的接收函数可以直接介绍一整个字符串,所以可以在之前打印“->”用于区分,但是原来的serialgetchar是一个字符一个字符接收,很难实现这样的效果。