---

Friday, September 16, 2022

Simple Long Range Radio from Raspberry Pi to raspberry Pi Pico using HC-12 433MHz

I have a project in progress that is the third generation of my Hen House Door project. I want to be able to open and close the door from my home automation controller (Raspberry Pi 2 running NodeRED). Unfortunately, the hen house is at the end of the garden and out of WiFi range. Anyway the door opener is low-power and solar powered, and WiFi uses too much current.

I've used NRF24 and CC1101 modules before and frankly there are a lot of wires to connect for SPI and generally a mess of libraries to try and get working. Wouldn't it be lovely (I mused) if there was a long range wireless module that talked UART serial and hid all the communication layer stuff. 

With a bit of googling, I was delighted to find the HC-12 modules that do exactly that. They take AT commands to configure them for power mode etc. But aside from that they work just like you had a cable between your two devices and talked serial along it.

Eventually, my Raspberry Pi 2 will talk to the hen house controller that is based on an ATTiny1616 at 1MHz and using a minimal amount of electricity except when driving the door motor. But as a first test, I thought it would be good to establish a link between a Pico and a regular Raspberry Pi (in this case, my Pi 400).

The Pico

Here's the Pico end.


The connections are:

  • GND on the Pico to GND on the HC-12
  • 3V on the Pico to 3V on the HC-12
  • Tx on the Pico to Rx on the HC-12
  • Rx on the Pico to Tx on the HC-12
To test it out, I used the program below - making sure its copied onto the Pico itself (use Save a Copy in Thonny) so I can take it mobile with a battery pack.


Here's the code for ease of copying:

from machine import UART
import time

uart = UART(0, 9600, timeout=400)

count = 0

while True:
    count += 1
    print(count)
    uart.write(str(count)+"\n")
    time.sleep(1)


The Raspberry Pi

Here's an HC-12 attached to my Pi400 by way of a GPIO adapter and some jumper wires.


The connections are:

  • GND on the Pi to GND on the HC-12
  • 3V on the Pi to 3V on the HC-12
  • Tx on the Pi (14) to Rx on the HC-12
  • Rx on the Pi ( 15) to Tx on the HC-12
The receiving code is just:

import serial

ser = serial.Serial('/dev/serial0', baudrate=9600)

while True:
    line = ser.readline().decode("utf-8")
    print(line)

Run the program, and it should start receiving numbers from the Pico. Wander around with the Pico powered from a USB battery pack and see how far you can get before you start loosing data. 


pi@pi400:~ $ nano hc_12.py 

pi@pi400:~ $ python3 hc_12.py 

1553


1554


1555


1556


1557


1558


Conclusion

Everything worked without a hitch and no need for any tricky SPI programming or dodgy libraries. Pyserial was all that was required. These are now my wireless modules of choice.

I'm just using the little coiled-wire antenna that came with the module and I'm getting more than enough range to reach the hen house even through a few walls. The modules have a tiny antenna connector, to which an external antenna can be attached for maximum range. The module datasheet claims 1km at maximum power and lowest baud rate. Please check that 433MHz is legal in your area.

Next up for me is using the module's AT commands to balance low power with range and change channel, to avoid interference.

Monday, September 12, 2022

A Better Web Server for Raspberry Pi Pico W

The Pico W is a fantastic little device, but the examples I have found for serving web pages from it were all too low-level for my taste.


I'm big fan of Bottle and Flask as light-weight web frameworks. And was very excited to find the microdot project. This looks very much like Flask and Bottle, using the decorator syntax (@) to mark functions that are to respond to particular web requests. This leads to code as simple as this for a minimal web server.

from microdot import Microdot

app = Microdot()

@app.route('/')
def index(request):
    return 'Hello, world!'

app.run()

Now thats what I call the right level of abstraction. Not a reference to socket in sight!

The great news is that microdot works with the Raspberry Pi Pico. Although it takes care of all the web serving for you, it will not initiate your WiFi connection. Connecting to WiFi has a few subtleties such as showing connection progress and handling failure to connect and re-connection. It's definitely a wheel that doesn't need reinventing every time you want to serve a web page. So, I have put together a tiny module to wrap that process up as well. Its available here: mm_wlan.

Once installed, your entire web server code for your Pico will just become this:

from microdot import Microdot
import mm_wlan

ssid = 'my network name'
password = 'my password'

app = Microdot()  
mm_wlan.connect_to_network(ssid, password)

@app.route('/')
def index(request):
    return 'Hello, from Pico'

app.run(port=80)

Step by Step

1. Install microdot and mm_wlan. This involves copying just two files, microdot.py and mm_wlan.py onto the file system of your Pico W.

You can do this by opening these files in Thonny and, with your Pico W connected to your computer choosing Save copy from the File menu. When prompted select Raspberry Pi Pico as the destination and save the file onto the Pico's file system using its original name.

2. Start a new file in Thonny and paste in the code immediately above. Change ssid and password to match your WiFi network and run it. When you do so, you will see the IP address that the server is running on in the Shell area at the bottom of the Thonny window. 


Now open a web browser on that IP address:

Hurray! Your Pico W is being a web server!

Adding Another Page

