Thermal Sensors

A while back, I had a cooking timer/thermometer that I frequently used around the kitchen. It had a metallic pointed probe, approximately 15 cm (6 in) in length, that could be stuck in a roast or poultry. The probe was attached to a long insulated wire ending in a jack that plugged into the timer/thermometer allowing the temperature to be measured outside the oven and for an alarm to go off when a set temperature was reached. The device eventually stopped working and the probe stayed in the back of one of my kitchen drawers. Going through the drawer the other day, I found the probe and wondered if I could use it without the long gone device. I decided to study it to figure out what type of thermal sensor it was and how I could use it to measure temperature.

In this post, I will explain how I determined the type of thermal sensor that was used to manufacture my cooking timer/thermometer probe. I will then describe how to use the sensor and compute temperature from it. Finally, I will build a small circuit and Arduino program to measure temperatures using the probe and report it back to the computer connected to the Arduino. The featured image at the beginning of this blog post was slightly modified from an original published by Ulf Šustek Seifert, CC BY-SA 3.0 , via Wikimedia Commons.

Thermal Sensor Characteristics

There are several types of electronic temperature sensors. The four major types are thermocouples, Resistance Temperature Detectors (RTD), thermistors and other semiconductor-based sensors. Unlike the first three major types that describe classes of devices, each semiconductor-based sensor device has its own characteristics and modes of operation. They range from simple silicon bulk resistance sensors to complex hybrid analog/digital integrated circuits. In post Temperature and Humidity Sensor I used the complex hybrid analog/digital sensor DHT22 to measure temperature and humidity. Because my probe is more than likely one of the first three types of thermal sensors, this post will concentrate on them. Their characteristics will be described in the next sections in order to determine the temperature probe’s sensor type.

Thermocouples

A thermocouple is an electrical device consisting of two dissimilar electrical conductors joined at one end, producing a small voltage, V, between the two conductors when the temperature at the junction of both conductors, Tj, is different from the temperature at the other end of the conductors, Tref, as shown in the diagram below. The conversion of temperature differences into a voltage is called the thermoelectric effect. Thermocouples are self sufficient in that they do not need external circuitry to operate and the measured voltage between the two conductors can be used to compute the temperature difference between the junction and the other ends of the conductors.

Thermocouples are an inexpensive way to measure temperature. They are durable, sturdy and can withstand harsh environments. However, since they measure a temperature difference, a reference temperature is required to obtain an absolute temperature measurement. Moreover, precisions below 1 °C (1.8 °F) are difficult to achieve for temperature differentials below 130 °C (234 °F) and precisions below 0.75% for temperature differentials above 130 °C (234 °F). Hence, thermocouples are best used for applications that do not require great precision and for measurements of high temperature differentials such as gas furnaces, diesel engines and ultra low freezers. The resistance across a thermocouple when the junction temperature is identical to the surrounding temperature is typically less than 20 Ω. The following symbols are used to depict thermocouples in diagrams.

Resistance Temperature Detectors

Resistance temperature detectors (RTD) are electronic devices that rely on the change of resistivity of pure metals at different temperatures. As depicted below, these devices consist of long thin metal wires wound or affixed to an insulated substrate, such as glass or ceramic. The metal used is typically copper, nickel, or platinum, with a preference for platinum which has the most stable resistance-temperature relationship over the the widest temperature range. The resistance of metals increases with temperature. Other materials, such as carbon, are also used for ultra-low temperatures. In a typical application, RTDs are used with other reference resistors and a reference voltage to convert the resistance of the device to a voltage that can be measured.

RTDs are very accurate, typically ±0.03 °C (±0.054 °F) down to ±0.001 °C (±0.0018 °F) for ultra high precision devices. They are stable and suitable for high precision applications, but they are more expensive, especially the platinum based devices, and have a limited temperature range, typically −200 °C (−328 °F) to 500 °C (932 °F) for industrial applications. Furthermore, RTDs have slower response times and are less sensitive to small temperature changes than other temperature sensors. RTDs are best used for applications that require high precision temperature measurements at or below 500 °C (932 °F), such as in laboratories. Typically, RTDs come in two varieties: 100 Ω and 1000 Ω, their base resistance at 0 °C (32 °F). The following symbols are used to depict RTDs in diagrams.

Thermistors

Thermistors are passive electronic devices typically made of ceramic in the form of compressed metal oxide or polymer shaped into disks or rods whose ends are attached to metal leads. The thermistor material is encased in a heat conducting but electrically insulating material. The electrical resistance of the material used within thermistors is highly dependent on temperature. There are two types of thermistor: devices whose resistance increases with temperature thus having a Positive Temperature Coefficient (PTC) and devices whose resistance decreases with temperature thus having a Negative Temperature Coefficient (NTC). PTC thermistors are mostly used in circuits to protect them against overcurrent conditions. NTC thermistors are mostly used for temperature sensing applications. As for RTDs, thermistors are used with other reference resistors and a reference voltage to convert the resistance of the device to a voltage that can be measured. The diagram below depicts the construction of a metal-oxide thermistor shaped as a disk.

Since we are interested in temperature sensing, we will discuss NTC thermistors. NTC thermistors have varying accuracies across temperature ranges. Metal-oxide thermistors are used for temperature ranging from −70 °C (−94 °F) to 400 °C (752 °F) while single crystal semiconductor thermistors are designed to be be used in ultra low temperatures, from 0.01 °K (−273.14 °C, −459.652 °F) up to 100 °K (−173.15 °C, −279.67 °F). Metal-oxide NTC thermistors can achieve fairly accurate temperature measurements over relatively small temperature ranges, typically ±0.1 °C (±0.18 °F) over a −40 °C (−40 °F) to 125 °C (257 °F) range. These thermistors are available in many resistance values ranging from 1 Ω to 5 MΩ, their resistance value at 25 °C (77 °F). NTC thermistors have good response times and are sensitive to small temperature changes. The following symbols are used to depict NTC thermistors in diagrams.

Determining Sensor Type

In order to figure out the type of temperature sensor that was used in the cooking timer/thermometer probe, I used a digital multimeter to measure the resistance across the leads at different temperatures and a cooking thermometer to provide a temperature reading. The very first measurement I made was at room temperature. The temperature was 23.7 °C (74.66 °F) and the measured resistance was 244.0 kΩ. From this first measurement we can readily say that the temperature sensor is neither a thermocouple nor a resistance temperature detector as the measured resistance is orders of magnitude higher than it would be for these sensor types.

I continued the experiment using a sous-vide precision cooker, a pot of water and a cooking thermometer. I installed the sous-vide precision cooker heating element, the cooking thermometer probe and the temperature sensor probe in the pot of water. I set the precision cooker to heat the water to 35 °C and took a first measurement when the temperature had been reached for at least 5 minutes. As the temperature was slightly changing over time, I measured the minimum and maximum temperatures and the minimum and maximum resistance values over 5 minutes and I jotted them down. I repeated the exercise, setting the precision cooker temperature and taking measurements for every temperature multiples of 5 °C (9 °F) until the precision cooker temperature reached 80 °C (176 °F). I computed the average temperature and the average resistance for each set temperature and wrote them down. The following table lists all temperature and resistance average values I obtained.

Temperature (°C / °F)Resistance (Ω)
35.0 / 95.0148 100
40.0 / 104.0119 400
45.0 / 113.097 050
50.0 / 122.079 300
55.0 / 131.064 950
60.0 / 140.053 435
65.0 / 149.044 280
70.0 / 158.036 765
75.0 / 167.030 670
80.0 / 176.025 370

As can be seen in the table, the resistance values decreased as the temperature increased. In other terms, the sensor being tested has a negative temperature coefficient. From this information, we can determine that the sensor within the temperature probe is an NTC thermistor. The following graph shows the temperature as a function of the thermal sensor probe’s measured resistance. As can be seen, it is not linear.

Resistance and Temperature Relationship

The relationship between the resistance of a semiconductor, such as a metal oxide, and temperature is well known. In 1968, John S. Steinhart and Stanley R. Hart proposed an equation that modelled very well the relationship between the resistance and temperature of thermistors. It is called the Steinhart-Hart equation and goes as follows:

1/T = A + B ln R + C (ln R)3

Where T is the temperature in degrees Kelvin, R is the resistance across the thermistor leads and A, B, and C are the Steinhart-Hart coefficients specific to the thermistor being used. In order to convert resistance to temperature, we must determine the values of coefficients A, B, and C. We need three temperature-resistance value pairs, that is the thermistor resistance value at three different temperatures to form a system of linear equations with three unknowns, A, B, and C. Let’s find the coefficients using the thermistor resistance values measured when the precision cooker temperature was set to 40 °C, 60 °C, and 80 °C respectively. Since 0 °C is equal to 273.15 degrees Kelvin, T1 = 313.15 °K, T2 = 333.15 °K, and T3 = 353.15 °K. The corresponding thermistor resistance values are R1 = 119 400 Ω, R2 = 53 435 Ω, and R3 = 25 370 Ω. Rewriting the Steinhart-Hart equation using the three resistance and temperature value pairs, we get the following system of linear equations.

A + B ln 119 400 Ω + C (ln 119 400 Ω)3 = 1/313.15 °K−1
A + B ln 53 435 Ω + C (ln 53 435 Ω)3 = 1/333.15 °K−1
A + B ln 25 370 Ω + C (ln 25 370 Ω)3 = 1/353.15 °K−1

or,

1 A + 11.690234 B + 1 597.6059 C = 3.1933578×10−3 °K−1
1 A + 10.886221 B + 1 290.1240 C = 3.0016509×10−3 °K−1
1 A + 10.141322 B + 1 042.9987 C = 2.8316579×10−3 °K−1

Solving the system of linear equations we get:

A = 7.3927571×10−4 °K−1
B = 1.9407191×10−4 °K−1
C = 1.1600851×10−7 °K−1

Substituting these values for the coefficients in the Steinhart-Hart equation, we get the following formula to get the temperature, T, in degrees Celsius, from the value of the resistance, R, of my temperature probe.

T = 1 / (7.3927571×10−4 + 1.9407191×10−4 ln R + 1.1600851×10−7 (ln R)3) − 273.15 °C

In order to check the precision of the equation, I used previously measured temperature sensor probe resistance values to compute the corresponding temperature. The following table shows the measured temperatures and resistance values, the computed temperature as well as the difference between the measured and computed temperatures, Delta T.

Measured T (°C)Measured R (Ω)Computed T (°C) Delta T (°C)
35.0148 10034.9597 −0.0403
40.0119 40040.00000.0000
45.097 05044.9716 −0.0284
50.079 30049.9367 −0.0633
55.064 95054.9643 −0.0357
60.053 43560.00000.0000
65.044 28064.9665 −0.0335
70.036 76569.9991 −0.0009
75.030 67075.02050.0252
80.025 37080.00000.0000

All computed values are within 0.1 °C of the measured values which is the precision of the cooking thermometer that I used. I further tested the precision of the equation using the very first temperature sensor resistance value, 244.0 kΩ, measured at room temperature, 23.7 °C (74.66 °F). Computing the temperature using the Steinhart-Hart equation, we get 23.7470 °C, a difference of 0.0470 °C, again, well within 0.1 °C of the measured value.

Converting Resistance to Voltage

As we did in post “A Light Activated Switch“, we will use a voltage divider as a mean to convert the thermistor’s electrical resistance into a voltage. The following circuit diagram depicts the voltage divider circuit and its connection to the Arduino micro-controller.

where the fixed resistor and the thermistor, RVD and RNTC respectively, form a voltage divider that divides the reference voltage, VRef, according to the formula

VRNTC = VRef RNTC / (RVD + RNTC)

We need to find a value for RVD that maximizes the voltage across RNTC for the temperatures we intend to measure with the thermistor. For this project, I intend to use the probe to eventually build a cooking thermometer and thus measure temperatures between 40 °C and 80 °C (104 °F and 176 °F). The voltage range across the thermistor between the chosen temperatures, ΔVRNTC, determined by the voltage difference when thermistor values are between Rlow and Rhigh, the thermistor resistance values at the lowest and highest temperatures. Using the formula above, the voltage range is

ΔVRNTC = VRef Rlow / (RVD + Rlow) − VRef Rhigh / (RVD + Rhigh)

If we plot the voltage range, ΔVRNTC, provided by the voltage divider for thermistor values between 40 °C and 80 °C (104 °F and 176 °F) as a function of RVD, we obtain the following graph.

In the graph, we see that there is a value for RVD that maximizes the voltage range for thermistor values between 40 °C and 80 °C (104 °F and 176 °F), thus maximizing the analog to digital conversion accuracy of the Arduino Uno. To find this value, we compute the first derivative with respect to RVD and find the value of RVD for which the derivative is zero giving us the RVD value for which the slope of the curve in the graph above is zero, corresponding to the curve’s maximum point. The first derivative with respect to RVD for the voltage range equation is

ΔVRNTC‘ = −VRef Rlow / (RVD + Rlow)2 + VRef Rhigh / (RVD + Rhigh)2

Finding the value of RVD for which this equation is zero, we get the value of RVD that maximizes the voltage divider voltage range ΔVRNTC.

The value of RVD determined using the formula above works when both Rlow and Rhigh are greater than 0. Since the thermistor resistance at 40 °C, Rlow, is 119 400 Ω and the thermistor resistance at 80 °C, Rhigh, is 25 370 Ω, we get

The closest Resistor value is 56 kΩ. The circuit now looks like the following

Breadboarding

The following picture depicts how to connect the different parts using a solderless breadboard, jumper wires, a thermistor, a 56 KΩ resistors, and an Arduino Uno micro-controller. In the schematic, I used a thermistor instead of the probe. Of course, wires connected to the sensor can be used in lieu of the thermistor depicted.

The Program

Here is the program that reads the thermistor’s voltage at the voltage divider, converts it to a resistance, then using the Steinhart-Hart equation, converts the resistance across the sensor to a temperature in degrees Celsius. The resistance across the sensor and the computed temperature are then returned to the connected PC.

/*
  Thermistor demonstration
  Program that computes the resistance across a thermistor and converts the
  value to a temperature. The temperature is then sent back to the connected
  PC through the serial interface. The blog post associated with this program
  can be found at https://lagacemichel.com
  MIT License
  Copyright (c) 2021, Michel Lagace
*/

// Steinhart-Hart equation parameters
const float zeroKelvin = -273.15;
const float coefA = 7.3927571E-04;
const float coefB = 1.9407191E-04;
const float coefC = 1.1600851E-07;

// Temperature voltage divider parameters
const float dividerResistor = 55440.0;
const float vcc = 5.011;
const int analogSteps = 1024;

// Wait time
const int oneSecond = 1000;

// void setup()
// Setup serial communication with connected PC.
void setup() {
  // 
  Serial.begin(9600);
}

// void loop()
// Repeatedly read sensor and return temperature to PC
void loop() {
  // Read voltage at the thermistor, convert to resistance, then to temperature
  float voltageR = analogRead(A0) * vcc / analogSteps + 1.0/(2.0*analogSteps);
  float resistance = voltageR*dividerResistor / (vcc - voltageR);
  float lnR = log(resistance);
  float temperature = 1.0 / (coefA + coefB * lnR + coefC * pow(lnR, 3.0)) + zeroKelvin;

  // Return thermistor resistance and temperature
  Serial.print("Thermistor resistance: ");
  Serial.print(resistance);
  Serial.print(" ohms, temperature: ");
  Serial.print(temperature);
  Serial.println(" C");

  // Wait one second
  delay(oneSecond);
}

After the standard program header, we declare the Steinhart-Hart equation coefficients, coefA, coefB, and coefC as computed in the previous sections above, and the value of absolute zero, zeroKelvin, in degrees Celsius. We then declare the values used in the voltage divider, measured using a digital multi-meter for maximum accuracy. We use the measured dividerResistor (RVD) and the measured Arduino board vcc (VCC). The number of steps in the analog to digital converter, analogSteps, is also declared. We declare the oneSecond constant to be used in the main loop to wait one second before starting a new iteration. The standard function setup() initializes the Serial communication channel and sets the communication speed to 9600 baud.

In the standard loop() function, we first read the analog input channel A0 taken across the thermistor and convert the analog step value to a floating point value in volts, voltageR. We use that value to compute the resistance across the thermistor, resistance. The temperature is then computed, using the Steinhart-Hart equation. The resistance and temperature are sent back to the PC using the Serial library. Finally, we wait one second before exiting the loop() function.

Putting It to Work

Build the circuit shown above and connect the Arduino Uno to your computer using a USB cable. On the computer, within the Arduino IDE, copy and paste the code above into a new sketch, compile and download the sketch then press the control, shift, and M keys simultaneously. This will make the Serial Monitor window appear. Every second, the Arduino program will send the temperature measured by the sensor as a character string back to the computer. The text is displayed on the Serial Monitor window and the output should look like the following:

What Next

Now that I have a way to use the thermistor sensor, I could build a standalone device, using the temperature probe, that would display the temperature at the probe and sound an alarm when a set temperature has been reached. In an upcoming post, I will discuss the construction of such a device.

Driving Seven Segment Displays

Many projects require the ability to display values. Until now, we have used the Serial library to display information on the computer connected to the Arduino. Particularly, in the Sensing Temperature and Humidity post, we used the Serial library to display temperature and humidity on the IDE’s Serial Monitor window. In this post, we will use a four digit seven-segment display to show numbers directly from the Arduino micro-controller.

The featured image at the beginning of this blog post was published by ©Raimond Spekking / CC BY-SA 4.0 (via Wikimedia Commons).

The Seven Segment Display

The seven-segment display is an electronic device that uses seven LED segments organized in the shape of a number eight. The LEDs are lit in different patterns to form numerals 0 to 9. An LED in the form of a dot is sometimes added at the bottom right of the seven segments to serve as a decimal point. The picture at the left, above, depicts a single digit seven-segment LED display. Each of its segments is identified by a letter as shown in the center diagram. The diagram at the right depicts the electrical connections within the seven-segment LED display. Some seven-segment displays have a common cathode, as shown, others have a common anode. For the remainder of the post, we will use common cathode seven-segment displays.

To use the seven-segment display, we connect the common cathode to ground and apply a voltage to each segment through a current limiting resistor. A voltage is applied to the current limiting resistor attached to the segment that needs to be lit while the segments that are to remain extinguished are connected to ground. The following diagram shows how the number four, ‘4’, can be formed by connecting segments ‘b’, ‘c’, ‘f’, and ‘g’ to a voltage and segments ‘a’, ‘d’, ‘e’ and the decimal point, ‘dp’, to ground.

Each segment requires its own current limiting resistor to ensure that regardless of the number of segments lit, current through each LED segment is the same, guaranteeing that each LED segment’s intensity remains constant. If a current limiting resistor was to be connected to the common cathode, a constant amount of current would be distributed between all lit segments, making display intensity diminish with the number of segments lit. Displaying a ‘1’ using two segments would be twice as bright as a ‘4’, using four segments, and three times brighter than a ‘9’ using six segments. Eight digital output pins are required to control and light each of the seven segments and decimal point. This is more than half of the available digital pins on the Arduino Uno which has fourteen digital pins externally available.

To drive more than one seven-segment digit, we use a method called multiplexing. When multiplexing, we activate one digit at a time and apply a voltage to each of the seven segments and decimal point. The digits are activated in sequence, fast enough for the eye not to notice that digits are not all lit at the same time, and only for a brief instant. To activate a digit, we ground its common cathode while the other common cathodes remain unconnected. To achieve this on the Arduino, the digital pin connected to the common cathode of the digit to activate is set to OUTPUT and a LOW value applied to it. This lights the digit’s segments whose digital pins are set to HIGH as the common cathode digital pin acts as a ground. For all other digits not being displayed, we let their common cathodes “float”, as if they were disconnected, by setting their digital pin modes to INPUT. In input mode, digital pins do not provide current, or very little, and act as if the pin is not connected.

The Four Digit Seven-Segment Display

