At the time, the Oshonsoft project had gone quiet, and no updates were forthcoming, so I moved over to SourceBoost C and started re-writing some of my own "libraries" of code from the PIC Simulator IDE sources.
Then, not so long ago, the whole Oshonsoft came back to life! And in it, a few updates - including implementing some of the suggestions made by users, such as my software serial idea.
Without a timeout, any time an instruction like:
SERIN rx, 9600, b
is met, the code hangs, waiting for a full byte to be received into the buffer. If no byte arrives, the code simply stops at this instruction, until one has been received.
With the latest updates, the compiler supports two new DEFINE directives, which allow you to say "if this interrupt event occurs, stop waiting for an incoming serial byte and get on with the rest of the code". For example, we can use
Define SERIN_TIMEOUT_REG = TIFR
Define SERIN_TIMEOUT_BIT = 2
The idea behind our test code is to send some data over serial, wait for a couple of seconds, and if nothing is received back from the host, to load some default values (and if serial data is received, to use this to set up the initialisation routine). It wasn't possible to use the hardware UART (so interrupt driven serial communication wasn't possible) to I needed some way to say "start listening for serial data, but after x number of seconds, give up and do this instead".
The new Oshonsoft routines provide exactly this functionality, if coupled with the timer1 module (set to run on a /256 prescaler, so, at 8Mhz, it takes about 2 seconds to rollover).
The first gotcha (and this kept us guessing for a few hours) is that any unhandled interrupt can cause an AVR microcontroller to reset. So if - as I did - you enable the timer1 overflow interrupt, but don't write a timer1 overflow interrupt "vector" (i.e. the code to handle the interrupt) then the mcu will reset when the interrupt fires.
Frustratingly, it's not always immediate - so there's little correlation between the interrupt firing and the mcu resetting. I spent hours checking and double-checking the pull-up resistor on the reset line and the fuse settings (watch dog, brown out etc) during programming in the mistaken belief that something else could be causing the mcu to reset.
So if you don't have an interrupt routine, the mcu resets.
The obvious solution is to write an interrupt handler.
So I did.
This stopped the mcu from resetting, but the serial-in function didn't abort as it should. I put the equivalent of "trace statements" in the interrupt handler (made the LED flash a number of times, at a particular rate) to make sure that it was firing correctly - and it was - but this didn't help.
The answer, in this case, was simply to disable the interrupt-set flag in the TIMSK register. This is the flag that says "when timer1 rolls over, fire an interrupt". The ability for the SerIn routine to monitor the state of the OVF1 flag is built in - it doesn't need an "external" interrupt handler to make it abort.
In fact, if an interrupt handler is written for the event that has been designated to abort the SerIn routine, it seems to disable the ability of that interrupt flag to force the abort. So in this case, the answer was to set up the timer1 as a free-running counter
'reset timer1 to zero
TCNT1H = 0x00
TCNT1L = 0x00
'set timer1 to overflow after about 2 seconds
TCCR1A = 0x00 'user timer1 in normal/counter mode
TCCR1B = 0x04 'use /256 prescaler at 8Mhz ~= 2sec
but specifically unset the flag (usually used) to create an interrupt event:
'disable timer1 interrupts
TIMSK.TOIE1 = 0
With these in place, it was a simple task to test that the abort routine was working as it should:
High LED
Serin rx, 9600, i
TCCR1B = 0x00 'turn off timer1
Low LED
declarations:
Dim i As Byte
Symbol tx = PORTA.5
Symbol rx = PORTA.5
Define SERIN_TIMEOUT_REG = TIFR
Define SERIN_TIMEOUT_BIT = 2
init:
'make all pins inputs
ConfigPin PORTG = Input
ConfigPin PORTF = Input
ConfigPin PORTE = Input
ConfigPin PORTD = Input
ConfigPin PORTC = Input
ConfigPin PORTB = Input
ConfigPin PORTA = Input
'enable pull-ups on all input pins
'(in avr you do this by setting the output register
'while putting the pin in an input state)
PORTG = 0x0ff
PORTF = 0xff
PORTE = 0xff
PORTD = 0xff
PORTC = 0xff
PORTB = 0xff
PORTA = 0xff
'disable the pull-up resistor on the
'serial tx/rx duplexed line
PORTA.5 = 0 'pin 46
'just to show the chip is doing something!
ConfigPin PG.0 = Output
For i = 1 To 5
High PG.0
WaitMs 100
Low PG.0
WaitMs 100
Next i
'send a message back to the host
ConfigPin PORTA.5 = Output
Serout tx, 9600, "Ready"
WaitMs 50
'disconnect the serial line (put it in input
'mode with no pull-up makes it high-impedance)
ConfigPin PORTA.5 = Input
'reset timer1 to zero
TCNT1H = 0x00
TCNT1L = 0x00
'disable timer1 interrupts
'(an interrupt vector stops the serin from aborting)
TIMSK.TOIE1 = 0
'set timer1 to overflow after about 2 seconds
TCCR1A = 0x00 'user timer1 in normal/counter mode
TCCR1B = 0x04 'use /256 prescaler at 8Mhz ~= 2sec
'now try reading some serial data in, set to time out
'when timer1 rolls over. If this works, we'll change the
'speed of the flashing LED. If it doesn't, the speed
'will stay at about one flash every 2 seconds
High PG.0
Serin rx, 9600, i
TCCR1B = 0x00 'turn off timer1
Low PG.0
loop:
'do nothing
High PG.0
WaitMs 500
Low PG.0
WaitMs 500
Goto loop
End
No comments:
Post a Comment