Есть ли более точная альтернатива micros()?

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

dimax пишет:

Arhat109-2 пишет:

dimax, а почему не оформить в виде обработчика прерывания по захвату таймера?

Я думал. Если сделать обычное прерывание, то компилятор сначала напихает туда команд  сохранения всех рабочих регистров в стек, это сразу 15-20 тактов  МК коту под хвост.  Этого нельзя допустить.  Значит надо выполнить ISR_NAKED (), сохранить по самому минимуму в стек, затем сохранить куда-то 16 бит данных из регистра ICR, перепрограммировать таймер на ловлю противоположного ипульса, восттановить сохраннёные регистры из стека и можно выходить. Просто на обычной ассемблерной вставке это скорее всего не сделать, придёться цеплять  внешний ассемблерный файл, который подключать к проекту. В общем довольно муторно, конечно была бы какая то серьёзная надобность, можно б было и написать..

ua6em пишет:

И как его применять???

Подать на вход ICP (пин8) импульс, функция выплюнет его длительность в тактах Мк

Ну я так и делал через ISR_NAKED, в нем асм-вставками, как и у вас ловил прерывания в миниум регистров. Да, есть ещё регистры GPIO аж 3шт .. :) Понятно что последовательным кодом и ручной проверкой флагов можно ловить меньшие интервалы, но у меня стояла задача исключить долгие ожидания типового pulseIn().

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ua6em, ну да. Обычной операцией присваивания.  И название функции нужно исправить на uint16_t asm_func(), видимо "u" куда-то пропало при вставлении кода.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

dimax пишет:

ua6em, ну да. Обычной операцией присваивания.  И название функции нужно исправить на uint16_t asm_func(), видимо "u" куда-то пропало при вставлении кода.

Показывает 0, длина импульсов 1000мксек и 2000мксек

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ua6em, функция точно рабочая. Если не работает значит у тебя что-то не так.  Версия IDE  та что советовал? КОмпилятор любит выкидывать непонятные ему куски кода.  Может помочь указывание volatile перед функцией. Если не поможет, то карму чистить нужно..))

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

dimax пишет:

ua6em, функция точно рабочая. Если не работает значит у тебя что-то не так.  Версия IDE  та что советовал? КОмпилятор любит выкидывать непонятные ему куски кода.  Может помочь указывание volatile перед функцией. Если не поможет, то карму чистить нужно..))


Скомпилировал оба примера в IDE 1.6.8 в первом сигнал брал с 12 пина во вотором с 8, в обоих случаях показывает 0.00 и 0 ))) Сигнал подаю с приемника радиоуправления FS-IA6...
Код второго:
 

/* взято отсюда
 *  http://arduino.ru/forum/programmirovanie/est-li-bolee-tochnaya-alternativa-micros
 *  
 */
uint16_t pulse = 1500;

void setup() {
  Serial.begin(57600);
}

volatile uint16_t asm_func(){
asm volatile (         
"cli"                     "\n\t" // дабы никто не мешал
"sts 0x80,__zero_reg__"   "\n\t" // TCCR1A=0
"ldi r17,0x41"            "\n\t" // RISING ICP detect , start timer1
"sts 0x81,r17"            "\n\t" // TCCR1B= (1<<ICES1)|(1<<CS10)
"sbic 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"sbi 0x16,5"              "\n\t" // тогда clear ICF1
"wait_begin:"             "\n\t" // ждём флага ICF 
"sbis 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"rjmp wait_begin"         "\n\t" //пропускаем ход
"lds r22, 0x86"           "\n\t" //сохранить ICR1L  
"lds r23, 0x87"           "\n\t" //сохранить ICR1H
"sbi 0x16,5"              "\n\t" //clear ICF1
"ldi r19,0x01"            "\n\t" // FALLING ICP detect
"sts 0x81,r19"            "\n\t" // TCCR1B=(0<<ICES1)|(1<<CS10)
"wait_end:"               "\n\t" // ждём флага ICF 
"sbis 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"rjmp wait_end"           "\n\t" // пропускаем ход
"lds r24, 0x86"           "\n\t" //save ICR1L
"lds r25, 0x87"           "\n\t" //save ICR1H
"sub r24, r22"            "\n\t" //ICR1L_new-ICR1L_old
"sbc r25, r23"            "\n\t" //ICR1H_new-ICR1H_old
"sei"                     "\n\t"
::: ); }


void loop() {
pulse = asm_func();
Serial.println(pulse);
delay(1000);
}

Можешь скинуть сюда бинарник, сравню

Взял другой твой скетч, откомпилировался, залился, работает, частоту показывает 50 дути 5  при ширине импульса 1000 мксек и 10 при ширине импульса 2000мксек )))
Дело было не в бобине )))
 

void setup() {
Serial.begin(9600);
pinMode (12,INPUT); // вход сигнала 
}

void loop() {

uint32_t tic1 = asm_func(1);
uint32_t tic2 = asm_func(0);
float duty=  (1/ ((float)tic1/tic2))*100;
float freq= float(16E6/tic1);
freq<100? Serial.print(freq,1) : Serial.print(freq,0);
Serial.print("  ");
Serial.println( duty,0 );
  
  }

