DAC Sinus Demo using new DMA, PDB0 Trigger, need help!

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

DAC Sinus Demo using new DMA, PDB0 Trigger, need help!

1,596 Views
markwyman
Contributor III

Hi All,

I am struggling a bit with the Sinus demo conversion between the old DMA example included in the PE documentation and the new DMA method. I am *almost* there, but not quite.

 

I copied the Sinus table and interrupt code right from the Demo:

In events.c:




void DA1_OnComplete(LDD_TUserData *UserDataPtr)
{
 /* Write your code here ... */
 static uint8_t ArrayPageCntr = 0U;
 DA1_SetBuffer(MyDacPtr, &Sinus[ArrayPageCntr*16U], 16U, 0U);
 (ArrayPageCntr < 15) ? (ArrayPageCntr++):(ArrayPageCntr = 0);
}




And in my int_dac.c:


uint16_t Sinus[SINUS_LENGTH] = {
 2047U, 2097U, 2147U, 2198U, 2248U, 2298U, 2347U, 2397U, 2446U, 2496U, 2544U, 2593U, 2641U, 2689U,
 2737U, 2784U, 2830U, 2877U, 2922U, 2967U, 3012U, 3056U, 3099U, 3142U, 3184U, 3226U, 3266U, 3306U,
 3346U, 3384U, 3422U, 3458U, 3494U, 3530U, 3564U, 3597U, 3629U, 3661U, 3691U, 3721U, 3749U, 3776U,
 3803U, 3828U, 3852U, 3875U, 3897U, 3918U, 3938U, 3957U, 3974U, 3991U, 4006U, 4020U, 4033U, 4044U,
 4055U, 4064U, 4072U, 4079U, 4084U, 4088U, 4092U, 4093U, 4094U, 4093U, 4092U, 4088U, 4084U, 4079U,
 4072U, 4064U, 4055U, 4044U, 4033U, 4020U, 4006U, 3991U, 3974U, 3957U, 3938U, 3918U, 3897U, 3875U,
 3852U, 3828U, 3803U, 3776U, 3749U, 3721U, 3691U, 3661U, 3629U, 3597U, 3564U, 3530U, 3494U, 3458U,
 3422U, 3384U, 3346U, 3306U, 3266U, 3226U, 3184U, 3142U, 3099U, 3056U, 3012U, 2967U, 2922U, 2877U,
 2830U, 2784U, 2737U, 2689U, 2641U, 2593U, 2544U, 2496U, 2446U, 2397U, 2347U, 2298U, 2248U, 2198U,
 2147U, 2097U, 2047U, 1997U, 1947U, 1896U, 1846U, 1796U, 1747U, 1697U, 1648U, 1598U, 1550U, 1501U,
 1453U, 1405U, 1357U, 1310U, 1264U, 1217U, 1172U, 1127U, 1082U, 1038U, 995U, 952U, 910U, 868U,
 828U, 788U, 748U, 710U, 672U, 636U, 600U, 564U, 530U, 497U, 465U, 433U, 403U, 373U,
 345U, 318U, 291U, 266U, 242U, 219U, 197U, 176U, 156U, 137U, 120U, 103U, 88U, 74U,
 61U, 50U, 39U, 30U, 22U, 15U, 10U, 6U, 2U, 1U, 0U, 1U, 2U, 6U,
 10U, 15U, 22U, 30U, 39U, 50U, 61U, 74U, 88U, 103U, 120U, 137U, 156U, 176U,
 197U, 219U, 242U, 266U, 291U, 318U, 345U, 373U, 403U, 433U, 465U, 497U, 530U, 564U,
 600U, 636U, 672U, 710U, 748U, 788U, 828U, 868U, 910U, 952U, 995U, 1038U, 1082U, 1127U,
 1172U, 1217U, 1264U, 1310U, 1357U, 1405U, 1453U, 1501U, 1550U, 1598U, 1648U, 1697U, 1747U, 1796U,
 1846U, 1896U, 1947U, 1997U
};
LDD_TError Error;
LDD_TDeviceData *MyDacPtr;




void DAC_init(void)
{
 MyDacPtr = DA1_Init(NULL);
}

 

I have the DAC set up exactly the same way as in the documentation, with a 16-word Hardware Buffer with a Hardware Trigger from the PDB. The DAC hardware appears to be working just fine, and I get signal out. I get regular complete interrupts for the DMA as well. It just seems my DMA setup is incorrect as the last 2 words written to the DAC output appear to be delayed and present the incorrect data, though it is still altered data from the DAC hardware table.

 

I have screen caps of my settings in DMA Help.pdf if anyone cares to take a look, as well as a scope image to display what I am seeing. The "glitches" in the sinusoidal waveform are the last two samples sent according to setting the breakpoint in the DA1_OnComplete() event.

 

The root of what I need:

16 2-byte values copied into the DAC hardware buffer from my Sinus[] table every time the DAC has completed sending out the last 16 2-byte values and increment source buffer to the next 16 2-byte value. At the end of the source buffer (multiple of 16) I rotate back to the beginning of the table.

 

