位域底层类型宽度是否影响行为?AM335X PRU寄存器异常问题
位域统一类型后AM335X PRU外设异常原因分析
背景与问题
在对接硬件外设时,我用位域定义32位宽的寄存器。最初按照位宽规则混合使用uint8_t/uint16_t/uint32_t定义位域:
// 更多定义在上方 union { volatile uint32_t I2C_REVNB_HI; volatile struct { uint16_t FUNC : 12; uint8_t RSVD0 : 2; uint8_t SCHEME : 2; uint16_t RSVD1 : 16; } I2C_REVNB_HI_bits; }; // 0x04 - 0x08 // 更多定义在下方
规则为:位宽≤8用uint8_t,8<位宽≤16用uint16_t,16<位宽≤32用uint32_t。为简化代码,我将所有位域的底层类型统一为uint32_t:
// 更多定义在上方 union { volatile uint32_t I2C_REVNB_HI; volatile struct { uint32_t FUNC : 12; uint32_t RSVD0 : 2; uint32_t SCHEME : 2; uint32_t RSVD1 : 16; } I2C_REVNB_HI_bits; }; // 0x04 - 0x08 // 更多定义在下方
但统一类型后外设行为异常,需要明确两种实现的底层差异。代码运行在AM335X处理器的可编程实时单元(PRU)上,使用clpru编译器编译。
clpru编译器位域规则
根据clpru编译器文档,位域处理遵循以下规则:
- 位域按声明的底层类型处理
volatile位域按其声明类型访问,多次访问不会被合并- 结构体的大小由位域的声明类型决定
测试过程与结果
Update 1:赋值测试结果一致
执行赋值测试后,两种实现的输出均为0x0000CFFF,无差异:
i2c_refactored.I2C_REVNB_HI = 0; i2c_refactored.I2C_REVNB_HI_bits.SCHEME = ~0; i2c_refactored.I2C_REVNB_HI_bits.FUNC = ~0; DEBUG_MEMORY_0.status = i2c_refactored.I2C_REVNB_HI; i2c_original.I2C_REVNB_HI = 0; i2c_original.I2C_REVNB_HI_bits.SCHEME = ~0; i2c_original.I2C_REVNB_HI_bits.FUNC = ~0; DEBUG_MEMORY_1.status = i2c_original.I2C_REVNB;
Update 2:改用unsigned int仍异常
将位域底层类型改为unsigned int后,外设异常问题依然存在。
Update 3:汇编对比未发现逻辑错误但问题持续
编写测试用例并生成汇编代码(DEBUG_MEM0地址为0x10000,DEBUG_MEM1为0x10248),未发现明显逻辑错误,但外设异常依旧。
测试用例代码:
int main(void){ volatile pru_I2C_tmp i2c_refactored = {0}; volatile pru_I2C i2c_working = {0}; i2c_refactored.I2C_REVNB_HI = 0; i2c_refactored.I2C_REVNB_HI_bits.SCHEME = ~0; i2c_refactored.I2C_REVNB_HI_bits.FUNC = ~0; DEBUG_MEM0.status = i2c_refactored.I2C_REVNB_HI; i2c_working.I2C_REVNB_HI = 0; i2c_working.I2C_REVNB_HI_bits.SCHEME = ~0; i2c_working.I2C_REVNB_HI_bits.FUNC = ~0; DEBUG_MEM1.status = i2c_working.I2C_REVNB_HI; __halt(); return 0; }
对应汇编代码:
[0x0000] 0x240000c0 LDI R0.w2, 0x0000 [0x0001] 0x24080080 LDI R0.w0, 0x0800 [0x0002] 0x0504e0e2 SUB R2, R0, 0x04 [0x0003] 0x2eff818e UNKNOWN-F2 [0x0004] 0x230007c3 JAL R3.w2, 0x0007 [0x0005] 0x240001ee LDI R14, 0x0001 [0x0006] 0x230040c3 JAL R3.w2, 0x0040 [0x0007] 0x05ffe2e2 SUB R2, R2, 0xff [0x0008] 0x240800ef LDI R15, 0x0800 [0x0009] 0x2400d8f0 LDI R16, 0x00d8 [0x000a] 0xe1fd02c3 SBBO R3.b2, R2, 253, 2 [0x000b] 0x05b3e2e2 SUB R2, R2, 0xb3 [0x000c] 0x0100e2ee ADD R14, R2, 0x00 [0x000d] 0x230033c3 JAL R3.w2, 0x0033 [0x000e] 0x2408d8ef LDI R15, 0x08d8 [0x000f] 0x2400d8f0 LDI R16, 0x00d8 [0x0010] 0x01d8e2ee ADD R14, R2, 0xd8 [0x0011] 0x230033c3 JAL R3.w2, 0x0033 [0x0012] 0x240000e0 LDI R0, 0x0000 [0x0013] 0x24c000e1 LDI R1, 0xc000 [0x0014] 0xe1042280 SBBO R0, R2, 4, 4 [0x0015] 0x0104e2e0 ADD R0, R2, 0x04 [0x0016] 0xf100208e LBBO R14, R0, 0, 4 [0x0017] 0x12e1eee1 OR R1, R14, R1 [0x0018] 0xe1002081 SBBO R1, R0, 0, 4 [0x0019] 0x240fffe1 LDI R1, 0x0fff [0x001a] 0xf100208e LBBO R14, R0, 0, 4 [0x001b] 0x12e1eee1 OR R1, R14, R1 [0x001c] 0x2eff818e UNKNOWN-F2 [0x001d] 0xe1002081 SBBO R1, R0, 0, 4 [0x001e] 0x240001c1 LDI R1.w2, 0x0001 [0x001f] 0x24000081 LDI R1.w0, 0x0000 [0x0020] 0xf1042280 LBBO R0, R2, 4, 4 [0x0021] 0xe1002180 SBBO R0, R1, 0, 4 [0x0022] 0x240001c1 LDI R1.w2, 0x0001 [0x0023] 0x24024881 LDI R1.w0, 0x0248 [0x0024] 0xe1dc228e SBBO R14, R2, 220, 4 [0x0025] 0xf1dd0200 LBBO R0, R2, 221, 1 [0x0026] 0x13c00000 OR R0.b0, R0.b0, 0xc0 [0x0027] 0xe1dd0200 SBBO R0, R2, 221, 1 [0x0028] 0x240fff80 LDI R0.w0, 0x0fff [0x0029] 0xf1dc02c0 LBBO R0.b2, R2, 220, 2 [0x002a] 0x1280c080 OR R0.w0, R0.w2, R0.w0 [0x002b] 0xe1dc0280 SBBO R0, R2, 220, 2 [0x002c] 0xf1dc2280 LBBO R0, R2, 220, 4 [0x002d] 0xe1002180 SBBO R0, R1, 0, 4 [0x002e] 0x2a000000 >> HALT [0x002f] 0x01b3e2e2 ADD R2, R2, 0xb3 [0x0030] 0xf1fd02c3 LBBO R3.b2, R2, 253, 2 [0x0031] 0x01ffe2e2 ADD R2, R2, 0xff [0x0032] 0x20c30000 JMP R3.w2 [0x0033] 0x5100f00c QBEQ 12, R16, 0 [0x0034] 0x10eeeef1 AND R17, R14, R14 [0x0035] 0x24003000 LDI R0.b0, 0x0030 [0x0036] 0x70f00002 QBGE 2, R0.b0, R16 [0x0037] 0x10f0f000 AND R0.b0, R16, R16 [0x0038] 0x0400f0f0 SUB R16, R16, R0.b0 [0x0039] 0xff00cf12 LBBO R18, R15, 0, b0 [0x003a] 0xef00d112 SBBO R18, R17, 0, b0 [0x003b] 0x5100f004 QBEQ 4, R16, 0 [0x003c] 0x0000efef ADD R15, R15, R0.b0 [0x003d] 0x0000f1f1 ADD R17, R17, R0.b0 [0x003e] 0x21003500 JMP 0x0035 [0x003f] 0x20c30000 JMP R3.w2 [0x0040] 0x230042c3 JAL R3.w2, 0x0042 [0x0041] 0x21004100 JMP 0x0041 [0x0042] 0x10000000 AND R0.b0, R0.b0, R0.b0 [0x0043] 0x20c30000 JMP R3.w2
底层差异与异常原因
对比汇编代码可以发现两种实现的核心差异在于寄存器访问粒度:
混合类型实现(i2c_working):
- 修改
SCHEME位时([0x0025]-[0x0027]),采用单字节LBBO/SBBO操作,仅读写寄存器的对应字节 - 修改
FUNC位时([0x0028]-[0x002b]),采用半字LBBO/SBBO操作,仅读写寄存器的对应半字
- 修改
统一uint32_t实现(i2c_refactored):
- 修改
SCHEME和FUNC位时,均采用32位字LBBO/SBBO操作,每次都完整读写整个32位寄存器
- 修改
对于硬件外设寄存器而言,部分寄存器对访问宽度有严格要求:
- 某些寄存器要求原子性的窄宽度访问,32位读写可能会意外修改或触发其他未预期的位
- 部分状态/控制位在被32位读写时会触发硬件行为(如状态位自动清零、外设启动特定操作),而窄宽度访问则不会触发这类行为
虽然赋值测试的最终寄存器值相同,但两种实现产生的总线事务完全不同,硬件对不同宽度的总线事务响应不一致,这就是导致外设行为异常的根本原因。
内容的提问来源于stack exchange,提问作者lrdewaal
相关产品推荐
相关产品推荐

