cembeddedfpgamicroblaze

How to control a single bit within a variable?


I'm working on an LCD controller in a Microblaze soft CPU I've embedded in an FPGA. To connect to the top level Verilog file's outputs, I've used a single 8bit GPO.

In my C, I've used placeholder variables for each bit I'm controlling, for example:

LcdDataBus = (cmd & 0xF0);  //Send higher nibble
LCD_RS = 0;
LCD_RW = 0;
LCD_EN = 1;

If I were programming a PIC, I could simply define each as a pin on the uC, i.e. #define LCD_RS PORTA,0.

However, I believe I can only access the port through a function

data = XIOModule_Initialize(&gpo, XPAR_IOMODULE_0_DEVICE_ID);
data = XIOModule_Start(&gpo);

Where data is some variable.

Is there a way I could #define a single bit in data, so LCD_RS = bit0, LCD_RW = bit1, LCD_E = bit3, and my LCD data bus could be the next four bits?


Solution

  • As you want a very precise defined way of bit control, the safest way is to use read-modify-write.

    volatile uint32_t *reg_address;
    uint32_t data;
       data  = *reg_address;
       data |= (1<<LCD_RS_BIT_POS); // Set bit 
       // data &= ~(1<<LCD_RS_BIT_POS); // Clear bit 
       *reg_address = data;
    

    Alternative is to add bit-set/bit clear address locations to you LCD port register map (This is Verilog code):

    if (cpu_select && cpu_write)
      case (cpu_address[4:2])
      3'h0 : lcd_reg <= cpu_wdata[7:0]; // Direct write
      3'h1 : lcd_reg <= lcd_reg  |  cpu_wdata[7:0]; // set bit(s) write
      3'h2 : lcd_reg <= lcd_reg  & ~cpu_wdata[7:0]; // Clear bit(s) write
      ....
      endcase
    

    You asked for setting more bits.
    A basic approach is to clear all the bits you want to change, then set them again with the desired values.

    For example: You have a 4 bit value (bits 4,5,6,7) a R/W bit (2) plus an address bit (0). You turn those into a mask: 0b11110101.

       // Write to LCD
       data  = *reg_address;
       data &= ~0xF5; // Clear all the bits
       if (address==1)
         data |= (data_nibble<<4) | (1<<LCD_WRT_BIT_POS)  |(1<<LCD_ADR_BIT_POS) ; 
       else // Address 0 
         data |= (data_nibble<<4) | (1<<LCD_WRT_BIT_POS); 
       *reg_address = data;
    

    But....
    In your case you probably only need to control the 'E' signal separately. And you know that it should start low.The whole procedure becomes simpler:

       uint32_t data;
    
       // Set the data, R/Wnot and RS all at the same time 
       if (write_data)
          data = nibble_data<<4; // R/Wn=0, RS=0. E=0
       else // Write command 
          data = nibble_data<<4 | (1<<RS_BIT_POS); // R/Wn=0, RS=1, E=0
       *reg_address = data;
    
       // Pulse E high and low:
       *reg_address = data | (1<<E_BIT_POS); // E high
       some_kind_of_wait_routine(); // Wait a while
    
       *reg_address = data; // E low 
       some_kind_of_wait_routine();  // Wait a while to guarantee minimum low time!
    

    For a read you do not need to set the data but you must set the Read line high. However the LCD display can be operated without reading.