Five Considerations for Microcontroller Embedded Programming

In the process of microcontroller programming, if a designer can master multiple programming languages simultaneously, then that designer is undoubtedly a very talented individual. However, mastering assembly, C, and C++ at the same time is quite challenging, and many beginners struggle significantly while learning just one of these languages. This article specifically compiles insights from engineers with years of experience in the embedded programming field, summarizing them into a set of considerations that can guide embedded programming practices. Interested readers are encouraged to take a look.

In microcontroller embedded programming, the two most challenging aspects are interrupts and memory management (MM). The reason some people find them not difficult is that in most cases, chip manufacturers have already provided the necessary configurations. However, if the designer works for the chip manufacturer, they must be able to write the configuration files themselves.

These two aspects are considered difficult because they require writing in assembly or C-like languages, which are lower-level. Interrupts can be categorized into external and internal interrupts. External interrupts have two implementation modes: hardware interrupt mode and software interrupt mode, which are relatively simple and belong to the application layer. In contrast, internal interrupts are much more complex, primarily arising from situations such as restarts, bus errors, overflows, and checksum errors. Many software developers do not write corresponding interrupt service routines (ISRs) because they are too difficult and generally not needed. However, when they do occur, they can lead to fatal errors. Therefore, from the perspective of overall system robustness, it is essential to have corresponding ISRs, as recommended by experts from Freescale. Below, we will discuss the considerations that should be taken into account in embedded programming.

Delays
Embedded programming often involves hardware operations, such as ADCs, turning on or off a current source, all of which require time. Therefore, immediately reading the value of a register after issuing these commands will not yield the desired results, and the reasons may not be apparent. Sometimes, the required delay can be quite long, reaching the millisecond level, while in general, microsecond-level delays are sufficient, depending on the clock frequency of each chip, not just the MCU’s bus clock frequency.

Variables
Generally, if the scope and lifetime of a variable are very clear, it should be defined as a relative variable, such as const or static. This approach minimizes errors. It is not advisable to define all variables as global variables, as this complicates management. If an error occurs, the consequences can be severe. The same applies to functions; global variables and common functions must be declared to avoid errors during calls. Some compilers do not report errors for undeclared functions, but they may issue warnings about implicit type conversions during calls. This point should be approached with particular caution.

Macro Definitions
During the programming process, specific numbers should be defined using macros whenever possible. This practice is beneficial for clarity and future maintenance; otherwise, after some time, one may forget what a particular number represents. Macro definitions do not impose any burden on the program, as they are replaced during compilation. Therefore, they can be widely used. It is worth mentioning that macro definitions are not limited to constants; they can also define functions. Since they are directly replaced, they avoid stack operations, improving program execution efficiency. However, this also increases code size, so they are generally used for simpler functions. One drawback is that during the replacement process, parameter types are not checked, which increases security risks. A solution to this issue is to use inline functions, which inherit the advantages of macro definitions while compensating for their drawbacks. This is the best choice, but it falls within the C++ domain and has a certain level of difficulty. Interested readers can refer to relevant materials for more information.

Floating Point Operations
Most low-end microcontrollers do not support floating-point operations, and therefore, they are rarely used in practice. To reduce costs, floating-point operation modules are generally omitted. This raises a question: what if floating-point operations are needed? Observant readers may notice that even microcontrollers without floating-point operations can still use float or double data types during simulation and debugging, yielding accurate results. This is because the compiler automatically calls library functions to implement this, typically through iterative methods, which results in very slow execution. This method is not recommended. Instead, a ‘fixed-point’ approach is usually adopted to solve this problem. For example, a 32-bit data value can be assumed to have its lowest 8 bits as the decimal part, and calculations can be performed by shifting, similar to integer operations. This method is complex but can achieve high precision. Another method is to directly scale the value by 10 raised to the power of N for integer calculations to obtain an approximate value. Therefore, to avoid unnecessary complications, floating-point operations should always be avoided whenever possible.

Watchdog
Taking a triple watchdog as an example, watchdog 1 checks the clock frequency, watchdog 2 monitors a small segment of code that must be fed within a relatively short time, generally requiring feeding every 250 to 650 microseconds, while watchdog 3 monitors a larger segment of code that must be fed within a longer time, typically within 100 milliseconds. All three conditions must be met simultaneously, which requires a clear understanding of the code execution process; otherwise, it may lead to feeding errors and restarts.

It is important to emphasize that in microcontroller embedded programming, the quality of the program is often determined by the details. The level of detail and flexibility in a program correlates with the accumulation of knowledge and practical experience over time. Although programming can be a very tedious and even monotonous process, the joy of success can make all the effort worthwhile.

Leave a Comment