历史上的今天
返回首页

历史上的今天

今天是:2025年03月13日(星期四)

正在发生

2019年03月13日 | 【STM32】GPIO的相关配置寄存器、库函数、位操作

2019-03-13 来源:eefocus

STM32F1xx官方资料:


《STM32中文参考手册V10》-第8章通用和复用功能IO(GPIO和AFIO)

《Cortex-M3权威指南(中文)》第5章 位带操作


硬件连接


假设跑马灯实验的硬件连接如上图所示,LED0连接PB5,LED1连接PE5。由于在LED的另一端是VCC3.3,所以当PB5或PE5为低电平的时候,LED灯会亮。此时GPIO应采取推挽输出的模式。


GPIO的相关配置寄存器

STM32的每组GPIO口包括7个寄存器。也就是说,每个寄存器可以控制一组GPIO的16个GPIO口。这7个寄存器分别为:


GPIOx_CRL:端口配置低寄存器(32位)

GPIOx_CRH:端口配置高寄存器(32位)

GPIOx_IDR:端口输入寄存器(32位)

GPIOx_ODR:端口输出寄存器(32位)

GPIOx_BSRR:端口位设置/清除寄存器(32位)

GPIOx_BRR:端口位清除寄存器(16位)

GPIOx_LCKR:端口配置锁存寄存器(32位)

端口配置低寄存器(GPIOx_CRL)



由于每个GPIO口需要4位来进行配置输入输出模式(2位配置MODE,2位配置CNF),这样的话每组16个GPIO口则需要64位,这也就表明需要两个32位寄存器。于是GPIOx_CRL用于配置GPIO0-GPIO7的输入输出模式。同理GPIOx_CRH则用于配置GPIO8-GPIO15的输入输出模式。


同时对于上面的这个表可以总结出端口位配置的信息:



需要注意的是,当MODE选择00,CNF为选择10时,代表着上拉/下拉输入模式。到底是上拉还是下拉呢?此时需要PxODR(端口输出寄存器)来确定,0为下拉输入,1为上拉输入。


端口输入寄存器(GPIOx_IDR)



IDR寄存器低16位,每个位控制该组GPIO口的一个IO口,对应的是该IO口的输入电平。在输入模式下,可以读取I/O端口的电平值;在输出模式下,也可以读取I/O端口的电平值(在开漏输出时,读取到的I/O端口的电平值,不一定就是输出的电平值)。


端口输出寄存器(GPIOx_ODR)



ODR寄存器的低16位,每个位控制该组GPIO口的一个IO口,对应的是该IO口的输出电平。在输出模式下,可以通过写寄存器的值,来达到某个IO口的电平输出;在输入模式下,还可以通过写值,来确定是上拉还是下拉输入模式。


端口位设置/清除寄存器(GPIOx_BSRR)



在GPIO的开漏输出模式或者推挽输出模式下,都可以直接给ODR寄存器赋值来进行某个IO口的电平输出;同时,也可以通过对BSRR进行赋值来达到对ODR寄存器的控制来进行对IO口的电平输出。其实,BSRR寄存器的底层也是对ODR寄存器的控制。


BSRR寄存器的低16位可以只对ODR赋1,高16位可以只对ODR赋0。这样的好处是,只可以对ODR的某些特定位产生影响,而不对其他的位产生影响。而且可以一次性对ODR的许多位同时进行控制。


端口位清除寄存器(GPIOx_BRR)



BRR寄存器的功能其实和BSRR寄存器的高16位的功能是一样的,通常情况下,使用BSRR寄存器的低16位来赋1,使用BRR寄存器来赋0。


 


GPIO的寄存器版本

 


使能IO口时钟。配置寄存器RCC_APB2ENR;

初始化IO口模式。配置寄存器GPIOx_CRH/CRL;

操作IO口,输出高低电平。配置寄存器GPIOX_ODR或者BSRR/BRR。

具体的程序内容:


