bad behavior on an attempt to implement multiplexing on STM32 - Page 1
Forum section: electronics / general

author: RawCode
author_rank: Regular Contributor
timestamp: July 08, 2019, 09:51:49 am
content: Hello there!I'm quite new to the STM32 mcu, so I'm trying to implement basic things to understand how it works. To do so, I tried to multiplex a 4 rows by 11 columns LED array, using two timers, one to drive the 4 rows with the PWM output and another timer to switch the LEDs in the columns.My code kinda works, but it works not as I expected: for example, it turns on in the same row two LEDs, one of them is switched on as it should be, the other led should be lit in another row and not in this row. Same behavior in the other rows. It seems like the PWM switching is too slow in comparison to the switching of the columns, and because of this there is this this "ghosting" effect, but i set the timers with the same prescaler and the same period count in both timers, so i don't know (prescaler set to 32000 and period count set to 1 @ timer clock 32MHz)This is my code:Code:void displayNumber(struct time input){volatile uint16_t intTimerOld=0;uint8_t cnt=0;htim2.Instance->CCR1 = 100;// set the duty cycle to 100htim2.Instance->CCR2 = 100;htim2.Instance->CCR3 = 100;htim2.Instance->CCR4 = 100;HAL_TIM_Base_Start_IT(&htim3);while(intTimer < 6500){if(intTimerOld != intTimer){switch(cnt){case 0:HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);//stop all the PWMHAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_3);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_4);resetPin();associateNumber(0);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);//start the first PWMcnt++;break;case 1:HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_3);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_4);resetPin();associateNumber(0);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);cnt++;break;case 2:HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_3);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_4);resetPin();associateNumber(0);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);cnt++;break;case 3:HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_3);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_4);resetPin();associateNumber(0);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);cnt++;break;case 4:HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_3);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_4);resetPin();associateNumber(10);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);cnt=0;break;}intTimerOld = intTimer;}}resetPin();htim2.Instance->CCR1 = 0;htim2.Instance->CCR2 = 0;htim2.Instance->CCR3 = 0;htim2.Instance->CCR4 = 0;HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_3);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_4);HAL_TIM_Base_Stop_IT(&htim3);intTimerOld=0;intTimer=0;}The function resetPin() is a custom function that I made, which reset to low all the pin of the columns, using for example "HAL_GPIO_WritePin(GPIOC, MUX_9_Pin, GPIO_PIN_RESET)"The function associatePin() is also a custom function that I made, which select the right column by its argument and it switch it to high, using for example "HAL_GPIO_WritePin(GPIOA, MUX_1_Pin, GPIO_PIN_SET)"Do you have any suggestions to achieve a proper multiplexing? What am I doing wrong? Can you help me?

author: capt bullshot
author_rank: Super Contributor
timestamp: July 08, 2019, 10:41:47 am
content: Your source isn't complete, so I can't see the whole point of your code, and which way you're using the timers.Anyway, the case 4: of switch(cnt) looks suspicious to me since it kind of repeats case 1:In general, I looks OK to use four timer outputs in either PWM or one-shot mode to generate a multiplexing pattern for variable intensity of the LEDs. Another timer would drive an interrupt routine to advance the common columns. Within this interrupt routine one would (in this order)turn off all rowsturn off active columnturn on next columninitiate PWM or one-shot timers with the new column datareturn and wait for next interruptSome care has to be taken to start the row timers synchronously enough to have a non-flickering result. I'd program the row timers period to the column interrupt rate, and use the row timers in one-shot mode.

author: andersm
author_rank: Super Contributor
timestamp: July 08, 2019, 11:59:41 am
content: Note that since the intTimerOld variable is used only inside that one function, there's no point in declaring it volatile.

