iron's blog

Using the ATtiny404

The ATTiny404 is a modern AVR microcontroller, with the date on the manual stating 2021. If you are used to older AVR chips such as the ATmega328, ATmega8u2 or the ATtiny88, the ATtiny404 works almost entirely different. To get started, it is (probably) incompatible with your existing flashing tools. The usbasp will not work as it uses a new protocol called UPDI (which only uses three wires: +v, data and -v). If you are used to setting fuses, forget it, there are no fuses anymore on these modern AVR chips. And for the final cherry on top; the supplied C++ headers have a totally different API.

Programming using UPDI

Programming the ATtiny404 is fairly staight forward if you have an UPDI programmer. But you probably don’t have one. That is why you can transform any old Arduino nano into a small programmer. The JTAG2UPDI project can be used together with a simply guide. When the UPDI programmer is assembled, the rest is plug and play. An example Platformio config looks as follows:

[env:ATtiny404]
platform = atmelmegaavr
board = ATtiny404
framework = arduino
upload_port = /dev/ttyUSB0
build_flags =
  -D F_CPU=20000000L

No fuses? How is the clock speed set?

This is actually a big improvement, no longer is your hardware cursed whenever you forget to check the fuses. All functionality which was previously set by fuses is now set in your code, similar to other register. Setting the cock speed to 20MHz is as simple as

_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSC20M_gc);
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0);

No more forgetting about fuses, or using fuse calculators to configure the microcontroller.

Converting UART to SPI

My first project with the ATtiny404 will be a conversion tool from UART to I2C. The UART device is an AJ-SR04M ultrasonic distance sensor. This device has an UART interface and spits out the distance it measure periodically. The ESP32 that I intend to use the sensor on only has a single UART interface, so it would make my life much easier if the sensor spoke I2C instead. An ATtiny404 is a cheap microcontroller which is perfectly capable of transforming UART to I2C.

First we setup the I2C and UART functionalities:

#define UART_BAUD_RATE (4 * F_CPU) / 9600

// UART
int8_t sigrow_val = SIGROW_OSC16ERR3V; // get calibration value
int32_t baud_setting = 8333;           // calculate baud register value
baud_setting *= (1024 + sigrow_val);   //
baud_setting /= 1024;                  //
USART0.BAUD = (uint16_t)baud_setting;  // set BAUD
USART0.CTRLA |= USART_RXCIE_bm;        // Enable USART0 RX complete interrupt
USART0.CTRLB |= USART_RXEN_bm;         // Enable RX functionality

// I2C
TWI0.SCTRLA |= TWI_APIEN_bm;  // Enable TWI Address interrupt
TWI0.SCTRLA |= TWI_DIEN_bm;   // Enable TWI Start/Stop interrupt
TWI0.SCTRLA |= TWI_PIEN_bm;   // Enable TWI Stop interrupt
TWI0.SCTRLA |= TWI_PMEN_bm;   // Enable TWI Adress recognition
TWI0.SCTRLB |= TWI_ACKACT_bm; // Enable TWI ACK
TWI0.SADDR = 0b00000001;      // I2C Address
TWI0.SCTRLA |= TWI_ENABLE_bm; // Enable TWI Slave

Then we store the distance sensor’s value whenever it sends it via UART

volatile static uint8_t last_state_high = 0;
volatile static uint8_t last_state_low = 0;

static volatile uint8_t usart_byte_index = 0;
ISR(USART0_RXC_vect) {
  if (USART0.STATUS & USART_RXCIF_bm) {
    volatile uint8_t datah = USART0.RXDATAH;
    volatile uint8_t datal = USART0.RXDATAL;

    switch (usart_byte_index) {
    case 1:
      last_state_low = datal;
      break;
    case 2:
      last_state_high = datal;
      break;

    default:
      break;
    }

    usart_byte_index++;
    if (usart_byte_index >= 4) {
      usart_byte_index = 0;
    }
  }
}

And then simply return it when our I2C interface is called

static void i2c_ack() { TWI0.SCTRLB = TWI_SCMD_RESPONSE_gc; }
static void i2c_nack() { TWI0.SCTRLB = TWI_SCMD_RESPONSE_gc | TWI_ACKACT_NACK_gc; }
static void i2c_stop() { TWI0.SCTRLB = TWI_SCMD_COMPTRANS_gc; }

ISR(TWI0_TWIS_vect) {
  static uint8_t i2c_byte_index;
  uint8_t status = TWI0.SSTATUS;

  if (status & TWI_DIF_bm) {
    if (status & TWI_DIR_bm) {
      switch (i2c_byte_index) {
      case 0:
        TWI0.SDATA = last_state_low;
        break;
      case 1:
        TWI0.SDATA = last_state_high;
        break;
      default:
        TWI0.SDATA = 0;
        break;
      }
      i2c_ack();
      i2c_byte_index++;
    }
  } else if (status & TWI_APIF_bm) {
    if (status & TWI_AP_ADR_gc) {
      i2c_ack();
      i2c_byte_index = 0;
    } else
      i2c_stop();
  }
}

And there you go, a simple UART to I2C interface implemented on an ATtiny404.

Thank you for reading this article.
If you spot any mistakes or if you would like to contact me, visit the contact page for more details.