“It’s hard to be a bright light in a dim world.”
– Gary Starta
A common 5mm LED isn’t the best in every situation, often a bit more power is required. That’s why I’ve connected a 3W power RGB LED to an STM32F0-Discovery board.
The first problem when connecting the power LEDs is provide the right amount of current. Ad-hoc chips are available but I was focused on make all the things work with material I already have, so I chosen a less professional approach: power resistors.
My RGB LED is rated for 350mA for each color and I am assuming to provide 12Vdc. The forwarding voltage is different from color to color.
- RED has a Vf of 2.2V
- GREEN and BLUE have a Vf of 3.4V
The common series resistor law is:
R = (V – Vf) / I
In my case I have:
- For RED: R = (V – Vf) / I = (12V – 2.2V) / 0.35A = 28Ω
- For BLUE and GREEN: R = (V – Vf) / I = (12V – 3.4V) / 0.35A = 25Ω
The powers to dissipate are instead:
- For RED: P = R * I * I = 28Ω * 0.35A * 0.35A = 3.5W
- For GREEN and BLUE: P = R * I * I = 25Ω * 0.35A * 0.35A = 3W
I strongly suggest using HIGHER wattage resistors – due to heating – easily available at RadioShack or other distributors.
However, I am using 47Ω 5W resistors (the only available at my home) for all channels. The current flow with this value is (assuming an average Vf of 3V):
I = (V – Vf) / R = (12V – 3V) / 47Ω = 0.2A
and the power to dissipate:
P = R * I * I = 47Ω * 0.2A * 0.2A = 1.9W
Obviously in this way I’m not using all the LED’s power but about 65%. The calculations are correct and verified with a multimeter, exactly 0.62A are absorbed during emission of white color (each RGB channel near 100% duty cycle).
The LED itself must be cooled, I have mounted it on a non-common heatsink… Have you ever dismounted an XBOX 360? Well, the joystick’s receiver antenna is a really nice thing to make a heatsink 😉 – see image below –
To drive each R-G-B channel I have used two transistors and two resistor, in Darlington configuration
R3, R6, R9 are power resistors (read above) of 5W
The circuitry described until now is perfectly suitable for a broad range of microcontrollers (from low cost Arduino boards to high performace Raspberry’s).
I’ve realized all the circuitry needed on a prototype board and I’ve mounted it on a STM32F0-Discovery board, I have also added an LM7805 power regulator with 1000uF capacitor and a NRF24L01+ module for wireless control.
Using the excellent STM32CubeMX software provided by ST, I planned each connection in this way
Briefly:
- PA0 is connected to the USER button on the STM32F0 board
- PA5 PA6 PA7 are used by NRF24L01+ module (SPI protocol)
- PB10 PB11 PB12 are used by NRF24L01+ module (control signals)
- PC8 PC9 are connected to the LEDs on STM32F0 board
- PA8 PA9 PA10 PA11 are PWM outputs of Timer1 – we will use only the first 3 signals to drive the RGB LED
- PB6 PB7 are connected the USART1 module – used for debugging
Let’s take a look at the code.
Here we initialize GPIO PA8-PA9-PA10 and the timer. This part is a modified version of “PWM generation” sample code available in the Standard Peripheral Libraries
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
void PWM_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; /* GPIOA Clock enable */ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); /* GPIOA Configuration: Channel 1, 2, 3 as alternate function push-pull */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_2); /* TIM1 Configuration --------------------------------------------------- Generate 3 PWM signals with 3 different duty cycles: TIM1 input clock (TIM1CLK) is set to APB2 clock (PCLK2) => TIM1CLK = PCLK2 = SystemCoreClock TIM1CLK = SystemCoreClock, Prescaler = 0, TIM1 counter clock = SystemCoreClock SystemCoreClock is set to 48 MHz for STM32F0xx devices An example is generate 3 PWM signal at 17.57 KHz: - TIM1_Period = (SystemCoreClock / 17570) - 1 The channel 1 duty cycle is set to 50% The channel 2 duty cycle is set to 37.5% The channel 3 duty cycle is set to 25% The Timer pulse is calculated as follows: - ChannelxPulse = DutyCycle * (TIM1_Period - 1) / 100 Note: SystemCoreClock variable holds HCLK frequency and is defined in system_stm32f0xx.c file. Each time the core clock (HCLK) changes, user had to call SystemCoreClockUpdate() function to update SystemCoreClock variable value. Otherwise, any configuration based on this variable will be incorrect. ----------------------------------------------------------------------- */ /* Compute the value to be set in ARR regiter to generate signal frequency at 10.00 Khz */ TimerPeriod = (SystemCoreClock / 10000 ) - 1; /* TIM1 clock enable */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 , ENABLE); /* Time Base configuration */ TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period = TimerPeriod; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); /* Channel 1, 2,3 and 4 Configuration in PWM mode */ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; TIM_OCInitStructure.TIM_Pulse = 0; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset; TIM_OC1Init(TIM1, &TIM_OCInitStructure); TIM_OCInitStructure.TIM_Pulse = 0; TIM_OC2Init(TIM1, &TIM_OCInitStructure); TIM_OCInitStructure.TIM_Pulse = 0; TIM_OC3Init(TIM1, &TIM_OCInitStructure); /* TIM1 counter enable */ TIM_Cmd(TIM1, ENABLE); /* TIM1 Main Output Enable */ TIM_CtrlPWMOutputs(TIM1, ENABLE); } |
Now the microcontroller is ready to generate the needed 3 PWM signals but is configured to produce a 0% duty cycle, so the LED seems powered off. The Standard Peripheral Library doesn’t provide a fast way to change PWM duty so I have used a trick. I wrote a function that directly write on the CCR registers of Timer1, so that the PWM duty cycle is instantly changed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void updateRGB(uint16_t rVal, uint16_t gVal, uint16_t bVal) { /* Compute the value to be set in ARR regiter to generate signal frequency at 10.00 Khz */ TimerPeriod = (SystemCoreClock / 10000 ) - 1; /* Compute CCR1 value to generate a duty cycle at rVal% for channel 1 */ Channel1Pulse = (uint16_t) (((uint32_t) rVal * (TimerPeriod - 1)) / 0xFFFF); /* Compute CCR2 value to generate a duty cycle at gVal% for channel 2 */ Channel2Pulse = (uint16_t) (((uint32_t) gVal * (TimerPeriod - 1)) / 0xFFFF); /* Compute CCR3 value to generate a duty cycle at bVal% for channel 3 */ Channel3Pulse = (uint16_t) (((uint32_t) bVal * (TimerPeriod - 1)) / 0xFFFF); TIM1->CCR1 = Channel1Pulse; TIM1->CCR2 = Channel2Pulse; TIM1->CCR3 = Channel3Pulse; } |
After initializing the system with PWM_Config, is possible to generate every color just invoking updateRGB with correct parameters.
The effect is exactly what I expect, but not what I like. In commercial RGB application there is a very nice fading effect from a color to another and I want to achieve the same result. Another function in needed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void fadeToColor(uint16_t rVal, uint16_t gVal, uint16_t bVal, uint16_t milliSecs) { int32_t redStep, greenStep, blueStep; uint16_t index; redStep = (rVal - RED_value) / milliSecs; greenStep = (gVal - GREEN_value) / milliSecs; blueStep = (bVal - BLUE_value) / milliSecs; for(index = 0; index <= milliSecs; index++) { updateRGB(RED_value + (redStep)*index, GREEN_value + (greenStep)*index, BLUE_value + (blueStep)*index); Delay(1); } updateRGB(rVal, gVal, bVal); Delay(1); RED_value = rVal; GREEN_value = gVal; BLUE_value = bVal; } |
With this function is possible to change color in a progressive way, and within a given time, leading to an eye-candy effect.
This is the effect when powered by 5V USB source
This is instead the effect when powered with 12V
The complete source code is available HERE. Please note that nrf24 library was not written by me but only modified (refer to library’s header) and the zip contains also the Standard Peripheral Library from ST but with a modified folder structure.
If you liked this article, please share 😀