DAC waveform generation with DMA on K64

キャンセル
次の結果を表示 
表示  限定  | 次の代わりに検索 
もしかして: 

DAC waveform generation with DMA on K64

1,208件の閲覧回数
davenadler
Senior Contributor II

I need to generate a repeating waveform on the DAC.
I would like to set up DMA from a table in memory with:

  • control over the DAC update rate, and
  • automatic repeat when the end of the table is reached.

Is there an example available showing how to do this?

Thanks,
Best Regards, Dave

0 件の賞賛
返信
5 返答(返信)

12件の閲覧回数
mjbcswitzerland
Specialist V

Dave

I see that you got caught out by the DAC HW trigger source. As far as I remember this is specified somewhere but I didn't locate it in the K64 user's manual when I just looked.

In the uTasker code I have

                if ((ptrDAC_settings->int_dac_controller == 0) && (DMAMUX0_DMA0_CHCFG_SOURCE_PIT0 != ptrDAC_settings->ucDmaTriggerSource)) {
                    _EXCEPTION("DAC0 can only be triggered from PIT channel 0!!");
                }

which exceptions in case this were to be attempted because one does forget such details occasionally and then it fails in an obvious manner before having to (re-)debug and finally remember that it is not the first time it happened...

Remember that your code is a reference for K parts and is not portable to low-power KL parts. The uTasker interface will run on any chip with DMA and a DAC. For portability you could encapsulate the DMA configuration part where you can then supply versions of it for the two different DMA controller implementations. The KL's DMA is not as powerful as the one in the K64 and it requires some tweaking to get identical operation but it is not difficult to achieve.

Regards

Mark

0 件の賞賛
返信

12件の閲覧回数
davenadler
Senior Contributor II
    dac_config_t dacConfigStruct;
    DAC_GetDefaultConfig(&dacConfigStruct);
    dacConfigStruct.referenceVoltageSource = kDAC_ReferenceVoltageSourceVref2;
    dacConfigStruct.enableLowPowerMode = false;
    DAC_Init(DAC0, &dacConfigStruct);
    dac_buffer_config_t dacBufferConfigStruct;
    DAC_GetDefaultBufferConfig(&dacBufferConfigStruct);
    dacBufferConfigStruct.triggerMode = kDAC_BufferTriggerBySoftwareMode;
    DAC_SetBufferConfig(DAC0, &dacBufferConfigStruct);
    DAC_SetBufferValue(DAC0, 0U, 0x7FF); // Succeeds: quick test to output 3.3v/2

    // Here's what we're trying to send to DAC:
    static const uint16_t DACdata[16] = {0x000, 0x0110, 0x220, 0x330, 0x440, 0x550, 0x660, 0x770, 0x880, 0x990, 0xAA0, 0xBB0, 0xCC0, 0xDD0, 0xEE0, 0xFF0, };

    // Use DMAMUX0 internal channel number 0 to connect PIT timer1 event to DMA channel 0 request
    DMAMUX_Init(DMAMUX0);
    DMAMUX_Type *pDMAMUX0 = DMAMUX0; // expose for debugger, since "Peripherals" viewer doesn't work in Kinetis Studio
    DMAMUX_DisableChannel(DMAMUX0, 0/* DMAMUX channel number */); // Disable channel prior configuring it
    // DMAMUX source is unused when using periodic trigger; unnecessary? except default source 0 is "Disable"?
    DMAMUX_SetSource(DMAMUX0, 0/* DMAMUX channel number */, kDmaRequestMux0AlwaysOn63 /*48 PDB*/);
    DMAMUX_EnablePeriodTrigger(DMAMUX0, 0/*uint32_t channel*/);
    DMAMUX_EnableChannel(DMAMUX0, 0/* DMAMUX channel number */);

    // Set up DMA channel 0 to read from data buffer and write to DAC
    DMA_Type* pDMA0 = DMA0; // expose for debugger, since "Peripherals" viewer doesn't work in Kinetis Studio
    edma_config_t edmaConfig;
    EDMA_GetDefaultConfig(&edmaConfig);
    //edmaConfig.enableContinuousLinkMode = true;
    EDMA_Init(DMA0,&edmaConfig);

    // Crappy FSL driver functions don't set SLAST etc; do it directly, aaarrrggggg....
    DMA0->CR = 0; // default mode of operation for this TCD (no minor loop mapping, etc)
    DMA0->TCD[0].SADDR = (uint32_t)(&DACdata[0]); // source address
    DMA0->TCD[0].DADDR = (uint32_t)(&DAC0->DAT);  // destination address
    // Source data and destination data transfer size
    DMA0->TCD[0].ATTR = DMA_ATTR_SSIZE(kEDMA_TransferSize2Bytes) | DMA_ATTR_DSIZE(kEDMA_TransferSize2Bytes);
    assert(DMA0->TCD[0].ATTR==0x0101);
    DMA0->TCD[0].SOFF = 2; // source address incremented by 2 after each 2-byte transfer
    DMA0->TCD[0].DOFF = 0; // destination address is fixed DAC; do not increment
    DMA0->TCD[0].NBYTES_MLNO = 2; // 2 bytes to DAC per transfer
    DMA0->TCD[0].SLAST = -16*2; // decrement source back to start address after completing a major loop
    DMA0->TCD[0].DLAST_SGA = 0; // destination address (DAC) is not adjusted after completing a major loop
    DMA0->TCD[0].BITER_ELINKNO =
      DMA0->TCD[0].CITER_ELINKNO = 16; // transfers per major loop
    DMA0->SERQ = DMA_SERQ_SERQ(0/*channel*/); // enable hardware requests for channel 0 (enable DMA channel)

    uint8_t dmaErr = DMA0->ES;
    assert(dmaErr==0); // no errors reported by DMA module
    uint8_t dmaErq = DMA0->ERQ;
    assert((dmaErq&1)==1); // DMA channel 0 is enabled

    // Set up PIT timer as a 5 msec timer: PIT channel 1 is hardwired to DMA channel 0 trigger
     #define PIT_CHANNEL kPIT_Chnl_1
    {
         pit_config_t pit_setup;
         PIT_GetDefaultConfig(&pit_setup);
         pit_setup.enableRunInDebug = 1;
         PIT_Init(PIT,&pit_setup);
    }
    PIT_SetTimerPeriod(PIT, PIT_CHANNEL, USEC_TO_COUNT(5000U, CLOCK_GetFreq(kCLOCK_BusClk)/*PIT_SOURCE_CLOCK*/));
    PIT_StartTimer(PIT, PIT_CHANNEL); // start the timer...

    // BUG: Timer doesn't start DMA (tried bit kPIT_Chnl_1 and kPIT_Chnl_0)...
  #if 1
    // Manually kicking the DMA channel below does transfer AOK into the DAC,
    // and successfully wraps around to start of buffer after major loop.
    for(int i=20; i; i--) {
         DMA0->SSRT = 0/*channel*/; // kick DMA channel 0 - manually force a minor loop transfer
        uint8_t dmaErr = DMA0->ES;
        assert(dmaErr==0);
        for(int j=2000000; j; j--) {};
    };
  #endif
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