uint32_t asm_func(uint8_t flag){
asm volatile (         
"cli"                    "\n\t"
"mov r21,%0"    "\n\t" // флаг что считывать
//конфигурация таймеров
"ldi r18,0x83"            "\n\t" // GTCCR=0x83
"out 0x23,r18"            "\n\t" // GTCCR=0x83
"sts 0x80,__zero_reg__"   "\n\t" // TCCR1A=0
"ldi r19,0x4"             "\n\t" 
"sts 0x81,r19"            "\n\t" // TCCR1B=1<<CS12
"sts 0x85, __zero_reg__"  "\n\t"//TCNT1L=0
"sts 0x84, __zero_reg__"  "\n\t"//TCNT1H=0
"sts 0xB0,__zero_reg__"   "\n\t" // TCCR2A=0
"ldi r20,0x1"             "\n\t" 
"sts 0xB1,r20"            "\n\t" // TCCR2B=1<<CS20
"sts 0xB2,__zero_reg__"   "\n\t" // TCNT2=0
"sts 0x70,__zero_reg__"   "\n\t" // TIMSK2=0
// пройти если ноль
"wait_begin:"             "\n\t"
"sbic 0x03,4"             "\n\t"
"rjmp wait_begin"         "\n\t"
// пройти если единица -> первый импульс
"wait_start:"             "\n\t"
"sbis 0x3,4"              "\n\t"
"rjmp wait_start"         "\n\t"
"sts 0x43,__zero_reg__"   "\n\t" // GTCCR=0 старт синхра
 // импульс пошёл, ждём ноль
"wait_pause:"             "\n\t"
"sbic 0x03,4"             "\n\t"
"rjmp wait_pause"         "\n\t"
"cpi r21,__zero_reg__"    "\n\t" //
"brne duty"  "\n\t"
// импульс кончился, ждём  конца периода (единицу)
"wait_end:"               "\n\t"
"sbis 0x3,4"              "\n\t"
"rjmp wait_end"           "\n\t"
"duty:"                   "\n\t"
"out 0x23,r18"            "\n\t" // GTCCR=0x83 стоп таймеры
//вывод 3х байт
"lds r22, 0xB2"           "\n\t" //  tctn2  0 байт
"lds r23, 0x84"           "\n\t"  // TCNT1L 1 байт
"lds r24, 0x85"           "\n\t"  // TCNT1H 2 байт
"lds r25, __zero_reg__"   "\n\t" // пустой  3 байт
"sei"                     "\n\t"
::"r" (flag): ); }


 

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ua6em, ну вот вынудил проверить. Всё работает. Импульс сгенерил таймером2 с этой же дуни .

 

 

Для проверки генерю 2-м таймером импульс периодом 510 тактов (Phase Correct PWM Mode) , из которых 2 такта лог"0" и  508 тактов лог "1". 11 пин выхода таймера2 соединён с 8 пином входа ICP. Как видно по скриншоту определяет астрономически точно, но это потому что сигналы синхронны :)

Скетч. и готовый  hex

void setup() {
Serial.begin(9600);
pinMode(11,OUTPUT);
pinMode (8,INPUT);
TCCR2A=(1<<COM2A1)|(1<<COM2A0)|(1<<WGM20);
TCCR2B=1<<CS20;
TIMSK2=0;
OCR2A=1;
}

void loop() { Serial.println(asm_func()); }

uint16_t asm_func(){
asm volatile (         
"cli"                     "\n\t" // дабы никто не мешал
"sts 0x80,__zero_reg__"   "\n\t" // TCCR1A=0
"ldi r17,0x41"            "\n\t" // RISING ICP detect , start timer1
"sts 0x81,r17"            "\n\t" // TCCR1B= (1<<ICES1)|(1<<CS10)
"sbic 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"sbi 0x16,5"              "\n\t" // тогда clear ICF1
"wait_begin:"             "\n\t" // ждём флага ICF 
"sbis 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"rjmp wait_begin"         "\n\t" //пропускаем ход
"lds r22, 0x86"           "\n\t" //сохранить ICR1L  
"lds r23, 0x87"           "\n\t" //сохранить ICR1H
"sbi 0x16,5"              "\n\t" //clear ICF1
"ldi r19,0x01"            "\n\t" // FALLING ICP detect
"sts 0x81,r19"            "\n\t" // TCCR1B=(0<<ICES1)|(1<<CS10)
"wait_end:"               "\n\t" // ждём флага ICF 
"sbis 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"rjmp wait_end"           "\n\t" // пропускаем ход
"lds r24, 0x86"           "\n\t" //save ICR1L
"lds r25, 0x87"           "\n\t" //save ICR1H
"sub r24, r22"            "\n\t" //ICR1L_new-ICR1L_old
"sbc r25, r23"            "\n\t" //ICR1H_new-ICR1H_old
"sei"                     "\n\t"
::: ); }

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

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

dimax пишет:

ua6em, ну вот вынудил проверить. Всё работает. Импульс сгенерил таймером2 с этой же дуни .

 

 

Для проверки генерю 2-м таймером импульс периодом 510 тактов (Phase Correct PWM Mode) , из которых 2 такта лог"0" и  508 тактов лог "1". 11 пин выхода таймера2 соединён с 8 пином входа ICP. Как видно по скриншоту определяет астрономически точно, но это потому что сигналы синхронны :)

Скетч. и готовый  hex

void setup() {
Serial.begin(9600);
pinMode(11,OUTPUT);
pinMode (8,INPUT);
TCCR2A=(1<<COM2A1)|(1<<COM2A0)|(1<<WGM20);
TCCR2B=1<<CS20;
TIMSK2=0;
OCR2A=1;
}

