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 Erich Styger, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}