Нарушая запреты. Опыт программирования. Часть 3

   Следующая разработка с которой я хотел бы познакомить читателя - разрядное устройство для аккумуляторных батарей (далее, аккумуляторов).
   Необходимость такого устройства не очевидна, но полезна, особенно если имеется несколько однотипных аккумуляторов.  В частности, подобная ситуация сложилась у автора данной статьи, когда он имел в наличии несколько 9 В аккумуляторов типа 7Д-0,115 и импортный металлогибридный.
   Зачем надо перед зарядкой предварительно разрядить аккумулятор? Чтобы емкость его не уменьшалась от зарядки к зарядке. Так называемый эффект памяти, когда не полностью разряженный аккумулятор после зарядки уже не отдает паспортной емкости. 100 гарантированных циклов заряда позволяют использовать режим доразряда - заряда.
   Сама доразрядка представляет собой в простейшем случае разрядный резистор, подключаемый к аккумулятору в качестве нагрузки и вольтметр, с помощью которого контролируют степень разряда аккумулятора. Для 9 В аккумуляторов разряд проводят до достижения на аккумуляторе 7 В. При этом ток разряда не должен превышать 10 мА для аккумуляторов первого типа и 40 мА для аккумуляторов второго типа. Отсюда легко определить величину сопротивления разрядного резистора. Для определения емкости  аккумулятора, надо измерить время разряда свеже заряженного аккумулятора. Ясно, что выполнить эту работу для нескольких аккумуляторов практически невозможно. Поэтому появилась идея на база такой же платы микроконтроллера ATMega16 собрать автомат, который бы и разряжал и измерял емкость аккумуляторов в автоматическом режиме.
   Нагрузочные резисторы были собраны из нескольких последовательно включенных резисторов, так чтобы напряжение на входах АЦП не превысило максимально допустимое ни при каких обстоятельствах. Был использован тумблер, подключающий к аккумулятору ту или иную нагрузку. Все остальное взяла на себя программа. В случае неверной установки тумблера для аккумулятора ничего страшного не происходит. Просто он разряжается не рекомендованным для разрядки током.

#include "defines.h"

#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
//#include <math.h>

#include <avr/io.h>

#include <util/delay.h>
#include <avr/interrupt.h>
#include <string.h>

#include "lcd.h"
#include "uart.h"

void adc_init(void);
static void delay_1s(void);
static volatile int32_t tt=9000;
static volatile int32_t tc=0;
static volatile int32_t cur=0;
static volatile int32_t midl_cur=100;
static volatile int FlStart=0;
static volatile unsigned int t=0;
ISR(INT0_vect)
{
FlStart=1;
}
ISR(TIMER1_COMPA_vect) // 1 мсек прерывания (16-ти разрядный TIMER1), см. AvrCalc.exe, перезапускать таймер не надо

if (tt>7000)
{  // Acc разряжен - не измеряем
    if (++t>1000)
  {
    ++tc;
      t=0;
    ADCSRA |= _BV(ADSC); // начало преобразования ADC Start Conversion = 1
  }
    } 
}
ISR(ADC_vect)
{
    int32_t t=(int32_t)ADCW;
    tt = t*16232L/1024L;  // в мВ 15200
    if (tt<3000) {
       tt=tt*3697L/1000L;// for 10 mA 3775
     cur=tt/86L;
    }else{
     cur=tt/21L;
    }
midl_cur=(midl_cur+(int16_t)cur)>>1;
}
/*
 * Do all the startup-time peripheral initializations.
 */
