Часть 16: DMA — Прямой доступ к памяти без участия процессора

Мы добрались до технологии, которая кардинально меняет правила игры. DMA (Direct Memory Access) — это периферийный модуль, который может пересылать данные между памятью и периферией без участия CPU. Это как если бы у вас появился второй процессор, который занимается только пересылкой данных.

Проблема: На ATmega328P (Arduino Uno) нет аппаратного DMA. Это недостаток архитектуры AVR. Но мы можем эмулировать DMA с помощью таймеров и прерываний, создавая программный «DMA-контроллер».

Решение: Используем таймер 2 (8-битный) для генерации прерываний с фиксированной частотой. В обработчике прерывания мы читаем байт из памяти и отправляем его в периферию.

Программный DMA для WS2812B (светодиодная лента)

Самый сложный протокол для Arduino без DMA — это WS2812B, где каждый бит кодируется длительностью импульса 0.8-1.2 мкс. Обычно мы используем библиотеку Adafruit_NeoPixel, которая занимает процессор на 100%.

Хак: Используем таймер 1 для создания точных задержек на уровне ассемблера, освобождая CPU для других задач:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>

#define LED_COUNT 60
#define BUFFER_SIZE (LED_COUNT * 3)  // 3 байта на пиксель (RGB)

uint8_t ledBuffer[BUFFER_SIZE];

// Настройка таймера 1 для генерации прерываний каждые 1.25 мкс
void setupDMATimer() {
    // Настраиваем пин для LED (пин 6 на PORTD)
    DDRD |= (1 << PD6);
    
    // Таймер 1 в режим Fast PWM с частотой 800 кГц
    TCCR1A = (1 << COM1A0) | (1 << WGM10);  // OC1A (пин 9) не используется
    TCCR1B = (1 << WGM12) | (1 << CS10);    // Прескалер 1 = 16 МГц
    
    // Устанавливаем период: 16 МГц / 800 кГц = 20 тактов
    OCR1A = 19;  // 800 кГц
    
    // Включаем прерывание по совпадению
    TIMSK1 = (1 << OCIE1A);
}

volatile uint8_t bitIndex = 0;
volatile uint8_t byteIndex = 0;
volatile uint8_t currentByte = 0;

ISR(TIMER1_COMPA_vect) {
    // Кодируем бит для WS2812B
    if (bitIndex == 0) {
        // Загружаем следующий байт
        if (byteIndex < BUFFER_SIZE) {
            currentByte = ledBuffer[byteIndex++];
            bitIndex = 8;
        } else {
            // Конец передачи
            PORTD &= ~(1 << PD6);  // LOW
            return;
        }
    }
    
    bitIndex--;
    
    // Отправляем бит
    if (currentByte & (1 << bitIndex)) {
        // Бит = 1: HIGH на 0.8 мкс, LOW на 0.4 мкс
        PORTD |= (1 << PD6);
        _delay_us(0.8);
        PORTD &= ~(1 << PD6);
        _delay_us(0.4);
    } else {
        // Бит = 0: HIGH на 0.4 мкс, LOW на 0.8 мкс
        PORTD |= (1 << PD6);
        _delay_us(0.4);
        PORTD &= ~(1 << PD6);
        _delay_us(0.8);
    }
}

void setup() {
    setupDMATimer();
    sei();
    
    // Заполняем буфер случайными цветами
    for (int i = 0; i < BUFFER_SIZE; i++) {
        ledBuffer[i] = random(255);
    }
}

void loop() {
    // CPU свободен! Можно делать что угодно
    // DMA отправляет данные в фоне
}

Вам также может понравиться

About the Author: ардуинчиков

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *