'--------------------------------------------------------------------------
'
' Copyright 1996 Donald Kinzer, All Rights Reserved
'
' This program is intended to monitor and control a diesel motor-generator
' set.  A simple feedback loop is used to control the motor speed and,
' therefore, the output frequency of the generator.  A linear actuator
' stepper motor controls the engine speed via a throttle linkage.  The motor
' speed is calculated from a tach input derived from a magnetic pickup on the
' motor's flywheel.  Also, the generator's load current is estimated by
' digitizing the output of a current transformer and then applying a
' transformation function to arrive at the estimated current.  The RPM,
' frequency and load current are displayed on an LCD display unit.
'
' The controller is based on the Basic Stamp II microcontroller from
' Parallax.  The Stamp II has 16 I/O pins and a set of pins for
' communicating with the chip for the purpose of programming, etc.  The
' following discussion will center on the interaction of the I/O pins
' with the remaining circuitry on the controller board.
' 
' The controller consists of the Stamp II, a RAM subsystem, an LCD for
' displaying information, a linear stepper motor for controlling the engine
' throttle, a tachometer input, two analog generator load sensor inputs, and
' various switch inputs.  Because the number of bits of input and output
' exceeds the number of available pins, the interaction between the Stamp II
' and the remainder of the circuitry is more complicated that it would 
' otherwise be.  I/O pin 8 is used to read the value of up to eight inputs.
' A multiplexor, controlled by I/O pins 0-2 selects among the signals to
' read.  I/O pins 0-2 also control a decoder which generates an active low
' signal on one of eight outputs.  I/O pin 3 strobes the decoder to activate
' the selected output.
' 
' I/O pin 4 is used as a multi-purpose 'mode' selector.  For the RAM
' subsystem it acts as address bit 0 effectively selecting the high byte/low
' byte of a word to be read or written.  It is also used as a register
' selector for the LCD unit.  I/O pin 5 is used as a read/write selector for
' both the RAM subsystem and the LCD unit.  I/O pin 6 is used as a serial
' data output directed to the RAM subsystem and the A/D converter.  I/O pin 7
' is used as a data clock for serial input/output operations.  Lastly, I/O
' pins 12-14 are used to control the stepper motor.  The remaining I/O pins
' (9, 10, 11 and 15) are unused.

'--------------------------------------------------------------------------
' These constants define the select codes for the system components.  The
' value is output on i/o pins 0-2 which are fed to both a multiplexer and
' a decoder.  The former is used to select one of eight inputs to read
' while the latter is used to generate one of eight output strobes for
' various devices.
ADC_PORT		con	0	' the ADC port (R/W)
TACH_PORT		con	1	' the tach input port (R)
RAM_ADDR_ENBL	con	1	' enable signal for the address latch (W)
DATA_RD			con	2	' the LCD & RAM input (R/W)
DISP_ENBL		con	3	' the strobe for the display controller (R/W)
MAN_CW			con	4	' manual CW signal (switch) (R)
RAM_ENBL		con	4	' strobe signal for activating the RAM (W)
MAN_CCW			con	5	' manual CCW signal (switch) (R)
LIMIT_CW		con	6	' linear actuator limit switch (R)
LIMIT_CCW		con	7	' linear actuator limit switch (R)

' Port constants.
PORT_ADDR_MASK	con	7	' low three bits
PORT_STROBE		con	3	' port 3 strobes the decoder
MODE1			con	4	' LCD register select, RAM odd/even
MODE2			con	5	' LCD & RAM R/W
DATA_OUT		con	6	' data out
DCLK			con	7	' data clock

DATA_IN			con	8	' data in

MOTOR_RUN		con	12	' low to run the stepper
MOTOR_STEP		con	13	' high to step once
MOTOR_CCW		con	14	' high to run CCW

PORT_DISABLE	con	8	' value to OR with port values above
						' to disable the port select

' Commands for the LCD
LCD_INIT		con	$38		' 8 bits, 2 lines, 5x7 cell
LCD_CLEAR		con	$01		' clear the display memory
LCD_MODE_SET	con	$0c		' display on, no cursor, no blinking
LCD_ADDR_INC	con	$06		' increment cursor after writing
LCD_ADDR_DEC	con	$04		' decrement cursor after writing

LCD_SET_ADDR	con	$80		' add to the desired address
LCD_BUSY		con	$80		' busy flag

' Display addresses
CAP_ADDR		con	$00		' where the caption is displayed
RPM_ADDR		con	$40		' where the current RPM is displayed
FREQ_ADDR		con	$46		' where the current frequency is displayed
AMP1_ADDR		con	$4a		' where the channel 1 current is displayed
AMP_SEP_ADDR	con	$4c		' where the separator for ch1/ch2 is displayed
AMP2_ADDR		con	$4d		' where the channel 2 current is displayed

'--------------------------------------------------------------------------
' variable declarations
i			var	byte	' gen'l purpose index
cnt			var	word	' gen'l purpose counter
result		var	word	' gen'l purpose result variable
ptr			var	word	' gen'l purpose pointer
addr		var	byte	' desired display or RAM  address
dataWord	var	word	' data to send to the LCD or RAM
dataLow		var	dataWord.lowByte
dataHigh	var	dataWord.highByte
loopDelay	var	byte
lcdStat		var	byte	' status of the LCD
lcdValue	var	word	' value to display on the LCD
lcdSupprLZ	var	bit		' flag to suppress leading zeroes
sampleIdx	var	byte
lastRPM		var	word
adcChan		var	bit		' specifies the ADC channel to read
stepCCW		var	bit		' step direction (high = CCW)
running		var	bit
atSpeed		var	bit
autoMode	var	bit

adcConfig	var	nib		' Configuration bits for ADC
adcStartB	var	adcConfig.bit0	' Start bit for comm with ADC
adcSglDif	var	adcConfig.bit1	' Single-ended or diff mode
adcChanSel	var	adcConfig.bit2	' Channel selection
adcMsbf		var	adcConfig.bit3	' Output 0s after xfer complete

'--------------------------------------------------------------------------
' data declarations
lcdInitDataLen	data	5
lcdInitData	data	LCD_INIT,LCD_CLEAR,LCD_MODE_SET,LCD_ADDR_INC,LCD_SET_ADDR
captions	data	" RPM  HZ   LOAD",0
calStr		data	"Calibrating...",0
manMode		data	"Manual Mode", 0

'--------------------------------------------------------------------------
'--------------------------------------------------------------------------
'
' Execution begins here at startup.
'
'--------------------------------------------------------------------------
'--------------------------------------------------------------------------
' set up the port directions and initialize the outputs
	outs=$1f7f		' all except data clock and motor run high
	dirl=%11111111	' all outputs
	dirc=%0000		' all inputs
	dird=%1111		' all outputs
	lcdSupprLZ = 1

' initialize the LCD subsystem
	read lcdInitDataLen,cnt
	for i = 0 to (cnt - 1)
		read lcdInitData+i,dataLow
		gosub sendLCDCmd
	next

' Now we check for a request to enter manual mode.
	autoMode = 1	' set for automatic control mode
	gosub checkManMode
	ptr = calStr
	if (autoMode <> 0) then putCaption
	ptr = manMode
putCaption:
	gosub outputText	' output the selected message

restart:
	running = 0
	atSpeed = 0
	lastRPM = 0
' initialize the frequency samples to 0 (stored in RAM)
	dataWord = 0
	for addr = 0 to 9
		gosub writeRamWord
	next
	sampleIdx = 0

' Initialize the control system
	gosub seekMotorHome
	loopDelay = 0
	stepCCW = 0
	cnt = 500
	gosub stepMotor

' put up the display boilerplate characters, value will be filled in later
	dataLow = LCD_SET_ADDR | CAP_ADDR
	gosub sendLCDCmd
	ptr = captions
	addr = CAP_ADDR
	gosub outputText
	dataLow = LCD_SET_ADDR | AMP_SEP_ADDR
	gosub sendLCDCmd
	dataLow = "+"
	gosub sendLCDData

' test code for development purposes only
'	stepCCW = 1
'	cnt = 800
'	gosub stepMotor

'--------------------------------------------------------------------------
'--------------------------------------------------------------------------
'
' Now that the initialization is completed, we begin the control loop.
'
'--------------------------------------------------------------------------
'--------------------------------------------------------------------------
controlLoop:
	gosub readTach		' get the current speed (actually 1/10th of speed)
	addr = sampleIdx
	dataWord = result
	gosub writeRamWord	' write the current reading

' Add the previous 9 readings to the current reading thereby effectively
' computing the average over 10 readings.
	for i = 9 to 1
		addr = (sampleIdx + i) // 10
		gosub readRamWord
		result = result + dataWord
		if (i <> 9) then continueAvg
		lastRPM = result * 5	' average the last two readings
continueAvg:
	next

' display the averaged rpm value (four digits)
	addr = RPM_ADDR
	lcdValue = result
	cnt = 4
	gosub outputValue

' calculate the frequency (rpm / 30) and display (two digits)
	addr = FREQ_ADDR
	lcdValue = (result + 15) / 30
	cnt = 2
	gosub outputValue

' see if manual mode is being requested
	gosub checkManMode

' handle manual speed control if requested
	if (autoMode <> 0) then checkSpeed
	outa = MAN_CCW | PORT_DISABLE
	if (in8 <> 0) then checkManSpeedUp
	stepCCW = 1
	goto setManSpeed
checkManSpeedUp:
	outa = MAN_CW | PORT_DISABLE
	if (in8 <> 0) then speedDone
	stepCCW = 0
setManSpeed:
	cnt = 25
	gosub stepMotor
	goto speedDone

checkSpeed:
' analyze the resulting reading and decide whether or not to correct
	if (loopDelay > 0) then speedDone
	if (running AND (lastRPM < 1000)) then restart
	if (lastRPM < 500) then speedDone	' not running?
	running = 1
	if (lastRPM <= 1790) then speedUp
	atSpeed = 1
	if (lastRPM <= 1810) then speedDone
speedDown:
	' here we back off the throttle but don't delay further correction
	cnt = lastRPM - 1800
	stepCCW = 1
	gosub stepMotor
	goto speedDone
speedUp:
	' here we increase the throttle and then delay further correction
	loopDelay = 2
	result = 1800 - lastRPM
	cnt = 25
	if (atSpeed) then doSpeedUp
	cnt = 300
doSpeedUp:
	cnt = lastRPM MAX cnt
	stepCCW = 0
	gosub stepMotor
speedDone:

' read the current transducers and display the result
	for adcChanSel= 0 to 1
		gosub getADCResult
		gosub convertToAmps
		lcdValue = result
		addr = AMP1_ADDR + (adcChanSel * 3)
		cnt=2
		gosub outputValue
	next

' provide a sync signal for an oscilloscope
	toggle 15

' prepare for the next sample cycle
	sampleIdx = (sampleIdx + 1) // 10
	if (loopDelay = 0) then controlLoop
	loopDelay = loopDelay - 1
	goto controlLoop

'--------------------------------------------------------------------------
'--------------------------------------------------------------------------
'
' Subroutines
'
'--------------------------------------------------------------------------
'--------------------------------------------------------------------------

'--------------------------------------------------------------------------
' This function checks to see if the manual cw/ccw switch is being
' thus signifying a request to enter manual mode.
checkManMode:
	outa = MAN_CCW | PORT_DISABLE
	if (in8 = 0) then setManMode
	' see if the CW switch is on
	outa = MAN_CW | PORT_DISABLE
	if (in8 <> 0) then checkManModeDone
setManMode:
	autoMode = 0
checkManModeDone:
	return

'--------------------------------------------------------------------------
' This routine initiates an A/D conversion on the LTC1298 channel
' specified by 'adcChan' and places the result in 'result'.
'
getADCResult:
	outa = ADC_PORT | PORT_DISABLE
	high DATA_OUT		' Set data pin for first start bit
	low MODE2		' write mode
	adcConfig = adcConfig | %1011	' Set all bits except channel
	low PORT_STROBE		' Activate the ADC
	shiftout DATA_OUT,DCLK,LSBFIRST,[adcConfig\4]	' Send config bits
	high MODE2		' read mode
	shiftin DATA_IN,DCLK,MSBPOST,[result\12]	' Get data bits
	high PORT_STROBE	' Deactivate the ADC
	return

'--------------------------------------------------------------------------
' This routine takes the value in 'result' and converts it to the
' equivalent in amps.  Because of the non-linear nature of the
' transducer response curve of the current sensors, the calculation is
' done in segments using a conversion formula that was empirically
' determined to produce results that are within 10% or so.
convertToAmps:
	if (result >= 1500) then convertToAmpsCase4
	if (result >= 1000) then convertToAmpsCase3
	if (result >= 200) then convertToAmpsCase2
	if (result >= 115) then convertToAmpsCase1
	result=0
	return
convertToAmpsCase1:
	' approximate -1.28 + 0.0243 X with rounding
	result = ((((result * 200) + 41) / 82) -128 + 50) / 100
	return
convertToAmpsCase2:
	' approximate 0.223 + 0.017 X with rounding
	result = ((result * 2) + 26 + 59) / 118
	return
convertToAmpsCase3:
	' approximate 5.1 + 0.0108 X with rounding
	result = (((((result * 10) + 463) / 926) * 10) + 51 + 5) / 10
	return
convertToAmpsCase4:
	' approximate -.0699 + 0.0131 X with rounding
	result = ((((result * 10) - 53) + 380) / 760)
	return

'--------------------------------------------------------------------------
' This function moves the stepper motor to its 'home' position by first
' making sure that it is off of the limit switch and then moving back
' to the limit.  Stepping is done at a rate of about 250 pulses/sec or
' 1/4 ips.
'
seekMotorHome:
	high PORT_STROBE
' see if the stepper is already at the limit
	outa = LIMIT_CCW | PORT_DISABLE
	low MOTOR_CCW
seekMotorHomeOffLimitLoop:
	pulsout MOTOR_STEP,2
	pause 3
	if (in8 = 0) then seekMotorHomeOffLimitLoop
' Now the stepper is off of the limit switch, move it back
	high MOTOR_CCW
seekMotorHomeLimit:
	pulsout MOTOR_STEP,2
	pause 3
	if (in8 <> 0) then seekMotorHomeLimit
'	stepperPos = 0
	return

'--------------------------------------------------------------------------
' This routine steps the motor the number of times give by 'stepCnt' in
' the direction given by stepCCW.  If the limit is reached in the
' direction being stepped toward, stepping is terminated and the 'stepCnt'
' variable reflects the number of steps not taken.
'
stepMotor:
	high PORT_STROBE
	outa = LIMIT_CCW | PORT_DISABLE
	high MOTOR_CCW
	if (stepCCW <> 0) then stepMotorLoop
	low MOTOR_CCW
	outa = LIMIT_CW | PORT_DISABLE
stepMotorLoop:
	if (in8 = 0) then stepMotorDone
	if (cnt = 0) then stepMotorDone
	pulsout MOTOR_STEP,2
	cnt = cnt - 1
	pause 3
	goto stepMotorLoop
stepMotorDone:
	return

'--------------------------------------------------------------------------
' This routine reads the tach input and calculates rpm and frequency.
' The tach input is derived from a magnetic pickup on the flywheel.  The
' flywheel has 120 teeth and therefore the tach input frequency is 120
' times the number of revolutions per second.  A sample interval of 50ms
' (which gives an accuracy of about 1% at 1000 rpm) yields the formula
' below to convert the tach sample count to RPM:
'
'         COUNT pulses * 60 sec/minute
'        ---------------------------------
'         .05 sec *  120 pulses/revolution
' or
'
'         COUNT * 10
'
' The value actually returned by this function is 1/10 of the current
' speed.
'
readTach:
	outa = TACH_PORT | PORT_DISABLE
	count DATA_IN,50,result
	return

'--------------------------------------------------------------------------
'--------------------------------------------------------------------------
'
' LCD Subsystem
'
' The LCD subsystem is an 8-bit, two register controller capable of
' handling several read/write commands.  The MODE1 I/O pin selects between
' the command mode and the data mode while the MODE2 I/O pin selects
' between reading and writing.
'
' When writing, the data going to the LCD system is shifted out and latched
' and then the LCD unit is strobed to accept the command or data.  When
' reading, the LCD is enabled to output its data and the data is shifted
' in.
'
' Note that the LCD unit shares its data bus with the RAM subsystem.
'
'--------------------------------------------------------------------------
' This function reads the status register and loops until the LCD
' is not busy.
lcdWaitNotBusy:
	low MODE1	' cmd register
	high MODE2	' read mode
lcdWait:
	outa = DISP_ENBL | PORT_DISABLE
	low PORT_STROBE
	pulsOut DCLK,1	' latch the data
	high PORT_STROBE
	outa = DATA_RD | PORT_DISABLE
	low PORT_STROBE
	shiftIn DATA_IN,DCLK,MSBPRE,[lcdStat\8]
	high PORT_STROBE
	if ((lcdStat & $80) <> 0) then lcdWait
	return	

'--------------------------------------------------------------------------
' This function sends a command to the LCD controller
sendLCDCmd:
	gosub lcdWaitNotBusy
	low MODE1	' cmd register
	gosub writeLCD
	return

