Освоение Атмеги или кипит наш разум

msv

Код нормальный, хотя можно оптимизировать. А если учитывать переполнения, то будет и более-менее рабочий. 😃 Правда он вроде звучит как ответ на мою реплику “…не понял, как это можно решить именно на чисто аппаратном pwm”, а отношение ни к аппаратному pwm, ни даже к прерываниям не имеет…

Pavel_E

Помаленьку продвигаемся. Вчера заставил 8-битный таймер генерировать 54Гц и управлять машинкой регулируемыми импульсами длиной от 0 до 2048мкс с шагом 8мкс. Это повысило реальную точность позиционирования реальной машинки в 12 раз по сравнению с режимом PWM. Уже достаточно, но с минимальными усложнениями текста точность еще можно повысить раза в 1,5 а частоту подобрать ровно 50Гц.

Алогритм следующий:
Нам нужно отмерить 2 интервала - один регулируемый (1…2мс, лучше 1,8…2,2мс) и один можно нерегулируемый - 20мс +/-. Я сделал это так:
Используется 8-ми битный таймер и 2 прерывания - по срабатыванию компаратора (TIM2_COMP) и по переполнению (TIM2_OVF). При этом, для повышения точности установки первого интервала (время импульса) нужно максимальную его продолжительность подвести как можно ближе к максимальному значению таймера (255).
Первый интервал задается включением таймера с режимом PWM и отсекается прерыванием по окончанию импульса заданной продолжительности. Продолжительность задается значением регистра OCR2. По срабатыванию прерывания режим PWM отключается.
Второй интервал задается тупо количеством циклов таймера. Каждый цикл отнимает единичку от переменной и когда она становится “0”, снова включает режим PWM и снова накидывает количество циклов до нужного значения. Это значение считаем арифметически и подбираем под нужную частоту импульсов.

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

Выкладываю листинг. Прошу покритиковать.

unsigned char tim2_countdown=8; //задаем количество циклов таймера для установки частоты импульсов.
// 1000000Гц/8/256/50Гц=9,76… Ближайшее: 10 - один на первый цикл, остается 9.

interrupt [TIM2_COMP] void timer2_compare(void) //прерывание по окончанию импульса (режим PWM)
{
TCCR2=0b01001010; // отключаем режим PWM
PORTC.1=0; // выключаем стороннюю ногу.
}

interrupt [TIM2_OVF] void timer2_ovf(void) //прерывание по переполнению timer2
{
if (tim2_countdown!=0) //смотрим сколько циклов осталось до окончания периода
tim2_countdown=tim2_countdown-1;
else
{
TCCR2=0b01101010; //снова включили режим PWM
OCR2=stop_signal; //установили отсечку импульса PWM
tim2_countdown=8; //заново установили количество циклов
PORTC.1=1; // включили стороннюю ногу. На ней начался сигнал.
}
}

void main(void)
{
int stop_signal=188; //переменная длинны импульса, начальное значение соотв. 1500мкс

PORTC=0x00; //включаем порт C на выход, сигнал на все ноги
DDRC=0xFF;

TCCR2=0b01101010; // включить таймер, режим PWM
TCNT2=0x00; //начальное значение таймера
OCR2=stop_signal; //начальное значение длины импульса
TIMSK=0b11000000; // включены прерывания по окончанию импульса PWM и переполнению таймера

#asm(“sei”) //какой-то рудимент, оно тут нужно?

while (1)
{
for(stop_signal=110;stop_signal<=255;stop_signal=stop_signal+1) //меняем длину импульса от 888мкс ((110+1)*8) до 2048мкс ((255+1*8)
{
led(stop_signal); //выводим на светодиодный индикатор значение
delay_ms(50); // чтобы машинка успевала отрабатывать
}
}
}

Вешаем машинку на PC0 - шевелится почти что в полном диапазоне. Задача управления машинкой решена.

sht0p0r

регистр TCNT доступен на чтение и запись, считать до 255 не обязательно.

TCCR2|=(1<<COM20); // это установит в 1 COM20 знать конкретный бит необязательно они уже проименованы.

TCCR2&=(~(1<<COM20)) // это сбросит в 0 COM20 не затрагивая остальные биты регистра
ненадо заботится о том чтобы сохранить/знать текущее состояние
когда проверяешь “0” то достаточно if (! tim2_countdown)

Pavel_E

Спасибо за коммент, учту. Эти вещи мне в принципе знакомы, но специально пока делаю текст наиболее наглядным. Чтобы мозг не отвлекался на расшифровку собственного кода.

sht0p0r:

регистр TCNT доступен на чтение и запись, считать до 255 не обязательно.

Это я в курсе, но в моем алгоритме писать в TCNT2 вроде не зачем. Ну есть один момент - для того, чтобы точно вывести частоту в 50Гц можно один цикл сократить, задав ему начало счтета не с 0, а с какой-то величины. Но это уже на этапе причесывания кода.

Тут есть один косяк в программе, который я вчера понять не успел. А именно, для вывода сигнала я вынуждено использую ногу PC0 (регистр PORTC.0). Хотя работа одного цикла таймера в PWM режиме должна автоматом поднимать и опускать ногу OC2(PD7). А она это делает только один раз. Или где-то ошибка, или PWM не желает запускаться. Хотя компаратор запускается и работает… Как разберусь, можно будет еще 2 операции убрать из прерываний - установка и уборка состояня ноги PC0.

