这可能是近期暂时最后一篇c嵌入式的文章了

基础的串口使用

参照网上的stm32教程套路引入标准库,初始化芯片手册上对应串口引脚 ,初始化stm32串口功能,然后有数据了就自然在寄存器上,就这样,你的波特率跟对方一样寄存器上就自然不断刷新数据。不及时取数据就自然被冲掉 就这样简单粗暴。要想接住数据打开对应的IRQHandler中断函数即可。由于很快被冲掉代码执行时间不宜过长,正常情况下一般都还是要搞个简单的buffer来存的。也没有啥连接 粘包开始标识结束标识这一说,一般都简单的利用一个定时器来断包。

  1 #include "sys.h"
2 #include "usart.h"
3
4
5 #define USAER1_REC_MAX 64 //串口1最大接收数据量
6
7 /* 串口1接收数据缓存结构体 */
8 struct Usart1_RecData_t{
9 uint16_t recFlag:1; //数据接收标记 0:接收未完成 1:接收完成等待处理
10 uint16_t recLen:15; //接收数据长度
11 char buff[USAER1_REC_MAX]; //接收数据缓存指针
12 }static usart1Rec = {
13 .recFlag = 0,
14 .recLen = 0,
15 };//定义结构体Usart1_RecData_t 并立即初始化一个变量usart1Rec
16
17 //初始化定时器
18 void TIM2_Int_Init(const uint16_t arr, const uint16_t psc)
19 {
20 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
21 NVIC_InitTypeDef NVIC_InitStructure;
22
23 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //时钟使能
24
25 //定时器TIM2初始化
26 TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载值
27 TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置预分频值
28 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
29 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
30 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
31
32 TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //使能指定的TIM2中断,允许更新中断
33
34 NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断
35 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4; //先占优先级4级
36 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
37 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
38 NVIC_Init(&NVIC_InitStructure);
39
40 TIM_Cmd(TIM2, ENABLE);
41 }
42
43 void USART_SendByte(USART_TypeDef* USARTx, uint16_t Data) {
44 USART_SendData(USARTx, Data);
45 while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);//每发送一个字符都等待数据寄存器空确保发送完成
46 }
47
48 void USART_SendString(USART_TypeDef* USARTx, char* str) {
49 //关于前导判断是没写的 问题不大 ,不可能手动触发 或者手按能达到毫秒级导致数据没有判断而覆盖的吧
50 while (*str != '\0') {//遍历每一个字符直到\0
51 USART_SendByte(USARTx, *str++);
52 }
53
54 while (USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
55 }
56
57 void USART_SendString2(USART_TypeDef* USARTx, char* str, uint16_t len) {
58
59 for (uint16_t sendlen = 0; sendlen < len; sendlen++)
60 USART_SendByte(USARTx, *str++);
61 while (USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
62 }
63
64 //定时器中断函数 利用定时器来断包
65 void TIM2_IRQHandler(void)
66 {
67 if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源
68 {
69 usart1Rec.recFlag = 1; //标记接收完成
70
71 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIMx的中断待处理位:TIM 中断源
72 TIM_Cmd(TIM2, DISABLE); //关闭定时器2
73 }
74 }
75
76 //串口中断处理函数
77 void USART3_IRQHandler() {
78
79 if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) {//如果可以读了
80 if (usart1Rec.recFlag == 0) {
81 //关键就在这个地方了,如果还没读满,关键就在这一块了
82 //后面的使用只需要usart1Rec.recFlag usart1Rec.buff 即可
83 //满了后 usart1Rec.recFlag 置1 就不再往里填了 免的冲掉数据
84 //注意了关键就在于心跳喂狗 超过10ms 也会认为读完了 定时器进行usart1Rec.recFlag=1
85 //并且关闭定时器,定时器平常是不工作的,开启也仅仅是从此处开启的 纯粹把作为一个喂狗秒表
86 //所以效率跟易用性的话都还是可以的
87 if (usart1Rec.recLen < USAER1_REC_MAX)
88 {
89 TIM_SetCounter(TIM2, 0);//计数器清空,通过这种边读边喂心跳的方式把buf填充满
90 if (usart1Rec.recLen == 0)
91 TIM_Cmd(TIM2, ENABLE);//使能定时器
92 usart1Rec.buff[usart1Rec.recLen++] = (char)USART_ReceiveData(USART3);//接收数据到缓存
93
94 }
95
96 }
97 else
98 {
99 usart1Rec.recFlag = 1;
100 USART_ReceiveData(USART3);
101 }
102 }//下面这句没意义啊 标志位都不能读 还读啥,纯粹只是做做样子 然后放弃这些数据
103 else
104 USART_ReceiveData(USART3);
105
106 }
107
108
109
110 //pData:读取数据的缓存
111 //返 回 值:实际接收的数据大小,0表示未接收到数据
112 uint16_t Get_Usart3RecData(char* pData)
113 {
114 uint16_t len = 0;
115
116 if (usart1Rec.recFlag != 0) //接收一帧数据结束
117 {
118 /* 获取数据 */
119 len = usart1Rec.recLen;
120 //memset(pData, 0, len);
121 //pData=(char *)malloc(len);
122 memcpy(pData, usart1Rec.buff, len);
123
124 /* 清缓存 */
125 memset(&usart1Rec, 0, sizeof(usart1Rec));
126 return len;
127 }
128 else //接收一帧数据未结束
129 return 0;
130 }
131
132 void USART3_Init(void) {
133 //使能两个时钟
134 GPIO_InitTypeDef GPIO_InitStruct;
135 //下面的写法一个意思
136 //1串口和串口引脚的时钟使能
137 // RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE);
138 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
139 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
140
141 //为啥 uart1 就必须设置pa9 pa10 ,因为是对应了的 ,芯片手册 厂家就是这么规定的
142 //pa2 usart2_tx //pa3 usart2_rx
143 //pb10 usart3_tx //pb11 usart3_rx
144 //pa9 usart1_tx //pa10 usart1_rx
145
146 //进行串口对应端口引脚的模式设置
147
148 //2gpio端口模式设置
149 GPIO_InitStruct.GPIO_Pin = USART1_GPIO_PIN_TX;//pa9 发送,配置成推挽输出
150 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; //GPIO_Mode_Out_PP;
151 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
152 GPIO_Init(USART_GPIO_PORT, &GPIO_InitStruct);
153
154 //输入 则不需要配置速度
155 GPIO_InitStruct.GPIO_Pin = USART1_GPIO_PIN_RX;//pa10 接收
156 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;//GPIO_Mode_IN_FLOATING;
157 GPIO_Init(USART_GPIO_PORT, &GPIO_InitStruct);
158
159 //3串口参数初始化
160 USART_InitTypeDef USART1_InitStruct;
161 USART1_InitStruct.USART_BaudRate = 9600;
162 USART1_InitStruct.USART_WordLength = USART_WordLength_8b;
163 USART1_InitStruct.USART_StopBits = USART_StopBits_1;
164 USART1_InitStruct.USART_Parity = USART_Parity_No;
165 USART1_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
166 USART1_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
167
168 USART_Init(USART3, &USART1_InitStruct);
169
170 //4配置串口接收使用的nvic
171 //开启中断
172 USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
173 NVIC_InitTypeDef NVIC_InitStruct;
174 NVIC_InitStruct.NVIC_IRQChannel = USART3_IRQn;
175 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 3;//1;
176 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;//1;
177 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
178 NVIC_Init(&NVIC_InitStruct);
179
180 //通过定时器来进行断续 分包 ,如果超过10ms没有数据来则 结束,作为一个包
181 TIM2_Int_Init(99, 7199); //10ms中断一次
182 memset(usart1Rec.buff, 0, USAER1_REC_MAX); //清除缓存
183
184 //5使能串口
185 USART_Cmd(USART3, ENABLE);
186
187 }
188
189 #pragma import(__use_no_semihosting)
190 struct __FILE
191 {
192 int handle;
193 };
194 FILE __stdout;
195 void _sys_exit(int x)
196 {
197 x = x;
198 }
199
200 int fputc(int ch, FILE* f)
201 {
202 USART_SendData(USART3, (uint8_t)ch);
203 while (USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET);
204 return (ch);
205 }
206 int fgetc(FILE* f)
207 {
208 while (USART_GetFlagStatus(USART3, USART_FLAG_RXNE) == RESET);
209 return (int)USART_ReceiveData(USART3);
210 }

手持表项目用一种先进的双缓冲手段来收发

 1 /* 串口1接收数据缓存结构体 */
2 struct Usart1_RecData_t{
3 uint16_t recFlag:1; //数据接收标记 0:接收未完成 1:接收完成等待处理
4 uint16_t recLen:15; //接收数据长度
5 uint8_t buff[USART1_REC_MAX]; //接收数据缓存指针
6 };
7
8 static struct Usart1_RecData_t usart1Rec[USART1_REC_NUM];
9 static uint8_t usart1RecCacheNum = 9; //当前使用的缓存空间
10 static uint8_t readCacheNum = 0; //当前读取的缓存
11 static uint8_t sendBuff[2 * USART1_REC_MAX];

处理数据时还可以用信号量来控制硬件资源访问冲突

1     xSemaphoreTake(MutexSemaphore, portMAX_DELAY); //获取互斥信号量
2 relativeHumi = sysData.relativeHumi;
3 pageID = sysData.pageID;
4 xSemaphoreGive(MutexSemaphore); //释放信号量

其它一些深入的理解

虽然学计算机 都会有看的书上写 网络部分的 什么ISO7个层啊  什么传输层 链路层啊 啥啥啥的,但是从来没有专门去背过那玩意儿。也了解平常我们自己使用的socket 基本上处于传输层。

首先对于TCP socket 来说的的话 处在receipt阻塞状态 本身就能够对 对方掉线进行一个感知,利用这点机制 我们也可以send 0字节来对连接状态判断,为什么能够感知 ,TCP协议栈底层已经实现了,你可以理解为像单片机处理TTL电平信号那样 操作系统底层已经编了些程序代码实现了。如果你既不进行发送 也不进行接收动作 则是感知不到离线状态的 ,但是一般都会处于一个接收阻塞循环里,所以严格来说是可以不用心跳处理的。然后另一个关于缓冲,TCP stream 本身 就有缓冲机制 这个也是协议栈帮我们实现了 ,并且网络设备还会自动的根据你receipt的处理速度 进行流控。可以看出来这玩意儿高深的很 原来传输层以下还有这么多机制要处理 ,什么三次握手, 并不只是串口那种无脑的根据频率处理电信号 原来协议栈底层帮我们做了这么多事情。而winsocket 只是操作系统暴露给我们的一个协议栈 数据流接口而已 ,这是传输层的 然后我们的一切工作都基于其之上,就像学校老师教的 他这种接口的方式 也并不是面向对象的 更形象点来说的话 类似打电话或者 管道水流 处理。一般情况下我们只需要简易处理粘包,无需管缓存 和数据断包  甚至无需校验 无需管数据错乱,因为TCP已经实现了数据完整性保证机制。 然后我们只需在之上构建业务即可。

这么多年了想不到自己的水平到现在才将就会写一丁点网络通讯的东西 ,真的是越搞这个越觉得自己资质平平。这么多年的浸淫 socketTCP也算是写了一些了 串口上位机也做过 ,进而做了单片机底层相关,算是有那么点自己独到理解的想法 意味 看法在那吧 。

前面谈论了TCP 。最明显的区别也是常识  以前电脑上玩棋牌游戏 或者qq传文件的时候 我们可以把网线拔掉 等几秒钟甚至十秒钟再插上 ,程序整个交互过程不会受任何影响,对比一下串口简直low逼的就不是一个水平的东西,由于串口是及时性的波特率在那管着的 你保持我这个频率动作自然就能够收到,数据在你的单片机寄存器里就待那么0.001秒的时间 ,如果你单片机运行频率如果低于 那毫无疑问是取不到的,或者你单片机取到了非要用冗长的处理代码干别的那后面的数据丢了我也管不着,对比一下TCP 这真是low逼的不能再low了 这不就是ttl电讯号吗 就是原始人啊 啥串口不串口。但是对比一下TCP 最主要的两点 也只是 加了 数据缓冲 和数据完整性保障。想象一下你自己是一台电脑 网卡的底层那几根儿金属线跟串口有啥区别 说实话没任何区别 网卡底层照串口处理TTL电信号一样处理自己的,只不过low逼的串口到这就结了,TCP则以socket接口为界限 对收到的电信号进行了包装(这个属于网卡固件 或者操作系统系统级提供我们就不去掰扯它了哈,你可以单片机4个IO口 加上 移植LWIP 固件 按照国际通行惯例 处理握手 缓冲 且不说包装成跟socketAPI一样 ,能跟其他TCP互通就成功了一半了,这不就是基于串口电讯号做了一张网卡么)这样基于socketAPI提供给我们应用程序的就是一个非常现代化的东西了 众所周知的 最基本的就有了  数据完整性保障 和数据缓冲 特性。

由此可理解 socket是操作系统暴露的 API ,以及TCP在操作系统层面的底层固件 自动处理握手确保数据收发成功 缓冲, 以及与操作系统嵌合 通过socket暴露给传输层使用 这句意味深长的话了吧。

看了很多的单片机串口代码写的都是非常的普通的不能再普通了,直到看到了手持表的FIFO双指针缓冲读取技术(相对于纯软件层看到的 有了缓冲) ,然后再加上freertos本身提供的互斥量管控(软件层见到的仅仅是一个清晰的统一API的IO调用 也可以多线程。底层确对硬件IO资源进行了管控调度排队处理 随便你上层咋个乱调用 我底层只有两根电线 频率只有那么高),不是low逼串口么 ,怎么回事,怎么样感觉画风是不是有点变了 有那么一点点操作系统底层LWIP固件的影子了,世界的运转机制是如此的相像。

通过串口通信 对TCP传输层以下的理解的更多相关文章

  1. TCP传输层协议的流程

    http://blog.chinaunix.net/uid-24399976-id-77905.html 通过对互联网的认识,我们发现TCP传输层协议是网络进行工作的核心也是基础.它的重要性我们在此也 ...

  2. [计网笔记] 传输层---TCP 传输层思维导图

    传输层思维导图 TCP笔记 为什么是三次握手和四次挥手 https://blog.csdn.net/baixiaoshi/article/details/67712853 [问题1]为什么连接的时候是 ...

  3. TCP/IP传输层,你懂多少?

    1. 传输层的主要功能是什么?2. 传输层如何区分不同应用程序的数据流?3. 传输层有哪些协议?4. 什么是UDP协议?5. 为什么有了UDP,还需要TCP?6. 什么是TCP协议?7. 怎么理解协议 ...

  4. 网络通信协议八之(传输层)TCP协议详解

    传输层协议 分段是为了提高传输效率,封装是指给每个数据段添加一个编号 端到端的传输是逻辑上的端到端,并不是真正意义上的发送方某层与接收方某层之间的传输 IP协议只是保证数据报文发送到目的地,为主机之间 ...

  5. 传输层TCP

    /*************************************************************************************************** ...

  6. 计算机网络概述---传输层 UDP和TCP

    传输层的功能 传输层为应用进程间提供端到端的逻辑通信(网络层是提供主机之间的逻辑通信), 传输层两大重要的功能:复用 和 分用. 复用:在发送端,多个应用进程公用一个传输层: 分用:在接收端,传输层会 ...

  7. 实验:传输层:UDP协议 学习笔记

    一.传输层协议 从之前介绍的网络层协议来看,通信的两端是两台主机,IP数据报首部就标明了这两台主机的IP地址.但是从传输层来看,是发送方主机中的一个进程与接收方主机中的一个进程在交换数据,因此,严格地 ...

  8. 传输层:UDP 协议

    一.传输层协议 从之前介绍的网络层协议来看,通信的两端是两台主机,IP 数据报首部就标明了这两台主机的 IP 地址.但是从传输层来看,是发送方主机中的一个进程与接收方主机中的一个进程在交换数据,因此, ...

  9. 传输层-Transport Layer(上):传输层的功能、三次握手与四次握手、最大-最小公平、AIMD加法递增乘法递减

    第六章 传输层-Transport Layer(上) 6.1传输层概述 在之前的几章内容中,我们自底向上的描述了计算机网络的各个层次,还描述了一些处于不同层次下的经典网络协议(如以太网.无线局域网.或 ...

  10. Delphi 串口通信数据位长度对传输数据的影响 转

      针对串口通信,关于设置数据位长度对通信的影响,如图: 在串口数据通信中,会看到串口参数设置.其中“数据位”设置,共有四档选项,分别是8.7.6.5.那么改变这个参数会对数据的传输有什么影响呢? 我 ...

随机推荐

  1. 2023NOIP A层联测32 T4 红楼 ~ Eastern Dream

    2023NOIP A层联测32 T4 红楼 ~ Eastern Dream 根号分治加分块. Ps:分块后面真的用的多. 思路 考虑根号分治,将 \(x\) 分为 \(x \leq \sqrt n\) ...

  2. MaskLLM:英伟达出品,用于大模型的可学习`N:M`稀疏化 | NeurIPS'24

    来源:晓飞的算法工程笔记 公众号,转载请注明出处 论文: MaskLLM: Learnable Semi-Structured Sparsity for Large Language Models 论 ...

  3. How to disable ipv6 in ubuntu

    To disable ipv6, you have to open /etc/sysctl.conf using any text editor and insert the following li ...

  4. UML 基础:类图

    这是关于统一建模语言.即UML 里采用的基本图的一系列文章的一部分.在我 先前关于序列图的文章 里,我把重点从 UML 1.4 版,转移到 OMG的采用UML 2.0版草案规范(又称为UML 2).在 ...

  5. 使用 Antlr 开发领域语言

    高 尚 (gaoshang1999@163.com), 软件工程师, 中国农业银行软件开发中心 简介: Antlr 是一个基于 Java 开发的功能强大的语言识别工具,Antlr 以其简介的语法和高速 ...

  6. 前端每日一知之你需要知道的JSON.stringify

    脑图在线链接 本文内容依据[web开发]公众号精彩文章总结而来

  7. 注册美区 Apple ID 账号!都2020年了,你还没有一个自己的海外苹果ID?

    写在前面: 小伙伴们学腻了技术,不防今天来点大家都感兴趣的海外苹果 Apple ID 吧! 今天就教大家怎么注册美区 Apple ID,这个方法也是目前注册苹果美区  Apple ID  最快最简单的 ...

  8. Qt/C++推流程序自动生成网页远程查看实时视频流(视频文件/视频流/摄像头/桌面转成流媒体rtmp+hls+webrtc)

    一.前言说明 推流程序将视频流推送到流媒体服务器后,此时就等待验证拉流播放,一般可以选择ffplay命令行播放或者vlc等播放器打开播放,也可以选择网页直接打开拉流地址播放,一般主流的浏览器都支持网页 ...

  9. Qt数据库应用20-csv文件转xls

    一.前言 最近又多了个需求就是将csv格式的文件转xls,需求一个接着一个,还好都是真实的需求,而且都是有用的需求,并不是不靠谱的需求,不靠谱的需求就比如程序自动识别手机壳颜色自动换背景颜色或者边框颜 ...

  10. Qt编写地图综合应用60-覆盖物坐标和搜索

    一.前言 地图应用中有时候需要开启悬浮工具栏,用户可以直接在地图上绘制矩形.多边形.圆形.线条等,于是需要提供一个函数接口,能够获取到用户绘制的这些图形形状对应的信息.比如坐标点.圆形的中心点和半径. ...