And I was sick of the slow frame rate I was getting from my Game Boy Camera running off an Arduino. I thought a fast ADC and a more powerful MCU might help that. These were my good excuses to dive into the world of Propeller and write a driver object in Propeller Assembly.
|Propeller, ADC, and lots of wires = fun|
The ADS7888 interface consists of an active low chip select (!CS), serial clock (SCLK) and serial data out (SDO). Dropping !CS initiates ADC conversion and clocks out the first of 4 leading 0's. The remaining three 0's followed by eight data bits with MSB first, are all clocked out just after the falling edge of SCLK. Raise !CS after the 12th bit for high throughput mode, and start all over again after a brief wait time.
|Timing diagram from ADS7888 datasheet, 12-clock frame|
So all I had to do is raise and lower pins and shift in data at the right times. And the Propeller makes this really easy, thanks to deterministic timing. No interrupts and each instruction takes 4 clock cycles (with few exceptions that you can avoid or plan around). So you can sync and run multiple cogs in lockstep.
I started by sketching out the instructions in Spin, then converting to assembly, learning on the fly. Propeller assembly strikes me as much easier to learn than others I've tried (6502, x86, VAX, ...) I focused initially on just getting the right pins up and down in the right sequence. Then coded in the serial input. Then I added NOP instructions where necessary to ensure consistent SCLK periods throughout.
Tuning the Assembly Code
The total SCLK period was pretty long at this stage in development. Data acquisition is pretty tightly constrained by the Game Boy Cam's clock period of 2000ns so it was time to tune the code.
After finding the longest loop (serial shift in), I was able to reduce the number of instructions to 6 total (below) and removed NOPs elsewhere to reduce the SCLK period throughout.
:loop2 test sdo, ina wz ' SCLK down, data valid after 7ns shl data, #1 ' shift data left 1 bit if_nz or data, #%01 ' stick the next bit into data (or don't) nop ' SCLK up sub j, #1 wz ' deterministic timing if_nz jmp #:loop2 ' deterministic timing
I then moved initialization code into NOP slots later in the code to reduce setup time shortening the overall acquisition period.
Next I split the SCLK signal generation statements out to run in a separate cog, synchronizing with !CS using WAITPNE.
DAT RunClock org 0 clock or dira, sclk1 mainloop or outa, sclk1 waitpne cs1, cs1 ' sync on !CS down :clkloop andn outa, sclk1 ' SCLK up test cs1, ina wz ' if !CS high nop or outa, sclk1 ' SCLK down if_nz jmp #mainloop ' if !CS high jmp #:clkloop cs1 long |< 3 ' ADS7888 !CS pin sclk1 long |< 5 ' ADS7888 SCLK pin
This shortened the SCLK period by two instructions yielding an acquisition period of about 975ns. That leaves 1025ns of the Game Boy Camera's clock period free. At 80MHz, that's 12.5ns per instruction, or 82 instructions per XCK period to store the pixel data in shared system memory, and maybe do something else with the data. In the same cog that's driving the camera, that is.
Meanwhile, several other cogs will be available, each with a full 2000ns (160 instructions) for each of the 15744 pixels, plus some idle time between frames to do some interesting processing. I suspect I'll be able to do some cool image processing as a result. Especially since the Game Boy Camera does on-chip edge detection.
I think it's safe to say I'll be able to do more than just detect candle flames by the time I'm finished, here. Including, I hope, obstacle avoidance, line following, even sidewalk/lane following. Who knows?
Source code is here.
I extensively used my trusty Hitachi V-1050F oscilloscope to check the consistency of the timing, and the timing of SCLK versus !CS. The "B Display" feature magnifies and displays a small section of a longer waveform period. I could scroll through a long trace from beginning to end to ensure consistent timing between SCLK and !CS.
|"B Display" shows a magnified window of a long trace|
I hooked up a potentiometer as a voltage divider connected to VIN so I could check readings. The scope let me see the serial data coming out of the ADC, and I set up a wrapper program to send the ADC result over serial to the PC. I could verify that the serial data sent by the ADC matched the value read in by the software. Doing this caught a timing bug that dropped the LSB of the result.
|%1001011 = 0x4b|
I may double check the timing on all three pins at once with my HP 1650A logic analyzer if I can clear off my workspace.
Time to write a Propeller driver for the Game Boy Camera. I may have to combine the camera and ADC driver code for efficiency. My old digital logic text from college suggests that a ROM-based state machine might be the most efficient way to run the camera and ADC together. If I do that, it'll make a fun article. I'm considering experimenting with a parallel-interface ADC to further boost performance.