Designing and launching IoT devices
Designing embedded systems is a complicated process, and the decisions we make at the very beginning have a big impact on the next stages. Defining key requirements is an important point from which to start work. It is on their basis that the concepts of system operation are developed and captures possible problems.
In the next step, designers choose the most important components of the system, taking into account technical parameters, price and availability of components. A large number of aspects that must be analyzed increases the risk of making a mistake, which may lead to the failure of the entire project.This stage should be carried out with particular care and attention to every detail.
In embedded systems, software and hardware are closely related. Therefore, the boundary between these two elements is very thin. Sometimes we are looking for a software error, debugging the firmware, while the fault occurs on the hardware side or vice versa. It is difficult to define the only right way to proceed when you detect malfunction. When analyzing the problem, it is worth realizing that in addition to the limited hardware resources of microcontrollers (a small amount of memory and low clock frequency), embedded systems have an additional parameter - time. We often use the concept of a real-time system in the context of embedded systems. If the response time is insufficient, the system is not considered to be functional, even if it is functional in terms of functionality. An example of malfunctioning may be too late operation of airbags during car collisions.
How to prepare for debugging?
If you participate in the project from the very beginning and the first prototype is just created, you are in a good position. First, analyze the scheme very carefully and try to understand how the device should work. If you do not understand something, do not go to the next blocks, because you will have to understand them later. In case you still have doubts, ask the designer to explain the idea to be followed by the block. After getting to know how the whole system works, it's good to think about what problems we can face and what would make debugging easier. In most cases, it is a good idea to put diagnostic LEDs. You can also omit the output of key signals in the form of a PAD test on a PCB. This is very important in the age of miniaturization, where the components are very small, and multilayer printed circuits.
For a typical application, it is worth having direct access to:
- all power lines - allows you to check the voltage level and power quality when working under load
- serial bus lines - diagnostics of communication between external peripherals
- IRQ interrupt firing lines
- other lines important from the point of view of the device functioning, eg analog signals coming into the ADC converter
- an excess signal used to trigger an oscilloscope or analysis using a logic analyzer, eg to measure the execution time of an interrupt service function.
Planning software issues and ways to verify them is equally important.It is worth answering a few questions at this stage:
- What communication channel will the diagnostic information be transmitted?It may turn out that semihosting will be inconvenient or insufficient and you should think about using the UART or SPI bus.
- What will you change the settings, control the control and trigger test functions on the device?
- What will be responsible for controlling stack and RAM occupancy?
- How to measure the use of CPU resources during normal application work?
When developing the architecture of the entire system, blocks operating close to the equipment should be clearly distinguished - for example, peripheral drivers and modules that implement specific functions. Dividing the application into modules increases the clarity of the code and simplifies debugging. Whenever possible, do not analyze errors in the algorithm code directly on the target device. It is better to create a computer-independent program on your computer and cover them with the appropriate tests. Easier access to processed data increases the debugging and efficiency of work.Here, it should be noted that the mentioned code fragments should be written in a way that allows transfer between platforms.This brings further benefits.If we decide to use a given module for another project running on a completely different hardware, it is very likely that we will run it without any major problems.
Already at the design stage of the device it is worth equipping with evaluation boards and starting software preparation. Parallel design work of teams responsible for hardware and software allow to speed up the process of creating a working prototype. It also gives the opportunity to test uncertain ideaseven before ordering printed circuits.This increases the chances of producing a fully working prototype as early as the first iteration.
Organization of work
The team of programmers should organize their work in such a way as to first of all implement all drivers and components working with the equipment. Using blocks that support the lowest layer, tests are performed to check the interaction of components. If the device will eventually be powered by a battery or accumulator, it is worth preparing a second program variant that will introduce the target into the lowest possible energy consumption. This gives the opportunity to verify the prototype as soon as it is assembled and leads to faster finding any problems that designers report.Only after preparing the test firmware, you should go on implementing the functional modules.
Form of the program
When it comes to the form of the program itself, it is worth remembering about the more frequent use of assertions in the code. By using the ASSERT macro, it is possible to catch unexpected circumstances while the application is running. It boils down to the fact that programmers can verify whether the assumptions in their code are true. Using assertions definitely helps in finding irregularities or assumptions errors. Unfortunately, many engineers do not put them in their programs.
It is also an interesting idea to add an instruction to turn on a red LED and trigger a software breakpoint in a function that implements assertion support. This allows you to detect anomalies even if the targetis not currently connected to the debugger.Not only that, if we connect the debugger board (without disconnecting the power supply), we are able to determine what caused the stop.
Starting the prototype
The first time you start the prototype of the device, it is worth connecting it to a laboratory power supply with the ability to monitor and reduce the power consumption. If the motherboard does not charge more electricity than we expect, then we check the voltage levels of all power sections with the multimeter. Only after making sure that the power supply is correct, connect the debugger and start further work. The next stage is programming the target device, the previously prepared testing program and a detailed analysis of the periphery and cooperating components. Unfortunately, you may encounter a lot of problems during startup. Modern microcontrollers have extensive configuration options with a large number of control and status registers for one block, and their setting is not always obvious. Viewing only one document is not usually enough. When the equipment is not working properly, we are forced to repeatedly analyze several catalog notices simultaneously.
Before you try to verify other functionality, make sure all drivers are working properly and you can rely on them. To avoid device operation in an unpredictable way, it is worth checking:
- writing and reading from external memory, especially with a parallel interface
- frequency of SPI and I²C bus operation within the permitted range of external components
- operation of microcontroller modules, e.g. ADC conversion time
It is good practice to document the results of the tests carried out.For example, by saving a screenshot showing the recorded waveforms of the SPI bus, with the correct frequency and phase relation to other signals.
The characteristic feature of embedded systems is that errors can be located not only in the software but also in the hardware. While debugging, we look for vulnerabilities in the code, but also hardware conflicts, disruptions or unmatched delays. Often, hardware problems are attempted to be removed using software bypasses. This is more economically effective, but it does not change the fact that the cause of the problem must be found anyway.
The basis for efficient debugging
The basis for efficient debugging is the ability to save events or values for further analysis. Often this is done by separating a piece of RAM or Flash memory and successively adding subsequent events. After finishing the cycle that interests us, you can stop the debugger or download data via any interface. If the buffer is in RAM, we gain the ability to record events at a very high speed. The downside of tracking buffers is that they take up a large portion of memory , and recorded data is harder to impose on external hardware events.
Instead of collecting data in RAM, you can send them using several pins or quick serial interfaces, eg SPI and intercept using a logic analyzer. This reduces the memory demand, and the redundant code does not take up much space and is fast. With this approach, the only restriction is the availability of pins on the side of the target device and the memory capacity of the analyzer. External synchronization of events ceases to be a problem, and the use of a relatively new logic analyzer gives you a lot of triggering possibilities. The more pins you have, the easier it is to transfer data from the device. Usually there are not many spare pins or there is no easy access to them, which forces "being creative".Good design practice is to output the remaining unused signals, especially when designing the first prototype.This allows for more efficient debugging and expanding the possibilities of evaluation of subsequent prototypes.
Frequent application failures
When you observe frequent application failures for unknown reasons, you should plan for a simpler case, instead of continuing to debug with complete functionality. This will reduce the number of factors that can cause error, so debugging will require less effort. In the next steps, you can continue to "dismantle" the application so that the error is still repeatable. The simplified version of the application obtained in this way has fewer dependencies, which makes it easier to find the source of the problem. Always try to look at the problem widely. Do not focus on just one aspect.The fault can occur in the application software, driver or hardware.If the modification of the operating conditions (eg temperature, bus speed) causes a change in the frequency of the problem, the cause is probably related to the equipment.
As you can see, running prototypes of devices is not a simple task.Their final success is determined by many factors.It is important to scrupulously approach all activities and do not stop at the pursuit of perfection, which - as we all know - probably does not exist.