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!
Great example! Did you already try the BP I2C sniffer on the Wiimotion Plus?
That’s what he did…
Hello you use pull-up ?
Hi, I did not use any pull-up resistors.
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
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)