Search code examples
cavrlcd

When on 4 bit data mode, is it possible to use the other 4 bits for other stuff?


So im writing a C program to interface an LCD on 4 bit data mode. However, I was wondering if I could use the other 4 bits to do something else like an external interrupt. To be more specific, Im using PORTD for the data lines on the arduino however I also need pin PD2 to use the INT0 interrupt (pushbutton). In my program I noticed I kept setting the lower 4 bits to 0 when sending commands:

PORTD = cmd & 0xf0;
flashLCD();
PORTD = (cmd & 0x0f) << 4;

This works perfectly but it sets the other bits to 0. This is called on a timer which means Im sending commands repeatedly. Therefore, I tried saving the previous value of the register and append it with some bitwise operations:

uint8_t initial_state = PORTD;
PORTD = (cmd & 0xf0) | (initial_state & 0x0f);
flashLCD();
PORTD = ((cmd & 0x0f) << 4) | (initial_state & 0x0f)

It sends the cmd on the LCD, however it still wont respond to the interrupt. I wanted to know if theres something Im not taking into consideration or if my logic is incorrect. Thanks.

Edit: Nvm I figured it out. My LCD library resetted the port register to 0 always even when on 4 bit mode so the other unused ports were being reset as well. I just changed the library so that I can use the other ports when on 4 bit mode.


Solution

  • You correctly noted that other bits can be preserved by using the bitwise operations

    PORTD = (PORTD & 0x0F) | (high_bits << 4);
    

    But! this line is going to be compiled into several machine instructions:

    1. load PORTD value into a register
    2. perform bitwise AND on the register
    3. load high_bits into another register
    4. perform other calculations (left shift on the high bits etc.)
    5. perform bitwise OR
    6. store the result back into PORTD

    Let's imagine, somewhere between 1 and 6 an interrupt fires, it stop the code execution, and it changes lower bits of PORTD. After the interrupt routine completes, the program continues to execute and it will rewrite all 8th bits of PORTD by what was stored in the registers before the interrupt, thus overwriting lower bits of PORTD, discarding all changes made in the interrupt routine.

    So, there are two approaches to make write operation to PORTx atomic.

    First: just disable interrupt for the time of PORTx register update:

    uint8_t old_sreg = SREG; // save SREG register (including I flag)
    cli(); // clear I flag, thus prohibiting interrupts
    PORTD = (PORTD & 0x0F) | (high_bits << 4); // perform the operation
    SREG = old_sreg; // restoring SREG and I flag, if it was set before
    

    Second approach works only on new AVR cores (eg. ATmega328, 1284, 2560 etc), but will not work on older ones (ATmega8, 32 etc). Refer to the datasheet, I/O-Ports -> Ports as General Digital I/O -> Toggling the Pin section. Writing ones into bits of PINx will invert corresponding bits of PORTx. Using it, it is possible to update only required bits of PORTx register, leaving other intact, thus removing need of locking out interrupts. It may be useful in time-critical environment, where interrupt should fire as quick as possible.

    PIND = (PORTD ^ (high_bits << 4)) & 0xF0;
    

    Of course it will work if it guaranteed the interrupt may change only other (in this example - lower) bits of the PORTD. If the interrupt can also write in the same bits, then this may cause unexpected results, so, be careful.