Отправка данных через SPI с DMA

Мне нужно как можно быстрее отправить данные с Arduino DUE на внешний ЦАП. Для этого я использую DMA & SPI и я хочу, чтобы DMA извлекал данные из памяти и отправлял их в SPI, который просто ретранслировал их через вход Master Output Slave. До сих пор я делал передачу DMA из одной переменной в другую, проснулся отлично. Я использую тот же код, но меняю адрес на SPI_TDR (регистр передачи данных), к сожалению, он не работает. Я предполагаю, что адрес не подходит, но если да, то что мне делать?

Вот мой код:

#include <dmac.h>
#include <SPI.h>

#define DMA_CH 0         //№ канала прямого доступа к памяти
#define DMA_BUF_SIZE 32  //Запоминание DMA

uint32_t g_dma_buf2[DMA_BUF_SIZE];

void setup() {
  Serial.begin(9600);
  SPI.begin();

  SPI0->SPI_WPMR = 0x53504900;             //Ключ защиты
  SPI0->SPI_IDR = 0x0000070F;              //Прерывания деактивации
  SPI0->SPI_MR = SPI_MR_MSTR | SPI_MR_PS;  // мастер SPI
}

void loop() {
  Serial.println("+++++");
  pmc_enable_periph_clk(ID_DMAC);

  uint32_t i;
  uint32_t cfg;
  dma_transfer_descriptor_t desc;

  for (i = 0; i < DMA_BUF_SIZE; i++) {
    g_dma_buf2[i] = i;
    Serial.print(g_dma_buf2[i]);
  }
   Serial.println();

  dmac_init(DMAC);
  dmac_set_priority_mode(DMAC, DMAC_PRIORITY_ROUND_ROBIN);
  dmac_enable(DMAC);
  cfg = DMAC_CFG_SOD_ENABLE | DMAC_CFG_AHB_PROT(1) | DMAC_CFG_FIFOCFG_ALAP_CFG;  //Регистр конфигурации CFG
  dmac_channel_set_configuration(DMAC, DMA_CH, cfg);

  desc.ul_source_addr = (uint32_t)g_dma_buf2;
  desc.ul_destination_addr = SPI0->SPI_TDR;
  desc.ul_ctrlA = DMAC_CTRLA_BTSIZE(DMA_BUF_SIZE) | DMAC_CTRLA_SRC_WIDTH_WORD | DMAC_CTRLA_DST_WIDTH_WORD;
  desc.ul_ctrlB = DMAC_CTRLB_SRC_DSCR_FETCH_DISABLE | DMAC_CTRLB_DST_DSCR_FETCH_DISABLE | DMAC_CTRLB_FC_MEM2MEM_DMA_FC | DMAC_CTRLB_SRC_INCR_INCREMENTING | DMAC_CTRLB_DST_INCR_FIXED;
  desc.ul_descriptor_addr = 0;

  SPI_Enable(SPI0);
  dmac_channel_multi_buf_transfer_init(DMAC, DMA_CH, &desc);
  dmac_channel_enable(DMAC, DMA_CH);
  Serial.println("*****");

  while (!dmac_channel_is_transfer_done(DMAC, DMA_CH)) { Serial.print('X'); }
Se
  Serial.print("SR : "); Serial.println(SPI0->SPI_SR, HEX);
  Serial.print("TDR : "); Serial.println(SPI0->SPI_TDR, HEX);
  Serial.print("PSR : "); Serial.println(PIOA->PIO_PSR, HEX);   //PIO_SODR
  Serial.print("OSR : "); Serial.println(PIOA->PIO_OSR, HEX);
  Serial.println(DMAC->DMAC_CH_NUM[0].DMAC_SADDR , HEX);
  Serial.println(DMAC->DMAC_CH_NUM[0].DMAC_DADDR, HEX);
  Serial.println("-----");
}

В основном я использую этот пример: _OPENTOPIC_TOC_PROCESSING_d91e3076">https://ww1.microchip.com/downloads/en/Appnotes/Atmel-42291-SAM3A-3U-3X-4E-DMA-Controller-DMAC_ApplicationNote_AT07892.pdf#_OPENTOPIC_TOC_PROCESSING_d91e3076 А вот техническое описание микрочипа: https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-11057-32-bit-Cortex-M3-Microcontroller-SAM3X-SAM3A_Datasheet.pdf

Вы можете видеть в нижней части моего кода несколько отпечатков, из них у меня появилось много идей: может ли PIO блокировать данные? Адрес PIO_PA26A_SPI0_MOSI может работать? Может ли SPI заблокировать данные из-за невыполнения условий?

Приветствуются любые идеи, я занимаюсь этим некоторое время.

