Since I made my little Atmega16 avr development board I wanted to make a program to read out a Wiimotion Plus which I bought from Dealextreme for about 12 bucks. The Wiimotion Plus works with the Two Wire Interface (TWI/I2C) and since I already got the protocol working with an EEPROM chip I needed to do some investigation on this WM+. This wasn’t a big problem since I do have a Bus Pirate which is a super awesome tool for interfacing new chips without writing code! So I did not have to worry about writing code, I only had to try and find the right commands to put on the I2C communication line. So I played with the Bus Pirate and the WM+ for some time and found out that its real easy to interface the WM+.

Below is my terminal log of the Bus Pirate with comments:

HiZ> i
Bus Pirate v3a
Firmware v5.8 (r504)  Bootloader v4.3
DEVID:0x0447 REVID:0x3043 (B5)
http://dangerousprototypes.com

HiZ> m
1. HiZ
2. 1-WIRE
3. UART
4. I2C
5. SPI
6. 2WIRE
7. 3WIRE
8. KEYB
9. LCD
x. exit(without change)

(1)> 4
Set speed:
 1. ~5KHz
 2. ~50KHz
 3. ~100KHz
 4. ~400KHz

(1)>
Ready

I2C> W
Power supplies ON

I2C> (0)
 0.Macro menu
 1.7bit address search
 2.I2C sniffer

I2C> (1)
Searching I2C address space. Found devices at:
0xA6(0x53 W) 0xA7(0x53 R)	// Transmit to device at address is 0x53 << 1 = A6 I2C>
[0xa6 0xfe 0x04]         	// Transmit to address a6
I2C START BIT
WRITE: 0xA6 ACK
WRITE: 0xFE ACK  		// Send 0x04 to 0xFE
WRITE: 0x04 ACK
I2C STOP BIT    		// WM+ adress is changed to 0x52 and it is active

I2C> (1)
Searching I2C address space. Found devices at:
0xA4(0x52 W) 0xA5(0x52 R)	// Address changed to 0x52 (0x52 << 1 = A4) I2C>
[0xa4 0x00]      		// Point to address 0x00, request to send 6 bytes of data
I2C START BIT
WRITE: 0xA4 ACK
WRITE: 0x00 ACK
I2C STOP BIT

I2C> [0xa5 r:6]         	// Point to a5 and receive 6 bytes of data
I2C START BIT
WRITE: 0xA5 ACK
READ: 0x94  ACK 0x88  ACK 0xE8  ACK 0x83  ACK 0x7E  ACK 0x7E
NACK    			// Last byte is confirmed by a NACK
I2C STOP BIT
I2C>

So I implemented the I2C protocol for the WM+, but is it so simple to implement this in my Atmega16 development board (thingy)?

The answer is that it depends on what you want to do with it, I have implemented it in c code in AVRStudio and it works but it is not easily to understand the 6 bytes the WM+ gives to me. I found this site with more information about the 6 bytes and the whole protocol but I did not implemented it in my source code. Below is the program I wrote for reading the WM+ and send the received data back to the PC with rs-232 serial interface. The code is not very clean, but it works ok. It is written for an Atmega16 at 1Mhz. Download the source package.

// TWI_WM+.c
#include <util/delay.h>
#include <avr/io.h>
#include "uart.h"
#include <stdlib.h>

#define USART_BAUDRATE 9600        //Set Baudrate
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)
#define BASEADR        0xA4    // Address of an initialized WM+
#define TW_READ        1        // LSB of the busaddress is to send write/read
#define TW_WRITE    0        // LSB of the busaddress is to send write/read

void i2c_start(void);
void i2c_stop(void);
void i2c_send(uint8_t) ;
uint8_t i2c_receive(uint8_t);

int wmp_data[6];    // Array for 6 bytes of WM+ wmp_data
int yaw, pitch, roll; //three axes
int yaw0, pitch0, roll0; //calibration zeroes

// Serial communication initialization
void Init_uart(void)
{
 UCSRB |= (1 << RXEN) | (1 << TXEN);                   // Turn on the transmission and reception circuitry
 UCSRC |= (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1);// Use 8-bit character sizes 8N1
 UBRRL = BAUD_PRESCALE;                                 // Load lower 8-bits of the baud rate value into the low byte of the UBRR register
 UBRRH = (BAUD_PRESCALE >> 8);                         // Load upper 8-bits of the baud rate value into the high byte of the UBRR register
 uart_puts("\n\r\f");                                // Send string \n=new line \r=carriage return \f=clear screen
 uart_puts("Start!\n\r");
}

// Start communication with the WM+ and initialize it
void wmpOn(void)
{
 i2c_start();
 i2c_send((0x53<<1)+TW_WRITE);    // Initialize-adress is 0xA6 (0x53 << 2)
 i2c_send(0xFE);                    // Send 0x04 to 0xFE
 i2c_send(0x04);                    // WM+ jumps to address 0x52 and is now active
 i2c_stop();
}

// Request the WM+ to send wmp_data
void wmpSendZero(void)
{
 i2c_start();
 i2c_send((0x52<<1)+TW_WRITE);    // Go to address 0xA4 = (0x52 << 1)
 i2c_send(0x00);                    // Send 0x00 to change the address bit
 i2c_stop();
}

// Receive WM+ wmp_data
void wmpReceive(void)
{
 wmpSendZero();                     //send zero before each request (same as nunchuck)
 i2c_start();
 i2c_send((0x52<<1)+TW_READ);    // Go to address (0xA4) and wait for incoming wmp_data

 for (int i=0;i<5;i++)
 {
 wmp_data[i]=i2c_receive(1);    // Save readings in array
 }
 wmp_data[5]=i2c_receive(0);    //6th reading gives NO ACT (NACK) so check on NACK!

 i2c_stop();

}

