I am writing firmware for a device in C. The Software allows the PC to communicate with this device over the serial interface (UART). The firmware contains multiple layers as following:
My problem is the error handling since in C there are no exceptions. Here is how I've implemented my firmware and I am trying to see if there is a more efficient and compact way to build it while still handling errors efficiently. I want to avoid checking at each layer the status of the call of the lower layer. The code below is very compact and in reality, I have a long sequence of send_uart_commands in the block layer.
// Communication layer
operation_status_t send_uart_command(command_id_t id, command_value_t value)
{
// Send data over UART
// Return success if the operation is successful; otherwise failure
}
// Block layer
operation_status_t enable_block1(void)
{
if (send_uart_command(BLOCK1_COMMAND_1, 10) != operation_success)
return operation_failure;
if (send_uart_command(BLOCK1_COMMAND_2, 20) != operation_success)
return operation_failure;
// A list of sequences
if (send_uart_command(BLOCK1_COMMAND_N, 15) != operation_success)
return operation_failure;
return operation_success;
}
operation_status_t enable_block2(void)
{
if (send_uart_command(BLOCK2_COMMAND_1, 1) != operation_success)
return operation_failure;
if (send_uart_command(BLOCK2_COMMAND_2, 8) != operation_success)
return operation_failure;
return operation_success;
}
// API layer
operation_status_t initialize(void)
{
if (enable_block1() != operation_success)
return operation_failure;
if (enable_block2() != operation_success)
return operation_failure;
// A list of calls to the functions in the block layer
return operation_success;
}
One of the many big problems with exception handling like in C++ is that exceptions can crash through all layers like a cannonball. So when you are writing some completely unrelated code, you suddenly get that cannonball in your face: "UART framing error!" When you haven't even touched the UART code...
Therefore "I want to avoid checking at each layer the status of the call of the lower layer" is the wrong primise. Rather you should do like this:
For example: "UART framing error" might useful to the code calling the UART driver, but useless to the higher layer application. "Possible incorrect baudrate settings" might be a more relevant error description which you should pass along. Though in some cases you want detailed errors available even at the higher layers.
One reason why you might want that, is that it's common and often good design to have a centralized error handler on the top layer, which can make decisions of state changes, print/log errors etc from one single place in the code. Instead of doing that from all over the place. You'll often find the top layer of a microcontroller application looking something like this:
void main (void)
{
/* init & setup code called */
for(;;)
{
kick_watchdog(); // the only place in the program where you do this
result = state_machine[state]();
if(result != OK)
{
state = error_handler(result);
}
}
}
As for your specific code, it looks just fine and mostly doesn't contradict anything of what I've written above. It is always good to return an error code upon error - less confusing than goto
, or even worse: massively nested statements and/or loops with error condition flags.