void loop() { Serial.println(asm_func()); }

uint16_t asm_func(){
asm volatile (         
"cli"                     "\n\t" // дабы никто не мешал
"sts 0x80,__zero_reg__"   "\n\t" // TCCR1A=0
"ldi r17,0x41"            "\n\t" // RISING ICP detect , start timer1
"sts 0x81,r17"            "\n\t" // TCCR1B= (1<<ICES1)|(1<<CS10)
"sbic 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"sbi 0x16,5"              "\n\t" // тогда clear ICF1
"wait_begin:"             "\n\t" // ждём флага ICF 
"sbis 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"rjmp wait_begin"         "\n\t" //пропускаем ход
"lds r22, 0x86"           "\n\t" //сохранить ICR1L  
"lds r23, 0x87"           "\n\t" //сохранить ICR1H
"sbi 0x16,5"              "\n\t" //clear ICF1
"ldi r19,0x01"            "\n\t" // FALLING ICP detect
"sts 0x81,r19"            "\n\t" // TCCR1B=(0<<ICES1)|(1<<CS10)
"wait_end:"               "\n\t" // ждём флага ICF 
"sbis 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"rjmp wait_end"           "\n\t" // пропускаем ход
"lds r24, 0x86"           "\n\t" //save ICR1L
"lds r25, 0x87"           "\n\t" //save ICR1H
"sub r24, r22"            "\n\t" //ICR1L_new-ICR1L_old
"sbc r25, r23"            "\n\t" //ICR1H_new-ICR1H_old
"sei"                     "\n\t"
::: ); }

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

Залил! Выходит дело не в скетче и с 11 выхода и с приемника гонит 0 )))
Значит дело в самой ардуине

По объединению таймеров - может народ, что подскажет если не получится...
Идея достойная жеж

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ua6em пишет:

Залил! Выходит дело не в скетче и с 11 выхода и с приемника гонит 0 )))
Значит дело в самой ардуине

Фантастика какая-то, не может такого быть.  Там и без источника импульса , если просто воткнуть проводок  в 8 пин, то в сериал мониторе начинает мусор сыпаться. Импульс с 11 ноги кстати  ваш осциллограф может и не схватить, там всего 120нС в ноле.

PS: Кстати, а залил что, хекс или скомпилил у себя? Если второе, то доверия меньше))

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

dimax пишет:

ua6em пишет:

Залил! Выходит дело не в скетче и с 11 выхода и с приемника гонит 0 )))
Значит дело в самой ардуине

Фантастика какая-то, не может такого быть.  Там и без источника импульса , если просто воткнуть проводок  в 8 пин, то в сериал мониторе начинает мусор сыпаться. Импульс с 11 ноги кстати  ваш осциллограф может и не схватить, там всего 120нС в ноле.

PS: Кстати, а залил что, хекс или скомпилил у себя? Если второе, то доверия меньше))


Залил твой HEX вот так - C:\Users\User\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino14/bin/avrdude -CC:\Users\User\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino14/etc/avrdude.conf -v -patmega328p -carduino -PCOM3 -b57600 -D -Uflash:w:C:\ARDUINO\hex\icp_asm.ino.hex:i

Да и скомпилил, тот же баг, ясно жеж что в ардуине какой то косяк...
Залил в другую ардуино, тот же эффект...
Чудеса всё чудесатее и чудесатее при том, что твой код, что был в том же разделе выше работает на ура

 

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ua6em, а что за плата? У меня для Уно скомпилировано, соответссно с нановским бутлоадером может залиться криво. Лучше программатором влить.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

dimax пишет:

ua6em, а что за плата? У меня для Уно скомпилировано, соответссно с нановским бутлоадером может залиться криво. Лучше программатором влить.


платы nano от robodyn... понял, надо загрузчик от UNO вшить и попробовать

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ua6em, нашел косячёчек, компилятор не понимал что я что-то вывожу из функции  (но в 1.6.8 -понимал! ) Как грится век живи -век учись !   Проверил,  работает в 1.8.5

void setup() {
Serial.begin(9600);
pinMode(11,OUTPUT);
pinMode (8,INPUT);

TCCR2A=(1<<COM2A1)|(1<<COM2A0)|(1<<WGM20);
TCCR2B=1<<CS20;
TIMSK2=0;
OCR2A=1;

}

void loop() {

Serial.println(asm_func());

}


uint16_t asm_func(){
uint16_t data;
asm volatile (         
"cli"                     "\n\t" // дабы никто не мешал
"sts 0x80,__zero_reg__"   "\n\t" // TCCR1A=0
"ldi r17,0x41"            "\n\t" // RISING ICP detect , start timer1
"sts 0x81,r17"            "\n\t" // TCCR1B= (1<<ICES1)|(1<<CS10)
"sbic 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"sbi 0x16,5"              "\n\t" // тогда clear ICF1
"wait_begin:"             "\n\t" // ждём флага ICF 
"sbis 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"rjmp wait_begin"         "\n\t" //пропускаем ход
"lds r22, 0x86"           "\n\t" //сохранить ICR1L  
"lds r23, 0x87"           "\n\t" //сохранить ICR1H
"sbi 0x16,5"              "\n\t" //clear ICF1
"ldi r19,0x01"            "\n\t" // FALLING ICP detect
"sts 0x81,r19"            "\n\t" // TCCR1B=(0<<ICES1)|(1<<CS10)
"wait_end:"               "\n\t" // ждём флага ICF 
"sbis 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"rjmp wait_end"           "\n\t" // пропускаем ход
"lds r24, 0x86"           "\n\t" //save ICR1L
"lds r25, 0x87"           "\n\t" //save ICR1H
"sub r24, r22"            "\n\t" //ICR1L_new-ICR1L_old
"sbc r25, r23"            "\n\t" //ICR1H_new-ICR1H_old
"sei"                     "\n\t"
: "=w" (data)
:
: 
);
return data;
}

 

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

dimax пишет:

ua6em, нашел косячёчек, компилятор не понимал что я что-то вывожу из функции  (но в 1.6.8 -понимал! ) Как грится век живи -век учись !   Проверил,  работает в 1.8.5

void setup() {
Serial.begin(9600);
pinMode(11,OUTPUT);
pinMode (8,INPUT);

TCCR2A=(1<<COM2A1)|(1<<COM2A0)|(1<<WGM20);
TCCR2B=1<<CS20;
TIMSK2=0;
OCR2A=1;

}

void loop() {

Serial.println(asm_func());

}


uint16_t asm_func(){
uint16_t data;
asm volatile (         
"cli"                     "\n\t" // дабы никто не мешал
"sts 0x80,__zero_reg__"   "\n\t" // TCCR1A=0
"ldi r17,0x41"            "\n\t" // RISING ICP detect , start timer1
"sts 0x81,r17"            "\n\t" // TCCR1B= (1<<ICES1)|(1<<CS10)
"sbic 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"sbi 0x16,5"              "\n\t" // тогда clear ICF1
"wait_begin:"             "\n\t" // ждём флага ICF 
"sbis 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"rjmp wait_begin"         "\n\t" //пропускаем ход
"lds r22, 0x86"           "\n\t" //сохранить ICR1L  
"lds r23, 0x87"           "\n\t" //сохранить ICR1H
"sbi 0x16,5"              "\n\t" //clear ICF1
"ldi r19,0x01"            "\n\t" // FALLING ICP detect
"sts 0x81,r19"            "\n\t" // TCCR1B=(0<<ICES1)|(1<<CS10)
"wait_end:"               "\n\t" // ждём флага ICF 
"sbis 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"rjmp wait_end"           "\n\t" // пропускаем ход
"lds r24, 0x86"           "\n\t" //save ICR1L
"lds r25, 0x87"           "\n\t" //save ICR1H
"sub r24, r22"            "\n\t" //ICR1L_new-ICR1L_old
"sbc r25, r23"            "\n\t" //ICR1H_new-ICR1H_old
"sei"                     "\n\t"
: "=w" (data)
:
: 
);
return data;
}

 

А я уж постеснялся озвучивать, что и на UNO не пошло ни через компиляцию ни через загрузку HEX )))
Надеюсь, что исследование было полезным.
заработало, компилил в 1.8.7
Функция правда получается блокирующая, но не суть, интересно, как она отрабатывает с библиотекой servo.h

И сам отвечу, конфликтует! Не измеряет однако. Оно и не удивительно, используют же один таймер

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

 Коллега Arhat109-2 обнадёжил, что asm-вставки нормально работают в прерывании, поэтому решил воплотить  старую задумку -измерить  как быстро после появления стороннего сигнала на входе сработает прерывание INT0/1 Подал сигнал от внешнего генератора на INT0, в прерывании дёргаю ногу PB4(Пин12).  Опция ISR_NAKED отключает вмешательство компилятора, поэтому нога поднимается уже спустя 2 такта после входа прерывание. Посколько я не использовал рабочие регистры, то сохранять в стек ничего не нужно. Команда sbi не изменяет флагов, поэтому регистр SREG тоже нет нужды бэкапить. В общем прерывание реально пустое. Nop-ов добавил что б немного подзатянуть импульс.

#include <avr/interrupt.h>
int main (){
EICRA=1<<ISC01 | 1<<ISC00;//rising int0 
EIMSK=1<<INT0; //int 0 enable
DDRB|=1<<4;// pin12=output
sei();
while(1);
}

//компилятору запрещено добавлять свой код в прерывание
ISR(INT0_vect,ISR_NAKED){
asm( "sbi 0x05, 4" );//через 2 такта будт лог 1
asm( "nop" );  //1 такт
asm( "nop" );  //1 такт
asm( "nop" );  //1 такт
asm( "nop" );  //1 такт
asm( "nop" );  //1 такт
asm( "cbi 0x05, 4" ); //2 такта
asm( "reti" );  // 4 такта
}

И вот что получилось - На осциллограф выведено жёлтый луч -внешний сигнал пришедший на вход INT0 . Бирюзовый - сигнал снятый с 12 ноги.

Как видно по статистике дельта фронтов прыгает. В среднем  от появления сигнала на входе до выполнения прерывания - около 700nS без учёта двух тактов.  В реальной программе с сохранением регистров в стек добавится ещё куча команд. Счёт уже на микросекунды пойдёт.

И второй эксперимент - просто циклический опрос ноги.

int main (){
DDRB|=1<<4;// pin12=output
sei();
while(1){
 asm volatile (     
// пройти если единица -> первый импульс
"wait_start:"             "\n\t"
"sbis 0x9,2"              "\n\t" //Есть 1?
"rjmp wait_start"         "\n\t" // тогда пропускаем
"sbi 0x05, 4"             "\n\t" // PINB|=1<<4
"nop"                      "\n\t"
"nop"                      "\n\t"
"nop"                      "\n\t"
"nop"                      "\n\t"
"nop"                      "\n\t"
"cbi 0x05, 4"              "\n\t" // PINB&=~1<<4
"wait_low:"             "\n\t" 
"sbic 0x09,2"             "\n\t" // ноль пришёл?
"rjmp wait_low"         "\n\t" // тогда уходим


);
 
  };//end while
} 

