You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
ASYD/ASYD_Safety/Ada_Microbit/boards/MicroBit/src/microbit-ios.adb

441 lines
14 KiB

------------------------------------------------------------------------------
-- --
-- Copyright (C) 2017-2020, AdaCore --
-- --
-- Redistribution and use in source and binary forms, with or without --
-- modification, are permitted provided that the following conditions are --
-- met: --
-- 1. Redistributions of source code must retain the above copyright --
-- notice, this list of conditions and the following disclaimer. --
-- 2. Redistributions in binary form must reproduce the above copyright --
-- notice, this list of conditions and the following disclaimer in --
-- the documentation and/or other materials provided with the --
-- distribution. --
-- 3. Neither the name of the copyright holder nor the names of its --
-- contributors may be used to endorse or promote products derived --
-- from this software without specific prior written permission. --
-- --
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS --
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT --
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR --
-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT --
-- HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, --
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT --
-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, --
-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY --
-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT --
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE --
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --
-- --
------------------------------------------------------------------------------
with HAL; use HAL;
with nRF.ADC; use nRF.ADC;
with nRF.Device; use nRF.Device;
with nRF.PPI; use nRF.PPI;
with nRF.Timers; use nRF.Timers;
with nRF.GPIO.Tasks_And_Events; use nRF.GPIO.Tasks_And_Events;
with nRF.Events; use nRF.Events;
with nRF.Interrupts; use nRF.Interrupts;
package body MicroBit.IOs is
-- The analog out feature is implemented as PWM signal. To generate the PWM
-- signals we use a timer with the configuration described bellow.
--
-- Because of the limited number of timer comparators and GPIOTE channels,
-- we can only have 3 PWMs on the system at the same time. However there
-- are 5 pins allowed to use PWM, so we need to dynamicaly allocate the
-- PWM based on user requests.
--
-- Timer configuration:
--
-- Comparator 0, 1, 2 are used to control the pulse width of the 3 PWMs.
-- Each of those comparator is associated with a PWM and a pin. When the
-- timer counter reaches the value of a comparator, the associated pin
-- toggles.
--
-- Comparator 3 is use to control the period. When the timer counter reaches
-- its value, all pins toggle.
--
-- Comparator 3 also trigger an interrupt. In the handler for this
-- interrupt, we update all the comparator values and start the timer again.
--
--
-- Int handler and timer start Cmp 0 Cmp 1 Cmp 2 Cmp3, Timer stop and interrupt
-- v v v v v
-- _______________________________ ____
-- |_______________________|
-- ______________________________________ ____
-- |________________|
-- _____________________________________________ ____
-- |_________|
--
-- ^------------------ Timer loop sequence -------------------^
--
-- Since all the timer events trigger a toggle of the pin, we have to make
-- sure that the pin is at a good state (high) when starting the timer,
-- otherwise the waveform could be inverted. This is why the GPIO channels
-- are always configured when the timer is reconfigured.
--
-- PPI and GPIOTE:
--
-- To trigger a pin toggle from the timer compare events we use the
-- following configuation.
--
-- Two PPI channels are used for each PWM pin. For a PWM X, one PPI channel
-- is used to trigger a GPIOTE task on comparator X event, a second PPI
-- channel is used to trigger a GPIOTE event on comparator 3 event. So
-- the comparator 3 event is used by all PWMs.
--
-- For a PWM X, GPIOTE channel X is configure to do a pin toggle when its
-- task is activated by one of the two PPI channels described above.
-- We keep track of the current mode of the pin to be able to detect when a
-- change of configuration is needed.
type Pin_Mode is (None, Digital_In, Digital_Out, Analog_In, Analog_Out);
Current_Mode : array (Pin_Id) of Pin_Mode := (others => None);
-- PWM --
Number_Of_PWMs : constant := 3;
type PWM_Allocated is range 0 .. Number_Of_PWMs;
subtype PWM_Id is PWM_Allocated range 0 .. Number_Of_PWMs - 1;
No_PWM : constant PWM_Allocated := Number_Of_PWMs;
PWM_Alloc : array (Pin_Id) of PWM_Allocated := (others => No_PWM);
PWM_Timer : Timer renames Timer_0;
PWM_Interrupt : constant Interrupt_Name := TIMER0_Interrupt;
PWM_Global_Compare : constant Timer_Channel := 3;
PWM_Precision : constant := 4;
PWM_Period : UInt32 := 2_000 / PWM_Precision;
type PWM_Status is record
Taken : Boolean := False;
Pulse_Width : Analog_Value;
Cmp : UInt32 := 10;
Pin : Pin_Id;
end record;
PWMs : array (PWM_Id) of PWM_Status;
function Has_PWM (Pin : Pin_Id) return Boolean
is (PWM_Alloc (Pin) /= No_PWM);
procedure Allocate_PWM (Pin : Pin_Id;
Success : out Boolean)
with Pre => not Has_PWM (Pin);
procedure Deallocate_PWM (Pin : Pin_Id)
with Pre => Has_PWM (Pin),
Post => not Has_PWM (Pin);
procedure Configure_PPI (Id : PWM_Id);
procedure Configure_GPIOTE (Id : PWM_Id);
procedure Init_PWM_Timer;
function To_Compare_Value (V : Analog_Value) return UInt32;
procedure PWM_Timer_Handler;
----------------------
-- To_Compare_Value --
----------------------
function To_Compare_Value (V : Analog_Value) return UInt32
is
Cmp : constant UInt32 :=
UInt32 (Float (PWM_Period) * (Float (V) / Float (Analog_Value'Last)));
begin
if Cmp = 0 then
return 1;
elsif Cmp >= PWM_Period then
return PWM_Period - 1;
else
return Cmp;
end if;
end To_Compare_Value;
------------------
-- Allocate_PWM --
------------------
procedure Allocate_PWM (Pin : Pin_Id;
Success : out Boolean)
is
begin
for Id in PWM_Id loop
if not PWMs (Id).Taken then
PWMs (Id).Taken := True;
PWMs (Id).Pin := Pin;
PWM_Alloc (Pin) := Id;
Configure_PPI (Id);
Success := True;
return;
end if;
end loop;
Success := False;
end Allocate_PWM;
--------------------
-- Deallocate_PWM --
--------------------
procedure Deallocate_PWM (Pin : Pin_Id) is
begin
if PWM_Alloc (Pin) /= No_PWM then
nRF.GPIO.Tasks_And_Events.Disable (GPIOTE_Channel (PWM_Alloc (Pin)));
PWMs (PWM_Alloc (Pin)).Taken := False;
PWM_Alloc (Pin) := No_PWM;
end if;
end Deallocate_PWM;
-------------------
-- Configure_PPI --
-------------------
procedure Configure_PPI (Id : PWM_Id) is
Chan1 : constant Channel_ID := Channel_ID (Id) * 2;
Chan2 : constant Channel_ID := Chan1 + 1;
begin
-- Use one PPI channel to triggerd GPTIOTE OUT task on the compare event
-- associated with this PWM_Id;
nRF.PPI.Configure
(Chan => Chan1,
Evt_EP => PWM_Timer.Compare_Event (Timer_Channel (Id)),
Task_EP => Out_Task (GPIOTE_Channel (Id)));
-- Use another PPI channel to triggerd GPTIOTE OUT task on compare 3 event
nRF.PPI.Configure
(Chan => Chan2,
Evt_EP => PWM_Timer.Compare_Event (PWM_Global_Compare),
Task_EP => Out_Task (GPIOTE_Channel (Id)));
nRF.PPI.Enable_Channel (Chan1);
nRF.PPI.Enable_Channel (Chan2);
end Configure_PPI;
----------------------
-- Configure_GPIOTE --
----------------------
procedure Configure_GPIOTE (Id : PWM_Id) is
begin
-- Configure the GPIOTE OUT task to toggle the pin
nRF.GPIO.Tasks_And_Events.Enable_Task
(Chan => GPIOTE_Channel (Id),
GPIO_Pin => Points (PWMs (Id).Pin).Pin,
Action => Toggle_Pin,
Initial_Value => Init_Set);
end Configure_GPIOTE;
-----------------------
-- PWM_Timer_Handler --
-----------------------
procedure PWM_Timer_Handler is
begin
Clear (PWM_Timer.Compare_Event (PWM_Global_Compare));
PWM_Timer.Set_Compare (PWM_Global_Compare, PWM_Period);
PWM_Timer.Set_Compare (0, PWMs (0).Cmp);
PWM_Timer.Set_Compare (1, PWMs (1).Cmp);
PWM_Timer.Set_Compare (2, PWMs (2).Cmp);
PWM_Timer.Start;
end PWM_Timer_Handler;
--------------------
-- Init_PWM_Timer --
--------------------
procedure Init_PWM_Timer is
begin
PWM_Timer.Set_Mode (Mode_Timer);
PWM_Timer.Set_Prescaler (6);
PWM_Timer.Set_Bitmode (Bitmode_32bit);
-- Clear counter internal register and stop when timer reaches compare
-- value 3.
PWM_Timer.Compare_Shortcut (Chan => PWM_Global_Compare,
Stop => True,
Clear => True);
PWM_Timer.Set_Compare (PWM_Global_Compare, PWM_Period);
for Id in PWM_Id loop PWM_Timer.Set_Compare (Timer_Channel (Id),
To_Compare_Value (PWMs (Id).Pulse_Width));
if PWMs (Id).Taken then
Configure_GPIOTE (Id);
end if;
end loop;
Enable_Interrupt (PWM_Timer.Compare_Event (PWM_Global_Compare));
nRF.Interrupts.Register (PWM_Interrupt,
PWM_Timer_Handler'Access);
nRF.Interrupts.Enable (PWM_Interrupt);
end Init_PWM_Timer;
---------
-- Set --
---------
procedure Set
(Pin : Pin_Id;
Value : Boolean)
is
Pt : GPIO_Point renames Points (Pin);
Conf : GPIO_Configuration;
begin
if Current_Mode (Pin) /= Digital_Out then
if Has_PWM (Pin) then
Deallocate_PWM (Pin);
end if;
Conf.Mode := Mode_Out;
Conf.Resistors := No_Pull;
Conf.Input_Buffer := Input_Buffer_Connect;
Conf.Sense := Sense_Disabled;
Pt.Configure_IO (Conf);
Current_Mode (Pin) := Digital_Out;
end if;
if Value then
Pt.Set;
else
Pt.Clear;
end if;
end Set;
---------
-- Set --
---------
function Set
(Pin : Pin_Id)
return Boolean
is
Pt : GPIO_Point renames Points (Pin);
Conf : GPIO_Configuration;
begin
if Current_Mode (Pin) /= Digital_In then
if Has_PWM (Pin) then
Deallocate_PWM (Pin);
end if;
Conf.Mode := Mode_In;
Conf.Resistors := No_Pull;
Conf.Input_Buffer := Input_Buffer_Connect;
Conf.Sense := Sense_Disabled;
Pt.Configure_IO (Conf);
Current_Mode (Pin) := Digital_In;
end if;
return Pt.Set;
end Set;
--------------------------
-- Set_Analog_Period_Us --
--------------------------
procedure Set_Analog_Period_Us (Period : Natural) is
begin
PWM_Period := UInt32 (Period) / PWM_Precision;
-- Update the comparator values for ech PWM
for PWM of PWMs loop
PWM.Cmp := To_Compare_Value (PWM.Pulse_Width);
end loop;
end Set_Analog_Period_Us;
-----------
-- Write --
-----------
procedure Write
(Pin : Pin_Id;
Value : Analog_Value)
is
Success : Boolean;
Pt : GPIO_Point renames Points (Pin);
Conf : GPIO_Configuration;
begin
if not Has_PWM (Pin) then
-- Stop the timer while we configure a new pin
PWM_Timer.Stop;
PWM_Timer.Clear;
Allocate_PWM (Pin, Success);
if not Success then
raise Program_Error with "No PWM available";
end if;
-- Set the pin as output
Conf.Mode := Mode_Out;
Conf.Resistors := No_Pull;
Conf.Input_Buffer := Input_Buffer_Connect;
Conf.Sense := Sense_Disabled;
Pt.Configure_IO (Conf);
Pt.Clear;
Current_Mode (Pin) := Analog_Out;
Init_PWM_Timer;
PWM_Timer.Start;
end if;
PWMs (PWM_Alloc (Pin)).Pulse_Width := Value;
PWMs (PWM_Alloc (Pin)).Cmp := To_Compare_Value (Value);
end Write;
------------
-- Analog --
------------
function Analog
(Pin : Pin_Id)
return Analog_Value
is
Result : UInt16;
begin
if Current_Mode (Pin) /= Analog_In then
if Has_PWM (Pin) then
Deallocate_PWM (Pin);
end if;
Current_Mode (Pin) := Analog_In;
end if;
Result := Do_Pin_Conversion (Pin => (case Pin is
when 0 => 0,
when 1 => 1,
when 2 => 2,
when 3 => 7,
when 4 => 4,
when 10 => 6,
when others => 5),
Input => Pin_One_Forth,
Ref => VDD_One_Forth,
Res => Res_10bit);
if Result > UInt16(Analog_Value'Last) then
Result := 0;
end if;
return Analog_Value (Result);
end Analog;
end MicroBit.IOs;