+.. [TEP131] TEP 131: Creating a New Platform for TinyOS 2.x
+
+Appendix A: Sensor Driver Examples
+====================================================================
+
+1. Analog ADC-Connected Sensor
+------------------------------
+
+The Analog sensor requires two components
+
+* a component to present the sensor itself (HamamatsuS1087ParC)
+
+* a component to select the appropriate hardware resources, such as
+ ADC port 4, reference voltage 1.5V, and a slow sample and hold time
+ (HamamatsuS1087ParP).
+
+The AdcReadClientC component and underlying machinery handles all of
+the arbitration and access to the ADC.
+
+::
+
+ tos/platforms/telosa/chips/s1087/HamamatsuS1087ParC.nc
+
+ // HIL for the HamamatsuS1087 analog photodiode sensor
+ generic configuration HamamatsuS1087ParC() {
+ provides interface Read<uint16_t>;
+ provides interface ReadStream<uint16_t>;
+ provides interface DeviceMetadata;
+ }
+ implementation {
+ // Create a new A/D client and connect it to the Hamamatsu S1087 A/D
+ // parameters
+ components new AdcReadClientC();
+ Read = AdcReadClientC;
+
+ components new AdcReadStreamClientC();
+ ReadStream = AdcReadStreamClientC;
+
+ components HamamatsuS1087ParP;
+ DeviceMetadata = HamamatsuS1087ParP;
+ AdcReadClientC.AdcConfigure -> HamamatsuS1087ParP;
+ AdcReadStreamClientC.AdcConfigure -> HamamatsuS1087ParP;
+ }
+
+::
+
+ tos/platforms/telosa/chips/s1087/HamamatsuS1087ParP.nc
+
+ #include "Msp430Adc12.h"
+
+ // A/D parameters for the Hamamatsu - see the MSP430 A/D converter manual,
+ // Hamamatsu specification, Telos hardware schematic and TinyOS MSP430
+ // A/D converter component specifications for the explanation of these
+ // parameters
+ module HamamatsuS1087ParP {
+ provides interface AdcConfigure<const msp430adc12_channel_config_t*>;
+ provides interface DeviceMetadata;
+ }
+ implementation {
+ msp430adc12_channel_config_t config = {
+ inch: INPUT_CHANNEL_A4,
+ sref: REFERENCE_VREFplus_AVss,
+ ref2_5v: REFVOLT_LEVEL_1_5,
+ adc12ssel: SHT_SOURCE_ACLK,
+ adc12div: SHT_CLOCK_DIV_1,
+ sht: SAMPLE_HOLD_4_CYCLES,
+ sampcon_ssel: SAMPCON_SOURCE_SMCLK,
+ sampcon_id: SAMPCON_CLOCK_DIV_1
+ };
+
+ async command const msp430adc12_channel_config_t* AdcConfigure.getConfiguration() {
+ return &config;
+ }
+
+ command uint8_t DeviceMetadata.getSignificantBits() { return 12; }
+ }
+
+2. Binary Pin-Connected Sensor
+------------------------------
+
+The Binary sensor gets a bit more complex, because it has three
+components:
+
+* one to present the sensor (UserButtonC)
+
+* one to execute the driver logic (UserButtonLogicP)
+
+* one to select the appropriate hardware resources, such as MSP430
+ Port 27 (HplUserButtonC).
+
+Note that the presentation of this sensor is not arbitrated because
+none of the operations are split-phase.
+
+::
+
+ tos/platforms/telosa/UserButtonC.nc
+
+ // HIL for the user button sensor on Telos-family motes
+ configuration UserButtonC {
+ provides interface Get<bool>; // Get button status
+ provides interface Notify<bool>; // Get button-press notifications
+ provides interface DeviceMetadata;
+ }
+ implementation {
+
+ // Simply connect the button logic to the button HPL
+ components UserButtonLogicP;
+ Get = UserButtonLogicP;
+ Notify = UserButtonLogicP;
+ DeviceMetadata = UserButtonLogicP;
+
+ components HplUserButtonC;
+ UserButtonLogicP.GpioInterrupt -> HplUserButtonC.GpioInterrupt;
+ UserButtonLogicP.GeneralIO -> HplUserButtonC.GeneralIO;
+ }
+
+::
+
+ tos/platforms/telosa/UserButtonLogicP.nc
+
+ // Transform the low-level (GeneralIO and GpioInterrupt) interface to the
+ // button to high-level SID interfaces
+ module UserButtonLogicP {
+ provides interface Get<bool>;
+ provides interface Notify<bool>;
+ provides interface DeviceMetadata;
+
+ uses interface GeneralIO;
+ uses interface GpioInterrupt;
+ }
+ implementation {
+ norace bool m_pinHigh;
+
+ task void sendEvent();
+
+ command bool Get.get() { return call GeneralIO.get(); }
+
+ command error_t Notify.enable() {
+ call GeneralIO.makeInput();
+
+ // If the pin is high, we need to trigger on falling edge interrupt, and
+ // vice-versa
+ if ( call GeneralIO.get() ) {
+ m_pinHigh = TRUE;
+ return call GpioInterrupt.enableFallingEdge();
+ } else {
+ m_pinHigh = FALSE;
+ return call GpioInterrupt.enableRisingEdge();
+ }
+ }
+
+ command error_t Notify.disable() {
+ return call GpioInterrupt.disable();
+ }
+
+ // Button changed, signal user (in a task) and update interrupt detection
+ async event void GpioInterrupt.fired() {
+ call GpioInterrupt.disable();
+
+ m_pinHigh = !m_pinHigh;
+
+ post sendEvent();
+ }
+
+ task void sendEvent() {
+ bool pinHigh;
+ pinHigh = m_pinHigh;
+
+ signal Notify.notify( pinHigh );
+
+ if ( pinHigh ) {
+ call GpioInterrupt.enableFallingEdge();
+ } else {
+ call GpioInterrupt.enableRisingEdge();
+ }
+ }
+
+ command uint8_t DeviceMetadata.getSignificantBits() { return 1; }
+ }
+
+::
+
+ tos/platforms/telosa/HplUserButtonC.nc
+
+ // HPL for the user button sensor on Telos-family motes - just provides
+ // access to the I/O and interrupt control for the pin to which the
+ // button is connected
+ configuration HplUserButtonC {
+ provides interface GeneralIO;
+ provides interface GpioInterrupt;
+ }
+ implementation {
+
+ components HplMsp430GeneralIOC as GeneralIOC;
+
+ components new Msp430GpioC() as UserButtonC;
+ UserButtonC -> GeneralIOC.Port27;
+ GeneralIO = UserButtonC;
+
+ components HplMsp430InterruptC as InterruptC;
+
+ components new Msp430InterruptC() as InterruptUserButtonC;
+ InterruptUserButtonC.HplInterrupt -> InterruptC.Port27;
+ GpioInterrupt = InterruptUserButtonC.Interrupt;
+ }
+
+3. Digital Bus-Connected Sensor
+-------------------------------
+
+The Digital sensor is the most complex out of the set, and includes
+six components:
+
+* one to present the sensor (SensirionSht11C)
+
+* one to request arbitrated access and to transform the sensor HAL
+ into the sensor HIL (SensirionSht11P)
+
+* one to present the sensor HAL (HalSensirionSht11C)
+
+* one to perform the driver logic needed to support the HAL, which
+ twiddles pins according to a sensor-specific protocol
+ (SensirionSht11LogicP).
+
+* one to select the appropriate hardware resources, such as the clock,
+ data, and power pins, and to provide an arbiter for the sensor
+ (HplSensirionSht11C).
+
+* one to perform the power control logic needed to support the power
+ manager associated with the arbiter (HplSensirionSht11P).
+
+This bus-connected sensor is overly complex because it does not rely
+on a shared framework of bus manipulation components. A sensor built
+on top of the I2C or SPI bus would likely require fewer components.
+
+::
+
+ tos/platforms/telosa/chips/sht11/SensirionSht11C.nc
+
+ // HIL interface to Sensirion SHT11 temperature and humidity sensor
+ generic configuration SensirionSht11C() {
+ provides interface Read<uint16_t> as Temperature;
+ provides interface DeviceMetadata as TemperatureDeviceMetadata;
+ provides interface Read<uint16_t> as Humidity;
+ provides interface DeviceMetadata as HumidityDeviceMetadata;
+ }
+ implementation {
+ // Instantiate the module providing the HIL interfaces
+ components new SensirionSht11ReaderP();
+
+ Temperature = SensirionSht11ReaderP.Temperature;
+ TemperatureDeviceMetadata = SensirionSht11ReaderP.TemperatureDeviceMetadata;
+ Humidity = SensirionSht11ReaderP.Humidity;
+ HumidityDeviceMetadata = SensirionSht11ReaderP.HumidityDeviceMetadata;
+
+ // And connect it to the HAL component for the Sensirion SHT11
+ components HalSensirionSht11C;
+
+ enum { TEMP_KEY = unique("Sht11.Resource") };
+ enum { HUM_KEY = unique("Sht11.Resource") };
+
+ SensirionSht11ReaderP.TempResource -> HalSensirionSht11C.Resource[ TEMP_KEY ];
+ SensirionSht11ReaderP.Sht11Temp -> HalSensirionSht11C.SensirionSht11[ TEMP_KEY ];
+ SensirionSht11ReaderP.HumResource -> HalSensirionSht11C.Resource[ HUM_KEY ];
+ SensirionSht11ReaderP.Sht11Hum -> HalSensirionSht11C.SensirionSht11[ HUM_KEY ];
+ }
+
+::
+
+ tos/chips/sht11/SensirionSht11ReaderP.nc
+
+ // Convert Sensirion SHT11 HAL to HIL interfaces for a single
+ // client, performing automatic resource arbitration
+ generic module SensirionSht11ReaderP() {
+ provides interface Read<uint16_t> as Temperature;
+ provides interface DeviceMetadata as TemperatureDeviceMetadata;
+ provides interface Read<uint16_t> as Humidity;
+ provides interface DeviceMetadata as HumidityDeviceMetadata;
+
+ // Using separate resource interfaces for temperature and humidity allows
+ // temperature and humidity measurements to be requested simultaneously
+ // (if a single Resource interface was used, a request for temperature would
+ // prevent any humidity requests until the temperature measurement was complete)
+ uses interface Resource as TempResource;
+ uses interface Resource as HumResource;
+ uses interface SensirionSht11 as Sht11Temp;
+ uses interface SensirionSht11 as Sht11Hum;
+ }
+ implementation {
+ command error_t Temperature.read() {
+ // Start by requesting access to the SHT11
+ return call TempResource.request();
+ }
+
+ event void TempResource.granted() {
+ error_t result;
+ // If the HAL measurement fails, release the SHT11 and signal failure
+ if ((result = call Sht11Temp.measureTemperature()) != SUCCESS) {
+ call TempResource.release();
+ signal Temperature.readDone( result, 0 );
+ }
+ }
+
+ event void Sht11Temp.measureTemperatureDone( error_t result, uint16_t val ) {
+ // Release the SHT11 and signal the result
+ call TempResource.release();
+ signal Temperature.readDone( result, val );
+ }
+
+ command uint8_t TemperatureDeviceMetadata.getSignificantBits() { return 14; }
+
+ command error_t Humidity.read() {
+ // Start by requesting access to the SHT11
+ return call HumResource.request();
+ }
+
+ event void HumResource.granted() {
+ error_t result;
+ // If the HAL measurement fails, release the SHT11 and signal failure
+ if ((result = call Sht11Hum.measureHumidity()) != SUCCESS) {
+ call HumResource.release();
+ signal Humidity.readDone( result, 0 );
+ }
+ }
+
+ event void Sht11Hum.measureHumidityDone( error_t result, uint16_t val ) {
+ // Release the SHT11 and signal the result
+ call HumResource.release();
+ signal Humidity.readDone( result, val );
+ }
+
+ command uint8_t HumidityDeviceMetadata.getSignificantBits() { return 12; }
+
+ // Dummy handlers for unused portions of the HAL interface
+ event void Sht11Temp.resetDone( error_t result ) { }
+ event void Sht11Temp.measureHumidityDone( error_t result, uint16_t val ) { }
+ event void Sht11Temp.readStatusRegDone( error_t result, uint8_t val ) { }
+ event void Sht11Temp.writeStatusRegDone( error_t result ) { }
+
+ event void Sht11Hum.resetDone( error_t result ) { }
+ event void Sht11Hum.measureTemperatureDone( error_t result, uint16_t val ) { }
+ event void Sht11Hum.readStatusRegDone( error_t result, uint8_t val ) { }
+ event void Sht11Hum.writeStatusRegDone( error_t result ) { }
+
+ // We need default handlers as a client may wire to only the Temperature
+ // sensor or only the Humidity sensor
+ default event void Temperature.readDone( error_t result, uint16_t val ) { }
+ default event void Humidity.readDone( error_t result, uint16_t val ) { }
+ }
+
+::
+
+ tos/platforms/telosa/chips/sht11/HalSensirionSht11C.nc
+
+ // HAL interface to Sensirion SHT11 temperature and humidity sensor
+ configuration HalSensirionSht11C {
+ // The SHT11 HAL uses resource arbitration to allow the sensor to shared
+ // between multiple clients and for automatic power management (the SHT11
+ // is switched off when no clients are waiting to use it)
+ provides interface Resource[ uint8_t client ];
+ provides interface SensirionSht11[ uint8_t client ];
+ }
+ implementation {
+ // The HAL implementation logic
+ components new SensirionSht11LogicP();
+ SensirionSht11 = SensirionSht11LogicP;
+
+ // And it's wiring to the SHT11 HPL - the actual resource management is
+ // provided at the HPL layer
+ components HplSensirionSht11C;
+ Resource = HplSensirionSht11C.Resource;
+ SensirionSht11LogicP.DATA -> HplSensirionSht11C.DATA;
+ SensirionSht11LogicP.CLOCK -> HplSensirionSht11C.SCK;
+ SensirionSht11LogicP.InterruptDATA -> HplSensirionSht11C.InterruptDATA;
+
+ components new TimerMilliC();
+ SensirionSht11LogicP.Timer -> TimerMilliC;
+
+ components LedsC;
+ SensirionSht11LogicP.Leds -> LedsC;
+ }
+
+::
+
+ tos/chips/sht11/SensirionSht11LogicP.nc
+
+ generic module SensirionSht11LogicP() {
+ provides interface SensirionSht11[ uint8_t client ];
+
+ uses interface GeneralIO as DATA;
+ uses interface GeneralIO as CLOCK;
+ uses interface GpioInterrupt as InterruptDATA;
+
+ uses interface Timer<TMilli>;
+
+ uses interface Leds;
+ }
+ implementation {
+
+ ... bus protocol details omitted for brevity ...
+
+ }
+
+::
+
+ tos/platforms/telosa/chips/sht11/HplSensirionSht11C.nc
+
+ // Low-level, platform-specific glue-code to access the SHT11 sensor found
+ // on telos-family motes - here the HPL just provides resource management
+ // and access to the SHT11 data, clock and interrupt pins
+ configuration HplSensirionSht11C {
+ provides interface Resource[ uint8_t id ];
+ provides interface GeneralIO as DATA;
+ provides interface GeneralIO as SCK;
+ provides interface GpioInterrupt as InterruptDATA;
+ }
+ implementation {
+ // Pins used to access the SHT11
+ components HplMsp430GeneralIOC;
+
+ components new Msp430GpioC() as DATAM;
+ DATAM -> HplMsp430GeneralIOC.Port15;
+ DATA = DATAM;
+
+ components new Msp430GpioC() as SCKM;
+ SCKM -> HplMsp430GeneralIOC.Port16;
+ SCK = SCKM;
+
+ components new Msp430GpioC() as PWRM;
+ PWRM -> HplMsp430GeneralIOC.Port17;
+
+ // HPL logic for switching the SHT11 on and off
+ components HplSensirionSht11P;
+ HplSensirionSht11P.PWR -> PWRM;
+ HplSensirionSht11P.DATA -> DATAM;
+ HplSensirionSht11P.SCK -> SCKM;
+
+ components new TimerMilliC();
+ HplSensirionSht11P.Timer -> TimerMilliC;
+
+ components HplMsp430InterruptC;
+ components new Msp430InterruptC() as InterruptDATAC;
+ InterruptDATAC.HplInterrupt -> HplMsp430InterruptC.Port15;
+ InterruptDATA = InterruptDATAC.Interrupt;
+
+ // The arbiter and power manager for the SHT11
+ components new FcfsArbiterC( "Sht11.Resource" ) as Arbiter;
+ Resource = Arbiter;
+
+ components new SplitControlPowerManagerC();
+ SplitControlPowerManagerC.SplitControl -> HplSensirionSht11P;
+ SplitControlPowerManagerC.ArbiterInit -> Arbiter.Init;
+ SplitControlPowerManagerC.ArbiterInfo -> Arbiter.ArbiterInfo;
+ SplitControlPowerManagerC.ResourceDefaultOwner -> Arbiter.ResourceDefaultOwner;
+ }
+
+::
+
+ tos/platforms/telosa/chips/sht11/HplSensirionSht11P.nc
+
+ // Switch the SHT11 on and off, and handle the 11ms warmup delay
+ module HplSensirionSht11P {
+ // The SplitControl interface powers the SHT11 on or off (it's automatically
+ // called by the SHT11 power manager, see HplSensirionSht11C)
+ // We use a SplitControl interface as we need to wait 11ms for the sensor to
+ // warm up
+ provides interface SplitControl;
+ uses interface Timer<TMilli>;
+ uses interface GeneralIO as PWR;
+ uses interface GeneralIO as DATA;
+ uses interface GeneralIO as SCK;
+ }
+ implementation {
+ task void stopTask();
+
+ command error_t SplitControl.start() {
+ // Power SHT11 on and wait for 11ms
+ call PWR.makeOutput();
+ call PWR.set();
+ call Timer.startOneShot( 11 );
+ return SUCCESS;
+ }
+
+ event void Timer.fired() {
+ signal SplitControl.startDone( SUCCESS );
+ }
+
+ command error_t SplitControl.stop() {
+ // Power the SHT11 off
+ call SCK.makeInput();
+ call SCK.clr();
+ call DATA.makeInput();
+ call DATA.clr();
+ call PWR.clr();
+ post stopTask();
+ return SUCCESS;
+ }
+
+ task void stopTask() {
+ signal SplitControl.stopDone( SUCCESS );
+ }
+ }