---

Showing posts with label arduino. Show all posts
Showing posts with label arduino. Show all posts

Thursday, May 1, 2025

Tuesday, April 15, 2025 Accessible FM Radio Case Study - Part 3 - Audio Tests

[Part 1] [Part 2] [Code and Design Files]

After some promising results with the PCB loop antenna, in this post, I'm starting to look at the audio output.

The intention for the final system is to design a single PCB incorporating the power supply, radio receiver, power amplifier and antenna. But before launching in to this, I want to do as much testing and developing as possible using the modules that I have to hand.

In Part 2 I was getting reasonable signal strengths from the PCB antenna, but I hadn't really listened to the reception, except through headphones.

The TEA5767 module I used has an audio amplifier built-in, but its the now obsolete TDA1308 which is a stereo amplifier offering 80mW at 32Ω per channel. No good for driving a speaker directly.

To try out what I have so far, I had a rummage around and found an old version of the MonkMakes Amplified Speaker and connected it to the output of the radio module and to the 5V supply of the Arduino.


I was not expecting results. The Amplified Speaker, is a very low power quite tinny thing and the quality was pretty awful. Key point were:

  • Terrible sound quality
  • Significant hiss
  • Clicks every time the Serial Monitor reported the signal strength.
Touching the antenna to increase the signal strength got rid of the hiss. I am not too worried about this, as the FM reception at our house is pretty bad. Window-sills being required even for really good FM receivers.

The USB interference is not be a problem for the final device, as it will be battery powered, but it does serve as a warning that interference could well be a problem for the future.

I wanted to make sure that the TEA5767 output wasn't inherently low quality, so I connected the audio out of the TEA5767 module to the line-in of HiFi. The results were very acceptable. The USB click was still there, but overall reception was better (slightly different position).

Looking at datasheets and browsing component catalogues, I quite like the look of the PAM8320. It's a class D (think PWM) amplifier that can provide up to 20W. I don't need anything like that much power, but having some oomph in reserve is usually a good thing.

I think its getting close to the time, where I start to design the system as a whole.

Tuesday, April 15, 2025

Accessible FM Radio Case Study - Part 2 - PCB Antenna

[Part 1] [Part 3] [Code and Design Files]

In the first part of this series, I looked at the overall requirements for the project. A key one of these was to avoid the need for the classic 1/4 wave telescopic antenna, which is, something of a hazard.

Cars haven't had telescopic antenna for some time now. It turns out they are generally a conductive trace built into the car glass. (Not much use for this project). However, I had noticed the 'shark-fin' antenna on the top of cars. It seems, that according to this research paper. It is possible to make a compact PCB antenna suitable for receiving FM broadcast transmissions in the normal range of 88-108 MHz.

PCB antenna design

The research paper gives a detailed design for the PCB, including all dimensions. I have recreated this in KiCAD and you can find the design files and even generated CAM files for production in the github repository for this project.


The PCBs came back from JLCPCB and looked good, so I made a couple of the boards up using the surface mount components specified in the paper. You can see the values in the picture above.

I actually made up 2 boards, one with the intended SMA co-axial connector, and one using a chopped off end of a 3.5mm audio connector, to make it easy to connect the antenna to the TEA5767 board that I was using for testing.


I used a hot glue gun to stick down the audio lead to prevent the wires pulling off.



TEA5767 Module

As a first test of the feasibility of using this type of PCB coil antenna, I used the TEA5767 module shown here.


There are two 3.5mm headphone style sockets on the board. One is for stereo output, and can directly drive headphones and the other is for attaching an antenna. 4 header pins provide power and the 2 I2C bus lines.

The module has an I2C interface that I will control using an Arduino Uno. The I2C interface allows you to set the tuning and volume, but also, crucially allows you to obtain the signal strength.

I can use this to compare the performance of the antenna against other antenna options, by tuning to a known radio station and then switching antennas.

The TEA5767 module is also 5V operation so I can connect it directly to the Arduino Uno and use the Arduino to power it.

The wiring is as follows:

  • 5V on the Arduino (red) to Vcc on the radio module.
  • GND on the Arduino (blue) to GND on the radio module.
  • SCL on the Arduino (yellow) to SCL on the radio module.
  • SDA on the Arduino (orange) to SDA on the radio module.


You can find the Arduino test sketch here.

