I recently acquired a Devantech CMPS03 magnetic compass module. I was unable to find any examples of Arduino code for it using its PWM output, but I was able to write a simple program for testing it. I tried using both the PulseIn() function and interrupts, and found that using interrupts yielded the best results. Having ofter been the beneficiary of using code made available by others, I am pleased to offer this code for anyone to use freely, should they find it useful. Here is the code:
// Tests CMPS03 compass module using interrupts
// Gives more repeatable readings than using PulseIn()
//
// For excellent discussion of methods for using interrupts,
// See: http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
// http://rcarduino.blogspot.co.uk/2012/04/how-to-read-multiple-rc-channels-draft.html
// http://rcarduino.blogspot.com/
// http://rcarduino.blogspot.com/2012/11/how-to-read-rc-channels-rcarduinofastlib.html
// On the CMPS03:
// Connect Pin 1 to +5v
// Connect Pin 9 to gnd
// Connect Pin 4 to Pin 3 (Interrupt 1) on the Arduino
// I left CMPS03 pins 2 and 3 open despite statement in the data sheet that they should be clamped high
#define PWM_PIN 3 // Pin 3 on the Arduino digital i/o pins
#define COMPASS_FLAG 1 // This will be used to tell if there is a new interrupt
#define RC_FLAG 2
// shared variables are updated by the ISR and read by loop().
// In loop() we immediately take local copies so that the ISR can keep ownership of the
// shared ones. To access these in loop we first turn interrupts off with noInterrupts()
// we take a copy to use in loop and then turn interrupts back on as quickly as possible
// This ensures that we are able to receive new signals
volatile uint8_t bUpdateFlagsShared; // holds the update flags defined above
volatile uint16_t unCompassInShared;
volatile uint16_t unRCInShared;
// These are used to record the rising edge of a pulse in the ISR functions
// They do not need to be volatile as they are only used in the ISRs. If we wanted
// to refer to these in loop and the ISR then they would need to be declared volatile
uint32_t ulCompassStart;
void setup(){
Serial.begin(57600); // Enable Serial for testing
pinMode(PWM_PIN, INPUT);
attachInterrupt(1,ISRforCompass,HIGH); // Use Arduino Hardware Interrupt 1, pin 3
}
void loop(){
float heading = 0;
static uint16_t unCompassIn;
static uint8_t bUpdateFlags; // local copy of update flags
if(bUpdateFlagsShared){ // Has there been a new interrupt?
noInterrupts(); // Yes, turn interrupts off while we make copies of the shared variables
bUpdateFlags = bUpdateFlagsShared; // Copy the flags
if(bUpdateFlags & COMPASS_FLAG)unCompassIn = unCompassInShared; // Get throttle input
bUpdateFlagsShared = 0; // Reset the shared flag
interrupts(); // we have local copies of the inputs, so now we can turn interrupts back on
heading = ((float)unCompassIn)/100;
if(heading > 360) heading = heading - 360;
Serial.print("Compass heading: "); Serial.println((float)heading, 1);
bUpdateFlags = 0;
}
Serial.println("looping");
delay(1000); // So we can look at the output
}
// Interrupt-service routine (ISR)
void ISRforCompass(){
// if the pin is high, its a rising edge of the signal pulse, so lets record its value
if(digitalRead(PWM_PIN) == HIGH){ // Start of servo command
ulCompassStart = micros();
}
else{ // End of the pulse
unCompassInShared = (uint16_t)(micros() - ulCompassStart); // Get the elapsed time (us)
bUpdateFlagsShared |= COMPASS_FLAG; // Update flag and indicate new Throttle input
}
}
Hope this helps someone.
Ted