Skip to content

Why Arduino code is not deploy in commercial/industry products

arduino.png

A friend of mine is asking this question and my answers are:

1. Reliability of Source Code

Arduino is originally target for electronics beginner hobbyists to quickly building electronics circuit or prototype. Thus the requirement is less stringent as compare to embedded development. If a device is not working, a simple device reset in Arduino is fine. However, this is not the case on embedded products.
In embedded development, getting the device to work is just part of the work. The most important part of the work would be ensuring system running well in all situations. This inherently means the firmware require to anticipate errors of input parameter, sensor values and there has to be some recovery to ensure system is reliable.

2. Efficiency of System

Arduino system is written with 2 main function, setup() and loop(). Setup() stage is called when MCU initialize during power up while loop() is call in a superloop case. Is some cases, these 2 function is sufficient to perform system functionality.
In embedded system, efficiency is important key aspect to development. An highly efficient system will require less powerful MCU, thus saving in resources and cost. To achieve that, a different light weight operating system(e.g. round-robin task scheduler, RTOS) will be use in system. A superloop will not be suitable in this case as superloop tends to have slower system responds as compare to operating system

3. System/Code Optimization

In embedded system, every bytes (on flash or RAM) count, thus system use to write in optimism way to produce highly efficient code with smallest code size. This may not be the case in Arduino code. When such Arduino code is being use in embedded systems, more code/RAM would be require. However, with advance of technologies, this impact will not be as great as old days.

4. Arduino Pin-out

Arduino has a standard pin-out assignment to ease development. While in embedded systems, each pin-out is use differently depending on hardware design. If a Arduino code hard coded to use these standard pin-out, then there is a possibilities libraries is not usable on embedded system due to this limitation.

 

 

 

Advertisements

Enable Parallel Compilation

Nowadays, there is more and more MCU vendors that adopt Eclipse platform as the MCU IDE. The drawback of eclipse is it always progress slower, which I suspect is due to the the implementation is done in Java. Nevertheless, sometimes waiting for compilation is becoming a norm.

If we have a multicore processor, which is very common with i5 processor or i7 processor. Then there is an option where we can speed up the compilation process : parallel job compilation. Using this features, each of the source code can be assign to different processor core for compilation, enabling a concurrent compilation.

To enable this settings, just right clicked on the project, then select ‘Properties’,  project settings window will display as below. Then follow image below to enable an parrallel job compilation.

parralleljob

It is possible to reduce the compilation process by half of the time or even more.

Since this is Eclipse based feature, if you are building desktop software using Eclipse, same settings is available.

While for terminal (command line) compilation using gcc, same option can be enable using ‘-j4’ (for 4 parallel job) or ‘-j8’ (for 8 parallel job).

C Language ‘static’ Keyword In Function Scope

In C language, when we want to have a global variable that exist inside a function(function scope), we can use ‘static’ keyword to achieve this. Example as below:

void function1(void){    static uint8_t global_variable=0;    uint8_t variable=0;

    global_variable++;
    variable++;
    printf("global_variable1:%d, variable:%d\n", global_variable, variable\n");}
void main(void)
{
    function1(); //output: global_variable:1, variable:1
    function1(); //output: global_variable:2, variable:1
}

Example Code 1

In superloop environment(all task is being called from a always-true while loop), and function1() is not called from ISR, above would works every well.

But the above code will not work(in some cases) when it is use into RTOS(real time operating system) environment. Depending on the system design, few scenario may happen and it require a different changes.

Case 1: Only Require One ‘global_variable’ In System

In this case, despite system having multiple task, we only require one global_variable for all task.

With RTOS enviornment, ‘global_variable’ become a share resource, thus it has to be ensure its atomic operation. Without the atomic operation gurantee, while task1 is accessing and changing ‘global_variable’, task1 may be preempt by task2, which may also make change ‘global_variable’. Mutex_lock/unlock can be use for avoid such scenario as below:

void function1(void){    
    static uint8_t global_variable=0;  
    uint8_ p_global_variable;  
    uint8_t variable=0;

    mutex_lock(&gv);
    global_variable++;
    p_global_variable = global_variable;
    mutex_unlock(&gv);
    variable++;
    printf("global_variable1:%d, variable:%d\n", p_global_variable, variable\n");
}

Example Code 2

One example of such is when we want to know how many times function1 is being called by all the tasks in the system.

In above code, we have modified such that it can be use by multiple task, this is what generally call thread-safe function

Case 2: Each Task Require One ‘global_variable’

This case assume each task have their own copy of ‘global_variable’. E.g: 2 task is in the system and each one have a copy of ‘global_variable’.

In this scenario, we need to ‘duplicate’ the variable for eacth task. Since ‘static’ keyword only allow us to have a copy of ‘global_variable’, we can no longer use this keyword. Instead we have to create task dependant global variable and passing in such global variable

uint8_t Task1_global_variable=0;
uint8_t Task2_global_variable=0;

void function1(uint8_t *global_variable){    
    uint8_t variable=0;

    //mutex_lock(&gv);
    *global_variable++;
    //mutex_unlock(&gv);
    variable++;
    printf("global_variable1:%d, variable:%d\n", *global_variable, variable\n");
}
void Task1Loop(void)
{
    while(1){
        function1(&Task1_global_variable);
    }
}
void Task2Loop(void)
{
    while(1){
        function1(&Task2_global_variable);
    }
}

Example Code 3

When Task1_global_variable is use elsewhere of the code, we would need to ensure atomic operation by using mutex.

Conclusion

From above example, we can see how ‘static’ code would change when applying in RTOS environment. In general, RTOS code would be write in different form as compare to superloop environment. One have to employ the linux multi-thread programming methology in RTOS, particularly on resource sharing, concurrency issues.

TI Sensortag cc2650: BLE Device Monitor OAD (Over-The-Air-Download) Fail : Solving Method

To perform OAD on TI sensortag, TI software ‘BLE Device Monitor’ can be use. However, the OAD is not stable and many time the process will fail during data transfer.

With trial and error, a few changes on BLE Device Monitor settings allow me to create a stable OAD update. Below are the changes require to enable a stable OAD update.

So far has been testing with TI software stack version: 2.02.01.18 and working well.

before_resize

The default settings in Program tab: Blk/conn(4) and 20ms

 

after

Change to above settings to create stable OAD

Coding Standard – 2.0

This is subsequent article from my previous one here.

A Switch Case Must Have A Default Case

Make it as a habit to include a default case whenever you create a switch case. This will use as a checking point in expecting the unexpected and force us to think of a recovery method. Example, do not do this:

switch(cond){
    case 0:
        case_0_action();
        break;
    case 1:
        case_1_action();
        break;
}

Do this

switch(cond){
    case 0:
        case_0_action();
        break;
    case 1:
        case_1_action();
        break;
    default:
        handle_the_unexpected();
        break;
}

While(1) Always Wrote With A Break

When we are waiting for hardware to complete UART transmission or waiting for ADC result, we usually would write in such a way:

while(1){
    if(SFR_Flag == 1)
        break;
}

Instead of writing as below, I usually avoid creating infinite loop and create an escape case as example below:

unsigned char ReadADC(unsigned char *result)
{
    unsigned char count=0;
    
    while(1){
        if(SFR_Flag == 1)
            break;
        count++;
        if(count >= MAX_WAIT)
            return 1;
    }
    //perform adc read
    return 0;
}

Type Checking: Getting Compiler To Do The Validation

When calling function, we need to passing parameter in/out. We can use a common type such at unsigned char/unsigned int to represent our parameter. But there is a better way in doing this. A typical method of passing parameter as below:

#define MAX_ACTION_KEY (2)
unsigned char Function1(unsigned char param1)
{
    if(param1 >= MAX_ACTION_KEY)
        return 1;
    return 0;
}

We can do the following instead:

typedef enum{
    RET_OK=0,
    RET_INVALID_PARAM_IN,
    RET_ERROR,
    MAX_RET_ERROR
}EnumReturnCode;

typedef enum{
    ACTION_KEY_LEFT=0,
    ACTION_KEY_RIGHT,
    MAX_ACTION_KEY
}EnumActionKey;

EnumReturnCode Function1(EnumActionKey param1)
{
    if(param1 >= MAX_ACTION_KEY)
        return RET_INVALID_PARAM_IN;
    return RET_OK;
}

The advantages of using this method:

  • compiler will perform stict type checking and report warning/error when invalid type is use. We can utilise this features to ensure we always pass the valid parameter into Function1
  • Is more elaborate to whoever reading the source code

Why We Need Hardware Abstraction Layer (HAL)?

In embedded system development, one of the tedious work is to control microcontroller(MCU) hardware through special function register(SFR). Microcontroller hardware such as timer/interrupt/GPIO etc is control through SFR. Applications software that implement business logic will then ride on top of the hardware. From applications perspective, there are 2 way of accesssing the hardware:

  • directly access SFR from application or
  • through HAL(Harwdare Abstraction Layer) API.

HAL-API is a separation layer between application and hardware, any MCU hardware control has to be done by calling this HAL-API layer.

Among the advantages of using HAL-API as lists below:

  • Enforce a design that have clear separation between HAL and application
  • Ease code maintainence. Hardware modification will only inolve code change in HAL. And testing is only require to be perform on HAL layer, without application
  • Ease future migration to a new microcontroller(by re-implementing hardware driver, with application layer unchange)
  • Ease future changes if expansion is require to support external hardware
  • Enable engineers to think and work in modular design

HAL

Coding Standard – 1.0

Throughout my career life as embedded software/firmware engineer, I always study and ‘blend’ my changes by following existing style. Along the way I pick up way of coding that has become part of a norm. Maybe it has become too common that I think everyone would practise the same, and apparently I am wrong on this. I have come across come code that is seems should not be written by any engineer. This encourage me to write on this article, listing down coding method. Hopefully someone will find it useful and avoiding the same pitfall in future.

Never Use Magic Number

All the number should be declare by defining a symbol, and use the symbol in the source code. It is prohibited to use number in the source code directly.

Example: Replace the following

for(i=0;i<5;i++)

with

#define NUM_ITEMS    (5)
for(i=0;i<NUM_ITEMS;i++)

The rasionale behind this is for future code maintenance. E.g. increasing the items from 5 to 6 just require a line change. While for the first code, engineer have to search all the source code and change each of it from 5 to 6.

One Define At Single Place

For one project, when defining a symbol, always ensure there is only a single define located at a single file. Never, never define the same symbol in multiple files.

Example: File A

#define NUM_ITEMS    (5)

File B, same define again

#define NUM_ITEMS    (5)

Instead, create a project scope header file, e.g. global_define.h, and put the NUM_ITEMS define in it.

File A, File B, just include the header file

#include "global_define.h"

Do Not Create Multiple Define That Represent Same Meaning

Defining the same item with different name is a bad practise. Example, defining a size of buffer:

#define MAX_BUFFER_SIZE       (10)
#define MAX_BUFF_SIZE         (10)
#define MAX_CAR               (10) //buffer is use to represent car

Abusing define in such a way will create un-maintainable source code. It would be a nightmare to make changes of these code, like increasing the buffer size from 10 to 11.

Lastly…

The key points is always writing maintainable code. Always ask yourself what if in future things need expands, is my code good enough to cater that?