STM32E407 Timer->DMA->GPIO

ChibiOS public support forum for topics related to the STMicroelectronics STM32 family of micro-controllers.

Moderators: RoccoMarco, barthess

kai
Posts: 3
Joined: Mon Feb 02, 2015 5:29 pm

STM32E407 Timer->DMA->GPIO

Postby kai » Mon Feb 02, 2015 6:00 pm

Hi,

I have been working quite a bit with ChibioS recently and am now venturing into DMA for some fast Parallel-IO. Well at least to a point where I don't know how to carry on...

First of all what I am trying to accomplish:
I need to control a set of actuators that have a 16bit parallel-bus-type interface. The actuators are daisy-chained on a 16bit interface with an extra set-pulse input. On each pulse on the set-line, the last 16-bit input is pulsed to the next actuator and so on. So what I need to do is provide 16-bit values one after the other on a GPIO from an array and as soon as they are present on the IO-pins, generate a puls on a separate IO-pin. The problem is, that the 16-bit words need to be supplied with 1 MHz and the controller needs to do computations and handling of serial interfaces on the side.

My idea so far was to use the pwm module with timer 1 to generate a 1MHz square wave aka the the update pulse and use the timer overflow DMA-request to automatically copy the next 16bit word from an array to the GPIOF output data register.

The hopefully relevant code snippets so far:

Code: Select all


#define STM32_TIM_CR2_CCDS                  (1U << 3)

// PWM configuration Timer1
static PWMConfig pwmcfg2 = {
  1000, /* PWM clock frequency */
  100, /* PWM period */
  NULL,  /* No callback */
  {
    {PWM_OUTPUT_DISABLED, NULL},
    {PWM_OUTPUT_DISABLED, NULL},
    {PWM_OUTPUT_DISABLED, NULL},
    {PWM_OUTPUT_ACTIVE_HIGH, NULL},
  },
  0, /* CR2 register initialization*/
  #if STM32_PWM_USE_ADVANCED
  0, /* TIM BDTR (break & dead-time) register initialization */
  #endif
  STM32_TIM_DIER_UDE /* TIM DIER register initialization */
};


int main(void) {

  uint16_t pattern[] = {0x000C,0x000C,0x000C};

  halInit();
  chSysInit();
 
  pwmStart(&PWMD1, &pwmcfg2);
  PWMD1.tim->CR2 |= STM32_TIM_CR2_CCDS;
  pwmEnableChannel(&PWMD1, 3, 30);

  // Allocate the stream
  if (dmaStreamAllocate( STM32_DMA2_STREAM5, 0, NULL, NULL ))
    chSysHalt();

  // Set the source Address
  dmaStreamSetPeripheral( STM32_DMA2_STREAM5, pattern );

  // Set the destination address
  dmaStreamSetMemory0( STM32_DMA2_STREAM5, GPIOF->ODR );

  // set the size of the output buffer
  dmaStreamSetTransactionSize( STM32_DMA2_STREAM5, 1 );

  // config 16-bit HWORD transfers, memory to peripheral,
  // inc source address, fixed dest address
  dmaStreamSetMode( STM32_DMA2_STREAM5,
                    STM32_DMA_CR_PL(0) | STM32_DMA_CR_PSIZE_HWORD |
                    STM32_DMA_CR_MSIZE_HWORD | STM32_DMA_CR_DIR_M2P |
                    STM32_DMA_CR_PINC | STM32_DMA_CR_CIRC |
                    STM32_DMA_CR_CHSEL(6));

  dmaStreamSetFIFO(STM32_DMA2_STREAM5, 0);

  dmaStreamEnable(STM32_DMA2_STREAM5);

  while (TRUE) {
    chThdSleepMilliseconds(500);
  }
}


The timer itself is working and is generating the expected PWM output on the channel3 pin. GPIOF is setup as pushpull output and works OK when toggling with palTogglePad().

I don't know where or how to start debugging the DMA controller. I have already studied previous posts on the topic ( http://forum.chibios.org/phpbb/viewtopic.php?f=16&t=1669 http://forum.chibios.org/phpbb/viewtopic.php?f=8&t=1648, but I could not get the code snippets to work with the PWM module. I have also tried the GPT-module, but I can't get the Interrupt for the callback function to stop. At 1MHz interrupt rate, the controller stops working.

I hope that I could explain the prblem clearly and that someone can help.

Best regards,
Kai

User avatar
Giovanni
Site Admin
Posts: 14457
Joined: Wed May 27, 2009 8:48 am
Location: Salerno, Italy
Has thanked: 1076 times
Been thanked: 922 times
Contact:

Re: STM32E407 Timer->DMA->GPIO

Postby Giovanni » Mon Feb 02, 2015 6:15 pm

Hi,

Are you using version 2.6.x? in 3.0.0 the callback is always optional, so you could run the PWM or the GPT even at higher frequencies. The version 3.0.0 is not yet officially released but you can get it from the repository or by downloading ChibiStudio preview 8.

About the DMA controller, I usually look at the registers and see if it is programmed the way I expect it. In order to control if it is triggered by the PWM you could check if the pointers are increasing or the counter decreasing.

If the DMA is not "moving" then check the DMA/PWM configurations.

Giovanni

jscott
Posts: 129
Joined: Tue Jul 03, 2012 3:50 pm
Location: Middle Georgia, USA
Contact:

Re: STM32E407 Timer->DMA->GPIO

Postby jscott » Tue Feb 03, 2015 3:04 am

Hi Kai,

Welcome into the deep end!

You know you are going to have fun when you realize that you need to do DMA. :lol:

I can take a look over your code tomorrow, but a few things that come to mind right now...

Check to make sure the DMA channel is not dedicated to something else...
Already looks like you are using DMA2. It took me a week or two to figure that out, but it was listed in the manual...
Read the DMA AND Timer sections of the manual MANY times. There are a LOT of details for each, and using them both makes it a even worse.

Good news is that several of us have worked it out, and should eventually be able to help you get it working.

-John Scott

kai
Posts: 3
Joined: Mon Feb 02, 2015 5:29 pm

Re: STM32E407 Timer->DMA->GPIO

Postby kai » Tue Feb 03, 2015 5:51 pm

Hi and thanks for those fast replies! It's awesome to be able to actually be able to talk to the people who develop this nice peice of software :D

Giovanni wrote:About the DMA controller, I usually look at the registers and see if it is programmed the way I expect it. In order to control if it is triggered by the PWM you could check if the pointers are increasing or the counter decreasing.

If the DMA is not "moving" then check the DMA/PWM configurations.
Giovanni


OK, you are basically saying use the debugger... Well, went there, did that. Found out what the embsys-plugin is and what it does :oops: Found loads of misconfigurations and personal misconceptions about the timer and DMA modules. Nothing was as I expected it to be and my expectations where also wrong.

jscott wrote:Hi Kai,
Read the DMA AND Timer sections of the manual MANY times. There are a LOT of details for each, and using them both makes it a even worse.


Oh yes, been in that document all of today... Not that hard, once you grasp the concepts. Now I know, what the registers should look like :roll:

So after learning how the debugger really works, how the peripherals really work and finding out, what I really want, the code works like a charm. Thx for the apperently obvious hints :oops: Copy&paste without knowing whats going on sucks.

What actually was messed up (for others to avoid those mistakes):
  • DMA destination and source adresses were mixed up
  • used GPIO->ODR instead of &GPIO->ODR (content instead of adress)
  • UDE bit in DIER timer register for DMA request generation was not set as expected. Manually setting it after the timer init fixed that.
  • Buffer-Size was given in bytes instead of half-words
  • PINC instead of MINC: DMA wrote all over the place instead of incrementing the source adress
  • DMA was (silently?!) never activated because no peripheral in mcu/halconf with DMA-usage were activated. Activating eg. SPI1 solved the problem. Is there any other way to just activate DMA without a dummy peripheral that uses it?

The working code for future venturers into DMA+GPIO wave form generation:

Code: Select all

#define PATTERN_SIZE            8
#define STM32_TIM_CR2_CCDS      (1U << 3)

// PWM configuration Timer1
static PWMConfig pwmcfg1 = {
  84000000, /* PWM clock frequency */
  42, /* PWM period */
  NULL,  /* No callback */
  {
    {PWM_OUTPUT_DISABLED, NULL},
    {PWM_OUTPUT_DISABLED, NULL},
    {PWM_OUTPUT_DISABLED, NULL},
    {PWM_OUTPUT_ACTIVE_HIGH, NULL},
  },
  STM32_TIM_CR2_CCDS, /* CR2 register initialization*/
  #if STM32_PWM_USE_ADVANCED
  0, /* TIM BDTR (break & dead-time) register initialization */
  #endif
  STM32_TIM_DIER_UDE /* TIM DIER register initialization, is ignored by OS */
};

