Freesteel Blog » Optocoupler data transmission

Optocoupler data transmission

Friday, December 19th, 2014 at 9:46 pm Written by:

I’ve been having “fun” trying to deal with the noise on the MS5611 altitude sensor. It’s an extremely accurate piece of kit (detecting approx 10cm of altitude). Last month I discovered that it is very sensitive to its own working temperature, which depends on how frequently it is read. You miss one interrupt and it throws everything out of whack.

So I got one of these Adafruit Trinkets, which is a tiny microcontroller board just to poll the barometer on a timed loop, and attempted to bit-bang the values down one of its output pins to a digital read pin on the main Arduino. Here’s what the the code looks like:

void sendbitbangbyte(uint8_t v)
    digitalWrite(PinOut, LOW); 
    digitalWrite(PinOut, (v & 0x01 ? HIGH : LOW)); 
    digitalWrite(PinOut, (v & 0x02 ? HIGH : LOW)); 
    digitalWrite(PinOut, (v & 0x04 ? HIGH : LOW)); 
    digitalWrite(PinOut, (v & 0x08 ? HIGH : LOW)); 
    digitalWrite(PinOut, (v & 0x10 ? HIGH : LOW)); 
    digitalWrite(PinOut, (v & 0x20 ? HIGH : LOW)); 
    digitalWrite(PinOut, (v & 0x40 ? HIGH : LOW)); 
    digitalWrite(PinOut, (v & 0x80 ? HIGH : LOW)); 
    if (v & 0x80) {
        digitalWrite(PinOut, LOW);
    digitalWrite(PinOut, HIGH);

Sensible programmers wouldn’t do this because they’d use one of the many standard libraries available that operats on a standard protocol. But I wanted to tactically slot this into the main loop that polls and waits (9 milliseconds) for the barometer/thermometer to make its reading with the minimum disturbance possible. It’s important to keep everything regular and synchronized.

Here’s the contents of the main loop where it’s transmitting data following a request and before a reading.

void loop()
    while (m20)  ;  // wait for interrupt counter to clock over to 0
    // m20 advances every approx 2ms

    Wire.write(0x58);        // request raw pressure
    while (m20 < 2)  ;
    sendbitbangbyte(0x00 | (uint8_t)(TEMP & 0x3F)); 
    while (m20 < 3)  ;
    sendbitbangbyte(0x40 | (uint8_t)((TEMP >> 6) & 0x3F)); 
    while (m20 < 4)  ;
    sendbitbangbyte(0x80 | (uint8_t)(m20count & 0x3F)); 

    while (m20 < 5)  ;
    D2 = readRegister24();   // read raw pressure
    Wire.write(0x48);        // request raw temp

    UpdatePr(); // calc Pr, TEMP from D1, D2
    while (m20 < 7)  ;
    sendbitbangbyte(0x00 | (uint8_t)(Pr & 0x3F)); 
    while (m20 < 8)  ;
    sendbitbangbyte(0x40 | (uint8_t)((Pr >> 6) & 0x3F)); 
    while (m20 < 9)  ;
    sendbitbangbyte(0xC0 | (uint8_t)((Pr >> 12) & 0x3F)); 

    while (m20 < 10)  ;
    D1 = readRegister24();   // read raw temp

    // m20 clocks to 0 when it reaches 11

The important thing to note is that I'm sending 2 batches of three bytes, and it leaves the pin high when it's not transmitting. Originally I set BITTIME to 50microseconds, which means that a byte would occupy approximately 0.5ms and tend to be sent every 2ms with a 4ms gap between the batches and about 8ms between the pairs.

Here's how that shows up on the oscilloscope:

It's possible to zoom in and see the individual bits. This shows lots of square waves super-imposed, which are shaded according to how frequently each bit value is on or off.


The receiving code is interesting, because I didn't want to occupy the main Arduino in a busy loop waiting for the bits to come in. So I did an attachInterrupt on the incoming data to call my function interrupt3measure() whenever its value changes.

Normally you'd use a separate clock pin in, where the data pin is set to 1 or 0, and then the clock pin is changes by the sender, triggering the receiver interrupt to read the next binary bit into its shift register. Here I'm using only one pin and making use of the fact that I know the bits are approximately 58microseconds long [time for delayMicroseconds(50); digitalWrite(PinOut, value)]. By measuring the time between the changes I can shift multiple same bits in from the top as they arrive.

uint16_t BITTIME = 58; 
volatile int16_t recb = 0x0000;  
volatile uint8_t bitcount = 0x00; 
volatile int16_t receivedbyte = -1;  // -1 means no byte yet
void interrupt3()
    uint32_t m = micros(); 
    uint32_t md = m - m0; 

    // 15 bitwidths since last reset, so reset now
    if (md > BITTIME * 15) {
        recb = 0x8000;   
        bitcount = 0x00; 
        m0 = m; 
    } else {
        // calculate the number of bits to shift in
        uint8_t gbits = ((md - bitcount * BITTIME + BITTIME / 2) / BITTIME); 
        recb = (recb >> 1) ^ 0x8000;  // shift and toggle
        recb = recb >> (gbits - 1);   // shift additional bits
        bitcount += gbits; 

        // boundary bit and trigger bit shifted out (got a byte)
        if ((recb & 0x007F) != 0) {
            uint16_t lrecb = recb; 
            while (lrecb & 0x3F)
                 lrecb = lrecb << 1; 
            receivedbyte = recb & 0x00FF;  

This is going to be pretty efficient for interrupts on the main Arduino because, for example, to send a byte 0 there would only be two interrupts called, and gbits would be 9.


So this is how it operated happily, until I started running those vibration motors again, and the tiny, tiny buzzing on the ground voltage altered the temperature in the barometer by 0.01degC and lost its precision.

I was in the process of isolating each motor circuit completely with an optocoupler each, when I suddenly thought, "hang on, why don't I just isolate the barometer itself?".

Of course, a crappy optocoupler from Adrian's spares drawer switches too slowly, as I could see from the oscilloscope trace of the signal on the other side which no longer a perfect square wave:
(This picture was taken after I had tripled the length of bits to 180microseconds each, just to make sure the information got through. A third of the width is cutting it too fine.)

Now, after a long day of soldering, I've got my lovely little isolated barometer with a four wire input socket, two for an independent power, and two to connect to the isolated data stream on the other side of the optocoupler.

I might make a second, and then I'll be able to put them above and below the wing to measure the differences in air pressure that would indicate the location of the lift. The black phone connector (from the leftovers in the Housahedron box) is a really useful item for robustly connecting these wires. I am already aware that if I have any electricals to connect between the batteries and computers in my harness and any sensors on the wing, I am liable to connect those up first and feel like I'm done, and then attempt to set off the hill without actually connecting myself to the glider. And that would be bad. Must take care.

Next job is to 3D print a nice little box to put this circuit in. I have never done any 3D printing before. Finally I have an excuse. The favoured tool in the hackspace is OpenSCAD, oddly enough. Real CAD systems like SolidWorks are just too much of a drag in terms of the way programmers want to work on small parametrized solid components.

Leave a comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <blockquote cite=""> <code> <em> <strong>