Particle Bench Demo + 128x32 Mono OLED

The functionality of the prior Bench Test / Demonstration with the 128x32 mono OLED added to the Xenon.   The code demonstrates how to configure and display text and the three pushbuttons on the OLED, and it provides information about the following:

  • The Particle device name is shown when the OLED pushbutton A is pressed, and every 30 seconds when no other event occurs
  • The system time when OLED button B is pressed
  • The system version when the OLED button C is pressed
  • An error message is displayed if the Mesh.publish() command fails in response to the external pushbutton.
  • Displays a message when the cloud function "BlinkZenonLED" is triggered, and it displays the value for the argument.

 

Xenon_A


/* 
    particle.io Xenon
    device name: Xenon_A
    sketch filename: bench_OLED.ino
    
   Particle Xenon + AF 128x32 mono OLED

    Demonstrations:
      - Expose a variable through the Cloud so that it can be called 
        with GET /v1/devices/{DEVICE_ID}/{VARIABLE}.
      - Expose a function through the Cloud so that it can be called 
        with POST /v1/devices/{DEVICE_ID}/{FUNCTION}.
      - Publish an event to the Mesh network that will be 
        forwarded to all registered listeners (received by device 
        Argon_A and responded by blinking Argon_A's LED).
    
    A0  connected to a Partical variable "XenonA0" and reports the measured
		value to the Device Cloud console every 25 seconds. 
		The value of AnalogA0Val is also published to the Particle Cloud as 
		event "XenonAeventA0" so it can be accessed with a WebHook. 

    D5  Turns on/off in response to pushbutton.
		Blinks in response to Particle function "BlinkXenonLED".  

    D7  Pushbutton triggers an event published to the Particle Device Cloud.    
    
    The 128x32 monochrome OLED uses I2C (D0 & D1; 0x3C) D2,D3,D4
    
    D2,
    D3,
    D4  Pushbuttons on 128x32 mono OLED
        
*/

// AF 128x32 mono OLED   
// https://github.com/rickkas7/oled-wing-adafruit
#include "oled-wing-adafruit.h"
OledWingAdafruit display;
int lastDisplay = 0;


int pinLED = D5; 
int pinPushButtonPullDown = D7;
int buttonState = LOW;         
int lastButtonState = LOW;
int lastDebounceTime = 0;  // the last time the output pin was toggled
int debounceDelay = 50;    // The switch debounce time.  50 to 100 ms


// Read analog input A0 quickly (every 1000 us or 1 kHz), calculate the 
// avg, min, max and then publish avg to the Particle Cloud every 25 sec.
#include "math.h"
int pinAnalogIn = A0;
double AnalogA0Val = 0.0;
// Set the value below equal to the number of bits for the ADC.
//  Argon, Boron, Xenon have 12-bit ADC
byte ADC_bits = 12; 
// vRef is the max analog input voltage (in mV)
float vRef = 3300.0;
// Timer for analogRead(pinADC)
// The min time to read one analog pin on Argon/Xenon/Boron is 10 us (100 kHz)
// 1000 us = 1 ms = 0.001 sec = 1 kHz
// 100 us = 0.1 ms = 0.0001 sec = 10 kHz
const unsigned long sampleWindow = 1000;  // sample window width in ms
unsigned long timerAlap = micros();  // timer
// Timer for Particle.Publish() of 
const unsigned long timerB = 25000;  // Publish value to Particle Cloud every 25000 ms = 25 sec
unsigned long timerBlap = millis();  // timer
// pinADC values in mV
float mVmax = 0.0;
float mVmin = vRef;
float mVsum = 0.0;
unsigned long samples = 0;


// Cloud function "BlinkXenonLED" that blinks the Xenon LED on pinLED.
int iBlinkXenonLED(String sBlinkCmd);


