Flythings学习(四)串口通信

文章目录

  • 1 串口编程基本步骤
    • 1.1 打开串口
      • 1.2 配置串口
    • 1.3 读串口
    • 1.4 发送串口
    • 1.5 关闭串口
  • 2 综合使用
  • 3 如何在软件上保证串口稳定通信
  • 4 flythings中的串口通讯
  • 5 协议接收部分使用和修改方法
  • 6 通讯协议数据怎么和UI控件对接


1 串口编程基本步骤

串口通信有5个步骤
1.打开串口
2.配置串口
3.读串口
4.写串口
5.关闭串口

1.1 打开串口

#include <fcntl.h>

 int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);

open是linux系统函数,open成功,返回0,失败返回1。/dev/ttys0可以理解为串口号,类似于windows系统上的com1,

1.2 配置串口

int openUart() {
   int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
   struct termios oldtio = { 0 };
   struct termios newtio = { 0 };
   tcgetattr(fd, &oldtio);
   //设置波特率为115200
   newtio.c_cflag = B115200 | CS8 | CLOCAL | CREAD;
   newtio.c_iflag = 0; // IGNPAR | ICRNL
   newtio.c_oflag = 0;
   newtio.c_lflag = 0; // ICANON
   newtio.c_cc[VTIME] = 0;
   newtio.c_cc[VMIN] = 1;
   tcflush(fd, TCIOFLUSH);
   tcsetattr(fd, TCSANOW, &newtio);

   //设置为非阻塞模式,这个在读串口的时候会用到
   fcntl(fd, F_SETFL, O_NONBLOCK);
   return fd;
}

termios函数族提供了终端接口,用于控制非同步通信端口
他的结构体

struct termios  
{  
    unsigned short c_iflag; /* 输入模式标志*/  
    unsigned short c_oflag; /* 输出模式标志*/  
    unsigned short c_cflag; /* 控制模式标志*/  
    unsigned short c_lflag; /*区域模式标志或本地模式标志或局部模式*/  
    unsigned char c_line; /*行控制line discipline */  
    unsigned char c_cc[NCC]; /* 控制字符特性*/  
}; 

每个所对应的关系在下面这个博客
termios 相关知识https://blog.csdn.net/lizuobin2/article/details/51775277

tcgetattr(fd, &oldtio); 这个函数的作用,是取得终端介质(fd)初始值,并把其值 赋给oldtio;函数可以从后台进程中调用;但是,终端属性可能被后来的前 台进程所改变。

tcflush是丢掉写入引用的对象,但是尚未传输的数据,或者收到但是尚未读取的数据

tcsetattr(fd, TCSANOW, &newtio);
设置与终端相关的参数 (除非需要底层支持却无法满足),使用termios_p 引用的 termios 结构。optional_actions (tcsetattr函数的第二个参数)指定了什么时候改变会起作用,这里是改变立即发生

1.3 读串口

#include <fcntl.h>
unsigned char buffer[1024] = {0};
int ret = read(fd, buffer, sizeof(buffer));

1.4 发送串口

#include <fcntl.h>
unsigned char buffer[4] = {0};
buffer[0] = 0x01;
buffer[1] = 0x02;
buffer[2] = 0x03;
buffer[3] = 0x04;
int ret = write(fd, buffer, sizeof(buffer));

write可以发送串口数据

1.5 关闭串口

#include <fcntl.h>
close(fd);

2 综合使用

/*
 * test.cpp
 *
 *  Created on: 2024年10月14日
 *      Author: AA
 */


#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char **argv){

	// 创建串口
	int fd = open("/dev/ttyS0",O_RDWR | O_NOCTTY);

	if(fd < 0){
		// 打开串口失败
		return -1;
	}


	// 配置串口
	struct tremios oldtio = {0};
	struct termios newtio = {0};
	tcgetattr(fd,oldtio);

	newtio.c_cflag = B115200 | CS8 | CLOCAL | CREAD;
	newtio.c_iflag = 0;
	newtio.c_oflag = 0;
	newtio.c_lflag = 0;
	newtio.c_cc[VTIME] = 0;
	newtio.c_cc[VMIN] = 1;
	tcflush(fd, TCIOFLUSH);
	tcsetattr(fd, TCSANOW, &newtio);
	// 设置为非阻塞模式
	fcntl(fd, F_SETFL, O_NONBLOCK);

	// 监听和发送
	while (true) {
	    unsigned char buffer[1024] = {0};
	    int ret = read(fd, buffer, sizeof(buffer));
	    if (ret > 0) {
	      //依次将读取到的数据输出到日志
	      for (int i = 0; i < ret; ++i) {
	        LOGD("收到%02x", buffer[i]);
	      }

	      //当收到数据时,再将收到的数据原样发送
	      int n = write(fd, buffer, ret);
	      if (n != ret) {
	        LOGD("发送失败");
	      }

	      //当收到0xFF时,跳出循环
	      if (buffer[0] == 0xFF) {
	        break;
	      }
	    } else {
	      //没收到数据时,休眠50ms,防止过度消耗cpu
	      usleep(1000 * 50);
	    }
	  }


	close(fd);
	return 0;
}


