2020-04-07 07:12 PM
Hello everyone!
For last week I am struggling to use Variable PWM duty cycle to generate signal for an array of addressable LEDs (WS2812) which I had successfully running on STM32F103.
I was forced to use STM32F412 and ever since I cannot port my old code to work with the new MCU.
Few days ago I decided to shift my attempt to a register level of things and I cannot update CCR3 using DMA, to allow me changing the duty cycle simultaneously (every ARR reload).
I found this topic and it lead me to a very helpful code, which in theory would allow me to dump values into CRR3 using DMA, but the DMA doesn't seem to put any values in CCR3.
Can you please tell me if you see anything I am doing wrong in my code?
I hope it is not too much code in one post
#include "stm32f412rx.h"
void GPIO_Init(void);
void TIM_Init(void);
void DMA_Init(void);
const uint16_t valuesTable[] = { // Values to be dumped CCR3 registers simultaneously
20, 20,
30, 10,
20, 20,
30, 10
};
void main(void){
__HAL_RCC_TIM4_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_DMA1_CLK_ENABLE();
GPIO_Init();
DMA_Init();
TIM_Init();
while(1){
}
}
void GPIO_Init(void){
GPIOB -> MODER |= GPIO_MODER_MODE8_1; // ALTERNATE FUNCTION MODE
GPIOB -> OTYPER &= ~GPIO_OTYPER_OT8; // PUSH-PULL TYPE
GPIOB -> OSPEEDR |= GPIO_OSPEEDER_OSPEEDR8_1; // FAST SPEED
GPIOB -> PUPDR |= GPIO_PUPDR_PUPDR8_1; // PULLDOWN
GPIOB -> AFR[1] |= (2UL << (0U)); // ALTERNATE FUNCTION #2 (TIM4 CH3)
}
void TIM_Init(void){
/* CR1 - CONTROL REGISTER 1
* CR2 - CONTROL REGISTER 2
* PSC - PRESCALER REGISTER
* ARR - AUTO-RELOAD REGISTER
* SR - STATUS REGISTER
* SMCR- SLAVE MODE CONTROL REGISTER
* DIER- DMA/INTERRUPT ENABLE REGISTER
* EGR - EVENT GENERATION REGISTER
* CCMR1-CAPTURE/COMPARE MODE REGISTER 1
* CCMR2-CAPTURE/COMPARE MODE REGISTER 2
* CCER- CAPTURE/COMPARE ENABLE REGISTER
* CNT - COUNT REGISTER
* CCR3- CAPTURE/COMPARE 3 REGISTER IN ITS OWN
* DMAR- DMA ADDRESS FOR FULL TRANSFER
* */
TIM4 -> CR1 &= ~(0x3UL <<(8U)); // CLOCK DIVISION 1 (NO DIVISION)
TIM4 -> CR1 &= ~(TIM_CR1_CMS_1); // EDGE ALIGNED MODE
TIM4 -> CR1 &= ~(TIM_CR1_DIR); // COUNTER AS UPCOUNTER
TIM4 -> CR1 &= ~(TIM_CR1_OPM); // ONE-PULSE MODE DISABLED
TIM4 -> CR1 &= ~(TIM_CR1_URS); // UPDATE REQUEST SOURCE
TIM4 -> CCMR2 = 0
|( 1 * (6UL << (4U))) // OUTPUT COMPARE PWM 1 MODE
|( 1 * (1UL << (3U))) // OUTPUT COMPARE 3 PRELOAD ENABLE
|( 0 * (1UL << (2U))) // OUTPUT COMPARE FAST MODE ENABLE
;
TIM4 -> CCER = 0
|( 1 * (1UL << (8U))) // CAPTURE/COMPARE OC3 ACTIVE
|( 0 * (1UL << (9U))) // CC3 POLARITY ACTIVE LOW
;
TIM4 -> DCR = 0
|(17UL) // INDEX OF CCR3 REGISTER
|((0UL) << (8U)) // 1 TRANSFER
;
TIM4 -> ARR = 40; // AUTO-RELOAD REGISTER
TIM4 -> PSC = 10; // PRESCALER
TIM4 -> DIER |= (1UL <<(14U)); // UPDATE DMA REQUEST ENABLE
TIM4 -> CR1 |= TIM_CR1_ARPE; // AUTO-RELOAD PRELOAD ENABLE
DMA1_Stream7 -> CR |= DMA_SxCR_EN; // DMA GO!!
TIM4 -> CR1 |= TIM_CR1_CEN; // TIM GO!!
}
void DMA_Init(void){
/* CR - DMA STREAM CONFIGURATION REGISTER
* NDTR - DMA STREAM NUMBER OF DATA TO TRANSFER
* PAR - DMA STREAM PERIPHERAL ADDRESS
* M0AR - DMA STREAM MEMORY ADDRESS 1
* M1AR - DMA STREAM MEMORY ADDRESS 2
* FCR - DMA STREAM FIFO CONTROL REGISTER
* */
DMA1_Stream7 -> M0AR = (uint32_t)valuesTable; // MEMORY(BUFFER) ADDRESS
DMA1_Stream7 -> PAR = (uint32_t)&TIM4->DMAR; // PERIPHERAL ADDRESS (SHADOW REGISTER)
DMA1_Stream7 -> NDTR = sizeof(valuesTable) / sizeof(valuesTable[0]); // TRANSFER SIZE
DMA1_Stream7 -> CR = 0
|( 1 * (2UL << (25U))) // CHANNEL 2 SELECTED
|( 0 * (0UL << (23U))) // MBURST SINGLE TRANSFER
|( 0 * (0UL << (21U))) // PBURST SINGLE TRANSFER
|( 0 * (1UL << (19U))) // CURRENT TRGET (ONLY IN DOUBLE-BUFFER MODE)
|( 0 * (1UL << (18U))) // DOUBLE-BUFFER MODE
|( 1 * (1UL << (16U))) // PRIORITY LEVEL MEDIUM
|( 0 * (0UL << (15U))) // PERIPHERAL INCREMENT OFFSET SIZE 0
|( 1 * (1UL << (13U))) // MEMORY DATA SIZE HALFWORD
|( 1 * (1UL << (11U))) // PERIPH DATA SIZE HALFWORD
|( 1 * (1UL << (10U))) // MEMORY INCREMENT MODE ENABLE
|( 0 * (1UL << (9U))) // PERIPH INCREMENT MODE ENABLE
|( 1 * (1UL << (8U))) // CIRCULAR MODE ENABLE
|( 1 * (1UL << (6U))) // DATA TRANSFER DIRECTION (MEM 2 PER)
|( 0 * (1UL << (5U))) // PERIPHERAL AS FLOW CONTROLLER
|( 0 * (1UL << (4U))) // TRANSFER COMPLETE INTERRUPT ENABLE
|( 0 * (1UL << (3U))) // HALF TRANSFER COMPLETE INTERRUPT ENABLE
|( 0 * (1UL << (2U))) // TRANSFER ERROR INTERRUPT ENABLE
|( 0 * (1UL << (1U))) // DIRECT MODE INTERRUPT ENABLE
;
}
2020-04-07 08:12 PM
Don't you want to write values to CCRx and not DMAR? The CCRx registers have shadow registers themselves.
2020-04-07 08:35 PM
Thank you for the response and the reason I went with the DMAR was because it was implemented by someone else in their code, I pretty much went with
what they did, hoping I could at least get what they did and make changes from there.
I just changed it to CCR3 and got the same result, nothing is being sent to the CCR3.
I also tried, generating a signal that would send all ones to the LEDs and all of them lit up, but that was just a PWM at the right
duty cycle and ARR period. That confirmed that the PWM works fine but I still cannot vary the CCR value to imitate the ones and zeroes in the stream.
2020-04-07 10:34 PM
> TIM4 -> DIER |= (1UL <<(14U)); // UPDATE DMA REQUEST ENABLE
Are you sure? I don't have RM at hand but this doesn't sound right to me.
Why don't you use the symbols from the CMSIS-mandated device header?
JW
2020-04-07 11:17 PM
2020-04-08 09:00 AM
Hi JW,
I'm sorry, I had this register set as:
TIM4 -> DIER |= TIM_DIER_UDE
and it didn't work for me so I started to mess around and forgot to put it back to what it was originally.
It is back to TIM4 -> DIER |= TIM_DIER_UDE .
The reason I didn't use CMSIS mandated header was because I was getting confused with what each one of them meant and I
went to the RM and started plugging the values from RM.
I will go back and use CMSIS defines to make it easier to read for you, thank you for getting back!
2020-04-08 09:04 AM
Whatever, just read out the registers' content and check/post
JW
2020-04-08 10:39 AM
I cleaned it up a little
void GPIO_Init(void);
void TIM_Init(void);
void DMA_Init(void);
const uint16_t valuesTable[] = { // Values to be dumped CCR3 registers simultaneously
20, 20,
30, 10,
20, 20,
30, 10
};
void main(void){
__HAL_RCC_TIM4_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_DMA1_CLK_ENABLE();
GPIO_Init();
DMA_Init();
TIM_Init();
while(1){
}
}
void GPIO_Init(void){
GPIOB -> MODER |= GPIO_MODER_MODE8_1; // ALTERNATE FUNCTION MODE
GPIOB -> OTYPER &= ~GPIO_OTYPER_OT8; // PUSH-PULL TYPE
GPIOB -> OSPEEDR |= GPIO_OSPEEDER_OSPEEDR8_1; // FAST SPEED
GPIOB -> PUPDR |= GPIO_PUPDR_PUPDR8_1; // PULLDOWN
GPIOB -> AFR[1] |= (1UL << (0U)); // ALTERNATE FUNCTION #2 (TIM4 CH3)
}
void DMA_Init(void){
DMA1_Stream7 -> M0AR = (uint32_t)valuesTable; // MEMORY ADDRESS
DMA1_Stream7 -> PAR = (uint32_t)&TIM4->DMAR; // PERIPHERAL ADDRESS (SHADOW REGISTER)
DMA1_Stream7 -> NDTR = sizeof(valuesTable) / sizeof(valuesTable[0]); // TRANSFER SIZE
DMA1_Stream7 -> CR = 0
|( 1 * DMA_SxCR_CHSEL_2) // CHANNEL 2 SELECTED
|( 1 * DMA_SxCR_PL_0) // PRIORITY LEVEL MEDIUM
|( 1 * DMA_SxCR_MSIZE_0) // MEMORY DATA SIZE HALFWORD
|( 1 * DMA_SxCR_PSIZE_0) // PERIPH DATA SIZE HALFWORD
|( 1 * DMA_SxCR_MINC) // MEMORY INCREMENT MODE ENABLE
|( 1 * DMA_SxCR_CIRC) // CIRCULAR MODE ENABLE
|( 1 * DMA_SxCR_DIR_0) // DATA TRANSFER DIRECTION (MEM 2 PER)
;
}
void TIM_Init(void){
TIM4 -> CR1 &= ~(TIM_CR1_CKD_1); // CLOCK DIVISION 1 (NO DIVISION)
TIM4 -> CR1 &= ~(TIM_CR1_CMS_1); // EDGE ALIGNED MODE
TIM4 -> CR1 &= ~(TIM_CR1_DIR); // COUNTER AS UPCOUNTER
TIM4 -> CR1 &= ~(TIM_CR1_OPM); // ONE-PULSE MODE DISABLED
TIM4 -> CR1 &= ~(TIM_CR1_URS);
TIM4 -> CCMR2 = 0
|( 1 * (TIM_CCMR2_OC3M_2 << (1U))) // OUTPUT COMPARE PWM 1 MODE
|( 1 * (TIM_CCMR2_OC3PE)) // OUTPUT COMPARE 3 PRELOAD ENABLE
;
TIM4 -> CCER = 0
|( 1 * (TIM_CCER_CC3E)) // CAPTURE/COMPARE OC3 ACTIVE
;
TIM4 -> BDTR = 0 | TIM_BDTR_MOE;
TIM4 -> DCR = 0
|(15UL) // INDEX OF CCR3 REGISTER
|(TIM_DCR_DBL_0) // 1 TRANSFER
;
TIM4 -> ARR = 40;
TIM4 -> PSC = 10;
TIM4 -> DIER |= TIM_DIER_UDE; // UPDATE DMA REQUEST ENABLE
TIM4 -> CR1 |= TIM_CR1_ARPE; // AUTO-RELOAD PRELOAD ENABLE
DMA1_Stream7 -> CR |= DMA_SxCR_EN; // DMA GO!!
TIM4 -> CR1 |= TIM_CR1_CEN; // TIM GO!!
}
2020-04-08 11:07 AM - edited 2023-11-20 09:25 AM
> |( 1 * DMA_SxCR_CHSEL_2) // CHANNEL 2 SELECTED
No. DMA_SxCR_CHSEL_2 is bit 2 of the CHSEL bitfield, so this sets the CHSEL bitfield to 0b100 = 4 i.e. you've selected channel 4.
You want
|( 2 * DMA_SxCR_CHSEL_0) // CHANNEL 2 SELECTED
or, maybe visually better, if you have newer header file (the ***_Pos symbols were not defined in the headers in the SPL era)
| (2 << DMA_SxCR_CHSEL_Pos)
> TIM4 -> DIER |= TIM_DIER_UDE; // UPDATE DMA REQUEST ENABLE
yes, that's better than triggering DMA from CC3, but then you need not Channel 7 but channel 6:
If after doing this still no cigar, read out the registers' content in debugger and check/post.
JW
2020-04-08 01:28 PM
I'm sorry if I ask a dumb question,
but what is the difference between TIM4_CH3 and TIM4_UP can they be used on the same GPIO Pin?
Do I need to use different CCR registers? would it be CCR1 CCR2 or so on?
In the data sheet for the MCU Alternate functions for the pin I have to use are TIM4_CH3 or TIM10_CH1.
I am totally clueless about the TIMx_UP