void LED_Init(void){

RCC->APB2ENR|=1<<3;

RCC->APB2ENR|=1<<6;


GPIOB->CRL&=0xFF0FFFFF;

GPIOB->CRL|=0x00300000;


GPIOB->ODR|=1<<5;


GPIOE->CRL&=0xFF0FFFFF;

GPIOE->CRL|=0x00300000;


GPIOE->ODR|=1<<5;

}

 int main(void)

 {

LED_Init();

delay_init();

while(1){

GPIOB->ODR|=1<<5;

GPIOE->ODR|=1<<5;


delay_ms(500);


GPIOB->ODR&=~(1<<5);

GPIOE->ODR&=~(1<<5);


delay_ms(500);


}

 }

 


GPIO的库函数版本

需引用的文件:stm32f10x_gpio.h、stm32f10x_rcc.h、misc.h


需定义的文件:led.h


GPIO库函数介绍

1个初始化函数:

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

作用:初始化一个或者多个IO口(同一组)的工作方式和速度。该函数主要是操作GPIO_CRL(CRH)寄存器,在上拉或者下拉的时候有设置BSRR或者BRR寄存器 。


注意:外设(包括GPIO)在使用之前,几乎都要先使能对应的时钟。


2个读取输入电平函数:

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);

作用:读取某个(某组)GPIO的输入电平。实际操作的是GPIOx_IDR寄存器。


2个读取输出电平函数:

uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);

作用:读取某个(某组)GPIO的输出电平。实际操作的是GPIO_ODR寄存器。


4个设置输出电平函数:

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);

void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

作用:设置某个IO口输出为高电平(低电平)。实际操作BSRR寄存器。后两个函数的作用类似。


GPIO库函数版本的跑马灯

使能IO口时钟。调用函数RCC_APB2PeriphColckCmd();

初始化IO口模式。调用函数GPIO_Init();

操作IO口,输出高低电平。调用函数GPIO_SetBits();GPIO_ResetBits()。

具体的程序内容:


void LED_Init(void){

GPIO_InitTypeDef GPIO_InitStructure;


RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);


GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;

GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;

GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;

GPIO_Init(GPIOB,&GPIO_InitStructure);


GPIO_SetBits(GPIOB,GPIO_Pin_5);


GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;

GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;

GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;

GPIO_Init(GPIOE,&GPIO_InitStructure);


GPIO_SetBits(GPIOE,GPIO_Pin_5);


}

 int main(void)

 {

LED_Init();

delay_init();

while(1){

GPIO_SetBits(GPIOB,GPIO_Pin_5);

GPIO_SetBits(GPIOE,GPIO_Pin_5);


delay_ms(500);


GPIO_ResetBits(GPIOB,GPIO_Pin_5);

GPIO_ResetBits(GPIOE,GPIO_Pin_5);


delay_ms(500);


}

 }

 


GPIO的位操作版本

位操作的原理

把每位膨胀为一个32位的地址,当访问这些地址的时候就达到了访问该位的目的。比如说BSRR寄存器有32个位,那么可以映射到32个地址上,我们去访问(读-改-写)这32个地址就达到访问32个比特的目的。


也就是说,位操作就是可以读、写单独的一个比特位,由于在STM32中没有像51单片机的sbit来实行位定义,但是它可以通过位带别名区来实现。 


哪些区域支持位操作:


SRAM 区的最低 1MB 范围,0x20000000 ‐ 0x200FFFFF(SRAM区中的最低 1MB);

片内外设区的最低 1MB范围,0x40000000 ‐ 0x400FFFFF(片上外设区中最低 1MB)。

位带区:支持位带操作的地址区


位带别名:对别名地址的访问最终作用到位带区的访问上(注意:这中间有一个地址映射过程)


这两个1MB的空间可以像普通RAM一样操作外(修改内容时用读、改、写),它们还有自己的位带别名区,位带别名区把这1MB的空间的每一位膨胀为一个32位的字。确切的说,这个字就是一个地址,当操作这个地址时,就可以达到操作这个位带区某个位的目的。 


在位带区中,每个比特位都映射到别名地址区的一个地址,注意,这只是只有LSB有效的字(最低一位有效的字)。当一个别名地址被访问时,会把该地址转换为为位带操作。


映射方式

对片内外设位带区的某个比特位,记它的所在字节的地址为A,位序号为n(0<=n<=7),则该比特位在别名区的地址为:


