历史上的今天
返回首页

历史上的今天

今天是:2024年09月19日(星期四)

正在发生

2019年09月19日 | 第13章 GPIO-位带操作—零死角玩转STM32-F429系列

2019-09-19 来源:eefocus

本章参考资料:《STM32F4xx 中文参考手册》存储器和总线构架章节、GPIO章节,《Cortex®-M4内核编程手册》2.2.5 Bit-banding。学习本章时,配套这些参考资料学习效果会更佳。


13.1 位带简介

位操作就是可以单独的对一个比特位读和写,这个在51单片机中非常常见。51单片机中通过关键字sbit来实现位定义,F429中没有这样的关键字,而是通过访问位带别名区来实现。


在F429中,有两个地方实现了位带,一个是SRAM区的最低1MB空间,另一个是外设区最低1MB空间。这两个1MB的空间除了可以像正常的RAM一样操作外,他们还有自己的位带别名区,位带别名区把这1MB的空间的每一个位膨胀成一个32位的字,当访问位带别名区的这些字时,就可以达到访问位带区某个比特位的目的。

图 131 F429位带地址


13.1.1 外设位带区

外设位带区的地址为:0X40000000~0X400F0000,大小为1MB,这1MB的大小包含了APB1/2和AHB1上所以外设的寄存器,AHB2/3总线上的寄存器没有包括。AHB2总线上的外设地址范围为:0X50000000~0X50060BFF,AHB3总线上的外设地址范围为:0XA0000000~0XA0000FFF。外设位带区经过膨胀后的位带别名区地址为:0X42000000~0X43FFFFFF,这部分地址空间为保留地址,没有跟任何的外设地址重合。


13.1.2 SRAM位带区

SRAM的位带区的地址为:0X2000 0000~X200F 0000,大小为1MB,经过膨胀后的位带别名区地址为:0X2200 0000~0X23FF FFFF,大小为32MB。操作SRAM的比特位这个用得很少。


13.1.3 位带区和位带别名区地址转换

位带区的一个比特位经过膨胀之后,虽然变大到4个字节,但是还是LSB才有效。有人会问这不是浪费空间吗,要知道F429的系统总线是32位的,按照4个字节访问的时候是最快的,所以膨胀成4个字节来访问是最高效的。


我们可以通过指针的形式访问位带别名区地址从而达到操作位带区比特位的效果。那这两个地址直接如何转换,我们简单介绍一下。


1.    外设位带别名区地址

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


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

0X42000000是外设位带别名区的起始地址,0x40000000是外设位带区的起始地址,(A-0x40000000)表示该比特前面有多少个字节,一个字节有8位,所以*8,一个位膨胀后是4个字节,所以*4,n表示该比特在A地址的序号,因为一个位经过膨胀后是四个字节,所以也*4。


2.    SRAM位带别名区地址

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


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

公式分析同上。


3.    统一公式

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


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

 2 #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr &

   0x000FFFFF)<<5)+(bitnum<<2))

addr & 0xF0000000是为了区别SRAM还是外设,实际效果就是取出4或者2,如果是外设,则取出的是4,+0X02000000之后就等于0X42000000,0X42000000是外设别名区的起始地址。如果是SRAM,则取出的是2,+0X02000000之后就等于0X22000000,0X22000000是SRAM别名区的起始地址。


addr & 0x00FFFFFF 屏蔽了高三位,相当于减去0X20000000或者0X40000000,但是为什么是屏蔽高三位?因为外设的最高地址是:0X2010 0000,跟起始地址0X20000000相减的时候,总是低5位才有效,所以干脆就把高三位屏蔽掉来达到减去起始地址的效果,具体屏蔽掉多少位跟最高地址有关。SRAM同理分析即可。<<5相当于*8*4,<<2相当于*4,这两个我们在上面分析过。


最后我们就可以通过指针的形式操作这些位带别名区地址,最终实现位带区的比特位操作。


 1 // 把一个地址转换成一个指针

 2 #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))

 3 

 4 // 把位带别名区地址转换成指针

 5 #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))

13.2 GPIO位带操作

外设的位带区,覆盖了全部的片上外设的寄存器,我们可以通过宏为每个寄存器的位都定义一个位带别名地址,从而实现位操作。但这个在实际项目中不是很现实,也很少人会这么做,我们在这里仅仅演示下GPIO中ODR和IDR这两个寄存器的位操作。


从手册中我们可以知道ODR和IDR这两个寄存器对应GPIO基址的偏移是20和16,我们先实现这两个寄存器的地址映射,其中GPIOx_BASE在库函数里面有定义。


1.    GPIO 寄存器映射

代码 9 GPIO ODR 和 IDR 寄存器映射


