Статьи

Обновлен логический анализатор Freedom Board с DMA

Вопрос: Как создать недорогой логический анализатор с открытым исходным кодом менее чем за 15 долларов?
Ответ: объедините плату Freedom KL25Z с OLS !

Сигнал 500 Гц с коэффициентом заполнения 50% в LogicSniffer

Сигнал 500 Гц с коэффициентом заполнения 50% в LogicSniffer

В последнее время я много имею дело с DMA (см. « Учебное пособие: PWM с DMA на ARM / Kinetis »), поэтому я нашел время, чтобы реорганизовать код для лучшего использования DMA. В « Freedom Logic Analyzer с DMA » мне нужно было соединить PTD2 и PTD0, потому что это было маршрутизация часов для триггера DMA. На самом деле, в этом нет необходимости, и это можно сделать прямо из таймера / DMA :-).

Общая концепция такова:

  1. AtimeronTPM0 / Channel 1 (pinPTD1) генерирует сигнал ШИМ (50%), который запускает передачу DMA. Поскольку PTD1 также является синим светодиодом RGB, я легко могу проверить скорость передачи DMA с помощью другого логического анализатора:

    Частота дискретизации DMA 1 МГц

    Частота дискретизации DMA 1 МГц

  2. Частота TPM0 / Channel1 определяет частоту дискретизации DMA вOLS:

    Частота выборки в OLS

    Частота выборки в OLS

  3. В приложении я настраиваю DMA и настраиваю частоту дискретизации по мере необходимости.
  4. Клиент OLS / SUMP делает все остальное: запускает и настраивает частоту, затем передает результаты клиенту.

Компоненты

Чтобы упростить программное обеспечение, я использую компоненты Processor Expert. Компонент Init_TPM инициализирует мой ШИМ, который затем активирует DMA:

Конфигурация TPM0

Конфигурация TPM0

При этом у меня базовая частота 48 МГц, генерирующая ШИМ с максимальной частотой 24 МГц с включенным запросом DMA.

Внутри приложения я использую макросы PDD для изменения частоты:

static void TMR_SetTimerValue(uint32_t val) {
  TPM_PDD_WriteModuloReg(TPM0_BASE_PTR, val); /* set period of TPM0 */
  TPM_PDD_WriteChannelValueReg(TPM0_BASE_PTR, 1, val/2); /* channel 1: PWM low 50% */
}

И он инициализируется с периодом 1 МГц и включенной передачей DMA для канала DMA 1

static void TMR_Init(void) {
  TMR_SetTimerValue(LOG_TMR_FREQ/1000000); /* default of 1 MHz */
  TPM_PDD_EnableChannelDma(TPM0_BASE_PTR, 1); /* enable DMA for channel */
}

Сам DMA настроен с адресами источника и назначения как это:

static void InitDMA(void) {
  /* enable DMA MUX0: */
  DMAMUX_PDD_EnableChannel(DMAMUX0_BASE_PTR, 0, PDD_ENABLE); /* enable DMA MUX0 */
  /* PIT triggering for DMA0: */
  DMAMUX_PDD_EnableTrigger(DMAMUX0_BASE_PTR, 0, PDD_DISABLE); /* disable PIT Trigger */
  /* use TPM0 overflow for DMA0 request: */
  DMAMUX_PDD_SetChannelSource(DMAMUX0_BASE_PTR, 0, 25); /* KL25Z reference manual, 3.4.8.1, p64: source number 25 TPM0 CH1 DMA source */
  
  /* DMA channel 0 source configuration: */
  DMA_PDD_SetSourceAddress(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, (uint32_t)&GPIOC_PDIR); /* set source address */
  DMA_PDD_SetSourceAddressModulo(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, DMA_PDD_CIRCULAR_BUFFER_DISABLED); /* no circular buffer */
  DMA_PDD_EnableSourceAddressIncrement(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, PDD_DISABLE); /* source address will be incremented by transfer size */
  DMA_PDD_SetSourceDataTransferSize(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, DMA_PDD_8_BIT); /* Transfer size from source  */
  
  /* DMA channel 0 destination configuration: */
  DMA_PDD_SetDestinationAddress(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, (uint32_t)&sampleBuffer[0]); /* set destination address */
  DMA_PDD_SetDestinationAddressModulo(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, DMA_PDD_CIRCULAR_BUFFER_DISABLED); /* no circular buffer */
  DMA_PDD_EnableDestinationAddressIncrement(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, PDD_ENABLE); /* auto-increment for destination address */
  DMA_PDD_SetDestinationDataTransferSize(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, DMA_PDD_8_BIT); /* Transfer to destination size */
  
  /* DMA channel 0 transfer configuration: */
  DMA_PDD_EnableTransferCompleteInterrupt(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, PDD_ENABLE); /* request interrupt at the end of the DMA transfer */
  (void)DMA_PDD_GetRequestAutoDisableEnabled(DMA_BASE_PTR, DMA_PDD_CHANNEL_0); /* disable DMA request at the end of the sequence */
}

В конце передачи DMA (с заполненным образцом буфера) он вызовет прерывание, где я установил флаг:

void LOGIC_OnComplete(void) {
  finishedSampling = TRUE;
}

Последний этап — начать выборку сигналов, что довольно просто: установите адрес назначения и количество байтов для передачи, а затем перейдите:

static void TransferDMA(void) {
  DMA_PDD_SetDestinationAddress(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, (uint32_t)&sampleBuffer[0]); /* set destination address */
  DMA_PDD_SetByteCount(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, bufferSize); /* set number of bytes to transfer */
  DMA_PDD_EnablePeripheralRequest(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, PDD_ENABLE); /* enable request from peripheral */
}

Все остальное сохраняется как есть, с незначительным рефакторингом. Дополнительным преимуществом является то, что количество исходных файлов уменьшено, и теперь все намного чище :-).

Резюме

Теперь у меня есть еще лучшее и более простое решение DMA для моего недорогого логического анализатора, которое хорошо работает с частотой дискретизации до 2 МГц. С 4 и до 24 МГц точность не очень хорошая, но до сих пор не ясно, что является причиной этого. Во всяком случае, это недорогой анализатор, и 2 МГц вполне прилично менее чем за 15 долларов :-). Единственное, что KL25Z имеет только 16 Кбайт оперативной памяти. Я рассматриваю возможность его портирования на FRDM-K64F, который имеет 256 Кбайт оперативной памяти и более быстрый процессор.

Проект был обновлен на GitHub. В проекте также есть файл S19, так что если у вас есть плата, вы можете просто запрограммировать плату с помощью загрузчика OpenSDA .

Счастливое нюхание ?