XC8 + PIC 16F1509 with the Wii Nunchuk (yet another…)

A quick and dirty PIC to Wii Nunchuk project
A quick and dirty PIC to Wii Nunchuk project

I needed a “quick fix” of bench time.. I’ve been working on hunny-dos replacing light switches with dimmers, replacing our HVAC control with a remote network interfaced controller, etc… I needed a little bench time! I took the first quick-looking project off my “shelf of unfinished or unstarted projects”. Wii Nunchuk + a little eBay interface board that cost me a dollar or two. I spent the evening getting some C code set up using the data sheet at robotshop and some 80s music.

The TAUTIC 20 Pin PIC dev board was an obvious, I already had done some recent I2C code on it; reuse, reduce, recycle!

I wrote the code to look for the “Z” button push. The C & Z buttons threw me off a bit because I think the datasheet was a little off in some of the text. The last two bits (LSB) of the 5th status response bytes indicate the button positions of BOTH buttons. It incorrectly states one bit is for one button and the other for the other. This is the table I figured out using the Saleae logic analyzer:

0Bxxxx xx11 no buttons pushed

0Bxxxx xx01 C pushed

0Bxxxx xx00 Z pushed

0Bxxxx xx10 both buttons pushed

This is a white original Wii remote…. I didn’t do any OR decoding, etc.. I didn’t really pay much attention to the other data other than verifying it actual works.

The code doesn’t do much; we initialize the *white* nunchuk and then turn on LATA1 port when the Z button is pressed (but not the C and Z simultaneously). From here everything is popped into a variable for your happy hardware hacking needs!

Well anyways.. what you’re here for! The code:


/*
 * File:   main.c
 * Author: Charles M Douvier
 * Contact at: http://iradan.com
 *
 * Created on January 26, 2014, 12:00 PM
 *
 * Target Device:
 * 16F1509 on Tautic 20 pin dev board
 *
 * Project:
 *  Wii Nunchuck
 *
 * Version:
 * 0.1  Start Bit, and Control Byte ... check
 * 0.2  Read_State Success

 *
 */
#ifndef _XTAL_FREQ
#define _XTAL_FREQ 4000000 //4Mhz FRC internal osc
#define __delay_us(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000000.0)))
#define __delay_ms(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000.0)))
#endif

#include 
#include 
#include 
#include 

//config bits
#pragma config FOSC=INTOSC, WDTE=OFF, PWRTE=ON, MCLRE=ON, CP=OFF, BOREN=OFF, CLKOUTEN=OFF, FCMEN=OFF
#pragma config WRT=OFF, STVREN=OFF, LVP=OFF


#define _XTAL_FREQ 4000000 //defined for delay

    unsigned int ACK_bit;
    int i, x;   //garbage flags
    long int tempi, tempn, tempx, tempy, temp10, temp25;
    unsigned char byte, tempbyte1, tempbyte2;
    unsigned char StatusByte;
    unsigned char RESP1Byte, RESP2Byte, RESP3Byte, RESP4Byte, RESP5Byte;
    unsigned char RESP6Byte, RESP7Byte, RESP8Byte, RESP9Byte, RESP10Byte;
    char buf[10];

void init_io(void) {

    ANSELA = 0x00; // all port A pins are digital I/O
    ANSELB = 0x00; // all port A pins are digital I/O
    ANSELC = 0x00; // all port B pins are digital I/O

    TRISAbits.TRISA0 = 0; // 
    TRISAbits.TRISA1 = 0; // 
    TRISAbits.TRISA2 = 0; // 
    TRISAbits.TRISA3 = 1; // /MCLR
    TRISAbits.TRISA4 = 0; // 
    TRISAbits.TRISA5 = 0; // 

    TRISBbits.TRISB4 = 1; // RB4 I2C SDA, has to be set as an input
    TRISBbits.TRISB5 = 1; // RB5 NC (RESERVED RS232)
    TRISBbits.TRISB6 = 1; // RB6 I2C SCLK, has to be set as an input
    TRISBbits.TRISB7 = 0; // RB7 NC (RESERVED RS232)

    TRISCbits.TRISC0 = 0; // 
    TRISCbits.TRISC1 = 0; // 
    TRISCbits.TRISC2 = 0; // 
    TRISCbits.TRISC3 = 0; // 
    TRISCbits.TRISC4 = 1; // 
    TRISCbits.TRISC5 = 1; // 
    TRISCbits.TRISC6 = 1; // 
    TRISCbits.TRISC7 = 1; // 
}

