During the process of microcontroller programming, if a designer can master multiple programming languages simultaneously, then this designer is certainly a very talented individual. However, it is quite challenging to be proficient in Assembly, C, and C++ at the same time. Many beginners encounter obstacles while learning even one of these languages, leading to frustration. This article specifically compiles insights from engineers with many years of experience in the embedded programming field into a list of considerations that can guide embedded programming experience. Interested friends, come take a look. In microcontroller embedded programming, the two most difficult parts are interrupts and memory management (MM). Some people find these not too difficult because, in most cases, chip manufacturers have already prepared the configurations. However, if the designer works for the chip manufacturer, they must be capable of writing configuration files themselves. These two aspects are challenging because they require writing in Assembly or a C-like language, which is considered lower level. Interrupts can be categorized into external interrupts 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, mainly triggered by events such as restarts, bus errors, overflows, and checksum errors. Many software developers typically 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 catastrophic errors. Therefore, considering the robustness of the entire system, it is essential to have corresponding ISRs, as recommended by experts from Freescale. Below are some important issues to pay attention to in embedded programming. Delays in embedded programming often involve hardware operations, such as ADCs, turning on or off a current source, all of which require time. Therefore, when issuing these commands, immediately reading the register values 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; generally, microsecond-level is sufficient, depending on the clock frequency of each chip, not just the MCU’s bus clock frequency. Variables should be defined relative to their scope and lifecycle when their purpose is clear, such as using const or static. This helps prevent errors. It is not advisable to define all variables as global variables, as managing them can be troublesome, and errors can be quite destructive. Functions should also be declared as global variables and common functions to minimize errors during calls. Some compilers do not report errors for undeclared functions, but will issue warnings about implicit type conversions during calls. Care should be taken in this regard. Macro definitions should be used for specific numbers during programming. This practice makes the code more intuitive and easier to maintain later, as one can easily forget what a number represents over time. Macro definitions do not burden the program since they are replaced during compilation, so they can be used widely. It is worth noting that macro definitions are not limited to constants; they can define functions as well. Since they are directly replaced, they avoid stack operations and improve execution efficiency, although they can increase code size. A drawback is that the replacement process does not check parameter types, increasing security risks. To address this, one can use inline functions, which inherit the benefits of macro definitions while compensating for their shortcomings. However, this belongs to the C++ domain and has some complexity, so it will not be elaborated on here. Interested readers can refer to related materials. Floating-point operations are generally not supported by most low-end microcontrollers, so they are rarely used in practice. To reduce costs, floating-point modules are often omitted. This raises the question of what to do if floating-point operations are needed. Careful readers may notice that even microcontrollers without floating-point support can still use float or double data types during simulation and debugging, and the results are accurate. This is because the compiler automatically calls library functions to implement these operations, typically using iterative methods, which results in very slow execution. This method is not recommended; instead, the “fixed-point” method is usually employed, where a 32-bit data value can assume its lowest 8 bits as the fractional part and perform shift calculations similar to integer operations. This method is complex but can be very precise. Another method is to directly multiply by 10 to the power of N for integer calculations to obtain approximate values. Therefore, to avoid unnecessary complications, floating-point operations should always be avoided when possible. Watchdogs are exemplified by a triple watchdog system. Watchdog 1 checks the clock frequency, watchdog 2 monitors a small piece of code that must be fed within a short time frame, typically between 250us to 650us, while watchdog 3 monitors a larger piece of code that must be fed within a longer time frame, generally within 100ms. All three conditions must be satisfied simultaneously, requiring a clear understanding of the code execution process; otherwise, feeding errors can lead to restarts. It is important to emphasize that the quality of a program in microcontroller embedded programming is often determined by details. The thoroughness and flexibility of a program correlate with the accumulation of knowledge and practical experience. Although programming can be a very tedious and even boring process, the joy of success can make this effort worthwhile.
Scan the QR code below to follow us!
On the electronic road, let’s walk together!