'updated 21 Mar 06. ' The open source software design of OSCA(TM) is governed by a "not for profit" code. See construction manual for details. ' Open Source Cox Amplifier (OSCA) is a mic amplifier for coxswains, with the ability to show ' the crew's rating, speed and other rowing information on a parrallel connected LCD display. ' OSCA incorporates a digital volume control (DS 1666) that enables the coxswain to alter the amplification ' of the microphone through the OSCA menu system. A GPS board is used for speed readout. A reed switch, located under the seat of a rower ' is connected to OSCA, which allows OSCA to calculate the stroke per minute (rating) of the crew as well as other info. ' This program runs on the Picaxe(R) 18X IC within OSCA. The 18X controls the LCD via parallel interface, processes GPS signals, and controls the ' digital volume control. Full info is in the manual. ' Note that variables are extensively re-used for other purposes throughout the code. If you want to add to the code, I suggest using the peek and poke command ' to keep track of variables. ' poke 80 equals volume control setting ' poke 81 equals metronome setting ' poke 82 equals the status of the speed sensor. 0 = unknown or not present, 1 = GPS, 2 = propeller pickup (propeller pickup not yet implemented) ' poke 83 and 84 equals the current speed whether it be knots, miles or km/h ' poke 85 holds the status of the metronome. 0 = dont send, 1 = send ' read/write 254 shows which mode OSCA is in. mode 0 = knots; mode 1 = km/h; mode 2 = MPH (imperial) ' ' Copyright A. Philip Pawlowski 2005 ' OSCA is a trademark of A. Philip Pawlowski ' Picaxe products are developed and distributed by Revolution Education LTD. ' Picaxe is a trademark licenced by Microchip Technology inc. '-------------------------------------------------------------------------------------------- ' protocol of communication between the 08M and 18X ' First send a high command to wake up the 08M. Set pin 0 high for more than 10ms. ' Then send a N2400 string with the qualifier "osca" then two or more numbers. The first sets the ' metronome. If it is a zero, the metronome will switch off. If it is anything other, the metronome ' will sound the metronome beat till a serial string is received with a zero in it. ' The second number selects the power down feature. Setting it to 0 will turn off OSCA ' initialise: EEPROM 0,(" Batt Level: HighMedLowSpeed:ocationRating:VolumeTravelDist/stroke:KnotsMetricImperialNo GPSMetronomeDisplay:Switch Off!OSCA V1.0.1 ") 'store the text in the EEPROM memory write 254,1 ' set to km/h mode default high 1 ' disable volume control gosub StartupVolume ' set volume to mid range gosub init ' initialise LCD b1 = 1 gosub wrins poke 81,10 ' sets the metronome to its lowest level for b3 = 126 to 137 'write "OSCA V x.x.x" on LCD on top row read b3, b1 gosub wrchr next b3 b1 = 12 'turn off cursor gosub wrins ' the following checks to see if a GPS is fitted as the primary speed sensor. If not, OSCA will not hang on serin wait 5 for b3 = 0 to 200 if pin2 = 1 then GPSpresent pause 5 next poke 82,0 'set memory location 82 to 0. this signifies that OSCA has not picked up the presence of a GPS goto main GPSpresent: poke 82,1 'GPS has been found. ' ---------------Menu commands to follow. There are 5 main screens. The first shows speed and rating, the second shows ' ---------------Speed and Distance per stroke, the third shows the battery level and the units to be displayed eg. metric ' ---------------imperial, or marine (knots), Menu 4 shows the metronome feature, which beeps out stroke per minute ' ---------------The final menu allows OSCA to be turned off. main: 'main menu shows speed and rating pause 500 b13 = 1 gosub writespeed 'just write speed: on top line gosub writerating 'just write rating: on bottom line men1: gosub CheckBattLevel gosub getspeed gosub writeUnitSpeed b12 = 1 'set trigger to reset LCD gosub checkswitches if b12 <> 1 then main 'if volume button has been pressed, then b12 will equal 2. Therefore refresh the screen. if b13 <> 1 then menu2 gosub getrating gosub writeUnitRating gosub checkswitches if b12 <> 1 then main 'if volume button has been pressed, then b12 will equal 2. Therefore refresh the screen. if b13 <> 1 then menu2 goto men1 Menu2: ' Menu 2 Writes Distance per stroke and speed on the display b13 = 2 gosub writespeed gosub writeDstroke pause 400 'debounce switches men2: gosub CheckBattLevel gosub getspeed gosub writeUnitSpeed gosub getRating gosub WriteUnitDstroke b12 = 1 'set trigger to reset LCD gosub checkswitches if b12 <> 1 then menu2 'if volume button has been pressed, then b12 will equal 2. Therefore refresh the screen. if b13 <> 2 then menu3 goto men2 Menu3: 'Menu 3 writes "batt level" and "display" b13 = 3 gosub WriteBattLevel Gosub WriteDisplay pause 500 men3: gosub CheckBattLevel gosub WriteUnitBattLevel gosub WriteUnitDisplay gosub checkswitches2 if b13 <> 3 then menu4 goto men3 Menu4: ' Menu4 writes Metronome on display b13 = 4 gosub WriteMetronome pause 500 'debounce switches men4: gosub CheckBattLevel gosub checkswitches3 if b13 <> 4 then menu5 goto men4 ' Menu5: ' Menu 5 writes switch off on display b13 = 5 gosub WriteSwitchOff pause 300 men5: gosub CheckBattLevel gosub checkswitches4 if b13 <> 5 then main goto men5 '---------------------- this section checks the pushbuttons on the front panel and jumps to adjust volume and other variables if necessary---- checkswitches: readadc 1,b1 ' if b1 < 190 then WriteVolume if pin6 = 1 then ChangeMenu b12 = 1 return checkswitches2: readadc 1,b1 if b1 < 130 then ChangeDisplayUp if b1 > 135 and b1 < 190 then ChangeDisplayDown if pin6 = 1 then ChangeMenu return ChangeDisplayUp: read 254,b1 if b1 = 2 then nothing b1 = b1 + 1 write 254,b1 gosub WriteUnitDisplay nothing: return ChangeDisplayDown: read 254,b1 if b1 = 0 then nothing b1 = b1 - 1 write 254,b1 gosub WriteUnitDisplay return checkswitches3: readadc 1,b1 if b1 < 130 then MetronomeUp if b1 > 135 and b1 < 190 then MetronomeDown if pin6 = 1 then ChangeMenu return MetronomeUp: poke 85,1 'set flag to send metronome data as it has now changed peek 81,b1 if b1 > 49 then nothing b1 = b1 + 1 poke 81,b1 goto WriteUnitMetronome MetronomeDown: poke 85,1 'set flag to send metronome data as it has now changed peek 81,b1 if b1 <= 10 then WriteUnitMetronome b1 = b1 - 1 poke 81,b1 goto writeUnitMetronome checkswitches4: readadc 1,b1 if b1 < 190 then PowerDown if pin6 = 1 then ChangeMenu return ChangeMenu: b13 = b13 + 1 'move to next menu return ' '------------------ this section adjusts the volume by enabling the digital volume control IC.--------------------------- ' enable the IC by setting the 18X pin 1 low (not high)! ' then set pin 4 high to select up volume or low to select down volume ' then pulse pin 2 to move the 128 step digital potentiometer to the value required. StartupVolume: low 1 high 4 for b0 = 1 to 70 'Change the 70 value to a higher value for a higher startup mic volume pause 5 ' but if you do, make sure you poke register 80 to reflect the high 2 ' value of the volume control. The value of the volume control should be pause 5 ' from ascii 0 to ascii 9 low 2 next high 1 poke 80,55 ' A value of 84 steps is 84/12= 7 (the volume control value that is printed return ' on the LCD that corresponds to 84 steps from zero. increment: 'sets the volume step rate to reflect the amount of volume steps required. for b0 = 0 to 12 '12 increments per button press gives us just over ten levels of volume. pause 5 high 2 pause 5 low 2 next return '------------------------- 'This section calculates all of the numbers to be displayed on the LCD and displays them ------------------- '-------------------------- That includes the distance per stroke, and changes decimals to ascii for display on the LCD WriteUnitMetronome: b2 = b1/10 'changing decimal value of b1 into tens and units b3 = b2 * 10 b4 = b1 - b3 b6 = b2 + 48 'change to ascii b8 = b4 + 48 'change to ascii b9 = b1 'high 0 'pause 100 'low 0 pause 100 'serout 0,N2400,("osca",b9,1) if b9 = 10 then DisplayMetronomeOff let b1 = 198 gosub wrins b1 = b6 gosub wrchr b1 = 199 gosub wrins b1 = b8 gosub wrchr b1 = 200 gosub wrins b1 = " " gosub wrchr return DisplayMetronomeOff: b1 = 198 gosub wrins for b3 = 122 to 124 read b3, b1 gosub wrchr next b3 return WriteUnitDstroke: read 254,b6 peek 83,b0 'load the speed, which is in memory 83 and 84, into b0 and b1 peek 84,b1 b7 = b7 - 48 * 10 'change the ascii value of rating to a decimal and change to tens b8 = b8 - 48 + b7 'change the ascii value of rating to a decimal and add to tens to make the rating a decimal. Rating is now in b8 if b6 = 2 then WriteUnitFeetStroke 'if OSCA is set to imperial, write feet per stroke otherwise write metres per stroke or knots per stroke w0 = w0 * 10 / 6 / b8 goto WriteUnitDistStroke WriteUnitFeetStroke: 'Slightly different maths to work out feet per stroke before displaying w0 = w0/6*528/b8/10 goto WriteUnitDistStroke WriteUnitDistStroke: 'Writes the two digits that show metres per stroke or feet per stroke or knots per stroke!! b8 = w0/10//10 Or "0" 'extracts units digit b11 = w0//10 Or "0" 'extracts decimal b1 = 205 'move to bottom left of display gosub wrins b1 = b8 gosub wrchr b1 = b11 gosub wrchr return WriteUnitRating: let b1 = 200 'move to next char gosub wrins let b1 = b7 gosub wrchr let b1 = 201 'next char gosub wrins let b1 = b8 gosub wrchr return '------------------------------------------------------The following code calculates km/h or MPH or knots based on settings--------------------- WriteUnitSpeed: 'memory location 254 sets the units to be used. 0 = marine (knots and metres) 1 = metric (km and metres) and 2 = imperial (MPH and feet) read 254,b6 if b6 = 0 then WriteUnitKts ' each subroutine changes the value of b4 to multiply by the amount required to change units. if b6 = 1 then WriteUnitKm if b6 = 2 then WriteUnitMPH goto WriteUnitSpeedNow WriteUnitSpeedNow: ' remember that speed is still in w1. We want to save this so that we can calculate the ' distance per stroke later. ' w1 is of course b2 and b3 so we will save these values in memory position 83 and 84 for later use poke 83,b2 poke 84,b3 let b1 = 135 'move to next char gosub wrins let b1 = b7 gosub wrchr let b1 = 136 ' gosub wrins let b1 = b8 gosub wrchr let b1 = 137 'move to next char and so on.... gosub wrins let b1 = b9 gosub wrchr let b1 = 138 gosub wrins let b1 = b11 gosub wrchr return WriteUnitKm: b4 = 185 '------one knot equals 1.85 km/h (thats where the 185 came from) goto writeUnitSpd WriteUnitMPH: b4 = 115 '------one knot equals 1.15 MPH (thats where the 115 came from) goto writeUnitSpd WriteUnitKts: b4 = 100 goto WriteUnitSpd WriteUnitSpd: '___________________________This section changes the ascii speed string to decimal km/h or MPH depending on b4 then outputs ascii to the display._________ if b8 = "." then b7isUnits if b9 = "." then b7isTens return b7isUnits: b7 = b7 - 48 * 10 'change ascii digit to decimal b9 = b9 - 48 'change ascii digit to decimal b8 = b7 + b9 'b8 now holds the speed of the vessel in decimal knots * 10 w1 = b8 * b4 /100 'w1 now holds the speed in decimal * 10 ExtractUnits: b7 = w1/100//10 OR "0" 'extracts tens digit b8 = w1/10//10 Or "0" 'extracts units digit b9 = "." b11 = w1//10 Or "0" 'extracts decimal gosub writeUnitSpeedNow return ' b7isTens: w1 = b7 - 48 * 100 b8 = b8 - 48 * 10 b11 = b11 - 48 w1 = w1 + b8 + b11 w1 = w1 * b4 / 100 goto ExtractUnits '----------------------------------------Battery level indicator is a resistor divider on pin 0. ---------------- ' ---------------------------------------Real world testing shows a medium voltage level (9-9.5V) '----------------------------------------Corresponding to ADC value of 158 to 180. ' ************************************************************************************ CheckBattLevel: readadc 0,b7 if b7 < 127 then BattExtremelyLowSounder 'tells the 08M to sound low batt alarm and shut off return BattExtremelyLowSounder: for b1 = 0 to 100 serout 0,N2400,("osca",0,2) next return WriteUnitBattLevel: readadc 0,b1 if b1 >= 145 then BattFull 'Change these values if using a battery pack that is not 9.6V nominal if b1 < 145 and b1 > 140 then BattMed if b1 <= 140 then battLow return '------------------------- This section writes the menu info on the LCD as necessary. ----------------- BattFull: 'writes High in position 140 on LCD let b1 = 140 gosub wrins for b3 = 19 to 22 read b3, b1 gosub wrchr next b3 return BattMed: 'writes Med in pos 140 let b1 = 140 gosub wrins for b3 = 23 to 25 read b3, b1 gosub wrchr next b3 b1 = " " gosub wrchr ' return BattLow: 'writes Low in pos 140 let b1 = 140 gosub wrins for b3 = 26 to 28 read b3, b1 gosub wrchr next b3 b1 = " " gosub wrchr return WriteUnitDisplay: read 254,b1 if b1 = 0 then WriteKnots if b1 = 1 then WriteMetric if b1 = 2 then WriteImperial return WriteKnots: b1 = 200 'move to bottom middle display gosub wrins b1 = " " gosub wrchr for b3 = 73 to 77 ' write Knots read b3,b1 gosub wrchr next b1 = " " 'blank out any other text that was there gosub wrchr b1 = " " gosub wrchr return WriteMetric: b1 = 200 'move to bottom middle display gosub wrins b1 = " " gosub wrchr for b3 = 78 to 83 'write Metric read b3,b1 gosub wrchr next b1 = " " gosub wrchr return WriteImperial: b1 = 200 'move to bottom middle display gosub wrins for b3 = 84 to 91 'write Imperial read b3,b1 gosub wrchr next return writeRating: let b1 = 192 'move to start of bottom row LCD gosub wrins ' for b3 = 42 to 48 'write "Rating" on LCD on bottom row read b3, b1 gosub wrchr next b3 return ' ' writeSpeed: ' let b1 = 1 gosub wrins let b1 = 128 'move to start of top row LCD gosub wrins for b3 = 29 to 34 'write "Speed:" on LCD on top row read b3, b1 gosub wrchr next b3 return WriteBattLevel: 'Displays the words "Battery Level:" on LCD let b1 = 1 'reset screen gosub wrins 'send reset command to LCD for b3 = 1 to 14 ' setup for...next loop (" Batt level" positions 1 to 14 on LCD) read b3, b1 ' read letter from EEPROM into variable b1 gosub wrchr ' send character to LCD next b3 ' next loop return ' WriteDstroke: let b1 = 192 'move to start of bottom row LCD gosub wrins for b3 = 61 to 72 ' 61 to 72 correspond EEPROM memory that reads "Dist/Stroke:" read b3, b1 ' read letter from EEPROM into variable b1 gosub wrchr ' send character to LCD next return WriteMetronome: let b1 = 1 gosub wrins let b1 = 130 'move to second segment of top row LCD gosub wrins for b3 = 98 to 106 'write "Metronome" on LCD on top row read b3, b1 gosub wrchr next b1 = 12 'turn off cursor gosub wrins return WriteSwitchOff: b1 = 1 gosub wrins let b1 = 130 'move to second segment of top row LCD gosub wrins for b3 = 115 to 125 'write "Switch Off!" on LCD on top row read b3, b1 gosub wrchr next b3 b1 = 12 'turn off cursor gosub wrins peek 85,b1 if b1 = 1 then SendMetronomeData return SendMetronomeData: poke 85,0 high 0 ' pause 200 low 0 pause 200 serout 0,N2400,("osca",b9,1) return WriteDisplay: ' let b1 = 192 'move to start of bottom row LCD gosub wrins for b3 = 107 to 114 'write "Display" on LCD on bottom row read b3, b1 gosub wrchr next b3 return writeVolume: b12 = 2 'this notifies the menu that a button has been pressed, and forces a refresh on the screen let b1 = 1 gosub wrins let b1 = 133 'move to start of top row LCD gosub wrins for b3 = 49 to 54 'write "Volume" on LCD on top row read b3, b1 gosub wrchr next b3 let b1 = 200 'move down a line and write current volume. gosub wrins peek 80,b1 'current value of the volume control is remembered in FSR location 80 gosub wrchr ' let b1 = 12 'turn off cursor gosub wrins ' '--------------------- The following commands control the volume control settings ------------------------------------- checkswitchesLoop: ' This loop alters the digital volume control and when timed out goes back to main screen For b2 = 0 to 200 readadc 1,b1 'if b1 > 190 then nobuttonpressed 'this part checks the state of the left and right arrow switches if b1 < 130 then upvolume 'if the right switch is pushed, the ADC value is around 0 to 130 if b1 > 135 and b1 < 190 then downvolume 'if the left switch is pushed the ADC value is around 135-190 'if no switches are pressed, the value is over 190 pause 10 next return '------------------The following commands control what the arrow buttons do at various parts of the menu.---***************----------- PowerDown: 'This string sends an "Off command" to the 08M. It then drops the power line. the signal is sent high 0 ' 5 times for reliability pause 100 low 0 pause 20 for b1 = 0 to 5 serout 0,N2400,("osca",0,0) pause 50 next return upvolume: low 1 high 4 gosub increment high 1 peek 80,b3 if b3 = 57 then WriteVolume 'if volume is already at "9" (max) then goto write volume b3 = b3 + 1 poke 80,b3 goto WriteVolume downvolume: low 1 low 4 gosub increment high 1 peek 80,b3 if b3 = 48 then WriteVolume ' if volume is already at minimum "0" then goto writevolume b3 = b3 - 1 poke 80,b3 goto WriteVolume '__________________________________Rating (stroke) grab string commands to follow (as sent by the 08M)--------- Getrating: peek 81,b1 'Check to see if the metronome has been activated. If so, skip waiting for rating string, but store 'the ascii value of the rating into b7 and b8 if b1 = 10 then DefinitelyGetRating 'Definitely wait for the rating if metronome is off. 'Otherwise, place metronome rating into ascii characters for calculations b7 = b1/10//10 Or "0" 'extracts units digit into ascii b8 = b1//10 Or "0" 'extracts decimal into ascii return DefinitelyGetRating: ' I know... I know.... terrible name for a label!! serin 7,N2400,("rok"),b6,b7,b8,b9 ' rok from stroke is the starting qualifier. b7 and b8 hold the ascii value. b9 can be a decimal in future 'b7 = "2" 'Comment these values back in if you are testing your calculations 'b8 = "4" if b7 = "0" then ChangetoZero 'if b7 equals ascii zero then we need another zero for b8 return ChangetoZero: b7 = "0" b8 = "0" goto nothing '___________________________________GPS grab string commands to follow__________________________ GetSpeed: peek 82,b1 'first must see if the GPS is there (we dont want to hang on serin if the GPS is not present) b7= "0" b8= "0" b9= "." b11= "0" if b1 = 0 then nothing 'Return to the main program if GPS is not present. Do this without receiving serin. serin 2,N4800,("GPRMC,"),b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b6,b7,b8,b9,b10,b11,b12 'b7= "2" 'Comment these values back in if you want to test some maths but don't want to be "on the move" 'b8= "1" 'b9= "." 'b11= "4" return CheckSatFix: ' Not used in this program but you can check if a sat lock is established by checking if b0=1 after reciving a serial input from GPS serin 2,N4800,("$GPGGA,"),b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0,b0 return '___________________________________ Generic LCD commands to follow. See interfacing circuits pdf at www.picaxe.com for full info _____________ '-----------------------------------This is a parallel interface, with Hitachi (like) chipset. init: let pins = %00000010 ‘ Clear all output lines let b4 = 0 ‘ Reset variable b3 pause 200 ‘ Wait 200 ms for LCD to reset. let pins = 50 ‘ Set to 8-bit operation. pulsout 3,1 ‘ Send data by pulsing ‘enable’ pause 10 ‘ Wait 10 ms pulsout 3,1 ‘ Send data again pulsout 3,1 ‘ Send data again let pins = 34 ‘ Set to 4-bit operation. pulsout 3,1 ‘ Send data. pulsout 3,1 ‘ Send data again. let pins = 130 ‘ Set to two line operation pulsout 3,1 ‘ Send data. let b1 = 14 ‘ Screen on, cursor on instruction gosub wrins ‘ Write instruction to LCD return wrchr: let pins = b1 & 240 OR %00000010‘ Mask the high nibble of b1 into b2. high 2 ‘ Make sure RS is high pulsout 3,1 ‘ Pulse the enable pin to send data. let b2 = b1 * 16 ‘ Put low nibble of b1 into b2. let pins = b2 & 240 OR %00000010 ‘ Mask the high nibble of b2 high 2 ‘ Make sure RS is high pulsout 3,1 ‘ Pulse enable pin to send data. return wrins: let pins = b1 & 240 OR %00000010‘ Mask the high nibble of b1 into b2. pulsout 3,1 ‘ Pulse the enable pin to send data. let b2 = b1 * 16 ‘ Put low nibble of b1 into b2. let pins = b2 & 240 OR %00000010‘ Mask the high nibble of b2 pulsout 3,1 ‘ Pulse enable pin to send data. high 2 ‘ Back to character mode return