嵌入式寄存器位操作

位操作

在嵌入式编程中,常常需要涉及到寄存器的位操作,使能某个功能,设置 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) 会在编译优化时被优化为常量。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270