3 如何在软件上保证串口稳定通信

串口通信会制定通信协议,一般包括帧头、帧尾、帧内容、校验等部分。并且由于read函数不能保证一次性将当时串口收到的所有数据都返回,所以需要多次调用read函数,然后拼接起来

//提高buffer数组的作用域,使得while循环中不会清空数据
unsigned char buffer[1024] = {0};
// 增加一个`legacy`变量,表示buffer中遗留的数据长度
int legacy = 0;
while (true) {
 //根据legacy的大小,调整缓冲区的起始指针及大小,防止数据覆盖
 int ret = read(fd, buffer + legacy, sizeof(buffer) - legacy);
 if (ret > 0) {
   if ((buffer[0] == 0xFF) && (buffer[1] == 0x55)) {
     if ((ret + legacy) == 10) {
       LOGD("正确读到一帧数据");
       //清空legacy
       legacy = 0;
     } else if (ret < 10) {
       legacy += ret;
       LOGD("协议头正确,但是帧长度不够,则暂存在buffer里");
     }
   }

   //当收到数据时,再将收到的数据原样发送
   int n = write(fd, buffer, ret);
   if (n != ret) {
     LOGD("发送失败");
   }

   //当收到0xFF时,跳出循环
   if (buffer[0] == 0xFF) {
     break;
   }
 } else {
   //没收到数据时,休眠50ms,防止过度消耗cpu
   usleep(1000 * 50);
 }
}

用于处理串口通信的while循环应该单独开一个线程

4 flythings中的串口通讯

flythings自带一套串口通讯的框架
在这里插入图片描述

uart协议解析和封装的串口HAL层:
uartContext:串口的实际控制层,提供串口的开关、收发接口
ProtocolData:定义通讯的数据结构体,用于保存通讯协议转化出来的实际变量
protocolSender:完成数据发送的封装
ProtocolParser:完成数据的协议解析部分,然后将解析号的数据放入ProtocolData中,同时管理了应用监听串口数据变化的回到接口
APP应用逻辑层:
通过ProtocolParser提供的接口注册串口数据接收监听获取串口更新出来的ProtocolData
通过ProtocolSender提供的接口往MCU发送指令信息

这个流程大概是这样
在这里插入图片描述
具体流程
在这里插入图片描述
最终都是通过UartContext与MCU进行串口通信的,

5 协议接收部分使用和修改方法

在这里插入图片描述

通讯协议格式修改
CommDef.h 文件中定义了同步帧头信息及最小数据包大小信息:

// 需要打印协议数据时,打开以下宏
//#define DEBUG_PRO_DATA

// 支持checksum校验,打开以下宏
//#define PRO_SUPPORT_CHECK_SUM

/* SynchFrame CmdID  DataLen Data CheckSum (可选) */
/*     2Byte  2Byte   1Byte    N Byte  1Byte */
// 有CheckSum情况下最小长度: 2 + 2 + 1 + 1 = 6
// 无CheckSum情况下最小长度: 2 + 2 + 1 = 5

#ifdef PRO_SUPPORT_CHECK_SUM
#define DATA_PACKAGE_MIN_LEN        6
#else
#define DATA_PACKAGE_MIN_LEN        5
#endif

// 同步帧头
#define CMD_HEAD1    0xFF
#define CMD_HEAD2    0x55

在ProtocolParser文件中配置文件命令格式

/**
 * 功能:解析协议
 * 参数:pData 协议数据,len 数据长度
 * pdata[0]、[1]、[2]、[3]都是协议头,[4]开始是数据
 * 返回值:实际解析协议的长度
 */
