Author Topic: Very low speed, Receive only, Software UART.  (Read 2016 times)

0 Members and 1 Guest are viewing this topic.

Offline firewalkerTopic starter

  • Super Contributor
  • ***
  • Posts: 2452
  • Country: gr
Very low speed, Receive only, Software UART.
« on: December 31, 2018, 07:31:54 am »
I have developed a CAN-BUS OBD-II Emulator.

I also want to add K-Line communication. K-Line is half duplex, 10400 baud UART with a 5 baud initialization byte (0x33).

My mcu (ATmega328p) clocked at 16 MHZ can;t do 5 baud. I was thinking using a software UART using external interrupt (PD2). After the 5 baud initialization, use normal hardware UART at 10,4 kBaud. The interrupt pin is tied to Rx pin.

How would you implement it? Design a "full" software UART with interrupts and a timer?

Alexander.
Become a realist, stay a dreamer.

 

Offline xyrtek

  • Regular Contributor
  • *
  • Posts: 65
  • Country: us
Re: Very low speed, Receive only, Software UART.
« Reply #1 on: December 31, 2018, 07:41:25 am »
 
The following users thanked this post: firewalker

Offline Rerouter

  • Super Contributor
  • ***
  • Posts: 4700
  • Country: au
  • Question Everything... Except This Statement
Re: Very low speed, Receive only, Software UART.
« Reply #2 on: December 31, 2018, 07:44:55 am »
if you want something to work and your not struggling for resources yet, use a software serial library,

Also I hope your planning to use a proper reciever or atleast try and match it, its not ground = 0, its >1/3 of Vref is 0, there is a stupid amount of kline gear that does not pull below say 2.5V, your fine to pull to ground for TX, but you have to cater for RX or you will go wrong.
 
The following users thanked this post: firewalker

Offline nick_d

  • Regular Contributor
  • *
  • Posts: 120
Re: Very low speed, Receive only, Software UART.
« Reply #3 on: December 31, 2018, 11:44:28 am »
Is performance an issue?

What a normal UART does is poll the line at 16x the baudrate clock (here 5x16=80Hz or 10400x16=166400Hz) for a start bit, and then 8 clocks later, start collecting samples 16 clocks apart, i.e. in the middle of each bit, including the start bit which is used for false start bit detection.

So if your 16MHz micro can cope with an interrupt every 100 clocks or so, usually lasting about say 10 to 20 clocks, then why not do it all in software on an absolutely bog standard repeating timer interrupt (set it up once in initialization code then leave it). Yes, this is inefficient. But it's also hardly any code and difficult to make a mistake.

Of course you can fiddle with the different peripherals in various interrupts (get an edge triggered interrupt for the 5 baud start bit, in this set a timer interrupt to occur in the middle of each bit, then after 10 of these enable the UART) but I suggest to start simple and let your design evolve as you add features, so that you're doing the minimum work and writing the simplest and slowest code that still meets the realtime requirements of the application.

cheers, Nick
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3246
  • Country: ca
Re: Very low speed, Receive only, Software UART.
« Reply #4 on: December 31, 2018, 02:51:06 pm »
Just measure the duration of the first pulse. The start bit will take the line down for exactly one baud. Then wait for the stop bit, which will come after 8 bauds and enable UART. If you wish, you can measure data pulses while doing this. You should get:

1 baud low (start bit)
2 baud high
2 baud low
2 baud high
2 baud low
1 baud high (stop bit)
next byte starts here

 
The following users thanked this post: firewalker

Online langwadt

  • Super Contributor
  • ***
  • Posts: 4729
  • Country: dk
Re: Very low speed, Receive only, Software UART.
« Reply #5 on: December 31, 2018, 03:28:06 pm »
I have developed a CAN-BUS OBD-II Emulator.

I also want to add K-Line communication. K-Line is half duplex, 10400 baud UART with a 5 baud initialization byte (0x33).

My mcu (ATmega328p) clocked at 16 MHZ can;t do 5 baud. I was thinking using a software UART using external interrupt (PD2). After the 5 baud initialization, use normal hardware UART at 10,4 kBaud. The interrupt pin is tied to Rx pin.

How would you implement it? Design a "full" software UART with interrupts and a timer?

Alexander.

if you have a timer interrupt the system that runs faster than, say, 4-8 time the baudrate just use that and simple
statemachine pulling the pin
 
The following users thanked this post: firewalker

Offline firewalkerTopic starter

  • Super Contributor
  • ***
  • Posts: 2452
  • Country: gr
