6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Fri Sep 20, 2024 6:48 pm

All times are UTC




Post new topic Reply to topic  [ 51 posts ]  Go to page Previous  1, 2, 3, 4
Author Message
PostPosted: Sun Nov 08, 2015 7:37 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8510
Location: Southern California
Quote:
It sure is nice to be able to change baud rates without having to mess with hardware. Unlike the 6551, you're not limited to 19200 baud (max programmable speed), either. I gather there are a few hard-to-find DIP UARTs out there like this if you're willing to part with $20 or more for one of them.

I'm not knockin' your project at all--it'll be great to have this working, especially if you will then supply pre-programmed parts--but do know that you can still get the higher data rates out of the '51 with speeds under software control. It's near the bottom of my I/O ICs page of the 6502 primer:

      Similar to the above, you can get much higher data rates out of the ACIA, still under software control-- it's just that it won't be from the 1.8432MHz crystal and the ACIA's own baud-rate generator. Instead, use the VIA's T1 in free-run mode toggling PB7 on every timeout with no further attention from the processor, and select the rate by writing to T1CL and T1CH as shown in the code above. The ACIA data sheet says the ACIA is good to 125kbps, and I suspect that that's rather conservative. 115200 is a standard bit rate, and you can get it by running the system on a Φ2 rate of 7.3728MHz (which is a standard crystal & oscillator frequency) and storing 0 in T1CL and T1CH. Then you can also get all the other standard speeds by writing different values to T1.
      Code:
      Writing    0 to T1 gives 115200 bps.  (These apply to a Φ2 rate of 7.3728MHz.)
      Writing    2 to T1 gives  57600 bps.
      Writing    4 to T1 gives  38400 bps.
      Writing    6 to T1 gives  28800 bps.
      Writing  $0A to T1 gives  19200 bps.
      Writing  $0E to T1 gives  14400 bps.
      Writing  $16 to T1 gives   9600 bps.
      Writing  $2E to T1 gives   4800 bps.
      Writing  $5E to T1 gives   2400 bps.
      Writing  $BE to T1 gives   1200 bps.
      Writing $2FE to T1 gives    300 bps.  (The 2 goes in the VIA's T1CH.)

      The bit rates shown here are exact. With adjusted T1 values, it would also work to run Φ2 twice as high, at 14.7456MHz which is another standard crystal & oscillator frequency. Other Φ2 rates would work too; but to hit the standard RS-232 bit rates exactly (or at least with an acceptable error of about 1% or less), you'll have to work around the poor granularity caused by the big jumps in PB7 output frequency that result from single-step adjustments in T1 value when it's near 0.

If you have SPI or Microwire one way or another, you can use the $8.50 (price in singles, at Mouser) MAX3100 UART in a 14-pin DIP that goes up to 230kbps with a 3.6864MHz crystal and save a lot of board space. The MAX3110 is a MAX3100 plus line drivers and receivers built in. Unfortunately that one is not available in DIP, but in SO-28 which you can put on an SOIC-to-DIP adapter. The MAX3111 is the 3.3V version.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Sun Nov 08, 2015 3:01 pm 
Offline

Joined: Wed Sep 23, 2015 8:14 pm
Posts: 171
Location: Philadelphia, PA
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.


Top
 Profile  
Reply with quote  
PostPosted: Sun Nov 08, 2015 3:23 pm 
Offline

Joined: Wed Sep 23, 2015 8:14 pm
Posts: 171
Location: Philadelphia, PA
GARTHWILSON wrote:
I'm no knockin' your project at all--it'll be great to have this working, especially if you will then supply pre-programmed parts--but do know that you can still get the higher data rates out of the '51 with speeds under software control. It's near the bottom of my I/O ICs page of the 6502 primer:

As usual, I make the mistake of reading the datasheet and assuming the table of supported programmable baud rates is accurate... :)