/*
 *  I2C Functions
 *
 */

void I2C_ACK(void)
{
   PIR1bits.SSP1IF=0;          // clear SSP interrupt bit
   SSP1CON2bits.ACKDT=0;        // clear the Acknowledge Data Bit - this means we are sending an Acknowledge or 'ACK'
   SSP1CON2bits.ACKEN=1;        // set the ACK enable bit to initiate transmission of the ACK bit to the serial eeprom
   while(!PIR1bits.SSP1IF);    // Wait for interrupt flag to go high indicating transmission is complete

}

void Send_I2C_Data(unsigned int databyte)
{
    PIR1bits.SSP1IF=0;          // clear SSP interrupt bit
    SSPBUF = databyte;              // send databyte
    while(!PIR1bits.SSP1IF);    // Wait for interrupt flag to go high indicating transmission is complete
}

unsigned char RX_I2C_Data (void)
{

    RCEN = 1;               //
    while( RCEN ) continue;
    while( !BF ) continue;
    byte = SSPBUF;
   return byte;
}

void I2C_Control_Write(void)
{
    PIR1bits.SSP1IF=0;          // clear SSP interrupt bit
    SSP1BUF = 0xA4;             // send the control byte 
    while(!PIR1bits.SSP1IF)     // Wait for interrupt flag to go high indicating transmission is complete
        {
        i = 1;
          // place to add a breakpoint if needed
        }
    PIR1bits.SSP1IF=0;

}

void I2C_Control_Read(void)
{
    PIR1bits.SSP1IF=0;          // clear SSP interrupt bit
    SSP1BUF = 0xA5;             // send the control byte
    while(!PIR1bits.SSP1IF)     // Wait for interrupt flag to go high indicating transmission is complete
        {
        i = 1;
          // place to add a breakpoint if needed
        }
    PIR1bits.SSP1IF=0;
   }

void I2C_Start_Bit(void)
{
    PIR1bits.SSP1IF=0;          // clear SSP interrupt bit
    SSPCON2bits.SEN=1;          // send start bit
    while(!PIR1bits.SSP1IF)    // Wait for the SSPIF bit to go back high before we load the data buffer
        {
        i = 1;
        }
    PIR1bits.SSP1IF=0;
}

void I2C_check_idle()
{
    unsigned char byte1; // R/W status: Is a transfer in progress?
    unsigned char byte2; // Lower 5 bits: Acknowledge Sequence, Receive, STOP, Repeated START, START

    do
    {
        byte1 = SSPSTAT & 0x04;
        byte2 = SSPCON2 & 0x1F;
    } while( byte1 | byte2 );
}
/*
 * Send the repeated start message and wait repeated start to finish.
 */
void I2C_restart()
{
    I2C_check_idle();
    RSEN = 1; // Reinitiate start
    while( RSEN ) continue;
}

void I2C_Stop_Bit(void)
{
    PIR1bits.SSP1IF=0;          // clear SSP interrupt bit
    SSPCON2bits.PEN=1;          // send stop bit
    while(!PIR1bits.SSP1IF)
    {
        i = 1;
        // Wait for interrupt flag to go high indicating transmission is complete
    }
}

