The NetP Project: Creating a Network of Processors with Arduino Mini Pro Boards


Arduino is truly remarkable; I have a deep fondness for it. It can be likened to the “Lego” of the electronics and automation world. Presently, I’m engrossed in a project where numerous MCU boards are orchestrated to form an interconnected network of processors, aptly named NetP. These boards establish communication via the i2c bus. The selection comprised Arduino Mini Pro boards operating at a voltage of 3.3V. To facilitate the transfer of sketches from the Arduino IDE to the Mini Pros, a USB to serial FTDI board is indispensable. Additionally, in order to accommodate the keyboard, a TTL level converter is employed to shift from 3.3V to 5V.

At the moment, several functionalities have been successfully implemented, while there remains a list of pending tasks. Furthermore, certain aspects are awaiting ingenious solutions, aligned with our aspirations.

Accomplishments thus far include:

The most prominent component is the console, comprised of a keyboard and a scrolling display. This facilitates interaction with the nearby MCU, Netp0 (Console), which, in turn, enables communication with other constituents of the local i2c networkβ€”Netp1 (SD), Netp2 (Updater), Netp3 (Clock), and so forth.

Alternatively, you have the option to forgo the console’s display and keyboard by establishing a Bluetooth connection with a smartphone. This enables remote control, and I have impeccably configured the app “Serial Bluetooth Terminal 1.31” for this purpose, although alternative apps are also viable options.


netp-drawio Network of Processors

Each member of the Netp MCU ensemble is assigned distinct attributes: an individualized i2c bus address (0x0a), a designated name (CONSOLE), a unique ID (p0), and a corresponding number (0). It’s worth noting that the i2c bus may also accommodate additional elements, currently limited to an EEPROM and a Real Time Clock board. It’s imperative to allocate separate i2c addresses for each member or constituent.

The overarching design principle is plug-and-play functionality. Adding a new Netp member to the setup seamlessly integrates it into the network, a process readily discernible through the “scan” command, which identifies newly connected members.

Functionality of the CONSOLE (p0):

  • scan: Initiating a scan [inquiry into the i2c bus, e.g., scan, generates a roster of active addresses on the bus presented as “DDD (0xEE)”… culminates with “‘n’ found”].
  • ckpower: Querying power status [e.g., ckpower, yields “v3=3.3 v5=5.0 vraw=7.0-12.0”].
  • ?: Requesting command elucidation [e.g., ?, retrieves a catalogue of available commands on the board along with available memory].
  • reset: Enforcing a reboot [e.g., reset compels the MPU to undergo a reset].
  • <pxname> <cmd>: Dispatching <cmd> to processor <pxname> [e.g., SD dir /, delivers the outcome of <cmd> from processor <pxname>].

The “Scan” command is automatically activated at 30-second intervals, facilitating seamless plug-and-play integration. To ascertain availability, simply execute the “scan” command, revealing names and addresses of active components. Employ the “?” command to unveil the array of implemented commands on the console. For insights into the commands operational on the SD member, initiate the “p1 ?” command. If you wish to obtain a listing of files on the SD, executing “p1 dir /” will sufficeβ€”similarly for other functionalities.

Functionality of the SD (p1):

  • ?: Request assistance [e.g., ?, provides an inventory of accessible commands on the board along with available memory].
  • mw/mwa: mw <FileName>=<Value> [initiates composition/modification of Value within file name, e.g., mw temp01=24 (FileName=Value), yields “written”] (mwa will append Value to the file)
  • mr/mra: mr <FileName> [extracts Value from corresponding file name, e.g., mr temp01 (FileName is temp01, Value is 24, returns ‘Value’ or “not found”)] (mra will retrieve multi-line Value from the file)
  • reset: reset [initiates MPU reset, e.g., reset]
  • dir: dir <path> [scans for file names commencing from ‘path’, e.g., dir /], provides a roster of file ‘names’ along with their ‘size’… concludes with ‘n found’]
  • mkdir: mkdir <dirname> [constructs a fresh directory (including relevant subdirectories), e.g., mkdir /music, sdmkdir /logic/params/binary, yields “built” or “error”]
  • rmdir: rmdir <dirname> [erases a directory, for example, rmdir /music, produces “removed” or “error or not empty”]
  • rename: rename <OldFileName> <NewFileName> [renames a file, i.e. rename myfile.txt bestfile.txt, returns “renamed” or “not found”]
  • del: del <FileName> [delete a file, i.e. del myfile.txt, returns “deleted” or “not found”]
  • type: type <FileName> <Mode> [reads a file and displays in mode 0=CHR, 1=BIN, 2=HEX, i.e. type myfile.txt CHR, returns the content of the file… and filesize ‘n’ and bytes red ‘n’ at the end]
  • eeupl: eeupl <Filename> [read a file and upload it to the EEPROM, write and verify all data, i.e. eeupl /mysketch.hex, returns upload, written, verified…]

