I would like to use an Arduino as a protocol converter. The PC side is 9600 serial, the other side is 5 bit baudot at low speeds. The output side is between 50 and 300, possibly 600 baud.
The output side not only outputs the data but must also read back data at the same rate. The read back is either a similar time to the output, or could be timed differently.
I cannot use software serial for the input, because I believe it waits in the read method, so I am liable to drop computer characters.
So I am trying to drive the output and input by interrupt.
At the moment the output seems to be working, but I have timing issues with the input. Some characters are OK and some are not. It gets worse at the higher speeds.
Also I must be doing something wrong because I cannot use digitalWrite or digitalRead in the interrupt routine. I don't know why this does not work?
I am using millis() in one of the routines called by the polling loop, is that allowable with an interrupt routine?
The complete sketch is long, so I am posting the interrupt code, setup() and loop(). There are other routines, but no interrupt ones.
Using 0012 on linux, duemilanove.
My current test set up just has the output pin wired back to the input pin, at the TTL level, so I am trying to receive the sent characters back through the interrupt serial input.
The interrupt design is:
TIMER2 set to trigger interrupts on timer overflow at a multiple of half a bit time.
When an interrupt occurs, the first thing to do is reset the timer so the next interrupt will occur at the next half bit time.
The output side counts interrupts, and at a bit time occurs, shifts and outputs the data. A flag mechanism tells the interrupt routine to start.
The input side counts interrupts, and when a half bit time occurs, samples the input pin, making the value available, again using a flag, to a polling receive routine.
The idea is that the interrupt routines only occur about once per millisecond and do not take a lot of processing. The main loop must run faster than one half a bit time.
/* software output, reading data to be output from serial host in.
Also software input to read back the data
*/
#include <avr/interrupt.h>
#include <avr/io.h>
#define DEBUG 1
#define REAL_DTR_SIGNALLING 0
// see scaling spreadsheet, set for 50 baud, 64 prescaler, txmult=24,rxmult=12
// see scaling spreadsheet, set for 150 baud, 64 prescaler, txmult=8, rxmult=4
// see scaling spreadsheet, set for 300 baud, 64 prescaler, txmult=4, rxmult=2
#define SW_TXMULT 24
#define SW_RXMULT (SW_TXMULT/2)
#define INIT_TIMER_COUNT 48 // see scaling spreadsheet for value
#define RESET_TIMER2 TCNT2 = INIT_TIMER_COUNT
volatile int sw_tx_mult_counter = 0;
volatile int sw_rx_mult_counter = 0;
const int sw_tx_mult_constant = SW_TXMULT;
const int sw_rx_mult_constant = SW_RXMULT;
// used for signalling to interrupt routine for output
volatile int int_out_rdy = 0;
volatile int int_out_data;
volatile int int_out_bits;
// used for signalling from interrupt for input
volatile int int_in_read_pin;
volatile int int_in_rxtimer_gone = 0;
void setup_interrupt()
{
//Timer2 Settings: Timer Prescaler /64 CS22 only
TCCR2B |= (1<<CS22);
TCCR2B &= ~((1<<CS21) | (1<<CS20));
TCCR2B &= ~(1<<WGM22);
// Use normal mode
TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
// Use internal clock - external clock not used in Arduino
ASSR &= ~(1<<AS2);
//Timer2 Overflow Interrupt Enable, clear the two output compare interrupt enables
TIMSK2 |= (1<<TOIE2);
TIMSK2 &= ~((1<<OCIE2A) | (1<<OCIE2B));
RESET_TIMER2;
sei();
}
// Arduino runs at 16 Mhz,
// the prescaler is set to 64
// 1/ ((16000000 / 64) / 256) = 1 / 10000
ISR(TIMER2_OVF_vect) {
RESET_TIMER2;
if (sw_tx_mult_counter++ >= sw_tx_mult_constant) {
sw_tx_mult_counter = 0;
if (int_out_rdy)
{
if (int_out_bits--)
{
if (int_out_data & 0x40)
{
//digitalWrite(doutPin, 1);
PORTB |= 0x20;// corresponds to Arduino Pun 13
}
else
{
//digitalWrite(doutPin, 0);
PORTB &= ~(0x20);
}
int_out_data <<= 1;
}
else
{
int_out_rdy = 0;
}
}
}
if (sw_rx_mult_counter++ >= sw_rx_mult_constant) {
sw_rx_mult_counter = 0;
int_in_read_pin = PINB & 0x01;// why does digitalRead not work in here?
//int_in_read_pin = digitalRead(rdbackPin);
int_in_rxtimer_gone = 1; //to be picked up by loop routine
}
}
// more conversion code and buffering omitted
/* Class Definition
Reads data in for PLBO or reception purposes
*/
class OP_DataIn {
private:
volatile int dxbit;
volatile char c_data;
volatile char o_data;
volatile int completed;
void reset_state(void);
char ca[40];
volatile int cnt;
public:
OP_DataIn();
int process(void);
char get_read_char(void);
void put_dbg(int x);
char *get_dbg();
}; // end class definition
inline void OP_DataIn::put_dbg(int x) {
if (cnt >= sizeof(ca)) return;
ca[cnt++] = (x==1) ? '1' : '0';
ca[cnt] = '0';
};
inline char *OP_DataIn::get_dbg() {
return ca;
};
OP_DataIn::OP_DataIn() :
completed(0)
{
reset_state();
}
inline void OP_DataIn::reset_state(void) {
dxbit = 0;
c_data = 0;
cnt=0; // resets the debug buffer counter
}
inline int OP_DataIn::process(void) {
int j = int_in_read_pin;
if (int_in_rxtimer_gone) {
int_in_rxtimer_gone = 0;
if (dxbit < 0 || dxbit >14) {
Serial.print("dxbit err ");
Serial.print(dxbit);
}
switch (dxbit) {
case 0:
// look for a transition for a start bit
if (j == 0) {
dxbit++;
}
break;
case 1:
// if still zero, a good transition, else reset
if (j == 0) {
dxbit++;
}
else {
reset_state();
}
break;
case 3:
case 5:
case 7:
case 9:
case 11:
case 12:
dxbit ++;
break;
case 2: // bit 1
case 4: // 2
case 6: // 3
case 8: // 4
case 10: //5
dxbit ++;
if (j)
{
c_data |= 0x01;
}
c_data <<= 1;
put_dbg(j);
break;
case 13: // stop bit
put_dbg(j);
if (j != 0) {
o_data = c_data >> 1;
}
else
{
Serial.print("sb");
}
completed = 1;
reset_state();
break;
default:
Serial.print("dx err2");
completed = 1;
reset_state();
break;
} // end switch
} // end if timer gone
return completed;
} // class member
inline char OP_DataIn::get_read_char(void)
{
completed = 0;
#if DEBUG
Serial.print(get_dbg()); Serial.print(" ");
#endif
reset_state();
return o_data;
}
/* Class Definition
serial output, 1 start, 5 data and 1 stop
* Written so that it can be used in a loop
*/
class OP_DataOut {
public:
OP_DataOut() {
};
void start(char _cdata);
inline int is_ready(void) { // indicates the output ready to do the next char
return (int_out_rdy == 0);
}
}; // end class definition
inline void OP_DataOut::start(char _cdata)
{
_cdata &= 0x1f; // make sure its only 5 bits
int_out_data = 0; // clear all bits to ensure start bit
int_out_data = (_cdata << 1) | 0x01; // put stop bit on at end
int_out_bits = 7; // if you change the figure, change the mask in the interrupt routine too
int_out_rdy = 1; // set flag for interrupt routine to start output
}
// end DataOut functions
OP_DataOut op_datout;
OP_DataIn op_datain;
RingBuffer ringbuffer;
Do_Control_Lines control_lines;
ConvertAlphabet convertalphabet;
ConvertIA2_IA5 c2_to_5;
void setup() // run once, when the sketch starts
{
pinMode(rdbackPin, INPUT);
pinMode(doutPin, OUTPUT);
pinMode(dtrPin, INPUT);
pinMode(dsrPin, OUTPUT);
pinMode(rtsPin, INPUT);
pinMode(ctsPin, OUTPUT);
digitalWrite(doutPin, HIGH);
Serial.begin(9600);
setup_interrupt();
}
void loop() // run over and over again
{
control_lines.process(&ringbuffer);
if (Serial.available()) {
ringbuffer.put_char(Serial.read());
}
if (convertalphabet.is_ready_for_put() && ringbuffer.current_contents() > 0) {
convertalphabet.put_IA5(ringbuffer.get_char());
}
if (op_datout.is_ready() && convertalphabet.is_ready_for_get()) {
op_datout.start(convertalphabet.get_IA2());
}
if (op_datain.process()) {
char c = op_datain.get_read_char();
#if DEBUG
Serial.print(byte(c),HEX);
Serial.print(":");
#endif
if (c2_to_5.convert(c))
{
char c1 = c2_to_5.get_result();
Serial.print(c1);
Serial.print(":");
};
}
} // end loop