/************************************************************************ Created 25 Jan 2012 by Mike Hord @ SparkFun Electronics. This code is beerware- feel free to make use of it, with or without attribution, in your own projects. If you find it helpful, buy me a beer the next time you see me at the local- or better yet, shop SparkFun! Filename: EasyPoint_Demo.ino- basic demo code and functions for using the Austria Microsystems N35P112 EasyPoint mini-joystick module. Created in Arduino 1.0. Tested with an Arduino Pro @ 8MHz with the following connections: EasyPoint RESET -> A0 (14) EasyPoint INT -> A1 (15) EasyPoint PB -> A2 (16) EasyPoint SDA -> A4 (18) EasyPoint SCL -> A5 (19) ************************************************************************/ #include // Include the I2C library. The EasyPoint is a pretty // standard I2C peripheral. #define JS_ADDR 0x41 // this is the default I2C address of the joystick. // it can be changed to 0x40 by bridging a solder // jumper on the board. // Pin definitions for this example code #define nRESET 14 // Pull this signal LOW to reset the joystick. #define INT 15 // When the joystick controller is properly configured, // this line will go LOW if the controller detects a // deviation of greater than a certain value, and stay // LOW until the values move back into the "dead zone". // Default behavior is to go "high" whenever a conversion // is completed- it will go low again when you read the // Y-value register. #define PB 16 // Pushbutton output. This pin is grounded when the stem // of the joystick is pushed in, and floating otherwise. // No pull-up is provided on the board! // N35P112 Register definitions // NB- while there are other registers listed in the datasheet, these are the // only ones necessary for correct operation. The others are read-only or // initialize to the right value and SHOULD NOT be messed with. #define JS_CTRL_REG_1 0x0F // 8-bit configuration register. (def: 11110000) // bit 7 1 = active mode, 0 = low power mode // EasyPoint MUST be in low power mode for INT on threshold mode! // bits 6:4 low power readout time base register. Wake-up interval for low // power mode output: // 000 -> 20ms, 001 -> 40ms, 010 -> 80ms, 011 -> 100ms // 100 -> 140ms, 101 -> 200ms, 110 -> 260ms, 111 -> 320ms // bit 3 1 = interrupt disabled, 0 = interrupt enabled // bit 2 0 = INT pin low on every new coordinate calculation // 1 = INT pin low when threshold values exceeded and while // threshold values are exceeded // bit 1 write to 1 to reset device // bit 0 1 = conversion complete, data valid. Read only! #define JS_T_CTRL_REG 0x2D // Scaling factor for X/Y coordinates. // Datasheet says to set this to 0x06 which is NOT the default power-up value. // No further information is provided, so make sure you write it after power on // or reset of the joystick! #define JS_ID_CODE_REG 0x0C // 8-bit manufacturer ID code. Read only. // Reads as 0x0C- useful to test data communications with the device. #define JS_X_REG 0x10 // 8-bit register containing a signed 8-bit // value representing the current position of // the joystick in X. #define JS_Y_REG 0x11 // Y position register. Reading this register // resets the status of the INT output, so it // should be read AFTER the X value. // INT threshold registers. If the X or Y value is greater than the value in the // corresponding "P" register or less than the value in the "N" register, and // JS_Control_Register_1 bit 2 = 1 and bit 3 = 0, and the device is set to poll // its read values automatically, #define JS_XP_REG 0x12 #define JS_XN_REG 0x13 #define JS_YP_REG 0x14 #define JS_YN_REG 0x15 // X and Y zero-state values. The EasyPointInit() function will populate these values // with the initial at-rest offset of the joystick. This can be used later to determine // the true offset from center when reading the value, as well as to calculate a // reasonable threshold for setting interrupt levels. The datatype "char" is used because // it is a signed 8-bit value, which is the same size as the X/Y values. char XZero = 0; char YZero = 0; void setup() { Serial.begin(57600); // Intialize the Serial library. Wire.begin(); // Initialize the I2C library. pinMode(INT, INPUT); // The interrupt pin from the EasyPoint should be input... digitalWrite(INT, HIGH); // ...and is open drain, so it needs a pull-up. pinMode(PB, INPUT); // The pushbutton pin from the EasyPoint should be input... digitalWrite(PB, HIGH); // ...and is a NO button, so it needs a pull-up. pinMode(nRESET, OUTPUT); // The reset line to the EasyPoint should be output. EasyPointInit(); // This is a nice, packaged function that sets up the EasyPoint. EasyPointIntSetup(XZero, YZero, 25, 25); // Set up the interrupt function. Not // needed, but nice to include! This is // setting it to flag an interrupt if the // reading is 25 points off center or more. Serial.println("Setup completed successfully."); } // This example loop is really simple- it just polls the INT pin and PB pin, looking // for transitions and announcing them over serial. void loop() { // As mentioned above, the INT pin will go low if the joystick is moved off center. // No delay is necessary in this loop because the polling rate of the EasyPoint is // set to 200ms, which gives us a decently slow polling rate to prevent huge data // rates from overwhelming the serial console. if(digitalRead(INT)==LOW) { Serial.println("INT!!!!"); Serial.println(readXAxis(), DEC); // readXAxis() returns a signed 8-bit result. Serial.println(readYAxis(), DEC); // readYAxis() returns a signed 8-bit result. } // The pushbutton output (i.e., what happens when you push down on the joystick) is // a NO button- when you push it down, it grounds the pin. Thus, we look for a LOW // on that pin to detect a press. if(digitalRead(PB)==LOW) { Serial.println("BUTTON!!!!"); delay(250); // debounce/throttle the data rate. } } // A simple function for setting the internal interrupt function to flag the // processor if the joystick moves more that x_delta or y_delta units from // the points specified by x_null and y_null. A function to set up an // asymmetrical dead zone is left as an exercise for the reader; the principle // is the same- write the appropriate registers, etc etc. void EasyPointIntSetup(char x_null, char y_null, byte x_delta, byte y_delta) { char xp, xn, yp, yn; // 8-bit signed values for the trigger points. xp = x_null + x_delta; // calculate the positive trigger point for x writeI2CReg(JS_ADDR, JS_XP_REG, xp); // set the positive trigger point for x xn = x_null - x_delta; // calculate the negative trigger point for x writeI2CReg(JS_ADDR, JS_XN_REG, xn); // set the negative trigger point for x yp = y_null + y_delta; // calculate the positive trigger point for y writeI2CReg(JS_ADDR, JS_YP_REG, yp); // set the positive trigger point for y yn = y_null - y_delta; // calculate the negative trigger point for y writeI2CReg(JS_ADDR, JS_YN_REG, yn); // set the negative trigger point for y // Now we need to actually tell the device we want to interrupt on values outside // the deadzone, and to activate the interrupt pin. No assumptions- let's be // responsible and change ONLY the bits we need. Start by reading the value // of Control Register 1. byte temp = readI2CReg(JS_ADDR, JS_CTRL_REG_1); // Next, we'll clear bit 3, to ensure that the INT pin is active. temp = temp & 0b11110111; // Then, clear bit 7, to put the EasyPoint into self-timed mode (otherwise, // it will only do a conversion after a read, which means you need to poll // it to initiate a read, which makes the INT pin useless). temp = temp & 0b01111111; // Now, clear bits 6:4, so we can set the polling rate. See register description // above for details on various values of these bits. temp = temp & 0b10001111; // Set the bits according to the desired polling rate. Here, I'll set it to // poll every 200ms. temp = temp | 0b01010000; // Then, set bit 2, to tell the device to interrupt based on range and not // every time a conversion is complete. temp = temp | 0b00000100; // NB- for clarity, I broke the bit sets/clears up into smaller operations. We // COULD combine the bit clearing/setting operations into two lines, like this: // temp = temp & 0b00000111; // temp = temp | 0b01010100; // Finally, write the adjusted value back into Control Register 1. writeI2CReg(JS_ADDR, JS_CTRL_REG_1, temp); } // This function handles most of the EasyPoint specific initialization stuff- // registers that MUST be set to a certain value, calculation of zero-point // offsets, verification of connection. void EasyPointInit() { // First things first: let's reset the joystick controller to make sure it's // in a known state before we start playing with it. THIS IS VERY IMPORTANT- // if you do NOT properly reset the EasyPoint, it won't work right!!! digitalWrite(nRESET, HIGH); delay(1); digitalWrite(nRESET, LOW); delay(1); digitalWrite(nRESET, HIGH); delay(100); // give the joystick time to come online // The datasheet tells us to initialize the T Control Register to 0x06 for this // product but doesn't say why, nor why it doesn't power up to this value. // Don't forget to do this! writeI2CReg(JS_ADDR, JS_T_CTRL_REG, 0x06); // Now let's look at the X/Y offset. Ideally, the joystick would be at 0,0 at // rest; practically, it could be as much as 30-50 counts positive or negative. char X, Y; // char is the signed 8-bit data type in Arduino. char X_temp=0, Y_temp=0; readXAxis(); // read and discard a couple of values- the first read after boot readYAxis(); // seems to be a little off on the low side. delay(5); // Now we collect four data points from each axis and calculate the average. // We delay between conversions to allow a new conversion to complete- we COULD // write some code to poll the conversion bit, or configure the INT pin, but // let's be lazy instead. for (byte i = 0; i<4; i++) { X_temp += readXAxis(); Y_temp += readYAxis(); delay(5); } XZero = X_temp/4; YZero = Y_temp/4; if (readI2CReg(JS_ADDR, JS_ID_CODE_REG) != 0x0C) Serial.println("EasyPoint failed to respond."); } // readXAxis() and readYAxis() are just nice little wrappers around the I2C read function which // automatically send the appropriate register addresses and convert the result from an unsigned // 8-bit value to a signed 8-bit value. char readXAxis() { return readI2CReg(JS_ADDR, JS_X_REG); } char readYAxis() { return readI2CReg(JS_ADDR, JS_Y_REG); } // The Wire library contains no discrete "read" or "write" functionality, so let's build our own. // I included an option to change the slave device's address but NOT the ability to send or // receive multiple bytes- generalizing that would have involved some unneccessarily ugly // programming for the simplicity of this application. byte readI2CReg(byte slave_addr, byte reg_addr) { // An I2C read transaction with the EasyPoint using the Wire library involves five steps: // Wire.beginTransmission() begins setting up a packet to transfer over I2C- the 7-bit // address plus the R/W bit set to W mode. Wire.beginTransmission(slave_addr); // Wire.write() adds the 8-bit value passed to it to the packet. This can be repeated as // necessary for multiple byte transfers; for the EasyPoint, the first byte transmitted // is ALWAYS the address of interest. Wire.write(reg_addr); // Wire.endTransmission() transmits the packet that the previous two functions assembled. Wire.endTransmission(); // Wire.requestFrom() pushes out the slave address followed by the R/W bit set to R mode. // The result is that the device starts spitting out data based on (in this case) the // current value in the address register. We need to clarify how many bytes we expect back; // the (byte) typecast prevents an error in the compiler caused by an ambiguity in the // expression of constants. It's dumb, but it works. Wire.requestFrom(slave_addr, (byte)1); // Wire.read() returns a byte read into the buffer by the Wire.requestFrom() function. // Since we're only worried about reading one register at a time, we can simply return // that value directly. return Wire.read(); } void writeI2CReg(byte slave_addr, byte reg_addr, byte d_buff) { // Writing the EasyPoint over I2C is a little easier than reading from it: // Wire.beginTransmission() starts assembling a packet for the device at slave_addr. Wire.beginTransmission(slave_addr); // Wire.write() adds data to the packet- the first piece of data is the address we // of the register we want to write data into. Wire.write(reg_addr); // The second piece of data is the actual data we want to write. Wire.write(d_buff); // Wire.endTransmission() sends the packet. No return value is expected or needed for // our purposes here. Wire.endTransmission(); }