Probably the easiest way to do this is check the status of the UIE bit becuase the stopping of the transmission is done by this line.
Code: Select all
TIM4->DIER &= ~TIM_DIER_UIE; //disable interrupt flag to end transmission.
Probably the easiest way to do this is check the status of the UIE bit becuase the stopping of the transmission is done by this line.
Code: Select all
TIM4->DIER &= ~TIM_DIER_UIE; //disable interrupt flag to end transmission.
Yes you can tell that it is no longer transmitting by checking UIE, but as it stands you don't know how long is has been in that state, so you don't know whether the output has been low long enough to provide the "reset". That time varies depending on the LED. You could maybe count off some multiple of the "dummy" pulses with CCR1 = 0, or change ARR for one longer period.flyboy74 wrote: ↑Wed May 06, 2020 9:11 pm
Probably the easiest way to do this is check the status of the UIE bit becuase the stopping of the transmission is done by this line.Code: Select all
TIM4->DIER &= ~TIM_DIER_UIE; //disable interrupt flag to end transmission.
After I saw the occasional dropped bit, I inserted code to provide a trigger in show_neopixels:
Code: Select all
void show_neopixels(){
GPIOD->BSRR = GPIO_BSRR_BS14; // trigger up
pos = 0; //set the interupt to start at first byte
mask = 0B10000000; //set the interupt to start at first bit
TIM4->DIER |= TIM_DIER_UIE; //enable interupt flag to be generated to start transmission
GPIOD->BSRR = GPIO_BSRR_BR14; // trigger down
}
This only uses one timer channel, but as was mentioned above this could be extended to use more timer channels to output data to several strings simultaneously. Could probably synchronise timers for even more channels.
Code: Select all
void DMA1_Stream6_IRQHandler(void)
{
uint16_t *buf=0;
int i;
if ( DMA1->HISR & DMA_HISR_HTIF6 )
{
DMA1->HIFCR |= DMA_HIFCR_CHTIF6; // clear half-transfer interrupt
buf = DMAbuf; // to fill 1st half
}
if ( DMA1->HISR & DMA_HISR_TCIF6 )
{
DMA1->HIFCR |= DMA_HIFCR_CTCIF6; // clear transfer complete interrupt
buf = DMAbuf + BITS_PER_LED; // to fill 2nd half
}
if ( !buf ) return;
if ( (pos < sizeof(LED_data)) )
{
for ( i=0; i<BYTES_PER_LED; i++,pos++ ) // sizeof(pos) is multiple of BYTES_PER_LED
for ( mask=0x80; mask; mask>>=1 )
{
if ( LED_data[pos] & mask )
*buf = high_CCR1;
else
*buf = low_CCR1;
buf++;
}
}
else // out of data
{
if ( lastbit < TRESET ) // approx for now
{
for ( i=0; i<BITS_PER_LED; i++,*buf++=0 ); // no pulses
lastbit++;
}
else
DMA1_Stream6->CR &= ~DMA_SxCR_EN; // DMA1 stream6 disable
}
}
I didn't look super close at your code so correct me if I am wrong. You create a buffer big enough to hold all the vaules for the CCR for 48 bits (I assume you can use uint8_t as values are always low) so that would be a total of an extra 48 bytes on top of the array that stores the whole led strip. Then you use a double buffered DMA to transfer the buffer to the CCR and after every byte it fires an interrupt that updates half the buffer.ozcar wrote: ↑Thu May 07, 2020 3:31 am Inspired by Flyboy's code, I now came up with a version that uses DMA, but with a buffer only big enough for 2 LED's worth of data. Instead of having to handle one interrupt per bit, there is only 1 interrupt per LED (24 bits). The interrupt routine has to do more work now, but it still comes out ahead in terms of CPU usage. This is what the interrupt routine looks like:
I suspect somebody may have done something similar already. I don't know if it can be done using the STM32duino core. Yet another thing for me to look at some time.Code: Select all
void DMA1_Stream6_IRQHandler(void) { uint16_t *buf=0; int i; if ( DMA1->HISR & DMA_HISR_HTIF6 ) { DMA1->HIFCR |= DMA_HIFCR_CHTIF6; // clear half-transfer interrupt buf = DMAbuf; // to fill 1st half } if ( DMA1->HISR & DMA_HISR_TCIF6 ) { DMA1->HIFCR |= DMA_HIFCR_CTCIF6; // clear transfer complete interrupt buf = DMAbuf + BITS_PER_LED; // to fill 2nd half } if ( !buf ) return; if ( (pos < sizeof(LED_data)) ) { for ( i=0; i<BYTES_PER_LED; i++,pos++ ) // sizeof(pos) is multiple of BYTES_PER_LED for ( mask=0x80; mask; mask>>=1 ) { if ( LED_data[pos] & mask ) *buf = high_CCR1; else *buf = low_CCR1; buf++; } } else // out of data { if ( lastbit < TRESET ) // approx for now { for ( i=0; i<BITS_PER_LED; i++,*buf++=0 ); // no pulses lastbit++; } else DMA1_Stream6->CR &= ~DMA_SxCR_EN; // DMA1 stream6 disable } }
The DMA buffer is 48 uint16_t values, so it is not that huge. No particular reason for the buffer to be exactly that many entries, but 48 for two LEDs worth seemed like a reasonable number. The values will fit in uint8_t so maybe there is a way to cut the size of the buffer in half. I did avoid using uint32_t sized entries because TIM4 uses only 16 bits.flyboy74 wrote: ↑Thu May 07, 2020 9:12 am I didn't look super close at your code so correct me if I am wrong. You create a buffer big enough to hold all the vaules for the CCR for 48 bits (I assume you can use uint8_t as values are always low) so that would be a total of an extra 48 bytes on top of the array that stores the whole led strip. Then you use a double buffered DMA to transfer the buffer to the CCR and after every byte it fires an interrupt that updates half the buffer.
I decided to check if this method has been used before, and the very first thing brought up by my uncle Google is this: http://stm32f4-discovery.net/2018/06/tu ... eds-stm32/ .
Ha Ha Ha just when you think you have invented something new you find out someone else had the same ideaozcar wrote: ↑Thu May 07, 2020 10:56 pmI decided to check if this method has been used before, and the very first thing brought up by my uncle Google is this: http://stm32f4-discovery.net/2018/06/tu ... eds-stm32/ .
I did not look very closely at the actual code, but he also decided to make the DMA buffer big enough for 2 LED's worth of data. He drew a nice picture there of the DMA buffer to show how it works, but TBH I could not follow all of what he was saying in the description below that - he made it sound lot more complicated than I thought it needed to be (but then maybe I missed something vital in my attempt).
I note his DMA buffer contains uint32_t values, while mine were uint16_t - I'm still not 100% sure if you could get it to work with uint8_t.
I have been thinking about why a bit could possibly get dropped, the only way would be for the interrupt to fire twice causing the the registers to be updated twice.ozcar wrote: ↑Wed May 06, 2020 11:34 pm If I miss the trigger there (which I can't see happening anyway), then I get to see nothing. With that in place, I saw it usually working OK and outputting 8 bits, but very occasionally only 7, with the first bit missing.
I'm not saying it is going to fail exactly every 20th time - for me it was very random. However, when I eventually worked out what it was, I found I could get it to fail almost 100% of the time. The clue is there in what I did to fix it.
I was seeing it with the timing as you had it for 400kHz LEDs, but I only have 800kHz LEDs. With the different timing, and perhaps the optimisation set, it might be more or less likely to occur. If I get a chance, I will see if I can get it to happen with the 800kHz LEDs attached - I'm pretty sure it would cause a visible effect on the LEDs, particularly if the data sent was something like 0x808080808080... or 0x800000800000...
The good news is that with your modified interrupt routine the first bit does not get lost.flyboy74 wrote: ↑Fri May 08, 2020 7:53 am
I have changed my interrupt handler a little bit so that all the update code is inside the loop that tests TIM_SR_UIF bit and I don't reset this bit till the end of the handler that way it can't fire again till after exit.
Please test this update and see if your still get a dropped bit https://github.com/OutOfTheBots/STM32_N ... ter/main.c