With the multiplexing technique, eight digital pins are required for all of the segments and decimal point, and one digital pin per digit to display. To display four digits, twelve digital pins are required. Seven-segment displays come in a variety of packaging and sizes. For this project, I used a 0.36″ (9.2 mm) common-cathode 4 digit seven-segment display. For all of this to work, electrical requirements must be met. The electrical characteristics of each of the LEDs making up the seven-segment display are similar to single LEDs as discussed in my Arduino’s Blink post. Of importance are the absolute maximum forward current, IF, and the forward voltage, VF. For this project, I used the four digit, common-cathode, seven-segment display 3461AS from XLITX. For each segment, the absolute maximum forward current is 30 mA and the forward voltage is typically 1.8 V. According to the specification, the relative luminosity increases linearly from 0 mA up to 20 mA. The following diagram shows the internal structure of the 3461AS four digit seven-segment display and its pinout.

Each Arduino digital pin can source or sink 40 mA. In order to drive each of the four digits, we will use 8 digital output pins and connect them to the seven segments and decimal point anodes through a current limiting resistor, and we will use 4 digital output pins, each connected to the common cathode of each digit. As described previously, to light a segment of one of the digits, we apply a HIGH, or 5 V, to the segment’s anode and apply a LOW, 0 V to the digit common cathode of the segment to be lit.

Up to eight segments can be lit at once all drawing the same amount of current. We must limit the current through each segment to no more than 5 mA in order not to exceed the 40 mA an Arduino digital pin can sink. If each segment drops 1.8 V, current limiting resistors will each drop 3.2 V. According to Ohm’s law, R = V / I and to draw a maximum of 5 mA, the current limiting resistor must be at least 3.2 V / 0.005 A or 640 Ω. We will use 1 KΩ current limiting resistors, limiting each segment’s current to 3.2 V / 1000 Ω, or 3.2 mA, and limiting the total current sunk by the digital pin connected to the common cathode to 25.6 mA. The following diagram depicts the electrical circuitry to connect a four digit seven-segment display to an Arduino.

The Arduino’s digital pins 2 through 9 drive segments ‘a’ through ‘g’ and the decimal point through eight 1 KΩ current limiting resistors. The common cathodes of digits one through four are connected to the Arduino’s digital pins 10 through 13 respectively.

Breadboarding

The following picture depicts how to connect the different parts using a solderless breadboard, jumper wires, a seven-segment four digit display, eight 1 KΩ resistors, and an Arduino Uno micro-controller.

Displaying a Floating-Point Number

To demonstrate how to drive the four digit seven-segment display, I wrote a program that increments a floating-point number from −99.9 to 999.9 in 0.1 increments, five times a second and displays it on the four digit seven-segment display. Each digit is displayed in turn for 1.25 ms every 5 ms, thus updating at 200 Hz with a duty-cycle of 25%. The program can be found on Github. To run the program, download file FourDigitSevenSegmentDisplay.ino and load it in the Arduino IDE. Compile and load the program onto the Arduino microcontroller and watch the floating-point value increment on the four digit seven-segment display. In the following sections, I will explain how to convert each digit to their seven-segment counterpart, and how to manage the conversion of a floating-point number into a sequence of digits. First, we define the floating-point display format, the way we want the floating-point number to be displayed on the seven-segment four digit display.

We set the number of decimal places to one. With four digits, the smallest number that can be displayed is −99.9 and the largest number is 999.9. When numbers are between −9.9 and −0.1 or between 10.0 and 99.9, only 3 digits are required and the leftmost digit is left blank. When numbers are between 0.0 and 9.9, only 2 digits are required and the two leftmost digits are left blank. The following diagram shows how to display all of these use cases.

The Demonstration Program

The program starts with the usual header followed by different constants and definitions used throughout the program. First, we define the digital pins used to drive segments ‘a’ through ‘g’ (SEGMENT_A through SEGMENT_G) and the decimal point (SEGMENT_DP). Then, we define the digital pins used to activate digits (DIGIT_1 through DIGIT_4). Note that the program assumes that the digital pin numbers of the pins driving segments ‘a’ through ‘g’ are consecutive and in incrementing order. This also holds for the digital pin numbers of the pins activating digits 1 through 4. The number of digits is defined in constant numberOfDigits. Constant digitTimeOn contains the number of microseconds each digit remains lit while constant timeBetweenIncrements contains the amount of time in seconds between each increment. Constant microsecondsInASecond contains the number of microseconds in a second.

/*
  Four digit seven-segment display demonstration
  Program that displays a floating-point counter on a four-digit
  seven-segment display. It is associated with the Four Digit
  Seven Segment Display blog post at https://lagacemichel.com
  MIT License
  Copyright (c) 2021, Michel Lagace
*/

// Digital output pins to turn on or off each segment of the selected digit
#define SEGMENT_A 2
#define SEGMENT_B 3
#define SEGMENT_C 4
#define SEGMENT_D 5
#define SEGMENT_E 6
#define SEGMENT_F 7
#define SEGMENT_G 8
#define SEGMENT_DP 9

// Digital output pins to select each of the four digits
#define DIGIT_1 10
#define DIGIT_2 11
#define DIGIT_3 12
#define DIGIT_4 13
const int numberOfDigits = 4;

// Number of microseconds to leave digit on to achieve 200 Hz
const int digitTimeOn = 1250;

// Interval in seconds between counter increments - 0.2 sec for 5 counts/sec
const float timeBetweenIncrements = 0.2;
const float microsecondsInASecond = 1000000.0;

// Number of digits after decimal points and floating-point constants
const int scaleOfNumber = 1; // number of digits after decimal point
const int scalingFactor = pow(10.0, scaleOfNumber);
const float increment = 1.0 / scalingFactor;
const float minimumValue = -pow(10.0, numberOfDigits - 1) / scalingFactor + increment;
const float maximumValue = pow(10.0, numberOfDigits) / scalingFactor - increment;

// Counters to control value displayed on seven-segment display
float displayCounter = minimumValue;
long iterations = 0;

// Segment encodings for digits 0, 1, 2, 3, 4, 5, 6, 7, 8, and 9 and for the
// minus // sign '-'. Each bit represents a segment in the following order:
// n/a, a, b, c, d, e, f, g. The most significant bit is not used. Segments
// are identified as follows
//          a
//      +-------+
//      |       |
//     f|       |b
//      |   g   |
//      +-------+
//      |       |
//     e|       |c
//      |       |
//      +-------+  . dp
//          d
const int segmentPatterns[] = {
  0b1111110, // 0: a, b, c, d, e, f
  0b0110000, // 1: b, c
  0b1101101, // 2: a, b, d, e, g
  0b1111001, // 3: a, b, c, d, g
  0b0110011, // 4: b, c, f, g
  0b1011011, // 5: a, c, d, f, g
  0b1011111, // 6: a, c, d, e, f, g
  0b1110000, // 7: a, b, c
  0b1111111, // 8: a, b, c, d, e, f, g
  0b1111011, // 9: a, b, c, d, f, g
};

Constant scaleOfNumber states the number of decimal places to display; constant scalingFactor contains the power of ten corresponding to the number of decimal places; constant increment is the floating-point value to add to the displayed floating-point value at every increment time; constant minimumValue is the minimum floating-point value than can be displayed; constant maximumValue is the maximum floating-point value that can be displayed before returning to minimumValue. Global variable displayCounter is the floating-point value displayed on the seven-segment display. It is initialized to the minimum floating-point value to display, minimumValue. Global variable iterations counts the number of loop() iterations between displayCounter increments.

Lastly, segmentPatterns, is an array of constant integers that contain seven-segment encodings for numbers zero to nine as a pattern of lit and extinguished segments on each of the seven-segment display digits. Each encoding uses the least significant seven bits of each constant integer to represent the state of each of the seven segments, from ‘a’ to ‘g’, left to right. The most significant bit, the left-most one, represents segment ‘a’. The bit to its right represents segment ‘b’ and so on until the least significant bit which represents segment ‘g’. Each encoding is a series of bits where a ‘1’ represents a lit segment and a ‘0’ an extinguished one. For instance, digit ‘four’ is formed by lighting segments ‘b’, ‘c’, ‘f’ and ‘g’. So, the second, third, sixth, and seventh bits from the left are set to ‘1’ and all other bits remain ‘0’. The encoding for ‘four’ is thus 0b0110011.

Extinguishing All Digits

After a reset and between each displayed digit, all digits are extinguished. This is done by floating all digit digital pins attached to the common cathodes. Floating of digital pins is achieved by setting their modes to INPUT.

// Extinguish all digits in the four digit seven-segment display
void extinguishDigits()
{
  // Float all common cathodes by setting all digit pins as INPUT
  for (int currentDigit = DIGIT_1; currentDigit <= DIGIT_4; currentDigit++)
  {
    pinMode(currentDigit, INPUT);
  }
}

Displaying a Minus Sign

To display a minus sign on one of the digits we need to light segment ‘g’. First, we extinguish all digits using function extinguishDigits(); then we make digital pins associated to segments ‘a’ through ‘f’ and the decimal point LOW; we make the digital pin associated to segment ‘g’ HIGH; and we finally activate the specified digit by setting the digit’s pin mode to OUTPUT and making the pin LOW.

// Displays a minus sign on the specified digit.
void displayMinusSign(int digit)
{
  // Extinguish all digits
  extinguishDigits();

  // Turn off all digit segments
  for (int currentSegment = SEGMENT_F; currentSegment >= SEGMENT_A; currentSegment--)
  {
    digitalWrite(currentSegment, LOW);
  }

  // Turn off decimal point segment
  digitalWrite(SEGMENT_DP,LOW);

  // Turn on g segment to display minus sign ('-')
  digitalWrite(SEGMENT_G, HIGH);

  // Activate requested digit
  int selectedDigitPin = digit + DIGIT_1 - 1;
  pinMode(selectedDigitPin, OUTPUT);
  digitalWrite(selectedDigitPin, LOW);
}

Displaying a Digit

To display a digit, we light and extinguish the appropriate segments according to the pattern in the segmentPatterns array corresponding to the digit to be displayed. First, we extinguish all digits using function extinguishDigits(); then we get the pattern and put it into variable segments. If the value to display is not between 0 and 9, all segments are extinguished.

We then loop through the segments in reverse order, starting with segment ‘g’. At each iteration of the loop, we use the bitwise AND operator ‘&’ to extract the least significant bit of the pattern. The bitwise AND operator performs a logical AND operation on each pair of corresponding bits between two values. If both bits are ‘1’, the resulting bit is ‘1’, otherwise, the resulting bit is ‘0’. In the code, the operation ‘segments & 0b0000001‘, results in 0b0000001, or ‘1’, if the least significant bit of segments is ‘1’, and 0b0000000, or ‘0’, if the least significant bit of segments is ‘0’. On the first iteration of the loop, the least significant bit of segments corresponds to the state of segment ‘g’. Segment ‘g’ is set to HIGH or LOW according to the value of segments‘ least significant bit. We then shift the value of segments right by one bit, making the least significant bit correspond to segment ‘f’. The loop then repeats and turns on or off each segment in turn, in reverse order, for segments ‘f’, ‘e’, ‘d’, ‘c’, ‘b’, and ‘a’.

// Display a value, between 0 and 9 on the specified digit of the display
// and display the decimal point if required.
void displayDigit(int digit, int value, bool decimalPoint)
{
  // Extinguish all digits
  extinguishDigits();

  // Prepare the segments to light
  int segments = 0;
  if ((value >= 0) && (value <= 9))
  {
    segments = segmentPatterns[value];
  }

  // turn on or off each segment
  for (int currentSegment = SEGMENT_G; currentSegment >= SEGMENT_A; currentSegment--)
  {
    digitalWrite(currentSegment, segments & 0b00000001);
    segments = segments >> 1;
  }

  // Set the decimal point
  digitalWrite(SEGMENT_DP, decimalPoint);

  // Select requested digit
  int selectedDigitPin = digit + DIGIT_1 - 1;
  pinMode(selectedDigitPin, OUTPUT);
  digitalWrite(selectedDigitPin, LOW);
}

The decimal point is then set according to the value of the Boolean argument decimalPoint. We finally activate the specified digit by setting the digit’s pin mode to OUTPUT and making the pin LOW.

Displaying a Floating-Point Number

In order to display the number, we extract each digit from right to left, first from the fractional part, then from the integer part. If the number is in the displayable range, we make the number positive, remembering the sign, we then extract the integer and fractional parts of the number as two integers, integerPart and fractionalPart. We first display the fractional part by iterating through the number of decimal places, dividing the number by 10, displaying the remainder, obtained using the modulo operator ‘%‘, and continuing with the result of the division. We thus iterate through all fractional part digits from right to left. Each fractional part digit stays lit for digitTimeOn microseconds.

We then enter a loop to display the integer part. The first integer part digit is always displayed, even if it is zero, along with the decimal point following it. As we did for the fractional part, at each iteration of the loop, we divide the integer part by 10, display the remainder and continue with the result of the division. The loop ends when the remaining integer part, remainingValue, reaches zero. each integer part digit is lit for digitTimeOn microseconds.

// Display the number passed as a parameter on the four digit
// seven-segment display.
void displayNumber(float number)
{
  // Process digits from right to left
  int currentDigit = numberOfDigits;

  // Only numbers smaller than the maximum value can be displayed
  if ((number >= minimumValue) && (number <= maximumValue))
  {
    // Scale the number and extract decimal and integer portions of number
    bool negative = false;
    if (number < 0.0)
    {
      negative = true;
      number = -number;
    }
    int integerPart = number;
    int fractionalPart = (number - integerPart) * scalingFactor;

    // Display fractional part
    int remainingValue = fractionalPart;
    for (int i = 0; i < scaleOfNumber; i++)
    {
      int digit = remainingValue % 10;
      remainingValue = remainingValue / 10;
      displayDigit(currentDigit--, digit, false);
      delayMicroseconds(digitTimeOn);
    }

    // Display integer part
    remainingValue = integerPart;
    bool decimalPoint = true;
    do
    {
      int digit = remainingValue % 10;
      remainingValue = remainingValue / 10;
      displayDigit(currentDigit--, digit, decimalPoint);
      decimalPoint = false;
      delayMicroseconds(digitTimeOn);
    } while (remainingValue > 0);

    // Display minus sign if number is negative
    if (negative)
    {
      displayMinusSign(currentDigit--);
      delayMicroseconds(digitTimeOn);
    }
  }

  // Extinguish all digits ensuring that all digits are displayed the same amount of time
  // and wait a bit if not all digits were lit, ensuring constant intensity for all numbers
  // currentDigit actually contains the number of digits left.
  extinguishDigits();
  delayMicroseconds(digitTimeOn * currentDigit);
}

If the number is negative, we display a minus sign on the next digit to the left. The minus sign is displayed for digitTimeOn microseconds. All digits are then extinguished and a delay, in microseconds, corresponding to the number of blank digits times the value of digitTimeOn is introduced to ensure that digits are always turned on the same amount of time regardless on the number of digits displayed. This guarantees that all digits of the four digit seven-segment display have always the same intensity.

Setting up the Arduino

During the setup() portion of the Arduino program, we float all digit selection pins by setting the pin mode to INPUT using the extinguishDigits() call. We then set the mode of the digital pins driving the segments ‘a’ through ‘g’ and the decimal point to OUTPUT. Global variables displayCounter and iterations are reset to their default values.

// Set all digital pins used for digit selection as input to extinguish all digits
// and set display segments as outputs.
void setup() {

  // Float all digit selection digital pins as input
  extinguishDigits();
  
  // Set all segment digital pins as output and turn them off
  for (int currentSegment = SEGMENT_G; currentSegment >= SEGMENT_A; currentSegment--)
  {
    pinMode(currentSegment, OUTPUT);
  }

  // Set decimal point digital pin as output and turn it off
  pinMode(SEGMENT_DP, OUTPUT);

  // Reset counters
  displayCounter = minimumValue;
  iterations = 0;
}

The Main Loop

In the main loop() we increment the iteration counter and then check if the number of iterations adds up to the time to wait between increments, timeBetweenIncrements. If so, we reset the number of iterations, increment DisplayCounter and if it exceeds maximumValue, it is set to minimumValue. DisplayCounter is then displayed on the four digit seven-segment display through a call to displayNumber().

// Continuously display the counter and increment it at the specified interval.
void loop() {
  // Increment number of iterations until the interval between increments has been reached
  // Each iteration takes digitTimeOn*numberOfDigits microseconds
  iterations++;
  if ((iterations * digitTimeOn * numberOfDigits / microsecondsInASecond) > timeBetweenIncrements)
  {
    // Reset loop counter and increment display counter
    iterations = 0;
    displayCounter += increment;

    // Reset display counter if it has reached the maximum value
    if (displayCounter > maximumValue)
    {
      displayCounter = minimumValue;
    }
  }

  // Display floating-point counter value
  displayNumber(displayCounter);
}

What Next

Of course, displaying a counter is not the best use for a four digit seven-segment display. It did, however, allow us to test all use cases and ensure that the functions and program were functioning properly. Instead of showing a counter, one could display temperature, light intensity, voltage, current, or any value of interest. We have just gained the capability of displaying values without being connected to a computer.

A Light Activated Switch

Up until now we mostly talked about digital signals and digital values. Digital signals are series of discrete values that vary at regular intervals of time in discrete steps. All digital signals can be represented as sequences of integers. There are a finite number of values each digital quantity can have, including when it is represented by a floating-point number. By contrast, analog signals are values that continuously vary with time. Analog signals can be represented using real numbers. Real-world measurements of voltage, current, or speed are analog signals. Since computers and processors can only work with digital values and signals, any real-world analog quantity or signal must be converted to its digital counterpart in order to be processed. Fortunately, Arduino boards have analog input ports that convert voltages into digital values.

The featured image at the beginning of this blog post is titled “Sodium Light Lit Tree at Rogerthorpe Manor” by Andy Beecroft, CC BY-SA 2.0 and was downloaded as-is from the Wikimedia Commons.

Analog to Digital Conversion

The micro-controller on the Arduino board has an analog to digital converter (ADC) that converts the voltage found at any one of the analog input pins to an integer value between 0 and 1023. The analog to digital converter splits the input voltage range into 1024 equal steps. By default, the Arduino’s analog input voltage range is between 0 volt and the Arduino’s supply voltage (VCC), approximately 5 volts for the Arduino Uno. Each step in the range spans VCC/1024 volts or approximately 0.00488 volts (4.88 mV) for the Arduino Uno. So, if the voltage at the analog input pin is between 0 V and 4.88 mV then the digital value is 0; if it is between 4.88 mV and 9.76 mV then the digital value is one; and so on until the input value is between VCC − 4.88 mV and VCC producing a discrete value of 1023. Hence, the discrete value returned by the ADC is

n = V∙1024 / VCC

where n is the integer discrete value returned by the ADC and V is the voltage at the analog input pin of the Arduino.

Once the input voltage has been converted to a discrete digital step value, we may want to convert that digital number to its corresponding digital floating-point voltage value to be able to use it in equations or formulas. We could convert the digital value to two floating-point numbers corresponding to the voltage range covered by the digital value returned by the ADC. A value of n at the input pin corresponds to analog voltages between n∙VCC/1024 and (n + 1)∙VCC/1024. For instance, if the value sampled is 100, the corresponding voltages would be between 100∙VCC/1024 and 101∙VCC/1024, or approximately between 0.488 V and 0.493 V on the Arduino Uno. However, using two values is not very practical. Instead, we can use the average value between the two extremes and state that there is a possible error of up to half the value of a step. Using our example, the average voltage for a sample value of 100 would be

(100∙VCC/1024 + 101∙VCC/1024) / 2 with a maximum error of ±0.5∙VCC/1024

On the Arduino Uno, by default, the voltage corresponding to a value of 100 is thus approximately 0.491 V ± 2.44 mV. More generally, the equation to convert the value returned by the ADC to a floating-point voltage value is

Voltage = (n + 0.5)∙VCC/1024 ± 0.5∙VCC/1024

