use cases where you utilizes bitwise operators?

Thread Starter

MTech1

Joined Feb 15, 2023
147
When do you typically utilize bitwise operators and masking in embedded software programming?

can you explain the scenarios or use cases where you commonly utilizes bitwise operators and masking to manipulate data?

such as when reading data from an ADC , transmitting data via UART
 

BobTPH

Joined Jun 5, 2013
8,108
Used often for accessing bit fields in hardware registers. For instance, a register might contain a 3 bit field starting at bit 2 in a byte. You could access it with:

shift right by 2.
and with 7

In C, we normally just declare these as structs containing bit fields and let the compiler do the shifting and masking.
 

nsaspook

Joined Aug 27, 2009
12,308
You need to be careful using bitwise operators and masking in the normal C language context during some embedded software programming operations that deal directly with hardware and possible interrupts.

Often masks and bitwise operators are used with machine language specific atomic get/set/clear instructions on software designed to be multi-tasking at the OS or interrupt level.

For example, you can clear or set specific 32-bit register bits in a atomic (non-interruptible way in one instruction) in systems where that same register might being accessed concurrently by another process.
https://microchipdeveloper.com/xc32:atomic-access-of-sfrs

Like other Microchip devices, PIC32 devices use Special Function Registers (SFRs) to interact with peripherals. These registers can be accessed like ordinary variables in C source code when using the MPLAB® XC32 compiler, but since they directly affect or are directly affected by hardware, there can be read-modify-write problems associated with code that might normally be used to change individual bits with these registers.

To get around this problem, virtually all registers have an alternate register set that can be used to set, clear, or toggle the bits within the primary register. The names of the alternate registers consist of the primary register name appended with SET, CLR, or INV. Each of these alternate registers can be assigned a value in the usual way, but this write operation will instead set, clear, or toggle (respectively) bits within the primary register. Where a bit in the written value is one, this will trigger the appropriate operation in the primary register at the corresponding bit position. Thus, a plain assignment to the alternate registers can be used to replace assignment-OR, assignment-AND, or assignment-XOR (respectively) operations directly on the primary register.
The code to assign a value to an alternate register takes fewer instructions than performing the relevant operation on the primary register, and, importantly, a simple assignment is an atomic operation. Atomic operations cannot be interrupted and are safe to use in situations where the content of the destination register could be changed by interrupt code or by hardware.

Consider, for example, the LATA latch register. It can be read and written in the usual way, but in addition to this register, its alternate registers are defined: LATASET, LATACLR, and LATAINV. The following table shows the operation performed when writing to these alternate registers and the equivalent code which acts on the primary register and which this write replaces.

Operation on LATAAlternate register usageReplaces
set bit #6LATASET = 0x40;LATA |= 0x40;
clear bit #6LATACLR = 0x40;LATA &= ~0x40;
toggle bit #6LATAINV = 0x40;LATA ^= 0x40;
C:
static void ads_int_setup(void)
{
    IEC0CLR = _IEC0_INT2IE_MASK;    /* disable interrupt */
    INTCONbits.INT2EP = 0;
    IFS0CLR = _IFS0_INT2IF_MASK;    /* clear flag */
    IPC3bits.INT2IP = 1;
    IPC3bits.INT2IS = 1;
    IEC0SET = _IEC0_INT2IE_MASK; /* enable interrupt */
}
https://github.com/nsaspook/RIOT/blob/PIC32MZEF/examples/rn4020_riot/test.X/ads1220.c
C:
void eic_irq_configure(int irq_num)
{
    (void)irq_num;
    /* Only timer interrupt supported currently */
    assert(irq_num == EIC_IRQ_TIMER);

    /* Enable IRQ0 CPU Timer Interrupt */
    IEC0SET = _IEC0_CTIE_MASK;

    /* Set IRQ 0 to priority 1.0 */
    IPC0SET = 1 << _IPC0_CTIP_POSITION | 0 << _IPC0_CTIS_POSITION;
}
Here we create the SET mask using various operators like shifts with constants and logical operators for the mask we need, then execute the atomic IPC0SET instruction to set the bits using that mask.
C:
static void release_dma_chan(uint8_t chan)
{
    assert(chan < DMA_NUMOF);

    IEC4CLR = _IEC4_DMA0IE_MASK << chan; /* Disable the DMA interrupt. */
    IFS4CLR = _IFS4_DMA0IF_MASK << chan; /* Clear the DMA interrupt flag. */
    DCHxCON(pic_dma[chan]) = 0;
    DCHxECON(pic_dma[chan]) = 0;
    DCHxINT(pic_dma[chan]) = 0;
}

