' {$STAMP BS2p} ', Char Definitions.bsp} ' {$PBASIC 2.5} 'Char Definitions.bsp '**************************************************************************** ' This is a program used with a Parallax Basic Stamp 2sx ' module. The BS2sx is the control computer for an electric vehicle ' battery monitoring system. This systems function is to read battery voltage ' and display it to the vehicle driver. The system supports 18 6Volt batteries. '**************************************************************************** ' CHANGES ' version 4 ' * added 5x averaging to voltage display ' ' version 5 ' * make bargraph scale adjustable: 2v-8v,3v-8v,4v-8v,5v-8v ' ' '**************************************************************************** ' Variable definition and Constants '**************************************************************************** ' some counters i VAR Byte k VAR Byte p VAR Byte l VAR Byte ' global state constants BARGRAPHSTATE CON 1 BATTVOLTSTATE CON 2 DATAAQSTATE CON 3 ' data aquisition state DATAAQOFF CON 0 DATAAQONLOW CON 2 DATAAQONHIGH CON 3 ' DATa aquisition mode/state dataaqmode VAR Nib ' data collection freq vars dataaqcout VAR Byte ' global state variable globalstate VAR Byte ' battery selected for display in battvoltstate BattSelected VAR Byte ' variables used by read ADC function - used as arguments (set before running func) voltage VAR Word channel VAR Byte chipselection VAR Byte ' EPROM position of character for argument to DrawItem itemmempos VAR Byte ' temp vars temp1 VAR Word temp2 VAR Word colmnpos VAR Byte temp VAR Byte nblow VAR Word nbhigh VAR Word ' variables used internally by Stamp for Button command btnwork1 VAR Byte btnwork2 VAR Byte ' var to indicate battery voltage scale bargraphscale VAR Byte '******************************************************************** '******* initialize variables *************************************** ' initialize vars used internally by Button comand - don't use otherwise btnwork1 = 0 btnwork2 = 0 ' init the selected batter to battery 0 BattSelected = 0 ' init global state to bar graph mode globalstate = BARGRAPHSTATE ' set bar graph scale to 5 volts to 8 volts scale bargraphscale = 4 '******************************************************************** ' main program function '******************************************************************** Main: ' set main io to 1 or 0 as needed - io layout is: 1111111100111101 = 0xFF3D MAINIO DIRS = $FF3D ' set all aux io to outputs - 1 corrosponds to output in DIRS AUXIO DIRS = $FFFF ' first initialize the LCD ' initialize the LCD - first set lcd reset low, wait 100ms, then go high LOW 1 PAUSE 100 HIGH 1 ' reset display - ao_rd_wr_d7_d6..._d0 LOW 0 OUTS = $1C52 HIGH 0 ' set LCD bias LOW 0 OUTS = $1452 HIGH 0 ' set ADC to reverse LOW 0 OUTS = $1432 HIGH 0 ' set to normal common output LOW 0 OUTS = $1812 HIGH 0 ' set built in resistance - values 0x20 to 0x27. it sets contrast ratio base value LOW 0 OUTS = $492 HIGH 0 ' set LCD contrast. this is a two command operation - first send Volume Mode Set Cmd LOW 0 OUTS = $1032 HIGH 0 ' now send contrast value, the second part of the LCD contrast operation LOW 0 ' original, medium contrast ' OUTS = $592 ' higher contrast OUTS = $592 ' maximum contrast ' OUTS = $592 HIGH 0 ' set power mode - turn on internal pwr supply features - ' Volt Regulator, Volt follower,booster circuit LOW 0 OUTS = $5F2 HIGH 0 ' turn on display LOW 0 OUTS = $15F2 HIGH 0 ' initialize first state GOSUB InitBargraphLCD ' **** MAIN PROGRAM LOOP: Loop forever! ****** DO WHILE 1 ' case statement to select and run current program state SELECT globalstate CASE BARGRAPHSTATE ' run bar graph "funcitonality" ' read each voltage then display to LCD FOR chipselection = 3 TO 5 FOR channel = 0 TO 5 ' run function to get one battery voltage, with channel and chipselection as arguments GOSUB Read_ADC 'display current battery voltage bar on LCD, with channel and chip selection as input GOSUB LCD_Bargraph NEXT ' check mode button and call function to change state if button pressed MAINIO BUTTON 7, 1, 100, 50, btnwork1, 1, ModeButtonFn BUTTON 6, 1, 100, 50, btnwork2, 1, FunctionButtonFn NEXT CASE BATTVOLTSTATE ' single battery voltage state - read and display selected battery voltage ' read selected batteries voltage then display to LCD ' select ACD chip and channel chipselection = (BattSelected / 6) + 3 channel = BattSelected // 6 ' average 4 readings for noise rejection - good electronics design?! temp1 = 0 FOR l = 0 TO 4 ' run function to get one battery voltage, with channel and chipselection as arguments GOSUB Read_ADC temp1 = voltage + temp1 NEXT ' divid to get average voltage = temp1 / 5 'display current battery voltage numerically on LCD ' send one batteries voltage to LCD in decimal format ' change voltage to decimal and send out serial port ' this is a decimal approximation but accruate for 0-1024 to two decimal places ' I have to convert back to voltages based on a 10 bit 1024 count ADC, 3V voltage span, ' 10K / 6.04K resistor voltage divider .3765586x gain. ' Instrument amp gain = 100,000ohm/6040ohm + 1. ' formula is ' Display V = (Counts * 3)*(10,000 + 6040) / (1024 * 6040) ' simplified: ' Display V = Counts * 30 / 3856 ' To get voltage in a fixed point system do in this order ' V = Counts * 30 ' then go ' V = V / 3856 ' first get the integer part of the number temp1 = voltage * 30 ' draw - battery voltage, first set draw location page AUXIO LOW 0 OUTS = $1692 HIGH 0 ' set high column nib LOW 0 OUTS = $252 HIGH 0 ' set low column nib LOW 0 OUTS = $1B2 HIGH 0 ' print the whole part of voltage left of decimal point itemmempos = (temp1 / 3856) GOSUB DrawItem ' print decimal point itemmempos = 30 GOSUB DrawItem ' drwa numbers after decimal point. this is done by taking the number you want ' to devide, temp1, and taking the modulus of it with the dividor, 3856, then ' multiplying by 10. this number, call it X is saved for the next digit. the current digit ' is X / 3856. ' So IF I wanted TO GET the first digit of 2/3, Go: ' 2 // 3 = 2 ' 2 * 10 = 20 ' 20 / 3 = 6 (.66667, but this isn't here in fixed point) ' since 2/3 = .6666666667, the first digit is, correctly, 6 FOR p = 0 TO 1 ' first digit remainder of desired dividor * 10 temp1 = (temp1 // 3856) * 10 ' display just created digit on LCD of this value: itemmempos = (temp1 / 3856) GOSUB DrawItem NEXT CASE DATAAQSTATE ' run data aquisition setup fun GOSUB DataAquisionSetup CASE ELSE ' unrecognized state - set to default globalstate = BARGRAPHSTATE ENDSELECT ' switch to main io MAINIO ' check mode button and call function to change state if button pressed BUTTON 7, 1, 100, 50, btnwork1, 1, ModeButtonFn ' check function button and call function to change data if button pressed BUTTON 6, 1, 100, 50, btnwork2, 1, FunctionButtonFn ' put a "label" marker to allow for returning from functions called by Button return_to_main_label: ' continue main loop LOOP ' end of main - program done. don't get here ever... END '******************************************************************** '******************************************************************** ' funciton update state when mode button pressed ' ONLY FOR USE FROM MODE BUTTON INSTRUCTION FROM MAIN '******************************************************************** ModeButtonFn: ' mode button pressed - clear lcd GOSUB ClearLCD ' now update state TO NEXT state IF (globalstate = BARGRAPHSTATE) THEN ' set flag to go to new state globalstate = BATTVOLTSTATE ' init new state GOSUB InitBattVolt ELSEIF (globalstate = BATTVOLTSTATE) THEN globalstate = DATAAQSTATE ' set data aquisition mode to off dataaqmode = DATAAQOFF ' ****update LCD for this state/function change GOSUB LCD_Datasetup ' reset the counter for high or low frequency data collection nblow = 0 ELSE ' in dataaq state - set state flag to bargraph globalstate = BARGRAPHSTATE ' initialze new state GOSUB InitBargraphLCD ENDIF ' return to maain - Return command doesn't work when called from button... GOTO return_to_main_label RETURN '******************************************************************** '******************************************************************** ' function updatae variables accordingly when fucntion button pressed ' ONLY FOR USE FROM FUNCTION BUTTON INSTRUCTION FROM MAIN '******************************************************************** FunctionButtonFn: ' function button pressed- change data according to mode IF (globalstate = BARGRAPHSTATE) THEN ' increment scale counter bargraphscale = bargraphscale + 1 IF (bargraphscale >= 6) THEN bargraphscale = 3 ENDIF ELSEIF (globalstate = BATTVOLTSTATE) THEN ' in battery monitor state - increment the battery selected for display BattSelected = Battselected + 1 ' roll over the number when at max battery IF (BattSelected > 17) THEN BattSelected = 0 ENDIF ' functio button hit and data updated ' redraw - Battery Label (or 1-6 or A-L) AUXIO LOW 0 OUTS = $1652 HIGH 0 ' set high column nib LOW 0 OUTS = $2B2 HIGH 0 ' set low column nib LOW 0 OUTS = $152 HIGH 0 ' draw selected battery label 1-6 A-L IF (BattSelected < 6) THEN itemmempos = BattSelected + 1 ELSE itemmempos = Battselected + 4 ENDIF GOSUB DrawItem ELSEIF (globalstate = DATAAQSTATE) THEN ' in data aquistiion state - increment data aquisition mode IF (dataaqmode = DATAAQOFF) THEN ' switch to low data aquisition rate dataaqmode = DATAAQONLOW ' set the frequency counter to low freq data collection nbhigh = 10000 'LOWDATAOUTPUTLOOPS ELSEIF (dataaqmode = DATAAQONLOW) THEN ' switch to high data aquisitoin rate dataaqmode = DATAAQONHIGH ' set the frequency counter to high freq data collection nbhigh = 0'HIGHDATAOUTPUTLOOPS ELSEIF (dataaqmode = DATAAQONHIGH) THEN dataaqmode = DATAAQOFF ' reset the counter for high or low frequency data collection nblow = 0 ENDIF ' update lcd GOSUB LCD_Datasetup ENDIF ' RETurn to maain - Return command doesn't work when called from button... GOTO return_to_main_label RETURN '******************************************************************** '******************************************************************** ' INitialize Bar Graph LCD '******************************************************************** ' this function will initialize the bar graph display by drawing hte ' battery labels, scale markers, and over/under voltage lines InitBargraphLCD: ' clear LCD GOSUB ClearLCD ' draw dotted "range" lines at ' 5.25V (675 counts) AND 7.45V (958 counts)--- or 4.8V (617) like Mr. Oba of Soleq said? ' should I also draw voltage scale? or both? scale may take up too much space... AUXIO 'page LOW 0 OUTS = $16F2 HIGH 0 ' set high column nib LOW 0 OUTS = $212 HIGH 0 ' set low column nib LOW 0 OUTS = $12 HIGH 0 ' print the battery labels along bottom 1-6 FOR p = 1 TO 6 itemmempos = p GOSUB DrawItem NEXT ' BATTS A-L FOR p = 10 TO 21 itemmempos = p GOSUB DrawItem NEXT RETURN '******************************************************************** ' function/state initlization of Batteries Voltage state '******************************************************************** InitBattVolt: ' draw - battery label (or 1-6 or A-L) LOW 0 OUTS = $1652 HIGH 0 ' set high column nib LOW 0 OUTS = $252 HIGH 0 ' set low column nib LOW 0 OUTS = $52 HIGH 0 ' print "BATTERY" in middle of screen itemmempos = 11 'B GOSUB DrawItem itemmempos = 10 'A GOSUB DrawItem itemmempos = 26 'T GOSUB DrawItem itemmempos = 26 'T GOSUB DrawItem itemmempos = 14 'E GOSUB DrawItem itemmempos = 25 'R GOSUB DrawItem itemmempos = 28 'Y GOSUB DrawItem itemmempos = 29 'BLANK SPACE GOSUB DrawItem IF (BattSelected < 6) THEN itemmempos = BattSelected + 1 GOSUB DrawItem ELSE itemmempos = Battselected + 4 GOSUB DrawItem ENDIF ' draw "V" for voltage after numbers to be printed later ' draw - battery label (or 1-6 or A-L) LOW 0 OUTS = $1692 HIGH 0 ' set high column nib LOW 0 OUTS = $2B2 HIGH 0 ' set low column nib LOW 0 OUTS = $12 HIGH 0 itemmempos = 27 'V char GOSUB DrawItem ' draw decimal point RETURN '******************************************************************** '******************************************************************** ' subroutine to read ADC '******************************************************************** Read_ADC: ' this function has arguments - *chipselection*, for the ADC to select, ' and *channel*, for the channel on the selected ADC to be slected ' the args are set outside, so care has to be take when calling this funciton ' set to use main IO MAINIO ' set chip select output to enable Front, Rear1 or Rear2 ADC. LOW chipselection ' create command - 11000 binary 0x18, setting start bit and the single ended bit ' channel is the channel that is to be converted and muxed - it is zero based ' write to select channel and convert SHIFTOUT 0, 2, MSBFIRST, [($18 | channel) \6] ' read channel 1 voltage: pins - 2=clock,0=dout, 1=din SHIFTIN 1, 2, MSBPOST, [voltage \11] ' set chip select back high HIGH chipselection ' RANDOM voltage ' voltage = voltage / 64 ' return to call fucntion RETURN '******************************************************************** ' Clear LCD '******************************************************************** ClearLCD: FOR p = 7 TO 0 AUXIO ' set to page LOW 0 OUTS = $1612 + (p << 5) HIGH 0 ' set column back to zero ' set high column nib LOW 0 OUTS = $212 HIGH 0 ' set low column nib LOW 0 OUTS = $12 HIGH 0 FOR i = 0 TO 127 LOW 0 OUTS = $16 HIGH 0 NEXT NEXT RETURN '******************************************************************** ' Display current voltages in a bar graph on LCD '******************************************************************** LCD_Bargraph: ' display this many pixels: voltage count * (full scale #pixels) / 1024 AUXIO ' subtract off the equivalent of 3 volts from adc counts read to ' set scale to 3 volt minimum instead of 0 volt minimum - also set to 0 if ' it will be negative. 3 volts is 385 counts ' **if you want to use 2 volts instead use 257 counts. ' ** also could just cut off at 5.25 volts?! ' subtract off the desired scale positoin - the scale count bargrpahscale ' ranges from 3 to 5, and is user selectable with the fucnction switch. ' 128 counts is about 1 volt, so go bargraphscale x 128 and rescale based on ' that. ' first make sure i don't geT A negative number (it rolls over to 65535) IF (voltage > (bargraphscale * 128)) THEN ' IF (voltage > 385) THEN ' voltage not less than min scale, subtract off scale temp1 = voltage - (bargraphscale * 128) ' temp1 = voltage - 385 ELSE temp1 = 0 ENDIF ' calc pixels (temp1) = voltage in counts * full scale pixels / full scale ADC counts ' full scale adc counts varies based on bargraphscale temp1 = (temp1 * 56) / (1024 - (bargraphscale * 128)) ' temp1 = temp1 * 56 / (1024 - 385) '***************************************************************************************** ' ******* should I round pixels based on the first decimal place...? TAKES TIME: ' see function LCD_OneBattery comments for rounding/decimal place calculation explination '' IF ((((voltage * 56) // 1024) * 10 / 1024) >= 5) THEN ' IF ((((voltage * 56) // (1024 - (128 * bargraphscale))) * 10 / (1024 - (128 * bargraphscale))) >= 5) THEN ' IF (temp1 < 55) THEN ' round pixel up one ' temp1 = temp1 + 1 ' ENDIF ' ENDIF '***************************************************************************************** ' calculate column for this battery colmnpos = ((((chipselection - 3) * 6) + channel) * 7) ' calculate the solid page incmrent i = 7 - (temp1 / 8) ' make the high and low nib column addres to write to display - which requires two cmds for this nblow = $12 + (colmnpos.LOWNIB << 5) nbhigh = $212 + (colmnpos.HIGHNIB << 5) IF (i <> 7) THEN ' write solid pages FOR p = 6 TO i ' set page, column to start LOW 0 OUTS = $1612 | (p << 5) ' set high column nib based on the battery currently printing HIGH 0 LOW 0 OUTS = nbhigh HIGH 0 LOW 0 ' set low column nib OUTS = nblow HIGH 0 'write $00 LOW 0 OUTS = $16 HIGH 0 ' write column FOR k = 0 TO 4 LOW 0 OUTS = $1FF6 HIGH 0 NEXT 'write $00 LOW 0 OUTS = $16 HIGH 0 NEXT ENDIF ' draw partial page that isn't whole solid page - it will be the last one on top ' set page, column to start LOW 0 OUTS = $1612 | ((i - 1) << 5) ' set high column nib based on the battery currently printing HIGH 0 LOW 0 OUTS = nbhigh HIGH 0 LOW 0 ' set low column nib OUTS = nblow HIGH 0 ' first column is always blank LOW 0 OUTS = $16 HIGH 0 ' figure out how many pixels to write on top of full pages just written ' pixels extra are remainder of temp1/8 pages. temp1 is total pixels. ' since we are unfortunately drawing from bottom of lcd, do flip bits ' by taking FF and shifting it by the ammount of hte remainder. since ' storing in p, a byte, it should shift off end p = $FF << (8 - (temp1 // 8)) ' p is data, so shift to data section of IO, then use temp2 to send to io in for loop temp2 = $16 + (p << 5) FOR k = 0 TO 4 LOW 0 OUTS = temp2 HIGH 0 NEXT 'write $00 LOW 0 OUTS = $16 HIGH 0 ' now clear the pages above the pages written with a bar. IF (i <> 1) THEN i = i - 2 FOR p = i TO 0 ' set page, column to start LOW 0 OUTS = $1612 | (p << 5) ' set high column nib based on the battery currently printing HIGH 0 LOW 0 OUTS = nbhigh HIGH 0 LOW 0 ' set low column nib OUTS = nblow HIGH 0 ' draw spaces to blank out if column gets smaller FOR k = 0 TO 7 LOW 0 OUTS = $16 HIGH 0 NEXT NEXT ENDIF ' draw 5.25 low voltage line. 5.25V is 674.8 counts, so I will use ' 675 counts as the cutoff. also must subtract offset (128 counts x bargraphscale) ' set page, column to start ' if page is 5.25v page, write $xx, for 675 - (128 * bargraphscale) count line ' calculate page of 5.25v low limit line ' first get total pixels temp1 = ( (675 - (128 * bargraphscale)) * 56) / (1024 - (128 * bargraphscale)) ' get page #: page zero is on top, so have to invert number accordingly temp2 = 6 - (temp1 / 7) ' get extra pixels. pixel 7 is at bottom, so have to convert temp1 = 8 - (temp1 // 8) ' go to desired page just calculated LOW 0 OUTS = ($1612 | (temp2 << 5)) ' set column in var nbhigh ' set high column nib based on the battery currently printing HIGH 0 LOW 0 OUTS = nbhigh HIGH 0 LOW 0 ' set low column nib OUTS = nblow HIGH 0 ' write line LOW 0 ' this is a potential bug if changed - shift 4 will overwrite data if temp1 is 0 ' however, a "1" will be overwriten with a "1", and temp1 is, coicedentally, 0 OUTS = ($16 | (1 << (temp1 + 4))) ' done writing LCD - set the chip select to on, disabled HIGH 0 ' return to main loop RETURN '******************************************************************** '******************************************************************** ' Draw an item on dislpay: ' 0-6,A-L,N,O,P,R,S,T,U,V,W,Y,:,5x8 fill ' this information will be written into the eprom with the DATA command ' in the consecutive order above, and is in the file "Char Definitions.bsp" '******************************************************************** DrawItem: FOR k = 0 TO 6 AUXIO LOW 0 READ ((itemmempos * 7) + k), temp OUTS = $16 + (temp << 5) HIGH 0 NEXT RETURN '******************************************************************** ' function/state that selects data collection state '******************************************************************** DataAquisionSetup: IF (dataaqmode = DATAAQOFF) THEN ' data aquision is off - do nothing ELSE ' in data aquisition state ' read adcs and send data out serial port if frequency count is reached IF (nblow >= nbhigh) THEN nblow = 0 ' read each voltage then send out the serial port for data collection ' for each ADC, 6 batteries per ADC - chip selects are on IO points 3-5 FOR chipselection = 3 TO 5 ' for each batter measured by each adc FOR channel = 0 TO 5 ' run function to get one battery voltage, with channel and chipselection as arguments GOSUB Read_ADC ' send voltage to serial port 115200 baud rate ' for 155200 use 16385. Formula is int(2,500,00/baud) - 20 +16384 ' for 19200 use 16494 ' this will just send out the current battery number to the serial port... ' change voltage to decimal and send out serial port ' this is a decimal approximation but accruate for 0-1024 to two decimal places ' ** check function LCD_OneBattery comments for more info ' first get the integer part of the number temp1 = voltage * 30 SEROUT 16, 16494, [DEC (temp1 / 3856), "."] FOR k = 0 TO 1 ' remainder * 10 temp1 = (temp1 // 3856) * 10 ' display just created digit SEROUT 16, 16494, [DEC1 (temp1 / 3856)] NEXT SEROUT 16, 16494, [" "] NEXT NEXT ' done sending 18 batteries - send carriage return to serial port SEROUT 16, 16494, [CR] ENDIF ' we are in data collection mode - increment data count for low/high freq control nblow = nblow + 1 ENDIF RETURN '******************************************************************** '******************************************************************** ' Display data aquisition setup on LCD '******************************************************************** LCD_Datasetup: ' send new title to LCD ' draw - battery label (or 1-6 or A-L) AUXIO LOW 0 OUTS = $1652 HIGH 0 ' set high column nib LOW 0 OUTS = $272 HIGH 0 ' set LOW column Nib LOW 0 OUTS = $92 HIGH 0 ' print "DATA SETUP" in middle of screen itemmempos = 13 'D GOSUB DrawItem itemmempos = 10 'A GOSUB DrawItem itemmempos = 26 'T GOSUB DrawItem itemmempos = 10 'A GOSUB DrawItem SELECT dataaqmode CASE DATAAQOFF LOW 0 OUTS = $1692 HIGH 0 ' set high column nib LOW 0 OUTS = $272 HIGH 0 ' set LOW column Nib LOW 0 OUTS = $12 HIGH 0 itemmempos = 29 '__ GOSUB DrawItem itemmempos = 23 'o GOSUB DrawItem itemmempos = 15 'f GOSUB DrawItem itemmempos = 15 'f GOSUB DrawItem itemmempos = 29 '__ GOSUB DrawItem CASE DATAAQONLOW ' set page LOW 0 OUTS = $1692 HIGH 0 ' set high column nib LOW 0 OUTS = $272 HIGH 0 ' set LOW column Nib LOW 0 OUTS = $12 HIGH 0 itemmempos = 23 'o GOSUB DrawItem itemmempos = 22 'n GOSUB DrawItem itemmempos = 29 '__ GOSUB DrawItem itemmempos = 21 'L GOSUB DrawItem itemmempos = 23 'o GOSUB DrawItem CASE DATAAQONHIGH ' page LOW 0 OUTS = $1692 HIGH 0 ' set high column nib LOW 0 OUTS = $272 HIGH 0 ' set LOW column Nib LOW 0 OUTS = $12 HIGH 0 itemmempos = 23 'o GOSUB DrawItem itemmempos = 22 'n GOSUB DrawItem itemmempos = 29 '__ GOSUB DrawItem itemmempos = 17 'H GOSUB DrawItem itemmempos = 18 'I GOSUB DrawItem ENDSELECT RETURN