BigEd wrote:
I think it's not so helpful to deny the premise of a thread. This thread is about using a microcontroller to emulate a UART.
Just for the record, I don't take any offense at anything BDD is saying. I respect his opinion and preference to use a "real" UART and thanks to him I've discovered at least one source for a moderately-priced DIP-based UART I can use other than the pile of ACIA's I recently bought on eBay.
BigEd wrote:
I don't fully understand that race condition, but I see that it's a bit of an obstacle to making a reliable system. Let me try to summarise what I believe is going on:
The problem, I'm sure, is my poor skills at describing the problem in sufficient detail. However, now that you've described your understanding, I can at least see where I have misled you.
BigEd wrote:
- From outside the PIC, we want to read a register which the PIC is providing
- The (external) micro starts a read access
- The PIC gets an interrupt
- The PIC's interrupt handler starts up
- The interrupt handler reads a flag register which records which register the micro wanted to read
- The interrupt handler provides the data appropriate to that register
This is where your understanding strays. Although the PIC is a lot faster than the 65C02, it's not nearly fast enough to be able to process an interrupt and provide requested data in a couple of PHI2 clock cycles as would be required if it worked this way. Instead, the PIC has to pre-load the register with the value that the CPU will see when it reads it. If the CPU reads the register, some flags are set (and an interrupt is generated) letting the PIC know which register was read so that it may reload them or otherwise take appropriate action. Now, events which are external (and asynchronous) to the CPU reads are also happening (serial communication) and need to modify the contents of these registers that the CPU is reading -- and this is the source of the race condition.
My solution in part was to add code to the critical section which modifies the output registers as follows: It checks to see if a bus access is pending (interrupts are disabled here) and only if one is not pending does it update the register. This avoids the issue where the register empty flags are cleared in the critical section of the code while an interrupt is pending, followed by the interrupt firing when the critical portion of the code is exited but not working correctly because the flags are set wrong.
Code:
__builtin_disi(0x3fff); // disable interrupts
regs_6850.status |= uart_rxda;
temp = (uint16_t)regs_6850.status | ((uint16_t)regs_6850.rdr << 8);
if (temp != PMDOUT1)
if (PMSTATbits.OB1E == 0)
PMDOUT1 = temp;
__builtin_disi(0); // enable interrupts
In the above code, the nested if statement checks to be sure the "output buffer 1 empty" flag is clear (e.g. bus access is not imminent) before updating PMDOUT1 which is the output buffer (actually two of them as it is 16-bits wide). The code is structured the way it is in order to minimize the number of cycles between this check and the setting of the PMDOUT1 register. This window is where the race condition exists and is what I'm trying to address. I should probably look at the assembly output of the compiler to insure that it is as small as possible, though I haven't seen the problem manifest since I began using this code.
The entire problem would go away if I eliminated interrupt processing from the design. But that would increase latency on register updates by at least a factor of ten which I believe would significantly degrade the overall performance of the system.
BigEd wrote:
The problem is that the same 'flag register' is used by the hardware to communicate to the interrupt handler which register is wanted, as well as recording which registers are full (not yet read) or empty (have been read)
Almost. Not which register is currently wanted, but which register was just read (and thus may need to be "refilled" in some contexts).
BigEd wrote:
If the non-interrupt code wants to place data into the output register, it needs to write to this same flag register, and this write can be in a race against the write the hardware does on an external read.
The flag registers are read-only. The hardware updates them (clears them) when the corresponding registers are written to. In the above code, writing to PMDOUT1 clears the OB0E and OB1E flags for example.
BigEd wrote:
Can you instead arrange that the non-interrupt code places outgoing data into a circular buffer, and let the interrupt handler deal with moving data from the buffer to the output register?
The problem is that I don't know which data to put into the output registers a priori. It depends on external events over which I have no knowledge or control (e.g. serial communications). However, your idea would be perfect for the case where you knew ahead of time what data to provide next.