int parseProtocol(const BYTE *pData, UINT len) {
    UINT remainLen = len;    // 剩余数据长度
    UINT dataLen;    // 数据包长度
    UINT frameLen;    // 帧长度

    /**
     * 以下部分需要根据协议格式进行相应的修改,解析出每一帧的数据
     */
    while (remainLen >= DATA_PACKAGE_MIN_LEN) {
        // 找到一帧数据的数据头
        // pdata[0]如果不是协议头,说明是数据,往后继续找,直到找到协议头
        while ((remainLen >= 2) && ((pData[0] != CMD_HEAD1) || (pData[1] != CMD_HEAD2))) {
            pData++;
            remainLen--;
            continue;
        }
		
		// 如果剩下的长度小于最小的包长,则说明已经传完了,退出循环
        if (remainLen < DATA_PACKAGE_MIN_LEN) {
            break;
        }

		
        dataLen = pData[4];
        frameLen = dataLen + DATA_PACKAGE_MIN_LEN;
        if (frameLen > remainLen) {
            // 数据内容不全
            break;
        }

        // 打印一帧数据,需要时在CommDef.h文件中打开DEBUG_PRO_DATA宏
#ifdef DEBUG_PRO_DATA
        for (int i = 0; i < frameLen; ++i) {
            LOGD("%x ", pData[i]);
        }
        LOGD("\n");
#endif

        // 支持checksum校验,需要时在CommDef.h文件中打开PRO_SUPPORT_CHECK_SUM宏
#ifdef PRO_SUPPORT_CHECK_SUM
        // 检测校验码
        if (getCheckSum(pData, frameLen - 1) == pData[frameLen - 1]) {
            // 解析一帧数据
            procParse(pData, frameLen);
        } else {
            LOGE("CheckSum error!!!!!!\n");
        }
#else
        // 解析一帧数据
        procParse(pData, frameLen);
#endif

        pData += frameLen;
        remainLen -= frameLen;
    }

    return len - remainLen;
}

在这里插入图片描述

如果协议头需要更改

// 1.修改协议头部分的定义,如果协议头长度有变化,则要注意修改协议头判断部分语句。
#define CMD_HEAD1    0xFF
#define CMD_HEAD2    0x55

// 2.协议头长度变化的时候需要修改这里。
while ((mDataBufLen >= 2) && ((pData[0] != CMD_HEAD1) || (pData[1] != CMD_HEAD2)))


// 3.协议长度需要更改
// 这里的pData[4] 代表的是第5个数据是长度的字节,如果变化了在这里修改一下。
dataLen = pData[4];
// 帧长度一般是数据长度加上头尾长度。如果协议中传的长度计算方式发生变化修改这个部分。
frameLen = dataLen + DATA_PACKAGE_MIN_LEN;

6 通讯协议数据怎么和UI控件对接

使用procParse进行解析

/*
 * 协议解析
 * 输入参数:
 *     pData: 一帧数据的起始地址
 *     len: 帧数据的长度
 */
void procParse(const BYTE *pData, UINT len) {
    /*
     * 解析Cmd值获取数据赋值到sProtocolData结构体中
     */
    switch (MAKEWORD(pData[2], pData[3])) {
    case CMDID_POWER:
        sProtocolData.power = pData[5];
        LOGD("power status:%d",sProtocolData.power);
        break;
    }
    notifyProtocolDataUpdate(sProtocolData);
}

通过notifyProtocolDataUpdate 将封装好的数据,发送给UI界面

sprotocolData可以增加数据变量

在UI的activity中,默认已经创建好了一个串口数据回调接口

static void onProtocolDataUpdate(const SProtocolData &data) {
    // 串口数据回调接口
    if (mProtocolData.power != data.power) {
        mProtocolData.power = data.power;
    }

    if (mProtocolData.eRunMode != data.eRunMode) {
        mProtocolData.eRunMode = data.eRunMode;
        mbtn_autoPtr->setSelected(mProtocolData.eRunMode == E_RUN_MODE_MANUAL);
        if (mProtocolData.eRunMode != E_RUN_MODE_MANUAL) {
            mbtn_external_windPtr->setText(mProtocolData.externalWindSpeedLevel);
            mbtn_internal_windPtr->setText(mProtocolData.internalWindSpeedLevel);
        }
    }
    ...
}

mProtocolData是UI界面默认的数据,在onUI_init的时候进行初始化,通过串口更新

APP层在ProtocolSender.cpp,当APP层需要发送数据到MCU的时候直接调用sendprotocol函数,并在sendProtocol函数中实现

/**
 * 需要根据协议格式进行拼接,以下只是个模板
 */
