树莓派3B串口通信

news2024/11/14 15:01:06

树莓派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-CRS-422RS485等。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;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2115674.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

乐凡北斗 | 手持北斗智能终端的作用与应用场景

在科技日新月异的今天&#xff0c;北斗智能终端作为一项融合了北斗导航系统与现代智能技术的创新成果&#xff0c;正悄然改变着我们的生活方式和工作模式。 ​北斗智能终端&#xff0c;是以北斗卫星导航系统为核心&#xff0c;集成了高精度定位、导航、授时等功能的智能设备。它…

JavaEE:多线程进阶(JUC [java.util.concurrent] 的常见类)

文章目录 JUC什么是JUCCallable 接口理解 Callable理解FutureTask ReentrantLock信号量 SemaphoreCountDownLatch JUC 什么是JUC JUC的全称为: java.util.concurrent. JUC是Java并发工具包的一部分。它提供了一组并发编程工具和类&#xff0c;用于处理多线程编程和并发任务。…

SprinBoot+Vue校园数字化图书馆系统的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质…

Linux 操作系统 进程(1)

什么是进程 想要了解什么是进程&#xff0c;或者说&#xff0c;为什么会有进程这个概念&#xff0c;我们就需要去了解现代计算机的设计框架(冯诺依曼体系)&#xff1a; 计算机从设计之初就以执行程序为核心任务&#xff0c;也就是运算器从内存中读取&#xff0c;也只从内存中…

24款奔驰CLE升级原厂360全景影像效果怎么样

24款奔驰 CLE 轿跑&#xff1a;全景视野&#xff0c;驾驭无忧 24款奔驰 CLE 轿跑&#xff0c;以其优雅的线条和卓越的性能&#xff0c;成为道路上的一道亮丽风景。而升级原厂 360 全景影像&#xff0c;将为您的驾驶增添更多安全与便利。 原厂 360 全景影像的升级&#xff0c;…

算法分享——《双指针》

