Generic clocked serial protocol on STM32? - Page 1
Forum section: electronics / general

author: jmw
author_rank: Frequent Contributor
timestamp: June 20, 2020, 07:05:29 am
content: I want to implement the serial protocol for the TI TLC5926 for STM32. The TLC5926 is a LED driver that's functionally a shift register with some extra features for current control. There's a clock, serial data in, a latch enable (LE) pin that copies the shift registers to the output drivers, and an output enable (OE) pin that turns on/off all the output drivers.I made a quick prototype using a USART peripheral, which works, but I also need to control some of the extra features, which involves twiddling the OE and LE lines in some specific, timed sequences synchronous with the clock. It's no longer exactly a single, byte-framed serial line that can be managed using USART, and in case, USART wasn't a perfect match since the serial protocol doesn't need stop bits for example.What's the preferred way of implementing this on STM32? My thoughts are to use a timer to generate the clock, and then toggle GPIOs on interrupts on the falling edge. Is this the best way of doing it? If timers are the core of how it will work, can someone tell me a bit more about which modes to use (timers are complicated and I have yet to master them)?

author: ozcar
author_rank: Frequent Contributor
timestamp: June 20, 2020, 08:33:16 am
content: How were you generating the clock when using the USART?Maybe if you gave a bit more info on the sequences required and what speed you need, somebody could give some ideas.I found some STM32 example code that talks to a board using TLC5926 here:https://microcontrollershop.com/product_info.php?products_id=7167From a quick look, they appear to use an eclectic mix of SPI, bit banging, and with a timer thrown in for good measure.

author: jmw
author_rank: Frequent Contributor
timestamp: June 20, 2020, 04:58:27 pm
content: I don't need to generate a clock for the USART, just configure it in synchronous mode and it derives its clock from the peripheral clock behind the scenes.This is a timing diagram of the sequences used:Thanks for the pointer to that example code, I'll have a look. I didn't really say in the original post, but I'd like to do it a little nicer than bit-banging in the main loop.

author: ucanel
author_rank: Regular Contributor
timestamp: June 21, 2020, 12:34:56 am
content: I am not sure but maybe this may done with two spi.Stm32 has very useful dma features,you may send all the data with spi using dmaand you would not have to do anything in your main code.As an another way:Stm32 has dma feature that spits out whole 16 bits to a PortX with dma.If you have a whole port to spare and enough ram or flash memory space you can use this method.In that case you will have to create all the needed states of the port to do what you wish to do according to the desired signals by TLC5926.As a stm32 gpio dma example you may take a look at this:https://justanotherelectronicsblog.com/?p=201I have tried gpio dma and it is very useful.You may trigger gpio transfer intervals via timer.

author: ozcar
author_rank: Frequent Contributor
timestamp: June 21, 2020, 07:24:25 am
content: Quote from: ucanel on June 21, 2020, 12:34:56 amWouldn't you want to do 32 bit transfers so you can write to either half of the GPIO BSRR?At least that is what I have done before - in fact I just butchered something similar I had here and managed to get something looking like the required “switch to special mode”. I couldn't get it to go any faster than around 2.5MHz on the “blue pill” I had lying here though, but did not try to see why.That was done sending this to one of the GPIO BSRRs. This is using lines 0,1,2, but of course any could be used as long as they are on the same port, and it does not disturb the other lines so you don't need exclusive use of the whole port:Code:const uint32_t modespecial = {GPIO_BSRR_BR0 | GPIO_BSRR_BS1 | GPIO_BSRR_BR2,GPIO_BSRR_BS0, // 1GPIO_BSRR_BR0 | GPIO_BSRR_BR1,GPIO_BSRR_BS0, // 2GPIO_BSRR_BR0 | GPIO_BSRR_BS1,GPIO_BSRR_BS0, // 3GPIO_BSRR_BR0 | GPIO_BSRR_BS2,GPIO_BSRR_BS0, // 4GPIO_BSRR_BR0 | GPIO_BSRR_BR2,GPIO_BSRR_BS0, // 5GPIO_BSRR_BR0 // done};

author: ucanel
author_rank: Regular Contributor
timestamp: June 21, 2020, 11:11:57 am
content: Quote from: ozcar on June 21, 2020, 07:24:25 amYes, that is a good approach thanks for pointing out,i did not think of that.As a side note:https://gist.github.com/iwalpola/6c36c9573fd322a268ce890a118571caQuoteUsing this method with dma spits out gpio valuesweird protocols may be achieved without effecting all gpio pins.QuoteIt may easily be about clock configuration of the gpioor the time taken by code that shifts and calculates the state of the gpio.

author: ozcar
author_rank: Frequent Contributor
timestamp: June 21, 2020, 09:13:58 pm
content: Quote from: ucanel on June 21, 2020, 11:11:57 amI've just realised that maybe I was not clear about exactly what I did there. I was using DMA there (triggered by a timer) to transfer the “modespecial” array to the BSRR.I just tried doing this instead:Code:uint32_t *p,*e;for ( p=modespecial, e=p+11; p<e; GPIOA->BSRR = *p++);That saw the clock running at up to 18MHz. I say “up to”, because the first 2 (why 2??) clock pulses were consistently slower than the last three.But then this is definitely contrary to the stated aim of avoiding bit banging.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: June 21, 2020, 10:05:04 pm
content: DMA shares the data bus with the CPU, so it will not use every clock cycle. There is a priority setting that can be tweaked.Another option is to use multiple SPI modules with one configured to master and connected to clk/sdi/sdo of TLC, others configured to slaves with their clock inputs connected to master’s output and data MOSIs connected to LE and OE. You load the data representing LE/OE patterns into slaves tx regs, then fire a data transfer on the master - slaves will output their data synchronously to the master clk out.

author: Elasia
author_rank: Frequent Contributor
timestamp: June 21, 2020, 11:51:05 pm
content: I just happened to be browsing by and saw this.. i use the same chip, bit bang itenjoybool Loop(void){if (lSetLEDs){IO_LED_CLK_Toggle();switch (lLEDCounter0){case 0:IO_LED_CLK_SetLow(); // Set Clock LowIO_LED_DAT_SetLow(); // Set Data LowlLEDCounter0++;break;case 1:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 2:// Clock LowlLEDCounter0++;break;case 3:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 4:// Clock LowlLEDCounter0++;break;case 5:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 6:// Clock LowlLEDCounter0++;break;case 7:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 8:// Clock LowlLEDCounter0++;break;case 9:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 10:// Clock LowlLEDCounter0++;break;case 11:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 12:// Clock LowlLEDCounter0++;break;case 13:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 14:// Clock LowlLEDCounter0++;break;case 15:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 16:// Clock LowlLEDCounter0++;break;case 17:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 18:// Clock LowlLEDCounter0++;break;case 19:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 20:// Clock LowlLEDCounter0++;break;case 21:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 22:// Clock LowlLEDCounter0++;break;case 23:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 24:// Clock LowlLEDCounter0++;break;case 25:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 26:// Clock LowlLEDCounter0++;break;case 27:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 28:// Clock LowlLEDCounter0++;break;case 29:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 30:// Clock LowlLEDCounter0++;break;case 31:// Clock HighIO_LED_DAT_LAT = 0; // set high 1 or low 0lLEDCounter0++;break;case 32:// Clock LowlLEDCounter0++;break;case 33:// Clock High// Set LatchIO_LED_LE_SetHigh();lLEDCounter0++;break;case 34:// Clock Low// End LatchIO_LED_LE_SetLow();lLEDCounter0++;break;case 35:// Clock HighlLEDCounter0++;break;case 36:// Clock LowlLEDCounter0++;break;case 37:lLEDCounter0 = 0;lSetLEDs = 0;IO_LED_OE_SetLow(); // Enable current flowreturn true;break;default:lLEDCounter0 = 0;lSetLEDs = 0;}}return false;}

author: ozcar
author_rank: Frequent Contributor
timestamp: June 22, 2020, 07:18:53 am
content: Quote from: ozcar on June 21, 2020, 09:13:58 pmI had a bit of an afterthought...As I was apparently bumping into the limits of what the DMA could do, I tried without using the timer at all, by just setting DMA for memory-to-memory.That gives one transfer per 139ns resulting in clock frequency of 3.6MHz.

author: ucanel
author_rank: Regular Contributor
timestamp: June 25, 2020, 10:01:39 pm
content: @ozcarAccording to the AN4666Parallel synchronous transmission using GPIO and DMAat table 2 and table 5document says achievable pin frequency is up to 10MHz forcpu running at 80MHz,document is for STM32L4 and STM32F4.I have not examine the document for where is these clock values comes from.https://www.google.com/url?sa=t&source=web&rct=j&url=https://www.st.com/resource/en/application_note/dm00169730-parallel-synchronous-transmission-using-gpio-and-dma-stmicroelectronics.pdf&ved=2ahUKEwjqktn99p3qAhXS-yoKHSrzDm4QFjAAegQIBxAC&usg=AOvVaw0tJyZqBQnNJoi3XcYrdK8v

author: ozcar
author_rank: Frequent Contributor
timestamp: June 26, 2020, 02:27:58 am
content: Quote from: ucanel on June 25, 2020, 10:01:39 pmI just tried mem to mem DMA (so no timer) on a F407 with CPU & AHB bus 168MHz, and got 21 MHz clock (168/8), that is one transfer to BSRR every 24ns. I did see the pulses stretched at times, presumably when the DMA stream had to wait for access to the bus.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: June 26, 2020, 06:36:50 am
content: You can try freeing the bus by setting DMA to fire a completion interrupt, start the transfer, execute WFI instruction - it will stall the core until interrupt.

author: jmw
author_rank: Frequent Contributor
timestamp: June 27, 2020, 06:12:42 pm
content: DMA to the BSRR looks very cool, but using 4 bytes of memory for every clock half-cycle seems heavyweight especially if used to clock out long strings of serial data. I worked up my own prototype over the last 2 days and it seems to work well. It's sort of in between main loop bit-banging and hands-off DMA.It uses a timer in PWM mode to generate the clock.PWM is set up so the falling edge is aligned with the timer update event, and the interrupt handler loads outputs into GPIOs at that time.When the last bit is clocked out, the clock output is disabled and another timer is used to generate a one-shot pulse on LE.The OE pin is usually a PWM output for dimming, but is temporarily turned into a GPIO when the special mode sequences are output.

author: aheid
author_rank: Regular Contributor
timestamp: June 28, 2020, 03:12:14 am
content: One idea that came to mind was using a timer in PWM mode and use DMA to update the pulse (duty cycle) value. For the extra inputs maybe slaved timers could work.No idea if this would be any good but...

author: Simon
author_rank: Unknown
timestamp: June 28, 2020, 09:07:22 pm
content: You seem to be a victim of political correctness that wanted to get rid of master/slave terminology and therefore did not call it SPI either but just serial port. It's SPI not USART, SPI is a serial port much simpler than traditional serial ports meant for more complex comms. SDI is MOSI (master out slave in) and the error checking output SDO is MISO (master in slave out). You can create an array variable of 8 bit variables and with interrupts keep loading the data buffer.Most serial ports have a transmit shift register and a transmit buffer register. The shift register is what the data is sent out from one bit at a time which in the case of SPI is the same as the receive register as the data changes around from master to slave and from slave back to master and in a normal two way communication the master would start the transaction and if necessary put junk into the transmit buffer to get a message sent so that the slave is in turn to send one back. This is why they talk about error checking as you get back the data you send and check it's still the same.You do not necessarily need to connect the MISO pins together. So you usually have a buffer empty interrupt and a data transmitted complete interrupt. You dump the first byte into the transmit buffer and instantly this is put into the shift register whereupon the data buffer empty interrupt fires and you put the next byte of your array in and so on, every time a byte completes transmission the buffer dumps the byte you just put in into the shift register and demands more data so that you put the next byte in while the last is being sent. You use an array of bytes so that you can increment the index on the array variable each time you put a byte into the buffer until you run out of elements. Quite simple really. I doubt you have to meet any minimum speed for the transmission, it's SPI, it all works just as fast as you clock it.

author: jmw
author_rank: Frequent Contributor
timestamp: June 28, 2020, 09:27:13 pm
content: Quote from: Simon on June 28, 2020, 09:07:22 pmPolitical correctness? Nah, I just read the datasheet. It's technically not SPI because the MISO could switch between one of three pins (SDO, LE, or OE). 99% of the time it's the SDO pin and you could use SPI (or USART since stop bits are harmless) but you'll still need to do something custom for the special sequences that require synchronized pulses on the other two lines. There's clearly a number of ways to get it done:Bit-bang everythingUse a SPI (or USART) peripheral, and bit-bang the special sequencesEmulate a peripheral shift register through GPIOs and timer interrupt handlerDMA transfers to the BSRR for every output bit

author: Simon
author_rank: Unknown
timestamp: June 28, 2020, 09:37:13 pm
content: Without reading it in detail I think you are refering to the pin that latches the data to the outputs once the register has been filled? Just use a GPIO during the interrupt. Interrupt says that the data has been shifted, so you strobe the load data to outputs on the LED controller and then move onto the next byte. The SPI clock should only run when the master transmits, until you load the next byte it can't continue to transmit so halts the clock. If they have made it more complicated than that I'd find another driver, loads of them out there, i was looking at something similar not long ago that I did not use but it was not this complicated.

author: jmw
author_rank: Frequent Contributor
timestamp: June 28, 2020, 09:47:06 pm
content: Right, that's a reasonable way to handle the output latch signal. But there's more with this chip: you can adjust the current gain by sending it a magic clocked sequence on the LE and OE pins, then shifting in a 8-bit gain factor. The question is how to handle every use case of this device. Having tested my idea (#3 on my earlier list), I'm leaning toward #2 now. The interrupt handler and GPIO setting limits the output clock speed, and having a hardware shift register is going to be faster.

author: Simon
author_rank: Unknown
timestamp: June 29, 2020, 06:57:33 am
content: It sounds like they are trying to be too clever. These devices are meant to be cheap conveniences that save you a massive pain in the ass of I/O nightmare when you need to drive many LED's. Typically you can cascade them with the data out connected to the data in of another chip with a string as long as you like limited only by the refresh rate you want and the speed you can get the data sent through. To be honest the LED brightness control is a nice to have but in order to not make a chip that is too different from it's competitors they have gone and made a mess. If you want to control the brightness then just control the brightness, you are the one powering the LED's so control the power, I don't know if you can PWM the LED supply as these appear to be constant current sinks but should work, failing that a controllable voltage regulator or for efficiencies sake a SMPS. I see a resistor pin for current setting, anything you can do with that ? i believe a mosfet with a couple or resistors makes a half decent voltage controlled resistor. You can turn PWM outputs from your micro into voltages with a low pass filter.

author: capt bullshot
author_rank: Super Contributor
timestamp: June 29, 2020, 09:37:26 am
content: Quote from: jmw on June 28, 2020, 09:47:06 pmHaving done various of the mentioned method before (including the DMA-to-GPIO method, which is quite nice to produce a constant data stream to drive e.g. an LED matrix or DACs), here's my two cents:I'd suspect this special mode wouldn't be use too often. So under this assumption, I'd use an SPI or USART to shift out the data, and revert to bit-banging for the special mode. In case the "regular" date aren't updated too often also, I'd bit-bang the whole thing. Usually bit-banging is implemented faster (in terms of your time spent for writing the code) than fiddling with all the special modes of the peripherals to achieve a non-standard protocol. So unless you are under real-time or performance constraints, just use bit-banging. For one special case, I've re-used some old bit-banging I2C code instead of using the buggy I2C peripheral to save days of debugging

author: Simon
author_rank: Unknown
timestamp: June 29, 2020, 11:33:20 am
content: If you are familiar with SPI on your chip it should be a doddle. I spent several days agonizing over the SPI on the SAMC, I did not come out with any production code but some knowledge and experience of how to tackle such peripheral handling. to bit bang you need to use delays and hold up your application or start writing interrupt driven code with a timer at which point you have just written an unofficial SPI library that was more work and less efficient than the right tool for the job.

author: jmw
author_rank: Frequent Contributor
timestamp: June 29, 2020, 04:31:13 pm
content: Quote from: capt bullshot on June 29, 2020, 09:37:26 amYep, that's what I've since tried, SPI in transmit-only mode with DMA transfers, and the special sequences bit-banged. It's reliably clocking out data at 16 MHz and I'm much more happy with the result than trying to set GPIOs on interrupt.

author: Bassman59
author_rank: Super Contributor
timestamp: June 29, 2020, 06:43:06 pm
content: Quote from: Simon on June 28, 2020, 09:07:22 pmActually, it's not SPI. Itisa "serial peripheral interface," but it's not SPI in the usual sense of a clock, a chip select, and a data line in each direction synchronous to that clock and valid when the chip select is asserted. And there's no such thing as an SPI "spec," just that usual way of doing it.There are so many variants on the simple synchronous serial interface that I'm genuinely surprised when I come across a peripheral that is indeed "SPI." Thankfully, most of my work is in FPGAs so I can design the interface to work as the peripheral requires and not have to cajole a microcontroller's synchronous serial port to do what is needed.Right now I'm looking at a thing that uses what the chip manufacturer calls "SPI" to load registers. But it's a shift register that's 384 (!) bits long, and instead of a chip select, there's a sync signal you assert to latch the shifted data into the registers. In the FPGA that calls for daisy-chaining a bunch of SRL32s. I might just use a dual-port BRAM for this -- one bit wide on the shifter side and 8 or 16 bits wide on the other side so it can be loaded.

author: Simon
author_rank: Unknown
timestamp: June 29, 2020, 08:12:32 pm
content: On the one hand you say there is no official spec on the other you differentiate between actual and SPI like chips. Which one is it? I have never seen any documentation about SPI other than the blurbs people put together online and what the micro controller datasheets tell me. If the micro can do it and it's beneficial great but another may not. Some micros may have bigger shift registers other are limited to the common and therefore deemed to be official 8 or 9 bits. The SAMC has address recognition on it's "SPI" interface, can they still call it SPI with this non standard feature - oh wait there is no standard and why is that feature not available for the actual usart? Why does the lin mode of the USART have auto baud syncing, why can't I use that on RS485/UART mode? oh wait nothing really stops me sticking an RS485 transciver on a lin interface providing i write my code to handle the direction which is why you do on RS485 anyway when the MCU does not "officially" support RS485 by which they mean saving you one line of code to assert the receive or transmit mode in the application code.Every chip has various options covering a minimum that everyone else does with some more bells and whistles. The data input on the master is often an option to be enabled because often like in this case or little LCD's there is no data to come back so you save a pin rather than be forced to not use it for SPI but not as GPIO either.

author: Bassman59
author_rank: Super Contributor
timestamp: June 30, 2020, 06:45:46 pm
content: Quote from: Simon on June 29, 2020, 08:12:32 pmYou should have noticed that I put "SPI" in quotation marks.There seems to be a de facto (look that up) standard for SPI that has four "modes," and the differentiation between those "modes" is in the state at which the clock idles and whether the first data bit is driven out on MOSI prior to the first falling clock edge or whether it goes out on that first falling clock edge. Silicon Labs, among others, seems to like this definition, and it describes how their parts actually work.QuoteAnd that's why I put "SPI" in quotes -- anyone can call something "SPI."I think most engineers understand that "SPI" is nothing more than a shift register. All of the other stuff, like address recognition, is built on top of the shift register.Autobaud is not really interesting in a synchronous protocol (like "SPI"). There is no reason why a receiving device needs to figure out the baud rate -- the clock is there!Anyways, I think you doth protest too much.QuoteSo what? So you figure out what features the USART (or as NXP calls them, "FLEXCOMM" which makes sense to me) has or doesn't have and you use them or you don't.What we colloquially call "SPI" is just one variant on the synchronous "serial" port implemented with a couple of shift registers and some logic.I mean, what's a SERDES? Is there a standard?Now if you REALLY want to till at windmills, complain when someone says, "UART transmit" or "OSERDES." Ask yourself why those are stupid.