你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32L4零基础学习笔记(七)串口通讯之简单数据协议实现

[复制链接]
D5Power 发布时间:2017-11-16 10:50
本帖最后由 D5Power 于 2017-11-16 16:46 编辑 7 I# _" K0 T4 l' m

$ h( O- S% D/ g) M, {1 c$ S

, H4 m0 \* f: x2 r# `$ i, f
为了实现可变长度数据的发送,必须要自己定义协议。我们先定义一个比较简单的协议:
4 `! j9 U8 e& ]9 Z% p
数据包头:2字节,第一字节用来存储命令码,第二字节用来存储整个数据包的长度(不含包头部分)
数据包包体:N字节,根据不同的命令码发送不同的数据。

8 |, b. Y, {, I/ E% {
设置为1个字节中断,并在中断时移动指针并再次打开中断,从而保证接受到的数据按顺序排列在缓冲区中。然后再对缓冲区的内容进行分析。
. ^, l% e* O' }# f" Z" v( V- x  L
为了保证解包的过程不会占用中断进程,解包操作放在了主循环中,并设置10毫秒的延迟。程序整体思路如下:

2 z+ }5 X$ W/ K' o" ]
1.jpg
在开始之前,先做了个小测试,确认收到N个字节的时候,1字节的接受中断会被触发N次。底层确认没问题后,开始撸代码咯。先来看一下头文件里的各种声明:

0 C) k/ J# f2 v' B
// 包头尺寸
#define MIN_HEAD_SIZE 2
// 最大包体尺寸,清注意匹配你的开发板最大内存。否则会分配失败的
#define MAX_PACK_SIZE 16
// 包头结构体
struct __head_buff
{
        char cmd;
        char length;
};
// 完整数据包结构体
struct __d5pack
{
        char cmd;
        char length;
        char data[14]; // 根据最大包体尺寸,扣除包头长度
};

% s2 k+ F. f( y. \- X6 ]7 s& C
// 数据包待处理队列
struct __d5pack *d5list[64];
//待处理数据包个数
int d5list_count = 0;
6 O; D2 Z9 I7 u( k& S# _% p8 T" I
然后在主循环前,处理上述声明中需要初始化的变量。
0 [' |- K7 ~' L& U+ r
; {' H* [& f8 Q$ ~8 C
        // 将pBuffer指向buffer首地址
        pBuffer = (uint8_t *)&buffer;
        // 将pPack指向buffer首地址
        pPack = (uint8_t *)&buffer;
        // 打开1字节接收中断
        HAL_UART_Receive_IT(&huart2,pBuffer,1);
( [6 ]9 P7 D5 K* v! n: ^
接下来是中断部分,处理的核心逻辑。每收到一个字节,pBuffer就会自增,从而保证接收到的字节按顺序推入到缓冲区中。recived_count也会自增,用来提供后续的数据接受数量的判断。

$ d$ R* M, ~: k' k3 d+ N- N
pBuffer++;
recived_count++;

: ?; J1 C7 r9 D/ J% a  ~" P) l
由于每收到一个字节,recived_count就会自增,因此可以根据recived_count来判断缓冲区的字节是否满足解包要求。由于数据包可能是分多次发过来的,因此我们需要知道,现在我们是已经开始接受一个数据包的包体了,还是连包头都没收到。这两个部分的处理是有区别的。
0 Q7 O4 o7 I* `7 F/ O% }
鉴于此,我设置了一个head_is_ready的变量,当2字节的包头发送完,即会置为1:
1 y6 O9 Y- }7 y3 u% i" A. s
        if(head_is_ready==0)
        {
                if(recived_count>=MIN_HEAD_SIZE)
                {
                        head = (struct __head_buff *)buffer;
                        head_is_ready = 1;
                        packsize = head->length+MIN_HEAD_SIZE;
                }
        }...
7 y8 y2 N$ L5 r+ a: J  j9 s7 O
在以上代码中,当缓冲区中放了超过2个字节,就会进行解包操作。通过结构体__head_buff,将缓冲区中的内容格式化,并获取到当前的数据包的总尺寸(length+MIN_HEAD_SIZE)。
  ^1 ?# _; Q9 g! Q, E7 L  b
