Read the ATTiny24/44/84 datasheet and Program some board to do something (unusual)

The first part of this week is mostly reading and understanding what's happening within the ATTinys. I'll go over what I got from the chapters of this quite long datasheet.

Chapter 1: Pins
Description of the pins, mostly VCC, GND, RESET and the two ports A and B, accessed in code as 8 bit registers PORTA and PORTB.
Chapter 2: Overview
Overview of all components including a global block diagram.
Chapter 3: General Information
General mentions including where to find resources, the availability of code examples.
Chapter 4: CPU Core
Overall description of the components of the ATTiny CPU and how it works. It describes the Harvard architecture (data and code stored separately), the arithmetic logic unit (ALU), status register and general purpose registers, and stack pointer (mostly useful for compilation or writing assembly), the pipelined instruction execution (so that each instruction takes 1 clock cycle to be fetched and executed, modulo branching), reset and the basic interruption modes. Notably, all 32 individual registers are 8-bits, but the last three pairs can also be used for 16-bits operations (registers X, Y and Z).
Registers: SPH and SPL for the stack pointer, and SREG for the global AVR status.
Chapter 5: Memory
Overview of the different types of memory available. The programs instructions are in flash memory (~10000x writes); the data are in SRAM (~100000 writes); data that must be kept over reset / poweroff can use the EEPROM while taking care to avoid interrupts during write; and finally I/O and peripherals have their own memory block.
Registers: EEARH and EEARL for EEPROM addressing, EEDR for the EEPROM data to read/write, EECR for controlling the write/read access mode of EEPROM, and finally GPIORx (x=0,1,2) for general-purpose input/output registers.
Chapter 6: Clock Systems
Description of the different clocks being used by the chip as well as the different options of clock sources (internal D/C, with and without calibration, external crystal, low-frequency crystals), and the clock prescaler to save power and/or change the frequency in software.
Fuses: CKSEL for the type of clock to use, SUT for startup timing, and CKDIV8 for the initial state of the prescaler (frequency divided by 8 by default).
Registers: CKSEL for the type of clock to use, OSCCAL for oscillator calibration, and CLKPR for the clock prescaler.
Chapter 7: Power and Sleep Modes
Overview of the different sleep modes (towards less power): idle, ADC noise reduction, power-down, and stand-by; and explanation of the different wake-up sources (interruptions), as well as the specific ways to further reduce power consumption by disabling components.
Registers: MCUCR for controlling the type of sleep mode when using SLEEP(), and PRR for further power reduction abilities, as well as DIDR0 for disabling digital input on analog pins.
Chapter 8: System Control and Reset
Description of the different reset methods including the system procedure on reset (e.g. startup), the external reset, brown-out detection and watchdog reset.
Registers: MCUSR for flags describing which source triggered the reset (watchdog, external, brown-out or power-on), and WDTCSR for watchdog timer control and status.
Chapter 9: Interrupts
Descriptions of the interruption vector as the list of instructions for different interruptions at the beginning of the program space, addresses 0x00 (reset, by default) to 0x10.
Registers: MCUCR MCU control register with two last bits for mode of the external interruption on pin INT0 (level = low or any level, edge = on falling or rising edge), and GIMSK, GIFR and PCMSK0/1 for enabling some interruptions (beyond register SREG from Chapter 4).
Chapter 10: I/O Ports
Presentation of the main port (PORTA and PORTB) functions as well as their extended function possibilities. Examples of code for setting output pins, or enabling pull-up resistors on inputs (PORTA = (1 << PA1) | (1 << PA2); DDRA = (1 << DDA1);). Need for synchronization after switching direction (i.e. needs _NOP() or something else before reading from port after direction change). List of alternate functions for ports including analog functions, interrupt functions, USI, and SPI on port A; crystal, interrupt, clock output, debug wire and reset on port B. Port diagrams separating PORTXn, DDRXn and PINXn for data write/read, mode, and reading/toggling value.
Registers: MCUCR with global pull-up disable bit, PORTA, DDRA and PINA and similar counterparts for B
Chapters 11, 12 & 13: 8-bits and 16-bits timer/counters with PWM
Details of the implementation and components for both counting external events, and generating Pulse Width Modulation (PWM) waveforms using specific pins. PWM is useful for driving specific devices with varying speeds / intensity (e.g. LED intensity) while using only logic signals (0/1) with varying frequencies. Multiple modes are available: normal which does default timing / counting of events for interruptions, Clear Timer on Compare Match for specifying a specific count event for interruption; and Fast PWM using single-slope PWM, Phase Correct PWM using dual slopes ands its more advanced version Phase and Frequency Correct PWM. The prescaler can be reset to synchronize the timer/counter with program execution. An external clock can be used for the timer/counter, but then its frequency fclk_ext < fclk_I/O/2.5.
Registers: TCCRnx (x=A,B,C and n=0 for 8-bits and n=1 for 16-bits registers) for the timer-counter controls (compare-match output modes, and waveform generation modes); TCNTn for direct read/write access to the timer counter; OCRnx for the value to compare and match (for interruptions and/or waveform generation); TIMSKn for timer interruption masks, and TIFRn for interruption flags / states. Finally, GTCCR provide synchronization bits for the counter/timer and their prescaler.
Chapter 14: Universal Serial Interface (USI)
Overview of USI with a block diagram, explanation of the three- and two-wires modes of communication (SPI vs I2C) with the master taking care of the clock signal. In three-wires mode (SPI), the clock falling edges allow storing the next bit (out of 8), whereas the rising edge corresponds to the time for setting the next output bit. The communication is bidirectional so that read and write happen at the same time. After 16 edges (8 reads and 8 writes), one iteration of the transfer is done, and both sides have sent and received 8 bits, and an interruption happens. If using buffering, the next 8-bits iteration can starts; else both sides have to read the data as quickly as possible before starting another round. In two-wires mode (I2C), the clock is also driven by one master (but multiple are possible), and slaves follow the order of the master with unidirectional messages on the single data wire. The master leads by sending messages that either read, write or do a combination of both, with specific slave addresses (7-bits or 10-bits). Both clock and data lines are open-drain (either connected to GND = low, or not = floating). This means that each node can pull to ground, and safe transfers only happen when everyone agrees; consequently, newcomers can attach without creating short-circuits. A slave can pull the clock low for flow control (aka clock stretching, if needing time to read / write). Similarly, the data line can be pulled low to signal a transfer, which can lead to arbitration in case multiple masters are trying to transfer.
Registers: USICR for enabling interrupts, setting both wire and clock modes, USISR for flags, line status and counter value, USIDR where the transfer data is stored / received, and USIBR as read buffer.
Chapter 15: Analog Comparator
Details of the analog comparison function allowing interrupt events for specific analog values (by default using pins AIN0 and AIN1).
Registers: ACSR for control and status to enable, select modes of the comparator, and read interrupt flags, ADCSRB for using a specific pin of the ADC instead of AIN1, and DIDR0 to disable digital input buffers for AIN1/0 and thus reduce power consumption if not needing digital input functions.
Chapter 16: Analog to Digital Converter
The ADC module converts a single (or pair) of voltages with respect to a reference voltage into a 10-bits value. The voltage reference can either be VCC, pin AREF, or an internal 1.1V voltage. While multiple analog input channels are available, a single one can be converted to digital at a time, taking around 13 clock cycles for a conversion. The modes of conversion include Single Conversion which triggers a single conversion, ADC Auto Trigger which uses a trigger signal to start a conversion (and interrupt for the end of the conversion), including Free Running mode that continuously does conversion. ADC Noise Canceler mode can be used to avoid having circuitry running and influencing the ADC result, by doing the conversion while the CPU is paused, and waking-up conversion ends. The conversion result can be interpreted in three forms: Single Ended Conversion ADC=1024 VIN/VREF going from GND to Vref, Unipolar Differential Conversion ADC=1024 × GAIN × (VPOS - VNEG) / VREF with GAIN either 1 or 20 (36dB), or Bipolar Differential Comparison ADC=512 × GAIN × (VPOS - VNEG) / VREF. Finally, ADC can be used to read temperature using an on-chip temperature sensor using ADC8 channel.
Registers: ADMUX for selecting the voltage reference, the combination of analog inputs, and the gain to use; ADCSRA for enabling the ADC, starting conversions, enabling auto-trigger as well as interruptions and the corresponding flag, and selecting the ADC clock prescaler; ADCL/H containing the 10-bits conversion result; ADCSRB for selecting the auto-trigger source, result alignment and bipolar input mode (defaults to unipolar); and DIDR0 for disabling digital input on ADC-able pins.
Chapter 17: debugWire for On-Chip Debugging
The debugWIRE Enable fuse can be used to use the RESET pin as debug line. Specifics of the implementation are very "coarse" and delegate to the AVR Studio for using the debugging capabilities. TODO: figure out if there's something usable with GCC!
Chapters 18 and 19: Self-Programming and Memory-Programming (fuses)
These two chapters go into details about how to upload programs to the MCU and further describe the 3 fuse bytes that can be programmed either for programming purposes, or for specific design decision such as the clock source.
Fuses: Fuse Extended Byte containing mostly the SELFPRGEN bit for programming; Fuse High Byte as RSTDISBL,DWEN,SPIEN,WDTON,EESAVE,BODLEVEL1/2/3 for disabling external reset, enabling debugwire, enabling program download, setting whether watchdog timer is always on, whether EEPROM stay beyond programming, and the Brown-Out level; Fuse Low Byte as CKDIV8,CKOUT,SUT1,SUT0,CKSEL3/2/1/0 for dividing the clock frequency by 8, enabling clock output pin, selecting the startup timing, as well as the type of clock. Finally, the Device Signature Byte can be used to figure out which device we're dealing with (ATTIny 24/44/84).
End chapters
The remaining chapters go over electrical data including ratings, speed and other useful information of all components (chapters 20 and 21); they provide a register summary (chapter 22), and instruction set summary (chapter 23); as well as ordering (chapter 23), packaging (chapter 24) information and errata (chapter 25) and revision history (chapter 26).

Basic Programming

Before going on with USB programming, I first decided to test led and switches on my first board. Finding the correct constants is obvious given the datasheet (although it looked all completely mysterious before I had read it). In this case, I am using the two pins on the left side before the last at the bottom, i.e. PB2 for the led and PA7 for the switch.

The output corresponds to setting DDRB = 1 << PB2; and then outputting the desired voltage bit (low = 0, or high = 1) with PORTB = 1 << PB2;.

The input from the switch is connected to ground when not pressing, and disconnected when pressing (counterintuitive to me). This means that we want to use a pullup resistor, which we activate with PORTA = 1 << PA7; (without changing DDRA since default is input). Now, whenever we press the button, the reading from PINA will be 0, and by default it will be 1 << PA7.

I tested multiple variants (modes) of connecting the switch and the led:

#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>

typedef enum {
    ON_OFF,
    STATEFUL,
    BLINKING,
    CHANGING,
    PULSING
} SwitchMode;

int main(void) {
    //
    // main
    //
    static unsigned char switch_curr, switch_last;
    static unsigned char state;
    static unsigned char counter;
    static unsigned char i;
    
    SwitchMode mode = PULSING;
    
    //
    // set clock divider to /1
    //
    CLKPR = (1 << CLKPCE);
    CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);
    //
    // initialize output pins
    //
    PORTA = (1 << PA7); // enable pull-up for switch input
    DDRA  = 0; // all inputs on port A
    PORTB = (1 << PB2); // enable output for led
    DDRB  = (1 << PB2);
    
    // synchronize
    _delay_ms(1);
    
    // initial state
    switch_curr = switch_last = PINA & (1 << PA7);
    state = switch_curr;
    
    //
    // main loop
    //
    while (1) {
        // get switch state
        switch_curr = PINA & (1 << PA7);
        
        switch(mode){
            case ON_OFF:
                // use switch to decide whether to have led on or off
                state = switch_curr;
                break;
                
            case BLINKING:
                // just toggle state
                state = !state;
                _delay_ms(100);
                break;
                
            case CHANGING:
                // toggle but change the delay from counter
                state = !state;
                if(counter >= 100)
                    counter = 0;
                else
                    counter += 1;
                for(i = 0; i < counter; ++i)
                    _delay_ms(1);
                break;
                
            case PULSING:
                // toggle but change the delay from counter
                state = !state;
                // change off-timing
                if(state){
                    if(counter > 200)
                        counter = 0;
                    else
                        counter += 5;
                    for(i = 0; i < counter; ++i)
                        _delay_us(100);
                }
                break;
                
            case STATEFUL:
            default:
                // use change of state to invert led state
                if(switch_curr != switch_last && !!switch_last)
                    state = !state;
                break;
        }
        switch_last = switch_curr;
        
        // led output
        if(state)
            PORTB = 1 << PB2; // PORTB |= (1 << PB2); // led on
        else
            PORTB = 0; // &= ~(1 << PB2); // led off
        
        // wait a bit
        if(mode == PULSING)
            _delay_ms(1);
        else
            _delay_ms(1);
   }
}