1 // GPIO ODR 和 IDR 寄存器地址映射


 2 #define GPIOA_ODR_Addr    (GPIOA_BASE+20)

 3 #define GPIOB_ODR_Addr    (GPIOB_BASE+20)

 4 #define GPIOC_ODR_Addr    (GPIOC_BASE+20)

 5 #define GPIOD_ODR_Addr    (GPIOD_BASE+20)

 6 #define GPIOE_ODR_Addr    (GPIOE_BASE+20)

 7 #define GPIOF_ODR_Addr    (GPIOF_BASE+20)

 8 #define GPIOG_ODR_Addr    (GPIOG_BASE+20)

 9 #define GPIOH_ODR_Addr    (GPIOH_BASE+20)

10 #define GPIOI_ODR_Addr    (GPIOI_BASE+20)

11 #define GPIOJ_ODR_Addr    (GPIOJ_BASE+20)

12 #define GPIOK_ODR_Addr    (GPIOK_BASE+20)

13 

14 #define GPIOA_IDR_Addr    (GPIOA_BASE+16)

15 #define GPIOB_IDR_Addr    (GPIOB_BASE+16)

16 #define GPIOC_IDR_Addr    (GPIOC_BASE+16)

17 #define GPIOD_IDR_Addr    (GPIOD_BASE+16)

18 #define GPIOE_IDR_Addr    (GPIOE_BASE+16)

19 #define GPIOF_IDR_Addr    (GPIOF_BASE+16)

20 #define GPIOG_IDR_Addr    (GPIOG_BASE+16)

21 #define GPIOH_IDR_Addr    (GPIOH_BASE+16)

22 #define GPIOI_IDR_Addr    (GPIOI_BASE+16)

23 #define GPIOJ_IDR_Addr    (GPIOJ_BASE+16)

24 #define GPIOK_IDR_Addr    (GPIOK_BASE+16)

现在我们就可以用位操作的方法来控制GPIO的输入和输出了,其中宏参数n表示具体是哪一个IO口,n(0,1,2...16)。这里面包含了端口A~K ,并不是每个单片机型号都有这么多端口,使用这部分代码时,要查看你的单片机型号,如果是176pin的则最多只能使用到I端口。


2.    GPIO位操作

代码 10 GPIO 输入输出位操作


 1 // 单独操作 GPIO的某一个IO口,n(0,1,2...16),n表示具体是哪一个IO口

 2 #define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)

//输出

 3 #define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)

//输入

 4 

 5 #define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)

//输出

 6 #define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)

//输入

 7 

 8 #define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)

//输出

 9 #define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)

//输入

10 

11 #define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)

//输出

12 #define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)

//输入

13 

14 #define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)

//输出

15 #define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)

//输入

16 

17 #define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)

//输出

18 #define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)

//输入

19 

20 #define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)

//输出

21 #define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)

//输入

22 

23 #define PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n)

//输出

24 #define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)

//输入

25 

26 #define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr,n)

//输出

27 #define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr,n)

//输入

28 

29 #define PJout(n)   BIT_ADDR(GPIOJ_ODR_Addr,n)

//输出

30 #define PJin(n)    BIT_ADDR(GPIOJ_IDR_Addr,n)

//输入

31 

32 #define PKout(n)   BIT_ADDR(GPIOK_ODR_Addr,n)

//输出

33 #define PKin(n)    BIT_ADDR(GPIOK_IDR_Addr,n)

//输入

 


3.    主函数

该工程我们直接从LED-库函数操作移植过来,有关LED GPIO 初始化和软件延时等函数我们直接用,修改的是控制GPIO输出的部分改成了位操作。该实验我们让相应的IO口输出高低电平来控制LED的亮灭,负逻辑点亮。具体使用哪一个IO和点亮方式由硬件平台决定。


代码 11 main 函数


 1 int main(void)

 2 {

 3 

/* LED 端口初始化 */

 4     LED_GPIO_Config();

 5 

 6 

while (1) {

 7 

// PH10 = 0,点亮LED

 8         PHout(10)= 0;

 9         SOFT_Delay(0x0FFFFF);

10 

11 

// PH10 = 1,熄灭LED

12         PHout(10)= 1;

13         SOFT_Delay(0x0FFFFF);

14     }

15 

16 }

推荐阅读

史海拾趣

ELINA INDEK公司的发展小趣事

作为一家领先的电子公司,因美纳深知自己的社会责任。公司积极参与各种公益活动,推动基因测序技术在医疗、环保等领域的应用。同时,因美纳还注重可持续发展,通过采用环保材料、优化生产流程等方式降低对环境的影响。这种积极履行社会责任和推动可持续发展的做法赢得了社会各界的广泛赞誉。

EREM公司的发展小趣事