bool sendProtocol(const UINT16 cmdID, const BYTE *pData, BYTE len) {
    BYTE dataBuf[256];

    dataBuf[0] = CMD_HEAD1;
    dataBuf[1] = CMD_HEAD2;            // 同步帧头

    dataBuf[2] = HIBYTE(cmdID);
    dataBuf[3] = LOBYTE(cmdID);        // 命令字节

    dataBuf[4] = len;

    UINT frameLen = 5;

    // 数据
    for (int i = 0; i < len; ++i) {
        dataBuf[frameLen] = pData[i];
        frameLen++;
    }

#ifdef PRO_SUPPORT_CHECK_SUM
    // 校验码
    dataBuf[frameLen] = getCheckSum(dataBuf, frameLen);
    frameLen++;
#endif

    return UARTCONTEXT->send(dataBuf, frameLen);
}

按下按键发送时

BYTE mode[] = { 0x01, 0x02, 0x03, 0x04 };
sendProtocol(0x01, mode, 4);

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Telegram——Bot 机器人/小程序入门指南

一、Bot 介绍 在 TG 中,机器人可以用于接收和发送消息、管理群组(在有权限的情况下可以封禁用户、删除消息、置顶消息等)、通过API进行编程操作、使用 Inline 查询功能在不同的聊天室中提供查询服务、创建自定义键盘按钮、发出账单并收款、接入小程序游戏等。 然而,Bot 默…

单片机中断概念以及示例

中断允许控制寄存器 CPU对中断系统所有中断以及某个中断源的开放和屏蔽是由中断允许寄存器IE控制的。 EX0(IE.0)&#xff0c;外部中断0允许位&#xff1b;EX01&#xff0c;打开外部中断0中断&#xff1b;EX00关闭外部中断0中断。 ET0(IE.1)&#xff0c;定时/计数器T0中断允许…

沪尚茗居装修秘籍:嵌入式蒸烤箱,让厨房生活更精彩

在装修厨房时&#xff0c;选择一款合适的嵌入式蒸烤箱不仅能提升烹饪效率&#xff0c;还能为厨房增添一份现代感。沪尚茗居深知用户对厨房电器的需求&#xff0c;从实际出发&#xff0c;为用户推荐选购嵌入式蒸烤箱的实用技巧&#xff0c;让厨房生活更加美好。    首先&…

Real-World Image Variation by Aligning Diffusion Inversion Chain

https://proceedings.neurips.cc/paper_files/paper/2023/file/61960fdfda4d4e95fa1c1f6e64bfe8bc-Paper-Conference.pdfhttps://rival-diff.github.io/ 问题引入 针对的是image varation generation这个任务&#xff0c;tuning free的&#xff1b; methods C C C表示condit…

【closerAI ComfyUI】电商模特一键换装解决方案来了!细节到位无瑕疵!再加上flux模型加持,这个工作流不服不行!

不得了了兄弟们。这应该是电商界的福音&#xff0c;电商模特一键换装解决方案来了&#xff01;细节到位无瑕疵&#xff01;再加上flux模型加持&#xff0c;这个工作流不服不行&#xff01; 这期我们主要讨论如何使用stable diffusion comfyUI 制作完美无瑕疵的换装工作流。** …

《Linux从小白到高手》综合应用篇:详解Linux系统调优之服务器硬件优化

List item 本篇介绍Linux服务器硬件调优。硬件调优主要包括CPU、内存、磁盘、网络等关键硬件组。 1. CPU优化 选择适合的CPU&#xff1a; –根据应用需求选择多核、高频的CPU&#xff0c;以满足高并发和计算密集型任务的需求。CPU缓存优化&#xff1a; –确保CPU缓存&#x…

C++和OpenGL实现3D游戏编程【连载15】——着色器初步

&#x1f525;C和OpenGL实现3D游戏编程【目录】 1、本节实现的内容 上一节我们介绍了通过VBO、VAO和EBO怎样将顶点发送到GPU显存&#xff0c;利用GPU与显存之间的高效处理速度&#xff0c;来提高我们的图形渲染效率。那么在此过程中&#xff0c;我们又可以通过着色器&#xff…

【C++网络编程】(二)Linux平台下UDP客户/服务端程序

Linux平台下UDP客户/服务端程序 图片来源&#xff1a;https://subingwen.cn/linux/udp/ UDP服务器无法直接检测客户端断开连接。 UDP 服务端 server.cpp #include <iostream> #include <cstdlib> // std::exit #include <cstring> // memset #i…

【数据结构与算法】单链表探秘:解锁数据结构中的灵动链条

大家好&#xff0c;我是小卡皮巴拉 文章目录 目录 一.单链表初探&#xff1a;构建数据结构的基石 1.1 单链表的概念及结构 1.2 节点的理解 1.3 链表的性质 二.单链表的作用&#xff1a;为何它是数据处理的优选 2.1 动态内存管理&#xff1a;内存世界的魔术师 2.2 高效的…

