Обсуждаем контроллеры компании Atmel.
Ответить

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

Вс окт 08, 2023 08:15:34

А сделал я как предложила Just_Fluffy. Перфект! и респект ! Не знаю как поставить плюсик в телефоне.

В ПРОЕКТЕ ссылку на Just_Fluffy только нужно не забыть указать. А то ведь всё я, да я...)

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

Пн окт 09, 2023 07:47:08

Без этого никак. Будет ссылка :)

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

Пн май 27, 2024 19:35:40

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

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


камень 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 KiB) Скачиваний: 709
qqqqq.JPG
(46.95 KiB) Скачиваний: 720

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

Пн май 27, 2024 20:34:42

timex, у вас таймер переполняется каждые 8 мкс.
500 гц - это 2000 мкс, делаете в обработчике прерываний счетчик от 0 до (250-1) - и стартуете АЦП один раз в 250 прерываний

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

Пн май 27, 2024 21:19:16

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...
}

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

Пн май 27, 2024 22:07:23

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, мгут работать не всегда так, как ожидалось бы от них.
Вроде на сайте у Гайвера было про работу с таймерами в применении к ардуине.

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

Вт май 28, 2024 15:05:34

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

хотелка: АЦПировать с частотой в 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
//----------//

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

Ср май 29, 2024 17:53:26

... Например, используя другой режим. Например, 7 - ...


спасибо, уважаемый, я и забыл что тики-тиками, а прерывание по переполнению в 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)
в если в нём будут включены прерывания по переполнению И по сравнению, то они будут выполнятся оба, согласно их приоритетам, верно?

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

Ср май 29, 2024 21:23:51

если ты запишешь в OCR0A 249 и включишь режим 7 (top: OCR0A), то после 249 в счетчик запишется 0, и счетчик никогда не досчитает до 256, чтобы сработало переполнение.

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

Чт май 30, 2024 04:15:25

timex писал(а):...они будут выполнятся оба, согласно их приоритетам, верно?
Да. Сначала TIMER0 COMPA, затем TIMER0 OVF.

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

Чт май 30, 2024 08:59:40

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

timex писал(а):спасибо, уважаемый
уважаемая...

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

Чт июн 06, 2024 11:10:11

Искренне прошу помощи, не понимаю как реализовать задержку в 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 схема вообще не включается
Изображение

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

Пт июн 07, 2024 05:35:50

Не верю. Вы не запустили таймер, не разрешили прерывания. Даже если это будет выполнено, таймер будет отсчитывать не одну секунду, а гораздо больше. Как вариант отсчета 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

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

Пт июн 07, 2024 16:09:47

большое спасибо, надеюсь смогу откорректировать код самостоятельно

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

Вт июл 23, 2024 21:04:40

Добрый вечер. Моя задача относится к разряду "Помигать светодиодом". Это если рассматривать задачу как упрощенную модель. На самом же деле это драйвер открытия/закрытия топливной форсунки инжекторной системы фазированного распределенного впрыска четырехцилиндрового двигателя внутреннего сгорания. В принципе задачу я решил. Но, как говорится, тупо и в лоб. Во всяком случае в протеусе все работает. В целом написание прошивки для ЭБУ я решил по возможности разделить на отдельные блоки. Вычислительная часть(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 KiB) Скачиваний: 65
Pulse.png
(8.51 KiB) Скачиваний: 58
SPI_Master_Slave.png
(83.77 KiB) Скачиваний: 59

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

Чт июл 25, 2024 11:28:30

какова минимальная скважность работы форсунок?
(может ли наслаиваться открытие одной форсунки на открытие второй, третьей....)

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

Чт июл 25, 2024 16:13:23

Эйлер Леонард писал(а):инкриминирует
если не знаешь, как пишется слово, посмотри в словаре.
а инкриминировать можно кому-то, например, преступление.

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

Чт июл 25, 2024 20:58:18

"Задержанному инкрементировали ещё и сопротивление полиции"
:)

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

Пт июл 26, 2024 00:35:50

Я ни на какие вопросы отвечать не буду без присутствия моего авокадо!

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

Вт июл 30, 2024 21:05:39

Добрый вечер.
Решено. Использовал 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 KiB) Скачиваний: 53
Test-0.png
(83.61 KiB) Скачиваний: 49
Ответить