嵌入式开发交流网论坛

标题: GOKIT硬件STM32源码分析 [打印本页]

作者: 下星期去美国    时间: 2020-1-15 17:01
标题: GOKIT硬件STM32源码分析
从收到机智云的第一批试用板子到现在也挺久了,一直没时间和大家分享,今天抽空写下我的感受,机智云gokit3.X是2016年6月25日发布的新产品,模型与Gokit2基本相同,采用底板+功能板+模组的方式,保留arduino接口,可扩展性不错,具备以下功能:

有3种配置:
GoKit3(V) - 语音模组版 (GoKit3功能板+底板+宇音天下模组)
GoKit3(S) - SoC版(乐鑫模组(GoKit3转接板)+底板+GoKit3功能板)
GoKit3(H) - 高性能模组版
[attach]36186[/attach]

我拿到的是带宇音天下模组Lark7618的语音识别版本,机智云还送了esp8266的模块,赞一个!试用了下效果还不错,一些基本的开源项目论坛已经很多了,我就不多说了,我着重分享下STM32端整个程序的流程,机智云传输这么稳定得益于它整个框架的完整性,包括协议的制定以及解码部分的机制,是一个比较好的框架,下面重点分析:
[attach]36187[/attach]

首先我们来看主函数,开始先将系统初始化,主要包括Flash接口和时钟的配置,这里初始化为72MHz,之后是用户数据部分初始化

[attach]36188[/attach]

主要包括系统延时、串口、RGB灯、LED灯、电机、温湿度、红外对管等的初始化,之后开启看门狗,复位时间2s,然后就是按键的配置了,按键部分程序也不错,值得我们借鉴,我们找到按键定义的地方,


然后追踪定义,我们go to definition,查看singleKey数组的定义结构体,定义如下

[attach]36189[/attach]

主要有按键Num,GPIO端口,以及长按短按的回调函数。之后看到将数组地址与按键数值组合成keysTypedef_t这样一个结构体,方便调用结构体各个参数,明白了按键的定义,我们接着看按键的初始化

[attach]36190[/attach]

将每个按键的物理GPIO以及长短按回调函数都赋值到相应数组,这里一共初始化了2个按键,之后将数组地址传到keys这个结构体变量,初始化之后就可以通过keys来调用按键的所有功能了。按键的具体控制原理后面会将。接下来是机智云协议的初始化,之后打印一串初始化成功的数据。


那么gizwitsInit;初始化了什么东西呢,我们进去看看

[attach]36191[/attach]

这里就是初始化定时器和串口了,定时器初始化了定时器3,时钟9分频,自动重装载值7199,算下来1ms中断一次,我们追踪到定时器中断

[attach]36192[/attach]

看到这个定时器的作用是提供一个时基, gizwitsProtocol.timerMsCount每秒加1。然后串口这里初始化了串口2,主要用来通讯,之后就是串口环形buff的一些初始化,下节介绍。

之后就进入主循环了,喂狗,用户数据的处理以及整个协议的运转一直循环。

[attach]36193[/attach][attach]36194[/attach]


这个图是用Xmind做的,详细分析了整个程序的框架,当然,精髓不在这里,关于按键和串口环形buffer的数据处理方式,我们下节再探讨。

昨天分享了整个程序的架构,今天重点分析一下串口接收数据这块的机制
我们知道,通过串口接收数据一般有3中方式,轮询、中断以及DMA。轮询模式为堵塞模式,必须要定时去查询收到的数据;中断模式为非堵塞模式,也是平时用的比较多的,但每次只能接收一个字节;还有一个比较好的方法那就是用串口的空闲中断+DMA实现串口数据的接收,在接收一帧数据只需要中断一次,这样就可以接收不定长数据了。机智云这里采用的方式2,即常规的中断方式。

数据通讯采用的串口2,引脚为GPIO2和GPIO3,在gizwitsInit中进行初始化
[attach]36195[/attach]

我们进去看看

上图主要初始化了一些硬件接口,并开启中断,这也是我们一般的写法,再往下看,看到一个pRb的结构体,这是个什么呢,我们追踪下,下面是pRb的定义
[attach]36196[/attach]

我们先来解释下环形缓冲区的原理:

环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区中可写的缓冲区。通过移动读指针和写指针就可以实现缓冲区的数据读取和写入。在通常情况下,环形缓冲区的读用户仅仅会影响读指针,而写用户仅仅会影响写指针。
这里的rbCapacity代表缓冲区的容量,head指向了读区域,tail指向了写区域,rbBuff指向缓冲区的入口地址,示意图入下
[attach]36197[/attach]

明白了结构体的定义,我们接着往下看

[attach]36198[/attach]

rbCreate,顾名思义,此函数的作用用于创建缓冲区,将缓冲区的head/Tail都指向缓冲区的首地址,那么rbCapacity和rbBuff在哪里赋值的呢?我们返回去看gizwitsInit;
[attach]36199[/attach]

看到这里我们就明白了,继续往下看
[attach]36200[/attach]
这个函数为删除缓冲区函数,将结构体里面的数据全部清零

[attach]36201[/attach]

这个函数为获取缓冲区的总容量,很好理解
[attach]36202[/attach]

接下来这个函数为缓冲区有多少数据可以读,有三种情况:
1、Head和Tail都指向同一个地址,可读大小为0,返回0,这种情况只会出现在缓冲区还没有数据的时候,使用之后就不会出现头尾重合的现象;

2、HeadTail,如下图所示,缓冲区已经写满,并且从开头处重新写了数据,可读部分为蓝灰色区域(rb_capacity(rb) - (rb->rb_head - rb->rb_tail));


[attach]36203[/attach]

接下来的函数为可写区域大小,直接用总容量rb_capacity(rb)减去可读区域大小就好了。
[attach]36204[/attach]

然后是读数据函数,从Head处开始读,读取count个数据,放到data地址开始的数据区域,如下图所示,也是分为三种情况
[attach]36205[/attach]

1、HeadTail,且count中的数据小于从Head到缓冲区尾部的个数,即小于下图中的蓝灰色,与第一种情况一样,直接复制相应内存,之后修改Head指针即可。


3、Head>Tail,且count中的数据大于从Head到缓冲区尾部的个数,即大于下图中的看灰色,这种情况我们就先把Head到缓冲区尾部的数据复制到data处,再把绿色区域的复制过去,这里绿色部分并不会超过Tail,写操作中做了限制。

[attach]36206[/attach]

最后是写数据函数,把从data指向的地址,写到Tail指向的地址,写count个数据,返回成功写入的个数,在这里判断了要写入的数据大小要小于可写区域大小,防止数据覆盖,如下图所示,也是分为三种情况
[attach]36207[/attach]

<div align="left">1、Head




欢迎光临 嵌入式开发交流网论坛 (http://www.dianzixuexi.com/bbs/) Powered by Discuz! X3.2