📄 main.c
字号:
/*-----------------------------------------------------------------
NiMH_Charger Copyright (c) 2007 by Jack Botner
main.c
This program is a stand-alone NiMH charger for 2 AA cells.
In also it monitors the status of the charge and displays
information on a 16x2 LCD display.
- The voltage across the batteries under charge is monitored
on ADC0 and ADC1. This makes it possible to display cell
voltage and charge current.
- The temperature is monitored on ADC2 so we can stop the
fast charge if things get too hot.
In addition we have a piezo beeper on PB2 and a pushbutton
("zero counters") on PB1. There is a yellow status LED on PD7.
The serial port is implemented so that on request, information
is sent to the host computer every minute. The host commands are
'r' or 'R' - are you there? Response is 'Y'
'b' or 'B' - begin sending data
'e' or 'E' - stop sending data
The serial protocol is 9600, stop=1, bits=8, parity=N
Processor: ATmega168
Clock: Internal, 4.0 MHz
-----------------------------------------------------------------*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <string.h>
#include <stdlib.h>
#include "common.h"
#include "lcd.h"
// Timer/Counter1 has to count 20000 counts. At 0.25 us per count,
// that gives 5000 us or 5.0 ms. Since it counts up, it must
// be primed with 65536 - 20000 = 45536.
#define TIMER1_HIGH 0xb1 // x'b1e0' = 45536
#define TIMER1_LOW 0xe0
// The resistor between ADC0 and ADC1, in tenths of an ohm.
#define RESISTOR_TENTHS_OHMS 79 // Rsense
// The maximum cell voltage threshold
#define MAX_CELL_MV 1700
// The maximum cell temperature during fast charge
#define MAX_TEMPERATURE 450 // temperature, tenths degrees C
// The maximum time (in 10 second units) to stay on fast charge
#define MAX_10SECS 1800 // 5 hours
#define RED_LED_PORT PORTD
#define RED_LED_PIN PD2
#define YELLOW_LED_PORT PORTD
#define YELLOW_LED_PIN PD3
#define RELAY_PORT PORTB
#define RELAY_PIN PB0
// Global variables
static uint16_t uiVoltHigh_ADC=0; // last ADC0 sample
static uint16_t uiVoltLow_ADC=0; // last ADC1 sample
static uint16_t uiTemp_ADC=0; // last ADC2 sample
static uint16_t uiVoltHigh_MV=0; // voltage at high side of Rsense
static uint16_t uiVoltLow_MV=0; // voltage at low side of Rsense
static uint16_t uiTemp_MV=0; // last ADC2 sample
static uint16_t uiCurrent_mA=0; // charge current
static uint16_t uiHighestCellVoltage=0; // highest voltage during fast charge
static int16_t iTempCtenths=0; // temperature, tenths of degrees C
static uint32_t ulChgCtr=0L; // charge accumulator
static uint16_t uiReadings=0; // number of current readings
static uint16_t uiBeepMs=0; // for scheduling a beep
static uint16_t uiTenSecCtr=0; // incremented during fast charge
uint8_t ucFlags=0; // program flags
uint16_t uiBtnState=0; // used for button debouncing
#define TXSIZE 40 // Tx buffer size
static uint8_t ucTxBytes=0; // bytes in the Tx buffer
static uint8_t ucTxIndex=0; // index of next byte to send
static int8_t cTxBuffer[TXSIZE];
// Internal Functions
static void build_host_msg( void );
static void displayCharge( void );
static void displayReadings( void );
static void processADC( void );
static void state_analysis( void );
static void system_init(void);
/*-----------------------------------------------------------------
main()
The mainline initiates the A/D conversion, processes the result
and formats the output for display on the LEDs. The actual
output to the LED display is handled by the timer0 interrupt
handler.
-----------------------------------------------------------------*/
int main(void)
{
const char *message1 = "NiMH Charger 0.1";
const char *message2 = "by Jack Botner";
static uint8_t ucOnceHere=1;
uint16_t uiLoopCtr=0; // loop counter
uint8_t ucMinuteCtr=0; // count 10 secs to one minute
system_init(); // initialise cpu
lcd_init( LCD_DISP_ON );
lcd_home();
lcd_puts( message1 );
lcd_gotoxy( 0, 1 ); // line 2
lcd_puts( message2 );
set_bit( YELLOW_LED_PORT, YELLOW_LED_PIN ); // yellow led on
set_bit( RED_LED_PORT, RED_LED_PIN ); // red led on
beep( 200 ); // sound the beeper
delay_ms( 800 ); // waste time
clear_bit( YELLOW_LED_PORT, YELLOW_LED_PIN ); // yellow led off
clear_bit( RED_LED_PORT, RED_LED_PIN ); // red led off
while( 1 )
{
if ( test_bit( ucFlags, TEN_SECONDS ) )
{
// Every 10 seconds, check for change in system state.
state_analysis(); // analyze charger state
// If in fast charge, update the charge counter.
if ( test_bit( ucFlags, FASTCHARGE_ON ) )
{
// Only update the charge/time while on fast charge
ulChgCtr += (unsigned long) uiCurrent_mA;
++uiReadings; // increment number of readings
++uiTenSecCtr; // increment time on fast charge
}
if ( test_bit( ucFlags, ENABLE_STATS ) )
{
++ucMinuteCtr;
if ( ucMinuteCtr == 6 )
{
build_host_msg();
UDR0 = cTxBuffer[0];
ucTxIndex = 1;
--ucTxBytes;
set_bit( UCSR0B, UDRIE0 ); // enable Tx interrupts
ucMinuteCtr = 0;
}
}
clear_bit( ucFlags, TEN_SECONDS );
}
if ( test_bit( ucFlags, ZERO_BUTTON_PRESS ) )
{
// Respond to the clear button by zeroing out the
// charge counter and sample counter. Then update
// the display and reset the button press logic.
uiTenSecCtr = 0;
uiReadings = 0;
ulChgCtr = 0L;
displayCharge(); // update the display now
ucFlags = 0;
uiBtnState = 0;
beep( 50 ); // short beep
}
++uiLoopCtr;
if ( uiLoopCtr > 40000 )
{
// Periodically refresh the LCD display
displayReadings();
displayCharge();
if ( ucOnceHere )
{
// This needs to be done early but has to wait
// for the display routines to run once.
state_analysis(); // analyze charger state
ucOnceHere = 0;
}
uiLoopCtr = 0;
}
if ( uiBeepMs ) // signal required?
{
beep( uiBeepMs );
uiBeepMs = 0;
}
}
}
/*-----------------------------------------------------------------
Timer 1 overflow interrupt handler
Timer 1 is set up to create an interrupt every 5 ms. This
interval can be used for polling the input ports and debouncing
the push button.
2000 interrupts are counted to give a 10 second interval. This
is used to accumulate the charge current.
The 5 ms interval is achieved by using a prescaler of 1, giving
a counter cycle = clock cycle, which is 0.25 us, then counting
20000 cycles.
-----------------------------------------------------------------*/
ISR(TIMER1_OVF_vect)
{
static uint16_t uiTenSecCtr=0;
sampleButton(); // sample the zero button input port every 5 ms
processADC(); // process the A/D sampling every 5 ms
++uiTenSecCtr;
if ( uiTenSecCtr == 2000 ) // 10 seconds have elapsed
{
set_bit( ucFlags, TEN_SECONDS ); // signal the mainline
uiTenSecCtr = 0; // reset loop counter
}
TCNT1H = TIMER1_HIGH; // reinitialize the timer counter
TCNT1L = TIMER1_LOW;
}
/*-----------------------------------------------------------------
USART Rx data interrupt
Only a few one-byte messages are received, so no buffering is
required. Also errors are ignored.
-----------------------------------------------------------------*/
ISR(USART_RX_vect)
{
uint8_t ucByte;
ucByte = UDR0;
if ( ucByte == 'r' || ucByte == 'R' )
{
// 'R' = are you there, respond with 'Y'
UDR0 = 'Y';
return;
}
if ( ucByte == 'b' || ucByte == 'B' )
{
// 'B' = Begin sending statistical data
set_bit( ucFlags, ENABLE_STATS );
uiBeepMs = 100;
return;
}
if ( ucByte == 'e' || ucByte == 'E' )
{
// 'E' = End sending statistical data
clear_bit( ucFlags, ENABLE_STATS );
uiBeepMs = 100;
}
}
/*-----------------------------------------------------------------
USART Tx data interrupt
-----------------------------------------------------------------*/
ISR(USART_UDRE_vect)
{
UDR0 = cTxBuffer[ucTxIndex++];
--ucTxBytes;
if ( !ucTxBytes )
clear_bit( UCSR0B, UDRIE0 ); // no more data to send
}
/*-----------------------------------------------------------------
system_init()
-----------------------------------------------------------------*/
static void system_init()
{
// The fuses are set to provide an internal clock, running at
// 8.0 MHz with a prescaler of 8. Change the prescaler from
// 8 to 2. The procedure requires two steps as per the device
// specifications.
CLKPR = 0b10000000; // set clock prescaler change enable
CLKPR = 0b00000001; // set prescaler to 2
DIDR0 = 0b00000111; // disable digital buffer: ADC0 ADC1 ADC2
// PORTB:
// PB0 (output) Charge Select Relay
// PB1 (input) "Zero Counters" pushbutton
// PB2 (output) Beeper
// PB3 (n/a) ISP MOSI
// PB4 (n/a) ISP MISO
// PB5 (n/a) ISP SCK
// PB6 (n/a) not used
// PB7 (n/a) not used
DDRB = 0b00000101; // PortB0, 2 = output
PORTB = 0b00000010; // initialise PortB (pull-up on PB1)
// PORTC:
// PC0 (input) ADC0 voltage sense (high)
// PC1 (input) ADC1 voltage sense (low)
// PC2 (input) ADC2 temperature sense
// PC3 (output) LCD RS
// PC4 (output) LCD RW
// PC5 (output) LCD E
// PC6 (n/a) ISP !Reset
DDRC = 0b00111000; // PortC3..5 = output
PORTC = 0b00000000; // initialise PortC
// PORTD:
// PD0 (input) USART RxD
// PD1 (output) USART TxD
// PD2 (output) Red LED
// PD3 (output) Yellow LED
// PD4 (output) LCD DB4
// PD5 (output) LCD DB5
// PD6 (output) LCD DB6
// PD7 (output) LCD DB7
DDRD = 0b11111110; // PD0=input, all others=output
PORTD = 0b00000000; // initialise PortD
// Initialize ADC0
ADMUX = 0b01000000; // ref=AVcc; input=ADC0; right-adjust
ADCSRA = 0b10000101; // enable ADC; disable interrupt; prescaler=32
ADCSRB = 0b00000000; // no auto trigger
// Initialize Timer1. Timer1 is used to count 10 second intervals
// for accumulating the charge. It uses a 5ms interrupt interval
// so the push button can be sampled too.
TCNT1H = TIMER1_HIGH;
TCNT1L = TIMER1_LOW;
TCCR1A = 0b00000000; // normal counter modes
TCCR1B = 0b00000001; // prescaler = 1 (turns timer1 on)
TCCR1C = 0b00000000; // normal counter mode
set_bit( TIMSK1, TOIE1 ); // enable timer/counter1 overflow interrupt
// Initialize the USART to 9600 baud 8 bit no parity etc
UBRR0L = 25; // 9600 baud
UBRR0H = 0;
UCSR0A = 0b00000000;
UCSR0C = 0b00000110; // Async mode; no parity; 1 stop bit; 8 data bits
UCSR0B = 0b10011000; // Rx Compl. Int; Rx Enable; Tx Enable
sei(); // enable global interrupts
}
/*---------------------------------------------------------------------
processADC()
This function is called by the timer/counter1 interrupt handler
every 5 ms. Its purpose is to process the result of the last A/D
conversion, and to initiate the next conversion.
The clock is running at 4 mHz and the A/D prescaler is 32, giving a
A/D clock of 125 kHz or 8 us. Most conversions take 13 ADC clock
cycles, or 104 us. (At startup the conversion takes 25 cycles or
200 us.) So if we initiate a conversion every 5 ms, it will be long
completed by the next 5 ms tick.
There are 3 ADC channels to sample, and we want to sample each
channel 8 times successively to get an average. ADC0 and ADC1 are
sampling a voltage with some power supply ripple so its important
to get an average. This requires a total of 24 conversions, or 120
ms. This means that we can do just over 8 complete cycles of A/D
conversions per second, which is more than enough.
---------------------------------------------------------------------*/
static void processADC()
{
static uint8_t ucCounter=0, ucDiscard=1;
static uint16_t uiAccum;
uint16_t uiAdc;
if ( !ucCounter )
{
// The 1st time in there was no conversion initiated, so
// just initiate one.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -