Зарегистрирован: Пт май 22, 2009 19:23:54 Сообщений: 28
Рейтинг сообщения:0
добрый день всем!
спрошу тут, раз есть тема и такая историческая...
камень mega328
таймер0 настроен на прерывание по переполнению каждые 8 мкС (125 кГц) (8 МГц / 64);
Таймер работает в режиме 3 - Fast PWM, TOP=0xFF, upd. of OCRx at BOTTOM (0x00), TOV flag set on MAX (0xFF)) на таймере работает измерение задержек (millis)
остальные таймеры заняты другими вещами..
хотелка: АЦПировать с частотой в 500 Гц
вопрос: могу ли я получить прерывание от того же таймера по сравнению значения - ISR (TIMER0_COMPA_vect) путём включения соотв. бита TIMSK0 |= (1 << OCIE0A), и записью значения в регистр OCR0A: OCR0A = 249 ?
т.е. прерывание по переполнению по-прежнему будет происходить, но хотелось б чтобы ещё происходило и прерывание по сравнению. это допустимо в режиме "3"?
кстати, разные калькуляторы дают разное значение OCR, кто считается более "правильным"?
timex, у вас таймер переполняется каждые 8 мкс. 500 гц - это 2000 мкс, делаете в обработчике прерываний счетчик от 0 до (250-1) - и стартуете АЦП один раз в 250 прерываний
Зарегистрирован: Пт май 22, 2009 19:23:54 Сообщений: 28
Рейтинг сообщения:0
Just_Fluffy, это понятно.. но хочется сделать по красоте, а код обработчика и инициализации таймера на 8 мкс не хотелось бы трогать (оно библиотечное).
у меня сейчас по осциллограмме 479.9 Гц происходит прерывание по сравнению OCR, с любым значением OCR - не пойму пока почему...
ре-инициализация уже тикающего таймера0:
Код:
//настройка прерывания TC0 cli(); //выключаем возможное прерывание по переполнению TC0 (работает для millis и micros из chrono.cpp) TIFR0 = (1<<TOV0); //reset flag int of overflow OCR0A = 0xF9; TIMSK0 |= (1 << OCIE0A); //TC0 Output Compare Match A Interrupt Enable (only interrupt, no OC0A pin toggle) sei();
(когда АЦПировать надоест - я сбрасываю бит прерывания по сравнению у регистра маски прерываний таймера0)
обработчик:
Код:
//прерывание по сравнению значения TC0 ISR (TIMER0_COMPA_vect) { //do some... ADCSRA |= (1<<ADSC); //start conversion while (ADCSRA & (1<<ADSC)); //wait for conversion to complete //do some... }
таймер0 настроен на прерывание по переполнению каждые 8 мкС (125 кГц) (8 МГц / 64);
вот эта фраза меня немного в заблуждение ввела. У вас таймер тактируется от 8 МГц с делителем 64 ? Тогда прерывание по переполнению возникает не каждые 8 мкс, а в 256 раз реже. 8 мкс - это период тактовой частоты на вход счетчика таймера. Соответственно, 8 мгц / 64 = 125 кгц. У вас таймер в режиме 3 - FastPWM с ТОР = 0xFF. соответственно, таймер считает от 0 до 255 каждые 8 мкс. И прерывание по переполнению возникает каждые 8*256 = 2048 мкс - 2,048 мс. 1/2048 = ~488,3 Гц - все по честному. И как бы вы не настраивали регистр сравнения - таймер все равно будет молотить полный цикл в 256 отсчетов. Вам нужно как то уменьшать ТОР. Например, используя другой режим. Например, 7 - FastPWM, TOP in OCR0A - тогда можно записать в OCR0A значение 250-1 и получить период таймера (64*250)/8МГц = 0,002 с = 2 мс. Т.е. таймер будет молотить как раз на ваших 500 гц
Но, судя по некоторым вашим словам - у вас не просто атмега, у вас ардуина с ее IDE, millis-ами, analogWrite-ами... И следует быть готовым к тому, что функции ардуины, использующие таймер 0, мгут работать не всегда так, как ожидалось бы от них. Вроде на сайте у Гайвера было про работу с таймерами в применении к ардуине.
"при выполнении данного кода ни одного котика таймера не пострадало..."
ATMEGA328, 8MHz PS: сорян за ассемблер, но думаю принцип понятен
//----------// .equ ADC_TMR_CONST=19 //константа для программного таймера ADC_TMR_CONST=(Fcpu/делитель АЦП/13/500Гц) .dseg ADC_TMR: .byte 1 //переменная программного таймера
.cseg //в коде начальной инициализации: ... LDI R16,(1<<ADLAR)|0 //настройка опоры, канала, выравнивания STS ADMUX,R16 LDI R16,(1<<ADEN)|(0<<ADATE)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(0<<ADPS0) //включаем АЦП, прерывание STS ADCSRA,R16 //и делитель, получаем тактирование 125кГц (для АЦП надо от 50 до 200), при этом само АЦП требует 13 тактов для преобразования LDI R16,ADC_TMR_CONST //настраиваем программный таймер (точнее делитель) STS ADC_TMR,R16 ...
//в ISR обработчика АЦП: IN R3,SREG //сохраняем статус PUSH R16 //сохраняем R16 LDI R16,(1<<ADEN)|(0<<ADATE)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(0<<ADPS0) //перезапуск АЦП STS ADCSRA,R16 LDS R16,ADC_TMR //уменьшаем таймер DEC R16 BRNE NO_RESET_ADC_TMR //пока не обнулится LDI R16,ADC_TMR_CONST //если обнулился, инициализируем по-новому STS ADC_TMR,R16 LDS R16,ADCH //и вот тут читаем АЦП, эта строчка при тактовой в 8МГц будет выполнятся с частотой в 500Гц POP R16 //восстанавливаем регистры OUT SREG,R3 RETI //выход NO_RESET_ADC_TMR: STS ADC_TMR,R16 //а тут просто сохраняем текущее значение таймера POP R16 OUT SREG,R3 RETI //----------//
_________________ Неправильно собранная из неисправных деталей схема нуждается в отладке и сразу не работает... (С)
но тем не менее остался вопрос, если записать что-то в OCR0A, например 249, включить режим 3 таймера0 (waveform generation mode (3): Fast PWM, top: 0xFF, upd. of OCRx at: BOTTOM, TOV flag set on: MAX), включить прерывание по переполнению И прерывание по сравнению OCR0A,
то после запуска таймера по-идее сначала возникнет прерывание по сравнению (и не важно, успеем ли из него выйти по RETI или нет), а следующим (через 7 тактов, т.к. 256-249=7) возникнет прерывание по переполнению, верно?
даташит как-то обходит стороной этот вопрос..
и второй вопросик, в режиме 7 таймера0 (waveform generation mode (7): Fast PWM, top: OCR0A, upd. of OCRx at: BOTTOM, TOV flag set on: TOP) в если в нём будут включены прерывания по переполнению И по сравнению, то они будут выполнятся оба, согласно их приоритетам, верно?
если ты запишешь в OCR0A 249 и включишь режим 7 (top: OCR0A), то после 249 в счетчик запишется 0, и счетчик никогда не досчитает до 256, чтобы сработало переполнение.
_________________ Мудрость приходит вместе с импотенцией... Когда на русском форуме переходят на Вы, в реальной жизни начинают бить морду.
по-идее сначала возникнет прерывание по сравнению (и не важно, успеем ли из него выйти по RETI или нет), а следующим (через 7 тактов, т.к. 256-249=7) возникнет прерывание по переполнению, верно?
В принципе, верно, если сравнение попадает в период пересчета таймера. Но. Если не принимать специальных мер, то внутри обработчика прерывания другие прерывания запрещены. Поэтому выполнится сначала первое прерывание (а будет оно выполняться тактов 25 минимум (вход/выход, сохранение/восстановление регистров...), потом, после выхода из прерывания, выполнится минимум одна команда основной программы, а потом уже, если есть активные флаги прерываний - будет выполняться следующее прерывание.
Цитата:
они будут выполнятся оба, согласно их приоритетам, верно?
Согласно их очередности возникновения. Если же в процесе выполнения обработчика прерывания возникает несколько других прерываний - то да, они будут выполняться согласно их приоритетности, определенной таблицей прерываний. Т.е. чем меньше номер прерывания, тем выше приоритет. И этот приоритет в 8-битных классических АВРках не редактируется.
Искренне прошу помощи, не понимаю как реализовать задержку в 1 секунду на ATmega32..... Задача у меня такая: «Будильник». Исходное значение цифр на семисегментном индикаторе «9». Нажатием кнопки на входе РА0 запускается таймер с обратным счетом: через 1 секунду индицируется цифра «8» и т.д. до «0». При достижении «0» включается светодиод на выходе РВ0. Я наверстал что-то по интернет гайдам, но по итогу ни к чему не пришёл(
init: ; Инициализация стека ldi temp, low(RAMEND) out SPL, temp ldi temp, high(RAMEND) out SPH, temp
; Инициализация портов ldi temp, 0x00 out DDRA, temp ; Порты A на ввод ldi temp, 0xFF out DDRC, temp ; Порты C на вывод ldi temp, 0xFF out DDRB, temp ; Порты B на вывод
; Начальное значение счетчика ldi counter, 9 out PORTC, counter
start_timer: ; Настройка таймера для задержки в 1 секунду ldi temp, high(DELAY) out TCNT1H, temp ldi temp, low(DELAY) out TCNT1L, temp ret
timer_interrupt: ; Уменьшение счетчика и обновление индикатора dec counter out PORTC, counter cpi counter, 0 brne timer_interrupt ; Включение светодиода sbi PORTB, 0 reti
В результате компиляции этого чуда получаю вот такую ошибку: AVR Simulator: Invalid opcode 0xffff at address 0x005858 В самой AVR код запускается и работает, но вот в Proteus схема вообще не включается
Не верю. Вы не запустили таймер, не разрешили прерывания. Даже если это будет выполнено, таймер будет отсчитывать не одну секунду, а гораздо больше. Как вариант отсчета 9 секунд с использованием режима CTC.
Зарегистрирован: Пн ноя 04, 2019 09:58:29 Сообщений: 104 Откуда: г. Нижний Тагил Свердл. обл.
Рейтинг сообщения:0
Добрый вечер. Моя задача относится к разряду "Помигать светодиодом". Это если рассматривать задачу как упрощенную модель. На самом же деле это драйвер открытия/закрытия топливной форсунки инжекторной системы фазированного распределенного впрыска четырехцилиндрового двигателя внутреннего сгорания. В принципе задачу я решил. Но, как говорится, тупо и в лоб. Во всяком случае в протеусе все работает. В целом написание прошивки для ЭБУ я решил по возможности разделить на отдельные блоки. Вычислительная часть(Master), где надо много и быстро обрабатывать данные с датчиков и исполнительный блок(Slave ATtiny88), который работает с уже готовым значением длительности для открытого состояния форсунки. Но у меня получилось четыре раба и каждый раб знает когда открыть форсунку и на какую длительность. Рабы работают слаженно и вполне покладисто. Каждый использует delayMicrosecond(). Поскольку от раба никакой другой задачи не требуется как открыть/закрыть одну форсунку, то блокирующий delay вполне себе оправдан. Но содержать такую толпу рабов как то, не то что бы накладисто, а как-то не прилично. Хотелось бы иметь одного умного раба, который работал на таймере и управлял всеми четырьмя форсунками. Каждая форсунка открывается по сдвигом по фазе на четверть периода. Сразу же напрашивается таймер, который инкриминирует в своем прерывании переменную от которой раб откусывает значение длительности открытого состояния форсунки, ну там - зафиксировать текущее значение, сравнить, ну и всё такое... В прикрепленном файле я для себя нарисовал картинку-шпаргалку. Может быть у кого-то есть мыслишка на этот счет или полезная ссылка. Код Мастера SPI. Спойлер
// Генериравание импульса заданной ширины в микросекундах. // Прерывания отключены во время генерации импульса. void pulseMicroseconds(uint16_t us){ if( us == 0 ){ return; }
// задерживает указанное количество микросекунд // работает для тактовой частоты 1 MHz и выше __attribute((always_inline)) static inline void delayMicroseconds(uint16_t us){
// if us is a compile-time constant result is accurate to 1 cycle if (__builtin_constant_p(us)) { _delay_us(us); return; } // when us is not known at compile time, delay is accurate to +/- 2us // plus an overhead of 3 CPU cycles // когда us неизвестен во время компиляции, задержка составляет +/- 2us // плюс накладные расходы в 3 цикла процессора
const float fMHz = (F_CPU/1000000.0); // subtract two for rounding before dividing by 4 // вычтите два для округления перед делением на 4 us -= 2; delay4us: // delay 4us per loop, less 4 cycles for overhead // задержка 4 мкс на цикл, минус 4 цикла накладных расходов _delay_us(4.0 - (4.0 / fMHz)); asm volatile ("sbiw %[us], 4" : [us]"+w"(us)); asm goto( "brpl %l[delay4us]" :::: delay4us); }
какова минимальная скважность работы форсунок? (может ли наслаиваться открытие одной форсунки на открытие второй, третьей....)
_________________ Просто не учи физику в школе, и вся твоя жизнь будет наполнена чудесами и волшебством Безграмотно вопрошающим про силовую или высоковольтную электронику я не отвечаю, а то ещё посадят за участие в (само)убиении оболтуса...
Зарегистрирован: Пн ноя 04, 2019 09:58:29 Сообщений: 104 Откуда: г. Нижний Тагил Свердл. обл.
Рейтинг сообщения:0
Добрый вечер. Решено. Использовал Timer/Counter2(8 bit) ATmega328P в режиме Clear Timer On Compare (CTC). Настроил прескалер и регистр сравнения OCR2A, F_CPU 16MHz. Обязательно необходимо установить разрешение, оно-же дискретность приращения для инкрементируемой переменной в обработчике прерывания TIMER2_COMPA_vect. При единице не работает. Нормальная обработка выполняется от 10. Установил 100 для надежности. Высокий уровень на лапке МК устанавливается в тот момент, когда приходит сигнал с датчика коленчатого вала INT1_vect. Там-же, в INT1_vect, фиксируется время каждого сигнала. Синхронизация сигналов выполняется по одиночноу сигналу от распредвала в прерывании INT0_vect. Низкий уровень на лапке МК устанавливается по истечении заданного интервала времени. Происходит сравнение разницы фиксированного и текущего времени возвращаемого функцией get_miсros() со значением uS для открытого состояния форсунки. Функции установки уровней (HIGH/LOW) вызываются по указателю из прерываний - внешнее прерывание INT1_vect (HIGH) и прерывание таймера TIMER2_COMPA_vect (LOW). Численное значение времени(uS) открытого состояния форсунки вычисляется другим МК(Master) и по готовности передается по SPI целевому МК(Slave).
Информации по теории и принципам работы распределенного впрыска ДВС в интернете очень мало. В основном только общее представление. В деталях и тонкостях приходится разбираться путем рассуждений. Например - могут ли все четыре форсунки находиться одновременно в открытом состоянии? Да, могут. Это происходит на режиме высоких оборотов (выше какого-то предела). Поскольку высокие обороты требуют большего количества топлива и, соответственно, большего времени открытого состояния. Но с высокими оборотами сокращается и время периода рабочего цикла. Часть топлива подается на закрытый клапан. Т.е. ШИМ 90-95%. Одновременное открытое состояние всех 4-х форсунок наступает при заполнении ШИМ более чем 25%. - четверть периода рабочего цикла. Что видно и на виртуальном осциллографе в протеусе. Файл main.cpp Спойлер
// ============ implementation ============ // GLOBAL VARIABLES
// Ошибка калибровки. Отрицательное или положительное число // которое будет добавлено или вычтено из значения регистра таймера. enum err { MICROS_ERR_CAL = 0 };
// тип (*имя_указателя)(типы_параметров); void (*interruptCallback)(uint16_t);
// Returns current microseconds // Возвращает текущие миллисекунды uint32_t micros_get(){ uint32_t ms; ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ ms = microseconds; } return ms; }
// Turn on timer and resume time keeping // Включите таймер и возобновите отсчет времени void micros_resume(){ TIMSK2 |= (1 << OCIE2A); TCCR2B = CLOCKSEL; power_timer2_enable(); }
// Pause time keeping and turn off timer to save power // Приостановите отсчет времени и выключите таймер для экономии энергии void micros_pause(){ TIMSK2 &= ~(1 << OCIE2A); TCCR2B = 0; power_timer2_disable(); }
// Reset microseconds count to 0 // Сбросить счетчик миллисекунд до 0 void micros_reset(){ ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ microseconds = 0; } }
// Add time // Добавить время void micros_add(uint32_t ms){ ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ microseconds += ms; } }
// Subtract time // Вычесть время void micros_subtract(uint32_t ms){ ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ microseconds -= ms; } }
// Attach a custom function to timer interrupt // Прикрепите пользовательскую функцию к прерыванию таймера void micros_interrupt_attach( void (*funcPtr)(uint16_t) ){ interruptCallback = funcPtr; }
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 13
Вы не можете начинать темы Вы не можете отвечать на сообщения Вы не можете редактировать свои сообщения Вы не можете удалять свои сообщения Вы не можете добавлять вложения