static void ioinit(void)
{
  uart_init();
  lcd_init();
  adc_init();
  TIMSK = _BV(OCIE1A); // разр. прер. T1 compare OCIE1A флаг разрешения прерывания по события "Совпадение А" таймера счетчика Т1
  TCNT0 = 0; //
  SFIOR |=  _BV(PSR10); // сброс прескаллера, в 0 вернется автоматически
  TCCR0 = 5; // установим прескалер тaймера T0  fclk/1024. прескалером Т1 управляют по даташиту с. 82, с.107, с.108 разряды CS10,CS11,CS12 (b0-b2) регистра TCCR1B
  PORTD |= _BV(PD2); // подключим внутренний резистор 20...50 кОм к PD2 (INT0) кнопка
  DDRD &= ~_BV(PD2); // PD2 (INT0) - сконфигурируем как вход (белая кнопка)
  PORTD &= ~_BV(PD6); // выведем 0 - конденсатор разряжен - реле не включено
  DDRD |= _BV(PD6); // PD6 - сконфигурируем как выход
  MCUCR = 0X0a; // прерывание по спаду на INT1,0
  GICR |= _BV(INT0);   // Разрешение прерывания INT0
  /*  подробно установки таймера Т1 см. файл readmy.txt  */
  // используем программу AvrCalc.exe для кварца 11,059 МГц и выкл прескалере получаем на таймере 1
  //получим то что записали в OCR1AH и OCR1AL
  OCR1AH = 0x2b; // для 1 mсек  при прескалере 1:1
  OCR1AL = 0x33;    // 11059 значение
  TCCR1A = 0; // 4-я мода и выводы счетчика отключены и все в норме
  TCCR1B = 0x09; // 1001 = 0x01 (х/1) + 0x08 - (мода 4 СТС) - сброс в 0 при достижении значения в OCR1A
  TIFR = 0xFF; // сбросим все флаги прерываний записью в этот регистр единиц с. 110 даташита и евстифеев
    GIFR = 0xFF; // сбросим флаги прерываний INT0, INT1, INT2 - даташит с. 66
  ACSR=0x80;          // компаратор выключить
}

FILE uart_str = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
FILE lcd_str = FDEV_SETUP_STREAM(lcd_putchar, NULL, _FDEV_SETUP_WRITE);

