树莓派学习笔记(四):串口通信与相关开发

串口通信及其开发

协议概述

串口通信是一种多机间通信的方式,所谓多机通信,就是不同的模块之间进行数据交互。

比如基于Cortex A53的树莓派与基于C51的语音模块,各有自己独立的硬件和软件配置,在人发出语音指令时,语音模块根据预先的配置执行相应的动作:从TX口向树莓派发送数据,树莓派在自身的RX口收到数据后,通过自身的TX口向语音模块发出控制指令。

串口通信,特点是一根TX+一根RX线,数据按位传输。模块间RX与TX交叉相接,这决定了串口通信是全双工模式,通信的任何一方在使用一根线接收数据的同时,可以使用另一根线发送数据(好比“对骂”)

窗口两端的通信双方要进行交流,要满足两个条件:

  • 数据收发的频率,即波特率要一致,“语速不能太快“,否则会造成遗失
  • 数据格式一致,“说话对方要听得懂”,基本的格式是数据位+停止位+奇偶位校验位,具体数据格式和详见C51部分

树莓派与电脑串口通信

由于之前树莓派刷机配置基本环境的时候被我们配置为用于打印内核启动信息和登录了,所以这里需要将/boot/cmdline.txt改回来:

1
dwc_otg.lpm_enable=0 console=tty1 【console=serial0,115200】 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait

删除上面中括号包含的内容,这样就解除了登录信息对串口的占用。

然后重启,开始编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<wiringSerial.h>
#include<stdio.h>
#include<wiringPi.h>

int main(){
int fd;
char cmd;
if(wiringPiSetup() == -1){
printf("wiringPi setup error!");
return -1;
}
fd=serialOpen("/dev/ttyAMA0",9600);
while(1){
serialPuts(fd,"heartbeat package \r\n");
delayMicroseconds(1000000);
while(serialDataAvail(fd) != 0){
cmd = serialGetchar(fd);
printf("get:%c\n",cmd);
if(cmd == 'o'){
printf("open port\n");
serialPuts(fd,"open success\r\n");
}
else if(cmd == 'c'){
printf("close port\n");
serialPuts(fd,"close success\r\n");
}
else serialPuts(fd,"un recongnized option\r\n");
}
}
return 0;
}

主体的思路就是每隔一秒发送一个心跳包,另外接收串口缓存区中的数据并进行判断,识别其中存在的指令并给出反馈。

这里的重点是引用头文件wiringSerial.h,以及串口数据读取的几个函数,包括:serialOpenserialPutsserialGetcharserialPutchar,其作用与使用方法(以下内容来自树莓派wiringPi详解,作者lulipro):

int serialOpen (char *device, int baud) device:串口地址,即设备所在的目录。默认一般是”/dev/ttyAMA0”;baud:波特率
返回:正常返回文件描述符,否则返回-1失败。
打开并初始串口
void serialClose (int fd) fd:文件描述符 关闭fd关联的串口
void serialPutchar (int fd, unsigned char c) fd:文件描述符c:要发送的数据 发送一个字节的数据到串口
void serialPuts (int fd, char *s) fd:文件描述符s:发送的字符串,字符串要以’\0’结尾 发送一个字符串到串口
void serialPrintf (int fd, char *message, …) fd:文件描述符message:格式化的字符串 像使用C语言中的printf一样发送数据到串口
int serialDataAvail (int fd) fd:文件描述符返回:串口缓存中已经接收的,可读取的字节数,-1代表错误 获取串口缓存中可用的字节数。
int serialGetchar (int fd) fd:文件描述符返回:读取到的字符 从串口读取一个字节数据返回。如果串口缓存中没有可用的数据,则会等待10秒,如果10后还有没,返回-1所以,在读取前,做好通过serialDataAvail判断下。
void serialFlush (int fd) fd:文件描述符 刷新,清空串口缓冲中的所有可用的数据。
*size_t write (int fd,const void * buf,size_t count) fd:文件描述符buf:需要发送的数据缓存数组count:发送buf中的前count个字节数据返回:实际写入的字符数,错误返回-1 这个是Linux下的标准IO库函数,需要包含头文件#include <unistd.h>当要发送到的数据量过大时,wiringPi建议使用这个函数。
*size_t read(int fd,void * buf ,size_t count); fd:文件描述符buf:接受的数据缓存的数组count:接收的字节数.返回:实际读取的字符数。 这个是Linux下的标准IO库函数,需要包含头文件#include <unistd.h>当要接收的数据量过大时,wiringPi建议使用这个函数。

使用串口调试工具打开串口,运行上述代码,效果如下图所示,可以看到数据的收发和指令的识别正常:

树莓派与语音模块的串口通信

语音模块在前面C51课程部分和全志部分都有介绍,这里原理就不再详述,主要调试一下串口通信,便于后续整合开发的时候实现指令控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<wiringSerial.h>
#include<stdio.h>
#include<wiringPi.h>

int main(){
int fd;
int n_read;
char cmd[128]={'\0'};
if(wiringPiSetup() == -1){
printf("wiringPi setup error!");
return -1;
}
fd=serialOpen("/dev/ttyAMA0",9600);//语音模块的串口通信波特率
while(1){
n_read = read(fd,cmd,sizeof(cmd));
if(n_read == 0){//理论上read会一直阻塞到有键盘输入为止,实际测试时约十秒无输入就会强制返回0
printf("timeout!\n");
continue;
}
if(strstr(cmd,"open")!=NULL){//这里在测试的时候发现发送的指令会被截断,所以不直接用strcmp
serialPuts(fd,"open light\n");
//do something
}
if(strstr(cmd,"close")!=NULL){
serialPuts(fd,"close light\n");
//do other things
}
printf("get %dbyte, content:%s",n_read,cmd);
memset(cmd,'\0',sizeof(cmd)/sizeof(char));
}
return 0;
}

树莓派学习笔记(四):串口通信与相关开发
https://dockingyuan.top/2023/01/06/raspberry/4-串口通信及其开发/
作者
Yuan Yuan
发布于
2023年1月6日
许可协议