Translate

Monday, 20 May 2019

How to make a supersize thermometer with a strip RGB led and Arduino.


The idea came to me by experimenting with intelligent RGB LED strips, that is equipped with a control microchip, like the NEOPIXEL based on a WS2812B chip. The interesting thing is that just one bit is enough to control numerous LEDs. The number of pixels is not limited by the signal level, always retransmitted by each chip, but by the transmission speed.
On the web there are several libraries, I have tested both the FastLED-master and the Adafruit_NeoPixel, both can drive various smart led and controller chips. To measure the temperature I tried three types of sensors but in the final version I used an LM35.
Of course this linear display is suitable for displaying other physical quantities and the number of LEDs can easily be increased.

The WS2812B LEDs

They can be mounted on a flexible and adhesive strip of printed circuit board, the appearance of which is shown in Figure 1. The density is 60 LEDs per meter.
Figure 1 - Typical appearance of a WS2812 strip.
The chips, powered at + 5V with C = 100nF by-pass capacitor, are connected in cascade according to the schematic shown in figure 2.
Figure 2 – Strip led wiring diagram.
The serial communication protocol is NRZ (Non Return to Zero) and the data is 24 bits, eight bits per color in the order GRB (Green, Red, Blue) with the most significant bit first. In this way up to 2^24 = 16777216 colors are realized. The bit rate can vary from 400 to 800 [kbits/s]. Figure 3 shows the diagrams of the packets transmitted and how they are interpreted by the individual chips.
Figure 3 - Data transmission protocol.
The first packet, sent by the MPU, is acquired by the first D1 chip that retransmits the second and subsequent ones. The second chip D2 acquires the second packet and retransmits the next ones, and so on. At the end of the cycle, a pause of at least 50 µs resets the system.
Each LED consumes up to 20 mA, so 60 mA maximum per chip. If I wanted to represent a light bar with 30 leds of white light, that is with the three colors on, I would have a consumption of 1.8 A with considerable dissipation and variation of current. In order to drastically reduce the current I prefer to turn on only one led at a time using one of primary colors RGB and also change its brightness.

My LM35 sensor prototype
It is an analog sensor, from National Semiconductors / Texas Instruments, with output proportional to degrees Celsius and a slope of 10 mV/°C, so the output signal is relatively low.
Its measurement range is from 2 to 150°C with a power supply from 4 to 20 volts and, with particular circuits, it can measure up to -55°C, figure 4 shows the sensor and pin layout.
If the Arduino ADC converter has Vref=5V (default), the resolution is about 5mV, or 0.5°C. But the supply voltage is not stable and varies significantly if Arduino is powered via USB or by Vin. If I program analogReference (INTERNAL) the resolution becomes almost 1 mV because the internal reference of the ADC is about 1.1V (from 1 to 1.2V) and is also much more stable than the default mode. I used this reference mode even if the display resolution is one degree.
To measure the temperature of a house it seems to me an economic sensor and easy to manage. The scale of my display has 30 RGB LEDs starting from 5 °C up to 34 °C. The schematic is that of figure 5.
Figure 5 - Electrical diagram of the thermometer.
As a precaution, I powered the LED strip with a separate regulator as, in the event of a fault more LEDs could be lit simultaneously, the small controller on Arduino Nano could overheat and even burn.
The average consumption of my prototype is around 50 mA, this also because I only turn on
 one led at a time and also reduced its brightness. The power supply is similar to that used for Arduino, preferably use voltages between 7.5 and 9 volts, even not stabilized.

I used an Arduino Nano because it is compact and with a 0.1" pitch, necessary for prototyping matrix board. The sensor is connected to pin A0 and I have connected a potentiometer on pin A1 to calibrate the thermometer without modifying the program. For the prototype I used a matrix board, as shown in figure 6. Notice at the top the sensor that comes out of the box.
Figure 6 - Aspect of the components of the prototype.
List of electronic components
component
description
component
description
R1
47 kW ±1% metal film
LD
RGB strip LED type WS2812B
R2
220 W ±5%
Arduino
Arduino Nano board
Rp1
5 kW trimmer
U1
LM35 sensor
C1
100 µF,25V electrolytic capacitor
U2
LM7805, 5V regulator
C2,C3,C4
100 nF ceramic capacitor

dissipator

Prototype construction
The simplest constructive solution is to buy the led strip aluminium profile complete with its diffuser and to transfer on it the thermometric scale numbers.
I used a "U" aluminum profile of about 15x10 mm, 600 mm long for a total of 30 LEDs with a scale from 5 °C to 34 °C. The strip has a density of 60 LEDs per meter, so the pitch is about 16.7 mm.
At the end of the bar I fixed a small box containing the electronics, while beside the led bar I glued an "L" shaped profile of about 10x20 mm on which I drew the thermometric scale with a normographe, then I painted it with a transparent varnish protection. This solution, quite "do it yourself" is visible in the introductory photo. The writing can also be done with a label printer or with self-adhesive numbers. The version with the special profile and relative diffuser is also aesthetically the most valid solution.

The program
For this program I used the Adafruit_NeoPixel library (https://github.com/adafruit/Adafruit_NeoPixel) for the LED strip, because it is simpler and more compact.
Nothing prevents you from using multiple LEDs to enlarge the scale, updating the program on the #define NUM_LEDS 30 line and also modifying the control of the LEDs. With some modifications we can use a scale in Fahrenheit and with hardware and software interventions we can extend the scale even to negative values.
The display resolution is one degree, so I convert the temperature that is of float type to integer and address with this the led to turn on.
I have used mostly only one of these leds and with moderate intensity, for example:
pixels.Color (0, 0, 70);
it turns on one of the RGB colors (the blue LED, in this case), with an intensity of only 70/255. This greatly reduces consumption and does not disturb the eyes too much. Only at the ends of the scale, to better highlight the exceeding of the limits, I use violet (red + blue) and yellow (red + green) colors that require two LEDs, but I turn them on only for one second every five. Reducing consumption also avoids heating the sensor and distorting the measurement.
The led chip[0] is the one at the bottom: it is lit blue for a temperature of 5 °C or flashes violet for lower temperatures. The led chip[29] is the highest one and lights up red for a temperature of 34 °C or flashes yellow for higher temperatures.
My program turns on the LED chips based on the following criteria:
·         Chip led[0] a violet flash of 1 second for T < 5°;
·         Chip led[0÷17] blue led on for 5° ≤ T <18°;
·         Chip led[18÷14] green led on for 18°T≤ 24°;
·         Chip led[25÷29] red led on for 25°T≤ 34°;
·         Chip led[29] e yellow flash of 1 second for T > 34°C.
Of course it is very easy to change the limits to better adapt them to personal wellbeing thresholds.
The function pixels.Color() sets the colors with three bytes corresponding to the primary colors, respectively: R (Red) G (Green) B (Blue). The intensity of the colors varies from 0,0,0 corresponding to the LEDs that are all off (black) up to 255,255,255 for the three LEDs that are on (white). Varying the intensity of the individual LEDs the displayed color varies. There are on-line computers that simulate the color generated.
As already seen, the program reads the potentiometer to make a correction of about -3° up to + 6°.
At the beginning I put VREF = 1050 mV, this value is obtained by measuring the voltage with a digital voltmeter, otherwise put VREF = 1100.

The code
/* program ArduTempLedLM.ino Arduino strip led thermometer
 use a LM35 or TMP35 as temperature sensor
 Giovanni Carrera, rev. 11/05/2019 */
#include <Adafruit_NeoPixel.h>

int Temp;
float NtomV;
const float VREF = 1050;// in mV, this value can be read on VREF pin

#define PIN  2 // used for strip led data
#define NUMPIXELS 30  // number of leds in strip (5° to 34°C)
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
#define DELAYVAL 500 // Time (in milliseconds) to pause between pixels

void setup() {
  Serial.begin(9600);
  pixels.begin();// INITIALIZE NeoPixel strip object delay( 500 );
  analogReference(INTERNAL); // internal ADC reference input = 1100V
  NtomV = VREF/1023;// constant of conversion into millivolts
}

void loop() {
  int val = analogRead(A0);// read the LM35 sensor
  float temperature = NtomV*val/10.0;// convert to Celsius
  val = analogRead(A1);// read the potentiometer for T correction
  float corr = NtomV*val/10.0 - 3;// adjusting factor, about -3 to +6
  temperature -= corr;// Temperature correction
  Serial.print("  Temperature = ");
  Serial.print(temperature,1);
  Serial.println(" °C");
  Temp = (int)temperature;
  if (Temp < 5){
    pixels.setPixelColor(0, pixels.Color(100, 0, 150));// bright violet color
    pixels.show();
    delay(1000);
    pixels.setPixelColor(0, pixels.Color(0, 0, 0));
    pixels.show();
    }
  else if (Temp < 18){// blue led for T<18°
    pixels.clear(); // Set all pixel colors to 'off'
    // pixels.Color() takes RGB values, from 0,0,0 up to 255,255,255
    pixels.setPixelColor(Temp-5, pixels.Color(0, 0, 70));// moderately bright blue color
    pixels.show();
    }
  else if (Temp >= 18 && Temp <= 24){
    pixels.clear(); // Set all pixel colors to 'off'
    pixels.setPixelColor(Temp-5, pixels.Color(0, 70, 0));// moderately bright green color
    pixels.show();
    }
  else if (Temp > 24 && Temp <= 34){
    pixels.clear(); // Set all pixel colors to 'off'
    pixels.setPixelColor(Temp-5, pixels.Color(70, 0, 0));// moderately bright red color
    pixels.show();
  }
  else {
    pixels.setPixelColor(29, pixels.Color(150, 50, 0));// bright yellow color
    pixels.show();
    delay(1000);
    pixels.setPixelColor(29, pixels.Color(0, 0, 0));
    pixels.show();
  }
  delay(5000);

}

References
1)      “WS2812B-V4, Intelligent control LED integrated light source”, http://www.world-semi.com/
2)      “FastLED Basic usage”, Daniel Garcia, https://github.com/FastLED/FastLED/wiki/Basic-usage,16 Aug 2017.
3)      “LM35 Precision Centigrade Temperature Sensors”, Texas Instruments, SNIS159H, August 1999–revised December 2017.

