Как принять сигнал с приемника в AVR (PWM|Digital)

Bare

Вот накидал тестовую программку для ардуино, на основе исходников одного коптера (aeroquad). Читает 4 канала. Правил на основе своих скудных знаний, часть кода так и не понял до конца. 😦 Тестировал на серво-тестере (аппы рядом нет), пробовал цеплять тестер сразу на 4 канала, вроде читает. Если зацепить только на один канал, то несколько секунд все нормально, потом появляется паразитный сигнал на других каналах. Почему? Я понять не могу. Еще один глюк, неизвестный мне, : если в самом начале скетча не объявить любую переменную типа байт, то компилироваться не хочет, ругается на ошибки определения переменных. Реализовано на одном прерывание.
На ардуино, приемник, цеплять на 4,5,6,7 digital pin или на меге PCINT20,PCINT21,PCINT22,PCINT23.

rc_pwm.zip

Tester500
Bare:

Еще один глюк, неизвестный мне, : если в самом начале скетча не объявить любую переменную типа байт, то компилироваться не хочет, ругается на ошибки определения переменных.

В RC_CH4.H на 42 строке есть такое:
const int getRaw(byte channel)
Вот компилятор и ругается:
In file included from rc_pwm.cpp:5:
rc_ch4.h:42: error: ‘byte’ has not been declared

Bare
Tester500:

В RC_CH4.H на 42 строке есть такое:
const int getRaw(byte channel)
Вот компилятор и ругается:
In file included from rc_pwm.cpp:5:
rc_ch4.h:42: error: ‘byte’ has not been declared

byte - тип переменной.
channel - имя переменной.
На что тут ругаться?
А если я напишу byte bbbbb1; перед инклудом? то почему не ругается?

P.S. Программированием , последний раз, я занимался лет 8 назад. Может я что-то совсем забыл. Вроде переменная объявлена правильно, или я не прав? Подскажите plz…

Tester500

Если кусок кода с ошибками перенести из .h в основной код, то ошибки нет. Как там настроено с обработкой .h разбираться надо.

PS:
Если не секрет, что вообще планируется? А то брать проект и ковырять его куски обычно сложнее чем собрать свое.

AndyBig

Возможно компилятор, видя в исходнике тип byte автоматом подключает определение этого типа… Хотя он должен и в заголовочнике так же реагировать, так что это вопрос реализации компилера 😃
Вставьте в начало заголовочника

#ifndef byte
#define byte unsigned char
#endif

Ну или как в этом компиляторе выглядит директива прекомпилера #ifndef (может #ifnotdef или #if !defined())

Bare
Tester500:

Если кусок кода с ошибками перенести из .h в основной код, то ошибки нет. Как там настроено с обработкой .h разбираться надо.

PS:
Если не секрет, что вообще планируется? А то брать проект и ковырять его куски обычно сложнее чем собрать свое.

Значит все таки глюк препроцессора, Для препроцессора инклуд, это просто вставить указанный файл в инклуде, в текущий. И по идее инклуд = перенос кода в основной. Я раньше сталкивался с подобными проблемами, когда забывал поставить скобку или точку с запятой где-то.

Планируется собрать устройство на ардуино нано имеющее следующий функционал :

  1. Цепляется к датчику холла искрового зажигания или ставится свой датчик холла на выходной вал двигателя.
  2. Возможность подключения дисплея к устройству для использования его в качестве тахометра, и для настройки устройства.
  3. К устройству подключается канал газа и серва газа.
  4. Устройство контролирует минимальные обороты, и не дает им опускаться ниже заданных.
  5. Возможность задать кривую газу, для аппаратуры которая это не поддерживают.
  6. И еще что-то придумается по дороге.

Первоначально, я хотел написать библиотечку для считывания с каналов приемника сигнала. А то без нее остальное не получится.

Соответственно исходники потом выложить народу под GNU.

Tester500
Bare:

Кстати, зачем нужен подтягивающий резистор?

R3?

А без таймера никак. Чем еще времянку мерить? Либо аппаратный таймер и процессор свободен, или программный процессор этим занят.

