Ok, I've built a very rough example (with loads of hardcoding and assumptions) ... this seems to work nicely at 9600 baud ... it's actually all hardcoded at that speed for the moment. Obviously there's a whole lot of things that could be done to make this more efficient, and there's a few more error checks that would be needed (like getting a new start bit when the timer hadn't gone off etc) which would also make it more resilient against major timer issues.
I would have thought you could get this to use Timer0 (without changing wiring.c) with some clever use of the compare interrupts, but I don't think the standard 64 clock cycle resolution would cut it for much faster than 9600, although I expect you could manage multiple 9600's ok -- that will be my next test as well as seeing how fast I can push this.
If you needed to support multiple serial lines at different rates, then the timer mechanism would need to get more sophisticated.
Please don't laugh at my code ... this was done in a hurry, I did borrow some from NewSoftSerial, and it's my first Arduino code (other than noddy examples) ...
#include <avr/interrupt.h>
#include "pins_arduino.h"
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
#if !defined(digitalPinToPCICR) // Courtesy Paul Stoffregen
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__)
#define digitalPinToPCICR(p) (((p) >= 0 && (p) <= 21) ? (&PCICR) : ((uint8_t *)NULL))
#define digitalPinToPCICRbit(p) (((p) <= 7) ? 2 : (((p) <= 13) ? 0 : 1))
#define digitalPinToPCMSK(p) (((p) <= 7) ? (&PCMSK2) : (((p) <= 13) ? (&PCMSK0) : (((p) <= 21) ? (&PCMSK1) : ((uint8_t *)NULL))))
#define digitalPinToPCMSKbit(p) (((p) <= 7) ? (p) : (((p) <= 13) ? ((p) - 8) : ((p) - 14)))
#else
#define digitalPinToPCICR(p) ((uint8_t *)NULL)
#define digitalPinToPCICRbit(p) 0
#define digitalPinToPCMSK(p) ((uint8_t *)NULL)
#define digitalPinToPCMSKbit(p) 0
#endif
#endif
uint8_t bit = 0; // 0 = start bit, 1-8 = data, 9= stop bit
volatile uint8_t data = 0; // the byte we are reading
// This is a noddy buffer to store the input... no overflow at the moment
uint8_t buffer[32];
int p_save = 0;
int p_get = 0;
volatile int avail;
// Quick way of twiddling the right bits given the bit coming in...
uint8_t bitmasks[8] = { 0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80 };
// Bits for the port access...
int port;
volatile uint8_t *portRegister;
uint8_t bitMask;
// This will be called for a pin state change, but we'll also call it if the timer
// goes off...
inline void handle_interrupt()
{
uint8_t val = *portRegister & bitMask;
// If we're the first signal the we are expecting a start bit (low), anything else
// we just ignore.
if(bit == 0) {
if(val) return;
data = 0;
bit++;
// Allow the timer to run...
TCNT2 = 255-247; // 247 = 988us which is 9.5 bits
sbi(TIFR2, TOV2); // this clears the overflow flag to stop instant interrupts.
sbi(TIMSK2, TOIE2);
} else {
// This is a bit hacky ... we'll count what time the bit comes in using the timer
// but we'll allow 13 timer ticks (52us) of flexibility, this is good since we
// can cope with timing problems, but it does mean the timer will probably come in
// at bit 10 (or wrap to bit 0 in this calc...)
bit = (TCNT2 - ((255-247) - 13)) / 26;
if(bit == 9 || bit == 0) {
// clear the timer...
cbi(TIMSK2, TOIE2);
bit = 0;
buffer[p_save] = data;
p_save++;
if(p_save == 32) p_save = 0;
avail++;
return;
}
if(!val) {
data &= ~bitmasks[bit-1];
} else {
data |= bitmasks[bit-1];
}
}
}
#if defined(TIMER2_OVF_vect)
ISR(TIMER2_OVF_vect)
{
cbi(TIMSK2, TOIE2);
// pretent we've had a signal change...
handle_interrupt();
}
#endif
#if defined(PCINT0_vect)
ISR(PCINT0_vect)
{
handle_interrupt();
}
#endif
#if defined(PCINT1_vect)
ISR(PCINT1_vect)
{
handle_interrupt();
}
#endif
#if defined(PCINT2_vect)
ISR(PCINT2_vect)
{
handle_interrupt();
}
#endif
int rx_pin = 5;
int tx_pin = 6;
void setup() {
Serial.begin(9600);
pinMode(13, OUTPUT);
pinMode(rx_pin, INPUT); // RX
pinMode(tx_pin, OUTPUT); // TX
digitalWrite(rx_pin, HIGH); // pullup for normal logic
*digitalPinToPCICR(rx_pin) |= _BV(digitalPinToPCICRbit(rx_pin));
*digitalPinToPCMSK(rx_pin) |= _BV(digitalPinToPCMSKbit(rx_pin));
port = digitalPinToPort(rx_pin);
portRegister = portInputRegister(port);
bitMask = digitalPinToBitMask(rx_pin);
// Let's mess around with timer2....
// Need to set it to normal mode, since the wiring.c code will set phase correct PWM
cbi(TCCR2A, WGM20);
// Let's make sure we have normal port operation
cbi(TCCR2A, COM2A0);
cbi(TCCR2A, COM2A1);
cbi(TCCR2A, COM2B0);
cbi(TCCR2A, COM2B1);
// We'll set the /64 scaler, since this gives us 4us per tick on a 16Mhz board, so the
// overflow will happen at 1024us ... we need 988us for a 9.5 bit 9600 clock
// probably already set by wiring, but we'll do it anyway
sbi(TCCR2B, CS22);
}
int by;
void loop() {
while(avail > 0) {
// This is a "get" routine ... but it's in here for now...
cli();
by = buffer[p_get];
p_get++; if(p_get == 32) p_get = 0;
avail--;
sei();
Serial.print(by, BYTE);
}
delay(100);
}
Lee.