1.8" TFT Display to Baby-O with LSM303DLH Compass

I started a new post to differentiate between the different projects for the 1.8" TFT display. This one focuses on the LSM303DHL Compass module from Pololu to a BabyO using Arduino code.

This sketch sets up the display to show the output from the compass. The accelerometer data for the XYZ planes is displayed in text and by a graphical bar. The accelerometer data ranges from -2048 to +2047. The bar graph is setup to convert the accel data to +/-2g. The heading is displayed in text as degrees and by a compass dial. The Arduino sketch code is posted below.

I kept some of my Serial Monitor debug code in the sketch.

I plan on mounting this to my “tank” platform and using Wixels to send the information back to my computer.

Keep having fun,
Charlie


/*
Use this sketch to display Compass data for the LSM303DLH sold by Pololu on a 1.8" TFT
display.  This sketch uses the default +/-2g setup.

ex:
int aXmin = -2048;
int aXmax = 2047;
int aYmin = -2048;
int aYmax = 2047;
int aZmin = -2048;
int aZmax = 2047;

compass.read();
int myAccX = map(compass.a.x, aXmin, aXmax, 0, 100);
int myAccY = map(compass.a.y, aYmin, aYmax, 0, 100);
int myAccZ = map(compass.a.z, aZmin, aZmax, 0, 100);

If you ran the Acc Calibration sketch, you will have +/- 1g values.  
The 1g values could be helpful in making decisions in your sketch.

Todo: Add code to display the accellerometer bars

*/

// Setup for Pololu BabyO328P
// Serial from BabyO has to be wired to the Pololu USB Programmer
// On the Arduino, PDO/PD1 is wired to the ATMEGA16U PD3(Tx)/PD2(Rx)
// Serial Pins 328P Rx/Tx = 30/31 PD0/PD1 (BabyO Header 8/9)
// Wire BabyO 8/9 to Pololu Wixel Tx/Rx - Power Wixel from the Compass 3v3 pin
// Note: Tx(9) gets wired to Rx and Rx(8) to Tx as a NULL MODEM
// Grounds should be connected as well

// Pins SCLK and MOSI are fixed in hardware, and pin 10 (or 53) must be an output.
// Using Arduino UNO Pins as they are mapped to a ATMEGA328P
// Note: MOSI PB3 is also mapped to Motor Control in the BabyO
// So Motor and SPI Usage are mutually exclusive
// Need to pick up PB3 from the BabyO ISP Header Pin 4
// Use Board: Pololu Orangutan or 3pi robot w/ ATmega328P via Programme

                  //                 Arduino          328P  BabyO
// sclk and mosi are pre-defined in<SPI.h>
// #define sclk 13   // for BabyO328 use pin 13 Mapped to PB5 (Header 07)
// #define mosi 11   // for BabyO328 use pin 11 Mapped to PB3 (ISP Header Only)
#define cs 10     // for BabyO328 use pin 10 Mapped to PB2 (Header 05)
#define dc 7      // for BabyO328 use pin 07 Mapped to PD7 (Header 12)
#define rst 8     // for BabyO328 use pin 08 Mapped to PB0 (Header 03)

// Color definitions
#define	BLACK           0x0000
#define	BLUE            0x001F
#define	RED             0xF800
#define	GREEN           0x07E0
#define CYAN            0x07FF
#define MAGENTA         0xF81F
#define YELLOW          0xFFE0  
#define WHITE           0xFFFF
// added more colors to original example
#define DKBLUE          0x0018
#define DKGREEN         0x0600
#define DKRED           0x9802
#define ORANGE          0xF400
#define CREAM           0xFF96
#define BEIGE           0xFF50

#include <ST7735.h>
#include <SPI.h>
#include <Wire.h>
#include <LSM303.h>

// Option 2: must use the hardware SPI pins 
// (for UNO thats sclk = 13 and sid = 11) and pin 10 must be 
// an output. This is much faster - also required if you want
// to use the microSD card (see the image drawing example)
ST7735 tft = ST7735(cs, dc, rst);

// the backlight PWM is controlled indirectly by ADC7 on the BabyO
const int backlight = 9;      // Digital Pin 7 PWM for backlight
int brightness = 255;         // PWM for backlight Values 0 - 255