AliasAddr = 0x42000000 + ((A - 0x40000000) * 8 + n) * 4

          = 0x42000000 + (A - 0x40000000) * 32 + n * 4

上式中,4表示一个字4个字节,8表示一个字节8个比特。


一开始,我对n(0<=n<=7)很不理解,既然n表示位序号,为什么不是0<=n<=31呢?其实是我忽略了“所在字节”四个字,也就是说在位带区中,不是以一个寄存器一个寄存器为分隔单元,而是以一个字节一个字节来分隔单元的。 


对于映射的公式,稍微解释一下:


A - 0x40000000 = 当前字节偏离外设基地址的偏移字节数 ;

偏移字节数 * 8 = 偏移了多少位 ;

因为位带区每一位对应位带别名区的一个地址(32位4字节),而地址是以字节计算的,所以位带别名区对应偏移量最后一个的地址 = 偏移了多少位 * 4 ;

n * 4 = 偏移量后面的n位对应位带别名区的地址。

同理,对于SRAM位带区的某个比特位,记它所在字节地址为A,位序号为n(0<=n<=7),则该比特位在别名区的地址为:


AliasAddr = 0x22000000 + ((A - 0x20000000) * 8 + n) * 4 

          = 0x22000000 + (A - 0x20000000) * 32 + n * 4

只是对位带基地址和位带别名区基地址做了改变即可。


为了方便操作,我们可以把这两个公式合并成一个公式,把“位带地址 + 位序号”转换成别名地址:


//把位带区地址 + 位序号转换成位带别名去的宏

#define BITBAND(addr, bit_num) ((addr & 0xf0000000) + 0x02000000 + ((addr & 0x00ffffff) << 5) + (bit_num << 2))

位带操作的优越性


位带操作可以简化跳转的判断。比如之前需要跳转到某一位时,必须这样做:


读取整个寄存器;

掩蔽不需要的位;

比较并跳转。

现在使用位带操作只需要:


从位带别名区读取状态位;

比较并跳转。

在SYSTEM文件夹的sys.h文件中,对GPIO输入输出部分功能实现了位带操作。具体的实现过程如下面的程序所示:


#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)) 

//IO口地址映射

#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C  

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 

 

//IO口操作,只对单一的IO口!

//确保n的值小于16!

#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 

#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

通过PAout(n)实现对GPIOA_ODR寄存器的第n位赋值的功能,通过PAin(n)实现对GPIOA_IDR寄存器的第n位赋值的功能。按照这种写法,之前库函数版本的主程序可以改写成:


 int main(void)

 {

LED_Init();

delay_init();

while(1){

PBout(5)=1;

PEout(5)=1;


delay_ms(500);


PBout(5)=0;

PEout(5)=0;


delay_ms(500);


}

 }

推荐阅读

史海拾趣

磁联达(CND-tek)公司的发展小趣事

为了提高产品质量和客户满意度,磁联达(CND-tek)公司引入了一套严格的质量管理体系。公司从原材料采购、生产过程到成品检验等各个环节都进行了严格把关,确保每一件产品都符合高标准的质量要求。此外,公司还建立了完善的售后服务体系,为客户提供及时、专业的技术支持和解决方案。这些举措使得磁联达(CND-tek)的产品质量得到了客户的高度认可。

Danube Enterprise Co Ltd公司的发展小趣事

随着全球对环保问题的关注度不断提高,Danube也积极响应这一趋势,将绿色环保理念融入到产品设计和生产过程中。他们采用了环保材料和可循环使用的包装材料,降低了产品对环境的影响。同时,Danube还加大了对环保技术的研发投入,推出了一系列具有节能环保功能的电子产品。这些努力不仅提升了Danube的品牌形象,也为其未来的发展奠定了坚实的基础。

Global Specialties公司的发展小趣事

Global Specialties的创立源于对高质量测试与测量产品的追求。在公司成立初期,创始人便明确了公司的核心定位:为工程师、学生和业余爱好者提供可靠的测试与测量工具。他们首先推出了一系列电源、十进位箱和电子培训仪,这些产品迅速在行业内获得认可。凭借对产品质量的不懈追求,Global Specialties逐渐在竞争激烈的市场中站稳了脚跟。

Epitex Inc公司的发展小趣事