USB Board

The recitation session over USB programming was very interesting and triggered my interest. I thus looked for tutorials beyond what Mike Specter had talked about and found the long detailed work of Andrew Mao who used an ATMega16U2 with Hardware USB (for easy programming over USB) to program his board to be a basic keyboard. One idea that went through my head was to play pong with the mouse, and potentially use the keyboard for extra inputs and/or outputting results and help information. The problem decomposition is as such:

  1. Create similar hardware USB board
  2. Test basic functionality and programming to ensure the board is working
  3. Test keyboard functionalities (first to write basic text, then to trigger shortcuts for opening programs)
  4. Find and test mouse functionalities (can we query its position? how do we move it?)
  5. Test HUB functionalities to enable using both keyboard and mouse (the keyboard to print instructions at startup, and the mouse for playing pong with the user.

For the board, I decided to use similar components to the ones of Andrew as a first attempt. The one component we do not have in stock is the 16 MHz crystal. At first, I asked Gavin and eventually we found out that there were extra crystals in the EDS stock, some of which were 16 MHz ones. I looked for the model on digikey and found the reference PLE SRMP49 which corresponds to a 16MHz crystal in series resonance. At first, I was slightly suprised since all crystals I had seen explanations for had a load capacitance that had to be matched "in parallel", and had made my design following this assumption (similarly to Andrew and Mike). Seemingly, multiple crystal circuit types exists including the more common "parallel resonance" and this less common "series resonance" that does not require any extra capacitor. However, at the same time, one of my colleague who I had asked for a 16 MHz crystal answered that he had a defective Arduino Uno I could harvest the crystal from. As a first attempt, I am going with the Arduino one since it's proven to work.

The second difference was the LED which, although very similar, looks slightly different. I found the reference CLV1A-FKB for the part in our stock and it seems that the orientation is slightly different. I ended up with the wrong orientation initially when populating the components, but easily swapped the corresponding resistor for the red led as I understood the correct orientation. This was my first personal usage of the heated tweezer, and it was quite fun and easy!

For the Eagle schematics, I wanted to use the USB pad, which was supposed to be in an extra SparkFun library. Eagle has a way to download these extra libraries and includes the "SparkFun" component library, or ... so I thought. Unfortunately, the library it downloads is actually not up to date. I had to download the one from the github repository to find the correct USB pad component in SparkFun-Connectors.lbr. At this point, I traced and cut the PCB. No major issue.

The soldering was less tricky than I thought. As suggested on some tutorials, I positioned the ATMega part first and then taped it to ensure I could more precisely align it before fully fixing it and soldering its pads. I eventually had solder connect multiple pads as expected and I tried to use the wick without any result again... I still can't use it propertly! However, this time I tried the slurping hot desoldering gun next to the tweezer and this is now my favorite desoldering tool. It does exactly what I want (and even slightly more intensely than I'd like it to, but removing too much is fine since adding solder back is much easier). I then tried to test the board by plugging it straight into a computer. Most computers I tried would trigger the green led showing that power was available, but they would not detect it as it should have (as DFU device). Debugging time!

