My journey with ATtiny4313

The context

For a personal project, I want to program an Atmel ATtiny4313 microcontroller. I use microcontrollers for several years, but not on a regular basis (maybe 3-4 times a year), mostly for testing and generally with a deceptive result. But failure isn't the best training?
This article aims to share my errors and success, as well as being a personal log.

The environment

Here is the environment I'm using:

  • Linux Ubuntu 20.04
  • Arduino IDE 1.8.19 installed as snap (containerized application)
  • Arduino Mini rev 5
  • Arduino usb2serial board
  • Breadboard

Programming the Programmer

The idea is to program the Arduino Mini to behave as an ISP, In-System Programmer, compatible with STK500. Once done, the ISP will communicate with the ATtiny via the SPI port.

  1. Place the components on the breadboard and do the wiring as follows:

    Note: To have a better timing, I had to add an 8 MHz crystal between pins 4 and 5, as well as 2 * 15pF capacitors. Refer to section 6.2.5 of the ATtiny 4313 datasheet. I also added a 100nF ceramic disk decoupling capacitor between pins 10 and 20, over the chip; maybe not strictly required, but at least it couldn't harm.

  2. Start Arduino IDE
  3. Select the board (in my case Arduino Mini)
  4. Select the programmer (in my case AVR ISP mkII)
  5. Load the sketch: File -> Examples -> 11.ArduinoISP -> ArduinoISP
  6. Edit the sketch if using the Arduino Mini: pin 11 and 12 must be swapped. The pinout of Arduino Mini is:
    Pin 10 = SS (used for RESET)
    Pin 11 = MISO
    Pin 12 = MOSI
    Pin 13 = SCK
    
  7. Click on upload
  8. Exit Arduino IDE

Install the Arduino-Tiny library

  1. Download the library from github.
  2. Extract it at the right place. In my case, it was: ~/snap/arduino/85/Arduino/hardware. It will create the folder tiny.
  3. Follow the instructions (also available here) to create boards.txt.
  4. I had to fix two lines in boards.txt:
    ...
    406   attiny4313at8.name=ATtiny4313 @ 8 MHz
    407 
    408   attiny4313at8.upload.tool=arduino:arduinoisp
    409   attiny4313at8.upload.using=arduino:arduinoisp
    ...
    434   attiny4313at1.name=ATtiny4313 @ 1 MHz
    435
    436   attiny4313at1.upload.tool=arduino:arduinoisp
    437   attiny4313at8.upload.using=arduino:arduinoisp
    ...
    

Burn the ATtiny bootloader

As stated in Oscar Liang's blog, there is no real bootloader but simply a setting of fuses. However, this was the trickiest part which took many trials and errors. The standard way to proceed is :

  1. Start Arduino IDE
  2. Select the board: ATtiny4313 @ 8 MHz
  3. Select the programmer: Arduino as ISP
  4. Load the sketch: File -> Examples -> 01.Basic -> Blink
  5. Click on Tools -> Burn Bootloader
  6. Click on File -> Preferences -> Show verbose output during: and check both compilation and upload
As you might imagine it didn't work as first, I encountered many times the following message:
avrdude: Device signature = 0x000000
avrdude: Yikes!  Invalid device signature.
         Double check connections and try again, or use -F to override
         this check.
