Introduction
Most (real :-)) embedded software is written in C. Although C is a general-purpose procedural programming language, it is perfectly possible and useful to write object-oriented code in it. Object-orientation – well, GOOD object-orientation – allows for better code maintenance by managing code complexity.
Actually, there are two ways to manage software complexity:
- abstractions, and
- divide-and-conquer.
While divide-and-conquer is an algorithm design paradigm, thinking in OO (a programming paradigm) – in combination with strong domain knowledge – can help us to find and write good abstractions. But be warned, bad OO is sometimes worse, than no OO at all. In the following sections, we demonstrate how a small piece of pseudo-code evolves from procedural to object-oriented.
Bottom-up without abstraction
Consider the following pseudo-code snippet. The code blinks a GPIO LED called ‘LEDx’ when a device is ‘processing something’.
IF is_processing_something THEN state = read_bit_in_gpio_reg of LEDx IF state == 0 THEN set_bit_in_gpio_reg of LEDx ELSE clear_bit_in_gpio_reg of LEDx END ELSE clear_bit_in_gpio_reg of LEDx END
This code reads and manipulates bits in GPIO registers, and is clearly the result of bottom-up thinking without abstraction. Nothing wrong with that as long as it doesn’t get any more complex (unfortunately, most low-cost embedded systems do get more complicated but a rework/redesign is never done…).
Let us move on to the next level of complexity. Suppose we want to support active-high and active-low GPIO leds. The code then becomes:
IF is_processing_something THEN state = read_bit_in_gpio_reg of LEDx IF state == 0 THEN IF LEDx is active_high THEN set_bit_in_gpio_reg of LEDx ELSE clear_bit_in_gpio_reg of LEDx ELSE IF LEDx is active_high THEN clear_bit_in_gpio_reg of LEDx ELSE set_bit_in_gpio_reg of LEDx END ELSE IF LEDx active_high THEN clear_bit_in_gpio_reg of LEDx ELSE set_bit_in_gpio_reg of LEDx END
And this is where complexity starts: nested IFs and first signs of code duplication. The time to refactor this code is NOW. And therefor, we introduce the most fundamental abstraction: the ‘function’ :-).
The function
IF is_processing_something THEN IF is_on(LEDx) THEN led_off(LEDx) ELSE led_on(LEDx) END ELSE led_off(LEDx) END
Or even better:
IF is_processing_something THEN led_toggle(LEDx) ELSE led_off(LEDx) END
This is good application code! Only one level of abstraction, implementation details are hidden (active_high/low, gpio). The LED interface is such that the application developer can focus on the algorithm.
Some developers prefer to write application code before the functions are implemented. This approach is called top-down. The danger here is over-engineering (too much abstraction) or code that does not map very well on the lower software layers (bad abstraction, bad interfaces).
Evolving to object-orientation
Although controlling LEDs by GPIO is very common in embedded systems, there are other ways to implement the hardware e.g. pwm, LED drivers (e.g. ti tlc), DACs. But of course, as a user, you want to have a unified interface which supports multiple implementations (polymorphism). And this is exactly what the leds-class framework of the Linux kernel does.
A user can control leds via sysfs (the unified interface, see /sys/class/leds/<device>). Behind this interface a led framework takes care of different implementations. The core of the framework uses basic OO in order to be device-agnostic: it would be bad to have to change the core each time a new led device is added. Developers do not like to repeat ‘switch/case’-code like this:
FUNCTION led_off(LED led) SWITCH led_type(led) CASE LED_GPIO THEN do_gpio_stuff END CASE LED_PWM THEN do_led_pwm_stuff END ... END END
Maintenance becomes harder when these kind of switch/case statements come back repeatedly in the code (suppose a new device must be added…).
Back to the Linux led framework. A new led device can be added by implementing struct led_classdev (there are several examples available in drivers/leds directory). In this struct OO is achieved by function pointers (e.g. blink_set, brightness_set). The new device (struct led_classdev) registers itself in the core (include/linux/leds.h, leds-class.c, leds-core.c). The core only calls the function pointers and does not need to know about the underlying led hardware.
Speak Your Mind