Where n is the digital value returned by the ADC. The reason we used VCC in the equation is that the actual power supply voltage for the Arduino Uno is never quite 5 volts and the value measured and returned by the ADC is relative to VCC. In applications requiring more accuracy, the actual supply voltage would have to be measured, or better yet, a precise and stable voltage reference would have to be supplied. The conversion of input voltages into digital values by the ADC and the conversion from the digital value to its floating-point value can be visualized in the following graph.

In the graph, VCC has been changed to Vref, the reference voltage of the ADC which is VCC by default on the Arduino Uno.

An Automatic Light Switch

Light intensity is an analog real-world value that can be measured and converted to a voltage. Several light sensitive devices can be used to do so, and one such device is the photo-resistor. In the next few sections, we will see how a photo-resistor can be used to convert light intensity into a measurable voltage, how to read from an analog input pin on the Arduino Uno, and how to turn a light appliance on when surrounding light intensity drops below a predetermined threshold. But first, what is a photo-resistor?

The Photo-Resistor

A photo-resistor, or LDR for Light Dependent Resistor, is a two-terminal passive device whose resistance varies according to the amount of visible light reaching it. The resistance across its leads decreases as more light reaches its detection surface. As for any electronic device, the photo-resistor has specifications detailing its characteristics. Some of the main characteristics are

  • Light resistance at 10 lux. This is the resistance in ohms across the device’s leads when 10 lux of light illuminates the device. The lux (symbol: lx) is a measure of illuminance, the amount of light that hits or goes through a surface. 10 lx approximately corresponds to the amount of light produced by a 60-watt incandescent light bulb reaching a one square meter surface 2.5 meters away from the light bulb.
  • Gamma (γ). This is the relationship between how the resistance changes with respect to light intensity changes. The value corresponds to the difference of the logarithm of the resistance divided by the difference of the logarithm of illuminance. γ = (log R1 − log R2) / (log I2 − log I1), where R1 is the resistance when illuminance is I1 and R2 is the resistance when illuminance is I2. Note that the order of resistance values is reversed from the order of illuminance values in the equation.
  • Dark resistance at 0 lux. This is the resistance in ohms across the device leads when no light reaches the device.
  • Power dissipation (at 25 °C). The maximum power the device can dissipate at a temperature of 25 °C.
  • Max voltage (at 25 °C). The maximum voltage the device can withstand between its leads at a temperature of 25 °C.

The symbol for a photo-resistor is shown above on the right. Also pictured on the left, photo-resistor GL5528, which has the following electrical characteristics:

  • Light resistance at 10 lux: 8 KΩ to 20 KΩ
  • Gamma (γ): 0.7
  • Dark resistance at 0 lux: 1.0 MΩ (minimum)
  • Power dissipation (at 25 °C): 100 mW
  • Max voltage (at 25 °C): 150 

Computing Resistance and Illuminance

There are two characteristics of the photo-resistor that can help us determine its resistance for a given illuminance: the light resistance at 10 lux, R10lx, and the gamma, γ. Let’s take the gamma equation stated earlier in the photo-resistor characteristics section:

γ = (log R1 − log R2) / (log I2 − log I1)

Substituting R1 with the light resistance at 10 lux, R10lx, and I1 with 10 lx we get the following equation:

γ = (log R10lx − log R) / (log I − log 10)

where R is the unknown resistance value for a given illuminance I. Using the logarithm laws, we can solve the equation for R, which gives:

R = (10γ∙R10lxγ) / Iγ

Similarly, we can solve the same equation for I, yielding:

I = (10∙R10lx1/γ) / R1/γ

The light resistance at 10 lux for photo-resistor GL5528 is between 8 KΩ and 20 KΩ according to its electrical specification. We will use an average light resistance at 10 lux of 14 KΩ. The gamma for photo-resistor GL5528 is 0.7 for illuminances between 10 lx and 100 lx. Substituting the light resistance value at 10 lux, the gamma and an illuminance of 10 lx in the above equations we get:

R = (100.7∙14,0000.7) / I0.7  or
R ≈ 70,000 / I0.7

and

I = (10∙140001/0.7) / R1/0.7  or
I ≈ 8,400,000 / R1.43

Converting Light Intensity to Voltage

We will use a voltage divider, as described in post “A Better Transistor Switch Circuit,” as a mean to convert photo-resistor electrical resistance into voltage. The following circuit diagram depicts the voltage divider circuit and its connection to the Arduino micro-controller.

We have to choose a resistance, R1, which limits enough current so that the phot-resistor does not overheat and so that the voltage at the analog input is close to the midpoint of the full voltage range. According to the GL5528 photo-resistor characteristics, the maximum power that the photo-resistor can dissipate is 100 mW at 25 °C. As we have previously seen in the Arduino’s Blink post, power dissipated through both the resistance and the photo-resistor is obtained by multiplying voltage and current together,

P = V∙I

and since I = V/R according to Ohm’s law, replacing I in the previous equation and R with the sum of both resistors, we get

P = V2 / (R1 + R2)

We know that the voltage, VCC, is approximately 5 volts and that P must be smaller than 0.1W. Hence, the following must hold:

V2 / (R1 + R2) < 0.1 W,
25 V2 / 0.1 W < R1 + R2

The sum of the resistance of both resistors must be larger than 250 Ω. Also, since we want to activate the light when the light intensity is below 10 Lux, or when the photo-resistor’s resistance is at or below 14 KΩ. Choosing resistor value of 15 KΩ for R1 satisfies both conditions. If R1 is 15 KΩ, the sum of both resistances is certainly larger than 250 Ω. Moreover, the voltage at the analog input pin will be approximately 2.41 V since

I = V / (R1 + R2) = 5 V / (15 KΩ + 14 KΩ) ≈ 0.172 mA

and

VR2 = R2∙I = 14K ∙ 0.172 mA ≈ 2.41V

The complete circuit becomes

We have the light detection part of the circuit. To activate the light, we will build on the circuit developed as part of the “A Better Transistor Switch Circuit” post and replace the push button switch with a GL5528 photo-resistor and connect the photo-resistor to an analog input pin instead of a digital input pin. The complete circuit is as follows:

Breadboarding

The following picture depicts how to connect the different parts using a solderless breadboard, jumper wires, a transistor, a diode, a relay, a photo-resistor, a 15K resistor, a 1.8K resistor and a 3.3K resistor. Connections to the household appliance are not shown.

The Light Activated Switch Program

We want the circuit to switch the lights on at dawn when it becomes dark and to switch the lights off in the morning when it is bright again. So, we want to switch the lights on when the illuminance at the photo-resistor falls below a preset value. Switching the lights on increases light intensity and turning them off when illuminance is above the same preset value as when we turned them on will cause oscillations, turning the lights repeatedly on and off. To prevent these unwanted oscillations, we need to implement hysteresis. Hysteresis is the dependence a system’s state has on its previous states; in our case, lights are to switch on when illuminance falls below a preset value only if their current state is off; and to switch them off if the illuminance is much higher than what it was when the lights were turned on only if their current state is on. Hysteresis will prevent oscillations if the illuminance at which the lights are to turn off is higher than the sum of the illuminance at which the lights are to turn on and the additional illuminance introduced by switching the lights on.

For example, let’s say a light appliance is to turn on when the light intensity caused by environmental lighting makes the illuminance at the photo-resistor fall below 10 lx. The light appliance being turned on causes an additional illuminance of 60 lx that can be measured at the photo-resistor. At this point, the illuminance at the photo-resistor is 70 lx. Turning the light appliance off only when the illuminance at the photo-resistor reaches 10 lx more, for a total of 80 lx, allows the lights to be turned off when the illuminance at the photo-resistor caused by environmental lighting is 20 lx, thus preventing any unwanted oscillations and turning the light appliance off at an appropriate light intensity.

In other words, the program must

  • Turn the light appliance on, if it was previously off, when the illumination falls below 10 lx.
  • Turn the light appliance off, if it was previously on, when the illumination rises above 80 lx.

We have already determined, from the photo-resistor specification that the device’s resistance at 10 lx is 14 KΩ on average. Using the equations developed earlier, the photo-resistance at 80 lx is 70,000 / 800.7 Ω, or approximately 3.3 KΩ. The voltage, V10, at the photo-resistor at 10 lx is

V10 = (5 V ∙ 14 KΩ) / (15 KΩ + 14 KΩ) ≈ 2.41V.

The voltage, V80, at the photo-resistor at 80 lx is

V80 = (5 V ∙ 3.3 KΩ) / (15 KΩ + 3.3 KΩ) ≈ 0.902 V.

As seen earlier, the integer value, n, read from the ADC is n = V∙1024 / VCC, where V is the voltage at the analog input. Thus, the integer value read from the ADC when the illuminance at the photo-resistor is 10 lx, and the voltage is 2.41 V, is 493. Similarly, the integer value read from the ADC when the illuminance at the photo-resistor is 80 lx, and the voltage is 0.902 V, is 184. Because the voltage varies inversely to luminance, the following will be implemented:

  • Turn the light appliance on, if it was previously off and the analog input reads greater than 493.
  • Turn the light appliance off, if it was previously on and the analog input reads less than 184.

The following Arduino program completes the post. Cut and paste the code in your Arduino IDE and download it to complete the project. It will turn the light appliance on when it gets dark and off again in the morning.

/*
  Light Dependent Resistor (LDR) Sensor Analog Reader Sketch
  Program that reads light intensity as an integer value from
  a GL5528 sensor, turns on a light appliance when the
  illuminance is smaller than 10 lx, if the light appliance
  is already off, and turns the light appliance off when the
  illuminance is greater than 80 lx, if the light appliance
  is already on. The program is associated with the "A Light-
  Activated Switch" blog post on https://lagacemichel.com
  MIT License
  Copyright (c) 2020, Michel Lagace
*/

// LDR (photo-resistor) analog input port and light appliance
// digital output port
#define LDR_PORT A0
#define LIGHT_SWITCH 11

// LDR voltage at which to turn the lights on and off
const int V10 = 493; // Analog value when LDR is 10 lx
const int V80 = 184; // Analog value when LDR is 80 lx
const int WaitInterval = 1000; // Interval between each loop

// Light appliance state
static bool LightOn = false;

// Setup the board.
void setup() {
  pinMode(LIGHT_SWITCH,OUTPUT);
  LightOn = false;
  digitalWrite(LIGHT_SWITCH,LightOn);
}

// This is the main loop. It acquires the luminance then turns
// on the light appliance if it is off and the illuminance
// falls below 10 lx (the voltage is above 2.41V), and turns
// off the light appliance if it is on and if the illuminance
// increases above 80 lx (the voltage is below 0.902 V).
void loop() {

  // Get sensor analog value from GL5528 sensor
  int LdrIntegerValue = analogRead(LDR_PORT);
 
  // If light is below 10 lux and currently off, turn on the light
  if (!LightOn && (LdrIntegerValue > V10)) {
    LightOn = true;
  }
  // If light is above 80 lx and currently on, turn off the light
  else if (LightOn && (LdrIntegerValue < V80)) {
    LightOn = false;
  }
  digitalWrite(LIGHT_SWITCH,LightOn);

  // Wait a while before repeating
  delay(WaitInterval);
}

Following the usual header, you will find definitions for LDR_PORT, the analog input port to read illuminance from, and LIGHT_SWITCH, the digital output port to control the light appliance. We then define three constants. First, V10, the voltage above which (or illuminance below which) to turn the light on, V80, the voltage below which (or illuminance above which) to turn the light off, and WaitInterval, a constant in milliseconds determining the time to wait between loops. Finally, the Boolean variable LightOn holds the on or off state of the light appliance, HIGH for turned on and LOW for turned off.

In the setup() function, we prepare the light appliance control by setting the LIGHT_SWITCH digital port to OUTPUT, and turn the light appliance off. In the loop() function, we read the analog input into variable LdrIntegerValue. If illuminance is below 10 lux, the voltage above V10, and currently off, turn the light appliance on. If illuminance is above 80 lx, the voltage below V80, and currently on, turn the light appliance off. Wait the prescribed amount of time and repeat.

WARNING:

The project in this post involves household mains high-voltages. Use caution whenever dealing with high-voltage wiring, including following directions carefully and following general safety practices. Safe assembly and operation of this project is the user’s responsibility. If unsure or if local laws prohibit the assembly of high-voltage circuits, get the help of a professional electrician. Do not make changes to the system while the device is plugged in.

CPU Interrupted

Up until now, we have used a technique called polling to perform inputs from the Arduino‘s digital I/O pins. Polling consists in actively sampling the status of an external device or memory as a synchronous activity. On the Arduino, this usually occurs within the built-in loop() function. Polling is the method of choice in the majority of cases as it allows a program to easily and synchronously read external device states and values and handle external events. However, there are times when we want a program to handle an external event the moment it happens. This is what we explore in this post on interrupts.

Interrupts

In digital computers, a Central Processing Unit (CPU) sequentially executes instructions that manipulate data. This sequential execution of instructions can be interrupted by events to execute a different sequence of instructions, called an interrupt service routine, allowing the CPU to handle an event the moment it happens. Typically computers can handle interrupts from external sources such as timers, electrical signals, power failure, or events internal to the CPU such as accesses to invalid memory addresses, divisions by zero, and special interrupt instructions.

An interrupt is similar to a function call in that the current program counter is pushed onto the stack, thus remembering where to return upon completion of the interrupt service routine. Upon receiving an interrupt signal, the CPU completes the current instruction, disables interrupts, pushes the program counter onto the stack and calls the interrupt service routine. Upon executing a “return from interrupt” instruction at the end of the interrupt service routine, the program counter is popped from the stack, interrupts are enabled, and execution resumes where it had been interrupted in the program’s sequence of instructions. The difference between interrupt handling and a function call is that interrupt handling is disabled upon entering the interrupt service routine and that while function calls occur within the sequential flow of instructions, interrupts may occur anytime and anywhere within program execution. The fact that interrupts may occur anywhere in a program’s sequence of instructions may cause side effects which we will discuss later in this post.

Setting up Interrupt Service Routines

On the Arduino UNO, interrupt service routines can be written to handle events associated with external interrupt requests, pins, timers, Serial Peripheral Interface (SPI) data transfers, Inter-Integrated Circuit (I2C) data transfers, Universal Synchronous/Asynchronous Receiver/Transmitter (USART), Analog to Digital Converter (ADC), Electrically Erasable Programmable Read Only Memory (EEPROM) and flash memory accesses. In this post, we will only deal with external interrupts using the Arduino built-in function attachInterrupt(). On the Arduino Uno, external interrupts are tied to levels and state changes for digital input pins 2 and 3. Interrupts may be set to occur when the state of the input pin changes, on the rising or falling edge of the signal, or when the input pin is LOW. The attachInterrupt() function is used as follows:

attachInterrupt (interrupt, ISR, mode)

Where interrupt is the number of the interrupt. On the Arduino Uno, interrupt number 0 corresponds to external interrupts from digital pin 2 and interrupt number 1, corresponds to external interrupts from digital pin 3. Interrupt numbers may differ between Arduino board types. For this reason, it is recommended not to use the interrupt number directly, but to use the digitalPinToInterrupt() built-in function that returns the interrupt number associated to the pin. ISR (Interrupt Service Routine) is the name of the function to be called when an interrupt occurs. This function takes no argument and does not return a value. Finally, mode, specifies the mode of operation for the external interrupt. The following values are supported for the mode argument:

  • LOW – Interrupt when the pin is LOW.
  • CHANGE – Interrupt when the pin changes from LOW to HIGH or HIGH to LOW.
  • RISING – Interrupt when the pin changes from LOW to HIGH.
  • FALLING – Interrupt when the pin changes from HIGH to LOW.

Typically, the attachInterrupt() function is called in the built-in setup() function, where we set the pin mode of digital input 2 or 3 to INPUT or INPUT_PULLUP, and attach an interrupt service routine to one of the two pins. For instance, in the following code snippet:

// Switch value will be read from digital input pin 2
define INPORT 2

// Interrupt Service Routine
void buttonPress() {
  // Handle button press
}

// Setup the board.
void setup() {
  // Set Arduino's input ports
  pinMode(INPORT, INPUT);

  //Initialize interrupt service routine
  attachInterrupt(digitalPinToInterrupt(INPORT), buttonPress, FALLING); 
}

We declare a function, buttonPress(), that takes no argument and does not return a value. This function is called when an interrupt occurs. Within the built-in setup() function, we set the mode of digital pin 2 to INPUT using the built-in pinMode() function. We then attach interrupt service routine buttonPress() to digital pin 2. The interrupt service routine is called when the value at the input pin goes from HIGH to LOW, from 5 volts to 0 volts. The built-in function attachInterrupt() is used to attach the interrupt service routine buttonPress() to the digital pin. Note the use of the built-in function digitalPinToInterrupt() to make the use of digital pin 2 independent of board type.

Interrupt Side-Effects

Since interrupt service routines take no arguments and do not return information, they can only exchange information with the rest of the program through global variables. Global variables are variables declared in the program’s header, outside of functions, that are accessible at all times by all parts of the program. We have used global variables already to hold information shared between the setup() and loop() built-in functions and to hold information persistent between iterations of the loop() built-in function.

Side-effects arise because interrupt service routines use and modify information that is also used and modified by the main program. Because interrupts may occur anytime while a program is running, the interrupt service routine may modify information while the main program instructions were in the midst of looking at or even modifying the same information. In the next sections, we will look at two types of side effects that occur within a micro-controller when using interrupts. The first type of side-effect is volatility caused by the use of a high-level programming language when dealing with interrupts. The second type of side-effect is concurrent data access that cause data manipulations to be erroneous in certain conditions.

Volatility

When we write code for the Arduino, we do so in a programming language called C++ which is translated into machine code, real instructions for the Atmega processor on the Arduino Uno board, by a compiler. When it translates C++ code into machine code, the compiler makes assumptions and optimizes the final code as much as it can. It may decide, for instance, never to store a variable in memory and use the processor’s storage registers instead, or it may decide to delay storing the content of a variable in memory until the end of a routine. This may be problematic for variables that are to be used by an interrupt service routine as well as in the main program. The interrupt service routine may start using a variable by loading it from memory while the main program was manipulating the same variable temporarily held in one of the processor’s a storage register.

The C++ language supports a directive called volatile which directs the compiler to always use the variable from main memory and not from a storage register. A variable should be declared volatile if its value can be changed outside the scope and control of the code section in which it appears. This is the case of all variables that are manipulated by the interrupt service routine as well as in the rest of the program’s code. the volatile directive is used within the declaration of a variable and generally precedes the variable type declaration as in the following variable declaration:

volatile int counter = 0;

Variable counter is declared as a volatile signed integer (int) initialized with the value 0. Its value can be changed by the main program as well as within an interrupt service routine directly in main memory.

Concurrent Data Access

Errors due to concurrent data access occur when, upon an interrupt, the interrupt service routine uses or modifies global variables while code in the main program uses or modifies the same global variables. Several things can go wrong, among which: the main program reads the global variable, the interrupt service routine stores a new value for the global variable, then the main program overwrites the global variable, causing the interrupt routine to have no effect; the main program updates the global variable with an intermediate computation value, then the interrupt service routine erroneously uses the intermediate value; the main program uses the global variable to make a sequence of operations and in the midst of the sequence, the interrupt service routine modifies the global variable causing errors in the main program sequence.

All of these errors are caused by the fact that a value was used or modified in mid-stride by two parts of the same program. The way to fix these problems is to use and modify values atomically, which is to say in an indivisible way. For instance, if a value used in both the main program and in an interrupt service routine is to be read, modified, and saved, then all of these operations must occur as if done by a single uninterruptible instruction. The way to prevent interruption while a series of instructions is being executed is to disable interrupts while the value is used and then to enable interrupts again. To do this on the Arduino, we use the built-in functions noInterrupts() and interrupts(). Hence, in the following code sequence slightly modified from the previous post:

noInterrupts();
digitalWrite(LED0, litLED == 0);
digitalWrite(LED1, litLED == 1);
digitalWrite(LED2, litLED == 2);
digitalWrite(LED3, litLED == 3);
interrupts();

