本帖最后由 GCT 于 2022-7-14 11:59 编辑
《使用寄存机点亮LED》 课后作业 在入门篇 第七节课最后,火老师留了两道题,如图
题目
1.点亮其他两个LED灯。(绿灯已经在课上演示过了,要实现红、蓝灯点亮) 2.写一个简单的延时函数,让灯闪烁。 遇到的问题用红色字体和下划线标出了。
第一题
依葫芦画瓢,先看LED原理图。
LED原理图
LED使用的GPIOB(General Purpose Input/Output)端口,其中红灯在PB5端口、绿灯在PB0端口、蓝灯在PB1端口。
要想让一个外设运行,先要让它的心脏——时钟使能,然后再使能端口。 先打开ST官方的参考手册,找到存储器映像(Memory Map)(英文版p51 中文版p28)
存储器映像
可以看到RCC的基地址为0x40021018,GPIO端口B的基地址为0x40010C00,GPIOB使用APB2总线。
找到APB2外设时钟使能寄存器RCC_APB2ENR(英文版p70 中文版p112)
偏移地址为 0x18,和基地址相加可以算出 RCC_APB2ENR 的物理地址为 0x40021018。 还可以得到一个信息,初始值所有位为0(Reset value)。
接着往下看
可知GPIOB的时钟用第三位控制,置1使能。 置1使用 |= 按位或运算而不是直接赋值,为了不改变原有的位。
下面演示一下计算过程:(涉及到低四位,高位全部是零,为了方便看就省略了) (1<<3) = 1000 0000 | 1000 = 1000 第3位成功置1 代码实现:
- *(unsigned int*)0x40021018 |=(1<<3);
复制代码
然后要配置GPIO的输出模式和速度。
找到端口配置低寄存器GPIOx_CRL(端口配置低寄存器负责控制GPIOx的0-7端口,高位寄存器负责控制GPIOx的8-15端口)
(英文版p171 中文版p113)
这里中文版和英文版写的不一样,中文版是x=A..E,英文版x=A..G,英文版多了GPIOF、GPIOG。103VE似乎只有GPIOA-GPIOE
偏移量0x00,GPIOx_CRL基地址就是物理地址0x40010C00。
值得提一下这里的初始值0x4444 4444,先要说明这里的32位是怎么组成的。
寄存器可控制0-7共八个端口,每个端口用四位控制。高两位是CNFy,确定输入/输出模式(推挽/开漏);低两位是MODE,用于确定输入还是输出,还有输出速度。
初始值把每一个端口初始化为4,也就是0100,这个在下图中也有体现(reset state)。
选择推挽输出模式(?课上说的是推挽输出,可是实际结果似乎是开漏输出?(补充:后来上课讲到需要先清零,这样就没有问题了)),输出速度10MHz即可
红色LED在PB5,蓝色LED在PB1
按需要将第20位或第4位 置1即可
下面是计算过程,同样省略高位只写了低八位:
蓝LED置1过程:(低八位)
(1<<(4*1)) = 0001 0000 //4代表一个端口的四位,右边的1代表PB1
0100 0100 | 0001 0000 = 0101 0100
置位结果是开漏输出,速度10MHz?
红LED置1过程同上
代码实现:(color变量代表对应颜色的端口号,红=5,蓝=1,绿=0)
- *(unsigned int*)0x40010C00 |=(1<<(4*Color));
复制代码
最后是配置端口输出数据寄存器GPIOx_ODR(英文版p173 中文版p115)
偏移量0x0C,加上基地址可得GPIOx_ODR物理地址0x40010C0C。
初始值为0x0000 0000
LED是置0点亮,置零&=~ 按位或 取反 运算。
将LED蓝所在的1位置0,应该执行如下操作:(依然为了方便看,只写了低八位)
1<<1 = 00000010 //右边的1代表PB1
0000 0000 & ~(0000 0010) = 0000 0000 & 1111 1101 = 0000 0000
实现代码:
- *(unsigned int*)(0x40010C0C) &=~(1<<Color);
复制代码
总结一下,寄存器点灯程序的三部分:时钟使能、配置输出模式、配置输出。
将不同颜色的端口定义到了宏定义,整合得到如下完整代码:
(一开始我写的时候只改了ODR寄存器,没改CRL....然后一直不亮....后来仔细又参考手册才发现CRL也需要修改...)
- #include "stm32f10x.h"
- #define Red 5
- #define Green 0
- #define Blue 1
- int main(void)
- {
- int Color=0;
- Color=Red; //根据需要改变Color的值Red/Blue/Green
- *(unsigned int*)0x40021018 |=(1<<3); //控制RCC寄存器 打开GPIOB端口的时钟
- *(unsigned int*)0x40010C00 |=(1<<(4*Color));//控制CRL寄存器 配置IO口为输出
- *(unsigned int*)(0x40010C0C) &=~(1<<Color);//控制ODR寄存器
- }
- //函数体为空,目的是为了骗过编译器不报错
- void SystemInit(void)
- {}
复制代码
第二题
说到延时先想到了用循环实现延时。但是转念一想程序运行代码都是很快的,以微秒计数的,可能达不到毫秒甚至秒级别的延时,精度也会极差,就没往下想。
然后就想到了中断和延时,但是翻阅了好久的参考手册以及附带的其他资料,发现和GPIO寄存器操作不太一样,还需要多学习才能搞出来...
于是求助了网络,找到一篇比较清晰的文章:https://blog.csdn.net/Firefly_cjd/article/details/106709259,最简单的方法居然真的是让程序循环来实现延时。
- //微秒级的延时
- void delay_us(int delay_us)
- {
- volatile unsigned int num;
- volatile unsigned int t;
-
- for (num = 0; num < delay_us; num++)
- {
- t = 11;
- while (t != 0)
- {
- t--;
- }
- }
- }
- //毫秒级的延时
- void delay_ms(int delay_ms)
- {
- volatile unsigned int num;
- for (num = 0; num < delay_ms; num++)
- {
- delay_us(1000);
- }
- }
复制代码 实现原理是很简单的for循环体和while循环体,但是有一个细节需要注意:关键字volatile。这个关键字以前c语言编程的时候从来没有出现过。
网上查了一下得到这样的解释:volatile属于C语言的关键字,《C Primer Plus》 是这样解释关键字的:关键字是C语言的词汇,由于编译器不具备真正的智能,所以你必须用编译器能理解的术语表示你的意图。开发者告诉编译器该变量是易变的,无非就是希望编译器去注意该变量的状态,时刻注意该变量是易变的,每次读取该变量的值都重新从内存中读取。
(来自:https://blog.csdn.net/weixin_38815998/article/details/102840096;这一篇也有很详细的解释:https://blog.csdn.net/hgsdfghdfsd/article/details/52129364)
看完概念还是有点一知半解。经过测试,如果去掉关键字volatile,延时的时间会缩短很多,可能加了关键字volatile访问变量值的过程所耗费的时间会变长吧。
使用这个延时函数,结合上面的点灯代码,我实现了一个颜色的灯闪烁操作 以及 两个颜色的灯延时切换的操作,代码如下:
红灯闪烁:
- #include "stm32f10x.h"
- #define Red 5
- #define Green 0
- #define Blue 1
- void delay_us(int delay_us);
- void delay_ms(int delay_ms);
- void init();
- int main(void)
- {
- int Color=0;
- *(unsigned int*)0x40021018 |=(1<<3);
- while(1)
- {
- Color=Red;
- *(unsigned int*)0x40010C00 |=(1<<(4*Color));
- *(unsigned int*)(0x40010C0C) &=~(1<<Color);
- delay_ms(50);
- init();
- delay_ms(50);
- }
- }
- //函数体为空,目的是为了骗过编译器不报错
- void SystemInit(void)
- {}
- //微秒级的延时
- void delay_us(int delay_us)
- {
- volatile unsigned int num;
- volatile unsigned int t;
- for (num = 0; num < delay_us; num++)
- {
- t = 11;
- while (t != 0)
- {
- t--;
- }
- }
- }
- //毫秒级的延时
- void delay_ms(int delay_ms)
- {
- volatile unsigned int num;
- for (num = 0; num < delay_ms; num++)
- {
- delay_us(1000);
- }
- }
- void init()
- {
- *(unsigned int*)0x40010C00 =0x44444444;
- *(unsigned int*)(0x40010C0C) =0x00000000;
- }
复制代码
红蓝交替:
- #include "stm32f10x.h"
- #define Red 5
- #define Green 0
- #define Blue 1
- void delay_us(int delay_us);
- void delay_ms(int delay_ms);
- void init();
- int main(void)
- {
- int Color=0;
- *(unsigned int*)0x40021018 |=(1<<3);
- while(1)
- {
- Color=Red;
- *(unsigned int*)0x40010C00 |=(1<<(4*Color));
- *(unsigned int*)(0x40010C0C) &=~(1<<Color);
- delay_ms(50);
- init();
- Color=Blue;
- *(unsigned int*)0x40010C00 |=(1<<(4*Color));
- *(unsigned int*)(0x40010C0C) &=~(1<<Color);
- delay_ms(50);
- init();
- }
- }
- //函数体为空,目的是为了骗过编译器不报错
- void SystemInit(void)
- {}
- //微秒级的延时
- void delay_us(int delay_us)
- {
- volatile unsigned int num;
- volatile unsigned int t;
- for (num = 0; num < delay_us; num++)
- {
- t = 11;
- while (t != 0)
- {
- t--;
- }
- }
- }
- //毫秒级的延时
- void delay_ms(int delay_ms)
- {
- volatile unsigned int num;
- for (num = 0; num < delay_ms; num++)
- {
- delay_us(1000);
- }
- }
- void init()
- {
- *(unsigned int*)0x40010C00 =0x44444444;
- *(unsigned int*)(0x40010C0C) =0x00000000;
- }
复制代码 本人是初学者,可能在编写过程中会有一些错误,也有可能在学习中有一些理解错误。有什么不足还请大佬们指点~感谢~
(顺便吐槽一下发帖的Markdown编辑器有bug啊...这个帖子写了三次...)
|