Over a million developers have joined DZone.

Updated Freedom Board Logic Analyzer with DMA

DZone's Guide to

Updated Freedom Board Logic Analyzer with DMA

· IoT Zone ·
Free Resource

Question: How to build a low-cost logic open source logic analyzer for less than $15?
Answer: combine the Freedom KL25Z board with OLS!

500 Hz signal with 50% duty cycle in LogicSniffer

500 Hz signal with 50% duty cycle in LogicSniffer

I’m dealing recently a lot with DMA (see “Tutorial: PWM with DMA on ARM/Kinetis“), so I took the time to refactor the code to use DMA in a better way. In “Freedom Logic Analyzer with DMA” I needed to bridge PTD2 and PTD0, because this was routing the clock for the DMA trigger. Actually, this is not necessary and can be done directly from the timer/DMA :-).

The overall concept is this:

  1. AtimeronTPM0/Channel 1 (pinPTD1)isgeneratingaPWM signal (50% duty) which triggers a DMA transfer.Because PTD1 is as well the blue RGB LED, I can easily check the DMA transfer rate with another logic analyzer:
    1 MHz DMA sampling frequency

    1 MHz DMA sampling frequency

  2. The frequencyofTPM0/Channel1determinestheDMA sampling rateinOLS:
    Sampling Rate in OLS

    Sampling Rate in OLS

  3. In the application, I configure the DMA and adjust the sampling frequency as needed.
  4. The OLS/SUMP client does the rest: triggers and configuring the frequency, then transmitting the results to the client.


To simplify the software, I’m using Processor Expert components. The Init_TPM component initializes my PWM which then will trigger the DMA:

TPM0 Configuration

TPM0 Configuration

With this, I have a base frequency of 48 MHz generating a PWM with a maximum frequency of 24 MHz, with DMA request enabled.

Inside the application, I’m using PDD macros to change the frequency:

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% */

And it gets initialized with 1 MHz period and DMA transfer enabled for DMA channel 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 */

The DMA itself gets configured with source and destination addresses like this:

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,, 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 */

At the end of the DMA transfer (with the sample buffer full), it will raise an interrupt, where I set a flag:

void LOGIC_OnComplete(void) {
  finishedSampling = TRUE;

The last piece is to start the sampling of the signals, which is pretty simple: set the destination address and byte count to transfer, and then go:

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 */

Everything else is kept as is, with some minor refactoring. The added benefit is that the number of source files is reduced, and everything is much cleaner now :-).


I have now an even better and simpler DMA solution for my low-cost logic analyzer, which works well up to 2 MHz sampling frequency. With 4 and up to 24 MHz the accuracy is not that good, still not clear what is causing this. Anyway, this is a low-cost analyzer, and 2 MHz is pretty decent for less than $15 :-). The only thing is that the KL25Z only has 16 KByte of RAM. I’m considering porting it to the FRDM-K64F which has 256 KByte RAM and a faster CPU.

The project has been updated on GitHub. The project has as well a S19 file so if you have a board, you simply can program the board with the OpenSDA bootloader.

Happy Sniffing :-)


Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}