/*
 * Modified from original library and examples by big12boy 2017
 * https://github.com/big12boy/TEA5767
 * 
 * Mostly reformat and recomment and simplification.
 * 
 * Signal strength only updates if you do a reset. Use Arduino Reset button
 * 
 * Tested on Arduino Uno connected to TEA5767 module. 
 * 
 * This used to test TEA5767 basic operation and measure signal strength for various antenna.
 * 
 * See blog post here for more detail: 
 * https://www.doctormonk.com/2025/04/accessible-fm-radio-case-study-part-1.html
 * 
 */

#include <TEA5767.h>
TEA5767 radio = TEA5767();

float frequency = 93.0; // Enter your own Frequency in MHz. Look up the frequency of a station near you. 
long baud = 9600;       //Enter your own Baudrate. I always use 9600.

void setup() {
  Serial.begin(baud);
  Wire.begin();
}

void loop() {
  radio.setFrequency(frequency);
  printReady();
  printStereo();
  printSignalLevel();
  
  Serial.println();
  delay(1000); 
}

void printReady(){
  int rdy = radio.getReady();
  Serial.print("Ready: ");
  Serial.println(rdy);
}

void printStereo(){
  bool stereo = radio.isStereo(); 
  Serial.print("Stereo: ");
  Serial.println(stereo);
}

void printSignalLevel(){
  short level = radio.getSignalLevel(); 
  Serial.print("Signal (0-15): ");
  Serial.println(level);
}

Before running the program on your Arduino, change the line: 

float frequency = 93.0;

to a frequency of a station that you can receive strongly with an FM radio. In my case, this is BBC Radio 4.
When you open the serial monitor in the Arduino IDE and set the baud rate to 9600, you should see something like this:


You can, of course, plug some headphones in to the radio module to hear how much noise is accompanying the signal.

Test Results

Trying to keep all other things constant, the antenna options I measured, were:

  • No antenna, sig strength 0/15
  • 23cm telescopic antenna, sig strength 4/15
  • PCB antenna, sig strength 8/15
On listening to the audio on headphones, the quality was quite reasonable with only a little hissy noise.

This is really good news, and I'm hoping we can do without the telescopic antenna.

In Part 3 of this series, I'm going to start looking at audio amplifier and speaker options, as this will inform decisions about the power supply.

Friday, April 11, 2025

Accessible FM Radio Case Study - Part 1

In this series of blog posts, I am going to document my journey (which I am about to start) in designing a FM Radio receiver for which I have the very specific requirement of being usable by my brother, who suffered a stroke a few years ago. This has left him with cognitive impairment and use of just his left arm.

This blog will effectively form my notes, and follow my usual design process which is:
  1. Investigate technologies
  2. Create 'spike' examples to de-risk various aspects of the design
  3. Breadboard or strip-board prototype
  4. Testing
  5. Prototype PCB creation
  6. Enclosure and user testing
As I hope this will end up in a design that will help others, I am going to release all the CAD and firmware files under an open license. 


User interface

  • Volume control - rotary, a pot, therefore with absolute positioning and no turning down to silent.
  • On/Off switch - probably a stylish toggle switch, but low physical resistance and ok for one handed operation without having to hold the radio.
  • Three preset buttons. Real switches with an indicator to see which is selected. Obvious icons (probably on paper) to select the channel. Talk (BBC Radio 4), Classical (BBC Radio 3), Pop (BBC Radio 2).
  • A separate interface (behind a panel - or using a BLE phone app) to configure stations

Other requirements

  • Large capacity LiPo battery power
  • USB charge and pass through
  • Decent power amplifier and speaker
  • FM broadcast receiver (*)
  • Bluetooth audio for wireless headphones
  • No eye-pokey antenna
  • Stable enclosure (one-hand friendly)
* Why FM rather than DAB or Internet Radio. Well DAB is power hungry and offers no real advantage (except perhaps future-proofing) over FM. 

As for internet radio? Well, this was my first thought as it solves the antenna problem. However, when I have looked at this before, it's quite difficult to get stable URIs for station streams, especially for BBC stations, which is likely all my brother will listen to.

Looking at what was commercially available, I found this


Which is actually pretty close to what I was planning. And if all else fails, I could easily see myself getting one for my brother. However, although you can't see it here, it has an eye-poker antenna and is powered by D-cells. Neither of which I am keen on.


Technologies

At the moment, my initial list of technologies to experiment with includes:
  • A PCB antenna (as used in 'shark-fin' car antenna) -- this is the big risk, and subject of my next blog post.
  • The  RDA5807M radio receiver IC (I might also try TEA5767 to see which is more sensitive)
  • An ATTiny1614 microcontroller 
  • A power amp probably D-class. Ideally 10W or more
I've ordered some RDA5807M modules from Aliexpress and started designing a PCB FM broadcast antenna using the research paper I found. I will need both the radio module and the PCB antenna for my first experiments.

Wednesday, January 24, 2024

Lies, Damn Lies and Analog Inputs (comparing ADCs on ESP32, Pico and Arduino)

After some inconsistent and unreliable results reading an analog input from an ESP32 board, I decided to get all scientific and do some experimenting with an ESP32, a Raspberry Pi Pico and an Arduino Uno R3.

Method

My test setup was a bench power supply providing the reference voltage to be measured by the test board. The output of the bench PSU had a dummy load of a 470Ω resistor and a 100nF capacitor in parallel (the latter largely for superstitions reasons) as the voltage output looks extremely stable on a DMM voltmeter.

This output from the bench power supply was then applied directly to an analog input and GND of the board being tested.

I was particularly interested in three things:

* finding any dead-zones at each end of the analog input voltage range

* measuring the reproducibility of the readings

* linearity through the range

Another time, I'd like to look at the input impedance of the ADC (analog to digital convertor) and the effects of how rapidly you sample. But I'll leave that for another day.

To measure the reproducibility of the readings, each time the test voltage was changed, 100 readings would be taken, and the mean and standard deviation of that set recorded. That way, when it came to plotting the readings from the boards, I could add some error bars.

For the ESP32 and Pico, I used MicroPython and for the Arduino Uno, I used Arduino C. The Arduino readings were scaled up to 16bit unsigned values (max value 65536) to be consistent with the MicroPython version. In all cases, the default ADC settings were used.

ESP32

For this experiment, I used an ESP32 Lite (sometimes also called LOLIN32 Lite). These are a ubiquitous low-cost ESP32 board, with built-in WiFi and Bluetooth.

Analog input maximum voltage 1.0V



Here's the plot

The red error bars show +- 3 standard deviations (SDs) from 100 samples. Nearly all of the sample values would fall within 3 SDs and 60% would fall within 1 SD.

There is a sizeable dead zone until the voltage rises to about 0.05V and a lot of noise around the readings, evidenced by the large error bars. But it retains pretty good linearity once you get past that up to the 1V upper limit.

Raspberry Pi Pico

The Raspberry Pi Pico uses Raspberry Pi's RP2040 chip. It'a another popular, good value board.

It's maximum analog input voltage is the full 3.3V supply range.


Here's the plot for the Pico - 


This is considerably better than the ESP32, with smaller 3 x SD error bars, a small dead zone at the low voltage end and some slight tail-off in linearity at the 3.3V end.

Arduino Uno R3

Despite its age, the Arduino Uno R3 (not the fancy new one) is still my go-to board for any experimentation or early stage project work that doesn't need a specific microcontroller. I'll admit, it's partly out of familiarity and inertia on my part.



And here are the results.


And there we have it. Very little deviation between readings and great linearity across the whole range, right up to 5V. The Uno with it's ancient Atmega328 is streets ahead of the other two boards.

Conclusion

On looking at the documentation in MicroPython and learning that the analog readings for a Pico and ESP32 come at a massive 16 bit precision (a number between 0 and 65536) it's easy think that their analog inputs are much better than the paltry 10 bits of an Arduino (0 to 1023 reading range). But this is to confuse precision with accuracy. It's why pure megapixels is not the best way to judge a camera. So much depends on the lens.

So, if you are trying to get decent accuracy and reproducibility from your analog readings, then you probably want to take a set of readings and average them -- or use an Arduino Uno R3!

Test Programs

ESP32

from machine import ADC, Pin
from time import sleep
from math import sqrt

analog = ADC(12)

p = 0.05
n = 100

while True:
    readings = []
    for i in range(0, n):
        readings.append(analog.read_u16())
        sleep(p)
    total = 0
    for i in range(0, n):
        total += readings[i]
    mean = total / n
    dist_tot = 0
    for i in range(0, n):
        dist = readings[i] - mean
        dist_tot += dist * dist
    
    print(mean, sqrt(dist_tot / n))
    
    input('Press enter to read again')

Pico

from machine import ADC, Pin
from time import sleep
from math import sqrt

analog = ADC(28)

p = 0.05
n = 100


while True:
    readings = []
    for i in range(0, n):
        readings.append(analog.read_u16())
        sleep(p)
    total = 0
    for i in range(0, n):
        total += readings[i]
    mean = total / n
    dist_tot = 0
    for i in range(0, n):
        dist = readings[i] - mean
        dist_tot += dist * dist
    
    print(mean, sqrt(dist_tot / n))
    
    input('Press enter to read again')