'--------------------------------------------------------------------------
' This function sends data to the LCD controller
sendLCDData:
	gosub lcdWaitNotBusy
	high MODE1	' data register
	gosub writeLCD
	return

'--------------------------------------------------------------------------
' This function sends either a command or data to the LCD depending
' on the state of MODE1.
writeLCD:
	shiftOut DATA_OUT,DCLK,MSBFIRST,[dataLow\8]
	pulsOut DCLK,1
	outa = DISP_ENBL | PORT_DISABLE
	low MODE2	' write mode
	pulsOut PORT_STROBE,1
	high MODE2
	return

'--------------------------------------------------------------------------
' This function outputs a string of text beginning at the current
' display address and continuing until a null byte is encountered.
outputText:
	for i = 0 to 100
		read ptr + i, dataLow
		if (dataLow = 0) then outputTextDone
		gosub sendLCDData
	next
outputTextDone:
	return

'--------------------------------------------------------------------------
' This function outputs the current value of 'lcdValue' in decimal format
' beginning at the display address given by 'addr'.  The number of
' digits displayed is given by the variable 'cnt'.  Leading zeros are
' suppressed if 'lcdSupprLZ' is non-zero
outputValue:
	' set up the display for outputting from right to left
	dataLow = LCD_SET_ADDR + ((addr + cnt - 1) & $7f)
	gosub sendLCDCmd
	dataLow = LCD_ADDR_DEC
	gosub sendLCDCmd

