Помогите с обработка ШИМ ардуиной

plotter1

✩✩✩✩✩✩✩
24 Ноя 2021
20
1
Прошу разобраться с непонятной хренотенью, с которой бодаюсь уже неделю.
Предыстория. Нужно ардуинкой преобразовать входящий шим сигнал (f = 250Hz) в собственно числовую величину этого сигнала (т.е. в проценты заполнения перевести), чтобы дальше уже этими значениями в коде пользоваться. Вначале я хотел просто натравить на шим два аппаратных прерывания на RISING и соответственно FALLING и в прерываниях записывать micros в переменные, а в лупе уже вычитать из одной переменной вторую, а из полученной дельты делать проценты. Но при активации сразу двух прерываний всё начинает работать крайне не стабильно и заставить этот код работать вообще не получилось ни в каком виде. Второй пришедший в голову вариант это использовать прерывание RISING как начало отсчета, а конец брать из быстрого digitalRead:
C++:
void setup() {
  Serial.begin(57600);
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt (2), pwmris, RISING);
  pinModeFast(3, INPUT);
  }

void pwmris() {
  flag = true;
  pwm = micros();
}
void loop() {
  bool fall = digitalReadFast(3);
  if (fall == false & flag) {
    pwm1 = micros();
    flag = false;
    delta = pwm1 - pwm;
    byte percent = map(delta, 12, 3460, 1, 99);
    Serial.println(percent);
  }
И всё это работает как часы, но только до тех пор, пока в код не вставишь ещё что нибудь. Стоит дописать например:
C++:
if (fall == false & micros() - pwm1 > 5000)  Serial.println("0%");
Как в порту изредка (раз в 0,1-0,2сек) начинает проскакивать единица независимо от скважности шим на входе. Т.е. переменная delta становится меньше 12мкс. Но почему???
 

PiratFox

★★★★★✩✩
13 Фев 2020
1,706
474
@plotter1, дело в некорректном алгоритме подсчёта и методе измерения. Не стоит изобретать велосипед. Для измерения длительности импульса на любом входе МК можно и нужно использовать функцию pulseIn(pin, level). Эта функция измеряет и возвращает длительность импульса на входе pin. Аргумент level задаёт измеряемый уровень: при значении HIGH измеряется длительность единицы, при LOW - длительность нуля. Так как частота(период) измеряемого сигнала известны, то не составит труда вычислить процент заполнения. Подробнее об этой функции, например здесь.
 
Изменено:
  • Лойс +1
Реакции: Старик Похабыч

Геннадий П

★★★★★★✩
14 Апр 2021
1,970
632
45
Проще использовать одно прерывание на изменение, в котором вначале считывать состояние пина.
 

poty

★★★★★★✩
19 Фев 2020
3,233
940
@plotter1, С какой точностью Вы пытаетесь определить заполнение? 250Гц - это период 4мс. Даже если взять 256 интервалов, типичных для ШИМ Ардуино, - это значит, что минимальный интервал - около 15 микросекунд. Т.е., при большом заполнении у Вас буквально десятки микросекунд для обработки. При малом заполнении нужно будет экономить каждый такт уже внутри первого прерывания, иначе второе будет задержано.
Нужно избавляться от блокирующих функций, типа Serial.println при измерениях. И учитывать, что прерывания в МК всё равно случаются (хотя бы для подсчёта millis), поэтому нужно решение минимизации вызванного этим джиттера.
То есть, я бы измерял не каждый период, а, допустим, через один или даже через несколько. Это даст время на адекватную обработку/передачу полученного результата. Это даст возможность выключить все прерывания, кроме необходимых для измерения, на интервал измерения. Инициировал бы сброс таймера от компаратора/ICP (без прерывания, это сэкономит пару микросекунд + не будет зависеть от других прерываний в МК) прерывание от среза импульса использовал бы для считывания TCNT - это давало бы сразу интервал, практически без вычислений. TCNT - это байт или слово, поэтому вычисления с ним происходят гораздо быстрее unsigned long для таймера micros() (который тоже не потребуется вызывать).
 
Изменено:
  • Лойс +1
Реакции: vortigont

plotter1

✩✩✩✩✩✩✩
24 Ноя 2021
20
1
То есть, я бы измерял не каждый период, а, допустим, через один или даже через несколько. Это даст время на адекватную обработку/передачу полученного результата.
А это интересная идея. Возьму на заметку.

@plotter1, дело в некорректном алгоритме подсчёта и методе измерения.
О да! Вы были правы на 100%!
Ваш покорный слуга решительно неправильно понимал логику работы активации прерываний. А оказывается в разделе "Генерирование и чтение сигналов" подробно всё расписано с примерами. Теперь я знаю, в чем я баран и теперь я чуть меньше баран :)
 
  • Ахах! +1