Редактировать: SPI не является необходимостью, идея состоит в том, чтобы отправлять данные без ограничения длины (в отличие от UART). Я рассматриваю возможность использования SSC.

, 👍3

Обсуждение

Я посмотрел на блок-схему (раздел 32.2), и мне интересно, действительно ли DMA может обращаться к SPI или мне нужно управлять каким-то промежуточным уровнем., @Vlad


2 ответа


Лучший ответ:

2

Мне удалось решить эту проблему, но не с помощью SPI, я использую SSC µcontroller (который выполняет I²S и автоматически использует DMA):

uint32_t liste[] PROGMEM = { 0x8F66F1, 0x0, 0xAAAAAB };  //Данные для отправки
#define DMA_BUF_SIZE (sizeof(liste) / sizeof(liste[0]))  //Размер буфера DMA

uint32_t i;

void setup() {
  uint clk_div = 0x90;  //Требуется разделитель часов
  clk_div = (clk_div / 24) / DMA_BUF_SIZE;
  clk_div = floor(clk_div + 0.5);
  clk_div = clk_div * 24 * DMA_BUF_SIZE;  //Скорректированный делитель часов -> MCK/2*clk_div

  PMC->PMC_WPMR = 0x504D4300;                                    //Деактивация защиты PMC
  PMC->PMC_PCER0 = (1 << ID_SSC);                                //Активация часов SSC
  PMC->PMC_SCER |= 0x100;                                        //часы активации
  PIOA->PIO_WPMR = 0x50494F00;                                   //Защита от деактивации Порт I/OA
  PIOA->PIO_PDR = PIO_PDR_P14 | PIO_PDR_P15 | PIO_PDR_P16;       //Ввод/вывод, управляемый периферийным устройством
  PIOA->PIO_ABSR |= PIO_PA14B_TK | PIO_PA15B_TF | PIO_PA16B_TD;  //Назначение ввода/вывода SSC

  SSC->SSC_CR = SSC_CR_RXDIS | SSC_CR_TXDIS | SSC_CR_SWRST;                                                     //Деактивация и сброс SSC
  SSC->SSC_WPMR = 0x53534300;                                                                                   //Защита от деактивации SSC
  SSC->SSC_IDR = 0xFFFFFFFF;                                                                                    //Прерывания деактивации
  SSC->SSC_IER = 0x00000000;                                                                                    //Прерывания деактивации bis
  SSC->SSC_CMR = clk_div;                                                                                       //Управление часами
  SSC->SSC_TFMR = SSC_TFMR_DATLEN(0x18) | SSC_TFMR_MSBF | SSC_TFMR_DATNB(0);                                    //Управление передачей данных
  SSC->SSC_TCMR = SSC_TCMR_CKS_MCK | SSC_TCMR_CKO_CONTINUOUS | SSC_TCMR_START_CONTINUOUS | SSC_TCMR_STTDLY(0);  //Часы во время управления переводом
  SSC->SSC_CR = SSC_CR_TXEN;                                                                                    //Активация передачи SSC
}

void loop() {
  for (i = 0; i < DMA_BUF_SIZE; i++) {
    ssc_write((Ssc*)SSC, (uint32_t)liste[i]);  //Передача SSC
  }
}

На случай, если кто-то попробует сделать то же самое.

,

0