Тут прога ждёт единицу, выдаёт импульс, затем снова ждёт ноль.   Скриншот выкладывать не буду, он такой-же только цифры другие - min290, max480, avg -387. Однако в 2 раза быстрее реагирует, хотя ожидал цифр ещё меньше. Но блокирующая. Но зато никаких потенциальных накладных расходов на сохранение в стек. Думайте сами, решайте сами, иметь или не иметь :)

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

По первому коду: вроде всё согласно даташита, ну или я не увидел косяков. Цена деления 200нсек.

1. Вход в naked перывание: 2 пуша адреса возврата (по 2 такта - SRAM!) + выборка 2 слов из таблицы векторов прерываний + исполнение команды jump (там лежит именно она). Итого 7 тактов.

2. Нога поднимается гарантировано ещё через 2 такта, уже имеем 9 тактов между фронтами;

3. Схема реагирования на входной сигнал по даташиту в среднем 2 такта (для INTx и 4 такта для PCINT, как понял) - вот ни 11 тактов между фронтами в среднем. Там жеж и писано что +- 1 такт, смотря когда придет сигнал .. полностью подтверждается вышим средним от 10 до 12 тактов.

4. Ширина импульса - это 5 тактов нопов + 2 такта на команду (задержан результат как первой так и второй ОДИНАКОВО) .. 7 тактов. Хорошо согласуется с вашей осцилограммой.

.. разве что похоже частота в 16Мгц несколько "гуляет" и слегка понижена. :)

P.S. Когда давал рекомендацию использовать таймер для определения длительностей импульсов (и не в одной теме сразу!), именно это и имел ввиду (пройдено уже давно) и даже где-то приятно что столько человек бросилось сразу же разбираться с режимом захвата таймера.

Он не даром сделан, ибо точнее. :)

P.P.S. уточнил по даташиту на Мега2560 - выделил жирным. Собственно, отсутствие на типовой меге2560 ножек ICP для режима "захват таймера" и подвигло отрисовать свою версию. Там ещё и PCINT далеко не все и третий UART отсутствует.

Замер длительности импульса ещё очень удобен для датчика цвета TCS3200, лучше которого мне пока повстречать не довелось. Видеть цвет при общей освещенности в 6-10лк .. это лучше чем "кошка на страже". :)

RuleZZZZ
Offline
Зарегистрирован: 16.02.2015

dimax пишет:

ua6em, нашел косячёчек, компилятор не понимал что я что-то вывожу из функции  (но в 1.6.8 -понимал! ) Как грится век живи -век учись !   Проверил,  работает в 1.8.5

void setup() {
Serial.begin(9600);
pinMode(11,OUTPUT);
pinMode (8,INPUT);

TCCR2A=(1<<COM2A1)|(1<<COM2A0)|(1<<WGM20);
TCCR2B=1<<CS20;
TIMSK2=0;
OCR2A=1;

}

void loop() {

Serial.println(asm_func());

}


uint16_t asm_func(){
uint16_t data;
asm volatile (         
"cli"                     "\n\t" // дабы никто не мешал
"sts 0x80,__zero_reg__"   "\n\t" // TCCR1A=0
"ldi r17,0x41"            "\n\t" // RISING ICP detect , start timer1
"sts 0x81,r17"            "\n\t" // TCCR1B= (1<<ICES1)|(1<<CS10)
"sbic 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"sbi 0x16,5"              "\n\t" // тогда clear ICF1
"wait_begin:"             "\n\t" // ждём флага ICF 
"sbis 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"rjmp wait_begin"         "\n\t" //пропускаем ход
"lds r22, 0x86"           "\n\t" //сохранить ICR1L  
"lds r23, 0x87"           "\n\t" //сохранить ICR1H
"sbi 0x16,5"              "\n\t" //clear ICF1
"ldi r19,0x01"            "\n\t" // FALLING ICP detect
"sts 0x81,r19"            "\n\t" // TCCR1B=(0<<ICES1)|(1<<CS10)
"wait_end:"               "\n\t" // ждём флага ICF 
"sbis 0x16,5"             "\n\t" // TIFR1& (1<<ICF1) Да?
"rjmp wait_end"           "\n\t" // пропускаем ход
"lds r24, 0x86"           "\n\t" //save ICR1L
"lds r25, 0x87"           "\n\t" //save ICR1H
"sub r24, r22"            "\n\t" //ICR1L_new-ICR1L_old
"sbc r25, r23"            "\n\t" //ICR1H_new-ICR1H_old
"sei"                     "\n\t"
: "=w" (data)
:
: 
);
return data;
}

 

Здравствуйте. Подскажите пожалуйста, как из вашей функции вывести не только длительность логической '1', а еще и длительность логического '0', который следует за '1'? Или даже лучше длительность импульса в сумме ('1' + '0'). Потом мне нужно будет просуммировать например 20 импульсов ('1' + '0') и уже исходя из этого посчитать частоту. Если со вторым я справлюсь, то с первым прошу у вас помощи.

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

RuleZZZZ, пример из ссылки #12 не подошёл?