As a result of limited memory availability, certain commands have been deactivated, retaining only those essential for my needs. My past encounters have emphasized the importance of refraining from pushing memory usage close to its maximum capacity, as this may lead to unforeseeable MCU behavior. For the SD sketch, I believe I’ve meticulously optimized the code to conserve memory space, but there might still be room for further improvements.

The mw and mr commands function within the /MEMORY/ subdirectory, whereas mwa and mra are applicable within the /LOG/ subdirectory.

Functionality of the UPDATER (p2):

  • ?: ? [ask for help, i.e. ?, returns a list of available commands on board and free memory]
  • reset: reset [resets MPU, i.e. reset]

Functionality of the CLOCK (p3):

  • ?: ? [ask for help, i.e. ?, returns a list of available commands on board and free memory]
  • reset: reset [resets MPU, i.e. reset]
  • red: red <Mode> [switch on or off the red LED, i.e. red on, returns “ON” or “OFF”]
  • green: green <Mode> [switch on or off the green LED, i.e. green on, returns “ON” or “OFF”]
  • dht: dht [get temperature and humidity from DHT board, i.e. DHT, returns the values]
  • gettemp: gettemp [get temperature from RTC board, i.e. gettemp, returns the temperature value]
  • gettime: gettime [get time from RTC board, i.e. gettime, returns the time value as per DD/MM/YYYY HH:MM:SS]
  • settime: settime [set the time to RTC board, i.e. settime 31/12/2018 10:20:30, returns the time value as per DD/MM/YYYY HH:MM:SS]

netp-proto2 Network of Processors

List of Components:

  • 4 x Arduino Mini Pro 3.3v
  • Micro SD Card adapter
  • MicroSD memory card
  • RTC zs-042 module
  • DHT-11 sensor
  • Protoboard power supply from 7-12V to 3.3V and 5V
  • 4 x protoboards
  • Red LED
  • Green LED
  • 2 x 470uF 25V capacitors
  • 100nF capacitor
  • HC05 bluetooth module
  • FTDI USB-Serial converter
  • Display 2.4″ TFT LCD 240×320 RGB, driver IC ILI9341
  • USB mini keyboard
  • TTL level converter 3.3V <-> 5V for keyboard USB
  • USB female + cable
  • Active buzzer
  • EEPROM i2c 1024
  • Push button
  • 100 ohm resistor
  • 4 x 5k1 ohm resistors
  • 2k2 ohm resistor
  • 6k8 resistor
  • 2 x 120 ohm resistor
  • Many wires..

netp-bb Network of Processors

i2c Communication Bus Protocol:

Netpx members establish communication through the i2c BUS utilizing the Wire.h library, employing a customized protocol based on messages and events.

When a command like “SD dir /” is inputted from the console keyboard, the name “SD” is initially translated into the corresponding address (e.g., 0x0b) by referencing the netRegisterNames[] matrix. This address is then dispatched onto the BUS using the askCommandOnPx() function. On the recipient end, a receiveEvent() event is triggered, leading to the local execution of the requested command and the subsequent transmission of its data. The identifier for these messages is the chr(7) BEL byte.

Furthermore, during the scan command, an additional message is conveyed through the BUS using the whois() function in conjunction with Wire.requestFrom(). This message is employed to solicit the name and supplementary details from a potential Netpx member at a designated address, such as 0x0c. In the absence of a suitable response, the device is categorized as an “other device.” On the receiving side, a netWhoIsEvent() event is triggered. The identifier for these messages is the chr(5) ENQ byte.

