Montag, 23. Mai 2016

TWI (or I2C) communication

Introduction

The I2C protocol is used for communication between different parts (up to 127 participants) of a system using only two wires. I will show a very simple example with two ATMEGA8 microcontrollers that communicate via this protocol. Atmel calles this not I2C, but TWI (two wire interface). A detailed description is on page 157 of the ATMEGA8 datasheet. It is quite long and there are many ways of using the TWI interface. It takes a bit of time reading the section and understand what is going on. The example here can be used as a "quickstart" guide into this protocol and to get some working code as a starting point for own implementations.
The following picture shows the topology of a TWI network: all participants are connected to two wires that are held at VCC potential by two pull-up resistors. There is a clock line (SCL) and a data line (SDA). Communication happens always between exactly two participants at one time (with the exception of a general call, but I'll not describe that). The participant that initiates the communication is called "master" and adresses another participant which is called "slave". As part of each transmission, the master has to sent the address of the slave. All slaves are continuously monitoring their TWI lines and have to react if their address is called.

modified picture from the ATMEGA8 datasheet, showing the layout of my small example with one master and one slave, indicated in red
My example will be the following: one ATMEGA8 measures an analog voltage level with it's ADC and transmit the information over a TWI connection to another ATMEGA8 for visualization. The measuring ATMEGA8 will provide the data as a TWI slave and I give it the address 0x01. The displaying ATMEGA8 will be the master and initiates the transmission. In this configuration, my TWI slave will always be in a "slave transmit" mode, and my master will always be in a "master receive" mode. Other combinations "slave receive" and "master transmit" are also possible, but I want to keep the example small.
I will use the TWI in connection with interrupts, so some basic knowledge of interrupt mechanisms is needed to understand this example.
The following picture shows a schematic drawing of my setup. I didn't draw the connections to the ISP programmer. Note that the two devices really are only connected by the power lines and the two TWI wires (in blue).  I used ADC3 input to connect a variable voltage divider. To keep things simple, I only transmit a single byte over the TWI interface. The two least significant bits from the 10-bit ADC value are truncated and only the 8 most significant bits are transferred. The reset pins of both controllers are connected to ensure that both start working at the same time.



The implementation on a bread board looks like this:


Relevant TWI Registers

The TWI subsystem in the ATMEGA8 is controlled by some registers. These are described on pages 165-167 of the datasheet.
The TWI Bit Rate Register (TWBR) contains a divisor for the clock frequency and determines the TWI clock speed. I leave that register untouched, which will result in a TWI clock speed that is 1/16 of the CPU clock speed.

The TWI Control Register (TWCR) is used to enable the TWI hardware (set TWEA bit to 1) and is also used to interact with the hardware between transmission actions (via the TWINT bit). In TWI tranmissions, the control is passed back and forth between the user application program and the TWI hardware on the chip. The application code has to set some bits in that register according to what the next TWI action should be. After setting the TWINT bit, the TWI hardware starts working again - independent from the application code. It will first clear the TWINT bit. Whenever the TWI hardware is done with its actions, it sets the TWINT bit again. The application code can either poll that bit to check when the TWI hardware is done, or it can request an interrupt that is triggered by the TWI hardware. In this example I've chosen the latter possibility.
If the application code is notified by the TWINT bit in the TWCR register, it can obtain the status of the TWI hardware from the TWS[3:7] bits in the TWI Status Register (TWSR).
For all four operation modes of the TWI interface (master transmit, master receive, slave transmit, slave receive) the application code has a finite number of options. All of them are listed in the ATMEGA8 datasheet in tables.

After the TWINT bit was set (and an interrupt was triggered) the application code might find the number 0xA8 in the TWS[3:7] bits. The table lists all the possible options, which are chosen by setting or clearing certain combination of bits in the TWSTA, TWSTO and TWEA bits in the TWCR register. After setting the desired action, the TWI hardware is activated again by setting the TWINT bit again. Given the tables for all four communication modes, there are many many ways of realizing communication between two TWI devices. Often, the slave will be a given part (e.g. a sensor with I2C interface) and its behavior will be documented in its datasheet. Using an ATMEGA8 to read such a sensor requires to implement the correct a master behavior. In this demonstration example, both devices will be ATMEGA8. I have implemented a very simple (might be the simplest possible) slave behavior in the following code.

Slave Transmitter Program

When the TWI was addressed, the interrupt routine ISR(TWI_vect) is called, and the switch statement will go to the first case. The transmission data is loaded into the TWI Data Register (TWDR) and the first row of the above table is chosen, i.e. indicating the last data byte. In the case of only one data byte, the first byte is also the last.
After sending the byte, the TWI hardware will get the acknowledge signal from the master. Then, the TWEA bit has to be set again which tells the TWI hardware to monitor the address line and trigger another interrupt if its address is called. The transferred data is generated by the ADC, using only the 8 most significant bits of the 10-bit ADC value.
#include <avr/io.h>
#include <avr/interrupt.h>

#define F_CPU 1000000UL  // 1 MHz
#include <util/delay.h>
#include <util/twi.h>

#define SLAVE_ADDRESS (0x01)

uint8_t adc_value; // the value to send

// interrupt routine for TWI message handling
ISR(TWI_vect)
{
  // react on TWI status and handle different cases
  uint8_t status = TWSR & 0xFC; // mask-out the prescaler bits
  switch(status)
  {
    case TW_ST_SLA_ACK:   // own SLA+R received, acknoledge sent
         TWDR = adc_value;
         TWCR &= ~((1<<TWSTO) | (1<<TWEA));
    break;
    
    case TW_ST_LAST_DATA: // last byte transmitted ACK received     
         TWCR |= (1<<TWEA); // set TWEA to enter slave mode
    break;
    }
    TWCR |= (1<<TWINT);  // set TWINT -> activate TWI hardware
}

int main(void)
{
  // TWI setup
  sei(); // enable global interrupt
 
  // set slave address to 0x01, ignore general call
  TWAR = (SLAVE_ADDRESS << 1) | 0x00;
  // TWI-ENable , TWI Interrupt Enable
  TWCR |= (1<<TWEA) | (1<<TWEN) | (1<<TWIE); 
    
  // ADC setup
  ADCSRA |= (1<<ADEN);
  ADMUX  |= ( (1<<REFS1) | (1<<REFS0) ); // select internal reference
  ADMUX  |= 3;   // select channel 5 (pin ADC5)
 
  // infinite loop
  for (;;)
  {
    ADCSRA |= (1<<ADSC);        // start single measurement
    while(ADCSRA & (1<<ADSC));  // wait until measurement done
    
    // read result bytes (low and high) and reduce to 8-bits
    adc_value = ADCL;
    adc_value >>= 2;          // drop 2 least significant bits
    adc_value |= (ADCH << 6); // add two most significant bits
  }
}

Master Receiver Program

Another ATMEGA8 is loaded with the following program that can obtain the ADC value from the slave transmitter described above. The transmission is initialized by setting the TWSTA bit in the TWCR register. This only makes sense if there is no other transmission in progress, as indicated by the ongoing_transmission global variable. If the transmission is initiated, the address of the slave is given by the content of the data register in the first case of the switch statement: the 7 most significant bits of the TWDR register contain the address, while the least significant bit is 1 for a reading action and 0 for a writing action (from the perspective of the master). In this example we are a master receiver and we want to read. Our TWDR content is thus (SLAVE_ADDRESS << 1) | 0x01;
In the next TWI interrupt, we have the confirmation of the slave that it has acknowledged our call. We say that we want to acknowledge after receiving the data in the next TWI action (the TWEA bit remains set). After the next interrupt, the data is available in the TWDR data register. The only thing left to do is to send a stop condition by setting the TWSTO bit.
In the first and last case of the switch statement, the ongoing_transmission variable is managed to indicate to the main program that a transmission is ongoing.
#include <avr/io.h>
#define F_CPU 1000000UL  // 1 MHz
#include <util/delay.h>

#include <avr/interrupt.h>
#include <util/twi.h>

#define SLAVE_ADDRESS (0x01)

uint8_t value; // contains the received value

uint8_t ongoing_transmission = 0;

// interrupt routine for the timer0 overflow interrupt
ISR(TWI_vect)
{
  // react on TWI status and handle different cases
  uint8_t status = TWSR & 0xFC; // mask-out the prescaler bits
  switch(status)
  {
    case TW_START:  // start transmitted
         ongoing_transmission = 1;
         // write SLA+R, SLA=0x01
         TWDR = (SLAVE_ADDRESS << 1) | 0x01;
         TWCR &= ~((1<<TWSTA)); // clear TWSTA
    break;
  
    case TW_MR_SLA_ACK: // SLA+R transmitted, ACK received 
         TWCR &= ~((1<<TWSTA) | (1<<TWSTO)); 
    break;
  
    case TW_MR_DATA_ACK: // data received, ACK returned
         ongoing_transmission = 0;
         value = TWDR;
         TWCR |= (1<<TWSTO);  // write stop bit
         TWCR &= ~(1<<TWSTA); // clear start bit
    break;
  }
  TWCR |=   (1<<TWINT);  // hand over to TWI hardware
}

int main(void) 
{
  // TWI setup
  sei(); // enable global interrupt
  // TWI-ENable , TWI Interrupt Enable
  TWCR |= (1<<TWEA) | (1<<TWEN) | (1<<TWIE); 

  // LED setup
  DDRB  = 0x3f; // PORTB[0:5] as output
  DDRC  = 0x03; // PORTC[0:1] as output
 
  for (;;) // infinite main loop
  {
    // initiate new transmission if 
    //    no transmission is in progress
    if (!ongoing_transmission) TWCR |= (1<<TWSTA); 
 
    PORTB = value;    // display number
    PORTC = value>>6; //   as LED pattern 
  }
}


Bus Action

Finally, a look at the TWI bus lines with the oscilloscope to see what is going on:

The upper signal is the SCL line, the bottom signal is the SDA line. Both lines are high if nothing is happening because of the pull-up resistors.
Start and stop conditions are indicated by a SDA change while SCL is high. During address and data transmissions, the SCL line goes high when the transmitter has a stable voltage level on the SDA line so that the receiver can sample the voltage level. The first burst of 9 clock pulses is during the 7-bit address transmission (plus read bit and acknowledge bit) from the master to all TWI participants. The second burst of clock pulses is the transmission and acknowledge of the data byte.

More

The example here is minimal, and not robust against failures on the TWI bus. A proper implementation would have to handle all cases that are describe in the four mode tables of TWI operation. Otherwise the TWI hardware might get stuck.
I also didn't mention at all the possibility of multiple masters on one TWI bus. There is a way of preventing bus collisions if two masters signal a start condition at the same time. 
There is the possibility that one master addresses all participants at the same time in a so called general call.

Samstag, 21. Mai 2016

Analog Digital Conversion (ADC)

Introduction

In many applications, analog voltages are involved, and the actions of the microcontroller should depend on a given analog voltage level (for example a data logger). In order to convert it into a digital value (i.e. a binary number), analog to digital converters (ADCs) are used. The ADC compares the measured voltage to a reference voltage. The resulting number will be the ratio of the measured voltage and the reference voltage, where a ratio of 1 or greater would be mapped to the highest possible 10-bit value (0b1111111111), and 0 or smaller would be mapped to the lowest value (0b0000000000). The ATMEGA8 has an integrated 10-bit ADC that is connected to 6 of the pins in the DIP package (marked red in the following picture). Here I present a small setup and some C code that demonstrates how the ADC can be used.

Setup

A schematic drawing of the setup is shown in the next picture. Relevant pins of the ATMEGA are labeled. The connections to the programmer (also serving as power supply) are shown in blue. Note that the programmer can stay connected all the time and will not interfere with the operation of the circuit.
The voltage divider is connected to the ADC5 pin. 10 LEDs are used to display the binary value of after the analog to digital conversion. The AVCC pin has to be powered in this example (connected to VCC). Previous examples, not using the ADC, didn't need that pin. If low noise is important, the AVCC power rail should be a separate extra low noise power supply.

There are 3 sources for the ADC reference voltage for the ATMEGA8: the supply voltage, a voltage provided at the AREF pin, or an internal 2.56V reference.
I will use the internal reference. In this case a capacitor has to be connected tot the AREF pin. If you build that circuit, I encourage you to disconnect that capacitor and see what happens to the output: it will be completely random.

Firmware

As always, making use of the ADC requires to write the correct values to the right registers, and as always, everything is well documented in the ADC section of the datasheet. The following program will measure the voltage from a variable resistor divider with the ADC and use the value to switch some LEDs that will show the binary representation of the measured ADC value.

The relevant section of the ATMEGA8 datasheet starts on page 189. There are two registers that need to be manipulated to get the ADC running: ADCSRA and ADMUX (described on pages 199 & 200 in the datasheet)



The program will do the ADC setup and enter an infinite loop, in which it measures the voltage at ADC5 pin and displays the value on the LEDs. The LED on pin PC3 has the most significant bit of the 10-bit value, and PB0 has the least significant bit.

Setting the ADEN bit in ADCSRA register to 1 enables the ADC subsystem
ADCSRA |= (1<<ADEN);

The voltage reference is selected by the two bits REFS1 and REFS0 in the ADMUX register. Table 74 on page 199 of the datasheet shows, that both bits have to be set in order to get the internal voltage reference.
ADMUX |= ( (1<<REFS1) | (1<<REFS0) );

Since I connected my analog signal to ADC5, the number 5 has to be written to the MUX[0:3] part of the ADMUX register
ADMUX |= 5;

A conversion is started by setting ADSC bit to 1 in the ADCSRA register. If the conversion is done, the ADC sets this bit back to 0.
ADCSRA |= (1<<ADSC);        
while(ADCSRA & (1<<ADSC)); // wait until conversion is done

The result is then available in the registers ADCL and ADCH, which can be combined to a single 10-bit value (of course stored in a 16-bit integer).
uint16_t value;
value = ADCL; 
value |= (ADCH<<8); 

The complete program

#include <avr/io.h>
#define F_CPU 1000000UL  // 1 MHz
#include <util/delay.h>

int main(void)
{
  // enable ADC
  ADCSRA |= (1<<ADEN);
  ADMUX |= ( (1<<REFS1) | (1<<REFS0) ); // internal reference
  ADMUX |= 5;                           // select ch. 5 (pin ADC5)
 
  // LED setup
  // PORTB[0:5] as output
  DDRB  = 0x3f; // 0011 1111
  // PORTC[0:2] as output
  DDRC  = 0x0f; // 0000 1111
 
  for (;;) 
  {
    uint16_t value;

    ADCSRA |= (1<<ADSC);        // start single measurement
    while(ADCSRA & (1<<ADSC));  // wait until measurement done

    // read result bytes (low and high)
    value  = ADCL;
    value |= (ADCH<<8);
  
    // display number
    PORTB = value;
    PORTC = value>>6;
  }
}

Finally, some pictures of my setup, showing three different settings of the voltage divider. A multimeter is connected to the ADC input voltage to verify the ATMEGA8 measurement. The 10-bit value can be seen on the right hand side with the least significant bit on the lowest LED and the most significant bit on the top LED.




More

This example is intended to get you started. The ATMEGA8 ADC can do more. For example the sampling frequency can be adjusted using the ADPS[0:2] bits in the ADCSRA register. The ADC can be configured to trigger interrupts and it can operate in a noise cancellation mode. All this is described in detail in the ADC section of the datasheet.