C++ question. Passing array values of an undeclared/initialised array - Page 1
Forum section: electronics / general

author: carl0s
author_rank: Unknown
timestamp: July 31, 2018, 07:57:06 pm
content: I have read about variadics. I'm not sure if that's just not the done thing though. Or is that what I should be doing?I want to do this:SSD2828_Send_DCS(0xFF, 3, {0x01, 0x81, 0x98});where FF is the cmd/register, 3 is the data size, and the array is the data.I realise the normal way would be to store the 3 bytes in an array and pass that, but then it's two lines for each instruction, and I was hoping to have something that looked like a simple list just like the initial-code I've been given by the LCD mfr.I can think of another couple of options:1. Overloaded functions. The data is always between 0 and 3 bytes from what I can see. But then I'd be writing the same function 4 times and having to alter/correct 4 separate bits of essentially the same function.2. Fixing my datasize at 32 bits, and, I was going to say casting down to an 8 bit int if the value is <0xFF. Up until this very moment I thought this was a none-starter as I'd have to write a 1 byte 0x01 as 0x00000001, but that's not true is it. I can just pass 0x01.My completely untested Send_DCS command, which probably won't work because I've probably screwed up the array pointer passing stuff, looks like this:Code:void SSD2828_SPI_WriteCmd(uint8_t cmd) {HAL_GPIO_WritePin(GPIOH, GPIO_PIN_3, GPIO_PIN_RESET);HAL_SPI_Transmit(&hspi5, &cmd, 1, 100);}void SSD2828_SPI_WriteData(uint8_t data) {HAL_GPIO_WritePin(GPIOH, GPIO_PIN_3, GPIO_PIN_SET);HAL_SPI_Transmit(&hspi5, &data, 1, 100);}void SSD2828_Send_DCS(uint8_t cmd, uint8_t dataSize, uint8_t* data){SSD2828_SPI_WriteCmd(0xBC);SSD2828_SPI_WriteData(dataSize);SSD2828_SPI_WriteData(0x00);SSD2828_SPI_WriteCmd(0xBF);SSD2828_SPI_WriteData(cmd);while (dataSize >0) {SSD2828_SPI_WriteCmd(0xBF);SSD2828_SPI_WriteData(data[dataSize-1]);dataSize--;}}The SPI_WriteCmd and SPI_WriteData do work and the SSD2828 responds to an ID request so that's all good.I think I'm going to go with the 'uint32_t' data.. that should work, I think.. :-/ then deal with it byte by byte in the send function. Data is never more than 4 bytes.Update: So I'm sort of thinking like this. Untested. I tried testing in Python but it's tricky.. doesn't do/show bitshifts the same.Code:void SSD2828_Send_DCS(uint8_t cmd, uint8_t dataSize, uint32_t data){SSD2828_SPI_WriteCmd(0xBC);SSD2828_SPI_WriteData(dataSize);SSD2828_SPI_WriteData(0x00);SSD2828_SPI_WriteCmd(0xBF);SSD2828_SPI_WriteData(cmd);for (int i = 0; i < dataSize; i++) {SSD2828_SPI_WriteCmd(0xBF);SSD2828_SPI_WriteData((uint8_t) (data >> 8 * i));}}

author: carl0s
author_rank: Unknown
timestamp: July 31, 2018, 08:41:25 pm
content: Works a treat. Screen still dead to the world, but I can experiment without having to write 8 lines for each command.

author: orin
author_rank: Frequent Contributor
timestamp: July 31, 2018, 10:50:09 pm
content: Getting way too complicated. Simplest is to use the \x escape in a string.Make the third parameter const uint8_t * and:SSD2828_Send_DCS(0xFF, 3, (const uint8_t *)"\x01\x81\x98");Sure it costs a terminating null, but it's no worse than packing in a uint32_t. And no, don't use strlen() on it as I anticipate 0 would be a valid command byte.Or, if you have C++11, delete the second parameter and make the last parameter std::initializer_list<const uint8_t>, then you have:Code:#include <initializer_list>void SSD2828_Send_DCS(uint8_t cmd, std::initializer_list<uint8_t> data){SSD2828_SPI_WriteCmd(0xBC);SSD2828_SPI_WriteData((uint8_t)data.size());SSD2828_SPI_WriteData(0x00);SSD2828_SPI_WriteCmd(0xBF);SSD2828_SPI_WriteData(cmd);for ( uint8_t dataByte : data ){SSD2828_SPI_WriteCmd(0xBF);SSD2828_SPI_WriteData(dataByte);}}// Usage:SSD2828_Send_DCS(0xFF, {0x98, 0x81, 0x01}); // Yes, the order is reversed to the original code which was sending the bytes in reverse order... was that the intent?No pointers in sight and the compiler counts the command bytes for you. You might not like the code it generates though since it copies the initializers onto the stack, then constructs the std::initializer_list object.

author: andersm
author_rank: Super Contributor
timestamp: July 31, 2018, 11:20:24 pm
content: Not very pretty, and the Library Fundamentals TS v2 support, doesn't generate any actual template code:Quote

author: helius
author_rank: Super Contributor
timestamp: August 01, 2018, 12:50:35 am
content: Quote from: carl0s on July 31, 2018, 07:57:06 pmVariadic functions are useful when there is some computation required to determine the type of the next argument, e.g. scanf.QuoteIn C99 you can use compound literals, also known as "cast constructors". The function implementation doesn't need to change at all from the case where you declare an array and then call the function using the array.SSD2828_Send_DCS(0xFF, 3, (char []) {0x01, 0x81, 0x98});

author: carl0s
author_rank: Unknown
timestamp: August 01, 2018, 01:10:53 am
content: Lots of interesting and different methods there - thanks very much guys.Orin: yes I did intentionally reverse the order. The SPI interface description in the SSD2828 datasheet says to give register first, then data (16 bits), with least significant byte first. It doesn't tell you that this doesn't apply to the 0xBF FIFO.However I have since realised (was unsure, due to vagueness of datasheet, but found this:http://www.chn-lcd.com/news/lcdinfo/18.html) that for the 0xBF register, which is a FIFO dump for bytes that are sent on as MIPI bytes, this doesn't apply. Also it doesn't need the other 8 bits filling, (datasheet shows a 16 bit register like all the others, but says something about it being 8 bits if you're using an 8 bit interface). Also the register address 0xBF doesn't need to be supplied before subsequent bytes. It must just keep reading everything into (through) 0xBF until you've sent the number that was written to register 0xBC.For now, I have gone with a variation on what I started with (reversed order as mentioned above). I'm not 100% happy with it, because if you want to send 0x98, 0x81, 0x00, well you might accidentally think to write it as 0x98810, and that wouldn't work. It has to be 0x988100.It's basically about on a par with my understanding of C & C++ at this stage, which is helpfulCode:void SSD2828_Send_DCS(uint8_t cmd, uint8_t dataSize, uint32_t data){SSD2828_SPI_WriteCmd(0xBC);SSD2828_SPI_WriteData(dataSize + 1); // SSD2828 register needs the size of the whole packet - command and data, so +1SSD2828_SPI_WriteData(0x00);SSD2828_SPI_WriteCmd(0xBF);SSD2828_SPI_WriteData(cmd);while (dataSize > 0) {SSD2828_SPI_WriteData((uint8_t)(data >> 8 * (dataSize-1)));dataSize--;}}Screen still not doing anything, but I think my SSD2828 on a chipqwik qfn68 adapter might be done badly. It was a can't-wait test run in the oven before I'd even converted the oven to a DIY toaster or got a microscope or anything. I literally opened up the £20 toaster and properly toasted a chipquick qfn68 pcb + stencil adapter kit in it. One delaminated and bridged, the other seemed like it might be OK. I'm surprised it does anything at all (it's talking to me over SPI, that's something). I have parts on the way to do a proper board.Also the rat's nest of dupont cables probably aren't going to work for >250MHz differential signals anyway, maybe.

author: criznach
author_rank: Newbie
timestamp: August 01, 2018, 03:46:38 pm
content: Making things more complex to make the source code look nice?

author: andyturk
author_rank: Frequent Contributor
timestamp: August 01, 2018, 05:15:56 pm
content: I don't know anything about the SSD2828, but I have written drivers for other SSD displays (tiny OLEDs) in C++. Sending each display command as a separate function call works, but it does potentially chew up a lot of flash. Even if you can combine some of the SPI writes with varadic template tricks, you still end up with a lot of source code to deal with.In my own drivers, I've used C++ to implement an interpreter of sorts which accepts an array of "commands" to be sent. It's more compact than having in-line function calls:Code:void SSD1306::reset() {static const uint8_t sequence[] = {VIEW_IO_UNSELECT,VIEW_IO_RESET(1),VIEW_IO_SLEEP(2),VIEW_IO_COMMANDS,VIEW_IO_SELECT,0xae, // display off0xd5, 0x80, // clock divide ratio0xa8, 0x3f, // multiplex ratio0xd3, 0x00, // display offset0x40, // display start line0x8d, 0x14, // charge pump0x20, 0x00, // horizontal memory mode0xa1, // segment remap0xc8, // com output scan direction0xda, 0x12, // com pins hardware configuration (2nd byte = 0x02 for 32 rows)0x81, 0x8f, // contrast control0xd9, 0xf1, // pre-charge period0xdb, 0x40, // VCOMH deselect level0xa4, // display pixels from RAM0xa6, // display normal (1==on)0xaf, // display onVIEW_IO_UNSELECT,VIEW_IO_END};io.interpret(sequence);}The interpreter idea came originally fromug8lib/u8g2

author: orin
author_rank: Frequent Contributor
timestamp: August 01, 2018, 06:06:04 pm
content: Quote from: helius on August 01, 2018, 12:50:35 amThat would be nice, but unfortunately not valid C++. Some compilers might accept it with (const unsigned char []) - we should be using unsigned char or you'll likely get a narrowing conversion error for 0x81 and 0x98.From what I can tell, using initializer lists will create a temporary on the stack and copy the values into it, where as what you really want is the equivalent of:static const unsigned char[] cmdN = {0x01, 0x81, 0x98};Carl: what CPU are you using?

author: Retep
author_rank: Regular Contributor
timestamp: August 01, 2018, 06:45:03 pm
content: Quote from: orin on August 01, 2018, 06:06:04 pmOn a halfway decent optimizing compiler initializer lists are not created on the stack and copied. If you want see what the compiler makes of your code the Compiler Explorer site is very useful:https://godbolt.org/g/dCfPxm

author: carl0s
author_rank: Unknown
timestamp: August 01, 2018, 10:44:23 pm
content: I've been a bit preoccupied writing functions to use the '24 bit 3-wire' mode, so that I don't have to use a GPIO pin to as a data/command select.I just finished it, but the chip didn't respond.I knew it was because the SPI peripheral on the stm32 chip is working in 8 bit mode, as it only has options from 1 to 16 bits, and was pausing between bytes.Well, after failing at googling for "24 bit SPI stm32 hal" etc., and reading about the NSS Pulse, but not really appreciating what it meant, I tried to turn it off anyway, and it frickin' works. Woop. I had it sending via DMA too which was easier than I thought, although it made no difference to the inter-packet gap. All this with CubeMX and a tiny bit of amateur C :-)Here's what I've been stuck with. There should be a '0x28, 0x28' response (chip ID), but alas, as there isn't a steady stream of 24 bits in the frame, it doesn't happen..and here's what I finally have after disabling the NSS Pulse. I thought returning chip select back high between packets was the norm, but it's not. Phew!and here's my new SPI functions for the SSD2828's 24 bit 3 wire mode, where data/command select, and read/write is part of the first byte. DMA stuff removed for simplicity/portability.Code:void SSD2828_SPI_WriteReg24b3(uint8_t reg, uint16_t data){/* SSD2828's 24 bit 3 wire SPI mode. Set the STM32 peripheral for 8 bit mode, hardware NSS.* Data must be given as the full 16 bit value, MSBit and MSByte - no need to reverse the data byte order like there is when using 8 bit mode.*/uint8_t SPI24bitPkt1 = {0x70, 0x00, reg}; // this is the initial register select, with the first byte identifying command, and write.HAL_SPI_Transmit(&hspi5, SPI24bitPkt1, 3, 100); // next two bytes are register to read. for 24 bit format it's MS-byte first in the data./* That's the first 24 bit packet sent, which said that we are about to write data to the register 'reg'* Now we send a similar first byte, but bit 7 indicates data rather than command. bit 8 still says write.* first 6 bits are always 011100. Then we have the 16 bits of data. MS-byte first when in 24 bit mode*/uint8_t SPI24bitPkt2 = {0x72, (uint8_t) (data >> 8), (uint8_t) data};HAL_SPI_Transmit(&hspi5, SPI24bitPkt2, 3, 100);}void SSD2828_SPI_ReadReg24b3(uint8_t reg, uint8_t* data){/* SSD2828's 24 bit 3 wire SPI mode. Set the STM32 peripheral for 8 bit mode, hardware NSS.*/uint8_t SPI24bitPkt1 = {0x70, 0x00, reg}; // this is the initial register select, with the first byte identifying command, and write.// you'd think it should be 0x71 to indicate read, but apparently not. We're just selecting the registerHAL_SPI_Transmit(&hspi5, SPI24bitPkt1, 3, 100); // next two bytes are the register. for 24 bit format it's MS-byte first in the data.uint8_t SPI24bitPkt2 = {0x73, 0, 0}; // yes this time we set the 1 for Read. The SSD2828 will start sending after the first byte.//HAL_SPI_Transmit(&hspi5, SPI24bitPkt2, 3, 100);HAL_SPI_TransmitReceive(&hspi5, SPI24bitPkt2, data, 3, 100);}void SSD2828_Send_DCS24b3(uint8_t cmd, uint8_t dataSize, uint32_t data){/* Send MIPI DCS Packet with SSD2828's 24 bit 3 wire SPI mode.* Set the STM32 peripheral for 8 bit mode, hardware NSS. Data/Command wire not needed.* Supply up to 4 bytes (32 bit) of data in one go, as a single 0x12345678 parameter.*/SSD2828_SPI_WriteReg24b3(0xBC, (uint16_t) (dataSize +1) );if (data > 0xFFFF) SSD2828_SPI_WriteReg24b3(0xBF, (uint16_t) (data >> 16)); //send MSB 16 bits if there are any.SSD2828_SPI_WriteReg24b3(0xBF, (uint16_t) data); // send the other 16 bits.}

author: carl0s
author_rank: Unknown
timestamp: August 01, 2018, 11:41:11 pm
content: Well, it seems the transmit and receive buffers for HAL_SPI_TransmitReceive have to be the same size. That's why I was only getting one byte.Also strange, but without DMA, even with the baud rate dropped to 11MHz, whenever there is a response, the SPI comms seems to pause. but everything still works out OK.I'm not sure whether it's best to accept the pause, given the simpler code and the fact that it seems to work (no DMA stuff that CubeMX did for me that I'd struggle to understand), or do DMA and have the logic look exactly like the datasheet says it shoud.