Arduino Peripheral for HC2016

As many of you know, I don’t have a great deal of time for Arduino – I cannot tell you how many months I wasted on those daft cheap Ethernet boards for them which never really worked reliably no matter what I did – so I probably have a mental block by now.

However, there can be no arguing that a board costing £1.28 inc. postage has to be worth at least a second look. The picture below shows where this all fits into the scheme of things…

Home Control 2016

And so it was today, I’d just finished putting some polishing touches on the I2c code, fresh from having gotten the BMP280 working – and I was looking at A/D boards to add to the arsenal when I remembered that Arduinos have A/D in… not stunningly high definition but good enough for checking batteries and light levels etc.

Nano[6]At that point, mid-coffee I remembered I’d bought one of these little critters from AliExpress. Be careful – not all of these bring A4 and A5 out, some have micro-usb connectors, others don’t. Some have 3v3 regulators, some don’t.  I find the most useful ones just have the FTDI connector on them and no USB – but then I have FTDI connectors coming out of my ears.

For the purposes of this item – a 3v3 regulator is not needed as presumably if you’re fastening I2c devices to the ESP8266, you’ll feed 3v3 to the whole lot. Anyway, use whichever suits you best. I’m also assuming pullups are in place – the Arduino has pullups but I doubt they are strong enough.

SO – the point of this is – it is quite easy to make an Arduino into an I2c slave – so for £1.28 you can make a port extender, more inputs, some analog inputs, PWM outputs – just about anything really as long as whatever it is doing doesn’t take up any time as the board needs to respond to I2c commands quickly. I have a MUCH more powerful device on the way from China with lots more pins etc. but for now, the humble Chinese Nano gets the job.

The simple WIRE library with a little code turns the Nano or similar into a device – I’ve chosen to make it DEVICE 9 by default  – don’t REALLY want to use up ports making that programmable but then because the board has EEPROM I’ve made a hopefully reliable method to store the device number in EEPROM!

In the simplest example, sending I2c commands to this device from the home control software discussed elsewhere in this blog – let’s say to turn output 13 on…


And indeed that was the very first command I made it respond to as an I2c slave – mainly because on these boards, port 13 has a LED attached to it!!!

Clearly turning it off would be:


Or how about reading the state of input 10?


So here I’ve chosen to create the command nano – command 1 is set ports (2 is read ports)… port is 13, last parameter is 1 or 0 for on or off.  Immediately we have a port expander with several useful ports. For ease, the software I put into the Nano checks to see if the port has already been setup correctly and does that if not – hence avoiding annoying setup code at the ESP end.

With the simplest code and assuming A4 is used as the SCL and A5 is used as SDA, you end up with a “nano i2c peripheral” able to offer (if you get the right board offering A0-A7):

  • 6 8-bit PWM channels
  • 6 8-bit ANALOG inputs

i.e. ALL of that. You could instead choose to have 18 general purpose I/O lines etc.

I’m sure it would not take most of you too long to figure out ALL SORTS of other configurations but for the sake of this project and this board example– there are ports 2-21 where Arduino A0 is 14. Now,  if your board DOES have A6 and A7, note that they can ONLY be used as analog inputs – they cannot be used as ordinary inputs OR outputs – that’s just a simple feature of the board, not the software.

The point being – they are SO cheap and with this code make good general purpose I2c peripherals – you have to ask yourself – in some cases, why you would use anything else!

So before we start – this will only work for short strings or series of numbers with the standard WIRE library for Arduino – see the blog where I learned the hard way this weekend that WIRE has a 32 byte incoming buffer AND a 32 byte transfer buffer and if you try to send more than that – the Arduino crashes – I’ve updated my WIRE to 128 bytes (so that’s 192 bytes more than before ) and it is working a treat with long strings – the reason I want that is because though you won’t see it in this basic code, I’m now working on running QTECH 160*120 displays in the Arduino peripheral.

I’ve updated the code and here is the current state of affairs – evolving rapidly, for the Nano - expect this to change – again  -  this time tomorrow it will no doubt have changed - again.


// A simple i2c SLAVE - default device number 9 - reads instructions from
// master and either sets outputs or returns inputs accordingly.
#include <Wire.h>
#include <EEPROM.h>

#define MAXPORTS 21
#define ADDR_LOC1 0
#define ADDR_LOC2 1

#define SET_OUTPUT  1
#define READ_INPUT  2
#define SET_PWM     4
#define READ_ANALOG 5
#define SET_ADDRESS 6

byte ports[MAXPORTS];
byte params[6];
byte paramp;
byte retParam;
byte bigcount;
byte device=9;

