Interrupt driven bit-banger serial code Need fresh eyes to see where it's wrong

Hi all,

I wrote a small interrupt driven bit-banger to use for diagnostics on an ATtiny85 processor. The TX parts works perfectly, the receive part also works, but sometimes it misses characters. For example, I might send a string of letters in a row and what is received is something like this:

[b]
abcd[missed]fg[missed]ijk

[/b]

The receive code looks for a falling edge (which is taken to be the start bit). The edge detect is then disabled, a bit count (10) sent to the main timer ISR and then each bit is pulled in and assembled into received data. When done, "rx ready" is flagged and the edge detect is re-enabled. The same timer ISR is used for both TX and RX. Here's the code - I can't see where I'm going wrong... maybe one of you can.

ISR (TIMER0_COMPA_vect) // timer fires once per 8.68 usec (115200 baud bit rate)
{
    ///////////////// transmit section ///////////////
    if (tx_bits) {
        tx_bits--;
        // set or clear TXO bits based on data
        _BV (tx_bits) & data_out ? IO_PORT |= TXO : IO_PORT &= ~TXO;
    } else {
        tx_bits = 0;
    }
    //////////////////////////////////////////////////

    ////////////////// receive section ///////////////
    if (rx_bits) {
        rx_bits--;
        // set or clear data bits based on RXI
        IO_PIN & RXI ? data_inp |= _BV (rx_bits) : data_inp &= ~_BV (rx_bits);
    } else {
        rx_bits = 0;
        if (!rx_rdy) { // if rx was in progress AND is done
            buff_inp = data_inp; // buffer rx'd data
            GIMSK |= _BV (INT0); // re-enable start bit detection
            rx_rdy = 1; // tell world serial data is ready
        }
    }
    //////////////////////////////////////////////////
}

// bit swap function to send serial data in the
// correct direction (LSB first)
uint8_t bit_reverse (uint8_t x)
{
    x = ((x >> 1) & 0b01010101) | ((x << 1) & 0b10101010);
    x = ((x >> 2) & 0b00110011) | ((x << 2) & 0b11001100);
    x = ((x >> 4) & 0b00001111) | ((x << 4) & 0b11110000);
    return x;
}

////////////////////////////////////////////////////////////////////
//  Serial data format, 8 bits, no parity
//
//  start                                          stop
//  bit-----+   0   1   2   3   4   5   6   7   +---bit
//  ______  |  ___ ___ ___ ___ ___ ___ ___ ___ _|______
//        | | |   |   |   |   |   |   |   |   | | .   ^--idle high
//        | | |lsb|   |   |   |   |   |   |msb| | .
//        |___|___|___|___|___|___|___|___|___|   .
//
////////////////////////////////////////////////////////////////////
uint8_t serial_inp (void)
{
    while (!rx_rdy); // wait until serial data available
    cli(); // interrupts off
    GIMSK |= _BV (INT0); // got current byte, re-enable start bit detect
    sei(); // interrupts on
    return bit_reverse ((uint8_t) ((buff_inp >> 1) & 0xFF)); // return data
}

Any ideas will be appreciated. Pointless questions like "why do you want to do this" will not be appreciated. :slight_smile:

Have you ensured that you are taking samples in the centres of each bit?

The centre of the first bit will be 1.5 bit widths after the detection of the falling edge of the start bit

You may be interested in my Yet Another Software Serial

And I suspect that 115200 is too ambitious - doing stuff at 8µsecs intervals does not leave the Arduino much time to do stuff. Get it working at 9600 baud first.

I recently discovered the Attiny1634 which has HardwareSerial - works fine at 500,000 baud

...R

Robin2:
Have you ensured that you are taking samples in the centres of each bit?

The centre of the first bit will be 1.5 bit widths after the detection of the falling edge of the start bit

You may be interested in my Yet Another Software Serial

And I suspect that 115200 is too ambitious - doing stuff at 8µsecs intervals does not leave the Arduino much time to do stuff. Get it working at 9600 baud first.

I recently discovered the Attiny1634 which has HardwareSerial - works fine at 500,000 baud

...R

I (supposedly) receive all 10 bits, then shift the whole thing down by 1 bit to get the data.

I did actually try resetting the timer counter (TCNT0) to 1/2 the interval of a bit to "synchronize" the bit reading to the center of a bit, but that didn't work any better.

Actually, 8 usec is a LOT of time to do stuff... 176 clock cycles between bits... more or less...

The serial code is only for debugging (like "what exactly does "X" contain at this point?").

As far as hardware serial... can't use it on the Tiny85 (nor any simpler derivatives) because the '85 doesn't have a UART. It's got a "USI" (universal serial interface) for doing SPI and 2-Wire, but making it emulate a UART is a pain in the behind.

All I wanted was a simple bit-banger for testing... and I can't see where I'm going wrong with the code I have.

making it emulate a UART is a pain

Have you seen the USI serial library?

Won't help with your code but does work well with ATtiny processors...
tiny - Google Search debug knock bang

Krupski:
I (supposedly) receive all 10 bits, then shift the whole thing down by 1 bit to get the data.

I thought the whole purpose of this Thread was because your code is not receiving all the bits correctly :slight_smile:

I did actually try resetting the timer counter (TCNT0) to 1/2 the interval of a bit to "synchronize" the bit reading to the center of a bit, but that didn't work any better.

My experience suggests that something like that is essential.

Or, to be more blunt, the ONLY reason I can think for software-serial not working is incorrect timing.

...R