I’ve updated my Roast Calculator to make it work clearer with mobile devices. Use it to find the cooking time of your favorite meat roast.
Link: https://www.g7smy.co.uk/roast

I’ve updated my Roast Calculator to make it work clearer with mobile devices. Use it to find the cooking time of your favorite meat roast.
Link: https://www.g7smy.co.uk/roast
I recently posted a legal document written in 1827, this was written in hand using a quill pen (quite possibly made from a goose feather) and it is rather difficult to read, there is no punctuation, commas or full stops not even an apostrophe and the capitalisation is all over the place. There are fewer characters in this alphabet, with some serving a dual purpose in giving a value based on context rather than appearance.
The ‘m’ and ‘n’ characters are upside down, uppercase ‘I’ and ‘J’ are both the same and some characters have still not been fully completed, so ‘c’ looks like an ‘r’ and ‘e’ looks like a ‘c’. The double ‘s’ – ‘ss’ had still not been invented at this time and words with these are spelt ‘fs’, so ‘passages’ is written as ‘pafsesges’. I have constructed this cheat sheet from that document and hopefully the chart will help you in your quest:
Letter | Example | |||
A | ![]() |
![]() |
and | ![]() |
B | ![]() |
![]() |
between | ![]() |
C | ![]() |
![]() |
second | ![]() |
D | ![]() |
![]() |
hundred | ![]() |
E | ![]() |
![]() |
same | ![]() |
F | ![]() |
![]() |
of | ![]() |
G | ![]() |
![]() |
shillings | ![]() |
H | ![]() |
![]() |
her | ![]() |
I | ![]() |
![]() |
said | ![]() |
J | ![]() |
![]() |
jointly | ![]() |
K | ![]() |
Blake | ![]() |
|
L | ![]() |
latter | ![]() |
|
M | ![]() |
![]() |
made | ![]() |
N | ![]() |
![]() |
grant | ![]() |
O | ![]() |
![]() |
Joseph | ![]() |
P | ![]() |
![]() |
appointed | ![]() |
Q | ![]() |
quality | ![]() |
|
R | ![]() |
presents | ![]() |
|
S | ![]() |
assign | ![]() |
|
T | ![]() |
![]() |
the | ![]() |
U | ![]() |
fourth | ![]() |
|
V | ![]() |
revoked | ![]() |
|
W | ![]() |
![]() |
wife | ![]() |
X | ![]() |
execution | ![]() |
|
Y | ![]() |
yearly | ![]() |
|
Z | ![]() |
Elizabeth | ![]() |
After few hours reading the words start to pop out, it helps that the document is written is a formulaic legalese style with many repeated phrases (were they paid by the word?).
In this post I am looking to discuss setting up a Trinocular Microscope for use with photography and video, covering my own experiences in getting the thing working and aspects that are not covered in the fairly useless manual. The type I am using is sold as an Industrial Inspection Microscope giving a magnification range of 3.5X to 90X depending on the installed optics. These look to come from a single factory in China and are distributed under various different brands by a variety of shops on the internet and in places such as ebay and Amazon.
In my electronics work I have been moving across to SMD (Surface Mount Devices) components, these can be rather small and fiddly for which a microscope is just the business. I chose a stereo microscope over the cheaper monocular microscope camera as this gives me a binocular depth of field view so when micro-soldering components I can make out the distance of the iron tip in relation to the board and component. In the past I have tried similar with a camera and monitor setup and this did not work for me.
Clockwise from the top:
Other Parts
An essential addition to the microscope is some kind of illumination, an LED ring light is an excellent place to start, this mounts onto a slot on the barlow lens or a screw in adapter. You will be wanting one thats both adjustable, really bright and gives and even spread of light, look for those with at least 144 bright white LED’s.
Barlow Lenses fit below the objective lenses, they are used to either reduce the magnification slightly to increase the working distance, or to add additional magnification.
With the 0.5X barlow fitted the working distance can be raised from 9cm to around 15cm, I have not yet found any real use for the 2X barlow.
I have also fitted a 48mm UV (plain glass) filter from a camera shop, this is to simplify cleaning up the various emissions created when soldering, to this I have added a 48-52mm stepping adapter ring to give a place for the LED ring light to attach to as the 0.5X barlow lens does not have a suitable slot.
The objective lenses Magnification is shown on the right hand magnification knob, from 0.7X to 4.5X, the eyepieces are 10X and the barlow is at 0.5X the zoom is calculated by multiplying all the magnifications:
Generally I am more concerned about the image size being appropriate for the work I am doing, but when specifying a scope you need to know what it can do, these inspection microscopes are excellent for electronics but completely useless for extreme closeups such as seeing the cells from a layer of onion.
The Field of View is shown on the eyepieces, it is the second part of the code: WF10X/20 this field number is the diameter of your objective view in millimeters at 1X magnification, the formula is:
These trinocular microscopes have two main types, those that are simul-focal and those that are not. As mentioned before, the standard (for want of a better name) have a lever on the left side which needs to be pulled to enable a view for the camera, this in turn disables the left eyepiece, this becomes a problem if you wish to video while you are working, for photography is is less so. These standard microscopes are a bit cheaper, around £80 – £100 than the simul-focus ones and the image quality from the optics is just the same.
I first started with a standard microscope, in the photo at the top of this page, the small stand makes it very stable for photography, and although it does not have a reducing barlow lens I have successfully used it for a few electronics projects. I have now moved onto a simul-focal microscope with a long boom arm, this is the AmScope SM-4TPZ, this type of microscope is popular among the independent repair shops on YouTube (see the links below), the boom arm and barlow lens makes it more flexible to use.
Setting up for eye focus or for camera focus is relatively straight forward, but getting the camera and the eyes to be working in the same focus can be tricky. The eyes are much more tolerant and have a greater depth of field than the camera, the following steps should give you the results you are looking for (with a 0.5X Barlow Lens, knock 5cm off the height if you don’t have one):
Now that the eyes have been setup, its time for the camera. The smaller the cameras sensor the further away it needs to be:
With everything in place, you should have a stable in focus range of around 3cm from your work surface where for a particular magnification nothing but the focus knob needs to be changed.
When taking photographs using the microscope my camera of choice is a Fuji X-Pro2. This is a mirrorless camera with an APS-C sensor, the reasons for this are: its lighter than most DSLR’s, an old fashioned mechanical plunger type cable release can be used, because the sensor size is smaller it crops out much of the tunnel effect caused by looking down a long tube, and the live view has a manual focus assist which can be used to zoom the live view display and help take a nice sharp image.
The camera sensor size makes a huge difference, to illustrate this here are two photos, one with the Fuji and another with a Canon DSLR full-frame both uncropped:
Normally I would crop to a square inside the image.
The depth of field is rather shallow and there is no aperture to change. To get around this I have used Image Stacking/Focus Merge, where you take a series of photos starting at the top of your object and focusing slightly further down, each image being a slice, I then use Affinity Photo to create a fully focused image.
I set for a low ISO, around 100-400 and being lazy I use auto-exposure. Despite the bright LED light exposure times are generally quite long, 40th-60th of a second and bumping up the ISO to grainy makes little difference, cropping in post can make high ISO grain more apparent. I have made a suitable mount for a macro flash from the lid of an old spray can, a resize ring, small bits of wood and plenty of epoxy (I should have documented this).
One thing you may see in your images is a white dot in the centre of the photo, this is most obvious when fully zoomed in and is caused by light bouncing around the trinocular tube leading to the camera:
this is simply fixed by inserting a tube of matte black card running the length of the camera mount, in this case 40mm:
I use a dedicated microscope camera for video, the photo cameras while I am sure they would work well have a built in time limit for recording and need attention, I also want to record directly on computer. The camera I use is a generic nameless blue box bought of ebay, on the computer it has the name “H1400 USB Camera” and I have it on a USB 2.0 port outputting 1080p at 30fps in to Open Broadcaster without any problems, it can also do HDMI and can take still images at 14 megapixels (apparently).
The big drawback with the camera is that the sensor used is tiny compared to those used for photography, this appears to be common among almost all video cameras designed for microscopes. To partly fix this a reduction lens can be fitted to the Trinocular Port, I use an AmScope FMA050 (aka RU050) 0.5X reduction lens which I got of ebay. You can see the cameras view in the sketch drawn on 5mm graph paper below, with the magnification set to 0.7X the outer circle is what I see through the eyepieces, the rectangle is what the video camera supplies to the computer:
These cameras would also benefit from being able to provide an image in the old 4:3 aspect ratio as that is much more square than the 16:9 widescreen. The view to your eyes in the microscope will always be substantially better than that presented by the camera.
For a trinocular microscope suitable for micro-soldering I would recommend a simul-focus, its only little extra money for the functionality with the 0.5X barlow lens. AmScope do cheat the magnification a bit, not pointing out the need to swap barlow lenses round to get the advertised 3.5X to 90X range, but with the 0.5X barlow attached it goes upto 22.5X or without it is 45X, on the electronics for most of the time I work at around 15X only zooming in close for inspecting bad soldering and showing off.
As I live in the UK, buying through Amazon UK has the advantage that import duty is paid at checkout, AmScope ship from the USA (a well traveled Microscope, made in China) using UPS, this prevents holdups in customs and UPS’s hefty tax collection fee, ebay have a similar Global Shipping Programme.
Using the microscope for the first time is an odd experience, its interesting how your fingers adjust to the micro-distances, finding the occasional need to wave the soldering iron about until it appears in your field of view and looking at dead insects under the microscope, while eating, is probably best avoided.
Microscope Suppliers
YouTube microscope users – Independent Repair Shops, mostly iPhone and Apple Macs
Bluetooth Low Energy – BLE – Bluetooth 4.0 is an industry-standard wireless protocol built for the Internet of Things – IoT, it is designed to provide connectivity for devices operating from low capacity power sources such as coin cell batteries.
In this introduction to BLE I’ll be configuring a Raspberry Pi2 computer to talk to a smart watch. We will be installing the latest version of BlueZ from source, enabling BLE support. This is not a tutorial on decoding the data from the watch I am just using it as an example, although I may write about decoding it in a future posting.
I am using a ASUS USB-BT400 Bluetooth 4.0 Dongle on a Raspberry Pi2 but this will work on any computer with a Debian based distribution. Your dongle must be BLE/Bluetooth 4.0 capable otherwise this won’t work. I am using an ID107HR activity tracker with pedometer and heart rate monitor, randomly chosen from the list of cheap ones available on Amazon. While using the Pi to talk to the the watch make sure Bluetooth on the phone is off as it can only connect to one device at a time.
The current distribution of Raspbian – jessie on the Raspberry Pi comes with version 5.23 of the BlueZ Bluetooth stack that’s rather old, dating from September 2014 which lacks many of the features we will be needing. The current version 5.44 of the BlueZ has many changes in the package with many familiar components such as hcitool and gatttool being depreciated, so I will be ignoring those and using the available commands, bluetoothctl, on the terminal.
With Raspbian – jessie installed we will need to update the Pi make sure some packages are installed and then installing the latest version of BlueZ. But first, remove the installed version 5.23 of BlueZ:
1 2 |
$ sudo apt-get --purge remove bluez $ sudo apt-get autoremove |
Next, perform the traditional housekeeping updates then install the build tools and USB libraries. Those parts that are installed already will be automatically skipped.
1 2 3 4 |
$ sudo apt-get update $ sudo apt-get upgrade $ sudo apt-get install build-essential $ sudo apt-get install -y libusb-dev libdbus-1-dev libglib2.0-dev libudev-dev libical-dev libreadline-dev |
Now, download the source code, at time of writing the current stable release is version 5.44, check the BlueZ site for the latest version.
1 2 3 4 |
$ cd ~ $ wget http://www.kernel.org/pub/linux/bluetooth/bluez-5.44.tar.xz $ tar xf bluez-5.44.tar.xz $ cd bluez-5.44/ |
Inside the BlueZ directory, configure, make (this takes a while), and install. The experimental option adds BLE support and enabling the library allows for python use later on:
1 2 3 |
$ ./configure --enable-experimental --enable-library $ make $ sudo make install |
At this stage we will need to check that the installation worked and that we can see your bluetooth dongle. With your bluetooth dongle in a USB port you should see it on your list of USB devices, here you see mine as device ID: 0b05:17cb ASUSTek Computer, Inc.:
1 2 3 4 5 6 |
$ lsusb Bus 001 Device 006: ID 0b05:17cb ASUSTek Computer, Inc. Bus 001 Device 004: ID 046d:c52e Logitech, Inc. Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub |
You will also need to enable the experimental services, edit the file /lib/systemd/system/bluetooth.service and in the [Service] section change the ExecStart line to end with –experimental:
1 |
ExecStart=/usr/local/libexec/bluetooth/bluetoothd --experimental |
Start the bluetooth service, and while we are at it enable it to load on boot:
1 2 |
$ sudo systemctl start bluetooth $ sudo systemctl enable bluetooth |
Once started, check the status of the bluetooth daemon with:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ sudo systemctl status bluetooth ● bluetooth.service - Bluetooth service Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled) Active: active (running) since Sat 2017-05-06 19:54:28 BST; 54s ago Docs: man:bluetoothd(8) Main PID: 10407 (bluetoothd) Status: "Running" CGroup: /system.slice/bluetooth.service └─10407 /usr/local/libexec/bluetooth/bluetoothd --experimental May 06 19:54:27 raspberrypi bluetoothd[10407]: Bluetooth daemon 5.44 May 06 19:54:28 raspberrypi systemd[1]: Started Bluetooth service. May 06 19:54:28 raspberrypi bluetoothd[10407]: Starting SDP server May 06 19:54:28 raspberrypi bluetoothd[10407]: Bluetooth management interface 1.14 initialized |
Should you need to, the service can be stopped and prevented from loading on boot with:
1 2 |
$ sudo systemctl stop bluetooth $ sudo systemctl disable bluetooth |
Finally, you may want to enable auto-power on for the device, to do so create this bluetooth config file:
1 2 |
$ sudo mkdir /etc/bluetooth $ sudo nano /etc/bluetooth/main.conf |
and add these two lines:
1 2 |
[Policy] AutoEnable=true |
You should restart the Pi at this point and check that the daemon has loaded properly with sudo systemctl status bluetooth
Start the bluetooth controller, you should see your dongles MAC address and alias:
1 2 |
$ sudo bluetoothctl [NEW] Controller 5C:F3:70:80:8A:A6 raspberrypi [default] |
for first time use, try scanning to find your watch, if it doesn’t appear it is out of range, its battery is flat, or your dongle does not support BLE, here you can see it as ID107 HR:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[bluetooth]# power on Changing power on succeeded [CHG] Controller 5C:F3:70:80:8E:FB Powered: yes [bluetooth]# scan on Discovery started [CHG] Controller 5C:F3:70:80:8E:FB Discovering: yes [NEW] Device C5:E8:FB:BF:F2:6C ID107 HR [CHG] Device C5:E8:FB:BF:F2:6C RSSI: -63 [CHG] Device C5:E8:FB:BF:F2:6C ManufacturerData Key: 0x001c [CHG] Device C5:E8:FB:BF:F2:6C ManufacturerData Value: 0x02 [CHG] Device C5:E8:FB:BF:F2:6C ManufacturerData Value: 0x00 [CHG] Device C5:E8:FB:BF:F2:6C ManufacturerData Value: 0xc5 [CHG] Device C5:E8:FB:BF:F2:6C ManufacturerData Value: 0xe8 [CHG] Device C5:E8:FB:BF:F2:6C ManufacturerData Value: 0xfb [CHG] Device C5:E8:FB:BF:F2:6C ManufacturerData Value: 0xbf [CHG] Device C5:E8:FB:BF:F2:6C ManufacturerData Value: 0xf2 [CHG] Device C5:E8:FB:BF:F2:6C ManufacturerData Value: 0x6c [CHG] Device C5:E8:FB:BF:F2:6C ManufacturerData Value: 0xe3 [CHG] Device C5:E8:FB:BF:F2:6C AdvertisingFlags: 0x00 ...etc... [bluetooth]# scan off |
bluetoothctl remembers your devices, so when you next use the program the watch appears on the list at the start. The controller has a number of options, these can be seen with help command. You can use show to view the status of your dongle:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[bluetooth]# show Controller 5C:F3:70:80:8A:A6 Name: raspberrypi Alias: raspberrypi Class: 0x000000 Powered: no Discoverable: no Pairable: yes UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb) UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb) UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb) UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb) UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb) Modalias: usb:v1D6Bp0246d052C Discovering: no |
The list of UUID’s show the services supported by the Dongle. Now we can power the dongle on, set the agent – this manages the connection, and then connect to the watch on which the bluetooth symbol will appear. Once connected there will be a pause then you will see a list of attributes supported by the watch, it is advertising the services available:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
[bluetooth]# power on Changing power on succeeded [CHG] Controller 5C:F3:70:80:8A:A6 Powered: yes [bluetooth]# agent on Agent registered [bluetooth]# default-agent Default agent request successful [bluetooth]# connect C5:E8:FB:BF:F2:6C Attempting to connect to C5:E8:FB:BF:F2:6C [CHG] Device C5:E8:FB:BF:F2:6C Connected: yes Connection successful [NEW] Primary Service /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service0008 00001801-0000-1000-8000-00805f9b34fb Generic Attribute Profile [NEW] Characteristic /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service0008/char0009 00002a05-0000-1000-8000-00805f9b34fb Service Changed [NEW] Descriptor /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service0008/char0009/desc000b 00002902-0000-1000-8000-00805f9b34fb Client Characteristic Configuration [NEW] Primary Service /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c 00000af0-0000-1000-8000-00805f9b34fb Unknown [NEW] Characteristic /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000d 00000af6-0000-1000-8000-00805f9b34fb Unknown [NEW] Characteristic /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f 00000af7-0000-1000-8000-00805f9b34fb Unknown [NEW] Descriptor /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f/desc0011 00002902-0000-1000-8000-00805f9b34fb Client Characteristic Configuration [NEW] Characteristic /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char0012 00000af2-0000-1000-8000-00805f9b34fb Unknown [NEW] Descriptor /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char0012/desc0014 00002902-0000-1000-8000-00805f9b34fb Client Characteristic Configuration [NEW] Characteristic /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char0015 00000af1-0000-1000-8000-00805f9b34fb Unknown [CHG] Device C5:E8:FB:BF:F2:6C UUIDs: 00000af0-0000-1000-8000-00805f9b34fb [CHG] Device C5:E8:FB:BF:F2:6C UUIDs: 00001800-0000-1000-8000-00805f9b34fb [CHG] Device C5:E8:FB:BF:F2:6C UUIDs: 00001801-0000-1000-8000-00805f9b34fb [CHG] Device C5:E8:FB:BF:F2:6C ServicesResolved: yes [ID107 HR]# |
and now that we have connected we can ask for some info:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[ID107 HR]# info C5:E8:FB:BF:F2:6C Device C5:E8:FB:BF:F2:6C Name: ID107 HR Alias: ID107 HR Appearance: 0x0341 Paired: no Trusted: no Blocked: no Connected: yes LegacyPairing: no UUID: Unknown (00000af0-0000-1000-8000-00805f9b34fb) UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb) UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb) ManufacturerData Key: 0x001c ManufacturerData Value: 0x02 ManufacturerData Value: 0x00 ManufacturerData Value: 0xc5 ManufacturerData Value: 0xe8 ManufacturerData Value: 0xfb ManufacturerData Value: 0xbf ManufacturerData Value: 0xf2 ManufacturerData Value: 0x6c ManufacturerData Value: 0xe3 [ID107 HR]# |
These UUID’s are used to describe the sevices available on the device, some are pre-defined and can be found in the a href=”https://www.bluetooth.com/specifications/gatt/characteristics” target=”_blank” rel=”noopener noreferrer”>GATT schema, others are vendor specific and unless they publicly release these, decoding can become rather difficult. There are four types of attribute:
Each attribute is identified by a 128 bit ID, for example, one of the characteristics from the list above: 00002902-0000-1000-8000-00805f9b34fb, the first eight bits are used as an unique identifier: 00002902 and are shown as UUID’s: 0x2902. Data is contained in services, each service has a number of characteristics that may contain further descriptions depending on the requirement of the characteristic. You can see how the data is mapped out in this chart:
A spreadsheet with the watch data reformatted and tastefully coloured to illustrates this. Observe the Service URL column, it looks a lot like a directory structure:
Here we see two services /service0008 and /service000c looking further into the second service: /service000c we see that it has four characteristics, and to of those have descriptors. We can interrogate the characteristics and descriptors to glean further information by selecting the attribute and reading, like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[ID107 HR]# select-attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f [ID107 HR:/service000c/char000f]# read Attempting to read /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x02 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0xa0 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x23 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x01 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x19 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x92 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 [CHG] Attribute /org/bluez/hci0/dev_C5_E8_FB_BF_F2_6C/service000c/char000f Value: 0x00 02 a0 23 00 00 00 01 00 00 00 19 00 00 00 92 00 ..#............. 00 00 00 ... |
Which is all very nice, but not particularly helpful as the manufacturer has chosen to use custom, proprietary, UUID’s for the watch. We don’t know the instructions to send to have the watch realease its data.
Inevitably, you’ll be wanting to automate connections. This becomes easy with the automation scripting language expect. Install, then make a script file:
1 2 3 4 |
$ sudo apt-get install expect $ cd ~ $ nano bttest $ chmod +x bttest |
In this example the script forgets the watch, finds the watch, connects to the watch, gets some info and then disconnects:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
#!/usr/bin/expect -f set timeout 10 set prompt ".*#" set usrpasswd "" set address "C5:E8:FB:BF:F2:6C" ## execute blutetoothctl spawn sudo bluetoothctl expect -re ".*password.*" send "$usrpasswd\r" ## forget about the device - if connected previously expect -re $prompt send "remove $address\r" expect -re $prompt sleep 3 ## switch on the dongle send "power on\r" expect "Changing power on succeeded" expect -re $prompt ## scan for devices send "scan on\r" expect -re $prompt sleep 5 send "scan off\r" expect -re $prompt ## set the agent send "agent on\r" expect "Agent registered" send "default-agent\r" expect -re $prompt sleep 2 ## connect to watch send "connect $address\r" expect -re $prompt ## get some info send "info $address\r" expect -re $prompt ## disconnect send "disconnect $address\r" sleep 2 expect -re "\[bluetooth\]#" ## bye send "exit\r" |
in the script, send sends a command, don’t forget to add the carriage return – \r and expect is used to wait for a response within the timeout period, here it is set to 10 seconds. expect -re is using regex when looking for a reply, otherwise it uses a literal string. So much more can be done with expect and there are many tutorials, such as this one written by FluidBank.
For analysing bluetooth data a couple of very useful tools are available, Wireshark and Android data logging. I will go through the installation but not look at the data in any detail, this posting is getting a bit long. This Section is in two parts, installing Wireshark and Android Debug Bridge.
Sniffing with the Shark
Wireshark is a network and bluetooth packet sniffer, it shows you network and bluetooth traffic occurring on your Pi. Here is a quick installation method for a reasonably new version of Wireshark (v2.2.4) from the backports, answer yes to the question “Should non-superusers be able to capture packets?”:
1 2 3 4 5 |
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7638D0442B90D010 $ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 8B48AD6246925553 $ echo 'deb http://httpredir.debian.org/debian jessie-backports main contrib non-free' | sudo tee -a /etc/apt/sources.list.d/jessie-backports.list $ sudo apt-get update $ sudo sudo apt-get -t jessie-backports install wireshark wireshark-dev libwireshark-dev |
and if you get a message about permissions, reconfigure the package and answer yes:
1 |
$ sudo dpkg-reconfigure wireshark-common |
Start Wireshark and double click your bluetooth device on the list, in my case bluetooth0. There is not much to see as Wireshark will only see traffic between the watch and the Pi:
Android Debug Bridge – ADB
For Anroid 4.2.2 and above, activate developer mode on the phone, go to Settings, tap About Phone and at the bottom of the list tap Build Number three times. Back in the main settings page Developer Options has appeared, tap developer and turn USB debugging On. With the phone plugged into a USB port a little Android head should appear in the information bar at the top-left of the screen. To begin we will need to install some udev rules written by Nicolas Bernaerts:
1 2 3 |
$ sudo wget -O /etc/udev/rules.d/51-android.rules https://raw.githubusercontent.com/NicolasBernaerts/ubuntu-scripts/master/android/51-android.rules $ sudo chmod a+r /etc/udev/rules.d/51-android.rules $ sudo service udev restart |
Install the android tools, confirm that you have at least version 1.0.31, and start ADB
1 2 3 4 5 6 7 8 |
$ sudo apt-get install android-tools-adb android-tools-fastboot $ adb version Android Debug Bridge version 1.0.31 $ adb devices * daemon not running. starting it now on port 5037 * * daemon started successfully * List of devices attached 064be417008eef9f offline |
At this point on the phone an allow USB debugging dialog will appear, give permission and always trust to authorise it. ADB will now show the device as a device:
1 2 3 |
$ adb devices List of devices attached 064be417008eef9f device |
If the device list is empty, with everything plugged in good and proper and the phone setup in developer mode, start your diagnosis by checking udev; open another terminal window and view logging with udevadm monitor –environment and reload with sudo udevadm control –reload I’m not entirely sure what I did to get it from ‘not working’ to ‘working’. If all else fails elevate yourself to root.
Data Capture
With ADB now setup we can capture the Bluetooth data being exchanged. With bluetooth off, in the Developer Settings find Enable Bluetooth HCI snoop log and turn it On. In the smartwatch app synchronise with your watch, once complete turn Bluetooth off manually – this is to minimise the amount of captured data. Don’t forget to turn logging off on the phone when done. To find where the log file has been stored and copy the file from the phone to the Pi use:
1 2 3 |
$ adb shell "cat /etc/bluetooth/bt_stack.conf | grep FileName" BtSnoopFileName=/sdcard/btsnoop_hci.log $ adb pull /sdcard/btsnoop_hci.log ~/ |
We can now use Wireshark to read the log file…
This wasn’t quite the posting I originally had in mind, I wanted to decode the data from the watch for my own use, making something more useful, impressive graphs and charts, than that provided by the Android App VeryFit 2.0 but as the manufacturer has chosen to use proprietary GATT codes it makes the job that much harder. It may be much simpler to just buy an expensive FitBit and download the data from them. But with writing this I now know a few things that were previously unknown, and I hope that this has provided some light to your BlueZ (a pun!, right at the end!).
In Part One I covered the byte codes sent by the Zoom Remote Controller RC1 and decoded data sent over the wire to the remote from the Zoom H2n Recorder.
In this post I will be covering the use of an Arduino style micro-controller to decode the signals sent by the remote, then control the recorder. I have used a Tennsy 3.1 Arduino clone as this is a small controller with two additional hardware serial ports, works with 3.3volt logic, and a with the addition of a crystal and button battery a real-time clock.
The connections on the remotes four pin 2.5mm jack, with pin one being the tip:
On the Teensy there are two hardware serial UARTs available in addition to that used by the USB port, UART2: Pin 9 (RX2), Pin 10 (TX2) and UART3: Pin 7 (RX3), Pin 8 (TX3). Serial data is sent at 2400 baud, 8 bits, no parity, 1 stop (8n1). The response data shown is for when the recorder is in XY Stereo mode (0x20, 0x21), different codes are returned when other recording modes are used, see the end of Part One for details.
This first chunk of code is for monitoring the outputs of the remote control and recorder. Connect Remote Receive – RX on the remote to RX2 – Pin 9 on the Teensy and Remote Transmit – TX to RX3 – Pin 7 and Ground to Ground on the Teensy. This program will output data received to the Arduino IDE’s serial monitor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
//written for a Teensy 3.x #define MONITOR_LED 13 int incomingByte2 = 0; int incomingByte3 = 0; unsigned long nowMillis = 0; void printByte(int p, int b) { char out[40]; sprintf(out, "RX%d: %8lu\t0x%x\t%d",p,nowMillis,b,b); Serial.println(out); } void setup() { delay(1000); Serial.begin(9600); Serial.println("ready"); // incoming data Serial2.begin(2400, SERIAL_8N1); // Remote Receive - RX Serial3.begin(2400, SERIAL_8N1); // Remote Transmit - TX pinMode(MONITOR_LED, OUTPUT); digitalWrite(MONITOR_LED, HIGH); } void loop() { nowMillis = millis(); if (Serial2.available() > 0) { incomingByte2 = Serial2.read(); printByte(2, incomingByte2); } if (Serial3.available() > 0) { incomingByte3 = Serial3.read(); printByte(3, incomingByte3); } } |
The output is in four columns; the UART seeing activity, current milliseconds and the received data in hexadecimal and decimal values.
Sending the command to the recorder blindly is quite straight forward, just send the bytes to the Remote Transmit – TX pin on the recorder (Zoom RX). This can be seen in the following, when run it starts the recorder recording for ten seconds. Connect: Remote TX to TX2 on the Teensy, Remote RX to RX2 and Ground to Ground.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
//written for a Teensy 3.x #define MONITOR_LED 13 // bytes to start and stop the recorder, a negative number is used as a delay int record[5] = { 0x81, 0x0, -100, 0x80, 0x0 }; void zoomTX(int d[], int len) { // show what is going to be done for (int i = 0; i < len; i++) { char out[40]; if (d[i] < 0) { sprintf(out,"TX2: delay(%d)",abs(d[i])); Serial.println(out); } else { sprintf(out, "TX2: %d\t0x%x", i, d[i]); Serial.println(out); } } // do the command for (int i = 0; i < len; i++) { if (d[i] < 0) { delay(abs(d[i])); } else { Serial2.write(d[i]); } } } void setup() { delay(1000); Serial.begin(9600); Serial.println("ready"); Serial2.begin(2400, SERIAL_8N1); // connection to Zoom. pinMode(MONITOR_LED, OUTPUT); digitalWrite(MONITOR_LED, LOW); delay(2000); Serial.println("REC: start"); zoomTX(record, sizeof(record)/sizeof(int)); digitalWrite(MONITOR_LED, HIGH); Serial.println("-----------------"); delay(10000); // wait ten seconds Serial.println("REC: stop"); zoomTX(record, sizeof(record)/sizeof(int)); digitalWrite(MONITOR_LED, LOW); Serial.println("-end-"); } void loop() { // nothing to loop } |
The next stage is to have the Teensy control the Zoom and listen for a response from the recorder. Again, as before connect: Remote TX to TX2 on the Teensy, Remote RX to RX2 and Ground to Ground. For the demonstration I have added three buttons to act as the controller.
The following code needs more development work, I ran out of time, but I think gives a good starting point for further investigation. I have placed the commands for the remote in a structure, each command; record, pause and mark has four components, the command to transmit to the Zoom, the expected responses when starting and stopping and a flag to store the status.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
//written for a Teensy 3.x #define MONITOR_LED 13 #define RED_BTN 2 #define GRN_BTN 3 #define BLU_BTN 4 unsigned long nowMillis = 0; unsigned long prevMillis = 0; unsigned long buttonPressDelay = 400; unsigned long serialTimeout = 8000; // it can take a few seconds for the zoom to start recording struct ZOOMTX { int record[4][5] = { {0x81, 0x0, -100, 0x80, 0x0}, // Transmit command data - a minus number is used as a delay(xxx) {0x20, 0x20, 0x21}, // Expected Response - Start (in XY Stereo mode) {0x21, 0x21, 0x20}, // Response - Stop {0} }; // status: record[3][0] = 1: recording, 0: not recording int pause[4][5] = { {0x80, 0x2, -100, 0x80, 0x0}, {0x21, 0x21, 0x20}, {0x20, 0x21}, {0} }; int mark[4][5] = { {0x80, 0x1, -100, 0x80, 0x0}, {0x20, 0x20}, {0x20, 0x20}, {0} }; int noAction[2] = { 0x20, 0x20 }; // this happens when pause is pressed while not recording. Response in XY Stereo mode }; struct LED_MONITOR { const unsigned int onTime = 300; const unsigned int offTime = 500; unsigned long prevMillis = 0; unsigned int wait = onTime; boolean state = true; }; ZOOMTX ZoomTX; LED_MONITOR LEDmonitor; void printByte(int p, int b) { char out[40]; sprintf(out, "RX%d: %8lu\t0x%x\t%d", p, millis(), b, b); Serial.println(out); } boolean getButton(byte btn) { boolean b = digitalRead(btn); if (b == true) { while (digitalRead(btn) != b) { delay(5); // wait until button release; } return true; } return false; } void zoomTransmit(int d[], int len) { for (int i = 0; i < len; i++) { char out[40]; if (d[i] < 0) { sprintf(out, "TX2: delay(%d)", abs(d[i])); Serial.println(out); } else { sprintf(out, "TX2: %d\t0x%x", i, d[i]); Serial.println(out); } } for (int i = 0; i < len; i++) { if (d[i] < 0) { delay(abs(d[i])); } else { Serial2.write(d[i]); } } } // http://forum.arduino.cc/index.php?topic=5157.0 boolean arrayCompare(int *a, int *b, int len_a, int len_b) { int n; if (len_a != len_b) { return false; } for (n = 0; n < len_a; n++) { if (a[n] != b[n]) { return false; } } return true; } int arrayLen(int a[5]) { int c=0; for (int n = 0; n < 5; n++) { if (a[n] == 0) { break; } c++; } return c; } boolean zoomCommand(int cmd[4][5]) { int responseBytes[5] = {0}; // for incoming serial data unsigned long serialNow = millis(); int expectedResponse[3] = {0}; int responseLen = 0; if (cmd[3][0] == 0) { // copy the expected response into expectedResponse Serial.println("start"); memcpy(expectedResponse, cmd[1], sizeof(cmd[1])); // expected start response responseLen = arrayLen(cmd[1]); } else { Serial.println("stop"); memcpy(expectedResponse, cmd[2], sizeof(cmd[2])); // expected stop response responseLen = arrayLen(cmd[2]); } // clear the incoming serial2 buffer while(Serial2.available()) { Serial2.read(); } // send the command to the zoom zoomTransmit(cmd[0], sizeof(cmd[0]) / sizeof(int)); // listen for a response while ((Serial2.available() > 0 && Serial2.available() < responseLen) && ((millis() - serialNow) < serialTimeout)) { } // listen for responseLen bytes or until serialTimeout if (Serial2.available() == 2) { // only two bytes received? for (int n = 0; n < 2; n++) { responseBytes[n] = Serial2.read(); printByte(2, responseBytes[n]); } if (arrayCompare(responseBytes, expectedResponse, 2, 2)) { Serial.println("RX: OK - Acknowledged! (two bytes)"); // mark has a two byte response return true; } if (arrayCompare(responseBytes, ZoomTX.noAction, 2, 2)) { Serial.println("RX: no action data received - Zoom not in the state we thought it was?"); } else { Serial.println("RX: did not understand the response - two bytes"); // probably not in XY Stereo mode? } return false; } else { // three or more bytes for (int n = 0; n < 3; n++) { // check the first three responseBytes[n] = Serial2.read(); printByte(2, responseBytes[n]); } if (arrayCompare(responseBytes, expectedResponse, 3, 3) == true) { Serial.println("RX: OK - Acknowledged!"); return true; } else { Serial.println("RX: did not understand the response - three bytes"); // probably not in XY Stereo mode? } } return false; } // https://www.baldengineer.com/millis-ind-on-off-times.html void toggleMoitorLEDstate(unsigned long n) { if ((unsigned long)(n - LEDmonitor.prevMillis) >= LEDmonitor.wait) { if (LEDmonitor.state) { LEDmonitor.wait = LEDmonitor.offTime; } else { LEDmonitor.wait = LEDmonitor.onTime; } LEDmonitor.state = !(LEDmonitor.state); LEDmonitor.prevMillis = n; } } void setup() { delay(1000); Serial.begin(9600); Serial.println("ready"); Serial2.begin(2400, SERIAL_8N1); // zoom connection pinMode(MONITOR_LED, OUTPUT); pinMode(RED_BTN, INPUT); pinMode(GRN_BTN, INPUT); pinMode(BLU_BTN, INPUT); digitalWrite(MONITOR_LED, LOW); } void loop() { nowMillis = millis(); if (getButton(RED_BTN) && nowMillis >= (prevMillis + buttonPressDelay)) { Serial.print("RECORD: "); if (zoomCommand(ZoomTX.record)) { if (ZoomTX.record[3][0] == 0) { digitalWrite(MONITOR_LED, HIGH); ZoomTX.record[3][0] = 1; // recording started } else { digitalWrite(MONITOR_LED, LOW); ZoomTX.record[3][0] = 0; // recording stopped ZoomTX.pause[3][0] = 0; // recording now not paused (just in case) } } else { Serial.println("RECORD COMMAND FAILED!"); } prevMillis = nowMillis; } // pause only works when recording if (getButton(GRN_BTN) && ZoomTX.record[3][0] == 1 && nowMillis >= (prevMillis + buttonPressDelay)) { Serial.print("PAUSE: "); if (zoomCommand(ZoomTX.pause)) { ZoomTX.pause[3][0] = !(ZoomTX.pause[3][0]); // toggle the pause status digitalWrite(MONITOR_LED, HIGH); } else { Serial.println("PAUSE COMMAND FAILED!"); // probably not recording, or not recording in XY Stereo } prevMillis = nowMillis; } if (getButton(BLU_BTN) && ZoomTX.record[3][0] == 1 && nowMillis >= (prevMillis + buttonPressDelay)) { Serial.print("MARK: "); if (zoomCommand(ZoomTX.mark)) { Serial.println("recording marked"); } else { Serial.println("PAUSE COMMAND FAILED!"); // probably not recording, or not recording in XY Stereo } prevMillis = nowMillis; } // blink the LED while paused if (ZoomTX.pause[3][0] == 1) { digitalWrite(MONITOR_LED, LEDmonitor.state); toggleMoitorLEDstate(nowMillis); } } |
There is a problem when resuming from pause, because the Zoom sends codes to flash the LED on the remote this can pick up the wrong pair of bytes; such as 0x21 0x21 instead of the expected 0x20 0x21.
I expect to be revisiting this, adding a timer function plus external battery for long running. I’m not sure how useful listening for a response is, sending the record command toggle on its own seems fairly robust without the need to check.
The Zoom H2n is a portable sound recorder looking like an old fashioned microphone it is a handheld device that provides an assortment of stereo and surround recording modes, it records onto an SD card in MP3 or WAV format with options for various bitrates and frequencies. The unit is powered internally by two AA batteries and can also be powered from the USB port. The recorder I am experimenting on has firmware version 2.00 installed.
The Zoom Remote Controller RC2 is a wired four pin 2.5mm jack plug connection, this remote has three buttons: record, mark, and pause there is also an LED to show when the H2n is recording. It is purchased separately from the recorder and only appears to be available bundled in an accessory pack. It looks difficult to take apart without leaving some damage, and this may not be necessary for decoding.
In this post I am looking to see how the remote works and find what control method it employs so in Part Two I can use an Arduino style micro-controller to provide an external trigger such as for timed recordings.
For testing I have made a breakout lead, this is essentially an extension cable split in half with a couple of molex style connectors allowing me to plug it into a breadboard. With this I have found the cable has the following connections, with pin one being the tip of the 2.5mm jack plug:
When checking with a multimeter I found continuity from the negative of the left battery (on the Mic Gain side) to pin three, ground, of the jack, there is also a connection between the positive of the right-hand battery to pin one of the jack but on the multimeter in diode mode there looks to be a capacitor, the voltage rises until no apparent connection is indicated. With no activity on the recorder the RX and TX pins show ~2.7 volts.
Determining the RX and TX pins turned out to be straightforward. When you press the record button after a moment the recording LED lights up, on the oscilloscope I can see three different square wave patterns for the three different buttons on the TX pin and a single type of square wave on RX to light the LED. The following images show the signal for the record button then that sent in response to to light the LED.
Also when buttons are pressed on the recorder data is seen on the RX line. For a more detailed examination I will need to break out the logic analyser.
Following some research where the remote for a Zoom H4n was examined, I set both channels being used on the logic analyser to the following:
First I decoded the buttons on the remote with the trigger on the logic analyser set to falling edge on the Remote TX line. On TX there is a pause between the two pairs of command bytes which appears to be the length of time the button was pressed, around 350-500ms, there is also a pause between bytes in the RX response, the value of the response changes depending on which recording mode you are in, those shown below are when the recorder is in XY Stereo:
Record Start | |||||
TX: | 0x81 0x0 | ~ | 0x80 0x0 | ||
RX: | 0x20 | 1.85s | 0x20 0x21 | record LED on | |
Record Stop | |||||
TX: | 0x81 0x0 | ~ | 0x80 0x0 | ||
RX: | 0x21 0x21 0x20 | 1.9s | 0x20 0x20 | record LED off | |
Pause (while recording) | |||||
TX: | 0x80 0x2 | ~ | 0x80 0x0 | ||
RX: | 0x21 | 51ms | 0x21 0x20 | then this repeats | |
492ms | 0x20 0x21 | to flash the LED | |||
492ms | 0x21 0x20 | until pause is pressed again | |||
Resume from Pause | |||||
TX: | 0x80 0x2 | ~ | 0x80 0x0 | ||
RX: | 0x21 0x21 | LED on | |||
Mark | |||||
TX: | 0x80 0x1 | ~ | 0x80 0x0 | ||
RX: | 0x21 | 492ms | 0x20 0x21 | LED on |
I was also able to capture the following activity sent to the remote when various buttons were pressed on the recorder itself with the recorder in XY Stereo mode. Other models of the recorders made by Zoom have more advanced remotes, such as the RC4 as featured in this hack of the H4n. I suspect they would work on this machine too. This time I set the logic analyser to trigger with a falling edge on Remote RX. I think the 0x20 code is used to indicate the display illumination has been turned off. I saw activity on all buttons except the Mic Gain knob.
Power On – Without remote attached | |||||
TX: | Lots of random activity | ||||
RX: | 0x10 | 400ms | 0x80 0x81 | ||
1.2s | 0x10 0x0 0x80 0x0 | ||||
140ms | 0x0 0x0 0x80 0x80 0x0 | ||||
~2ms | 0x0 0x80 0x0 0x0 0x80 0x0 | ||||
Power On – With remote attached | |||||
TX: | 0x0 | five pulses 30ms apart | |||
30ms | 0x0 0xA1 0x80 0x0 0xA1 | ||||
RX: | following pulses on TX | 0x80 0x81 0x80 0x10 | |||
1.2s | 0x10 0x0 | ||||
148ms | 0x0 0x0 | ||||
~4ms | 0x0 0x0 0x0 | ||||
Record Start | |||||
RX: | 0x20 | 20ms | 0x20 0x21 | LED on | |
Record Stop | |||||
RX: | 0x21 0x21 0x21 0x20 | ~ | 0x20 | ||
~283ms | 0x20 0x20 | LED off | |||
Menu \ Home – Into Menu | |||||
RX: | 0x20 0x20 0x0 | ||||
Exit from Menu | |||||
RX: | 0x20 | 45ms | 0x20 | ||
Play Switch: Up/Down/Press | |||||
RX: | 0x20 | 55ms | 0x20 | ||
Clipping detect (tapping the microphone with a pen) | |||||
RX: | 0x10 0x2 | 56ms | 0x2 | ||
59ms | 0x10 | ||||
60ms | 0x10 | ||||
354ms | 0x10 | ||||
Volume: Up and Down | |||||
RX: | 0x20 | ||||
Recording Mode Change: 4 channel surround | |||||
RX: | 0x10 0x10 0x30 | 14ms | 0x30 0x30 | ||
122ms | 0x34 | ||||
60ms | 0x30 | ||||
60ms | 0x30 | ||||
Recording Mode Change: XY Stereo | |||||
RX: | 0x30 0x30 0x20 | 216ms | 0x20 | ||
Recording Mode Change: 2 channel surround | |||||
RX: | 0x20 0x20 0x30 0x30 | 18ms | 0x30 | ||
148ms | 0x30 | ||||
164ms | 0x30 0x6 | ||||
56ms | 0x14 0x30 | ||||
477ms | 0x30 | ||||
Recording Mode Change: MS Stereo | |||||
RX: | 0x30 0x30 0x10 | 170ms | 0x10 | ||
50ms | 0x12 |
I think these response codes are to light up various LED’s on the more advanced Zoom RC4 remote, this suggests that other remotes would work in this recorder.
The following table shows the response codes given with different microphone configurations when record is clicked to start recording:
TX: | 0x81 0x0 ~100ms delay 0x80 0x0 |
XY Stereo: | 0x20 750ms delay 0x20 0x21 |
2 Channel Surround: | 0x30 750ms delay 0x30 0x31 |
MS Stereo: | 0x10 750ms delay 0x10 0x11 |
4 Channel Surround: | 0x30 750ms delay 0x30 0x31 |
In Part Two I will be covering the use of a Arduino style micro-controller as an alternative remote control.
I have a had a few requests on how to add more than one MCP23017 port expander to the Arduino via the i2c bus, this chip is a very useful and easy to use component that adds up to 16 digital I/O ports to the Arduino. This demonstration uses two MCP23017’s with three LEDs for output, one RGB LED to loop through a selection of colours, a single colour that blinks on and off, and another RGB LED that is controlled by a four button keypad. I have written the program using the millis() timer rather than the delay() function to maintain the illusion of multi-tasking.
The port expander has a three pins, A0, A1, and A2 for which an address can be set, each MCP23017 on your i2c bus must be set to have its own address, this is a three bit address and up to eight expanders can be used. Although I suspect things may noticeably slow down as you add more expansion. There is an SPI version available, the MCP23S07, that may be better for use with larger setups.
The address connections are shown the chart below, where zero is a connection to ground and one is a connection to 5V (or 3.3 volts). The MCP Address column refers to the address used by the Adafruit driver as you will see later.
chip address |
hardwired address | i2c address |
MCP address |
||
A2 | A1 | A0 | |||
000 | GND | GND | GND | 0x20 | 0 |
001 | GND | GND | 5v | 0x21 | 1 |
010 | GND | 5v | GND | 0x22 | 2 |
011 | GND | 5v | 5v | 0x23 | 3 |
100 | 5v | GND | GND | 0x24 | 4 |
101 | 5v | GND | 5v | 0x25 | 5 |
110 | 5v | 5v | GND | 0x26 | 6 |
111 | 5v | 5v | 5v | 0x27 | 7 |
In my circuit I have assigned the first expander address 0x20 and the second 0x21.
The port expander has been designed to run on a supply of 2.5v to 5.5v, so using a 5v supply from the USB port should be OK for a modest number of LED’s, you will need to calculate what your power requirements will be, approx 20mA per LED, so in this demonstration: 7 LED’s x 20mA = 140mA, plus whatever the chip itself is using. Bear in mind that each GPIO pin on the expander can only handle a maximum of current 25mA, and that the maximum total power dissipated must not exceed 700mW (after which point it’ll let the magic smoke out).
A Teensy 3.2 has a 3.3V supply but this is rated at 250mA maximum, the Ardunio UNO looks to be around 450mA on the 5V and only 50mA on the 3.3V output. For running from a battery I would look to use a 6v supply and a buck converter such as this Pololu step-down regulator.
On the left we see the Arduino UNO with the two i2c wires coming from pins A4 – SDA and A5 – SCL, two 4.7K ohm resistors are used for pullup, the port expanders are daisy chained along this bus, using pins 12 – SCL and 13 – SDA. See how the addresses are set on pins 15, 16 and 17, and note the 1K ohm resistor on the reset pin (18), without this resistor the circuit will work for a while then stop. The GPIO pins are connected as appropriate and the button switches do not need pull-up resistors as the port expanders internal pull-ups are turned on in the software.
Also if you are using RGB LED’s you will want to adjust the values I have given here, different colours have different power requirements so different resistor values are required to get a properly balanced colour LED. The RGB LEDs I have used are common anode, 5v is applied to the common and the path to ground to through the expander, this inverts the logic so setting the pin HIGH turns the LED off, and LOW turns it on.
The Adafruit MCP23017 library assigns each GPIO pin a number as you can see in the following diagram:
On both expanders the RGB LEDs are on ports 8,9 and 10 (to save me writing separate code for each RGB LED), the blink LED on port 7 of the first expander, and the buttons occupy ports 4, 5, 6, and 7 of the second. The first three buttons are used to toggle the red, green and blue in the second RGB LED while the fourth turns them all off. The other two LEDs are just doing things to show that they can do stuff.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
#include <Wire.h> #include <Adafruit_MCP23017.h> // https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library // setup the port expander Adafruit_MCP23017 mcp0; // assign each expander its own mcp name Adafruit_MCP23017 mcp1; const unsigned int onTime = 300; const unsigned int offTime = 1000; unsigned long previousMillis = 0; unsigned long rgbPMillis = 0; const unsigned long rgbOnTime = 200; unsigned long tt = onTime; boolean LEDstate = true; const byte rgbLED[3] = {8, 9, 10}; const byte blinkLED = 7; int count=0; int lastCount=1; // using a structure to group together the button controls. struct MCP_BUTTONS { const byte buttns[4] = {4, 5, 6, 7}; // buttons are attached to these GPIO ports boolean btnState[4] = {false, false, false, false}; boolean btnPressed = false; byte btnKey = 0; }; MCP_BUTTONS btns; // http://forum.arduino.cc/index.php?topic=119261.0 void displayBinary(int count) { char binary[4] = {0}; count += 8; // fix the length to four bytes 3 + 8 = 11 = 0b1011 itoa(count,binary,2); // convert count to a base 2 char array char* string = binary + 1; // remove the most significant (first) bit = 0b011 for(byte i = 0; i < 3; i++) { mcp0.digitalWrite(rgbLED[i],string[i] - '0'); // - '0' converts the bit to HIGH or LOW } } boolean isButtonBeingPressed() { boolean b; // check to see that the button has been released before setting the settings if (btns.btnPressed == true && mcp1.digitalRead(btns.buttns[btns.btnKey]) == true) { btns.btnPressed = false; btns.btnState[btns.btnKey] = !(btns.btnState[btns.btnKey]); // toggle the logic return true; } // read the four buttons, see if one is being pressed for (byte i = 0; i < 4; i++){ b = mcp1.digitalRead(btns.buttns[i]); if (b == false) { btns.btnKey = i; btns.btnPressed = true; break; } } return false; } void doThingWithButton() { boolean b; // the fourth button switches all LEDs off and resets the button state if (btns.btnState[3] == true) { for (byte i = 0; i < 4; i++){ mcp1.digitalWrite(rgbLED[i],true); btns.btnState[i] = false; } return; } for (byte i = 0; i < 3; i++){ b = !(btns.btnState[i]); // invert the logic for common anode LED: HIGH = off, LOW = on. mcp1.digitalWrite(rgbLED[i],b); } } void mcpSetup() { // setup the RGB LEDs - the RGB LEDs are common anode, so the output needs to be high to turn them off. for (byte i = 0; i < 3; i++) { mcp0.pinMode(rgbLED[i], OUTPUT); mcp0.digitalWrite(rgbLED[i], HIGH); // turn LEDS off mcp1.pinMode(rgbLED[i], OUTPUT); mcp1.digitalWrite(rgbLED[i], HIGH); // turn LEDS off } // setup the buttons for (byte i = 0; i < 4; i++) { mcp1.pinMode(btns.buttns[i], INPUT); mcp1.pullUp(btns.buttns[i], HIGH); // activate internal pullup resistor } } void setup() { delay(1000); Serial.begin(9600); Wire.begin(); mcp0.begin(0); // 0 = i2c address 0x20 mcp1.begin(1); // 1 = i2c address 0x21 mcpSetup(); mcp0.pinMode(blinkLED, OUTPUT); // setup the blink led mcp0.digitalWrite(blinkLED, LOW); Serial.println("ready"); } void loop() { mcp0.digitalWrite(blinkLED, LEDstate); // only change the RGB colour when the counter has changed if (count != lastCount) { displayBinary(count); lastCount = count; } boolean btn = isButtonBeingPressed(); if (btn == true) { doThingWithButton(); } unsigned long currentMillis = millis(); // the blinking LED is lit for a sorter time than it is off // I forgot where I got this code from, but will update once I do. if ((currentMillis - previousMillis) >= tt) { if (LEDstate) { tt = offTime; } else { tt = onTime; } LEDstate = !(LEDstate); previousMillis = currentMillis; } // increment the counter every rgbOnTime millis, then reset it when all three bits are true (0b111) if ((currentMillis - rgbPMillis) >= rgbOnTime) { count++; if (count > 7) { count = 0; } rgbPMillis = currentMillis; } } |
A quick captive nut how-to, for when you are making a wooden box that has a lid you need to remove on an irregular basis. Wood screws tend to maul the wood after a while and then the lid falls off, these captive nuts are easy to do and just work.
You will need:
In this example I am using an M4 nut and bolt and an 8mm drill bit. You may need to cut your bolt to length.
1. Drill your hole
in this case about 1 cm, deep enough so when you insert the nut and bolt the expoy will cover the nut. Clean it up, removing wood shavings and other debris.
2. Grease Up
To prevent the expoy sicking to the bolt smear some Vaseline onto your bolt, only a small amount is required, but you should get it into the thread, make sure you keep the nut clean. Thread the nut back onto the bolt, leaving 4-5mm of bolt protruding, as shown:
3. Mix up the Expoy
Mix a blob about the size of a marrowfat pea, enough to fill half of the hole. Drizzle this into the hole.
4. Plunge the nut and bolt into the hole
Wiggle it about a bit to make sure the expoy is well distributed. Position the bolt how you would like and leave to set. If you see the bolt moving use some sticky tape to hold it in place.
5. Remove the bolt
After about 10 to 15 minutes, the expoy will have set (unless you got that weird stuff). Use a screwdriver for at least the first turn as there will be a little adhesion, but it’ll come out cleanly.
If you have any excess expoy protruding it’s still quite soft at this time so cut it way with a Stanley knife as I have done in the example. Once fully cured expoy makes a hard plastic that can be difficult to cut.
The amount to cut off your bolt is the length of your bolt less the thickness of your lid less a bit of wiggle room, I use a cutting disc on a Dremel, and file the cut edge smooth. You need to ensure its long enough to go through the nut once cut as the expoy has a thread that gives a misleading nutness (technical term!) that soon wears away.
I hope you enjoy your captive nuts, I am sure they will give you many years of service. The same principle can also be applied to making captive bolts, especially if you wanted to use Wing Nuts for easier access.
Here is a circuit that allows you to use five buttons; up, down, left, right and select, on one analogue pin with the Arduino. It is a adaptation of one used on this LCD shield manufactured by DF Robot. I have used a 10K pull-up resistor and added a 100nF capacitor to help with debounce. The downside is that this can only detect one button press at a time, the button with the lowest resistor value is returned, and the output varies when different voltages are used to power it requiring an update to your software.
In this post I am looking to explore the effect of the existing resistor values, and how to choose your own resistors. This kind of circuit is called a multiple voltage divider or voltage ladder where each rung of the ladder, the switches in the diagram above, produces a different output voltage. Pressing one of the buttons on our circuit changes the output to the analogue port from 5V to a lower one determined by the number of resistors (R1 to R4) in series before the button, as you go down the ladder the output voltage to the pin increases. Here is a short sketch I used to test the circuit and show the voltage reading for each button:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
//converts analogue reading to volts int sensorPin = A0; int sensorValue = 0; float volts = 5; float maxVal = 1023; // maximum value read for 5v float max3V3 = 671; // maximum value read when 3.3 volts used. void showVolts(int val) { float voltage = val * (volts / maxVal); Serial.print(val); Serial.print(", volts: "); Serial.println(voltage); } void setup() { // the arduino uno is set to read analogue ports at 5v. So has a lower maximum at 3.3 volts. if (volts == 3.3) { maxVal = max3V3; } Serial.begin(9600); delay(1000); showVolts(analogRead(sensorPin)); // with no button pressed, this should equal volts. Serial.println("--------------------"); delay(4000); } void loop() { sensorValue = analogRead(sensorPin); if (sensorValue <= (int) maxVal-1) { // ignore input when no button pressed showVolts(sensorValue); } delay(20); } |
The results from this are shown in the chart below, for the power I used the regulated 5V and 3.3V outputs on the Ardunio Uno, if you run this set-up yourself you may notice an anomalous reading when you release the button, more about this later. Note that a button press drops the power down to the recorded value, the pin is normally high (5V/3.3V) through the 10K resistor.
Resistance1 | at 5v | at 3v3 | ||||
Btn | Value | Meter | reading2 | volt | reading2 | volt |
S1 | none | 0 | 0V | 0 | 0V | |
S2 | 330Ω | 334Ω | 31 | 0.15V | 19 | 0.10V |
S3 | 950Ω | 964Ω | 88 | 0.43V | 57 | 0.29V |
S4 | 1950Ω | 1948Ω | 166 | 0.81V | 108 | 0.53V |
S5 | 5250Ω | 5270Ω | 352 | 1.72V | 230 | 1.14V |
There are two columns for the resistance, the value marked on the resistors and the multimeter reading. It is best to use 1% tolerance resistors for this circuit to reduce the chances of the readings drifting close to the next. A good spread of output voltages ensures accurate switching.
All this works very well, but what to do if you wish to add more buttons, or have different output voltages? Looking at the circuit, each button can be seen as a voltage divider, such as shown in the diagram on the left. Where R1 is the 10K pull-up resistor and R2 is the sum of the resistors for the button being pressed.
With this simplified model we can calculate any missing value, but for now there are a couple of things to do; calculate the output voltage for the given input voltage and resistance and more usefully for us, work out a value for R2 to give us the desired output voltage.
Using Switch 2 in our existing circuit for example, with 5 volt power:
And again with Switch 4. This has three resistors in series so the value for R2 in the voltage divider is: 330R + 620R + 1K = 1950 ohm:
its rather satisfying when your calculations closely agree with the real world readings.
So, lets try working out some resistors based on the following specification: powered at 5V with five buttons, each 0.20V apart; S1: 0V, S2: 0.20V, S3: 0.40V etc. We know the input voltage, output voltage, and resistor R1, but not R2, here is the formula for finding the resistance of R2:
These values show the resistance required for each switch and to find the resistor the previous resistance value needs to be deducted. As these values probably won’t be in the E24 Standard Value range and won’t be available in the shops I have the nearest available value in the Standard Resistor column:
Btn | Resistance | Resistor | Standard Resistor |
|
S1 | none | |||
S2 | 417Ω | 417Ω | R1: | 390Ω |
S3 | 870Ω | 453Ω | R2: | 430Ω |
S4 | 1364Ω | 494Ω | R3: | 560Ω1 |
S5 | 1905Ω | 541Ω | R4: | 560Ω |
Obviously, we will need to get this onto breadboard for testing.
Using the same sketch as before I re-ran the tests with the new resistor values, the results are shown below; I have also included an Expected column to show the calculated voltage using the method shown earlier.
at 5v | at 3v3 | ||||||||
btn | Resistor | Resistance | expected | reading | volt | expected | reading | volt | |
S1 | none | – | 0 | 0V | – | 0 | 0V | ||
S2 | R1 | 390Ω | 390Ω | 0.19V | 39 | 0.19V | 0.12V | 25 | 0.12V |
S3 | R2 | 430Ω | 820Ω | 0.38V | 76 | 0.37V | 0.25V | 50 | 0.25V |
S4 | R3 | 560Ω | 1380Ω | 0.61V | 122 | 0.60V | 0.40V | 81 | 0.40V |
S5 | R4 | 560Ω | 1940Ω | 0.81V | 165 | 0.81V | 0.53V | 108 | 0.53V |
That, to me, looks close enough to the 0.20V separation at 5V specified to provide some stable hardware. Here is a short sketch to demonstrate the use of analogue buttons, it returns a number corresponding to the button pressed. When you release a button its resistance changes momentarily to give the Arduino a false reading, to fix this I have added de-bounce functionality to this program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
// analog button test #define ANALOG_BTN A0 byte btnVal = 0; byte lastButton = 0; unsigned long lastBtnPress; // changes the sensorValue into the number of the button pressed byte getButton(int sensorValue) { // change these sensorValue numbers to suit your own button readings if (sensorValue > 150) { return 5; } if (sensorValue > 110) { return 4; } if (sensorValue > 70) { return 3; } if (sensorValue > 30) { return 2; } if (sensorValue >= 0) { return 1; } return 0; } // which analog select button has been pressed? // byte imp analog input byte whichSelectBtn(byte imp) { const int sensorMax = 1023; // 671 for 3.3 volts // return when no buttons are being pressed if (analogRead(imp) >= sensorMax-1) { return 0; } byte buttonOut = 0; const int samples = 150; const int debounce = 30; // no of milisecs to wait before accepting a button change // take an average reading unsigned int sensorValue = 0; for (int i = 0; i < samples; i++) { sensorValue = sensorValue + analogRead(imp); } sensorValue = sensorValue / samples; buttonOut = getButton(sensorValue); // if buttonOut changes too rapidly, then ignore the change - debounce if (buttonOut != lastButton && lastBtnPress > millis()) { buttonOut = 0; } lastBtnPress = millis() + debounce; lastButton = buttonOut; return buttonOut; } void setup() { Serial.begin(9600); delay(1000); Serial.println("ready"); } void loop() { btnVal = whichSelectBtn(ANALOG_BTN); if (btnVal > 0) { Serial.print("button "); Serial.println(btnVal); if (btnVal == 5) { Serial.println("select button"); } // etc... or use a switch statement } } |
With this substitution on R3, changing the desired 510 ohm resistor for a 560 ohm, It may be interesting to see what kind of tolerance range we may be able to use. So, for the 0.60V output the optimal resistor would be 494 ohm but we only have Standard Resistor values available and to find the resistance value for our calculation to find the output voltage we need to add 820 ohm (R1 + R2) to R3:
Resistor | Resistance | Volts |
430Ω | 1250Ω | 0.5556V |
470Ω | 1290Ω | 0.5713V |
510Ω | 1330Ω | 0.5869V |
560Ω | 1380Ω | 0.6063V |
620Ω | 14400Ω | 0.6294V |
Clearly, using four decimal places shows is that a 510 ohm resistor is rounded up to 0.60V, while the 560 ohm rounds up to 0.61V, so for our purposes both are suitable. The other resistors will be outside the specification.
We also need to look at how much power our circuit is consuming, this is for two reasons: to ensure that our resistors are properly rated and that the power supply can supply enough power. The Ardunio Uno provides a regulated supply of 20mA at 5V and 50mA at 3.3V. The calculation is done with Ohms law:
So, in the original circuit the shortest path to ground is through Switch 1 and the 10K resistor:
a very low current, this is the maximum the circuit can use. Optionally, to work out the power use for another button add up all the resistors before the switch, for example in the original circuit Switch 5 comes to: 15250 ohm
Just to round things off, here are four formulae for finding any resistor and voltage in the divider circuit:
I am needing to control a solenoid from the Arduino to do some high speed water splash photography. The Solenoid works at 12v and draws up to 2.5w when open, obviously a direct connection to the Arduino is out of the question. Looking in my parts bucket I found an FQP30N06L MOSFET and recalled that I’d used these to control a small motor.
The FQP30N06L is a N-Channel MOSFET and can switch up to 60V DC at 32A, this is more than chunky enough for the solenoid. For use with the Arduino it needs to have ‘logic-level’ switching of 5V, looking at the datasheet for the Static Drain-Source On-Resistance – RDS(on) we can see a test condition where VGS=5V (or 4.5V) exists then we know the MOSFET is suitable.
The resistor R1 is there to protect the Arduino. On a MOSFET when a voltage is first applied to the Gate it can appear as a short to ground, limiting the current will prevent a surge that may cause damage. The Arduino supplies 5V at 40mA on a digital I/O pin.
A MOSFET switches when there is a charge at the Gate, the higher the charge the wider the gate opens and more current can flow through the Source to the Drain. Looking on the datasheet for our MOSFET the Gate Threshold Voltage VGS(th) shows a minimum of 1V and a maximum of 2.5V, this means that below 1V the gate is closed, at 1V the gate is ajar, as the voltage increases the gate opens wider until you reach 2.5V where it is fully open, power can go above this but the gate will not open any further. Going above 20V on the gate VGS will break the MOSFET.
So we need a resistor low enough to fully open the gates, but high enough to prevent the Arduino spluttering. So I chose to limit the current to 20mA and calculated the resistor with Ohms Law:
R = V / I
5V / 20mA = 250R (I used a 220R as that’s the nearest I had, this limits the current to 23mA)
Ground on the Ardunio is shared with the 12V power supply for the solenoid.
Connect the circuit to your Arduino, the MOFSET’s gate to a digital output and ground. You may want to avoid digital pins 0 and 1 as these are on the serial port and can cause the solenoid to rattle unexpectedly.
Here is the Solenoid equivalent of blink, with the solenoid circuit connected to digital pin 3 it is switched on and off once a second:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#define SOLENOID 3 #define LED 13 void setup() { pinMode(SOLENOID, OUTPUT); pinMode(LED, OUTPUT); } void loop() { digitalWrite(SOLENOID, HIGH); digitalWrite(LED, HIGH); delay(1000); digitalWrite(SOLENOID, LOW); digitalWrite(LED, LOW); delay(1000); } |
Comedy effect can be had by reducing the timings to milliseconds, although keeping that up for too long will probably knacker the solenoid.