Saturday, 10 November 2018

Very few wires for a numeric keypad for Arduino


Very few wires for a numeric keypad for Arduino

Giovanni Carrera, 23/11/2018
The techniques for reading numerical keyboards with a reduced number of Arduino pins as analog inputs are described.

Introduction

The aim of this study is to drastically reduce the number of pins required by a numeric keypad. This is because we often need many I/O pins compared to those available on the Arduino Uno or Nano boards.
With the help of some resistor you can turn the keypad to use only a few analog inputs, saving several digital pins. In some cases, as we will see, just one analogue input will suffice. Programs to manage them are also described.

Matrix keyboards

The ordinary numerical keypads are structured in matrix because they require less wires and are organized in rows by columns. Usually the lines are configured as input and the columns as output. The program scans the array to see if a key has been pressed. A 4x3 keypad, like the membrane one shown in the figure, requires 7 Arduino digital pins. Numerous libraries are available on the net to use it with Arduino.


There are also several keyboard applications with one analogue output, but in my opinion they are very unreliable for this type of keyboards. To simulate them I created a template and a spreadsheet program to try out the functionality. The following figure shows the scheme and the equivalent model of a 4x3 keyboard with an analogue output.
The biggest problem is that the range of variation is very limited and, consequently, also the intervals corresponding to each key. It is also difficult to distinguish the keys because the intervals may overlap in some cases. Having written the analysis program, I verified that the values they had chosen in some projects found on the network had these drawbacks, with minimum intervals of only 10mV.

My solution
The solution I propose is much better even if it uses three analog inputs, with a circuit like the one shown in the following figure.
In this case you save only 4 pins against six of the previous version but it's worth it because the intervals are now only four, in addition to the rest position that is the zero volts (no key pressed). In the equivalent scheme (b) the R1e indicates one of the four resistors R1-R4, while R2e is one of the three resistors R5-R7.
Now let's see how to make the four widest possible intervals. First of all the resistors R2e = R5 = R6 = R7 can be made equal, then we can set R1 = 0 to have the outputs corresponding to the first line at 5V. With the values shown in the table:
R1 =
0 W
R2 =
330 W
R3 =
1  kW
R4 =
3  kW
R5,R6,R7 =
1  kW
Vcc =
5.000 
The following intervals are obtained for each output:
Key
Vx [V]
DV [V]
NADC
DN
NADC-100
NADC+100
[1]-[2]-[3]
5.00
0.000
1023
0
923
1023
[4]-[5]-[6]
3.76
1.241
769
254
669
869
[7]-[8]-[9]
2.50
1.259
512
257
412
612
[*]-[0]-[#]
1.25
1.250
256
256
156
356
As you can see, the intervals on the three outputs are the largest possible and you can use six standard resistors with a tolerance of ±5%. With an extra analogue input and another resistor, a 4x4 keyboard can be used. The following figure shows the connections with Arduino.

In the diagram the resistor R1 is connected to line 2 because the one on line 1 has drawn it with a wire, so the resistor references have been scaled by one. Pin assignments can be modified according to needs, as long as they are pin configurable as analog.
Of course, if Arduino were powered at 3.3V, nothing would change because the ADC converter as default uses the supply voltage and the ADC numbers don’t change.
To test the test program, not having a keyboard of this type, I built it with recycled keys, the figure below shows my prototype. The 5-pin right connector is used to connect it to the Arduino.

The Analog4x3Keyb.ino program
I reported the lower (NADCm100) and higher (NADCp100) limits on two numeric arrays and one character array for the 12 keys, arranged in columns. As you can see, in my keypad I used two different keys because they were more useful for my applications than the standard '*' and '#' keys that are more suitable for telephone applications.
To scan the three analogue channels, I initially used an index loop, but it didn’t work, so I used the KeyScan function. Double conversion is necessary to avoid the bounce of the buttons. For applications it is better to turn everything into a function.
I consider a button pressed if the value of one of the three outputs is greater than zero, for safety I put the value 40. The program first verifies on which column the key was pressed, then identifies the key itself.

/* program Analog4x3Keyb
 *  test for 4x3 keys keyboard with 3 analog outs
 *  G. Carrera - 2/11/2018
 */

int ledPin = 13; // internal LED
// limits of keyboard output values:
const int NADCm100[4] = {923,669,412,156};
const int NADCp100[4] = {1023,869,612,356};
const char key[13] = {'1','4','7','C','2','5','8','0','3','6','9','E'};
int keyval[3];
int i,colp,val;


void setup(){
 pinMode(ledPin, OUTPUT);
 Serial.begin(9600); // used with serial monitor
 digitalWrite(ledPin, LOW);// led off
}

void loop() {
  KeyScan();// read analog keyboard
  if (keyval[0]+keyval[1]+keyval[2] > 40){ // a key was pressed
  delay(10);// antibounce
  KeyScan();// reread analog keyboard
  for (i=0; i < 3; i++){//identify which column it belongs to
    if (keyval[i] > 40){
      colp= i;
      val= keyval[i];// this is the corresponding value
      for (int j=0; j < 4; j++){// identify which key was pressed on the column
        if (val >= NADCm100[j] && keyval <= NADCp100[j]){
          digitalWrite(ledPin, HIGH);// led on
          Serial.print("col= "); Serial.print(colp);
          Serial.print(", val= "); Serial.print(val);
          Serial.print(", key= "); Serial.println(key[colp*4+j]);
          delay(500);
          digitalWrite(ledPin, LOW);// led off
          break;
        }
      }
    }
  }
 }
}

void KeyScan(){// read analog keyboard
  keyval[0]= analogRead(A0);
  delay(1);
  keyval[1]= analogRead(A1);
  delay(1);
  keyval[2]= analogRead(A2);
  delay(1); 
}
Keyboards with single common
I had a strange little keyboard organized as 12x1. After thinking a little bit about it, the simplest solution was to make a voltage divider and read the output voltage with an analog input.
I realized the circuit illustrated in the following figure, as you can see, only three wires connect it with Arduino. Naturally, this system can be easily adapted to even fewer keys, I do not recommend using too many keys because the intervals become too small. The keyboards organized in matrix, for example 3x4 or 4x4 have great difficulty for the analogue output, as already seen.

How to calculate the voltage ranges
To calculate the limits of the values used to identify the keys, I wrote a program in Excel.
The following figure shows the equivalent circuits of the divider (a) and the particular case of the keyboard (b).

In the case of the divider of figure (a), the output voltage Vout is:
Vout = Vin* R2p/(R1p+R2p)

If in our resistive network we press the key [2], we can refer to the previous divider formula if we put: R1p = 2*R
R2p = 10*R*Rz/(10*R+Rz)

In fact, the series of resistors of the low branch is in parallel with Rz. The resistor Rz (R13 in the diagram) serves to bring the input voltage to zero, ie it acts as an analog pull-down.
Using these formulas for each key, appropriately adapted, the following table is obtained.

Key
Vo [V]
DV [V]
R2p [kW]
R1p [kW]
NADC
DN
NADC-20
NADC+20
[0]
5.00
0.000
4.98
0
1023
0
1003
1023
[1]
4.54
0.458
4.60
0.464
929
94
909
949
[2]
4.10
0.443
4.22
0.928
839
90
819
859
[3]
3.67
0.431
3.84
1.392
751
88
731
771
[4]
3.25
0.421
3.44
1.856
665
86
645
685
[5]
2.84
0.413
3.04
2.32
580
85
560
600
[6]
2.43
0.407
2.63
2.784
497
83
477
517
[7]
2.03
0.403
2.21
3.248
414
83
394
434
[8]
1.62
0.401
1.79
3.712
332
82
312
352
[9]
1.22
0.401
1.35
4.176
250
82
230
270
[*]
0.82
0.403
0.91
4.64
168
82
148
188
[#]
0.41
0.407
0.46
5.104
84
84
64
104
The NADC number is what can be read from the Arduino ADC converter (10 bit). The numerical interval DN between one key and the other goes from 84 to 94, even though all the resistors are equal, in my case R = 464 W, these intervals are slightly different towards the high values because of R13. For safety I chose the limits within ± 20, obtaining the NADC-20 and NADC + 20 values.
The experimental measurements on the prototype gave values very close to those calculated. The following figure shows my prototype detached from the keyboard for visibility reasons. It connects to the keyboard with a 13-pin female strip and a second 3-pin connector connects it to the Arduino.

Components list
·        1 x keypad 12x1 keys or 12 single keys connected to a common
·        12 x resistors R1..R12 = 470 W ±1%, in my case I used 464 W.
·        1 x resistor  R13= 47 kW ±5%.
·        1 x 100 nF capacitor.
·        strip connectors

The Analog12x1Keyb.ino program
The program required some attention as regards the bounce phenomena on the keys. The pressed key leads to values higher than zero + possible noise, even here I have placed 40 as a limit. In the program I created two numeric arrays with the calculated limits and a third of ascii characters to assign the key. I only wrote a system test program and tested it on an Arduino Uno.

/* program Analog12x1Keyb
 *  test for analog 12x1 keys keyboard
 *  G. Carrera - 27/10/2018
 */
int keybPin = A0;// keyboard analog input
int ledPin = 13; // internal LED
// limits of keyboard output values:
const int NADCm20[12] = {1003,909,819,731,645,560,477,394,312,230,148,64};
const int NADCp20[12] = {1023,949,859,771,685,600,517,434,352,270,188,104};
const char key[13] = {'0','1','2','3','4','5','6','7','8','9','*','#'};

void setup(){
 pinMode(ledPin, OUTPUT);
 Serial.begin(9600); // used with serial monitor
 digitalWrite(ledPin, LOW);// led off
}

void loop() {
  int val = analogRead(keybPin);// read analog keyboard
  if (val > 40){ // a key was pressed
    delay(10);// antibounce
    val = analogRead(keybPin);// re-read
    for (int i=0; i < 12; i++){
      if (val >= NADCm20[i] && val <= NADCp20[i]){// the key[i] was pressed
        digitalWrite(ledPin, HIGH);// led on
        Serial.print(val);
        Serial.print(" , ");
        Serial.println(key[i]);
        delay(500);
        digitalWrite(ledPin, LOW);// led off
        break;
      }
    }  
  }
}

With a few modifications you can create a function that, if called, returns the character and if no key has been pressed returns a thirteenth character to indicate this state, for example 'N'.
This solution is particularly suitable in cases where you want to make a small custom keyboard, so we can organize it as we see fit. Another useful application is for 8-pin microcontrollers like ATtiny that have very few I/O pins.

Keyboards with a few keys
With a few buttons, such as 4x1, you can also use a different configuration, as shown in the following figure. The table is the result of optimization with Excel with the standard values: R1 = 470W, R2 = 1 kW, R3 = 3 kW and R4 = 1.5 kW.

With this circuit a resistor is saved, but the values of the upper branch of the divider are not all the same as in the circuit discussed above. To have equal intervals must be:
R3 = 2 * R4, R2 = R3 / 3 and R1 = R2 / 2
But it is difficult to use all standard values. The ranges are wide and the resistors can have a tolerance of ±5%. The standard 3 kW resistor can be obtained with two 1.5 kW resistors in series or a 3.3 kW resistor in parallel with a 33 kW one.

The Analog4x1Keyb.ino program
/* program Analog4x1Keyb
 *  test for 4x1 keys keyboard with 3 analog outs
 *  G. Carrera - 8/11/2018
 */

int ledPin = 13; // internal LED
int keybPin = A0;// keyboard analog input
// limits of keyboard output values:
const int NADC_L[4] = {907,646,393,118};
const int NADC_H[4] = {1023,897,636,383};
const char key[5] = {'1','2','3','4'};
int i,keyval;


void setup(){
 pinMode(ledPin, OUTPUT);
 Serial.begin(9600); // used with serial monitor
 digitalWrite(ledPin, LOW);// led off
}

void loop() {
  if (analogRead(keybPin) > 40){ // a key was pressed
    delay(10);// antibounce
    keyval= analogRead(keybPin);// reread analog keyboard
    if (keyval > 40){
      for (int i=0; i < 4; i++){// identify which key was pressed on the column
        if (keyval >= NADC_L[i] && keyval <= NADC_H[i]){
          digitalWrite(ledPin, HIGH);// led on
          Serial.print("val= "); Serial.print(keyval);
          Serial.print(", key= "); Serial.println(key[i]);
          delay(500);
          digitalWrite(ledPin, LOW);// led off
          break;
        }
      }
    }
  }
}