树莓派3B串口通信
文章目录
- 树莓派3B串口通信
- 一、串口的基本认知
- 1.1 关于电器标准和协议:
- RS232
- RS422
- RS485
- 1.2 关于串口的电平:
- UART
- RS232电平
- TTL电平
- 1.3 串口通信引脚接线:
- 1.4 串口的通信协议:
- 二、树莓派串口通信开发
- 2.1 树莓派的串口引脚:
- 2.2 串口调试工具:
- 2.3 查看串口驱动文件:
- 2.4 基于WiringPi的串口通信:
- 2.5 不使用WiringPi库自己实现串口通信:
一、串口的基本认知
-
串口通信(Serial Communication)是一种在计算机和外部设备或计算机之间进行的串行数据传输方式。它采用逐位(bit)顺序传输数据,相对于并行通信而言,**串口通信使用的数据线较少,在远距离通信中可以节约通信成本。**串口通信广泛应用于各种设备之间的数据传输,如计算机与外设(如打印机、鼠标、键盘等)、微控制器之间的通信等。
-
串口通信的基本参数包括波特率(数据传输速率)、数据位(每个数据包的位数)、停止位(用于表示单个数据包的结束)、奇偶校验位(用于错误检测)等。这些参数需要在通信双方之间预先约定,以确保数据的正确传输和解析。
-
串口通信的主要优点包括使用简单的线路结构、成本较低、适用于远距离通信等。然而,它也有一些缺点,如传输速度相对较慢、传输距离受限等。因此,在选择通信方式时,需要根据具体的应用场景和需求进行权衡。
1.1 关于电器标准和协议:
串行接口按电气标准及协议来分包括RS-232-C、RS-422、RS485等。RS-232-C、RS-422与RS-485标准只对接口的电气特性做出规定,不涉及接插件、电缆或协议。
RS232
也称标准串口,最常用的一种[串行通讯接口,比如我们的电脑主机的9针串口 ,最高速率为20kb/s,RS-232是为点对点(即只用一对收、发设备)通讯而设计的,其传送距离最大为约15米。所以RS-232适合本地设备之间的通信
RS422
由于接收器采用高输入阻抗和发送驱动器比RS232更强的驱动能力,故允许在相同传输线上连接多个接收节点,最多可接10个节点。即一个主设备(Master),其余为从设备(Slave),从设备之间不能通信,所以RS-422支持点对多的双向通信。
RS-422的最大传输距离为1219米,最大传输速率为10Mb/s。平衡双绞线的长度与传输速率成反比
RS485
是从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电平与单片机进行通信,需要将电脑的USB转成TTL电平,需要用到CH340工具将USB转换成TTL电平实现电脑与MCU之间的通信
1.3 串口通信引脚接线:
- RXD:数据接收引脚,
- TXD:数据发送引脚
- VCC:电源正极
- GND:电源负极
1.4 串口的通信协议:
串口通信协议中的波特率、奇偶检验位和停止位等参数是非常重要的,它们决定了数据的传输方式和数据的完整性检查。以下是一些常见的串口通信参数:
- 波特率(Baud Rate):
波特率是串口通信中的传输速率,它表示每秒传输的比特数。常见的波特率包括 9600、115200、57600 等。
通信的双方必须使用相同的波特率。波特率过高可能会导致数据传输错误,因此需要根据硬件和通信距离来选择适当的波特率。
- 数据位(Data Bits):
数据位指定每个数据字节中的位数,通常为 5、6、7 或 8 位。大多数情况下,使用 8 位数据位以支持 8 位的二进制数据。
- 奇偶检验位(Parity):
奇偶检验位用于检测数据传输中的错误。通常有以下几种选项: 无校验位:不使用奇偶检验位。 奇校验位:确保数据位中有奇数个 “1”。 偶校验位:确保数据位中有偶数个 “1”。 奇偶检验位通常用于检测单比特错误。
- 停止位(Stop Bits):
停止位表示数据字节的结束。通常有 1 位和 2 位停止位选项,其中 1 位停止位是最常见的选择。 这些参数通常一起组合,以确定数据的帧格式。例如,常见的串口通信设置是:
波特率:9600 数据位:8 无奇偶检验位 1 位停止位 这意味着每个数据帧由 10 位组成:1 位起始位、8 位数据位、无奇偶检验位和 1 位停止位。
正确配置这些参数对于串口通信非常重要,因为通信的双方必须使用相同的参数,否则会导致数据传输错误。通常,串口设备的规格表明了所需的通信参数设置。在编程中,你需要使用相应的库函数来设置这些参数,以确保正确的数据传输和校验。
二、树莓派串口通信开发
2.1 树莓派的串口引脚:
前面我们在对树莓派刷机的时候,默认串口是接到蓝牙上面的,因为想要看到树莓派的启动过程,所以我们把串口配置到了调试信息上,后来我们树莓派配置完成以后我们又把串口配置成默认的了,所以这里就不需要配置了,如果想要配置我们这样做:
- 修改 /boot/cmdline.txt:
1. sudo vi /boot/cmdline.txt
2. 将“console=serial,115200”删除
然后我们保存退出,接着执行下面指令重启树莓派:
sudo reboot
2.2 串口调试工具:
使用安信可串口调试助手,亲测好用,可以调节波特率,可以设置快捷键等。
2.3 查看串口驱动文件:
Linux系统一切皆文件,每一个硬件设备对应一个文件,进入下面路径查看串口文件:
cd /dev
2.4 基于WiringPi的串口通信:
#include <stdio.h>
#include <wiringPi.h>
#include <wiringSerial.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
int fd;
/* 发送线程函数 */
void* Sendhandler(void* arg)
{
char *sendBuffer = NULL; //发送缓冲区
sendBuffer = (char *)malloc(128 * sizeof(char)); //分配发送缓冲区内存
while(1){
memset(sendBuffer, '\0', 128); //清空发送缓冲区
printf("请输入要发送的内容:\n");
scanf("%s", sendBuffer); //输入要发送的内容
getchar();
while(*sendBuffer!= '\0'){
serialPutchar(fd, *sendBuffer); //发送数据
sendBuffer++; //指向下一个位置
}
}
pthread_exit(NULL); //退出线程
}
/* 接收线程函数 */
void* Recvhandler(void* arg)
{
char readBuffer[128]; //接收缓冲区
while(1){
int len = serialDataAvail(fd); //检查串口是否有数据
if(len > 0){
memset(readBuffer, '\0', 128); //清空接收缓冲区
for(int i=0; i<len; i++){
readBuffer[i] = serialGetchar(fd); //读取串口数据
}
printf("接收到的收据是: %s\n", readBuffer);
}
}
pthread_exit(NULL); //退出线程
}
int main()
{
pthread_t idSend; //发送线程ID
pthread_t idRecv; //接收线程ID
fd = serialOpen("/dev/ttyAMA0", 9600); //打开串口
if(fd < 0){
printf("打开串口设备失败!\n");
return -1;
}
pthread_create(&idSend, NULL, Sendhandler, NULL); //创建发送线程
pthread_create(&idRecv, NULL, Recvhandler, NULL); //创建接收线程
if(wiringPiSetup() == -1){ //初始化wiringPi
printf("初始化wiringPi失败! \n");
}
pthread_join(idSend, NULL); //等待发送线程退出
pthread_join(idRecv, NULL); //等待接收线程退出
serialClose(fd); //关闭串口
return 0;
}
2.5 不使用WiringPi库自己实现串口通信:
#ifndef __MYSERIALTOOL_H__
#define __MYSERIALTOOL_H__
/*
* @Author: <NAME> 打开指定的串口设备,并设置波特率
*
* @param device 串口设备名称,如"/dev/ttyUSB0"
* @param baud 波特率,如9600、115200等
* @return 成功返回文件描述符,失败返回-1
*/
int mySerialOpen(const char *device, const int baud);
/*
* @Author: 向指定的串口设备发送字符串
*
* @param fd 串口设备文件描述符
* @param str 要发送的字符串
* @return 无
*/
void mySerialSendString(const int fd, const char *str);
/*
* @Author: 从指定的串口设备读取字符串
*
* @param fd 串口设备文件描述符
* @param buffer 读取到的字符串存放的缓冲区
* @return 读取到的字符串的长度,失败返回-1
*/
int mySerialReadString(const int fd, char *buffer);
#endif
#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>
/*打开指定的串口设备,并设置波特率*/
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;
default:
printf("不支持的波特率!\n");
return -2;
}
//打开串口设备
if( (fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY)) == -1){
printf("无法打开串口设备\n");
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; // 返回文件描述符
}
/*向指定的串口设备发送字符串*/
void mySerialSendString(const int fd, const char *str)
{
int ret;
ret = write(fd, str, strlen(str)); // 发送字符串
if(ret == -1){
printf("串口发送失败!\n");
exit(-1); // 发送失败,退出程序
}
}
/*从指定的串口设备读取字符串*/
int mySerialReadString(const int fd, char *buffer)
{
int n_read;
n_read = read(fd, buffer, 32); // 读取串口数据
if(n_read == -1){
printf("串口读取失败!\n");
exit(-1); // 读取失败,退出程序
}
}
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include "mySerialTool.h"
int fd;
void* readSerial(void* arg)
{
char readBuffer[32] = {'\0'}; //定义读缓冲区
while(1){
memset(readBuffer, '\0', sizeof(readBuffer)); //清空缓冲区
int n_read = mySerialReadString(fd, readBuffer); //读取串口数据
printf("从串口中读取的数据是:%s,读取了%d个字节\n", readBuffer, n_read);
}
pthread_exit(NULL); //线程退出
}
void* sendSerial(void* arg)
{
char sendBuffer[32] = {'\0'}; //定义发送缓冲区
while(1){
memset(sendBuffer, '\0', sizeof(sendBuffer)); //清空缓冲区
printf("请输入要发送的数据:\n");
scanf("%s", sendBuffer); //输入要发送的数据
mySerialSendString(fd, sendBuffer); //发送数据
}
pthread_exit(NULL); //线程退出
}
int main(int argc, char **argv)
{
pthread_t readThread; //读线程ID
pthread_t sendThread; //发送线程ID
char deviceName[32] = {'\0'};
if(argc < 2){ //查看命令行参数是否存在
printf("Usage: %s /dev/ttyAMA0\n");
return -1;
}
strcpy(deviceName, argv[1]); //将命令行参数赋值给deviceName
fd = mySerialOpen(deviceName, 9600); //打开串口
if(fd < 0){
printf("打开串口设备失败\n");
return -1;
}
pthread_create(&readThread, NULL, readSerial, NULL); //创建读线程
pthread_create(&sendThread, NULL, sendSerial, NULL); //创建发送线程
pthread_join(readThread, NULL); //等待读线程结束
pthread_join(sendThread, NULL); //等待发送线程结束
while(1){
sleep(5);
}
close(fd); //关闭串口
return 0;
}