РРМ индикатор

Mishanya

Хочу сделать РРМ индикатор с тестером серво машинок.
На данный момент я стою в самом начале изучения AVRов.
Принцип работы самого индикатора представляю себе так.
PPM с приёмника подаю на ножку INT0 прерывание срабатывает при любом изменение на входе.
Таймер подщитывает длину импульса и если длина в приделе от 1-2ms выводим результат на LCD в ms
или в градусах поворота серво машинки.
Проблемма на данный момент в конфигурации таймера и фюзов.
Вот тут вариант схемки:

serg111

Вчем конкретно проблемма? Таймер не считает или инт0 не отрабатывает?

Mishanya
serg111:

Вчем конкретно проблемма? Таймер не считает или инт0 не отрабатывает?

Таймер считает, но результат каждый раз разный, такое впечатление, что он живёт своей жизнью, не обращая внимание на INT0.

PigTail

Хм… конкретнее алгоритм, что и как считаете, зачем любое изменение на входе, а не конкретный фронт, какой таймер пользуете, установки таймера, почему на INT0, а не на ICP1, если хотите работающую вещь получить ставьте кварц.

Boroda
Mishanya:

Таймер считает, но результат каждый раз разный

Так и есть, таймер выдаёт лишь текущее значение “своего” времени. Надо считать значение по восходящему фронту INT, а затем вычесть его из значения, считанного по падающему фронту. Разность и будет длительностью импульса. Ещё можно по возрастающему фронту обнулить таймер, тогда на прерывании по спаду он будет содержать уже правильное значение PPM. Думайте, пишите, Вы идёте в правильном направлении!
Бывает, что прерывание INT временно заблокировано другим, а таймер продолжает считать. К моменту разблокировки он будет содержать ошибку времени ожидания. Для борьбы с такой неприятностью AVR содержит механизм захвата внешнего ШИМ. При событии на ноге ICP, текущее значение таймера сохраняется в регистре ICR и выставляется флаг запроса прерывания. В документации всё подробно описано.

Mishanya
Boroda:

Так и есть, таймер выдаёт лишь текущее значение “своего” времени. Надо считать значение по восходящему фронту INT, а затем вычесть его из значения, считанного по падающему фронту. Разность и будет длительностью импульса. Ещё можно по возрастающему фронту обнулить таймер, тогда на прерывании по спаду он будет содержать уже правильное значение PPM.

Как мне в одном прирывании отличить возрастающий фронт, от спадающего?

PigTail

какой таймер пользуете, установки таймера, почему на INT0, а не на ICP1, если хотите работающую вещь получить ставьте кварц.

Какой таймер лучше для моей цели 16 или 8 битный (сейчас пользую timer0 8bit).
ICP1 занят под LCD_RS, кварц нет проблем, поставлю.

EagleB3
Boroda:

AVR содержит механизм захвата внешнего ШИМ

Как эта штука называется в даташитах? Чтобы поиском в даташите найти…

Хвост_Слона

16-битный таймер, кварца для Ваших целей не надо, фузы ставить под внутрений генератор на 8 мгц, таймер тактировать 1000 кгц. 1тик=1 микросекунда. Из-за двухбайтовой арифметики таймер можно не сбрасывать, а просто вычислять duration=timer-old_timer; (переменные int)

Boroda
Mishanya:

Как мне в одном прирывании отличить возрастающий фронт, от спадающего?

Прочитать значение PIND.2 (INT0)

EagleB3:

Как эта штука называется в даташитах? Чтобы поиском в даташите найти…

Input Capture Unit

VRV

скажу пару слов:
даже если с таймером разберетесь и он правилоьно определит длительность каналов- показания всеравно будут прыгать, т.к в пачке идет 6-8 импульсов, соответствующих каждому каналу. Т.о. примерный алгоритм следующий:
1- найти синхроимпульс, длительность которого больше 2.5 мс(у отдельных передатчиков на канал может идти больше 2 мс)
2-начать измерение длительности импулсов, до седующего синхро. данные измерения писать в массив,
3- вывести значения длительности для выбранного канала.

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

EagleB3
Mishanya:

Хочу сделать РРМ индикатор с тестером серво машинок.
На данный момент я стою в самом начале изучения AVRов.

Загляни сюда. Исходники там есть, а превратить декодер в сервотестер, КМК, задачка в самый раз для начинающего.

Mishanya

Спасибо, почитаю. Жаль, что коментарии не видно, белеберда какая-то.

VRV
Mishanya:

Спасибо, почитаю. Жаль, что коментарии не видно, белеберда какая-то.

в блокноте открой-скорее всего с кодировкой трабл
а вооще сам проект для разбора не самый удачный(хотя если разберешся-останеться ток жки подцепить)

8 days later
Mishanya

Таймер бежит, результат на LCD каждый раз другой и разброс большой.
Где-то ошибаюсь. Может, подскажите где?
Прошу сильно не ругать.

#include <mega8.h>
#include <delay.h>
#include <stdio.h>
#include <lcd.h>

void ppm_display (void);

char lcd_buffer[33];
int lcd_1,lcd_2;

// Alphanumeric LCD Module functions
#asm
.equ __lcd_port=0x18 ;PORTB
#endasm

// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void)
{
if (PIND.2 == 1)
{
TIMSK=0x04; // Timer1 Overflow Interrupt Enable
TCCR1A=0x00; // Timer1 Control Register A
TCCR1B=0x01; // Start Timer1
TCNT1H=0x00; // Timer1 TCNT1H
TCNT1L=0x00; // Timer1 TCNT1L
}
else
{
TCCR1B=0x00; // stop Timer1
lcd_1 = TCNT1L;
lcd_2 = TCNT1H;
ppm_display();
}
}

//Timer 0 interrupt
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{

}

void main(void)
{
// Port B initialization
PORTB=0xFF;
DDRB=0xFF;

// Port C,D initialization
PORTC=0x00;
DDRC=0x00;
PORTD=0x00;
DDRD=0x00;

// Timer/Counter 1 initialization
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;

// External Interrupt(s) initialization
GICR|=0x40; // INT0: On
MCUCR=0x01; // INT0 Mode: Any change
GIFR=0x40; // INT1: Off

// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x04;

lcd_init(16); // LCD module initialization

#asm(“sei”) // Global enable interrupts

while (1)
{
}
}

void ppm_display (void)
{
#asm(“cli”)
lcd_gotoxy(0,0);
sprintf(lcd_buffer,“PPM L=%dms”,lcd_1);
lcd_puts(lcd_buffer);
lcd_gotoxy(0,1);
sprintf(lcd_buffer,“PPM H=%dms”,lcd_2);
lcd_puts(lcd_buffer);
delay_ms(200); //delay 200ms
lcd_clear(); //Lcd clear
lcd_1 = 0x00;
lcd_2 = 0x00;
#asm(“sei”)
}

VRV

прочитай 10 пост.
а вобще то сервртестер сам предполагает генерацию импульсов(те передатчик ненужен!), т.о. осушествляется генерация импульсов длительностью 1-2 мкс(например от данных крутилки->ацп) и периодом 20 мс.

Mishanya
VRV:

а вобще то сервртестер сам предполагает генерацию импульсов(те передатчик ненужен!), т.о. осушествляется генерация импульсов длительностью 1-2 мкс(например от данных крутилки->ацп) и периодом 20 мс.

В том-то и дело, что мне передатчик нужен. А 10й пост я читал, но только мне он не как не помог.

PigTail

Для начала вынести ppm_display(); из прерывания в main, пока Вы там отображаете на индикаторе что угодно может происходить, так никто не делает. И кстати на какой частоте у Вас все это происходит и что Вы в действительности смотрите канальный импульс или пакет PPM ?

EagleB3

…Ну и у ног же у Меги8 много, а задач у Вас - мало. Так используйте две ноги внешних прерываний. Перемкните их; одну (скажем, PD2) настройте на прерывание по фронту, другую (удобнее PB0 (=ICP1), но если PortB занят обслуживанием дисплея, то можно и PD3) настройте на прерывание по тылу. В Int0 будете таймер запускать, а в INT1 останавливать и ресетить.

Я когда-то делал чем-то похожий дивайс - хронограф для измерения скорости пули пневматической винтовки. Разница только в том, что у меня были два сигнала - от старт- и стоп-оптопары. Камень - ATMEGA8535, сделано было примерно так:

//Управление прерываниями
#define TOIE1  2 // Бит 2 (в регистре TIMSK)
#define TEC1   5 // Бит 5 (в регистре TIMSK)
#define INT0   6 // Бит 6 (в регистре GICR)
#define TOV1   2 // Бит 2 (в регистре TIFR)
#define ICF1   5 // Бит 5 (в регистре TIFR)
#define INTF0  6 // Бит 6 (в регистре GIFR)

#define I_T1Ovf_Enable  TIMSK |=  (1<<TOIE1) //Разрешить прер. переполнения T1
#define I_T1Ovf_Disable TIMSK &= ~(1<<TOIE1) //Запретить прер. переполнения T1
#define I_T1Cpt_Enable  TIMSK |=  (1<<TEC1)  //Разрешить прер. захвата T1
#define I_T1Cpt_Disable TIMSK &= ~(1<<TEC1)  //Запретить прер. захвата T1
#define I_INT0_Enable  GICR |=  (1<<INT0)    //Разрешить прер. INT0
#define I_INT0_Disable GICR &= ~(1<<INT0)    //Запретить прер. INT0

#define F_T1Ovf_Clr  TIFR |=  (1<<TOV1)      //Сброс флага прер. переполнения T1
#define F_T1Ovf_Set  TIFR &= ~(1<<TOV1)      //Уст. флага  прер. переполнения T1
#define F_T1Cpt_Clr TIFR |=  (1<<ICF1)       //Сброс флага прер. захвата T1
#define F_T1Cpt_Set TIFR &= ~(1<<ICF1)       //Уст. флага прер. захвата T1
#define F_INT0_Clr   GIFR |=  (1<<INTF0)     //Сброс флага прер. INT0
#define F_INT0_Set   GIFR &= ~(1<<INTF0)     //Уст. флага прер. INT0


// External Interrupt 0 service routine
interrupt [EXT_INT0] void ext_int0_isr(void) {
   //TCCR1A=0x00; //Не нужно, ибо и так ==0.
    #ifdef OnNewton // Тестирование падающим сердечником
        TCCR1B=0x83;   // 288,000 kHz - захват с антидребезгом
    #else
        TCCR1B=0x81;   // 18432,000 kHz - захват с антидребезгом
    #endif

    I_INT0_Disable;     //Запрещаем реагировать на Int0
    I_T1Ovf_Enable;    //Разрешаем прерывание по переполнению - вдруг пропустим стоп...
    I_T1Cpt_Enable;    //Разрешаем прерывание ICP1 - ждем фронт стоп-импульса
    F_T1Ovf_Clr;        //Очищаем флаги прерываний
    F_T1Cpt_Clr;
    F_INT0_Clr;
}


// Timer 1 input capture interrupt service routine
interrupt [TIM1_CAPT] void timer1_capt_isr(void) {
    resT=ICR1;   // Сохраняем подсчитанное значение
    TCCR1B=0x00;  // Останов таймера
    TCCR1A=0x00;
    TCNT1H=0x00; // Обнуляем таймер для последующего запуска
    TCNT1L=0x00;

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

    I_INT0_Enable;       //Разрешаем прерывание Int0 - ждем фронт старт-импульса
    I_T1Ovf_Disable;    //Запрещаем прерывание по переполнению - таймер сейчас стоит
    I_T1Cpt_Disable;    //Запрещаем прерывание ICP1 - пока нет старта, стоп не ловим
    F_T1Ovf_Clr;         //Очищаем флаги прерываний
    F_T1Cpt_Clr;
    F_INT0_Clr;
}