sht0p0r
Pavel_E:

#asm(“sei”) //какой-то рудимент, оно тут нужно?

это асемблерная вставка в с достаточно sei();// разрешение прераваний
cli(); соответственно запрет
DDR регистр на выход
правильно поставить WGM
больше ничего ненадо.

Pavel_E

Всё, поставленные задачи выполнены и перевыполнены. Всем участникам спасибо за помощь в понимании этого безобразия! Особенно тов. Штопору - за деятельное участие и вне рамок данного форума.

Итак, МК умеет:

  • брать сигнал от приемника по двум каналам;
  • расшифровывать и переводить в значение длительности импульсов в мкс;
  • обрабатывать (в моем случае - нормировать в диапазон 1000…2000мкс);
  • усреднять значение по одному из каналов и выводить на четырехразрядный светодиодный цифровой индикатор;
  • генерировать сигнал на двух каналах.

Как обещано, выкладываю исходник на С для конструктивной критики.
Тов. MSV, будешь код контроля акков прикручивать?

servo_control.rar

msv

Что явно бросилось в глаза -

  • запрещать прерывания в конце обработчика нет необходимости.
  • индикацию лучше полностью перенести в фоновый цикл.
    Я немного другую задачу ставлю, для начала полноценный декодер.
    За паяльник пока не брался, а код в принципе на скору руку слепил, в VMLAM уже трудится. Программно нарощенным таймером0, замеряю импульсы PPM с отбраковкой явной фигни (шума). Усредняю и по готовности от приема корректного кадра или по тайм-ауту, генерю канальные импульсы с помощью TIM1 поочередно на 8 каналов. Но это так, баловство… Идеи, сколько интересного можно замутить, пока явно опережают исполнение… 😃 В перспективе это будет супер-декодер (конечно с fail-save, поисковая сирена, контроль акков, вывод дискретных сигналов в натуральном(дискретном) виде, вывод аналоговых каналов в виде ШИМ, возможность установки замедления по каналам, да там глядишь и хотя бы простенький логер итд итп). Все это конечно будет конфигурироваться с компа спец. софтом, ну для начала по RS232.
    Пока не слишком много времени заниматься этим проектом (а будет еще меньше 😦), но если сотворю- продам за бешенные бабки китайцам… 😃
Pavel_E
msv:

Что явно бросилось в глаза -

  • запрещать прерывания в конце обработчика нет необходимости.
  • индикацию лучше полностью перенести в фоновый цикл.

Мерси за коммент! Индикация недоработана сознательно - буду адаптировать под конкретные задачи, это так база. В пределе она должна уметь автоматом выводить числа со знаками и плавующей точкой, символы… Только не нужно пока, а вычислений добавляется.

А каков смысл переноса индикации в фоновый цикл? Т.е. в обработчик прерывания, да? Что так, что так будет ресурсы отжирать. Только в основном цикле программы это можно делать хоть раз в секунду, а таймер крутится сильно быстрее. Сейчас у меня по таймеру только переключение светящихся светодиодов. А вычисления какому диоду светиться делаются в программе. Если вычисления опоздали, ну и ладно, второй раз то же число отобразится, никто и не заметит.

msv

Да нет…, фоновый цикл это и есть основной цикл проги. Туда и пихаются все фоновые задачи (не критичные к времени реакции/исполнения). Дело в том, что даже разрешив прерывания в начале обработчика прерывания, несколько тактов (лень смотреть сколько) проц будет с запрещенными прерываниями. В принципе ничего страшного, но если можно это избежать, то лучше и не делать.

Pavel_E
msv:

В принципе ничего страшного, но если можно это избежать, то лучше и не делать.

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

Nick_Shl
msv:

разрешив прерывания в начале обработчика прерывания

А вот с этим надо быть очень аккуратным! Если прерывание не успеет обработаться до возникновения его самого, и условия не изменятся - пожираем стек и падаем…

msv

Да, еще… длинный код в обработчике можно выполнить примерно так:
PORTA=led_str[tim2cnt];
PORTB&=0xf0;
PORTB|=1<<tim2cnt;
if(++tim2cnt>=4) tim2cnt=0;
Ну это, так… косметика…

Pavel_E
Nick_Shl:

А вот с этим надо быть очень аккуратным! Если прерывание не успеет обработаться до возникновения его самого, и условия не изменятся - пожираем стек и падаем…

Провел эксперимент. Засунул в прерывание delay_ms(100) - т.е. задержку примерно на 40 циклов запуска этого прерывания. Все равно работает, зараза! Глотает другие прерывания, мерцает диодами, но машинками худо-бедно управляет. Как объяснить сей феномен?

msv:

Да, еще… длинный код в обработчике можно выполнить примерно так: PORTA=led_str[tim2cnt]; PORTB&=0xf0; PORTB|=1<=4) tim2cnt=0; Ну это, так… косметика…

Да, думал я это автоматизировать. Только все получались какие-то громоздкие и долгие конструкции. PORTB&=0xf0; в голову не приходило, мерси.