static void
delay_1s(void)
{
  uint8_t i;

  for (i = 0; i < 100; i++)
    _delay_ms(10);
}
/*
 * Инициализация АЦП
 * Вывод AREF (pin 29) через ЧИП конденсатор 0,1 мк соединен с землей AGND (pin 28).
 * 1. внутренний источник опорного напряжения
 * 2. работа в режиме одиночных преобразований (Free Running Mode) ADATE=0 (после сброса)
 * 3. для нулевого канала ADMUX = 0 (пока)
 * 4. входы АЦП ADC0/PA0 (pin 37), ADC1/PA1 (p.36), ADC2/PA2 (p.35), ADC3/PA3 (p.34), ADC4/PA4 (p.33), ADC5/PA5 (p.32), ADC6/PA6 (p.31), ADC7/PA7 (p.30) - используется только ADC0/PA0 (pin 37)
 * 5. результат ADCH(старшие 2 бита), ADCL(младшие 2 бита)
 * 6. управление делителем (скоростью преобразования) ADCSRA
 * 7. диапазон Твыб=65:260 мкс (Fд=4:15 кГц).
  Для управления АЦП в ATmega16 применяются всего 3 регистра:
  ADCSRA - регистр управления и состояния
  ADMUX - регистр управления мультиплексором
  SFIOR -  регистр специальных функций
 *
void adc_init(void) {
  DDRA = 0;    // у АЦП всe выводы порта РА - входы АЦП (8 шт) выведены на плате на контакты
                // под пайку
ADMUX = 0x45;  // 0x40

  ADCSRA &= ~_BV(ADPS0);//ADPS0=0
ADCSRA |= _BV(ADPS1); //ADPS1=1
ADCSRA |= _BV(ADPS2); //ADPS2=1, т.е. 6 (делитель на 64) - частота выборок > 10 кГц K[64]=Fclk[11059кГц]/(13*Fд[кГц]) см. Datasheets с.214 тактовая АЦП почти 200 кГц (максимальная)
ADCSRA |= _BV(ADIF);  // очистка флага окончания преобразования
ADCSRA |= _BV(ADIE);  //ADIE=0 - прерывания АЦП
ADCSRA |= _BV(ADEN);  // включить АЦП
// произвести первое чтение до начала работы
ADCSRA |= _BV(ADSC); // начало преобразования ADC Start Conversion = 1
    while (bit_is_clear(ADCSRA, ADIF)); // ожидания флага окончания преобразования
ADCSRA |= _BV(ADIF); // очистка флага окончания преобразования
//DDRA &= ~_BV(PA5); // PA5 - сконфигурируем как вход
}

int main(void)
{
  static int32_t m_tc;
  static int32_t ah;
  static int32_t cu=10;
  ioinit();

  stdout = stdin = &uart_str;
  stderr = &lcd_str;

  PORTD &= ~_BV(PD6); // выведем 0 - конденсатор разряжен - реле не включено
  fprintf_P(stderr,PSTR( "  T - meter     \n"));
  delay_1s();
  while (bit_is_set(PIND, PD2));
  _delay_ms(10);
  while (bit_is_clear(PIND, PD2));
  fprintf_P(stderr,PSTR( "Get Acc & puch \n"));
  delay_1s();
  sei();      // enable all interrupts
  PORTD |= _BV(PD6); // выведем 1 - реле включено
  _delay_ms(100);
  FlStart=0;
  tt = 9000;
  while(1)
  {
  if (tt<7000) { //3160
cli();      // disable all interrupts
PORTD &= ~_BV(PD6); // выведем 0 - Acc разряжен - реле не включено
m_tc = tc; // sec
m_tc = (m_tc*100L)/3600UL; // hour*100
ah = m_tc*(cu+5L)/10L; // Ahour*100
lcd_gotop();
fprintf(stderr,"Acc ,  mAh       ", ah/100,ah); // for R = 10 kOm
while (bit_is_clear(PIND, PD2)){FlStart=1;};
}else{
lcd_gotop();
if (abs((tc)) < 3L) {
fprintf(stderr,"Uacc = ,  B     ",(int16_t)(tt/1000L), abs((int16_t)(((tt)/10))));
}else if (abs((tc)) < 6L)
      {
cu=midl_cur;
fprintf(stderr,"Iacc = ,  mA       ", (int16_t)(cu/10L), abs((int16_t)(cu)));
  }else{
fprintf(stderr," :  :        ",tc/3600UL, (tc)/60, tc);
  }
}
    if (FlStart==1) {
    tc=0;
tt=9000;
sei();      // enable all interrupts
PORTD |= _BV(PD6); // выведем 1 - конденсатор на заряд - реле включено
_delay_ms(100);
    FlStart=0;
fprintf_P(stderr,PSTR( "\nGet accumulator \n"));
delay_1s();
delay_1s();
}
  }
  return 0;
}

Здесь видны комментарии от прошлой программы, но что поделаешь, спешка.
Файл defines.h
#include <avr/pgmspace.h>

/* CPU frequency */
#define F_CPU 11059000UL
//#define F_CPU 2560000UL // error +1,1 %

/* UART baud rate */
#define UART_BAUD  9600  /* 9600 */

#define UART_PORTOUT PORTD
#define UART_PORTIN  PIND

/* ADC */
#define ADC_PORTIN  PINA
#define ADC0        PORT0  /* pin сonnection of capacitor Cx for C-meter */

/* HD44780 LCD port connections */
#define HD44780_PORT C
#define HD44780_RS PORT0
#define HD44780_RW PORT1
#define HD44780_E  PORT2
#define HD44780_D4 PORT4
#define HD44780_D5 PORT5
#define HD44780_D6 PORT6
#define HD44780_D7 PORT7

   Для запуска процесса применяется, как и в прошлый раз, кнопка, которая включает реле, подключающее аккумулятор к нагрузке.
В процессе разряда выводятся поочередно напряжение на аккумуляторе и ток разрядки.  Как только напряжение на аккумуляторе станет менее 7 В, реле отпускает и на экран ЖКИ выводится рассчитанная емкость аккумулятора в ма/ч.


Рецензии