Let's now add a second page to the web server called memory that reports how much free memory the Pico W has. The previous page returned plain text rather than HTML. The default response type of microdot is text, so even if we had put HTML tags into the response, it is displayed literally and not be interpreted by the browser as HTML.

For this new page we will need to import the gc (Garbage Collector) package:

import gc

and then add a handler like this:

@app.route('memory')
def index(request):
    response = '<h1>Free Memory={} bytes</hi>'.format(gc.mem_free())
    return response, {'Content-Type': 'text/html'}

Notice that now, as well as returning the response text, we also return the content type.

Run the program and, when you point your browser to the page memory, this is what you should see:


Try refreshing the page a few times and you should see the free memory change.

Here's the whole program:

from microdot import Microdot
import mm_wlan
import gc 
 
sid = 'my network name'
password = 'my passord'

app = Microdot()  
mm_wlan.connect_to_network(ssid, password)  
 
@app.route('/')
def index(request):
    return 'Hello, from Pico' 
 
@app.route('memory')  
 
def index(request):
    response = '<h1>Free Memory={} bytes</hi>'.format(gc.mem_free())
    return response, {'Content-Type': 'text/html'} 
 
app.run(port=80)   

Templates

As your web pages get a bit more complex, then some kind of templating will help you construct the response text. Here's an example of a multi-line example taken from an example project in the MonkMakes Plant Monitor.



from microdot import Microdot
import mm_wlan
from pmon import PlantMonitor

ssid = 'network name'
password = 'password'

html = """
    <!DOCTYPE html>
      <meta http-equiv="refresh" content="1" >
<html>
<head> <title>My Plant</title> </head>
<body>
<h1>Pico W Plant Monitor</h1>
<h2>Water: {water}</h2>
<h2>Temp (C): {temp}</h2>
<h2>Humidity: {humidity}</h2>
</body>
</html>
"""
pm = PlantMonitor()
app = Microdot()
mm_wlan.connect_to_network(ssid, password)

@app.route('/')
def index(request):
    w = pm.get_wetness()
    t = pm.get_temp()
    h = pm.get_humidity()
    response = html.format(water=w, temp=t, humidity=h)
    return response, {'Content-Type': 'text/html'}

app.run(port=80)


The neat trick here is to use the meta tag to automatically refresh the wen page every second. Perhaps a better way to do this would be to have a microdot page that serves the sensor values of JSON that Javascript then handles on the browser, with the home page including the Javascript to do that. But, that's a lot more work.

Summary

The microdot framework will take a lot of the effort out of your Pico W projects where web serving is required. It's a really impressive bit of framework code.

The microdot framework is pretty complete, supporting different request types, request parameters and pretty much anything you could expect from such a compact web server framework.





Friday, January 14, 2022

Pimping a Toy Kitchen

We were given a toy kitchen for our grandchild to use. It had knobs for the hob, but they didn’t do anything except click. So I thought an upgrade might be in order.
This isn't a step-by-step writeup - I didn't photograph each step, so you'll have to fill in some of the blanks yourselves if you want to make something like this. You'll also need access to a laser cutter to make the diffusers.
I thought about using an Arduino or Pico, but decided there was little to be gained, so the design just uses resistors, switches and LEDs.



Each ring uses 4 super-bright red LEDs behind a laser-cut arrangement of pearlescent acrylic. This gives a very pleasing and scarily realistic effect.

You can also see a video of it in action here. The idea is that the controls are pots with a built-in switch, so that when you turn them a bit, they click on and the hob lights up dimly as if the ring is just on. As you turn the knob more clockwise the brightness increases to a maximum.


To make this I used:
  • 2 x 1kΩ 1/4W linear pot with SPST switch (from eBay)
  • 2 x large knobs to fit the above
  • 2 x 470Ω 1/4W resistors
  • 8 x high brightness red LEDs (Vf 2.2V If 20mA)
  • 3xAA 4.5V batter box
  • 2 x A4 sheets of 3mm pearlescent acrylic
Here's the schematic for one of the rings. If you find you have a lower Vf for your LEDs then you will need to add a suitable fixed resistor in series with the 4.5V supply and the pot. 100Ω should cover most eventualities. You will need to get the pot the right way around, so that when you first turn the knob a bit and the switch closes, you will effectively have the full 1k of the pot in parallel with the 470Ω resistor. At the far end of the travel, the pot resistance will be close to 0Ω, for maximum brightness.


And here's a closup of the back of one of the rings showing a healthy amount of hot-glue keeping everything in place.




Here are the laser cutting files, should you wish to adapt the design to you own kitchen.

https://github.com/simonmonk/toy_kitchen_upgrade

I had fun making this, and grandchild number one love it!

Tuesday, August 25, 2020

Testing the micro:bit's Analog Inputs

The BBC GPIO connection rings (labelled 0, 1 and 2) of a micro:bit can all be used as analog inputs.

You might think, that as long as you are careful to to exceed the 3V input voltage limit, then you can measure any low voltage whatever the source. Perhaps a photoresistor in a voltage divider arrangement with a fixed resistor. 

