Счетчик импульса тока на atmega8

Обсуждаем контроллеры компании Atmel.
Ответить
Открыл глаза
Сообщения: 42
Зарегистрирован: Сб ноя 29, 2014 21:20:52

Сообщение panya »

interrupt [TIM0_OVF] void timer0_ovf(void){
count++;
U=ADCW;

}

main (volid){

unsigned short int U; U=0;
unsigned short int A1; A1=0;
unsigned short int A2; A2=0;

unsigned int St1t2; St1t2=0; //переменная хранит промежуточные вычисления площади прямоугольной трапеции

char U_ACP[32]; //переменная в которой будет формироваться строка для вывода на lcd
char TIME[32]; //переменная текущего времени для вывода на lcd
char RES_IMP[32]; //переменная текущего импульса для вывода на lcd

ADMUX=0xC0; // Номер порта и опорное напряжение

TCCR0=0хХХ //запускаем таймер
TIMSK=0хХХ //разрешаем прерывание по переполнению
ADCSR=0xE5; // Инициализация АЦП и Делитель 32 (125кГц) и непрерывное преобразование и запуск первого преобразования

lcd_init(16); // LCD module initialization на 16

while(1){
if(count==0){}else{
if(U>0){ //В большенстве случаев чистая резистивная нагрузка 60А
if(count%2==0){A2=U;}else{A1=U;}}
else{
if(count%2==0){A2=U;}else{A1=U;}}

if(A1==0 && A2==0){S_res=S_res;}else{St1t2=((A1+A2)/2)*((A1+A2)/2);}

lcd_gotoxy(0,0);
sprintf(U_ACP,"I: %dA",(int)(U*kt)); //kt коэфициент переводящий все это безобразие в ток первичной цепи
lcd_puts(U_ACP);

lcd_gotoxy(10,0);
sprintf(TIME,"t: %ds",(t1/100));
lcd_puts(TIME);

lcd_gotoxy(0,1);
sprintf(RES_IMP,"B: %dkA2*s",S_res);
lcd_puts(RES_IMP);


КАК заставить вот здесь остановиться и ждать следующего прерывания??
может залесть в безконечный цикл и ждать пока TIM0 заполнится до 0xFF. А там прерывание и само настанет??

}
}
}
Реклама
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Сообщение COKPOWEHEU »

Что это за нечитаемый ужас с кривым форматированием? Предыдущая версия была лучше. Разве что ADMUX добавлен. Какой смысл в использовании прерываний если хотите его дожидаться в основной програаме? Тогда бы отказались от прерывания и проверяли флаг вручную! В общем случае это хана точности. В данном конкретном - хз, поскольку таймер так и не настроен. Но sprintf едва ли справится за те 540 тактов, да еще вызванный трижды, да еще с прочимы вычислениями.
>> f(count%2==0)
Проверка четности делается по-другому. Это не считая того, что здесь она вообще не нужна.
>> sprintf(U_ACP,"I: %dA",(int)(U*kt)); //kt коэфициент переводящий все это безобразие в ток первичной цепи
А точно нужна такая громоздкая библиотека как stdio? Может можно обойтись решениями полегче? Тем более что в коде я не увидел ничего слоднее вывода числа в десятичном формате.
>> unsigned int St1t2; St1t2=0;
>> St1t2=((A1+A2)/2)*((A1+A2)/2);
Выходное значение 16 бит, а значение АЦП - 10. После умножения, в общем случае, будет 20 бит, то есть в St1t2 не влезет даже одно измерение. Возьмите 32-х или 64-х разрядное число.
.
Давайте все-таки попробуем привести программу к рабочему состоянию последовательно, а не прыгая туда-сюда? Для этого надо взять позапрошлый кусок, в котором в прерывании к числу добавлялось значение АЦП в квадрате, без определения начала-конца импульса, без вывода на дисплей. Потом привести его к читаемому виду (включая именование битов в регистрах настроек АЦП и таймера). Потом рассчитать значение таймера. Потом... впрочем, сначала хотя бы это.
Реклама
Открыл глаза
Сообщения: 42
Зарегистрирован: Сб ноя 29, 2014 21:20:52

Сообщение panya »

// Обработка прерывания по переполнению
interrupt [TIM0_OVF] void timer0_ovf(void){

U=ADCW;

}

void main (void){
ADMUX=0xC0; // Номер порта и опорное напряжение

TCCR0 = (1<< CS00 | 0<< CS01 | 0<<CS02) //запускаем таймер c коеф. 256 можно и 1024 идеально 20 замеров за полупериод (10мс) (F atmega 4Mz)

TIMSK= (1<<TOIE0) //разрешаем прерывание по переполнению

ADCSR = (1<<ADEN | 1<<ADSC | 1<<ADFR | 0<<ADIF | 0 << ADIE | 0b101<<ADPS0);

}
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Сообщение COKPOWEHEU »

TCCR0 = (1<< CS00 | 0<< CS01 | 0<<CS02) //запускаем таймер c ltkbntktv 256 (F atmega 4Mz)
Предделитель здесь 1, но прерывание возникает с частотой тактирования, деленной на 256, то есть 15625 Гц.
ADCSR = (1<<ADEN | 1<<ADSC | 1<<ADFR | 0<<ADIF | 0 << ADIE | 0b101<<ADPS0);
Согласно даташиту, тактовая частота модуля АЦП равно тактовой контроллера, деленной на 32 (это стоило написать в комментарии), то есть 125 кГц. На одно измерение требуется 13 тактов, то есть снимать данные можно с частотой 9615 Гц. Для надежности лучше еще реже.
Таймер надо настроить на частоту прерывания меньше 9,5 кГц, иначе будет снимать одно и то же значение несколько раз, а это бесполезно. Я бы предложил использовать TIMER2 COMP в режиме СТС. Предделитель 1:8 (CS=0b010), значение OCR2 получается не меньше 25, лучше 26-28 (там формула не такая простая).
main (volid){
Для работы с прерываниями надо их разрешить. Команда sei или как-то так.
interrupt [TIM0_OVF] void timer0_ovf(void){
U=ADCW;
}
Учитывая, что результат преобразования занимает 10 бит, а нужно возводить в квадрат, переменная U должна быть не меньше 20 бит. Подходит 32-битное unsigned long. Впрочем, насчет его размера в cvavr могу ошибиться, лучше бы проверить.
Для расчета площади призмы нам нужно взять сраднее арифметическое текущего значения и предыдущего и умножить на высоту (в данном случае - время между измерениями). Я бы делал это примерно так

Код: Выделить всё

volatile unsigned long long AdcRes=0; //обратите внимание на модификатор volatile. Эта переменная используется в прерывании.
//если не ошибаюсь, она занимает 64 бита. Можно взять и 32-битную, но особо скорости это не добавит
interrupt [TIM2_COMP] void timer2_comp(void){ //тут могу ошибиться, с синтаксисом cvavr знаком мало
 static unsigned long LastVal=0; //обратите внимание на модификатор static
 unsigned long U=ADCW;
 LastVal += U; //все операции будут проводиться с LastVal, поскольку в конце надо туда сохранить только что измеренное значение
 LastVal >>= 1; //деление на 2, только занимает не десятки тактов, а всего 4.
 LastVal = LastVal * LasVal; //специально использовали прерывание по таймеру, чтобы все времена были одинаковыми. А раз так, незачем нагружать контроллер лишней работой, и на это время можно домножить в конце.
 AdcRes += LastVal; //добавляем а конечному результату вычисленный квадрат напряжения
 LastVal = U; //и сохраняем текущее значение АЦП
}
Это я писал, чтобы было достаточно понятно. Можно записать короче, а можно добавить немного ассемблерной магии или еще как-то оптимизировать.
После окончания преобразования можно домножить AdcRes на некую константу, пропорциональную времени между прерываниями, сопротивлению, на котором все меряется, опорному напряжению, чему-нибудь еще, не важно, все равно подбирать придется :-) Потом преобразовать в строку (вариантов масса, atoi, деления, вычитания степеней десяти, у каждого свои преимущества) и вывести на дисплей. Пока подробно расписывать не буду, жду реакцию на свой кусок кода.
Теперь вопросы: достаточно ли частоты съема напряжения почти 9 кГц или надо поднимать частоту преобразования? Надо ли добавлять критерий начала импульса, или только конца?
Реклама
Эиком - электронные компоненты и радиодетали
Открыл глаза
Сообщения: 42
Зарегистрирован: Сб ноя 29, 2014 21:20:52

Сообщение panya »

COKPOWEHEU писал(а):ое текущего значения и предыдущего и умножить на высоту (в данном случае - время между измерениями). Я бы делал это примерно так

Код: Выделить всё

volatile unsigned long long AdcRes=0; //обратите внимание на модификатор volatile. Эта переменная используется в прерывании.
//если не ошибаюсь, она занимает 64 бита. Можно взять и 32-битную, но особо скорости это не добавит
interrupt [TIM2_COMP] void timer2_comp(void){ //тут могу ошибиться, с синтаксисом cvavr знаком мало
 static unsigned long LastVal=0; //обратите внимание на модификатор static
 unsigned long U=ADCW;
 LastVal += U; //все операции будут проводиться с LastVal, поскольку в конце надо туда сохранить только что измеренное значение
 LastVal >>= 1; //деление на 2, только занимает не десятки тактов, а всего 4.
 LastVal = LastVal * LasVal; //специально использовали прерывание по таймеру, чтобы все времена были одинаковыми. А раз так, незачем нагружать контроллер лишней работой, и на это время можно домножить в конце.
 AdcRes += LastVal; //добавляем а конечному результату вычисленный квадрат напряжения
 LastVal = U; //и сохраняем текущее значение АЦП
}
Это я писал, чтобы было достаточно понятно. Можно записать короче, а можно добавить немного ассемблерной магии или еще как-то оптимизировать.
После окончания преобразования можно домножить AdcRes на некую константу, пропорциональную времени между прерываниями, сопротивлению, на котором все меряется, опорному напряжению, чему-нибудь еще, не важно, все равно подбирать придется :-) Потом преобразовать в строку (вариантов масса, atoi, деления, вычитания степеней десяти, у каждого свои преимущества) и вывести на дисплей. Пока подробно расписывать не буду, жду реакцию на свой кусок кода.
Теперь вопросы: достаточно ли частоты съема напряжения почти 9 кГц или надо поднимать частоту преобразования? Надо ли добавлять критерий начала импульса, или только конца?
Немного непривычно, не думал таким образом. Очень интересно. ))

Зачем делать это (LastVal = U; //и сохраняем текущее значение АЦП) При новом прерывании LastVal=0, или предполагается ее использовать дальше и заодно AdcRes умножить на время, в перерыве между прерываниями (как-то"перерыв между прерываниями не звучить")
Реклама
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Сообщение COKPOWEHEU »

Специально же сказал обратить внимание на модификатор static. Он означает, что переменная выделяется один раз в начале программы, а не при каждом заходе в прерывание. Получается почти как глобальная переменная, но без доступа извне. В ней хранится результат предыдущего преобразования.
AdcRes можно умножить на время в самом конце. Время-то постоянное, один период частоты ~9 кГц (точное значение зависит от таймера). Ну и само по себе время не слишком нужно, там же наверняка есть и другие коэффициенты, на которые надо умножить.
...
Проверил свой код, пару мест надо исправить. Проерял в avr-gcc + proteus + железо (но на моей макетке все не проверить, тем более без аппаратного отладчика).
1. Частота таймера в режиме СТС задается не как F_CPU/(2*N*(OCRx+1)), а как F_CPU/(N*(OCRx+1)), в 2 раза выше, несмотря на даташит. То есть в OCR2 надо писать не 26, а 55. Тогда между прерываниями проходит 447 тактов или 111.75 мкс.
2. Ни Студия, ни протеус не знают, что такое режим непрерывного преобразования. Для эмуляторов надо его каждый раз запускать вручную, в железе не надо.
3а. Работа с 64-разрядными числами в avr-gcc реализована медленно. На сложение с 32-разрядным уходит аж 408 тактов, или 102 мкс.
3б. Поэтому была написана своя реализация, на ассемблере, которая работает за 79 тактов, или 19.75 мкс.
3в. Работа с 32-разрядными числами проблем вроде не вызывает, можно пользоваться ими. Но точных чисел не знаю.
4. При использовании встроенной 64-разрядной арифметики прерывание не успевает обработаться. При самописной - успевает и занимает 318 тактов или 79.5 мкс, считая вход-выход.
5. Если есть другие прерывания, требующие быстрой реакции - придется делать по-другому: в прерывании только считывать значение АЦП и взводить флаг, а расчеты проводить в основной программе.
С cvavr дела не имел, поэтому не знаю, какие времена будут там. Также не знаю, как там в случае чего подключать ассемблерный модуль. Свою версию могу выложить, если надо.
Реклама
Открыл глаза
Сообщения: 42
Зарегистрирован: Сб ноя 29, 2014 21:20:52

Сообщение panya »

Код: Выделить всё

unsigned short int U; U=0; // пришло с АЦП 
        unsigned long S_int; S_int=0; // S_intermediate
            unsigned long long S_res; S_res=0; // S_resultat
            
  char U_ACP[32]; //переменная в которой будет формироваться строка для вывода на lcd
        char TIME[32]; //переменная текущего времени для вывода на lcd
            char RES_IMP[32]; //переменная текущего импульса для вывода на lcd

ADMUX=0xC0; // Номер порта и опорное напряжение
TCCR0 = (1<< CS00 | 0<< CS01 | 1<<CS02); // 1024 (39 раз за полупериод пром. частоты вполне устраивает)
TIMSK= (1<<TOIE0);

ADCSR = (1<<ADEN | 1<<ADSC | 1<<ADFR | 0<<ADIF | 0 << ADIE | 0b101<<ADPS0); 

// обработка прерывания по переполнению
interrupt [TIM0_OVF] void timer0_ovf(void){
    U=ADCW;    
    }

void main(void){
    sei();
        S_int=U*U;
        S_res+=S_int;

while(1){t++;}

}
Полагаю что за время пока заполняется tim0 S_int и S_res выполнят возложенные на них функции, на экран выведется информация и все будет хорошо.
COKPOWEHEU писал(а):Также не знаю, как там в случае чего подключать ассемблерный модуль. Свою версию могу выложить, если надо.
Да, было бы неплохо. Спасибо.
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Сообщение COKPOWEHEU »

void main(void){
sei();
S_int=U*U;
S_res+=S_int;

while(1){t++;}

}
Форматирование опять сползло. Инициализация переехала куда-то наверх и теперь бомжует вообще между функций. Вы можете выкладывать логически завершенные, читаемые и прокомментированные куски кода?
TCCR0 = (1<< CS00 | 0<< CS01 | 1<<CS02); // 1024 (39 раз за полупериод пром. частоты вполне устраивает)
Чуть больше 15 Гц. Не маловато? Можно взять предделитель 1:8 (0b010 << CS00), будет почти 2 кГц или 2048 тактов. АЦП работает на частоте чуть меньше 9кГц, так что запас приличный.
interrupt [TIM0_OVF] void timer0_ovf(void){
Так на каком варианте остановились, расчет в прерывании или в промежутке? Пока не похоже ни на то, ни на другое.
Да, было бы неплохо. Спасибо.
Под cvavr вряд ли скомпилируется, но логику посмотреть сгодится. Определитесь все-таки с алгоритмом.
Вложения
firmware.tar.gz
(1.47 КБ) 431 скачивание
Открыл глаза
Сообщения: 42
Зарегистрирован: Сб ноя 29, 2014 21:20:52

Сообщение panya »

Определился. Хочу вот так.

Если кто знает и хочет помочь оптимизировать или улучшить, я только за.

Код: Выделить всё

/*****************************************************
Chip type               : ATmega8
Program type            : Application
AVR Core Clock frequency: 4,000000 MHz
Memory model            : Small
External RAM size       : 0
Data Stack size         : 256
*****************************************************/

#include <mega8.h>

// Alphanumeric LCD Module functions
#asm
   .equ __lcd_port=0x18 ;PORTB
#endasm
#include <lcd.h>
#include <delay.h>
#include <stdio.h>

 unsigned short int U; U=0; // пришло с АЦП 
        unsigned long S_int; S_int=0; // S_intermediate
            unsigned long long S_res; S_res=0; // S_resultat
                bool t_min; t_min=0;
                    unsigned long long t; t=0; // круто время считать не запретишь)
            
  char U_ACP[8]; //массив в котором будет формироваться строка для вывода на lcd
        char TIME[8]; //массив текущего времени для вывода на lcd
            char RES_IMP[8]; //массив текущего импульса для вывода на lcd

// обработка прерывания по переполнению
interrupt [TIM0_OVF] void timer0_ovf(void){
    U=ADCW;
        if(U=0){t_min++;}else{t_min=0;}; //минимальное кол-во тактов (при U=0) прерывания для отключения АЦП и прерывания   
    }
    
void main(void){
sei();
    S_int=U*U; 
        S_res+=S_int;
       
lcd_init(16); // LCD module initialization на 16

//приветствие 
lcd_puts("-Initialization-"); 
    lcd_gotoxy(0,1);
        lcd_puts("   ( ^____^ )   "); 
    delay_ms(1000);
    lcd_clear();
  
ADMUX=0xC0; // Номер порта и опорное напряжение
TCCR0 = (1<< CS00 | 0<< CS01 | 1<<CS02); // 1024 (39 раз за полупериод пром. частоты вполне устраивает)
TIMSK = (1<< TOIE0);

ADCSR = (1<<ADEN | 1<<ADSC | 1<<ADFR | 0<<ADIF | 0 << ADIE | 0b101<<ADPS0);     
       
while(1){
    if(t_min==20){ // 39 - полупериод пром. частоты
        TIMSK = (0<<TOIE0); // запрещаем прерывание по переполнению
            ADCSR = (0<<ADEN); //выключили АЦП
                TCCR0 = (0<< CS00 | 0<< CS01 | 0<<CS02); //выключили таймер
    
    // здесь времени много можно за скорость віполнения операций не переживать 
    I=sqrt(S_res/t); // в конце надо сделать стобы соответствовало формуле A=I^2*t --> I=sqrt(A/t)
    
    lcd_gotoxy(0,0);
        sprintf(Strom,"I: %.2dA",I);
            lcd_puts(Strom);  
    
    t=t/100;
            
    lcd_gotoxy(10,0);
        sprintf(TIME,"t: %ds",t);
            lcd_puts(TIME);   
            
    lcd_gotoxy(0,1);
        sprintf(RES_IMP,"B: %dkA2*s",S_res);
            lcd_puts(RES_IMP);
            
                }else{ // а вот здесь то времени не очень много, надо экономить
                    t++;
                    I=U*kt; // где kt конечный коеф. преобразования типа float
                    
                        lcd_gotoxy(0,0);
                            sprintf(Strom,"I: %.2dA",I); // Выводим последнее амплитудное значение
                                                        // а можно и подождать период вычислить среднеквадратичное и выдать реальное зн. действующего тока
                                lcd_puts(Strom);  
                                
                            lcd_gotoxy(10,0);
                                sprintf(TIME,"t: %ds",t);
                                    lcd_puts(TIME);   
                
                        Sres=Sres/1000; // хочу сделать условие (добавив kA2*s) и поделил на 1000 либо (MA2*s) и разделить на 1000000
                                        // еще его нужно как-то на время между прерываниями умножить
                
                        lcd_gotoxy(0,1);
                            sprintf(RES_IMP,"B: %dkA2*s",S_res);
                                lcd_puts(RES_IMP);
                            };
        }; // закрыли while(1)
    } // закрыли main    
Может где-то косовато, на быструю руку набросал
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Сообщение COKPOWEHEU »

bool t_min; t_min=0;
...
if(t_min==20){ // 39 - полупериод пром. частоты
Конечно, bool это не однобитная переменная, а как минимум 8-битная, но типизацию соблюдать все-таки надо.
if(U=0){t_min++;}else{t_min=0;}; //минимальное кол-во тактов (при U=0) прерывания для отключения АЦП и прерывания
Неа, это обнуление переменной U. Сравнение в Си "==", а не "=". Про сравнение именно с нулем уже писал.
Ту кашу, что внутри while(1) анализировать страшно. Комментариев по существу там нет, а то что есть - либо противоречит коду, либо констатация очевидной команды. Описания, зачем это делается, нет.
panya писал(а):Определился. Хочу вот так.
Лучше бы текстом написали, была бы хоть возможность помочь п осуществу. Потому что приведенный код (опять без форматирования, да сколько ж можно!) как надо работать не будет. Программу лучше писать последовательно, а не втыкать куски кода куда придется. Впрочем, если не хотите помощи - не надо.
Открыл глаза
Сообщения: 42
Зарегистрирован: Сб ноя 29, 2014 21:20:52

Сообщение panya »

COKPOWEHEU писал(а):
bool t_min; t_min=0;
Лучше бы текстом написали, была бы хоть возможность помочь п осуществу. Потому что приведенный код (опять без форматирования, да сколько ж можно!) как надо работать не будет. Программу лучше писать последовательно, а не втыкать куски кода куда придется. Впрочем, если не хотите помощи - не надо.
Помощи как раз хочу.

Предположим. аппаратик включился:

пока не поступили преобразования от АЦП (что нибудь кроме 0) светим на экране (ток: 0, импульс: 0, время: 0). Как только получаем от АЦП что-то начинаем считать, и примерно с периодом в 1 сек, обновлять данные на экране(где пишем текущее время, текущий ток, и текущее значение импульса). Как только получаем от АЦП 10-30 (эт как захочется) U равных 0 (U=0); останавливаем и выводим на экран последние значения.

И больше ничего не делаем

Примерно так. )

..........

Немного написал еще )

Код: Выделить всё

/*****************************************************
Chip type               : ATmega8
Program type            : Application
AVR Core Clock frequency: 4,000000 MHz
Memory model            : Small
External RAM size       : 0
Data Stack size         : 256
*****************************************************/

#include <mega8.h>

// Alphanumeric LCD Module functions
#asm
   .equ __lcd_port=0x18 ;PORTB
#endasm
#include <lcd.h>
#include <delay.h>
#include <stdio.h>

unsigned short int SPANNUNG; SPANNUNG=0; // SPANNUNG пришло с АЦП
    unsigned long int T_OFF; T_OFF=0; // T_OFF
        unsigned long int S_INT; S_INT=0; // S_intermediate (Проможуточное S)

unsigned long long S_RES; S_RES=0; // S_resultat
    unsigned long long T; T=0; // круто время считать не запретишь
            
char U_ACP[8]; //массив в котором будет формироваться строка для вывода на lcd
    char TIME[8]; //массив текущего времени для вывода на lcd
        char RES_IMP[8]; //массив текущего импульса для вывода на lcd

// обработка прерывания по переполнению 
// если наступило прерывание и напряжение равно нулю, то делать T_OFF++, зачем поймём дальше
interrupt [TIM0_OVF] void timer0_ovf(void){
    SPANNUNG=ADCW;
        if(SPANNUNG==0){T_OFF++;}else{T_OFF=0;};   
    }
    
void main(void){
    sei();
        S_INT=SPANNUNG*SPANNUNG; 
            S_RES+=S_INT;

    ADMUX = 0xC0; // Номер порта и опорное напряжение
        TCCR0 = (1<< CS00 | 0<< CS01 | 1<<CS02); // 1024 (39 раз за полупериод пром. частоты вполне устраивает)
            TIMSK = (1<< TOIE0);

    ADCSR = (1<<ADEN | 1<<ADSC | 1<<ADFR | 0<<ADIF | 0 << ADIE | 0b101<<ADPS0);    

    lcd_init(16); // LCD module initialization на 16

    // приветствие 
    lcd_puts("-Initialization-"); 
        lcd_gotoxy(0,1);
            lcd_puts("   ( ^____^ )   "); 
    delay_ms(1000);
    lcd_clear();

        while(1){
//условие работы !!!! Инвертор работает на резистивную нагрузку, а потом по интегральной зависимости времени отключения выключается
Например при токе в 65А примерно через 60 сек
// если секундомер что-то намотал (тоесть изделие работало (считало ток, импульс, время)) и при этом 20 прерываний подряд нету напряжения (вот здесь T_OFF пригодилось) то: читаем дальше коментарии
            if(SPANNUNG==0 && T>0 && T_OFF==20){
                TIMSK = (0<<TOIE0); // запрещаем прерывание по переполнению
                    ADCSR = (0<<ADEN); //выключили АЦП
                        TCCR0 = (0<< CS00 | 0<< CS01 | 0<<CS02); //выключили таймер

//если попали в такой режим где мы уже не работаем, то значить, перед этим мы работали, и насобирали какие то данные. Поэтому поступим так: по окончанию работы необходимо вывести время, когда остановились (нам его считал Т), импульс (а его нам считал  S_res), а ТОК нужно получить из формулы  A=I^2*t --> I=sqrt(A/t)

                I=sqrt(S_RES/T); // в конце надо сделать стобы соответствовало формуле A=I^2*t --> I=sqrt(A/t)
    
                lcd_gotoxy(0,0);
                    sprintf(Strom,"I: %.2dA",I);
                        lcd_puts(Strom);  
            
                lcd_gotoxy(10,0);
                    sprintf(TIME,"t: %ds",T/100); // T/100 = 1000мс/100=1секунда или можно счетсик тимер1, шоб прерывание делал и считал секунды
                        lcd_puts(TIME);   
            
                lcd_gotoxy(0,1);
                    sprintf(RES_IMP,"B: %dkA2*s",S_res);
                        lcd_puts(RES_IMP);
            
            }else{ // А если не плохой режим, и мы удачно маслаем счетчиком импульсом и током то, тоже выведем данные, вот только текущие данные 
                T++;
                    I=SPANNUNG*kt; // где kt конечный коеф. преобразования типа float
                    
                        lcd_gotoxy(0,0);
                            sprintf(Strom,"I: %.2dA",I); // Выводим последнее амплитудное значение
                                                        // а можно и подождать период вычислить среднеквадратичное и выдать реальное зн. действующего тока
                                lcd_puts(Strom);  
                                
                            lcd_gotoxy(10,0);
                                sprintf(TIME,"t: %ds",t);
                                    lcd_puts(TIME);   
                
                        Sres=Sres/1000; // хочу сделать условие (добавив kA2*s) и поделил на 1000 либо (MA2*s) и разделить на 1000000
                                        // еще его нужно как-то на время прерывания умножить
                
                        lcd_gotoxy(0,1);
                            sprintf(RES_IMP,"B: %dkA2*s",S_res);
                                lcd_puts(RES_IMP);
                            };
        }; // закрыли while(1)
    } // закрыли main    
Ну как-то так. Надеюсь описал подробно
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Сообщение COKPOWEHEU »

пока не поступили преобразования от АЦП (что нибудь кроме 0)
REFS=0b11 вроде как подключает внутренний ИОН, не отличающийся точностью и равный (для ATmega8) 2,56 В. То есть младший разряд соответствует 2,5 мВ. Переключение младшего разряда произойдет примерно на половине этого напряжения, то есть 1,25 мВ. Вы уверены, что сможете настолько подавить помехи? Вы уверены, что дрейф или внутренние шумы не сдвинут ноль преобразователя? Вы уверены, что импульс такой величины надо считать импульсом? Параметров импульсов я не знаю, поэтому подберите более-менее правдоподобный уровень, который однозначно не будет сочтен шумом.
примерно с периодом в 1 сек, обновлять данные на экране
В целом разумно. Можно чуть быстрее, до 2-10 Гц, но это не принципиально. 1 Гц смотрится медленно, просто по ощущениям.
где пишем текущее время, текущий ток, и текущее значение импульса
Интегрируется работа тока. Выводить хотите ток или мгновенную мощность (значение АЦП или его квадрат, грубо говоря)? Со временем тоже не так просто: лучше воспользоваться Timer1_comp и выставить "круглую" частоту прерывания для простоты расчетов. Скажем, 5 кГц (этого, кстати, можно добиться и на ущербном Timer2). Если планируется счет времени, выделить под него отдельный таймер - распространенное решение, основные достоинства которого - простота, точность и малые затраты ресурсов контроллера. Рекомендую :-)
Как только получаем от АЦП 10-30 (эт как захочется) U равных 0 (U=0); останавливаем и выводим на экран последние значения.
Не очень понятен принцип. Надо, чтобы после прекращения импульса показывалось, что импульс кончился (спец. символ что ли на дисплее показать) и отображать его параметры. Сколько времени? Что-то около 5 сек или до следующего импульса? Кстати, чем выше частота опроса АЦП (при постоянной тактовой в рекомендуемых пределах!), тем больше точность. Я бы все-таки рекомендовал взять хотя бы 1 кГц, лучше 5-8. Ошибка при интегрировании и так будет накапливаться, лучше уменьшить насколько возможно.
Как только получаем от АЦП
Так все-таки в прерывании или в основном цикле? Если описанная функциональность и правда вся, которая нужна, эти варианты равноправны. Только с обработкой в прерывании попроще, в том числе и потому что он уже рассмотрен.
unsigned long S_int;
32 байта? Если вывод раз в секунду, особых требований к скорости нет. Можно и 64-разрядное число взять. Впрочем, это не принципиальный вопрос.
.
Было бы неплохо, если бы Вы ответили на выделенные вопросы, или указали свое мнение по ним.
.
Давайте не будем спешить и сначала напишем алгоритм. Логику работы я себе представляю примерно так:
1. Нужен отсчет времени, чтобы основной программе каждую секунду (или сколько там) выдавался сигнал, что можно начинать вывод на дисплей. Кроме того, нужно считать прошедшее с момента последнего события время.
2. Нужно регулярно (с равными интервалами) опрашивать АЦП и добавлять его в интеграл.
3. Нужно определять начало и конец импульса. Программа дложна "помнить", на каком этапе находится: ожидание начала импульса, ожидание конца импульса или ожидание окончания индикации предыдущего импульса. Это навскидку, может придется расширять. Скорее всего, флаговый автомат будет хорошим решением.
4. Все настройки лучше делать в начале программы, благо менять их вряд ли придется.
5. Нужно организовать вывод. Преобразование из вычисленного интеграла суммарной мощности (фактически, просто умножение на некую константу, да она у Вас есть, kt), вывод этого числа, времени и мгновенного значения импульса (если находимся в соответствующем режиме), вывод самого режима (может оказаться удобным, но вопросы удобства это вопросы удобства, то что мне кажется удобным сейчас может оказаться неудобным на самом деле).
.
Восле написания логики, скорее всего можно будет соптимизировать.
.
Выделенные пункты достаточно независимы, и смешивать их не стоит. Если хотите сразу писать код - прошу, умоляю, с человеческим форматированием и комментариями по существу. Блоки кода (в циклах, if'ах и т.п.) должны быть выделены. Обычно это делается отступами. При объявлении переменных, они выравниваются либо по типу, либо по первому символу, одна над другой; если тип одинаковый, то можно в строчку. Если отдельные биты в переменной несут отдельные функции (как в настройках таймера или АЦП), их лучше записывать по отдельности, как сделано в ADCSR, но не сделано в ADMUX. Отдельные модули (инициализация, вывод на дисплей, рассчет) лучше оформить как функции или макросы. Расход памяти и скорости при этом не измениться, в читаемость повысится. Соответственно, каждый модуль можно поименовать: "инициализация", "математика", "шаманство". Вроде на основные ошибки форматирования указал.
Если с какими-то моими высказываниями не согласны - лучше распишите с какими и почему. Это всем полезно будет. И еще раз повторю, писать лучше постепенно, проверяя после каждого значимого этапа. Порой и очевидные конструкции преподносят сюрпризы, как тот же СТС-режим таймера. Лучше, когда их обнаружению не препятствует остальной код, работоспособность которого точно также не проверена.
Открыл глаза
Сообщения: 42
Зарегистрирован: Сб ноя 29, 2014 21:20:52

Сообщение panya »

Код: Выделить всё

REFS=0b11 вроде как подключает внутренний ИОН, не отличающийся точностью и равный (для ATmega8) 2,56 В. То есть младший разряд соответствует 2,5 мВ. Переключение младшего разряда произойдет примерно на половине этого напряжения, то есть 1,25 мВ. Вы уверены, что сможете настолько подавить помехи? Вы уверены, что дрейф или внутренние шумы не сдвинут ноль преобразователя? Вы уверены, что импульс такой величины надо считать импульсом? Параметров импульсов я не знаю, поэтому подберите более-менее правдоподобный уровень, который однозначно не будет сочтен шумом.
Шунт: 200А 75мв --> 75/200=2.5мВ на шунте при 1 Ампере
В действительности будут использоваться Операционники с переменными резистрами в цепи ООС. Думаю потолок будет 80А действующего, и коэф. по ООС подберу необходимые настройки, если не получиться то никогда не поздно написать if(U>5) .

Тоесть с шунта будет поступать на операционник а с него через процедуру двухполупериодного выпрямления на АЦП.
При этом току в 80 А будет соответствовать 2.5 В на АЦП. (спасибо операционникам и наличии ООС)

Код: Выделить всё

В целом разумно. Можно чуть быстрее, до 2-10 Гц, но это не принципиально. 1 Гц смотрится медленно, просто по ощущениям.
Согласен.

Код: Выделить всё

Интегрируется работа тока. Выводить хотите ток или мгновенную мощность (значение АЦП или его квадрат, грубо говоря)? Со временем тоже не так просто: лучше воспользоваться Timer1_comp и выставить "круглую" частоту прерывания для простоты расчетов. Скажем, 5 кГц (этого, кстати, можно добиться и на ущербном Timer2). Если планируется счет времени, выделить под него отдельный таймер - распространенное решение, основные достоинства которого - простота, точность и малые затраты ресурсов контроллера. Рекомендую
Если б знать как это реализовать...

Код: Выделить всё

Не очень понятен принцип. Надо, чтобы после прекращения импульса показывалось, что импульс кончился (спец. символ что ли на дисплее показать) и отображать его параметры. Сколько времени? Что-то около 5 сек или до следующего импульса? Кстати, чем выше частота опроса АЦП (при постоянной тактовой в рекомендуемых пределах!), тем больше точность. Я бы все-таки рекомендовал взять хотя бы 1 кГц, лучше 5-8. Ошибка при интегрировании и так будет накапливаться, лучше уменьшить насколько возможно.
Смысл такой: Есть инвертор напряжения и нагрузка дне него. Собирается схема +(плюс) инвертора -- рубильник -- здоровенный резюк -- -(минус) инвертора последовательно. Запускаем эту цепочку при положении рубильника (выключено), и получаем:

Инвертор на холостом ходу, тока нет, изделие Х показывает, что ток 0А, Импульс 0А, время протекания тока 0с. Как только включаеться рубильник (точнее начинает протекать ток в цепи) изделие должно начать считать время, импульс, и время (сколько прошло времени от появления тока до этого момента).

Потом через некоторое время отключается сам инвертор, а наше изделие Х показывает сколько времени протекал ток. сколько насчитал импульса а ток вычисляет из формулы A=I^2*t --> I=sqrt(A/t)

И програмулину написал. может не так как Вы её представляете, или (может всетаки неправильно написал), но как по мне, в ней осталось только правильно стартонуть счетчик времени.
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Сообщение COKPOWEHEU »

COKPOWEHEU писал(а): 3а. Работа с 64-разрядными числами в avr-gcc реализована медленно. На сложение с 32-разрядным уходит аж 408 тактов, или 102 мкс.
Оказывается, была отключена оптимизация. Если включить - луть более 100 тактов, что уже вполне приемлемо. Вот это я опозорился, конечно. :oops:
Смысл такой:
То есть импульс всего один? Не будем тогда изобретать универсальную конструкцию. Вот когда появится необходимость...
A=I^2*t --> I=sqrt(A/t)
Время здесь лишнее. На него умножается при интегрировании, на него же надо делить в конце. Лучше просто стабилизировать временные интервалы.
И програмулину написал.
Так я и не понял, как Вы синхронизируете АЦП с таймером. Допустим, в прерывании (счет данных важнее индикации).
.
Вот примерный код, как я его представляю. Вроде все прокомментировано, только что он на avr-gcc, а не на cvavr, но платформозависимого там вроде немного, можно подправить. Вывод чисел не делал - он зависит от библиотеки дисплея, желаемой точности и прочего. Инициализацию тоже - она простая.
Вложения
firmware.c.tar.gz
(2.56 КБ) 435 скачиваний
Открыл глаза
Сообщения: 42
Зарегистрирован: Сб ноя 29, 2014 21:20:52

Сообщение panya »

Спасибо за исходник.
Посмотрел код, мне кажется что в местах отмененных кругами будет сброс интегрирования (все что нажито непосильным трудом почти за пол периода - сбрасывается), хотя могу и ошибаться:

Код: Выделить всё

 if(U<ADC_MIN){ //если измеренное значение меньше порога
   AdcRes=0; //сбрасываем все, что успело наинтегрироваться, это наводки и прочий мусор
   wtime=0;
И не понимаю для чего это?

Код: Выделить всё

if(sec>1000){flag |= F_SEC; sec=0; time++;}

Код: Выделить всё

ISR(TIMER2_COMP_vect){ //прерывание по сравнению с частотой 5 кГц (предделитель 1:8. OCR2=99)
 static unsigned int sec=0; //количество прошедших измерений, каждые 1000 измерений выставляем флаг вывода на экран
 unsigned int U=ADCW; //это очевидно
 static unsigned int wtime=0; //сколько циклов подряд измеренное значение было выше/ниже порога.
 sec++;
 if(sec>1000){flag |= F_SEC; sec=0; time++;} //отчсет временных интервалов по 5 Гц, или 0.2 сек
//этот кусок уже рассмотрен
 LastVal+=U;
 LastVal>>=1;
 LastVal*=LastVal;
 AdcRes+=LastVal;
 LastVal=U;
//проверяем, в каком состоянии устройство находится. Оно ждет начала импульса или его конца? Если оно дождалось конца - прерывание блокируется и сюда мы не вернемся 
 if(!(flag & F_START)){ //ждем начала
  if(U<ADC_MIN){ //если измеренное значение меньше порога
   AdcRes=0; //сбрасываем все, что успело наинтегрироваться, это наводки и прочий мусор
   wtime=0;
  }else{ //А вот если измеренное число больше
   wtime++;
   if(wtime>WAIT_ST){ // если на протяжении WAIT_ST измерений мы получали только "полезный" сигнал и ни одного "шумового"
    wtime=0; //сбрасываем счетчик - он понадобится в фазе измерения полезного сигнала
    flag |= F_START; //переходим в эту самую фазу
    time=0; //сбрасываем глобальный счетчик времени. Теперь у нас есть глобальная точка отсчета - стартовы импульс
    sec=0; //локальный счетчик тоже сбрасываем. Скорее всего будет выброс в виде задержки отображения, но на тактй частоте это не страшно
   }//if(WAIT_ST)
  }//if(ADC_MIN)
 }else{ //ждем конца
  if(U<ADC_MIN){ //если измеренное число меньше порога, надо проверить не случайная ли это флуктуация
   wtime++; //для этого переменной wtime считаем
   if(wtime>WAIT_EN){ //и если насчитали более WAIT_EN измерений подряд, которые меньше порога - импульс кончился наверняка
    flag |= (1<<F_FINISH); //тогда выставляем флаг финиша
    ms=sec; //сохраняем счетчик отдельных измерений для основной программы
    TIMSK=0; //и запрещаем это прерывание
   }
  }else{ //если же среди кучи низких уровней попался высокий - считаем тех всего лишь случайной помехой
   wtime=0; //и начинаем отсчет заново
  }//ADC_MIN
 }
}
Вложения
image029.png
(34.59 КБ) 285 скачиваний
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Сообщение COKPOWEHEU »

Посмотрел код, мне кажется что в местах отмененных кругами будет сброс интегрирования (все что нажито непосильным трудом почти за пол периода - сбрасывается), хотя могу и ошибаться:
Верно, я рассчитывал на импульс как на импульс, а не ка серию импульсов. Можно исправить второе условие по образцу первого, чтобы оно сбрасывалось по высокому уровню и считало количество измерений низкого. Надеюсь, справитесь?)
И не понимаю для чего это?

Код: Выделить всё

if(sec>1000){flag |= F_SEC; sec=0; time++;}
Прерывание возникает с частотой 5 кГц, для отображения это много, поэтому каждые 1000 измерений (частота, соответственно, 5 Гц) проводим отображение информации на дисплее. Время отображается на том же этапе, поэтому менять его чаще бессмысленно, пользователь этого просто не увидит. А вот для отображения финальной информации (когда импульс уже кончился и надо выдать максимально точные данные) время пересчитывается с учетом sec (которое перекидывается из локальной переменной в глобальную ms) Может, можно было сделать sec глобальной, но так безопаснее, да и требуется всего один раз.
Открыл глаза
Сообщения: 42
Зарегистрирован: Сб ноя 29, 2014 21:20:52

Сообщение panya »

Немного на общий суд. Немного не понятна работа с флагами. я ими никогда не пользовался (

Код: Выделить всё

/*****************************************************
Chip type               : ATmega8
Program type            : Application
AVR Core Clock frequency: 4,000000 MHz
Memory model            : Small
External RAM size       : 0
Data Stack size         : 256
*****************************************************/

#include <mega8.h>

// Alphanumeric LCD Module functions
#asm
   .equ __lcd_port=0x18 ;PORTB
#endasm
#include <lcd.h>
#include <delay.h>
#include <stdio.h>

volatile unsigned long long S_RES=0;	// результат
    volatile unsigned long S_INT=0; // S_intermediate (Проможуточное S)
        unsigned char FLAG=0; // состояние системы и его биты
        
volatile unsigned int TIME=0; // время (с дискретностью 0.2 сек) с момента последнего события
   volatile unsigned int MS=0; // количество циклов измерения после последнего изменения time

unsigned int sec=0; 
    
#define ADC_MIN	2 // порог срабатывания АЦП
#define WAIT_ST	500	//сколько измерений подряд должно оказаться выше порога, чтобы счесть импульс начавшимся
#define WAIT_EN	500	//сколько измерений должно быть ниже порога, чтобы импульс считать закончившимся
#define KOEFF_R	1	//коэффициент преобразования из значения АЦП в ток.

//****************************** Прерывание по совпадению ******************************//

ISR(TIMER2_COMP_vect){ 
    static unsigned int kol=0; // кол-во прошедших измерений, каждые 1000 измерений выставляем флаг вывода на экран
        if(kol==5000){flag |= F_SEC; kol=0; time++;}
        
        unsigned int SPANNUNG=ADCW;
    static unsigned int WTIME=0; //сколько циклов подряд измеренное значение было выше/ниже порога.
            
    if(SPANNUNG<ADC_MIN){
        sec++;
            if((sec>WAIT_EN) && time>0){flag |= F_FINISH;} // WAIT_EN =500 а time=1 будет спустя 4500, вывод: (sec>WAIT_EN) && time>0 такое возможно если оно уже работало во всех остальных случаях невозможно.
                else{flag |= F_START;}
        }else{
            sec=0;
            
            S_INT=SPANNUNG*SPANNUNG;
            S_RES=S_RES+S_INT;}
            }
 
void main(void){

//****************************** Системные настройки ******************************//

    ADMUX=0xC0; // Номер порта и опорное напряжение 2.56
    ADCSR=(1<<ADEN | 1<<ADSC | 1<<ADFR | 0<<ADIF | 0<<ADIE | 1<<ADPS2 | 1<<ADPS1 | 0<<ADPS0); // Настройки АЦП
    
    TCCR2=(0<<CS22 | 1<<CS21 | 0<<CS22); // 5 kHz
    OCR2=0х63; // число 99
    
    lcd_init(16); // инициализация экрана
    
    sei(); // разрешение прерывания

//*********************************************************************************//

void start_lcd(){
    lcd_gotoxy(0,0);
    lcd_puts("I: 0A");  

    lcd_gotoxy(10,0);
    lcd_puts("t: 0s");
            
    lcd_gotoxy(0,1);
    lcd_puts("A: 0kA2*s");
    }


while(1){ну а тут развлекаемся}
}
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Сообщение COKPOWEHEU »

volatile unsigned long long S_RES=0; // результат
volatile unsigned long S_INT=0; // S_intermediate (Проможуточное S)
unsigned char FLAG=0; // состояние системы и его биты
Чем обусловлено именно такое форматирование?
ISR(TIMER2_COMP_vect){
Неужели в cvavr это работает?
OCR2=0х63; // число 99
А прямым текстом присвоить 99 было не судьба? :shock:
volatile unsigned int TIME=0; // время (с дискретностью 0.2 сек) с момента последнего события
volatile unsigned int MS=0; // количество циклов измерения после последнего изменения time
Зачем было менять регистр на заглавные буквы? Это же не константы.
unsigned int SPANNUNG=ADCW;
В Си принято использовать идентификаторы из заглавных букв для констант.
sec++;
не объявлено.
if((sec>WAIT_EN) && time>0){flag |= F_FINISH;} // WAIT_EN =500 а time=1 будет спустя 4500, вывод: (sec>WAIT_EN) && time>0 такое возможно если оно уже работало во всех остальных случаях невозможно.
На первый взгляд сработает сразу же, sec не сбрасывается, досчитает до 501 и условие будет выполнено.
ADMUX=0xC0; // Номер порта и опорное напряжение 2.56
Сделай наконец по-человечески!
while(1){ну а тут развлекаемся}
}
А что судить-то, ничего ж не сделано, оно даже не скомпилируется.
Ждем форматирования и приведения к хотя бы компилируемому состоянию.
Открыл глаза
Сообщения: 42
Зарегистрирован: Сб ноя 29, 2014 21:20:52

Сообщение panya »

Немного привел к читаемому виду:

Код: Выделить всё

/*****************************************************
Chip type               : ATmega8
Program type            : Application
AVR Core Clock frequency: 4,000000 MHz
Memory model            : Small
External RAM size       : 0
Data Stack size         : 256
*****************************************************/

#include <mega8.h>


#asm
   .equ __lcd_port=0x18 ;PORTB
#endasm
#include <lcd.h>
#include <delay.h>
#include <stdio.h>
 //**************************************** Перепенные
volatile unsigned long S_RES=0;	// результат
    volatile unsigned long S_INT=0; // S_intermediate (Проможуточное S)

volatile unsigned short int kol=0; // кол-во прерываний
    volatile unsigned short int count=0; // дискретность 0.2 секунды, хватит на 2621 секунду работы
        unsigned short int watch=0; // Выводимое время, хватит на 13105 секунд но count'а хватит на 2621 секунд, поэтому всеравно 2621

volatile unsigned short int adc_t=0; // тоже не большая величина и незачем выделять ей много места
    unsigned short int spannung=0; // das ist spanung
    
    double strome=0;
    double s_inte=0;
    double s_rese=0;

volatile unsigned short int flag=0;
//**************************************** Перепенные end


//**************************************** Макросы       
#define ADC_MIN	2 // порог срабатывания АЦП
#define WAIT_EN	500	//сколько измерений должно быть ниже порога, чтобы импульс считать закончившимся
#define KOEFF_R	0.1	//коэффициент преобразования из значения АЦП в ток.
//**************************************** Макросы end

//**************************************** Масивы
char TIME[1];
char RES_IMP[32];
char STROM[32];

interrupt[TIM2_COMP] void timer2_comp_isr(void){
    spannung=ADCW;
    kol++;
        if(kol==1000){kol=0; count++; flag=1;}
    
    if(spannung<ADC_MIN){
        adc_t++;
            if((adc_t==WAIT_EN) && (count>0)){
                flag=2; // 2 вывод на экран окончания
                lcd_clear(); // очистили дисплей
                    TIMSK=(0<<OCIE2); // больше нету прерываний, переменные остаются неизменными
                        TCCR2=(0<<CS22 | 0<<CS21 | 0<<CS22 | 0<<WGM21); // больше нету таймера по совпадению OCR2
                            ADCSR = (0<<ADEN); //выключили АЦП
                            }
                                else{flag=3;}  // тогда скорее всего это start
        }else{ // тогда скорее всего рабочий режим
            adc_t=0;
            
            S_INT=spannung*spannung;
            S_RES=S_RES+S_INT;}
}

void Update_LCD(){

 lcd_gotoxy(0,0);
 sprintf(STROM,"I:%dA",spannung);
 lcd_puts(STROM);  
            
 if(count%5==0){watch++;}
 lcd_gotoxy(10,0);
 sprintf(TIME,"t:%ds",watch);
 lcd_puts(TIME);
 
 S_RES=S_RES*0.0002;
 lcd_gotoxy(0,1);
 sprintf(RES_IMP,"A:%dA2*s",S_RES);
 lcd_puts(RES_IMP); 

}

void start_LCD(){
    lcd_gotoxy(0,0);
    lcd_puts("I:0A");  

    lcd_gotoxy(10,0);
    lcd_puts("t:0s");
            
    lcd_gotoxy(0,1);
    lcd_puts("A:0kA2*s");
}

void end_LCD(){
    S_RES=S_RES*(watch/5000);
    spannung=(int)sqrt (S_RES/watch);
    
    lcd_gotoxy(0,0);
    sprintf(STROM,"I:%dA",spannung);
    lcd_puts(STROM); 
   
    lcd_gotoxy(10,0);
    sprintf(TIME,"t:%ds",watch);
    lcd_puts(TIME);
    
    S_RES=S_RES*(watch/5000);
    lcd_gotoxy(0,1);
    sprintf(RES_IMP,"A:%dA2*s",S_RES);
    lcd_puts(RES_IMP);

}

void main(void){

//****************************** Системные настройки ******************************//

    ADMUX=0xC0; // Номер порта и опорное напряжение 2.56
    ADCSR=(1<<ADEN | 1<<ADSC | 1<<ADFR | 0<<ADIF | 0<<ADIE | 1<<ADPS2 | 1<<ADPS1 | 0<<ADPS0); // Настройки АЦП
    
    TCCR2=(0<<CS22 | 1<<CS21 | 0<<CS22 | 1<<WGM21); // 5 kHz и по совпадению OCR2
    OCR2=0x64; // число 100
    TIMSK=(1<<OCIE2); // включили счетчик
    
    lcd_init(16); // инициализация экрана
    
    #asm("sei") // разрешение прерывания

//*********************************************************************************//


while(1){ 

if(flag==1){Update_LCD(); flag=0;};
if(flag==3){start_LCD(); }
if(flag==2){end_LCD(); }  

 }

}

1. Наметилась проблема с выводом данных. Так-то cv avr не желает делать так (float=int*XXX) где ХХХ число а float и int типы переменных.
2. По прежнему выводится ерунда в S_RES
3. Не берется корень (
4. На данный момент бесплатная версия cv avr, и не хочет long long
5. В прерывании вроде все в порядке
Говорящий с текстолитом
Аватара пользователя
Сообщения: 1525
Зарегистрирован: Чт июн 10, 2010 20:11:19

Сообщение COKPOWEHEU »

1. Наметилась проблема с выводом данных. Так-то cv avr не желает делать так (float=int*XXX) где ХХХ число а float и int типы переменных.
Это нормальное поведение. Если хотите нормально умножить - сначала присвоение, потом умножение. int x; y=x; y*=XXX;
sprintf(RES_IMP,"A:%dA2*s",S_RES);
2. По прежнему выводится ерунда в S_RES
Рискну предположить, что выводится всего 2 байта из 4. Я бы сделал свое преобразование числа в строку с нужной разрядностью и положением точки, и это выводил. sprintf довольно тяжелый и медленный, он оправдан разве что когда надо выводить с хитрым форматированием, в одном месте целый, в другом дробные, в разных записях и т.п.
3. Не берется корень (
Как именно не берется? Можно попробовать подключить maih.h.
4. На данный момент бесплатная версия cv avr, и не хочет long long
Мыши плакали, кололись...?
5. В прерывании вроде все в порядке
unsigned short int spannung=0; // das ist spanung
Спасибо за комментарий по-немецки :shock: но неужели нельзя было назвать хотя бы voltage, или просто U? Тем более, что это не напряжение, а только результат преобразования, не несущий сам по себе физического смысла.
volatile unsigned short int adc_t=0; // тоже не большая величина и незачем выделять ей много места
Один фиг 2 байта, что short, что int.
volatile unsigned long S_RES=0; // результат
volatile unsigned long S_INT=0; // S_intermediate (Проможуточное S)
...
S_INT=spannung*spannung;
Опять заглавными буквами. По поводу последней строки см. 1, оно сначала вычислит (int)U*(int)U а уже потом приведет то что останется к long.
if(spannung<ADC_MIN){
adc_t++;
if((adc_t==WAIT_EN) && (count>0)){
Это условие выполнится после 66036 измерений, или через 13 сек. Условие на строгое равенство вообще не всегда хорошая идея, без него выполнится всего через 1500 измерений, или 0.3 сек.
void Update_LCD(){
...
sprintf(RES_IMP,"A:%dA2*s",S_RES);
lcd_puts(RES_IMP);
}
Тут надо аккуратно, S_RES большой, хватит ли времени 5 раз в секунду обновлять, тем более что прерывание занимает порядочную долю ресурсов.
TCCR2=(0<<CS22 | 0<<CS21 | 0<<CS22 | 0<<WGM21); // больше нету таймера по совпадению OCR2
В данном случае запись TCCR2=0 была бы лучше. Неужели я настолько запугал человеческим оформлением отдельных знаяащих битов?
ADMUX=0xC0; // Номер порта и опорное напряжение 2.56
А нет, не запугал. Кстати, в прерывании форматирования как не было, так и нет. И в объявлениях глобальных переменных тоже.
if(flag==1){Update_LCD(); flag=0;};
if(flag==3){start_LCD(); }
if(flag==2){end_LCD(); }
Интересное понимание флагов, но в данном случае вполне оправданное. Только переменную flag лучше переименовать в state (состояние) или zustand для любителей "das ist zustand". flag по смыслу больше подходит для набора из 8 независимых битов.
OCR2=0x64; // число 100
TIMSK=(1<<OCIE2); // включили счетчик
А не проще записать десятичное число в десятичной системе? Второй комментарий не соответствует истине - эта строка разрешает прерывание, а не запускает таймер.
interrupt[TIM2_COMP] void timer2_comp_isr(void){
...
lcd_clear(); // очистили дисплей
Очищать дисплей в прерывании?!
if(spannung<ADC_MIN){
adc_t++;
if((adc_t==WAIT_EN) && (count>0)){
Лучше считать не глобальное время, а сколько измерений сделано. Кстати, усреднение тока нужно по импульсу или по всему времени работы? Изменения переменной watch выполнено довольно странно.
.
В целом, условие в прерывании вряд ли сработает.
Надо следить за разрядностью выводимого значения.
Надо доделать форматирование. В частности, определиться, где удобнее набор битов, где шестнадцатеричное число, где лесятичное.
Операторы, расположенные в одном блоке лучше выравнивать по вертикали, а не диагонали. Комментарии существенно облегчают чтение кода и, соответственно, совместную работу с ним.
Ответить

Вернуться в «AVR»