author: RawCode
author_rank: Regular Contributor
timestamp: July 08, 2019, 12:22:55 pm
content: thanks for your reply.Quote from: capt bullshot on July 08, 2019, 10:41:47 amyes, i know. I'm doing it on purpose: this project should be something like a watch, and to show a dot between the hours and the minutes i'm adding the 4th case to turn on the 11th column in the second row as a dot.Quote from: capt bullshot on July 08, 2019, 10:41:47 amI'm doing something like you said: i'm using a timer to output the PWM signals and another timer to trigger an interrupt every 1ms to change row(if my calculations are correct). In the interrupt routine btw i'm just updating a volatile variable that counts how many times the timer triggers the interrupt. I'm using this number also as the end of the multiplexing routine. So, the whole operation is done in the main.c, and in the interrupt routine i'm just updating a variable. am i doing it wrong?Quote from: capt bullshot on July 08, 2019, 10:41:47 ambtw i didn't quite understand this. what do you mean by program the row timers period to the column interrupt rate? and what is the one-shot mode? what is its purpose?anyway. this is my whole code(is quite long, i'll post it on PasteBin). There are 3 timers setted, one of them (TIM9) is used to drive a small motor and it works fine. TIM 3 is the timer uset to switch the row, and TIM2 is used for the PWM signals.https://pastebin.com/r9vKT5wT//main codehttps://pastebin.com/PzP6jGgy//interrupt service routinesAs you can notice in the picture the 4th row(the last in the right) has its 11th column lit as the second row, and it should not be lit. Btw, this ghosting behavior is casual.

author: capt bullshot
author_rank: Super Contributor
timestamp: July 08, 2019, 01:14:41 pm
content: So I've skimmed over your code:It's not the way I'd do that task, but never mind. I'd put the multiplexing within the TIM3 interrupt, so then main task doesn't have to bother with that.Didn't follow your logic for the intentional "case 4:", but my common sense tells me that this can't be the right way, you'd light up the decimal point in "case 1:" additionally to the number.I can see one issue with the timers: Setting the prescaler to 32000 and the period to 1 makes the counter effectively count from zero to one and reset then to zero. No way to generate a useful PWM signal that way with TIM2. What you want is to set the prescaler to a value that allows the counter to count up to a as high as possible value without overflowing. So assuming a 32MHz clock, one could set the prescaler to 1 and the period to 32000 (minus one -> 31999) to achieve 1ms period. Now you can set the CCR registers to any value from 0 to 31999 to set the LED intensity.You can apply the same to TIM3 (generating the interrupt) but your way should work too here. Otherwise, you could use TIM2 reload event to trigger the interrupt, as it seems to have the same period and don't need TIM3 anymore.One-shot mode (can't be be bothered to look up the term STM uses for their timers, could be named differentyl) means you start the timer and it runs to the terminal value (the set period), then stops. For the next cycle you'd have to start it again - as far as I can see you're doing that anyway. One-shot mode would just turn off your LEDs after one PWM cycle, so you'd just have to load the CCR registers with their new values and start the timer again. For this to work, program the timer outputs to start the cycle with "LED on" and "LED off" at compare event.Regarding the "ghosting" between the second and fourth line of LEDs: This isn't the typical "ghosting" one can usually see with multiplexed LEDs. Ghosting results when one common line is turned off and the next is turned on while the individual lines aren't fully switched over to their next state. This would rather look like one LED above or below a lit LED in the vertical lines would slightly glow.Looks like you're using transistors for the row and column drive, their turn-off delay often causes ghosting with multiplexed LEDs, but from what I can tell this isn't a issue (yet) here.

author: capt bullshot
author_rank: Super Contributor
timestamp: July 08, 2019, 01:26:18 pm
content: Just an addition:I thing I've got your row / column scheme the wrong way when I wrote about the ghosting.Your photo indeed shows some kind of ghosting, I guess it's an result of having the prescaler set to 32000 and the period set to 1 - so depending on the timing of your main() in relation to the timer overflowing, simply the wrong column of LEDs get powered. Due to the prescaler set to 32000 you've got very coarse control over the TIM2 outputs, so I guess they simply sometimes skip a columg and sometime don't.

author: RawCode
author_rank: Regular Contributor
timestamp: July 08, 2019, 02:29:11 pm
content: Thanks for the explanation.I modified my code as you suggested, changing the prescaler and the period to 1 and 32000 respectively in TIM2 (i also did it in TIM3), but there is still this ghosting problem.yes, i'm using some transistor to drive the LED, but i don't think that they're causing this issue: the frequencies involved are to low, aren't they?i also tried to don't use the associatePin() and resetPin(), but the ghosting is still there.Any other suggestions?Anyway i did another photo that shows better this ghosting problem. It seems like the process involved in switching on/switching off the PWM outputs is too slow

author: capt bullshot
author_rank: Super Contributor
timestamp: July 08, 2019, 03:02:30 pm
content: Assuming the ghosting to be seen between the middle two columns, I can think of two reasons for that:- Slow transistor turn-off (yes, this can still be visible at 1ms scanning clock) - try to add more dead time between turning off the LEDs and turning on the next column.- Update behaviour of the timer registers - I didn't look into these details of your code, and don't know what the HAL calls you've used do in detail, I do prefer to write the registers directly (use HAL only for first time initialization). Using HAL doesn't replace reading the manual to get a good understanding of how the peripherals behave. So what might be the issue: Updating the the CCR registers doesn't change the timer output at the same instant, but is delayed depending on the particular configuration, this also might cause the ghosting.If this is the first time you use these timers, you might want to draw some timing diagrams using pen and paper showing the timer operation in relation to the register updates you do, you'll have to dig through the timer section of the reference manual to get there. From my experience, this helps to get an idea of when the HAL functions and CCR register writes actually update the output pin, then you'll get an idea of how to program the timer to act like you want it to. Maybe it's already doing so, then look at the transistors again ...Another (simple) way to rule out the transistors would just be to completely skip TIM2 and use GPIO for both columns and rows. Once this works, switch your code to use the timer again.

author: RawCode
author_rank: Regular Contributor
timestamp: July 08, 2019, 10:15:07 pm
content: Thanks for your suggestions.I managed to solve the ghosting problem: slowing down the timer to a human period, i noticed some glitches during the switching-on/switching-off cycles, even configuring the register instead of using the HAL library. So i tried to remove those glitches introducing some "blanking" case in the switch, between every switch-on.And there we are, a multiplexing without any ghosting! I have to tune a bit the TIM3 and TIM2, to get a non flashing behavior and a the LEDs not too much dimmed, but it worksThank you all sinceriously!This is my code now:Code:void displayNumber(struct time input){uint16_t intTimerOld=0;volatile uint8_t cnt=0;HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);htim2.Instance->CCR1 = 0;htim2.Instance->CCR2 = 0;htim2.Instance->CCR3 = 0;htim2.Instance->CCR4 = 0;HAL_TIM_Base_Start_IT(&htim3);while(intTimer < 1000){if(intTimerOld != intTimer){switch(cnt){case 0:resetPin();htim2.Instance->CCR4 = 0;cnt++;break;case 1:associateNumber(input.minutesD);cnt++;break;case 2:htim2.Instance->CCR1 = 12499;cnt++;break;case 3:resetPin();htim2.Instance->CCR1 = 0;cnt++;break;case 4:associateNumber(input.minutesU);//associateNumber(10);cnt++;break;case 5:htim2.Instance->CCR2 = 12499;cnt++;break;case 6:resetPin();htim2.Instance->CCR2 = 0;cnt++;break;case 7:associateNumber(10);cnt++;break;case 8:htim2.Instance->CCR2 = 12499;cnt++;break;case 9:resetPin();htim2.Instance->CCR2 = 0;cnt++;break;case 10:associateNumber(input.secondsD);cnt++;break;case 11:htim2.Instance->CCR3 = 12499;cnt++;break;case 12:resetPin();htim2.Instance->CCR3 = 0;cnt++;break;case 13:associateNumber(input.secondsU);cnt++;break;case 14:htim2.Instance->CCR4 = 12499;cnt=0;break;}intTimerOld = intTimer;}}resetPin();htim2.Instance->CCR1 = 0;htim2.Instance->CCR2 = 0;htim2.Instance->CCR3 = 0;htim2.Instance->CCR4 = 0;HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_3);HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_4);HAL_TIM_Base_Stop_IT(&htim3);intTimerOld=0;intTimer=0;}

author: capt bullshot
author_rank: Super Contributor
timestamp: July 09, 2019, 05:31:03 am
content: You're welcome.QuoteThat's a pretty good idea, I'd just have used a scope.