Go Down

Topic: Software serial implementation (Read 524 times) previous topic - next topic

tobyb121

Jan 03, 2013, 04:15 pm Last Edit: Jan 03, 2013, 04:22 pm by tobyb121 Reason: 1
Hi there,

I'm working on a little project then needs to have two serial devices connected to it, it basically reads analog voltages from A0-5, and a gps module connected via a serial port at 9600 baud, does a little bit of processing and sends this data over a second serial port.
The second serial port needs be full duplex, as it is used to also receive control commands from a pc, so I have naturally used the hardware serial port for this. The GPS module therefore needs to be connected using some sort of software UART.
I looked at first at the Software Serial library, but the problem with that is that it from what I can tell from the code, it blocks the processor during receive. The module is fixed at 9600 b/s, a full set of NMEA messages could easily be 4-500 bytes and so this will block the processor for nearly half a second, time that needs to be spent measuring A0-5, which I want to be happening about 10 times a second.

I therefore decided to build my own software serial implementation using an external interrupt on pin 2, and timer1, I know this is a very resource heavy way to do it, but I don't need these functions for anything else so that doesn't concern me.

I've written the code below to test my iplementation, using an uno, with pin 0 (HW UART Rx) connected directly to pin 2. I then just send it characters from realterm and it should echo them back to me. The problem is that it seems to miss the first bit every time, so for example 'a' (B01100001) gets returned as 0xC2,0xFF (B11000010 B11111111) i.e. it reads the start bit as the LSB of 'a' reads bits 0-6 as 1-7 then the last bit, a 0, is recognised as a new byte, then it gets all 1's (no transmission).

I cannot understand at all from my code why it reads the start bit as bit 0, but I've been trying to get it to work for ages now.

Would someone more experienced than myself mind having a look at my code and seeing if they can work out the problem.
I have made this to work with an uno, though I think should work with any 16MHz processor.

Thanks,

Tobyb121

EDIT: One other thing I should mention, in debugging this I added an array that would store value of micros() at each timer interrupt and the initial falling edge, this showed that the first timer interrupt happens about very quickly (<20us I think) after the falling edge, implying that it tirggers immediately after exiting the ext interrupt routine

Code: [Select]

#define BUFFER_SIZE 128 //Number of bytes to store in a buffer

byte _i;                //current bit
char _b;                //shift byte to store current byte
char buf[BUFFER_SIZE];  //buffer to store received bytes
byte _ptrRead;          //position in buffer to read from          
byte _ptrWrite;         //position in buffer to write to

int _baud;              //period of each bit in instruction cycles

void initialiseSerial(int baud){
       _ptrRead=0;
       _ptrWrite=0;
 
pinMode(PIN2,INPUT);

_baud=2000000/baud;    //Calculate baud (based on 16MHz processor)
       
SREG|=B10000000;       //Global interrupt enable

       // Setup timer1 to interrupt on overflow, internal clock, 1:8 prescaler
TCCR1A=0;
TCCR1B=B00000010;
TIMSK1=B00000000;

       //External Interrupt enable on pin2 falling edge
EICRA=B00000010;
EIMSK=B00000001;
}

void Timer1InterruptRoutine(){
       // reset timer to trigger in next bit
TCNT1=0xFFFF-_baud;
       
       //if less than eight bytes have been written
if(_i<8){
_b|=digitalRead(PIN2)<<_i;  //read the pin and add it to the temp byte
_i++;
}
else{  //when all bytes have been received
buf[_ptrWrite++]=_b;       //Add the temp byte to the buffer  
TIMSK1=B00000000;          //Switch off the timer
EIMSK=B00000001;           //Re-enable the falling edge interrupt
}

}


ISR(TIMER1_OVF_vect){
Timer1InterruptRoutine();
}

//Fired when falling edge detected on pin 2
void Int0InterruptRoutine(){
       //reset temporary storage variables
_b=0;
_i=0;

       //Disable external interrupt for now
EIMSK=B00000000;

       //Enable timer and set it to overflow in 1.5*baud ticks, in middle of bit0
TCNT1=0xFFFF-3*_baud/2;
TIMSK1=B00000001;
}

ISR(INT0_vect){
Int0InterruptRoutine();
}

// Read a byte from the buffer and increment the pointer
char readByte(){
char b;
b=buf[_ptrRead++];
_ptrRead%=BUFFER_SIZE;
return b;
}

// Check if there are bytes available to read
byte bytesAvailable(){
if(_ptrWrite<_ptrRead)
return _ptrWrite+BUFFER_SIZE-_ptrRead;
else
return _ptrWrite-_ptrRead;
}


void setup()
{
       pinMode(PIN3,OUTPUT);
       pinMode(PIN2,INPUT);
Serial.begin(9600);
initialiseSerial(9600);
}

void loop()
{
if(bytesAvailable())
 Serial.print((char)readByte());  //Continuously check the buffer and write any received bytes to the hardware serial
}

PaulS

Quote
I looked at first at the Software Serial library, but the problem with that is that it from what I can tell from the code, it blocks the processor during receive.

It blocks the processor during the receive of ONE character.

Quote
a full set of NMEA messages could easily be 4-500 bytes and so this will block the processor for nearly half a second

You don't have to receive them all. You should be able to tell the GPS which one(s) you want.


tobyb121

Thanks for the quick reply, I have looked at reducing the NMEA sentances that are sent, unfortunatly the data sheet is rubbish so I havn't got this to work yet, however I will still need at least the GGA and RMC sentances, which is likely to be around 200 characters, and so will block for about 0.2s, and is still a bit long for me. I think that reducing the sentances will help, but I'm still going to have a problem.

PeterH

If you only want to perform an analog read every 100ms and consume stuff read from SoftwareSerial at 9600 bps then I don't see a problem. SoftwareSerial is interrupt based so as long as you check for received data often enough to prevent the 64-byte receive buffer from overflowing it should be fine.

All you need is a loop() which: reads a character from the SoftwareSerial port if it is available, and buffers it locally; processes the buffered input when a complete NMEA sentence has been received; performs the analog read if is it time to do that.
I only provide help via the forum - please do not contact me for private consultancy.

Nick Gammon


The second serial port needs be full duplex, as it is used to also receive control commands from a pc, so I have naturally used the hardware serial port for this. The GPS module therefore needs to be connected using some sort of software UART.


Not necessarily. The high-volume connection would be better as hardware serial. Occasional commands from  the PC could be software serial (and indeed a lower baud rate if necessary). Although, it might be better to have software serial (connected to the PC) be at as high a baud rate as you can comfortably get. That way the blocking in software serial is minimized.

Go Up