Author Topic: Embedded Design and Programing - Do's and Don'ts and both HW and SW  (Read 800 times)

Louis L

  • Administrator
  • Hero Member
  • *****
  • Posts: 567
Here's a list of good and bad practices. The title says "embedded" because there are certain things about embedded devices that are not necessarily present in other kinds of designs. That said, many of the tips below apply any and everywhere.

Please feel free to reply with additional tips and I will update the OP.

  • Closed loops. Use them.
    Closed loops refers to the practice of monitoring the affects of a command to verify that the correct actions have taken place. For example, if you are turning a wheel by some amount, you want a sensor that can tell you whether that wheel has indeed turned in response to your command. Obviously if you're sitting next to your device, you can see it turn but what if your wheel is on a Mars rover? From a programming point of view, this means you can't just send the command to turn the wheel, you must wait to verify that it did move. From a design point of view, there needs to be a sensor that software can use to monitor the wheel.
  • Read the hardware state; don't assume.
    If you have close loops, you probably have sensors that can tell you the state of all your hardware at any given time. This is good. It means that when software wants to do something with the hardware, it should always check the hardware first. Going back to our wheel, if software is again going to turn the wheel, it needs to know where it currently is. Instead of relying on its own saved internal state of where the wheel was the last time it was moved, software should use the sensors to see where it is now. Why do this? Well what if a gust of wind came by and moved the wheel? Remember that embedded devices are in the real world and can fall victim to outside forces.

    So the rule for software is to read the hardware state and then act on it. Avoid assuming anything on the state of the hardware.
  • Timeout
    Just because you told the wheel to turn doesn't mean it can. Maybe it's stuck. If software keeps trying to turn it when it can't the motor may burn out. So all operations that involve hardware need to time-out. After a reasonable amount of time, if the operation is not done (as relayed closed loop sensors), software should stop trying to force the action and report back an error. Timeouts need to be long enough to let the action take place in under differing circumstances (for example if the battery is weak, the wheel may turn more slowly); but not so long that the motor burns out.
  • Initialize variables
    All software should do this but it's worth mentioning again. Compilers often will initialize your variables for you but it's not a good idea to rely on this features. As soon as you declare a var, give it a default starting value. It'll also help you think about the flow of this variable in use.
  • Constants
    Modern programming languages all have a means of defining values that don't change - constants. In nature, constants can be values like pi or g (the acceleration of gravity). In a program anything with a fixed value is a constant. As a general rule, if your software has a numerical value (ex: "4" wheels on the drive train or "360" degrees in a circle) in the code, you should replace it with a constant (ex: NUM_WHEELS = 4 or DEGREES_IN_CIRCLE = 360). Place all the constants in the same file, If you ever need to change a constant, you'll know where to find it and once changed, it will apply everywhere it's  used.
  • Volatile
    Modern day compilers can be extremely efficient. So much so that they can cause havoc on code. If you read from a variable after a prior access, the compiler can optimize the flow so that the value you want is still in a cache or maybe even in a CPU register. This is great for speed. But what happens if the location you are trying to read is actually a hardware register? And what if that register is dependent on outside events and its value can change at any moment?

    Languages like "C" have the keyword "volatile". This denotes a location whose value can change unexpectedly and instructs the compile to generate code that will read this location each time it is needed - cache is not used. Our robot programming is accomplished through a layer of code provided by WPI so we seldom touch the hardware directly. But if we ever do, there will be a lot of volatile references.
  • User interface design
    UI design is a whole science in itself. If you're not convinced of this, go onto the web and look at the many web sites out there. Some are models of efficiency. Others are a horror show. UI design is about making something that makes sense to the user; not necessarily to the programmer. Or put another way, don't let the programmers define the UI; let the users do it.

    Please keep the UI neat and organized! Don't throw buttons all over the place. Be forward looking - a UI design that makes sense today needs to still make sense a year from now when you try to explain it to a novice.

    One of the more difficult things we have to deal with is the balance between automation and direct manual control. We want automation because that is after all, what a robot is good at. But we also want direct control when something goes bad and we have to take over. Serving both of these needs increases the number of UI controls and can clutter an interface so be careful and thoughtful.
  • Idiot Proofing.
    It doesn't matter what the User's Manual says is allowed or not allowed. What matters is what the user does. When you present a UI to a user, you can bet that someone will do something that's not allowed within seconds. The software has to be ensure that illegal user inputs do not cause problems. Software also has to make sure combinations of commands and hardware operations do not interfere with one another.

    Other times, there can be scenarios where you do want more than one action to be in use. Software be careful to allow or disallow as needed.
  • Serializing in a multitasking world
    Be careful with multiple commands from a user. This can happen unintentionally when more than one button is depressed by a user. How will you code handle this? Will it try to run both commands at the same time? Will it run one and queue the other? Or will run one and ignore the other? Whatever is the correct response for your situation, be sure you justify the final implementation.
  • Edge and Level
    When we think of electrical signals (zeroes and ones) we often think of them as signal levels where a "low" or "0" is a low voltage (like 0.1V) and a "high" or "1" is a high voltage (like 4.8V). Those are "Levels". But there's another property of signaling - "edges". An Edge is a transition. It's the act of transitioning from one state to another; such as going from Low to High thus there are Falling Edges and Rising Edges. Edges are normally important in the domain of electrical circuits where a signal edge is used to latch states into a register. For example an interrupt (see next topic) from an external source may be registered when its signal changes from High to Low. The falling edge is what's captured.

    But Edges are also important in software. One such area is when a value is samples repeatedly. For example if a button is used as an input device, it may be samples repeatedly (polling - see below) by software. If the intent is to have the button perform a certain task, then all we care about is the transition from button-off to button-on. We don't care about the button state's Level but rather its Edge. See the Forum topic "Controller Button Presses..." for more details.
  • Interrupts vs Polling
    A passenger in a car asking "Are we there yet?" repeatedly is polling. An interrupt is just what it sounds like - someone stopping what you're doing so you can go do something else, before coming back and resuming your previous task. Interrupts are the functional pacemakers of modern computers. They allow processors to share their time across multiple jobs; they allow keyboard key pressed to be registered to an application; they allow a power failure to initiate a clean shutdown of your computer.

    Polling is old school in many ways but it has its place. It's simple to implement. It's very effective when used in a limited environment. Our RoboRio constantly polls for activity. It's the reason your Java/C++ Execute function or LabVIEW teleop look has to be fast - it must finish its job before the next poll takes place.

    So what do you do if you have a task that takes more than the allotted time? The answer is you get someone else to do the work; someone who is not time restricted. The command based processing of WPILib is such an entity. In LabVIEW, you do this work in the Periodic Tasks VI.
« Last Edit: January 30, 2018, 03:18:38 PM by Louis L »