Snippets

Peter Scargill Nano Peripheral IR (IR section untested, input always uses 2, output needs 3 if used)

Created by Peter Scargill last modified
// This update September 2018 Peter Scargill
//
// So this is a relatively simple,cheap peripheral using a £1.50 NANO board from China.
// It is supported by my ESP8266 software but as it is an I2c slave you could run it
// from anything able to handle I2c. For example I have found that some of the NanoPi SBCs
// are not too keen on even a bright LED on their IO pins and from an operating
// system like Linux, getting PWM on several pins is just not on... so - plug in this device
// (default ID 9) with pullups (always needed for I2c) and you can gain PWM, ADC
// and general IO for very little money or better, use with ESP-GO to add veryu inexpensive funtionality to the ESP8266.
//
// This version supports IR on pin 2 - you need this - follow installation instructions properly
// https://github.com/cyborg5/IRLib2
//
// Pin 2 is used for IR in using a standard 38khz type IR receiver.  PWM not affected.
// Note the RETURN information - 6 bytes with the 5th being status.
// in the case of IR, 0,1,2,3 are 32 bit value, 4 is protocol.  If protocol is zero, nothing there.
// 32 byte circular buffer.
//
// if and when IR output is used - this has to be gpio pin 3.
//
// As a guide you could use 3,5,6 and 9, 10 and 11 for PWM (unless you use these pins for general IO or 3 for IR out)
// you can use 4, 7, 8, 12 and 13 as input/output (I tried using 0 and 1 for GPIO - no go - 0 flashes. 2 is IR input on this version, can't 
// use for other purposes.
// on power up - 1 has pullup - best just avoid using these two for general IO - use for serial IO).
// remember 13 probably has a LED attached on the board so best used for output.
//
// You could use A0 (14), A1 (15), A2 (16) and A3 (17) as analog or digital inputs - possibly
// A6 (20) and A7 (21) if available on your board. Set to 1.1v full scale.
// A4 and A5 are used for the I2c where A4 is SDA and A5 is SCL.
//
// On the blog at https://tech.scargill.net you'll see several examples of using I2c.
//
// Late addition - servos - any of the pins 2-13 can be a servo. command is 11 - so device, command, pin, value
// Send value 255 to disconnect a servo and note if ANY pin is set up as a servo you lose PWM options on pins 9 and 10.
// Just disconnect all individually to get the PWM back (normally all disconnected at power up).
// Values 0-180 but this varies with different servos. Mine buzzed at 0 !! See Arduino Servo library
//
// The board becomes a simple i2c SLAVE - default (programmable) device number 9 - reads instructions from
// master and either sets outputs or returns inputs accordingly.
//
// There is also now a soft fade option for PWM (up to 6 PWM channels), a tone generator and Dallas temperature chip support for up to 2 chips.
// Here I use a simplified version of my DS18B20 code from years back. This starts the conversion at the END
// of the code - so the first value is rubbish - read the blog as this is hidden - and there are no delays. On the assumption of one chip
// per pin, no need for search either!

// This version returns 6 bytes - the LAST one is a status byte - 1 if busy. For IR, bytes 0,1,2,3 are value, 4 is protocol. Simply send that out 
// for transmission...  I'm using serial for debug right now - so can't use serial command - simply scrap that if you want to use serial out.
//
#include <EEPROM.h>
#include <Wire.h>
#include <Servo.h> /// note that if you use ANY servo, you lose PWM on pins 9 and 10.
#include <avr/pgmspace.h>
#include <OneWire.h>

// install the IR library if you want it - https://github.com/cyborg5/IRLib2 - pin 2 here used as input..
#include <IRLibDecodeBase.h>  //We need both the coding and
#include <IRLibSendBase.h>    // sending base classes
#include <IRLib_P01_NEC.h>    //Lowest numbered protocol 1st
#include <IRLib_P02_Sony.h>   // Include only protocols you want
#include <IRLib_P03_RC5.h>
#include <IRLib_P04_RC6.h>
#include <IRLib_P05_Panasonic_Old.h>
#include <IRLib_P07_NECx.h>
#include <IRLib_HashRaw.h>    //We need this for IRsendRaw
#include <IRLibCombo.h>       // After all protocols, include this
#include <IRLibRecvPCI.h>
IRrecvPCI myReceiver(2); //pin number for the receiver
IRdecode myDecoder;  

struct IRS {
  uint8_t protocol;
  uint32_t value;
};
IRS ir[32];

uint8_t ir_ip=0;
uint8_t ir_op=0;

IRsend mySender;

#define MAXPORTS 21

#define SET_OUTPUT  1
#define READ_INPUT  2
#define READ_INPUT_PULLUP 3
#define SET_PWM     4
#define READ_ANALOG 5
#define SET_ADDRESS 6
#define PORTSET 7
#define PORTOUT 8
#define PORTIN 9
#define SEROUT 10
#define SERVO 11   // value 255 disconnects.... - normally use 0-180
#define FADE 12    // pwm but you set desired colour, software soft fades from current to desired
#define TONE 13
#define NOTONE 14
#define DALLAS1 15
#define DALLAS2 16
#define IRIN 17
#define IROUT 18
#define SETSERIAL 20 // 0 means turn serial off, by default on. Other values- 1=300, 2=1200, 3=2400, 4=9600, 5=28800, 6=57600, 7=115200 baud