What to do with that? Actually, the Arduino IDE uses avrdude to program the chip; we can use avrdude directly (without the IDE) to have more controls.
  1. Open a terminal
  2. To ease my trial-and-error, I defined an alias and a variable:
    $ alias avrdude='/snap/arduino/85/hardware/tools/avr/bin/avrdude -vvvv'
    $ export CONF=/snap/arduino/85/hardware/tools/avr/etc/avrdude.conf
    
  3. Then, I was able to start avrdude like this:
    $ avrdude -C ${CONF} -p attiny4313 -c stk500v1 -P /dev/ttyACM0 -b19200 -t
    
    The meaning of options are:
    -vvvv             <- very verbose 
    -p attiny4313     <- type of target 
    -c stk500v1       <- programmer id.
    -P /dev/ttyACM0   <- USB port
    -b 19200          <- baudrate
    -e                <- erase the chip (1) 
    -Uefuse:w:0xFF:m  <- configure the extended fuse byte (2)
    -Uhfuse:w:0x9F:m  <- configure the high fuse byte (3)
    -Ulfuse:w:0xE4:m  <- configure the low fuse byte (4)
    

    Notes:
    1. actually fill the flash and the EEPROM with 0xFF
    2. The values configured for Extended fuse are
      Bit  Value Bit name    Description
      7    1     Bit 7       (unprogrammed)
      6    1     Bit 6       (unprogrammed)
      5    1     Bit 5       (unprogrammed)
      4    1     Bit 4       (unprogrammed)
      3    1     Bit 3       (unprogrammed)
      2    1     Bit 2       (unprogrammed)
      1    1     Bit 1       (unprogrammed)
      0    1     SELFPRGEN   Disable Self-Programming instruction
      
    3. The values configured for High fuse are
      Bit  Value Bit name    Description
      7    1     DWEN        Disable debugWIRE
      6    0     EESAVE      Allow EEPROM to be erased 
      5    0     SPIEN       Enables serial programming and downloading of data to device
      4    1     WDTON       Sets watchdog timer permanently on
      3    1     BODLEVEL2   Brown-Out Detection level:
      2    1     BODLEVEL1      111 = disabled
      1    1     BODLEVEL0   
      0    1     RSTDISBL    Do not disable external reset
        
    4. The values configured for Low fuse are
      Bit  Value Bit name    Description
      7    1     CKDIV8      Do not divides clock by 8 (hence 8 MHz)
      6    1     CKOUT       Output system clock on pin 6
      5    1     SUT1        Start-up time: slow rising power (default).
      4    0     SUT0           (6 + 14 clock ticks + 64 ms)
      3    0     CKSEL3      Clock select: 
      2    1     CKSEL2         0100 = Calibrated internal RC Oscillator 
      1    0     CKSEL1                (8 MHz)
      0    0     CKSEL0
          

  4. I had to reset the Arduino a couple of time, due to incorrect timing, but I always had errors. Then I setup the fuses manually like this:
    $ avrdude -C ${CONF} -p attiny4313 -c stk500v1 -P /dev/ttyACM0 -b19200  -F -t
    avrdude> sig
    >>> sig 
    Device signature = 0x1e920d
    
    Yes!!! This is the signature of ATtiny4313, as noted in the datasheet:

    For the ATtiny4313 the signature bytes are:
    1. 0x000: 0x1E (indicates manufactured by Atmel).
    2. 0x001: 0x92 (indicates 4KB Flash memory).
    3. 0x002: 0x0D (indicates ATtiny4313 device when 0x001 is 0x92)

    Now, we can setup the fuses:
    avrdude> d lfuse         <- dump the value of lfuse 
    0000  64   |d  |         <- current value 
    avrdude> w lfuse 0 0xe4  <- set the value to 0xE4. I had to change the value later (see below). 
    avrdude> d lfuse         <- check the value again 
    0000  e4                 <- Yes!! 
    avrdude> w hfuse 0 0x9f  <- set the value to hfuse 
    avrdude> d hfuse
    0000  9f                 <- Ok 
    avrdude> d efuse
    0000  ff                 <- Already the good value 
    

Blink

I put a LED and resistor on pin 9, and add the following line to blink.ino:

  #define LED_BUILTIN PIN_D5   
Blink did upload successfully and my LED started to blink. Yess!!

And now let's do MIDI

The purpose of this project is to replace a dead Soundpool MO4. In short, four ATTiny (one per MIDI out) must read the incomming data on the parallel port (MIDI message), determine who will manage the message and send it to the output via the USART.

The wiring

I decided to use a quad tri-state buffer (SN74LS125) to feed the MIDI current loop. The datasheet says to keep sinking below 24 mA, but does not mention if for the entire unit of per gate. Anyway, since the MIDI electrical characteritics says the current loop must be 5 mA, we are good. However, this means feeding the loop thru 2 * 500 ohm resistors, instead of 2 * 220 ohm resistors that we commonly see in schematics. Currently, I use 220 ohm since I use only one gate.
The schematics is:

