5

I'm building a breadboard Z80-based computer. As now, I have the CPU hooked up to an EEPROM and an I/O device (an HD44780 character display) with appropriate decoding logic.

The ROM chip starts at address 0x0000 while the I/O device exposes two registers at addresses 0x00 and 0x01 when the IORQ pin is active (low).

I wrote the following program:

;HD44780 LCD test procedure

LCD_INSTR_REG: EQU %00000000
LCD_DATA_REG: EQU %00000001

;Reset procedure
ld a,%00111000
out (LCD_INSTR_REG),a
ld a,%00001000
out (LCD_INSTR_REG),a
ld a,%00000001
out (LCD_INSTR_REG),a

;Init procedure
ld a,%00111000
out (LCD_INSTR_REG),a
ld a,%00001110
out (LCD_INSTR_REG),a

;Write characters to display
ld a,%01000100
out (LCD_DATA_REG),a
ld a,%01100001
out (LCD_DATA_REG),a
ld a,%01101110
out (LCD_DATA_REG),a
ld a,%01101001
out (LCD_DATA_REG),a
ld a,%01100101
out (LCD_DATA_REG),a
ld a,%01101100
out (LCD_DATA_REG),a
ld a,%01100101
out (LCD_DATA_REG),a
ld a,%00100001
out (LCD_DATA_REG),a

halt

That, once compiled with GNU z80asm, translates to the following code:

Machine code

You can see several 3E instructions that load an immediate value to register A, interleaved by D3 instructions that load to I/O register 00 (and then 01) the value present in register A. The last instruction (76) is the halt instruction.