Arduino

int p = 50;

const int n = 100;


unsigned int readings[n];


void setup() {

  Serial.begin(9600);

}


void loop() {

  if (Serial.available()) {

    Serial.read();

    Serial.println("measuring");

    for (int i = 0; i < n; i++) {

      readings[i] = analogRead(A0) * 64; // 16 bit not 10

      delay(p);

    }

    float total = 0.0;

    for (int i = 0; i < n; i++) {

      total += float(readings[i]);

    }

    float mean = total / n;

    float dist_total = 0.0;

    for (int i = 0; i < n; i++) {

      float dist = float(readings[i] - mean);

      dist_total += (dist * dist);

    }

    float sd = sqrt(dist_total / n);

    Serial.print(mean); Serial.print(' '); Serial.println(sd);

  }

}



Saturday, August 19, 2023

Disposable e-cigarette - Part 4 (Making batteries safe)

 In the fourth part of this series of posts, we'll take a look at making a battery extracted from an e-cig safe to use in your products. 

DANGER: 

There is a really good chance that the battery of a discarded e-cig is still holding quite a lot of energy. The fluid runs out first. So if, during disassembly, the unit triggers (quite likely), then a big current will flow and the heating element will get hot. Similarly any accidental short between the leads to the battery could easily cause a fire.

Cut the leads to the element (one at a time) and to the battery (again one at a time) as soon as you can access them. 

Also have a contingency plan such as fire blanket (not a bucket of water) or open window through which the flaming device can safely be thrown in the event of it catching fire.

If in any doubt that you can do this safely, then don't do it.

The e-cig batteries generally lack the 'protection circuit' found ready soldered across the terminals of the LiPo cell that carry out the following functions:

  • Preventing overcharging (can cause the battery to get hot and catch fire)
  • Preventing over discharging (rendering the battery incapable of holding charge)
  • Over-current protection, safeguarding against short-circuit of battery terminals. (can cause the battery to get hot and catch fire)
Fortunately these protection chips are, well, cheap as chips, because they are used in the billions. You can buy a small panel like this for a few dollars:


Here's the link for the one I bought.
That's right 10 for $1.30!

The idea is that the LiPo cell terminals are soldered to one pair of terminals on the protection PCB and then another pair of terminals are then used to connect to the battery that add the protections.


The terminals B+ and B- are for the LiPo cell and P+ and P-. The protection PCB is more likely to stay attached to the cell, if it's soldered close to the terminals, like this:


Notice I have also attached flying leads to protection PCB. It's a good idea not to strip the far ends of these leads until you need to, to prevent accidental short-circuits.

From now on, you should only connect to these leads and not directly to the cell. This will in most cases work exactly the same, except that there is now minimal risk of starting a fire with it.

Note that the protection PCB is not a charging circuit. It makes the LiPo cell safe, but that doesn't mean that you can just connect it to a voltage source and charge it. You will need a LiPo charging circuit to do that properly.

If you have a bench power supply, where you can set both the voltage and the current limit, then a safe way to charge the cell is to:
  1. To be ultra-safe, set the bench power supply's current limit to 1/10 of the cells capacity. For example, for 360mAh like the one shown, set the current limit to 36mA. 
  2. Set the voltage to 4.2V
  3. Connect the battery terminals (the connection PCB terminals - NOT the LiPo cell) and wait until the current drops to 0mA after about 10 hours.
At your own risk, you might like to charge the battery a lot faster - say at 500mA by setting that as the maximum current and still using 4.2V. Because the cells in e-cigs are designed for quite high-current use (several Amps) they should be fine for charging at this sort of current. But keep and eye on it, and stop immediately if the battery starts to get hot.

Monday, July 13, 2020

Unit Testing and Arduino

When I worked in software, I was an early adopter of agile software development (eXtreme Programming) and so have always loved unit tests and TDD. Like many people from a software background, when I first started programming Arduino, my instinct was to write frameworks, patternize and objectify my code. Then I realised that I have 32k of program space to play with and a really big program might stretch to 100 lines of code (shock horror). So I adjusted my big-software thinking and like everyone else drew the line at functional decomposition and occasional library writing.

Background

For the fun of it, I recently decided to make a calculator (pocket calculator if you happen to have huge pockets) from the schematic design up.

For this project I wanted to use my new favourite low cost microcontroller (the ATTINY816) with Arduino IDE using Spence Konde's rather fantasic Arduino core.