static void init_dma_chan(uint8_t chan, uint32_t irq_num, volatile unsigned int *SourceDma, volatile unsigned int *DestDma, spi_t bus)
{
    assert(chan < DMA_NUMOF);
    assert(bus != 0 && bus <= SPI_NUMOF_USED);

    pic_dma[chan].regs = (volatile uint32_t *)(&DCH0CON + (chan * DMA_REGS_SPACING));
    pic_dma[chan].bus = bus;

    IEC4CLR = _IEC4_DMA0IE_MASK << chan; /* Disable the DMA chan interrupt. */
    IFS4CLR = _IFS4_DMA0IF_MASK << chan; /* Clear the DMA chan interrupt flag. */
    DMACONSET = _DMACON_ON_MASK; /* Enable the DMA module. */
    DCHxCON(pic_dma[chan]) = 0;
    DCHxECON(pic_dma[chan]) = 0;
    DCHxINT(pic_dma[chan]) = 0;
    DCHxSSA(pic_dma[chan]) = KVA_TO_PA(SourceDma); /* Source start address. */
    DCHxDSA(pic_dma[chan]) = KVA_TO_PA(DestDma); /* Destination start address. */
    DCHxSSIZ(pic_dma[chan]) = 1; /* default Source bytes. */
    DCHxDSIZ(pic_dma[chan]) = 1; /* default Destination bytes. */
    DCHxCSIZ(pic_dma[chan]) = 1; /* Bytes to transfer per event. */
    DCHxECON(pic_dma[chan]) = irq_num << _DCH0ECON_CHSIRQ_POSITION; /* cell trigger interrupt */
    DCHxECONSET(pic_dma[chan]) = _DCH0ECON_SIRQEN_MASK; /* Start cell transfer if an interrupt matching CHSIRQ occurs */
    DCHxINTSET(pic_dma[chan]) = _DCH0INT_CHBCIE_MASK; /* enable Channel block transfer complete interrupt. */
    /*
     * set vector priority and receiver DMA trigger enables for the board hardware configuration
     */
    *(dma_config[chan].ipc_regset) = dma_config[chan].ipc_mask_p & (SPIxPRI_SW0 << dma_config[chan].ipc_mask_pos_p);
    *(dma_config[chan].ipc_regset) = dma_config[chan].ipc_mask_s & (SPIxSUBPRI_SW0 << dma_config[chan].ipc_mask_pos_s);
    IEC4SET = dma_config[chan].iec_mask << chan; /* DMA interrupt enable if needed */
}

/* disable receive interrupts and set the UART buffer mode for DMA */
static void spi_reset_dma_irq(spi_t bus)
{
    assert(bus != 0 && bus <= SPI_NUMOF_USED);

    switch (bus) {
    case 1:
        IEC3CLR = _IEC3_SPI1RXIE_MASK; /* disable SPIxRX interrupt */
        SPI1CONbits.SRXISEL = 1; /* not empty */
        IFS3CLR = _IFS3_SPI1RXIF_MASK; /* clear SPIxRX flag */
        break;
    case 2:
        IEC4CLR = _IEC4_SPI2RXIE_MASK;
        SPI2CONbits.SRXISEL = 1;
        IFS4CLR = _IFS4_SPI2RXIF_MASK;

        break;
    default:
        break;
    }
}
https://github.com/nsaspook/RIOT/blob/PIC32MZEF/cpu/mips_pic32_common/periph/spi.c
 
Last edited:

djsfantasi

Joined Apr 11, 2010
9,131
Outside of setting/ clearing hardware registers, I have used bitwise operations to maintain a list of task statii in a program. It was an animatronic multitasking run-time system that taxed the memory of my MCU. By implementing status flags as bits in 64 bit ulong variables, I used 1/8th of the memory otherwise which was sufficient to cover any memory used by full variables (depending on the MCU, it could have been 1/64th).
 

WBahn