Ideally I don't even want the interrupt to need to move things to the next 16 2-byte values in my Sinus[] table, and return to the top of the table all on its own. I see some checks that may do that, but they don't seem to account for the total table size?

 

Thank you for any help. New to using DMAs, I never had the luxury before.

Labels (1)
Tags (1)
0 Kudos
5 Replies

1,164 Views
davenadler
Senior Contributor I

Hi Mark - I'm using a K64F which doesn't have PDB0 connected in any useful way, so I use PIT0.
Anyway, perhaps you will find this useful: https://community.nxp.com/thread/436140
Hope that helps,
Best Regards, Dave

0 Kudos

1,164 Views
markwyman
Contributor III

Thanks Mark,

I have had great experiences with PE in the past, the output source code it generates is very well documented, clean, and is "user code style independent". On top of it, the built-in clock mode/power state support is not to be underestimated. Nobody accounts for the effort it takes to drop to low-power states until it is late in the project cycle. PE has that up front, and it is somewhat easy to turn on as an afterthought. Oh, and for the most part, normally, it takes very little time to get peripherals up and running. Also... erratas are sometimes accounted for that I would have been otherwise hung up on. That aside...

Quick question, how are you inserting your code into the post? I don't see a way to do that.

I am closer now, but still having issues. I got rid of  my OnComplete() interrupt routine that moved the buffer point through the table, and then set my Transfer Settings (as seen in the prior post) to 16 transition counts, and 16 request counts, which should scan my 256 sample table. The first 16 to copy 16 samples into the DAC buffer, the next 16 major loops to progress through my table 16 times to pull all 256 samples.

Now I get a good solid 2-sample glitch at the restart of the table rather than every 16 samples as before, and very small short glitches every 16 samples (as if there is a race condition while DMA is writing new values into the buffer).

I think I may have to take a different approach and reduce my number of samples to 16 and use an analog filter on the output in order to get my generated frequency higher anyhow. This means I can ditch DMA and move on with things. I would still like to see an example for this DMA controller that works. 

Thanks again,

-Mark W

0 Kudos

1,164 Views
mjbcswitzerland
Specialist V

Hi Mark