To my surprise, making a calculator that actually does arithmetic well is a lot harder than expected. One obvious hurdle is that float in Arduino C is only 32 bits. So once your numbers get to about 7 or 8 digits they become disconcertingly approximated. So, I thought, I'm (or used to be) a computer scientist, I'll just implement my own floating point representation with more precision than you can shake a stick at. So, I did, as a C++ library (I'll put it on Github when I've got it working and tidied up).

However, for the first time, since I had first started using Arduino, I felt the need to write unit tests, to make sure that my number class was doing arithmetic and generating an 8 character string plus decimal point position information that I could then easily map onto an 8 digit 7-segment display.

Unit Tests

If you haven't used unit testing before, then the basic idea is that you write a load of test functions (the more the better) that exercise the code being tested in some way and compare the outcome with the expected outcome. So, for example in a number class, you might want to check that when you add 2 and 2 you get 4, but also you write a test to make sure that when you add -123.45 and 0.01 you get -123.44 and not -123.46.

Having a unit test suite behind your code means that if you change your code to fix one bug, when you run the tests it will immediately tell you if you have broken something else in doing so. As you build up your suite of tests, you get more and more test coverage for the tricky situations that may uncover bugs.

My Solution for Arduino

The solution I came up with is very specific to the problem, so, I'll try and include some general principals rather than just the code.

Firstly, I created a separate test sketch, specifically for the purpose of testing my number class, without any of the other code related to keyboard scanning and display refresh.

The second thing I did was to get out an Arduino Uno (well actually I used a MonkMakesDuino) because one of the great things about the Uno, is that compiling and uploading a sketch is MUCH quicker than the likes of an ESP32 or indeed the ATTiny816 programmed using UPDI. So, the round-trip time when adding tests or fixing code is greatly reduced.

I used the Serial Monitor to view the results of the unit tests, and a test pass would simply marked by a single line giving the name of the test that passed. A test failure, would include as much information as possible to help with debugging.



Here's the start of my test sketch:

#include "CalcNum.h"

char digits[] = "........";
const int numDigits = 8;

void setup() {
  Serial.begin(9600);
  testNums();
  testAdd1();
  testAdd2();
  testSub1();
  testMult1();
  testMult2();
  testMult3();
  testDiv1();
  testDiv2(); 
}

void loop() {}

My number class is CalcNum (imaginative right!).
digits[] is a data structure used by CalcNum in its writeDigits() method that prepares a string for easy mapping onto an 8 digit 7-segment display.

All the test functions to be called are then listed out in the setup function, as we only need to run them once.

The first of these (testNums()) tests the representation of numbers themselves rather than arithmetic, so lets skip on to the test function testAdd1():

void testAdd1() {
  CalcNum x = CalcNum(12, 0);
  CalcNum y = CalcNum(3, 0);
  CalcNum z;
  CalcNum::add(x, y, &z);
  test("testAdd1", z, "      15", 7);
}

This function defines two numbers (x and y) using an exponent form (x = 12 x 10^0 = 12).
Adds them together and then calls the general purpose function test to see if the result was as expected.

As an aside, I haven't used C++ operator overloading in my number class, as this would inevitably lead to the need for dynamic memory allocation, which I avoid like the plague when working on embedded systems.

So, what are the parameters to test?

The first is a string, that is the name of the test, the second is the CalcNum to be checked. the third is the expected result string from calling writeDigits() - in this case 15 with leading spaces. The final parameter is the expected position of the decimal point on the display (zero indexed, left to right).

Here's what the function test looks like:

void test(char *testName, CalcNum z, char *expected, int expectedDP) {
  z.writeDigits(numDigits, digits);
  int dp = z.dpPos(numDigits);
  if (strcmp(digits, expected) == 0 && dp == expectedDP) {
    pass(testName, expected, expectedDP, z);
  }
  else {
    fail(testName, expected, expectedDP, z);
  }
}

As you can see, the test function compares the calculated and expected 8 digit string and decimal point positions and if the match, calls pass and if they don't calls fail.

void pass(char *testName, char *expected, int expectedDP, CalcNum z) {
  Serial.print("PASS: "); Serial.println(testName);
  //report(expected, dp, expectedDP, z);
}

The function pass just prints out a message that the test passed, along with the name of the passing test. Note the commented out call to report. Sometimes this gets commented back in to shed light on why one test passed when another didn't.

The fail function is much the same as pass, but with a different starting message.

void fail(char *testName, char *expected, int expectedDP, CalcNum z) {
  Serial.print("**** FAIL: "); Serial.println(testName);
  report(expected, expectedDP, z);
}

