最近小编也是在学习 STM32F1 系列,因为前一段时间学过 51 ,所以这几天总结了一下从 51 过度到 STM32 经验与心得,现在与大家分享分享我的心得。
对于引脚的操作和 STM32 与 51 的不同
在 51 中,我们如果想要操作 P0^0 这个引脚,应该有一下几种操作吧。
//这个是小编最开始学的操作IO口。
P0 = 0x01; //直接操作整个P0口,使P00口为高电平,其他为低电平。
P0^0 = 1; //单独操作一个51单片机IO口。
//后来学会了只影响一个IO口,而其他IO口不受影响。
P0 |= 0x01; //这样做可以保证在不影响P0的其他IO的情况下只改变P00口。
//在51单片机中还有一种独有的(这个我查阅了其他资料,并没有发现sbit是51单片机独有,但是我所学过的里面只有51能用sbit,就暂时说是51单片机独有吧,希望大佬指正。)引脚定义。
sbit IO = P0^0; //将P00口单独定义出来,这样就只会操作到一个IO口了。
以上就是我目前用到过的对于 51 单片机的 IO 口的操作。
不过我一直在思考为什么这样做?为什么 P0^0 这个符号就可以代表的 P0^0 这个引脚,是不是 STM32 也一样?
直到我学习了 STM32 ,我对于它的操作原理有了新的定义。
对于每一个 IO 口其实是有一个地址,要操作这个 IO 口其实是对这个地址的操作,下面我放一段 <STC12C5A60S2.H> 的代码进行讲解。
//由于原程序里面代码太长,我这儿截取几段来集中讲解。
//这儿我截取了51单片机里对P0和P1的定义
sfr P0 = 0x80; //首先我们要知道sfr的意义。
//sfr的定义有点长,在这儿的作用我就长话短说,它是将0x80定义为P0.
//这儿就可以发现,曾经我以为的P0就是IO口的引脚其实也是认为定义的,IO口真正的地址其实是0x80,这个也是接下来说STM32要用到的原理。
sbit P00 = P0^0; //对于sbit就不生疏了吧,这儿为什么要这样定义呢?
//因为我们在上面已经知道了P0的地址,0x80,这个是属于P0口的基地址,什么是基地址?简单来说,就是第一个地址。
//因此我们可以由此推断,P00就是P0的基地址在位移0位,也就是0x80,而P01就是P0位移1位,也就是0X81,所以后面的代码也就由此退出了。
sbit P01 = P0^1;
sbit P02 = P0^2;
sbit P03 = P0^3;
sbit P04 = P0^4;
sbit P05 = P0^5;
sbit P06 = P0^6;
sbit P07 = P0^7;
sfr P0M0 = 0x94;
sfr P0M1 = 0x93;
//下面与上面意义一样
sfr P1 = 0x90;
sbit P10 = P1^0;
sbit P11 = P1^1;
sbit P12 = P1^2;
sbit P13 = P1^3;
sbit P14 = P1^4;
sbit P15 = P1^5;
sbit P16 = P1^6;
sbit P17 = P1^7;
sfr P1M0 = 0x92;
sfr P1M1 = 0x91;
sfr P1ASF = 0x9D;
对于 51 单片机的 IO 口定义也就分析完了,现在我们开始进入正题,由于上面的知识,对于 STM32 我觉得应该会相当好理解了。
首先,我们要明白, STM32 中可没有 sfr 或者 sbit 或者 bit 这种方便的定义,所以我们只有操作地址来操作 IO 口。
在具体分析之前,有个概念必须了解一下,就是储存器,储存器映射,寄存器和寄存器映射。
储存器
储存器应该很好理解,就是每一个外设(外设就是我们上面说的 P0 , P1 等 IO 口在 STM32 中这些 IO 口被分为了不同的外设。)储存了一个地址,而对于储存器本身是不具有地址信息的,它的地址是由芯片厂商或用户分配,这个过程(给储存器分配地址的过程)就称为储存器映射。
寄存器
我们已经了解到了储存器映射,那么什么是寄存器映射呢?寄存器到底是什么呢?
已经知道了储存器, 那我们可以找到每个内存单元
(每一个外设都是分配了内存单元的,用 51 单片机来讲,就可以这样理解,P00 这一个 IO 口就占用 1 bit 的内存单元)
的地址,然后通过 C 语言指针的操作方式来访问这些单元,但是,每次都通过寻址来访问,不仅不好记忆,还容易出错。所以这个时候我们可以根据每个每个单元 功能 的不同,以功能为名给这些内存单元取一个别名,这个别名就是我们常说的寄存器。取名字这个过程就叫做寄存器映射(和储存器映射相仿。)。
看到这儿,想必对寄存器和储存器的概念已经很清晰了吧,接下来我们就可以深入了解 STM32 了!
上面是我所使用的 STM32F103 系列的寄存器的起始地址。
可能天马行空的这样看图不是很理解,接下来我就用代码与讲解的方式来讲解。
我就用图中的 GPIOB 这个寄存器来作为例子。(因为小编最开始接触 STM32 的时候就是学习的这个寄存器的使用。)
在图中可以看到 GPIOB 的起始地址是 0x40010c00;
所以我们就可以通过控制这个基地址来操作 GPIOB 了。但是,要怎么操作呢? 是像 51 单片机里面一样直接让这个地址等于 0 吗?显然不是!
从 STM32F103 的数据手册的目录,我们可以找到与 GPIO 有关的寄存器。
下面我们随便打开一个寄存器来分析。
有这个图片来分析,首先是偏移地址。什么是偏移地址? 因为对于 GPIO 口不只有 GPIOB ,还有 GPIOA 、 GPIOC 等等每个 GPIO 都有相对应的这种寄存器(图中是 BSRR )寄存器,所以,偏移地址就是 各自 GPIO 口的基地址所偏移后的地址。
//理论如果不好理解,可以参考代码,综合理解。
//由上可知GPIOB的基地址是0x40010c00,所以我们可以使用宏定义。
#define GPIOB_BASE ((unsigned int)0x40010c00)
//有了GPIOB的基地址的用定义,我们就可以在这个的基础上定义BSRR寄存器了(重点:使用指针!!!!!!)。
#define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE + 0x10)
//现在,我们就把GPIOB的BSRR寄存器的地址定义好了。
接下来我们先分析 BSRR 的作用。
由图我们可以看到, BSRR 一共有 32 位,低 16 位的作用是控制 GPIOB 的 16 个输出端口输出 1(高电平);
//就像是51单片机里面的。
P0 = 0x01;
而高 16 位是控制 GPIOB 的 16 个端口清除(就是输出 0(低电平) 。)。
所以,我们可以这样控制 STM32 的 GPIOB 的 1 到 16 口的输出。
//由于在这之上我们使用了宏定义,所以可以直接使用所定义的宏。
GPIOB_BSRR |= (1 << x); //从这儿就可看出,将GPIOB的GPIOB_x这个引脚设置为高电平了。
//具体想要输出/清除哪个引脚,只需要改变x的值就行,若x=0,就是使GPIOB的Pin0口输出高电平。
//当然,也可以直接用地址(不过比较麻烦。)。
*(unsigned int *)0x40010c10 |= (1 << x); //作用与上面相同。
这就完了?
当然不是!
再来看一下这张图:
上面有些 AHP ,APB2,是什么呢?
我们就要结合这张图了。
这儿可以看到 GPIO 、 ADC (这些称之为外设)等等外设是挂载到 APB1、 APB2、AHB上面的,在 STM32 中,要使用这些外设,我们就必须将这些总线的时钟打开(因为默认是关闭,所以需要手动打开。)。
这儿的操作与上面操作 GPIOB 操作一样,找到时钟( RCC )的基地址,再找到寄存器地址,通过操作寄存器的方法来是使APB2 (因为我们操作的是 GPIOB ,而 GPIOB 是挂载在 APB2 上面的)的 RCC 打开。
然后我们的 GPIOB就可以正常使用了。
#define RCC_BASE 0x40021000 //RCC的基地址。
#define RCC_APB2END *(unsigned int*)(RCC_BASE + 0x18) //RCC的使能寄存器地址。
RCC_APB2END |= (1 << 3) //由数据手册来看第三位是使能GPIOB的时钟。
好了,今天的分享就说完了,不过,这儿,只是从原理分析了 STM32 的 IO 的操作,对于固件库还有 HAL 库的讲解以后会分享,使用库可以更加容易的理解和使用 STM32 !
谢谢大家的耐心观看,代码之路任重道远,愿与大家努力习之!
版权声明:本文为CSDN博主「YVinci•」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_53624282/article/details/114070889
暂无评论