If we were not to disable interrupts while variable litLED is used, we would run in the possibility that an interrupt service routine modifies the value of litLED between calls to built-in function digitalWrite(). To illustrate the problem better, let’s imagine that interrupts are not disabled and that the value of litLED is 1. The first two digitalWrite() function calls are executed resulting in LED0 being extinguished and LED1 being lit. An interrupt occurs and the interrupt service routine increments the value of litLED by one, then returns. Execution resumes and the third digitalWrite() function call lights LED2 since the value of litLED is now 2. The end result is that LEDs 1 and 2 are simultaneously lit while the intent was to always light only one LED in sequence. Disabling interrupts while the sequence of digitalWrite() function calls execute resolves the problem. It is important to note from this example that the whole sequence must be executed with interrupts disabled because all four LEDs as a whole represent the value of variable litLED. If an interrupt does occur while interrupts are disabled, it is processed as soon as interrupts are re-enabled, right after the call to the interrupts() built-in function.

Putting it Together

In order to demonstrate the use of external interrupts on the Arduino, we will use the same circuit as was used in the previous post, Switch Debouncing. I built a circuit and Arduino program that sequentially turns LEDs on and off once a second as well as every time a high to low edge is detected on digital pin 2 of an Arduino Uno. A push-button is attached to digital pin 2 and we will use an interrupt service routine to react to the push-button being depressed.

The Electronics Setup

In this circuit, the Arduino’s digital input/output pin 2 is connected to a push-button switch and a 10 K pull-up resistor. The other end of the pull-up resistor is connected to the 5 V supply and the other end of the switch is connected to ground. The rest of the circuit consists of 4 LEDs whose cathodes are connected to ground through 330 Ω resistors and whose anodes are connected to the Arduino’s digital input/output 8, 9, 10, and 11 respectively. The following diagram depicts how to connect the different parts using a solderless breadboard, jumper wires, four LEDs, a push-button switch, a 10 K resistor and four 330 Ω resistor.

CPU Interruption Demonstration Sketch

The Arduino sketch used to demonstrate CPU interruption can be found on Github at https://github.com/lagacemichel/CpuInterrupted. Download the sketch CpuInterrupted.ino and load it in the Arduino IDE. You can also copy the following code directly into a new Arduino sketch within the IDE.

/*
Switch Interrupt sketch
Uses four LEDs connected to digital I/O pins 8, 9, 10, and 11 to demonstrate
how interrupts work. The LEDs are lit one after the other, once a second, as
well as when the switch is depressed. When the switch is depressed, it causes
an interrupt that calls an interrupt routine that increments the count and
then displays the LED corresponding to the count.
MIT License
Copyright (c) 2020, Michel Lagace
*/

// Delay time to wait at end of each loop
#define WAIT_TIME 1000

// Switch value will be read from pin 2
#define INPORT 2

// LED values will be output on pins 8, 9, 10, and 11
#define LED0 11
#define LED1 10
#define LED2 9
#define LED3 8
volatile int counter = 0; // Currently lit LED

// Increment count
void countUp() {
  counter++;
  if (counter > 3) {
    counter = 0;
  }
}

// Display count
void displayCount() {
  // Light up the appropriate LED
  digitalWrite(LED0, counter == 0);
  digitalWrite(LED1, counter == 1);
  digitalWrite(LED2, counter == 2);
  digitalWrite(LED3, counter == 3);
}

// Interrupt Service Routine
void buttonPress() {
  countUp();
  displayCount();
}

// Setup the board.
void setup() {
  // Set Arduino's input and output ports
  pinMode(INPORT, INPUT);
  pinMode(LED0, OUTPUT);
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);

  // Initialize currently lit LEDs
  counter = 0;

  //Initialize interrupt routine
  attachInterrupt(digitalPinToInterrupt(INPORT), buttonPress, FALLING);
}

// Repeat forever
void loop() {
  // Count up and display count
  noInterrupts();
  countUp();
  displayCount();
  interrupts();

  // Wait for a while
  delay(WAIT_TIME);
}

In the sketch above, we first define WAIT_TIME, the time to wait before lighting the next LED in the sequence, then INPORT, digital input port 2 from which the program will get interrupts, the four digital output ports attached to the LEDs to light in sequence, and finally, counter, the volatile integer variable maintaining which LED to light. Global variable counter is set to volatile because, as discussed previously, it is used and modified in the interrupt service routine as well as in the main program.

Next, we define two functions that are used throughout the program. Both functions do not take arguments and do not return values. Function countUp() increments global variable counter and keeps its value between 0 and 3. The other function, displayCount(), lights the LED corresponding to the value of counter and extinguishes the other LEDs.

The interrupt service routine buttonPress() is then defined. It is called by the Arduino internal software whenever the push-button is pressed. We will see later how the interrupt service routine is declared to the Arduino system. The buttonPress() routine increments the counter global variable using the countUp() function and then lights the appropriate LED using the displayCount() function. Note that interrupts are already disabled when entering the interrupt service routine which guarantees that all operations within the interrupt service routine are executed atomically, without interruption.

The setup() built-in function, called once before the loop() built-in function is repeatedly called, sets digital input/output port INPORT for INPUT and digital input/output ports LED0, LED1, LED2, and LED3 for OUTPUT. It then initializes the counter global variable to zero. No need to disable interrupts at this point since the interrupt service routine has not been set yet. Next, we declare the buttonPress() interrupt service routine through the attachInterrupt() built-in function. The digitalPinToInterrupt() built-in function is used to map INPORT to the internal interrupt number used by the system. Digital input port INPORT is set to interrupt and call the buttonPress() interrupt service routine when the signal at the INPORT digital input pin goes from HIGH to LOW by using the FALLING parameter.

Finally, the loop() built-in function is repeatedly called. It increments the counter global variable using the countUp() function and then lights the LED associated to the value of counter using the displayCount() function. Both countUp() and displayCount() functions are sandwiched between calls to built-in functions noInterrupts() and interrupts() ensuring that manipulations of the counter global variable are done atomically and that no interrupts occur while the global variable is acted upon. The delay() built-in function is then called to make the program wait the specified amount of time.

Demonstration

Set up the circuit as shown previously and, using the Arduino IDE, compile and download the code onto the Arduino board. As the code starts executing, the LEDs will light in sequence, one after the other, every second. Every time the switch is depressed, an interrupt is generated and the buttonPress() interrupt service routine gets called, which increments the counter and lights the next LED in the sequence, thus demonstrating the use of interrupts on an Arduino board.

Switch Debouncing

In the LED Toggle with a Push-Button Switch post, I have explained how electro-mechanical devices, such as push-button switches, do not close or open an electrical circuit instantaneously, causing electrical noise. Every time a switch closes or opens, spring-loaded pieces of metal bounce causing the circuit to be opened and closed rapidly for a brief moment. In my previous post, I explained how to counteract switch bounce using software. In this post I will show a simple electronic solution to switch bounce using a resistor and a capacitor. In the Arduino’s Blink post we have seen that resistors are electronic devices that restrict current flow according to Ohm’s law. We will now describe a new passive electronic device, the capacitor. The featured image at the beginning of this blog post was created by Michael Maggs and edited by Richard Bartz, CC BY-SA 3.0, and can be found in the Wikimedia Commons.

Capacitors

A capacitor is a two-terminal device that can store electrical energy. It consists of two leads attached to conducting bodies that are separated from one another by an insulator, called a dielectric. Because of the dielectric, charges cannot move from of conducting body to the other within the device. Instead, charges of equal magnitude and opposite sign accumulate on each conducting body in proportion to the voltage across the device. The capacitance of a capacitor is defined as the ratio of the magnitude of the charge Q on either conducting bodies to the magnitude of the potential difference V between the two conducting bodies.

C = Q/V

It follows from its definition that the unit of capacitance is coulombs per volt. A capacitance of one coulomb per volt is said to have a value of one farad (1 F) in honor of Michael Faraday. In terms of quantity of electrons, one coulomb corresponds to 6.25×1018 electrons. The most common type of capacitor consists of two conducting plates parallel to each other and separated by a distance which is small in comparison to the linear dimensions of the plates such as in the following figure. Note that the quantity of positive charges is always the same as the quantity of negative charges and that the total charge remains zero.

undefined

In electronic schematics, capacitors are represented by two parallel lines separated by a gap with perpendicular lines attached to each parallel line representing the leads as in the following figure. Note the second representation in the middle which has a straight line and a curved one. Both representations are valid. Some capacitors are polarized, that is that one lead must always have a higher voltage than the other in order to function properly and not be damaged. A plus sign indicates the positive lead of a polarized capacitor as shown with the capacitor symbol at the right.

We have determined the relationship between charge and voltage. It would be more interesting to establish the relationship between current and voltage. It turns out that current is defined as the rate of change of charge over time. Hence, current is defined as

i = dQ/dt

Where dQ, delta charge, is the change in charge and dt, delta time, is the change in time. Current is thus defined as the quantity of charge per second, or the number of coulombs per second. One ampere, the standard unit of current is defined as one coulomb per second. Since Q = C•V, the relationship between current and voltage is

i = dQ/dt = C•dv/dt

The current in a capacitor is equal to the capacitance of the capacitor times the rate of change of voltage over time. This type of equation is called a differential equation and solving it is beyond the scope of this post. Its solution depends on the function representing the change of voltage over time. I will present, however, solutions to this equation when dealing with digital circuitry.

Within digital circuitry, we are interested in the operation of the capacitor when values in the circuit change between a logical low, 0 V, and a logical high, 5 V on an Arduino Uno, and when the values in the circuit change from a logical high to a logical low. Instantaneously switching the voltage across a capacitor requires an infinite amount of current according to the equation above. The change in time is 0 seconds, being instantaneous, resulting in a division by zero. Of course, this does not happen in reality because there are no perfect conductors and wires and device leads are always slightly resistive, limiting the current required to charge up the capacitor. Let’s find out what happens when we put an actual resistor in series with a capacitor and switch the voltage from low to high. In the following figure, the voltage across the capacitor is initially 0 V and we close a switch, applying 5 V across the resistor and capacitor.

The moment the switch is closed, the voltage across the capacitor is 0 V while the voltage across the resistor is 5 V. As explained in the Blink post, Kirchhoff’s voltage law stipulates that the sum of voltage drops along a closed-circuit loop is 0 V. In this case, the battery provides 5 V while the resistor and capacitor drop 5 V together. At the moment the switch is closed and as the capacitor’s charges start accumulating on its conducting bodies, the current through the circuit is I = V/R, following Ohm’s law. As charges accumulate in the capacitor, the voltage across the capacitor increases and the voltage across the resistor decreases. Eventually, after a long time, the voltage across the capacitor reaches 5 V while the voltage across the resistor becomes 0 V and current ceases flowing in the circuit. Charges start by accumulating fast and as time goes by and as current becomes smaller and smaller, charges within the capacitor accumulate at a slower and slower rate. Intuitively, we can draw a graph of the voltage across the capacitor as time goes by as follows.

We have seen the behavior of a capacitor as it accumulates charges, or charges up, through a resistor. Let’s now see what happens when a capacitor discharges through a resistor. In the following figure, the voltage across the capacitor is initially 5 V and we close a switch, applying 5 V across the resistor as we close the circuit.

The moment the switch is closed, the voltage across the resistor reaches 5 V. Again, Kirchhoff’s voltage law stipulates that the sum of voltage drops along a closed-circuit loop is 0 V. In this case, the capacitor provides 5 V while the resistor drops 5 V. At the moment the switch is closed and as the capacitor’s charges start to flow out of its conducting bodies, the current through the circuit is I = V/R, following Ohm’s law. As charges flow out of the capacitor, the voltage across the capacitor and resistor decreases. Eventually, after a long time, the voltage across both capacitor and resistor reaches 0 V and current ceases flowing in the circuit. Charges start by flowing out of the capacitor fast and as time goes by and as current becomes smaller and smaller, the capacitor discharges at a slower and slower rate. Intuitively, we can draw a graph of the voltage across the capacitor as time goes by as follows.

It turns out that the voltage across the capacitor as a function of time for the discharging capacitor through a resistor is:

VC = V0e-t/RC

Where VC is the voltage across the capacitor, V0 is the initial voltage across the capacitor, t is the time in seconds after the switch has been closed, R is the value in ohms of the resistor and C is the value in farads of the capacitor. Similarly, the voltage across the capacitor as a function of time for the capacitor charging through a resistor is:

VC = V0(1 – e-t/RC)

Where VC is the voltage across the capacitor, V0 is the voltage across the voltage source, t is the time in seconds after the switch has been closed, R is the value in ohms of the resistor and C is the value in farads of the capacitor. The product of the resistance by the capacitance, RC, is called the RC time constant and its value is in seconds. It corresponds to the time it takes for the capacitor to charge or discharge by approximately 63%.

Capacitor Devices

Capacitors are manufactured in many ways and take many size, shape and form. All capacitor devices are built around the same principle: two conductors, often in the form of plates, separated by dielectrics. Where they differ is how they are constructed and the type of dielectrics used. Most capacitors used in circuits are of one of two types: non-polarized and polarized capacitors.

Ceramic, paper and film capacitors are non-polarized capacitors that are named after the dielectric material used in the capacitor. Electrolytic capacitors are polarized capacitors that are named after the material used as the anode, the positive lead of the capacitor, such as aluminium, tantalum, niobium, and the electrolyte used as the cathode, the negative lead of the capacitor. Generally speaking, non-polarized capacitors have values smaller than one microfarad and polarized capacitors have values greater or equal to one microfarad. The following picture shows three types of capacitors.

In the picture above, the first capacitor to the left is a ceramic disk capacitor with marking 680K 1KV Z5R. The middle capacitor is a Mylar film capacitor with marking NIH153. The third capacitor is an electrolytic aluminium capacitor with marking 2200µF 25V. The first two capacitors are non-polarized capacitors while the last one is polarized with its cathode marked with a minus sign.

Markings tell us about the value and ratings of the capacitor. For the first capacitor to the left, the marking 680K is read as follows: the first two digits represent the value of the capacitor, the third digit represents the power of 10 multiplier in picofarads, 10-12 farads, for the capacitor value and the letter represents the tolerance. In this case, the capacitor has a 68×100 picofarads value, 68 pF. The K represents a capacitance value tolerance of 10%. 1KV is the voltage rating of the capacitor, 1 kilovolt. Z5R represents the temperature characteristic of the capacitor, there is a maximum capacitance value shift of +15% when temperatures range from 10˚C to 85˚C. Specifications for ceramic disk capacitors can be found here.

In the case of the polyester film capacitor, the middle one, the first two digits represent the value of the capacitor and the third digit represents the power of 10 multiplier in picofarads, 10-12 farads, for the capacitor value. In this case, the capacitor has a value of 15×103 picofarads, 15000 pF, 15 nF, or 0.015 µF. I did not find the meaning for the “NIH” marking. The markings on the electrolytic capacitor are quite explicit: it has a value of 2200 microfarads and a voltage rating of 25 volts.

Demonstrating Mechanical Bounce in a Switch

In order to demonstrate the effect of switch bounce I built a circuit and Arduino program that sequentially turns LEDs on and off every time a high to low edge is detected at one of the digital inputs of an Arduino Uno. The circuit is as follows.

In this circuit, the Arduino’s digital input/output pin 12 is connected to a push-button switch and a 10 K pull-up resistor. The other end of the pull-up resistor is connected to the 5 V supply and the other end of the switch is connected to ground. The rest of the circuit consists of 4 LEDs whose cathodes are connected to ground through 330 Ω resistors and whose anodes are connected to the Arduino’s digital input/output 8, 9, 10, and 11 respectively. The following diagram depicts how to connect the different parts using a solderless breadboard, jumper wires, four LEDs, a push-button switch, a 10 K resistor and four 330 Ω resistor.

Switch Bounce Demonstration Program

The Arduino sketch used to demonstrate switch bounce can be found on Github at https://github.com/lagacemichel/SwitchBounce. Download the sketch SwitchBounce.ino and load it in the Arduino IDE. You can also copy the following code directly into a new Arduino sketch within the IDE.

Following the usual comment at the beginning of the sketch, you will find a definition for INPORT, the input port connected to the switch, and the Boolean value switchState holding the last state of the input switch. Then LED0, LED1, LED2, and LED3, the output ports to the four LEDs, are defined followed by the definition of Boolean value litLED holding a value between 0 and 3 corresponding to the currently lit LED.

The board circuitry is prepared for use in the setup() function. The pin mode for pin INPORT is set to INPUT, which will allow us to read the switch value. The pin mode for pins LED0, LED1, LED2, and LED3 is set to OUTPUT, allowing the program to output HIGH or LOW values to the LEDs. Within setup(), switchState is set to HIGH, the switch is not being depressed, and litLED is set to 0 (zero) indicating that LED0 is lit. We finally turn LED0 on and LED1, LED2 and LED3 off using the digitalWrite() built-in function.

/*
Switch Bounce sketch
Uses four LEDs connected to digital I/O pins 8, 9, 10, and 11 to demonstrate
the effect of a mechanical switch's bounce by lighting the LEDs in sequence
everytime the switch's input goes from high to low.
MIT License
Copyright (c) 2020, Michel Lagace
*/

// Switch value will be read from pin 12
#define INPORT 12
static bool switchState = HIGH; // State of the switch

// LED values will be output on pins 8, 9, 10, and 11
#define LED0 11
#define LED1 10
#define LED2 9
#define LED3 8
static unsigned int litLED = 0; // Currently lit LED

// Setup the board.
void setup() {
  // Set Arduino<s input and output ports
  pinMode(INPORT, INPUT);
  pinMode(LED0, OUTPUT);
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);

  // Initialize switch state and currently lit LED
  switchState = HIGH;
  litLED = 0;
  digitalWrite(LED0, HIGH);
  digitalWrite(LED1, LOW);
  digitalWrite(LED2, LOW);
  digitalWrite(LED3, LOW);
}

// Wait for an edge and return state
bool waitForEdge() {
  bool newValue = switchState;
  while (newValue == switchState) {
    newValue = digitalRead(INPORT);
  }
  switchState = newValue;
  return newValue;
}

// Repeat forever
void loop() {
  // Wait for a rising or falling edge
  bool value = waitForEdge();

  // Increase output on dropping edge (input is LOW when button is pressed)
  if (!value) {
    litLED++;
    if (litLED > 3) {
      litLED = 0;
    }

    // Light up the appropriate LED
    digitalWrite(LED0, litLED == 0);
    digitalWrite(LED1, litLED == 1);
    digitalWrite(LED2, litLED == 2);
    digitalWrite(LED3, litLED == 3);
  }
}

The waitForEdge() function waits until the switch value changes from its previously registered value in variable switchState to a new switch value. The function first copies the value of switchState into the newValue variable and loops, using a while loop, until the value of the switch value read using the digitalRead() built-in function becomes different. Variable switchState is set to the new switch value and the value is returned by the function.

Finally, the main loop() function repeatedly waits for a switch value change through a call to the waitForEdge() function. If the returned value is LOW, that is the push-button has been depressed making the (!value) condition true, we increment the value of litLED by one using the post increment operator ++. The post increment operator increases the value of the variable by one after all other operations in the statement have been evaluated. If the value of litLED is larger than 3 it is set to 0. The last four lines of the loop() function set the LED output pins to HIGH or LOW according to the value of variable litLED. LED0 is lit if variable litLED is 0, LED1 is lit if litLED is 1, and so forth for each LED output pin. Note that the second parameter of the digitalWrite() function takes a Boolean value. To set the digital output to HIGH, the parameter is set to true, and it is set to false to set the digital output to LOW. This allows us to use a Boolean test, such as the equality test, to set the output to the appropriate value.

Observing Switch Bounce

Without mechanical bounce, the circuit and program behaves as follows: every time the switch is depressed, the waitForEdge() function detects a change from HIGH to LOW and returns the value LOW. The main loop() function, because the returned value is LOW increments the value of variable litLED and the next LED is turned on while the currently lit LED is turned off. Each LED is turned on, in sequence, at each switch press.