Re: Very low speed, Receive only, Software UART.
« Reply #6 on: December 31, 2018, 04:19:02 pm »
Thanks all of you.

I already have a msec time keeping variable. I guess measuring the time between interrupt change and identifying the start bit is the easy way.
Become a realist, stay a dreamer.

 

Offline zikruk

  • Newbie
  • Posts: 2
  • Country: us
Re: Very low speed, Receive only, Software UART.
« Reply #7 on: January 24, 2019, 02:59:41 am »
Quote
Thanks all of you.

I already have a msec time keeping variable. I guess measuring the time between interrupt change and identifying the start bit is the easy way.
Did you implement that? If so how? Today, When I was sitting my new toto toilet, I realize that I cannot handle this code. It's a pain to handle this.
« Last Edit: February 20, 2019, 11:25:55 am by zikruk »
 

Online Doctorandus_P

  • Super Contributor
  • ***
  • Posts: 3855
  • Country: nl
Re: Very low speed, Receive only, Software UART.
« Reply #8 on: January 25, 2019, 01:43:17 am »
I have sucessfully used this topology for decoding DCF clock signals. with a free running timer and an interrupt on pin change which calculates time differences.
Most DCF pulses are 100ms or 200ms, (and of course 900ms or 800ms for the remainer of each second) but the cheap hardware I had delivered pulses of around 80ms and 160ms.
Combine that with the long pulses at the end of each minute and you have the same number of pulse widhts to consider as a UART serial stream.
As a bonus you can add pulse widht tolerances and a method to ignore short glitches.

