The Warp firmware is intended to be a demonstration and meassurement environment for testing the Warp hardware.
It provides facilities that we use to perform tests on the hardware such as activating the different programmable voltage regulator output voltages (16 different supply voltage levels), activating the programmable I2C pull-ups to different values (65536 different settings), changing the I2C and SPI bit rate, changing the Cortex-M0 clock frequency, and so on. Having a menu interface allows us to perform various experiments without having to re-compile and load firmware to the system for each experiment.
The Warp firmware is a tool for experimentation. You can also use it as a baseline for building real applications by modifying it to remove the menu-driven functionality and linking in only the sensor drivers you need.
The core of the firmware is in warp-kl03-ksdk1.1-boot.c
. The drivers for the individual sensors are in devXXX.c
for sensor XXX
. For example,
devADXL362.c
for the ADXL362 3-axis accelerometer. The section below briefly describes all the source files in this directory.
This is the CMake configuration file. Edit this to change the default size of the stack and heap.
This is the implementation of the SEGGER Real-Time Terminal interface. Do not modify.
Configuration file for SEGGER Real-Time Terminal interface. You can increase the size of BUFFER_SIZE_UP
to reduce text in the menu being trimmed.
Implementation of the SEGGER Real-Time Terminal interface formatted I/O routines. Do not modify.
Driver for Analog devices ADXL362.
Driver for AMG8834.
Driver for AS7262.
Driver for AS7263.
Header file with definitions used by both devAS7262.*
and devAS7263.*
.
Driver for BME680.
Driver for BMX055.
Driver for CCS811.
Driver forHDC1000 .
Driver for IS25WP128.
Driver for ISL23415.
Driver for L3GD20H.
Driver for LPS25H.
Driver for MAG3110.
Driver for MMA8451Q.
Driver for PAN1326.
Driver for SI4705.
Driver for SI7021.
Driver for TCS34725.
Definition of I/O pin configurations using the KSDK gpio_output_pin_user_config_t
structure.
Definition of I/O pin mappings and aliases for different I/O pins to symbolic names relevant to the Warp hardware design, via GPIO_MAKE_PIN()
.
Initialization assembler.
The core of the implementation. This puts together the processor initialization with a menu interface that triggers the individual sensor drivers based on commands entered at the menu.
You can modify warp-kl03-ksdk1.1-boot.c
to achieve a custom firmware implementation using the following steps:
-
Remove the definitions for all the
WarpI2CDeviceState
andWarpSPIDeviceState
structures for the sensors you are not using. -
Remove the function
repeatRegisterReadForDeviceAndAddress()
since that references all the sensors in the full Warp platform. -
Remove
activateAllLowPowerSensorModes()
andpowerupAllSensors()
since those assume they have access to all the sensors on the Warp platform. -
Modify
main()
to replace the menu (see thewhile (1)
loop) with the specific operations you want the hardware to perform after initialization.
You can inspect the baseline firmware to see what functions are called when you enter commands at the menu. You can then use the underlying functionality that is already implemented to implement your own custom tasks.
Implements functionality related to enabling the different low-power modes of the KL03.
Constant and data structure definitions.
This is currently valid only for the Warp variant, since this was written only for the AT45DB
This section describes the protocol that is used to store measurements into flash. Let us first discuss some terminology.
The Warp hardware comes with a selection of sensors which can be activated by setting the appropriate flag in config.h
. An example of a sensor is the MAG3110
. Each sensor produces a number of readings. Each such reading consists of a signed integer; the maximum size of a reading will depend on the sensor.
A collection of such readings from the activated sensors is a measurement. A measurement is an ordered sequence of readings from all the activated sensors. The writeAllSensorsToFlash
function in boot.c
generates such a measurement, and writes them to flash.
When printing to stdout
, the printSensorData<sensor>
function of the sensor (found in dev<sensor>.h
) is called. Here <sensor>
is MAG3110
for example. This function simply sequentially prints out readings as characters. Since we don't need to do any thing further, this process is fairly straightforward.
In order to write to flash, the writeAllSensorsToFlash
function creates an array of bytes that corresponds to the measurement. For each activated sensor <sensor>
, its appendSensorData<sensor>
function is called to append its readings to a buffer.
The purpose of writing to flash is to eventually read back and print out the measurements in a form that is similar to if we had printed to stdout
in the first place. If we kept the configuration of active sensors the same, we know exactly how many bytes correspond to a measurement, and the byte configuration of readings from any active sensor, allowing us to sequentially decode measurements easily.
However, for ease of use, we have made it is possible to write to flash with different configurations of the sensors, across multiple calls to writeAllSensorsToFlash
. This means that, when reading from the flash, we need to know which sensors were active when a particular measurement was written. This is done by first creating a 16b bitfield in front of each measurement which encodes the active sensor configuration.
We can see that this is done in writeAllSensorsToFlash
, as soon as an active sensor is configured. For example, the following is an excerpt from boot.c
:
#if (WARP_BUILD_ENABLE_DEVMAG3110)
numberOfConfigErrors += configureSensorMAG3110(
0x00, /* Payload: DR 000, OS 00, 80Hz, ADC 1280, Full 16bit, standby mode
to set up register*/
0xA0, /* Payload: AUTO_MRST_EN enable, RAW value without offset */
0x10);
sensorBitField = sensorBitField | 0b1000000;
#endif
Note sensorBitField = sensorBitField | 0b100000;
, which activates the bit that corresponds to the MAG3110
sensor.
When reading, this bitfield is first decoded to know exactly which sensors were active, and therefore how many bytes make up this measurement. Once those bytes have been read and decoded, the next measurement is decoded, read and printed out in the same way.
While this protocol makes writing and reading to flash more useful, it also means that any one adding new sensors need to follow the following guidelines to make sure that the protocol is not violated.
In the current version, 13 out of the 16 bits are used. They correspond to the following sensors being active.
0b0000000000000001
: the measurement number,0b0000000000000010
: theRTC->TSR
time stamp,0b0000000000000100
: theRTC->TPR
time stamp,0b0000000000001000
: theADXL362
sensor,0b0000000000010000
: theAMG8834
sensor,0b0000000000100000
: theMMA8451Q
sensor,0b0000000001000000
: theMAG3110
sensor,0b0000000010000000
: theL3GD20H
sensor,0b0000000100000000
: theBME680
sensor,0b0000001000000000
: theBMX055
sensor,0b0000010000000000
: theCCS811
sensor,0b0000100000000000
: theHDC1000
sensor,0b1000000000000000
: the number of config errors.
As an important note, this bitfield represents the order in which the readings from the sensors are used to create a measurement. The first element represents the least significant bit. For example, if the bitfield is 0b00000000100101111
, then the following readings are expected:
- 4B of the measurement number,
- 4B of the
RTC->TSR
time stamp, - 4B of the
RTC->TPR
time stamp, - 8B (4 readings of 2B) of
ADXL362
sensor readings, - 6B (3 readings of 2B) of
MMA8451Q
sensor readings, and - 12B (3 readings of 4B)of
BME680
sensor readings.
When writing a new sensor, the following guidelines should be followed in addition to other common sense steps:
-
Write both a
printSensorData<sensor>
andappendSensorData<sensor>
function. The former should print out the readings tostdout
, and the latter should append the readings to a buffer. The function signatures should be as follows; look at the existing sensors for examples of what should be inside them:void printSensorData<sensor>(bool hexModeFlag); uint8_t appendSensorData<sensor>(uint8_t* buf);
The output of
appendSensorData<sensor>
is the number of bytes that were appended by the function. -
Inside
dev<sensor>.h
, define the following 3 constant variables, with their appropriate values. As a sanity check,numberOfReadingsPerMeasurement<sensor> * bytesPerReading<sensor> = bytesPerMeasurement<sensor>
.const uint8_t bytesPerMeasurement<sensor> = <appropriate value>; const uint8_t bytesPerReading<sensor> = <appropriate value>; const uint8_t numberOfReadingsPerMeasurement<sensor> = <appropriate value>;
-
Decide on which free bit of the bitfield can be assigned to this sensor. Then, in
printAllSensors
, add the following line right after its configuration:sensorBitField = sensorBitField | 0b<bitfield value>;
-
Following the pattern of the
writeAllSensorsToFlash
function, use theappnedSensorData<sensor>
functions to append the readings to the buffer, respectively. Note that you must do this in the order that is specified by the bitfield. Otherwise, the data won't be stored in the correct order, and will be impossible to decode when reading from flash.When writing the
appendSensorData<sensor>
function, you must use the following template:bytesWrittenIndex += appendSensorData<sensor>(flashWriteBuf + bytesWrittenIndex);
This is to ensure that the buffer is appended to correctly.
-
Add a decoding case to the
writeAllSensorsToFlash
function inboot.c
, in the correct order. For example, if the new sensor is designated the 13th bit, then the following should be added AFTER the 12th case:if (sensorBitField & 0b1000000000000) { numberOfSensorsFound++; if (numberOfSensorsFound - 1 == sensorIndex) { *sizePerReading = bytesPerReading<sensor>; *numberOfReadings = numberOfReadingsPerMeasurement<sensor>; return; } }
Note that
bytesPerReading<sensor>
andnumberOfReadingsPerMeasurement<sensor>
are the constants defined in Step 2.