NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » RS232 Bit Banging producing unexpected results.

August 16, 2012
by Finis
Finis's Avatar

I'm working on a project for dmx lighting.

I intend on bit banging both for receiver and transmitter.

However, my initial test, bit banging a single char, repetitively. With rs232 protocol, start bit - 8 data bits - 2 stopbits. Transmitted to putty. Is not producing the character i'm expecting. I've tried switching the byte order, inverting the binary, and other things with no avail.

#include <avr/io.h>
#include <avr/interrupt.h>
#include "../libfinis/myDelay.h"

#define dmxpinHigh PORTD |= (1<<PD1)
#define dmxpinLow PORTD &= ~(1<<PD1)

#define readBit(BYTE,BIT) (BYTE & (1<<BIT))

volatile int8_t bitno = 0;
volatile char byte = 'a';
int main()
{
    UCSR0B = 0;
    DDRD |=(1<<PD1);
    timer_init();
    sei();
    dmxpinHigh;
    delay_ms(1);

    byte = 0b01000110;

    for(;;);

}

void timer_init() {

//Clock at 14745600 , 9600 Baud, every 104us
  TCCR0A = (1<< WGM01);
  TCCR0B = (1<<CS02); // 256
  TIMSK0 = (1<< OCIE0A);
  OCR0A = 6;
 }

