我们嵌入式软件究竟在写什么?我想聊聊这个东西,现在MCU几乎都是ARM的天下了,学通一个百样不愁。
抛开体系繁杂的东西不谈,其实最该明白的一点就是地址,当你明白就在操作地址,查,改,写的时候,你就会觉得不过如此。
首先你要知道对于CPU的视角看这些东西,都是取指令,处理,传出去,它其实是分不出来到底是一个SPI和UART或者一块内存。
就这个样子的
看这个框图,ARM内核和中断这些是最核心的东西,接着通过AHB这个总线连接了下面 APB1和2 ,就是从高速到低速,一级一级的控制。看SOC最重要的就是这个图。
抽象成这样
再看一个LPC1100,NXP的M0+的,也是这样的
这个图就是这样,从最高到最低囊括了一切,从总线到外设到内存:
地址分配
那上面这么多的器件,就被都抽象成了地址,只要对这个地址操作就行了。
更加详细的地址分配,这样看就分的更细致了
这个是STM32的
我们这里ADC在MCU里面的绝对地址是这个
接下来继续看,这个地址里面还有VC和LVD,每个外设都享有一些地址。那我们要办法再人性化一些!
首先我们知道起始的地址在这里
我们再把这个地址分块,分成一个一个的小格子BASE+address,就是对ADC这个外设的功能块进行了编码:
就是这样
ST家会写明这个偏移量是多少
我们看到这个CR到START之间有32bit,每个bit的作用是什么呢?
可以看到这些功能是什么样的!
代码里面如何做的?
我们使用C语言里面的结构体,把这段控制的地址进行一个封装,甚至说是映射吧。
这样就可以顺着地址进行一一的编码对应地址,union在内存里面的地址是连续的。
上面是对地址进行了编码,接下来就是操作这每一个位,我们还需要一些封装,其实不是封装,而是为了位运算的方便做的设计。
用于在同一块内存空间中存放不同的数据类型,以便能够通过不同的方式访问同一段内存。
因为地址空间一样,所以可以这样写
共享同一内存空间:联合体中的所有成员都共享同一块内存空间。换句话说,CR 和 CR_f 只是对同一段内存的不同访问方式。CR 变量允许直接访问整个 32 位的寄存器,而 CR_f 结构体则允许按位访问该寄存器。
CW_SYSCTRL->CR = 0x12345678; // 设置整个寄存器的值
uint32_t value = CW_SYSCTRL->CR; // 读取整个寄存器的值
整个地址都拿走了。
CW_SYSCTRL->CR_f.EN = 1; // 设置寄存器的 EN 位
uint32_t clk_value = CW_SYSCTRL->CR_f.CLK; // 读取寄存器的 CLK 位
单个位段是这样的。
对每一位给了掩码和位置
ADC_CR_BGREN_Pos
:这是一个位位置(bit position)的宏。它表示 ADC_CR
寄存器中 BGREN
位的位位置。
1UL
是一个无符号长整型常量,值为 1。UL
是指 unsigned long
类型,因此该宏定义表示 BGREN
位在寄存器中的位置是第 1 位(从 0 开始计数)。
这个宏用于定位 BGREN
位在寄存器中的具体位置,方便在位运算中使用。
看第二个:
位位置(Pos):表示位字段的位偏移。通过这些位置,开发者可以知道哪个位置表示某个功能。
位掩码(Msk):通过掩码,可以单独操作寄存器中的某一位。
在这个宏里面使用:
也就是我们要先获得CR这个寄存器
在这个地址里面做处理,就是我们把ADC_BASE后面的32个地址进行了强制的地址转换,其实就是给这片地址上了身份证。
我们要把CR控制器里面的BGREN使能
就是这样
这就是直接对位操作,但是这样看起来还是得看参考手册,还是不彻底。接下来就是要开始进一步封装,HAL库就要来了。
每一个外设都有一个头和具体的操作文件
这个先摆一下,反正就知道大部分都封装成了这样
我们先看这个:
定义了一系列位掩码宏(bitmask macros),用于操作一个 uint32_t 类型的变量中每一位。
它通过宏 bv(n) 定义了从第 0 位到第 31 位的每一个位掩码。这些宏可以用来设置、清除、检测或修改整数中相应位的值。
有了上面的知识就可以看这个了
看一个失能,其实是对时钟进行了操作
我们看几个函数,学习一下
首先是我们初始化
ADC_InitStruct:这是一个指向 ADC_InitTypeDef 结构体的指针,该结构体包含了 ADC 初始化所需的配置参数。
也就是说,H文件定义了一些结构。
然后在C文件里面具体操作和封装。
先学一个宏:
选择CR这个大地址里面,然后就是要清三个位置,接着赋值进去,是不是很简单。
接着这个,就是我上面写的
就是这样
在编程的时候其实就是库函数和对寄存器的直接读写混合的使用
总之的一切的起点就是地址,每一位都是一个开关。我们通过C语言的结构体来把相关外设的寄存器组织在一起方便控制。
接着我们又给了每一位的位置宏和掩码宏,来方便位运算控制。
最后进一步抽象,给出了对应的头文件里面的结构体,是对外设的进一步抽象。最后把前面的单条寄存器操作和一些校验放一起就是库函数了。一级一级的封装。
我想起来了大概14年的时候一些工程师天天骂ST的HAL库,死守标准库,现在看来真是愚蠢。都是封装罢了,有什么好吵的。