int sensorValueNew = 0;
int sensorValue7 = 0;      // Backlight Control
int sensor = 0;
int xFill = 0;

// Setting the DialRadius determines the size of the dial.
// int x, y sets the position of the center of the dial.
// All parameters associated with the dial will attempt to
// locate around the dial.  Making the DialRadius too large
// will push parts of text and the dial off the screen.
// Design differnt dials for compass, speed, wind direction, etc.
int DialRadius = 20;
int x = 116;
int y = 87;

// these are used to draw lines and the dial needle
double y_to = 0;
double x_to = 0;
int degree = 0;
double calcSin = 0;
double myPI = 3.14;

// variables to help draw the bars for the accell data
int aXmin = -2048;
int aXmax = 2047;
int aYmin = -2048;
int aYmax = 2047;
int aZmin = -2048;
int aZmax = 2047;

// Array used to draw the tick marks around the dial
double tickMarks[12] = {22.5, 45, 67.5, 112.5, 135, 157.5, 202.5, 225, 247.5, 292.5, 315, 337.5};

String sensorString = "0000";

LSM303 compass;

void setup() {
  Serial.begin(9600);	            // Open the COM port
  tft.initR();			    // initialize a ST7735R chip
  tft.writecommand(ST7735_DISPON);
  analogWrite(backlight, brightness);
  mainScreen();
}

void loop() {
  getCompass();
  drawNeedle(degree);
  updateAcc();
  updateBacklight();
  delay(100);
}

void mainScreen (void) {
  tft.setRotation(1);
  tft.setTextSize(1);
  tft.fillScreen(BEIGE);
  tft.fillCircle(x, y, DialRadius+7, CREAM);
  tft.drawCircle(x, y, DialRadius+6, BLACK);
  tft.drawCircle(x, y, DialRadius+3, BLACK);
  tft.fillCircle(x, y, DialRadius+1, CREAM);
  // Current N, S, E, W tick marks.  Will update to triangles later
  tft.drawLine(x, y-DialRadius-10, x, y-DialRadius-1, RED);
  tft.fillCircle(x, y-DialRadius-5, 2, RED);
  tft.fillTriangle(x-3, y+DialRadius+10, x+3, y+DialRadius+10, x, y+DialRadius+1, RED);
  tft.fillTriangle(x-DialRadius-10, y-3, x-DialRadius-10, y+3, x-DialRadius-1, y, RED);
  tft.fillTriangle(x+DialRadius+10, y-3, x+DialRadius+10, y+3, x+DialRadius+1, y, RED);
  // the center dot in the middle of the dial
  tft.fillCircle(x, y, 2, BLUE);
  // Text around the dial attempts to adjust to dial size
  tft.setCursor(x-2, y-DialRadius-17);
  tft.setTextColor(RED);
  tft.print("N");
  tft.setCursor(x-2, y+DialRadius+12);
  tft.setTextColor(RED);
  tft.print("S");
  tft.setCursor(x-DialRadius -17, y-3);
  tft.setTextColor(RED);
  tft.print("W");
  tft.setCursor(x+DialRadius+12, y-3);
  tft.setTextColor(RED);
  tft.print("E");
  tft.setCursor((x+((DialRadius+36)/2)), (y-((DialRadius+36)/2)));
  tft.setTextColor(RED);
  tft.print("NE");
  tft.setCursor((x+((DialRadius+30)/2)), (y+((DialRadius+30)/2)));
  tft.setTextColor(RED);
  tft.print("SE");
  tft.setCursor((x-((DialRadius+55)/2)), (y+((DialRadius+30)/2)));
  tft.setTextColor(RED);
  tft.print("SW");
  tft.setCursor((x-((DialRadius+55)/2)), (y-((DialRadius+35)/2)));
  tft.setTextColor(RED);
  tft.print("NW");
  // uses the tick marks array to draw the tick marks
  dispTicks();
  tft.drawRect(57, 1, 102, 10, BLACK);
  tft.drawRect(57, 12, 102, 10, BLACK);
  tft.drawRect(57, 23, 102, 10, BLACK);
  tft.drawHorizontalLine(58, 36, 102, BLACK);
  tft.drawVerticalLine(58, 34, 5, BLACK);  // -2.0
  tft.drawVerticalLine(71, 34, 5, BLACK);  // -1.5
  tft.drawVerticalLine(84, 34, 5, BLACK);  // -1.0
  tft.drawVerticalLine(96, 34, 5, BLACK);  // -0.5
  tft.drawVerticalLine(109, 34, 5, BLACK); //  0.0
  tft.drawVerticalLine(122, 34, 5, BLACK); //  0.5
  tft.drawVerticalLine(134, 34, 5, BLACK); //  1.0
  tft.drawVerticalLine(145, 34, 5, BLACK); //  1.5
  tft.drawVerticalLine(158, 34, 5, BLACK); //  2.0
  tft.setTextColor(BLACK);
  tft.setCursor(50,39);
  tft.print("-2");
  tft.setCursor(76,39);
  tft.print("-1");
  tft.setCursor(107,39);
  tft.print("0");
  tft.setCursor(132,39);
  tft.print("1");
  tft.setCursor(155,39);
  tft.print("2");
  compassSetup();
}

