Отправка данных через 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.
@Vlad, 👍3
Обсуждение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
}
}
На случай, если кто-то попробует сделать то же самое.
Я считаю, что мне удалось заставить это работать так, как вы задумали, без 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)
{
}
}
Надеюсь, это поможет.
- Программирование ведомого SPI для Arduino
- Использование экрана SD-карты на Arduino Due
- Запуск передачи SPI с помощью прерывания в Arduino Due
- 16-битный SPI на Arduino Due
- Поддерживает ли MFRC522 собственный SPI Arduino Due?
- Проблема с настройкой Arduino Due SPI на АЦП.
- Arduino Due как Triggered DMA SPI Slave - возможно?
- Использование выводов ICSP в качестве SPI в Arduino Due
Я посмотрел на блок-схему (раздел 32.2), и мне интересно, действительно ли DMA может обращаться к SPI или мне нужно управлять каким-то промежуточным уровнем., @Vlad