现在在伊顿公司实习,用的是freescale的mpc563处理器。开发流程大致是:应用层在Matlab建模,然后通过matlab的code generation自动生成代码,得到应用层的*.cpp和*.h文件。然后手打底层代码,同样是*.h和*.cpp文件。然后写Makefile,matlab掉脚本,gmake下调wind river的diab编译器,编译出*.32b文件。就相当于单片机的*.hex文件。然后download到控制器里去。
然后学长创业的公司,用的dsp28335,也是全部用c++。
因此意识到c++在底层也是有一定的应用前景的。
因而就有了封装stm32底层驱动类的想法。
大致的构思是:
保留stm32原有的官方C语言库。在C语言库基础上,将一些1初始化工作 2一些不常修改的配置 3一些需要查手册的配置都封装到库里。下面细讲。
1、初始化工作。STM32的外设的初始化非常类似。大部分都是:使能时钟,配置IO口,配置外设对应的功能。选择是否开中断和DMA。
2、不常修改的配置。比如串口通信,数据长度基本都是8,停止位1,无校验位。大部分时候波特率都在115200。因此把这些都设置为默认参数。在初始化时将省去大量工作
3、需要查手册的配置。还是串口通信,比如TX1 RX1 基本都用PA9 PA10, 定时器选定了通道就选定了IO口,每次查手册效率低。直接封装到类里边,虽然耗flash,初始化时多占点时间,但是开发效率高。
其实优劣无所谓,主要还是想练练手,也能尽快读懂公司的底层的代码的实现。
现在实现的架构如下:
1、对于除中断和DMA的外设,都由基类CPeriph派生来。CPeriph包含了构造函数,外设的初始化函数,DMA 和中断的指针。
2、对于所有中断,即INTE,都由CInte_Base派生来。CInte_Base的本质是NVIC_IRQChannel_。即这个中断类是针对哪条中断线的。其次还包含了一个NvicConfig()函数。对于所有的外设的cpp文件,还应派生出一个对应的针对该类的CInte_Periph。里面包含该外设自己特有的中断配置函数。
3、对于所有DMA,都由CDma_Base派生来。CDma_Base的本质是DMAy_Channelx_,即DMA通道。与INTE不同的地方在于:对于一个外设,比如串口,USART1,它只可能有一条中断线,但有可能有多个DMA通道,比如Txd通道和Rxd通道。因此我们需要可变数量的DMA对象(可变长度:链表),并且可以根据需要操作改对象(类似map的key和item)。
因此,在CPeriph.cpp文件,还封装了一个类,叫做CDmaKit。 Kit类的命名来自伊顿HCM底层数据结构的命名,原理上和map类似,即输入一个枚举变量key(DMA通道),返回一个对象的引用item(DMA类)。而Kit是基于双向链表的,加入一个节点时,即add(key, item)。按照C++ stl 的做法,理论上map的搜索是通过红黑二叉树实现的,在stm32里重写一个对我来说太难了。按照伊顿HCM的做法,Kit寻找一个item是通过遍历链表,匹配Key来实现的。重写一个难度不大。因此参照伊顿的代码写了一个CDmaKit。大致的操作就是DMA.add(key,
item)添加一个DMA通道。 然后DMA.handle(key), 返回Key所对应的DMA通道的对象,可以进行该DMA通道重启等操作。而Key为外设的DMA通道枚举值,比如对USART,分别为TXD和RXD,这两个枚举量在CUsart中定义。那么此时可以看到CDmaKit是模板类Kit继承而来的。这个T表示Kit中的item的数据类型。那么如果对于串口,即应该是CDma_Usart,若是定时器,则是CDma_Timer。因此在每个外设的cpp文件下,还需要有一个继承自CDma_Base的CDma_Periph类。
这里插入解释一下,为什么外设的构造函数的初始化函数要分开:
由于外设的类实例化之后,通常要作为全局变量。比如:串口COM1,我在main函数中要用COM1.printf(),在中断函数中也要COM1.printf()。因此COM1必然是要作为全局变量的。那么全局变量的声明就必须在Main函数之外了。要知道在main函数之外的全局变量,如果在不同的cpp文件,其构造函数的执行顺序是不可控的。如果我们在构造函数里进行了很多重要的比如指针的操作,需要限定某几个类的顺序,那这样将是无法实现的(因为顺序不可控,可能出现指针指飞)。因此,这里的实现是在构造函数里做一些简单的变量的初始化。主要的外设的初始化工作放在init()函数里进行操作。而init()函数统一在main函数一开始执行。
此外,DMA和中断为什么使用指针:
由于并不是每个外设都要开启中断或者DMA,但是作为一个类,一既然要包含这个成员,那么所有实例化对象都会有这个成员。为了突出DMA和INTE这两个功能的选择行,我选择以指针的形式来实现。即如果需要开启INTE,则在栈上new一个INTE出来使用。