The code

This is only a test code which plays a 8 note melody.

#include <avr/io.h>

// A little MIDI melody
uint8_t melody[8] = {0x24,0x27,0x2b,0x2d,0x30,0x2d,0x2b,0x27},
        note=0;
        
void setup() {
  UBRRH = (uint8_t)0x00;    // Set UBRR to divide by 16 in normal mode which in reality ...
  UBRRL = (uint8_t)0x0f;    //... divides the system clock (8MHz) by 256. 8000000 / 256 = 31250
  UCSRB = (1<<TXEN);        // Enable the transmitter, but not the receiver
  UCSRC = (1<<UCSZ1|1<<UCSZ0);  // 8 bits, no parity, 1 stop bit
}

void MIDIout(uint8_t n) {         // Play midi melody
  uint8_t i=(n & 0x07);
  
  UDR=(uint8_t)0x90;    	// Note ON channel 1
  UDR=melody[i];   			// Note
  UDR=(uint8_t)0x40;      	// Velocity
  
  delay(500);
    
  UDR=(uint8_t)0x80;    	// Note OFF channel 1
  UDR=melody[i];   			// Note
  UDR=(uint8_t)0x00;    	// Velocity
}

void loop() {
    MIDIout(note++);
}
Compile, upload and ... nothing! My synth stays silent. What's going on?

Hopefully, I have a digital oscilloscope (DSO), the fairly common Rigol DS1054z. Thanks to this video from Ben-tronics, I was able to find the issue:

A MIDI byte should be sent in 320 micro-seconds; in my case it span over 398 micro-seconds, way over the 1% precision required by the MIDI specs. The reason: I use the ATTiny internal oscillator, which is obvioulsy not calibrated. So I must change the low fuse and use an external crystal.

Adding a crystal

I added a 8 MHz crystal with 2 * 20 pF capacitors (the datasheet says 12 to 22 pF) and I changed the low fuse to sync the internal clock to the crystal:

$ avrdude -C ${CONF} -p attiny4313 -c stk500v1 -P /dev/ttyACM0 -b19200 -t
(...)
avrdude> w lfuse 0 0xcf
>>> w lfuse 0 0xcf   
With this change, my clock was tight and every byte has the correct timing:
I receive the note on (0x90) and note off (0x80), the following byte corresponds to the note from my melody, all right! But wait... Where is the third byte (velocity)? I did many tries, checked my code and the breadboard, but no luck: the third byte is always missing.

Don't send too fast...

A little glance at the datasheet on page 127 provides the explanation: you must wait for completion before sending a new byte. I was simply sending too fast. I fix the code, and the third byte appeared:

(...)
void Send(uint8_t data )
{
  while ( !( UCSRA & (1<:<:UDRE)) ) ;
  UDR = data;
}

void MIDIout(uint8_t n) {         // Play midi melody
  uint8_t i=(n & 0x07);
  
  Send(0x90);       // Note ON channel 1
  Send(melody[i]);  // Note
  Send(0x40);       // Velocity
  
  delay(500);
    
  Send(0x80);       // Note OFF channel 1
  Send(melody[i]);  // Note
  Send(0x00);       // Velocity
  delay(500);
}
(...)
But still no sound out of my synth (a Roland Sound Canvas SC8850). Hmm, what going on?
I reset the synth to its factory default, but still nothing. The synth works as I'm able to get the audio preview (by pushing the volume knob), but nothing when driven by MIDI, no matter which entry. I tested with another cable and another synth (a Novation XioSynth), checked the channels and the configuration on both, but I was not able to get any sound... I suspect both MIDI input opto-isolator (Sharp PC410) has burned during my testing. They both show 32 MOhm resistance on reverse, infinity on direct. Grr!

And finally...

To avoid another disaster, I used a MIDI Thru box (Korg KMT-60) between my breadboard and my synth. I connect another synth, set up the MIDI channel, and ... the little melody played!

Comments

Popular Posts