The execution of the code causes the following bytes to appear on data bus (picked up with an Arduino used as a poor man's logic analyzer):

HD44780 debugger
DATA BUS    HEX     RS
00000000    0x00    I
00000000    0x00    I
00000000    0x00    I
00000000    0x00    I
00000000    0x00    I
00000000    0x00    D
00000000    0x00    D
00000000    0x00    D
00000000    0x00    D
00000000    0x00    D
00000000    0x00    D
00000000    0x00    D
00000000    0x00    D

Every line is the bus state at the IORQ descending edge (but I obtain the same result if I trigger my code on the rising edge).

You can see, from left to right: the value of the single data bus bits, the same value in hex, the value of A0, that select the I/O device register (instruction is LOW and data is HIGH).

As you can see, while the correct addresses are set, the CPU is writing all zeroes to the data bus. Did I misunderstand the way the OUT instruction works? Otherwise, what could be the problem in my code?

Update

I decided to accept the answer from Spektre as it is complete and argumentative. By the way, note that in my case the problem was not due to timings, but two swapped lines in the address bus (so the CPU was reading in a whole different memory address, probably reading NOPs or garbage). My CPU was running at 140 Hz and I'd like to point out that, after fixing the addr bus pins, the LCD screen worked perfectly up to 14 kHz without needing to check the BUSY bit.

Over a certain frequency, BTW, adding delays as proposed by Spektre was not enough, as the signal was not kept on the data bus long enough for the LCD to read it. As far as I can understand, the only way to make the LCD work at frequencies in the MHz range is to use some kind of latched buffers/transceivers to decouple the LCD EN frequency from the CPU frequency.

10
  • 4
    Do you have a schematic of your circuit? – knol Oct 17 '20 at 11:49
  • 1
    Especially: How's the E input for the display controller generated? – tofro Oct 17 '20 at 12:13
  • 1
    You also need to check the BUSY bit in the instruction register before sending anything to the display. – tofro Oct 17 '20 at 12:16
  • 1
    And there are more bus conditions for signalling the validity of the data bus for an I/O operation than just /IOREQ. – tofro Oct 17 '20 at 12:21
  • 1
    Besides the eternal zero: please pay attention to the timing of reset and init. It needs milliseconds to perform those operations, as far as I remember... – Martin Maly Oct 17 '20 at 12:54
3
  1. LCD

    as mentioned before your display init is not correct (too fast) you do not need to check busy flag you just need to add waits... This is my driver for LCD1602 on AVR32 which uses the same LCD controller using 4bit interface an 2x16 character LCD:

     //------------------------------------------------------------------------------------------
     #ifndef _LCD1602_h
     #define _LCD1602_h
     //------------------------------------------------------------------------------------------
     volatile avr32_gpio_port_t *LCD_gpio = &AVR32_GPIO.port[AVR32_PIN_PA31>>5];
     #define LCD_shift (AVR32_PIN_PA28&31)
     #define LCD_mask (15<<LCD_shift)
     //------------------------------------------------------------------------------------------
     enum _LCD_key_enum
         {
         _LCD_key_none=0,
         _LCD_key_left,
         _LCD_key_right,
         _LCD_key_up,
         _LCD_key_down,  
         };
     //------------------------------------------------------------------------------------------
     void LCD_write4bit(U8 x)    // (internal) write 4bit using 4bit interface
         {
         U32 m0,m1;
         // L
         m1=x&15; m1<<=LCD_shift; m0=m1^LCD_mask;
         LCD_gpio->ovrc =m0; // clr
         LCD_gpio->oders=m0;
         LCD_gpio->gpers=m0;
         LCD_gpio->ovrs =m1; // set
         LCD_gpio->oders=m1;
         LCD_gpio->gpers=m1;
         gpio_set_gpio_pin(AVR32_PIN_PB00); // E
         cpu_delay_us( 1,clk_cpu);
         gpio_clr_gpio_pin(AVR32_PIN_PB00); // E
         cpu_delay_us(40,clk_cpu);
         }
     //------------------------------------------------------------------------------------------
     void LCD_write(U8 x)        // (internal) write 8bit using 4bit interface
         {
         LCD_write4bit(x>>4);
         LCD_write4bit(x);
         cpu_delay_us(40,clk_cpu);
         }
     //------------------------------------------------------------------------------------------
     void LCD_init()             // init used GPIO and LCD
         {
         // ADC keyboard
         gpio_enable_module_pin(AVR32_ADC_AD_4_PIN,AVR32_ADC_AD_4_FUNCTION);
         sysclk_enable_peripheral_clock(&AVR32_ADC);
         adc_configure(&AVR32_ADC);
         adc_enable(&AVR32_ADC,4);
         // LCD
         gpio_configure_pin(AVR32_PIN_PA31,GPIO_DIR_OUTPUT|GPIO_PULL_UP);    // D7
         gpio_configure_pin(AVR32_PIN_PA30,GPIO_DIR_OUTPUT|GPIO_PULL_UP);    // D6
         gpio_configure_pin(AVR32_PIN_PA29,GPIO_DIR_OUTPUT|GPIO_PULL_UP);    // D5
         gpio_configure_pin(AVR32_PIN_PA28,GPIO_DIR_OUTPUT|GPIO_PULL_UP);    // D4
         gpio_configure_pin(AVR32_PIN_PB11,GPIO_DIR_OUTPUT|GPIO_PULL_UP);    // RS
         gpio_configure_pin(AVR32_PIN_PB00,GPIO_DIR_OUTPUT|GPIO_PULL_UP);    // E
         gpio_clr_gpio_pin(AVR32_PIN_PB11); // RS
         gpio_clr_gpio_pin(AVR32_PIN_PB00); // E
         cpu_delay_ms(50,clk_cpu);
         LCD_write4bit(0x03); cpu_delay_ms(  5,clk_cpu);
         LCD_write4bit(0x03); cpu_delay_us(150,clk_cpu);
         LCD_write4bit(0x03); cpu_delay_us(150,clk_cpu);
         LCD_write4bit(0x02); cpu_delay_ms(  1,clk_cpu);
         const U8 LCD_2LINE   =0x08;
         const U8 LCD_1LINE   =0x00;
         const U8 LCD_5x10DOTS=0x04;
         const U8 LCD_5x8DOTS =0x00;
         LCD_write(0x20|LCD_2LINE|LCD_5x8DOTS);  
         const U8 LCD_DISPLAYON =0x04;
         const U8 LCD_DISPLAYOFF=0x00;
         const U8 LCD_CURSORON  =0x02;
         const U8 LCD_CURSOROFF =0x00;
         const U8 LCD_BLINKON   =0x01;
         const U8 LCD_BLINKOFF  =0x00;
         LCD_write(0x08|LCD_DISPLAYON|LCD_CURSOROFF|LCD_BLINKOFF);
         LCD_write(0x01); cpu_delay_ms( 2,clk_cpu);
         const U8 LCD_ADRINC=0x02;
         const U8 LCD_ADRDEC=0x00;
         const U8 LCD_SHIFTON=0x01;
         const U8 LCD_SHIFTOFF=0x00;
         LCD_write(0x04|LCD_ADRINC|LCD_SHIFTOFF);
         }
     //------------------------------------------------------------------------------------------
     void LCD_clear()                // clear screen and set position to 0,0
         {
         LCD_write(0x01); cpu_delay_ms( 2,clk_cpu);
         }
     //------------------------------------------------------------------------------------------
     void LCD_gotoxy(U8 x,U8 y)      // set position to x,y
         {
         LCD_write(0x80+(y<<6)+x);
         }
     //------------------------------------------------------------------------------------------
     void LCD_prntxt(const char *txt)    // print txt at current position
         {
         gpio_set_gpio_pin(AVR32_PIN_PB11); // RS
         for (int i=0;(i<16)&&(txt[i]);i++) LCD_write(txt[i]);
         gpio_clr_gpio_pin(AVR32_PIN_PB11); // RS
         }
     //------------------------------------------------------------------------------------------
     void LCD_prndec(U32 num,int digits) // print num at current position as decimal number aligned to digit places or unaligned if digits<=0
         {
         #define _digits 10
         static char txt[_digits+1];
         int i,a,b,e;
         if (digits<=0)
             {
             if (num<10) digits=1;
             else if (num<100) digits=2;
             else if (num<1000) digits=3;
             else if (num<10000) digits=4;
             else if (num<100000) digits=5;
             else if (num<1000000) digits=6;
             else if (num<10000000) digits=7;
             else if (num<100000000) digits=8;
             else if (num<1000000000) digits=9;
             else                      digits=10;
             }
         if (digits>_digits) digits=_digits;
         if (digits<      1) digits=1;
         for (i=1,b=1;i<digits;i++,b*=10);
         for (e=0,i=0;i<digits;i++)
             {
             a=(num/b);
             num-=a*b;
             b/=10;
             e|=a; e=1;
             if (e) txt[i]=a+'0';
             }
         txt[i]=0;
         LCD_prntxt(txt);
         #undef _digits
         }
     //------------------------------------------------------------------------------------------
     U32 LCD_key()
         {
         adc_start(&AVR32_ADC);
         U32 adc=adc_get_value(&AVR32_ADC,4);
              if (adc<  0+10) return _LCD_key_right;
         else if (adc<127+10) return _LCD_key_up;
         else if (adc<510+10) return _LCD_key_down;
         else if (adc<896+10) return _LCD_key_left;
         return _LCD_key_none;
         }   
     //------------------------------------------------------------------------------------------
     #endif
     //------------------------------------------------------------------------------------------
    

    Just ignore the AVR32 stuff and pay attention to waits:

     cpu_delay_ms(x,y); // waits x[ms]
     cpu_delay_us(x,y); // waits x[us]
    

    where first parameter is the time you want to wait and second is CPU clock used. Usage is like this:

     LCD_init();
    
     LCD_clear();
     LCD_gotoxy(0,0);
     LCD_prntxt("bla bla");
     LCD_gotoxy(0,1);
     LCD_prntxt("bla ...");
    
  2. Zeros on DB

    Zeros might be most likely due to problem on data bus like missing pull ups or fighting between Arduino and Z80, or wrongly configured Arduino ports or by reading by Arduino at wrong times... So you need to check:

    • What are the clocks for: Z80 and Arduino (ATMega328P?)?
    • How did you interconnected?
    • How are you getting data from Arduino?

    you have to take in mind the IO data buss is active only for a very short time so you should use External Interrupts on Arduino side for reading. The timing is like this:

    IO timing

    so you should sample on falling edge of /IORQ + some small delay to stabilize /WR. If you are sampling on rising edge of /IORQ your Arduino might be too slow and read after the data bus is invalid...

3
  • 2
    I decided to accept this answer as is complete and argumentative. By the way, note that in my case the problem was not due to timings, but two swapped lines in the address bus (so the CPU was reading in a whole different memory address, probably reading NOPs or garbage). My CPU was running at 140Hz and I'd like to point out that, after fixing the addr bus pins, the LCD screen worked perfectly up to 14 Khz without needing to check the BUSY bit. – penguin86 Dec 13 '20 at 17:14
  • It might be useful to emphasize that reliably waiting on the busy flag generally requires either knowing that the display is in 8-bit mode (which might not be true on startup) or knowing which half-byte it's expecting to output next. It might be possible to write a routine which would work reliably without the fixed delay at start, but if e.g. a display whose address was set to 0x00 was processing a 4-bit-mode request to set the address to 0x08, repeated reads might alternate 0x80 and 0x00 while that request is being processed (display busy; address bit 3 is clear) and afterward... – supercat Dec 13 '20 at 17:56
  • (display wouldn't be busy, but address bit 3 would be set). I'd guess that there would be ways for clever code to cut the worst-case initialization time substantially by using status reads but acknowledging the possible quirks, but I doubt that would generally be worth the effort. – supercat Dec 13 '20 at 18:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.