microbit-dal/source/MicroBitCompass.cpp

410 lines
11 KiB
C++
Raw Normal View History

microbit: Memory Optimisation Mega Update This release contains a widespread set of updates and optimisations to the micro:bit runtime, with a view to reducing the SRAM footprint of the whole system. This is to provide as much usable HEAP storage for application programs as possible. Specific updates and optimisations include: - Additional compilation flags to allow the core micro:bit runtime to be configured. These are defined in MicroBitConfig.h - A custom heap allocator. This is now included for two reasons: 1) To provide a simple mechanism to to utilise both the mbed heap space and other memory regions (such as unused memory in the SoftDevice region) as a single virtual heap. 2) To address some issues that have been noted that are attributable to heap fragmentation. The micro:bit heap allocator has a simple algorithm, but one that is chosen to respond well to the relativelt high 'heap churn' found in the micro:bit environment. All micro:bit components and user programs now use this heap allocator trasparently. - Updates to BLE services to remove persistent references to their GATT services. This consumes vast amounts SRAM, rather unecessarily. Instead only handles to the relevant GATT characteristics are now stored. This specifically includes: + MicroBitDFUService + MicroBitEventService + DeviceInformationService - Updates to the Fiber scheduler to save SRAM. More specifically: + Removed the need to hold an empty processor context to intialise fibers. + The IDLE fiber now runs without a stack + fiber stacks are now only created when a fiber is descheduled for the first time, thereby reducing heap churn. + the 'main' fiber is now recycled into the fiber_pool if it leaves app_main() + fibers created through invoke() now only maintains the necessary part of teh parent stack that is needed, thereby reducing the stack size of spawned fibers. - Updates to the Message Bus to reduce the overall memory footprint of processing events. More specifically: + Event handlers are now always called using invoke(), such that non-blocking event handlers no longer need a dedicated fiber to execute - thereby saving SRAM and processor time. + Processing of events from the event queue is now rate paced. Events only continue to be processed as long as there are no fibers on the run queue. i.e. event processing is no longer greedy, thereby reducing the number of fibers created on the runqueue. - Updates to BLUEZOENE code to bring up core BLE services even if they are not enabled by default. This allows programs that do not require BLE to operate to benefit from the full range of SRAM, whilst still allowing the device to be programmed over BLE. - Updates to the Soft Device initialisation configuration, reducing the size of the GATT table held in the top 1.8K of its 8K memory region to around 800 bytes. This is sufficient to run the default set of BLE services on the micro:bit so the additional memory is configured as HEAP storage by MicroBitHeapAllocator. - Minor changes to a range of components to integrate with the above changes. + rename of free() to release() in DynamicPWM to avoid namespace collision with MicroBitHeap free() + rename of fork_on_block to invoke() to enhance readbility. - Many code cleanups and updates to out of date comments.
2015-08-31 22:25:10 +00:00
#include "MicroBit.h"
/**
* Constructor.
* Create a compass representation with the given ID.
* @param id the event ID of the compass object.
* @param address the default address for the compass register
*
* Example:
* @code
* compass(MICROBIT_ID_COMPASS, MAG3110_DEFAULT_ADDR);
* @endcode
*
* Possible Events for the compass are as follows:
* @code
* MICROBIT_COMPASS_EVT_CAL_REQUIRED // triggered when no magnetometer data is available in persistent storage
* MICROBIT_COMPASS_EVT_CAL_START // triggered when calibration has begun
* MICROBIT_COMPASS_EVT_CAL_END // triggered when calibration has finished.
* @endcode
*/
MicroBitCompass::MicroBitCompass(uint16_t id, uint16_t address) : average(), sample(), int1(MICROBIT_PIN_COMPASS_DATA_READY)
{
this->id = id;
this->address = address;
//we presume it's calibrated until the average values are read.
this->status = 0x01;
//initialise eventStartTime to 0
this->eventStartTime = 0;
// Enable automatic reset after each sample;
writeCommand(MAG_CTRL_REG2, 0xA0);
// Select 10Hz update rate, with oversampling, and enable the device.
this->samplePeriod = 100;
this->configure();
//fetch our previous average values
average.x = read16(MAG_OFF_X_MSB);
average.y = read16(MAG_OFF_Y_MSB);
average.z = read16(MAG_OFF_Z_MSB);
if(average.x == 0 && average.y == 0 && average.z == 0)
status &= ~MICROBIT_COMPASS_STATUS_CALIBRATED;
// Indicate that we're up and running.
uBit.flags |= MICROBIT_FLAG_COMPASS_RUNNING;
}
/**
* Issues a standard, 2 byte I2C command write to the magnetometer.
* Blocks the calling thread until complete.
*
* @param reg The address of the register to write to.
* @param value The value to write.
*/
void MicroBitCompass::writeCommand(uint8_t reg, uint8_t value)
{
uint8_t command[2];
command[0] = reg;
command[1] = value;
uBit.i2c.write(address, (const char *)command, 2);
}
/**
* Issues a read command into the specified buffer.
* Blocks the calling thread until complete.
*
* @param reg The address of the register to access.
* @param buffer Memory area to read the data into.
* @param length The number of bytes to read.
*/
void MicroBitCompass::readCommand(uint8_t reg, uint8_t* buffer, int length)
{
uBit.i2c.write(address, (const char *)&reg, 1, true);
uBit.i2c.read(address, (char *)buffer, length);
}
/**
* Issues a read of a given address, and returns the value.
* Blocks the calling thread until complete.
*
* @param reg The based address of the 16 bit register to access.
* @return The register value, interpreted as a 16 but signed value.
*/
int16_t MicroBitCompass::read16(uint8_t reg)
{
uint8_t cmd[2];
cmd[0] = reg;
uBit.i2c.write(address, (const char *)cmd, 1);
cmd[0] = 0x00;
cmd[1] = 0x00;
uBit.i2c.read(address, (char *)cmd, 2);
return (int16_t) ((cmd[1] | (cmd[0] << 8))); //concatenate the MSB and LSB
}
/**
* Issues a read of a given address, and returns the value.
* Blocks the calling thread until complete.
*
* @param reg The based address of the 16 bit register to access.
* @return The register value, interpreted as a 8 bi signed value.
*/
int16_t MicroBitCompass::read8(uint8_t reg)
{
int8_t data;
data = 0;
readCommand(reg, (uint8_t*) &data, 1);
return data;
}
/**
* Gets the current heading of the device, relative to magnetic north.
* @return the current heading, in degrees. Or MICROBIT_COMPASS_IS_CALIBRATING if the compass is calibrating.
* Or MICROBIT_COMPASS_CALIBRATE_REQUIRED if the compass requires calibration.
*
* Example:
* @code
* uBit.compass.heading();
* @endcode
*/
int MicroBitCompass::heading()
{
if(status & MICROBIT_COMPASS_STATUS_CALIBRATING)
return MICROBIT_COMPASS_IS_CALIBRATING;
else if(!(status & MICROBIT_COMPASS_STATUS_CALIBRATED))
{
MicroBitEvent(id, MICROBIT_COMPASS_EVT_CAL_REQUIRED);
return MICROBIT_COMPASS_CALIBRATE_REQUIRED;
}
float bearing = (atan2((double)(sample.y - average.y),(double)(sample.x - average.x)))*180/PI;
if (bearing < 0)
bearing += 360.0;
return (int) (360.0 - bearing);
}
/**
* Periodic callback from MicroBit clock.
* Check if any data is ready for reading by checking the interrupt.
*/
void MicroBitCompass::idleTick()
{
// Poll interrupt line from accelerometer.
// Active HI. Interrupt is cleared in data read of MAG_OUT_X_MSB.
if(int1)
{
sample.x = read16(MAG_OUT_X_MSB);
sample.y = read16(MAG_OUT_Y_MSB);
sample.z = read16(MAG_OUT_Z_MSB);
if (status & MICROBIT_COMPASS_STATUS_CALIBRATING)
{
minSample.x = min(sample.x, minSample.x);
minSample.y = min(sample.y, minSample.y);
minSample.z = min(sample.z, minSample.z);
maxSample.x = max(sample.x, maxSample.x);
maxSample.y = max(sample.y, maxSample.y);
maxSample.z = max(sample.z, maxSample.z);
if(eventStartTime && ticks > eventStartTime + MICROBIT_COMPASS_CALIBRATE_PERIOD)
{
eventStartTime = 0;
calibrateEnd();
}
}
else
{
// Indicate that a new sample is available
MicroBitEvent e(id, MICROBIT_COMPASS_EVT_DATA_UPDATE);
}
}
}
/**
* Reads the X axis value of the latest update from the compass.
* @return The magnetic force measured in the X axis, in no specific units.
*
* Example:
* @code
* uBit.compass.getX();
* @endcode
*/
int MicroBitCompass::getX()
{
return sample.x;
}
/**
* Reads the Y axis value of the latest update from the compass.
* @return The magnetic force measured in the Y axis, in no specific units.
*
* Example:
* @code
* uBit.compass.getY();
* @endcode
*/
int MicroBitCompass::getY()
{
return sample.y;
}
/**
* Reads the Z axis value of the latest update from the compass.
* @return The magnetic force measured in the Z axis, in no specific units.
*
* Example:
* @code
* uBit.compass.getZ();
* @endcode
*/
int MicroBitCompass::getZ()
{
return sample.z;
}
/**
* Configures the compass for the sample rate defined
* in this object. The nearest values are chosen to those defined
* that are supported by the hardware. The instance variables are then
* updated to reflect reality.
*/
void MicroBitCompass::configure()
{
const MAG3110SampleRateConfig *actualSampleRate;
// First find the nearest sample rate to that specified.
actualSampleRate = &MAG3110SampleRate[MAG3110_SAMPLE_RATES-1];
for (int i=MAG3110_SAMPLE_RATES-1; i>=0; i--)
{
if(MAG3110SampleRate[i].sample_period < this->samplePeriod * 1000)
break;
actualSampleRate = &MAG3110SampleRate[i];
}
// OK, we have the correct data. Update our local state.
this->samplePeriod = actualSampleRate->sample_period / 1000;
// Bring the device online, with the requested sample frequency.
writeCommand(MAG_CTRL_REG1, actualSampleRate->ctrl_reg1 | 0x01);
}
/**
* Attempts to set the sample rate of the compass to the specified value (in ms).
* n.b. the requested rate may not be possible on the hardware. In this case, the
* nearest lower rate is chosen.
* @param period the requested time between samples, in milliseconds.
*/
void MicroBitCompass::setPeriod(int period)
{
this->samplePeriod = period;
this->configure();
}
/**
* Reads the currently configured sample rate of the compass.
* @return The time between samples, in milliseconds.
*/
int MicroBitCompass::getPeriod()
{
return (int)samplePeriod;
}
/**
* Attempts to determine the 8 bit ID from the magnetometer.
* @return the id of the compass (magnetometer)
*
* Example:
* @code
* uBit.compass.whoAmI();
* @endcode
*/
int MicroBitCompass::whoAmI()
{
uint8_t data;
readCommand(MAG_WHOAMI, &data, 1);
return (int)data;
}
/**
* Perform a calibration of the compass.
* This will fire MICROBIT_COMPASS_EVT_CAL_START.
* @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS
*/
void MicroBitCompass::calibrateStart()
{
if(this->isCalibrating())
return;
status |= MICROBIT_COMPASS_STATUS_CALIBRATING;
// Take a sane snapshot to start with.
minSample = sample;
maxSample = sample;
MicroBitEvent(id, MICROBIT_COMPASS_EVT_CAL_START);
}
/**
* Perform the asynchronous calibration of the compass.
* This will fire MICROBIT_COMPASS_EVT_CAL_START and MICROBIT_COMPASS_EVT_CAL_END when finished.
* @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS
*/
void MicroBitCompass::calibrateAsync()
{
eventStartTime = ticks;
calibrateStart();
}
/**
* Complete the calibration of the compass.
* This will fire MICROBIT_COMPASS_EVT_CAL_END.
* @note THIS MUST BE CALLED TO GAIN RELIABLE VALUES FROM THE COMPASS
*/
void MicroBitCompass::calibrateEnd()
{
average.x = (maxSample.x + minSample.x) / 2;
average.y = (maxSample.y + minSample.y) / 2;
average.z = (maxSample.z + minSample.z) / 2;
status &= ~MICROBIT_COMPASS_STATUS_CALIBRATING;
status |= MICROBIT_COMPASS_STATUS_CALIBRATED;
//Store x, y and z values in persistent storage on the MAG3110
writeCommand(MAG_OFF_X_LSB, (uint8_t)average.x);
writeCommand(MAG_OFF_X_MSB, (uint8_t)(average.x >> 8));
writeCommand(MAG_OFF_Y_LSB, (uint8_t)average.y);
writeCommand(MAG_OFF_Y_MSB, (uint8_t)(average.y >> 8));
writeCommand(MAG_OFF_Z_LSB, (uint8_t)average.z);
writeCommand(MAG_OFF_Z_MSB, (uint8_t)(average.z >> 8));
MicroBitEvent(id, MICROBIT_COMPASS_EVT_CAL_END);
}
/**
* Returns 0 or 1. 1 indicates that the compass is calibrated, zero means the compass requires calibration.
*/
int MicroBitCompass::isCalibrated()
{
return status & MICROBIT_COMPASS_STATUS_CALIBRATED;
}
/**
* Returns 0 or 1. 1 indicates that the compass is calibrating, zero means the compass is not currently calibrating.
*/
int MicroBitCompass::isCalibrating()
{
return status & MICROBIT_COMPASS_STATUS_CALIBRATING;
}
/**
* Clears the calibration held in persistent storage, and sets the calibrated flag to zero.
*/
void MicroBitCompass::clearCalibration()
{
writeCommand(MAG_OFF_X_LSB, 0);
writeCommand(MAG_OFF_X_MSB, 0);
writeCommand(MAG_OFF_Y_LSB, 0);
writeCommand(MAG_OFF_Y_MSB, 0);
writeCommand(MAG_OFF_Z_LSB, 0);
writeCommand(MAG_OFF_Z_MSB, 0);
status &= ~MICROBIT_COMPASS_STATUS_CALIBRATED;
}
/**
* Returns 0 or 1. 1 indicates data is waiting to be read, zero means data is not ready to be read.
*/
int MicroBitCompass::isIdleCallbackNeeded()
{
// The MAG3110 raises an interrupt line when data is ready, which we sample here.
// The interrupt line is active HI, so simply return the state of the pin.
return int1;
}
const MAG3110SampleRateConfig MAG3110SampleRate[MAG3110_SAMPLE_RATES] = {
{12500, 0x00}, // 80 Hz
{25000, 0x20}, // 40 Hz
{50000, 0x40}, // 20 Hz
{100000, 0x60}, // 10 hz
{200000, 0x80}, // 5 hz
{400000, 0x81}, // 2.5 hz
{800000, 0x82}, // 1.25 hz
{1600000, 0xb0}, // 0.63 hz
{3200000, 0xd0}, // 0.31 hz
{6400000, 0xf0}, // 0.16 hz
{12800000, 0xf1} // 0.08 hz
};