Menu

AVR128DA28 hardware I2C Bug - AVRDx TWI0

Ralf Pagel
2026-01-12
2026-01-23
  • Ralf Pagel

    Ralf Pagel - 2026-01-12

    Hello,

    I'm new to the forum and have only been working with GCB and the AVR128DA28 for a few weeks.

    The AVR128DA28 has a hardware bug at TWI (I2C).

    The Output Pin Override Does not Function as Expected:
    It overrides the output pin driver but not the output value when TWI is enabled. The output on the
    line will always be high when the value in the PORTx.OUT register is ‘1’ for the pins corresponding to
    the SDA or SCL.
    Work Around:
    Ensure that the values in the PORTx.OUT register corresponding to the SCL and SDA pins are ‘0’
    before enabling the TWI.

    (Source: AVR128DA-28-32-48-64-SilConErrataClarif-DS80000882.pdf)

    So hardware i2c does not work with AVR128DA28 in GCB, but I found a workaround for GCB. I only tested this with the AVR128DA28, but it should also work with the other chips affected by the error.

    The attached file contains the necessary additions.

     
  • Anobium

    Anobium - 2026-01-12

    Ralf - a great post. I have reviewed the change.

    Very good catch. Looks like I got away with this when I was testing. :-)


    Yes, the code that you add ( quite correctly ) sets HI2C_DATA = 0 and HI2C_CLOCK = 0 (and any preceding bit-banging recovery code in AVRDXTWI0MODE) is required for reliable hardware I²C/TWI operation on the mega4809.

    Why it is required

    1. Potential I²C bus lock-up on AVR devices
      The AVR hardware TWI module (especially on the DX series like mega4809) can encounter a "stuck bus" condition at power-up, after a reset, or if a previous transaction was interrupted. A slave device may hold SDA low, preventing normal operation. The hardware TWI peripheral cannot recover from this on its own – it will simply hang or report bus errors.

    2. Standard recovery technique requires bit-banging
      The only reliable way to clear a stuck bus is to manually bit-bang clock pulses on SCL while monitoring/forcing SDA:

    3. Configure SCL as output and toggle it (typically 9+ times) while keeping SDA as input or forcing it high.
    4. This allows any slave holding SDA low to complete its byte and release the line.
    5. After recovery, a STOP condition is generated (SCL high → SDA high).

    GCBASIC's AVRDXTWI0MODE routine implements exactly this recovery sequence using bit-banging before enabling the hardware TWI peripheral.

    1. Role of setting HI2C_DATA = 0 and HI2C_CLOCK = 0
      These lines are part of the recovery/initialisation sequence:
    2. They force both lines low initially (as outputs) to ensure a known state.
    3. Then the bit-banging code toggles SCL while pulling SDA high (or leaving it as input with pull-up) to generate clock pulses.
    4. After recovery, the pins are reconfigured for hardware TWI use (open-drain with pull-ups).

    Without forcing the lines low first, the recovery sequence could start in an undefined state and fail.

    1. Why not rely on hardware TWI alone?
      The mega4809 TWI peripheral has no built-in bus recovery. Microchip's application notes (e.g., AN3122 on TWI bus recovery) and AVR forums consistently recommend manual clocking (bit-banging) before enabling the peripheral. GCBASIC follows this best practice.

    2. Consequences of removing it

    3. On a clean power-up with no prior issues → it might work anyway.
    4. In real-world scenarios (brown-out, incomplete transaction, slave malfunction) → the TWI will lock up, HI2CStart/HI2CSend will timeout or fail, and the GLCD will not initialise correctly.

    Conclusion

    The bit-banging recovery (including the HI2C_DATA = 0 / HI2C_CLOCK = 0 lines) is required for robust operation. It is not redundant – it protects against common real-world bus lock-up conditions that the hardware TWI module cannot handle itself. Removing it would make the demo (and any hardware I²C code) fragile and prone to intermittent failures. This is why GCBASIC includes it automatically when HI2CMode Master is used on AVR DX devices.

    I will change the standard library immediately and you will see this in the next release. See attached for the .h that I will submit to the next release.

    Evan/Anobium

     
  • Anobium

    Anobium - 2026-01-12

    Any build after 1547 will have this revised library.

    :-)

     
  • Ralf Pagel

    Ralf Pagel - 2026-01-12

    Hi Evan,

    Thank you for the quick response.

    No doubt, the bit-banging recovery is required for robust operation.

    I tested your new file with my AVR128DA28 and – unfortunately, it doesn't work.

    The AVR128DA28 has a bug as described above. I had hoped that my posted suggestion would both provide a workaround for the bug and prevent that two transistors working against each other on the I2C bus (the slave pulls to GND and the AVR pulls to +5V). At best, this results in half the operating voltage on the bus, and at worst, it can damage the output drivers on the chips.

    Is there a plausible reason why the bit-bang doesn't use the direction registers as I demonstrated?

    If that's the case, and you continue using the SDA + SDL outputs as before, you can also work around the hardware bug as follows:
    After the bit bang, set both port pins to input (the bus remains unchanged) and then set both outputs to low before accessing the TWI0 registers. I haven't tested this myself, but it corresponds to the workaround mentioned by Microchip.

    Ralf

     
    • Anobium

      Anobium - 2026-01-12

      OK.

      Do you have 4k (ish) pull-ups?
      Make changes in the library to get this working? or, are you saying you new does work?

       
  • Ralf Pagel

    Ralf Pagel - 2026-01-12

    Yes, there are two 4.7kΩ pull-up resistors (adapter: YwRobot LCD1602 IIC).

    It works for me with the subroutine I posted in my first post.

    I copied the template for this subroutine from your hwi2c.h library. Using a trick I found in your help file, it replaces the subroutine AVRDXTWI0MODE (which is otherwise found in the ASM file).

    In my (replacement) subroutine, I made changes to the bus drivers for the bit bang so that the I2C ports on the AVR128DA28 function as open-collector outputs. A nice side effect is that the hardware bug in the AVR128DA28 no longer occurs because the OUT registers of the I2C ports always remain low.

    And, sorry, after you posted the modified hwi2c.h file, I thought you had incorporated my changes exactly and I could now do without the trick from your help file.

    Regards,
    Ralf

     
  • Anobium

    Anobium - 2026-01-13

    Let us get you change into the main library. Can you try and adapt the .h post be me previously?

    I will then incorporate here.

     
  • Ralf Pagel

    Ralf Pagel - 2026-01-13

    Hi,

    The modified library hwi2c.h is attached.

    Thank you.

     
  • Anobium

    Anobium - 2026-01-13

    Thanks. I do see a few more changes.

    // Bit bang a START/STOP sequence
      Dir HI2C_DATA In                // SDA and SCL idle high
      Dir HI2C_CLOCK In
      Wait 1 us
      Dir HI2C_DATA Out               // then, SDA low while SCL still high
      Wait 1 us                       // for this amount of time
      Dir HI2C_CLOCK Out              // end with SCL low, ready to clock
      Dir HI2C_DATA Out
      Wait 1 us                       // let ports settle
      Dir HI2C_CLOCK In               // make SCL=1 first
      Wait 1 us                       // hold for normal clock width time
      Dir HI2C_DATA In                // then make SDA=1 afterwards
      wait 1 ms
    

    I will setup other AVRs to test this has not broken anything. I completed extensive tests on the MEGA4809 family. It will be easy to isolate these different IC2 pieces of code if required.

    Good work.

    Evan

     
  • Ralf Pagel

    Ralf Pagel - 2026-01-14

    ... it's not over now.

    Unfortunately, I had to make another change to the hwi2c.h library.

    The following error occurred:
    During a continuous test, the LCD displayed strange characters after about half an hour.

    I then changed the following:

      Do
    
          TWI0Timeout = 0
    
          Dir HI2C_DATA IN     // <- this I set to "IN"
          Dir HI2C_CLOCK IN    // <- this I set to "IN"
    
          HI2C_DATA = 0                         
          HI2C_CLOCK = 0                        
    
          Do 
    
            If TWI0Timeout = 255 then Exit Sub  // Users can check for TWI0Timeout = TRUE
            TWI0Timeout++
    
            // Reset the TWI!
            TWI0_MCTRLB = TWI_FLUSH_bm
            TWI0_MSTATUS= 0
            TWI0_CTRLA = 0
            TWI0_MCTRLA = 0
            TWI0_MCTRLB = 0
    
            // Bit bang a START/STOP sequence
    

    With this, I have now tested the LCD for 12 hours without any errors.

    The update is attached.

    Ralf

     
  • Anobium

    Anobium - 2026-01-14

    Why would that change give stability? The reset sequence needs the ports to be output to drive the lines high and low. So, the change would now stop the reset from working.

    The problem may be related have the I2C/TWI working/operational when the reset sequence operating. I have looked into that and this could cause issues.

    Try this - this caches the setting. We need to cache/restore as the I2CInit a prior method.

    Sub AVRDxTWI0Mode
    
        Dim HI2CCurrentMode as Byte
        Dim TWI0Timeout as Byte Alias HI2CWaitMSSPTimeout
    
        Dim HI2C1StateMachine as byte
        Dim HI2CACKPOLLSTATE  as Byte
        Dim TWI0ACKPOLLSTATE  as Byte Alias HI2CACKPOLLSTATE
        Dim HI2C1lastError as Byte
        Dim TWI0LastError as Byte Alias HI2C1lastError
        Dim TWIStateCache as Byte Alias HI2C1lastError
    
        HI2CCurrentMode = 0
        TWI0Timeout = 0
    
        TWIStateCache = TWI0_MCTRLA
        If TWI0_MCTRLA.TWI_ENABLE_bp = 1 Then
          // Disable TWI explicitly, but, the state must 
          TWI0_MCTRLA.TWI_ENABLE_bp = 0
        End If
        Dir HI2C_DATA OUT
        Dir HI2C_CLOCK OUT
    
        Do
    
          TWI0Timeout = 0
    
          HI2C_DATA = 0                         
          HI2C_CLOCK = 0                        
    
          Do 
    
            If TWI0Timeout = 255 then Exit Sub  // Users can check for TWI0Timeout = TRUE
            TWI0Timeout++
    
            // Reset the TWI!
            TWI0_MCTRLB = TWI_FLUSH_bm
            TWI0_MSTATUS= 0
            TWI0_CTRLA = 0
            TWI0_MCTRLA = 0
            TWI0_MCTRLB = 0
    
            // Bit bang a START/STOP sequence
              Dir HI2C_DATA In                // SDA and SCL idle high
              Dir HI2C_CLOCK In
              Wait 1 us
              Dir HI2C_DATA Out               // then, SDA low while SCL still high
              Wait 1 us                       // for this amount of time
              Dir HI2C_CLOCK Out              // end with SCL low, ready to clock
              Dir HI2C_DATA Out
              Wait 1 us                       // let ports settle
              Dir HI2C_CLOCK In               // make SCL=1 first
              Wait 1 us                       // hold for normal clock width time
              Dir HI2C_DATA In                // then make SDA=1 afterwards
              wait 1 ms
    
          Loop While ( TWI0_MSTATUS and 3 ) = 3
    
          TWI0_CTRLA = SCRIPT_TWI_FAST_MODE  // for slow mode = 0 
    
          TWI0_DUALCTRL = 0
    
          //Debug Run
          TWI0_DBGCTRL = 0x00
    
          //Master Baud Rate Control
          TWI0_MBAUD = SCRIPT_TWI_BAUD //(uint8_t)TWI0_BAUD(100000, 0)
    
          TWI0_MCTRLA = 0x02
    
          TWI0_MSTATUS = 0x61
    
          //Master Address
          TWI0_MADDR = 0x00
    
          //FLUSH  ACKACT ACK MCMD NOACT 
          TWI0_MCTRLB = 0x08
    
          //Master Data
          TWI0_MDATA = 0x00
    
          TWI0_MCTRLA.0 = 1
    
          wait 10 ms
    
        Loop While ( TWI0_MSTATUS and 3 ) = 3
    
        // Restore the cache
        TWI0_MCTRLA = TWIStateCache
    
    End sub
    
     
  • Ralf Pagel

    Ralf Pagel - 2026-01-14

    Hi,

    Meanwhile, my LCD was also displaying strange characters with my last change. As you rightly suspected, this error has nothing to do with what I changed last. Unfortunately, errors that only occur sporadically are the hardest to find. I can only try different things and do a lot of testing. Perhaps it's also due to my display. I've now connected my YwRobot.


    Why would that change give stability? The reset sequence needs the ports to be output to drive the lines high and low. So, the change would now stop the reset from working.

    Yes, sorry, that was my mistake. It should actually be changed as shown below. I wanted to avoid the current spike that could occur if the slave pulls the bus low while the AVR's output pin is still high. Does it thus (hown below) the requirements of the reset sequence? It meets the requirements of the AVRs regarding the hardware error. And a short on the I2C bus can no longer occur.

        Do
    
          TWI0Timeout = 0
    
          HI2C_DATA = 0                         
          HI2C_CLOCK = 0                        
    
          Dir HI2C_DATA OUT
          Dir HI2C_CLOCK OUT
    
          Do 
    

    We need to cache/restore as the I2CInit a prior method.

    I don't understand why you're saving the state of TWI0_MCTRLA at the beginning of the subroutine and restoring it at the end. TWI0_MCTRLA is modified multiple times between these two commands. These changes were previously available to the rest of the program. Now, the state of TWI0_MCTRLA before the subroutine was executed is available to the rest of the program. Is this intentional?

    Ralf

     
    • Anobium

      Anobium - 2026-01-14

      From my experience I would increase the intra byte delays. 1 ms can make a huge difference, or try a slower LCDSpeed

      re Caching. The Caching now supports reusing this mode function to clear an I2C. Whatever the state was, it is reverted. Costs no RAM and only a few clock cycles.

      So, to resolve. Look at the delays.

       
  • Ralf Pagel

    Ralf Pagel - 2026-01-22

    Hi Evan,

    I recently bought a different LCD2004 display and thoroughly tested the program with it. The strange characters didn't reappear on the new display. However, when I reconnected my old LCD2004 display, the strange characters reappeared at irregular intervals. Therefore, I assume it's a hardware defect in my display.

    I also included the subroutine AVRDxTWI0Mode with "caching/restoring TWI0_MCTRLA" in the file hwi2c.h and tested it. For the test, I used an AVR128DA28. The program didn't run with it. Therefore, I added a few more lines of code. The TWI0_MCTRLA register will now only be restored if it has been backed up beforehand. This prevents a random value from being written to TWI0_MCTRLA. Now the program runs. I have marked the changed lines with a comment.

    This version runs flawlessly on my AVR128DA28 and meets the requirements of the I2C bus. I hope this change doesn't hinder the "I2CInit a prior method". Unfortunately, I cannot judge that because I am not familiar enough with the internal functions of GCB.

    Ralf

    Sub AVRDxTWI0Mode
    
        Dim HI2CCurrentMode as Byte
        Dim TWI0Timeout as Byte Alias HI2CWaitMSSPTimeout
    
        Dim HI2C1StateMachine as byte
        Dim HI2CACKPOLLSTATE  as Byte
        Dim TWI0ACKPOLLSTATE  as Byte Alias HI2CACKPOLLSTATE
        Dim HI2C1lastError as Byte
        Dim TWI0LastError as Byte Alias HI2C1lastError
        Dim TWIStateCache as Byte Alias HI2C1lastError
    
        HI2CCurrentMode = 0
        TWI0Timeout = 0
    
        // Set the output driver to LOW first before configuring them 
        // as outputs, otherwise a short circuit may occur.
        HI2C_DATA = 0       // added by Ralf
        HI2C_CLOCK = 0      // added by Ralf
    
        TWIStateCache = TWI0_MCTRLA
        If TWI0_MCTRLA.TWI_ENABLE_bp = 1 Then
          // Disable TWI explicitly, but, the state must 
          TWI0_MCTRLA.TWI_ENABLE_bp = 0
        End If
    
        Dir HI2C_DATA OUT
        Dir HI2C_CLOCK OUT
    
        Do
    
          TWI0Timeout = 0
    
          HI2C_DATA = 0                         
          HI2C_CLOCK = 0                        
    
          Do 
    
            If TWI0Timeout = 255 then Exit Sub  // Users can check for TWI0Timeout = TRUE
            TWI0Timeout++
    
            // Reset the TWI!
            TWI0_MCTRLB = TWI_FLUSH_bm
            TWI0_MSTATUS= 0
            TWI0_CTRLA = 0
            TWI0_MCTRLA = 0
            TWI0_MCTRLB = 0
    
            // Bit bang a START/STOP sequence
              Dir HI2C_DATA In                // SDA and SCL idle high
              Dir HI2C_CLOCK In
              Wait 1 us
              Dir HI2C_DATA Out               // then, SDA low while SCL still high
              Wait 1 us                       // for this amount of time
              Dir HI2C_CLOCK Out              // end with SCL low, ready to clock
              Dir HI2C_DATA Out
              Wait 1 us                       // let ports settle
              Dir HI2C_CLOCK In               // make SCL=1 first
              Wait 1 us                       // hold for normal clock width time
              Dir HI2C_DATA In                // then make SDA=1 afterwards
              wait 1 ms
    
          Loop While ( TWI0_MSTATUS and 3 ) = 3
    
          TWI0_CTRLA = SCRIPT_TWI_FAST_MODE  // for slow mode = 0 
    
          TWI0_DUALCTRL = 0
    
          //Debug Run
          TWI0_DBGCTRL = 0x00
    
          //Master Baud Rate Control
          TWI0_MBAUD = SCRIPT_TWI_BAUD //(uint8_t)TWI0_BAUD(100000, 0)
    
          TWI0_MCTRLA = 0x02
    
          TWI0_MSTATUS = 0x61
    
          //Master Address
          TWI0_MADDR = 0x00
    
          //FLUSH  ACKACT ACK MCMD NOACT 
          TWI0_MCTRLB = 0x08
    
          //Master Data
          TWI0_MDATA = 0x00
    
          TWI0_MCTRLA.0 = 1
    
          wait 10 ms
    
        Loop While ( TWI0_MSTATUS and 3 ) = 3
    
        // Restore cache - if data is cached
        If TWIStateCache.TWI_ENABLE_bp = 1 Then // added by Ralf
          TWI0_MCTRLA = TWIStateCache
        End If                                  // added by Ralf
    
    End sub
    
     
  • Anobium

    Anobium - 2026-01-23

    Hi Ralf,

    First, really well done on isolating the LCD issue — testing with a second module was exactly the right diagnostic step, and it’s great that you were able to confirm the behaviour so clearly. That kind of methodical approach always pays off.

    On the TWI side, your routine does reset the peripheral, but it’s worth spelling out exactly what’s happening so the behaviour is fully clear.

    What your code resets correctly

    Inside the routine, you explicitly clear:

    • TWI0_MCTRLB (with TWI_FLUSH_bm)
    • TWI0_MSTATUS
    • TWI0_CTRLA
    • TWI0_MCTRLA
    • TWI0_MCTRLB again

    This sequence forces the TWI master state machine back to its idle state, clears pending commands, and removes any lingering status flags. In other words, the internal TWI hardware is fully reset, and that part is absolutely correct.

    What happens on the bus lines

    The more subtle part is the bus‑line recovery. Your routine:

    • Drives SDA/SCL low briefly
    • Then switches both pins to input, allowing them to float high via pull‑ups
    • Then toggles direction to create transitions

    This works because the pull‑ups restore the lines to a valid idle level. It’s a practical approach, and in many real‑world cases it’s enough to get the bus unstuck.

    Microchip’s recommended I²C bus‑clear procedure — described in application notes such as AVR315: Using the TWI Module as I²C Master — is more explicit. The official sequence is:

    1. Drive SCL high (not float it).
    2. Pulse SCL low→high up to 9 times to release a slave that may be holding SDA low.
    3. Drive SDA high.
    4. Generate a STOP condition (SDA rising while SCL is high).

    Your routine doesn’t actively drive the lines high; instead, it releases them and relies on the pull‑ups. That’s why it works, but it’s not the full, guaranteed bus‑clear method recommended by Microchip.

    Where this leaves things

    Given that:

    • your hardware behaves correctly with this approach,
    • the TWI peripheral is fully reset, and
    • the floating‑line recovery is sufficient for your setup,

    I’m completely happy to leave your implementation as‑is. It’s a practical solution, and it clearly works reliably for your environment.

    If you ever want to revisit the bus‑clear logic in the future, the AVR315 sequence is the reference point — but there’s no need to change anything right now.

     
  • Ralf Pagel

    Ralf Pagel - 2026-01-23

    Hi Evan,

    Thank you very much for the detailed explanations.

    On which page of the AVR315 application note did you read about the I²C bus-clear procedure? I can't find anything about it there. Is it possible that you read about it somewhere else?

    In certain applications, series resistors (e.g., 220 ohms) are installed in the I²C bus to protect the driver transistors (and prevent reflections on the bus). In this case, safe push-high operation is of course possible with the I²C bus-clear procedure.

    My hardware behaves correctly with this approach.
    GCB should be used with this modification, and if users report errors, I will gladly revise the subroutine.

    Cheers
    Ralf

     
    • Anobium

      Anobium - 2026-01-23

      The AVR315 is the reference document. The reset uses turns off TWI and then issues STOPs etc. This cannot be achieved with the either the SDA or SCL floating.

       

Log in to post a comment.