#define STRUCTBASE 0

byte busy = 0;
struct STORAGE {
  byte chsm;
  byte device;
  byte t1;
  byte t2;
};

int tr1 = 255;
int tr2 = 255;

STORAGE stored;

byte ports[MAXPORTS];
byte params[128];
byte retparams[6];
byte paramp;

long mymillis;

uint32_t irout;

const PROGMEM  uint8_t ledTable[256] = // Nano is so pathetically short of RAM I have to do this!
{
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4,
  4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, 18,
  18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32, 33, 33, 34, 35, 36, 36, 37, 38, 39, 40, 40, 41,
  42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 67, 68, 69, 70, 71, 72, 73, 75, 76, 77,
  78, 80, 81, 82, 83, 85, 86, 87, 89, 90, 91, 93, 94, 95, 97, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, 113, 114, 116, 117, 119, 121,
  122, 124, 125, 127, 129, 130, 132, 134, 135, 137, 139, 141, 142, 144, 146, 148, 150, 151, 153, 155, 157, 159, 161, 163, 165, 166, 168, 170,
  172, 174, 176, 178, 180, 182, 184, 186, 189, 191, 193, 195, 197, 199, 201, 204, 206, 208, 210, 212, 215, 217, 219, 221, 224, 226, 228, 231,
  233, 235, 238, 240, 243, 245, 248, 250, 253, 255
};

byte fade[12][3];
Servo myservos[14]; // just for ease - so use any pin from 3 to 13... bit of waste but so what.

// Here's the Dallas code - end user needs to spot negative values...see https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf
int16_t dallas (int x)
{
  OneWire ds(x);
  byte i;
  byte data[2];
  int16_t result;
  ds.reset();
  ds.write(0xCC);
  ds.write(0xBE);
  for (i = 0; i < 2; i++) data[i] = ds.read();
  result = (data[1] << 8) | data[0];

  ds.reset();
  ds.write(0xCC);
  ds.write(0x44, 1);
  return result;
}


void setup(void) {
  // If you want serial - set the speed in the setup routine, if not, comment out
  int a;
  uint16_t time = millis();
  byte eeprom1, eeprom2;
  analogReference(INTERNAL);  // 1.1v
  Serial.begin(115200);
  // get info out of EEPROM
  EEPROM.get(STRUCTBASE, stored);

  // first check if EEPROM info is valid?
  if (stored.chsm != 0x3c)
  {
    stored.chsm = 0x3d;
    stored.device = 9;
    stored.t1 = 255;
    stored.t2 = 155;

    EEPROM.put(STRUCTBASE, stored);


  }

  for (a = 0; a < MAXPORTS; a++) ports[a] = 0; // all inputs
  Wire.begin(stored.device);           // join i2c bus with address #9 by default
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);

  paramp = 0;
  Serial.begin(115200);
  mymillis = 0;
  for (a = 0; a < 12; a++) {
    fade[a][0] = 0;
    fade[a][2] = 0;
  }
  for (a = 0; a < 128; a++) params[a] = 0;
  delay(100);
  if (stored.t1 != 255) tr1 = dallas(stored.t1);
  if (stored.t2 != 255) tr2 = dallas(stored.t2);
  tr1 = 85 * 16;
  tr2 = 85 * 16;
    myReceiver.enableIRIn(); // Start the receiver
}

