Таймеры/счётчики в AVR

Обсуждаем контроллеры компании Atmel.
OKF
Это не хвост, это антенна
Сообщения: 1385
Зарегистрирован: Вт июн 07, 2011 08:03:18

Re: Таймеры/счётчики в AVR

Сообщение OKF »

[uquote="ciaas",url="/forum/viewtopic.php?p=4485797#p4485797"]А сделал я как предложила Just_Fluffy. Перфект! и респект ! Не знаю как поставить плюсик в телефоне.[/uquote]
В ПРОЕКТЕ ссылку на Just_Fluffy только нужно не забыть указать. А то ведь всё я, да я...)
ciaas
Нашел транзистор. Понюхал.
Сообщения: 152
Зарегистрирован: Вт окт 11, 2022 13:45:06

Re: Таймеры/счётчики в AVR

Сообщение ciaas »

Без этого никак. Будет ссылка :)
Аватара пользователя
timex
Первый раз сказал Мяу!
Сообщения: 29
Зарегистрирован: Пт май 22, 2009 19:23:54

Re: Таймеры/счётчики в AVR

Сообщение timex »

добрый день всем!

спрошу тут, раз есть тема и такая историческая...


камень 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, кто считается более "правильным"?

Изображение
Изображение
Вложения
qqq2.JPG
(63.75 КБ) 820 скачиваний
qqqqq.JPG
(46.95 КБ) 845 скачиваний
Аватара пользователя
Just_Fluffy
Вымогатель припоя
Сообщения: 532
Зарегистрирован: Ср июн 29, 2022 16:25:45

Re: Таймеры/счётчики в AVR

Сообщение Just_Fluffy »

timex, у вас таймер переполняется каждые 8 мкс.
500 гц - это 2000 мкс, делаете в обработчике прерываний счетчик от 0 до (250-1) - и стартуете АЦП один раз в 250 прерываний
Белая и Пушистая
Аватара пользователя
timex
Первый раз сказал Мяу!
Сообщения: 29
Зарегистрирован: Пт май 22, 2009 19:23:54

Re: Таймеры/счётчики в AVR

Сообщение timex »

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...
}
Аватара пользователя
Just_Fluffy
Вымогатель припоя
Сообщения: 532
Зарегистрирован: Ср июн 29, 2022 16:25:45

Re: Таймеры/счётчики в AVR

Сообщение Just_Fluffy »

timex писал(а):таймер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, мгут работать не всегда так, как ожидалось бы от них.
Вроде на сайте у Гайвера было про работу с таймерами в применении к ардуине.
Белая и Пушистая
Аватара пользователя
Engineer_Keen
Друг Кота
Сообщения: 3868
Зарегистрирован: Пт янв 29, 2010 10:27:40
Откуда: Москва

Re: Таймеры/счётчики в AVR

Сообщение Engineer_Keen »

остальные таймеры заняты другими вещами..

хотелка: АЦПировать с частотой в 500 Гц
"при выполнении данного кода ни одного котика таймера не пострадало..." :)))

ATMEGA328, 8MHz
PS: сорян за ассемблер, но думаю принцип понятен :P

//----------------------------------------//
.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
//----------------------------------------//
Неправильно собранная из неисправных деталей схема нуждается в отладке и сразу не работает... (С)
Аватара пользователя
timex
Первый раз сказал Мяу!
Сообщения: 29
Зарегистрирован: Пт май 22, 2009 19:23:54

Re: Таймеры/счётчики в AVR

Сообщение timex »

[uquote="Just_Fluffy",url="/forum/viewtopic.php?p=4584555#p4584555"]... Например, используя другой режим. Например, 7 - ...[/uquote]

спасибо, уважаемый, я и забыл что тики-тиками, а прерывание по переполнению в 256 раз реже)

надо читать доки внимательнее)

пока я сделал так как и рекомендовали, изначально изменил режим таймера на 7, OCR0A = 250-1 и
и изменил дефайн для подсчёта задержек
с

Код: Выделить всё

#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
//#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
на

Код: Выделить всё

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 250))
но тем не менее остался вопрос, если записать что-то в 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)
в если в нём будут включены прерывания по переполнению И по сравнению, то они будут выполнятся оба, согласно их приоритетам, верно?
Аватара пользователя
Starichok51
Модератор
Сообщения: 19039
Зарегистрирован: Сб авг 14, 2010 15:05:51
Откуда: г. Озерск, Челябинская обл.

