cmakefilearduinoserial-portavrdude

Serial port monitoring crashes LCD screen (TC1602A-21T(R)(3))


I'm having the most befuddling issue with my LCD screen (TC1602A-21T(R)(3)). This is the screen sold with Arduino kits.

Here's the context: I'm performing bare metal programming on the ATMega328p on the Arduino UNO. So this is just C and a Makefile with avrdude, no Arduino libraries. My objective is to make a very basic keyboard-like device where the user uses pushbuttons to cycle a character on the LCD screen, save the character (sort of like old flip phones where you had to cycle through characters with the 0-9 buttons), and then the third pushbutton would send the word the user built up over the ATMega328p's UART, over USB, to a PC with a puTTY terminal monitoring /dev/ttyACM0. As of right now, my testing is with two of the three buttons: one to cycle a character on screen, the other to send it.

I independently verified that the UART communication works in a different project and learned to successfully control the LCD (based on the HD44780U driver & datasheet), so I know those components work fine by themselves. Putting them together, I am able to cycle characters on the LCD and send them over the UART-to-USB connection with the PC, but as soon as I connect puTTY or the Arduino serial monitor to /dev/ttyACM0, the LCD is trashed. I lose the ability to cycle characters on the screen with my pushbutton, but I've verified that the pushbutton itself is working and that the program properly monitors the input voltage by flashing the onboard LED when the voltage on my input pin is driven high (which should trigger a character cycle). I've got all buttons paired with 4.7k pull-down resistors, so I think I've ruled out floating voltages. I can also send characters, but only the default letter 'A' my code will send if all else fails. So UART still works, but not my LCD.

Now here's the kicker: copy-pasting my C file into Arduino IDE and flashing via the Arduino flash tool (which I understand to employ avrdude on Linux) writes the program to the LCD and the problem disappears! Communication is not crashed when monitoring over puTTY or the Arduino serial monitor.

I suspect then that the issue lies in my Makefile or in the way I interact with the Arduino bootloader (or rather the lack thereof). I know there's a second USB bridge coprocessor on the UNO that I'm not touching in any of this development, so it's a big unknown. I'm hoping its the Makefile because that's readily correctable. I can successfully compile and flash with this file, but with the above problem.

CC = avr-gcc
CFLAGS = -g -mmcu=atmega328p -DF_CPU=16000000UL 
OBJCOPY = avr-objcopy
AVR = sudo avrdude
AVRFLAGS = -F -v -p m328p -c arduino -P /dev/ttyACM0 -U flash:w:lcd.hex

default: lcd.elf
    ${OBJCOPY} -O ihex lcd.elf lcd.hex

lcd.o:
    ${CC} ${CFLAGS} -Os -c lcd.c -o lcd.o

lcd.elf: lcd.o
    ${CC} ${CFLAGS} -o lcd.elf lcd.o

flash:
    ${AVR} ${AVRFLAGS} lcd.hex

clean:
    rm -rf *.o *.elf *.hex

For reference, I've also added the UART setup and main driver code of my program, redacting the (large amount) of LCD setup code done. Since everything works when flashing with the Arduino tool, I am making the conservative assumption that I'm not bombing my LCD with my setup and that I've read the datasheet right.

Here is the UART setup, based on the ATMega328p datasheet:

void USART_Init()
{
   
    /* 
       set baud rate. Baud prescaler is #def'ed to (((F_CPU / (BAUD_RATE * 16UL))) - 1u)
       formula from Table 19-1 of ATMega328p datasheet
    */
    UBRR0H = (uint8_t)(BAUD_PRESCALER >> 8u);
    UBRR0L = (uint8_t)(BAUD_PRESCALER & 0xFFu);

    /* bit format 8N1: 8data, 1stop, no parity */
    UCSR0C |= (_BV(UCSZ01) | _BV(UCSZ00));

    /* enable Tx line. Also tested with Rx line enabled with no difference */
    UCSR0B |= _BV(TXEN0);
}

And here's the main code:

int main()
{
    /* LOW is #def to 0; HIGH #def to 1 in redacted code */
    uint8_t lastCharCycleState = LOW;
    uint8_t lastSendState = LOW;
    uint8_t pos = 0u;
    uint8_t letter = (uint8_t)'A';
    
    /* explicitly set PD7, PB1, PB0 to input */
    DDRD &= ~_BV(DDD7);

    /* currently unused third button */
    /* DDRB &= ~_BV(DDB1); */
    DDRB &= ~_BV(DDB0);

    // arbitrarily large wait time for testing
    _delay_ms(1000u);
    LCD_Configure();

    USART_Init();

    // arbitrarily large wait time to ensure everything is setup right
    _delay_ms(1000u);

    while (1u)
    {
        /* 
          button press defined as transition from low to high state. PROCESS
          macros are #def'ed in redacted code
        */
        if ((LOW == lastCharCycleState) && (HIGH == PROCESS_CYCLE_INPUT))
        {
            lastCharCycleState = HIGH;
            LCD_Write8Bits((uint8_t)DATA, letter);

            /* reset DDRAM address after it autoincrements from the above data write */
            LCD_Write8Bits((uint8_t)INSTRUCTION, (uint8_t)(0x80U | pos));
            
            /* using characters 32 - 126 to include spaces, letters, and common symbols */
            letter = (++letter % 126u);
            if (32u > letter)
            {
                letter = 32u;
            }
        }
        else if ((HIGH == lastCharCycleState) && (LOW == PROCESS_CYCLE_INPUT))
        {/* button release defined as transition from high to low state */
            lastCharCycleState = LOW;
        }

        if ((LOW == lastSendState) && (HIGH == PROCESS_SEND_INPUT))
        {
            USART_Transmit(letter);
            _delay_ms(5u);
            pos = 0u;
            lastSendState = HIGH;

            /* resets letter after display clear */
            letter = (uint8_t)'A';
            LCD_Write8Bits((uint8_t)INSTRUCTION, (uint8_t)CLEAR_DISPLAY);
        }
        else if ((HIGH == lastSendState) && (LOW == PROCESS_SEND_INPUT))
        {
            lastSendState = LOW;
        }

    }

    // should not reach this point
    return 0;
}

I've done some Googling but haven't found someone with this issue yet, and I'm still looking through avrdude documentation to see if I borked something. No luck as of yet.


Solution

  • As dimich explained, attaching puTTY to a serial port sent the DTR signal to my MPU, resetting it but not the LCD. Testing with a manual reset fixed the problem, so I just need to reset the LCD to restore normal functioning.