Wednesday, August 17, 2016

Upgrading NodeMCU Firmware to v1.5 on EPS8266 (ESP-12E)

I've been updating firmware on my NodeMCU few times without issues - at least up to 0.9
The installation procedure has changed with SDK 1.5 - I've spent few hours before I got it right, so maybe this post will help you to save some time.

Here you will find all required information, but depending on your chip manufacturer you will need different combination of some options, and this is exactly the part when it's getting a bit tricky.

Since things are changing rapidly, I will point out exact versions in this tutorial, so that it's clear what was working with what at time of this writing.

Installing esptool

Download esptool v1.1 https://github.com/themadinventor/esptool/releases/tag/v1.1 and unzip it.
This will give you possibility to execute esptool.py form command line - we will use it below.

Determining version of your ESP8266 chip

As usual there are different versions, and probably you will have to modify flashing commands if you have different one.
sudo python esptool.py -p /dev/tty.wchusbserial1410 flash_id

esptool.py v1.2-dev
Connecting...
Manufacturer: e0
Device: 4016

Well it's obviously ESP-12E with 4MB flash:

How do you tell? I've no idea - just google for "Manufacturer: e0 Device: 4016" and check what people are thinking.


Building custom firmware 

Now we have to obtain firmware containing Lua interpreter for our chip - it's everything that you will need in order to upload Lua scripts over serial. 

Basically you will need two binary files: 
  • custom firmware - something like: nodemcu-dev-7-modules-2016-08-10-10-43-59-integer.bin
  • initial-data-block (esp_init_data_default.bin)
Obviously you will need matching versions. 

There are two options to get those files: 
  • you can download it from my github repo - this will give you SDK 1.5.4.1
  • you can also use cloud service to build latest release. The link to latest initial-data-block can be found here - just search for esp_init_data_default.bin
I would prefer a latest version - as usual. But in case of some problems you can try using version provided on my github repo. At least this one was working in my case with ESP-12E, this might help you to locale a problem.

Erase flash

Put your chip into flash mode as described here. If you have development board it should have two buttons: flash and reset. In such case press flash and without releasing it press reset, after that release reset.

Now you have to determine com port, and change it in all commands in this tutorial. In my case it's: /dev/tty.wchusbserial1410

Let's finally erase flash!
sudo python esptool.py -p /dev/tty.wchusbserial1410 erase_flash
esptool.py v1.1
Connecting...
Erasing flash (this may take a while)...

Now disconnect ESP from usb port, so that it can fully reset - pressing reset button does not have the same effect !

Flashing new firmware

Put your chip again into flash mode.
I am assuming that you have in you current directory two binary files: one with firmware (nodemcu-dev-7-modules-2016-08-10-10-43-59-integer.bin) and second with init-stuff (esp_init_data_default.bin).
sudo python esptool.py -p /dev/tty.wchusbserial1410 write_flash -fm dio -fs 32m 0x000000 nodemcu-master-7-modules-2016-08-10-10-45-03-integer.bin 0x3fc000  esp_init_data_default.bin

esptool.py v1.1
Connecting...
Running Cesanta flasher stub...
Flash params set to 0x0240
Writing 385024 @ 0x0... 385024 (100 %)
Wrote 385024 bytes at 0x0 in 33.4 seconds (92.2 kbit/s)...
Writing 4096 @ 0x3fc000... 4096 (100 %)
Wrote 4096 bytes at 0x3fc000 in 0.4 seconds (80.0 kbit/s)...
Leaving...


Testing....

I've used ESPlorer, and it looks just fine.



Thursday, May 12, 2016

TS 555 timer with trigger network

I wanted to pimp my son's bobby car by installing few lights, they should go on for a few minutes once he starts moving the car. For this I've installed few LEDs and an micro switch attached to rear axle. So that the micro switch will get triggered from time to time.

Connecting LEDs was quiet simple, but I had some difficulties to build circuit that will enable the lights for few minutes. I've decided to use 555 timer - there are plenty of examples out there how to assemble it. I just wanted one, that will be energy efficient on stand by, and the lights should always go off after few minutes. This was not so easy, because you have to trigger the 555 by short impulse, otherwise it will never go off. My solution with micro switch has this one catch, that it can remain shorted for very long time. I've could ask my son to keep pushing the car until he hears the second click..... instead I've used a trigger network.

