M0 Sensor Shields

There are now a few compact low-power sensor breakouts available that can flat-mount straight on the M0 PCB (bottom side).

BME280 Breakout

This breakout includes a BME280 sensor (same sensor as the popular WeatherShield):

Please see the WeatherShield guide for sample code.

LSM9DS1 9DoF IMU Breakout

There is now a 9D0F LSM9DS1 sensor breakout which includes 3 sensors in 1 chip – 3-axis accelerometer, 3-axis gyroscope and 3-axis magnetometer!

Here’s a sample sketch that you can use to get the various readings available from the LSM9DS1 sensors:

/*****************************************************************
SFE_LSM9DS1 Library Simple Example Code - I2C Interface
The LSM9DS1 is a versatile 9DOF sensor. It has a built-in
accelerometer, gyroscope, and magnetometer. Very cool!

This Arduino sketch is a demo of the simple side of the SFE_LSM9DS1 library.
It'll demo the following:
* How to create a LSM9DS1 object, using a constructor (global variables section).
* How to use the begin() function of the LSM9DS1 class.
* How to read the gyroscope, accelerometer, and magnetometer
  using the readGryo(), readAccel(), readMag() functions and 
  the gx, gy, gz, ax, ay, az, mx, my, and mz variables.
* How to calculate actual acceleration, rotation speed, 
  magnetic field strength using the calcAccel(), calcGyro() 
  and calcMag() functions.
* How to use the data from the LSM9DS1 to calculate
  orientation and heading.

This example demonstrates how to use I2C. The pin-out is as follows:
   LSM9DS1 --------- Moteino
	 SCL ------------- SCL (A5 on Moteino/Arduino)
	 SDA ------------- SDA (A4 on Moteino/Arduino)
	 VDD ------------- 3.3V
	 GND ------------- GND

The LSM9DS1 has a maximum voltage of 3.6V. Make sure you power it
off the 3.3V rail! I2C pins are open-drain, so you'll be 
(mostly) safe connecting the LSM9DS1's SCL and SDA pins 
directly to a Moteino/Arduino.

Code by Jim Lindblom @ SparkFun Electronics
Adapted for MoteinoM0 by Felix Rusu, LowPowerLab.com
Original Creation Date: April 30, 2015
Distributed as-is; no warranty is given.
*****************************************************************/
// The SFE_LSM9DS1 library requires both Wire and SPI be
// included BEFORE including the 9DS1 library.
#include 
#include 
#include 

#ifdef MOTEINO_ZERO
  #ifdef SERIAL_PORT_USBVIRTUAL
    #define Serial SERIAL_PORT_USBVIRTUAL // Required for Serial on Zero based boards
  #endif
#endif

//////////////////////////
// LSM9DS1 Library Init //
//////////////////////////
// Use the LSM9DS1 class to create an object
LSM9DS1 imu;

///////////////////////
// Example I2C Setup //
///////////////////////
// SDO_XM and SDO_G are both pulled high, so our addresses are:
#define LSM9DS1_M	0x1E // Would be 0x1C if SDO_M is LOW
#define LSM9DS1_AG	0x6B // Would be 0x6A if SDO_AG is LOW

////////////////////////////
// Sketch Output Settings //
////////////////////////////
#define PRINT_CALCULATED
//#define PRINT_RAW
#define PRINT_SPEED 1000 // 250 ms between prints

// Earth's magnetic field varies by location. Add or subtract 
// a declination to get a more accurate heading. Calculate 
// your's here:
// http://www.ngdc.noaa.gov/geomag-web/#declination
#define DECLINATION -8.58 // Declination (degrees) in Boulder, CO.

#ifdef MOTEINO_ZERO
  #ifdef SERIAL_PORT_USBVIRTUAL
    #define Serial SERIAL_PORT_USBVIRTUAL // Required for Serial on Zero based boards
  #endif
#endif
  
void setup() 
{
  pinMode(LED_BUILTIN,OUTPUT);
  Serial.begin(115200);
  delay(2000);
}

void imuSetup() {
  // Before initializing the IMU, there are a few settings
  // we may need to adjust. Use the settings struct to set
  // the device's communication mode and addresses:
  imu.settings.device.commInterface = IMU_MODE_I2C;
  imu.settings.device.mAddress = LSM9DS1_M;
  imu.settings.device.agAddress = LSM9DS1_AG;
  // The above lines will only take effect AFTER calling
  // imu.begin(), which verifies communication with the IMU
  // and turns it on.
  if (!imu.begin())
  {
    Serial.println("Failed to communicate with LSM9DS1.");
    Serial.println("Double-check wiring.");
    Serial.println("Default settings in this sketch will " \
                  "work for an out of the box LSM9DS1 " \
                  "Breakout, but may need to be modified " \
                  "if the board jumpers are.");
    //while (1);
  }  
}