Bare
Tester500:

R3?

А без таймера никак. Чем еще времянку мерить? Либо аппаратный таймер и процессор свободен, или программный процессор этим занят.

Без дополнительного таймера сейчас все отлично работает. Используется только стандартная функция micros();. Помехи на других каналах убрал с помощью включения встроенного подтягивающего резистора. Теперь осталось только все привести в красивый вид - оформить в виде стандартной библиотеки для ардуино. Ниже пример работы с сервой, подключенной на 9 цифровой пин ардуино.

rc_pwm-111127a.zip

ilyxascrat
Bare:

Сейчас нашел в интернете
Документация по таймеру на ардуино
Документация по прерываниям на ардуино
Пытаюсь понять исходники библиотеки servo, немного не хватает знаний. Не пойму какие таймеры использует эта библиотека?

не успел нажать на кнопку спасибо за ссылки. хоть так нажму.

Probelzaelo
Tester500:

Длина импульса 2.1 мс, минимум 0.8 мс Кто что еще подскажет?

У моих дистух заявлены длины импульсов у одной 1000-2000мс а у другой 1200-2400 )) итого - надо настраиваться на передатчик …
0.8 - 2.4милисек или 800-2400микро сек (в зависимости кому в чем удобнее…) наверное в целых числах правильнее в принципе если удалить нолик и принять все более мелкие колебания за шум, то можно поместиться в один байт (int) … получается точность позиции один градус для сервы, и вдвое меньше на стиках, на сколько вероятно что на столько точные движения на пальцАх? к тому же по любому импульсы дрожат, ну никак не получаются точной длины даже если не трогать стики…

AndyBig:

Нафига синхронизация между ними?

По большому счету нафиг не нужна, но для порядка! опять же при синхронизированной паре можно достаточно точно отследить момент когда пора быть посылке, и даже делать попытки восстановления частично потеряного сигнала, к тому же можно оперативно делать правильные выводы о потере связи …

Bare:

Возможность подключения дисплея к устройству для использования его в качестве тахометра, и для настройки устройства.

На арду есть готовая либа частотомер )) считает встроенным 16 бит таймером-счетчиком напрямую до 8 Мгц только в путь… чем не тахометр? периодичность чтения задается вторым таймером и перемножается на коэффициэнт пересчета, на ЛСД выводится разумеется уже так как попросишь…

Tester500:

Настраиваем таймер так, чтобы он выдавал прерывания каждую (2мс-1мс)/256. 2мс общая длина импульса, 1мс длительность сигнала до начала отсчета.

Как то не серьйозно это, с таким успехом можно и в loop читать состояние всех портов и не задумываться об остальном… Прерывания хороши какраз тем, что бездельника - ЦПУ зовут только когда он нужен… а в остальное время хоть в спячку пусть идет, заодно и кушать будет меньше 😉

Bare:

Если я повешу первый обработчик на INT0 (PD2) а второй на PCINT2, и у меня придет сигнал на PD2, то сработают оба прерывания?

Сработают. при этом если запросы пришли одновременно то(на примере ATmega128 проц):
“Меньшие адреса обладают более высоким уровнем приоритетом. Сброс (RESET) имеет наивысший приоритет, за ним следует INT0 – запрос на внешнее прерывание по входу INT0.”
непосредственный и весьма полезный источник - www.gaw.ru/html.cgi/txt/doc/micros/avr/…/1_2.htm (ближе к концу текста)
Полный перечень векторов приведен к примеру тут же www.gaw.ru/html.cgi/txt/doc/micros/avr/…/6.htm
При вызове обработчика прерывания, пока выполняется одно прерывание все остальные прерывания запрещаются, при reti автоматически разрешаются…

Bare
Probelzaelo:

Как то не серьйозно это, с таким успехом можно и в loop читать состояние всех портов и не задумываться об остальном… Прерывания хороши какраз тем, что бездельника - ЦПУ зовут только когда он нужен… а в остальное время хоть в спячку пусть идет, заодно и кушать будет меньше

