BME学习心得(BME Study Notes)

Document created by Weihua Liang Employee on Dec 4, 2013Last modified by Weihua Liang Employee on Dec 6, 2013
Version 1Show Document
  • View in full screen mode

作者 Sam Wang & River Liang

RISC架构的MCU中,通常是加载-存储(Load and Store)的操作机制,而这种方式不能提供传统8bit架构MCU的直接位操作内存和地址空间。为此飞思卡尔在M0+系列MCU上集成了BMEBit Manipulation Engine)位操作引擎功能,例如KEKL系列里都带有BME,它从硬件上提供了对外设地址空间用读-修改-写的操作方式来实现位操作。

        使用BME能够降低总线的占用率和CPU执行时间,这些效果都能够降低系统的功耗。另外使用相比于用C语言实现相同功能的代码,使用BME能够更节省代码空间。这些可以参照

        BME功能支持访问从0x4000_0000开始的,大小为512K的地址空间,并把它映射成从0x4400_00000x5fff_ffff的内存空间。

        好了,长话短说。下面转入正题,我们应该如何使用BME来进行位操作,并达到节省代码空间、提高效率的效果。

一、写操作方式,对定义内容用写的方式来实现与、或、异或、位域插入功能

1BME&操作可以一次对IO的几个bit0     //     0x21<<26 | addr (A0~A19)

//GPIOA_PDOR   地址为   400F_F000

#define GPIOA_AND *((volatile unsigned char *) (0x44000000+0xFF000))

: GPIOA_AND=0xaa;

#define GPIOA_AND_I *((volatile unsigned int *) (0x44000000+0xFF000))

: GPIOA_AND_I=0x55aa;

实际上命令是将400f_f000的内容与目标数进行&运算。修改volatile unsigned char, volatile unsigned int, volatile unsigned long来实现BME的所谓8,16,32位操作.下面命令相同。

 

2BME|操作可以一次对IO的几个bit1     //       0x22<<26 | addr (A0~A19)

#define GPIOA_OR *((volatile unsigned char *) (0x48000000+0xFF000))

: GPIOA_OR=0xaa;

#define GPIOA_OR_I *((volatile unsigned int *) (0x48000000+0xFF000))

: GPIOA_OR_I=0x55aa;

实际上命令是将400f_f000的内容与目标数进行|运算。

 

3 BME^操作          //     0x23<<26 | addr (A0~A19)

#define GPIOA_XOR *((volatile unsigned char *) (0x4C000000+0xFF000))

例:GPIOA_XOR=0xaa;

#define GPIOA_XOR_I *((volatile unsigned int *) (0x4C000000+0xFF000))

: GPIOA_XOR_=0x55aa;

上面3个例子讲解了一般的与、或、异或等常用操作,下面来点复杂一点的。

                                                                                         

4 BME的位域插入操作BFIBit Field Insert//   (5<<28) | (bit<<23) | (width<<19) | addr (A0~A18)

#define BME_BFI_ADDR (ADDR, BIT, WIDTH)   (*(volatile uint32_t *) (((uint32_t) ADDR) | (1<<28) | (BIT<<23) | (WIDTH<<19)))

在这里bit是插入的位置,表示被操作目标的最低位开始被操作,Width这里是插入的数据长度

例:BME_BFI_ADDR(&ADC0_CFG1, 0x05, 0x01) = 0x40;

结果是将寄存器ADC0_CFG1bit5开始,用0x40bit5来替换ADC0_CFG1bit50x40bit6来替换ADC0_CFG1bit6,调用该命令后,寄存器ADC0_CFG1_ADIV = 2

相当于执行了mask = ((1 << (w+1)) - 1) << b;                          //等一系列位操作。

                            (ADC0_CFG1 & ~mask) | (0x40 & mask);

使用BFI功能需要注意的是,操作地址是A0A18GPIO寄存器的A0A19是从FF000开始,因此会有1bit 的地址冲突。为此,在使用BFI操作GPIO的寄存器时,使用的是内存映射出来的地址空间,此时GPIO的起始地址将为F000,如果还使用原来的地址,命令将会无效。之前提到的ANDORXOR操作,对于GPIO地址空间在FF000还是F000都适用

#define BME_BFI_GPIOA (BIT, WIDTH)        (*(volatile uint32_t *) ((uint32_t) (5<<28) | (BIT<<23) | (WIDTH<<19) | 0xF000))

例:BME_BFI_GPIOA(0,3) = 0x0a;

结果是GPIO_PDORbit0开始,一共4位被1010替换了。

 

二、读操作方式

5, BME的读操作使某位置1, Load-and-Set 1 Bit//

#define PTA1_SET   (void) (*((volatile unsigned char *) (0x4C000000+ (1<<21) + 0xF000)))

#define PTA1_SET_I   (void) (*((volatile unsigned int *) (0x4C000000+ (1<<21) + 0xF000)))

: PTA1_SET;   //效果是GPIOA1高电平         LAS1      1    GPIOA_PDOR地址的A0-A15

 

