Thanks Kalvin and everyone else for your suggestions and help. As Kalvin (and a few others) pointed out, I had screwed up the interrupt routine by not clearing the interrupt flag (I had misread somewhere that this happens automatically in the HAL documentation).
You probably did not misread, the HAL can clear the interrupt flag for you, but it must have a chance to do it.
Since you are writing your own TIM3 IRQ handler, the HAL IRQ handling for the timer is never invoked.
The routine in HAL (I did not check the specific one for L1xx, but they are all similar) will call a callback function you can define according to the specific interrupt (e.g. period, output compare etc.), but it must be invoked inside the IRQ handler:
void TIM3_IRQHandler(void)
{
HAL_TIM_IRQHandler(htim3)
}
in your case.
Your code seems generated by CubeMX, if you had specified that TIM3 would use interrupt this would have happened automatically (and you'd have learned less!), and you'd be left with filling in:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// Your code here
}
Is it worth doing it your way or the HAL way?
In this case, writing your own handler seems simpler, in others (when coping with several different kinds of interrupts) one would end up writing something quite similar to what is already in the HAL (check flags, dispatch and clear) so, provided the HAL is already in use...why not?
Another note: why keep the busy loop delay inside the handler, now that everything is working?
With a reasonable prescaler and period, one can get almost any rate from the timer, and any duty cycle using the PWM/OC capabilities.