Batt_Charger.txt

Warning: if you clip this code into your program, look out for possible html code slipping in.

REM Battery charger 2, SBC2000-062
REM ss, 11/98

REM   *****************************************************************
REM   This program was written to be compiled with revision 3 of VSTB.
REM   Because of this, it defines AIN() as a subroutine, since it only
REM   became a built-in function in revision 4. If you want to use this
REM   program, you will have to make the appropriate modifications.
REM   *****************************************************************

REM Here is a little battery charger project.
REM The charger is basically a "buck regulator" where the PWM output of an SBC2000-062
REM controls a series pass transistor into an inductor which charges the battery.
REM The charging current is measured and used to adjust the PWM.
REM The battery voltage is measured and used to terminate the charge cycle.

REM This code illustrates a very workable 3 button interface
REM and printing "decimal" numbers to the LCD.

SUBROUTINE delay(arg AS INTEGER)
	LOCAL index AS INTEGER
	FOR index = 0 TO arg
		REM
	NEXT
END

REM print a number formatted to right with DP
SUBROUTINE PRINT_DP(arg AS INTEGER,pos AS INTEGER)
	LOCAL T1 AS INTEGER
	LOCAL places AS INTEGER

	T1 = arg\1000
	places = 3

	REM print number from right to left one at a time
	DO
		LCD_COMMAND(0x80+pos+places)
		PRINT T1\10
		T1=T1/10
		places = places - 1
	LOOP UNTIL places = 0
	LCD_COMMAND(0x80+pos+places+1)
	PRINT "."
	T1 = arg/1000
	DO
		LCD_COMMAND(0x80+pos+places-1)
		PRINT T1\10
		T1=T1/10
		places = places - 1
	LOOP UNTIL T1=0
END

REM This uses pins 1,2,3, and 5 of the KEYPAD connector
CONSTANT up_key      AS INTEGER = 0
CONSTANT down_key    AS INTEGER = 4
CONSTANT enter_key   AS INTEGER = 8
CONSTANT no_key      AS INTEGER = -1
REM get key will return a debounced keypress
FUNCTION get_key() AS INTEGER
	STATIC last_key AS INTEGER
	DO
		DO
			get_key = KEYPAD(0)
		LOOP UNTIL get_key <> last_key
		last_key = get_key
	LOOP UNTIL get_key<>no_key
END

SUBROUTINE set_time(sec AS INTEGER, mn AS INTEGER, hr AS INTEGER)
	POKE(37,0):REM day
	POKE(38,hr):REM hour
	POKE(39,mn):REM minute
	POKE(40,sec):REM second
END

SUBROUTINE wait_one_second()
	LOCAL now AS INTEGER
	now = PEEK(40)
	DO
		REM
	LOOP UNTIL now <> PEEK(40)
END

GLOBAL beep_bit AS BIT = 6,7
SUBROUTINE tweet()
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
	beep_bit=0
	beep_bit=1
END

REM 8 bit PWM at fixed freq - 5 kHz (-062)
REM PWM control word
SUBROUTINE set_duty_cycle(arg AS INTEGER)
	CONSTANT VTI_T2CON   AS INTEGER = 0x12
	CONSTANT VTI_CCPR1L  AS INTEGER = 0x15
	CONSTANT VTI_CCP1CON AS INTEGER = 0x17
	CONSTANT VTI_PR2     AS INTEGER = 0x92
	CONSTANT VTI_TRISC   AS INTEGER = 0x95
	POKE(VTI_T2CON, (PEEK(VTI_T2CON) AND 0xFC) OR 0)
	POKE(VTI_PR2,255)
	POKE(VTI_CCPR1L, arg):REM Time hi
	POKE(VTI_CCP1CON, 0x0C + (1 * 0x10)):REM 2 lsb time hi
	POKE(VTI_TRISC, PEEK(VTI_TRISC) AND 0xFD)
	POKE(VTI_T2CON, PEEK(VTI_T2CON) OR 4)
END

FUNCTION read_PWM() AS INTEGER
	read_PWM = PEEK(VTI_CCPR1L)
END

SUBROUTINE adjust_PWM(arg AS INTEGER)
	LOCAL temp AS INTEGER
	temp = read_PWM()
	temp = temp + arg
	temp = MAX(MIN(temp,255),0)
	set_duty_cycle(temp)
END

CONSTANT VTI_ADRES      AS INTEGER = 0x1E
CONSTANT VTI_ADCON0     AS INTEGER = 0x1F
CONSTANT VTI_TRISA      AS INTEGER = 0x85
CONSTANT VTI_ADCON1     AS INTEGER = 0x9F
CONSTANT VTI_Analog_5   AS INTEGER = 2
REM CONSTANT VTI_Analog_3   AS INTEGER = 4
REM CONSTANT VTI_Analog_0   AS INTEGER = 6
CONSTANT VTI_Fosc_by_32 AS INTEGER = 0x81
GLOBAL   VTI_AD_GO      AS BIT = VTI_ADCON0,2
REM simple A/D, SBC2000-074/-062
FUNCTION AIN(channel AS INTEGER) AS INTEGER
	POKE(VTI_ADCON1,VTI_Analog_5)	:REM set up port a as 5 analog inputs
	POKE(VTI_TRISA, PEEK(VTI_TRISA) OR (2^channel))
	POKE(VTI_ADCON0, VTI_Fosc_by_32 OR (channel * 8))
	VTI_AD_GO = 1			: REM   Start the conversion
	AIN = PEEK(VTI_ADRES)		: REM   Read the result