Я считаю, что мне удалось заставить это работать так, как вы задумали, без SSI. Части этого кода были заимствованы из библиотеки SD-карт (https://github .com/openbci-archive/OpenBCI_8bit/blob/master/OpenBCI_8bit_SDfat_Library/SdFat/SdSpiSAM3X.cpp, около строки 110). Я пытаюсь запустить TFT, используя «кадровый буфер», т.е. но для этого мне нужен DMA, работающий с SPI.

В исходном коде кажется, что вам не хватает некоторых вещей в cfg. Согласно приведенному выше листингу кода, cfg должен выглядеть примерно так: DMAC_CFG_DST_PER(1) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD_ENABLE | DMAC_CFG_AHB_PROT(1) | DMAC_CFG_FIFOCFG_ALAP_CFG. Согласно таблице данных, DST_PER должен быть установлен на один из каналов DMA, показанных на странице 339, таблица 22-2. В данном случае используется канал 1, поскольку он соответствует SPI0_TX. Аналогично, вам, похоже, не хватает DMAC_CFG_DST_H2SEL для аппаратного подтверждения связи. Вероятно, есть еще несколько вещей, на которые я не обращаю внимания, так как с этим у меня тоже были немало проблем.

TL;DR Следующий код должен помочь с базовой передачей DMA из буфера:

#include "sam.h"

#define CLOCK_SPEED 84000000

void setup_spi()
{
    PMC->PMC_PCER0 |= (1 << ID_PIOA); // Включаем PIOA

    PIOA->PIO_ODR |= PIO_PA25;      // MISO-контакт — ввод
    PIOA->PIO_PDR |= PIO_PA25;
    PIOA->PIO_ABSR &= ~(PIO_PA25);  // Вывод MISO — (A) Периферийное устройство
    
    PIOA->PIO_OER |= PIO_PA26;      // Вывод MOSI — выход
    PIOA->PIO_PDR |= PIO_PA26;
    PIOA->PIO_ABSR &= ~(PIO_PA26);  // Вывод MOSI — (A) Периферийное устройство

    PIOA->PIO_OER |= PIO_PA27;      // Вывод SCLK — выход
    PIOA->PIO_PDR |= PIO_PA27;
    PIOA->PIO_ABSR &= ~(PIO_PA27);  // Вывод SCLK — (A) Периферийное устройство

    PIOA->PIO_OER |= PIO_PA28;      // CS-контакт 0 — выход
    PIOA->PIO_PDR |= PIO_PA28;
    PIOA->PIO_ABSR &= ~(PIO_PA28);  // CS Pin 0 - (A) Периферийное устройство
    PIOA->PIO_PUER |= PIO_PA28;

    
    // Отключаем SPI для его настройки
    SPI0->SPI_CR |= SPI_CR_SPIDIS;

    // Настройка SPI0 --------------
    PMC->PMC_PCER0 = (1 << ID_SPI0);        // Включаем часы SPI0

    // Мастер-режим, фиксированный периферийный режим, режим обнаружения ошибок отключен, фиксированные периферийные режимы
    SPI0->SPI_MR = SPI_MR_MSTR | SPI_MR_MODFDIS;
    
    // Режим и расчет тактовой частоты
    SPI0->SPI_CSR[0] |= SPI_CSR_NCPHA | (((int8_t)(CLOCK_SPEED / 1000000)) << 8);       // Необходимо закрепить расчет тактовой частоты в байте 2
    
    // 8 бит на передачу
    SPI0->SPI_CSR[0] |= SPI_CSR_BITS_8_BIT;
    
    SPI0->SPI_CR |= SPI_CR_SPIEN;           // Включаем SPI0

}

uint8_t pixbuf[] = "This is a test!"; // Буфер для передачи

void setup_dma()
{
    PMC->PMC_PCER1 |= (1 << (ID_DMAC - 32)); // Включаем часы контроллера DMA (?)
    
    DMAC->DMAC_EN = 0; // Отключаем контроллер DMA
    
    DMAC->DMAC_GCFG = DMAC_GCFG_ARB_CFG_ROUND_ROBIN; // Устанавливаем метод арбитража
    
    DMAC->DMAC_EN = 1; // Включаем контроллер DMA
    
    DMAC->DMAC_CHDR = DMAC_CHDR_DIS0; // Отключаем канал DMA 0
    
    DMAC->DMAC_CH_NUM[0].DMAC_SADDR = (uint32_t)pixbuf; // Устанавливаем исходный буфер
    DMAC->DMAC_CH_NUM[0].DMAC_DADDR = (uint32_t)&SPI0->SPI_TDR; // Установка буфера dst
    DMAC->DMAC_CH_NUM[0].DMAC_DSCR = 0x0;
    
    DMAC->DMAC_CH_NUM[0].DMAC_CTRLA = DMAC_CTRLA_DST_WIDTH_BYTE | DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_BTSIZE(0x10); // Устанавливаем разрядность источника/назначения (8 бит) и устанавливаем размер буфера равным 16
    DMAC->DMAC_CH_NUM[0].DMAC_CTRLB = DMAC_CTRLB_DST_INCR_FIXED | DMAC_CTRLB_SRC_INCR_INCREMENTING | DMAC_CTRLB_FC_MEM2PER_DMA_FC | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC;
    
    DMAC->DMAC_CH_NUM[0].DMAC_CFG = DMAC_CFG_DST_PER(1) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD_ENABLE | DMAC_CFG_AHB_PROT(1) | DMAC_CFG_FIFOCFG_ALAP_CFG; // Настраиваем контроллер DMA
    
    DMAC->DMAC_EBCISR; // Чтение регистра прерывания для очистки флагов прерывания
    
    DMAC->DMAC_CHER = DMAC_CHER_ENA0; // Включаем канал DMA 0
}

int main(void)
{
    /* Initialize the SAM system */
    SystemInit();
    
    setup_dma();
    setup_spi();
    while (!(DMAC->DMAC_CHSR & DMAC_CHSR_ENA0 << 0));
    uint32_t dma_status;

    dma_status = DMAC->DMAC_EBCISR;
    //SPI0->SPI_TDR = 'a'; // Выводим фиктивную информацию
    
    /* Replace with your application code */
    while (1) 
    {
    }
}

Надеюсь, это поможет.

,