RuleZZZZ
Offline
Зарегистрирован: 16.02.2015

dimax пишет:

RuleZZZZ, пример из ссылки #12 не подошёл?

Попробовал его - в симуляторе "Proteus 7" частота 5000 Гц, подаваемая на вход со скважностью 50%, скачет от 4600 Гц до 5000 Гц примерно, скважность скачет от 46% до 58% соответственно. Не могу понять в чем косяк. 

Green
Offline
Зарегистрирован: 01.10.2015

А помоему с ICP проще, и точнее, нежели с Асм вставками. Тут уже люди говорили по этому поводу. И измеряемая длительность не ограничивается 16-ю разрядами.

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Green, в принципе это и есть ICP, только без прерывания я в #34 писал в чём суть.

PS: после погружения в мир таймеров  stm32 всё это  стало каменным веком ;-)

Green
Offline
Зарегистрирован: 01.10.2015

Да согласен, для измерения коротких импульсов прерывания не подходят.
Как там любят говорить некоторые - кал мамонта?) 
Тут ещё и не с таким приходится работать.(

Green
Offline
Зарегистрирован: 01.10.2015

dimax пишет:

Green, в принципе это и есть ICP, только без прерывания я в #34 писал в чём суть.

Кстати, обработкку ICP можно сделать и без прерываний. Тогда все плюсы сохранятся.

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Green, так я и сделал без прерываний. Но ICP умеет ловить только один фронт, что не позволяет измерить длительность импульса. Поэтому пришлось на асме дописать ожидание флага, быстро перепрограммировать таймер на ловлю другого фронта.   Или есть какие то ещё варианты?

Green
Offline
Зарегистрирован: 01.10.2015

)) Да, всё верно, других вариантов не видно. Я не успел вчера посмотреть, извиняюсь. Подумал что это какая то софтовая реализация а-ля ICP.)

sadman41
Offline
Зарегистрирован: 19.10.2016

Green пишет:

Кстати, обработкку ICP можно сделать и без прерываний. Тогда все плюсы сохранятся.

Это с TIFR1&(1<<ICF1) ?

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

sadman41, ага. Можно всё это же на СИ написать, но вряд ли так компактно скомпилируется.

Green
Offline
Зарегистрирован: 01.10.2015

Тут, как обычно, дилема в скорости или оформлении. Думаю, на Си тоже будет компактно.

RuleZZZZ
Offline
Зарегистрирован: 16.02.2015

RuleZZZZ пишет:

Здравствуйте. Подскажите пожалуйста, как из вашей функции вывести не только длительность логической '1', а еще и длительность логического '0', который следует за '1'? Или даже лучше длительность импульса в сумме ('1' + '0'). Потом мне нужно будет просуммировать например 20 импульсов ('1' + '0') и уже исходя из этого посчитать частоту. Если со вторым я справлюсь, то с первым прошу у вас помощи.

Сдаюсь. В общем, со вторым я тоже не справился. :(

dimax, подскажите пожалуйста, как с помощью вашей ассемблерной вставки http://arduino.ru/forum/programmirovanie/schityvanie-impulsov#comment-218898 просуммировать например 20 первых импульсов для последующего расчета частоты (для уменьшения погрешности расчета) исходя уже из длины этих 20 импульсов, а не одного отдельно взятого импульса?

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

RuleZZZZ, ну и запускайте 20 раз функцию. Но это не имеет смысла, на низких частотах будет 20 раз одно и тоже число. А для измерения высоких этот алгоритм не подходит.

RuleZZZZ
Offline
Зарегистрирован: 16.02.2015

Частоты нужны от 3 кГц до 30 кГц примерно. Запускать функцию - это значит при появлении единицы на 12 порту запускать? И так 20 раз? Разве не будет задержки от считывания состояния пина?

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

RuleZZZZ, от 3кГц есть смысл использовать другой алгоритм -измерение кол-ва периодов за секунду, точность будет существенно выше. Есть стандартная библа для этого.

RuleZZZZ
Offline
Зарегистрирован: 16.02.2015

Если б все было так просто...

В том и дело, что мне не надо за секунду. А нужны всего-лишь 10-20 первых импульсов от начала их подачи, причем, что на 3 кГц, что на 30 кГц. При 3 кГц 10 имп. будут длиной около 3.3 мс, а при 30 кГц соответственно 330 мкс, то есть периоды в каждом случае будут разные и очень короткие относительно 1 секунды.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

RuleZZZZ пишет:

Если б все было так просто...

В том и дело, что мне не надо за секунду. А нужны всего-лишь 10-20 первых импульсов от начала их подачи, причем, что на 3 кГц, что на 30 кГц. При 3 кГц 10 имп. будут длиной около 3.3 мс, а при 30 кГц соответственно 330 мкс, то есть периоды в каждом случае будут разные и очень короткие относительно 1 секунды.

А может библиотека от ЕвгенийП TimeMeasure.h поможет отцу русской демократии )))

RuleZZZZ
Offline
Зарегистрирован: 16.02.2015

Спасибо за ссылку. Попробовал эту библиотеку - к сожалению, она измеряет только ширину импульса, когда на ноге '1' (либо я не разобрался), а мне нужно измерять общую ширину '1' + '0', чтобы потом рассчитать частоту.