END

REM return battery voltage in millivots
REM measured voltage is trimmed with pot
REM get enuf reading so result = millivolts
FUNCTION get_battery_voltage() AS INTEGER
	LOCAL index AS INTEGER
	FOR index = 1 TO 40
		get_battery_voltage = get_battery_voltage + AIN(0)
	NEXT
END

REM return charging current in millamps
REM get enuf reading so result = milliamps
REM 5.6 mV/ma
REM fix overflow
FUNCTION get_current() AS INTEGER
	LOCAL index AS INTEGER
	FOR index = 1 TO 3
		get_current = get_current + AIN(1)
	NEXT
END

GLOBAL number_of_cells AS INTEGER
GLOBAL battery_voltage AS INTEGER, cell_voltage AS INTEGER
GLOBAL battery_EOC_voltage AS INTEGER, highest_battery_voltage AS INTEGER
GLOBAL desired_charge_rate AS INTEGER, actual_charge_rate AS INTEGER
GLOBAL capacity AS INTEGER, measured_capacity AS INTEGER
GLOBAL mA_seconds AS INTEGER, mA_hours AS INTEGER
GLOBAL key AS INTEGER, index AS INTEGER

DIM cell_count[8] AS INTEGER CONSTANT = [1,2,3,4,5,6,7,8]
DIM cell_voltages[4] AS INTEGER CONSTANT = [1400,1450,3400,3450]
DIM charge_rates[6] AS INTEGER CONSTANT = [20,50,100,200,500,1000]

REM charger is used on NiCd cells with EOC = 1.40 or 1.45 Volts
REM and Lithium Metal cells with EOC = 3.4 or 3.45 Volts

REM *************************** Main Program ****************************


DO
	LCD_COMMAND(128)
	PRINT "Cells?  "
	index = 0
	DO
		number_of_cells = cell_count[index]
		LCD_COMMAND(192)
		PRINT number_of_cells,"       "
		key = get_key()
		SELECT key
			CASE up_key
				index = MIN(7,MAX(0,index+1))
			CASE down_key
				index = MIN(7,MAX(0,index-1))
		ENDSELECT
	LOOP UNTIL key = enter_key
			
	delay(100)

	LCD_COMMAND(128)
	PRINT "V/cell?  "
	index = 0
	DO
		cell_voltage = cell_voltages[index]
		PRINT_DP(cell_voltage,65)		
		key = get_key()
		SELECT key
			CASE up_key
				index = MIN(3,MAX(0,index+1))
			CASE down_key
				index = MIN(3,MAX(0,index-1))
		ENDSELECT
	LOOP UNTIL key = enter_key
	battery_EOC_voltage = cell_voltage * number_of_cells
	highest_battery_voltage = 0
	delay(100)

	LCD_COMMAND(128)
	PRINT "Rate?    "
	index = 0
	DO
		desired_charge_rate = charge_rates[index]
		LCD_COMMAND(192)
		PRINT desired_charge_rate,"     "
		key = get_key()
		SELECT key
			CASE up_key
				index = MIN(5,MAX(0,index+1))
			CASE down_key
				index = MIN(5,MAX(0,index-1))
		ENDSELECT
	LOOP UNTIL key = enter_key

	set_duty_cycle(0)
	set_time(0,0,0)

	LCD_COMMAND(128)
	PRINT "       V"

	LCD_COMMAND(192)
	PRINT "      mA"

	LCD_COMMAND(148)
	PRINT "     mAH"


	DO
		REM get battery voltage
		battery_voltage = get_battery_voltage()
		highest_battery_voltage = MAX(highest_battery_voltage,battery_voltage)
		PRINT_DP(battery_voltage,1)

		REM servo the PWM duty cycle to match the desired charge rate
		actual_charge_rate = get_current()
		adjust_PWM(((desired_charge_rate-actual_charge_rate)/5))
		IF desired_charge_rate > actual_charge_rate
			adjust_PWM(1)
		ELSE
			adjust_PWM(-1)
		ENDIF
		LCD_COMMAND(192)
		PRINT actual_charge_rate," "
		REM accumulate the charge into the battery
		mA_seconds = mA_seconds + actual_charge_rate
		IF mA_seconds >= 3600
			mA_seconds = mA_seconds-3600
			mA_hours = mA_hours + 1
		ENDIF
		LCD_COMMAND(148)
		PRINT mA_hours," "

		wait_one_second()
	LOOP UNTIL (battery_voltage>battery_EOC_voltage) OR (battery_voltage<(highest_battery_voltage-5*number_of_cells))

	set_duty_cycle(0)
	LCD_COMMAND(192)
	PRINT "     mAH"
	LCD_COMMAND(192)
	PRINT mA_Hours

	DO
		PRINT_DP(get_battery_voltage(),1)
		tweet()
	LOOP UNTIL KEYPAD(0) <> -1

	DO
		REM
	LOOP UNTIL KEYPAD(0) = -1
LOOP UNTIL 0