Я написал маленькую библиотечку (смотри вложение выше). Там все нормально работает. Не напрягаясь читает 4 канала. Использует только одно прерывание PCINT. Не использует таймер. Перед чтением данных, надо вызвать функцию read. Эта функция записывает актуальные данные в массив. А потом читаем из массива показания. Как только появится немного времени, приведу её в божеский вид.

Probelzaelo
Bare:

Я написал маленькую библиотечку

я ее уже взял, но пока не разглядывал… все равно в ближайших планах было разобраться с этой задачей, так что тоже покопаюсь…
скорее бы до планов добраться …

10 days later
Mastar

Я раньше тоже заморачивался с чтением отдельных каналов и решил взять отдельно PPM из приемника и по одному входу считывать данные для восьми каналов.

Ниже готовый скетчь для моей АрдуиныМега, там стоит АтМега 1280, но кому интересно, может переделать, но данные как вкопанные.

#include <TimerOne.h>

volatile uint16_t timerCount; // переменная счетчика
#define BTNPIN 2 //Входной сигнал PPM на 2 пине
int ppmImpuls[9]; // Массив импульсов для паузы и 8 каналов
int chImpuls[9] = {1000, 100, 100, 100, 100, 100, 100, 100, 100}; // Массив средних значений для паузы и 8 каналов
int chanel = 0 ; // Номер канала
int upImpuls = 50; // Положительный импульс
int Error = 2 ; // Переменная ошибок. 0 - помеха, 1 - длинная пауза, 2 - хороший сигнал

void setup()
{
Serial.begin(115200);
pinMode (BTNPIN, INPUT);
Timer1.initialize(10);
Timer1.attachInterrupt(callback);
attachInterrupt(0, impDown, RISING);
}