篇章九 【NPM】包管理工具

文章目录 一、认识NPM二、npm 镜像的设置与查看三、安装 NPM 工具1. 下载Node.js安装包2. 打开下载好的安装程序&#xff0c;点击下一步3. 选择接受许可协议&#xff0c;点击下一步4. 选择自己的安装路径&#xff08;默认是c盘&#xff09;&#xff0c;选择完成后&#xff0c;点…

喜讯AAA级!国信华源荣获全国水利建设信息化3A级信用

近日&#xff0c;中国水利工程协会发布了《2024年度水利建设市场主体&#xff08;供货、信息化单位&#xff09;信用评价结果公示》&#xff0c;国信华源荣获2024年度全国水利建设信息化单位AAA级信用评价。这一荣誉不仅是对国信华源在水利建设领域信息化能力和诚信经营的肯定&…

中波长线天线耦合的一个方法

围绕窗外墙外牵了10米的室外天线。 短波&#xff0c;fm都是很简单&#xff0c;一个夹子直接夹在拉杆天线上面&#xff0c;效果已经很好。 今天偶尔听到中波前面大约510khz的地方有个摩尔斯码。是成都附近机场的NDB。这个平时要在楼顶或者很空旷的地方才能收到。音量比较小&am…

vue2项目 实现上边两个下拉框,下边一个输入框 输入框内显示的值为[“第一个下拉框选中值“ -- “第二个下拉框选中的值“]

效果: 思路: 采用vue中 [computed:] 派生属性的方式实现联动效果,上边两个切换时,下边的跟随变动 demo代码: <template><div><!-- 第一个下拉框 --><select v-model"firstValue"><option v-for"option in options" :key&q…

神经网络中使用的激活函数有什么用?

&#x1f381;&#x1f449;点击进入文心快码 Baidu Comate 官网&#xff0c;体验智能编码之旅&#xff0c;还有超多福利&#xff01;&#x1f381; &#x1f50d;【大厂面试真题】系列&#xff0c;带你攻克大厂面试真题&#xff0c;秒变offer收割机&#xff01; ❓今日问题&am…

python项目实战——下载美女图片

python项目实战——下载美女图片 文章目录 python项目实战——下载美女图片完整代码思路整理实现过程使用xpath语法找图片的链接检查链接是否正确下载图片创建文件夹获取一组图片的链接获取页数 获取目录页的链接 完善代码注意事项 完整代码 import requests import re import…

Git推送被拒

今天开发完成一个新的需求&#xff0c;将自己的分支合并到test分支后&#xff0c;推送到远程仓库&#xff0c;结果显示推送被拒&#xff1a; 原因是因为有人更新了test分支的代码&#xff0c;我在合并之前没有拉取最新的test分支代码&#xff0c;所以他提示我“推送前需要合并…

Steinberg VST Live Pro v2.1.1 演出音频灯光控制软件

现场演出音频视频灯光控制软件 Steinberg VST Live Pro 将让现场表演更轻松。这是一款独特、稳定的软件解决方案&#xff0c;专为想要进行精彩表演的音乐家而设计&#xff0c;无论身在何处都能使用声音、灯光和视频等相关功能。VST Live附带大量虚拟乐器&#xff0c;音乐同步功…

STM32学习--4-1 OLED显示屏

接线图 OLED.c #include "stm32f10x.h" #include "OLED_Font.h"/*引脚配置*/ #define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x)) #define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))/*引脚初始化*/ void …

(27)QPSK信号在非相关平坦莱斯(Rician)衰落信道上的误码率性能MATLAB仿真

文章目录 前言一、Rician衰落信道模型的MATLAB代码二、在非相关的平坦Rician衰落信道上传输QPSK符号模型1.MATLAB仿真代码2.仿真结果 前言 本文首先给出莱斯衰落信道的建模函数&#xff0c;然后基于该函数给出在非相关的平坦Rician衰落信道上传输QPSK数字调制符号的MATLAB仿真…

iTOP-3A5000主控板龙芯自主指令系统外加机箱就是一台电脑主机

性能强采用全国产龙芯3A5000处理器&#xff0c;基于龙芯自主指令系统 (LoongArch)的LA464微结构&#xff0c;并进一步提升频率&#xff0c;降低功耗&#xff0c;优化性能。桥片采用龙芯 7A2000&#xff0c;支持PCIE 3.0、USB 3.0和 SATA 3.0.显示接口2 路、HDMI 和1路 VGA&…