cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F302 DMA to GPIO

daniel2
Associate II
Posted on July 09, 2015 at 22:17

Hi All,

I'm trying to drive some WS2812 LEDs which require a (400ns high + 800ns low) or (800ns high + 400ns low) signal to indicate a 1 or a 0.

So esentially I'm trying to take an array of data and output it directly to a GPIO at about 2.5MHz. I've never used the DMA before but from what I understand it should be able to ouput data straight from memory to a GPIO.

How should I try to accomplish this using the DMA, or are there any better ways of outputting data on a GPIO at a consistent clock?
8 REPLIES 8
Posted on July 10, 2015 at 02:41

I've demonstrated TIM driven DMA to/from GPIO in several STM32 families. Suggest you review those. Refer to the Reference Manual to understand the association between TIM triggers and DMA channels.

For 2.5 MHz, you're going to have to run the processor at some integer multiple of that frequency for the TIM dividers to function.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
daniel2
Associate II
Posted on July 10, 2015 at 17:45

Hi Clive,

That sounds like it would be useful to me- do you have these demonstrations available somewhere?

From further reading it seems like the best thing for me to do would be to do DMA transfers from RAM to the GPIO ODR. If I want to move 8 bits from RAM into the ODR for outputs PinA0 - PinA7, and use circular mode with a double buffer how should I configure the data sizes?

I was thinking the memoryDataSize should be HalfWord while PeripheralDataSize would be byte, but in some examples I've seen the opposite.

Posted on July 10, 2015 at 18:25

do you have these demonstrations available somewhere?

I post everything here, use the site search, or Google.

I'd probably look at doing a 32-bit write to GPIOx->BSRR

I'd look to update pattern buffers in a ping-pong fashion using DMA HT/TC interrupts.

[DEAD LINK /public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Data%20transfer%20from%20GPIO%20port%20to%20RAM%20buffer%20using%20DMA%20upon%20receiving%20a%20trigger%20signal%20on%20the%20timer%20capture%20input%20channel&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&TopicsView=https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/AllItems.aspx&currentviews=4111]https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=%2fpublic%2fSTe2ecommunities%2fmcu%2fLists%2fcortex_mx_stm32%2fData%20transfer%20from%20GPIO%20port%20to%20RAM%20buffer%20using%20DMA%20upon%20receiving%20a%20trigger%20signal%20on%20the%20timer%20capture%20input%20channel&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&TopicsView=https%3A%2F%2Fmy.st.com%2Fpublic%2FSTe2ecommunities%2Fmcu%2FLists%2Fcortex_mx_stm32%2FAllItems.aspx¤tviews=4111

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
daniel2
Associate II
Posted on July 14, 2015 at 01:31

I'm having some trouble getting these settings right. The code pasted below, does work, but I'm trying to get circular buffering working, and I believe I shouldn't be using M2M mode. However, if I disable M2M mode the whole things breaks, and I'm not sure how to setup the circular buffer.

1. Should I be doing this with M2M disabled or enable? 2. If disabled, then why is it not working if I disable M2M 3. My data packets are 24 bits each. Do I need to create a uint32_t array[2] to ping pong in or should I create a uint32_t array[48] and load the data 24 bits at a time? Either way how do I configure the MemoryDataSize, PeripheralDataSize, and bufferSize variables to have the DMA circle through my array?

/* Test on DMA1 Channel 2 */
void WS2812_DMA2_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /* Enable DMA1 clock */
DMA_DeInit(DMA1_Channel2); /* Reset DMA1 channe1 to default values */
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = ARRAYSIZE;
/* Memory Parameters */
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&source;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
/* Peripheral Parameters */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&GPIOC->BSRR;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_Init(DMA1_Channel2, &DMA_InitStructure);
//Enable DMA1 channel IRQ Channel */
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// Enable DMA1 Channel Transfer Complete interrupt
DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
}

daniel2
Associate II
Posted on July 15, 2015 at 16:16