文章目录 ✅[《移动零》](https://leetcode.cn/problems/move-zeroes/)&#x1f339;题目描述&#xff1a;&#x1f697;代码实现&#xff1a;&#x1f634;代码解析&#xff1a; ✅[《复写零》](https://leetcode.cn/problems/duplicate-zeros/)&#x1f339;题目描述&#xf…

心觉:潜意识是一个免费的“超级工作狂”,你居然不会用

我们常听说&#xff1a;潜意识的力量是意识到3万倍以上 你信吗 估计很多人不相信&#xff0c;不相信当然用不好 不相信的原因核心有两个&#xff1a; 没有体验过 寻求绝对的科学验证 这两个原因会让你对潜意识不相信&#xff0c;或者半信半疑 今天我也不会给你绝对的科学…

基于 Unet-MobileNet 网络实现的腹部肝脏语义分割

1、MobileUnet 网络 Unet是一种卷积神经网络&#xff08;CNN&#xff09;架构&#xff0c;通常用于图像分割任务 Unet架构由编码器和解码器组成。编码器负责捕获上下文并从输入图像中提取特征&#xff0c;而解码器负责上采样并生成分割掩模。 Unet中的编码器由多个卷积层组成…

汽车免拆诊断案例 | 捷豹 E-type怠速不稳定

故障现象 一辆3.8E型直列6缸的捷豹&#xff0c;该车的故障原因表现为怠速不稳定&#xff0c;转速不均匀&#xff0c;以及在节气门全开&#xff08;WOT工况&#xff09;时“回火”和加速踏板不能及时提速。这是该车型一个值得关注的典型案例。车主表示&#xff0c;几年前&#…

Python条形码生成

条形码基础知识 在开始编码之前&#xff0c;让我们先了解一下条形码的基本概念。条形码本质上是一种将数据编码成可视模式的方法&#xff0c;通常由一系列平行的黑色条和白色空格组成。常见的条形码类型包括&#xff1a; UPC&#xff08;通用产品代码&#xff09; EAN&#x…

828华为云征文|华为云Flexus X实例部署k3s与kuboard图形化管理工具

828华为云征文&#xff5c;华为云Flexus X实例部署k3s与kuboard图形化管理工具 华为云最近正在举办828 B2B企业节&#xff0c;Flexus X实例的促销力度非常大&#xff0c;特别适合那些对算力性能有高要求的小伙伴。如果你有自建MySQL、Redis、Nginx等服务的需求&#xff0c;一定…

数据结构(邓俊辉)学习笔记】排序 6——希尔排序:框架 + 实例

文章目录 1. 策略2.实例3.循秩访问4. 插入排序5.Shell序列 1. 策略 来学习一种非常别致的排序算法&#xff0c;也就是希尔排序。 希尔排序算法既有着悠久的历史&#xff0c;同时也仍然不失活力。该算法的别致之处在于&#xff0c;它不再是将输入视作为一个一维的序列&#x…

SpringTest框架JUnit单元测试用例获取ApplicationContext实例的方法

JUnit单元测试用例中使用Spring框架&#xff0c;之前我的使用方式很直接。 /*** 用于需要用到Spring的测试用例基类* * author lihzh* alia OneCoder* blog http://www.coderli.com*/ RunWith(SpringJUnit4ClassRunner.class) ContextConfiguration(locations { "/sprin…

七. 部署YOLOv8检测器-deploy-yolov8-basic

目录 前言0. 简述1. 案例运行2. 补充说明3. 代码分析3.1 main.cpp3.2 trt_detector.hpp3.2 trt_detector.cpp 4. INT8量化前瞻总结下载链接参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考 本次课程…

【车载开发系列】ParaSoft入门介绍

【车载开发系列】ParaSoft入门介绍 【车载开发系列】ParaSoft入门介绍 【车载开发系列】ParaSoft入门介绍一. ParaSoft的背景二. 设计理念三. ParaSoft C/CTest简介四. 具备常用功能1&#xff09;静态代码分析2&#xff09;代码覆盖率分析3&#xff09;模糊测试4&#xff09;自…

协议的认识和理解

目录 1. 协议 1.1. 站在日常生活的角度初始协议 1.2. 网络分层结构 (OS vs 网络) 1.2.1. 软件分层 1.2.2. 网络分层 1.3. 站在语言的角度理解协议 1. 协议 对于协议&#xff0c;我们可以用一句话概括它&#xff1a;协议本质上就是一种约定。 1.1. 站在日常生活的角度初始…

由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(四)

概述 在 WWDC 24 中,苹果推出了数据库框架 SwiftData 2.0,并为其加入了全新的 History Trace、“墓碑”等诸多激动人心的新功能。那么它们到底如何实际应用到我们的 App 中去呢? 想搞清楚历史记录追踪(History Trace)如何在 SwiftData 2.0 中大放异彩吗?看这篇就对了! …

压缩文件隐写

1、伪加密 &#xff08;1&#xff09;zip伪加密 考点&#xff1a;winhex打开压缩包&#xff1b;搜索504b0102(注意不是文件头部&#xff1b;zip文件头部伪504b0304);从50开始&#xff0c;往后面数第9&#xff0c;10个字符为加密字符&#xff0c;将其设置为0000即可变为无加密状…

攻防世界--->获取

做题笔记。 下载 查壳 64ida打开 main函数&#xff1a; 查找字符&#xff1a; 根据程序逻辑&#xff0c;创建了一个新文件并且进行了写入。 直接Linux上动调一下。 SharifCTF{b70c59275fcfa8aebf2d5911223c6589}

python安装以及访问openAI API

安装python 我是python小白&#xff0c;所以需要一步一步来&#xff0c;先安装。 一口吃不成胖子&#xff0c;记住。 从官网下载python&#xff0c;目前最新版本是3.12&#xff0c;但是据说稳定版3.11更好一点&#xff0c;所以&#xff0c;下载3.11&#xff0c;注意不要下载…