In this post I'll go one step further and automatically interpret the bus signals into memory read and write operations and output to the serial port.
Fortunately the HL168Y reads and writes to the EEPROM one byte at a time (the EEPROM supports page read/write which would complicate matters). The output format is one operation per line:
"R aaa vv" or "W aaa vv", where R indicates a read operation and W a write operation. The second parameter "aaa" is the memory location in hex (from 000 to 3fff) and "vv" is the 8 bit value in hex read from / written to the memory location.
On taking a blood pressure measurement the following bus activity was observed after the reading as complete:
W 010 05 W 011 1e W 012 86 W 013 28 W 014 10 W 015 11 W 016 82 W 017 44
Referring to the record format in part 2, that's month 5, day of month 0x1e (ie 30), hour is 6 with the PM flag on (ie 6pm), minutes 0x28 (40) and BP 111 / 82 with heart rate of 0x44 (68).
Signals to Read/Write flowchart
Signals to Read/Write Arduino C/C++ program
/** * I2C bus snooper. Written to eavesdrop on MCU to EEPROM * communications in a HL168Y blood pressure monitor. SCL * is connected to Arduino pin 2 and SDA to pin 3. * This version will decode read and write operations to * EEPROM outputting to serial port as * "R aaa vv" or "W aaa vv" records (one per line) where * aaa is 10 address bits as hex digits and vv is value as * hex digits. */ int SCL = 2; int SDA = 3; char hexval[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; #define LOG_SIZE 255 unsigned char datalog[LOG_SIZE]; unsigned char logptr; void setup() { pinMode(SCL, INPUT); pinMode(SDA, INPUT); Serial.begin(115200); } void loop() { unsigned char s, b, byteCounter, bitCounter, rwFlag; unsigned char addr_hi, addr_lo; unsigned int t = 0; logptr = 0; waitForStart: // Expect both SLC and SDA to be high while ( (PIND & 0b00001100) != 0b00001100) ; // both SLC and SDA high at this point // Looking for START condition. Ie SDA transitioning from // high to low while SLC is high. while ( (PIND & 0b00001100) != 0b00000100) { if ( (--t == 0) && (logptr > 0) ) { writeData(); } } firstBit: byteCounter = 0; bitCounter = 0; nextBit: // If SCL high, wait for SCL low while ( (PIND & 0b00000100) != 0) ; // Wait for SCL to transition high. Nothing of interest happens when SCL is low. while ( (PIND & 0b00000100) == 0) ; // Sample SDA at the SCL low->high transition point. Don't know yet if this is a // data bit or a STOP or START condition. s = PIND & 0b00001000; // Wait for SCL to transition low while monitoring SDA for a transition. // No transition means we have data or ACK bit (sample in 's'). A hight to // low SDA = START, a low to high SDA transition = STOP. if (s == 0) { // loop while SCL high and SDA low while ( (PIND & 0b00001100) == 0b00000100) ; if ( (PIND & 0b00001100) == 0b00001100) { // STOP condition detected if (logptr > LOG_SIZE - 20) { writeData(); } goto waitForStart; } } else { // loop while SCL high and SDA high while ( (PIND & 0b00001100) == 0b00001100) ; if ( (PIND & 0b00001100) == 0b00000100) { // START condition. goto firstBit; } } // OK. This is a data bit. bitCounter++; if (bitCounter < 9) { b <<= 1; // if data bit is '1' set it in LSB position (will default to 0 after the shift op) if (s != 0) { b |= 0b00000001; } goto nextBit; } // 9th bit (ack/noack) bitCounter = 0; byteCounter++; switch (byteCounter) { case 1: // 1010AAAW where AAA upper 3 bits of address, W = 0 for writer, 1 for read if ( (b & 0b11110000) != 0b10100000) { goto waitForStart; } // Set A9,A8 bits of address addr_hi = (b>>1) & 0b00000011; rwFlag = b & 0b00000001; break; case 2: // data if rwFlag==1 else lower 8 bits of address if (rwFlag == 1) { // data read from eeprom. Expect this to be the last byte before P //datalog[logptr++] = ' '; datalog[logptr++] = 'R'; datalog[logptr++] = ' '; datalog[logptr++] = hexval[addr_hi]; datalog[logptr++] = hexval[addr_lo>>4]; datalog[logptr++] = hexval[addr_lo & 0b00001111]; datalog[logptr++] = ' '; datalog[logptr++] = hexval[b >> 4]; datalog[logptr++] = hexval[b & 0b00001111]; datalog[logptr++] = '\n'; } else { addr_lo = b; } break; case 3: // only have 3rd byte if rwFlag==0. This will be the data. if (rwFlag == 0) { //datalog[logptr++] = ' '; datalog[logptr++] = 'W'; datalog[logptr++] = ' '; datalog[logptr++] = hexval[addr_hi]; datalog[logptr++] = hexval[addr_lo>>4]; datalog[logptr++] = hexval[addr_lo & 0b00001111]; datalog[logptr++] = ' '; datalog[logptr++] = hexval[b>>4]; datalog[logptr++] = hexval[b & 0b00001111]; datalog[logptr++] = '\n'; if (logptr > LOG_SIZE - 10) { writeData(); } break; } } // end switch goto nextBit; } void writeData () { for (int i = 0; i < logptr; i++) { Serial.print(datalog[i]); datalog[i] = 0; } logptr=0; Serial.println ("\n"); }
Converting read/writes operations into BP measurement records
I wrote a small JSP script to make the conversion.
<% String SEP = "\t"; if ( request.getMethod().equals("GET") ) { %> <form method="POST"> <textarea name="data" rows="20" cols="40"></textarea> <br /> <input type="submit" value="Submit" /></form> <% } else { response.setContentType("text/plain"); int[][] records = new int[40][8]; String[] lines = request.getParameter("data").split("\r\n"); for (String line : lines) { if ( ! (line.startsWith("R") || line.startsWith("W"))) { continue; } String[] p = line.split(" "); int addr = Integer.parseInt(p[1],16); int value = Integer.parseInt(p[2],16); // Addresses under 16 are not for BP readings if (addr < 16) { continue; } int recordIndex = (addr - 16)/8; int col = addr%8; records[recordIndex][col] = value; } for (int i = 0; i < 40; i++) { if (records[i][0] == 0) { continue; } // European format //out.write (records[i][1] + "/" + records[i][0] + "/2010 "); // US/spreadsheet format out.write (records[i][0] + "/" + records[i][1] + "/2010 "); // Hours // Stored in 12 hour clock value (lower nibble) and bit 7 = PM flag. int h = records[i][2]; h = h > 127 ? (h & 0xf) + 12 : (h & 0xf); if (h < 10) { out.write ("0"); } out.write (h + ":"); // Minutes int m = records[i][3]; if (m < 10) { out.write ("0"); } out.write ("" + m + SEP); // Systolic int sys = ((records[i][4] & 0xf0)>>4) * 100; sys += ((records[i][5] & 0xf0)>>4) * 10; sys += ((records[i][5] & 0x0f)); out.write ("" + sys + SEP); // Diastolic int dia = (records[i][4] & 0x0f) * 100; dia += ((records[i][6] & 0xf0)>>4) * 10; dia += ((records[i][6] & 0x0f)); out.write ("" + dia + SEP); // Heart rate out.write ("" + records[i][7]); out.write ("\n"); } } %>
Running this with the write records above results in the following BP record:
5/30/2010 18:40 111 82 68
I've chosen US date format as this works best with spreadsheets.