Nano I2c Peripheral

Nano deviceI’ve had a return to experimenting with my “nano peripheral”.  You may recall I was buying all sorts of parts – A/D converters, port expanders etc. to help with my solar monitoring – and some time ago I had a shot at making an I2c peripheral using an Arduino Nano (you don't need to look this up).

It even went as far as putting a display on the board – which I never ended up using and it occurred to me that it might be better to have the extra pins. Hence this new version of the NANO section of my home control diagram – but this is stand-alone – as long as you know how to use I2c you can use this.

Actually this might make a useful peripheral to control from one of the many single board computers such as Nanos, Raspberry Pi etc. as most can handle I2c and usually in something simple like Python so this might make a useful alternative to messing around with the pins on the main board - and let's face it, PWM on most of these boards is not that clever due to the operating system not really being real-time. I recall messing around with a NEO and I could get one hardware supported PWM output, the software PWM was atrocious. So immediately here you get half a dozen perfectly useful 8-bit PWM outputs on a little board costing less than a beer.

Home Control 2017

So above you see where it fits in the scheme of things – my general controller is an ESP8266 board with a boatload of properties – one of them being able to talk via I2c to various devices.

So the idea of this device is a “jack of all trades” -  unfeasibly cheap yet having useful specs – for example the A/D converter inputs are 10-bit – perfectly adequate for measuring battery voltages (you might need resistive dividers as the Arduino A/D input is 1.1v max in this case for value 1023).

This new device then is (by default) device 9 on the i2c bus though you can change that. Some pins are useful – others not so much – here below is an image of this “peripheral” which is nothing more than a programmed up Chinese £1.50 Nano board.

Nano peripheral

So as you can see (and bear in mind that not all NANO devices have the same pin-out) I’m making reasonable use of the pins. With I2c you can set outputs, read inputs, read analog inputs and set PWM outputs as well as sending a message out through the serial port. Of course this could be taken A LOT further as the code is not that complicated.

Don’t forget however if you are using this on it’s own with no other I2c devices – you need resistive pull-ups on SDA and SCL. I use 2k2. And of course you may want more than one..

Several Nano Peripheral Boards

If using this with my ESP8266 control software you need version 1.91 or better (updated 11/06/2017) as there was a timing error with earlier versions which prevented ADC reading.

I’m connecting this to the 5v supply on a WEMOS D1 (this Nano has it’s own 5v regulator) and no conversion of voltage levels is needed. The Home control manual has been updated so you have the commands in there – and the source code for the peripheral – likely to grown in the future – is below…

Servos:

I added in servos at the last minute after an almost expensive learning exercise.  You can control servos on any of the pins 2-13  - that’s a lot of servos – but as I found out – you can’t just run servos off the supply, say USB to the processor board. These things can take a fair bit of current (not control signal but power) so make sure they have adequate power – just one of them I tried was using 250ma when moving from A to B and that was just a little £4 toy servo. Consumption when still was nothing significant.

I learned a lot doing this – a quick look through the web showed that Arduino supports only 2 servos at once – and that PWM works – well both of those are WRONG. PWM does not work and made one of my servos get very hot… and the info out there about how many servos the Arduino can handle was also out a date – a simple trip to the Arduino site itself shows that the standard servo library can handle lots of them. So PWM is about the ratio between the ON time and the OFF time – servo driving is about the actual on period.

So -  I made servo control available on all of the pins up to and including 13 – so that’s 2-13 – not just the pins that handle PWM (use ANY servo and you lose PWM on pins 9 and 10 – no big deal there).

NANOOn wiring – and this will vary from servo to servo – I found (despite not finding this on the web) that of the 3 wires – brown, red and orange – the brown was ground, red was +5v and the orange was  a signal wire I could put straight onto a pin on the nano.

One of the annoying features of some Nano boards is lack of power and ground spares. Check this one out -

Ideas:

I have 780 bytes of RAM left which puts a limit on what I can add to this – what other features do you think might be nice to add to this – whatever they are they can’t take up too much processing time… maybe an edge-triggered counter on one of the inputs?  What else?

// So this is a simple attempt at a 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 units 
// 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. I originally put an SPI display on here but that 
// ate into too many otherwise useful pins.

// So as a guide you could use 3,5,6 and 9, 10 and 11 for PWM (unless you use these pins for general IO)
// you can use 2, 4, 7, 8, 12 and 13 as input/output (I tried using 0 and 1 - no go 0 flashes 
// on power up - 1 has pullup - best just avoid for GPIO 0 - 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 in - possibly 
// A6 (20) and A7 (21) if available. 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 from various boards.
//
// 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
//
// A simple i2c SLAVE - default device number 9 - reads instructions from
// master and either sets outputs or returns inputs accordingly.
//
// Not yet used but check out the enable interrupt library here
// https://github.com/GreyGnome/EnableInterrupt
// could be used for edge-triggered interrupts on any pin for pulse counting
//
//
#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>

#define MAXPORTS 21

#define EEPROM_CHSM 0
#define EEPROM_ID 1
#define EEPROM_USE_QTECH 2

#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

byte ports[MAXPORTS];
byte params[128];
byte retparams[2];
byte paramp;
byte bigcount;
byte device;

long mymillis;

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.
  
void setup(void) {
  // NO serial if using using 0-7 as port expansion (I'm not)
  // If you want serial - set the speed in the setup routine, if not, comment out
  uint16_t time = millis();
  byte eeprom1,eeprom2;
  analogReference(INTERNAL);  // 1.1v
  Serial.begin(115200);
  
 // first check if EEPROM info is valid?
 if (EEPROM.read(EEPROM_CHSM)!=0x3b)
 {
  for (int i = 0 ; i < EEPROM.length() ; i++) EEPROM.write(i, 0);
  EEPROM.write(EEPROM_CHSM,0x3b);
  EEPROM.write(EEPROM_ID,9);      // default I2c address 9
  EEPROM.write(EEPROM_USE_QTECH,1); /// default ON for QTECH
 }
  
 
  device=EEPROM.read(EEPROM_ID); // programmable address
  bigcount=0;
  Wire.begin(device);           // join i2c bus with address #9 by default
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent); 
  for (int a=0;a<MAXPORTS;a++) ports[a]=0;
  paramp=0;
  Serial.begin(115200);
  mymillis=0;
  for (int a=0;a<12;a++){ fade[a][0]=0; fade[a][2]=0; }
}