While this is basically true, if the source of the voltage to be measured has a high output impedance, at some point the voltage measured by the micro:bit will diverge from reality as the impedance of the voltage source being measured increases.

In reality, you can't measure something without altering it. The best we can do is to make the measurement errors small, so that they can be ignored.

This blog post determines the extent of this measurement error with the micro:bit's analog inputs.

Equipment Used

Here's a handy test program that reports the voltage at P0 when button A is pressed, that I used in this experiment.


Use a multimeter to measure the voltage between the 3V and GND ring connectors on the micro:bit and put this value in for Vanalog. I was powering the micro:bit from USB, so Vanalog was about 3.2V.

You will also need:

  • A digital multimeter on DC volts range
  • Resistors 100Ω, 1kΩ, 10kΩ, 100kΩ, 1MΩ, 10MΩ
  • A low-impedance voltage source such as bench power supply (set to 2V) or just an AA battery
  • An alligator lead

Setup

Here's the setup for the experiment.




The ground of the micro:bit is connected to the ground of the power supply and the positive lead from the power supply is clipped to one end of the test resistor. The alligator lead is used to connect the other end of the resistor the micro:bit P0 ring. The multimeter measures the voltage at the micro:bit's P0 ring (V1). V2 is the voltage reported on the micro:bit by the test program.

Results

Here are the results for a series of resistor values.

R (Ω)V1 (V)V2 (V)
01.992.05
1001.992.05
1,0001.992.05
10,0001.992.02
100,0001.991.77
1,000,0001.931.17
10,000,0001.750.92

Even the multimeter starts to give errors when connected to the voltage source via a 10MΩ resistor. The DMM used is specified as having an input impedance of 10MΩ but actually these results imply it's quite a bit better than that.

If we plot V1 and V2 against R, this is what we get:

As you can see, the micro:bit does pretty well up until a value of R of 10kΩ after which it goes down hill pretty rapidly.

Conclusion

If you want to use your micro:bit to measure a voltage, try and keep the output impedance of the voltage source lower than 10kΩ. 

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!





Sunday, April 12, 2020

Making Protective Face Shields with a K40 Laser Cutter - Part 3

Part 1 Part 2

We finally had a chance for the household to go ahead and make some shields.



Cleanliness

This was tricky, I don't have gloves, but do have a face mask each that covers our mouths and noses. So, I would not count these masks as sterile and as such have been careful to pass on that information to recipients.

So we had frequent hand washes and (for what its worth) baby wipes to wipe down the plastic that gets a bit of a dirty residue from the smoke produced during cutting. 

Fortunately the masks are easy to clean.

Finding Recipients

While waiting for the laser cutter to do its thing (about 8 mins / shield) there was plenty of time to open up Google Maps and search on care homes nearby. I emailed (where there was an email address on the website) or otherwise phoned a total of 5 homes within walking distance. 
One asked for 12 on the phone and was very grateful as they had no face shields at all. Another said the same on email (yesterdays delivery). And already we have two more deliveries to make tomorrow to care homes and GP surgeries. There is clearly a huge demand here in the UK.

What Next?

The next step is to find a cheaper source of material than eBay! and find out if there is a better way to distribute.

Although it seems that hospitals are the main focus and care homes and GP surgeries have rather been left to fend for themselves. So I feel that providing them with masks, fits both my small scale of production and a need that if it reduces infections will lighten the hospital load at the front end.

Tuesday, April 7, 2020

Making Protective Face Shields with a K40 Laser Cutter - Part 2

In part 1 of this post I looked at laser-cutting the plastic band. In this post, we can now attach the clear A4 sheet to the shield.

I used this from eBay with a thickness of 140 micros for the visor, which seems about right.


The A4 clear 'acetate' sheet fits in landscape aspect ratio and is punched with 4 holes. I used one of these:

.. and put one pair of holes in the middle, as you would a normal sheet of paper for a ring-binder. I then carefully put the other holes in at the edges by lining up one side of the hole punch puncher with an existing hole. This is fiddly, so 
Edit: This works fine,  didn't realize that if you push the guide rail on the side most of the way until it says A5 (on ours) punch one set of holes then flip the acetate sheet through 180 degrees and punch again, the holes are in the right places. Try this on paper first to avoid wasting acetate.


I have ordered one of these: which should make all the holes in one go.

The little plastic tabs on the strap fit snugly into the holes and hold the 'acetate' sheet in place.


Review

The inner band holding the head away from the visor works well and prevents misting up. The whole thing was comfortable even wearing glasses. I'm sure its a way from rigorous professional protection equipment, but I think its a lot better than nothing, or just a face mask on its own.

It's also entirely plastic and I would have thought washable. I have three of these now, which we are going to send to medical and carer friends of ours who have expressed an interest.

Now I know its possible on A4, I'm ordering more materials and we'll get cracking. I'll come back with a part 3 on manufacturing cleanly and experiences in donating these.

Other Resources

This all stems from the work here https://community.andmirrors.co.uk/t/covid-19-laser-cut-face-shield/168 and https://www.kitronik.co.uk/blog/kitronik-make-nhs-frontline-ppe-visors/

There are lots of other projects and designs out there and Google will stay a lot more up to date than any list I put here.