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

Pavel_E
Andrey73:

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

Именно поэтому тема и создана. Чтобы разобраться. Так что все советы всячески приветствуются!

За вчера думал попробовать прерывания и генерацию ШИМ. А успел освоить только вывод десятичной цифры на светодиодный индикатор. Осознал, что решительно не хватает ног на амтеге8 - индикация всего двух знаков отжирает 9 выводов! На все прочее остается маааало…

Читал даташит. Вроде все написано, а че-та непонятно. Объясните темному, что лучше использовать для срабатывания прерывания по сигналу от приемника?
Варианты: PD2 (INT0 - внешнее прерывание), PD3 (INT1- внешнее прерывание), PB0 (ICP1 - Timer/Counter1 Input Capture Pin), еще что-то? Как я это вижу - по изменению сигнала на ноге с 0 на 1 или с 1 на 0 включается прерывание, которое запускает или останавливает таймер. Если останавливает, то выдает в программу значение счетчика таймера.

Так?

msv:

ЗЫ Тоже вынашиваю идею сделать интегрированное устройство декодер+поисковую сиренку+контроль акков борта. Пока не определился на PIC или меге. Буду с интересом следить за темой.

Могу предложить поучаствовать 😃 Например по коду программы в части контроля акков борта. Пока не видел грамотных индикаторов липолек, которые бы кричали и моргали при просадке ЛЮБОГО элемента батареи ниже 2в. Индицировать-то все индицируют, а надо орать в голос.

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

Поисковая сиренка + фотовспышка (чтоб моргала в темное время суток) у меня тоже в планах.

sht0p0r
Pavel_E:

Читал даташит. Вроде все написано, а че-та непонятно. Объясните темному, что лучше использовать для срабатывания прерывания по сигналу от приемника?
Варианты: PD2 (INT0 - внешнее прерывание), PD3 (INT1- внешнее прерывание), PB0 (ICP1 - Timer/Counter1 Input Capture Pin), еще что-то? Как я это вижу - по изменению сигнала на ноге с 0 на 1 или с 1 на 0 включается прерывание, которое запускает или останавливает таймер. Если останавливает, то выдает в программу значение счетчика таймера.

Так?

нет не так
int 0,1 генерируют прерывания и запускают обработчик события (что в нем будет ваша проблема 😃 )
ICP1 может генерировать прерывание, а может и нет (только поднять флаг, что ICR1 обновлен) в любом случае в регистре ICR1 доступно положение счетчика таймера 1 в момент срабатывания ICP1.
таймеры при этом не перезапускаются пока вы это несделаете в ручную.
у вас 2 пути …
я сделал 2 канала с приемника по int и один, с датчика хола, по ICP

Pavel_E
sht0p0r:

int 0,1 генерируют прерывания и запускают обработчик события (что в нем будет ваша проблема )

Тогда так:
Вариант 1: на int0 или int1 заведен сигнал с приемника. Биты ISC11 и ISC10 регистра MCU установлены в значения 0 и 1 соответственно. Это должно установить режим “Any logical change on INT1 generates an interrupt request.” Т.е. любое изменение логического состояния вызывает прерывание. Правильно я понимаю? Тогда останется в обработчике прерывания посчитать время между его срабатываниями по нулю и по единице. Это можно сделать, запустив любой таймер по единице и остановив по нулю. Потом обнулить и снова в путь.

или Вариант 2:
Использовать ICP1. Установить прерывание по изменению состояния ноги и при срабатывании оного читать из регистра ICR1 сколько там натикало. И никаких таймеров запускать не надо.

А?

P.S.Я тоже вдруг сильно захотел 2 канала приемника и 1 датчик холла для контроля что вал крутится…

sht0p0r

я немного не так сделал
1 прерыванийе ставим на фронт
ждем
обработчик
1.1 берем timestamp с таймера 1 (он 16 бит)
1.2 проверяем что за прерывание пришло фронт или спад
1.3 пишем timestamp в timestamp_R или в timestamp_F (соответственно)
1.4 переключаем прерывание на определение фронта или спада (в зависсимости от того что пришло)
1.5 если пришел спад то поднимаем флаг(готово время для рассчета ширины импульса)
2 в основной программе
while(1){
2.1 если поднят флаг то рассчитываем ширину импулса (с учетом длинны счета таймера и переполнения, если таковое было между фронтом и спадом )
все можно наслаждаться.
2.2 если поднят флагг…
2.3 если поднят флаггг…
2.4 если поднят флаггггг…
2.n придушить бешеную собаку!!!
}
как использовать icp1 для тахометра попробуйте придумать сами
главное не перегружайте прерывания.
есть 2 пути
1 разрешить прерывание внутри преывания результаттом может стать stack overflow 😃
2 написать прерывания максимально короткими и быстрыми. таким образом быть почти уверенным, что прерывания не произойдут во время обработки другого.
тут опять же решать вам.
зы попробуйте написать сами экспириенс просто попрет…

Pavel_E

Тов. Штопор, мерси за алгоритм! Вроде вкурил что и как, пора в железо воплощать.

sht0p0r:

1 разрешить прерывание внутри преывания результаттом может стать stack overflow

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

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

