在51中让一个引脚输出高低电平只需要一个步骤,而在32中至少需要三个步骤。

开启对应GPIO的时钟配置对应IO口设置IO口

本文将一步步进阶的讲解,四种寄存器编程的方法。

使用地址赋值进行配置使用ST的宏进行配置只控制需要的位(位运算)

与(&),或(|)左移<<,右移>>使用ST的宏进行位运算

使用地址赋值进行配置 

第一步:启动对应IO口时钟,这里我们以PA0,PA1,PA8为例。

 从数据手册上可以看出,GPIOA在APB2时间线上,所以启动对应IO口时钟线,就是启动APB2。

如何打开寄存器时钟?

        这里以APB2外设使能寄存器(RCC_APB2ENR) 为例。启动寄存器本质上就是,找到寄存器的地址后赋值。当赋值的时候可以看上图,有0~31,它们从0开始,4个为一组为二进制数组,32个函数组成8个二进制数组,而把每组二进制转化为十六进制,就是赋值的值了。

从上图可得,当我们 区要启动GPIOA的时钟时,位2需要置位成1。

位3位2位1位00100

0100的二进制转化十六进制为4,所以等等给RCC_APB2ENR赋值的值为4。 

而从第一张图我们可以看到RCC_APB2ENR的偏移地址:0x18,其地址=偏移地址+基地址。

打开STM32F10x参考手册里面有一个“存储器映像”里面可以看到其基地址。

从上图可以看出我们需要的“复位和时钟控制”基地址为0x4002100。

所以RCC_APB2ENR的地址=0x40021000+0x18。

#include

int main(void)

{

//1.开启对应GPIO的时钟

*(uint32_t*)(0x40021000+0x18)=4;

//2.配置对应IO口

//3.设置IO口

}

右边的*是强制类型转换,左边的*是声明指针。

        从位0开始看,MODE0和CNF0为一组,控制GPIO0,那具体是GPIOA还是B还是其他的,就要看配置是GPIOx_CRL的x是什么了,可能是A,B或者其他的。CPIOx_CRL只能控制0-7寄存器,所以被叫做端口配置低寄存器。GPIOx_CRL控制8-15寄存器,所以被称为端口配置高寄存器。

         配置PA0,为输出模式所以MODE0=11,CNF0=00,00为通用推挽输出模式,可以去搜一下GPIO的八种工作模式。所以现在GPIOA_CRL=3,这里注意如果此时我们还是按上面的(unit32_t*)去强制转换的话3可能机会变成0x00000003,这就不对了,因为其他口的模式全部都被配置为00了,正常情况下应该是要保留的。后续会写到,如何保留,这里还是先用地址赋值的方式配置一下PA0。

 这里可以看到GPIOA的基地址为0x40010800。同理:

#include

int main(void)

{

//1.开启对应GPIO的时钟

*(uint32_t*)(0x40021000+0x18)=4;

//2.配置对应IO口

*(uint32_t*)(0x40010800+0x00)=3;

//3.设置IO口

}

 这里把PA0设置0,其他PA设置为1,即15~0为1111 1111 1111 1110,转化为十六进制即为fffe。

#include

int main(void)

{

//1.开启对应GPIO的时钟

*(uint32_t*)(0x40021000+0x18)=4;

//2.配置对应IO口

*(uint32_t*)(0x40010800+0x00)=3;

//3.设置IO口

*(uint32_t*)(0x40010800+0x0C)=0xfffe;

}

使用ST的宏进行配置

这里以RCC(Reset Clock Controller)为例。

通过右击RCC,转到定义。

 RCC_TypeDef一看就是一个结构体。RCC_BASE和结构体我们都打开看一下。

 一层一层打开后发现,就是一个大的地址,一点点通过分块,定位到小的地址。

        在刚才启动GPIO口的时钟就是这样的。

PERIPH_BASE=0x40000000,

APB2PERIPH_BASE(PERIPH_BASE+0x20000),

RCC_BASE=(APB2PERIPH_BASE+0x1000);

这样一计算,我们就可以得到基地址=0x40000000+0x200000+0x1000=0x40021000

        结构体的第一个成员的地址和结构体的地址是一样的。所以CR的地址就是结构体的地址。而结构体又有一个特征,结构体的成员的地址是连续的。假设CR=0,则CFGR=4;明明是连续的为什么CFGR不等于1呢?因为这里是uint32_t,4个字节。所以CIR=8,以此类推APB2ENR=24。

