打印
[Kinetis]

(转帖)来自飞思卡尔FAE的BME学习心得

[复制链接]
3371|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
FSL_TICS_ZJJ|  楼主 | 2013-12-12 15:54 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
作者 Sam Wang & River Liang
在RISC架构的MCU中,通常是加载-存储(Load and Store)的操作机制,而这种方式不能提供传统8bit架构MCU的直接位操作内存和地址空间。为此飞思卡尔在M0+系列MCU上集成了BME(Bit Manipulation Engine)位操作引擎功能,例如KE和KL系列里都带有BME,它从硬件上提供了对外设地址空间用读-修改-写的操作方式来实现位操作。
        使用BME能够降低总线的占用率和CPU执行时间,这些效果都能够降低系统的功耗。另外使用相比于用C语言实现相同功能的代码,使用BME能够更节省代码空间。这些可以参照
        BME功能支持访问从0x4000_0000开始的,大小为512K的地址空间,并把它映射成从0x4400_0000到0x5fff_ffff的内存空间。
        好了,长话短说。下面转入正题,我们应该如何使用BME来进行位操作,并达到节省代码空间、提高效率的效果。
一、写操作方式,对定义内容用写的方式来实现与、或、异或、位域插入功能
1:BME的&操作可以一次对IO的几个bit清0     //     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的几个bit置1     //       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的位域插入操作BFI(Bit 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_CFG1从bit5开始,用0x40的bit5来替换ADC0_CFG1的bit5,0x40的bit6来替换ADC0_CFG1的bit6,调用该命令后,寄存器ADC0_CFG1_ADIV = 2
相当于执行了mask = ((1 << (w+1)) - 1) << b;                          //等一系列位操作。
                            (ADC0_CFG1 & ~mask) | (0x40 & mask);
使用BFI功能需要注意的是,操作地址是A0到A18,而GPIO寄存器的A0到A19是从FF000开始,因此会有1bit 的地址冲突。为此,在使用BFI操作GPIO的寄存器时,使用的是内存映射出来的地址空间,此时GPIO的起始地址将为F000,如果还使用原来的地址,命令将会无效。之前提到的AND、OR、XOR操作,对于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_PDOR从bit0开始,一共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同时提取多个bit,Unsigned 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_PDOR从bit1开始提取1+1位,并以bit1为最低位赋值到目标变量。
         需要注意的是UBFX与BFI一样操作的都是映射内存空间,用来操作GPIO时要以F000为起始地址。
        BME执行的是读-修改-写操作,而我们很多寄存器有些位是w1c,也就是所谓的write-1-clear,写1清0的工作方式。使用BME时就需要特别注意和小心了,否则会出现很多不可预料的后果。      
        如果一个寄存器中有多个连续的W1C位,我们就不要使用LAS1来对寄存器写1清0了,因为在LAS1这个操作中,其中有一步操作是将数据读回(在reference manual中有read data return to core一说)。这一步会将原本不需要清0的位给清了。
        下面介绍这个情况的实验。
在我们M0+的PWM模块中,寄存器TPM0_STATUS所有有效位都为w1c,我们模拟一个情景:
系统48MHz,TPM时钟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_LAS1将bit1置1清0,得到的结果是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_BFI后bit8和bit2仍为1, bit0已经被清0了。这确实是我们想要的效果。
        此后我们遇上一个寄存器有多个连续w1c时,可以使用BFI的方式来改写寄存器w1c位的值,而位判断则采用UBFX的方式来提取该位域。
下面是针对比较器的CMP0_SCR寄存器操作的例程.
CMP0_SCR是8bit的寄存器bit1和bit2是w1c
#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对应bit2bit1为01          4对应bit2bit1为10
#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                分别是提取bit2和bit1的值
void CMP_Change(void)
{
If (CMP_SCR_CFR)
{
CMP_SCR_CFR_CLR;
}
                  If (CMP_SCR_CFF)
{
CMP_SCR_CFF_CLR;
}
}
        总结,BME功能可以有效提高M0+的位操作性能并减少代码占用空间,但用于处理w1c位时要特别小心,总的来说BME是个好东西,在内核资源紧张的时候可以给用户提供一个精简代码的手段。

相关帖子

沙发
xinyinxing| | 2013-12-12 16:32 | 只看该作者
这个讲的更清楚,果然好贴!!!感谢FAE,感谢版主分享!

使用特权

评论回复
板凳
黄小俊| | 2013-12-12 20:04 | 只看该作者
Good.虽然有点没看懂

使用特权

评论回复
地板
FSL_TICS_ZJJ|  楼主 | 2013-12-13 09:26 | 只看该作者
黄小俊 发表于 2013-12-12 20:04
Good.虽然有点没看懂

我自己写了两个关于BME的帖子,你可以先看看,然后再看这个就比较容易理解了。
还有,BME部分,你可以看reference manual相关章节。

使用特权

评论回复
5
yongjiayou| | 2017-2-16 16:14 | 只看该作者
FSL_TICS_ZJJ 发表于 2013-12-13 09:26
我自己写了两个关于BME的帖子,你可以先看看,然后再看这个就比较容易理解了。
还有,BME部分,你可以看r ...

看了飞思卡尔的BME模块,功能确实强大!我看完了这篇文档,发现点问题,在读操作方式中,宏定义的地址不对。比如第5条,设置GPIOA1为高电平,那么设置的是寄存器GPIOA_PDOR寄存器,
该寄存器的地址是0x400F_F000。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

165

主题

5069

帖子

88

粉丝