I coded the following, which *almost* works. The DMA transfer is never started by the PIT. If I manually kick the DMA, transfer works properly so it looks like the DMA setup is OK but the triggering setup is not. Any idea what I've done wrong?
Thanks in advance,
Best Regards, Dave

0 件の賞賛
返信

12件の閲覧回数
mjbcswitzerland
Specialist V

David

Take the uTasker K64 project and enable TEST_DMA_DAC in the file ADC_Timers.h
This will give a 10kHz ramping waveform on the DAC0 output.

To generate the waveform that you want, change the PITs rate to 5ms and pass your own waveform to the DMA function. Attached is the setup for your case (I will also add it as content to a following post for convenience but this will need to be moderated by NXP and so may be delayed....).

I have attached a binary that can be loaded to the FRDM-K64F when build for this.
The resulting waveform is below.

pastedImage_1.png

If you run the uTasker project in Visual Studio you can step the code to see exact register setup if you prefer to copy it to a different environment. Use its simulator to watch the DMA operations taking place to understand all internal workings and see what may have been missing in your code. Simply use the project in case you prefer to have simplest solution with flexible and fool-proof HAL interface for this and most other requirements.

Regards

Mark

0 件の賞賛
返信

12件の閲覧回数
mjbcswitzerland
Specialist V

Dave

Here is the setup from the attached file for convenience (just replace fnConfigurePIT() for your personal setup).

static void fnConfigurePIT(void)
{    
    static const unsigned short DACdata[16] = { 0x000, 0x0110, 0x220, 0x330, 0x440, 0x550, 0x660, 0x770, 0x880, 0x990, 0xAA0, 0xBB0, 0xCC0, 0xDD0, 0xEE0, 0xFF0, };
    DAC_SETUP dac_setup;

    PIT_SETUP pit_setup;                                                 // interrupt configuration parameters
    pit_setup.int_type = PIT_INTERRUPT;
    pit_setup.int_handler = 0;                                           // no interrupt since the PIT will be used for triggering DMA
    pit_setup.int_priority = PIT0_INTERRUPT_PRIORITY;                    // not used
    pit_setup.count_delay = PIT_MS_DELAY(5);                             // 5ms sampling
    pit_setup.mode = (PIT_PERIODIC);                                     // periodic DMA trigger
    pit_setup.ucPIT = 0;                                                 // use PIT0
    fnConfigureInterrupt((void *)&pit_setup);                            // configure PIT

    dac_setup.int_type = DAC_INTERRUPT;
    dac_setup.int_dac_controller = 0;                                    // DAC 0
    dac_setup.int_handler = 0;                                           // no interrupt used
    dac_setup.int_priority = 15;                                         // lowest priority (not used in this case)
    dac_setup.dac_mode = (DAC_CONFIGURE | DAC_REF_VDDA | DAC_NON_BUFFERED_MODE | DAC_FULL_BUFFER_DMA | DAC_ENABLE | DAC_BUFFER_DMA_START); // configure the DAC to use VDDA as reference voltage in non-buffered mode (using DMA)

    dac_setup.ptrDAC_Buffer = (unsigned short *)DACdata;
    dac_setup.ulDAC_buffer_length = (sizeof(DACdata));
    dac_setup.ucDmaChannel = 0;                                          // DMA channel 0 used
    dac_setup.ucDmaTriggerSource = DMAMUX0_DMA0_CHCFG_SOURCE_PIT0;       // PIT0 triggers the channel mux
    dac_setup.dac_mode |= DAC_HW_TRIGGER_MODE;                           // use HW trigger mode rather than SW triggered mode (this requires PIT to trigger it)

    fnConfigureInterrupt((void *)&dac_setup);                            // configure DAC
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Regards

Mark

0 件の賞賛
返信

12件の閲覧回数
mjbcswitzerland
Specialist V
0 件の賞賛
返信