void setup() {

  pinMode(pinLED, OUTPUT);
  pinMode(pinPushButtonPullDown, INPUT);
  pinMode(pinAnalogIn, INPUT);

  // Publish the value of A0 as a Particle variable.
  // The frequency of publishing is managed by Particle. 
  Particle.variable("XenonA0",AnalogA0Val);

    // register the cloud function (funcKey, funcName)
  Particle.function("BlinkXenonA", iBlinkXenonLED);

  // Subscribe to Particle Cloud event "PushbuttonArgonA" sent from Argon_A
  // when the button is pressed on Argon_A.  Blinks the LED when the event occurs. 
  // NOTE: If you Particle.publish() with no PUBLIC or PRIVATE parameter, then it
  //       defaults to PRIVATE and then you must use ALL_DEVICES for Particle.subscribe().
  //       Particle.publish("sEventName", "sData", PUBLIC) => Particle.subscribe(sEventName, ALL_DEVICES)
  //       Particle.publish("sEventName", "sData", PRIVATE) => Particle.subscribe(sEventName, MY_DEVICES)
  Particle.subscribe("PushbuttonArgonA", PushbuttonArgonAEventHandler, MY_DEVICES);
 
  // Verify the LED works by blinking it. 
  blinkLED(pinLED);

   /* START AF FeatherWing OLED 128x32 mono */

  display.setup();
  display.clearDisplay();

  //display.setTextColor();  BLACK,BLUE,RED,GREEN,CYAN,MAGENTA,YELLOW,WHITE
  //display.setTextColor(text color, background color);
  //Sets the text color and background color the text will print on.
  //For monochrome (single-color) displays, colors are always specified as simply 1 (set) or 0 (clear).
  display.setTextColor(WHITE);
  
  // Turn off text wrapping
  display.setTextWrap(false);
  
  // display.setFont(const GFX font);
  // See font information at: https://learn.adafruit.com/adafruit-gfx-graphics-library?view=all
  
  display.clearDisplay();
  //  .setTextSize(2): 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
  display.setTextSize(2);   
  //display.setCursor(horizontal position, vertical position);
  display.setCursor(0,0);   // rows @ 0,16
  //display.print("0123456789"); 10x2 chars
  display.print("Xenon_A");
  display.setCursor(0,16);   // rows @ 0,16
  display.print("v20201214");
  display.display();
  lastDisplay = millis();
  
  /* END AF FeatherWing OLED 128x32 mono */
   
  timerBlap = millis(); // reset the timer
  timerAlap = micros(); // reset the timer
}   // setup()