void loop()
{
  imuSetup();
  
  printGyro();  // Print "G: gx, gy, gz"
  printAccel(); // Print "A: ax, ay, az"
  printMag();   // Print "M: mx, my, mz"
  
  // Print the heading and orientation for fun!
  // Call print attitude. The LSM9DS1's magnetometer x and y axes are opposite to the accelerometer, so my and mx are substituted for each other.
  printAttitude(imu.ax, imu.ay, imu.az, -imu.my, -imu.mx, imu.mz);
  Serial.println();
  delay(PRINT_SPEED);
  //imu.sleepGyro();
}

void printGyro()
{
  // To read from the gyroscope, you must first call the
  // readGyro() function. When this exits, it'll update the
  // gx, gy, and gz variables with the most current data.
  imu.readGyro();
  
  // Now we can use the gx, gy, and gz variables as we please.
  // Either print them as raw ADC values, or calculated in DPS.
  Serial.print("G: ");
#ifdef PRINT_CALCULATED
  // If you want to print calculated values, you can use the
  // calcGyro helper function to convert a raw ADC value to
  // DPS. Give the function the value that you want to convert.
  Serial.print(imu.calcGyro(imu.gx), 2);
  Serial.print(", ");
  Serial.print(imu.calcGyro(imu.gy), 2);
  Serial.print(", ");
  Serial.print(imu.calcGyro(imu.gz), 2);
  Serial.println(" deg/s");
#elif defined PRINT_RAW
  Serial.print(imu.gx);
  Serial.print(", ");
  Serial.print(imu.gy);
  Serial.print(", ");
  Serial.println(imu.gz);
#endif
}

void printAccel()
{
  // To read from the accelerometer, you must first call the
  // readAccel() function. When this exits, it'll update the
  // ax, ay, and az variables with the most current data.
  imu.readAccel();
  
  // Now we can use the ax, ay, and az variables as we please.
  // Either print them as raw ADC values, or calculated in g's.
  Serial.print("A: ");
#ifdef PRINT_CALCULATED
  // If you want to print calculated values, you can use the
  // calcAccel helper function to convert a raw ADC value to
  // g's. Give the function the value that you want to convert.
  Serial.print(imu.calcAccel(imu.ax), 2);
  Serial.print(", ");
  Serial.print(imu.calcAccel(imu.ay), 2);
  Serial.print(", ");
  Serial.print(imu.calcAccel(imu.az), 2);
  Serial.println(" g");
#elif defined PRINT_RAW 
  Serial.print(imu.ax);
  Serial.print(", ");
  Serial.print(imu.ay);
  Serial.print(", ");
  Serial.println(imu.az);
#endif
}

void printMag()
{
  // To read from the magnetometer, you must first call the
  // readMag() function. When this exits, it'll update the
  // mx, my, and mz variables with the most current data.
  imu.readMag();
  
  // Now we can use the mx, my, and mz variables as we please.
  // Either print them as raw ADC values, or calculated in Gauss.
  Serial.print("M: ");
#ifdef PRINT_CALCULATED
  // If you want to print calculated values, you can use the
  // calcMag helper function to convert a raw ADC value to
  // Gauss. Give the function the value that you want to convert.
  Serial.print(imu.calcMag(imu.mx), 2);
  Serial.print(", ");
  Serial.print(imu.calcMag(imu.my), 2);
  Serial.print(", ");
  Serial.print(imu.calcMag(imu.mz), 2);
  Serial.println(" gauss");
#elif defined PRINT_RAW
  Serial.print(imu.mx);
  Serial.print(", ");
  Serial.print(imu.my);
  Serial.print(", ");
  Serial.println(imu.mz);
#endif
}

// Calculate pitch, roll, and heading.
// Pitch/roll calculations take from this app note:
// http://cache.freescale.com/files/sensors/doc/app_note/AN3461.pdf?fpsp=1
// Heading calculations taken from this app note:
// http://www51.honeywell.com/aero/common/documents/myaerospacecatalog-documents/Defense_Brochures-documents/Magnetic__Literature_Application_notes-documents/AN203_Compass_Heading_Using_Magnetometers.pdf
void printAttitude(
float ax, float ay, float az, float mx, float my, float mz)
{
  float roll = atan2(ay, az);
  float pitch = atan2(-ax, sqrt(ay * ay + az * az));
  
  float heading;
  if (my == 0)
    heading = (mx < 0) ? 180.0 : 0;
  else
    heading = atan2(mx, my);

  heading -= DECLINATION * PI / 180;

  if (heading > PI) heading -= (2 * PI);
  else if (heading < -PI) heading += (2 * PI);
  else if (heading < 0) heading += 2 * PI;

  // Convert everything from radians to degrees:
  heading *= 180.0 / PI;
  pitch *= 180.0 / PI;
  roll  *= 180.0 / PI;
  
  Serial.print("Pitch, Roll: ");
  Serial.print(pitch, 2);
  Serial.print(", ");
  Serial.println(roll, 2);
  Serial.print("Heading: "); Serial.println(heading, 2);
}

SD-card Logger Breakout

Need a few gigs of logging memory to complement your MoteinoM0 or other wireless projects? It’s no problem with this mini SD-card breakout which includes a power switch to put your SD-Card to sleep.

Here’s a simple sketch for MoteinoM0 that will read the SD-Card content then sleep the SD card and everything else (radio, FLASH-MEM, if any) for sub 10uA sleep:

/*****************************************************************
 LowPowerLab SD-card Breakout Listfiles Example Sketch

 This example shows how to list the files on an SD card

 For MoteinoM0 this can be flat-mounted on the PCB, just align with SPI pins
 Wiring for use with other Moteinos/Arduinos using SPI bus:
  MOSI - pin 11
  MISO - pin 12
  CLK - pin 13
  CS - pin 4
*****************************************************************/

#ifdef MOTEINO_M0
  #define SD_CS    10
  #define SD_ON    11
  #ifdef SERIAL_PORT_USBVIRTUAL
    #define Serial SERIAL_PORT_USBVIRTUAL // Required for Serial on Zero based boards
  #endif
#endif

#include          //get it here: https://www.github.com/lowpowerlab/spiflash
#include     //get it here: https://www.github.com/lowpowerlab/rfm69
#define NODEID        2   //must be unique for each node on same network (range up to 254, 255 is used for broadcast)
#define NETWORKID     100  //the same on all nodes that talk to each other (range up to 255)
#define GATEWAYID     1
#define FREQUENCY     RF69_915MHZ
#define IS_RFM69HW_HCW //uncomment only for RFM69HW! Leave out if you have RFM69W!
RFM69 radio;  //#if defined (__AVR_ATmega32U4__) RFM69 radio(8,7);

SPIFlash flash(SS_FLASHMEM, 0xEF30); //EF30 for 4mbit  Windbond chip (W25X40CL)

#define BOARD_MOSI_PORT                   (1)
#define BOARD_MOSI_PIN                    (10)
#define BOARD_MISO_PORT                   (0)
#define BOARD_MISO_PIN                    (12)
#define BOARD_SCK_PORT                    (1)
#define BOARD_SCK_PIN                     (11)
//#define SS_PORT                           (1)
//#define SS_PIN                            (9)
#define SD_CS_PORT                        (0)
#define SD_CS_PIN                         (18)

#include 
#include 
File root;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  pinMode(SS_FLASHMEM, OUTPUT);
  pinMode(A2, OUTPUT);
  pinMode(SD_CS, OUTPUT);
  pinMode(SD_ON, OUTPUT);
  digitalWrite(SS_FLASHMEM, HIGH);
  digitalWrite(A2, HIGH);
  digitalWrite(SD_CS, HIGH);
  digitalWrite(SD_ON, HIGH);

  Serial.begin(115200);
  delay(3000);

  if (radio.initialize(FREQUENCY,NODEID,NETWORKID))
  {
    Serial.println("Radio init OK, putting it to sleep...");
    radio.sleep();
  }
  else {
    Serial.println("Radio init fail, moving on...");
    //return;
  }

  if (flash.initialize())
  {
    Serial.println("Flash-MEM init OK, putting it to sleep...");
    flash.sleep();
  }
  else {
    Serial.println("Flash init fail, moving on...");
    //return;
  }

  digitalWrite(SD_ON, LOW); //SD power ON

  if (SD.begin(SD_CS))
  {
    Serial.println("SD card init OK, listing content...");
    root = SD.open("/");
    printDirectory(root, 0);
    Serial.println("done!");
  }
  else {
    Serial.println("SD card init fail, moving on");
    //return;
  }

  digitalWrite(LED_BUILTIN, HIGH);
  Serial.println("Sleeping MCU in 3 seconds...");
  delay(3000);
  
  digitalWrite(LED_BUILTIN, LOW);
  digitalWrite(SD_ON, HIGH); //SD power OFF

  //reset SD CS direction to ensure there is no drainage
  PORT->Group[SD_CS_PORT].OUTCLR.reg = (1<<SD_CS_PIN);
  PORT->Group[SD_CS_PORT].DIRCLR.reg = (1<<SD_CS_PIN);
  PORT->Group[BOARD_MOSI_PORT].PINCFG[BOARD_MOSI_PIN].reg = 0;

  Serial.flush();
  delay(100);
    
  standbySleep(); //puts the M0 to "forever" deep sleep for lowest power sleep mode
}

void loop() {
  //nothing here
}

void printDirectory(File dir, int numTabs) {
  while (true) {

    File entry =  dir.openNextFile();
    if (! entry) {
      // no more files
      break;
    }
    for (uint8_t i = 0; i < numTabs; i++) {
      Serial.print('\t');
    }
    Serial.print(entry.name());
    if (entry.isDirectory()) {
      Serial.println("/");
      printDirectory(entry, numTabs + 1);
    } else {
      // files have sizes, directories do not
      Serial.print("\t\t");
      Serial.println(entry.size(), DEC);
    }
    entry.close();
  }
}

void standbySleep() {
    // Set sleep mode
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

  //Disable USB
  USB->DEVICE.CTRLA.reg &= ~USB_CTRLA_ENABLE;

  //Enter sleep mode
  __WFI();

  //...Sleep forever

  //Enable USB
  USB->DEVICE.CTRLA.reg |= USB_CTRLA_ENABLE;
}

Nothing stops you to mount all three of the above breakouts on a single Moteino M0, just be careful to align the pins correctly!