The report function just prints out as much useful information about the result as possible, to help me fix the bug.

void report(char *expected, int expectedDP, CalcNum z) {
  Serial.print("got digits["); Serial.print(digits); Serial.print("]dp="); Serial.print(z.dpPos(numDigits));
  Serial.print("\t expected ["); 
  Serial.print(expected);Serial.print("]dp="); Serial.println(expectedDP);
  Serial.print("float: "); Serial.println(z.toFloat(), 10);
  Serial.print("m: "); Serial.print(z.m); Serial.print(" e:"); Serial.println(z.e);
  
  Serial.println();
}

Conclusion

Once this project is written, I probably won't write any more tests until I meet a similar project for which 'it seems to work ok' is not sufficient.

However, its really easy just to put together some tests if you need to. For me, it wan't even worth looking to see if anyone had made a framework to do this and then taking the trouble to work out how to use it.

I hope this write-up will help you if you find yourself needing some Arduino unit tests!





Friday, October 5, 2018

Arduino Air Quality Monitor

I've recently spent some time developing the MonkMakes CO2 Sensor for micro:bit and have spent a fair amount of time researching into indoor air quality. It struck me that I actually had no idea how healthy (or otherwise) the air is, in the house that I both sleep and work in. 

Time for a project! What's more I can reuse some of the sensors that I have accumulated while researching. These are low cost sensors, the whole lot costing probably less that GBP 50 (USD 50).

The project logs the following readings through USB back to the Arduino Serial Monitor, where you can copy and paste it into a spreadsheet:
  • True CO2 level using the serial interface to the MH-Z14A CO2 sensor.
  • tVOC (Total Volatile Organic Compounds) in parts per billion (ppb) using a low cost CCS811 MEMs sensor breakout board
  • eCO2 - equivalent CO2 (derived from the tVOC sensor by the CCS811. You don't need these readings if you have real CO2 measurement, but I wanted them for comparison.
  • Particulates - µg/cubic metre - this was measured using a Sharp GP2Y1010 optical particulate sensor.
It all squeezed onto a breadboard and I used a MonkMakesDuino Uno compatible as the 'Arduino'. 


The output in the Serial Monitor is in Tab separated fields that can just be copied and pasted into a spreadsheet.


Sensors

MH-Z14A (CO2)

You can find the MH-Z14A on eBay pretty cheap. While not the nicest CO2 sensor (I like the COZIR Ambient) they do have the benefit of being really cheap (for a true CO2 sensor) and when compared with better sensors are accurate enough for this kind of project.



The device has a number of interfaces, but I used the TTL serial interface. This just requires 4 pins from the device (19-Tx, 18-Rx, 17-GND, 16-5V) shown left to right in the figure above.

The sensor has a serial protocol that requires a message to be sent from the Arduino, some-time after which a response will be received from which the PPM (parts per million) of CO2 can be extracted.

The sensor uses spectral absorption to optically measure the CO2 compensation. No compensation is made for temperature (indoor use, so stable temp assumed) or altitude (atmospheric pressure - but this is a small factor).

You can find the datasheet here but the datasheet for the older version of this product here goes into more detail.


CCS811 (tVOC)

This air-quality sensor chip measure Total Volatile Organic Compounds (unhealthy chemicals with a boiling point that makes them likely to get into our air). We breathe these out and they are also found from pollutants and chemicals we use in our lives. There are not many of them in good clean outdoor air - in most places anyway.

My sensor came from eBay, but Sparkfun also sell a breakout board based on the same chip and made an Arduino library that I use in the code below.

It uses an I2C interface, but operates at 3.3V. You can find the datasheet here.

GP2Y1010 (Particulate density)

This is another eBay purchase. It measures reflections from particles in the air from which you can derive an approximate measurement of the number of µg per cubic meter of particles in the air. 



Pin 1 is the rightmost connection (white wire) in the figure above.

This depends on so many factors, not least the unknown mass of the particles, how much light (IR) they reflect etc that the reading has to be taken with a good dose of skepticism. But is does give an idea of how dusty the air is. 

The device uses an analog output with a sensitivity of 0.65 Volts per 100µg per metre cubed.

You can find the datasheet for this device here.

Schematic

Here's the schematic for the project.



The GP2Y1010 uses high current pulses of IR. To prevent the switching of the IR sender affecting the readings because of drops in the supply voltage, a 150Ω resistor charges a 200µF reservoir capacitor to supply the IR LED with a pulse of power. The internal amplifier of the GP2Y1010 must be supplied directly from the 5V rail (pin 6).

Note that the CCS811 is 3.3V supply. It also needs the WAK (Wake) and AD (I2C address option) pins tying to GND. The module I used has built-in I2C pullup resistors.

Software

Here's the sketch. I've added lots of comments. Between the comments and the datasheets, it should make sense. You will need to add the Sparkfun library to your IDE, before it will compile.

#include <SoftwareSerial.h>
#include "SparkFunCCS811.h"

#define CCS811_ADDR 0x5A

const int ledPin = A3;
const int sensorPin = A0;
const long samplePeriod = 1000L;   // sample period in ms

SoftwareSerial co2Sensor(10, 11); // RX, TX
CCS811 vocSensor(CCS811_ADDR);

/* 
 *  Messages to be sent by serial to the MH-Z14 CO2 Sensor
 */
const byte requestReading[] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
const byte zeroCalibrate[] =  {0xff, 0x87, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2};

byte result[9];   // buffer into which serial messages from the MH-Z14 are received

/*
 * Globals containing last sensor readings and the last time a set of readings was output
 */
long lastSampleTime = 0;   // more accurately when data was last logged to serial.
int vocCO2;
int tVoc;
int co2;
int partics;

void setup() {
  Serial.begin(9600);
  co2Sensor.begin(9600);  
  vocSensor.begin();
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH); // active low
  // write out headers for the columns of data
  Serial.println("CO2 (ppm)\teCO2 (ppm)\ttVOC(ppb)\tParts (µg/m3)");
}

