lipo-charger/lipo-charger.c

355 lines
7.6 KiB
C

/***************************************************************************
* Copyright (C) 09/2007 by Olaf Rempel *
* razzor@kopf-tisch.de *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; version 2 of the License, *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 8000000
#include <util/delay.h>
#include <stdio.h>
#define BAUDRATE 19200
#define UART_CALC_BAUDRATE(baudRate) ((uint32_t)(F_CPU) / ((uint32_t)(baudRate)*16) -1)
/*
* LCD:
* - CS Chip Select
* - RS Register Select
* - RW Read/Write
*/
#define RS PORTB6
#define CS PORTB7
#define RW PORTD3
#define LCD_D4 PORTD4
#define LCD_D5 PORTD5
#define LCD_D6 PORTD6
#define LCD_D7 PORTD7
#define LCD_DATA_MASK ((1<<LCD_D4) | (1<<LCD_D5) | (1<<LCD_D6) | (1<<LCD_D7))
/*
* power supply:
* - CH0 - voltage with prescaler (12V -> 2000mV; 15mV -> 2.5mV/bit)
* - CH1 - current with (5A -> 2500mV; 5mA -> 2.5mV/bit)
* - PWM - high-active output to buckconverter
*/
#define CH0 PORTC0
#define CH1 PORTC1
#define PWM PORTB1
#define NOP asm volatile ("nop")
#define MOD_WAITING 0x00
#define MOD_CHARGING 0x01
#define MOD_READY 0x02
#define VOLTAGE_CONNECT 9000
#define VOLTAGE_CHARGE 12450
#define CURRENT_CHARGE 49000
#define CURRENT_READY 2000
#define VOLTAGE_REMOVE 1000
static void lcd_wait_busy(void)
{
uint8_t status;
DDRD &= ~(LCD_DATA_MASK);
PORTD |= (1<<RW);
do {
PORTB |= (1<<CS);
NOP;
NOP;
status = (PIND & LCD_DATA_MASK);
PORTB &= ~(1<<CS);
PORTB |= (1<<CS);
NOP;
NOP;
status |= ((PIND & LCD_DATA_MASK) >> 4);
PORTB &= ~(1<<CS);
} while (status & 0x80);
DDRD |= LCD_DATA_MASK;
PORTD &= ~(1<<RW);
}
static void lcd_write_ctrl(uint8_t val)
{
PORTD &= ~(LCD_DATA_MASK);
PORTD |= (val & 0xF0);
PORTB |= (1<<CS);
NOP;
NOP;
PORTB &= ~(1<<CS);
PORTD &= ~(LCD_DATA_MASK);
PORTD |= ((val<<4) & 0xF0);
PORTB |= (1<<CS);
NOP;
NOP;
PORTB &= ~(1<<CS);
lcd_wait_busy();
}
static void lcd_write_data(uint8_t val)
{
PORTD &= ~(LCD_DATA_MASK);
PORTD |= (val & 0xF0);
PORTB |= (1<<RS);
PORTB |= (1<<CS);
NOP;
NOP;
PORTB &= ~(1<<CS);
PORTD &= ~(LCD_DATA_MASK);
PORTD |= ((val<<4) & 0xF0);
PORTB |= (1<<CS);
NOP;
NOP;
PORTB &= ~(1<<CS);
PORTB &= ~(1<<RS);
lcd_wait_busy();
}
/* heartbeat chars */
static const uint8_t mychars[] = {
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00,
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00,
};
static void lcd_init(void)
{
_delay_ms(25);
lcd_wait_busy();
/* 4bit, 2lines, 5x7 font */
lcd_write_ctrl(0x28);
/* display on, cursor off, blink off */
lcd_write_ctrl(0x0C);
/* cursor increments */
lcd_write_ctrl(0x06);
lcd_write_ctrl(0x40 | 0x00);
uint8_t i;
for (i = 0; i < sizeof(mychars); i++)
lcd_write_data(mychars[i]);
/* clear & home pos */
lcd_write_ctrl(0x01);
}
static int uart_putchar(char c, FILE *stream)
{
if (c == '\n')
uart_putchar('\r', stream);
loop_until_bit_is_set(UCSRA, UDRE);
UDR = c;
return 0;
}
static int lcd_putchar(char c, FILE *stream)
{
lcd_write_data(c);
return 0;
}
static FILE lcd = FDEV_SETUP_STREAM(lcd_putchar, NULL, _FDEV_SETUP_WRITE);
static FILE log = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
static uint16_t voltage;
static uint16_t current;
ISR(ADC_vect)
{
if (ADMUX & 0x01) {
current = ADCW * 50;
ADMUX = CH0;
} else {
voltage = ADCW * 15;
ADMUX = CH1;
}
/* start ADC again */
ADCSRA |= (1<<ADSC);
}
static uint8_t lcd_update;
static uint8_t mode = MOD_WAITING;
static uint8_t pwm;
ISR(TIMER0_OVF_vect)
{
static uint8_t cnt;
/* Come back in 1ms */
TCNT0 = 0xFF - 125;
/* update LCD every 250ms */
cnt++;
if (cnt == 250) {
cnt = 0;
lcd_update = 1;
}
/*
* charge with constant voltage of 12.45V
* and a current limit of 4.9A
*/
if (mode == MOD_CHARGING) {
if (voltage < (VOLTAGE_CHARGE -25) && current < (CURRENT_CHARGE -500))
if (pwm < 0xff)
pwm++;
if (voltage > VOLTAGE_CHARGE || current > CURRENT_CHARGE)
if (pwm > 0x00)
pwm--;
OCR1A = pwm;
TCCR1A |= (1<<COM1A1);
} else {
pwm = 0;
TCCR1A &= ~(1<<COM1A1);
}
switch (mode) {
case MOD_WAITING:
/* start charging when a voltage > 9V is detected (lipo connected) */
if (voltage > VOLTAGE_CONNECT)
mode = MOD_CHARGING;
break;
case MOD_CHARGING:
/* end charging if voltage > 12.45V and current < 200mA */
if (voltage >= VOLTAGE_CHARGE && current < CURRENT_READY)
mode = MOD_READY;
break;
case MOD_READY:
/* wait for lipo disconnect */
if (voltage < VOLTAGE_REMOVE)
mode = MOD_WAITING;
break;
}
}
int main(void)
{
DDRB = (1<<CS) | (1<<RS) | (1<<PWM);
DDRD = (LCD_DATA_MASK) | (1<<RW);
/* Set baud rate 19200 */
UBRRH = (UART_CALC_BAUDRATE(BAUDRATE)>>8) & 0xFF;
UBRRL = (UART_CALC_BAUDRATE(BAUDRATE) & 0xFF);
/* USART: rx/tx enable, 19200, 8n1 */
UCSRB = (1<<TXEN) | (1<<RXEN);
UCSRC = (1<<URSEL) | (1<<UCSZ1) | (1<<UCSZ0);
/* timer0: running with F_CPU/64 (125kHz) */
TCCR0 = (1<<CS01) | (1<<CS00);
/* timer1: running with F_CPU, 8bit Phase Correct PWM (16kHz) */
TCCR1A = (1<<WGM10);
TCCR1B = (1<<CS10) | (1<<WGM12);
/* enable Timer0 OVF Interrupt */
TIMSK = (1<<TOIE0);
/* external 2.56V reference, channel 0 */
ADMUX = CH0;
/* enable ADC with interrupts, 125kHz clk */
ADCSRA = (1<<ADEN) | (1<<ADSC) | (1<<ADIE) | (1<<ADPS2) | (1<<ADPS1);
sei();
lcd_init();
uint8_t step = 0;
uint8_t hours = 0, minutes = 0, seconds = 0;
while (1) {
if (lcd_update) {
/* first line, first char */
lcd_write_ctrl(0x80 | 0x00);
fprintf(&lcd, "%2d.%03dV ", voltage / 1000, voltage % 1000);
fprintf(&lcd, "%1d.%04dA ", current / 10000, current % 10000);
fprintf(&lcd, "0x%02x", pwm);
step = (step +1) & 0x03;
if (step == 0 && mode == MOD_CHARGING) {
seconds++;
if (seconds == 60) {
seconds = 0;
minutes++;
if (minutes == 60) {
minutes = 0;
hours++;
}
}
fprintf(&log, "%02d:%02d:%02d %05d %05d %03d\n",
hours, minutes, seconds, voltage, current, pwm);
}
/* second line, first char */
lcd_write_ctrl(0x80 | 0x40);
fprintf(&lcd, "%02d:%02d:%02d ", hours, minutes, seconds);
switch (mode) {
case MOD_WAITING:
fprintf(&lcd, " waiting ");
hours = minutes = seconds = 0;
break;
case MOD_CHARGING:
fprintf(&lcd, " charging");
break;
case MOD_READY:
fprintf(&lcd, " ready ");
break;
}
/* second line, last char */
lcd_write_ctrl(0x80 | 0x40 | 19);
lcd_write_data(step);
lcd_update = 0;
}
}
return 0;
}