void loop() {
    
  // Sample pinADC every sampleWindow microseconds
  if (timerAlap > micros() || timerBlap > millis()) {
    // micros() or millis() rollover
    mVmax = 0.0;
    mVsum = 0.0;
    mVmin = vRef;
    samples = 0;
    timerAlap = micros();// reset the timer
    timerBlap = millis(); // reset the timer
  }
  if (micros() - timerAlap > sampleWindow) {  
    float mV = Get_mVfromADC(pinAnalogIn);
    if (mV > mVmax) mVmax = mV;
    if (mV < mVmin ) mVmin = mV;
    mVsum = mVsum + mV;
    samples++;
    timerAlap = micros(); // reset the timer
  } else if (millis() - timerBlap > timerB) {  
    // Report the average value of A0 every timerB milliseconds
    //blinkLED(pinLED);
    AnalogA0Val = mVsum/float(samples);
    display.clearDisplay();
    display.setTextSize(2);   // 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
    display.setCursor(0,0);   // rows @ 0,16
    display.print("A0=");
    display.setCursor(37,0);
    display.print(AnalogA0Val);
    display.display();
    lastDisplay = millis();
    String sAnalogA0Val =  String(AnalogA0Val, DEC);
    bool bResult;
    bResult = Particle.publish("xenonAeventA0", sAnalogA0Val, PRIVATE);
    if (bResult != 1) {
        // event did not publish
        digitalWrite(pinLED, HIGH);
    }
    mVmax = 0.0;
    mVmin = vRef;
    mVsum = 0.0;
    samples = 0;
    timerAlap = micros();// reset the timer
    timerBlap = millis(); // reset the timer
  }

    display.loop();
    if (display.pressedA()) {
        // OLED button A on D2 pressed
    	display.clearDisplay();
    	display.setTextSize(2);   // 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
    	display.setCursor(0,0);   // rows @ 0,16
    	//display.print("012345678901234567890");  21 chars horizontally
    	display.println("Button A");
    	display.setCursor(0,16);
    	display.println("A0=");
    	display.setCursor(37,16);
    	display.println(AnalogA0Val);
    	display.display();
    	lastDisplay = millis();
    }
    
    if (display.pressedB()) {
        // OLED button B on D3 pressed
    	display.clearDisplay();
    	display.setTextSize(2);   // 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
    	display.setCursor(0,0);   // rows @ 0,16
    	display.println("Button B");
        display.setCursor(0,16);
        //Time.format(Time.now(), "Now it's %I:%M%p."); // Now it's 03:21AM.
        display.print(Time.format(Time.now(), "%I:%M%p"));
    	display.display();
    	lastDisplay = millis();
    }
    
    if (display.pressedC()) {
        // OLED button C on D4 pressed
    	display.clearDisplay();
    	display.setTextSize(2);   // 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
    	display.setCursor(0,0);   // rows @ 0,16
    	display.println("Button C");
        display.setCursor(0,16);
        display.print("v ");
        display.setCursor(21,16);
        display.print(System.version().c_str());
    	display.display();
    	lastDisplay = millis();
    }
    // Clear the display every 30 seconds
    if (lastDisplay > millis())  lastDisplay = millis();
    if (millis() - lastDisplay > 30000) { 
      display.clearDisplay();
      display.setTextSize(2);   // 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
      display.setCursor(0,0);   // rows @ 0,16
      //display.print("0123456789"); 10x2 chars
      display.print("Device:");
      display.setCursor(0,16);
      display.print("Zenon_A");
      display.display();
      lastDisplay = millis();
    }

  // Detect a change in the pushbutton state.
  // This logic is for a button using a pulldown resistor. 
  buttonState = digitalRead(pinPushButtonPullDown); 
  // if millis() or timer wraps around, reset it
  if (lastDebounceTime > millis())  lastDebounceTime = millis();
  if (buttonState != lastButtonState) { 
    // Multiple changes in the buttonState can occur when a pushbutton is 
    // pressed, or a switch is toggled. Use a debounce timer to only react
    // to a change in button state after an interval of debounceDelay. 
    lastButtonState = buttonState;
    //  Check if enough time has passed to evaluate another pushbutton press.
    if ((millis() - lastDebounceTime) > debounceDelay) {
      lastDebounceTime = millis();
      if (buttonState == HIGH) {
        digitalWrite(pinLED, HIGH);
        // Note that a Mesh.publish() cannot be simulated by the Particle Event Console,
        // and no events published by a device will be received by the Particle Event Console. 
        bool success;
        success = Mesh.publish("PushbuttonXenonA", "Pushbutton on Xenon_A");
        // Note that success == FALSE executes at least once. 
        // Therefore, it is better to check for success == TRUE
        if (success == 0) {
          // event published successfully
          digitalWrite(pinLED, LOW);
        } else {
          // Mesh.publish() error (leave the LED on)
          display.clearDisplay();
          display.setTextSize(2);   // 10 chars wide x 2 tall; each character is 12 pixels wide x 16 tall
          display.setCursor(0,0);   // rows @ 0,16
          display.print("ERROR #");
          display.setCursor(97,0);
          display.print(success);   // error code number
          display.setCursor(0,24);
          display.setTextSize(1);   // 20 chars wide x 4 tall; each character is 6 pixels wide x 8 tall
          //display.print("012345678901234567890");  21 chars horizontally
          display.print("Mesh.publish()");
          display.display();
          lastDisplay = millis();
        } // success
      } // buttonState
    } // millis()
  } // buttonState != lastButtonState

}   // loop()