I discovered that I needed to use the timer interrupts with DMA_M2M disabled. That answers 1. & 2. but I'm still wondering about how to setup the circular buffer.

I'm moving data from a uint16_t to the GPIO->ODR register. I have the source and destination both configured as halfword since they're both 16-bit registers. My array is 48 halfWords long (2 x 24 halfWord packets), and I want to transfer 192 halfWords (8 packets or twice through my 48 array). However I'm not clear on how to tell the DMA after how many bytes to circle back. If I set the buffersize to 96 it just keeps reading 48 halfWords past my array.

uint16_t source16[ARRAYSIZE] = {};
for (i=2; i<ARRAYSIZE/2;i++){
if (i%2 == 0){
source16[i] = i/2;
}
else{
source16[i]= LED_OFF_ODR;
}
}
WS2812_GPIO_Init();
WS2812_DMA_Init();
WS2812_Timer_Init();
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(USER_BUTTON_EXTI_LINE) == SET)
{
// Clear all flags for DMA
DMA_ClearFlag(DMA1_FLAG_TC2 | DMA1_FLAG_HT2 | DMA1_FLAG_GL2 | DMA1_FLAG_TE2);
// Configure number of bytes to transfer
DMA_SetCurrDataCounter(DMA1_Channel2, ARRAYSIZE*4);
// Enable DMA1 Channel2
DMA_Cmd(DMA1_Channel2, ENABLE);
// Enable TIM2 Update trigger for DMA
TIM_DMACmd(TIM2, TIM_DMA_Update, ENABLE);
/* Enable Timer 2 */
TIM_Cmd(TIM2, ENABLE);
EXTI_ClearITPendingBit(USER_BUTTON_EXTI_LINE);
}
}
void WS2812_Basic_DMA_Init2(void)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // Enable DMA1 clock
DMA_DeInit(DMA1_Channel2); // Reset DMA1 channe1 to default values;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // M2M Disabled- Peripheral mode (requires timer trigger)
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // Circular mode
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // High priority
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // Memory to Peripheral
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 16-bit Register
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // Always write to same register
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&GPIOC->ODR; // Output data for GPIO
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 16-bit array
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // Increment through array
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&source16; // 16-bit Source data
DMA_InitStructure.DMA_BufferSize = ARRAYSIZE * 4; // Size of source array x 4
DMA_Init(DMA1_Channel2, &DMA_InitStructure); // Initialize DMA
//Enable DMA1 channel IRQ Channel */
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// Enable DMA1 Channel Transfer Complete interrupt
DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
}

Posted on July 15, 2015 at 17:09

You'd want to initialize your pattern buffer for TWO sets of data, and configure BOTH the HT and TC interrupts.

The HT (Half Transfer) occurs after the FIRST set of data is dispatched, and you replace it with the THIRD set of data while the DMA is sending the SECOND set, after which you'll get the TC (Transfer Complete), and the circular DMA wraps around and starts over.

You will clearly need to generate new pattern data faster than the DMA is consuming it for this to work.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
daniel2
Associate II
Posted on July 15, 2015 at 19:15

I have done all this as far as I know. I'm not replacing the data on the HT yet, but I would like to see the DMA wrap around with just the two sets of data. However I don't understand how the DMA knows when to wrap. Like I said, if I set the buffer size to ARRAYSIZE * 2, the DMA does not currently wrap around but rather just keeps reading past my array. 

What's the mechanism for telling the DMA how many transfers to do before wrapping around?

Posted on July 15, 2015 at 21:22

The DMA doesn't understand bytes, it measures transfers as a count on elements, of size you specify

HT occurs at DMA_InitStructure.DMA_BufferSize / 2 elements

TC occurs at DMA_InitStructure.DMA_BufferSize

If DMA_InitStructure.DMA_BufferSize = 16, the array is a[0] .. a[15], if circular repeats as a[0] .. a[15], triggering HT at each a[7] and TC at each a[15]

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..