However, since there is mechanical bounce, every time the switch is pressed, there is more than one HIGH to LOW transition and the litLED variable is incremented one, two, or more times making the program skip LEDs when the switch is depressed. The same phenomenon may happen when the switch is released since mechanical bounce may happen both when the switch is depressed and when it is released. Try to hit the switch several times rapidly to see it happen. Sometimes the LEDs will light in sequence and at other times LEDs will be skipped.

A Switch Debounce Circuit

Following, is the switch bounce demonstration circuit to which I have added a resistor and a capacitor. The resistor, R1, is connected on one side between the pullup resistor and the switch and on the other side to the Arduino’s input pin. The capacitor, C1, is connected between the Arduino’s input pin and ground. The voltage across the capacitor is thus applied to the Arduino’s input pin. The idea is to try to keep the voltage at the input pin LOW when the switch is depressed even though the voltage value at the switch rapidly changes from HIGH to LOW and LOW to HIGH because of mechanical bounce. Similarly, we try to keep the voltage at the input pin LOW when the switch is released for as long as there is electrical noise caused by the mechanical bounce.

As discussed in a previous post, I measured up to 4 ms worth of noise when the micro-switch in this circuit is depressed or released. Since the system is to activate upon the switch being depressed, we want the voltage across the capacitor to drop fairly fast, but remain between 0 and 1 volt, a LOW value, for at least 4 ms. As a rule of thumb, we want the voltage to drop at least 10 times faster than when it comes back up. From the equation of a charging capacitor

VC = V0(1 – e-t/RC)

We want VC to remain between 0 V and 1 V for at least 4 ms. In the previous formula for a charging capacitor, we substitue 1 V for VC, the maximum capacitor value; 5 V for V0, the supply value; 4 ms for t, the maximum time for the capacitor voltage to reach 1 V; C1 for C, the value of the capacitor; and (10 K + R1) for R, the value of the resistor while charging the capacitor. We thus get

1 V = 5 V(1 – e-4 ms/(10 K+R1)C1)

4/5 = e-4 ms/(10 K+R1)C1)

ln 4/5 = -4 ms/(10 K+R1)C1

(10K +R1)C1 = -4 ms/ln 0.8

From the previous formula for a discharging capacitor

VC = V0e-t/RC

We want the capacitor voltage to reach 1 V, the maximum voltage for a LOW, in less than 0.4 ms. So, using the formula for the discharging capacitor, we substitute 1 V for VC maximum capacitor value; 5 V for V0, the initial capacitor voltage; 0.4 ms for t, the maximum time for the capacitor voltage to reach 1 V; C1 for C, the value of the capacitor; and R1 for R, the only resistor through which the capacitor discharges when the switch is depressed. We get

1 V = 5 Ve-0.4 ms/R1C1

1/5 = e-0.4 ms/R1C1

ln 1/5 = -0.4 ms/R1C1

R1C1 = -0.4 ms/ln 0.2

Hence we get two equations

10 K C1 + R1C1 = -4 ms/ln 0.8

R1C1 = -0.4 ms/ln 0.2

Substituting -0.4 ms/ln 0.2 for R1C1 in the first equation, we get

10K C1 – 0.4 ms/ln 0.2 = -4 ms/ln 0.8

C1 = (0.4 ms/ln 0.2 – 4 ms/ln 0.8)/10 K

C1 = 1.7 µF

C1 is approximately equal to 1.7 µF (microfarads). The standard capacitor value closest to 1.7 µF is 2.2 µF. Substituting 2.2 µF for C1 in the second equation, we get

R1 = -0.4 ms/(2.2 µF ln 0.2)

R1 is approximately equal to 112 Ω. The standard resistor value closest to 112 Ω is 100 Ω. In order to cope with a maximum switch bounce of 4 ms, we modify the circuit with C1 set to 2.2 µF and R1 set to 100 Ω. In the following circuit diagram, notice that the capacitor symbol has changed and that the capacitor is now polarized. This is because when capacitors have a value larger than 1 µF we generally use polarized capacitors such as electrolytic capacitors.

The following graph depicts what happens to the mechanical noise after the resistor and capacitor are added to the circuit. In blue, we have the voltage across the switch. Notice the two 1 ms spikes when the switch is depressed and the 1 ms downward spike 3 ms after the switch has been released. In orange, we have the voltage across the capacitor. Notice how all spikes have been flattened below 1 volt and how relatively quickly the voltage drops when the switch is depressed. It takes approximately 0.4 ms for the capacitor to discharge to 1 V when the switch is depressed. Conversely, it takes about 20 ms for the capacitor to charge to 3 volts, a HIGH value, after the switch has been released, limiting the number of successive switch activation to approximately 40 switch closures a second, which is more than acceptable.

The following picture depicts how to connect the different parts of the electronic debounce demonstration circuit using a solderless breadboard, jumper wires, four LEDs, a push button, a 2.2 µF capacitor, a 100 Ω resistor, a 10 K resistor and four 330 Ω resistor. Note that the lead aligned with the negative sign on the capacitor body is connected to ground.

Observing Switch Debounce

With the new circuit, whether there is mechanical bounce or not, the circuit and program behaves as follows: every time the switch is depressed, the waitForEdge() function detects a change from HIGH to LOW and returns the value LOW. The main loop() function, because the returned value is LOW increments the value of variable litLED and the next LED is turned on while the currently lit LED is turned off. Each LED is turned on, in sequence, at each switch press. Because the capacitor eliminates switch bounce, the LED sequence advances by only one LED every time the switch is depressed.

Morse Code Reader

In a previous post, I have shown how to send Morse code. Wouldn’t it be nice if we could read it and convert it back to text? This is what I intend to demonstrate in this blog post. We will see how to program the Arduino to read from a digital input pin and look at how to convert digital on/off information into Morse code sequences of dots and dashes, and then into text. The featured image at the beginning of this blog post is Copyright Museums Victoria / CC BY. The image was downloaded from Museums Victoria.

As mentioned in my previous post, Morse Code Generator, Morse code is a method of transmitting information as a series of on-off tones, lights, or clicks. Basically, letters, numbers, and punctuation marks are translated to a variable length collection of dots and dashes, of shorter and longer bursts of sound or light. The duration of the dot, the short burst, is the unit of time by which all other elements of Morse code are defined. A dash, the long burst, is a signal whose duration is three times that of the dot. The time between dots and dashes within an encoded character is one unit of time. the time between characters is three units of time and the time between words is seven units of time.

International Morse code is thus composed of five elements:

  • short mark, dot or ‘dit’: one time unit long
  • longer mark, dash or ‘dah’: three time units long
  • gap between the dots and dashes within a character: one time unit long
  • gap between letters of a word: three time units long
  • gap between words: seven time units long

Following, is the international Morse code equivalent, in dot (.) and dash (-) notation for each alphabetical and numerical character:

315px-International_Morse_Code.svg

A Question of Timing

Decoding Morse code is all about measuring time. Measuring the amount of time the tone, light or electrical signal is on and the amount of time the tone, light, or electrical signal is off. Take the following diagram depicting the Morse code signal for the text “A TEST”.

Morse Code Timing

It is the on signal time that determines whether a dot or dash was transmitted. As shown above, whenever the signal is on for one unit of time, a dot (or dit) was transmitted and whenever the signal is on for three units of time, a dash (or dah) was transmitted. The off signal time determines if we are within a character being transmitted, if we are between characters, if we are between words, or if we are at the end of a transmission. If the signal is off for one unit of time, we are within a character, if the signal is off for three units of time, we are between characters of a word, if the signal is off for seven units of time, we are between words, and if the signal is off for more than seven units of time, then the transmission is finished.

Measuring Time

In order to measure for how long the signal stays on or off, we must first determine when a signal starts to be on and when the signal starts to be off. In a previous blog post, LED Toggle with a Push-Button Switch, I introduced a piece of code that detects when a signal goes from the off to the on state or from the on to the off state. This piece of code is called an edge detector because it detects the moment the signal changes state, that is the moment an edge (vertical line) occurs in the digital signal. The piece of code introduced was the following:

// Wait for an edge and return state - LED Toggle post
bool waitForEdge() {
  bool startValue = digitalRead(INPORT);
  bool newValue = startValue;
  while (newValue == startValue) {
    newValue = digitalRead(INPORT);
    }
  delay(DEBOUNCE_DELAY);
  return newValue;
}

In the code, we first read the actual value of the signal, then read its value until it changes. Once the value changes, we wait a bit to let the signal settle. The function then returns the last value read. This works except for one thing: we need to detect end-of transmission, which occurs if a low signal lasts for significantly more than seven time units. We need to add a timeout to the logic and try to detect a state change, but only for a certain amount of time. Also, because we need to detect a state change from a previously known state, we want to pass the previous state to the function instead of doing an initial signal read in the function. This will allow for a state change occurring outside the function.

Following is the new detectEdge() function. The first modification to the edge detection function is that three parameters were added to the function: the Arduino digital input port to read from, a timeout value in milliseconds, and a Boolean value, startingState, containing the current state of the signal, on or off. At the beginning of the function body, we declare Boolean variable signalState that will be used to hold the current signal state. We then get the current process time in milliseconds.

Note that signalState is initialized to the startingState value, ensuring that signalState is always valid. It is a good practice to initialize variables upon their declaration as it prevents them from having unknown values. In the following code, if signalState was not initialized, it would be possible to exit the while loop upon a timeout, without ever assigning a value to signalState causing the function to return an unitialized value that may randomly take any value.

// Wait for an edge or timeout.
bool detectEdge(int port, int timeout, bool startingState) {
  unsigned long timerStart = millis();
  bool signalState = startingState;
  while (true) {
    if ((millis() - timerStart) > timeout) {
      break;
    }
    signalState = digitalRead(INPORT);
    if (signalState != startingState) {
      break;
    }
  }
  return signalState;
}

A while(true) infinite loop follows. Within the loop, we first check if we have timed-out and if so, exit the loop. If not, we read the signal value from the input port and if the signal value is different from the starting state, we also exit the loop. The loop continues indefinitely until either a timeout occurs or a signal value change is detected. The current signal state value is returned at the end of the function.

Evaluating Morse Code Time Units

Now that we are detecting edges, we can measure the time a signal remains on or off, true or false. We need to determine if a portion of the signal lasted for 1, 3, or 7 units of time or longer. We could simply take the elapsed time between edges and divide it by the time 1 unit of time takes. This would give us the exact number of time units a portion of the signal lasted, which could be, say, 0.9, 3.1, or 6.8. Intuitively, we can see that 0.9 time units is really 1 time unit, 3.1 is 3 time units, and 6.8 time units is 7 time units. When measuring signal time, we have to allow for a bit of error in the measurement of time or in the signal production. After all, Morse code can be manually produced by a key operator and speed will vary with time.

A solution to this problem could be to round the measured time unit to the nearest integer. Hence, 0.5 to 1.49 time units would be considered to be 1 time unit and 2.5 to 3.49 time units would be considered 3 time units. Now what would we do if we had some portions of signal that were measured to last 2, 4, 5, 6, 8, or 9 time units in this manner? Would we generate an error? Again, intuitively, 4 units of time is a lot closer to 3 units of time than it is to 7 units of time and 6 units of time is closer to 7 units of time. We are less interested in in-between values than we are in deciding if a portion of signal is closer to 1, 3, or 7 units of time, or if it is completely off the scale.

Since we want the number of units of time to be either 1, 3, or 7, we need to define ranges that translate into these values. Let’s use, as a first rule, that the time a portion of signal lasts must be within 50 percent of its nominal value in time units. That is a 1 time unit signal must be between 0.5 and 1.5 time units. For any portion of a measured signal, the measured time T, in time units, must then be within the ranges

1 time unit: 0.5 ≤ T < 1.5
3 time units: 1.5 ≤ T < 4.5
7 time units: 3.5 ≤ T < 10.5

Using this rule, all time measurements that are within 0.5 and 10.5 time units are covered. There is an overlap between 3.5 and 4.5 time units, however. This can be resolved by splitting the difference and specifying the following ranges

1 time unit: 0.5 ≤ T < 1.5
3 time units: 1.5 ≤ T < 4
7 time units: 4 ≤ T < 10.5

Anything below 0.5 time units is thus considered as if there was no signal, or as if there was noise on the line; anything above 10.5 time units is considered a time out, or an end of transmission. The following code translates an elapsed time in Morse code time units according to the rules just stated.

// Convert time in milliseconds to Morse code time units
int timeUnits(int elapsedTime, int timeUnit, int timeout) {
  int value = 15;
  if (elapsedTime < 0.5*timeUnit) {
    value = 0;
  }
  else if (elapsedTime < 1.5*timeUnit) {
    value = 1;
  }
  else if (elapsedTime < 4*timeUnit) {
    value = 3;
  }
  else if (elapsedTime < timeout) {
    value = 7;
  }
  return value;
}

The function timeUnits() takes three parameters: the elapsed time in milliseconds to be converted to Morse code time units; the number of milliseconds corresponding to one Morse code unit of time; and the number of milliseconds corresponding to a timeout. This last parameter could have been set to 10.5 time units in the code, but this timeout value is also used when detecting edges and in order to ensure that values are coherent throughout the code, it is set once and passed as a parameter.

The body of the function starts by assuming that there will be a timeout and sets the number of time units to 15, arbitrarily chosen to represent a timeout. Then the unit time value is set according to where the number of elapsed milliseconds fall within the range of acceptable values. Finally, the function returns the value of time units, either 0, 1, 3, 7, or 15.

Sending Text Back to the PC

Before continuing with Morse code signal analysis and decoding, let’s have a look at a very useful class in the Arduino library: the Serial class. It is used to allow the Arduino board to communicate with a computer or other devices. All Arduino boards have at least one serial port that communicate on digital pins 0 and 1 as well as with a computer through the board’s USB connector. You cannot use pins 0 and 1 for input/output and serial communication at the same time.

The Arduino Serial class supports several methods to configure, send and receive information from the serial port. In this project, I made use of three of these methods:

  • The begin() method is used to initialize the serial port with serial communication speed, called a baud rate, and communication options including the number of data bits, from 5 to 8; the parity type, odd, even, or none; and the number of stop bits.
  • The print() method sends ASCII characters corresponding to the method argument. If the argument is a String, the characters of the string are sent as is. If the argument is an integer or a floating-point number, the numerical value is first converted to a string and then sent as ASCII characters.
  • The println() method behaves the same as the print() method, but appends a carriage return character (ASCII 13 or ‘\r’) and a new line character (ASCII 10 or ‘\n’) to the information sent.

Converting Morse Code to Text

Now that we measure time and are evaluating the number of time units the signal is on or off, we can convert the signal to Morse code and then to text. First, we declare global information used throughout the code. In the following code snippet, we set the Morse code signal to be read from the Arduino’s board pin 8, INPORT. We set the unit of time, TIME_UNIT, to 50 milliseconds, corresponding to the CODEX speed standard of 20 Words Per Minute (WPM), and we set the timeout value, READ_TIMEOUT, at 10.5 times unit of time. There are three global variables used to hold information between calls to the loop() function: currentCode holds a Morse code character being read, currentLine holds the text string being decoded from Morse code, and currentState holds the current state of the signal being decoded.

/*
 Morse Code Reader
 Program that reads Morse code and outputs decoded information to the
 Serial interface. It is associated with the Morse Code decoder blog
 post on https://lagacemichel.com
 MIT License
 Copyright (c) 2019, Michel Lagace
 */

 define INPORT 8
 define TIME_UNIT 50
 define READ_TIMEOUT 10.5*TIME_UNIT
 String currentCode;
 String currentLine;
 bool currentState;

The next code snippet contains the setup() function that initializes the digital input pin to receive the Morse code signal, initializes global variables, and sets up the serial communication to communicate with the PC at 9600 baud, or approximately 10 characters per second.

// Setup the board.
 void setup() {
   pinMode(INPORT, INPUT);
   currentState = false;
   currentCode = "";
   currentLine = "";
   Serial.begin(9600);
 }

The decodeSequence() function converts a series of dots and dashes representing the signal sequence of a Morse code character into the equivalent ASCII character. In the code below, we first initialize a String, characters, that contains the collection of all supported characters, from ‘a’ to ‘z’ and from ‘0’ to ‘9’. We then have an array of String objects, codedCharacters, that contains the Morse code corresponding to each character in the first String. The decodeSequence() function takes one parameter, sequence, a String containing the Morse code to decode. The function returns a single ASCII character corresponding to the decoded Morse code signal. It starts by initializing character, the variable containing the decoded Morse code to an asterisk. This is the character returned if the Morse code cannot be found in codedCharacters. It loops through the list of Morse codes in codedCharacters comparing each one to the sequence to be decoded. If found, the index of the sequence found is used to identify the character in characters. The character found or an asterisk is returned at the end of the function.

// List of valid alphanumeric characters
static String characters = "abcdefghijklmnopqrstuvwxyz0123456789";

// Morse code sequences for each alphanumeric character
static String codedCharacters[] = { 
   ".-", "-…", "-.-.", "-..", ".", "..-.", "--.", "….",
   "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.",
   "--.-", ".-.", "…", "-", "..-", "…-", ".--", "-..-",
   "-.--", "--..", "-----", ".----", "..---", "…--",
   "….-", "…..", "-….", "--…", "---..", "----."};

 // Convert Morse code signal to a character
 char decodeSequence(String sequence) {
   int i;
   char character = '*';
   for (i = 0; i < sizeof(codedCharacters)/sizeof(String); i++) {
     if (codedCharacters[i] == sequence) {
       character = characters[i];
       break;
     }
   }
   return character;
 }

The Main Loop

The following piece of code contains the loop() function of the program. It waits for a signal’s edge, accumulates Morse code dashes and dots, decodes the dots and dashes into characters and assembles the characters into words to form a sentence. The loop() function is split into three sections: the edge detection section, the falling edge processing section and the rising edge processing section.

// This is the main loop. It monitors INPORT digital IO
// pin for Morse code, decodes the signal and sends decoded
// text to the attached PC.
void loop() {
  // Wait for a rising edge, falling edge, or time out
  unsigned long startTime = millis();
  bool previousState = currentState;
  currentState = detectEdge(INPORT, READ_TIMEOUT, previousState);
  int elapsedTimeUnits =
    timeUnits(millis() - startTime, TIME_UNIT, READ_TIMEOUT);

  // On a falling edge, append dot or dash to character
  if (previousState && !currentState) {
      if (elapsedTimeUnits == 1) {
        currentCode += ".";
      }
      else if (elapsedTimeUnits == 3) {
        currentCode += "-";
      }
  }

  // On a rising edge or time out, handle end of
  // character, word, or end of transmission. 
  else if (currentCode != "") {
    if (elapsedTimeUnits > 1) {
       currentLine += decodeSequence(currentCode);
      currentCode = "";
    }
    if (elapsedTimeUnits > 3) {
      currentLine += ' ';
    }
    if (elapsedTimeUnits > 7) {
      Serial.println(currentLine);
      currentLine = "";
    }
  }
}

In the edge detection section, we save the current time since the program started in milliseconds using the millis() function provided by the system. We then call the detectEdge() function which returns the current signal state after detecting an edge or a timeout. We measure the elapsed time in milliseconds by subtracting the current time since the program started from the time saved before detecting the edge. The time in milliseconds is converted to Morse code time units through a call to the timeUnits() function.

In the second section, if the previous signal state was on and the new state is off, we have a falling edge. Any other signal condition is either a rising edge or a timeout. In the case of a falling edge, if the on signal lasted for 1 time unit, add a ‘dot’ to currentCode. If the on signal lasted for 3 units of time, add a ‘dash’ to currentCode. Do not do anything if the signal lasted for any other amount of time.

In the third section, we process information only if some dots and dashes have been accumulated in the currentCode variable. If the off signal lasted 1 unit of time or less, do nothing and continue accumulating dots and dashes in the currentCode variable. If the off signal lasted for more than 1 time unit, decode the dots and dashes sequence into its corresponding character and add it to the sentence being formed; reset the sequence of dots and dashes. In addition, if the off signal lasted more than 3 time units, add a space character to the sentence. Finally, if the off signal lasted more than 7 time units, return the decoded sentence to the serial line through a call to Serial.println() and reset the current line.

Putting it All Together

Let’s use the same setup as was used in my previous post Raspberry Pi Speaks Arduino. Connect the different parts using a solderless breadboard, jump wires, a BC337-40 transistor, and a 10K, a 220K, and a 150K resistor as depicted. Through one of the breadboard’s ground rail, we connect the Raspberry Pi and Arduino’s ground pins. The Raspberry Pi’s 3.3-volt supply is connected to the transistor’s base through the 220K resistor. The Raspberry Pi’s GPIO4 output is connected directly to the transistor’s emitter. The Arduino’s 5-volt supply is connected to the transistor’s collector through a 10K resistor. The Arduino’s digital input 8 is connected directly to the transistor’s collector.

Get the Python and Arduino code for the Morse Code Reader demonstration by accessing the MorseCodeReader repository on Github at https://github.com/lagacemichel/MorseCodeReader and download a copy of MorseCodeTutorial.py on the Raspberry Pi and a copy of MorseDecodeTutorial.ino on your PC. On the PC, using the Arduino IDE, load the MorseDecodeTutorial.ino file and load it on the Arduino board. On the Raspberry Pi, run one of the Python 3 IDEs, load the MorseCodeTutorial.py file and run it.

On the PC, within the Arduino IDE, press the control, shift, and M keys simultaneously. This will make the Serial Monitor window appear. As the Python program on the Raspberry Pi sends Morse code signals, the Arduino board decodes it until an end of transmission is detected, then it sends the text, through the serial port, back to the PC. The text is displayed on the Serial Monitor window and the output should look like the following:

We have just demonstrated Morse code sent by a Raspberry Pi Python program, received by an Arduino digital input pin through a level shifting transistor, decoded back to text within the Arduino and sent to the PC for display through the serial communication port.

Raspberry Pi Speaks Arduino

For a project of mine, I needed to connect a Raspberry Pi’s output to an Arduino Uno’s input. The Raspberry Pi’s digital input/output uses 3.3-volt logic while the Arduino Uno digital input/output uses 5-volt logic. How does one convert from one logic level to the other? There are several ways to do this and most methods use a transistor switch controlled by 3.3-volt logic to drive 5-volt logic.

We must first define what constitutes a HIGH and a LOW on the Arduino Uno and the Raspberry Pi. For the Arduino Uno, a digital pin, whether input or output, is considered in a LOW state for voltages below 1.5 volts and in a HIGH state for voltages between 3 and 5 volts. The Raspberry Pi’s digital input and output pins are considered in a LOW state for voltages below 1 volt and in a HIGH state for voltages between 2 and 3.3 volts.

From 3.3 Volts to 5 Volts

In order to convert the Raspberry Pi’s digital output voltage levels to the Arduino’s digital input levels, we can use a transistor switch, similar to the one used to control a relay in a previous post. I will describe two transistor circuits: the logic inverter and the level converter using the transistor’s emitter as input.

Logic Inverter

In the circuit below, we want that if the input voltage is between 0 and 1 volt, the transistor be in cutoff mode, completely off, and the output voltage be between 3 to 5 volts. We also want that if the input voltage is between 2 and 3.3 volts, the transistor be in saturation mode, completely on, and the output voltage be between 0 to 1.5 volts. In other words, if the input is LOW, the output will be HIGH and if the input is HIGH, then the output will be LOW. This circuit is called a logic inverter. It converts logic levels but it inverts logical values. Because the input is considered LOW at or below 1 volt and HIGH at or above 2 volts, we want for the transistor to go from cutoff to saturation mode between input values of 1 volt and 2 volts. To ensure that LOW and HIGH values are met, let’s make the transistor be in cutoff mode for input values at of below 1.4 volts and let it be in saturation mode for values at or above 1.6 volts.

3.3V to 5V Inverted Transistor General

In cutoff mode, there is no transistor base current and the base-emitter voltage (Vbe) is at or below 0.7 volts. Hence, the current in Rbase and Rbias are the same and the voltage across Rbias is at or below 0.7 volts. When the input voltage is 1.4 volts, the following equation holds.

Vbe / RBias = (Vin – Vbe) / RBase

0.7 V / RBias = (1.4 V – 0.7 V) / RBase = 0.7 V / RBase

RBias = RBase

In saturation mode, the voltage across the collector and emitter is 0 volts and collector current is constant at Vcc / RLoad. We want the collector current to be large enough for the transistor to work properly, but small enough to consume the least power possible. Using a 10K resistor, the collector current is 5 V / 10K, or 0.5 mA which is within the BC337-40 bipolar junction transistor working values. According to its specification, the hfe value, the collector-base current amplification, is 400. The base current is thus

Ib = Ic / hfe

Ib = 0.5 mA / 400 = 1.25 μA

The current through RBase is equal to the currents through both RBias and the transistor’s base.  When the input voltage is 1.6 volts, the following equation holds.

(Vin – Vbe) / RBase = Vbe / RBias + Ib

(1.6 V – 0.7 V) / RBase = 0.7 V / RBias + 1.25 μA

Since, as we have seen above, RBias has the same value as RBase, the equation becomes

0.9 V / RBase = 0.7 V / RBase + 1.25 μA

RBase = RBias = 160K ≅ 220K

The circuit becomes

3.3V to 5V B input 2 base resistors Values

The following graph plots the input and output computed voltage values, in blue, and the values measured on an actual circuit implemented with the resistor values in the schematic above, in orange.

3.3V to 5V Biased Base Input

As can be seen, logical values are inverted and voltage values correspond to acceptable values for both the Raspberry Pi and Arduino’s HIGH and LOW voltage levels. Notice the shift of the target voltage values slightly to the left of 1.5 volts. This is caused by the fact that we are not using the exact resistor values as computed, because the saturated Vbe is not exactly 0.7 volts and the actual hfe is not exactly 400. Still, the results are more than acceptable, if we wanted an inverted signal.

Using the Emitter as Input

Let’s use the same circuit, but connect the base resistor to the Raspberry Pi’s supply, VccP, and the emitter to the Raspberry Pi digital output as in the following circuit.

3.3V to 5V Converted Transistor General

This is like rotating the previous circuit clockwise by 90 degrees. Now we want that if the input voltage is LOW, below 1 volt, that the transistor be in saturation and the output be at Vin, the Raspberry Pi digital output value.  We also want that if the input voltage is HIGH, at 2 volts or higher, that the transistor be in cutoff and the output be at the Arduino’s supply voltage. Because the permissible voltages for the LOW value on the Arduino, between 0 and 1.5 volts, are higher than those for the Raspberry Pi, between 0 and 1 volt, it is acceptable to use the Raspberry Pi digital output voltages as the Arduino’s input for LOW values.

In this circuit, as it was in the previous one, in cutoff mode there is no transistor base current and the base-emitter voltage is at or below 0.7 volts. Hence, the current in RBase and RBias are the same and the voltage across RBias is at or below 0.7 volts. Hence, when the input voltage is 1.6 volts, the following equation holds.

(VccP – Vbe – Vin) / RBase = Vbe / RBias

(3.3V – 0.7V – 1.6) / RBase = 0.7V / RBias

RBias = 0.7 • RBase

For saturation mode, the same reasoning as for the logic inverter holds about the base current to collector current relationship. Since we want saturation of the transistor to occur at 1.4 volts, the collector current is equal to the current going through RLoad.

Ic = (VccA – Vin) / RLoad

Ic = (5V – 1.4V) / 10K = 0.36 mA

The base current is thus

Ib = Ic / hfe

Ib = 0.36 mA / 400 = 0.9 μA

The current through the base is the current through RBase minus the current through RBias. When the input voltage is 1.4 volts, when we want transistor saturation to occur, the following equations hold.

Ib = (VccP – Vbe – Vin) / RBase – Vbe / RBias

0.9 μA = (3.3V – 0.7V – 1.4V) / RBase – 0.7V / RBias

Since, as we have seen above, RBias is 0.7 • RBase, we have

0.9 μA = (3.3V – 0.7V – 1.4V) / RBase – 0.7V / (0.7 • RBase)

0.9 μA = 1.2V / RBase – 1V / RBase

RBase = 0.2V / 0.9 μA = 222.222K ≅ 220K

We then compute RBias from the cutoff equations

RBias = 0.7 • RBase = 155.556K ≅ 150K

The circuit becomes

3.3V to 5V E input 2 base resistors Values

The following graph plots the input and output computed voltage values, in blue, and the values measured on an actual circuit implemented with the resistor values in the schematic above, in orange.

3.3V to 5V Biased Emitter Input

In the graph, logical values are converted to the same levels this time, and voltage values correspond to acceptable values for both the Raspberry Pi and Arduino’s HIGH and LOW voltage levels. Notice the shift of the target voltage values slightly to the right of 1.5 volts. This is caused by the fact that we are not using the exact resistor values as computed, because the saturated Vbe value is not exactly 0.7 volts and the actual transistor current amplification is not exactly 400. Nevertheless, results are more than acceptable.

Putting it Together

Now that we have a method to convert 3.3-volt logic to 5-volt logic, we can connect a Raspberry Pi 3 GPIO output to an Arduino digital input. As a test, we will program the Raspberry Pi to output Morse Code, read the output using an Arduino digital input and output the value read to the Arduino’s built-in LED. The code within this post can be found on GitHub by clicking here.

Breadboarding

First, let’s have a look at the complete circuit. The following picture depicts how to connect the different parts using a solderless breadboard, jump wires, a BC337-40 transistor, and a 10K, a 220K and a 150K resistor. Through one of the breadboard’s ground rail, we connect the Raspberry Pi and Arduino’s ground pins. The Raspberry Pi’s 3.3-volt supply is connected to the transistor’s base through the 220K resistor. The Raspberry Pi’s GPIO4 output is connected directly to the transistor’s emitter. The Arduino’s 5-volt supply is connected to the transistor’s collector through a 10K resistor. The Arduino’s digital input 8 is connected directly to the transistor’s collector.

3.3to5_bb

Morse Code Generator in Python

We have seen the morse code generator in a previous post. I have ported the Morse Code program to Python 3 for it to be run on the Raspberry Pi. I used IDLE (Integrated Development and Learning Environment) to code and run the Python 3 code on the Raspberry Pi. If you are new to the Raspberry Pi and want to set it up, please visit “Get started with Raspberry Pi” on the www.raspberrypi.org site.

# Morse Code Generator Python Program that translates a
# text string to Morse Code and outputs it to a GPIO pin.
# This program is used as part of a demonstration to show
# connectivity between a Raspberry Pi and an Arduino UNO.
# It is associated with the Raspberry Pi Speaks Arduino
# blog post on https://lagacemichel.com
# MIT License
# Copyright (c) 2019, Michel Lagace

import RPi.GPIO as IO
import time
UNIT_TIME = 0.1
BOARD_PIN = 7
# Characters to be encoded
characters = "abcdefghijklmnopqrstuvwxyz"

# Morse code sequences for each character
codedCharacters = [
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....",
"..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.",
"--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-",
"-.--", "--.." ]

# Setup the board. Digital port LED_BUILTIN in output mode
def setup():
    IO.setwarnings(False)
    IO.setmode (IO.BOARD)
    IO.setup(BOARD_PIN,IO.OUT)

# Function to output a dot: one unit on, one unit off
def outputDot():
    IO.output(BOARD_PIN,1)
    time.sleep(UNIT_TIME)
    IO.output(BOARD_PIN,0)
    time.sleep(UNIT_TIME)

# Function to output a dash: three units on, one unit off
def outputDash():
    IO.output(BOARD_PIN,1)
    time.sleep(UNIT_TIME*3)
    IO.output(BOARD_PIN,0)
    time.sleep(UNIT_TIME)

# Function to output a single character
def outputCharacter(c):
    # Find index of character to encode
    index = characters.find(c)
    # Ignore unencodable characters
    if (index >= 0):
        # Encode morse codeand output it
        code = codedCharacters[index]
        for ch in code:
            if ch == '-':
                outputDash()
            else: # if not '-', must be '.'
                outputDot()
        # Wait 3 units at the end of the letter
        # (2 units assuming previous dot or dash)
        time.sleep(UNIT_TIME*2)

# Function to encode a whole string
def sentence (text):
    # Output each character in turn
    for ch in text:
        # Only lower-case characters are encoded
        if ch != ' ':
            outputCharacter(ch.lower())
        # Spaces are encoded as 7 units,
        # (4 units assuming a previous character)
        else:
            time.sleep(UNIT_TIME*4)

# Function looped indefinitely
def loop():
    sentence("Mikes Electro Shack")
    time.sleep(UNIT_TIME*25) # Wait 4 spaces at the end

# Main program. Setup board then loop forever
setup()
while(True):
    loop()

Raspberry Pi Reader on the Arduino

The code on the Arduino is quite simple. It loops forever, reading the digital input connected to the Raspberry Pi, writes the value to the built-in LED, then waits 10 milliseconds befor starting over.

// Main RaspberryPiReader sketch
// Program that reads a digital input from a Raspberry Pi
// GPIO pin and outputs its value to an Arduino digital output
// pin. This sketch is used as part of a demonstration to show
// connectivity between a Raspberry Pi and an Arduino UNO.
// It is associated with the Raspberry Pi Speaks Arduino
// blog post on https://lagacemichel.com
// MIT License
// Copyright (c) 2019, Michel Lagace

// Define input and output ports
#define INPORT 8    // Input port connected to Raspberry Pi
#define OUTPORT 13  // Output port to built-in 

// Time to wait in milliseconds between samples
#define SAMPLE_DELAY 10

// Setup the board.
void setup() {
    pinMode(INPORT, INPUT);
    pinMode(OUTPORT,OUTPUT);
    digitalWrite(OUTPORT,LOW);
}

// Repeat forever
void loop() {
    // Read value from Raspberry Pi
    bool value = digitalRead(INPORT);
    
    // Output value to built-in LED
    digitalWrite(OUTPORT, value);
    
    // Wait before next sample
    delay(SAMPLE_DELAY);
}

Download the sketch on the Arduino then run the Python program on the Raspberry Pi. You will notice Morse Code being output by the Arduino’s buit-in LED.

Programming with Class

In this project we will use a Red-Green-Blue (RGB) LED to create color sequences, morphing or stepping from one color to the next. A push-button will allow the user to switch the color sequence being displayed. RGB LEDs are used in appliances to create moods by illuminating translucent materials with varying colors. Let’s first examine the characteristics of an RGB LED.

RGB LED

RGB LEDs consist of three LEDs, one red, one green, and one blue contained in a single translucent or transparent package. Each color is controlled separately, allowing a wide gamut of colors to be created by varying the amount of electrical current flowing through each of the LEDs. The following diagram shows the RGB LED schematic and its pinout. The RGB LED portrayed is a common cathode RGB LED. Common anode RGB LEDs are also available.

RGB LED Schematic

Apart from its packaging, the RGB LED works as if it was three independent LEDs and each can be wired in a circuit the way it was done in the ‘Blink’ circuit. The following diagram shows the circuit used for this project. It contains an RGB LED as well as a push-button that will be used to switch between color sequences. Note that we are using three resistors each separately limiting current in each of the LED. If we would have put a single resistor at the common cathode junction of the LEDs, the LEDs would have still lit, but their brightness would have been impacted by other LEDs being activated or not.

The RGB LED Circuit

RGB LED Circuit

Digital I/O pin 9 is connected to the red LED; digital I/O pin 10 is connected to the green LED; and digital I/O pin 11 is connected to blue LED. All connections are made through 330Ω resistors placed between the digital I/O pin and the anode of each light emitting diode. Digital I/O pin 12 is connected to a push button that is connected to ground when depressed. A 10K pullup resistor is connected between the digital I/O pin and the 5-volt supply, ensuring a HIGH input value when the push button is not depressed.

You might have noticed the ’tilde’ character, ‘~’, associated with some of the Arduino’s digital I/O pins. These digital I/O pins are special in that not only do they support digital output as HIGH and LOW values, but they also support analog output values. Instead of being output as a constant voltage value, analog values at these pins are output as a train of on and off pulses. The ratio between on and off is determined by the analog value. This is called pulse width modulation (PWM). In the Arduino, analog output values are discrete values between 0 and 255. For an analog value of 0, the output is always off; for an analog value of 64, the output is 25% on, 75% off; for an analog value of 128, the output is 50% on and off; and for an analog value of 255, the output is always on. The brightness of each LED is proportional to the amount of on time at the digital output. The frequency of the signal at pins 9, 10, and 11 on the Arduino Uno is approximately 490Hz.

Breadboarding

The following picture depicts how to connect the different parts using a solderless breadboard, jump wires, an RGB diode, a push button, a 10K resistor and three 330Ω resistors.

Programming with Class_bb

The RGB LED Program

The purpose of the program is to display a show of colors using the RGB LED. We want the LED to change colors following a sequence of pre-selected colors. The color change can be abrupt, or gradual, depending on the sequence. Because it would be arduous to recreate the code from the code snippets in this blog, I have created a Programming with Class repository on the Git Website. Simply click on the link, then on the “Clone or Download” button to download a zipped version of the files and copy them in a folder named “ProgrammingWithClass” on your computer. You can then load the “ProgrammingWithClass.ino” sketch in the Arduino Integrated Development Environment (IDE).

The program in this project can be fetched from the  Programming with Class repository on the Git Web site.

The Main Sketch

As usual, the Arduino sketch file starts with a comment stating the purpose of the program, the author and copyright notice. The program is licensed under the standard MIT license.

Next, we include two files: “rgbLed.h” and “rgbColor.h“. These files declare the rgbLed and rgbColor classes. We will cover the content of these files, what classes are, and how to program them later in this post. For now, let us simply accept that these classes exist and that we can create variables with them which I will describe shortly.

/*
Main ProgrammingWithClass sketch
Program that outputs sequences of colors to an RGB LED. The
sequence shown can be selected by a push button. This sketch
demonstrates the use of classes. It is associated with the
Programming with Class blog post on https://lagacemichel.com
MIT License
Copyright (c) 2018, Michel Lagace
*/
#include "rgbLed.h"
#include "rgbColor.h"

We have already seen an example of a class: the String class. The String class comes with the Arduino system. It allows programmers to easily work with character sequences. We already have used String classes as part of the Morse Code Generator project. One characteristic of variables created with classes is that they encapsulate, that is hide, the data that they contain. A String variable certainly contains a sequence of characters and possibly a length value, or maybe it is implemented using a null-terminated character sequence. We do not know, unless we actually look at the code implementing the String class and truly, we do not care as long as we can operate on a String. Another characteristic of variables created from classes is that we can call functions, called methods when associated to classes, directly acting upon the variable. As an example from the Morse Code Generator project, we use the length() method associated with a String as follows:

String text = "abcd";
int length = text.length();

In the code above, we create a variable text of type String, the name of the class, and initialize it with the character string “abcd”. We then create an integer variable called length and initialize it with the length, the number of characters, contained in the text variable using the length() method. The method is called by appending the variable name with a period (‘.‘) then the name of the method followed by parenthesis. Methods behave very similarly to functions and there could be parameters within the parenthesis following the method name if required. Notice the strength of variables created from classes as they behave almost like data types.

Variables created from classes are also called ‘objects‘. Along with a set of rules, design patterns, and best practices, objects are at the root of Object-Oriented Programming. Arduino software is written in C++, an object-oriented programming language that also allows C-language constructs. In the next section we will see how to use the rgbColor and rgbLed objects. Later in the blog, we will see the details of the implementation of these classes.

The rgbColor and rgbLed Classes

The rgbColor class allows programmers to create color objects based on the red, green and blue emissive color triad. Users can create a rgbColor object using the three colors or simply by declaring a rgbColor variable without stating values. rgbColor object declaration has the following forms: rgbColor color(float red, float green, float blue) or rgbColor color. Following are examples of rgbColor object declarations.

rgbColor red(1.0, 0.0, 0.0);
rgbColor black;