void I2C_NAK(void)
{
    PIR1bits.SSP1IF=0;           // clear SSP interrupt bit
    SSP1CON2bits.ACKDT=1;        // set the Acknowledge Data Bit- this means we are sending a No-Ack or 'NAK'
    SSP1CON2bits.ACKEN=1;        // set the ACK enable bit to initiate transmission of the ACK bit to the serial eeprom

    while(!PIR1bits.SSP1IF)     // Wait for interrupt flag to go high indicating transmission is complete
    {
        i = 1;
    }
}

/*
 *  nunchuk commands
 *
 */

void initialize_nunchuk(void)
{
// 0xA4 0x40 0x00

    I2C_Start_Bit();                     // send start bit
    I2C_Control_Write();                 // send control byte

    Send_I2C_Data(0x40);
    Send_I2C_Data(0x00);                 //

    I2C_Stop_Bit();
    __delay_ms(100);
}

void start_conversion_with_nunchuck(void)
{
// 0xA4 0x00

    I2C_Start_Bit();                     // send start bit
    I2C_Control_Write();                 // send control byte

    Send_I2C_Data(0x00);                 //0x00

    I2C_Stop_Bit();
			
}



void read_state (void)
{
/*
 *   STATUS, 6 Bytes
 *  Joy X
 *  Joy Y
 *  Accel X
 *  Accel Y
 *  Accel Z
 *  Z, C bits and LSB Accel

*/
//0x52 RX_STATUS
//STATUS, RESP1, RESP2, RESP3, RESP4, RESP5, RESP6

    I2C_Start_Bit();                     // send start bit
    I2C_Control_Read();

    RX_I2C_Data();                      //Joy X
	StatusByte = byte;
    I2C_ACK();

	RX_I2C_Data();                      //Joy Y
	RESP1Byte = byte;
    I2C_ACK();

	RX_I2C_Data();                      //Acc X
	RESP2Byte = byte;
    I2C_ACK();

	RX_I2C_Data();                      //Acc Y
	RESP3Byte = byte;
    I2C_ACK();

	RX_I2C_Data();                      //Acc Z
	RESP4Byte = byte;
    I2C_ACK();

    	RX_I2C_Data();                      //CZ Status LSB
	RESP5Byte = byte;
    I2C_ACK();

    RX_I2C_Data();                      //
	RESP6Byte = byte;
    I2C_NAK();                          //NAK

	I2C_Stop_Bit();                     // Send Stop Bit

}



int main(void) {

    OSCCONbits.IRCF = 0x0d;     //set OSCCON IRCF bits to select OSC frequency 4MHz
    OSCCONbits.SCS = 0x02;
    OPTION_REGbits.nWPUEN = 0;  //enable weak pullups (each pin must be enabled individually)

    init_io();

    __delay_ms(50);            //let the power settle


    TRISBbits.TRISB6 = 1;

    SSPSTATbits.SMP = 1;
    SSPCONbits.SSPM=0x08;       // I2C Master mode, clock = Fosc/(4 * (SSPADD+1))
    SSPCONbits.SSPEN=1;         // enable MSSP port
    SSPADD = 0x27;              //figure out which one you can ditch sometime (probably either)
    SSP1ADD = 0x27;             // 100KHz
                                //0x09 = 100KHz
    // **************************************************************************************


    initialize_nunchuk();
    x = 0;
    LATAbits.LATA1 = 0;
    __delay_ms(50);


    while (1) {
        x = 0;
        LATAbits.LATA1 = 0;

        start_conversion_with_nunchuck();
        __delay_ms(5);
        read_state();
        //RESP6Byte
        if (!(RESP5Byte & 0x02) && !(RESP5Byte & 0x01)){
            x = 1;
        }
        if (x) {
            LATAbits.LATA1 = 1;
        }
        __delay_ms(100);
    }
    return;
}

The only thing that might not work for your application right out of the box is the return through the loop turns off the output for just a bit while the button stays pressed… you would want to change this obviously.

Leave a Reply