Я эмбеддер не больно продвинутый; может, я тут слишком сильно “дую на воду” - но дивайс мой давно и успешно работает.

Mishanya
PigTail:

Для начала вынести ppm_display(); из прерывания в main, пока Вы там отображаете на индикаторе что угодно может происходить, так никто не делает. И кстати на какой частоте у Вас все это происходит и что Вы в действительности смотрите канальный импульс или пакет PPM ?

Если я вынесу ppm_display(); из прерывания в main, как я буду обращаться к сабрутине void ppm_display(void)?
Всё проиходит с чстотой 8MHz.

Я хочу мереть положительную часть импульса от подъема до спада с приёмника, именно она отвечает за угол поворота сервы.

EagleB3

Насчет 8 МГц - это ты маненько погорячился. Не все.
16-ти битный таймер должен натикать 65535 значений не более чем за, скажем, 2.1mS (2mS+5%). Исходя из этого ты выбираешь наименьший допустимый делитель таймера. Если этот делитель =1 - ну, значит, и тик таймера у тебя тоже 8МГц.
А дисплей обновлять - это песня отдельная. Тем более, HD44780 - с собственной оперативной памятью.
Если хочешь обновлять непременно после _каждого_ пойманного импульса, то можешь поставить и в прерывание “тыла”. Но тогда, пока выполняется функция обновления дисплея, все внешние прерывания должны быть запрещены. Успеешь закончить отображение и разрешить INT0 - поймаешь следующий импульс. Не успеешь - пропустишь следующий (или следующие…) импульсы.

Только зачем так часто? Глаз все равно этого не оценит. Чаще 25 раз в секунду - только мультики рисовать.

Взгляни на обычный код, который генерит CVAVR. Там инициализация камня, потом - цикл while.
Вот внутри цикла while у тебя будет вывод на дисплей ( ppm_display(); ). Выводиться будет то, что посчитано в прерывании останова. Так часто, как это позволит содержимое прочих строк внутри while. Так что, скорее всего, еще и замедление (внутри while) ставить придется, чтобы цифры на дисплее не мельтешили. Два - три раза в секунду, а то и за значением следить не сможешь. Будут цифры мелькать, как сотые секунды на электронном секундомере. Оно тебе надо?
А вот во время этих замедлений/простоев ты будешь замерять/обсчитывать свой импульс. “Обсчитывать” - скажем, можешь поставить осреднение медианным фильтром (сохраняешь некоторое число последних замеров (условно, 10); сортируешь по возрастанию, края (условно - по три значения) отбрасываешь; по оставшимся 4-м значениям считаешь среднее арифметическое и его показываешь).

PigTail
Mishanya:

Если я вынесу ppm_display(); из прерывания в main, как я буду обращаться к сабрутине void ppm_display(void)?

Я хочу мереть … отвечает за угол поворота сервы.

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

Boroda

В процедуре дисплея есть задержка на 200 мс. Обычно компиляторы делают её на таймере, уведомляя об это программиста только в документации. Если ppm_display рабочая, я-бы вообще без прерываний сделал, совсем их запретив. Обнуляем таймер и циклом ждём единицу на PPM ноге. По её приходу пускаем таймер, ждём обнуления ноги, и по событию сохраняем значение таймера. Полученое число отправляем в ЖК и терпеливо ждём возврата управления. И так по кольцу.
Когда код заработает правильно, его уже можно “причесать” и усложнить по необходимости.


Чтобы импульс гарантированно измерить с нарастающего фронта, перед ожиданием нарастания надо ещё добавить пустое ожидание спада.