文章目录
【Orangepi Zero2】串口通信
1.串口基本认知
串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方 式的扩展接口。串行接口(Serial Interface)是指数据一位一位地顺序传送。其特点是通信线路简 单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成 本,特别适用于远距离通信,但传送速度较慢
- 是设备间接线通信的一种方式
- 数据一位一位地顺序传送
- 双向通信,全双工
- 传送速度相对较慢
1.1关于电器标准和协议
串行接口按电气标准及协议来分包括RS-232-C、RS-422、RS485等。RS-232-C、RS-422与RS-485 标准只对接口的电气特性做出规定,不涉及接插件、电缆或协议。
RS-232
也称标准串口,最常用的一种[串行通讯接口,比如我们的电脑主机的9针串口 ,最高速率为20kb/s RS-232是为点对点(即只用一对收、发设备)通讯而设计的,其传送距离最大为约15米。所以RS-232适 合本地设备之间的通信

RS-422
由于接收器采用高输入阻抗和发送驱动器比RS232更强的驱动能力,故允许在相同传输线上连接多个接 收节点,最多可接10个节点。即一个主设备(Master),其余为从设备(Slave),从设备之间不能通 信,所以RS-422支持点对多的双向通信。 RS-422的最大传输距离为1219米,最大传输速率为10Mb/s。平衡双绞线的长度与传输速率成反比
RS-485
是从RS-422基础上发展而来的,无论四线还是二线连接方式总线上可多接到32个设备。

1.2.关于串口的电平
经常听说的UART
异步串行是指UART(Universal Asynchronous Receiver/Transmitter),通用异步接收/发送。 UART包含TTL电平的串口和RS232电平的串口
RS232电平
逻辑1为-3~-15V的电压, 逻辑0为3~15V的电压
- 笔记本通过RS232电平和单片机通信
![]() | ![]() |
|---|
TTL电平
TTL是Transistor-Transistor Logic,即晶体管-晶体管逻辑的简称,它是计算机处理器控制的设备 内部各部分之间通信的标准技术。TTL电平信号应用广泛,是因为其数据表示采用二进制规定, +5V等价于逻辑”1”,0V等价于逻辑”0”。 数字电路中,由TTL电子元器件组成电路的电平是个电压范围,规定: 输出高电平>=2.4V,输出低电平<=0.4V; 输入高电平>=2.0V,输入低电平<=0.8V
-
笔记本电脑通过TTL电平与单片机通信
-
TX发送线(端口)3.1
-
RX接收线 (端口)3.0
-
USB转TTL,使用ch340通信
USB转TTL,使用ch340通信

1.3.串口通信引脚接线
- RXD:数据输入引脚,数据接受,STC89系列对应P3.0口
- TXD:数据发送引脚,数据发送,STC89系列对应P3.1口
- 接线方式


1.3.1.串口编程要素
- 输入/输出数据缓冲器都叫做SBUF, 都用99H地址码,但是是两个独立的8位寄存器
- 代码体现为: 想要接收数据 char data = SBUF 想要发送数据 SBUF = data

- UART是异步串行接口,通信双方使用时钟不同,因为双方硬件配置不同,但是需要约定通信 速度,叫做波特率
对于电脑来说,别人做好了软件,鼠标点点点就能配置好,而苦逼单片机的波特率配置需要我们写 代码
点点点配置什么,我们代码也要配置对应参数

89C51单片机寄存器的配置以及串口的工作模式

字符 ‘a’ 是如何从单片机上传到PC的
a的ASSII码是97,16进制就是0x61, 二进制是01010001,这个8位就是数据位 串口工作模式1,一帧数据有10位,起始位(0),数据位,停止位(1) 那么a的一帧数据就是 0 1000 1010 1 起始位,a的低位到高位,停止位
除了速度要求,还要有数据格式,双方 暗号 对上了再发数据,所以有起始位,和停止位 的概念

一个字节有8位,比如字母‘a’的ASSII码是十进制97,二进制是 0110 0001 ,一次从地位到高位发 送,接收也是

2.Orangepi Zero2串口通信开发


Orangepi Zero2开发板有两组串口,上图那个串口是开发板刚开始登录的时候用到的,还有一组可以通过输入:gpio readall来查询,都是用的TTL针脚型接口,串口接线:TXD和RXD交叉接线,不要接错了。

wiringP库源码:链接:https://pan.baidu.com/s/1eldtWVRGJITLHnOGZSf2bA?pwd=jq46 提取码:jq46
/*在配套的wiringPi库里面有官方给的对应的原码给它复制过来*/
cp ../wiringPiFromWindows/wiringOP-master/examples/serialTest.c .
/*LINUX里面一切皆文件每个硬件设备对应一个文件,查询串口文件*/
ls /dev/ttyS*
/*dev串口S0用来调试信息,当然S0串口禁用掉默认串口的系统消息,也可以当成通信串口来使用,网上有各种命令和配置方式,供大家各自去研究. dev串口S1这个板子是不支持的.
dev串口S5用来信息交互 */
/dev/ttyS0 /dev/ttyS1 /dev/ttyS5

2.1串口调试工具
串口调试需要用到软件安信可串口调试助手,亲测好用,可以调节波特率,可以设置快捷键等。
相关下载链接:链接: https://pan.baidu.com/s/1nrq_xCjEuo78BR5pEIcDMA?pwd=vbn9 提取码: vbn9
2.2.wiringPi的串口开发与优化
/*wiringPi的串口开发与优化和串口信息交互*/
/*本代码,使用了两个线程:一个用于发送数据,另一个用于接收数据。程序首先尝试打开一个串口设备,然后创建两个线程分别处理发送和接收。发送线程从标准输入读取字符串,并通过串口发送出去。接收线程则循环检查串口是否有数据到达,并打印出来。主线程在启动了发送和接收线程后,进入一个无限循环,每10秒休眠一次*/
#include <stdio.h> // 标准输入输出库
#include <string.h> // 字符串操作函数库
#include <errno.h> // 错误号定义
#include <pthread.h> // POSIX线程库
#include <wiringPi.h> // Wiring Pi GPIO库
#include <wiringSerial.h> // Wiring Pi 串口通信库
#include <stdlib.h> // 通用工具库,如内存分配
#include <unistd.h> // UNIX标准函数库,如sleep
// 串口文件描述符
int fd;
// 发送线程处理函数
void* Sendhandler() {
char *sendBuf; // 定义发送缓冲区指针
sendBuf = (char*)malloc(32 * sizeof(char)); // 分配32个字符的内存
while (1) { // 无限循环
memset(sendBuf, '\0', 32); // 清空缓冲区
scanf("%s", sendBuf); // 从标准输入读取字符串到缓冲区
while (*sendBuf) { // 循环直到缓冲区字符串结束
serialPutchar(fd, *sendBuf++); // 通过串口发送当前字符
}
}
}
// 接收线程处理函数
void* Revhandler() {
while (1) { // 无限循环
while (serialDataAvail(fd)) { // 检查串口是否有数据可读取
printf("%c", serialGetchar(fd)); // 串口接收字符并打印
fflush(stdout); // 清空输出缓冲区,确保立即打印
}
}
}
// 主函数,程序的入口点
int main() {
int count; // 用于循环计数的变量
unsigned int nextTime; // 用于记录下一次操作的时间
pthread_t idSend; // 发送线程的线程ID
pthread_t idRev; // 接收线程的线程ID
// 尝试打开串口设备,失败则打印错误并退出
if ((fd = serialOpen("/dev/ttyS5", 115200)) < 0) {
fprintf(stderr, "Unable to open serial device: %s\n", strerror(errno));
return 1;
}
// 创建发送和接收线程
pthread_create(&idSend, NULL, Sendhandler, NULL);
pthread_create(&idRev, NULL, Revhandler, NULL);
// 初始化Wiring Pi库,失败则打印错误并退出
if (wiringPiSetup() == -1) {
fprintf(stdout, "Unable to start wiringPi: %s\n", strerror(errno));
return 1;
}
while (1) { // 主循环
sleep(10); // 休眠10秒
}
printf("\n");
return 0; // 正常退出
}
代码验证:
![]() | ![]() |
|---|
2.3.不用wiringPi,自己封装实现串口通信
如果使用的为Linux5.16内核的系统,uart5默认是关闭的,需要手动打开才能使用。
在/boot/orangepiEnv.txt中加入下面红色字体部分的配置,然后输入指令reboot,重启 Linux系统就可以打开uart5。
orangepi@orangepizero2:~$ sudo vim /boot/orangepiEnv.txt

进入linux系统后,先确认下/dev下是否存在uart5的设备节点
ls /dev/ttyS5

头文件:
/**
* 打开指定的串口设备,并设置波特率。
*
* @param device 串口设备的路径,如 "/dev/ttyS0"。
* @param baud 波特率值,如 B9600, B115200 等。
* @return 如果成功,返回一个非负的文件描述符;如果失败,返回 -1。
*/
int my_serialOpen (const char *device, const int baud);
/**
* 向指定的串口文件描述符写入一个字符串。
*
* @param fd 串口的文件描述符,由 my_serialOpen 函数返回。
* @param s 要发送的字符串。
*/
void my_serialPuts(const int fd, const char *s);
/**
* 从指定的串口文件描述符读取数据,直到遇到换行符或者读取到指定数量的字符。
*
* @param fd 串口的文件描述符。
* @param buffer 存储读取数据的缓冲区。
* @return 读取的字符数量,如果出错或到达文件末尾返回 -1。
*/
int my_serialGets(const int fd, const char *buffer);
/**
* 从指定的串口文件描述符读取单个字符。
*
* @param fd 串口的文件描述符。
* @return 如果有可用的字符,返回该字符;如果到达文件末尾或出错,返回 NULL。
*/
char* serialGetchar(const int fd);
头文件函数程序:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
/**
* 打开指定的串口设备,并设置给定的波特率。
*
* @param device 串口设备的路径,如 "/dev/ttyS0"。
* @param baud 波特率值,支持 9600 和 115200。
* @return 成功返回文件描述符,失败返回 -1 或 -2。
*/
int my_serialOpen (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;
default: return -2; // 不支持的波特率返回错误码
}
// 打开串口设备
if ((fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK)) == -1)
return -1;
// 设置文件描述符的标志为读写模式
fcntl(fd, F_SETFL, O_RDWR);
// 获取当前串口配置
tcgetattr(fd, &options);
// 设置串口为原始模式,无特殊处理
cfmakeraw(&options);
// 设置输入波特率
cfsetispeed(&options, myBaud);
// 设置输出波特率
cfsetospeed(&options, myBaud);
// 清除标志位并设置数据格式
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~PARENB; // 无奇偶校验位
options.c_cflag &= ~CSTOPB; // 1个停止位
options.c_cflag &= ~CSIZE; // 清除数据位
options.c_cflag |= CS8; // 设置为8位数据位
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 非规范模式,不执行输入处理
options.c_oflag &= ~OPOST; // 输出处理
// 设置读取时的超时和最小接收字符数
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 100; // 10秒超时
// 应用串口配置
tcsetattr(fd, TCSANOW, &options);
// 使用ioctl设置串口的DTR和RTS信号
ioctl(fd, TIOCMGET, &status);
status |= TIOCM_DTR;
status |= TIOCM_RTS;
ioctl(fd, TIOCMSET, &status);
// 短暂延时
usleep(10000); // 10毫秒延时
return fd; // 返回文件描述符
}
/**
* 向串口发送字符串。
*
* @param fd 串口的文件描述符。
* @param s 要发送的字符串。
*/
void my_serialPuts(const int fd, const char *s)
{
int n_write = write(fd, s, strlen(s)); // 写入字符串
if(n_write == -1) // 写入失败
{
perror("write"); // 输出错误信息
exit(-1); // 退出程序
}
}
/**
* 从串口读取数据到缓冲区。
*
* @param fd 串口的文件描述符。
* @param buffer 用于存储读取数据的缓冲区。
* @return 成功读取的字节数,失败返回 -1。
*/
int my_serialGets(const int fd, char *buffer)
{
int n_read = read(fd, buffer, 32); // 从串口读取最多32个字节
if(n_read == -1) // 读取失败
{
perror("read"); // 输出错误信息
exit(-1); // 退出程序
}
return n_read; // 返回读取的字节数
}
/**
* 从串口读取单个字符。
*
* @param fd 串口的文件描述符。
* @return 成功读取的字符指针,失败返回 NULL。
*/
char* serialGetchar (const int fd)
{
char buffer[1]; // 创建一个单字节的缓冲区
if (read(fd, buffer, 1) != 1) // 尝试读取一个字节
exit(-1); // 读取失败则退出
return buffer; // 返回读取的字符
}
主程序:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include "mySerial.h"
// 定义串口文件描述符
int fd;
/**
* 用于发送数据的线程函数。
*/
void* serialSend() {
char *send_buf; // 发送缓冲区指针
// 分配32字节的内存用于发送缓冲区,注意这里可能存在错误,sizeof(send_buf)会返回指针的大小而非所需
send_buf = (char *)malloc(32 * sizeof(char)); // 应使用sizeof(char)
while(1) {
// 清除缓冲区内容
memset(send_buf, '\0', 32);
// 从标准输入读取一行数据到发送缓冲区
fgets(send_buf, 32, stdin);
// 调用自定义函数发送数据
my_serialPuts(fd, send_buf);
}
}
/**
* 用于接收数据的线程函数。
*/
void* serialRecieve() {
char *recieve_buf; // 接收缓冲区指针
// 分配32字节的内存用于接收缓冲区,同上,sizeof(recieve_buf)可能不是期望的数值
recieve_buf = (char *)malloc(32 * sizeof(char)); // 应使用sizeof(char)
// 无限循环接收数据
while(1) {
// 清除缓冲区内容
memset(recieve_buf, '\0', 32);
// 调用自定义函数接收数据
my_serialGets(fd, recieve_buf);
// 打印接收到的数据
printf("GET->%s\n", recieve_buf);
// 刷新输出缓冲区,确保立即打印
fflush(stdout);
}
}
/**
* 主函数,用于初始化和创建线程。
*/
int main() {
pthread_t t1, t2; // 线程标识符
// 尝试打开串口设备并设置波特率为115200
if ((fd = my_serialOpen("/dev/ttyS5", 115200)) < 0) {
// 如果打开串口失败,输出错误信息并退出
fprintf(stderr, "Unable to open serial device: %s\n", strerror(errno));
return 1;
}
// 创建用于发送数据的线程
pthread_create(&t1, NULL, serialSend, NULL);
// 创建用于接收数据的线程
pthread_create(&t2, NULL, serialRecieve, NULL);
// 主线程进入一个无限休眠状态,这里可能应该加入对线程的适当管理或退出机制
while(1) {
sleep(3);
}
// 关闭主线程,不会执行到这里,因为前面的while循环是无限的
printf("\n");
return 0;
}
运行结果:







1515

被折叠的 条评论
为什么被折叠?