...else if(recived_count>=packsize){
                //printf("got full package!");
                // PART I
                recived_count-=packsize;
                head_is_ready=0;
               
    ​    ​    ​    ​// PART II1 i3 q+ T0 \  m% Q4 ]
                struct __d5pack * pack = malloc(sizeof(struct __d5pack));
                memcpy(pack,pPack,packsize);
                d5list_push(pack);
               
                // PART III
                memcpy(pPack,pPack+packsize,MAX_PACK_SIZE-packsize);
                pBuffer -=packsize;
               
        }
  @5 r0 d% X& p" [- U1 y
当包头被解析后,再收到数据就不会再进行包头的处理了,而是通过已经获取好的packagesize来检查是否把完整的数据包给接收完了。下一次再有数据,就应该是下一个数据包的包头了。因此head_is_ready需要置0,同时recived_count扣除掉当前已经收到的数据包长度(PART I)。

" a8 l  J4 U7 e( H& y; r
同时,把当前收到的完整数据包,通过结构体__d5pack进行格式化,并将指针推入指针数组d5list中。(推入方法为d5list_push,自己写的,d5list[count++] = p就好了)。等待通过主循环进行处理。上述代码见PART II

' q/ g- y' r5 r% N. v! h1 Z) `
当一个数据包解析完后,这段数据即可废弃。但是缓冲区后后面的数据可能是有效的,因此需要把后面的数据挪到前面来,替换掉已经用过的数据,同时,移动指针pBuffer到有效数据的末尾,接下来继续接受数据:
* ?! a1 v$ q1 K9 U1 v
HAL_UART_Receive_IT(&huart2,pBuffer,1);

% g5 A5 ?4 Z3 B, o
而在主循环中,则定期去处理已经放在待处理队列d5list
                while(d5list_length()>0)
                {
                        struct __d5pack * p = d5list_pop();
                        uint8_t cmd = p->cmd;
                        // ... ... 以下根据cmd进行对应的功能处理即可
                }
                HAL_Delay(10);
如以上代码所示,每10毫秒,如果队列不为空,则程序会依次取出指针,并获取其对应的命令码,再根据命令码进行后续的功能处理就可以了。

6 v# W" M8 n. G$ T" Z  d
----------------我是淫荡的分割线----------------
+ ^( @. z1 ^4 Q. v
编写中的笔记会在我个人的公众号进行,请各位高手斧正。
6 d  f0 b+ W( E# t" m0 b- s
083956bwddhh6ankua6poh.jpg
关于单片机、H5游戏开发和微信小程序
' k& J" v1 H' S% S4 {# b
赞 收藏 评论3 发布时间:2017-11-16 10:50

举报

3个回答
D5Power 回答时间:2017-11-16 17:13:58
发到第七篇啦。串口通讯暂时告一段落,还有几个问题没有摸清楚。继续研究的同时,开始摸摸其他东西了
april1818 回答时间:2017-11-20 02:30:43
谢谢分享 关于中断部分能再解释一下么。。  HAL_UART_Receive_IT(&huart2,pBuffer,1)调用该函数也就是说 每次中断只接收一个字节的数据 然后对接收到的数据进行处理 处理完毕再接收下一个数据即再调用  HAL_UART_Receive_IT(&huart2,pBuffer,1)9 g7 s: h# N' Z  Q8 e# q" |0 `

; L" j+ e) s9 C: t  y9 c0 \5 q  f8 i关于以下这部分的叙述 请问中断部分是写在哪里的?回调函数里吗?
+ Z' b  a: O$ {- h”HAL_UART_Receive_IT(&huart2,pBuffer,1);
2 K( `0 N0 X5 ^' H2 i& B( s7 M2 q9 f# t$ h6 n* V
接下来是中断部分,处理的核心逻辑。每收到一个字节,pBuffer就会自增,从而保证接收到的字节按顺序推入到缓冲区中。recived_count也会自增,用来提供后续的数据接受数量的判断。
4 ]4 N, H( n. i
$ H4 g" @+ f6 z# ?: ^! k1 DpBuffer++;
! A' t  c' m7 e% Rrecived_count++; ”- \% H8 r- p# w9 n" U5 g
joezhuang007 回答时间:2018-7-29 08:18:17
学习!

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版