Схема
Логика программы
В начале программы выполняются функции инициализации. Настраивается и запускается АЦП, конфигурируется порт, к которому подключен индикатор, и настраивается таймер Т0. Затем разрешаются прерывания, и микроконтроллер выполняет бесконечный цикл. В цикле опрашивается программный буфер АЦП и вычисляется значение напряжения. Вычисленное значение передается функции индикатора, которая преобразует его в двоично-десятичные цифры, затем в коды цифр индикатора и записывает их в массив (буфер).
Параллельно основной программе вызываются прерывания АЦП и таймера Т0. АЦП работает в режиме однократного преобразования, с внутренним опорным источником напряжения на 2,56В. Выравнивание вправо, используются все 10 разрядов. Результат преобразования АЦП накапливается 8 раз в переменной, усредняется и записывается в программный буфер.
В прерывании таймера Т0 происходит его перезапуск и вызывается функция обновления индикатора. Она гасит текущий отображаемый разряд и зажигает следующий. Структура проекта
Проект состоит из 3-ех программных модулей.main.c – основная программа
adc.c – функции для работы с АЦП
indicator.c – драйвер семисегментного 4-ех разрядного индикатора.
Драйвер семисегментного индикатора
В заголовочном файле indicator.h с помощью директивы #define определены порты и номера выводов микроконтроллера, к которому подключен индикатор.
//куда подключены сегменты
#define PORT_IND PORTB
#define DDR_IND DDRB
#define SEG_A 0
#define SEG_B 1
#define SEG_C 2
#define SEG_D 3
#define SEG_E 4
#define SEG_F 5
#define SEG_G 6
#define SEG_DP 7
//куда подключены упр. выводы
#define PORT_TR PORTD
#define DDR_TR DDRD
#define NUM1 0
#define NUM2 1
#define NUM3 2
#define NUM4 3
Для работы с индикатором используется три функции – функция инициализации, функция преобразования и функция обновления индикатора.
void IND_Init(void);
void IND_Output(unsigned int value, unsigned char comma);
void IND_Update(void);
Прототипы функций находятся в заголовочном файле, а их реализации в сишном.
Функция инициализации
//количество разрядов индикатора#define AMOUNT_NUM 4
//буфер семисегментного индикатора
unsigned char buf[AMOUNT_NUM];
void IND_Init(void)
{
PORT_IND = 0xff;
DDR_IND = 0xff;
PORT_TR |= (1<<NUM1)|(1<<NUM2)|(1<<NUM3)|(1<<NUM4);
DDR_TR |= (1<<NUM1)|(1<<NUM2)|(1<<NUM3)|(1<<NUM4);
for(unsigned char i = 0; i < AMOUNT_NUM; i++) buf[i] = 0;
}
Эта функция конфигурирует порты ввода-вывода, к которым подключен индикатор, в режим выхода и очищает программный буфер индикатора. Программный буфер – это массив в оперативной памяти микроконтроллера, назначение которого хранить коды цифр отображаемых на индикаторе. Своего рода примитивный видео-буфер. В основном цикле программы данные помещаются в буфер, а в прерывании таймера считываются из него
Функция преобразования
//массив для преобразования десятичных чисел в коды индикатораunsigned char number[] =
{
(1<<SEG_A)|(1<<SEG_B)|(1<<SEG_C)|(1<<SEG_D)|(1<<SEG_E)|(1<<SEG_F)|(0<<SEG_G), //0
(0<<SEG_A)|(1<<SEG_B)|(1<<SEG_C)|(0<<SEG_D)|(0<<SEG_E)|(0<<SEG_F)|(0<<SEG_G), //1
(1<<SEG_A)|(1<<SEG_B)|(0<<SEG_C)|(1<<SEG_D)|(1<<SEG_E)|(0<<SEG_F)|(1<<SEG_G), //2
(1<<SEG_A)|(1<<SEG_B)|(1<<SEG_C)|(1<<SEG_D)|(0<<SEG_E)|(0<<SEG_F)|(1<<SEG_G), //3
(0<<SEG_A)|(1<<SEG_B)|(1<<SEG_C)|(0<<SEG_D)|(0<<SEG_E)|(1<<SEG_F)|(1<<SEG_G), //4
(1<<SEG_A)|(0<<SEG_B)|(1<<SEG_C)|(1<<SEG_D)|(0<<SEG_E)|(1<<SEG_F)|(1<<SEG_G), //5
(1<<SEG_A)|(0<<SEG_B)|(1<<SEG_C)|(1<<SEG_D)|(1<<SEG_E)|(1<<SEG_F)|(1<<SEG_G), //6
(1<<SEG_A)|(1<<SEG_B)|(1<<SEG_C)|(0<<SEG_D)|(0<<SEG_E)|(0<<SEG_F)|(0<<SEG_G), //7
(1<<SEG_A)|(1<<SEG_B)|(1<<SEG_C)|(1<<SEG_D)|(1<<SEG_E)|(1<<SEG_F)|(1<<SEG_G), //8
(1<<SEG_A)|(1<<SEG_B)|(1<<SEG_C)|(1<<SEG_D)|(0<<SEG_E)|(1<<SEG_F)|(1<<SEG_G) //9
};
void IND_Output(unsigned int value, unsigned char comma)
{
unsigned char tmp;
for(unsigned char i = 0; i < AMOUNT_NUM; i++){
tmp = value % 10;
buf[i] = number[tmp];
value = value/10;
}
if (comma < AMOUNT_NUM) {
buf[comma] |= 1<<(SEG_DP);
}
}
Эта функция преобразует переданное ей 16-ти разрядное число (value) в коды цифр индикатора и записывает в программный буфер. Кроме этого она устанавливает в определенном разряде индикатора запятую. Номер разряда определяется переменной comma.
Преобразование выполняется следующим образом. Переданное функции число делится по модулю 10. Результатом выполнения этой операции будет остаток от деления числа на 10, что позволяет нам как бы отделить младший разряд десятичного числа от его остальной части. Используя результат деления по модулю в качестве индекса массива number, мы преобразуем это число в код цифры индикатора.
Для ясности небольшой пример.Возьмем число 123.
Первое выполнение цикла for - i = 0
tmp = value % 10 = 123 % 10 = 3
buf[0] = number[3]
value = value/10 = 123/10 = 12
Второе выполнение цикла for - i = 1
tmp = value % 10 = 12 % 10 = 2
buf[1] = number[2]
value = value/10 = 12/10 = 1
Третье выполнение цикла for - i = 2
tmp = value % 10 = 1 % 10 = 1
buf[2] = number[1]
value = value/10 = 1/10 = 0
Четвертое (последнее) выполнение цикла for - i = 3
tmp = value % 10 = 0 % 10 = 0
buf[3] = number[0]
value = value/10 = 0/10 = 0
Коды цифр индикатора в массиве number записаны с помощью макроопределений. Это позволяет подключать выводы сегментов индикатора к порту микроконтроллера в любом порядке.
Функция обновления индикатора
//макросы для настройки драйвера индикатора под другую схему#define LightOutAll() PORT_TR &= ~((1<<NUM1)|(1<<NUM2)|(1<<NUM3)|(1<<NUM4))
#define BurnDigit(port, digit) port |= (1<<digit)
#define ValueBuf() buf[count]
void IND_Update(void)
{
static unsigned char count = 0;
//гасим все разряды
PORT_IND = 0;
LightOutAll();
//зажигаем соответствующий разряд
if (count == 0) BurnDigit(PORT_TR, NUM1);
else if (count == 1) BurnDigit(PORT_TR, NUM2);
else if (count == 2) BurnDigit(PORT_TR, NUM3);
else BurnDigit(PORT_TR, NUM4);
//выводим код цифры в порт
PORT_IND = ValueBuf();
count++;
if (count == AMOUNT_NUM) count = 0;
}
Эта функция гасит текущий активный разряд семисегментного индикатора и зажигает следующий – “открывает” соответствующий транзистор и выводит в порт, к которому подключены сегменты индикатора, код цифры из массива (буфера).
Несколько выражений используемых в функции определены с помощью директивы #define - LightOutAll(), BurnDigit(port, digit), ValueBuf(). Это позволяет быстро и безболезненно настроить драйвер под другой тип индикатора или другую схему включения. Тут в принципе возможно 4 варианта:
индикатор с общим катодом подключенный напрямую к микроконтроллеру
#define LightOutAll() PORT_TR |= (1<<NUM1)|(1<<NUM2)|(1<<NUM3)|(1<<NUM4)
#define BurnDigit(port, digit) port &= ~(1<<digit)
#define ValueBuf() buf[count]
индикатор с общим катодом подключенный к микроконтроллеру с помощью транзисторов
#define LightOutAll() PORT_TR &= ~((1<<NUM1)|(1<<NUM2)|(1<<NUM3)|(1<<NUM4))
#define BurnDigit(port, digit) port |= (1<<digit)
#define ValueBuf() buf[count]
индикатор с общим анодом подключенный напрямую к микроконтроллеру
#define LightOutAll() PORT_TR &= ~((1<<NUM1)|(1<<NUM2)|(1<<NUM3)|(1<<NUM4))
#define BurnDigit(port, digit) port |= (1<<digit)
#define ValueBuf() ~buf[count]
индикатор с общим анодом подключенный к микроконтроллеру с помощью транзисторов
#define LightOutAll() PORT_TR |= (1<<NUM1)|(1<<NUM2)|(1<<NUM3)|(1<<NUM4)
#define BurnDigit(port, digit) port &= ~(1<<digit)
#define ValueBuf() ~buf[count]
Полный текст драйвера светодиодного семисегментного индикатора
Indicator.h
Indicator.c
Как вычисляется напряжение
Диапазон входных напряжений АЦП определяется источником опорного напряжения (ИОН). В нашем случае используется внутренний ИОН на 2,56 В. Для расширения диапазона измеряемых напряжений я поставил перед АЦП резистивный делитель. Он рассчитан таким образом, чтобы при напряжении в 30 В на входе делителя напряжение на входе АЦП не превышало 2,56 В.
Uadc = Uin*R14/(R14 + R15) = 30В*82 /(82 + 910) = 2,48 В
Чтобы вычислить напряжение на входе вольтметра нужно результат преобразования АЦП пересчитать в напряжение и домножить на коэффициент
(R14 + R15)/R14 = 992/82
Напряжение на входе АЦП - Uadc вычисляется по формуле:
Uadc = value * 2,56/(2^n – 1),
где n – разрядность АЦП, value – цифровое значение напряжения
Отсюда напряжение на входе вольтметра будет равно:
Uin = (value * 2,56 * 992)/(82 * 1023) = (value * 30,96)/1023
Чтобы не связываться с типом float и при этом иметь возможность вычислять напряжение с точностью до 2-ух знаков после запятой, числитель этой формулы я домножил на 100
Uin = (value * 3096)/1023
Тест вольтметра
Когда программа была завершена, мне стало интересно, насколько точные показания будет давать вольтметр по сравнению с мультиметром. Я подключил вольтметр к источнику питания и в диапазоне напряжений от 0 до 30 В снял его показания.
Результаты вы можете видеть в таблице. Первая колонка – это напряжение выставляемое на источнике питания, вторая - показания мультиметра Fluke, а третья – цифровой вольтметр на ATmega8.
Файлы
IAR.Вольтметр на микроконтроллереWinAvr.Вольтметр на микроконтроллере
CodeVision.Вольтметр на микроконтроллере
Проект для Proteus`a.Вольтметр на микроконтроллере
Comments
Супер!
ADMUX ^=1
Во это было бы вещь:)
Я сейчас делаю автоматическое зарядное устройство для авт. акумуляторов, там у меня будет и вольтметр и амперметр до 10А. Когда сделаю могу поделиться!
ждем с нетерпением
foxit, может поможешь с проектом. а то боюсь знаний не хватит:(
http://vrtp.ru/index.php?s=3371dc0157532aa94f7ba847e7ac1934&showtopic=10583&st=0entry175838
ИМХО, шедевр!
Нет времени полностью закончить,печат ная плата уже сделана - нужно только запаять детали.Прошивка сделана только на 80%. Если нет терпения- напишите свой мейл,и я скину то что я сделал (платы сделаны в Sprint-Layout)
1. Сумма 8-ми измерений = 6396.
2. 6396/8 = 799.5
3. 799.5 * 3096 / 1023 = 2419,60
Это наш "эталон" - 24.19 на индикаторе.
Давайте сделаем Ваш пример:
1. Сумма 8-ми измерений = 6396.
2. 6396/8 = 799 (int!)
3. 799 * 3096 / 1023 = 2418 (int!)
На индикаторе 24.18 вместо 24.19. Ошибка небольшая, но зачем она нужна? Делаем:
1. Сумма 8-ми измерений = 6396.
2. 6396 * 3096 / (1023*8) =
6396 * 3096 / 8184 = 2419 (int)
На индикаторе 24.19 как надо. Да еше и размер кода уменьшился, и время обработки прерывания.
Наш сигнал представляет собой сумму Uполезн. и Uшума. Полезное напряжение у нас константа, а значение шума подчинено закону нормального распределения. Вычисляя среднее значение нескольких выборок АЦП, мы уменьшаем шумовую составляющую. Этот подход называется синхронной фильтрацией или синхронным накоплением.
Quote: Это здесь вообще не критично
Правило одно - как можно большие числа хранить как можно дольше. С ними точнее.
Что еще нужно поменять кроме макросов драйвера под другой тип индикатора или другую схему включения?
Нужно ли изменять массив для преобразования десятичных чисел в коды индикатора?
Если не сложно поделитесь проектом для индикатора с общим анодом.
разобрался.
Перепутал подключение сегментов и анодов.
Но показывает значительную погрешность.
Скорее всего из-за разноса значений резисторов делителя напряжения.
Как программно(ввес ти коэффициент поправки) или аппаратно(добав ить переменный резистор в делитель) учитывать это?
Как произвести калибровку?
судя по названию индикатора он с общим анодом, а по схеме должен быть с общим катодом. Это ошибка в схеме, или я уже совсем все забыл?
Error: C:\cvavr2\7seg\ adc.c(7): undefined symbol 'REFS1'
Error: C:\cvavr2\7seg\ adc.c(9): undefined symbol 'ADCSR'
Error: C:\cvavr2\7seg\ adc.c(11): undefined symbol 'ADCSR'
Error: C:\cvavr2\7seg\ adc.c(38): undefined symbol 'ADCSR'
Error: C:\cvavr2\7seg\ main.c(30): undefined symbol 'TOIE0'
Error: C:\cvavr2\7seg\ main.c(31): undefined symbol 'CS02'
void IND_Update(void)
{
static uint8_t count = 0;
//гасим все индикаторы
LightOutAll();
//загружаем в порт символ
PORT_IND = ValueBuf();
//"поджигаем" нужное знакоместо
if (count == 0) BurnDigit(PORT_ TR, NUM1);
else if (count == 1) BurnDigit(PORT_ TR, NUM2);
else if (count == 2) BurnDigit(PORT_ TR, NUM3);
else BurnDigit(PORT_ TR, NUM4);
count++;
if (count == AMOUNT_NUM) count = 0;
}
К выводу AREF(32 нога) подавал 5В, к AVCC(30 нога) подавал 5В, замыкал их, в прошивке выставил внешний ИОН,
ADMUX = (0
Можете добавить в проект
Code:
ADCSRA |=(1<<ADIF) | (1<<ADSC);
Тогда и ресурсы МК будут использоваться рационально! И почему ошибка при компиляции под Mega32 ? Даташит посмотрю сам. Потом.
LightOutAll();
.';' expected, but ')' found
При возврате к прежнему коду пропадает и сообщение. НЕ подскажете, в чём дело?
Но после введения изменений под общий анод в Протеусе пропадает динамическая индикация. В пошаговом режиме индикация на разрядах меняется, а в режиме непрерывной работы индикатор тёмный. CPU загружен до 60%.
http://my-files.ru/50eg5l -измененные файлы.
RSS feed for comments to this post