Почитав еще несколько тем по прерываниям и таймерам, нарыл код от dimax, немного его допилил под свои нужны и получилось не то, что хотелось. Может кто наведет на путь истинный и подскажет в чем проблема и почему переменная 'alltic' вместо положенных просуммированных 64000 тиков за 20 периодов при 5 кГц (3200 тиков за 1 период * 20 периодов) выводит какое-то непонятное число 168341, после чего неправильно считает частоту сигнала? Если убрать из захвата прерываний переменные 'n' и 'alltic', и просто выводить переменную 'tic' как это было в первоначальном варианте, то все отлично выводится - 3200 (количество тиков за 1 период) и правильно считается частота - 5 кГц. Понимаю, что ошибка где-то в логике, но не могу понять, где именно.

 

#include <LiquidCrystal.h>
LiquidCrystal lcd(4, 5, 6, 7, 10, 9);
volatile unsigned int int_tic;
volatile unsigned long tic;
volatile unsigned long n, alltic = 0;
unsigned int freq;

void setup() 
{
lcd.begin(16, 2);
lcd.clear();   
pinMode (8,INPUT); //вход ICP
TCCR1B = 0; TCCR1A = 0; TCNT1 = 0;
TIMSK1 = (1<<ICIE1)|(1<<TOIE1);//создавать прерывание от сигнала на пине ICP1
TCCR1B = (1<<ICNC1)|(1<<ICES1)|(1<<CS10);//div 1
}

ISR (TIMER1_CAPT_vect) //прерывание захвата сигнала на входе ICP1
{ 
tic = ((uint32_t)int_tic<<16)|TCNT1; //подсчёт тиков
alltic += tic; 
n++;
ICR1=0; int_tic=0; TCNT1=0;  
}

ISR (TIMER1_OVF_vect) { //прерывание для счёта по переполнению uint
int_tic++; //считать переполнения через 65536 тактов
if (int_tic > (F_CPU/65536)) {tic=0; int_tic=0;} //если на входе пусто более секунды
}                                                //то обнулить счётчики

void loop()
{
if ( n == 20 )
{ 
  n = 0; 
  freq = (float(16E6/alltic))/20;
  lcd.clear();
  lcd.setCursor(0,0); 
  lcd.print(alltic);
  lcd.setCursor(0,1);
  lcd.println(freq);
  lcd.setCursor(6,1);
  lcd.println("Hz");
  delay(100);
  }
}

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

RuleZZZZ, а зачем вам суммировать 20 отсчётов? Там не должно быть разброса значений при хорошем сигнале. По поводу вопроса - перед выполнением арифметических операций с volatile-переменными размерностью >8бит  нужно запретить прерывания.

RuleZZZZ
Offline
Зарегистрирован: 16.02.2015

Сигнал будет с компаратора LM339. Суммированием хочу добиться лучшей точности, а также сдвига этих 20 периодов ближе-дальше по времени. То есть например считать не с первого импульса, а с 10-го или 20-го.

По поводу арифметических операций - то есть 'n' можно сделать 8бит и использовать внутри прерывания, а подсчет 'alltic' вывести в loop() с запретом прерываний в лупе во время этого счета?

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

RuleZZZZ, ну да. Можно ещё грамотнее -через макрос атомик блок. Я сам правда им не пользуюсь :)

RuleZZZZ
Offline
Зарегистрирован: 16.02.2015

Попробовал так сделать, но теперь переменная 'alltic' просто накапливает тики каждого периода и выводится на дисплей, то есть идет по нарастающей - 3200, потом 6400 и т.д. Частота при этом постоянная - '0'. В коде подсветил строки, которые поменял||добавил.

#include <LiquidCrystal.h>
LiquidCrystal lcd(4, 5, 6, 7, 10, 9);

volatile unsigned int int_tic;
volatile unsigned long tic;
volatile byte n, flag = 0;
volatile unsigned long alltic = 0;
unsigned int freq;

void setup() 
{
lcd.begin(16, 2);
lcd.clear();   
pinMode (8,INPUT); //вход ICP
TCCR1B = 0; TCCR1A = 0; TCNT1 = 0;
TIMSK1 = (1<<ICIE1)|(1<<TOIE1);//создавать прерывание от сигнала на пине ICP1
TCCR1B = (1<<ICNC1)|(1<<ICES1)|(1<<CS10);//div 1
}

ISR (TIMER1_CAPT_vect) //прерывание захвата сигнала на входе ICP1
{ 
tic = ((uint32_t)int_tic<<16)|TCNT1; //подсчёт тиков
n++; flag = 1;
ICR1=0; int_tic=0; TCNT1=0;  
}

ISR (TIMER1_OVF_vect) { //прерывание для счёта по переполнению uint
int_tic++; //считать переполнения через 65536 тактов
if (int_tic > (F_CPU/65536)) {tic=0; int_tic=0;} //если на входе пусто более секунды
}                                                //то обнулить счётчики

void loop()
{
  cli();
  if ( flag == 1 ) 
  {
    alltic += tic;
    flag = 0;
  }
  else if ( n == 20 )
  { 
  n = 0; 
  freq = (float(16E6/alltic))/20;
  alltic = 0;
  }
  sei();
  lcd.clear();
  lcd.setCursor(0,0); 
  lcd.print(alltic);
  lcd.setCursor(0,1);
  lcd.println(freq);
  lcd.setCursor(6,1);
  lcd.println("Hz");
  delay(100);
  }
}

 

RuleZZZZ
Offline
Зарегистрирован: 16.02.2015

