asynchronous RS232 code for a 20Mhz or 50Mhz SX18AC and include file for those who want to skip the following. The include file is originally from http://www.rhoent.com/, with changes to the FUSE and FUSEX definitions for the newer versions of the SX chips.
These are notes that I decided to take during my initial foray into the world of PICs and microcontrollers. I figured if I didn't take them, I'd soon forget what many of my initial sticking points and frusterations were. They're from the perspective of someone with a fair amount of experience with 8086 assembler, serial datacomm, and a little hardware knowhow, but no experience with PICs or microcontrollers.
One fine day, I decided I wanted to build a device that could receive commands via RS-232 while outputting a continuous stream of motor control pulses.
I started out with a Parallax BASIC stamp workalike from Scott Edwards Electronics, but quickly ran into a limitation with PBASIC on a version 1 stamp: I couldn't poll for received RS-232 characters. PBASIC's SERIN command wouldn't return until data was received, which would halt the output pulse stream. I probably could've gotten a BASIC stamp 2 board to work, but decided to abandon that route because they were too expensive to use as an OEM part in the event that I ever wanted to manufacture the device, plus the project would've been much too easy and I wouldn't have learned anything about PICs. :) I really wanted to learn how to write code for a microcontroller to do things like interface with RS-232 and possibly USB later on. I've always wanted to have microcontrollers in my geek bag.
I looked at the microcontroller chip used in the BASIC stamp board, a 16C56 PIC from Microchip and tried to figure out what I'd have to do to use those. Eventually, I realized that the 16F84 was a reprogrammable 16C56, but that the devices might not run at the 16Mhz that the Scott Edwards stamp did. Then I found the awesome Ubicom/Scenix SX chips, more info about which can be found here and elsewhere on the web.
The Scenix SX18AC/DP chips are fast - 50 Mhz - cost less than $5 apiece, small, simple, and very similar to the Microchip 16C58 PICs. A concern to me was that their current manufacturer, Ubicom, looked like they might be losing interest in supporting the chip. Their web site seemed much more interested in their latest stuff, an 80-pin monster off in a different application and market niche. Still, there was plenty of info and help available from enthusiasts and third-party message boards. I ordered a couple SX18AC/DP chips online (Parallax was backordered, but I found some at Jameco Electronics) and started to investigate what I'd need to download software into them.
I found plans for a 7-transistor parallel port programmer and software that used parts that I had laying about, and built it. The SX chips arrived, and I plugged one in and tried dumping the contents of its flash ROM to see what I could see. The programmer utility, scenix2.exe, said that FUSE==FFF, FUSEX==3FE, Device==FCE but displayed nothing for the chip's data contents. I had expected 3072 bytes (2048 12-bit words), so I wasn't sure if it was working or not. Perhaps the chip was erased, and scenic2.exe was assuming that a bunch of 0xFFs represented nonexistent bits. Another troubling thing was that the scenix2.exe software said that it wasn't for use with Scenix Rev 5 Chips, but I couldn't figure out what revision of chip I had. There's only a date code on the chips, AC0107AH which I suppose could mean July 2001, but I didn't know what that meant. http://www.ubicom.com/ was no help.
I downloaded MPLAB from Microchip, and then downloaded what looked like a great set of starter routines from Rhoent, seed18ac.asm, and compiled it using MPASM. It created a 1324-byte hex file. Lo and behold, it appeared to program, and a scenix2 /D returned a lot more data. A verify against the original hex file was successful, so the programmer appeared to be working. Yippee!
WARNING At some point, the RB4-RB7 pins and all four port A pins on my chip stopped working entirely. I must've blown them out with a static discharge. These things are CMOS chips, and are easily destroyed if the pins are touched when not grounded. I'm sure my polyester sweatshirt and cats usually on my lap didn't help much. Surprisingly, the chip core seemed to continue to work fine, so I just stuck with Port B pins 0 to 3 for all my initial messing around.
I wound up adding some pull-down resistors on the D0 to D3 inputs to the 74HC367 (although I used a 74LS244) so that none of the transistors would be switched on when the board was powered up without being connected to the PC's parallel port. I also added a socket for an external oscillator and a jumper to select whether the SX18 got its OSC0 input from the programmer transistors or the external oscillator so I could do test runs with an external oscillator without having to pull the chip and plug it into a different test board every time I flashed a little change to the chip. I didn't actually think to add those until after I'd gotten a ways into this, but if I had it to do over again, I'd have added the resistors and external oscillator socket and jumper right off the bat.
MPASM /aINHX8M /c- foo.asm
Next on my list was to put together a minimal build environment; the tools and procedures I'd use to code, flash, and test. First, I had to pick an assembler to use. MPASM, SASM, GPASM, CVASM? I got them all and poked at them a little bit. They're all a bit different, either using different mnemonics or having subtle differences that looked like they might not assemble each other's source without some edits and head scratching. I didn't have anything to base an opinion on.
seed18ac.asm compiled with MPASM and included something called sxdefs.inc, which contained stuff needed to support the SX, so I decided to go down that road. I found a very simple test program called sxflash.asm that purported to simply count on the 8 PORT B output pins. It was intended to be compiled under GPASM, so I thought I'd try to get it working under MPASM as an exercise. I got several errors with line numbers and dug in.
sxflash.asm contained a line "mode 0xf" that MPASM didn't like. I went to Microchip's site to find a quick reference and user guide for MPASM; http://www.microchip.com/1010/pline/tools/picmicro/code/mpasm/index.htm and http://gputils.sourceforge.net/gputils.pdf for some info about GPASM. There I learned that "mode" is specific to the SX, found an uppercase macro for it in sxdefs.inc, and made it work by simply adding "/c-" to the MPASM command line to turn off case sensitivity.
Looking at the MPASM user guide and the output hex files, I also determined that MPASM was generating 32 bit files. So, I also added /aINHX8M to the MPASM command line. Lucky for me, scenix2.exe must have been smart enough to just skip the special 32-bit 04 records in the INHX32 hex file that I had previously burned and verified. I had messed with Intel hex files a long time ago, back in the days of ASM and MLOAD on 8080 and Z80 CP/M machines, so the format was vaguely familiar.
FUSE and FUSEX
Another thing I had to do to get sxflash.asm to assemble was to add a DEVICE PINS18+PAGE4+BANKS8+blah blah blah line that I copied over from seed18.asm. Afterwards, I realized that this was setting "magic values" at locations 1010 and 1011 in the hex file. I finally figured out that this was where the FUSE and FUSEX bits are defined, which are used to select a number of chip characteristics at program time (as opposed to run time).
I found the definitions for the FUSE and FUSEX bits in the Ubicom SX chip datasheet and the Microchip 16C58 datasheet.
OPTIONX and STACKX didn't make much sense to me at the time because the SX chip combines them into a single bit, but a little later while reading about the RTCC register, the tick counter, I decided that they'd rarely ever be used.
Looking at the bit fields, I decided that a FUSE of 008 and a FUSEX of 0F3 (turbo mode, sync, internal 4Mhz oscillator, 4 pages, 8 banks, etc) would do for now, so I needed to figure out a DEVICE statement that that would generate those values.
Looking at how these bits were defined within sxdefs.inc, I got confused because some of them didn't seem to match the SX chip datasheet. In particular, the PAGES and BANKS bits in FUSEX occupied the lower 4 bits, when the SX datasheet showed only the lower 2 bits of FUSEX being used, with the next 2 bits being used for brownout timing trimming, whatever that was. I realized that Scenix/Ubicom might have actually changed the layout of this stuff at some point, and that could be what the scenix2.exe utility was talking about with different revisions of the chip. If I were a chip maker, avoiding changes that would break compatibility with a growing body of code would be pretty high priority. It's a critical component of how established processors gain and retain market share. In any event, I added a new batch of equates to my sxdefs.inc that would work with the FUSE and FUSEX layouts so that I could use lines like the following in my .asm source:
DEVICE EQU TURBO | SYNC | IRC | RC4MHZ ; Use internal RC clock or DEVICE EQU TURBO | SYNC | FOSCHI | FOSC2 ; Use external oscillatorThe top line generates a FUSE of 0x008, the bottom line generates a FUSE of 0x0AA and both lines generate a FUSEX of 0x0F3. In the hex file, the top line works out as :042020000800F300C1 and the bottom line works out as :04202000AA00F3001F
Ultimately, I ran into a problem with scenix2.exe, which refused to program any of the high 5 bits of FUSEX to zero. This turned out to be deliberate and perhaps required for older SX chips, but I needed to zero those high 5 bits. Fortunately, the scenix2.cc source was available. I downloaded and installed djgpp from http://www.delorie.com/djgpp/getting.html, removed the lines
int FsX = (S.Erase() & 0xf80) | (FuseX & 0x7f); FuseX = FsX;from scenix2.cc, compiled it with
gpp -o scenix2.exe scenix2.cc -O2and all was well again.
Special Registers, I/O registers, File Registers, and Banking
At this point, I sat down and read the Parallax tutorials to try to get an understanding of the chip's architecture, registers, banking, interrupts, and so on. Parallax's stuff uses the Scenix mnemonics which I found myself liking less because some are a little further from the native machine code than the Microchip mnemonics. There are some multiple-instruction macros being passed off as mnemonics, which for some geeky reason got my dander up. Some will like the Microchip mnemonics and some will like the Scenix mnemonics. The important thing is to understand the chip from a machine instruction standpoint. Then it's pretty easy to shift between one set of mnemonics and the other. I tried assembling a few of the tutorial things, looking up the mnemonics to feed MPASM in the 16C58 datasheet.
Instructions that operate on file registers typically use 5 bits to specify the register. The 32 possible registers are organized as two groups of 16 registers. The bottom 16 are always the globally available registers 0 to 15. They include the timer, program counter, status register, file select register, and the I/O ports, with 8 (9 on the SX18) available as RAM. The upper 16 are banked, with 3 bits of the file select register used to select which of the 8 banks of 16 registers to map in.
The special registers 0-7 include the I/O ports. All of these registers are accessed just like the file registers that you use to store stuff. So, you can test bit 3 of the status register to check the carry flag, of port B to check the state of a pin, or of one of your file register variables all with the same instruction.
Getting back to my original intent, which was to receive RS-232 while outputting a stream of timed pulses, I decided that I wanted to have at least two independent processes running; one to handle an incoming serial data stream and another to take care of generating the control pulses at regular intervals. A system that could handle an arbitrary number of processes would be even cooler; 6 bidirectional RS-232 ports all running asynchronously at different baud rates on an SX18AC with a watchdog interrupt to spare would be right spiff, hey?
To do that, I'd write a little block of code that I could put in at the beginning of each process subroutine that would determine whether the process has something to do now, or whether it should sleep for awhile longer, waiting for some previously chosen amount of time to elapse. I decided to base things on a unit of time called a jiffy, the length of which I could specify, in microseconds, at compile time. Something like 8 microseconds would work out well for RS-232, where, for example, the time between bits at 9600 baud is 104 microseconds. 13 jiffies between bits, or 6 jiffies after detecting the beginning of a start bit would do the trick. A process could sample one bit, then say "wake me up again in 13 jiffies" while the other processes continued to do whatever they were doing.
The main loop would compute the number of elapsed RTCC counts and convert that to the number of elapsed jiffies. Pretty simple, stupid, and without using any interrupts. Interrupts would be dandy too, but I'm not up for troubleshooting interrupt-related race condition and memory access issues my first time out on a microcontroller. One downside to a scheduler like this is that you get small variations in the wakeup times for processes that are always shorter than a jiffy but with periods that can be much greater. They're basically moire patterns in time, a result of already having fractional parts of jiffies accumulated at the time that a process goes to sleep. If a process asks to be woken up after one jiffy, it may in fact be woken up on the very next pass through the loop if the scheduler was close (but not quite there yet) to having enough RTCC counts for one jiffy.
In any event, my little scheduler didn't seem to be working at all. What to do, what to do... I studied my code ever so carefully and tried a few tests and could not determine what the problem was. I needed a debugger. I finally realized that I already had one: MPLAB, the package that I had downloaded earlier just to get MPASM, included MPSIM, an excellent 16C58 simulator. This allowed me to single-step through my program, where I discovered that the carry flag was the reverse of what I expected when using subtract. If you subtract 1 from 2, the carry flag is set, not cleared, and if you subtract 5 from 2, the carry flag is cleared, not set. After a subtraction, carry clear means negative (there was a carry), and carry set means zero or positive (there was no carry). I inverted my bit tests and that was it... the scheduler started working! Yippee!
Then came the fun part: Using the scheduler to implement a simple software UART, or at least the receiver part of it. Relying heavily on the scheduler's delay mechanism, I built a little state machine to sit there and poll for the start bit, then wait for about one half a bit width's time and verify that the start bit was still there, then wait one bit width's time and sample a data bit, repeat that seven more times, then verify that the stop bit was clear, process the character, and go back to polling state. Timing is everything: A part of reliable RS-232 receiving is being able to nail each incoming bit right in its center.
I couldn't get the internal 4Mhz internal RC clock to work very well, even adjusting it with the FUSE trim bits. I was able to make it work alright by pretending that there were only 96 microseconds between bits at 9600 baud (104 is more like it), but the problem seemed to be that the internal clock just wasn't accurate enough. It was a good exercise though, because in the process I came up with an easy way for processes to compensate for "oversleeping", getting a reduced amount of sleep the next time through and thus coming out to spending exactly as many microseconds in total as they would have had they always slept for exactly the number of scheduler "jiffies" that they intended. At higher clock rates, it shouldn't have been a problem, but it felt good to have a solution for slower clock rates anyway.
It was at this point that I added the external oscillator socket and jumper to the programmer board, got out the dual trace oscilloscope, plugged a 20 Mhz oscillator in, and it was Yippee! time once again:
The total width above is about 1 millisecond. The top trace is a lowercase 'u' on an RS-232 line at 9600 baud. The same signal was also being fed to the chip on the red test lead in the picture of the programmer board at the top of this page. I'm holding down the 'u' key in a terminal program on my PC, and the oscilloscope is triggering on the rising edge of each start bit. You can see the start bit at about +5 volts on the left, then the first data bit (LSB first) for the 'u' at -5 volts next to it. I was using a 22K resistor to drop things a bit before they got to the SX18 chip (otherwise they'd be at about +12 and -12 volts), and undervoltage protection diodes in the SX18 were cutting the input signal off internally at 0 volts when it fell below 0. For the data bits, +5 volts is a binary 0, and -5 volts is a binary 1. The data bits arrive in LSB to MSB order, so we're seeing 10101110 coming in after the start bit. Reversed would be 01110101, or 75 hex, or 117 decimal, an ASCII 'u'. The stop bit is just a return to negative voltage for one bit width or longer, the purpose of which is to give the receiver time to get rid of the received character and start looking for the rising edge of the start bit again. So, that was the signal that I needed to decode.
The bottom trace is the output of a pin that I toggled right before I sampled each data bit, so I could see how my timing was. The voltage scale is about half that of the upper trace. The signal is coming off the white test lead in the photo at the top of this page. Note how the rise/fall edges are almost exactly in the middle of the data bits of the top trace! That's how you receive RS-232 perfectly, kids! Woof woof woof!!! Booyah! Geek in th' house! Nerd posturing, all jocks kneel!! Etcetera, etcetera, so on and so forth.
With a working receiver, the transmitter code was trivial. To test it, I added one to a received character and transmitted it. When an 'X' was received, a 'Y' was transmitted, and so on.
Here's my asynchronous RS232 code and a copy of the include file originally from http://www.rhoent.com/ with changes to the FUSE and FUSEX definitions for the newer versions of the SX chips. I used this RS232 code in a hardware device attached to one of the Linux boxes here to keep the /dev/random device stocked with all the unpredictable bits it needs. That project is described here.
Dec 2003 - I started playing around with SX28AC chips, since SX18AC chips are no longer available. Everything above still applied, and I was happy that I had bothered to add a 28-pin socket to my little programmer earlier, except for the FUSEX bits. Needed to use 4F3 instead of 0F3 to set the PINS bit. Otherswise, the C port bits don't work and the B port bits 4-7 come out on the wrong pins.