On the end I had to find right values for all components in the way that it will guarantee the functionality and ensure low power consumption to not to drain batteries over few days. Actually I did not calculate those values, I've just used oscilloscope to make sure that timing on 555 pins is right. Probably I've got not a perfect values, but on the other hand side it does what is suppose to be doing and it runs on single battery for months :)



Here is the Fritzing drawing.


Thursday, February 4, 2016

Arduino LED Display

My latest project (https://github.com/maciejmiklas/LEDDisplay) contains driver for 8x8 LED Modules controlled via MAX722xx. It allows you to build display of custom size that is only limited by the hardware itself. Vertical and horizontal size can contain up to 256 modules, but before reaching this limit you would run out of Slave Select lines for controlling MAX chips, or you would be limited by amount of RAM. The fact is: you can control reasonable amount of MAX chips and build display of custom size ;)

I've tested the whole idea on display that consist of 8 LED Modules in horizontal and 3 in vertical position. This gives us 24 modules which are containing 1536 LEDs (88 * 38).

Hardware

First let's start with the controller, actually any Arduino will work, I've used Mega due to large number of digital output pins. You could also use Nano with shift register and alter way of addressing Select Slave lines in Display::send(...).

You will need extra power supply for driving LEDs - assuming that you are going to use more than one LED Matrix.

Driving single LED Matrix Module

I've used the MAX7219 and 788BS LED Matrix, this is the one with common anode. The schematic below illustrates wiring of LEDs, MAX and Arduino:

 This one is equivalent, but instead of single LEDs we have PIN layout of LED Module:
It might happen, that your LED Module has common cathode, in this case you have to rewire connection to MAX chip. You just have to keep in mind, that MAX hat two sets of pins, that are relevant here: Dig0-Dig7 are supposed to be connected to cathodes (-) and SegA-SegG to anodes(+). Additionally such change will swap rows with columns within sine LED module.

Connecting all LED Matrix together

In the previous chapter we've seen how to connect single LED Module with MAX chip. Now we will connect multiple LED Modules together into one large display. Below is the physical display that I've used for testing and examples. Each LED Module has label indicating its position and Select Slave line.
Here is the wiring (and image below in original size https://github.com/maciejmiklas/LEDDisplay/blob/master/doc/fritzing/LED_Display_schem.png):

Each 3-PIN connector on schematic above symbolizes one module described in previous chapter (LED Matrix + MAX72xx), now we've connected all those modules together.

All MAX722xx chips share common MOSI and SCK lines, MISO is not used, each chip occupies separate Slave Select line.

The position of LED Matrix on the schematic above directly corresponds to their location on the physical display that I've used for testing. Additionally each module has description indicating it's position and Select Slave line, so for example: (2,1) SS: 35 gives us second module on third row (counting from zero) and PIN:35 on Arduino for Select Slave line.

 

Software

 

Compiling

We are using standard Arduino libraries, so they are already available, the only exception is ArdLog. You have to import this LIB into your IDE. This basically means, that you have to download right release: https://github.com/maciejmiklas/ArdLog/releases/tag/v1.0.0 and unzip it into folder, where you usually place external libraries. In case of standard Arduino IDE on Mac it's ~/Documents/Arduino/libraries. On the end you should have following structure:

$ pwd
~/Documents/Arduino/libraries/ArdLog

$ ls
ArdLog.cpp ArdLog.h   LICENSE    README.md

 

Communication with MAX72xxx

We are using standard SPI library and Select Slave line on MAX chip for addressing. MAX is configured in LED Matrix mode - so there is nothing special. The setup method can be found in: Display::setup()

 

Setting things up

The main class of our interest will be the Display - it's responsible for setup of MAX chips and provides API for painting.
Before we start painting it's necessary to set thing up. Code below creates 2D array containing Select Slave lines and initializes display. The display itself consist of 3 rows, each one has 8 LED Modules. Obviously you can choose any responsible size, but I will stick to this one.

The layout of mentioned 2D array corresponds to physical display: each LED Module has dedicated MAX chip, and each chip has dedicated Select Slave line. First dimension of our array indicates physical row on display, second dimension indicates LED Module within this row, and the value itself contains PIN number for Select Slave line.

#include <Display.h>

Display *disp;

/**
 * Orientation of LED Kits (8x8 LED matrix) on display that I've used for testing.
 * The numbers are indicating Select Slave line of MAX7219.
 * 48, 46, 49, 47, 45, 43, 41, 39
 * 36, 34, 32, 30, 28, 26, 24, 22
 * 37, 35, 33, 31, 29, 27, 25, 23
 */
ss_t **ss;

ss_t** createSS() {
  ss_t **ss = alloc2DArray8(3, 8);

  // first row
  ss[0][0] = 48;
  ss[0][1] = 46;
  ss[0][2] = 49;
  ss[0][3] = 47;
  ss[0][4] = 45;
  ss[0][5] = 43;
  ss[0][6] = 41;
  ss[0][7] = 39;

  // second row
  ss[1][0] = 36;
  ss[1][1] = 34;
  ss[1][2] = 32;
  ss[1][3] = 30;
  ss[1][4] = 28;
  ss[1][5] = 26;
  ss[1][6] = 24;
  ss[1][7] = 22;

  // third row
  ss[2][0] = 37;
  ss[2][1] = 35;
  ss[2][2] = 33;
  ss[2][3] = 31;
  ss[2][4] = 29;
  ss[2][5] = 27;
  ss[2][6] = 25;
  ss[2][7] = 23;

  return ss;
}

void setup() {
  util_setup();
  log_setup();

  ss = createSS();


  // Test display consist of 8x3 LED Modules (3 rows, each one 8 Modules)
  disp = new Display(8, 3, ss);
  disp->setup();
}

There is one more method worth mentioning: log_setup(). Whole project has quiet precise logger - so that you can see what is actually happening. By default it's disabled, in order to enable it check out its documentation: https://github.com/maciejmiklas/ArdLog

 

Painting on the display 

Display consists of a few LED Modules, but form API perspective they are connected together into one continuous canvas. You can place on this canvas bitmap on any position given by such coordinates:

 (0,0) -----------------------------> (x)
      |
      |
      |
      |
      |
      |
      |                            (x max, y max)
      v (y)

The paint method has following syntax: paint(pixel_t x, pixel_t y, pixel_t width, pixel_t height, uint8_t data).

It allows you to paint a bitmap on given coordinates with limited width and height. So you can for example paint a bitmap on (3,4) that has 25x3 pixels. It might be larger than a actual display size - in this case it will get trimmed.

This is obvious and simple, but there is one catch - you have to provide right data. This is 2D array, where first dimension indicates vertical and second horizontal position on the display. Technically speaking data is flat array of pointers and each pointer points to array that represents one horizontal line on the display.

Moving over first dimension of data traverses over lines of the display. The second dimension of data represents horizontal pixels within single line, where each byte represents 8 pixels. Since our display consist of simple LEDs they can be either in on or off state, so each pixel is not being represented by one byte, but by one bit. In order to cover 16 pixels in horizontal position we need two bytes, 24 pixels require 3 bytes, and so on.
For example to fully cover display consisting of 8x3 LED kits (one used in our examples) we would need data[3][8]. Usually you will take array small enough to fit your bitmap and not one that will cover up whole display.

The paint(...) method updates internal buffer, in order to send content of this buffer to MAX chips you have to call flush(). The idea behind is to give you possibility to display few bitmaps on the display and after that paint the result. You can program few independent routines, that will update different part of the display and flush all changes at once.

Communication with MAX chips is not very fast and sending content of the whole display with every flush() is time consuming. You might be able to speed up this process by enabling double buffering (set DEOUBLE_BUFFER in Display.h to true). In this case flush() method will send only bytes that have changed, so you can call flush() with every loop and do not have to worry about loosing performance. The only drawback is increased usage of RAM: we are creating 2D array that allocates 8 bytes per each LED Kit plus few pointers that are usually required to maintain arrays.

2D arrays in this project have reduced memory footprint, because in order to create dynamic 2D array, we are creating actually 2 arrays with calculated offset (see: alloc2DArray8(....) in Util.h).

Examples

Requires Libs

Examples are using ArdLog, so you have to import this lib into Arduino IDE. Here are instructions: https://github.com/maciejmiklas/ArdLog

 

Simple Bitmap 

In this example we will display simple static bitmap with 8x8 pixels:
Here is the Arduino sketch: SimpleBitmap, now lest go over it:

First we have to initialize display, as we have done in above in chapter Setting things up. Next we have to create data that can hold our bitmap - it will have 8x2 bytes. This gives us up to 8 lines and 16 horizontal pixels. But the size of our bitmap is 9x8 pixels (width x height) and this will be also the size of the painted rectangle. It should be as small as possible, so that you could place another bitmap right next to it.

The display will obviously only paint the rectangle given by width/height and not whole data array. This is normal, that data array can hold more pixels than accrual size of out bitmap, because size of data is a multiplication o 8 and bitmap not necessary.

void setup() {
  util_setup();
  log_setup();

  ss = createSS();

  disp = new Display(8, 3, ss);
  disp->setup();

  data = alloc2DArray8(8, 2);
  data[0][0] = B01100001; data[0][1] = B10000000;
  data[1][0] = B01100001; data[1][1] = B10000000;
  data[2][0] = B01100001; data[2][1] = B10000000;
  data[3][0] = B01100001; data[3][1] = B10000000;
  data[4][0] = B01100001; data[4][1] = B10000000;
  data[5][0] = B00110011; data[5][1] = B00000000;
  data[6][0] = B00011110; data[6][1] = B00000000;
  data[7][0] = B00001100; data[7][1] = B00000000;

  disp->paint(27, 9, 9, 8, data);
}

void loop() {
  util_cycle();
  log_cycle();

  // Paint method updates only internal buffer, in order to send data to 
  // MAX chips you have to flush display. 
  disp->flush();

  delay(100000);
}

 

Static Text 

Now we will display static text, actually those are going to be two independent lines.

Here you can find Arduino sketch containing whole example: StaticText.

Your sketch needs setup method as we've already seen above (chapter: Setting things up), so we will not discus it again.

In order to display text you should use StaticText8x8.

Font is defined in: Font8x8, each character has 8x8 pixels.

Your code could look like this one (plus initialization stuff from Setting things up):

StaticText8x8 *sta1;
StaticText8x8 *sta2;

void setup() {
  util_setup();
  log_setup();

  ss = createSS();

  disp = new Display(8, 3, ss);
  disp->setup();

  sta1 = new StaticText8x8(disp, 64);
  sta1->box(14, 2, "Hello");

  sta2 = new StaticText8x8(disp, 64);
  sta2->box(5, 15, "World !");
}

void loop() {
  util_cycle();
  log_cycle();
  disp->flush();

  delay(100000);
}

We have created two text areas, each one containing different text and being display one under another.

 

Single Scrolling Text

This time we are going to display area containing text that will scroll from left to right. Link below contains youtube video - you can start it by clicking on it.
Lest analyze code (Arduino sketch):

Display *disp;

ScrollingText8x8 *message;
const char *textMessage;

void setup() {
  util_setup();
  log_setup();

  ss = createSS();

  disp = new Display(8, 3, ss);
  disp->setup();

  message = new ScrollingText8x8(disp, 48, 50, 5);
  message->init();
  textMessage = "This is an example of multiple scorlling areas ;)";
  message->scroll(8, 8, ScrollingText8x8::LOOP, textMessage);
}

void loop() {
  util_cycle();
  log_cycle();

  message->cycle();

  disp->flush();
}

The initialization of the display is the same as in examples above, so it's omitted here.

In order to display scrolling text we are using ScrollingText8x8. In setup() we are creating instance of this class and calling method scroll(...). This part only initializes scrolling, but does not play the animation itself. In order to play the animation you have to call cycle() and flush() in main loop and you must not have any additional delays there, otherwise you might get jagged animation.

During creation of ScrollingText8x8 we have provided speed of animation - actually it's a delay of 50ms per frame. Now calling cycle() in main loop will produce frames of animation according to provided delay. When the time comes the method cycle() will update display and finally method flush() will send updated content to MAX chips.

The whole implementation of ScrollingText8x8 is non blocking and it consumes CPU only when there is something to be done. Internally it's using simple State Machine.

There is one last thing: you have to keep text used for animation in global variable in order to avoid garbage collection. It's not being copied in scroll() to avoid memory fragmentation.

 

Scrolling Text Mixed 

This example is similar to one above, but this time we will display several scrolling areas:

Here is the sketch.
This code is similar to one with one scrolling area, but this time we have a few:

void setup() {
  util_setup();
  log_setup();

  ss = createSS();

  disp = new Display(8, 3, ss);
  disp->setup();

  uint8_t borderSpeed = 20;
  textUpDown = "* * * * * ";
  up = new ScrollingText8x8(disp, 64, borderSpeed, 1);
  up->init();
  up->scroll(0, 0, ScrollingText8x8::CONTINOUS_LOOP, textUpDown);

  down = new ScrollingText8x8(disp, 64, borderSpeed, 2);
  down->init();
  down->scroll(0, 16, ScrollingText8x8::CONTINOUS_LOOP, textUpDown);

  textLeftRight = "* ";
  left = new ScrollingText8x8(disp, 8, borderSpeed, 3);
  left->init();
  left->scroll(0, 8, ScrollingText8x8::CONTINOUS_LOOP, textLeftRight);

  right = new ScrollingText8x8(disp, 8, borderSpeed, 4);
  right->init();
  right->scroll(56, 8, ScrollingText8x8::CONTINOUS_LOOP, textLeftRight);

  message = new ScrollingText8x8(disp, 48, 50, 5);
  message->init();
  textMessage = "This is an example of multiple scrolling areas ;)";
  message->scroll(8, 8, ScrollingText8x8::LOOP, textMessage);
}

void loop() {
  util_cycle();
  log_cycle();

  up->cycle();
  down->cycle();
  right->cycle();
  message->cycle();
  left->cycle();


  disp->flush();
}

We have created few instances of ScrollingText8x8, each one containing different text and position on the display. In order to play animation you have to call cycle() on each instance, but you have to call only once flush(). Each call on cycle() will update it's part of the display and flush will send changed display to MAX chips.

Wednesday, February 3, 2016

Logger for Arduino over Serial Port

I am bit tired of using Serial as logger - mainly for two reasons: it does not support sprintf syntax and string are being held in RAM. For this reason I've implemented new library: https://github.com/maciejmiklas/ArdLog

ArdLog serves as simple logger for Arduino that creates formatted messages over Serial:
  • Each message has timestamp.
  • Each message within single loop has the same timestamp, so that you can logically connect activities together.
  • Messages can be formatted using sprintf syntax.
  • Text for the messages is being held in PROGMEM.

Installation

In order to install ArdLog you have to download desired release and unpack in into folder containing Arduino libraries. The is the result on MacOS:

$ pwd
/Users/fred/Documents/Arduino/libraries/ArdLog

$ ls
ArdLog.cpp ArdLog.h   LICENSE    README.md

Configuration

  • Logger is disabled by default, in order to enable it set LOG to true.
  • Messages are created over default Serial port. You can choose alternative port by setting: USE_SERIAL_1, USE_SERIAL_2 or USE_SERIAL_3 to true.
  • In order to print current time for each message set USE_CURRENT_TIME to true. By default logger will sample time only once at the beginning of each loop.

Getting up and running  

  1. Choose suitable configuration in ArdLog.h. In most cases you have to only set LOG to true.
  2. Call log_setup() in setup() method - this will initialize serial port.
  3. Call log_cycle() at the beginning of each loop() - it will sample current time.
  4. Put log messages into #if LOG log(F("....") #endif - once logger is disabled, it will not waste RAM and CUP.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <ArdLog.h>

uint16_t loopIdex = 0;

void setup() {
  log_setup();
}

void loop() {
  log_cycle();

  loopIdex++;

  #if LOG
    log(F("**** Loop %d ****"), loopIdex);
    log(F("T1 = %ld"), millis());
  #endif

  delay(100);

  #if LOG
    log(F("T2 = %ld"), millis());
  #endif

  delay(1000);
}
This is output created by example above:

>>[000-00:00:00,000]-> Logger initialized, free RAM: 1537
>>[000-00:00:00,003]-> Free RAM: 1527
>>[000-00:00:00,003]-> **** Loop 1 ****
>>[000-00:00:00,003]-> T1 = 9
>>[000-00:00:00,003]-> T2 = 112
>>[000-00:00:01,113]-> **** Loop 2 ****
>>[000-00:00:01,113]-> T1 = 1114
>>[000-00:00:01,113]-> T2 = 1215
>>[000-00:00:02,215]-> **** Loop 3 ****
>>[000-00:00:02,215]-> T1 = 2217
>>[000-00:00:02,215]-> T2 = 2318
>>[000-00:00:03,319]-> **** Loop 4 ****
>>[000-00:00:03,319]-> T1 = 3320
>>[000-00:00:03,319]-> T2 = 3421
>>[000-00:00:04,422]-> **** Loop 5 ****
>>[000-00:00:04,422]-> T1 = 4423
>>[000-00:00:04,422]-> T2 = 4525

Saturday, May 16, 2015

Direct Digital Synthesizer based on plain Arduino

Microcontroller is meant to control stuff and not to generate periodic signals - for this propose we would use a dedicated hardware - something like Atmega328 and AD9850.
But on the other side this might be an interesting project - not very useful, but at least we can manually build DDS and get good understanding of its functionality. The basic idea of such synthesizer is to create a software loop where each iteration will output single point of particular wave. Higher amount of points within single period increases resolution and reduces frequency - due to limited processing power.
There is also a good motivation to optimize the code - each extra operation consumes CPU cycles and decreases maximal output frequency.



Generated wave signals

Sine with 120 probes per period


Sine with 360 probes per period
Max frequency is limited to 1.8Khz where sine with 120 steps reaches 5.58Khz

Signal at 10Hz is smoother when compared to sine with 120 steps.

Square

Saw

Wave Generator

Arduino does not have Analog to Digital converter - there is only PWM generator, but in order to generate smooth wave we need stable analog voltage. Common pattern to solve this problem is to take a few digital outputs and connect them together trough voltage divider - you can see that on schematics in the right bottom corner. We are using Arduino's digital output D0-D7 because they are all managed by single register (Port D). This means that we can change value of all 8 bits (D0-D7) with single assignment, for example: PODTD = B10000001 would set D0 and D7 to 1. This also means that mapping between byte value and analog output is linear, here are few examples that I've measured using oscilloscope:

PORTD Out
B00000001 22 mV
B00000010 38 mV
B00000100 73 mV
B00001000 140 mV
B00010000 276 mV
B00100000 545 mV
B01000000 1.08 V
B10000000 2.18 V

PORTD Out
B00000001 22 mV
B00000011 56 mV
B00000111 125 mV
B00001111 265 mV
B00011111 543 mV
B00111111 1.1 V
B01111111 2.23 V
B11111111 4.47 V
Each generated wave consist of few steps - for example sine has 120 (or 360 because there are two versions). Each step is pre-calculated and stored in an array - main loop simply goes over this array and assigns value from it to PORTD.

To smooth out generated signal I've used capacitor 470pF on the output. The op-amp works as a buffer so that resistance of a device connected to the output cannot influence generated wave - capacitor would discharge faster and we would change characteristics of voltage divider.

Configuration

There are four buttons:
  • Frequency Up - increase frequency
  • Frequency Down - decrease frequency
  • Delay Step - value of the step for frequency change
  • Select Wave -  you can choose from from sine 120, sine 360, square and saw
Reaction on buttons is handled by interrupts, so that the main loop can concentrate on generating high frequency wave.

Frequency adjustment

There is a small catch... single press on Frequency Up does not necessary change the frequency by one hertz, but it changes the amount of wasted CPU cycles per step .... I will try to explain this from the beginning:
as you remember we are generating wave by going over pre-calculated table - each byte in such table will be assigned to PORTD and this happens within a single "step". In order to plot wave we have to go over whole table, once we are done, we have to start from the beginning. In order to change frequency, we have to alter time for each step - this is the only possibility to proportionally scale up he whole wave. The smallest amount of delay in our case is the single CPU operation - it's called NOP and it takes 1327 nano seconds - NOP itself is quicker but I've also considered time required to call a method.
For example single period of sine consist of 120 steps, increasing delay by one, would add one NOP operation to each step, meaning that single period would take additional 120 * 1327ns.
The good news is, that LCD display always displays correct frequency in hertz, only pressing Up and Down buttons changes it by few hertz. The bottom line of LCD display shows period time in nano seconds.

Code Optimization

My first version has used Arduino API and I was getting about 150Hz for sine with 120 steps - final optimization went up to 5,6KHz. I've used interrupts to handle input from buttons,  direct registry access, removed all unnecessary method calls, reduced size of a variables  - like from 16 to 8bits and finally exchanged floating points with integers.

There is always a tradeoff - code readability had decreased, direct access to  registers is also tricky, because they can be used for different proposals. Arduino API takes care of all those problems, but it need few extra CPU cycles.

Source Code

is on github

Monday, February 16, 2015

Simple plant monitor based on Arduino

The goal of this project is to build a controller that will inform you when your plant needs watering. You will see the maximal and current moisture level and the time passed since last watering. Finally when moisture level drops below defined threshold the alarm will rise - in form of LED flashing SOS signal.

Hardware

 I will go over some not obvious parts:
  • you will find fritzing project here
  • 4 PIN socket in the left bottom corner is meant to be connected to moisture sensor. It's be cheap one that you can get on ebay
  • photo resistor regulates insensitivity of LCD backlight
  • R1 regulates LCD brightness
  • R5 regulates backlight insensitivity for LCD display
  • R10 sets alarm threshold
  • R3 value is not 220 ohm but 10k (I have to correct schematic)

Software design

I've used Eclipse, source code is here . Whole project is divided into modules, each one in separate class file.

I dis not use interrupts, so every module is controlled from main loop in PlantMonitor.cpp.

Each module has to be initialized and has to execute some operations once in a while. For this propose each module defines two functions: abc_setup() and abc_cycle() - abc would be a module name. The setup method has to be called only once and this happens in PlantMonitor#setup(). The cycle method will be called on every loop. Each cycle method internally measures time and runs only when it should (like every 100ms) without blocking.

The method Util#util_millis() is used by each module to measure time. This method caches current time and returns this cached value - internally it obtains time at the beginning of each cycle.
Using the same time during each cycle not only reduces amount of calls going to the timer, also each log statement within the same loop contains identical time, so we can logically connect events.

Logger

The log module will produce messages over COM port. It cannot be disabled because ... there is no reason to do so -  we do not have any other usage for COM port and sometimes it's useful to see what is happening.
There are a few variable resistors, that can be used to set up things like alarm threshold. Those changes will be also printed over COM port, so that you can actually see how they effct functionality. Here is a log example:

>>[000-00:00:00,000]-> Initializing LCD module >>[000-00:00:00,000]-> Initializing hygrometer module
>>[000-00:00:00,038]-> Adopting LCD backlight. Sensor: 519, LCD: 177, Adjust(def 500): 605 
>>[000-00:00:00,038]-> Adjusted alarm sensitivity. 370 = 36% 
>>[000-00:00:01,000]-> Status -> Free RAM: 1270 
>>[000-00:00:12,000]-> Moisture has changed = 30%, Max: 30%, Status: 3 
>>[000-00:00:12,000]-> Recognised plant watering, status: 3 
>>[000-00:00:18,000]-> Reseting max proc based on adoption time after watering 
>>[000-00:00:18,000]-> Moisture has changed = 24%, Max: 24%, Status: 1 
>>[000-00:00:36,000]-> Reseting max proc based on adoption time after watering 
>>[000-00:00:36,000]-> Moisture has changed = 19%, Max: 19%, Status: 1 
>>[000-00:00:49,013]-> Adopting LCD backlight. Sensor: 559, LCD: 169, Adjust(def 500): 605 
>>[000-00:00:57,000]-> Reseting max proc based on adoption time after watering 
>>[000-00:00:59,169]-> Adopting LCD backlight. Sensor: 679, LCD: 145, Adjust(def 500): 605 
>>[000-00:01:12,139]-> Adopting LCD backlight. Sensor: 639, LCD: 153, Adjust(def 500): 604

Moisture - Hardware


We are using cheap moisture sensor that consists of two plates that you have to stick into a soil. The sensor delivers 0-5 volts based on moisture level. This means that we can directly connect it to Arduino analog input (A0).
Sensor plates corrode quickly, so it's a good idea to cover them with tin. Without this fix your sensor will last at most few weeks, or maybe you can get one that is already resistive to corrosion (one with gold surface). Applying extra tin will unfortunately increase current flow and influence readings, but we will correct this in a software (Hygrometer#MOISTURE_PROC_ADOPT).

Moisture - Software (Hygrometer.cpp)

The method hygro_sample(Moisture) returns current moisture level and status: "no change", "small change" and "level increased".
hygro_sample(Moisture) is being called on every loop, internally it executes only every 100ms, otherwise it returns "no change". With each run (every 100ms) it probes moisture level, but it does not return it immediately, it stores it in internal array and returns "no change". First after collecting 30 probes, it finds the median and returns proper status. It's worth mentioning, that moisture change is being recognized with tolerance of 5% - just to avoid bouncing.

LCD Display - Hardware

LCD display is connected in a standard way, variable resistor R1 can be used to adjust brightness.

The insensitivity of LCD backlight is controlled by photo resistor. Photo resistor is connected to analog input A1 over voltage divider - this is a common pattern used to archive stable readings that are independent from current changes.
Software calculates insensitivity of backlight based on reading from photo resistor - this is a linear function. Additionally variable resistor R5 connected to A2 is used to calculate constant for our function: lower resistance increases measured voltage on A2 and increases backlight. This means, that resistance change proportionally influences backlight for all light conditions (readings of photo resistor).
Resistance changes on R5 are logged over COM, so you can exactly see it's influence on backlight.

The LCD backlight is connected to PWN output on D9, but it does not go directly to LCD input, instead it goes trough RC filter and op-amp. The RC filter uses large capacitor, this gives us smooth changes of backlight insensitivity.
Actually we would not need this whole RC filter, because PWM frequency it to high to observe flickering and smoothness could be archived in software, but I just wanted to solve this in hardware and have nice constant voltage.

You can see on screenshots below dependency between PWM duty (blue) and constant voltage (yellow).




The time required  to change LCD backlight from maximal brightness to minimal takes 440 ms.

LCD Display - Software (Lcd.cpp)

The initialization phase (lcd_stup()) prints all static characters - those will not change any more,  all other characters will be printed only if they have changed - this reduces flickering.
The lcd_cycle() will get called on every loop from PlantMonitor#loop() and it adopts LCD backlight for changing enviorment.
Lcd.cpp contains also one function that prints time and another function that prints moisture - that's all.

SOS LED

It's just a LED connected to A10. The Sos.cpp controls the blinking frequency. The sos_setup() is called only once and it configures PIN A10 for output. sos_cycle() has to be called on every loop - it's non blocking method and it will switch the LED on and off in SOS rhythm. You can configure all possible time periods by changing corresponding variables in Sos.h. There are two more methods, that play significant role: sos_on() will enable SOS signal and respectively sos_off() will disable it. The cycle method have to be called on every cycle, even when SOS is disabled - in this case it returns immediately.

Low moisture level alarm

When moisture gets below defined level the LED controlled by Sos.cpp will start flushing. This is being controlled by Alarm.cpp. This module knows when the alarm has to be raised and when it stops, it also controls kind of alarm - currently it's a LED signal, but you could connect something else. In this case you have to only plug it on the right place in Alarm.cpp.
Alarm starts when moisture drops below level defined by variable resistor connected to PIN 3. The resistance will be mapped to percentage and changes to this resistor are printed to COM port - when you change the resistance you can read the threshold for alarm.

Getting up and running

Download latest release from here and compile cpp files, PlantMonitor.cpp contains main loop. I've used Eclipse, but importing this project makes no sense, because it contain hardcoded paths.

Optionally you can directly upload compiled hex file:

sudo avrdude -patmega328p -carduino -P/dev/ttyUSB0 -b57600 -Uflash:w:PlantMonitor.hex:a