So now that my hat seems well on its way, I decided it would be wise to dive back into my code. I know what I am doing on the hat construction side of things, but the electronics and coding is a bit more exploratory. Please bear with me on my journey. I have learned all I know about coding for Arduino form the Adafruit learning site and a few other online tutorials. I take a patchwork quilt mentality to coding, looking at other peoples projects, trying to understand why it works and then modifying it for my own purposes. I really hadn't done anything much with the code for this project since my proof of concept video.
You can see in the video that the dots appear to be tracking in one direction, but then drift away a bit.
Here is the code I used with the library files that I modified. You can also find the code here at codebender. https://codebender.cc/sketch:42502
The code compiles in the Arduino IDE at 5,300 bytes out of an allowed 5,310.
Gemma ring pointer V2
//using code from https://learn.adafruit.com/steam-punk-goggles?view=all //idea for pointer pixel from http://www.element14.com/community/groups/arduino/blog/2013/08/27/get-closer-challenge--navigation-glove //idea for library modification from http://www.slickstreamer.info/2014/02/led-bracelet-accelerometer-3dprinting.html #include <TinyWireM.h> #include "TinyLSM303_mag.h" #include <Adafruit_NeoPixel.h> #define Pin 1 #define numPixels 16 Adafruit_NeoPixel ring = Adafruit_NeoPixel(numPixels, Pin, NEO_GRB + NEO_KHZ800); TinyLSM303_mag lsm; // Pi for calculations - not the raspberry type const float Pi = 3.14159; void setup() { ring.begin(); ring.show();// Initialize all pixels to 'off' lsm.begin();// Initialize the sensors } void loop() { // Read the magnetometer and determine the compass heading: lsm.read(); // Calculate the angle of the vector y,x from magnetic North float heading = (atan2(lsm.magData.y,lsm.magData.x) * 180) / Pi; // Normalize to 0-360 for a compass heading if (heading < 0) { heading = 360 + heading; } byte d = map(heading,0,360,0,16);//map the heading to the size of the ring byte traceFront = d + 1; if (d == 16) { traceFront = 0; } byte traceBack = d-1; if (d == 0) { traceBack = 15; } //wipe the slate clean for(byte i=0;i<numPixels;i++) { ring.setPixelColor(i,ring.Color(0,0,0)); } ring.setPixelColor(d, ring.Color(0, 80, 100)); //set the closest pixel in the ring to the heading ring.setPixelColor(traceFront, ring.Color(20, 30, 40)); ring.setPixelColor(traceBack, ring.Color(20, 30, 40)); ring.show(); }
Tiny_LSM303_mag.h
/*************************************************************************** This is a library for the LSM303 Accelerometer and magnentometer/compass Designed specifically to work with the Adafruit LSM303DLHC Breakout These displays use I2C to communicate, 2 pins are required to interface. Adafruit invests time and resources providing this open source code, please support Adafruit andopen-source hardware by purchasing products from Adafruit! Written by Kevin Townsend for Adafruit Industries. BSD license, all text above must be included in any redistribution ***************************************************************************/ //Modified by David Crittenden July, 2014 //removed sections of accelerometer code to fit on GEMMA #ifndef __LSM303_H__ #define __LSM303_H__ #if (ARDUINO >= 100) #include "Arduino.h" #else #include "WProgram.h" #endif #include "TinyWireM.h" #define LSM303_ADDRESS_MAG (0x3C >> 1) // 0011110x #define LSM303_ID (0b11010100) class TinyLSM303_mag { public: typedef enum { LSM303_REGISTER_MAG_CRA_REG_M = 0x00, LSM303_REGISTER_MAG_CRB_REG_M = 0x01, LSM303_REGISTER_MAG_MR_REG_M = 0x02, LSM303_REGISTER_MAG_OUT_X_H_M = 0x03, LSM303_REGISTER_MAG_OUT_X_L_M = 0x04, LSM303_REGISTER_MAG_OUT_Z_H_M = 0x05, LSM303_REGISTER_MAG_OUT_Z_L_M = 0x06, LSM303_REGISTER_MAG_OUT_Y_H_M = 0x07, LSM303_REGISTER_MAG_OUT_Y_L_M = 0x08, LSM303_REGISTER_MAG_SR_REG_Mg = 0x09, LSM303_REGISTER_MAG_IRA_REG_M = 0x0A, LSM303_REGISTER_MAG_IRB_REG_M = 0x0B, LSM303_REGISTER_MAG_IRC_REG_M = 0x0C, LSM303_REGISTER_MAG_TEMP_OUT_H_M = 0x31, LSM303_REGISTER_MAG_TEMP_OUT_L_M = 0x32 } lsm303MagRegisters_t; typedef enum { LSM303_MAGGAIN_1_3 = 0x20, // +/- 1.3 LSM303_MAGGAIN_1_9 = 0x40, // +/- 1.9 LSM303_MAGGAIN_2_5 = 0x60, // +/- 2.5 LSM303_MAGGAIN_4_0 = 0x80, // +/- 4.0 LSM303_MAGGAIN_4_7 = 0xA0, // +/- 4.7 LSM303_MAGGAIN_5_6 = 0xC0, // +/- 5.6 LSM303_MAGGAIN_8_1 = 0xE0 // +/- 8.1 } lsm303MagGain; typedef struct lsm303MagData_s { int32_t x; int32_t y; int32_t z; int32_t orientation; } lsm303MagData; bool begin(void); void read(void); void setMagGain(lsm303MagGain gain); lsm303MagData magData; // Last read magnetometer data will be available here void write8(byte address, byte reg, byte value); byte read8(byte address, byte reg); private: }; #endif
Tiny_LSM303_mag.ccp
/*************************************************************************** This is a library for the LSM303 Accelerometer and magnentometer/compass Designed specifically to work with the Adafruit LSM303DLHC Breakout These displays use I2C to communicate, 2 pins are required to interface. Adafruit invests time and resources providing this open source code, please support Adafruit andopen-source hardware by purchasing products from Adafruit! Written by Kevin Townsend for Adafruit Industries. BSD license, all text above must be included in any redistribution ***************************************************************************/ #include <TinyLSM303_mag.h> /*************************************************************************** CONSTRUCTOR ***************************************************************************/ bool TinyLSM303_mag::begin() { TinyWireM.begin(); // Enable the magnetometer write8(LSM303_ADDRESS_MAG, LSM303_REGISTER_MAG_MR_REG_M, 0x00); return true; } /*************************************************************************** PUBLIC FUNCTIONS ***************************************************************************/ void TinyLSM303_mag::read() { // Read the magnetometer TinyWireM.beginTransmission((byte)LSM303_ADDRESS_MAG); TinyWireM.write(LSM303_REGISTER_MAG_OUT_X_H_M); TinyWireM.endTransmission(); TinyWireM.requestFrom((byte)LSM303_ADDRESS_MAG, (byte)6); // Wait around until enough data is available while (TinyWireM.available() < 6); // Note high before low (different than accel) uint8_t xhi = TinyWireM.read(); uint8_t xlo = TinyWireM.read(); uint8_t zhi = TinyWireM.read(); uint8_t zlo = TinyWireM.read(); uint8_t yhi = TinyWireM.read(); uint8_t ylo = TinyWireM.read(); // Shift values to create properly formed integer (low byte first) magData.x = (xlo | (xhi << 8)); magData.y = (ylo | (yhi << 8)); magData.z = (zlo | (zhi << 8)); // ToDo: Calculate orientation magData.orientation = 0.0; } void TinyLSM303_mag::setMagGain(lsm303MagGain gain) { write8(LSM303_ADDRESS_MAG, LSM303_REGISTER_MAG_CRB_REG_M, (byte)gain); } /*************************************************************************** PRIVATE FUNCTIONS ***************************************************************************/ void TinyLSM303_mag::write8(byte address, byte reg, byte value) { TinyWireM.beginTransmission(address); TinyWireM.write(reg); TinyWireM.write(value); TinyWireM.endTransmission(); } byte TinyLSM303_mag::read8(byte address, byte reg) { byte value; TinyWireM.beginTransmission(address); TinyWireM.write(reg); TinyWireM.endTransmission(); TinyWireM.requestFrom(address, (byte)1); value = TinyWireM.read(); TinyWireM.endTransmission(); return value; }
So, I reloaded that test code onto my Gemma and did some tests. I put everything neatly onto a breadboard with the NeoPixel ring directly above the magnetometer which was directly above a key chain compass. I drew a line with the x and y axis printed on the magnetometer onto a piece of cardboard, and taped my breadboard to it. Then I taped out a clock onto my work surface and set a ruler to point north according to my compass. I then recorded the position of the dot of light when I turned the cardboard to point to each hour. This is what I got:
Not very reliable. But the results were repeatable, so something had to be off. So I went back to the learning site at Learn.Adafruit.com to see what I could learn. I already knew that my accuracy was going to be compromised because I had deactivated the accelerometer and couldn't compensate for tilt. I found a calibration sketch with the original library, so I loaded it onto my Flora, hooked up the accelerometer, and plugged it into the computer to read the serial data.
Here is the code I used:
LSM_303 test
#include <Wire.h> #include <Adafruit_LSM303.h> Adafruit_LSM303 lsm; void setup() { Serial.begin(9600); // Try to initialise and warn if we couldn't detect the chip if (!lsm.begin()) { Serial.println("Oops ... unable to initialize the LSM303. Check your wiring!"); while (1); } } void loop() { lsm.read(); Serial.print("Accel X: "); Serial.print((int)lsm.accelData.x); Serial.print(" "); Serial.print("Y: "); Serial.print((int)lsm.accelData.y); Serial.print(" "); Serial.print("Z: "); Serial.println((int)lsm.accelData.z); Serial.print(" "); Serial.print("Mag X: "); Serial.print((int)lsm.magData.x); Serial.print(" "); Serial.print("Y: "); Serial.print((int)lsm.magData.y); Serial.print(" "); Serial.print("Z: "); Serial.println((int)lsm.magData.z); Serial.print(" "); delay(1000); }
Then I took my taped down cardboard setup and recorded the max and min values for x, y, and z in four directions. The numbers were a bit jumpy, so I let them settle well before noting the min and max. I did this process three times. Then I took the average of the min and max for each time, and then the average of those three numbers to establish the average number for each direction.
Now I had established the range for x and y. Z seemed to be superfluous. I found in a bit of code from one of last years contestants that these numbers could be used in the by mapping them to the total sensor range.
int16_t x = map(lsm.magData.x, -202, 224, -1000, 1000); int16_t y = map(lsm.magData.y, -397, -3, -1000, 1000);
I made these changes to my code, but had to comment out my "tracer" pixels because there wasn't enough room. Getting a functioning compass was priority one. I did the same controlled setup with much better results.
The dot of light stays mainly between 9 and 11, so I am calling this a success for the moment. Next, I needed to see if it were possible at all to light up the rest of my planned rig. 16 pixels around the brim, a ring of 16 at the cockade, and two pixels in the feather for a total of 34 pixels. Since space on the Gemma is so tight I am going to have to get really clever. In my original code, there is a colorwipe function that blacks out the previous pixel lit so only the current sensor reading is displayed. I discovered that I could use this bit to light other pixels in my rig if I used a color instead of 0,0,0. I set the last two pixel colors manually so that my feather could be a different color from the rest of the hat. I tried to compile this with the Arduino IDE and it compiled at 5,312 bytes out of an allowable 5,310. Two bytes to big! I then loaded this same code onto codebender and tried to compile it. It compiled it at 5, 300. Ten bytes to spare! I think I am going to continue to develop my code on codebender because it seems to have a better compiler for my current needs.
Here is the revised code. Available from codebender at https://codebender.cc/sketch:52246
Compass_Gemma_ring_pointer_V2b
//using calibration data //trying to light up entire unit //using code from https://learn.adafruit.com/steam-punk-goggles?view=all //idea for pointer pixel from http://www.element14.com/community/groups/arduino/blog/2013/08/27/get-closer-challenge--navigation-glove //idea for library modification from http://www.slickstreamer.info/2014/02/led-bracelet-accelerometer-3dprinting.html #include <TinyWireM.h> #include "TinyLSM303_mag.h" #include <Adafruit_NeoPixel.h> #define pin 1 #define numPixels 34 Adafruit_NeoPixel ring = Adafruit_NeoPixel(numPixels, pin, NEO_GRB + NEO_KHZ800); TinyLSM303_mag lsm; // Pi for calculations - not the raspberry type const float Pi = 3.14159; void setup() { ring.begin(); ring.show();// Initialize all pixels to 'off' lsm.begin();// Initialize the sensors } void loop() { // Read the magnetometer and determine the compass heading: lsm.read(); int16_t x = map(lsm.magData.x, -202, 224, -1000, 1000); int16_t y = map(lsm.magData.y, -397, -3, -1000, 1000); // Calculate the angle of the vector y,x from magnetic North float heading = (atan2(y,x) * 180) / Pi; // Normalize to 0-360 for a compass heading if (heading < 0) { heading = 360 + heading; } uint16_t d = map(heading, 0, 360, 0, 15);//map the heading to the size of the ring around the brim //wipe all the pixels with a color for(uint8_t i = 0; i < 32; i++) { ring.setPixelColor(i,ring.Color(0, 20, 50)); } ring.setPixelColor(d, ring.Color(255, 0, 0)); //set the closest pixel in the ring to the heading ring.setPixelColor(32,ring.Color(0, 100, 255)); ring.setPixelColor(33,ring.Color(0, 255, 100)); ring.show(); }
So now that I had a code running a more accurate compass and able to address all the pixels in my rig, I decided to try and breadboard my entire circuit. I had a strip of 8 sewable pixels that I had soldered together previously. I connected these to a bar strip of 8 pixels. These were then connected to the pixels in my feather. I plugged everything in and it all lit up for just a moment and then crashed. My Gemma was unresponsive. I unplugged everything, reloaded the code onto the Gemma and tried it out with just the ring of 16 to make sure that it worked. I realized that I needed to power my pixels with a separate power source so it doesn't over draw current from the Gemma pin. I hooked up everything back to the breadboard, this time only powering the magnetometer from the Gemma, the pixels were powered from a separate battery of the same size. Plugged everything in and it went super bright and weirdly jumpy in the color. I got scared and pulled the plug. Gemma was unresponsive again. Reloaded Gemma and went back to 16 to check if it was working, thankfully it was. So I think I am on the right path, I just need to do some more research into separate power sources. Once I get it stabilized I will continue to refine the code as much as I can.
Coming up next...
Back to my comfort zone, I am going to play with some velvet.
Top Comments