Реакции: PiratFox

Bruzzer

★★★✩✩✩✩
23 Май 2020
472
134
@plotter1,
Если вы все таки решите использовать прерывания, а не pulseIn, то обратите внимание, что в цикле loop надо запрещать прерывания при на время чтения - записи с много байтных переменных изменяемых в прерываниях. Если переменных несколько, то иногда лучше запретить прерывания, скопировать их все в локальные переменные, и потом разрешить прерывания.
Я советую начать с pulseIn, если допустимо ожидание равное периоду сигнала.
 

te238s

★★✩✩✩✩✩
14 Ноя 2021
374
98
Если частота заранее известна,логичней применить таймер в режиме захвата. Далее хоть каждый 1й,хоть каждый 10й период читать содержимое.
 

Bruzzer

★★★✩✩✩✩
23 Май 2020
472
134
@te238s,
Не понятна логика насчет заранее известной частоты. Режим захвата всегда хорош, когда нужна повышенная и предсказуемая точность. Но режим захвата отсутствует во "встроенных" функциях arduino, и с этого взгляда логично не использовать его, пока можно обойтись. А в данном случае максимальная погрешность < 10 мкс наверное допустима.
 
  • Лойс +1
Реакции: te238s

fidan

✩✩✩✩✩✩✩
26 Фев 2024
1
0
С pulseIn не получается , и еще функция map много ест времени.
Я отправил на 2 и 3 пин ардуино одину и ту же частоту,используя attachinter…(0,myfunction,Rising),
attachinter…(1,myfunction,falling) Считываем начало и конец импульса.
a в loop с milis отнимаем время начала и конеца, разница их будет длинна (в микрос)квадратного сигнала приходящего на 2.3 пин.

дальше с этим высчитанным временем можно в loop начать новый blink c dellay( высчитанное время);
 

vortigont

★★★★★★✩
24 Апр 2020
1,022
542
Saint-Petersburg, Russia
что бы правильно выработать решение нужно полноценно поставить задачу
- с какой точностью требуется определение скважности ШИМ
- как часто это нужно делать

тогда будет ясно где и чем можно идти на компромис и выработать решение которое не будет отжирать все ресурсы контроллера делая ненужную работу. Либо убедится в том что при требуемых параметрах программное решение на таком-то контроллере не имеет практическго смысла.
И да, "ардуино" это не контроллер.
 

bort707

★★★★★★✩
21 Сен 2020
3,058
910
Я отправил на 2 и 3 пин ардуино одну и ту же частоту,используя attachinter…(0,myfunction,Rising),
attachinter…(1,myfunction,falling) Считываем начало и конец импульса.
a в loop с milis отнимаем время начала и конца, разница их будет длина
В принципе, это уже правильный подход, но его можно чуть улучшить.
Например использовать один и тот же пин прерывания поочередно для Rising и Falling
А подсчет длительности вести без тяжелых функций миллис() и микрос, а запустив свой собственный счетчик на аппартном таймере.
Получается что-то типа этого (код для Атмега328):

C++:
uint16_t volatile LL,HL;
bool volatile up_edge = true;
int FRQ,DUTY;
void setup(){
Serial.begin(9600);
TCCR1A=0;TCCR1B=0;TCNT1 = 0;
TCCR1B =(1<<CS00)|(1<<CS01);//тик таймера 4мкс
  //First capture on rising edge
  TCCR1B |= (1 << ICES1);
  //Enable input capture interrupt
  TIMSK1 |= (1 << ICIE1);
  }

ISR(TIMER1_CAPT_vect){
  if (up_edge) {
    LL = ICR1;
    TCCR1B =(1<<CS00)|(1<<CS01);
    up_edge = false;
  }
  else {
    HL = ICR1;
    TCCR1B =(1<<CS00)|(1<<CS01)|(1 << ICES1);
    up_edge = true;
  }
 
  TCNT1 = 0;
}


void loop(){
    int _HL,_LL;
    cli();
    _HL=HL;
    _LL=LL;
    sei();
    FRQ=250000/(_HL+_LL);
    DUTY=(_HL*100)/(_HL+_LL);
    Serial.println(FRQ);
    Serial.println(DUTY);
   delay(1000);
}