STM32 and 24bit SPI DAC  - Page 1
Forum section: electronics / general

author: matt09
author_rank: Regular Contributor
timestamp: October 03, 2018, 11:16:36 am
content: Hi,I am using a DAC which takes data in 24 bit format, 8 control bits and 16 data.I currently have two separate large 8 bit and 16 bit arrays.I want to change between modes on the fly before each transfer using SPI_DATASIZE_8BIT / 16BIT, but it stays in the first declared mode. I.e I am trying to send 8,16.8,16 but I don't think this is possible.I could keep my first 8 bit control array as it and split the 16 bit data array into two separate 8 bit arrays, then send as A1 B1 C1, A2 B2 C2 but this seems messy - there would be 3 SPI send commands for one complete frame and condition loops.Perhaps it is as clean as the code can be while using HAL though?I am working with others unfamiliar to coding so would like to stick with using the HAL unless it is impossible for this.Could someone please advise?Thanks

author: AndyC_772
author_rank: Super Contributor
timestamp: October 03, 2018, 02:13:20 pm
content: IIRC, the SPI interface on STM32 can be configured into either 8 bit or 16 bit mode, so the cleanest way would be to send three sets of 8 bits. There's nothing wrong with code along the lines of:Code:spi_tx (control_array );spi_tx (data_array  & 0xff);spi_tx ((data_array  >> 8) 0xff);Don't reprogram the SPI interface just to avoid sending a 16 bit word in two halves.If the SPI clock is relatively 'slow', and you don't want to have your code hang around waiting for a transfer to complete, then you can always set up your three bytes in RAM and then use DMA.

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 03, 2018, 04:55:25 pm
content: Leave SPI in 8-bit mode. Just copy each 24-bit "word" in a temporary 3-byte buffer and send this 3-byte buffer with any of the HAL function that you see fit (blocking, IT or DMA).

author: ajb
author_rank: Super Contributor
timestamp: October 03, 2018, 05:50:42 pm
content: Actually, if you don't use the HAL, the STM32 does a "clever" thing where in 8-bit mode a 16-bit write to the SPI data register is interpreted as a write of two packed 8-bit values to the built-in FIFO. Normally this is rather annoying, because the register is declared as a 16-bit type, so in 8-bit mode you have to cast the register to an 8-bit type to force an 8-bit access--otherwise the compiler emits a 16-bit write and you get an extra null byte on the wire--but in your case you could do an 8-bit write for the control data and then a 16-bit write for the data word, and it would work exactly as you want it to, provided the endianness is correct.The same "cleverness" applies to read accesses, so you have to do the same 8-bit cast to extract the data from the receive FIFO correctly.However, I expect that the HAL hides all of this from you, and I'm not sure if you'd get into trouble by trying to skirt the HAL and write to the register directly.

author: AndyC_772
author_rank: Super Contributor
timestamp: October 03, 2018, 07:18:25 pm
content: Quote from: ajb on October 03, 2018, 05:50:42 pmI'm guessing that you've also spent a happy afternoon trying to track down the reason why the SPI interface keeps spitting out extra bits.I feel your pain, my friend

author: ajb
author_rank: Super Contributor
timestamp: October 03, 2018, 11:17:10 pm
content: Quote from: AndyC_772 on October 03, 2018, 07:18:25 pmBingo. They could have just made the data register a union within the SPI struct with DATA8 and DATA16 members, then the behavior would have been more obvious and you could control the access mode without a stupid typecast, but no, that would have been too convenient...

author: Siwastaja
author_rank: Super Contributor
timestamp: October 04, 2018, 05:00:28 pm
content: Indeed.Similarly, on the new STM32H7, they have a fancy new datagram counter feature on the SPI, and have two features to support it, a TSIZE register, and a TSER register, so that a 16-bit write operation to the "TSER" register is considered a command to extend the transaction. Hence, both registers must be addressed as 16-bit operations, separately. Now, this would be too easy, so they packed them into single 32-bit register, and decided to call that CR2. So you either modify the header file to have uint16_t tser and uint16_t tsize, or do some fancy pointer casting every time so that you don't mess up the TSIZE value when asking for an extension.STM32 SPI is kinda tricky to work with reliably yet efficiently; also with timings when writing to the registers. Every time you do something, you find a few new traps, such as a requirement to manually clear transfer complete flags before starting a new operation; failure to do so does not prevent normal operation, butdoesprevent DMA request generation, without giving any error flag or other indication. TX just doesn't start.But theyhavefixed some known issues as well; the newest incarnation of SPI flushes the TX fifo when you disable the SPI, so you can actually abort a transfer! Earlier, the only way to achieve this was through RCC reset registers.Theystilldon't support any kind of really useful hardware management for the nCS pin (yet bring a package of poorly documented complexity with their multimaster crap no one uses). Any variable-length messaging, or just generally wanting robustness on the communication using the nCS to delimit a packet - which is how SPI was originally designed! - instead of relying on a byte counter, still requires you to setup EXTI interrupts on the nCS pin changes, then resetting the SPI peripheral in the ISR.

author: matt09
author_rank: Regular Contributor
timestamp: October 08, 2018, 05:37:48 pm
content: Hi All,Thanks for all the replies. I'm new to STM32 programming but have had a nightmare with every peripheral containing bugs or snags so far - this is just my latest..So I tried the following code as suggested but have hit a new snag. The function doesn't seem to want to work with individual array elements. I tried both methods below but as soon as the element operator [ i] is introduced, the SPI line sits low.The function does work with arrays but sends blocks of elements as governed by the last function variable (currently set to 1 byte)uint8_t test[] = {85,170};HAL_SPI_Transmit_DMA(&hspi1,&test[i ],1);*************************************HAL_SPI_Transmit_DMA(&hspi1,*(&test+i),1);I assume this is possible else it you would only ever be able to sent parts of an array starting from index 0.Please could anyone advise?Many thanks

author: dgtl
author_rank: Regular Contributor
timestamp: October 08, 2018, 07:17:25 pm
content: Quote from: matt09 on October 08, 2018, 05:37:48 pmThe first one should work. The second one is wrong. Test is array, array name is pointer to first element. You don't need &. The * dereference gets value from pointer; you need to pass pointer. Correct is HAL_SPI_Transmit_DMA(&hspi1, test+i, 1). In both cases, the increment i is in units of array element size, not in bytes.Sending a single byte via DMA is overkill. Just write to data register and you're done.

author: matt09
author_rank: Regular Contributor
timestamp: October 08, 2018, 09:16:12 pm
content: So it turns out for some reason neither HAL_SPI_Transmit_DMA(&hspi1,&test[i ],1); or HAL_SPI_Transmit_DMA(&hspi1,test+i, 1) will work with HAL_SPI_Transmit_DMA.HAL_SPI_Transmit works fine, I was just using DMA in case of big data going out later, thanks for all the help.Now to see if it will all work with the DAC tomorrow with manual nSS...

author: SiliconWizard
author_rank: Super Contributor
timestamp: October 08, 2018, 09:41:08 pm
content: HAL_SPI_Transmit() is a blocking call. HAL_SPI_Transmit_DMA() is not. Probably the reason why you're having this issue. Using HAL_SPI_Transmit_DMA() is a bit more involved. You have to make sure the buffer exists during the whole transfer (using a local variable for the buffer and returning from the function that calls it before the end of the transfer is a sure way of shooting yourself in the foot for instance.) Also, if you're making two consecutive calls of HAL_SPI_Transmit_DMA(), that won't work. You have to wait for the end of the first transfer before starting the second. Two things to check.