void updateBacklight (void) {
  // Uses the built in POT at ADC7 to change the brightness
  // Valid values for PWM is 0 to 255.  analogRead can equal 0 to 1023
  // so we need to convert the analogRead to a valid PWM value.
  sensorValueNew = analogRead(A7);
  brightness = (sensorValueNew -3) / 4;
  // update the backlight brightness
  analogWrite(backlight, brightness);
  sensorValue7 = sensorValueNew;
}

void compassSetup(void){
  Wire.begin();
  compass.init();
  compass.enableDefault();
  
  // Calibration values. Use the Calibrate example program to get the values for
  // your compass.
  compass.m_min.x = -592; compass.m_min.y = -463; compass.m_min.z = -421;
  compass.m_max.x = +440; compass.m_max.y = +565; compass.m_max.z = 487;
}

void getCompass(void){
    compass.read();
    int heading = compass.heading((LSM303::vector){0,-1,0});
    degree = heading;
}

/*
void readCompass(void){
  compass.read();
  
  Serial.print("A ");
  Serial.print("X: ");
  Serial.print((int)compass.a.x);
  Serial.print(" Y: ");
  Serial.print((int)compass.a.y);
  Serial.print(" Z: ");
  Serial.print((int)compass.a.z);

  Serial.print(" M ");  
  Serial.print("X: ");
  Serial.print((int)compass.m.x);
  Serial.print(" Y: ");
  Serial.print((int)compass.m.y);
  Serial.print(" Z: ");
  Serial.print((int)compass.m.z);
  
  int heading = compass.heading((LSM303::vector){0,-1,0});
  Serial.print(" Heading: ");
  Serial.println(heading);
}
*/

void drawNeedle(double _degree){
  //  draw or erase needle
  
  // Erase the old needle
  tft.drawLine(x + x_to, y + y_to, x, y, CREAM);
  
  x_to = 0;
  y_to = 0;
  
  // I use our old friend from geometry, the pythagorean theorem, to draw the needle
  double x_t = 0;
  double calcRad = _degree * myPI / 180;
  // I set this up to use in place of multiple sin(calcRad) calls, but I
  // left the sin(calcRad) in for now
  calcSin = sin(calcRad);
  
  // Each quadrant around the circle has + or - values in relation to the center
  // point (x, y).  The sin() function returns +/- values for x_to, but y_to has
  // to be manipulated.
  if (degree > -1 && degree < 91)
  {
    x_to = sin(calcRad)*DialRadius;
    // The x_t value was inserted for trouble shooting.  May be removed later.
    // Shouldn't matter because (neg * neg) = positive number.
    x_t = -1 * x_to;
    y_to = -1 * sqrt((DialRadius*DialRadius)-(x_t * x_t));
  }
  if (degree > 90 && degree < 181)
  {
    x_to = sin(calcRad)*DialRadius;
    y_to = (sqrt((DialRadius*DialRadius)-(x_to * x_to)));
  }
  if (degree > 180 && degree < 271)
  {
    x_to = sin(calcRad)*DialRadius;
    y_to = (sqrt((DialRadius*DialRadius)-(x_to * x_to)));
  }
  if (degree > 270 && degree < 361)
  {
    x_to = sin(calcRad)*DialRadius;
    x_t = -1 * x_to;
    y_to = -1*(sqrt((DialRadius*DialRadius)-(x_t * x_t)));
  }
  
  // update the screen data
  tft.drawLine(x + x_to, y + y_to, x, y, BLUE);
  tft.fillCircle(x, y, 2, BLUE);
  tft.setCursor(2, 2);
  tft.setTextColor(BLUE);
  tft.print("AX:");
  tft.fillRect(2, 12, 50, 8, BEIGE);
  tft.setCursor(2, 12);
  tft.print(compass.a.x);
  
  tft.setCursor(2, 22);
  tft.setTextColor(BLUE);
  tft.print("AY:");
  tft.fillRect(2, 32, 50, 8, BEIGE);
  tft.setCursor(2, 32);
  tft.print(compass.a.y);
  
  tft.setCursor(2, 42);
  tft.setTextColor(BLUE);
  tft.print("AZ:");
  tft.fillRect(2, 52, 50, 8, BEIGE);
  tft.setCursor(2, 52);
  tft.print(compass.a.z);
  
  tft.setCursor(2, 87);
  tft.setTextColor(RED);
  tft.print("DEGREE:");
  tft.fillRect(2, 98, 60, 25, BEIGE);
  tft.setTextSize(3);
  tft.setTextColor(BLUE);
  tft.setCursor(4, 100);
  tft.print(degree);
  tft.setTextSize(1);
  tft.setTextColor(WHITE);
}

void dispTicks(void){
  // uses the tickMark[] array    
  for (int i = 0; i < 12; i++){
    
    double xtick_F = 0;
    double ytick_F = 0;
    double xtick_T = 0;
    double ytick_T = 0;

    double x_t = 0;
    double calcRad = tickMarks[i] * myPI / 180;
    calcSin = sin(calcRad);

    if (tickMarks[i] > -1 && tickMarks[i] < 91)
    {
      xtick_F = sin(calcRad)*(DialRadius+1);
      x_t = -1 * xtick_F;
      ytick_F = -1 * sqrt((DialRadius+1)*(DialRadius+1)-(x_t * x_t));
      xtick_T = sin(calcRad)*(DialRadius+7);
      x_t = -1 * xtick_T;
      ytick_T = -1 * sqrt((DialRadius+7)*(DialRadius+7)-(x_t * x_t));
    }
    if (tickMarks[i] > 90 && tickMarks[i] < 181)
    {
      xtick_F = sin(calcRad)*(DialRadius+1);
      x_t = xtick_F;
      ytick_F = sqrt((DialRadius+1)*(DialRadius+1)-(x_t * x_t));
      xtick_T = sin(calcRad)*(DialRadius+7);
      x_t = xtick_T;
      ytick_T = sqrt((DialRadius+7)*(DialRadius+7)-(x_t * x_t));
    }
    if (tickMarks[i] > 180 && tickMarks[i] < 271)
    {
      xtick_F = sin(calcRad)*(DialRadius+1);
      x_t = xtick_F;
      ytick_F = sqrt((DialRadius+1)*(DialRadius+1)-(x_t * x_t));
      xtick_T = sin(calcRad)*(DialRadius+7);
      x_t = xtick_T;
      ytick_T = sqrt((DialRadius+7)*(DialRadius+7)-(x_t * x_t));
    }
    if (tickMarks[i] > 270 && tickMarks[i] < 361)
    {
      xtick_F = sin(calcRad)*(DialRadius+1);
      x_t = -1 * xtick_F;
      ytick_F = -1 * sqrt((DialRadius+1)*(DialRadius+1)-(x_t * x_t));
      xtick_T = sin(calcRad)*(DialRadius+7);
      x_t = -1 * xtick_T;
      ytick_T = -1 * sqrt((DialRadius+7)*(DialRadius+7)-(x_t * x_t));
    }
    tft.drawLine(x + xtick_T, y + ytick_T, x + xtick_F, y + ytick_F, RED);
  }
}

