Construct a cheap Digital Storage Oscilloscope (DSO)

First: the circuit

DSO built with a 16C55

Some explanation

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 Parallax 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 Circuit Cellar 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.

;*							*
;*							*
;*     - 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
; 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
; 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
; 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
; 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)
; 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
;*	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
		mov	w,#0ch
		test	trace
		jnz	:slow
		call	bit_sync_level
		jmp	:cont
:slow		call	bit_sync_level2
		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
		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 display
If you like the project, just download 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,