void setup() {

  byte eeprom1,eeprom2;;; 
  if ((eeprom1^eeprom2)==255) device=eeprom1; // programmable address
  Wire.begin(device);           // join i2c bus with address #9 by default
  for (int a=0;a<MAXPORTS;a++) ports[a]=0;

void loop() {}  // not used yet

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {

  Wire.write(retParam); // respond with message of 1 bytes as expected by master
  //Wire.write(34); // respond with message of 1 bytes as expected by master
  //Wire.write(45); // test
  //Wire.write("This is it you know",19);

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void receiveEvent(int count) {
int tcount;
// no time consuming in here or the routine to send a byte back will be missed.
  while ((tcount--)&&(paramp<128))
  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;
    case READ_INPUT:
          if (ports[params[1]]!=2) { ports[params[1]]=2; pinMode(params[1],INPUT); } 
          retParam=digitalRead(params[1]); params[0]=0;
          if (ports[params[1]]!=3) { ports[params[1]]=3; pinMode(params[1],INPUT_PULLUP); } 
          retParam=digitalRead(params[1]); params[0]=0;
    case SET_PWM:
          if (ports[params[1]]!=4) { ports[params[1]]=4; pinMode(params[1],OUTPUT); } 
          analogWrite(params[1],params[2]); params[0]=0;
    case READ_ANALOG:
          if (ports[params[1]]!=2) { ports[params[1]]=2; pinMode(params[1],INPUT); } 
          retParam=analogRead(params[1]); params[0]=0;
    case SET_ADDRESS:
          EEPROM.update(ADDR_LOC1,params[1]); EEPROM.update(ADDR_LOC2,params[1]^255); 
          // update address - will take effect on next powerup of the device as you 
          // can only call "begin" once
    default: break;  


64 thoughts on “Arduino Peripheral for HC2016

  1. Yep this is definitely the way to go Peter,

    Use these as smart peripheral managers - keep adding to the case statements on the Arduino side and away you go

    1) Multiple DS18B20 hanging off the Nano/Mini
    2) Multiple Relays

    Some sort of watchdog in the Nano/Mini so that it restarts if it has not heard from the Master for a certain period of time i.e. hearbeat


    1. The only thing I'm struggling with is time - I wish the slave could hold the clock.... I think that's only something you can do in SPI...

      So for example - reading DS chips takes time - if I do that at some arbitrary period could I miss an incoming request... if I do it when requested, will the time taken make me miss the opportunity to respond... that all needs a little thought - but yes I'm already onto that case statement - first thing I want to do is add a programmable device address... that means 2 EEPROM locations, cross checking each other - maybe one the XOR of the other.

      Multiple relays is a given - the output controls are already there.. I think some kind of debouncing on inputs might be nice.

  2. If you have eeprom then use that to control what is happening - picaxe writes a 1 for when receiving and executing a request, clears that address when ready for the next


    1. Already stored address in EEPROM. As for commands - the issue with speed is at the other end - there is no way for the slave to tell the master to wait. Of course I can always instigate a short delay.

    1. oh, in ESPeasy case, that's just a dumb function that scans the bus and reports the addresses (in case of my 1602 lcd, 3F): when i add devices i have to select the correct address anyway, while a bette option would be to only populate the combobox with the addresses of found devices...

        1. No problem, just suggesting a little improvement... I'm good with your firmware, continuosly improved... Sorry to bother you, thanks for your work

    1. Been there, done Firmata. Not impressed - they STILL after all this time have not got it working properly on the 1284 chip., Sticking with I2c which I have to say is working very well. I can do far more this way, I already have the QTECH display running - will soon make an I2c command to fire text at it....

        1. Ok, so here's my current problem - can't get my head around this..

          Two commands from the ESP..

          {nano:9,7,"hi there",0}

          I've put a serial count check in receiveEvent
          So for the first command I get...

          Size 2
          Size 3

          and that is correct... first the command to, say grab the state of an input into a var - and the second command to actually read it - and that all works a treat....

          But the second one - which should return Size 10 (8 for the string - 1 for the zero on the end of the string and 1 for the command) - does indeed return 10... but then sometimes a second later returns 0.

          If I run the command again - it might return 10 or it might return 0 - it is as if somehow I am utterly messing the chip up......

          Got me demented...

        2. I'm looking at the I2c_master.c file for ESP8266 - I could well be missing something but aside from minor microsecond delays here and there I'm not seeing what i would expect to be a delay loop with timeout to wait for a slave... anyone checked that out?

  3. Here is one 5V / 16 MHz at £0.97 inc. shipping, from a shop I already bought from:

    And here is one 3.3V / 8MHz with integrated LDO, same price, same vendor:

    The 3.3V one is probably what you want to connect to an ESP8266 or an RPi.

    These one (and yours!) are nice, as you can plug an FTDI cable straight into them!

        1. Oh hell I had no intention of stopping at controlling pins - I already have the QTECH 128*160 display just about running (barring a technical issue with I2c which I'll discuss here in a minute) - but I guess your point is valid - as a poor person's IO and A/D - you can't beat 98 pence!!

  4. Here is one idea regarding addressing: have slaves enumerate at a fixed I2C address upon boot-up for a few seconds, and during this period if it receives a given command from he master, store the given address into EEPROM, then re-enumerate using the given stored address.

    This way, just using a master firmware with a specific address distribution mode, you can have slaves given an address one by one, without the need for jumpers or node-specific firmwares.

    1. I've added a command to accept a new address.. I'd not thought of rebooting on getting the new address - will put that in. I've not however put a timeout on it. What I have done is stored it in 2 EEPROM locations, one the XOR of the other - just to make sure that when the unit powers up it is fairly sure that there's a valid address in EEPROM.
      Right now I've a bigger problem in that sending a string of 10-15 chars to the little Nano - using WIRE which is the only way I know to get an I2c slave on an Arduino, it is failing. shorter packages seem to work perfectly. There is no pulse stretching on the WIRE implementation so I'm currently pondering how to get around this one - I'm not progressing with something that is less than 100% reliable. Perhaps I'll sort it - or I'm expecting a some what more expensive (£2.50 I recall or similar) 80Mhz unit which can handle Arduino source... time will tell.

    1. Good- but for Arduino IDE and hence probably lots of changes to get it to work under SDK... already have some debug on serial but will look to see if there are any ideas in there I can adopt.

  5. So this is the one I'm waiting for..

    £1.40 so slightly more expensive than the 328 board... (by 30p or so) but...

    At least in theory:

    5+ times faster - probably a lot more - should improve I2c performance
    LOTS more pins
    More program space, more RAM
    4 timers
    1 I2c buses!
    10 A/D channels with 12 bit resolution

    It is of course all down to available software but looks like a good deal to me.

      1. Yup. that one looks the business - as soon as the board turns up I'll give it a shot. Meanwhile I've a couple of new I2c boards arrived from China - that should keep me going for a while adding them in (or not).

      1. I've known Peter for a long, long time - and as you'll see in my upcoming blog entry - we were just talking this afternoon! I'll take a look at the other two. Always up for ideas... though right now - with working I2c and my A/D convertor has arrived in the post, I've more ideas than fingers.

        1. i follow them, and you too, of course...

          Peter is very skilled and advanced, you already know... i'm waiting for him to complete is gourgeous bench power supply...

          Ralph is very "teaching", long videos with full explaination of what he does and of the code itself, useful for who starts and more advanced, too...

          Andreas (i love his incipit: "here is the guy with the swiss accent, again" 😀 ) is very skilled too, does many things you do, too, as nextion, esp8266 advanced, and more... and i like to see their mailbags videos, always good to see new stuff to buy, and if it's worth or not...

        2. 🙂 A very Long time... over 40 years or there abouts now... Seems like yesterday we were at the Newcastle Personal Computer Society showing off gear in a 19" case..... Ahhh memories

  6. Peter,

    Just downloaded the latest version of the manual and was reading through it. Are you planning on doing discrete commands for reading from DS18B20 sensors or just using the generic Read_Input ?

    I want to run a couple of DS18B20s off each Nano/ESP hybrid - note sure if the command set has evolved that far - does not appear so from reading the Arduino sketch posted above ? Would seem to logical to pass a parameter as to port and what type of sensor and whether the results were to be returned in C or F ?


    1. I read DS18B20 units on either GPIO2 or GPIO14 - usually GPIO2 as our boards have connectors for that and the relevant pullup. Therefore I've not implemented DS reading on the Nano board as it's not needed. Also not implemented F reading as anyone reading C can convert.

      1. Peter, It appears though that you only support a single DS18B20 on each node i.e.. either GPIO2 or GPIO14 - not both. I have never like the daisychained bus for the DS18B20s (or any 1Wire device) so it seems like using a Nano/ESP hybrid for this would be perfect ?


  7. Peter,

    Just trying to wrap my head around this a little more - does the ESP do any sanity checking/cleansing of the commands that are sent from the Node-red environment or is it happy to simply pass along whatever it gets to the Arduino ? i.e. is it just the Arduino sketch that needs to be messed with from my end or also the ESP code ?


    1. Hi
      I also want to run multiple sensors on one line. i want to be able to read the flow and return temperatures of each radiator in my house, boiler flow & return, secondary return, hot water flow etc. This is for an idea i have for a super efficient domestic hot water heating system.

        1. Yeah its not just that - then you need USB power for each one (if using a NodeMCU etc) or multiple Sonoffs. If you go down the USB route you then need USB hubs to distribute the power to each unit etc etc

          But point taken - you are doing all this for free after all - and if you do not see any value in it - then fair enough


  8. Yep i saw that in the manual - but it appears that you only support a single temperature unit on each device ? In my case i want to run two for a particular application so thought that the Nano node would be ideal


Leave a Reply

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