/*
 * Send the calibrate message to the MH-Z14 - setting it to 400ppm
 */
void calibrate() {
  for (int i = 0; i < 9; i++) {
    co2Sensor.write(zeroCalibrate[i]); 
  }
}

void loop() {
  /*
   * Send the Z command after leaving the project in fresh 400ppm air for 5 mins
   */
  if (Serial.available()) {
    char ch = Serial.read();
    if (ch == 'z') {
      calibrate();
    }
  }
  /*
   * If the tVOC sensor has data ready, update the global variable
   */
  if (vocSensor.dataAvailable()) {
    vocSensor.readAlgorithmResults();
    vocCO2 = vocSensor.getCO2();
    tVoc = vocSensor.getTVOC();
  }
  /*
   * If we are due to log another row of data, then take the CO2 and
   * partical readings and then send them all to serial
   */
  long now = millis();
  if (now > lastSampleTime + samplePeriod) {
    lastSampleTime = now;
    co2 = readPPMSerial();
    partics = dustMicroGpm3();
    Serial.print(co2); Serial.print("\t"); 
    Serial.print(vocCO2); Serial.print("\t"); 
    Serial.print(tVoc); Serial.print("\t"); 
    Serial.println(partics);
  }
}

/*
 * Send a serial message (9 bytes) to the MH-Z14 and wait for a response
 * Parse the resulting data and return the reported CO2 concentration in ppm
 */
int readPPMSerial() {
  for (int i = 0; i < 9; i++) {
    co2Sensor.write(requestReading[i]); 
  }
  //Serial.println("sent request");
  while (co2Sensor.available() < 9) {}; // wait for response
  for (int i = 0; i < 9; i++) {
    result[i] = co2Sensor.read(); 
  }
  int high = result[2];
  int low = result[3];
  return high * 256 + low;
}

/*
 * Return the particulates reading in µg/m3
 */
int dustMicroGpm3() {
  digitalWrite(ledPin, LOW);  // active low pulse for the IR LED
  delayMicroseconds(280);     // wait before taking the analog reading
  int raw = analogRead(sensorPin);
  delayMicroseconds(40);
  digitalWrite(ledPin, HIGH); // end the pulse
  delayMicroseconds(9680);    // give the capacitor time to recharge

  float sensitivity = 0.0065; // V/µg/m3   .from the datasheet
  float v = float(raw) * 5.0 / 1023.0;
  return int(v / 0.0065);
}

Results

Here are the results for CO2, and eCO2, sampling every 10 seconds overnight in our bedroom. So, maybe time to sleep with the door open!



Links

You can find information on healthy levels of CO2 here.

For information on tVOC concentration and what it means, see here.




Thursday, June 14, 2018

Cuban Makers at "Copincha" in Havana