Re: Таймеры/счётчики в AVR

Сообщение Starichok51 »

если ты запишешь в OCR0A 249 и включишь режим 7 (top: OCR0A), то после 249 в счетчик запишется 0, и счетчик никогда не досчитает до 256, чтобы сработало переполнение.
Мудрость приходит вместе с импотенцией...
Когда на русском форуме переходят на Вы, в реальной жизни начинают бить морду.
akl
Друг Кота
Сообщения: 4444
Зарегистрирован: Пт мар 07, 2008 06:54:43
Откуда: Ижевск

Re: Таймеры/счётчики в AVR

Сообщение akl »

timex писал(а):...они будут выполнятся оба, согласно их приоритетам, верно?
Да. Сначала TIMER0 COMPA, затем TIMER0 OVF.
Аватара пользователя
Just_Fluffy
Вымогатель припоя
Сообщения: 532
Зарегистрирован: Ср июн 29, 2022 16:25:45

Re: Таймеры/счётчики в AVR

Сообщение Just_Fluffy »

timex писал(а):по-идее сначала возникнет прерывание по сравнению (и не важно, успеем ли из него выйти по RETI или нет), а следующим (через 7 тактов, т.к. 256-249=7) возникнет прерывание по переполнению, верно?
В принципе, верно, если сравнение попадает в период пересчета таймера.
Но. Если не принимать специальных мер, то внутри обработчика прерывания другие прерывания запрещены. Поэтому выполнится сначала первое прерывание (а будет оно выполняться тактов 25 минимум (вход/выход, сохранение/восстановление регистров...), потом, после выхода из прерывания, выполнится минимум одна команда основной программы, а потом уже, если есть активные флаги прерываний - будет выполняться следующее прерывание.
они будут выполнятся оба, согласно их приоритетам, верно?
Согласно их очередности возникновения. Если же в процесе выполнения обработчика прерывания возникает несколько других прерываний - то да, они будут выполняться согласно их приоритетности, определенной таблицей прерываний. Т.е. чем меньше номер прерывания, тем выше приоритет. И этот приоритет в 8-битных классических АВРках не редактируется.

timex писал(а):спасибо, уважаемый
уважаемая...
Белая и Пушистая
veent
Родился
Сообщения: 2
Зарегистрирован: Чт июн 06, 2024 10:57:13

Re: Таймеры/счётчики в AVR

Сообщение veent »

Искренне прошу помощи, не понимаю как реализовать задержку в 1 секунду на ATmega32..... Задача у меня такая: «Будильник». Исходное значение цифр на семисегментном индикаторе «9». Нажатием кнопки на входе РА0 запускается таймер с обратным счетом: через 1 секунду индицируется цифра «8» и т.д. до «0». При достижении «0» включается светодиод на выходе РВ0. Я наверстал что-то по интернет гайдам, но по итогу ни к чему не пришёл(

Код: Выделить всё

 .include "m32def.inc"

; Определение констант для таймера
.def counter = r16
.def temp = r17
.equ F_CPU = 8000000 ; Частота процессора 8 МГц
.equ DELAY = F_CPU/1024 ; Делитель для 1 секунды

.org 0x00
    rjmp init

.org OVF1addr
    rjmp timer_interrupt

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

    ; Разрешить прерывания
    sei

main_loop:
    ; Проверка нажатия кнопки
    sbis PINA, 0
    rcall start_timer
    rjmp timer_interrupt

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 схема вообще не включается
Изображение
akl
Друг Кота
Сообщения: 4444
Зарегистрирован: Пт мар 07, 2008 06:54:43
Откуда: Ижевск

Re: Таймеры/счётчики в AVR

Сообщение akl »

Не верю. Вы не запустили таймер, не разрешили прерывания. Даже если это будет выполнено, таймер будет отсчитывать не одну секунду, а гораздо больше. Как вариант отсчета 9 секунд с использованием режима CTC.

Код: Выделить всё

.include "m32def.inc"

; Определение констант для таймера
.def counter = r16
.def temp = r17
.equ F_CPU = 8000000 ; Частота процессора 8 МГц
.equ DELAY = F_CPU/1024 ; Делитель для 1 секунды

.org 0x00
    rjmp init

.org $000E
    rjmp timer_interrupt

init:
    ; Инициализация стека
    ldi temp, low(RAMEND)
    out SPL, temp
    ldi temp, high(RAMEND)
    out SPH, temp

    ; Инициализация портов
    ldi temp, 0x00
    out DDRA, temp ; Порты A на ввод

	SBI	PORTA,0		;включить подтяжку кнопки

    ldi temp, 0xFF
    out DDRC, temp ; Порты C на выводPINA 0 25
    ldi temp, 0xFF
    out DDRB, temp ; Порты B на вывод

; Начальное значение счетчика
    ldi counter, 9
    out PORTC, counter

start_timer:
    ; Настройка таймера для задержки в 1 секунду
	LDI	R22,HIGH(DELAY)
	OUT	OCR1AH,R22
	LDI	R22,LOW(DELAY)
	OUT	OCR1AL,R22

	LDI	R22,1<<OCIE1A
	OUT	TIMSK,R22

	LDI	R22,1<<WGM13|1<<WGM12	;CTC
	OUT	TCCR1B,R22

;	SBR	R22,1<<CS10 	;F_CPU/1
	SBR	R22,1<<CS12|1<<CS10 	;F_CPU/1024
    ; Разрешить прерывания
    sei
main_loop:
    ; Проверка нажатия кнопки
    sbic PINA, 0
    rjmp main_loop

	CBI	PORTB,0		;выключить светик

	OUT	TCCR1B,R22
	SBIS	PORTB,0
	RJMP	PC-1

    sbis PINA, 0
    rjmp 	PC-1

	RJMP	INIT
;*************************************************
timer_interrupt:
; Уменьшение счетчика и обновление индикатора
    dec counter
    out PORTC, counter
	BRNE	OUT_TIME
; Включение светодиода
	sbi PORTB, 0
OUT_TIME:
    reti
.exit
veent
Родился
Сообщения: 2
Зарегистрирован: Чт июн 06, 2024 10:57:13

Re: Таймеры/счётчики в AVR

Сообщение veent »

большое спасибо, надеюсь смогу откорректировать код самостоятельно
Аватара пользователя
Эйлер Леонард
Встал на лапы
Сообщения: 104
Зарегистрирован: Пн ноя 04, 2019 09:58:29
Откуда: г. Нижний Тагил Свердл. обл.

Re: Таймеры/счётчики в AVR

Сообщение Эйлер Леонард »

Добрый вечер. Моя задача относится к разряду "Помигать светодиодом". Это если рассматривать задачу как упрощенную модель. На самом же деле это драйвер открытия/закрытия топливной форсунки инжекторной системы фазированного распределенного впрыска четырехцилиндрового двигателя внутреннего сгорания. В принципе задачу я решил. Но, как говорится, тупо и в лоб. Во всяком случае в протеусе все работает. В целом написание прошивки для ЭБУ я решил по возможности разделить на отдельные блоки. Вычислительная часть(Master), где надо много и быстро обрабатывать данные с датчиков и исполнительный блок(Slave ATtiny88), который работает с уже готовым значением длительности для открытого состояния форсунки. Но у меня получилось четыре раба и каждый раб знает когда открыть форсунку и на какую длительность. Рабы работают слаженно и вполне покладисто. Каждый использует delayMicrosecond(). Поскольку от раба никакой другой задачи не требуется как открыть/закрыть одну форсунку, то блокирующий delay вполне себе оправдан. Но содержать такую толпу рабов как то, не то что бы накладисто, а как-то не прилично. Хотелось бы иметь одного умного раба, который работал на таймере и управлял всеми четырьмя форсунками. Каждая форсунка открывается по сдвигом по фазе на четверть периода. Сразу же напрашивается таймер, который инкриминирует в своем прерывании переменную от которой раб откусывает значение длительности открытого состояния форсунки, ну там - зафиксировать текущее значение, сравнить, ну и всё такое... В прикрепленном файле я для себя нарисовал картинку-шпаргалку. Может быть у кого-то есть мыслишка на этот счет или полезная ссылка.
Код Мастера SPI.
Спойлер

Код: Выделить всё

/* ATmega328P AtmelStudio  -std=c++14 */
#define F_CPU 1000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdlib.h>
#include "src\GPIO.h"

GPIO<PIN::PinD2> INT_0;
GPIO<PIN::PinD3> INT_1;

GPIO<PIN::PinC0> ss0;
GPIO<PIN::PinC1> ss1;
GPIO<PIN::PinC2> ss2;
GPIO<PIN::PinC3> ss3;

GPIO<PIN::MOSI> mosi;
GPIO<PIN::MISO> miso;
GPIO<PIN::SCK> sck;

uint16_t transfer16(uint16_t data);

volatile size_t k;
ISR(INT0_vect) {
k = 0;
}


volatile uint16_t p = 3000;
ISR(INT1_vect) {

switch(k) {
	
case 0: {
ss0.write(0);
transfer16(p);
ss0.write(1);
break;}
	
case 1: {
ss1.write(0);
transfer16(p);
ss1.write(1);
break;}

case 2: {
ss2.write(0);
transfer16(p);
ss2.write(1);
break;}

case 3: {
ss3.write(0);
transfer16(p);
ss3.write(1);
break;}

}

k++;

}

//================


int main(void){
cli();

ss0.output();  ss0.write(1);
ss1.output();  ss1.write(1);
ss2.output();  ss2.write(1);
ss3.output();  ss3.write(1);

mosi.output(); mosi.write(0);
miso.input(),  miso.pullup();
sck.output();  sck.write(0);

/**/

//  SPI2X   SPR1   SPR0    Тактовая частота SCK
//  1       0      0       fosc/2
//  0       0      0       fosc/4
//  1       0      1       fosc/8
//  0       0      1       fosc/16
//  1       1      0       fosc/32
//  0       1      0       fosc/64
//  1       1      1       fosc/64
//  0       1      1       fosc/128
// -------------------------------------------- fosc/8 ->
//       SPIE
//       |SPE
//       ||DORD
//       |||MSTR
//       |||| CPOL
//       |||| |CPHA
//       |||| ||SPR1
//       |||| |||SPR0
//       |||| ||||
SPCR = 0b0101'0001;
SPSR |= (1 << SPI2X);
//SPSR &= ~(1 << SPI2X);

INT_0.input(); INT_0.pullup();
INT_1.input(); INT_1.pullup();

EIMSK |= (1<<INT1)|(1<<INT0);
EICRA |= (1<<ISC11)|(1<<ISC10)|(1<<ISC01)|(1<<ISC00); // Нарастающий фронт INT1, INT0 генерирует прерывание.

sei();
while (1){
	
    }
	
return 0;
}

// =============

uint16_t transfer16(uint16_t data) {
	
union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } in, out;
	
in.val = data;

if ( !( SPCR & (1 << DORD) ) ) {

SPDR = in.msb;
asm volatile("nop"); 
while (!(SPSR & (1 << SPIF))) ;
out.msb = SPDR;
SPDR = in.lsb;
asm volatile("nop");
while (!(SPSR & (1 << SPIF))) ;
out.lsb = SPDR;

} else {
	
SPDR = in.lsb;
asm volatile("nop");
while (!(SPSR & (1 << SPIF))) ;
out.lsb = SPDR;
SPDR = in.msb;
asm volatile("nop");
while (!(SPSR & (1 << SPIF))) ;
out.msb = SPDR;

}

 return out.val;

}

//============
Код Slave SPI
Спойлер

Код: Выделить всё

/* ATtiny88 AtmelStudio  -std=c++14 */
#define F_CPU 1000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdlib.h>
#include "D:\lib\SPI\SPI.hpp"

GPIO<PIN::PinD5> jet;

//       SPIE
//       |SPE
//       ||DORD
//       |||MSTR
//       |||| CPOL
//       |||| |CPHA
//       |||| ||SPR1
//       |||| |||SPR0
enum {// |||| ||||
spcr = 0b1100'0000 };
SPI spiSlave( spcr );// spi2x = 0

volatile uint16_t duration_us;
ISR(SPI_STC_vect){
duration_us = spiSlave.transfer<uint16_t>(0);
jet.pulseMicroseconds(duration_us);

}

int main(void) {

jet.output();
jet.write(0);

sei(); // enable interrupts

while (1) { }

return 0;
}

//==========
void pulseMicroseconds(uint16_t us)
Спойлер

Код: Выделить всё

// Генериравание импульса заданной ширины в микросекундах.
// Прерывания отключены во время генерации импульса.
void pulseMicroseconds(uint16_t us){
if( us == 0 ){ return; }

uint8_t sreg = SREG; // F_CPU >= 1000000
__asm__ __volatile__("cli" ::: "memory"); //
sfr->pin |= MASK;
delayMicroseconds(us);
sfr->pin |= MASK;
SREG = sreg; //
__asm__ __volatile__("" ::: "memory"); //

};
void delayMicroseconds(uint16_t us)
Спойлер

Код: Выделить всё

// задерживает указанное количество микросекунд
// работает для тактовой частоты 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);
}
Вложения
Фрагмент-0.png
(192.14 КБ) 131 скачивание
Pulse.png
(8.51 КБ) 141 скачивание
SPI_Master_Slave.png
(83.77 КБ) 122 скачивания
Аватара пользователя
Ivanoff-iv
Друг Кота
Сообщения: 7077
Зарегистрирован: Пт ноя 11, 2016 05:48:09
Откуда: Сердце Пармы

Re: Таймеры/счётчики в AVR

Сообщение Ivanoff-iv »

какова минимальная скважность работы форсунок?
(может ли наслаиваться открытие одной форсунки на открытие второй, третьей....)
Для тех, кто не учил магию мир полон физики :)
Безграмотно вопрошающим про силовую или высоковольтную электронику я не отвечаю, а то ещё посадят за участие в (само)убиении оболтуса...
Аватара пользователя
Starichok51
Модератор
Сообщения: 19039
Зарегистрирован: Сб авг 14, 2010 15:05:51
Откуда: г. Озерск, Челябинская обл.

Re: Таймеры/счётчики в AVR

Сообщение Starichok51 »

Эйлер Леонард писал(а):инкриминирует
если не знаешь, как пишется слово, посмотри в словаре.
а инкриминировать можно кому-то, например, преступление.
Мудрость приходит вместе с импотенцией...
Когда на русском форуме переходят на Вы, в реальной жизни начинают бить морду.
Аватара пользователя
Jack_A
Друг Кота
Сообщения: 6307
Зарегистрирован: Вт апр 24, 2007 07:45:40
Откуда: Minsk

Re: Таймеры/счётчики в AVR

Сообщение Jack_A »

"Задержанному инкрементировали ещё и сопротивление полиции"
:)
Изображение
Аватара пользователя
Just_Fluffy
Вымогатель припоя
Сообщения: 532
Зарегистрирован: Ср июн 29, 2022 16:25:45

Re: Таймеры/счётчики в AVR

Сообщение Just_Fluffy »

Я ни на какие вопросы отвечать не буду без присутствия моего авокадо!
Белая и Пушистая
Аватара пользователя
Эйлер Леонард
Встал на лапы
Сообщения: 104
Зарегистрирован: Пн ноя 04, 2019 09:58:29
Откуда: г. Нижний Тагил Свердл. обл.

Re: Таймеры/счётчики в AVR

Сообщение Эйлер Леонард »

Добрый вечер.
Решено. Использовал 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
Спойлер

Код: Выделить всё

/* ATtiny88 AtmelStudio  -std=c++14 */
#define F_CPU 16000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include "D:\lib\MegaCore\corefiles\HardwareSerial.h"
#include "D:\lib\SPI\SPI.hpp"
#include "micros2.h"
#include "D:\lib\pin\GPIO.hpp"

GPIO<PIN::PinC0> jet0;
GPIO<PIN::PinC1> jet1;
GPIO<PIN::PinC2> jet2;
GPIO<PIN::PinC3> jet3;

GPIO<PIN::PinD2> INT_0;
GPIO<PIN::PinD3> INT_1;

void INT1_interrupt_attach( void (*funcPtr)(void) );
void (*INT1_interruptCallback)(void);

static void positionCheck(void);
static void Action_4Jet(uint16_t d);

volatile uint16_t duration_us = 0;//8000
volatile uint32_t lastTime0, lastTime1, lastTime2, lastTime3;

ISR(INT0_vect) {
GPIOR0 = 0;
}

ISR(INT1_vect) {
	
if(!!duration_us){
INT1_interruptCallback();
}

GPIOR0++;
}

//       SPIE
//       |SPE
//       ||DORD
//       |||MSTR
//       |||| CPOL
//       |||| |CPHA
//       |||| ||SPR1
//       |||| |||SPR0
enum {// |||| ||||
spcr = 0b1100'0000 };
SPI spiSlave( spcr );// spi2x = 0

ISR(SPI_STC_vect){
duration_us = spiSlave.transfer<uint16_t>(0);
Serial.println(duration_us);
}

// ISR Handlers
ISR(TIMER2_COMPA_vect) {
	microseconds += micros_resolution;

	if(interruptCallback){
		interruptCallback(duration_us);
	}

}

int main(void){
	
Serial.begin(57600);//
while(!Serial);
Serial.println("== TEST Slave ==");

jet0.output(); jet0.write(0);
jet1.output(); jet1.write(0);
jet2.output(); jet2.write(0);
jet3.output(); jet3.write(0);

INT_0.input(); INT_0.pullup();
INT_1.input(); INT_1.pullup();

EIMSK |= (1<<INT1)|(1<<INT0); // Разрешение прерывания на входе INT0, INT1
EICRA |= (1<<ISC11)|(1<<ISC10)|(1<<ISC01)|(1<<ISC00); // Нарастающий фронт INT1, INT0 генерирует прерывание.

INT1_interrupt_attach(positionCheck);

micros_interrupt_attach(Action_4Jet);

micros_init(F_CPU, 100);

while(1){ }
	
return 0;
}

// ==
static void Action_4Jet( uint16_t d_us ){
	
	if( (micros_get() - lastTime0) >= d_us ){
		lastTime0 = micros_get();
		jet0.write(0);
	}

	if( (micros_get() - lastTime1) >= d_us ){
		lastTime1 = micros_get();
		jet1.write(0);
	}

	if( (micros_get() - lastTime2) >= d_us ){
		lastTime2 = micros_get();
		jet2.write(0);
	}

	if( (micros_get() - lastTime3) >= d_us ){
		lastTime3 = micros_get();
		jet3.write(0);
	}
	
}
// ==
static void positionCheck(void){
	
switch(GPIOR0) {
	
case 0: {
	lastTime0 = micros_get();
	jet0.write(1);
break;}
	
case 1: {
	lastTime1 = micros_get();
	jet1.write(1);
break;}

case 2: {
	lastTime2 = micros_get();
	jet2.write(1);
break;}

case 3: {
	lastTime3 = micros_get();
	jet3.write(1);
break;}

}	
}

// ==
void INT1_interrupt_attach( void (*funcPtr)(void) ){
INT1_interruptCallback = funcPtr;
}
файл micros2.h
Спойлер

Код: Выделить всё

#ifndef MICROSJET_H
#define MICROSJET_H

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/power.h>
#include <util/atomic.h>

// Timer2
#if F_CPU > 1000000 // 1MHz - 20MHz
	#define CLOCKSEL 		(1 << CS21)
	#define MICROS_PRESCALER 		8
		
#elif F_CPU >= 125000 // 125KHz - 1MHz
	#define CLOCKSEL 		(1 << CS20)
	#define MICROS_PRESCALER 		1
		
#else
	#error "F_CPU out of range."
#endif

void micros_init(uint32_t f_cpu, uint16_t resolution);
uint32_t micros_get(void);
void micros_resume(void);
void micros_pause(void);
void micros_reset(void);
void micros_add(uint32_t us);
void micros_subtract(uint32_t us);
void micros_interrupt_attach( void (*funcPtr)(uint16_t) );

// ============ implementation ============
//	GLOBAL VARIABLES

// Ошибка калибровки. Отрицательное или положительное число
// которое будет добавлено или вычтено из значения регистра таймера.
enum err { MICROS_ERR_CAL = 0  };
	
// тип (*имя_указателя)(типы_параметров);
void (*interruptCallback)(uint16_t);

volatile uint32_t microseconds;
static uint16_t micros_resolution;

void micros_init(uint32_t f_cpu, uint16_t resolution ){// Timer settings
micros_resolution = resolution; // разрешение

// Clear registers
TCCR2A = 0;
TCCR2B = 0;
TCNT2 = 0;
	
TCCR2A |= (1 << WGM21); // CTC
TCCR2B |= CLOCKSEL; // Prescaler 8
TIMSK2 |= (1 << OCIE2A); // Output Compare Match A Interrupt Enable
OCR2A = (f_cpu / MICROS_PRESCALER / (1000000 / resolution)) + MICROS_ERR_CAL - 1;	

sei();	// enable Global Interrupts
}

// 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;
}

#endif // MICROSJET_H
Вложения
Test-1.png
(12.84 КБ) 116 скачиваний
Test-0.png
(83.61 КБ) 113 скачиваний
Ответить

Вернуться в «AVR»