SIGNAL(SIG_OUTPUT_COMPARE0A) 
{
    switch(bitno) 
    {
        case 0:
            //send start bit
            dmxpinLow;
            bitno++;
            //delay_us(1);
            break;

        case 1:
            //send bit 0
            if(readBit(byte,7)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 2:
            //send bit 1
            if(readBit(byte,6)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 3:
            //send bit 2
            if(readBit(byte,5)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 4:
            //send bit 3
            if(readBit(byte,4)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 5:
            //send bit 4
            if(readBit(byte,3)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 6:
            //send bit 5
            if(readBit(byte,2)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 7:
            //send bit 6
            if(readBit(byte,1)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 8:
            //send bit 7
            if(readBit(byte,0)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 9:
            //send stop bit 1
            dmxpinHigh;
            bitno++;
            break;

        case 10:
            //send stop bit 2
            dmxpinHigh;
            bitno = 0;

            break;

    }
    //delay_us(10);
}
August 17, 2012
by pcbolt
pcbolt's Avatar

Finis -

What kind of results are you getting out of PuTTy? Just curious because you have 'byte' set to 'a' first then 0b01000110 later on which seems to be 'b' (little endian version). Do you have an oscilloscope? It would be interesting to see what the output from the MCU looks like. I'm sure you checked this, but just in case, did you set parity to none in PuTTy? I would try a relatively long delay between frames (100 ms maybe). This is tricky the way your code is set up since everything is happening inside the timer interrupt. First you'd have to start with a count variable (16-bit) and add in a flag variable (both global and volatile)...

volatile uint16_t count;       // initialize this to 0 in "timer_init()"
volatile uint8_t ok_to_send;   // initialize this to 0 in "timer_init()"

Then inside the interrupt you can count the number of times it cycles through and only when enough cycles occur do you allow it to send your character. Something like this...

SIGNAL(SIG_OUTPUT_COMPARE0A){
  if (!ok_to_send){
    if (++count >= 1000){
      count = 0;
      ok_to_send = 1;
    }
    return;
  }
  // Place you switch statements here and just add the following
case 10:
  //send stop bit 2
  dmxpinHigh;
  bitno = 0;
  ok_to_send = 0;
  break;

After reading through this a little more, maybe you can just make "bitno" a 16-bit number, add a default case that resets "bitno" to 0 only after it reaches 1000 (instead of inside "case 10").

August 17, 2012
by Finis
Finis's Avatar

Thx for the reply PcBolt,

I'm getting "þþþþþþ..." when I assign 'b' to byte. I've tried :

cli();
delay_ms(100);
sei();

after my switch in the interrupt, still no luck... I did try a few different delay times too...

No, I don't have a an oscilloscope.

August 18, 2012
by pcbolt
pcbolt's Avatar

Finis -

I had a little break from the project I was working on so I was able to try out your code and test it with an O-scope. The output was as expected showing the bit pattern of '0b01000110' very clearly. However when I measured the time between pulses I got the following:

LOW 103.5 uSec x 2 (start bit and bit 7)
HI  122.5 uSec x 1 
LOW 119.6 uSec x 3
HI  117.0 uSec x 2
LOW 124.0 uSec x 1
HI  stayed high until next Tx

I used a 100 mSec pause between Tx's (using your cli(); sei(); in case 10). Hyperterminal was showing "B" 0x42 which is odd since I was trying to send 'b' so it was one bit off. I think when it runs through the case statements it is stretching out the timing enough to throw things off. I didn't have time to test it, but I was thinking of pre-loading the next bit value to send and once the interrupt fires, send the bit then spend the rest of the time figuring out the next bit to send and pre-load that one. Should at least be consistent. I'll let you know how it turns out.

August 18, 2012
by Finis
Finis's Avatar

Very interesting. I'm surprised there is so much deviation in timing. This may the sort of thing that would benefit from using assembly (might be a good time to learn). esp. since I intend to eventually to bang at 250k baud (4 usec bits).

August 18, 2012
by Finis
Finis's Avatar

I've change the structure a bit.

tried @ 9600 baud, and I get one character only "ÿ"

tried @ 115200 baud, and I get two character only "b0"

SIGNAL(SIG_OUTPUT_COMPARE0A) 
{
    cli();
    switch(bitno) 
    {

        if(bitno == 0) // start bit
        {
            dmxpinLow;
            bitno++;
        }

        else if (bitno > 8) // stop bits
        {
            dmxpinHigh;
            if(bitno == 10) bitno = 0; else bitno++;

        }

        else { // data bits
            if(byte & (1<<0)) dmxpinHigh; else dmxpinLow;
                byte << 1;
                bitno++;
        }
    }
    //delay_us(9);
    sei();
}
August 18, 2012
by pcbolt
pcbolt's Avatar

Finis -

Looks like I've over-thought the matter too much. The trouble is in this statement...

OCR0A = 6;

Since the timer takes a full (pre-scaler) clock cycle (17.3 uSec) to go from 6 back to 0, your actually counting 7 ticks (121.1 uSec) instead of 6 (104 uSec). I changed it to...

OCR0A = 5;

Everything worked fine. You do have to send LSB first and I did have remnants of my "pre-loader" code installed but it worked like a champ.

August 19, 2012
by Finis
Finis's Avatar

PcBolt,

Thank you, I've tried this both at 9600 and 115200 baud. It's working now as expected.

so to find the desired baud rate:
d - delay time,
c - clock speed,
p - pre-scaler,
r - OCR0A

[d / ( 1 / [ clk / p ] ) ] - 1 = r

working code:

#include <avr/io.h>
#include <avr/interrupt.h>
#include "../libfinis/myDelay.h"

#define dmxpinHigh PORTD |= (1<<PD1)
#define dmxpinLow PORTD &= ~(1<<PD1)

#define readBit(BYTE,BIT) (BYTE & (1<<BIT))

volatile int8_t bitno;
volatile char byte;
int main()
{
    UCSR0B = 0;
    DDRD |=(1<<PD1);
    timer_init();
    sei();
    dmxpinHigh;
    delay_ms(10);

    bitno = 0;
    //byte = 0b01100010;
    //byte = 0b01000110;
    byte = 'F';
    for(;;);

}

void timer_init() {
/*
//Clock at 14745600 , 9600 Baud, every 104us
  TCCR0A = (1<< WGM01);
  TCCR0B = (1<<CS02); // 256
  TIMSK0 = (1<< OCIE0A);
  OCR0A = 5;

*/
//Clock at 14745600 , 115200 Baud, 
  TCCR0A = (1<< WGM01);
  TCCR0B = (1<<CS00) | (1<<CS01); // 64
  TIMSK0 = (1<< OCIE0A);
  OCR0A = 1;

 }

SIGNAL(SIG_OUTPUT_COMPARE0A) 
{
    cli();

        switch(bitno)
    {
        case 0:
            //send start bit
            dmxpinLow;
            bitno++;
            break;

        case 1:
            //send bit 0
            if(readBit(byte,0)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 2:
            //send bit 1
            if(readBit(byte,1)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 3:
            //send bit 2
            if(readBit(byte,2)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 4:
            //send bit 3
            if(readBit(byte,3)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 5:
            //send bit 4
            if(readBit(byte,4)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 6:
            //send bit 5
            if(readBit(byte,5)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 7:
            //send bit 6
            if(readBit(byte,6)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 8:
            //send bit 7
            if(readBit(byte,7)) dmxpinHigh; else dmxpinLow;

            bitno++;
            break;

        case 9:
            //send stop bit 1
            dmxpinHigh;
            bitno++;
            break;

        case 10:
            //send stop bit 2
            dmxpinHigh;
            bitno = 0;

            break;

    }
    sei();
}
August 20, 2012
by Ralphxyz
Ralphxyz's Avatar

Finis, so now you have working code what exactly is your dmx lighting project?

Your code looks interesting how are you going to use it?

Ralph

August 20, 2012
by Finis
Finis's Avatar

Hi Ralph,

Well, I,m interested in building intelligent lighting fixtures for theater and stage. As well as, building controllers. I would like to be able to control my fixtures with a lighting console, etc..

The DMX protocol is interesting. Physically it's RS485. DMX transmit up to 512 channels (slots) of data. where each fixture (light, fog, etc) is set to a channel (or multiple channels).

it runs at 250k baud and is based on frames that start with a break.

transmission starts with a break - low for 88us then a mark - high for 8us

then a 11bit packet is sent all low (1 start bit(low), 8 data bits, 2 stop bits(high))

then another 11bit packet is sent, the byte in this will be data for ch 1

then another 11bit packet is sent, the byte in this will be data for ch 2
...
...
then another 11bit packet is sent, the byte in this will be data for ch 512

then the frame starts over with the break.

I've seen AVR code on the net that uses the built in UART. They detect the break by looking for frame errors. I dont like that method as a frame error could be an error and not a break. Seems like a guess, even if it's correct, mostly. Not to mention, not having control over the ch count. As timing can be crucial.

I've been reading up on assembly I may try to write this in asm. It's seems easy enough (lol, after a curve) and I could calculate the exact number of clock cycles each operation takes. Pretty neat. Any route from here should be fun...

Post a Reply

Please log in to post a reply.

Did you know that interrupts can be used to trigger pieces of code when events happen? Learn more...