As I stated, I don't know PE since I prefer a high level of control of complete project code in a manner that is processor independent (one code that runs on all Kinetis devices and so doesn't need porting and has compatible ones for other processor families such as Coldfire and LPCs).
I didn't say that I know that it doesn't work well but I think that it doesn't necessarily offer as broad a range of solutions compared to a maintained library. Also, not everyone prefers to use it (some like bare metal, some like high-end pre-emptive OS and it is not possible to please everyone all the time). However I work on about 30..40 Kinetis based product developments a year and have also rescued some that started using such methods but ran into the weeds, therefore I am quite convinced that there are different methods that can be used to get the required results and only be comparing them does one realise which is the best for each case. Personally, I use simulation massively since it allows (for me) highest efficience in developent, testing and debugging and avoids much head-scratching because it is very rare that HW debugging is necessary.
- Clocking/low power: I always have this integrated in the framework of every project so the processor always waits when there are no pending tasks. One can set the wait/sleep level dynamically to other levels whenever needed, use port and module wakeups/alarms without any need to develop anything: http://www.utasker.com/kinetis/LLWU.html
- Peripherals: I have a framework that includes all peripherals, optional DMA, etc. that needs no development to use and has been proven in several years of industrial environments in hundreds of products. As well as intergrated stacks like TCP/IP, USB, FAT, MODBUS so again simple defines allows switching in and out large functionality groups.
- Erratas: I can set the processor's MASK as a define and all relevant erratas are worked around. In case of known restrictions, the simulation will warn of limitations when they are hit.
- Since the framework has been developed over 12 years on Coldfire, LPCs, Kinetis based on real-world project requirements there are many modes/modules that have been added to solve such needs that are not found in traditional development chains (such as FAT emulation: http://www.utasker.com/docs/uTasker/uTaskerEmulatedFAT.pdf) but find repeated use to elegantly solve many reoccurring project requirements.

Enough ranting:

To post code you need to click on the "Expand Toolbar" to see all options. Then in the "More" drop-down menu selected "Syntax highlighter". Then chose C as Language and past in your code.
Beware that the down side of this is that the post needs to be "moderated" and so, depending on who is moderating/time zone etc., may delay it by several minutes to several hours.

DMA:
Since I haven't see the code I don't really understand what is going on yet so will wait to see it first. It may be possible to plug it into the uTasker Kinetis simulator to see the behavior and then explain it.

Regard

Mark

0 Kudos

1,164 Views
markwyman
Contributor III

Thanks Mark,

After fighting a few issues with the PE beans (such as the SIM_SOPT7 register not being handled by the trigger select in the ADC bean) among other head scratchers, I am in agreement with you. My next project will likely be developing in a very similar fashion of what you are using.

When I am trying to concentrate on the hardware side of things on my boards, and just want to tweak a few values, PE makes a great deal of sense. Especially when I only get my hands on these things one a year at best. It is difficult to get up to speed on an overwhelming SDK to understand where everything is, and how to use it properly, especially on a new to me processor. PE is good at wrapping all that up so I can get on with testing hardware. Oh, and it is free, which is pretty important on this proof of concept project.

After all I am a software guy that has been wrangled into too much analog and RF design with a heavy emphasis in PCB layout... No time for the stuff I like to do. So thanks for your time and help!

It took me all of about 20 seconds to stuff my SIN table into the 16-sample DAC buffer, set the PDB timer appropriately, and crank out a signal at 13.3kHz. It is ugly looking and full of steps, but a little analog LPF will clean that right up! No DMA, no oddities, no BS. After all, this is all to test some ideas out.

I looked at posting the DMA code, and it is not easy to read since it is a "jack of all trades" generated from PE, and the values stuffed into registers are generated by the GUI. In other words, I think it is just a little more work than I can ask of someone to do on my behalf when I am probably not going to use it. I was hoping there would be a Freescale/NXP/(Qualcomm?) expert here that would simply say "hey! poke an offest of -2 in this register after a full transfer to prevent that!" Instead I get a memory dump through the DAC when I mess things up.

I get the feeling based on recent flurries of activities on the SDK releases, that PE is no longer being heavily developed. Also the lack of updated documentation is telling. Oh well.

Thanks

0 Kudos

1,164 Views
mjbcswitzerland
Specialist V

Hi

I don't have any experience with the PE generated code (I prefer 'hand-crafted' and well tested code).

If you post the complete code I may however be able to tell you what is going wrong though or what is missing.

As reference, the uTasker project includes powerful and simply DMA configurations that works on any Kinetis with DMA (also allowing KL parts to do the same without needing any end of buffer interrupts). Below is how it is done from a PIT (to use PDB, a PDB would be setup instead as time base and the DMA trigger set to it [DMAMUX0_CHCFG_SOURCE_PDB] rather than the PIT trigger [DMAMUX0_DMA0_CHCFG_SOURCE_PIT0]).
The uTasker project also simulates the Kinetis parts (in approx.) real time, including the interrupt and DMA operation in order to better understand and control the behavior as well as debugging the causes when it doesn't operate as expected.

There is also a discussion in http://www.utasker.com/docs/uTasker/uTaskerADC.pdf  and the same technique can be used to generate such signals on PWM outputs (to increase the number of DACs available).

Regards

Mark

#define LENGTH_OF_SINE_BUFFER    128                                     // number of points in the sine wave period (frequency = sample frequency / LENGTH_OF_SINE_BUFFER) 
                                                                         // LENGTH_OF_SINE_BUFFER shoudl be 64, 128, 256 etc. for automatic KL DMA circular buffer operation

// PIT configuration
//
static void fnConfigurePIT(void)
{
    static unsigned short *ptrSineBuffer = 0;
    DAC_SETUP dac_setup;
    PIT_SETUP pit_setup;                                                 // interrupt configuration parameters
    pit_setup.int_type = PIT_INTERRUPT;
    pit_setup.int_priority = PIT0_INTERRUPT_PRIORITY;                    // not used
    pit_setup.count_delay = PIT_US_DELAY(100);                           // 10kHz sample frequency
    pit_setup.mode = (PIT_PERIODIC);                                     // periodic DMA trigger
    pit_setup.int_handler = 0;                                           // no interrupt since the PIT will be used for triggering DMA
    ptrSineBuffer = uMallocAlign((LENGTH_OF_SINE_BUFFER * sizeof(unsigned short)), (LENGTH_OF_SINE_BUFFER * sizeof(unsigned short))); // get buffer aligned for KL DMA circular buffer operation
    // Define the waveform to be generated
    //
    {
        #define PI           (double)3.14159265
        #define ANGLE_STEP   (double)((double)(2 * PI)/(double)LENGTH_OF_SINE_BUFFER);
        int i;
        double angle = 0;
        for (i = 0; i < LENGTH_OF_SINE_BUFFER; i++) {                    // prepare a sine wave
            ptrSineBuffer[i] = (unsigned short)((sin(angle) * 0x7ff) + (0xfff/2)); // one period of sine wave with half-supply DC value
            angle += ANGLE_STEP;
        }
    }
    pit_setup.ucPIT = 0;                                                 // use PIT0
    fnConfigureInterrupt((void *)&pit_setup);                            // configure and start PIT
    // Configure DAC to use PIT0 triggered DMA
    //
    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 = ptrSineBuffer;                             // the input buffer to be used
    dac_setup.ulDAC_buffer_length = (LENGTH_OF_SINE_BUFFER * sizeof(unsigned short)); // the number of bytes in the buffer
    dac_setup.ucDmaChannel = 0;                                          // DMA channel 0 used (PIT0 can onyl trigger DMA channel 0)
    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 and astart generation of repetitive output signal
}
0 Kudos