//1.开启对应GPIO的时钟

*(uint32_t*)(0x40021000+0x18)=4;

        可是为什么这里是0x18呢?因为这个是十六进制。十进制的24转化成十六进制就是18。

所以我们可以这样写程序。

RCC->APB2ENR=4;

        两个代码是一模一样的。下面这种可读性也很好。

        同理GPIOA的配置也是一样的。

         所以代码就可以换成下面这种。

//1.开启对应GPIO的时钟

*(uint32_t*)(0x40021000+0x18)=4;

//2.配置对应IO口

*(uint32_t*)(0x40010800+0x00)=3;

//3.设置IO口

*(uint32_t*)(0x40010800+0x0C)=0xfffe;

RCC->APB2ENR=4;

GPIOA->CRL=3;

GPIOA->ODR=0xfffe;

只控制需要的位(位运算)

与(&),或(|)

RCC->APB2ENR = (RCC->APB2ENR+4);

RCC->APB2ENR |=4;

//1.开启对应GPIO的时钟

*(uint32_t*)(0x40021000+0x18)=4;

//2.配置对应IO口

*(uint32_t*)(0x40010800+0x00)=3;

//3.设置IO口

*(uint32_t*)(0x40010800+0x0C)=0xfffe;

RCC->APB2ENR=4;

GPIOA->CRL=3;

GPIOA->ODR=0xfffe;

RCC->APB2ENR |=4;

//这四步就等于GPIOA->CRL = 3;一步

GPIOA->CRL |=1;

GPIOA->CRL |=2;

GPIOA->CRL &=~4;

GPIOA->CRL &=~8;

//

GPIOA->ODR &=~1;

         上下两个代码是一样的。建议写下面那个,这样改参数的时候就不会改到其他东西。

1001=9

原式1010| 91011& 91000

        也就是说,如果你先把某一位写1 那就或(|)一下,想写0你就取反后与(&)一下。

        在以后可能会遇到更改GPIO的情况,建议先把你想编写的位置清零其余位置不变,再去编写,不仅可以让代码更加直观,也能减少错误,而且不用像上面那种一步要分为四步。

例如:

#define SDA_OUT() {GPIOB->CRL &= 0x0FFFFFFF; GPIOB->CRL |= 0x30000000;}

#define SDA_IN() {GPIOB->CRL &= 0x0FFFFFFF; GPIOB->CRL |= 0x40000000;}

        当GPIOB_CRL&0x0FFFFFFF时,因为与(&)必须都为 1才能是1,所以前面0的位置位15,14,13,12被全部清零了,然后再去通过或(|)更改CRL。

左移(<<),右移(>>)

RCC->APB2ENR |= 1<<2;

GPIOA->CRL |=1<<0;

GPIOA->CRL |=1<<1;

GPIOA->CRL &=~(1<<2);

GPIOA->CRL &=~(1<<3);

GPIOA->ODR &=~(1<<0);

这种就非常直观了,只需要对着操作手册,改哪就位移哪,1用(|),0用(&)。

        上面几种,已经非常方便了,但是如果不看数据手册,根本看不出来改了什么。所以就有更好的一种方式,不仅更改了配置,还能一样看出来改了什么。所以我们就可以用下面这个方法。

使用ST的宏进行位运算

RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

GPIOA->CRL |= GPIO_CRL_MODE0_0;

GPIOA->CRL |= GPIO_CRL_MODE0_1;

//也可以放一起GPIOA->CRL |=(GPIO_CRL_MODE0_0|GPIO_CRL_MODE0_1)

GPIOA->ODR &=~GPIO_CRL_CNF0_0;

GPIOA->ODR &=~GPIO_CRL_CNF0_1;

GPIOA->ODR &=~GPIO_ODR_ODR0;

 举个例子:

GPIOA->CRL |= GPIO_CRL_MODE0_0;

控制GPIOA_CRL的mode0中的0,(|)则表示将其置1。

我们转到定义看一下

可以看出方法4和方法3其实是一样的,只不过方法4是ST已经帮我们定义好了,我们直接用就可以了。 

精彩内容

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: