In the earlier chapters we had brief encounters with containers like Vectors from the predefined library. Behind the curtains all the containers are generic in the sense that the individual items are of an unspecified data type. In Ada such generic packages though parameterized with the data type of the contained items, the entire source is compiled and verified for syntax and in many cases semantic consistency (where possible).
In this projectlet, we will tackle a challenge encountered in embedded applications by designing a generic package.
10.1 Problem Statement
Embedded applications tend to live forever ie once powered on, they function without any arbitrary terminations till explicitly shutdown in a planned way. An insulin delivery pump for example continues to perform its function thought it might sleep most of the time waking up periodically to deliver say a basal insulin dosage. Temperature control of houses, office buildings, robots operating in a warehouse assembling products are other examples.
In all such instances certain variables e.g. setpoints can be considered critical. Any corruption of such critical variables due to application malfunction, electrical malfunction or other such disturbances if not detected could lead to disastrous consequences.
Several techniques are followed in manipulating such critical variables:
Protect with a checksum or a CRC. Recompute the checksum every time such a variable is updated and cross verify the CRC when accessing them.
Store the values in multiple copies and compare them every time they are accessed. While accessing if the copies are not identical then handle this as a serious failure.
Both have their benefits; the first however may impose a moderately higher compute load which may be intolerable in an embedded environment. In this projectlet the latter approach is illustrated using Generics.
10.2 Design Consideration
The entire support should be designed to be agnostic to the type of the critical variable. In addition the constraint checking while retrieving the values would require specifial Get and Set support.
0002 | generic
0003 |
0004 | type Item_Type is private;
0005 | with function "=" (left, right : Item_Type) return Boolean;
0006 |
0007 | package critical is
0008 |
0009 | AllocatorError : exception;
0010 | VariableCorruption : exception;
0011 |
0012 | type AllocatorType is
0013 | access function (secondary : Boolean := False) return access Item_Type;
0014 | procedure SetAllocators (a : not null AllocatorType);
0015 |
0016 | type Variable_Type is private;
0017 | procedure Set (var : in out Variable_Type; value : Item_Type);
0018 | function Get (var : Variable_Type) return Item_Type;
0019 |
0020 | private
0021 | type ItemPtr_Type is access all Item_Type;
0022 | type Variable_Type is record
0023 | primary : ItemPtr_Type;
0024 | secondary : ItemPtr_Type;
0025 | end record;
0026 | end critical;
Of particular note is the Allocator. While typically the variables could just be anywhere in RAM, or heap, this package supports provide different allocators for the primary value and a distinct one for the backup or secondary copy. In embedded applications these might reside in distinct sections relocatable at link time. This provides additional protection or ability to detect corruptions.
Of course once a corruption is detected, the consequence is dependent entirely on the application. This generic package simply raises an appropriate exception that can be handled by the application.
10.3 Getter and Setters for the variable
Values of the variables then have to be stored in distinct memory locations - provided by the allocator. While accessing the 2 copies are compared using the equality function (“=”) which is a parameter to the generic package.
0011 | procedure Set (var : in out Variable_Type; value : Item_Type) is
0012 | begin
0013 | if var.primary = null then
0014 | if allocate = null then
0015 | raise AllocatorError;
0016 | end if;
0017 | var.primary := ItemPtr_Type (allocate.all);
0018 | var.primary.all := value;
0019 |
0020 | var.secondary := ItemPtr_Type (allocate.all (True));
0021 | var.secondary.all := value;
0022 | return;
0023 | end if;
0024 |
0025 | if var.primary.all = var.secondary.all then
0026 | var.primary.all := value;
0027 | var.secondary.all := value;
0028 | return;
0029 | end if;
0030 |
0031 | raise VariableCorruption;
0032 | end Set;
0033 |
0034 | function Get (var : Variable_Type) return Item_Type is
0035 | begin
0036 | if var.primary.all = var.secondary.all then
0037 | return var.primary.all;
0038 | end if;
0039 | raise VariableCorruption;
0040 | end Get;
The first time a value is assigned, the allocators are invoked to assign the storage. Once allocated they are never deallocated which is typical for this application.
This implementation compares the 2 values while accessing as well as while the values are being adjusted.
10.4 Specialization
Depending on an application’s critical variable, the generic package is instantiated:
0002 | with critical_float;
0003 | package cvars is
0004 | tempSetpoint : critical_float.Variable_Type;
0005 | pressureSetpoint : critical_float.Variable_Type;
0006 | function allocator (secondary : Boolean) return access Float;
0007 | end cvars;
The variables are defined in this package. This is not by necessity and is more a convenience. In particular the package body makes it automatic to provide the requisite allocators. In this example a trivial allocator is provided:
0002 | package body cvars is
0003 | function allocator (secondary : Boolean) return access Float is
0004 | begin
0005 | return new Float;
0006 | end allocator;
0007 | begin
0008 | critical_float.SetAllocators (allocator'Access);
0009 | end cvars;
The allocator which simply uses the new keyword which in turn allocates the memory from the default heap is provided. Initializing the allocator is done as part of the elaboration of the package. The elaboration happens implicitly without having to be explicitly invoked.
The variables are Set and retrieved using the appropriate methods: