A Tiny Compass With ATtiny85

8,897

138

42

Introduction: A Tiny Compass With ATtiny85

About: I am a professor at the Faculty of Sciences of the National Autonomous University of Mexico. I teach Computational Physics and Basic Computing. My students and I have a lot of fun with Arduino!

This is our first project with ATtiny85; a simple pocket digital compass (in collaboration with J. Arturo Espejel Báez).


ATtiny85 is a high performance and low power microcontroller. It has 8 Kbytes of programmable flash memory. Due to this, the challenge in this project was to reduce the size of the program, since the circuit is very simple, thanks to the I2C protocol.

Supplies

For the Compass:

  • ATtiny85
  • HMC5883L Magnetometer
  • SSD1306 I2c 0.96" 128x64 OLED Display
  • Self-locking square button switch
  • 3.7V 300mAh Lipo Li-polymer Battery
  • 3D printed case (2 parts, please find the STL links)

For the Charger:

  • Two pieces of PCB; 17x10mm and 13x18mm
  • 3D printed case (2 parts, please find the STL links)
  • Micro USB 5V 1A TP4056 Lithium battery charger module

Step 1: The Program

It is necessary to load the program AB.ino into the ATtiny85 before wiring it in the circuit. For this, you can follow any of the tutorials on the internet, such as https://www.instructables.com/id/DIY-Attiny-Progr...
To compile the program, you need to install the library ssd1306 by Alexey Dynda, available in https://platformio.org/lib/show/1904/ssd1306

Step 2: The Circuit

Step 3: Wiring the ATtiny85

It is convenient to cut the unused pins of the ATtiny before soldering.

Prepare two 10-cm pairs of wire by stripping two 2-mm sections halfway and separated by about 5 mm from each other, as shown in the 1st and 2nd photos. Solder one section of the first pair of cables (A) to SDA (pin 5) and the other section to SCL (pin 7) as shown in the 3rd picture. With the other pair of wires (B), solder one cable to GND (pin 4) and the other one to +V (pin 8), like in the 4th photo.

Step 4: Wiring the OLED Display

Solder the four wires of one side of the ATtiny (SDA, SCL, +V, and GND) to the corresponding contacts of the OLED display and glue it to the case. Protect the display board with insulating tape.

Step 5: Place the Charger Contacts

Take two wires from a male header pin connector. Fold each one forming a hook as in the first photo. Insert one in the lateral side of the display case, and the other in the bottom lid as shown.

Step 6: Wiring the HMC5883L

Glue the HMC5883L magnetometer to the bottom lid as shown. Solder the SCL and SDA wires from the ATtiny to the corresponding contacts of the magnetometer, fold the charger contact wire and solder to the GND contact. Solder the +V and GND wires from the ATtiny to the corresponding contacts. Protect the magnetometer board with insulating tape.

Step 7: Wiring the Battery

Solder the negative pole of the battery to pin 4 of the ATtiny, and the positive to the charger contact in the side of the case. Add a wire from this contact to the switch (see next step).

Step 8: Wiring the Switch

Solder the wire from the lateral charger contact to one contact of the switch, and then another one to the +V contact of the magnetometer. Now you can test the Compass and glue the bottom lid.

Step 9: Calibrating

The program AB.ino has an automatic calibrating algorithm. You only have to turn on and rotate the compass 360º as shown in the video.

ATTENTION!
Never connect both external contacts as this would short-circuit the battery.

Step 10: Charger I

Cut two pieces of PCB of 17 mm x 10 mm and 13 mm x18 mm. Drill a hole in the small piece that matches with the hole in the round 3D printed part, pass a wire through and solder it. Glue the PCB as shown in the photo.

Step 11: Charger II

Solder a wire in the 17x10mm PCB piece and pass it throw the slot in the 3D printed part. Glue it as shown.

Step 12: Charger III

Fit and glue the 3D printed parts as shown and solder the wires to the battery charger module. The wire soldered in the bottom part is the negative. Now you can charge the compass' battery with a mini USB cable.

Maps Challenge

Second Prize in the
Maps Challenge

Be the First to Share

    Recommendations

    • Backyard Contest

      Backyard Contest
    • Summer Fun: Student Design Challenge

      Summer Fun: Student Design Challenge
    • Stone, Concrete, Cement Challenge

      Stone, Concrete, Cement Challenge

    42 Comments

    0
    Jobtn
    Jobtn

    10 months ago

    Hello,
    very Nice compass, i have received from Amazon a qmc5883 ans not a hmc5883 ,as i understand thé code si not thé same . What i havé to change in the program?
    best regards
    jean louis

    0
    Jobtn
    Jobtn

    Reply 10 months ago

    Hello,
    i havé received my oled, it s not working with the qmc 5883 (DB5883 write on the chip),i make some changes And it is working now but the heading is not very stable With this chip???
    best regards

    //tiny compass QMC5883
    #include <Wire.h>
    #include "ssd1306.h"
    #define address 0x0D // QMC5883 chip with DB5883
    const int k92=92,k31=31,k26=26;
    int xx,yy,zz;
    float xxx,yyy,MaxX=-5000,MinX=5000,MaxY=-5000,MinY=5000;
    const uint8_t G[4] = {
    0B00111100,
    0B01100110,
    0B01100110,
    0B00111100
    };

    const uint8_t PROGMEM N[8] = {
    0B11111111,
    0B11111111,
    0B11111111,
    0B00011110,
    0B01111100,
    0B11111111,
    0B11111111,
    0B11111111
    };

    const uint8_t PROGMEM S[8] = {
    0B01001110,
    0B11011111,
    0B11011111,
    0B11011011,
    0B11011011,
    0B11111011,
    0B11111011,
    0B01110010
    };

    const uint8_t PROGMEM E[8] = {
    0B11111111,
    0B11111111,
    0B11111111,
    0B11011011,
    0B11011011,
    0B11011011,
    0B11000011,
    0B11000011
    };

    const uint8_t PROGMEM W[8] = {
    0B00111111,
    0B01111111,
    0B11111111,
    0B01100000,
    0B11111100,
    0B01100000,
    0B11111111,
    0B01111111,
    };

    SPRITE sN,sS,sE,sW,sG;

    void setup(){
    Wire.begin();
    Wire.beginTransmission(address);
    Wire.write(0x09);
    Wire.write(0x1D);
    Wire.endTransmission();
    ssd1306_128x64_i2c_init();
    ssd1306_fillScreen(0x00);
    ssd1306_setFixedFont(ssd1306xled_font8x16);
    sN = ssd1306_createSprite( 0, 0, 8, N);
    sS = ssd1306_createSprite( 0, 0, 8, S);
    sE = ssd1306_createSprite( 0, 0, 8, E);
    sW = ssd1306_createSprite( 0, 0, 8, W);
    sG = ssd1306_createSprite(34, 47, 4, G);
    }

    void loop(){
    int b;
    float a;
    Wire.beginTransmission(address);
    Wire.write(0x00);
    Wire.endTransmission();

    Wire.requestFrom(address, 6);
    if(6<=Wire.available()){
    xx = Wire.read()<<8;
    xx |= Wire.read();
    zz = Wire.read()<<8;
    zz |= Wire.read();
    yy = Wire.read()<<8;
    yy |= Wire.read();
    }

    if (xx>=MaxX) MaxX=xx;
    if (xx<=MinX) MinX=xx;
    if (yy>=MaxY) MaxY=yy;
    if (yy<=MinY) MinY=yy;

    xxx = -50+100*(xx-MinX)/(MaxX-MinX);
    yyy = -50+100*(yy-MinY)/(MaxY-MinY);
    a = atan2(yyy,-xxx);
    b = a*180/PI-90;
    if (b<0) b+=360;
    if (b>180) b-=360;

    sN.x=k92+k26*cos(a+PI); sN.y=k31+k26*sin(a+PI);
    sS.x=k92+k26*cos(a); sS.y=k31+k26*sin(a);
    sW.x=k92+k26*cos(a+PI/2); sW.y=k31+k26*sin(a+PI/2);
    sE.x=k92+k26*cos(a-PI/2); sE.y=k31+k26*sin(a-PI/2);
    sN.eraseTrace();sS.eraseTrace();sW.eraseTrace();sE.eraseTrace();
    sN.draw();sS.draw();sW.draw();sE.draw();sG.draw();
    printGrados(b);
    }

    void printGrados(int g){
    char gStr[5] = "0000";
    gStr[0] = (g<0) ? '-':' ';
    g=abs(g);
    gStr[1] = '0' + g / 100;
    gStr[2] = '0' + (g /10)%10;
    gStr[3] = '0' + (g % 100) % 10;
    ssd1306_printFixed(0,50, gStr, STYLE_NORMAL);
    }

    0
    raul7321
    raul7321

    Reply 10 months ago

    Please, make sure that once powered on, rotate it 360° degrees to calibrate it. Please let me know if you still have any problems!

    0
    Jobtn
    Jobtn

    Reply 9 months ago

    Hello,
    after some trials, i confirm the qms5883 found on amazon is not working well even with an uno.
    j have to found an hms
    best regards
    jlouis

    0
    Lodestone1
    Lodestone1

    Reply 9 months ago

    Hi Jobtn, I had a similar problem but got lucky. I had receive both versions of the chip as I order a spare.
    It seems it is a flip of a coin as to which chip you might get.
    I am surprised that something that is sold as an identical board can be so different in operation.
    I see you changed the I2c address (0x1E to 0x0D) for the different chip which is a good start but apparently the data register addresses/order are different. Have you seen this https://github.com/nodemcu/nodemcu-firmware/issues/2187


    0
    Jobtn
    Jobtn

    Reply 9 months ago

    Hi lodestone1

    you are genius, it s working very nice

    thank you

    best regards

    jean louis

    0
    Lodestone1
    Lodestone1

    Reply 9 months ago

    No just an insomniac, I couldn’t sleep last night so I read through the data sheets.

    You are welcome.

    Tim

    0
    Lodestone1
    Lodestone1

    Reply 9 months ago

    Hi raul7321
    Having found that I had both the QMC and HMC chips on the two identical (not) boards I had received from china, I decided to try and learn something about the differences.
    My understanding of C++ is pitiful but building upon what Jobtn has done so far to try and resolve the issue I ended up with this.
    I have annotated what I think is happening.
    Crucially there is an addition reset register.
    This is stable and calibrates fine.

    #include <Wire.h>
    #include "ssd1306.h"
    #define address 0x0D // QMC5883
    const int k92=92,k31=31,k26=26;
    int xx,yy,zz;
    float xxx,yyy,MaxX=-5000,MinX=5000,MaxY=-5000,MinY=5000;
    const uint8_t G[4] = {
    0B00111100,
    0B01100110,
    0B01100110,
    0B00111100
    };
    const uint8_t PROGMEM N[8] = {
    0B11111111,
    0B11111111,
    0B11111111,
    0B00011110,
    0B01111100,
    0B11111111,
    0B11111111,
    0B11111111
    };
    const uint8_t PROGMEM S[8] = {
    0B01001110,
    0B11011111,
    0B11011111,
    0B11011011,
    0B11011011,
    0B11111011,
    0B11111011,
    0B01110010
    };
    const uint8_t PROGMEM E[8] = {
    0B11111111,
    0B11111111,
    0B11111111,
    0B11011011,
    0B11011011,
    0B11011011,
    0B11000011,
    0B11000011
    };
    const uint8_t PROGMEM W[8] = {
    0B00111111,
    0B01111111,
    0B11111111,
    0B01100000,
    0B11111100,
    0B01100000,
    0B11111111,
    0B01111111,
    };
    SPRITE sN,sS,sE,sW,sG;
    void setup(){
    Wire.begin();
    Wire.beginTransmission(address);
    Wire.write(0x0B); //Define set rest period
    Wire.write(0x01); //as recomended on the data sheet
    Wire.endTransmission();
    Wire.begin();
    Wire.beginTransmission(address);
    Wire.write(0x09); //mode register opened
    Wire.write(0x1D); //mode set continuous
    Wire.endTransmission();
    ssd1306_128x64_i2c_init();
    ssd1306_fillScreen(0x00);
    ssd1306_setFixedFont(ssd1306xled_font8x16);
    sN = ssd1306_createSprite( 0, 0, 8, N);
    sS = ssd1306_createSprite( 0, 0, 8, S);
    sE = ssd1306_createSprite( 0, 0, 8, E);
    sW = ssd1306_createSprite( 0, 0, 8, W);
    sG = ssd1306_createSprite(34, 47, 4, G);
    }
    void loop(){
    int b;
    float a;
    Wire.beginTransmission(address);
    Wire.write(0x00); //start address of data registers , six consecutive
    Wire.endTransmission();
    Wire.requestFrom(address, 6); // read the six consecutive data registers the order is completly different from HMC chips !
    if(6<=Wire.available()){
    xx = Wire.read(); // X LSB on QMC
    xx |= Wire.read()<<8; // X MSB on QMC
    yy = Wire.read(); // y LSB on QMC
    yy |= Wire.read()<<8; // y MSB on QMC
    zz = Wire.read(); // z LSB on QMC
    zz |= Wire.read()<<8; // z MSB on QMC
    }
    if (xx>=MaxX) MaxX=xx;
    if (xx<=MinX) MinX=xx;
    if (yy>=MaxY) MaxY=yy;
    if (yy<=MinY) MinY=yy;
    xxx = -50+100*(xx-MinX)/(MaxX-MinX);
    yyy = -50+100*(yy-MinY)/(MaxY-MinY);
    a = atan2(yyy,-xxx);
    b = a*180/PI-90;
    if (b<0) b+=360;
    if (b>180) b-=360;
    sN.x=k92+k26*cos(a+PI); sN.y=k31+k26*sin(a+PI);
    sS.x=k92+k26*cos(a); sS.y=k31+k26*sin(a);
    sW.x=k92+k26*cos(a+PI/2); sW.y=k31+k26*sin(a+PI/2);
    sE.x=k92+k26*cos(a-PI/2); sE.y=k31+k26*sin(a-PI/2);
    sN.eraseTrace();sS.eraseTrace();sW.eraseTrace();sE.eraseTrace();
    sN.draw();sS.draw();sW.draw();sE.draw();sG.draw();
    printGrados(b);
    }
    void printGrados(int g){
    char gStr[5] = "0000";
    gStr[0] = (g<0) ? '-':' ';
    g=abs(g);
    gStr[1] = '0' + g / 100;
    gStr[2] = '0' + (g /10)%10;
    gStr[3] = '0' + (g % 100) % 10;
    ssd1306_printFixed(0,50, gStr, STYLE_NORMAL);
    }

    0
    Lodestone1
    Lodestone1

    Reply 9 months ago

    Hi Jobtn

    I have experimented further and now have the QMC working and stable.
    The additional changes I made from your code are the reset register (without this being set I found it was totally unstable) and the order of the LSB/MSB and the X,Y,Z registers.
    I have put the code in another comment to the author. As I say I'm only just getting to grips with coding so don't laugh.

    0
    ronw5
    ronw5

    1 year ago

    I got error messages when verifying. ???

    Arduino: 1.8.10 (Windows 10), TD: 1.48, Board: "ATtiny25/45/85, Disabled, CPU, ATtiny85, 8 MHz (internal), B.O.D. Disabled"
    C:\Users\ronald\Documents\Arduino\hardware\ATTinyCore-master\avr\libraries\Wire\Wire.cpp: In member function 'void TwoWire::setClock(uint32_t)':
    C:\Users\ronald\Documents\Arduino\hardware\ATTinyCore-master\avr\libraries\Wire\Wire.cpp:85:3: error: 'TWBR' was not declared in this scope
    TWBR = ((F_CPU / frequency) - 16) / 2;
    ^~~~
    C:\Users\ronald\Documents\Arduino\hardware\ATTinyCore-master\avr\libraries\Wire\Wire.cpp:85:3: note: suggested alternative: 'TIFR'
    TWBR = ((F_CPU / frequency) - 16) / 2;
    ^~~~
    TIFR
    Multiple libraries were found for "ssd1306.h"
    Used: C:\Users\ronald\Documents\Arduino\libraries\ssd1306
    Multiple libraries were found for "SPI.h"
    Used: C:\Users\ronald\Documents\Arduino\hardware\ATTinyCore-master\avr\libraries\SPI
    Multiple libraries were found for "Wire.h"
    Used: C:\Users\ronald\Documents\Arduino\hardware\ATTinyCore-master\avr\libraries\Wire
    Not used: C:\Users\ronald\Documents\Arduino\libraries\Wire
    exit status 1
    Error compiling for board ATtiny25/45/85.
    This report would have more information with
    "Show verbose output during compilation"
    option enabled in File -> Preferences.

    0
    raul7321
    raul7321

    Reply 1 year ago

    It seems like a conflict with the ssd1306 library. Apparently you have several libraries installed with the same name. I recommend making sure that it is being compiled with the correct library (the one that corresponds to the link).
    You can locate duplicate libraries and rename them temporarily.

    Let me know if you need more help.

    0
    Rought_1
    Rought_1

    Reply 9 months ago

    Hello! I have the same problem. The SSD1306 library is what you need. I would be very grateful to you if you put the HEX file here.

    0
    Lodestone1
    Lodestone1

    Reply 9 months ago

    Many thanks for this I wish I'd seen it sooner. Thanks also to ronw5 for asking the question as I was not originally using "Spence Konde's ATTiny Core" which seems to have things compiling nicely.
    Now to see it working.
    Thanks again raul7321 for posting this instructable.

    0
    Rought_1
    Rought_1

    Reply 9 months ago

    Thank you, raul7321!

    0
    Rought_1
    Rought_1

    Reply 9 months ago

    Thank you! But the installation of this library gave nothing, the errors are the same as before:
    In file included from d:\program\arduino-nightly\hardware\tools\avr\avr\include\avr\io.h:99:0,
    from D:\PROGRAM\arduino-nightly\hardware\arduino\avr\libraries\Wire\utility\twi.c:25:
    D:\PROGRAM\arduino-nightly\hardware\arduino\avr\libraries\Wire\utility\twi.c:390:15: error: 'TWINT' undeclared (first use in this function)
    Dear author, please provide a link to your hex file. I am very limited in time and I do not have the opportunity to debug the AB program.

    0
    raul7321
    raul7321

    Reply 9 months ago

    Hi. I don't have much time either, but I uploaded the .hex file to the instructable, in step 1

    0
    raul7321
    raul7321

    Reply 9 months ago


    Hi. I uploaded the .hex file to the instructable, in step 1

    0
    raul7321
    raul7321

    Reply 9 months ago

    Hi. I uploaded the .hex file to the instructable, in step 1