美文网首页
嵌入式寄存器位操作

嵌入式寄存器位操作

作者: 一个大大大坑 | 来源:发表于2021-06-29 21:16 被阅读0次

    位操作

    在嵌入式编程中,常常需要涉及到寄存器的位操作,使能某个功能,设置 gpio select, 配置外设等。

    回顾一下 C 语言的位运算

    • ~ 按位取反运算符,按二进制位进行"取反"运算。~0b001=0b110; ~0b00=0b11; 1=-2;0=-1
    • ^ 按位异或运算符,按二进制位进行"异或"运算。0^0=0; 0^1=1; 1^0=1; 1^1=0;
    • | 按位或运算符,按二进制位进行"或"运算。0|0=0; 0|1=1; 1|0=1; 1|1=1;
    • & 按位与操作,按二进制位进行"与"运算。0&0=0; 0&1=0; 1&0=0; 1&1=1;
    • << 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补 0)。
    • >> 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补 0,负数左补 1,右边丢弃。

    对寄存器的访问是通过内存地址进行,一个地址可访问一个字 4byte(4*8=32bit) 的寄存器数据。

    • 字 = 4byte
    • 半字 = 2byte
    • 字节 = byte = 8bit
    • 位 = 1/8 字节

    需要注意的是,所有涉及寄存器值修改的操作。必须遵循以下三步流程:

    1. 获取该地址上的值
    2. 按需修改 bit 位上的值
    3. 将修改后的值设置至该地址

    寄存器访问需要注意大端小端的问题,大小端问题也称为字节序问题。关于大小端问暂时不做阐述。

    • Big-Endian: 低地址存放高位;
    • Little-Endian: 低地址存放低位;

    bit 操作

    本节描述了如何将一个 bit 设置成 0 或者 1。

    若需要修改 bit 31 为 1,则可以 a = a|(1<<31);

    • 对于 bit 16 ,无论其值为何,均会被设置为 1
    • 对于 其余各位,无论其值为何均保持不变。
    0x ???? ???? ???? ???? ???? ???? ???? ????
    |
    0x 0100 0000 0000 0000 0000 0000 0000 0000
    =
    0x ?1?? ???? ???? ???? ???? ???? ???? ????
    

    若需要修改 bit 31 为 0,稍微复杂一点,可以 a = a& (~(1<<31))

    • 使用 ~(1<<31) 构造操作数
    • & 操作后,对于 bit31,无论其值为何,均会被设置为 0
    • & 操作后,对于其余各位,无论其值为何均保持不变。
    0x ???? ???? ???? ???? ???? ???? ???? ????
    &
    (
    ~
    0x 0100 0000 0000 0000 0000 0000 0000 0000
    )
    =
    0x ?0?? ???? ???? ???? ???? ???? ???? ????
    
    
    0x ???? ???? ???? ???? ???? ???? ???? ????
    &
    0x 1011 1111 1111 1111 1111 1111 1111 1111
    =
    0x ?0?? ???? ???? ???? ???? ???? ???? ????
    

    示例

    以配置 uart0 为例,配置 uart 中有如下两个步骤,下文以这两个步骤进行举例:

    1. 使能 uart 时钟
    2. 配置 gpio 选择 uart 模式

    为了使能 uart0 时钟,需要设置寄存器BUS_CLK_GATING_REG3(默认值 0x00000000) 第 16 bit 为 1

    0b 0000 0000 0000 0000 0000 0000 0000 0000
    ;设置 bit 16 为1
    0b 0000 0000 0000 0000 1000 0000 0000 0000
    =
    0x 0    0    0    0    8    0    0    0
    

    可以直接将值 0x00008000 设置至寄存器 BUS_CLK_GATING_REG3

    writel(BUS_CLK_GATING_REG3,0x8000)
    

    但是!这样做会有一些问题,我们仅需要操作的是 bit16, 如果其他 bit 上已经配置了值,经过上述操作后,就会被覆盖。
    所以在进行修改时要确保其他位不被改变。

    所以需要使用位运算进行修改。并且必须遵循寄存器修改三步骤。若需要修改 bit 16 为 1,则可以 a = a|(1<<16);

    • 对于 bit 16 ,无论其值为何,均会被设置为 1
    • 对于 其余各位,无论其值为何均保持不变。

    那么上述操作可以变成:

    reg = readl(BUS_CLK_GATING_REG3)
    reg = reg & (1<<16)
    writel(BUS_CLK_GATING_REG3,reg)
    

    解释一下:

    1. 使用 readl 宏获取到该寄存器值,如果没有宏定义也可以直接使用 *((volatile unsigned int *)(addr)),addr 为内存地址。
    2. 使用位操作 & 对 寄存器值和操作数 (1<<16) 进行按位与运算,将第 16 位设置为 1. 如果对 1<<16 有疑问,请看例 2.
    3. 使用 writel 宏设置该寄存器值,如果没有宏定义也可以直接使用 *((volatile unsigned int *)(addr)) = (value),addr 为内存地址,value 为值。

    相关参考: 关键字volatile

    上述代码仅作为示例,在实际的编程中通常使用如下简写方式:

    readl(BUS_CLK_GATING_REG3) &= (1<<16)
    

    或者使用汇编语言编写:

    <略>
    

    多 bit 操作

    多 bit 操作思路与单 bit 操作一样,只是在计算修改至时使用了不同的操作数.

    为了使能 uart0 gpio,需要设置寄存器 PA_CFG0_REG(默认值 0x77777777) bit 22:20(PA5_SELECT) 为 0x2(0b010),设置 bit 18:16(PA4_SELECT) 为 0x2(0b010)

    如果是默认值 0x77777777

    0x 7    7    7    7    7    7    7    7
    0x 0111 0111 0111 0111 0111 0111 0111 0111
    

    则需要修改为 0x77772277

    0x 7    7    7    2    2    7    7    7
    0x 0111 0111 0111 0010 0010 0111 0111 0111
    

    但需要注意的是,在该地址的其他位上还存在其他配置,在进行修改时要确保其他位不被改变。需要遵循寄存器修改三步骤。

    需要设置寄存器 bit 22:20 18:16 均为 0x2(0b010),则是:

    • 设置 bit 21,17 为 1, 可以使用 a|= (0b10001<<17)
    • 设置 bit 22,20 18,16 为 0,可以使用 a&(~0b1010101<<16)
    int reg
    reg = readl(PA_CFG0_REG)
    /* 设置bit _,21,_,_,_,17_, 为1 */
    /* 同等于 reg|=(0x22<<16);reg|=(0b100010<<16) */
    /* 同等于 reg|=(0x11<<17);reg|=(0b010001<<17) */
    /* 同等于 reg|=(0b00000000000100010000000000000000) */
    reg|=0x00220000
    
    /* 设置bit 22,_,20, 18,_,16 为0 */
    /* 同等于 reg|=(~0b00000000001010101000000000000000) */
    /* 同等于 reg|=(~0x55<<16);reg|=(0b1010101<<16) */
    reg&=(~0x550000)
    writl(PA_CFG0_REG,reg)
    

    更好的, 由于 GPIO 的一个引脚选择由 4bit 控制,在配置时可以将两个位操作视为整体。
    上述操作也可视为将 bit 24:21 bit 20:17 均设置为 0x2,然后使用

    • 清除,readl(PA_CFG0_REG)&=0xff<<16
    • 设置,readl(PA_CFG0_REG)|=0x22<<16

    的方式进行寄存器修改。

    值得注意的是,在无法一次到位(非原子)的寄存器修改中,可能存在竞态或者中间状态。

    例如在 a&=0xff<<16 执行完成,下一步还未开始时,GPIO 寄存器被设置为 0x000 其代表了设置 GPIO 为 out 状态而非 uart0 。

    所以在涉及多 bit 操作时,需要使用内存对寄存器值进行中转,计算完成后再写入。

    int tmp
    tmp = readl(PA_CFG0_REG)
    tmp &=(0xff<<16)
    tmp |=(0x22<<16)
    writel(PA_CFG0_REG,tmp)
    

    在实际的编程中,通常使用类似 reg&= (1<<16) 的可读性更高的方式,(1<<16) 会在编译优化时被优化为常量。

    相关文章

      网友评论

          本文标题:嵌入式寄存器位操作

          本文链接:https://www.haomeiwen.com/subject/tcnkultx.html