outputValueLoop:
	dataLow = (lcdValue // 10) + $30
	gosub sendLCDData
	cnt = cnt - 1
	lcdValue = lcdValue / 10
	if (lcdSupprLZ <> 0) AND (lcdValue = 0) then outputValueFill
	if (cnt > 0) then outputValueLoop

outputValueFill:	' fill remaining positions with spaces
	if (cnt <= 0) then outputValueDone
	dataLow = $20
	gosub sendLCDData
	cnt = cnt - 1
	goto outputValueFill

outputValueDone:	' put back in incrementing mode
	dataLow = LCD_ADDR_INC
	gosub sendLCDCmd
	return

'--------------------------------------------------------------------------
'--------------------------------------------------------------------------
'
' RAM Subsystem
'
' The RAM subsystem is required because the need for read/write storage
' exceeds the amount available in the Stamp II.  Although EEPROM could be
' used for this purpose, the number of times that the EEPROM can be written
' limits its usefulness.
'
' The RAM subsystem is organized as 64 16-bit words and is read from and
' written to one word at a time (actually, it operates on two 8-bit bytes).
' To interact with the RAM subsystem, the following functions first output
' an address (serially) to a latch.  Then, if writing, the value to be
' written is output serially to another latch after which the RAM chip
' is strobed to store the data.  If reading, the RAM output is read in
' serially.  As mentioned earlier, two successive bytes are always read
' and written for each operation.
'
' Note that the RAM subsystem shares its data bus with the LCD subsystem.

'--------------------------------------------------------------------------
' This function writes the data given by 'dataWord' to the address given
' by 'addr' in the RAM chip.  Recall that a word is written to RAM storage
' by sending it out serially to a shift register/latch after which the
' value is strobed into the RAM chip.
writeRamWord:
	' write out the RAM address
	outa = RAM_ADDR_ENBL | PORT_DISABLE
	low PORT_STROBE
	shiftOut DATA_OUT,DCLK,MSBFIRST,[addr\6]
	high PORT_STROBE

	' prepare for writing
	low MODE2	' write mode
	outa = RAM_ENBL | PORT_DISABLE

	' write the low byte
	shiftOut DATA_OUT,DCLK,MSBFIRST,[dataLow\8]
	pulsOut DCLK,1
	low MODE1
	pulsOut PORT_STROBE,1	' strobe the RAM chip

	' write the high byte
	shiftOut DATA_OUT,DCLK,MSBFIRST,[dataHigh\8]
	pulsOut DCLK,1
	high MODE1
	pulsOut PORT_STROBE,1	' strobe the RAM chip

	high MODE2
	return

'--------------------------------------------------------------------------
' This function reads the RAM data from the address given by 'addr'
' and returns it in 'dataWord'.  Recall that the word from the RAM storage
' is read in serially.
readRamWord:
	' write out the RAM address to the address latch
	outa = RAM_ADDR_ENBL | PORT_DISABLE
	low PORT_STROBE
	shiftOut DATA_OUT,DCLK,MSBFIRST,[addr\6]
	high PORT_STROBE

	' prepare to read
	high MODE2	' read mode

	' read the low byte
	low MODE1
	outa = RAM_ENBL | PORT_DISABLE
	low PORT_STROBE
	pulsOut DCLK,1		' latch the data
	high PORT_STROBE
	outa = DATA_RD | PORT_DISABLE
	low PORT_STROBE
	shiftIn DATA_IN,DCLK,MSBPRE,[dataLow\8]
	high PORT_STROBE

	' read the high byte
	high MODE1
	outa = RAM_ENBL | PORT_DISABLE
	low PORT_STROBE
	pulsOut DCLK,1		' latch the data
	high PORT_STROBE
	outa = DATA_RD | PORT_DISABLE
	low PORT_STROBE
	shiftIn DATA_IN,DCLK,MSBPRE,[dataHigh\8]
	high PORT_STROBE

	return

'--------------------------------------------------------------------------
'--------------------------------------------------------------------------
'
' Test Functions (for development purposes only)
'
'--------------------------------------------------------------------------

'--------------------------------------------------------------------------
' This test loop drives the stepper from limit to limit using the JOG mode
'motorCycle:
'	gosub seekMotorHome
'	high PORT_STROBE
'motorLoop2:
'	outa = LIMIT_CW | PORT_DISABLE
'	low MOTOR_CCW
'stepCWLoop:
'	pulsout MOTOR_STEP,2
''	stepperPos = stepperPos + 1
'	pause 3
'	if (in8 <> 0) then stepCWLoop
'	high MOTOR_CCW
'	outa = LIMIT_CCW | PORT_DISABLE
'stepCCWLoop:
'	pulsout MOTOR_STEP,2
''	stepperPos = stepperPos - 1
'	pause 3
'	if (in8 <> 0) then stepCCWLoop
'	goto motorLoop2

'--------------------------------------------------------------------------
'This test loop drives the stepper from limit to limit using the RUN mode
'motorLoop:
'	high PORT_STROBE
'	high MOTOR_CCW
'	low MOTOR_RUN
'	outa = LIMIT2 | PORT_DISABLE
'limit2Loop:
'	pause 5
'	if (in8 <> 0) then limit2Loop
'	high MOTOR_RUN
'	low MOTOR_CCW
'back2Loop:
'	pulsout MOTOR_STEP,5
'	pause 5
'	if (in8 = 0) then back2Loop
'	low MOTOR_RUN
'	outa = LIMIT1 | PORT_DISABLE
'limit1Loop:
'	pause 5
'	if (in8 <> 0) then limit1Loop
'	high MOTOR_RUN
'	high MOTOR_CCW
'back1Loop:
'	pulsout MOTOR_STEP,5
'	pause 5
'	if (in8 = 0) then back1Loop
'	goto motorLoop

'--------------------------------------------------------------------------
' this test loop reads the ADC and displays the converted value
'	adcChanSel=0
'adcLoop:
'	gosub getADCResult
'	cnt = 4
'	addr = RPM_ADDR
'	lcdValue = result
'	gosub outputValue
'	gosub convertToAmps
'	lcdValue=result
'	addr=AMP1_ADDR
'	cnt=2
'	gosub outputValue
'	pause 500
'	goto adcLoop

'--------------------------------------------------------------------------
' This test code initializes values, randomly changes them and displays
'	dataLow = LCD_SET_ADDR | AMP_SEP_ADDR
'	gosub sendLCDCmd
'	dataLow = "+"
'	gosub sendLCDData
'
'	rpm = 1750
'	amp1 = 25
'	amp2 = 45
'dispLoop:
'	lcdSupprLZ = 1
'	lcdValue = rpm
'	cnt = 4
'	addr = RPM_ADDR
'	gosub outputValue
'	random rpm
'	rpm = rpm // 2000
'
'	lcdSupprLZ = 0
'	lcdValue = (rpm + 29) / 30
'	cnt = 2
'	addr = FREQ_ADDR
'	gosub outputValue
'
'	lcdValue = amp1
'	cnt = 2
'	addr = AMP1_ADDR
'	gosub outputValue
'	random amp1
'	amp1 = amp1 // 100
'
'	lcdValue = amp2
'	cnt = 2
'	addr = AMP2_ADDR
'	gosub outputValue
'	random amp2
'	amp2 = amp2 // 100
'
'	pause 500
'	goto dispLoop
'	ptr = text
'	gosub outputText
'loop:
'	goto loop