В общем, если кому интересно, то вроде бы удалось победить свое ТЗ. Напоминаю, что нужно узнать время в тиках, которое необходимо посчитать за N-нное количество периодов сигнала, чтобы в дальнейшем уже высчитать частоту этого сигнала. Причем сигнал может быть с разной скважностью. Также необходимо, чтобы была возможность сдвига чтения сигналов по времени, то есть чтобы можно было сделать выборку N-нного количества импульсов не с первого, а например с 10-го, или 20-го импульса. Частоты нужны примерно от 3 кГц до 30 кГц, если больше 30 кГц, то лишним не будет.

Так вот, надыбал еще один код в интернете, на основании которого сделал то, что мне нужно, и кажысь оно работает, по крайней мере в симуляторе Протеуса. УРА, товарищи! Выкладываю, вдруг кому будет еще интересно. И возможно кто-то сможет подсказать, какие "подводные" камни могут приключиться в реальной работе? Ведь в "ISR (TIMER1_CAPT_vect)" слишком много операций производится. 

#include <LiquidCrystal.h>
LiquidCrystal lcd(4, 5, 6, 7, 10, 9);

volatile boolean first;
volatile boolean triggered;
volatile unsigned long overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;
unsigned long allTime = 0;
byte n = 0;
unsigned int freq;

// timer overflows (every 65536 counts)
ISR (TIMER1_OVF_vect) 
{
  overflowCount++;
}  // end of TIMER1_OVF_vect

ISR (TIMER1_CAPT_vect)
  {
   // n++;
  // grab counter value before it changes any more
  unsigned int timer1CounterValue;
  timer1CounterValue = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;
  
  // if just missed an overflow
  if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 0x7FFF)
    overflowCopy++;
  
  // wait until we noticed last one
  if (triggered)
    return;

  if (first)
    {
    startTime = (overflowCopy << 16) + timer1CounterValue;
    first = false;
    return;  
    }
    
  finishTime = (overflowCopy << 16) + timer1CounterValue;
  triggered = true;
  TIMSK1 = 0;    // no more interrupts for now
      
  }  // end of TIMER1_CAPT_vect
  
void prepareForInterrupts ()
  {
  noInterrupts ();  // protected code
  first = true;
  triggered = false;  // re-arm for next time
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  
  TIFR1 = bit (ICF1) | bit (TOV1);  // clear flags so we don't get a bogus interrupt
  TCNT1 = 0;          // Counter to zero
  overflowCount = 0;  // Therefore no overflows yet
  
  // Timer 1 - counts clock pulses
  TIMSK1 = bit (TOIE1) | bit (ICIE1);   // interrupt on Timer 1 overflow and input capture
  // start Timer 1, no prescaler
  TCCR1B =  bit (CS10) | bit (ICES1);  // plus Input Capture Edge Select (rising on D8)
  interrupts ();
  }  // end of prepareForInterrupts
  

void setup () 
  {
  lcd.begin(16, 2); //количество столбцов и строк дисплея
  lcd.clear(); // clear the screen
  
  // set up for interrupts
  prepareForInterrupts ();   
  } // end of setup

void loop () 
  {
  // wait till we have a reading
  if (!triggered)
    return;
 
  n++;
  unsigned long elapsedTime = finishTime - startTime;
  allTime += elapsedTime;
  
  if ( n == 20 )
  {
       
  // period is elapsed time
  //unsigned long elapsedTime = finishTime - startTime;
  // frequency is inverse of period, adjusted for clock period
  float freq = (F_CPU*n / float (allTime));  // each tick is 62.5 ns at 16 MHz
   
  lcd.clear();
  lcd.setCursor(0,0); 
  lcd.print(allTime);
  lcd.setCursor(0,1);
  lcd.println(freq);
  lcd.setCursor(6,1);
  lcd.println("Hz");
  allTime = 0; 
  n = 0;  
  // so we can read it  
  delay (100);
  }
  prepareForInterrupts ();   
}   // end of loop

 

nik182
Offline
Зарегистрирован: 04.05.2015

Вы в курсе куда пойдёт программа, если случайно выполнится 82 строка?

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

А оно ему ннада?

RuleZZZZ
Offline
Зарегистрирован: 16.02.2015

nik182 пишет:

Вы в курсе куда пойдёт программа, если случайно выполнится 82 строка?

Неа, не в курсе, подскажите пожалуйста. А что не так со строкой этой? То, что мне надо, программа выполняет - при подаче импульсов выводит на экран частоту, при прекращении подачи импульсов оставляет на экране последнее значение частоты. При повторной подаче импульсов очищает экран и снова выводит нужную частоту.

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

RuleZZZZ пишет:

nik182 пишет:

Вы в курсе куда пойдёт программа, если случайно выполнится 82 строка?

Неа, не в курсе, подскажите пожалуйста.

Перейдет к строке 79. ;)) Тут все нормально. В lcd не нужно println писать, просто это бессмысленно, а так - не перживай, если и есть пара недочетов, то не критичных. Только с "n" ты ничего не делаешь, случайно закомментил?

RuleZZZZ
Offline
Зарегистрирован: 16.02.2015

Спасибо за комментарий. 'n' в 21 строке - забыл убрать оттуда, так как пытался сразу там сделать подсчет количества периодов. Но потом сделал в лупе, так как понял, что триггер "щелкает" после захвата фронта импульса и идет дальнейшее выполнение программы. Думаю этого мне и не хватало, я никак не мог прикрутить "флаг", при котором программа в основном цикле будет видеть, что пришел фронт импульса.