Arizona Chris
Joined: 20 Dec 2014 Posts: 69 Location: Arizona
|
A Robot / Pic Project Digital compass chip - RS232.... |
Posted: Sat Sep 26, 2015 6:57 am |
|
|
This project was initiated because the amount of time spent on incorporating a digital compass into our latest indoor robot (Plant watering robot) was huge, and the amount of ROM space it took up on the main processor with its math libraries took just over 3k of valuable program space. The robot is done now and working great on a daily basis, but I vowed never to try to put such a large sensor driver code into my next robots! Thus this chip was born, out of a need to accomplish this task. Before I go any further on this, just a brief overview of what is needed these day to put a compass in a project such as a mobile robot or any other PIC project requiring a sense of orientation.
In the past, we had to use such expensive devices as the Devantech digital compass which is around $70 and puts out a pulse that varies from 0 to 36mS for zero to 360 degrees. While it is fairly straightforward to measure such data, it would be far better to directly
receive fast serial data from a compass to fetch our orientation quickly and without the time it takes to measure such a long pulse. Today, we have dual and triple axis magnetometers for under $30 that require rather complex code to read and set up. In addition, nearly all of the popular compass products require a 3.3v power supply and need bi directional level shifters to both send and revive data. This puts a huge damper on most of us using these devices for sure. Now, however you can buy from Adafruit a triple axis magnetometer break out board, only an inch square that has BOTH voltage regulation and level shifters built in for direct interface with standard 5v PIC micros. And the price - $9. Buy a handful of these like we did for this price! You can read about this awesome value here:
http://www.adafruit.com/products/1746
For this project, I used a still extant PIC16F876a processor, but any smaller PIC with a UART and I2C will work fine. As I said, the program takes up about 3k of ROM, so make sure your chip can have this amount with a bit of room to spare for mods. Data is sent at a constant rate of at least 10 measurements per second. Here I have added in the drivers for my New Haven LCD from Jameco, (which is a $16 killer deal in itself) so you can see the data live to check the function of the compass out. In addition, it sends data over the serial line at 9600kb with the RS232 protocol. The update rate of the data even with the LCD output running at the same time is about once every 68mS. For a compass, thats really fast and plenty for navigation of any mobile robot. For flying robots you might want to pull the LCD stuff out to gain a bit of speed.
By off loading the extensive compass driver and math libraries off your main processor, you have given yourself a lot more programming space, and do not have to hassle with putting such complex and perhaps interacting code into your main program! We use this exact code in our latest robot and it has worked flawlessly for months now. So get those compass boards on order now and get that robot rolling!
For more info, Ive posted a complete write up, schematics, and photos here:
http://www.schursastrophotography.com/PICprojects/compasschip1.html
To access the compass data from the main processor, here is some typical code. Remember, Im not trying to highlight the full full receive code because it is something you put in your main processor, which can be any device with a UART! :
Code: |
//For example reciving compass data on pin A0:
#use rs232(baud=9600, rcv=Pin_A0, bits=8,parity=N, DISABLE_INTS,stream=COMPASS)
//Note: the "disable_ints is critical, turns off constant barrage of interrupts
//during the aquisition of data in the get command so timing is not affected.
GETCOMPASS(); //go get compass data serially
//********* Functions which have prototypes at top of program ****************
void GETCOMPASS(void) {
//test loop to see if data has come in lasting maximum of 1/2 second:
compasstimeout = 0; //reset timeout
while (!kbhit(COMPASS) && (++compasstimeout < 50000)) //half second wait IF NO SIGNAL
delay_us(10);
if (kbhit(COMPASS)) { //We have a signal
lowbyte = fgetc(COMPASS);
highbyte = fgetc(COMPASS);
bearing2 = MAKE16(highbyte,lowbyte); //RAW
bearing = (float) bearing2 / 100; //degrees with 2 decimals
}
}
|
And here is the main compass chip code, using an PIC16F876a:
Code: |
//****************************************************************************
//Chris Schur
//(Compass Sensor Chip - 1 16F876a)
//Date: 9/14/15
//****************************************************************************
/*Description of this Program:
This version - Sends 5 digits serially of compass data from the Adafruit compass module
*/
//I/O Designations ---------------------------------------------------
// RA0:
// RA1:
// RA2:
// RA3:
// RA4: (Open Collector output)
// RA5:
// RB0:
// RB1:
// RB2:
// RB3:
// RB4:
// RB5:
// RB6:
// RB7:
// RC0: Status LED output
// RC1: LCD output
// RC2:
// RC3: INPUT - SCL - to magnetometer
// RC4: INPUT - SDA - to magnetometer
// RC5:
// RC6: serial TX UART
// RC7:
//--------------------------------------------------------------------
//Include Files:
#include <16F876A.h> //Normally chip, math, etc. used is here.
#include "math.h" //required for compass
//Directives and Defines:
#fuses NOPROTECT,HS,NOWDT //xtal is used
#use delay(crystal=10MHz) //xtal speed
//set up i2c - data, clock pins are here hardware on 877a.part uses slow spec.
#use I2C(master, sda=PIN_C4, scl=PIN_C3, slow, FORCE_HW) //HARDWARE IMPLEMENT
#use fast_io(ALL) //must define tris below in main when using this
// HMC5883 required Registers
#define W_DATA 0x3C //Used to perform a Write operation
#define R_DATA 0x3D //Used to perform a Read operation
#define CON_A 0x00 //Sets up measurement and sampling parameters.
#define CON_B 0x01 //Send continuous MeVARurement mode.
#define MOD_R 0x02 //Read/Write Register, Selects the operating mode. Default = Single meVARurement
#define X_MSB 0x03 //Read Register, Output of X MSB 8-bit value.
#define X_LSB 0x04 //Read Register, Output of X LSB 8-bit value.
#define Z_MSB 0x05 //Read Register, Output of Z MSB 8-bit value.
#define Z_LSB 0x06 //Read Register, Output of Z LSB 8-bit value.
#define Y_MSB 0x07 //Read Register, Output of Y MSB 8-bit value.
#define Y_LSB 0x08 //Read Register, Output of Y LSB 8-bit value.
//for LCD:
#use rs232(baud=9600, xmit=Pin_C1, bits=8, parity=N,stream=SERIALNH)
//for data out to main processor:
#use rs232(baud=9600, xmit=Pin_C6, bits=8, parity=N,STREAM=COMPASS)
//****************************************************************************
//Global Variables:
int8 M_data[6]; //Array - 6 units 8 bit Measured datas (compass)
int16 Xm=0,Ym=0,Zm=0; //16 bit X,Y,Z variables (compass)
float bearing; //final compass reading.
int16 bearing2; //compass bearing x 100, no decimal
int highbyte,lowbyte; //high byte and low byte for serial data
//****************************************************************************
//Functions/Subroutines, Prototypes:
//for compass only:
void hmc5883_write(int add, int data); //write to function
int16 hmc5883_read(int add); //Read from function
void hmc5883_init(); //Sets up starting conditions
void read_reg(); //reads compass registers, calc xyz
float calc_heading(); //calculates returns bearing.
//Clears LCD Display:
void LCDCLR() {
fputc(0xFE,SERIALNH); //Command Prefix
fputc(0x51,SERIALNH); //Clear screen
}
//Sets LCD to line 2 start point
void LCDLN2() {
fputc(0xFE,SERIALNH); //Command Prefix
fputc(0x45,SERIALNH); //set cursor command
fputc(0x40,SERIALNH); //Set cursor to next line, pos 40 = start line 2
}
//****************************************************************************
//-- Main Program
//****************************************************************************
void main(void) {
// Set TRIS I/O directions, define analog inputs, compartors:
set_tris_A(0b10000);
set_tris_B(0b00000000);
set_tris_C(0b00011000);
//(analog inputs digital by default)
//Initialize variables and Outputs: --------------------------------------
output_low(Pin_C0); //status off
delay_ms(1000); //LCD warmup time
//SET BRIGHTNESS OF LCD TO MID RANGE. (DEFAULT = 1)
fputc(0xFE,SERIALNH); //Command Prefix
fputc(0x53,SERIALNH); //set cursor command
fputc(8,SERIALNH); //4 is mid 0-8
delay_ms(25);
LCDCLR();
delay_ms(25);
fprintf(SERIALNH,"AUTO COMPASS-1");
delay_ms(250);
LCDLN2();
fprintf(SERIALNH,"READY");
delay_ms(1000);
//Setup for timers, PWM, and other peripherals:
//----------------------------------------------------------------
//MAIN:
hmc5883_init(); //Initialize compass settings.
while (true) {
read_reg(); //read compass registers, calc xyz
bearing = calc_heading(); //calculates & returns final compass bearing
bearing2 = (int16)(bearing * 100); //mult by 100, redefine type to int16
// so we can send it serially as two bytes. Yeilds 0 - 35999
//Convert to two 8 bit integers to send:
lowbyte = MAKE8(bearing2,0);
highbyte = MAKE8(bearing2,1);
//send data serially:
fputc(lowbyte,COMPASS);
fputc(highbyte,COMPASS);
delay_ms(10);
LCDCLR();
delay_ms(10);
fprintf(SERIALNH,"H= %Lu ",bearing2);
delay_ms(25);
} //elihw
} //naim
//********* Functions which have prototypes at top of program ****************
//for compass only:
void hmc5883_write(int add, int data) {
i2c_start();
i2c_write(W_DATA); //0x03
i2c_write(add);
i2c_write(data);
i2c_stop(); }
int16 hmc5883_read(int add) {
int retval;
i2c_start();
i2c_write(W_DATA); //0x03
i2c_write(add);
i2c_start();
i2c_write(R_DATA); //0x3D
retval=i2c_read(0);
i2c_stop();
return retval; }
void hmc5883_init() {
hmc5883_write(MOD_R, 0x00); //0x02, 0x00
delay_ms(100);
hmc5883_write(CON_A, 0x10); //0x00, 0x10
delay_ms(100);
hmc5883_write(CON_B, 0x20); //0x01, 0x20
delay_ms(100); }
void read_reg() { //read compass registers
//read registers in compass
M_data[0]=hmc5883_read(0x04); //Read X (LSB)
M_data[1]=hmc5883_read(0x03); //Read X (MSB)
M_data[2]=hmc5883_read(0x08); //Read Y (LSB)
M_data[3]=hmc5883_read(0x07); //Read Y (MSB)
M_data[4]=hmc5883_read(0x06); //Read Z (LSB)
M_data[5]=hmc5883_read(0x05); //Read Z (MSB)
//Create Word from Highbyte and Lowbyte data:
Xm=make16(M_data[1],M_data[0]);
Ym=make16(M_data[3],M_data[2]);
Zm=make16(M_data[5],M_data[4]);
}
float calc_heading() {
//Calculate using math.h function the bearing.
float Heading = atan2((signed int16)Ym,(signed int16)Xm)* 180 / pi + 180;
//correct for this equation yielding values 180 off:
if (Heading < 180)
Heading = Heading + 180;
else if (Heading > 180)
Heading = Heading - 180;
else if (Heading == 180)
Heading = 0;
return Heading;
}
|
|
|