/ CPX_Compass / CPX_Compass.ino
CPX_Compass.ino
  1  // SPDX-FileCopyrightText: 2018 Dave Astels for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  /* Circuit Playground Express compass. */
  6  
  7  /* Adafruit invests time and resources providing this open source code. */
  8  /* Please support Adafruit and open source hardware by purchasing */
  9  /* products from Adafruit! */
 10  
 11  /* Written by Dave Astels for Adafruit Industries */
 12  /* Copyright (c) 2018 Adafruit Industries */
 13  /* Licensed under the MIT license. */
 14  
 15  /* All text above must be included in any redistribution. */
 16  
 17  #include <Wire.h>
 18  #include <Adafruit_NeoPixel.h>
 19  #include <Adafruit_Sensor.h>
 20  #include <Adafruit_LSM303_U.h>
 21  #include <math.h>
 22  
 23  /* Assign a unique ID to this sensor at the same time */
 24  Adafruit_LSM303_Mag_Unified mag = Adafruit_LSM303_Mag_Unified(12345);
 25  
 26  // Replace these two lines with the results of calibration
 27  //---------------------------------------------------------------------------
 28  
 29  float raw_mins[2] = {1000.0, 1000.0};
 30  float raw_maxes[2] = {-1000.0, -1000.0};
 31  //---------------------------------------------------------------------------
 32  
 33  float mins[2];
 34  float maxes[2];
 35  float corrections[2] = {0.0, 0.0};
 36  
 37  
 38  // Support both classic and express
 39  #ifdef __AVR__
 40  #define NEOPIXEL_PIN 17
 41  #else
 42  #define NEOPIXEL_PIN 8
 43  #endif
 44  
 45  // When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
 46  // Note that for older NeoPixel strips you might need to change the third parameter--see the strandtest
 47  // example for more information on possible values.
 48  Adafruit_NeoPixel strip = Adafruit_NeoPixel(10, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
 49  
 50  // Map direction pie slices (of 30 deg each) to a neopixel, or two for the missing ones at USB & power.
 51  int led_patterns[12][2] = {{4, 5}, {5, -1}, {6, -1}, {7, -1}, {8, -1}, {9, -1}, {9, 0}, {0 -1}, {1, -1}, {2, -1}, {3, -1}, {4, -1}};
 52  
 53  #define BUTTON_A 4
 54  
 55  void fill(int red, int green, int blue) {
 56    for (int i = 0; i < 10; i++) {
 57      strip.setPixelColor(i, red, green, blue);
 58    }
 59    strip.show();
 60  }
 61  
 62  
 63  // Do some initial reading to let the magnetometer settle in.
 64  // This was found by experience to be required.
 65  // Indicated to the user by blue LEDs.
 66  void warm_up(void)
 67  {
 68    sensors_event_t event;
 69    fill(0, 0, 64);
 70    for (int ignore = 0; ignore < 100; ignore++) {
 71      mag.getEvent(&event);
 72      delay(10);
 73    }
 74  }
 75  
 76  
 77  // Find the range of X and Y values.
 78  // User needs to rotate the CPX a bunch during this.
 79  // Can be refined by doing more of the saem by pressing the A button.
 80  // Indicated to the user by green LEDs.
 81  void calibrate(bool do_the_readings)
 82  {
 83    sensors_event_t event;
 84    float values[2];
 85  
 86    fill(0, 64, 0);
 87  
 88    if (do_the_readings) {
 89      unsigned long start_time = millis();
 90      while (millis() - start_time < 5000) {
 91  
 92        mag.getEvent(&event);
 93        values[0] = event.magnetic.x;
 94        values[1] = event.magnetic.y * -1;
 95        if (values[0] != 0.0 && values[1] != 0.0) { /* ignore the random zero readings... it's bogus */
 96          for (int i = 0; i < 2; i++) {
 97            raw_mins[i] = values[i] < raw_mins[i] ? values[i] : raw_mins[i];
 98            raw_maxes[i] = values[i] > raw_maxes[i] ? values[i] : raw_maxes[i];
 99          }
100        }
101        delay(5);
102      }
103    }
104  
105    for (int i = 0; i < 2; i++) {
106      corrections[i] = (raw_maxes[i] + raw_mins[i]) / 2;
107      mins[i] = raw_mins[i] - corrections[i];
108      maxes[i] = raw_maxes[i] - corrections[i];
109    }
110    fill(0, 0, 0);
111  }
112  
113  
114  void setup(void)
115  {
116    strip.begin();
117    strip.show();
118  
119    pinMode(BUTTON_A, INPUT_PULLDOWN);
120  
121    /* Enable auto-gain */
122    mag.enableAutoRange(true);
123  
124    /* Initialise the sensor */
125    if(!mag.begin())
126    {
127      /* There was a problem detecting the LSM303 ... check your connections */
128      fill(255, 0, 0);
129      while(1);
130    }
131  
132    warm_up();
133  
134    // If reset with button A pressed or calibration hasn't been done, run calibration and report the results
135    if (digitalRead(BUTTON_A) || (raw_mins[0] == 1000.0 && raw_mins[1] == 1000.0)) {
136      while (!Serial);
137      Serial.begin(9600);
138      Serial.println("Compass calibration\n");
139  
140      raw_mins[0] = 1000.0;
141      raw_mins[1] = 1000.0;
142      raw_maxes[0] = -1000.0;
143      raw_maxes[1] = -1000.0;
144      calibrate(true);
145  
146      Serial.println("Calibration results\n");
147      Serial.println("Update the corresponding lines near the top of the code\n");
148      Serial.print("float raw_mins[2] = {"); Serial.print(raw_mins[0]); Serial.print(", "); Serial.print(raw_mins[1]); Serial.println("};");
149      Serial.print("float raw_maxes[2] = {"); Serial.print(raw_maxes[0]); Serial.print(", "); Serial.print(raw_maxes[1]); Serial.println("};\n");
150  
151      while(1);
152    } else {
153      calibrate(false);
154    }
155  }
156  
157  
158  // Map a value from the input range to the output range
159  // Used to map MAG values from the calibrated (min/max) range to (-100, 100)
160  float normalize(float value, float in_min, float in_max) {
161    float mapped = (value - in_min) * 200 / (in_max - in_min) + -100;
162    float max_clipped = mapped <  100 ? mapped : 100;
163    float min_clipped = max_clipped > -100 ? max_clipped : -100;
164    return min_clipped;
165  }
166  
167  
168  void loop(void)
169  {
170    // Pressing button A does another round of calibration.
171    if (digitalRead(BUTTON_A)) {
172      calibrate(true);
173    }
174  
175    sensors_event_t event;
176    mag.getEvent(&event);
177  
178    float x = event.magnetic.x;
179    float y = event.magnetic.y * -1;
180  
181    if (x == 0.0 && y == 0.0) {
182      return;
183    }
184  
185    float normalized_x = normalize(x - corrections[0], mins[0], maxes[0]);
186    float normalized_y = normalize(y - corrections[1], mins[1], maxes[1]);
187  
188    int compass_heading = (int)(atan2(normalized_y, normalized_x) * 180.0 / 3.14159);
189    // compass_heading is between -180 and +180 since atan2 returns -pi to +pi
190    // this translates it to be between 0 and 360
191    compass_heading += 180;
192  
193    // We add 15 to account to the zero position being 0 +/- 15 degrees.
194    // mod by 360 to keep it within a circle
195    // divide by 30 to find which pixel corresponding pie slice it's in
196    int direction_index = ((compass_heading + 15) % 360) / 30;
197  
198    // light the pixel(s) for the direction the compass is pointing
199    // the empty spots where the USB and power connects are use the two leds to either side.
200    int *leds;
201    leds = led_patterns[direction_index];
202    for (int pixel = 0; pixel < 10; pixel++) {
203      if (pixel == leds[0] || pixel == leds[1]) {
204        strip.setPixelColor(pixel, 4, 0, 0);
205      } else {
206        strip.setPixelColor(pixel, 0, 0, 0);
207      }
208    }
209    strip.show();
210    delay(50);
211  }