一些废话
就算是想摆烂,也不想像被关在天际的大牢里一样掉技能啊。这个系列硬件基础基于原子哥的战舰开发板(STM32F103ZE),会根据自己的状态每天更新各个模块的探索情况,主要会记录一些我自己没法在原本教程中直接领悟或者之前工作中没注意但是有用的东西
正式开始
谁不是从跑马灯开始学起的,不过虽然简单,关于位带操作的部分还是认真翻回文件结构的部分认真的看了一下
点亮一个LED,你可以
GPIOB->BSRR=GPIO_PIN_5;//也是点亮这个LED
或者
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET);//还是点亮这个LED
但是你也可以按教程里面一样
原代码(摘自原子哥教程)
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOB_ODR_Addr (GPIOB_BASE+12)
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n)
#define LED_0 PBout(5) //LED0
......
LED_0 = 1;//点亮LED
LED_0 = 0;//再把它熄了
获得一个十分简洁炫酷的操作LED的方式
这段之前只是拿到手里用而已,现在认真分析一下
从底层往上看
第一个宏定义
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n)
最简单的一个,只是简单的把BIT_ADDR这个宏定义的第一个参数addr用另外一个宏定义
#define GPIOB_ODR_Addr (GPIOB_BASE+12)
替换了而已,观感上可以让程序更像51单片机那样简单
#define GPIOB_ODR_Addr (GPIOB_BASE+12)
至于这个宏定义中的GPIOB_BASE这个宏定义,当然是STM库里面自带一个,指示的是B端口寄存器起始的位置,有关它的状态、设置等等一系列的寄存器都会按顺序在这个地址之后排队,而这其中,控制B端口输出的寄存器在第12位(这个说法有点不准确,这里就当是这样吧)
emm,简单来说你可以想象一下公司食堂开饭了,只有一个窗口,公司规定一个部门的人要排在一起再组成一条长龙,并且在各自部门中要按自己的业绩排名排好(假设他们的业绩都不会变化),那现在已经是中饭时间了,你现在想找销售业绩第三好的人(为什么吃饭的时候还要找人,死去的上班痛苦记忆开始攻击我),你自然是可以直接大吼,张三出来!
张三 = 出来;
但是你也可以按照你丰富的业务经验,想起销售是排队的第5个部门,你找到第五个部门,往后数到三,不容分说把他拉出来
队伍中的第五个部门+第三个人 = 出来;
至于GPIOB_BASE,按这个理解,它只不过是GPIOB这个部门里面业绩最好的那个而已,至于具体这个部门里面其他人怎么排的,还请看STM32的芯片手册
不过要是你仔细一想,不对啊,感觉还是直接喊张三出来快啊
废话,你只找张三肯定快啊,问题是我们的工作就是在午饭的时候喊不同的人干活(什么魔鬼),连着来你人都要喊傻了
但是如果你按照后面一种方式,那么同样部门的人你可以这么叫
队伍中的第五个部门+第三个人|第五个人 = 出来;
你甚至可以这么叫
队伍中的第六个部门+张三以外的人 = 出来;
大幅增加嗓子寿命啊!这就是位带操作了
回过头来看另外的宏定义
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n)
PBout和GPIOB_ODR_Addr我们已经搞清楚了,那它用的BIT_ADDR这个是干什么的?
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
两眼一闭,先去除volatile关键字,再把addr改成x来看
*((unsigned long *)(x))
还是有点困难?那这样呢?
*((unsigned long *)(x)) = 1;
是不是括号里面先把x转换成了一个长整型指针,然后对这个指针指向的地址赋1?
至于volatile关键字,这个之前学习的时候确实忽略了,建议观看https://zhuanlan.zhihu.com/p/343688629这位大佬,说得很详细
而
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
嘛,很明显,只是为了把BITBAND 产出的地址信息传递给MEM_ADDR的工具人罢了
那么终于来到最后一步,长得跟施法咒语一样的玩意
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
一开始看,就感觉看上去是最要命的一个,实际上原子哥教程中有提到过,位带这段参考的是Cortex-M3权威指南87页到92页(这个手册确实看得头疼,但真的很不错),结合文件中的这张图
可以看到外设所占用的部分中,最下面的1MB叫位带区,而上面的32MB叫位带别名区,然后我们看下面这张图
这个是这个指南译者添加的,画的是SRAM的位带对应关系,不过外设那边也是一样的原理,可以看到位带区的每一位都对应了上面位带别名区的32位——只看彩色部分,从0x20000000到0x20000001,对应的是上面彩色部分的,0x22000000到0x22000020(可不是到1C的28倍,想想0x20000001起始对应的是什么?)
拿前面的例子来说,这个是给你配了另外一个小队,每个小队分管原来食堂队伍中其中连续的32个人,每次要叫人的时候,你只需要和你的小队中其中一个人说,去找你管的人中的谁谁谁就可以了
。
但是这么做,你管的这个小队,他也是人,也要吃饭的啊,所以你把他放到队伍最前面(外设地址最前面),那现在又来了个问题,你管的小队和你的小队管的这块人中间隔了一批子和这件事没什么关系的人(即上面图中0x40100000到0x41FFFFFF这块被位带区和位带别名区夹着的地方)。那么你的小队队员想找到原来的人,是不是要多往后数你的小队长度+这批子闲人的长度,即0x2000000,是不是有点眼熟?
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
里面的0x2000000就是这么来的,至于addr & 0xF0000000则是为了区分SRAM和外设地址(一个是2开头,一个是4开头),(addr &0xFFFFF)<<5是不是左移了5位?左移5位是乘上了2的5次方,2的5次方是32——32,32倍,你看,是不是都对上了?
还有最后一个问题,bitnum<<2是什么意思?这个嘛,我们前面字到位的转换是按32倍计算的,后面这个位到位的转换自然要除去一个字节中位的数量,也就是要除以8,即乘以4.
这么一来就全部搞清楚了,回过头看
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOB_ODR_Addr (GPIOB_BASE+12)
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n)
#define LED_0 PBout(5) //LED0
......
LED_0 = 1;//点亮LED
LED_0 = 0;//再把它熄了
这串代码也就是通过宏定义将“”B“和5”两个信息逐层上传,经过位带操作映射一下直接对接到PB5的输出控制寄存器
可不要嫌弃搞这么多就是为了点亮一个LED,(这不是还有熄灭吗XD),都说了这个操作可以针对外设,那它的外设可远不止一个LED
算了,今天恰撑着了,先写这么多吧,况且用这个写日志我害得适应一下