Last week I found myself in front of a room full of Cubans in Havana, delivering a course on Arduino in broken Spanish. Was this a variation on the kind of dream where you are sitting down to an exam without having done a stroke of revision? Or the dream where you find yourself at work having forgotten to put on your trousers? 
The sequence of events that led to this radical departure from my comfort zone - not so much stepping outside it, but more waving it goodbye and jumping on a plane - started a couple of years ago when I was contacted by Jonnet who had seen a Spanish language edition of one of my books and wanted to borrow it for her Cuban partner Mauri.
Mauri and Jonnet were keen to establish some kind of makerspace in Havana to provide a place for local makers could come and share ideas, tools and resources. Cubans, by necessity, are an inventive and creative group of people. Resources are scarce and so a make-do-and-mend culture is totally natural to them. If ever there was a race of makers, it is probably the Cubans.
My wife Linda and I have been learning Spanish for years despite little evidence of success. We also spent our honeymoon in Cuba and have very fond memories of the country. And so, to cut a long story short, in a rash moment I volunteered to run an Arduino course at their makerspace when it was up and running. And a year or so later, that is how I found myself in front of a group of Cubans, talking about Arduino in Spanish.
Mauri, the driving force behind the makerspace also lives in the house containing the makerspace in quarters above the two rooms that make up the space.  The space is called "Copincha" - "pincha" is Cuban slang for "work" and so "copincha" is a way of saying "co-work".
Mauri and friends have used scrap wooden packaging cases to make box stools for sitting on as well as large work tables with table-tops that are removable to reveal ample storage space for tools and supplies to make best use of the space. There's probably ten square meters of table top and maybe twenty stools for sitting on. That is quite some labour of love!

My Arduino course was the first course to be run at the makerspace and since we had mixed levels of ability, with some very skilled Arduinoistas, I ran introductory Arduino lessons in the morning and in the afternoon offered "consultations" to the advanced groups who had projects that they wanted to work on.
I brought Arduino compatibles (actually MonkMakesDuinos) together with basic component kits and breadboards with me but the course relied on the students having access to a laptop to program the MonkMakesDuinos. In true Cuban style, this involved a fair amount of borrowing and inventive solutions. For example, one student used a homemade Raspberry Pi laptop to program the Arduino. Another used a Windows tablets with a pop-up keyboard. With sharing and cooperation everyone was able to participate.
I am very pleased to hear that the course is due to be repeated in July (when many Cubans are on leave from their day jobs). This time, the course is being delivered by Yusinier, one of the local experts at the course who amongst his many skills in art and technology has taught Arduino to university students.
The course and supporting code is available as open source on GitHub here and I would very much welcome any pull-requests to improve the Spanish.
Here are just a couple of the things that the course attendees made during the course:
Juan, a retired mechanical engineer with a passion for replica cars, worked on adding automation to a fibre-glass modelled replica he was building from scratch.

The chassis of the car was built to use a single gear-motor as rear-wheel drive and a servo motor to control the steering. This was combined with an ultrasonic range-finder to detect obstacles and invoke an avoidance function that involved a randomly chosen turn to the left or the right, followed by reversing. The design also included automatic brake lights. The steering mechanism was made entirely from waste pieces of plastic and skilful use of a Dremmel.
Juan has an enormous collection of vintage model cars that he restores, some of which are for display and some for sale at a shop he runs with friends, at San Lázaro 1060 entre San Francisco y Espada - somewhere around here.
Yusinier is a visual artist and film maker and amongst other things has to devise his own props, such as remotely triggered fake bullet strikes. He often uses Arduino and his project during the course was the controller for a motorized camera dolly.




The attendees at this first course at Copincha were: Sergio Valdés García, Yusnier Mentado Fernández, Lisa Gómez Blanco, Juan Infante, Alberto José Quesada Madrigal, Javier Quesada Madrigal, Os Leans and Jossue Arteche .

In England, when I need components, an Arduino or a cheap module from China, all of these things are easily obtainable with a few clicks of a mouse. This is not so easy in Cuba. For most Cubans a £30 Raspberry Pi represents more than a months pay. Even if they want one, they cannot just order it from Amazon or any of the other sources that we take for granted. Typically, getting such things into Cuba relies on visitors to Cuba like us bringing things with us when we visit. Donating things as personal gifts to Cubans. Visitors to Cuba are advised in the guide books to bring crayons, bars of soap and Lego as gifts so why not things for makers?


Perhaps if you are planning a trip to this beautiful country and will be visiting Havana, you could contact Mauri (maurice.haedo@gmail.com) and see if he could make use of a few Arduino clones, or eBay specials for "Copincha". If you do, you will be assured of a warm welcome a great coffee and a glimpse behind the scenes of  this amazing country.