Joined Mar 31, 2012
29,512
Lots of places where bit banging is used, both in embedded and hosted environments.

Have a microcontroller that can't multiply and you need to write a software multiplication routine? -- Bit banging to the rescue.

Implementing an interface such as SPI or a UART on a machine that doesn't have that built in? Bit banging to the rescue.
 

nsaspook

Joined Aug 27, 2009
12,308
Outside of setting/ clearing hardware registers, I have used bitwise operations to maintain a list of task statii in a program. It was an animatronic multitasking run-time system that taxed the memory of my MCU. By implementing status flags as bits in 64 bit ulong variables, I used 1/8th of the memory otherwise which was sufficient to cover any memory used by full variables (depending on the MCU, it could have been 1/64th).
That's a good use case too. I've used a similar scheme of bits to automate auto-scanning ADC background conversions using interrupts.

https://github.com/nsaspook/mbmc_k42/blob/master/mbmc_k42.X/daq.h
C:
/*
* Normal or high volt channels (bit set)
*/
#define ADC_V_CHAN_TYPE  0b0010000000100000

/*
* current sensor configuration
*/
//#define BAT_100A // USE the 100A honeywell sensor for battery current, reduces range but has better data within that range
#define NUM_C_SENSORS    3
#define ADC_C_CHAN        0b0000100000000011 // 5 volt hall current sensor bitmap
#define ADC_C_CHAN_TYPE   0b0000000000000001 // 0 - 100A, 1=200A or battery 100A type if BAT_100A is defined
#define ADC_C_CHAN_MPPT   0b0000100000000000 // 0 - 100A mppt channel
/*
* temp sensor configuration
*/
#define NUM_T_SENSORS    0
#define ADC_T_CHAN         0b0000000000000000 // 5 volt temp sensor bitmap
#define ADC_T_CHAN_TYPE    0b0000000000000000 // type bitmap
https://github.com/nsaspook/mbmc_k42/blob/master/mbmc_k42.X/daq.c
C:
/*
* turn ADC values into standard program values
*/
float conv_raw_result(const adcc_channel_t chan, const adc_conv_t to_what)
{

    switch (to_what) {
    case CONV:
        if (!(ADC_SCAN_CHAN >> chan & 0x1))
            return NAN;

        if (ADC_C_CHAN >> chan & 0x1) { // current conversion
            if (ADC_C_CHAN_TYPE >> chan & 0x1) {
#ifdef BAT_100A
                return((float) ((int16_t) get_raw_result(chan)) - R.n_offset[A100B]) * R.n_scalar[A100B];
#else
                return((float) ((int16_t) get_raw_result(chan)) - R.n_offset[A200]) * R.n_scalar[A200];
#endif
            } else {
                if (ADC_C_CHAN_MPPT >> chan & 0x1) {
                    return((float) ((int16_t) get_raw_result(chan)) - R.n_offset[A100M]) * R.n_scalar[A100M];
                } else {
                    return((float) ((int16_t) get_raw_result(chan)) - R.n_offset[A100]) * R.n_scalar[A100];
                }
            }
        } else {
            if (ADC_T_CHAN >> chan & 0x1) { // temp conversion
                return 25.0; // filler until sensor is selected
            } else { // voltage conversion
                if (ADC_V_CHAN_TYPE >> chan & 0x01) {
                    return((float) get_raw_result(chan) * V_SCALE_H) / 1000.0f;
                } else {
                    return((float) get_raw_result(chan) * V_SCALE) / 1000.0f;
                }
            }
        }
        break;
    case O_CONV:
        if (ADC_C_CHAN >> chan & 0x1 || ADC_T_CHAN >> chan & 0x1)
            return((float) get_raw_result(chan) * C_SCALE) / 1000.0f;

        if (ADC_V_CHAN_TYPE >> chan & 0x01) {
            return((float) get_raw_result(chan) * V_SCALE_H) / 1000.0f;
        } else {
            return((float) get_raw_result(chan) * V_SCALE) / 1000.0f;
        }

        break;
    default:
        return 0.0;
        break;
    }
    return 0.0;
}
 
Last edited:

MrChips

Joined Oct 2, 2009
29,850
I use shifts and masks all the time. The list is too long to show.

Simple, shift left by one bit is multiply by 2. Shift right by one bit is divide by 2.
 
Top