2020-04-05 05:12 AM
Using the CubeMX perspective in the CubeIDE (latest version) I configured the DMA2 to work with Timer1 in the memory to peripheral mode.
I am using the STM32 F4 Discovery board (STM32F407VG)
I need a Hello World on how to push one value of an array to the GPIO pins. The GPIO pins used are GPIOE pins 8 through 15.
If the array value is 1 (0xFF), the GPIO pins should have the values "1 1 1 1 1 1 1 1"
Configuration Tim1:
Configuration DMA:
The following code was used after initialization of the peripherals (initialization was done using auto generated code):
The GPIO pins are set high at initialization. When this code runs they should be set low, because the values in the array are all zeros (did not include that code for simplicity).
When the code runs, it does execute the functions. It does not throw an error, I checked it with the status return value of the functions. It jumps into the DMA2_Stream5_IRQHandler afterwards, so I'm guessing it did something. The GPIO pins are however never set low.
What am I doing wrong?
How do I get the values from an array onto some GPIO pins using the DMA2 peripheral?
Thanks in advance!
Solved! Go to Solution.
2020-04-06 12:15 PM
Your help has been amazing @berendi .
Below is the code that works and its explanation.
It takes the values inside the array (8 bits per value) and maps them onto the 8 GPIO pins.
#define BUFLEN (512)
uint8_t dmabuf[BUFLEN];
int i;
for(i=0; i<BUFLEN; i++) dmabuf[i] = 0xAB;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN | RCC_AHB1ENR_DMA2EN;
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
//Set GPIOE8-15 as output
GPIOE->MODER = (GPIOE->MODER & 0x0000FFFF) | 0x55550000;
//Set up DMA2 Stream 5.
DMA2_Stream5->NDTR = BUFLEN;
DMA2_Stream5->M0AR = &dmabuf;
GPIOE->ODR = 0x0000;
DMA2_Stream5->PAR = (((uint32_t)0x40021000) | ((uint32_t)0x00000014) | ((uint32_t)0x00000001));
// DMA2_Stream5->PAR = (uint32_t)&GPIOE->ODR;
DMA2_Stream5->CR =
(6u << DMA_SxCR_CHSEL_Pos) | // select channel 6, see DMA request mapping in the reference manual
// DMA_SxCR_MSIZE_0 | // 0b01:memory data size 16 bit
// DMA_SxCR_PSIZE_0 | // 0b01:peripheral data size 16 bit
DMA_SxCR_MINC | // memory increment
DMA_SxCR_DIR_0 | // 0b01: memory to peripheral
DMA_SxCR_EN | // enable channel
0;
//Now set up the timer
TIM1->ARR = 11 - 1; // Set to the required period - 1
//TIM1->BDTR = TIM_BDTR_MOE; // required on advanced timers
TIM1->DIER = TIM_DIER_UDE; // enable DMA on timer update (overflow)
TIM1->CR1 = TIM_CR1_CEN; // start the timer
Code explanation:
Lines 1-4: Define and fill an array. In this case it is filled with the value 0xAB. This way I could verify that each individual GPIO pin had its own value and not some default value.
Lines 6-7: Enable the clocks for GPIO group E, DMA2 and Timer1.
Line 10:
Line 13: Set the Number of Data Register to the amount of data to be send. In this case it is the whole array so it is BUFLEN.
Line 14: Set the Memory 0 Address Register to the address of the array. This address contains the starting point of the data to be sent.
Line 15: As @Community member pointed out this register just needs to be set to 0. I tested the code and it still works.
This was the tricky part. In line 15 we set the Output Data Register to 0xFF000000. The values in this register will tell the DMA which pins should receive the output. By setting the values in the register to 0xFF000000 we only select the pins 8-15.
Lines 16-17: The Peripheral Address Register holds the address of the GPIOE->ODR register (which we just configured) . We can set the address in a couple of ways, both are essentially the same:
Lines 18-25:
Lines 28: Set the period Timer1
Line 29: According to @Community member this is only needed when you want to output a waveform using this timer. I removed this line, tested the code and it still works.
Line 30: When Timer1 counts up to its maximum value, send the next DMA value.
Line 31: Start Timer1
Thank you very much @berendi .
When I finish the project, i'll probably upload a video to YouTube explaining the VGA signal and i'll post the link here if I do.
2020-04-05 11:11 AM
I could never figure it out in HAL, it might depend on the CubeMX / HAL versions, or on some hard to find setting. It is poorly documented.
Using the registers as documented in the reference manual usually works.
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN | RCC_AHB1ENR_DMA2EN;
RCC->APB2EN |= RCC->APB2_TIM1EN;
Set GPIOE8-15 as output
GPIOE->MODER = (GPIOE->MODER & 0x0000FFFF) | 0x55550000;
Set up DMA2 Stream 5.
DMA2_Stream5->NDTR = BUFLEN;
DMA2_Stream5->M0AR = &dmabuf;
DMA2_Stream5->PAR = (uint32_t)&GPIOE->ODR;
DMA2_Stream5->CR =
(6u << DMA_SxCR_CHSEL_Pos) | // select channel 6, see DMA request mapping in the reference manual
DMA_SxCR_MSIZE_0 | // 0b01:memory data size 16 bit
DMA_SxCR_PSIZE_0 | // 0b01:peripheral data size 16 bit
DMA_SxCR_MINC | // memory increment
DMA_SxCR_DIR_0 | // 0b01: memory to peripheral
DMA_SxCR_EN | // enable channel
0;
Now set up the timer
TIM1->ARR = 11 - 1; // Set to the required period - 1
TIM1->BDTR = TIM_BDTR_MOE; // required on advanced timers
TIM1->DIER = TIM_DIER_UDE; // enable DMA on timer update (overflow)
TIM1->CR1 = TIM_CR1_CEN; // start the timer
I haven't tested it, but it should work of the F4 series.
2020-04-05 11:41 AM
The code looks very promising, sadly there is an error.
RCC->APB2EN |= RCC->APB2_TIM1EN;
Two problems:
Except for this one line it does compile.
2020-04-05 01:17 PM
You can look up in the reference manual how it is actually called.
2020-04-05 01:57 PM
I looked it up in the Referance Manual
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
It did not work.
I am using the auto generated HAL code to set the clock configuration before any of this code runs.
I do not think that that is the issue, because I setup the clocks using pure RCC code and even then it did not work.
(Thanks for your help so far by the way)
The code so far:
#define BUFLEN (512)
uint16_t dmabuf[BUFLEN];
int i;
for(i=0; i<BUFLEN; i++) dmabuf[i] = 0xFF;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN | RCC_AHB1ENR_DMA2EN;
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
//Set GPIOE8-15 as output
GPIOE->MODER = (GPIOE->MODER & 0x0000FFFF) | 0x55550000;
//Set up DMA2 Stream 5.
DMA2_Stream5->NDTR = BUFLEN;
DMA2_Stream5->M0AR = &dmabuf;
DMA2_Stream5->PAR = (uint32_t)&GPIOE->ODR;
DMA2_Stream5->CR =
(6u << DMA_SxCR_CHSEL_Pos) | // select channel 6, see DMA request mapping in the reference manual
DMA_SxCR_MSIZE_0 | // 0b01:memory data size 16 bit
DMA_SxCR_PSIZE_0 | // 0b01:peripheral data size 16 bit
DMA_SxCR_MINC | // memory increment
DMA_SxCR_DIR_0 | // 0b01: memory to peripheral
DMA_SxCR_EN | // enable channel
0;
//Now set up the timer
TIM1->ARR = 11 - 1; // Set to the required period - 1
TIM1->BDTR = TIM_BDTR_MOE; // required on advanced timers
TIM1->DIER = TIM_DIER_UDE; // enable DMA on timer update (overflow)
TIM1->CR1 = TIM_CR1_CEN; // start the timer
2020-04-06 03:19 AM
Now I have tried it (with your fix) on some real hardware, it works. Timer and DMA are running, and it copies the contents of dmabuf into GPIOE->ODR.
I have just pasted it into the main() function of an empty project. Quite empty, I mean, I have kept only Reset_Handler(), SystemInit(), and the header files.
Try clearing the DMA and timer flag registers before enabling them, or find out where and how HAL is messing them up.
2020-04-06 06:00 AM
You are correct, it does push the values to the GPIO pins!
But to the wrong pins. The debugger shows me that the values are pushed to GPIO pins 0-7, not to 8-15.
The pins 8-15 are configured as output pins. I tried to change the address of
DMA2_Stream5->PAR = (uint32_t)&GPIOE->ODR;
but that did not help.
Would you mind sharing your code with me (or help me overcome this issue) @berendi ?
By the way, the reason I need those specific GPIO pins is because the VGA connector is connected to them (on the pcb to which the Discovery board is connected).
In school we have code that runs in the old Coocox IDE that is able to drive a VGA screen. I'm remaking the code in the CubeIDE for the whole class to use.
2020-04-06 09:16 AM
As you have defined dmabuf as an array of uint16_t values, I've assumed that you'd like to copy all 16 bits to ODR.
To copy 8 bit values, you have to
Read the DMA chapter in the reference manual.
2020-04-06 12:15 PM
Your help has been amazing @berendi .
Below is the code that works and its explanation.
It takes the values inside the array (8 bits per value) and maps them onto the 8 GPIO pins.
#define BUFLEN (512)
uint8_t dmabuf[BUFLEN];
int i;
for(i=0; i<BUFLEN; i++) dmabuf[i] = 0xAB;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN | RCC_AHB1ENR_DMA2EN;
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
//Set GPIOE8-15 as output
GPIOE->MODER = (GPIOE->MODER & 0x0000FFFF) | 0x55550000;
//Set up DMA2 Stream 5.
DMA2_Stream5->NDTR = BUFLEN;
DMA2_Stream5->M0AR = &dmabuf;
GPIOE->ODR = 0x0000;
DMA2_Stream5->PAR = (((uint32_t)0x40021000) | ((uint32_t)0x00000014) | ((uint32_t)0x00000001));
// DMA2_Stream5->PAR = (uint32_t)&GPIOE->ODR;
DMA2_Stream5->CR =
(6u << DMA_SxCR_CHSEL_Pos) | // select channel 6, see DMA request mapping in the reference manual
// DMA_SxCR_MSIZE_0 | // 0b01:memory data size 16 bit
// DMA_SxCR_PSIZE_0 | // 0b01:peripheral data size 16 bit
DMA_SxCR_MINC | // memory increment
DMA_SxCR_DIR_0 | // 0b01: memory to peripheral
DMA_SxCR_EN | // enable channel
0;
//Now set up the timer
TIM1->ARR = 11 - 1; // Set to the required period - 1
//TIM1->BDTR = TIM_BDTR_MOE; // required on advanced timers
TIM1->DIER = TIM_DIER_UDE; // enable DMA on timer update (overflow)
TIM1->CR1 = TIM_CR1_CEN; // start the timer
Code explanation:
Lines 1-4: Define and fill an array. In this case it is filled with the value 0xAB. This way I could verify that each individual GPIO pin had its own value and not some default value.
Lines 6-7: Enable the clocks for GPIO group E, DMA2 and Timer1.
Line 10:
Line 13: Set the Number of Data Register to the amount of data to be send. In this case it is the whole array so it is BUFLEN.
Line 14: Set the Memory 0 Address Register to the address of the array. This address contains the starting point of the data to be sent.
Line 15: As @Community member pointed out this register just needs to be set to 0. I tested the code and it still works.
This was the tricky part. In line 15 we set the Output Data Register to 0xFF000000. The values in this register will tell the DMA which pins should receive the output. By setting the values in the register to 0xFF000000 we only select the pins 8-15.
Lines 16-17: The Peripheral Address Register holds the address of the GPIOE->ODR register (which we just configured) . We can set the address in a couple of ways, both are essentially the same:
Lines 18-25:
Lines 28: Set the period Timer1
Line 29: According to @Community member this is only needed when you want to output a waveform using this timer. I removed this line, tested the code and it still works.
Line 30: When Timer1 counts up to its maximum value, send the next DMA value.
Line 31: Start Timer1
Thank you very much @berendi .
When I finish the project, i'll probably upload a video to YouTube explaining the VGA signal and i'll post the link here if I do.
2020-04-06 12:37 PM
GPIOE->ODR = 0xFF000000;
> Line 15: This was the tricky part. In line 15 we set the Output Data Register to 0xFF000000. The values in this register will tell the DMA which pins should receive
> the output. By setting the values in the register to 0xFF000000 we only select the pins 8-15.
What?
No, ODR is an output register, and it's 16 bit wide, so you simply set it to 0x0000 by this line.
> TIM1->BDTR = TIM_BDTR_MOE; // required on advanced timers
You don't need this unless you want output waveforms on the timer's pins.
JW