In the first case, a red object is created by setting the red component to 1.0 and the green and blue components to 0.0. Color components are set to floating point values between 0.0 and 1.0, from completely off to completely on. A rgbColor object can also be created without any component, in which case all components are set to 0.0, corresponding to black. rgbColor objects can also be created from other rgbColor objects, or by assigning a rgbColor object to another.

rgbColor darkRed(0.25, 0.0, 0.0);
rgbColor anotherRed(red);
anotherRed = darkRed;

rgbColor object anotherRed is created as a copy of object red, then assigned the value of just created rgbColor object darkRed. rgbColor also sports three methods to obtain the values, between 0.0 and 1.0, of each color component making up the color stored in a rgbColor object. The following depicts these methods.

float r = darkRed.red();
float g = darkRed.green();
float b = darkRed.blue();

Methods, as shown above, are function names following an object name, separated by a period (‘.‘). The rgbColor class supports one more method, the gradate() method.

rgbColor blue(0.0,0.0,1.0);
rgbColor green(0.0,1.0,0.0);
rgbColor cyan;
cyan.gradate(blue,green,0.5);

The gradate() method assigns to the rgbColor object a color between the first and second color specified, scaled according to the third function parameter, the gradient. The gradient specifies the amount mixed from each color according to the formula

Color = (1.0 – Gradient)•Color1 + Gradient•Color2

A value of 0.0 makes the resulting color equal to the first color, C1; a value of 1.0 makes the resulting color equal to the second color, C2; a value between 0.0 and 1.0 makes the resulting color a combination of both colors according to the gradient specified.

The program uses objects from a second class, the rgbLed class. Each rgbLed object represents an external RGB LED and the actual Arduino pins associated with it. There is no default value possible and three PWM capable digital output pins must be specified to create a rgbLed object.

rgbLed device(8,9,10);

In this line of code, a rgbLed variable is created for an RGB LED whose red LED is connected to the Arduino’s digital pin 8, whose green LED is connected to digital pin 9 and whose blue LED is connected to digital pin 10. The rgbLed class supports only one method, the set() method which assigns a rgbColor object to the rgbLed object.

rgbColor orange(1.0,0.2,0.0);
rgbLed device(8,9,10);
device.set(orange);

In this code snippet, we create the orange rgbColor object, the rgbLed device object representing the external RGB LED attached to the Arduino pins 8, 9 and 10, and we set the external RGB LED to light up orange.

Next in the Main Sketch

Back to the program, we define the RGB LED digital output pins, then the push-button digital input pin. We then set constants to cycle through colors in 10 seconds in 1000 steps, 10 milliseconds per step. We then initialize the global variable that keeps track of the color cycle step.

Next, we use a construct that was never used before, the enumerated type. Enumerated types declare sequences of value names making up an enumeration of things. Enumerated types are themselves named, allowing variables to be created using the enumerated type name. Enumerated types are declared using the enum reserved word followed by the enumerated type name, followed by an enumeration of names separated by commas and enclosed in curly brackets. The whole construct is followed by a semicolon.

In the code below, an enumerated type called colorScheme has the following enumerated value names: nothing, wheel, rainbow, all, and white. In Arduino’s memory, enumerated type value names are encoded as integers starting at 0. Hence, setting a colorScheme variable to rainbow will assign it the value 2 and setting a colorScheme variable to white will assign it the value 4. We added an extra value at the end of the enumeration, numberOfSchemes. It gets assigned the value 5, which is the number of actual color schemes in the list. Following the enumeration is a variable declaration making scheme a colorScheme variable initialized with nothing.

// RGB LED pins
#define REDPIN   9
#define GREENPIN 10
#define BLUEPIN  11

// Pushbutton pin
#define PUSHBUTTON 8

// Constants used to control timings
const int colorCycle = 1000;
const float cycleTime = 10.0;
const int stepTime = cycleTime/colorCycle*1000;
// Color step counter
static int colorStep = 0;

// Color scheme names
enum colorScheme {nothing,
                  wheel,
                  rainbow,
                  all,
                  white,
                  numberOfSchemes
};
static colorScheme scheme = nothing;

//Push Button current value
bool pushButton = HIGH;

// Global LED object
static rgbLed led(REDPIN,GREENPIN,BLUEPIN);

// Array of color sequences made of rgbColor objects
static const rgbColor wheelColors[] = 
  {redColor,yellowColor,greenColor,cyanColor,blueColor,magentaColor};
static const rgbColor rainbowColors[] = 
  {redColor,orangeColor,yellowColor,greenColor,blueColor,
   purpleColor,blackColor};
static const rgbColor allColors[] =
  {redColor,greenColor,blueColor,yellowColor,cyanColor,magentaColor,
   orangeColor,purpleColor,whiteColor,blackColor};

Following the enumerated type variable is a Boolean variable holding the current input value of the push-button. It is initialized to HIGH. As in the Toggle Push-Button blog post, we are interested in the moment the button is pushed as it goes from HIGH to LOW. After, we declare the variable led representing the external RGB LED connected to three Arduino digital output pins that support pulse width modulation. Then, we declare constant arrays of rgbColor values, three in all. Each array corresponds to a color sequence we want displayed at the RGB LED. The color values such as redColor and cyanColor are constants that are declared within the rgbColor class header file. The following RGB color constants have been declared:

  • redColor
  • greenColor
  • blueColor
  • yellowColor
  • cyanColor
  • magentaColor
  • orangeColor
  • purpleColor
  • whiteColor
  • blackColor

Computing the Color of the RGB LED

The main Arduino sketch contains two functions: glideThrough() and stepThrough(). Both functions take two parameters, the color array containing the sequence of colors to glide or step through, and an integer containing the number of colors in the array.

In these functions, each color gradient, or solid color depending on the function, is displayed for the same amount of time, depending on the number of colors in the array. The gradientSpan variable contains the number of steps out of the total number of steps to spend between two colors.

For the glideThrough() function, we select the index of the colors between which we will gradually morph. The first color is selected according to the current step in the color cycle. The second color is the next color in the array. If we have reached the end of the color array, the second color is set to the first color in the array, starting a new color cycle. This is done using the modulo operator ‘%‘, that provides the remainder of the division with the number of values in the array. The gradient value is set to a value between 0.0 and 1.0 corresponding to the step position between the two colors we want to blend, or glide through. Finally, we compute the color blend using the gradate() method of a rgbColor object and then assign the rgbColor object to the rgbLed led object using the set() method.

For the stepThrough() function, we select the color to display according to the current step in the color cycle. We create a rgbColor object initialized with the appropriate color in the color array and assign the rgbColor object to the rgbLed led object using the set() method.

// Glide through colors
void glideThrough(rgbColor colorArray[],int arraySize) {
  // Initialize color gradients
  float gradientSpan = (float)colorCycle/(float)arraySize;

  // Compute color array indices and gradient between colors
  int gradateFrom = colorStep/gradientSpan;
  int gradateTo = (gradateFrom + 1)%arraySize;
  float gradient = colorStep/gradientSpan - (float)gradateFrom;

  // Gradate between the two colors
  rgbColor color;
  color.gradate(colorArray[gradateFrom],colorArray[gradateTo],gradient);
  led.set(color);
}

// Step through colors
void stepThrough(rgbColor colorArray[],int arraySize) {
  // Initialize color gradients
  float gradientSpan = (float)colorCycle/(float)arraySize;

  // Compute color array indeces and gradient between colors
  int colorIndex = colorStep/gradientSpan;

  // Set the color according to time
  rgbColor color(colorArray[colorIndex]);
  led.set(color);
}

Main Sketch Setup

In the setup() function of the main sketch, we setup the push-button digital input pin. The RGB LED digital output pins are setup as part of the rgbLed object construction that we will see later in the post.

// Setup push button pin
void setup() {
  // Setup pin modes
  pinMode(PUSHBUTTON,INPUT);
}

Main Sketch Loop

The first thing we do in the sketch’s loop() function is to display colors according to the selected scheme. This is done through a series of if else statements, selecting the appropriate scheme. If scheme is nothing or white, the RGB LED is set to black or white respectively. If scheme is wheel or rainbow, the RGB LED is set to gradually morph from one color to the next of the wheelColors and rainbowColors respectively. If scheme is all, the RGB LED steps through all available color values. Anything else is a programming error and would set the RGB LED to a solid red color.

Next, we check if the push-button has changed state. If so, and if the old value is HIGH, making the transition from HIGH to LOW, we select the next color scheme. If scheme goes beyond the number of schemes, it is reset to nothing. Upon change of the color scheme, the color step is reset to zero. Upon a change of state of the push-button, the old push-button value is set to the new one.

Finally, we wait the computed step duration before starting the next step. This delay also serves as a de-bounce delay. We then increment the color step and cycle back to zero if the we have reached the end of the color cycle.

// Main loop. Repeatedly through color sequences
void loop() {
  // Output color according to selected scheme
  if (scheme == nothing) {
    led.set(blackColor);
  }
  else if (scheme == wheel) {
    glideThrough(wheelColors,sizeof(wheelColors)/sizeof(rgbColor));
  }
  else if (scheme == rainbow) {
    glideThrough(rainbowColors,sizeof(rainbowColors)/sizeof(rgbColor));
  }
  else if (scheme == all) {
    stepThrough(allColors,sizeof(allColors)/sizeof(rgbColor));
  }
  else if (scheme == white) {
    led.set(whiteColor);
  }
  else { // Program error, this should never happen
    led.set(redColor);
  }
  // Detect if button is going from HIGH to LOW. If so, select next
  // color scheme
  if (digitalRead(PUSHBUTTON) != pushButton) {
    if (pushButton) {
      colorScheme = colorScheme + 1;
      if (colorScheme >= numberOfSchemes) {
        colorScheme = nothing;
      }
      colorStep = 0; 
    }
    pushButton = !pushButton;
  }

  // Wait a bit, then proceed to next color step
  delay(stepTime);
  colorStep++;
  if (colorStep >= colorCycle) {
    colorStep = 0;
  }
}

Object-Oriented Programming

Classes are at the heart of object-oriented programming. They allow programmers to combine data structures, attributes, with code constructs, methods. It allows information collections to be treated as data types. This what we will see in the next sections. We will describe two classes, rgbColor and rgbLed. In C++, classes are declared in header files and code is developed in class implementation files. The header file, ending with the .h file type, contains the interface to objects created from the class while the implementation file, ending with the .cpp file type, contain the code of the different methods associated with objects of the class.

In order to act as data types, classes need to define a few methods that are called whenever an object is created, deleted, or assigned to. If not provided, C++ creates these methods by default. These methods are

  • the default constructor
  • the destructor
  • the copy constructor
  • the assignment operator

Classes that systematically declare these methods are said to follow the orthodox canonical class form. The default constructor is called whenever a new object is created from the class without any parameter or initial values. Its syntax is simply the name of the class followed by parenthesis.

className();

The destructor is called whenever an object goes out of scope, such as when a function exits. It allows the system to release memory and to relinquish links to other objects. Its syntax is the class name preceded by the tilde (~) symbol and followed by parenthesis.

~className();

The copy constructor allows an object of a class to be constructed from another object of the same class. Its syntax is the name of the class followed by a reference to a constant object of the same class within the method’s parenthesis. The & after the class name signifies that a reference to the object is passed to the function instead of a copy. The const keyword signifies that the object referenced cannot be modified.

className(const className&);

The assignment operator allows an object to be assigned from another one of the same class. Its syntax is a reference to a class name followed by operator = followed by a reference to a constant object of the same class within the method’s parenthesis. Here again, the ampersand signifies that we are passing and returning a reference to an object instead of a copy of the object. This is more efficient, especially if objects contain a lot of information.

className& operator = (const className&);

Within the class declaration, methods and attributes that are available outside of the class’ code are declared public. Within the class declaration, anything following a public: declaration is visible and usable by all code outside and within the class. Within the class declaration, anything following a private: declaration is only visible to code within the class. Generally speaking, attributes, that is variables within the class, are in a private section of the class declaration. Sometimes, we do not want the default constructor, copy constructor, or assignment operator to be automatically generated by the compiler and we do not want to provide them to class users. This is achieved by putting them in the private section of the class.

The following sections contain header files and implementation files for the rgbColor and rgbLed classes. Consult a C++ primer to better understand classes in C++.

rgbColor.h Header File

The rgbColor header file defines the rgbColor class. C++ and .ino files that want to use objects of the rgbColor class must include this file using the ‘#include‘ statement. The header file contains the class definition that declares a default constructor, a destructor. a copy constructor and an assignment operator within its public section. A constructor from red, green, and blue floating point components is also declared. Three accessor methods, red(), green() and blue() are declared to return the floating point value of each color component. Finally, the gradate() method explained earlier in the post is declared.

Then follows a private section containing the color components declared as bytes. Although colors are set and accessed as floating-point values between 0.0 and 1.0, they are stored as integer values between 0 and 255 in order to save memory space.

Finally, a set of constant rgbColor objects are created that contain the red, green, blue, yellow, cyan, magenta, orange, purple, white and black colors.

/*
rgbColor class (header)
Class rgbColor stores a color and operates on it.
MIT License
Copyright (c) 2018, Michel Lagace
*/
#if !defined(RGBCOLOR_HEADER)
#define RGBCOLOR_HEADER
#include "arduino.h"

// Class rgbColor
class rgbColor {
  public:
    // Orthodox Cannonical Form
    rgbColor(); // Default Constructor
    ~rgbColor(); // Destructor
    rgbColor(const rgbColor&); // Copy Constructor
    rgbColor& operator = (const rgbColor&); // Assignment operator
    // Other constructors 
    rgbColor(float r,float g,float b);
    // Object accessors (set/get)
    const float red() const;
    const float green() const;
    const float blue() const;
    // Object methods
    void gradate(const rgbColor c1, const rgbColor c2, float g);
  private:
    // Color components
    byte redComponent;
    byte greenComponent;
    byte blueComponent;
};

// Global constant colors 
static const rgbColor redColor(1.0,0.0,0.0);
static const rgbColor greenColor(0.0,1.0,0.0);
static const rgbColor blueColor(0.0,0.0,1.0);
static const rgbColor yellowColor(1.0,0.6,0.0);
static const rgbColor cyanColor(0.0,1.0,1.0);
static const rgbColor magentaColor(1.0,0.0,0.5);
static const rgbColor orangeColor(1.0,0.1,0.0);
static const rgbColor purpleColor(0.2,0.0,0.2);
static const rgbColor whiteColor(1.0,1.0,1.0);
static const rgbColor blackColor(0.0,0.0,0.0);
#endif

rbgColor.cpp Implementation File

The rgbColor implementation file contains the implementation of all methods, including constructors, destructor, assignment operator, declared in the header file. All method names within the file are prefixed with the class name followed by two colons. In the case of the rgbColor class, all its methods are prefixed with rgbColor::. We will describe each method in turn.

The default constructor rgbColor::rgbColor() initializes all color components to zero. The destructor rgbColor::~rgbColor() does nothing. The copy constructor rgbColor::rgbColor(const rgbColor& color) copies each color component from the source to the current object. The assignment operator rgbColor& rgbColor::operator =(const rgbColor& color) first checks if the source being copied from is the object itself. If not, it copies each color component from the source to the current object. The assignment operator also returns a reference to the rgbColor object.

The constructor rgbColor::rgbColor(float r,float g, float b) transforms each color component from a 0.0 to 1.0 value to a 0 to 255 value. It also clamps values between 0 and 255. The method rgbColor::gradate(rgbColor c1, rgbColor c2, float g) computes the color value between c1 and c2 at the gradient g specified. The gradient is a floating-point value between 0.0 and 1.0. The resulting color is computed according to the following formula.

Color = (1.0 – Gradient)•Color1 + Gradient•Color2

Finally, the rgbColor::redComponent(), rgbColor::greenComponent(), and rgbColor::blueComponent() methods return the value of each component as a floating point value between 0.0 and 1.0.

/*
rgbColor class (implementation)
Class rgbColor stores a color and operates on it
MIT License
Copyright (c) 2018, Michel Lagace
*/

#include "rgbColor.h"

// Default constructor. Set to black.
rgbColor::rgbColor() {
  redComponent = 0;
  greenComponent = 0;
  blueComponent = 0;
}

// Destructor. Does nothing.
rgbColor::~rgbColor() {
}

// Copy constructor. Constructs a color from another.
rgbColor::rgbColor(const rgbColor& color) {
  redComponent = color.redComponent;
  greenComponent = color.greenComponent;
  blueComponent = color.blueComponent;
}

// Assignment operator. Assigns a color to another.
rgbColor& rgbColor::operator =(const rgbColor& color) {
  if (&color != this) {
    redComponent = color.redComponent;
    greenComponent = color.greenComponent;
    blueComponent = color.blueComponent;
  }
  return *this;
}

// Constructor with initializers. Initialize the color with
// the specified red, green, and blue value.
rgbColor::rgbColor(float r, float g, float b) {
  // Limit red component between 0 and 255
  if (r <= 0.0) {     redComponent = 0;   }   else if (r >= 1.0) {
    redComponent = 255;
  }
  else {
    redComponent = (byte)(r*255.0);
  }
  // Limit green component between 0 and 255
  if (g <= 0.0) {     greenComponent = 0;   }   else if (g >= 1.0) {
    greenComponent = 255;
  }
  else {
    greenComponent = (byte)(g*255.0);
  }
  // Limit blue component between 0 and 255
  if (b <= 0.0) {     blueComponent = 0;   }   else if (b >= 1.0) {
    blueComponent = 255;
  }
  else {
    blueComponent = (byte)(b*255.0);
  }
}

// Set color value as a gradient between two values.
// Color1 and color2 are the values between which the new value
// will be set as a gradient between the two values. Gradient
// is a value between 0 and 1.
void rgbColor::gradate(const rgbColor startColor,
                       const rgbColor endColor,
                       float gradient) {
  redComponent = startColor.redComponent +
    (endColor.redComponent - startColor.redComponent)*gradient;
  greenComponent = startColor.greenComponent +
    (endColor.greenComponent - startColor.greenComponent)*gradient;
  blueComponent = startColor.blueComponent +
    (endColor.blueComponent - startColor.blueComponent)*gradient;
}

// Red, green and blue accessors. Return values.

const float rgbColor::red() const {
  return redComponent/255.0;
}

const float rgbColor::green() const {
  return greenComponent/255.0;
}

const float rgbColor::blue() const {
  return blueComponent/255.0;
}

rgbLed.h Header File

The rgbLed header file defines the rgbLed class. C++ and .ino files that want to use objects of the rgbLed class must include this file using the ‘#include‘ statement. The header file contains the class definition that declares a destructor within its public section. A constructor from red, green, and blue digital output pin assignment is also declared. The set() method allows users to assign a color to a rgbLed object. The default constructor, copy constructor and assignment operator are declared in the private section and cannot be used for rgbLed objects. Then follows a private section containing the pin numbers corresponding to the red, green and blue LEDs declared as integers.

/*
rgbLed class (header)
Class rgbLed represents an external rgbLed
MIT License
Copyright (c) 2018, Michel Lagace
*/

#if !defined(RGBLED_HEADER)
#define RGBLED_HEADER

#include "rgbColor.h"

//Class rgbLed
class rgbLed {
  public:
    // Orthodox cannonical form
    ~rgbLed(); // Destructor
    // Other constructors
    rgbLed(int,int,int);
    // Color assignment of RDG LED
    void set(const rgbColor);
  private:
    // Unusable and hidden orthodox cannonical form
    rgbLed();  // Default constructor
    rgbLed(const rgbLed&); // Copy constructor
    rgbLed& operator = (const rgbLed&); // Assignment operator
  private:
    // RGB LED associated Arduino pins
    int redPin;
    int greenPin;
    int bluePin;
};

#endif

rgbLed.cpp Implementation File

The rgbLed implementation file contains the implementation of all methods declared in the public section rgbLed header file. All method names within the file are prefixed with the class name followed by two colons. In the case of the rgbLed class, all its methods are prefixed with rgbLed::.