void loop()
{
for (int i=0; i <= 8; i++)
{
chImpuls[i] = (3 * chImpuls[i] + ppmImpuls[i]) / 4 ; // усреднение 4 импульсов
Serial.print(“CH”); Serial.print(i); Serial.print(“=”); Serial.print(chImpuls[i]);
Serial.print(" I=“); Serial.print(upImpuls); Serial.print(” “);
}
Serial.println(”");
}

void callback() { timerCount++; } //--------- Счетчик------------

void impUp() //---------- Функция обработки положительного импульса
{
detachInterrupt(0) ;
upImpuls = timerCount ;
if ( upImpuls > 43 && upImpuls < 51) { Error = 2 ; } else { Error = 0 ; delay(50) ; chanel = 0 ; }
timerCount = 0 ;
attachInterrupt(0, impDown, RISING);
}

void impDown() // Функция обработки отрицательного импульса
{
int ppm = timerCount;
detachInterrupt(0);
if( ppm > 400 ) { chanel = 0 ; } else { chanel ++ ; }
ppmImpuls[chanel] = ppm ;
timerCount=0;
attachInterrupt(0, impUp, FALLING);
}

Probelzaelo

А для чего используешь такое “хитрое” переназначение вектора прерывания да еще и внутри каждого обработчика?
detachInterrupt/attachInterrupt; Как то не айс, может как то попытать вроде такого?:

int Int0Pin=2;
void setup()
{


attachInterrupt(0, AllPulse, CHANGE);
}

Void AllPulse()
{
TimeInt = timerCount;
timerCount=0;

IF( digitalRead(Int0Pin) > 0)
{
impUP()
}
else
{
impDown()
}
}

void impUp() //---------- Функция обработки положительного импульса
{
upImpuls = timeInt ;
if ( upImpuls > 43 && upImpuls < 51) { Error = 2 ; } else { Error = 0 ; delay(50) ; chanel = 0 ; }
}

void impDown() // Функция обработки отрицательного импульса
{
int ppm = timeInt;
if( ppm > 400 ) { chanel = 0 ; } else { chanel ++ ; }
ppmImpuls[chanel] = ppm ;
}

Mastar

Напишите окончательный скетчь для получения значений каждого канала. Ваш вариант с функцией IF( digitalRead(Int0Pin) > 0) не устраивает самой функцией, которая только условно стабильна. Попробуйте мой и сравните со своим, в жизни, но не на эмуляторах.

Probelzaelo
Mastar:

Напишите окончательный скетчь для получения значений каждого канала. Ваш вариант с функцией IF( digitalRead(Int0Pin) > 0) не устраивает самой функцией, которая только условно стабильна. Попробуйте мой и сравните со своим, в жизни, но не на эмуляторах.

Не знаю что нестабильного в чтении состояния, пока еще ни разу жаловаться не приходилось. единственно внутри IF чтение мне самому не очень нравится, надежнее бы промежуточную переменную использовать, но как правило и так работает…
Пробовать буду, но немного поздже 😉

Mastar

Да нет, я немного о другом, что будет в главном цикле, а тут только одноразовый запуск прерывания без его отключения. Если его не сделать, то много функций перестанут работать правильно.

У меня одновременно с чтением PPM идет сбор данных с GPS, управление сервами и много-много всего другого где есть и работа с 2 портами и micros() и delayMicroseconds().

Для справочки по прерыванию

Замечание по использованию

Внутри функции обработки прерывания не работает delay(), значения возвращаемые millis() не изменяются. Возможна потеря данный передаваемых по последовательному соединению (Serial data) в момент выполнения функциии обработки прерывания. Переменные, изменяемые в функции, должным быть объявлены как volatile.

Полное описание arduino.ru/Reference/AttachInterrupt

Поэтому после измерения длительности, я отключаю прерывания, а потом снова включаю.

Вот данные на ком-порту
CH0-пауза между пакетами
I-импульс между каналами
CH1-CH8 - соответственно сами каналы.

CH0=1035 I=46 CH1=101 I=46 CH2=98 I=46 CH3=106 I=46 CH4=101 I=46 CH5=48 I=46 CH6=101 I=48 CH7=94 I=47 CH8=104 I=47
CH0=1035 I=48 CH1=101 I=47 CH2=98 I=47 CH3=106 I=47 CH4=101 I=46 CH5=48 I=46 CH6=101 I=46 CH7=94 I=46 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=48 CH3=106 I=47 CH4=101 I=47 CH5=48 I=47 CH6=101 I=47 CH7=94 I=46 CH8=104 I=47
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=46 CH3=106 I=46 CH4=101 I=46 CH5=48 I=46 CH6=101 I=48 CH7=94 I=48 CH8=104 I=48
CH0=1035 I=48 CH1=101 I=47 CH2=98 I=47 CH3=106 I=46 CH4=101 I=47 CH5=48 I=47 CH6=101 I=47 CH7=94 I=47 CH8=104 I=47
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=47 CH3=106 I=48 CH4=101 I=48 CH5=48 I=47 CH6=101 I=47 CH7=94 I=46 CH8=104 I=46
CH0=1035 I=47 CH1=101 I=47 CH2=98 I=47 CH3=106 I=47 CH4=101 I=47 CH5=48 I=47 CH6=101 I=48 CH7=94 I=48 CH8=104 I=47
CH0=1035 I=47 CH1=101 I=47 CH2=98 I=47 CH3=106 I=46 CH4=101 I=46 CH5=48 I=46 CH6=101 I=46 CH7=94 I=46 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=48 CH3=106 I=48 CH4=101 I=47 CH5=48 I=47 CH6=101 I=46 CH7=94 I=47 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=46 CH3=106 I=46 CH4=101 I=46 CH5=48 I=46 CH6=101 I=48 CH7=94 I=47 CH8=104 I=47
CH0=1035 I=47 CH1=101 I=47 CH2=98 I=46 CH3=106 I=47 CH4=101 I=46 CH5=48 I=46 CH6=101 I=46 CH7=94 I=46 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=48 CH3=106 I=48 CH4=101 I=47 CH5=48 I=47 CH6=101 I=47 CH7=94 I=47 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=46 CH3=106 I=46 CH4=101 I=46 CH5=48 I=46 CH6=101 I=47 CH7=94 I=47 CH8=104 I=48
CH0=1035 I=47 CH1=101 I=47 CH2=98 I=47 CH3=106 I=46 CH4=101 I=47 CH5=48 I=47 CH6=101 I=47 CH7=94 I=47 CH8=104 I=47
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=47 CH3=106 I=47 CH4=101 I=48 CH5=48 I=47 CH6=101 I=46 CH7=94 I=46 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=46 CH3=106 I=46 CH4=101 I=46 CH5=48 I=46 CH6=101 I=48 CH7=94 I=48 CH8=104 I=47
CH0=1035 I=48 CH1=101 I=47 CH2=98 I=46 CH3=106 I=46 CH4=101 I=46 CH5=48 I=46 CH6=101 I=46 CH7=94 I=46 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=48 CH3=106 I=48 CH4=101 I=47 CH5=48 I=46 CH6=101 I=46 CH7=94 I=47 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=46 CH3=106 I=46 CH4=101 I=46 CH5=48 I=46 CH6=101 I=47 CH7=94 I=48 CH8=104 I=47
CH0=1035 I=47 CH1=101 I=46 CH2=98 I=46 CH3=106 I=46 CH4=101 I=46 CH5=48 I=46 CH6=101 I=46 CH7=94 I=46 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=48 CH3=106 I=47 CH4=101 I=47 CH5=48 I=47 CH6=101 I=47 CH7=94 I=47 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=46 CH3=106 I=46 CH4=101 I=46 CH5=48 I=48 CH6=101 I=48 CH7=94 I=47 CH8=104 I=48
CH0=1035 I=47 CH1=101 I=47 CH2=98 I=47 CH3=106 I=47 CH4=101 I=46 CH5=48 I=46 CH6=101 I=46 CH7=94 I=46 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=48 CH2=98 I=48 CH3=106 I=47 CH4=101 I=48 CH5=48 I=47 CH6=101 I=46 CH7=94 I=46 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=46 CH2=98 I=46 CH3=106 I=46 CH4=101 I=46 CH5=48 I=48 CH6=101 I=48 CH7=94 I=48 CH8=104 I=47
CH0=1035 I=48 CH1=101 I=47 CH2=98 I=46 CH3=106 I=46 CH4=101 I=46 CH5=48 I=46 CH6=101 I=46 CH7=94 I=46 CH8=104 I=46
CH0=1035 I=46 CH1=101 I=48 CH2=98 I=48 CH3=106 I=47 CH4=101 I=47 CH5=48 I=46 CH6=101 I=46 CH7=94 I=46 CH8=104 I=46

Probelzaelo
Mastar:

что будет в главном цикле, а тут только одноразовый запуск прерывания без его отключения. Если его не сделать, то много функций перестанут работать правильно.

Это еще почему? в обработчик прерывания будем попадать кратковременно при получении каждого перепада на входе. При этом получаем длительность последнего импульса/паузы.
Во время нахождения в обработчике прерывания запрещены (условно, скидывается флаг EI, (бит 7 SREG)) ибо так устроен проц. по выходу - комманде reti прерывание снова разрешается (установится EI) в посте #136 в конце есть 2 ссылочки на не плохую русскую доку.
внутри обработчика функции micros() и delayMicroseconds() не работают и не нужны. Но “посещение” прерывания никак не влияет на их работу в основном цикле…

Mastar:

Внутри функции обработки прерывания не работает delay(), значения возвращаемые millis() не изменяются.

Согласен, если из обработчика прерывания вызывать delay() то результат может оказаться не предсказуемым, а millis(), если она почему то заинтересует, вернет значение которое было на момент входа в прерывание … внутри обработчика в принципе и не следует задаваться какими то временными интервалами, оттуда вообще надо уходить как можно быстрее, чтобы не тратить чужое время… Полагаю что все обработки переменных и анализ можно и нужно делать в основном цикле программы… тогда ни что не будет мешать принять данные и от других портов и устройств… у процессора “сил” достаточно много чтобы со всеми справиться, да еще и “перекурить” )

Mastar

У меня процик будет под завязку загружен, т.к. я решил на него повесить почти все, вот мой проект, правда на другом форуме и если это нарушает правила, то дайте знать или удалите ссылку.