2023-08-13 09:13 AM - edited 2023-08-13 09:15 AM
I've been doing a lot of embedded coding lately. I'm not a very experienced developer and I have been trying to develop a set of design guidelines to help me up the learning curve. I'd appreciate hearing what experienced embedded developers use for guidelines.
The SOLID principles make a lot of sense to me but I've added a few more of my own. Any comments on these will also be appreciated. If they're dumb, I'll be happy to modify or drop them.
2023-08-13 12:50 PM
I have been working in C and C++ since 1996. So some years of experience I have, in professional and in a non professional context. I do not consider myself a specialist. If you are part of a team, follow your team to make sure you keep being part of the team. If your objective is to learn, buy a board, read the RMXXX datasheets and start experimenting with the board. If you feel ok with your design rules, follow them, if they are in the way of your learning process, find alternatives.
2023-08-13 04:34 PM - edited 2023-08-18 11:50 AM
2. DMA and IT is considerably more complicated from a code standpoint than blocking functions. Sometimes blocking is just fine.
3. C vs C++ is generally an individual choice. Use the one you're more comfortable with. They are both effective. C is generally preferred for low level code.
4 and 5. Was never a fan of blind guidelines on function lengths. Sometimes long functions are warranted.
6. Over-generalization is a common pitfall. The I2C driver for a device often needs to be different than the SPI driver. Separating the communication method from the driver often leads to more obfuscation.
7. Use HAL or register-level access. LL adds a level of obfuscation without providing the usefulness of generalization (as HAL provides).
General rules are fine. Keeping a consistent style (indentation, naming conventions, whitespace choices) helps quite a bit.
Edit: also consider adding "no dynamic memory allocation" to the design guideline. In my opinion, that's one of the most important things to ensure code quality.
2023-08-18 09:32 AM
7: Any particular reason why you're "trying to stick to the STM low level drivers instead of the HAL drivers" ?
What do you see as the advantage to that approach?
2023-08-19 10:32 AM - edited 2023-08-19 07:08 PM
@Andrew Neil - That's a good question, @TDK also mentioned sticking to HAL or register level access. How I got to using the LL drivers is kind of a long, painful story that I'll try to keep short-ish. I started off my embedded programming learning curve a few years ago using HAL and quickly got very frustrated trying to figure out all the things HAL is doing "for me" under the hood and all the files I had to look at to figure out what it was doing. For example, if you're using HAL my recollection is if you want to find out something simple like what pins are being used for RX and TX in a UART, you have to look at HAL_init(), msp_init, msp_UART_init and probably several other places. It gets even more complicated when I started using DMA. I asked lots of questions on this forum and many of the members who were super helpful getting me up the learning curve were pretty negative about HAL. I eventually figured out how to use HAL reasonably well but using HAL forced me to structure my program according to the way HAL is structured rather than the way I wanted to structure my program which was always an irritation. A few months ago, I got some time to spend investigating programming at the register level and using LL drivers. Here's a couple of reasons why I chose the LL driver approach.
Reason 1:
LL_USART_SetTXFIFOThreshold(USART1, LL_USART_FIFOTHRESHOLD_1_8);
is much more readable to me than the equivalent register level command
ATOMIC_MODIFY_REG(USARTx->CR3, USART_CR3_TXFTCFG, Threshold << USART_CR3_TXFTCFG_Pos);
Reason 2:
I've found that using CubeMX (either stand alone or within CubeIDE) is a very effective way to get the peripheral driver initialization code I need. I was very pleased to discover that I can tell CubeMX to use the LL drivers instead of HAL drivers and I can tell pretty easily what's going on in the code it generates with LL drivers.
Now I can structure my programs in a way that makes sense to me. For example, my most recent program has a separate file for each of the 8 uarts (or usarts) with all the configuration details (baud rate, RX and TX pins, DMA stream numbers, etc.) and all the IRQ and call back functions specific to that uart. And a single file with all the functions I've written that are common to all UARTS like startDMAXfer, startITXfer and so on. So, if I want to change the functionality of the Character Match callback function for UART4, I only have to look in the UART4.cpp file. I've been off and mostly running with LL drivers ever since I figure out how to do this. OK, maybe not running but at least not limping too badly.