// Calibrate Zeroes
void calibrateZeroes()
{
 for (int i=0;i<10;i++)
 {
 wmpSendZero();
 i2c_start();
 i2c_send((0x52<<1)+TW_READ);    // Go to address (0xA4) and wait for incoming wmp_data

 for (int i=0;i<5;i++)
 {
 wmp_data[i]=i2c_receive(1);    // Save readings in array
 }
 wmp_data[5]=i2c_receive(0);    //6th reading gives NO ACT (NACK) so check on NACK!

 i2c_stop();

 yaw0+=(((wmp_data[3]>>2)<<8)+wmp_data[0])/10; //average 10 readings
 pitch0+=(((wmp_data[4]>>2)<<8)+wmp_data[1])/10;
 roll0+=(((wmp_data[5]>>2)<<8)+wmp_data[2])/10;
 }

 char str1[8];                        //Make array of 8 chars

 itoa(yaw0, str1, 10);                 //Convert value to chars 2=binair 10=decimal    16=hex
 uart_puts("Yaw0:");
 uart_puts(str1);

 itoa(pitch0, str1, 10);
 uart_puts(" Pitch0:");
 uart_puts(str1);

 itoa(roll0, str1, 10);
 uart_puts(" Roll0:");
 uart_puts(str1);
 uart_puts("\n\r");
}

int main(void)
{
 _delay_ms(100);
 // TWI baudrate
 //TWBR = (F_CPU / 100000UL - 16) / 2;        // TWI Baudrate 100KHz //NOT CORRECT FOR 1MHZ

 // wmp_data direction register
 DDRA = 0x00;    //Make port A input
 DDRC = 0xFF;    //Make port C output
 PORTC= 0xFF;    //Turn on Pullup-resistors

 const int timer    = 250;    // Loop wait time in ms

 Init_uart();    // Initialize uart
 wmpOn();        // Start communication with the WM+
 uart_puts("WM+ Initialized!\n\r");
 calibrateZeroes(); //calibrate zeroes
 uart_puts("Zero value's:\n\r");
 uart_puts("And I quote:\n\r");

 while(1)
 {
 wmpReceive();

 for (int i=0;i<6;i++)
 {
 char str[8];                        //Make array of 8 chars
 itoa(wmp_data[i], str, 10);         //Convert valueADC to chars 2=binair 10=decimal    16=hex

 uart_puts(str);
 uart_puts(" ");
 }
uart_puts("\n\r");
 _delay_ms(timer);
 }
}

// Send a char
void uart_putc(unsigned char c)
{
 while(!(UCSRA & (1 << UDRE)));       // wait before UDR is ready

 UDR = c;                            // Send char
}

// Send a string
void uart_puts (char *s)
{
 while (*s)    //While *s is not NULL
 {
 uart_putc(*s);
 s++;
 }
}

// i2c.c

/* I²C (TWI) Bus
 *
 */

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

#define BASEADR        0xA4    // Address of an initialized WM+
 #define TW_READ        1        // LSB of the busaddress is the write/read bit
 #define TW_WRITE    0        // LSB of the busaddress is the write/read bit

void i2c_start(void);
 void i2c_stop(void);
 void i2c_send(uint8_t) ;
 uint8_t i2c_receive(uint8_t);
 void TWI_ByteWrite(uint16_t, uint8_t);
 uint8_t TWI_ByteRead(uint16_t);
 int main(void);

// Startbyte
 void i2c_start(void)
 {
 TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);     // send start condition
 while ((TWCR & _BV(TWINT)) == 0) ;                 // wait for transmission
 }

// Stopbit
 void i2c_stop(void)
 {
 TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);        // send stop condition
 }

// Send byte
 void i2c_send(uint8_t DataByte)
 {
 TWDR = DataByte;
 TWCR = _BV(TWINT) | _BV(TWEN);             // clear interrupt to start transmission
 while ((TWCR & _BV(TWINT)) == 0) ;         // wait for transmission
 }

// receive byte
 uint8_t i2c_receive(uint8_t ack)
 {
 if (ack == 1)
 TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN);
 else
 TWCR = _BV(TWINT) | _BV(TWEN);          // clear interrupt to start transmission
 while ((TWCR & _BV(TWINT)) == 0) ;           // wait for transmission
 return TWDR;
 }
//uart.h

#define UART_BAUD_RATE 9600L
#define UART_BAUD_CALC(UART_BAUD_RATE,F_CPU) ((F_CPU)/((UART_BAUD_RATE)*16L)-1)

#define RBUFFLEN 40 //Bufferlenght

volatile unsigned char rbuff[RBUFFLEN];        // Ringbuffer
volatile uint8_t     rbuffpos,                     // Position
 rbuffcnt,
 udr_data;

unsigned char ser_getc (void);
void uart_putc(unsigned char);
void uart_puts (char *);
void uart_ini (void);

I hope you learned something of how easy it is to implement TWI/I2C, at least I did. I also did some debugging with the Bus Pirate I2C sniffer mode, which made it a lot easier to implement the protocol correct!

Comments
  1. Em says:

    Great example! Did you already try the BP I2C sniffer on the Wiimotion Plus?

  2. humbert says:

    Hello you use pull-up ?

  3. Daniel says:

    I’m kinda lost in the use of these prototypes.
    19 void TWI_ByteWrite(uint16_t, uint8_t);
    20 uint8_t TWI_ByteRead(uint16_t);

    Also the usage of _BV() function

  4. jjshortcut says:

    I’m not sure what is confusing about the first 2 prototypes? but for the _BV() function, this is just another way to write a bit, it’s the same as (1 << bit)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s