memcpy command alternating data while copying

Viewed 380

I am using a STM32 G474 to create a wavefrom with it's internal DAC. I give a lookup table to the direct memory access (DMA) module and that gives the values at the right time to the corresponding DAC channel. What sounds like the hard part is actually pretty straight forward and works just fine.

#define NS  64                             # number of samples

uint32_t Wave_Low[NS] = {2048,[...],2047};  # lookup table

int main(void)
{
HAL_DAC_Start_DMA( &hdac2,   DAC_CHANNEL_1, (uint32_t*)Wave_High, NS, DAC_ALIGN_12B_R);
*/ start DMA       use DAC2  channel 1  */
}

As the next step I want to change the signal form within the code. As I want this to happen without interruption, stopping the DMA and reinitializing it doesn't work (there is a 500 µs delay without a signal in between). Therefore I need to overwrite the lookup table. I've tried it like this:

#define NS  64                             # number of samples

uint32_t Wave_Low[NS] = {2048,[...],2047};  # lookup table 1
uint32_t Wave_High[NS] = {4096,[...],4067}; # lookup table 2
uint32_t Wave_Active[NS];                  #used lookup table

int main(void)
{  
memcpy(Wave_Active , Wave_High, NS );      #assign high wave as the currently used one

HAL_DAC_Start_DMA( &hdac2,   DAC_CHANNEL_1, (uint32_t*)Wave_Active, NS, DAC_ALIGN_12B_R);
*/ start DMA       use DAC2  channel 1  */
}

From my understanding this code should show the exact same behavior but the DAC signal differs significantly by showing the positive part of a sawtooth signal instead of the centered sine wave it's supposed to show. I'm a bit rusty with embedded C but that behavior definitely irritates me.

3 Answers

Several problems:

  • DMA buffers need to be volatile qualified or otherwise the compiler might go bananas when generating the code accessing them.

  • You use memcpy incorrectly, should have been memcpy(Wave_Active , Wave_High, sizeof Wave_Active);

  • The use of memcpy to begin with is often incorrect when it comes to hardware-related programming. Copying 256 bytes takes a lot of time. Worst case, your DAC might even request new data before you are done copying.

    The correct way to write such code would be to have several allocated buffers, then swap an "active" pointer to point at the one used. With the disclaimer that I don't understand the purpose of these arrays, something like this would be an immense speed optimization:

      volatile uint32_t Wave_Low[NS] = {2048,[...],2047};  # lookup table 1
      volatile uint32_t Wave_High[NS] = {4096,[...],4067}; # lookup table 2
      volatile uint32_t* Wave_Active = Wave_High;
    
      ...
      if(DMA_flag)
      {
        Wave_Active = (Wave_Active==Wave_Low) ? Wave_High : Wave_Low;
    
        /* you might have to tell the DMA which array to use next time here */
      }
    

The memcopy problem is probably caused by the typo and I will not fucus on it.

To copy new data to the buffer you need copy only the data which is not actually being read by the DMA. Otherwise the waveform might be disrupted. To archive it you need to:

  1. Enable Transfer complete interrupt and Half transfer interrupt
  2. In the interrupt handler or (as you use HAL) in the HAL callback function check for the cause of the interrupt. If it is Transfer complete interrupt copy the second half of the table, in the Half transfer interrupt copy the first path of the table.

example for STM32F3

if(hdac2 -> Instance -> ISR & DMA_ISR_TCIFx)
   memcpy(&Wave_Active[NS/2] , &New_Wave[NS/2], (NS / 2) * sizeof(Wave_Active[0]));
if(hdac2 -> Instance -> ISR & DMA_ISR_HCIFx)
   memcpy(&Wave_Active[0] , &New_Wave[0], (NS / 2) * sizeof(Wave_Active[0]));

where x is DMA channel used.

if the memcpy function is too slow I would personally write my own one (in this case copying 32 32bit words. It will be very fast.

void inline __attribute__((always_inline)) mymemcpy32WORDS(void *dest, const void *src)
{
    const uint64_t *src64 = src;
    uint64_t *dest64 = dest;

    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
    *dest64++ = *src64++;
}

https://godbolt.org/z/4xnq5v

For the best performance, you need to make sure that your arrays are 64bits aligned. If they are the copy of the 32 32bits words will take (assuming 170MHz clock) 1.13us and your 32 word DMA to DACtransfer will take 2us. You have enough time to process everything in the interrupt routine.

It should be fine to run from FLASH (it has to be checked) but if not you should put the interrupt handler to SRAM or even better the data to SRAM and code to CCMRAM

@ 0___________ suggested a solution with the right approach. He suggested overwriting one half of the lookup table while the DMA reads the other half. This would avoid reading a byte while it's getting read. The problem with this is that I'm sampling faster than memcpy can write the values.

Therefore I've tried the simple approach and bluntly overwrite the array with memcpy no matter what. This partly causes funny signal patterns (you can actually see which part of the lookup table ges overwritten first) but over all it works. This causes a signal transition within around 20 µs which is sufficient. As I can live with the imperfect signal pattern that will be my solution. Below is an oscilloscope screenshot of the signal transition from low to high.

Thanks for your help!

Oscilloscope screenshot of the transition from low to high lookup table

Related