The destructor rgbLed::~rgbLed() does nothing. The rgbLed::rgbLed(int redPin, int greenPin, int bluePin) constructor sets the specified pins as output and sets their digital value to LOW, extinguishing all LEDs. The rgbLed::set(rgbColor color) method writes an analog value between 0 and 255 corresponding to each color value to the RGB LED pins.

/*
rgbLed class (implementation)
Class rgbLed represents an external rgbLed
MIT License
Copyright (c) 2018, Michel Lagace
*/

#include "Arduino.h"
#include "rgbLed.h"

const int ANALOGMAX = 255;       // Maximum analog output value

// Destructor. Does nothing.
rgbLed::~rgbLed() {
}

// Constructor with initializers. Initalizes the LED with
// the specified red, green, and blue pins.
rgbLed::rgbLed(int r, int g, int b) {
  redPin = r;
  greenPin = g;
  bluePin = b;
  pinMode(redPin,OUTPUT);
  pinMode(greenPin,OUTPUT);
  pinMode(bluePin,OUTPUT);
  digitalWrite(redPin,LOW);
  digitalWrite(greenPin,LOW);
  digitalWrite(bluePin,LOW);
}

void rgbLed::set(rgbColor color) {
  // Output color to RGB LED
  analogWrite(redPin,color.red()*ANALOGMAX);
  analogWrite(greenPin,color.green()*ANALOGMAX);
  analogWrite(bluePin,color.blue()*ANALOGMAX);
}

A Better Transistor Switch Circuit

In the previous post, we have seen how to drive a relay using an NPN bipolar junction transistor. The circuit allowed a digital input signal to activate and deactivate a relay coil. As seen previously, the following graph depicts what is happening to the relay coil voltage drop with respect to the input voltage from an Arduino digital output pin.

Simple Transistor Relay Switch Plot

Looking at the bottom horizontal band, the relay electrical characteristics guarantee that the relay is off for input values between 0 volts and 1 volt; and looking at the top horizontal band, the relay electrical characteristics guarantee that the relay activates when the input voltage is above approximately 2.75 volts. What if we wanted the relay to remain off until the input voltage to be at least 2 volts, and the relay to be on when the input voltage is at most 3 volts?

Biasing the Base of the Transistor

There is a way in our circuit for base-emitter voltage drop not to be equal to Vin when the transistor is in cut-off mode. Remember that the transistor requires a forward voltage drop of 0.7 volts, for silicon-based bipolar junction transistors, between the base and emitter before current can flow through the base and thus the collector. While current is not flowing through the base or base resistor, the voltage at the base is the same as the voltage at the input. How can we make the voltage at the input of the base resistor higher than the voltage at the base? By using a voltage divider.

The Voltage Divider

The voltage divider is a simple circuit where two resistors in series divide the voltage amongst them. According to Kirchhoff’s voltage law, as seen in the Blink post, the sum of the electromotive forces, as provided by batteries, in any closed loop is equivalent to the sum of the voltage drops in that loop. Thus, in the following circuit,

voltage divider

The sum of voltage drops across R1 and R2 is equal to VDC. The current IDC in the circuit is, according to Ohm’s law, I = V/R or

IDC = VR1/R1 = VR2/R2
IDC = (VR1 + VR2) / (R1 + R2)
IDC = VDC / (R1 + R2)

The voltage across R2 is

VR2 = IDC•R2
VR2 = VDC•R2 / (R1 + R2)

If VDC is 2 volts and we want VR2 to be 0.7 volts then

0.7 V = 2 V•R2 / (R1 + R2)
0.35•R1 + 0.35•R2 = R2
0.35•R1 = 0.65•R2

Lets apply this to the complete switch circuit from the previous post and add a resistor, RBias between the base of the transistor and ground.

Transistor Switch with Bias

In this circuit, as previously computed, in order for the Vin value of 2 volts to produce a voltage of 0.7 volts across the base and emitter of the transistor, RBase and RBias must satisfy the following equation

0.35•RBase = 0.65•RBias
RBase = 0.65•RBias / 0.35

When the base-emitter voltage reaches 0.7 volts, current starts flowing into the base of the transistor and the base-emitter voltage remains 0.7 volts. According to Kirchhoff’s current law the sum of currents flowing into the junction of the base resistor, the bias resistor and the base node is equal to the sum of currents flowing out of that junction. The current flowing into the junction through the base resistor is

I = (Vin – 0.7 V) / RBase

The current flowing out of the junction through the bias resistor and the transistor’s base is

I = 0.7 V / RBias + IBase

Hence,

IBase = (Vin – 0.7V) / RBase – 0.7 V / RBias

Now, assuming a resistive relay coil load of 70 Ω, a power supply of 5 V and a transistor hFE of 250, the base current at start of transistor saturation is

IBase = 5 V / (70 Ω•250) ≅ 0.286 mA

As stated earlier, we want Vin to be 3 volts at saturation and using the base current equation, we get

0.286 mA = (3 V – 0.7V) / RBase – 0.7V / RBias

Replacing RBase with the voltage divider circuit equation computed for the resistor values at cutoff we get

0.286 mA = (3V – 0.7V) / (0.35 RBias/0.65) – 0.7V / RBias
RBias = 1795 Ω ≅ 1.8K

Replacing RBias in the cutoff voltage divider equation we get

RBase = 0.65•RBias / 0.35 = 3333 Ω ≅ 3.3K

Resulting New Circuit

The following diagram depicts the transistor relay switch circuit with a voltage divider at the base of the transistor switch. The values of the resistors are 3.3K for the base resistor and 1.8K for the bias resistor.

Connected Biased Transistor Relay Switch

The following graph depicts what is now happening to the relay coil voltage with respect to the input voltage from an Arduino digital output pin when the voltage divider is used at the base of the transistor.

Biased Transistor Relay Switch Plot

Notice how we get a cleaner and sharper voltage transfer between the input signal and the voltage at the relay coil. This new circuit ensures that weaker input digital signals can be used to operate the transistor relay switch.

Breadboarding

The following picture depicts how to connect the different parts using a solderless breadboard, jumper wires, a transistor, a diode, a relay, a push button, a 10K resistor, a 1.8K resistor and a 3.3K resistor. Connections to the household appliance are not shown.

A better Transistor Switch Circuit_bb

WARNING:

The project in this post involves household mains high-voltages. Use caution whenever dealing with high-voltage wiring, including following directions carefully and following general safety practices. Safe assembly and operation of this project is the user’s responsibility. If unsure or if local laws prohibit the assembly of high-voltage circuits, get the help of a professional electrician. Do not make changes to the system while the device is plugged in.

Transistor Driven Relay Switch

Up to now, we have seen how to input digital (on and off) information from a simple electromechanical device, the push-button, and how to output a digital signal to an LED. What if, instead of an LED, we wanted to turn on and off an actual household light. There are obvious electrical differences between LEDs and household lights. LEDs only require a few milliamperes of current and work very well in low voltage circuits such as the 5 volts provided by an Arduino board. A household light, on the other hand, requires mains electricity, between 110 and 240 volts depending on your location, and higher currents, between 200 milliamperes and one ampere. A microcontroller digital output cannot directly provide enough power to light a household light.

Relays are electromechanical devices, like the push-button. Instead of requiring a mechanical force to push on the device to close or open a circuit, it relies on an electromagnet to pull on a metal plate to close the circuit with metal contacts with which the plate makes a connection. We can thus close or open a higher voltage and higher current circuit by applying a voltage to the relays electro-magnet that pulls on a metal plate to close the circuit.

It the following sections, we will have a look at a few electronic and electromechanical devices: the relay, the diode, and the transistor.

Relays

Relays have two distinct, electrically independent parts. The first part is an electromagnet, a coil of insulated copper wire wound around a metal bracket, the yoke, that becomes magnetic when a current is applied to it. The other part is a spring-loaded metallic plate or armature resting on metal contacts. When a current is applied to the electromagnet, enough force is applied to the armature for it to disconnect from its resting contacts and make an electrical connection with another set of metallic contacts on which the armature rests for as long as there is electrical current in the electromagnet. When current stops flowing in the electromagnet, the spring attached to the metallic armature forces it back to its initial resting position, making an electrical connection between the metallic plate and the resting contacts.

Relay Parts

Relays come in a variety of sizes and ratings. Ratings are used to select a relay to be used for specific applications. Automotive relays, for instance have rated coil voltages of 12 volts. Relays intended to be used in household appliances have rated coil voltages of 120 or 240 volts. Following is a list of relay coil ratings supplied by manufacturers.

  • Rated Coil Voltage – the voltage that is intended to be applied to the coil to operate the relay.
  • Pull-In Voltage – the minimum voltage that can be applied to the coil for it to operate the relay.
  • Drop-Out Voltage – the voltage below which an activated relay will return to its resting state.
  • Maximum Continuous Voltage – the maximum voltage to be applied to the coil above which permanent damage occurs.
  • Nominal Operating Current – current flowing through the coil when the rated coil voltage is applied.
  • Nominal Operating Power – the power used by the coil when the rated coil voltage is applied.
  • Coil Resistance – the continuous current resistance of the coil in ohms.

Within circuit diagrams, the electromechanical relay is represented as a coil and contacts as in the following diagram depicting normally open and normally closed relay forms.

Relays

Relay contacts have ratings stating the relay’s intended use. Following are relay contact ratings supplied by manufacturers.

  • Contact Forms – the contact mechanism and the number of contacts in the contact circuit.
    • Form A – normally open (N.O.) contact.
    • Form B – normally closed (N.C.) contact.
    • Form C – changeover contacts.
    • MBB – Make-Before-Break contacts where normally open contacts close before normally closed contacts break open.
  • Rated Switching Power – the intended use value in watts of the load that can switched by the contacts.
  • Maximum Switching Voltage – maximum voltage that can safely be switched by the contacts.
  • Maximum Switching Current – maximum current that can safely be switched by the contacts.
  • Maximum Switching Power – the maximum power to be switched by contacts above which damage may occur.

For the current project, we will use a 5 volts miniature relay, the FRS10C-S12, to turn on and off a household lamp. This relay has the following coil characteristics:

  • Rated coil voltage of 5 volts.
  • Pull-in voltage of 3.75 volts.
  • Pull-out voltage of 0.5 volts.
  • Nominal operating current of 70 mA.
  • Coil resistance of 70 Ω.

It has the following contact characteristics:

  • Form C contact form, changeover contacts.
  • Contact rating of 12A at 125VAC (1,500W) or 10A at 250VAC (2,500 W)

The chosen relay’s coil operates on 5 volts, suitable for an Arduino provided power supply, but requires 70 mA to operate, which is much larger than the rated output current of 20 mA that each Arduino digital output can provide. We need a device that can boost the current provided by the digital output pin to drive the relay coil. That device is a transistor.

Transistors

Transistors are semiconductor devices used to amplify or switch electronic signals and electrical power. There are many types of transistors, but the most common is the bipolar junction transistor (BJT). There are two types of bipolar junction transistors, the NPN and PNP types, describing the material and configuration used to build the device. For the current project and tutorial, we will use an NPN transistor. There are several configurations that transistor circuits may use. In order to explain how the transistor operates, I will be using the common-emitter configuration, that is a transistor circuit with its emitter directly connected to ground. Consider the following circuit.

NPN Transistor

The transistor, labelled Q1, is at the center of the diagram. It is represented by a circle with a vertical bar from which three branches are attached. The diagonal branch at the top is called the collector. It is connected to a resistor, RLoad, representing the device to be switched on or off. The branch to the left of the transistor symbol, perpendicular to the bar is called the base. It is connected to a resistor, RBase, that controls the current flowing into the base. Finally, the diagonal branch with the arrow pointing outward is the emitter. If the arrow had been pointing towards the bar inside the symbol, we would have a PNP transistor. In this circuit, the emitter is connected to ground. The principle of operation of the transistor is that a small current flowing from the base to the emitter of the transistor will allow a larger current flowing from the collector to the emitter, thus amplifying the base current.

Transistors, like other electronic devices, have specifications telling us about the electrical limitations of the devices as well as information about their capabilities. In the circuit that we will build later on, we will use a BC337-40 NPN Bipolar Junction Transistor. I have found the following information from the product data sheet provided at SparkFun.

  • Maximum Collector-Base Voltage |VCES| – 50 V, the maximum voltage drop between the collector and the base.
  • Maximum Collector-Emitter Voltage |VCEO| – 45 V, the maximum voltage drop between the collector and the emitter.
  • Maximum Emitter-Base Voltage |VEBO| – 5 V, the maximum reverse voltage drop between the emitter and the base.
  • Maximum Collector Current |IC| – 800 mA, the maximum amount of current that can flow through the collector.
  • Power Dissipation |PD| – 650 mW, the power dissipation of the device.
  • Forward Current Transfer Ratio |hFE|, minimum – 250, the minimum amount of current amplification between the base current and the collector current.
transistor cbe

Here is a picture of the BC337-40 transistor above. It comes in a TO-92 package, a small plastic half cylinder with a flat face on which the transistor markings are written and three metal pins sticking out at the bottom of the package. When the transistor’s flat face is facing the reader, the collector pin is at the left side of the transistor, the base is the center pin, and the emitter pin is at the right side of the transistor.

Transistor Operation

One characteristic that all silicon transistors have is the forward bias voltage required between the base and the emitter for the transistor to work. Remember that in a previous post titled the Blink circuit, we saw that the LED had a constant voltage drop across its anode and cathode. Similarly, transistor based on silicon have a voltage drop of approximately 0.7 volts between their base and emitter when in operation. Below that voltage, no current flows through the base nor the collector. When the base-emitter voltage (VBE) of the transistor is increased to 0.7 volts, current starts flowing through the base and through the collector. The base-emitter voltage remains at 0.7 volts while current flows through the base. The amount of current flowing through the collector (IC) is proportional to the current flowing through the base (IB) times the Forward Current Transfer Ratio (hFE) of the transistor.

IC = IB = 0,   if VBE < 0.7 V
IC = hFE•IB,  if VBE ≥ 0.7 V

Common Emitter

Looking at the circuit above, an increase of voltage at Vin will get VBE to increase until it reaches 0.7 volts. Since no current flows through RBase, the voltage drop across the resistor is 0 volts and VBE = Vin. While VBE is less than 0.7 volts, the transistor is said to be in cut-off mode. As Vin is increased beyond 0.7 volts, current starts flowing through the base and collector, making the transistor enter the normal amplification operation mode. Following Ohm’s law, I = V / R, and since the current going through the base is the same as the current through the base resistor

IB = (Vin – 0.7 V) / RBase

In the normal amplification operation mode, the voltage across the load resistance RLoad, VLoad, is proportional to the collector current, itself proportional to the base current.

VLoad = IC•RLoad
VLoad = IB•hFE•RLoad
VLoad = ((Vin – 0.7 V) / RBase)•hFE•RLoad

As the voltage across RLoad increases, the voltage across the transistor’s collector and emitter pins decreases until VCE reaches 0 volts. At that point, any increase in the base current has no effect on the collector current and the transistor is said to have reached saturation. At saturation, we have:

IC = VCC / RLoad

The load to be turned on and off is the relay described earlier with a coil resistance of 70 Ω for a supply of 5 volts. At saturation, the collector current is 5 V / 70 Ω or approximately 71 mA. When connecting the base of transistor to an Arduino digital output pin through the base resistor, Vin is 0 volts when a LOW is output to the digital output and 5 volts when a HIGH is output to the digital output. Another way of stating this could be that any input voltage below 2.5 volts is LOW and anything above 2.5 volts is HIGH. Hence, we want a saturated transistor when the input voltage is above 2.5 volts. Assuming a transistor hFE of 250, we can use the equation to compute VLoad from Vin to compute RBase

VLoad = ((Vin – 0.7 V)•hFE•RLoad / RBase)
RBase = (Vin – 0.7 V)•hFE•RLoad /  VLoad
RBase = (2.5 V – 0.7 V) •250•70 Ω / 5 V
RBase =  6,300 Ω

The closest resistor value in my kit is 10K, which should be close enough. Lets now build the circuit. First, we replace the resistive load by the relay coil and we replace the base resistor with a 10 K resistor. The Vin input is replaced with the Arduino’s digital output pin 11.

Transistor Relay Switch

Note the addition of a new device between the terminals of the relay’s coil. Its symbol resembles that of the LED that we saw in previous projects, but without the outward arrows. The device is called a diode. As for the LED, current flows in the direction of the arrow, from anode to cathode. The diode in this circuit serves as a protection for the transistor. The coil of the relay stores energy as it is turned on and it releases that energy when it is turned off as a voltage pulse that can damage the transistor by exceeding its maximum rated collector voltage. The diode acts as a short, preventing the spike from damaging the transistor. In normal operation, the diode does not let current through.

Let’s have a look at a graph plotting the voltage drop across the relay’s coil as a function of the voltage at the Arduino’s digital output pin.

Simple Transistor Relay Switch Plot

On the plot, we see that the voltage applied to the load is 0 volts for as long as the input voltage is below 0.7 volts. Then, as input voltage increases, voltage at the relay coil increases until the input voltage reaches approximately 3.5 volts, at which point the voltage drop across the relay’s coil reaches 5 volts and the transistor enters saturation.

The vertical blue bands represent the guaranteed LOW and HIGH voltage values output by the Arduino’s digital output pin. The top horizontal blue band represents the voltage zone in which the relay is on and the bottom blue horizontal band represents the voltage zone in which the relay is guaranteed to be off. In the diagram, we note that the relay is off for all guaranteed values for Arduino’s LOW output and that it is on for all guaranteed values for Arduino’s HIGH output.

The maximum base current is 5 V / 10 K, or 0.5 mA, a totally acceptable value for the Arduino’s digital output capability. The next step, is to connect a household device to the relay.

The Final Circuit

The relay contacts are exactly like wall switch contacts and can be used to turn on or off household appliances. In the final circuit, the relay contacts are inserted as a switch in one of the wires of a lamp’s power cord. A push-button is used to turn the lamp on and off using the Arduino program described in the LED Toggle with a Push-Button Switch post.

Connected Transistor Relay Switch

Breadboarding

The following picture depicts how to connect the different parts using a solderless breadboard, jumper wires, a transistor, a diode, a relay, a push button and two 10K resistors. Connections to the household appliance are not shown.

Transistor Driven Relay Switch_bb

The Program

The following Arduino program completes the post. Cut and paste the code in your Arduino IDE and download it to complete the project. It will toggle the lamp on and off at each press of the pus button.

/* Household Light Toggle
   Uses a transistor connected to pin LED_BUILTIN as
   a switch for a relay that toggles on and off a
   household lamp at the press of a button.
   This sketch was written by Michel Lagacé, 2018-10-08
   This code is in the public domain. */

// Button value will be read from pin 12
#define INPORT 12
#define OUTPORT 11

// Time to wait in milliseconds to consider switch debounced
#define DEBOUNCE_DELAY 10

// LED state kept across loops
static bool outputValue;

// Setup the board.
void setup() {
    pinMode(INPORT, INPUT);
    pinMode(OUTPORT,OUTPUT);
    outputValue = LOW;
    digitalWrite(OUTPORT,outputValue);
}

// Wait for an edge and return state
bool waitForEdge() {
    bool startValue = digitalRead(INPORT);
    bool newValue = startValue;
    while (newValue == startValue) {
        newValue = digitalRead(INPORT);
    }
    delay(DEBOUNCE_DELAY);
    return newValue;
}

// Repeat forever
void loop() {
    // Wait for a rising or falling edge
    bool value = waitForEdge();

    // Toggle output on dropping edge (input is LOW when button is pressed)
    if (!value) {
        outputValue = !outputValue;
        digitalWrite(OUTPORT, outputValue);
    }
}

WARNING:

The project in this post involves household mains high-voltages. Use caution whenever dealing with high-voltage wiring, including following directions carefully and following general safety practices. Safe assembly and operation of this project is the user’s responsibility. If unsure or if local laws prohibit the assembly of high-voltage circuits, get the help of a professional electrician. Do not make changes to the system while the device is plugged in.