⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 main.c

📁 avr chargeer source code
💻 C
📖 第 1 页 / 共 2 页
字号:
/*-----------------------------------------------------------------
  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 + -