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.
441 lines
14 KiB
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;
|
|
|