
Now 3,500Hz is not far removed from 3,600Hz, which is three times the bit rate for a 1200 baud asynchronous serial data link. If the loop was adjusted to run at this frequency, it would be possible to sample the push-button input at this rate. This would allow a 'software UART' to decode characters sent from a computer. I also realised that it would be possible to share the input pin between the push-button and the serial data. At the end of each serial character, the asynchronous protocol requires a stop bit to be sent. For one bit period the input pin must be high. This allows button pushes to be detected, as the input pin will still be low at that point.
The main_loop, starting at line 936, begins by delaying until the timer TMR0 times out, then immediately reloads it with a value to give a 3,600Hz repetition rate (278 microseconds). Each of the 7 tasks is called in turn, then a 16-bit system timer (counting time in 278 microsecond chunks) is incremented, and the loop repeats. So long as the total run-time of all 7 tasks does not exceed the total time available, each will run at a regular rate. This type of organisation is called a cyclic executive.
The first task, beep_pin_driver_task, flips the beep output pin each time round, when a beep is required, and does nothing otherwise. Thus one difference between this code and the original is that the pitch of the beep is raised to 1,800Hz. Beeping is controlled by the location beep_mask. A call to one of the 'beep' subroutines (lines 740-746) sets this mask, and loads a time-out location with a value depending on the length of the required beep. The 7th task, beep_task, sets beep_mask back to zero when the time-out expires.
The second task in the schedule is serial_task, which looks for serial characters on the input pin. This is implemented as a table-driven state machine. It samples the input pin at three times the baud rate, initially looking for a start bit (low input level). Now when a low level is detected, all we know about the exact time at which the input line went low is that it is somewhere between the last two input sampling instants, three of which occur per bit time. If we assume that, on average, the real start of the start bit was half-way between these two samples, the middle of the start bit will be one third of a bit period after the second sample, i.e. at the next sample time. Confused? you should be. This diagram may help:
In the idle line state, serial_task is sampling the input line, looking for a start bit. Eventually it detects one. At the next sampling instant, it checks that the start bit is still there, as it should be. If it has gone away, serial_task assumes that the 'start bit' was just a noise spike, and goes back to looking for a real start bit. If the start bit was valid, then every three sample periods (i.e. once per bit period) one bit of the character is read in, starting with the LSB. After 25 sample periods, all eight bits have been read in. Now comes the crunch. One bit time later, the input must be high for a valid character. If it is still low, we have a button push; if high, a character. If the character is valid, it is copied to location ser_data and the RD flag is set. If not, flag BD is set.
Tasks input_task and button_task handle serial characters and button pushes respectively. Each waits in an idle state until the appropriate flag is set by serial_task. Each implements a state machine which decodes the input (in terms of characters and button push durations) and translates it into control information.
When the receiver is scanning, scan_task is responsible for changing frequency, detecting a satellite signal, and implementing the AOS and LOS time-outs. A call to StartScanning (line 849) will wake this task from its idle state and begin the scanning operation. Scanning is stopped by forcing the state of scan_task to 0 (e.g. line 646). While scanning, this task updates the LED display and tunes the receiver autonomously.
When the receiver is not scanning, either input_task or button_task must control the display and tune the receiver directly. Controlling the display is simple enough - a call to UpdateDisplay does that - but there is a complication when tuning the receiver. The I2C bus used to program the synthesiser chip is fairly slow, and it is not possible to send all the required data to the chip in the 278 microseconds available.
To get round this, a separate task I2C_task is responsible for programming the synthesiser. This task idles until told to send new data. it then translates the current channel number into programming data, and sends the data at the rate of one byte per 278 microsecond 'timeslice'. When this task is running, it takes by far the largest fraction of the 278 microseconds available! A minor complication arises because the vital frequency data must be sent in two bytes. Between the two bytes another task could slip in and change the channel number, so the synthesiser would get the high byte corresponding to one channel and the low byte from another. To get round this I2C_task keeps a local copy of the channel number, and uses this. Before going back to the idle state, it checks to see whether the 'real' channel is still the same as its local copy, and if not, it runs itself again with the new, changed channel.
Any task can produce a beep (and several do) by calling one of the 'beep' subroutines (lines 740-746). The beep_task will take care of all the timing involved.
At power-on, after initialising the system, the software checks for a low level on the button input pin. If the pin is low, the receiver alignment 'channel 11' (137.97MHz) is set, and serial_task is placed in a special 'black hole' state from which there is no exit. Since neither the RD nor BD flag will ever be set, the only way out of this situation is a power-on reset.
Under more normal circumstances, channel 1 is set and a 1 second beep initiated. My initial implementation jumped straight to main_loop at this point. However, the tone decoder used to detect the 2.4kHz subcarrier always gives a false detection just after power on, and this caused a false AOS. To get round this, the code now jumps to power_on_loop which runs only beep_pin_driver_task, I2C_task, and beep_task. Extra code at the start of this tasks monitors the status of beep_mask and jumps to main_loop when the beep finishes. The 1 second beep gives adequate time for the tone decoder to settle. During this period all LED segments are turned on as a confidence check.

Last modified 5th July 1999
If you have comments or suggestions, please email me at max@susato.demon.co.uk