I know the MC68B50 ACIA that I have will run at speeds up to 468000 baud with the very same 7.3727 MHz crystal you mention and it works fine so long as the system is configured to insert a wait state on the bus cycle. Otherwise it can't keep up with the 65C02 bus speed when driven by the same crystal. I don't have a 65C51 to play with and probably wouldn't have started on this project at all if they were currently available without significant hardware issues.

What I really like about the PIC-UART now is that it has NO crystal at all and should in theory work at any baud rate up to 921600 baud (including a plethora of non-standard rates) just by setting a 16-bit divisor. My only reservation about this is whether the built-in RC oscillator will be sufficiently stable over a wide enough operating temperature range. Certainly for room temperature projects, it seems to work fine, however. For other projects, it probably makes sense to use it with an external oscillator (since at least with the DIP package one of the two address lines is hardwired to the OSCOUT pin which would be used to connect a crystal directly to the chip).

Your idea of using the VIA to act as a baud rate generator for the ACIA is interesting, but still significantly more complex in that it now requires a two-chip solution to provide the same sort of versatility the PIC-UART will provide with a single 28-pin narrow DIP (using the built-in baud rate generator). If the PIC oscillator is tuned to 7.3728 MHz (with a x4 PLL to 29.491200 MHz), you get the same exact baud rates you would get with the VIA within the accuracy of the built-in oscillator, and if you need more you can use it with the same external 7.3728 MHz can oscillator you mention for the VIA.

GARTHWILSON wrote:
If you have SPI or Microwire one way or another, you can use the $8.50 (price in singles, at Mouser) MAX3100 UART in a 14-pin DIP that goes up to 230kbps with a 3.6864MHz crystal and save a lot of board space. The MAX3110 is a MAX3100 plus line drivers and receivers built in. Unfortunately that one is not available in DIP, but in SO-28 which you can put on an SOIC-to-DIP adapter. The MAX3111 is the 3.3V version.

An interesting idea. Once the PIC-UART is more fully tested, I will probably start on PIC-SPI and PIC-I2C. For 65C02/65C816 systems that require a SPI bus, this should result in a simple solution where you add one PIC-SPI and then as many SPI peripherals as you want including your UART, micro-SD card, ADC/DAC converters, etc.


Top
 Profile  
Reply with quote  
PostPosted: Sun Nov 08, 2015 6:21 pm 
Offline

Joined: Wed Sep 23, 2015 8:14 pm
Posts: 171
Location: Philadelphia, PA
Just finished some performance testing to see what the little PIC-UART will do.

Baud rate worked fine from 300 baud to 921600 baud. I'm sure I wasn't keeping the UART from idling some of the time at the higher baud rates, but there were no I/O errors noted. More comprehensive testing involving file transfers is something I plan as soon as I write a little x-modem transfer utility.

Bus speeds are where I ran into limits. The current code works up to about 4 MHz even without wait states. Above that and it looks like I'm not able to process data as quickly as the 65C02 can dish it out, even with 1 wait state. In particular, when the CPU writes to the PIC, it can take longer to hand the data off to the UART than it does for the CPU to read from the PIC, determine that there is room for another byte, and then write it again causing an overflow. There are some improvements I can make, but I suspect with polled I/O, bus speeds above 5-6 MHz are going to be out of reach unless some accommodation is made by the CPU to limit the rate of UART bus writes to below the rate required for the highest baud rates. :(

On the other hand, interrupt-driven I/O should work fine at much higher bus speeds due to the inherent handshaking nature of the protocol. Now the CPU won't bother the PIC until the PIC is ready and issues an interrupt. I think this is where I will focus my energy after a spending a wee bit more time for the polled I/O case.

Edit: Turning off the TX FIFO improves the maximum bus speed to about 5 MHz with generic polled I/O. The problem with the TX FIFO is that by the time the PIC control loop updates its state to reflect that it's full, the CPU has already read its status and initiated another write, causing an overflow. Unfortunately, the PIC does not expose the FIFO internals enough for me to change status based on it being 50% or 75% full either. Anyway, this issue should go away when interrupt-driven I/O is used, but in the meantime I've added an option to the code to allow the user to switch the TX FIFO on and off at compile time depending on his needs. Some further speedups to the control loop execution speed will lift this limit higher still.