Crystal and input issues

The first check was whether anything was shorted. Nothing seemingly was.

The second check was whether the power was distributed correctly and it seemed to be (5V between VCC and GND).

Then, I decided to check the clock signal (if it was possible to see it in action) using an oscilloscope. I didn't know whether the crystal was working since it could well have been the reason the Arduino I took it from was not working. That's where I saw that ... I saw nothing. I then probed the capacitors with the multimeter to check that my stacked 10 pF capacitors were summing correctly to 20 pF. As I was told later, measuring the capacitance cannot be done with a multimeter unless you extract the component since the remaining parts of the circuits influence the measured capacitance. However, this did show me the first problem which was that my capacitors were several of magnitude wrong. Indeed, I had picked 10 uF ones instead of the picofarad ones I should have!

I removed the stacked capacitors next to the crystal using the hot tweezer, and then checked again with the correct ones. The signal was now as expected! The only unexpected part was that the voltage to ground was only ~1.3V. This could have been an issue with power, but as it turns out, this is what I also measured on another Arduino UNO, so I assume this is how they work.

At this point, it was sadly not yet recognized as DFU device and I suspected that the data pins were not connecting. I decided to create a small USB pad extension (quickest milling done so far!). For this board, I designed it the simplest way with Gimp and using the trace of the previous board as reference for the pad size. The connection was done using a 4-pin header which I soldered on both sides.

At this point, the board shows up as DFU device! The rest will come as I go on with programming.