void loop() {


  if (myReceiver.getResults()) {//wait till it returns true  
    myDecoder.decode();  
    //myDecoder.dumpResults();  
    if ((myDecoder.protocolNum) && (myDecoder.value!=0xffffffff))
      {
       ir[ir_ip].protocol=myDecoder.protocolNum;
       ir[ir_ip].value=myDecoder.value;
       (++ir_ip)&=31;
       Serial.print(myDecoder.value,HEX); Serial.print (" - "); Serial.println(myDecoder.protocolNum);
      }
    myReceiver.enableIRIn();    //restart the receiver   
  }  


  if (mymillis < millis())
  {
    mymillis = millis() + 10;
    for (int a = 0; a < 12; a++)
    {
      if (fade[a][0])
      {
        if (fade[a][1] < fade[a][2]) {
          if (++fade[a][1] == fade[a][2]) fade[a][0] = 0;
          analogWrite(a, pgm_read_word_near(ledTable + fade[a][1]));
        }
        if (fade[a][1] > fade[a][2]) {
          if (--fade[a][1] == fade[a][2]) fade[a][0] = 0;
          analogWrite(a, pgm_read_word_near(ledTable + fade[a][1]));
        }
      }
    }
  }
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
  retparams[5]=busy;
  Wire.write(retparams, 6);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void receiveEvent(int count) {
  busy = 1;
  int a;
  int tcount;
  tcount = count;
  paramp = 0;
  for (a = 0; a < 6; a++) params[a] = 0;
  // Nothing time consuming or visual debugging in here if a RETURN VALUE is expected or the routine to send a byte back could be missed.
  while ((tcount--) && (paramp < 128))
  {
    params[paramp++] = Wire.read();
  }
  switch (params[0])
  {
    case SET_OUTPUT:
      if (ports[params[1]] != 1) {
        ports[params[1]] = 1;
        pinMode(params[1], OUTPUT);
      }
      digitalWrite(params[1], params[2] ? HIGH : LOW);
      break;
      
    case READ_INPUT:
      if (ports[params[1]] != 2) {
        ports[params[1]] = 2;
        pinMode(params[1], INPUT);
      }
      retparams[0] = 0; retparams[1] = digitalRead(params[1]);
      break;
    case READ_INPUT_PULLUP:
      if (ports[params[1]] != 3) {
        ports[params[1]] = 3;
        pinMode(params[1], INPUT_PULLUP);
      }
      retparams[0] = 0; retparams[1] = digitalRead(params[1]);
      break;
      
    case SET_PWM:
      if (ports[params[1]] != 4) {
        ports[params[1]] = 4;
        pinMode(params[1], OUTPUT);
      }
      analogWrite(params[1], params[2]);
      break;
      
    case READ_ANALOG:
      if (ports[params[1]] != 2) {
        ports[params[1]] = 2;
        pinMode(params[1], INPUT);
      }
      uint16_t anback; anback = analogRead(params[1]); retparams[0] = anback >> 8; retparams[1] = anback & 255;
      break;
    case SET_ADDRESS:
      stored.device = params[1]; EEPROM.put(STRUCTBASE, stored);
      // update address - will take effect on next powerup of the device as you
      // can only call "begin" once
      break;
    case SEROUT: char *m;
      m = (char *)&params[1];
      Serial.print(m);
      break;
    case SERVO : if (ports[params[1]] != 5) {
        ports[params[1]] = 5;
        myservos[params[1]].attach(params[1]);
      }
      if (params[2] == 255) {
        myservos[params[1]].detach();
        ports[params[1]] = 0;
        break;
      }
      myservos[params[1]].write(params[2]);
      break;
      
    case FADE:  // node-red and esp-go {nano:9,12,3,255} for a single output, for rgb on 3,5 and 6 assuming decice default 9)
                // {nano:9,12,3,255;nano:9,12,5,80;nano:9,12,6,10}
      if (ports[params[1]] != 4) {
        ports[params[1]] = 4;
        pinMode(params[1], OUTPUT);
      }
      fade[params[1]][0] = 1; fade[params[1]][2] = params[2];
      break;

    case TONE:  // can't do PWM on pins 2 and 11 while doing this... only one pin at a time...use NOTONE when finished
      if ((params[4] | params[5]) == 0) tone(params[1], (params[2] << 8) + params[3]); else tone(params[1], (params[2] << 8) + params[3], (params[4] << 8) + params[5]);
      ports[params[1]] = 0;
      break;

    case NOTONE:  // can't do PWM on pins 3 and 11 while doing TONE...
      noTone(params[1]); ports[params[1]] = 0;
      break;

    case DALLAS1:
      tr1 = dallas(params[1]);
      if (params[1] != stored.t1) {
        stored.t1 = params[1];   // no delay hence first value crap
        EEPROM.put(STRUCTBASE, stored);
      }
      retparams[1] = tr1 & 255; retparams[0] = tr1 >> 8;
      break;

    case DALLAS2:
      tr2 = dallas(params[1]);
      if (params[1] != stored.t2) {
        stored.t2 = params[1];  // no delay hence first value crap
        EEPROM.put(STRUCTBASE, stored);
      }
      retparams[1] = tr2 & 255; retparams[0] = tr2 >> 8;
      break;

    case IRIN:
      if (ir_ip==ir_op) retparams[4]=0;
      else {
            retparams[4]=ir[ir_op].protocol;
            retparams[3]=(ir[ir_op].value>>24);
            retparams[2]=(ir[ir_op].value>>16)&255;
            retparams[1]=(ir[ir_op].value>>8)&255;
            retparams[0]=ir[ir_op].value&255;
            (++ir_op)&=31;
            }
      break;

    case IROUT:
      irout=params[2] + (params[3]<<8) + (params[4]<<16) + (params[5]<<24);
      mySender.send(params[1],irout,params[6]);
      break;

    case SETSERIAL:
          switch (params[1]) {
            case 0 : Serial.end(); break;
            case 1 : Serial.begin(300); break;
            case 2 : Serial.begin(1200); break;
            case 3 : Serial.begin(2400); break;
            case 4 : Serial.begin(9600); break;
            case 5 : Serial.begin(28800); break;
            case 6 : Serial.begin(57600); break;
            case 7 : Serial.begin(115200); break;
            default: break;            
          }
          break;
    
    default: break;
  }
  busy = 0;
}

Comments (2)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.