As part of a prototype developed 12 months ago I was tasked with reading measurements from a blood pressure cuff [sphygmomanometer] in real time. Not surprisingly there are no consumer level devices that have a serial interface because what ‘normal’ person would want such a thing!
Initially we considered our own interface for a blood pressure cuff. Just run the pump and take the readings with our own processor and pressure sensor, how hard can it be. Rather difficult it seems, the processing and knowledge required to develop a device to perform even rudimentary readings would have completely blown the time budget. Instead we looked to hack an existing device, enter the Omron RS8.
The Omron RS8 met several of our design requirements, small, battery operated, quiet and entirely wrist mounted. So no heavy base unit. With the initial attack vector expected to be the NFC interface the case was opened and warranty voided. Thankfully the PCB was not too complicated, although populated on both sides the dual layer design made circuit tracing easy.
Working through the ICs on the top of the PCB and searching for data sheets showed the main IC to be something somewhat obscure and difficult to source information for. A Similar scenario again for the NFC chip, little available information in data sheets or examples on the web. Reviewing the product documentation gave the impression the NFC interface was designed to upload logs and not stream real time data. The rear side of the PCB however held promise. A separate IC was used to drive the segmented LCD display built into the housing, the BU9795A. Whilst still an obscure component a data sheet was available and it listed an SPI interface with a nice clear protocol.
Bring on the bus pirate!
With a USB microscope and the data sheet for the LCD driver the traces for MOSI, SCK and CS were traced and bodge wires connected. A quick confirmation with the scope to observe traffic and it is time to connect the bus pirate.
Traced SPI lines with marked vias
Using the passive packet sniffing macros built into the bus pirate it became somewhat trivial to capture data driving the display. A few hours analysing the code and it became clear to see how the master would rewrite the entire LCD display buffer several times a second. Considering the LCD displayed all of the information we wanted to capture sniffing this bus with another MCU would be the easiest way to capture the information for re-streaming.
The Plan.
Data needed to be transmitted to another MCU running the rest of our prototype. A UART was going to be the easiest interface with this device so we needed something to sit in the middle. Sadly the Arduino receives a lot of criticism from some parties who consider it to be a children’s toy or worse a tinkeres gadget as opposed to a serious engineering tool. This is ridiculous, for small prototypes, one offs and proofs of concept it is one of the best choices. Small cheap break out boards combined with a simple and free tool chain make it great for work within teams, not to mention a very broad support community with great examples easily available. Considering this and the other people working on the team an Arduino pro mini was selected as the interpreter chip. It would sniff traffic streaming to the LCD driver then mirror the display buffer in the Arduino’s memory. The main routine would then analyse the display buffer, interpret the pixels and stream the data out of the Arduino’s UART.
Bringing the plan together
The first step in this process is to map the LCD buffer to the digits & symbols on the display. With some experimentation the LCD and driver IC could be powered with the RS8’s main IC held in reset. The first few configuration bytes could be decoded thanks to the data sheet. Then the Arduino was used to drive the SPI bus writing to the display one pixel at a time and recording the corresponding bit address with the relevant pixel. Thankfully there is only 140 bits to work through….
With a pixel map in hand, captures of the SPI transmissions and a data sheet reverse engineering from this point is now somewhat procedural:
Capture each SPI session from CS Low to CS High
Ignore all configuration bytes
Capture display bytes and store in local memory
1 | // SPI interrupt routine |
2 | ISR (SPI\_STC\_vect) |
3 | { |
4 | byte c = SPDR;// grab byte from SPI Data Register |
5 | buf\[count\] = c; |
6 | processed = false; |
7 | count++; |
8 | }// end of interrupt routine SPI\_STC\_vect |
9 | |
10 | void loop (void) |
11 | { |
12 | if(digitalRead(csPin) == HIGH){//While there is no data being received |
13 | |
14 | 4\. Decode sniffed buffer into replica bit array \[tragic waste of memory but there way plenty spare\] |
15 | |
16 | \[caption id="" align="alignnone" width="599.0"\]![BU9795 Pixel segment bit map](/images/omronPixelMap.png) BU9795 Pixel segment bit map\[/caption\] |
17 | |
18 | while(count <= counted){ //for as long as there is still data in the buffer |
19 | byte dispProc = buf\[count\]; //store current byte |
20 | for(byte i = 0; i < 4; i++){ |
21 | disp\[addr\]\[i\] = (dispProc & 0x01); //store only last bit |
22 | dispProc << 2; //shift right by 1 value |
23 | } |
24 | addr++; |
25 | for(byte i = 0; i < 4; i++){ |
26 | disp\[addr\]\[i\] = (dispProc & 0x01); //store only last bit |
27 | dispProc << 2; //shift right by 1 value |
28 | } |
29 | addr++; //The display address indexes twice for every count index as array is only 4 bits |
30 | count++; |
31 | } |
With the display buffer replicated in the Arduino’s memory, pixels needed to be mapped to systolic and diastolic values. Both reading were represent in 7-segment configuration. Therefore 40 pixels* directly correlated to the values we required. As is common with 7-segment displays each pixel in the array is assigned a letter, a->g
*100’s segments missed ‘f’ element as displaying a value >299 would have some serious health implications.
By h2g2bob, sourced from Wikipedia
The corresponding bit in the display buffer for each pixel in the segment was formated into a sungle byte. I.E. 0b0ABCDEFG
This would give each number a unique pixel signature regardless of buffer location.
[7] = 0b01110000 = 112
[4] = 0b00110011 = 51
A switch/case statement could then decode the display value of each segment.
With the value of each segment the complete values for systolic and diastolic were extracted. The systolic pressure was constantly updated throughout the measurement process and the final value was shown at the end with diastolic. Several other bits were analysed to determine the current operating state of the cuff. This could be used to set bit flags if a reading was in progress, completed, idle or there was an error.
All that was left to do was format an output string and print it to the Arduino’s UART for use in another system. A final modification was to connect one of the outputs to the Start/Stop button on the device. Then the Arduino could start and stop measurements without user intervention.
Whilst not the worlds most elegant solution it was a great hack for a time sensitive application. The method was so reliable we removed the LCD completely and drastically shrink the size of our prototype.