6, BME的读操作使某位清0, Load-and-Clear 1 Bit

#define PTA2_CLR   (void) (*((volatile unsigned char *) (0x48000000 + (2<<21) + 0xF000)))

#define PTA2_CLR_I   (void) (*((volatile unsigned int *) (0x48000000 + (2<<21) + 0xF000)))

: PTA2_CLR;     //效果是GPIOA2低电平        LAC1      2     GPIOA_PDOR地址的A0-A15

 

7, BME同时提取多个bitUnsigned Bit Field Extract

8位内                     //UBFX      1位开始       1+1   GPIOA_PDOR地址的A0-A18

#define PTA_OUT    *((volatile unsigned char *) (0x50000000+ (1<<23) + (1<<19) + 0xF000))

16位内                   //UBFX      1位开始       1+1   GPIOA_PDOR地址的A0-A18

#define PTA_OUT_I    *((volatile unsigned int *) (0x50000000+ (1<<23) + (1<<19) + 0xF000))

: 初始值GPIO_PDOR = 0x3a;   //            11_1010

  temp = PTA_OUT; //                    此时temp = 0x01

: 初始值GPIO_PDOR = 0x35;   //            11_0101

  temp = PTA_OUT; //                    此时temp = 0x02

该宏定义UBFX功能是将GPIO_PDORbit1开始提取1+1位,并以bit1为最低位赋值到目标变量。

         需要注意的是UBFXBFI一样操作的都是映射内存空间,用来操作GPIO时要以F000为起始地址。

        BME执行的是读-修改-写操作,而我们很多寄存器有些位是w1c,也就是所谓的write-1-clear,写10的工作方式。使用BME时就需要特别注意和小心了,否则会出现很多不可预料的后果。      

        如果一个寄存器中有多个连续的W1C位,我们就不要使用LAS1来对寄存器写10了,因为在LAS1这个操作中,其中有一步操作是将数据读回(在reference manual中有read data return to core一说)。这一步会将原本不需要清0的位给清了。

        下面介绍这个情况的实验。

在我们M0+PWM模块中,寄存器TPM0_STATUS所有有效位都为w1c,我们模拟一个情景:

系统48MHzTPM时钟128分频,TPM0定时中断计数器最大值为37499,并使能溢出中断。

通道0设置为output compare模式的match output low,比较值为10000,不触发中断。

通道1设置为output compare模式的match output high,比较值为20000,不触发中断。

上面的设置可以使我们每50ms进入一次中断,需要我们在中断服务程序中清中断标志。

TPM0_STATUS 地址为 0x4003_8050

中断函数中设置断点观察TPM0_STATUS的值,为1_0000_0011 B

#define TPM0_STATUS_LAS1   (void) (*((volatile unsigned int *) (0x4C000000| (1<<21) | 0x38050)))

中断程序中用TPM0_STSTUS_LAS1bit110,得到的结果是TPM0_STATUS = 0,使用LAS1作用在该寄存器的其他位结果都一样。将其他不需要改动的位都清0了。

    我们换种方式。

#define TPM0_ STSTUS_BFI *((volatile unsigned int *) (0x50000000 | (0<<23) | (8<<19) | 0x38050)) =0x001

中断里用BFI去修改该连续的w1c位,从bit0开始,长度为8+1位,执行TPM0_STSTUS_BFIbit8bit2仍为1 bit0已经被清0了。这确实是我们想要的效果。

        此后我们遇上一个寄存器有多个连续w1c时,可以使用BFI的方式来改写寄存器w1c位的值,而位判断则采用UBFX的方式来提取该位域。

下面是针对比较器的CMP0_SCR寄存器操作的例程.

CMP0_SCR8bit的寄存器bit1bit2w1c

#define CMP_SCR_CFR_CLR *((volatile unsigned char *) (0x50000000+ (1<<23) + (1<<19) + 0x73003)) =4

#define CMP_SCR_CFF_CLR *((volatile unsigned char *) (0x50000000 + (1<<23) + (1<<19) +0x73003)) =2

             //           BFI                    第一位开始   1+1          2对应bit2bit101          4对应bit2bit110

#define CMP_SCR_CFR    *((volatile unsigned char *) (0x50000000 + (2<<23) + (0<<19) + 0x73003))

#define CMP_SCR_CFF    *((volatile unsigned char *) (0x50000000 + (1<<23) + (0<<19) + 0x73003))

            //            UBFX                分别是提取bit2bit1的值

void CMP_Change(void)

{

If (CMP_SCR_CFR)

{

CMP_SCR_CFR_CLR;

}

                  If (CMP_SCR_CFF)

{

CMP_SCR_CFF_CLR;

}

}

        总结,BME功能可以有效提高M0+的位操作性能并减少代码占用空间,但用于处理w1c位时要特别小心,总的来说BME是个好东西,在内核资源紧张的时候可以给用户提供一个精简代码的手段。

2 people found this helpful

Attachments

    Outcomes