The whole ISR that decodes the pulses looks like:
Code: [Select]
ISR( INT0_vect) { // SIG_INTERRUPT0)
static int16_t OldTime = 0; // Timer value of previous interrupt.
int16_t NewTime; // Timer value of this interrupt.
int16_t TimeDiff; // Timer difference.
static uint8_t SyncCount; // Number of minute sync pulses decoded.
static uint8_t BitFound; // A valid dcf bit is decoded.
static uint8_t Error = false; // Set on receiver errors.
static uint8_t BitCnt = 0; // Number of bits received for this minute.
static uint8_t *pTime = &Dcf.Minutes;// Start storing data here.
static uint8_t SyncIndex; // Index for storing a decoded dcf time.

NewTime = Tmr.Read();
TimeDiff = NewTime - OldTime;
OldTime = NewTime;

if(  ( TimeDiff > MAX_PULSE_OFFSET) // Store these pulse widhts in the debug array.
&&( TimeDiff < MAX_PULSE_OFFSET + MAX_PULSE))
  PulseWidths[TimeDiff - MAX_PULSE_OFFSET]++;

if(  ( TimeDiff > PW_0 - PULSETOL)
&&(TimeDiff < PW_0 + PULSETOL)) { // 100ms Pulse = logic 0.
if( BitFound == true) { // Cannot find 2 bits in a row.
Error = true;
DcfErrorCount++;
} else
Pw0++; // Accumulate statistics for debugging.

BitFound = true;
} else if(  (TimeDiff > PW_1 - PULSETOL)
&&(TimeDiff < PW_1 + PULSETOL)) { // 200ms Pulse = Logic 1.
if( BitFound == true) { // Cannot find 2 bits in a row.
Error = true;
DcfErrorCount++;
} else
Pw1++; // Accumulate statistics for debugging.

BitFound = true;
} else if( (( TimeDiff > PW_PZ_0 - PULSETOL)&&( TimeDiff < PW_PZ_0 + PULSETOL))
  ||(( TimeDiff > PW_PZ_1 - PULSETOL)&&( TimeDiff < PW_PZ_1 + PULSETOL))  ) {
// Delay of 800ms or 900ms between normal bits. Ignore these pulse widths.
if( BitFound == false) // Must have found a  valid bit before this delay.
Error = true;

BitFound = false;
} else if( ((TimeDiff > PW_MINUTE_0 - PULSETOL)&&(TimeDiff < PW_MINUTE_0 + PULSETOL))
  ||((TimeDiff > PW_MINUTE_1 - PULSETOL)&&(TimeDiff < PW_MINUTE_1 + PULSETOL))) {
BitFound = true;

if( BitCnt != BIT_COUNT_MINUTE) {
// Always set Bitcount to initialize for a new minute.
// Bug: This pulse is skipped for leap seconds.
Error = true;
BitCnt = BIT_COUNT_MINUTE;
}
} else {
// Else: TimeDiff has an illegal value.
BitFound = false;
Error = true; // We encountered an illegal pulsewith.
DebugEvent |= FLAG_DEBUG_PULSE;
DebugPWidth = TimeDiff;
DcfErrorCount++;
}

if( BitFound ) {
// A bit is found, now process it: Store, Calc Parity, Synchronize.
switch( BitCnt) {
/*
case 0: // First bit of each minute must be "0".
// Bug: This could miss a leap second.
if ( TimeDiff > (PW_1 - PULSETOL))
Error = true;
break;
// Bug: Un decoded bits.
// case 15: "Abnormal transmitter operation.
// case 16: "Summer time" anouncement. (Last hour before change).
// case 17: "CEST" (Central Europe Summer Time) is current time (UTC+2).
// case 18: "CET" (Central Europe Time) is current time (UTC+1).
// case 19: Leap second announcement (last hour before change).
case 20: // Start of encoded time, must be "1".
if( TimeDiff < (PW_0 + PULSETOL))
Error = true;
break;
*/
// case 28: Calculate even Parity over 7 minute bits.
case 29: // If we stored the minutes.
Dcf.Minutes &= 0x7F;// Erase parity bit.
pTime = &Dcf.Hours; // Start Storing Hours.
break;
// Bug: case 35: Calculate even parity over 6 hour bits
case 36: // If we stored the Hours.
Dcf.Hours >>= 1; // Hours are coded in 7 bits.
Dcf.Hours &= 0x3F; // Throw parity bit away.
pTime = &Dcf.Days;
break;
case 42: // If we stored the day (of the month).
Dcf.Days >>= 2;
pTime = &Dcf.WeekDay;
break;
case 45:
Dcf.WeekDay >>= 5;
Dcf.WeekDay--; // Count from 0 to 6 insead of 1 to 7.
pTime = &Dcf.Months;
break;
case 50:
Dcf.Months >>= 3;
pTime = &Dcf.Years;
break;
case 58: // Bug: Calculate even parity over 22 date bits.
// This bit may not shift the year value.
// Bug: Return here is ugly and bit count must be incremented.
BitCnt++;
return;
case BIT_COUNT_MINUTE: // Sync pulse for the next minute.
if( Error == true) {
SyncCount = 0;
} else {
/* 2015-12-19 Bug: Synching with an array for more reliability never worked as
intended. Therefore I disable it for now. Mayby I'll think of a better way
someday.

Simple solution is to narrow the sync down to a small time diff if we've ever
synched before.
Adopt: uint32_t MilliSecondsToday( void)? But not in this interrupt.

For now we just trust the decoded time.
*/
// if( DcfValidSync) { // Previously decoded DCF times are ok.
// Clock = DcfSync[SyncIndex]; // Copy the old value, that one is verified.
// } else
// if( DcfLastSync == 0) { // We have never synched before.
Clock = Dcf; // Use current value, there's nothing better.
// }
SyncCount++;
SyncIndex++;
if( SyncIndex > DCF_SYNC_TIMES)
SyncIndex = 0;

Dcf.Seconds = 0x00; // These are not in the dcf time.
Dcf.Hundredths = 0x03; // Small correction compensates for receiver delay.
// DcfSync[SyncIndex] = Dcf;

// if( (DcfLastSync == 0) // Sync, because we have never synced before.
// ||(DcfValidSync) ) { // Sync, because we have a lot of sync pulses.
DcfLastSync = 1; // Lowest nonzero number.
// DcfSyncCount++;
DebugEvent |= FLAG_DEBUG_SYNC;
// } else {
// DebugEvent |= FLAG_DEBUG_SYNC_DELAY;
// }
}
DebugSyncWidth = TimeDiff;
BitCnt = 0; // Fresh start for the next minute.
BitFound = false;
Error = false; // No errors at start of a minute.
pTime = &Dcf.Minutes; // Start storing data with minutes.
return; // No shifting or storing of bits, no bitcnt increment.
}

*pTime >>= 1;
if( TimeDiff > (PW_1 - PULSETOL)) { // Time was logic 1.
*pTime|=0x80; // Store the DCF bit in the msb of *pTime.
}
BitCnt++;
}
}


An impotant detail I want to point out:
By calculating an TimeDiff as a difference between 2 time stamps overflows of the timer are automatically corrected for.
This "algorithm" calculates correct intervals even if the hardware timer overflows between "old" and "new" timestamps!
Code: [Select]
TimeDiff = NewTime - OldTime;
« Last Edit: January 25, 2019, 01:48:28 am by Doctorandus_P »
 
The following users thanked this post: firewalker


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf