First: the circuit
Back in 1991, when PIC's were hot and the only competition came from Intel with their (then already) backdated
8051 series, a company called
wanted to get a foot between the MicroChip door. So they made some very fine development tools. And a
cross-cross assembler: they made a PIC assembler that used 8051-style mnemonics, that were translated back to
PIC mnemonics in assembly-time.
This way, existing 8051 users were tempted to migrate to the new MicroChip platform. They could keep their old sources and only had to change the circuits.... :o)
In those days, Parallax wanted to show the potential of the PIC processors by making a cheap and simple
Digital Storage Oscilloscope (DSO) based on one PIC, one (then) cheap DRAM and some passive parts.
They succeeded wonderfully well. Their PIC DSO performed very well and it convinced editors of leading magazines that the PIC could do anything the old '51 could do, but better.
Above you see the circuit drawing of the DSO (as I recall it). I found the accompanying software on a
CD-ROM. It contained just one file: "digscope.src" and I took the liberty to publish it on this page. This
source code was meant to be assembled with the Parallax X-X assembler (see above), so you will notice a more
than average 8051-mnemonic on the PIC.
I am now asking Parallax if they agree with me publishing their product on my webpage. After just a day or two, I got an answer. Parallax were honoured to see that after 14 years people were still interested in their designs and they didn't see any objections against me publishing the project.
This PIC DSO could record 4 channels of digital data over a period of time and then display the data on a normal, single channel analog scope. The buttons allowed the user to scroll through the data.
PIC DSO: the source
Below is the source code for the PIC DSO, as designed by the Parallax team in 1991 (yes: nineteen ninety ONE!). In cross cross assembler format.
;******************************************************** ;* * ;* PIC-Based DIGITAL SCOPE * ;* * ;* - Records 4 channels x 64K at 357 KHz * ;* - Displays recorded waveforms on oscilloscope * ;* - Straight or edge-triggered recording * ;* * ;* Designed by Parallax, Inc. * ;* Last updated 11/15/91 * ;* * ;******************************************************** ; device pic16c55,hs_osc,wdt_off,protect_off id checksum ;device's id word = checksum reset power_up ;jmp to power_up on reset ; ; ; RA is a 4-bit bidirectional data bus connecting the PIC, DRAM, and channels ; RB contains control signals - some pins are used as both inputs and outputs ; RC is an 8-bit output bus for DRAM address and DAC data ; zip = rb.0 ;can output a low to cancel dac we = rb.1 ;dram write enable bit cas = rb.2 ;dram column address select bit ras = rb.3 ;dram row address select bit tr = rb.4 ;can output low to stop noise record = rb.5 ;record button input left = rb.6 ;left button input right = rb.7 ;right button input display_on = 11100001b ;directions for rb display_off = 11110000b ;0=output, 1=input toggle = pa0 ;bit variables bit = pa1 ;(unused bits in status reg) direction = pa2 ; ; ; General register assignments (08h-1fh) ; org 08h ;set org to general registers trace_buffer ds 8 ;trace buffer holds 40h bits address_low ds 1 ;dram address low address_high ds 1 ;dram address high channel ds 1 ;channel number trace ds 1 ;trace position (x axis) level ds 1 ;level of line (y axis) temp ds 1 ;temporary usage register org 0h ;reset origin for rom code ; ; ;************************ ;* SUBROUTINES * ;************************ ; ; ; Clear memory from address to end ; clear_to_end clr ra ;make ra output lows mov !ra,#0000b ;to be written into dram mov w,#regular_loop ;load w with address clrb bit ;clear bit jmp write_entry ;write to end of memory ; ; ; Record pressed - get final record command and read 64K samples into dram ; handle_record call clear_address ;reset address clrb bit ;clear bit to flag left/right :debounce mov temp,#100 ;100 cycles of record debounce :wait sb left ;if left button, direction=0 clrb direction sb right ;if right button, direction=1 setb direction snb left ;if either button, set bit sb right setb bit jnb record,:debounce ;if record, reload debounce djnz temp,:wait ;record not pressed, 100 yet? mov w,#101000b ;get pos or neg edge for option snb direction ;according to direction mov w,#111000b mov option,w ;load option with w mov w,#regular_loop ;if left or right button is snb left ;still pressed, then do per- sb right ;sample triggering mov w,#trigger_loop write_entry mov temp,w ;save loop address in temp clrb we ;clear we to enable dram writes jnb bit,regular_loop ;if no button, regular trigger_loop clr rtcc ;reset rtcc :wait jnb record,clear_to_end ;if record button, abort jnb rtcc.0,:wait ;wait for trigger edge regular_loop mov rc,address_low ;2 cyc ;write row address clrb ras ;1 ;latch into dram mov rc,address_high ;2 ;write column address clrb cas ;1 ;latch into dram, write setb cas ;1 ;end write cycle setb ras ;1 ;return ras high, too mov w,temp ;1 ;get loop address in w inc address_low ;1 ;inc low address snz ;1/2s ;skip if not 0 incsz address_high ;1/2s ;inc high address, skip if 0 jmp w ;2 ;jmp to loop (14 cycles/loop) setb we ;done, return we high jmp make_ra_input ;compensate for write_entry ; ; ; Handle panning - perform pan if left and/or right buttons are pressed ; handle_panning mov w,#01h ;w=01h in case of left or right jb left,:not_left ;check for left button pressed jnb right,:both ;check for both buttons pressed clrb direction ;just left, clear direction bit jmp step_left ;step left 01h :not_left jb right,:neither ;check for right button pressed setb direction ;just right, set direction bit jmp step_right ;step right 01h :both mov w,#40h ;both buttons pressed jnb direction,step_left ;step either left or right 40h jmp step_right ;depending on direction bit :neither ret ;neither button, exit ; ; ; Step left w positions - don't go below address 0 ; step_left sub address_low,w ;subtract w from address_low jc :done ;if c=1 (no underflow), done dec address_high ;decrement address_high movsz w,++address_high ;skip if address_high was 0 :done ret clear_address clr address_low ;clear address word clr address_high ret ; ; ; Step right w positions - don't go above address 0ffc0h ; step_right mov temp,w ;store w in temp :loop mov w,++address_high ;check if address_high = 0ffh jnz :step ;if not, do step cje address_low,#0c0h,:done ;if address = 0ffc0h, done :step inc address_low ;inc address_low snz ;skip if not 0 inc address_high ;inc address_high djnz temp,:loop ;loop if more steps :done ret ; ; ; Refresh - cycle dram through 256 row accesses ; refresh clr rc ;clear rc for row addresses :loop clrb ras ;latch row address to refresh setb ras ;return ras high ijnz rc,:loop ;inc row address, loop if not 0 ret ; ; ; Get buffer pointer - fsr points to a trace_buffer register and w holds mask ; get_buffer_ptr mov w,>>trace ;get trace/2 in w mov fsr,w ;store in fsr rr fsr ;rotate right fsr rr fsr ;rotate right fsr clrb fsr.4 ;fsr = 08h-0fh (trace_buffer) mov w,trace ;get trace in w and w,#07h ;isolate lower 3 bits (0h-07h) lookup_mask jmp pc+w ;lookup bit mask according to w retw 01h,02h,04h,08h,10h,20h,40h,80h ;w holds mask after ret ; ; ; Trigger scope - make ra and tr output lows, generate sync pulse, start rtcc ; All I/O pins which connect to input channels or the trigger will output lows ; in order to reduce ac noise on the dac output during trace generation ; trigger_scope clr ra ;ra=gnd to reduce dac noise mov rb,#00001110b ;tr=gnd to reduce dac noise mov rc,#0ffh ;dac will go high when enabled mov !ra,#0000b ;make ra output mov !rb,#display_on ;make tr output and enable dac mov !rc,#00000000b ;make rc output (already was) mov option,#001000b ;rtcc counts instruction cycles clr rtcc ;clear rtcc for fresh count clr trace ;clear trace for caller's use ret ; ; ; Bit sync output - wait for rtcc's msb to go high, then output w ; bit_sync_level add w,level ;on entry, w must hold height bit_sync jnb rtcc.7,$ ;loop until rtcc's msb high mov rc,w ;output w to dac clrb rtcc.7 ;clear msb (low bits unchanged) ret ; ; ; Bit sync output - wait for rtcc's msb to go high, then output w with slewing ; this routine was added at the last minute so that traces look like functions ; bit_sync_level2 add w,level ;on entry, w must hold height mov 1eh,w mov 1fh,rc jnb rtcc.7,$ ;loop until rtcc's msb high clrb rtcc.7 ;clear msb (low bits unchanged) cje 1eh,1fh,:exit cjb 1eh,1fh,:a :b mov 1dh,#0ch :blp inc rc djnz 1dh,:blp jmp :exit :a mov 1dh,#0ch :alp dec rc djnz 1dh,:alp jmp :exit :exit ret ; ; ; Show levels ; show_levels call :lookup mov level,w clr w snb toggle mov w,#0ch jmp bit_sync_level :lookup add pc,channel ;jump to pc+channel retw 0b8h,98h,78h,58h ;levels for 4 channels ; ; ; Trace done - disable dac output, make ra and tr inputs ; trace_done clr w ;ready to output low on dac call bit_sync ;wait for current bit done clrb zip ;ready to disable dac mov !rb,#display_off ;disable dac and make tr input make_ra_input mov !ra,#1111b ;make ra input ret ; ; ;************************ ;* MAIN * ;************************ ; ; ; Power up ; power_up call trigger_scope ;call trigger_scope to init i/o call clear_address ;clear memory call clear_to_end ; ; ; Draw 4 channel traces ; display clr channel ;reset channel counter :trace call refresh ;refresh dram clr trace ;get trace_buffer from dram :z0 mov rc,address_low ;write low address byte to rc clrb ras ;latch row address (refreshes) mov rc,address_high ;write high address byte to rc clrb cas ;latch column address (rd/wr) mov w,channel ;get channel in w call lookup_mask ;translate w to bit mask and w,ra ;test bit in ra with mask movb bit,/z ;move not z into bit setb cas ;return cas high setb ras ;return ras high inc address_low ;inc low address snz ;skip if not 0 inc address_high ;inc high address call get_buffer_ptr ;get fsr and mask in w not w ;not w mask and indirect,w ;clear bit in trace_buffer not w ;return mask to normal snb bit ;skip if bit not set or indirect,w ;set bit in trace_buffer inc trace ;inc trace jnb trace.6,:z0 ;if trace not 40h yet, loop sub address_low,#40h ;restore address by subtracting subb address_high,c ;40h from address word call trigger_scope ;do sync pulse call show_levels ;show levels :sample call get_buffer_ptr and w,indirect mov w,#0 sz mov w,#0ch test trace jnz :slow call bit_sync_level jmp :cont :slow call bit_sync_level2 :cont inc trace ;inc trace position jnb trace.6,:sample ;if not 40h, show next sample call show_levels call trace_done inc channel ;inc channel jnb channel.2,:trace ;if not 4h, show next trace ; ; ; Show guide trace ; guide_trace call refresh ;refresh dram call refresh ;do it a couple of times to call refresh ;avoid triggering the scope call refresh ;prematurely on the guide trace call trigger_scope ;trigger scope mov level,#30h ;draw to the guide level call bit_sync ;w holds 30h :loop mov w,>>address_high ;check for global indicator mov temp,w ;get address_high/4 into w mov w,>>temp and w,#3fh xor w,trace ;xor w with trace, z=1 if same mov w,#10h ;(doesn't affect z) jz :show ;show global indicator if z=1 mov w,address_low ;check for guide indicator add w,trace and w,#3fh mov w,#08h sz clr w ;nothing, just line :show call bit_sync_level ;draw bit inc trace ;increment trace jnb trace.6,:loop ;if trace not 40h yet, loop call bit_sync_level ;draw last segment (w=0) call trace_done ;end trace ; ; ; Handle any button inputs ; sb record call handle_record call handle_panning xor status,#20h ;toggle 'toggle' bit in status jmp display ;loop to displayIf you like the project, just download digiscope.zip in the DOWNLOAD section! I had contact with the people of Parallax and they didn't mind me publishing this on Fruttenboel. Their main concern was about the availablity of the 53C464 DRAM chip.
Page created January 2005,