Top
 Profile  
Reply with quote  
PostPosted: Wed Nov 11, 2015 8:17 pm 
Offline

Joined: Wed Sep 23, 2015 8:14 pm
Posts: 171
Location: Philadelphia, PA
Part of the difficulty of making a software UART is dealing with issues of concurrency control: https://en.wikipedia.org/wiki/Concurrency_control

Since the CPU can interrupt the MCU anytime it wants with what amounts to a NMI (bus reads/writes), and since some of the CPUs requests are transactional in nature -- that is they have side effects which modify the state being maintained by the MCU, there come the issues of data integrity and consistency. Added to this is the inherently asynchronous nature of serial communications which throws more wood onto the fire.

Hardware UARTs sidestep these issues by fully updating their state in between bus cycles. Unfortunately, most MCUs like the PIC (or AVR for that matter) are not nearly fast enough to do this.

In the case where it is necessary for the UART state to be fully consistent at all times, there is a relatively low limit placed on the rate at which the bus can be run due to the many elements that go into the state, even for a simple UART like the MC6850. However, if this requirement can be relaxed so that only some portions of the state are guaranteed to be up-to-date (e.g. receive data available and transmit data register empty), then it becomes possible to keep this partial state updated across bus access. If one examines the typical use case for a UART with polled I/O, usually other flags within the UART status register are ignored by the drivers. Even if they are required to implement some needed functionality, all that is required to make them work is a second read of the status register with a tiny delay while the full system state is updated.

I will soon be testing a new release where partial state is kept updated across bus accesses and full state is updated at a lower rate (but as fast as is possible). This should allow polled I/O to run considerably faster than the 5 MHz rate I achieved before.

For the interrupt-driven I/O case, at the cost of slightly more interrupt latency, I have opted to insure that the state is fully updated whenever an IRQ is generated. This will reduce peak performance at the highest baud rates, but I consider it a necessary compromise to keep the state consistent. One cannot expect an interrupt routine to pause even for a short period of time while it waits for an I/O device to assemble its status.


Top
 Profile  
Reply with quote  
PostPosted: Fri Nov 13, 2015 3:52 pm 
Offline

Joined: Wed Sep 23, 2015 8:14 pm
Posts: 171
Location: Philadelphia, PA
The PIC-UART code has been significantly changed to reflect some careful thought about what it means for the UART to be in a consistent state. Although the status bits can be changed in three different parts of the code (receive interrupt, PMP interrupt, and main), I've attempted to prioritize things so that for the few cases where there is overlap in which status bits are updated, we never wind up in a situation that could lead the CPU to think there is data on the bus when there isn't, or where an external interrupt is generated before the system state is fully updated.

I've now tested generation of both receive and transmit interrupts and they appear to be working correctly.

As far as limitations are concerned, the PIC-UART should work fine up to 14 MHz bus speeds if a few things are kept in mind:

  • For bus speeds over about 5-6 MHz, a single wait state may be needed
  • If you're using polled I/O, you should limit the rate that you read the status register to not more than about once every 3 microseconds to insure you don't get a stale system state.
  • If you're using interrupt driven I/O, you should bear in mind that you may get more than one character per interrupt thanks to the receive FIFO and check the status register after reading in the first character to see if more are waiting.
  • If you're using high bus speeds and very high baud rates and need every last bit of performance available, you might be better off using a dedicated FIFO.

The Github repository has been updated with these latest changes. I'm going to declare this a "Beta" release and switch over to working on my PIC-SPI project for the time being.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 51 posts ]  Go to page Previous  1, 2, 3, 4

All times are UTC


Who is online

Users browsing this forum: No registered users and 36 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: