Search code examples
carmembeddedzynq

C bitwise OR is returning incorrect value


I have a void function that's supposed to set the first 8 bits of a 32-bit register. However, it keeps returning incorrect values (only powers of 2).

void ugvPwm_setSpeed(ugv_pwm *InstancePtr, u8 spd_val)
{
    u32 reg_read;
    u32 spd_sel;
    reg_read = MOTORPWM_mReadReg(InstancePtr->RegBaseAddress,
                                MOTORPWM_S00_AXI_SLV_REG0_OFFSET); // read original register value
    //
    xil_printf("\r\n\nugvPwm_setSpeed: reg0 = %x\r\n", reg_read);
    xil_printf("ugvPwm_setSpeed: duty = %x\r\n", (u32) spd_val);
    spd_sel = reg_read | (u32) spd_val;                        // bitwise OR in new value; PROBLEM HERE!
    xil_printf("ugvPwm_setSpeed: new reg0 (reg0 | duty) = %x\r\n\n", spd_sel);
    //
    InstancePtr->speedSelect = spd_val;
    MOTORPWM_mWriteReg(InstancePtr->RegBaseAddress, MOTORPWM_S00_AXI_SLV_REG0_OFFSET, spd_sel);
}

u32 and u8 on my platform correspond to unsigned int and unsigned char respectively. I've tried it using onlinegdb using unsigned int and unsigned char and it works perfectly. The output on my platform is shown below:

ugvPwm_setSpeed: reg0 = 300
ugvPwm_setSpeed: duty = 1
ugvPwm_setSpeed: new reg0 (reg0 | duty) = 301
ugvPwm_setSpeed: expected               = 301

ugvPwm_setSpeed: reg0 = 301
ugvPwm_setSpeed: duty = 2
ugvPwm_setSpeed: new reg0 (reg0 | duty) = 303
ugvPwm_setSpeed: expected               = 302

ugvPwm_setSpeed: reg0 = 303
ugvPwm_setSpeed: duty = 3
ugvPwm_setSpeed: new reg0 (reg0 | duty) = 303
ugvPwm_setSpeed: expected               = 303

ugvPwm_setSpeed: reg0 = 303
ugvPwm_setSpeed: duty = 4
ugvPwm_setSpeed: new reg0 (reg0 | duty) = 307
ugvPwm_setSpeed: expected               = 304

ugvPwm_setSpeed: reg0 = 307
ugvPwm_setSpeed: duty = 5
ugvPwm_setSpeed: new reg0 (reg0 | duty) = 307
ugvPwm_setSpeed: expected               = 305

ugvPwm_setSpeed: reg0 = 307
ugvPwm_setSpeed: duty = 6
ugvPwm_setSpeed: new reg0 (reg0 | duty) = 307
ugvPwm_setSpeed: expected               = 306

ugvPwm_setSpeed: reg0 = 307
ugvPwm_setSpeed: duty = 7
ugvPwm_setSpeed: new reg0 (reg0 | duty) = 307
ugvPwm_setSpeed: expected               = 307

ugvPwm_setSpeed: reg0 = 307
ugvPwm_setSpeed: duty = 8
ugvPwm_setSpeed: new reg0 (reg0 | duty) = 30F
ugvPwm_setSpeed: expected               = 308

ugvPwm_setSpeed: reg0 = 30F
ugvPwm_setSpeed: duty = 9
ugvPwm_setSpeed: new reg0 (reg0 | duty) = 30F
ugvPwm_setSpeed: expected               = 309

Solution

  • If you're trying to set the lowest-order 8 bits to a specific value, bitwise-or will not do that. Bitwise-or is basically a "one bit accumulator" - once a bit is set to one, all bitwise-or operations will preserve the 1 bit value.

    You need to clear the low-order 8 bits:

    u32 clearLowOrderBitsMask = ~( ( u32 ) 0xFF );
    spd_sel = reg_read & clearLowOrderBitsMask;
    

    That works by using bitwise-and to keep the upper 24 bits as before by doing a bitwise-and for the upper 24 bits with all 1 bits, and it clears the lower-order 8 bytes by doing a bitwise-and with all zero bits.

    Then you can set the bits:

    spd_sel = reg_read | (u32) spd_val;