void updateAcc(void){
  // update Accellerometer rectangles
  
  int myAccX = map(compass.a.x, aXmin, aXmax, 0, 100);
  int myAccY = map(compass.a.y, aYmin, aYmax, 0, 100);
  int myAccZ = map(compass.a.z, aZmin, aZmax, 0, 100);
  
  xFill = myAccX;
  tft.fillRect(58, 2, xFill, 8, DKGREEN);
  tft.fillRect(58+xFill+1, 2, 100-xFill-1, 8, WHITE);
  
  xFill = myAccY;
  tft.fillRect(58, 13, xFill, 8, DKBLUE);
  tft.fillRect(58+xFill+1, 13, 100-xFill-1, 8, WHITE);
  
  xFill = myAccZ;
  tft.fillRect(58, 24, xFill, 8, DKRED);
  tft.fillRect(58+xFill+1, 24, 100-xFill-1, 8, WHITE);
}

Hello.

Cool project. Your display looks very nice. I have added your project to our list of community projects!

- Ryan

Ryan,

Thanks for the posting to the community projects.

I am willing to answer any questions that anybody has. Has anyone else purchased one of these displays?
Just curious.

Charlie

Hi charlie,

I have tried to the implement a similar compass graphics on mikromedia 3.5" display, once. Out of curiosity part of my question is , how did u get the compass needle to rotate to the degree that you wanted.and the other other part of my question is ,the needle of the compass when it moves ,say from positionX to positionX+1,how do u handle the deletion of the needle line at positionX.

thankx ,

Vikram

OMG It has been a couple of years since I’ve been able to get back to check on things. So sorry that I didn’t answer the previous question.

To answer the last question first… Just in case anyone was curious, I used to use a C++ XOR pen command to delete the old needle, but since the option doesn’t exist for arduino code, I did a trick. I keep track of the current needle position and if the needle needs to move, I write the needle using the background color. In this case it was CREAM. In the function, drawNeedle, the first thing I do is to use the tft.drawLine command with the color CREAM which is the background on my compass display.

void drawNeedle(double _degree){
  
  // Erase the old needle
  tft.drawLine(x + x_to, y + y_to, x, y, CREAM);

The first part of the question, “how did u get the compass needle to rotate to the degree that you wanted”

The folowing is from the code. I use these variables to calculate the needle position based upon the degree.

// Setting the DialRadius determines the size of the dial.
// int x, y sets the position of the center of the dial.
// All parameters associated with the dial will attempt to
// locate around the dial center.  Making the DialRadius too large
// will push parts of text and the dial off the screen.
// Design differnt dials for compass, speed, wind direction, etc.
int DialRadius = 20;
int x = 116;
int y = 87;

// these are used to draw lines and the dial needle
double y_to = 0;
double x_to = 0;
int degree = 0;
double calcSin = 0;
double myPI = 3.14;

The getCompass function returns the degree. This is passed to the drawNeedle(degree) function. I then convert the degree to radians for the sin() function:

double calcRad = _degree * myPI / 180;

Here is the trick to calculating the needle (code from the program):

  // I use our old friend from geometry, the pythagorean theorem, to draw the needle
  // a^2 + b^2 = c^2
  // c = DialRadius
  double x_t = 0;
  double calcRad = _degree * myPI / 180;
  
  // Each quadrant around the circle has + or - values in relation to the center
  // point (x, y).  The sin() function returns +/- values for x_to, but y_to has
  // to be manipulated.
  if (degree > -1 && degree < 91)
  {
    x_to = sin(calcRad)*DialRadius;
    // The x_t value was inserted for trouble shooting.  May be removed later.
    // Shouldn't matter because (neg * neg) = positive number.
    x_t = -1 * x_to;
    y_to = -1 * sqrt((DialRadius*DialRadius)-(x_t * x_t));
  }

There is an IF statement for each quadrant of the circle because I need to determine the left and right endpoint for x_to and the up/down endpoint for y_to. So the command,

tft.drawLine(x + x_to, y + y_to, x, y, BLUE);

draws the needle using x and y as the center of the needle and “x+x_to” and “y+y_to” are the endpoints of the needle.

I hope this helps.

Charlie

Thank you for sharing your solution and updating this thread.

-Derrill