EREM公司起源于1960年代的瑞士日内瓦,由一群热衷于精密工具制造的工程师创立。在当时,电子行业正逐渐兴起,对高精度工具的需求日益增长。EREM的创始人看到了这一市场机遇,决定专注于生产高精度镊子和钳子,以满足电子行业的需求。他们凭借精湛的工艺和不懈的努力,逐渐在行业内建立了良好的声誉。

FCI connector [富加宜连接器]公司的发展小趣事

自FCI成立以来,公司凭借其专业的设计、精密的制造工艺和创新精神,迅速在全球连接器市场上崭露头角。通过不断的技术创新和产品升级,FCI逐渐成为了全球领先的连接器制造商之一。其产品线涵盖了通讯、电源、医疗等多个领域,为全球众多知名品牌提供了优质的连接器解决方案。

Collins Electronics Corp公司的发展小趣事

在电子行业竞争日益激烈的情况下,Collins Electronics Corp面临着巨大的挑战。为了保持竞争优势,公司加大了研发投入,不断优化产品性能。同时,他们还加强了对市场趋势的研究,以便及时调整产品策略。在一次行业技术革新中,公司凭借敏锐的市场洞察力和强大的研发能力,成功推出了一款符合市场需求的新产品,赢得了市场份额。

思瑞浦微电子科技(3PEAK INCORPORATED)公司的发展小趣事

随着公司规模的扩大,Collins Electronics Corp开始将目光投向国际市场。他们积极参加国际电子展会,与世界各地的潜在客户和合作伙伴交流。通过与不同国家的企业合作,公司成功打入了国际市场,产品销量大幅提升。这一过程中,他们不仅学习了国际先进的电子技术,还积累了丰富的跨国运营经验。

Alorium Technology公司的发展小趣事

随着科技的飞速发展,Collins Electronics Corp意识到技术创新是企业持续发展的关键。他们投入大量资源进行研发,推出了一系列具有创新技术的电子产品。其中,他们开发的一款新型高频无线电收发器,因其传输速度快、信号稳定而广受好评。这项技术不仅为公司带来了可观的利润,还推动了整个电子行业的技术进步。

问答坊 | AI 解惑

单片机C语言和汇编语言混合编程实例详解2

本帖最后由 paulhyde 于 2014-9-15 09:23 编辑 单片机C语言和汇编语言混合编程实例详解2 [ 本帖最后由 open82977352 于 2009-7-10 10:14 编辑 ]  …

查看全部问答>

PCI驱动问怪异题请教

我们开发了一块pci9052芯片的PCI接口板,出现怪异问题,请帮忙分析下! 基于PDC1000开发的接口板(使用PCI9052)。 数据采集有两部分,第一部分较少数据读写;第二部分大量数据频繁读写(80ms定时器,每80ms有2048次*3*12位/次,时钟由2M晶振产 ...…

查看全部问答>

为什么我的CEdit不显示啊~????

这几天刚刚装好EVC,连接板子上搞了半天,现在终于可以在板子上运行调试了,可是马上又有问题来了,我新开了个对话框,里面放了几个STATIC和几个Edit,刚刚开始的时候用中文的,Static里面的字是乱的,Edit不显示,改成英文的,Static那几个也可显示一个了``` ...…

查看全部问答>

时钟问题,讨论,请教!

用到了5个时钟,19.2k、40k、80k、480k、960k、dc综合之后(用的umc库)做了一下后仿40k跟80k时钟总是没有,一直保持为0;我单独把40k和80k拿出来后仿时钟都能出来,可一到总模块就不行了,这是为什么啊??我试着用smic库做了以下综合和后仿没有任 ...…

查看全部问答>

lm3s811小问题

谁知道 那个白色的 长条的 是什么?是液晶吗 ,为什么是粘上去了 ,今天被同学拿下来了,有知道 的告诉我一声,谢谢了 …

查看全部问答>

在单位遇到一个2B

刚才在写项目文档,居然我对面的一个2B突然站起来对我说让我敲键盘的声音小一点,说影响到他睡觉了,公司有规定12点到13点之间可以休息,但是都13.20了它还在睡觉,这个先不说,凭什么就让我打字的声音小点呀,我在工作,我做的是正当事情,难道正 ...…

查看全部问答>

ADI实验室电路DIY项目指南之传感器类应用1

报名参与:『ADI实验室电路DIY大赛』正式启动!https://bbs.eeworld.com.cn/thread-293726-1-1.html   其它DIY项目指南:https://bbs.eeworld.com.cn/thread-293869-1-1.html; https://bbs.eeworld.com.cn/thread-294114-1-1.html   ...…

查看全部问答>

方波放大电路有过冲,不知如何解决

想把一个输入Vpp=500mV,频率等于200K的方波放大9倍,两级放大,但是出现了过冲(应该算是振铃吧),不知道该如何解决,求大神们讲解下过冲的由来及解决方案,不胜感激…

查看全部问答>