STM32初学连载(寄存器版)——位段操作

  • JESSE7
  • LV4工程师
  • |      2017-07-23 19:15:27
  • 浏览量 810
  • 回复:0
前几篇连载中,我们对IO口的读写都是直接操作相应的寄存器,不免会显得麻烦,有没有简单一点的方法操控它呢。有,我们可以使用位段操作。现在诸多开发板的例程里都会加入位段操作,但是大部分都是展示了一个实现过程。我将原子的sys.h文件copy到了工程目录下,为了表示对原作者的尊重,我保留了原子的标识,只是将一些用不到的东西以注释的形式进行屏蔽,并且添加了一些原本没有的东西。原子在STM32F4开发指南中也对其中内容进行了简单介绍 这些文档虽然都展示了位段操作的过程,但是对其中原理还是一脸懵逼。这种时候我们就要借助一些更权威的工具书了,在《ARM Cortex-M3Cortex-M4权威指南》第6章第7小节对位段操作进行了比较详细的介绍。看到这,什么是位段呢。STM32中对读写的操作都是以字或半字(stm32中一个字是四个字节)为单位的,如果想要对一个位进行读写,就需要像前面操作的一样,进行移位。为了简化操作,将位进行膨胀,膨胀到字的层面,不就可以直接进行读写了吗?这就避免了移位操作,但是地址膨胀带来的是地址空间上的占用,所以这种膨胀非常有限,仅仅在SRAM和片上外设区有各有1M的位段区和与之对应的32M别名区。现在我们知道,要想操作位段区的某一位,就要去找对应的别名区地址,那别名区的地址怎么算呢,文中有公式,但是如何去理解呢。在这我谈谈我的理解。位段区和别名区都是连续的地址空间,都有各自的开始地址,这个开始地址肯定是要先去掉的,膨胀的是空间,跟它的起始地址是无关的。将一个位膨胀为一个字,扩大了32倍,这就可以将位段区的每一位和别名区的每个字一一对应。假如我们要计算字节A中第B位在别名区的地址,那就应该A-位段区起始地址,再将空间膨胀,即(A-起始地址)*32,这就算到了字节A在别名区的地址偏移,这时候一个位对应的是一个字(4字节),所以B相对与A的地址偏移应该是B*4,即算得B在别名区的地址偏移为(A-起始地址)*32+B*4;这时候再加上别名区的起始地址便是B在别名区的地址了。这和文档或是程序中的算法是一致的,我们也可以算算看,((addr & 0xF0000000)+0x2000000+((addr&0xFFFFF)<<5)+(bitnum<<2))((addr & 0xF0000000)这是取得了位段区的起始地址,((addr & 0xF0000000)+0x2000000这便是取得别名区的起始地址(位段区与相应的别名区的高4位地址是一致的,只是别名区起始地址相对位段区起始地址偏移了0x2000000)。((addr &0xFFFFF)<<5)这一步便是将空间进行膨胀,因为位段区的大小是1M,所以位段区的地址范围便是0x00000~0xFFFFF(addr &0xFFFFF)这是获得字节A(按上面的例子加入)的偏移地址,左移了5位,就相当于乘了32倍;(bitnum<<2)这是获取位B在别名区相对于A的偏移,左移2位,便是相当于乘了4倍。不知道我讲清楚了没有,也不知道讲的对不对。 讲了实现原理,我们就可以来看看程序如何实现


void led_active(uint8_t led_index, uint8_t led_status)

{

    if((led_index&LED1)==LED1)

    {

        if(led_status == LED_ON)   GPIOG->ODR &=~(0x01<<6); //输出低电平

        else              GPIOG->ODR |=  0x01<<6;    //输出高电平

    }

    if((led_index&LED2)==LED2)

    {

        if(led_status == LED_ON)   GPIOD->ODR &=~(0x01<<4); //输出低电平

        else              GPIOD->ODR |=  0x01<<4;    //输出高电平

    }

    if((led_index&LED3)==LED3)

    {

        if(led_status == LED_ON)   GPIOD->ODR &=~(0x01<<5); //输出低电平

        else              GPIOD->ODR |=  0x01<<5;    //输出高电平

    }

    if((led_index&LED4)==LED4)

    {

        if(led_status == LED_ON)   GPIOK->ODR &=~(0x01<<3); //输出低电平

        else              GPIOK->ODR |=  0x01<<3;    //输出高电平

    }

}
这是直接操作相应位的方式
#define PLED1    PGout(6)

#define PLED2    PDout(4)

#define PLED3    PDout(5)

#define PLED4       PKout(3)

void sys_led_active(uint8_t led_index, uint8_t led_status)

{

    if((led_index&LED1)==LED1) 

    {

        if(led_status == LED_ON)    PLED1 = 0;

        else                PLED1 = 1;

    }

    if((led_index&LED2)==LED2)

    {

        if(led_status == LED_ON)    PLED2 = 0;

        else                PLED2 = 1;

    }

    if((led_index&LED3)==LED3)

    {

        if(led_status == LED_ON)    PLED3 = 0;

        else               PLED3 = 1;

    }

    if((led_index&LED4)==LED4)

    {

        if(led_status == LED_ON)    PLED4 = 0;

        else                PLED4 = 1;

    }

}
这是应用了位段的方式,直接对相应寄存器进行赋值即可。 也可用位段的方式对当前状态的读取,但是这里需要注意的是,将一个位膨胀成一个字之后,它所能表示的就不单单只是‘0’和‘1’(尽管它的作用还是用于表示‘0’和‘1’),所以如果想通过读取别名区的寄存器值为‘1’还是不为‘1’来判断相应位的状态,这种做法是非常危险的,别名区的里的寄存器非常可能并不是存储的‘1’,而是别的非零数,所以这里要用判‘0’的方式。
  • 0
  • 收藏
  • 举报
  • 分享
我来回复

登录后可评论,请 登录注册

所有回答 数量:0
x
收藏成功!点击 我的收藏 查看收藏的全部帖子