Я еще подумал, может плюнуть на это дело и тупо мерить усредненное конденсатором напряжение выхода приемника? Но эта мысль вредная, пока отгоняю.

sht0p0r

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

последняя мысль очень вредная. имхо
книжка товарища Шпака “порограмирование на С авр и пик” как то так называется. вам поможет освоить это б…ство.

valera_o

если будете покупать, купите мегу88, для внешних прерываний можно использовать практически все ноги портов, памяти столько же, опять же мега8 снята с производства.
Книга “Микроконтроллеры AVR семейства Mega” А.В.Евстифеев 2007
тоже хороша для освоения устройсва контроллеров.

Boroda
Pavel_E:

или Вариант 2:
Использовать ICP1. Установить прерывание по изменению состояния ноги и при срабатывании оного читать из регистра ICR1 сколько там натикало. И никаких таймеров запускать не надо.

Надо. В регистр ICP записывается значение из РАБОТАЮЩЕГО tcnt1 именно в
момент прихода сигнала на ногу. Таймер при этом не останавливается. Когда ICP будет прочитан в прерывании или основном теле, вам не надо будет задумываться о том сколько тиков прошло с момента прихода фронта до момента чтения регистра таймера.

PigTail
valera_o:

… опять же мега8 снята с производства.

И где это такие сведения? 😉 www.atmel.com/dyn/products/devices.asp?Status=Matu…® 8-Bit RISC
Если топикстартеру ща еще подсунуть 88 с разницей в регистрах после 8, то он точно закипит 😃

Pavel_E

м-да…
😵
надо выспаться. может уложится по полочкам и будет просто 😃

Слушайте, как вы женам объясняете, чем занимаетесь, а?

Aleksey_Gorelikov

ну объясните ей, что вот такая мелкая микросхемка - ни что иное, как полноценный компутер, что такая штука может являться брелком к авто сигнализации, самой сигнализацией, что авто, что домашней, “мозгами” стиральной машины, пультом от телека, часами с будильником и т.д. и т.п. Что все зависит от того, какую программу для нее написать. Глядишь, попросит мк с акселерометром в утюг встроить, чтоб отключался сам, если его на любимых трусах забыли и дырку не прожег. 😃

Pavel_E

Новости с фронтов.
Убит вечер пятницы, вся суббота и воскресенье. Поставленная задача решена на 70%. Умею управлять сервой и выводить произвольную информацию на четырехразрядный светодиодный индикатор. Осталось научиться расшифровывать сигнал с приемника, но вроде уже понятно как.

Не хватило ножек. Мигрировал на 40ногую атмегу16. Зверь.

Заодно вопрос-восклицание. 8-и битный таймер не может достойно управлять сервой в режиме PWM. Расчет простой - максимум на период 20мс (50Гц) приходится 256 отсчетов таймера. Т.е. 12,8 отсчета на одну миллисекунду. Нам нужен регулируемый импульс длительностью в диапазоне от 0,8 до 2,2 мс. Т.е. в лучшем случае - 18 отсчетов. Если рабочий угол сервы 180градусов, то получается можем задавать не точнее 10 градусов! Короче, в сад или в 16 бит. А 16 бит жалко на такое дело транжирить…

msv

Для декодирования по алгоритму sht0p0r можно и 8-разрядный таймер программно наростить и брать с него timestamp. А 16-разрядный использовать для ШИМА.
А что собственно за девайс изобретаете?
Если это декодер, никак не врублюсь как аппаратным PWM можно раздать импульсы на 8 каналов…

Pavel_E
sht0p0r:

я немного не так сделал
1 прерыванийе ставим на фронт
ждем
обработчик
1.1 берем timestamp с таймера 1 (он 16 бит)
1.2 проверяем что за прерывание пришло фронт или спад
1.3 пишем timestamp в timestamp_R или в timestamp_F (соответственно)
1.4 переключаем прерывание на определение фронта или спада (в зависсимости от того что пришло)
1.5 если пришел спад то поднимаем флаг(готово время для рассчета ширины импульса)
2 в основной программе
while(1){
2.1 если поднят флаг то рассчитываем ширину импулса (с учетом длинны счета таймера и переполнения, если таковое было между фронтом и спадом )
все можно наслаждаться.
2.2 если поднят флагг…
2.3 если поднят флаггг…
2.4 если поднят флаггггг…
2.n придушить бешеную собаку!!!
}

А вот вопрос. Под “поднятием флага” понимается просто запись значения в какую-нибудь переменную для обработки программой? А как оно будет работать, если программа сложная и быстродействия не хватит, чтобы успевать между импульсами (20мс)?. Т.е. может быть, что флаг поднят, но значение по фронту уже перезаписалось другим импульсом, не успев быть учтенным в программе. И тут она начнет его учитывать…

msv:

Для декодирования по алгоритму sht0p0r можно и 8-разрядный таймер программно наростить и брать с него timestamp. А 16-разрядный использовать для ШИМА.

Наверно так и сделаю. Но уж очень удобна автоматизация работы внешних прерываний именно с 16-битным таймером. Тем более, на атмеге16 внешних прерываний 3.

Альтернативная мысль: “наростить программно” ШИМ на 8-битном таймере. Т.е. в цикл таймера загнать время импульса, а скважность считать по количеству циклов, используя для хранения количества переменные в программе. По переполнению таймера проверять сколько циклов натикало и если хватит, обнуляться и пускать сигнал заново.

А можно и не парить мозг, а оставить этот режим для того, для чего он и предназначен. Т.е. генерировать ШИМ для управления, например, коллекторным двигателем.

msv:

А что собственно за девайс изобретаете?

Да пока просто разобраться хочу что такое микроконтроллер, что он может и что я на нем могу. Вообще хочется научиться брать любой сигнал, обрабатывать программно и генерировать свой сигнал. Ну а в частности - написать приблуду для автоматического управления комнатной автомашинкой, чтобы при попадании в препятствие давала задний ход, разворачивлась и т.п. Лампочками моргала и сиреной гудела. И о разряде липолек информировала. И каналы управления инвертировала если ее перевернуть вверх ногами. Сына веселить.

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

Еще хочу брать NMEA с GPS, углы с гироскопов, высоту с бародатчиков и обороты с винта. И фигачить все это по радиоканалу в цифровом виде на землю. На земле принимать и выдавать на ноутбук в красивой графической форме в виде авиационных приборов.

И чтобы если стемнело, то включалась поисковая фотовспышка хотя бы раз в минуту.

Еще много чего хочу 😃 Вопрос, в какой момент это надоест или когда снова начнется работа…

sht0p0r

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

Pavel_E:

А вот вопрос. Под “поднятием флага”…

да

Pavel_E:

А как оно будет работать, если программа сложная и быстродействия не хватит, чтобы успевать между импульсами (20мс)?

просто не обработаете управляющий импульс.
добавте проверку флага в прерывании int(0,1) если не сброшен то return из прерывания.
ног там более чем достаточно что-то вы включаете не рационально. повесте вашу схемку, думаю ее можно опримизировать.
что мешает гнерировать 16 битным таймером pwm и паралельно брать со счетчика значения?
что мешает програмно, по прерыванию генерировать pwm на нескольких ногах процессора?
включаете прерывание по переполнению таймера, в преравании поднимаете флаг, в основной программе генерируете импульс(ы) задонной длительности с периудом переполнений таймера.во время генерации импульса можно с пользой для дела использовать время процессора (например вывести на индикатор, что нибудь).
активней пользуйтесь аппаратной частью процессора тогда и программа будет обрабатываться быстро.

все уже сделано до вас 😃 www.dmd.es/osd.htm

msv

Если программно наращивать таймер именно для получения timestamp, сложностей никаких быть не может.

что мешает програмно, по прерыванию генерировать pwm на нескольких ногах процессора?
включаете прерывание по переполнению таймера, в преравании поднимаете флаг, в основной программе генерируете импульс(ы) задонной длительности с периудом переполнений таймера.во время генерации импульса можно с пользой для дела использовать время процессора

Мне показалось, что Pavel_E делает декодер, и поэтому не понял, как это можно решить (по многочисленным советам) именно на чисто аппаратном pwm.
Хотя с вышеописанным алгоритмом тоже непонятка. Ведь есть 8-каналов (8 ног) с разным коэфф. заполнения и одинаковым периодом. Время от установки флагов до их обработки непредсказуемое, а значит качественный шим уже не получится.
Вообщем-то у меня вопросов то нет, что-нибудь наваяю, но вероятно и программные задержки ( с запрещенными в эти моменты прерываниями) использовать прийдется.
А в принципе (для абстрактной задачи) с советами по возможности для решения реал-тайм задач использовать прерывания и аппаратные возможности, а в фоновом цикле выполнять фоновые задачи, конечно трудно не согласиться… 😃

sht0p0r

например так :
по флагу поднимаемому таймером 50 раз в секунду
int width[8]; значения pwm для 8 каналов.
в основном теле пишем
{
start=TCNT1;
PORTD=0xFF;
{сюда напихать чегонибудь периодического на ~700us}
while((TCNT1+start)<MaxPWM)
{
if ((width[0]+start)<TCNT1) PORTD_Bit0=0; // это грубо на самом деле чуть сложнее, но не на много
if ((width[1]+start)<TCNT1) PORTD_Bit1=0;

if ((width[7]+start)<TCNT1) PORTD_Bit7=0;
// или вот гдето так
for (i =0 ;i<8;i++) if ((width[i]+start)<TCNT1) PORTD&(~(1<<i));
}
}

Pavel_E

Параллельно вопрос. Классический сигнал цифровых машинок - 50Гц, ширина импульса 1…2мс. Как воспримут машинки сигнал с частотой 45…55Гц? Но той же длительностью импульса?

sht0p0r

нормально воспримут. у меня с приемника spektrum AR7000 примерно 47 идет да еще и частота плавает… полный ужас короче, а машинки жрут и ничего 😃

токо 50 это для аналоговых, цифровые и 100 слопают.
импульс 1540 ± 760( если память не изменяет).

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 - шевелится почти что в полном диапазоне. Задача управления машинкой решена.