void PushbuttonArgonAEventHandler(const char *event, const char *data) {
  // Event handler for event "PushbuttonArgonA"
  //blinkLED(pinLED);
  display.clearDisplay();
  display.setTextWrap(false);
  display.setTextSize(1);   // 20 chars wide x 4 tall; each character is 6 pixels wide x 8 tall; 
  display.setCursor(0,0);   // rows @ 0,8,16,24
  display.print("Argon_A pushbutton");
  display.setCursor(0,24);   // rows @ 0,8,16,24
  display.print("data=");
  display.setCursor(31,24);   // rows @ 0,8,16,24; col=Len("data=")*6+1
  display.print(data);
  display.display();
  display.setTextWrap(true);
  lastDisplay = millis();
} //PushbuttonArgonAEventHandler()


// Cloud function "BlinkXenonLED" that blinks the Xenon LED on pinLED.
// This function gets called by a POST request
// curl https://api.particle.io/v1/devices/XENHAB925CN8WBJ/BlinkXenonA \
//     -d access_token=123412341234 \
//     -d "args=once"
int iBlinkXenonLED(String sBlinkCmd) {
  if(sBlinkCmd == "once") {
    blinkLED(pinLED);
    delay(10);
    blinkLED(pinLED);
    delay(10);
    blinkLED(pinLED);
    display.clearDisplay();
    display.setTextWrap(false);
    display.setTextSize(1);   // 20 chars wide x 4 tall; each character is 6 pixels wide x 8 tall; 
    display.setCursor(0,0);   // rows @ 0,8,16,24
    display.print("Cloud function:");
    display.setCursor(0,8);   // rows @ 0,8,16,24
    display.print("'BlinkZenonLED'");
    display.setCursor(0,24);   // rows @ 0,8,16,24
    display.print("args=");
    display.setCursor(31,24);  // rows @ 0,8,16,24; col=Len("args=")*6+1
    display.print(sBlinkCmd);
    display.display();
    display.setTextWrap(true);
    lastDisplay = millis();
    return 1;
  }
  else return -1;
} //  iBlinkXenonLED()


float Get_mVfromADC(byte AnalogPin) {
  // Reads the ADC for the pin AnalogPin and returns the voltage in millivolts.
  // Works with any ADC bit size, provided ADC_bits is set to the correct value.
  // *** Uses Aref, so you MUST feed a reference voltage to Aref.

  // The standard analogRead() function takes about 112 us for the Uno, Nano, Mega, 
  // and other AVR chips (8 kHz).  It takes 425 us for SAMD21 chips (Arduino Zero).
  // A special library analogReadFast() can decrease the analog read time to 21 us.  
  //   https://www.avdweb.nl/arduino/adc-dac/fast-10-bit-adc
  //   https://github.com/avandalen/avdweb_AnalogReadFast
  // Other techniques that modify the ADC prescaler can achieve 30 kHz. 
  
  // A note about 1023 & 1024:
  //  10 bit ADC has 2^10 = 1024 values on a scale from 0 to 1023.
  //  The correct ADC conversion is 1024 for a 10 bit ADC.
  //  The maximum ADC value is 1023 
  
  // read the value from the sensor:
  int ADCval = analogRead(AnalogPin);  
  //  Voltage at pin in milliVolts = (reading from ADC) * (vRef/pow(2,ADC_bits)) 
  //float mV = ADCval * (vRef / ( (float)pow(2,ADC_bits) ));
  float mV = ADCval * (vRef / (float(pow(2,ADC_bits))));
  //float mV = ADCval * (vRef / pow(2,ADC_bits));
  return mV;
} // Get_mVfromADC()


void blinkLED(byte ledPIN){
  //  consumes 300 ms.
  for(int i = 5; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(30);
    digitalWrite(ledPIN, LOW);
    delay(30);
  }    
} // blinkLED()

 


Do you need help developing or customizing a IoT product for your needs?   Send me an email requesting a free one hour phone / web share consultation.  

 

The information presented on this website is for the author's use only.   Use of this information by anyone other than the author is offered as guidelines and non-professional advice only.   No liability is assumed by the author or this web site.