void loop() {

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() {
  Wire.write(retparams,2);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void receiveEvent(int count) {
int tcount;
tcount=count;
paramp=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); params[0]=0;
          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]); params[0]=0;
          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]); params[0]=0;
          break;          
    case SET_PWM:
          if (ports[params[1]]!=4) { ports[params[1]]=4; pinMode(params[1],OUTPUT); } 
          analogWrite(params[1],params[2]); params[0]=0;
          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; params[0]=0;
          break;    
    case SET_ADDRESS:
          EEPROM.update(EEPROM_ID,params[1]);
          // update address - will take effect on next powerup of the device as you 
          // can only call "begin" once
          params[0]=0;
          break;
    //case PORTSET: DDRD=params[1]; params[0]=0; break;
    //case PORTOUT: PORTD=params[1]; params[0]=0; break;
    //case PORTIN: retparams[1]=PIND; params[0]=0; break;
    case SEROUT: char *m;
                 m=(char *)&params[1];
                 Serial.print(m);
                 params[0]=0; 
                 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]); params[0]=0;
                 break; 
    case FADE:
          if (ports[params[1]]!=4) { ports[params[1]]=4; pinMode(params[1],OUTPUT); } 
          fade[params[1]][0]=1; fade[params[1]][2]=params[2];  params[0]=0;
          break;     
          
   default: break;  
    }
}

Latest version stored here  -https://bitbucket.org/snippets/scargill/kRg5o

Facebooktwittergoogle_pluspinterestlinkedin

5 thoughts on “Nano I2c Peripheral

  1. Hi peter, I got my PRO mini boards with the 3 rows of pins on either side, SVG, so plenty of +v and gnd connections. Boards look nice and pitch across outer pins is 1.1" so they will span two breadboards set up side by side, nice! I got them from the link you provided. These will make a good I2C extension board for the ESP8266 as per your various developments. I hope you got yours too.

    On another subject did you ever get node-red running on your Synology drive? I need some help setting mine up. I got MQTT broker running at treat.

  2. Good to see the torrent of '404's that today blighted my daily favourite foray into barely comprehensible tech in a vain attempt to drink at the fountain of truth has been dried up. Repeated thanks for the entertaining lessons.

    1. HI there

      Apologies to all - still not completely sorted - the service provider is at a loss as to why this has happened. I've reverted to short permalinks for a time to resolve this - which won't please Google.

  3. let's discuss here what we were chatting, so other can share their thoughts 🙂
    so, here's how to have more memory on your Nano, making it look like an Uno 🙂
    https://www.youtube.com/watch?v=bpXUvRr2ywA

    you pointed out that has to be tested if in this way the analog pins A6 and A7 that are on the Nano but NOT on the Uno can be used...

    i think they should work...
    1) they are inside the atmel chip itself, only difference is that on SOME nano are brought out to actual pins
    2) i took the standard analoginput sample sketch, changed a0 to a6 and compiled using UNO as target, and it worked... same using a7

    and from: http://forum.arduino.cc/index.php?topic=152724.0

    "Just a reminder that this TQFP package (AU) has 2 more ADC's than the DIP package (P), but (Arduino) A6 and A7 are only analog inputs, they cannot be used for outputs - nor do they have any pull-ups on them."

    "You can't set A6/A7 as inputs, because they're ONLY connected to the A-D convert mux. (so you can instruct the A-D converter to "read pin A6".) No resistor required, though..."

    "A6 and A7 are 100% analog only pins and they are only available onboards with the surface mount version of the ATmega328. Don't try to treat them as digital inputs."

    "According to the ATmega328P datasheet, ADC0 through ADC5 are on port C, which is also a regular digital I/O port. However, ADC6 and ADC7 are orphans that can be addressed directly but aren't part of a digital I/O port. I have no idea why the MCU was designed that way, but that's the way it is."

    "So you should be able to use A6 and A7 as analog inputs, but you can't use them as digital outputs. Sorry. It's not a specific limitation of our boards as opposed to any other Arduino-compatible board, it's just a limitation of the MCU itself."

Leave a Reply

Your email address will not be published. Required fields are marked *