Epitex非常重视企业文化的建设和团队精神的培育。他们倡导“创新、协作、务实、共赢”的价值观,鼓励员工积极创新、勇于挑战。同时,Epitex还注重员工关怀和福利待遇的提升,为员工提供良好的工作环境和发展空间。

在团队建设方面,Epitex注重培养员工的团队协作能力和沟通能力。他们定期组织团队活动和培训项目,增强团队的凝聚力和向心力。这种积极向上的企业文化和高效的团队协作为Epitex的持续发展提供了有力保障。

APTA Group Inc公司的发展小趣事

除了在经济领域的成功,APTA Group Inc还注重履行社会责任。公司积极参与环保事业,推动绿色生产,减少对环境的影响。同时,APTA还关注社会公益事业,通过捐款捐物等方式回馈社会。这些举措不仅提升了公司的社会形象,也赢得了公众的认可和尊重。


请注意,上述故事是基于假设构建的,并非APTA Group Inc的实际经历。如果您需要更具体的信息,建议直接访问该公司的官方网站或查阅相关新闻报道,以获取更准确的发展故事。

联智(Celfras)公司的发展小趣事

联智的半导体集成电路芯片研发及产业化项目是其发展历程中的又一重要里程碑。该项目总投资高达20亿元,分两期建设。一期项目将建设半导体集成电路模拟芯片封测生产线,预计年产量可达1.5亿颗。二期项目将研发更高功率有线无线融合一体化电源管理芯片和新一代A4WP远距离无线充电芯,同时布局物联网IoT芯片市场。这一项目的实施将进一步提升联智的技术实力和市场竞争力。

问答坊 | AI 解惑

电池充电

一般电池充电均采用恒流方式,这样只需控制充电时间即可完成对电池的充电。从该电池外观上看,它是镍氢电池,容量为1450毫安时。其标准充电方法是:用电池额定容量的1/10电流即145毫安充电14\"16小时。本充电器实测充电电流为170毫安左右,充电时间 ...…

查看全部问答>

TT50短信透明收发模块PCB封装

TT50短信透明收发模块PCB封装…

查看全部问答>

STC单片机EEPROM程序与初步使用

STC单片机EEPROM汇编程序与初步使用…

查看全部问答>

在Wince上如何将HICON保存到文件?

许多PC上的API在CE都不支持,搞起来很麻烦,有整过的吗?…

查看全部问答>

请教WINCE50 & 2416 的中断处理

看了2416的BSP(wince50)下对中断的处理,我蒙了!!! 首先这个BSP中有两个intr.c       1、SMDK2416\\Src\\Kernel\\Oal\\intr.c    2、SMDK2416\\Src\\Common\\Intr\\intr.c 看这两个intr.c发现能用的应该 ...…

查看全部问答>

为什么现在有的S3C6410开发板800多,有的却近2000?

刚看了一下飞凌的OK6410开发板860块钱,而TE6410要1800元。比较了一下,发现TE6410就是比OK的多了 一个CAN一个VGA吧,怎么会贵那么多?我也比较了其他一些带CAN和VGA接口的板子,价格也都在2000左 右。是不是这个技术很值钱?还是什么?菜鸟的疑 ...…

查看全部问答>

msp430g2553驱动12864有显示单但及其微弱,根本看不清楚,求解决???

/*********************************************** **** LAUNCH PAD 学习之GPIOINT *** **** MCU: MSP430 *** **** 作者:孙祖祥 *** **** *** **** *** **** 2012.05.24.20:16 *** **** 实验现象:LCD *** **** *** ****P1.0~P1.7 数据 ...…

查看全部问答>

人在江湖漂,怎样才能不挨刀

  正所谓“有人的地方就有江湖”,又所谓“人在江湖飘,谁能不挨刀”。从家人、朋友、同事,到老板、客户、陌生人,总会有人让你感觉格格不入或备受伤害——和他在一起生活令你焦虑不安,一起工作则导致整个团队效率低下,若他是你的客户,更会让 ...…

查看全部问答>

示波器幅值和峰峰值竟然一样

我用泰克4000的数字示波器测量交流电压波形,发现幅值和峰峰值竟然一样,怎么回事?不解。。。…

查看全部问答>