4 Logging time
This chapter is based on the projectlet 03_logger
4.1 Goals of the project
As the design of an embedded application evolves and we begin to tackle diagnostic needs with tools such as a cli as the first step. The next most critical need is annunciating states and measurements. Proper diagnostics will need a timestamp, events and data in a predictable order from any of the applications implemented in the system. Enumerating the requirements then:
A clock function is needed that maintains a real time clock ie matching the external clock time. The controllers provide ticks which is an indication of the progression but is of questionable value in system diagnostics.
A logging channel that is used by the application. Different functions within the application may be operating independently eg as threads (tasks in Ada terminology). Thus the logging facility must be inherently thread safe.
The functions implemented in earlier projects - the blinker and the cli were not multithreaded and thus only those functions could operate at any time. The terminal (in the cli project) has to wait for user input but cannot hold up a motor control thread (task) for instance. Similarly blinking an LED should not hold up other application functions.
In this project, some of the above requirements are addressed - on an experimental basis while the main logging function itself does not benefit from the parallelizing effort. The logging function is simplistic in this project - just logging the clock periodically in its main thread/task.
4.2 Real time Clock
The STM32F4 discovery board incorporates an RTC (real time clock) that maintains a calendar. This is not extremely accurate and most real applications will probably utilizing a higher accuracy chip and connect an accurate high frequency clock signal in order to maintain the clock. However the onboard RTC (using LSI clocking) will be sufficient for experimentation.
4.2.1 Initialization
In embedded applications that rely on a clock, it is essential to design in a nonvolatile storage of the clock ie across a power cycle, the clock shall be maintained instead of having to be reinitialized. However in the STM32F4 discovery board, the clock needs initialization with a date, time and day of the week. Once initialized the clock is maintained and can be queried as needed.
File : support-rtclsi.ads
0006 | type day_name is (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday );
0007 | pragma Ordered( day_name );
0008 |
0009 | procedure Set_Date (Year, Month, Day, Weekday : UInt8) ;
0010 | procedure Set_Time (Hour, Minute, Second : UInt8) ;
0011 | procedure Get_Time (Hour, Minute, Second : out UInt8) ;
0012 | procedure Get_Date (Year, Month, Day, Weekday : out UInt8);
0013 | function Get_Day( weekday : UInt8 ) return String ;
0014 | function Get_Date return String ;
0015 | function Get_Time return String ;
0016 | function TodayName return String ;with that the clock is initialized:
File : logger.adb
0020 | support.rtclsi.Set_Date (Year => 26, Month => 03, Day => 19, Weekday => 3) ;
0021 | support.rtclsi.Set_Time (Hour => 10, Minute => 50 , Second => 0) ;4.2.2 Adjustment of the clock
It is to be noted that the rtc’s accuracy spec will lead to a drift in the clock over a period of time. If a system has multiple clocks, each drifting at a different rate then a system mechanism will have to be implemented to ensure period synchronization. In this project we provide a cli command to set the date and time which will suffice for experimentation.
By necessity, the initialization above uses a hardcoded date and time and in experimentation, one of the first user actions will be to use the cli to set the clock.
4.2.3 Consuming the clock
With the above initializations, the clock can be polled periodically and logged.
File : logger.adb
0033 | loop
0034 | support.logs.Put_Line( "Current time is: " & support.rtclsi.Get_Time ) ;
0035 | delay 2.0 ;
0036 | end loop ;4.3 Annunciation with the LED
In the previous projects, the LED blinking would end up blocking the entire controller. In this project, each LED is operated by an independent task with its own parameters. In a simplistic implementation, only the period of On and Off are used for illustration. The key learning point of this exercise is to make the operation independent - a task and provide a mechanism - messaging - to communicate with the task.
File : logger.adb
0025 | support.led.LED_Parameters.Set_Pattern( green_led_id , 'G' , 0.5 ) ;
0026 | support.led.LED_Parameters.Set_Pattern( red_led_id , 'R' , 1.5 ) ;
0027 | support.led.LED_Parameters.Set_Pattern( blue_led_id , 'B' , 2.5 ) ;
0028 | support.led.LED_Parameters.Set_Pattern( orange_led_id , 'O' , 3.5 ) ;
0029 | support.led.LED_Parameters.Set_Pattern( status_led_id , 'S' , 4.5 ) ; The above segment parameterizes the led tasks referred to by their ids. Each line targets a specific LED and the appropriate task can retrieve the appropriate parameter in its operation.
In conventional platforms, the above messageing will be achieved by a combination of entry, accept and other such features. In the runtime that is used in these projects namely embedded-stm32f4, they are not supported and an alternative is needed. We use a protected intermediary object for this purpose.
The project file provides the following specifications :
File : logger.gpr
0008 | for Target use "arm-eabi" ;
0009 | for Runtime ("Ada") use "embedded-stm32f4" ;
4.4 Logs transmission
For transmission of the logs, we can utilize one of the USART or UART peripherals with the GPIO pins. Beyond the low level interface, the synchronous discipline adopted for the earlier project cli is no longer suitable. The need is to be non blocking so multiple operations can be in progress simultariously. At this level, the logging operation and cli operation are independent consumers of the USART/UART peripherals.
Agail the adl provides examples of such non blocking serial io operations which is replicated in this project, affording us the option to adopt as necessary.
4.4.1 Non-blocking IO with Serial Ports
From a user’s perspective, a channel is defined as a combination of USART and the pins for TX and RX operation. The alternative to polling for operation completion is to connect to an appropriate interrupt - thus enabling other operatings to continue. The specifications for such an interface are:
File : serial_io-nonblocking.ads
0050 | package Serial_IO.Nonblocking is
0051 | pragma Elaborate_Body;
0052 |
0053 | type Serial_Port
0054 | (Device : not null access USART;
0055 | IRQ : Interrupt_ID;
0056 | IRQ_Priority : Interrupt_Priority)
0057 | is limited private;
0058 |
0059 | procedure Send
0060 | (This : in out Serial_Port;
0061 | Msg : not null access Message);
0062 | -- Start sending the content of Msg.all, returning potentially
0063 | -- prior to the completion of the message transmission
0064 |
0065 | procedure Receive
0066 | (This : in out Serial_Port;
0067 | Msg : not null access Message)
0068 | with
0069 | Post => Msg.Length <= Msg.Physical_Size and
0070 | (if Msg.Length > 0 then Msg.Content_At (Msg.Length) /= Msg.Terminator);
0071 | -- Start receiving Msg.all content, ending when the specified
0072 | -- Msg.Terminator character is received or the physical capacity
0073 | -- of Msg.all is reached. The terminator character is not stored.Though the specificiation details above are public, behind the scenes, the access to the port has to be exclusive implemented using the protected construct:
File : serial_io-nonblocking.ads
0078 | protected type Serial_Port
0079 | (Device : not null access USART;
0080 | IRQ : Interrupt_ID;
0081 | IRQ_Priority : Interrupt_Priority)
0082 | with
0083 | Interrupt_Priority => IRQ_Priority
0084 | is
0085 |
0086 | procedure Start_Sending (Msg : not null access Message);
0087 |
0088 | procedure Start_Receiving (Msg : not null access Message);
0089 |
0090 | private
0091 |
0092 | Next_Out : Positive;
0093 | Outgoing_Msg : access Message;
0094 | Incoming_Msg : access Message;
0095 |
0096 | procedure Handle_Transmission with Inline;
0097 | procedure Handle_Reception with Inline;
0098 | procedure Detect_Errors (Is_Xmit_IRQ : Boolean) with Inline;
0099 |
0100 | procedure ISR with Attach_Handler => IRQ;
0101 |
0102 | end Serial_Port;4.4.2 Orchestration of IO with interrupts
Since blocking on the IO operations is not desired, interrupts are leveraged to achieve a non blocking transmission. The application connects to the appropriate Interrupt and the processor delivers the interrupt to the specified interrupt service routine (ISR).
File : serial_io-nonblocking.adb
0109 | procedure ISR is
0110 | begin
0111 | -- check for data arrival
0112 | if Device.Status (Read_Data_Register_Not_Empty) and then
0113 | Device.Interrupt_Enabled (Received_Data_Not_Empty)
0114 | then
0115 | Detect_Errors (Is_Xmit_IRQ => False);
0116 | Handle_Reception;
0117 | Device.Clear_Status (Read_Data_Register_Not_Empty);
0118 | end if;
0119 |
0120 | -- check for transmission ready
0121 | if Device.Status (Transmission_Complete_Indicated) and then
0122 | Device.Interrupt_Enabled (Transmission_Complete)
0123 | then
0124 | Detect_Errors (Is_Xmit_IRQ => True);
0125 | Handle_Transmission;
0126 | Device.Clear_Status (Transmission_Complete_Indicated);
0127 | end if;
0128 | end ISR;Once setup transmission of a message proceeds:
File : serial_io-nonblocking.adb
0135 | procedure Start_Sending (Msg : not null access Message) is
0136 | begin
0137 | Outgoing_Msg := Msg;
0138 | Next_Out := 1;
0139 |
0140 | Device.Enable_Interrupts (Parity_Error);
0141 | Device.Enable_Interrupts (Error);
0142 | Device.Enable_Interrupts (Transmission_Complete);
0143 | end Start_Sending;In the above, setting up Next_Out initiates the transmissions - one character at a time starting with the first. Every Transmission_Complete interrupt should initiate the transmission of the next character and so on:
File : serial_io-nonblocking.adb
0064 | procedure Handle_Transmission is
0065 | begin
0066 | -- if Word_Lenth = 9 then
0067 | -- -- handle the extra byte required for the 9th bit
0068 | -- else -- 8 data bits so no extra byte involved
0069 | Device.Transmit (Character'Pos (Outgoing_Msg.Content_At (Next_Out)));
0070 | Next_Out := Next_Out + 1;
0071 | -- end if;
0072 | if Next_Out > Outgoing_Msg.Length then
0073 | Device.Disable_Interrupts (Source => Transmission_Complete);
0074 | Outgoing_Msg.Signal_Transmission_Complete;
0075 | Outgoing_Msg := null;
0076 | end if;
0077 | end Handle_Transmission;The operations continue and when all the required octet’s are transmitted, the application is informed by means of the Message object.
Message transmission proceeds in a similar fashion. In this application, a line terminator is designated and when the terminator is received, the message reception is considered complete. In this application, the logger channel does not handle inputs though an RX pin is designated. In the case of the cli which leverages the same features will utilize both - transmission and reception by means of the non blocking discipline.
The pattern above is pretty common at the low level of most embedded applications.
4.5 Command Language Interface
With the non blocking serial IO capabilities implemented, the main loop of the cli project is easily converted into a task functioning in parallel with the application - which is simple logging in this application :
File : support-services.adb
0009 | task body cli_Service_Type is
0010 | begin
0011 | loop
0012 | declare
0013 | Req : String := Receive;
0014 | begin
0015 | cmd.Execute( Req );
0016 | end ;
0017 | end loop ;
0018 | end cli_Service_Type;4.5.1 Design constraints
The design implemented and outlined above has imposed certain constraints such as a line terminator for receptions and handling of overflow conditions when the line terminator is not received. Later projects will illustrate how to handle arbitrary length messages with no line terminator designated.
Integrity requirements may also lead to adding a CRC based error checking to account for the inevitable glitches.
4.6 Support library
As we are expanding the footprint of the applications, we can see a library evolving. A family of packages under the moniker support is part of this project:
support.ads
support-cli.ads
support-led.ads
support-logs.ads
support-rng.ads
support-rtclsi.ads
support-services.ads
A later project will utilize these directly - illustrating a technique for reuse.