Pending Tasks (My Wishlist):

First and foremost, a command request queue is of utmost importance. Currently, when commands are dispatched from the console, the execution of subsequent commands is often neglected if sent in quick succession following the initial one. This proposed feature entails amassing all commands within a queue and subsequently executing them one by one, maintaining their appropriate sequence.

The second enhancement on my agenda involves the implementation of a scheduler. This scheduler would comprise a compilation of commands earmarked for execution at specific times or intervals. This list of commands could be interactively modified through console interactions, endowing a dynamic and responsive scheduling mechanism.

Lastly, my most ambitious aspiration pertains to the facilitation of storing updated or new sketches within a designated directory on the SD card. This revamped setup would involve the UPDATER overseeing the transfer of these sketches to the EEPROM before deployment onto the target MCU. To enable such functionality, the destination MCU, slated for the update, should be equipped to autonomously read from the EEPROM and perform the required autoupdate. However, achieving this functionality necessitates the modification of the conventional bootloader for seamless integration.

Do you have any thoughts or recommendations?

As I gather new developments, enhancements, additional components, or fresh concepts, I will share these updates within these sections. I eagerly await your insights and feedback on this ongoing project.


/* Arduino Net-P (i2c processors network) by Zonca Marco 2020
 * 'netMyNAME' ("CLOCK   ") max 8 char, 'netMyID' (p9) max 2 char, 
 *  address='netMyAddress' (0x16) max 0xFF, 'netMyPx' (9)
 * implemented commands (* = to do): HELP | ?, dht, gettemp, gettime, settime, red, green
#include <Wire.h>
#include <ds3231.h>  // RTC real time clock
#include <dht.h>

const int netMyPx = 9; // netMyPx 0-9
const int netMyAddress = 0x16;  // i2c address 0x16=HEX 22=DEC
const char *netMyID = "p9";  // netMyID
const char *netMyNAME = "CLOCK   ";  // netMyNAME
const int GreenLedPin=8;
const int RedLedPin=9;
const int DHTPin=14;
const int ResetPin=17;  // A3 put low for reset

char _CR[2]="";  // CR
char _BEL[2]="";  // BEL
char _ENQ[2]="";  // ENQ
char _ACK[2]="";  // ACK
char _SPACE[2]="";  // SPACE
char _MYADDR[2]="";  // netMyAddress

char lineString[65]="";
char recCommand[65]="";
byte recAddress=0;
byte recType=0;
char data[2]="";
bool isNetDataWaiting=false;
bool isKnownCommand = false;
bool isRedOn=false;
bool isGreenOn=false;
bool isBusy=false;

struct ts t; // RTC
dht DHT;  // HG + Temp sensor

void setup() {
  Wire.onReceive(receiveEvent);  // i2c event
  Wire.onRequest(netWhoIsEvent);  // i2c whois
  _CR[0] = 13;  // CR
  _BEL[0] = 7;  // BEL
  _ENQ[0] = 5;  // ENQ
  _ACK[0] = 6;  // ACK
  _SPACE[0] = 32;  // SPACE
  _MYADDR[0] = netMyAddress;
  pinMode(GreenLedPin, OUTPUT);
  pinMode(RedLedPin, OUTPUT);
  DS3231_init(DS3231_INTCN);  // RTC
  DHT.read11(DHTPin);  // DHT
}  // end setup()

void loop() {
  if (isNetDataWaiting == true) {
}  // end loop()

void execNetCommand() {  // executes received command
  char park[65]="";
  char command[65]="";
  if (recType == _BEL[0]) {  // char(7) = BEL command type
    if (s_compare(recCommand,"")==0) {  // empty command just do nothing
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,3);  // red
    if (s_compare(park,"red ") == 0) {
      if (strcmp(command, "on") == 0 || strcmp(command, "off") == 0 || strcmp(command, "?") == 0) {
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,5);  // green
    if (s_compare(park,"green ") == 0) {
      if (strcmp(command, "on") == 0 || strcmp(command, "off") == 0 || strcmp(command, "?") == 0) {
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,4);  // reset
    if (s_compare(park,"reset") == 0) {
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,2);  // dht
    if (s_compare(park,"dht") == 0) {
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,6);  // gettemp
    if (s_compare(park,"gettemp") == 0) {
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,6);  // gettime
    if (s_compare(park,"gettime") == 0) {
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,7);  // settime
    if (s_compare(park,"settime ") == 0) {
      if (strlen(command) == 19) {
    s_substring(park,sizeof(park),recCommand,sizeof(recCommand),0,0);  // ?
    if (s_compare(park,"?") == 0) {
    if (isKnownCommand == false) {
      s_assign(lineString,"unknown cmd",sizeof(lineString));
  }  // endif recType
}  // end execNetCommand()

void reset() {  // ------------------------------------------------------------ RESET

void help() {  // ---------------------------------------------- HELP
  s_assign(lineString, "red <on off ?>", sizeof(lineString));
  s_assign(lineString, "green <on off ?>", sizeof(lineString));
  s_assign(lineString, "dht", sizeof(lineString));
  s_assign(lineString, "gettemp", sizeof(lineString));
  s_assign(lineString, "gettime", sizeof(lineString));
  s_assign(lineString, "settime <DD/MM/YYYY HH:MM:SS>", sizeof(lineString));
  s_assign(lineString, "reset", sizeof(lineString));
}  // end help()

void ledred(const char *_s) {  // ---------------------------------------------- RED
  if (strcmp(_s, "on")==0) {
  if (strcmp(_s, "off")==0) {
  if (isRedOn) {
    sprintf(lineString, "%s","ON");
  } else {
    sprintf(lineString, "%s","OFF");
}  // end ledred()

void ledgreen(const char *_s) {  // ---------------------------------------------- GREEN
  if (strcmp(_s, "on")==0) {
  if (strcmp(_s, "off")==0) {
  if (isGreenOn) {
    sprintf(lineString, "%s","ON");
  } else {
    sprintf(lineString, "%s","OFF");
}  // end ledgreen()

void dht() {  // ---------------------------------------------- DHT
  float temp=0;
  int iintt=0;
  int idect=0;
  int iinth=0;
  int idech=0;
  DHT.read11(DHTPin);  // DHT
  temp = DHT.temperature; // read DHT temp C
  temp = DHT.humidity; // read DHT humidity
  sprintf (lineString, "%d.%dC %d.%d", iintt, idect, iinth, idech);
}  // end gettemp()

void gettemp() {  // ---------------------------------------------- GETTEMP
  float temp=0;
  int iint=0;
  int idec=0;
  temp = DS3231_get_treg(); // read RTC temp C
  sprintf (lineString, "%d.%dC", iint, idec);
}  // end gettemp()

void gettime() {  // ---------------------------------------------- GETTIME
  DS3231_get(&t);  // read RTC date & time
  sprintf(lineString, "%02d/%02d/%04d %02d:%02d:%02d", t.mday, t.mon, t.year, t.hour, t.min, t.sec);
}  // end gettime()

void settime(const char* _s) {  // ---------------------------------------------- SETTIME
  char park[5]="";
  s_substring(park, sizeof(park), _s, strlen(_s), 0,1);
  s_substring(park, sizeof(park), _s, strlen(_s), 3,4);
  s_substring(park, sizeof(park), _s, strlen(_s), 6,9);
  s_substring(park, sizeof(park), _s, strlen(_s), 11,12);
  s_substring(park, sizeof(park), _s, strlen(_s), 14,15);
  s_substring(park, sizeof(park), _s, strlen(_s), 17,18);
  DS3231_set(t);  // write RTC
  gettime();  // answer with new time
}  // end settime()

void prompt() {  // prompt, ready for commands
}  // end prompt()

void dispLine() {  // print information
  s_concat(lineString, _CR, sizeof(lineString));
  txBackToSender();  // tx
  s_clear(lineString, sizeof(lineString));
}  // end dispLine()

void receiveEvent(int howMany) {  // i2c event incoming requests
  if (isBusy==false) {
    int counter=0;
    s_clear(recCommand, sizeof(recCommand));  // sender command
    recAddress=0;  // sender address
    recType=0;  // sender cmd type
    if (howMany == 0) {  // ignores empty requests
    } else {
      while (Wire.available()) {
        if (counter==0) { recType = data[0]; } // 1st char = cmd type
        if (counter==1) { recAddress = data[0]; } // 2nd char = sender address
        if (counter >1) { // other chars = command
          s_concat(recCommand, data, sizeof(recCommand)); 
}  // end receiveEvent()

void netWhoIsEvent() {  // i2c event: 11 bytes = 1=char(5) ENQ, 2-9=netMyNAME 10=netMyPx 11=netMyAddress
  if (isBusy==false) {
    char ccommand[65]="";
    char park[3]="";
    s_assign(ccommand, _ENQ, sizeof(ccommand));  // ENQ
    s_concat(ccommand, netMyNAME, sizeof(ccommand));  // netMyNAME
    s_concat(ccommand, itoa(netMyPx,park,10), sizeof(ccommand));  // netMyPx
    s_concat(ccommand, _MYADDR, sizeof(ccommand));  // netMyAddress
}  // end netWhoIsEvent()

void txBackToSender() {  // tx back to sender
  char ccommand[65]="";
  s_assign(ccommand, _ACK, sizeof(ccommand));  // ACK=answer data
  s_concat(ccommand, _MYADDR, sizeof(ccommand));  // netMyAddress
  s_concat(ccommand, lineString, sizeof(ccommand));  // data
  Wire.beginTransmission (recAddress);
  Wire.write (ccommand);
  Wire.endTransmission (true);
}  // end txBackToSender()

//------------------------------------------------------------------------- CLEAR
void s_clear(char *dest_source_string, const int dest_sizeof) {  // fills-up with NUL=chr(0)
  memset(dest_source_string, 0, dest_sizeof);
}  // end s_clear()

//------------------------------------------------------------------------- ASSIGN
bool s_assign(char *dest_string, const char *source_string, const int dest_sizeof) {  // copies source to dest
  int _LenS=strlen(source_string);  // how many bytes
  if (_LenS > (dest_sizeof - 1)) {
    return true;  // err 1
  } else {
    strcpy(dest_string, source_string);
    return false;  // ok 0
}  // end s_assign()

//------------------------------------------------------------------------- SUBSTRING
bool s_substring(char *dest_string, const int dest_sizeof, const char *source_string, 
 const int source_sizeof, const int source_from, const int source_to) {  // copies source(from, to) to dest
  if ((source_from < 0) || (source_to < source_from) || ((source_to - source_from + 1) > (dest_sizeof - 1)) 
    || (source_to >= (source_sizeof-1)) || ((source_to - source_from + 1) > (strlen(source_string)))) {
    dest_string[0]=0;  // NUL
    return true;  // err 1
  } else {
    int _Count=0;
    for (int i=source_from;i<(source_to+1);i++) {
    dest_string[_Count]=0;  // ends with NUL
    return false;  // ok 0
}  // end s_substring()

//------------------------------------------------------------------------- CONCAT
bool s_concat(char *dest_string, const char *source_string, const int dest_sizeof) {  // append source to dest
  int _LenS=strlen(source_string);  // how many bytes source
  int _LenD=strlen(dest_string);  // how many bytes dest
  if ((_LenS + _LenD) > (dest_sizeof - 1)) {
    return true;  // err 1
  } else {
    strcat(dest_string, source_string);
    return false;  // ok 0
}  // end s_concat()

//------------------------------------------------------------------------- COMPARE
bool s_compare(const char *dest_string, const char *source_string) {  // compares source with dest
  int _LenS=strlen(source_string);  // how many bytes source
  int _LenD=strlen(dest_string);  // how many bytes dest
  if (_LenS != _LenD) {  // different length
    return true;  // are different 1
  } else {
    if (strcmp(dest_string, source_string) == 0) {
      return false;  // are the same 0
    } else {
      return true;  // are different 1
}  // end s_compare()

//------------------------------------------------------------------------- FREEMEM
int s_freemem() {
  extern int __heap_start,*__brkval;
  int v;
  return (int)&v -



About The Author

Scroll to Top