/*
 * Application entry point.
 */
int main(void) {

  uint16_t pattern[PATTERN_SIZE] = {0x0101,0x0202,0x0404,0x0808,
                                      0x1010,0x2020,0x4040,0x8080};
                            
  halInit();
  chSysInit();

  // configure the timer in up/down to reload after 84 timer clock tics
  // The clock is running at 84,000,000Hz,
  // so 84,000,000 / 42 /2 = 1.0 MHz in up/down mode
  pwmStart(&PWMD1, &pwmcfg1);
  pwmEnableChannel(&PWMD1, 3, 20);

  // customize timer setup to center aligned mode
  PWMD1.tim->CR1 |= STM32_TIM_CR1_CMS(0x01);
  // center aligned mode would generate update events (aka DMA-requests)
  // on overflow and underflow situations. Repetion counter waits
  // N+1 events before next interrupt/DMA request is generated.
  // So now DMA is only triggered on underflow with pwm-pulse in between.
  PWMD1.tim->RCR = 0x01;

  // OS resets UDE-bit, so set it here again
  PWMD1.tim->DIER |= STM32_TIM_DIER_UDE;

  // Allocate the stream
  dmaStreamAllocate( STM32_DMA2_STREAM5, 0, NULL, NULL );

  // Set the source Address
  dmaStreamSetPeripheral( STM32_DMA2_STREAM5, &GPIOF->ODR );

  // Set the destination address
  dmaStreamSetMemory0( STM32_DMA2_STREAM5, pattern );

  // set the size of the output buffer
  dmaStreamSetTransactionSize( STM32_DMA2_STREAM5, PATTERN_SIZE );

  // config 16-bit HWORD transfers, memory to peripheral,
  // inc source address, fixed dest address,
  // circular mode, select channel 6
  dmaStreamSetMode( STM32_DMA2_STREAM5,
                    STM32_DMA_CR_PL(0) | STM32_DMA_CR_PSIZE_HWORD |
                    STM32_DMA_CR_MSIZE_HWORD | STM32_DMA_CR_DIR_M2P |
                    STM32_DMA_CR_MINC | STM32_DMA_CR_CIRC |
                    STM32_DMA_CR_CHSEL(6));
 
  // init FIFO to 0 aka only one element
  dmaStreamSetFIFO(STM32_DMA2_STREAM5, STM32_DMA_FCR_FTH_1Q);
  dmaStreamEnable(STM32_DMA2_STREAM5);
 
  while (TRUE) {
      chThdSleepMilliseconds(500);
  }
}


Thank you very much for this awesome little RTOS!

User avatar
Giovanni
Site Admin
Posts: 14457
Joined: Wed May 27, 2009 8:48 am
Location: Salerno, Italy
Has thanked: 1076 times
Been thanked: 922 times
Contact:

Re: STM32E407 Timer->DMA->GPIO

Postby Giovanni » Tue Feb 03, 2015 6:19 pm

Good job, thanks for sharing.

About this:

Code: Select all

STM32_TIM_DIER_UDE /* TIM DIER register initialization, is ignored by OS */


This should not be the case, are you sure?

Giovanni

kai
Posts: 3
Joined: Mon Feb 02, 2015 5:29 pm

Re: STM32E407 Timer->DMA->GPIO

Postby kai » Wed Feb 04, 2015 11:52 pm

Giovanni wrote:

Code: Select all

STM32_TIM_DIER_UDE /* TIM DIER register initialization, is ignored by OS */

This should not be the case, are you sure?

Nope :shock:
The debugger constantly reported the bit at zero after timer init. I had already taken a look at the lld functions and macros and was convinced that there is no place, where the bit is reset. Maybe I have hidden some unintuitive code snippets somewhere that screw things up. Setting it manually does the job for now 8-)

Oh and the source/dest comments in my above code are still the wrong way round. Should look more like this:

Code: Select all

  // Set the destination Address
  dmaStreamSetPeripheral( STM32_DMA2_STREAM5, &GPIOF->ODR );

  // Set the source address
  dmaStreamSetMemory0( STM32_DMA2_STREAM5, pattern );


Return to “STM32 Support”

Who is online

Users browsing this forum: No registered users and 49 guests