From fcf8caa41f01ad4a1817365e93004cc62acb5076 Mon Sep 17 00:00:00 2001 From: Andry Ogorodnik Date: Sun, 16 Feb 2025 17:15:54 +0200 Subject: [PATCH] Add support for nRF24L01+ radio module --- components/src/radio/nrf24l01p/README.md | 10 + components/src/radio/nrf24l01p/nrf24l01p.adb | 1167 +++++++++++++++++ components/src/radio/nrf24l01p/nrf24l01p.ads | 917 +++++++++++++ .../nrf24l01p_f429disco.gpr | 15 + examples/shared/nrf24l01p/.gdbinit | 24 + examples/shared/nrf24l01p/README.md | 22 + .../nrf24l01p/src/nrf24l01p_example.adb | 544 ++++++++ examples/shared/nrf24l01p/src/watchdogs.adb | 63 + examples/shared/nrf24l01p/src/watchdogs.ads | 63 + 9 files changed, 2825 insertions(+) create mode 100644 components/src/radio/nrf24l01p/README.md create mode 100644 components/src/radio/nrf24l01p/nrf24l01p.adb create mode 100644 components/src/radio/nrf24l01p/nrf24l01p.ads create mode 100644 examples/STM32F429_Discovery/nrf24l01p_f429disco.gpr create mode 100644 examples/shared/nrf24l01p/.gdbinit create mode 100644 examples/shared/nrf24l01p/README.md create mode 100644 examples/shared/nrf24l01p/src/nrf24l01p_example.adb create mode 100644 examples/shared/nrf24l01p/src/watchdogs.adb create mode 100644 examples/shared/nrf24l01p/src/watchdogs.ads diff --git a/components/src/radio/nrf24l01p/README.md b/components/src/radio/nrf24l01p/README.md new file mode 100644 index 000000000..e877aa86b --- /dev/null +++ b/components/src/radio/nrf24l01p/README.md @@ -0,0 +1,10 @@ +This directory contains the driver for nRF24L01+ radio module. The nRF24AP2 +ICs are low-cost, high-performance 2.4 GHz ISM single-chip connectivity devices +with an embedded ANT protocol stack. + +nRF24L01 can also be driven with some reservations. For example, for Air_Data_Rate +use Rate_250kbps (2Mbps on nRF24L01) or Rate_1Mbps (1Mbps on nRF24L01) only. +Refer to the specifications for more information. + +The examples/STM32F429_Discovery/nrf24l01p_f429disco.gpr project contains +a simple example. diff --git a/components/src/radio/nrf24l01p/nrf24l01p.adb b/components/src/radio/nrf24l01p/nrf24l01p.adb new file mode 100644 index 000000000..437bc9d36 --- /dev/null +++ b/components/src/radio/nrf24l01p/nrf24l01p.adb @@ -0,0 +1,1167 @@ +------------------------------------------------------------------------------ +-- -- +-- Copyright (C) 2025, 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. -- +-- -- +------------------------------------------------------------------------------ + +package body NRF24L01P is + + procedure Set_Power + (RF_Setup : in out RF_SETUP_Register; + Power : Amplifier_Power) + with Inline; + + procedure Set_Rate + (RF_Setup : in out RF_SETUP_Register; + Rate : Air_Data_Rate) + with Inline; + + procedure Set_En_Rxaddr + (Reg : in out EN_RXADDR_Register; + Pipe : RX_Pipe; + Data : Bit) + with Inline; + + function To_RX_ADDR (Pipe : RX_Pipe) return Register_Address + with Inline; + + function Pipe_To_RX_PW (Pipe : RX_Pipe) return Register_Name + with Inline; + + function Boolean_To_Bit (Value : Boolean) return Bit + with Inline; + + function Payload_To_Bit (Value : Payload_Length_Type) return Bit + with Inline; + + -------------------- + -- Boolean_To_Bit -- + -------------------- + + function Boolean_To_Bit (Value : Boolean) return Bit is + begin + return (if Value then 1 else 0); + end Boolean_To_Bit; + + ------------- + -- CE_High -- + ------------- + + procedure CE_High (This : NRF24L01P_Driver) is + begin + This.Holder.CE_Pin.Set; + end CE_High; + + ------------ + -- CE_Low -- + ------------ + + procedure CE_Low (This : NRF24L01P_Driver) is + begin + This.Holder.CE_Pin.Clear; + end CE_Low; + + -------------- + -- CSN_High -- + -------------- + + procedure CSN_High (This : NRF24L01P_Driver) is + begin + if This.Holder.Kind = Software then + This.Holder.CSN_Pin.Set; + end if; + end CSN_High; + + ------------- + -- CSN_Low -- + ------------- + + procedure CSN_Low (This : NRF24L01P_Driver) is + begin + if This.Holder.Kind = Software then + This.Holder.CSN_Pin.Clear; + end if; + end CSN_Low; + + ----------------------- + -- Can_Be_Configured -- + ----------------------- + + function Can_Be_Configured (This : NRF24L01P_Driver) return Boolean is + begin + return not Is_Power_On (This) or else not Is_Wating_RX (This); + end Can_Be_Configured; + + --------------------- + -- Clear_RX_Buffer -- + --------------------- + + procedure Clear_RX_Buffer (This : in out NRF24L01P_Driver) is + begin + Write_Command (This, Cmd_FLUSH_RX); + end Clear_RX_Buffer; + + ------------------ + -- Clear_Status -- + ------------------ + + procedure Clear_Status + (This : in out NRF24L01P_Driver; + RX_Resived : Boolean := True; + TX_Sent : Boolean := True; + Max_TX_Retransmits : Boolean := True) + is + Data : Register := Read_Register (This, STATUS); + STATUS_R : STATUS_Register + with Import, Address => Data'Address; + begin + STATUS_R.RX_DR_RW := Boolean_To_Bit (RX_Resived); + STATUS_R.TX_DS_RW := Boolean_To_Bit (TX_Sent); + STATUS_R.MAX_RT_RW := Boolean_To_Bit (Max_TX_Retransmits); + + Write_Register (This, STATUS, Data); + end Clear_Status; + + --------------------- + -- Clear_TX_Buffer -- + --------------------- + + procedure Clear_TX_Buffer (This : in out NRF24L01P_Driver) is + begin + Write_Command (This, Cmd_FLUSH_TX); + end Clear_TX_Buffer; + + ----------------------- + -- Config_Power_Rate -- + ----------------------- + + procedure Config_Power_Rate + (This : in out NRF24L01P_Driver; + Power : Amplifier_Power; + Rate : Air_Data_Rate) + is + Data : Register := Read_Register (This, RF_SETUP); + RF_Setup_R : RF_SETUP_Register + with Import, Address => Data'Address; + begin + Set_Power (RF_Setup_R, Power); + Set_Rate (RF_Setup_R, Rate); + Write_Register (This, RF_SETUP, Data); + end Config_Power_Rate; + + ---------------------------------- + -- Configure_And_Enable_RX_Pipe -- + ---------------------------------- + + procedure Configure_And_Enable_RX_Pipe + (This : in out NRF24L01P_Driver; + Pipe : RX_Pipe; + Addr : Pipe_Address; + ACK : Boolean; + Payload : Pipe_Payload) + is + procedure Set_Address; + procedure Set_ACK; + procedure Set_Payload; + + -- Set_ACK -- + + procedure Set_ACK + is + Data : Register := Read_Register (This, EN_AA); + EN_AA_R : EN_AA_Register + with Import, Address => Data'Address; + begin + case Pipe is + when 0 => EN_AA_R.ENAA_P0_RW := Boolean_To_Bit (ACK); + when 1 => EN_AA_R.ENAA_P1_RW := Boolean_To_Bit (ACK); + when 2 => EN_AA_R.ENAA_P2_RW := Boolean_To_Bit (ACK); + when 3 => EN_AA_R.ENAA_P3_RW := Boolean_To_Bit (ACK); + when 4 => EN_AA_R.ENAA_P4_RW := Boolean_To_Bit (ACK); + when 5 => EN_AA_R.ENAA_P5_RW := Boolean_To_Bit (ACK); + end case; + + Write_Register (This, EN_AA, Data); + end Set_ACK; + + -- Set_Address -- + + procedure Set_Address + is + Size : Pipe_Address_Size renames This.Holder.Addr_Size; + begin + case Pipe is + when 0 .. 1 => + declare + Data : HAL.SPI.SPI_Data_8b (1 .. 1 + Positive (Size)); + CMD : Command_W_REGISTER := + (Address => To_RX_ADDR (Pipe), + Command => <>) + with Address => Data (Data'First)'Address; + Local : Pipe_Address (1 .. Pipe_Address_Index (Size)) := + Addr (1 .. Pipe_Address_Index (Size)) + with Address => Data (Data'First + 1)'Address; + begin + Write (This, Data); + end; + + when 2 .. 5 => + declare + Data : HAL.SPI.SPI_Data_8b (1 .. 2); + CMD : Command_W_REGISTER := + (Address => To_RX_ADDR (Pipe), + Command => <>) + with Address => Data (Data'First)'Address; + Local : Pipe_Address (1 .. 1) := Addr (1 .. 1) + with Address => Data (Data'First + 1)'Address; + begin + Write (This, Data); + end; + end case; + end Set_Address; + + -- Set_Payload -- + + procedure Set_Payload + is + Data : Register := Read_Register (This, DYNPD); + DYNPD_R : DYNPD_Register + with Import, Address => Data'Address; + begin + -- Set Static/Dynamic payload + case Pipe is + when 0 => DYNPD_R.DPL_P0_RW := Payload_To_Bit (Payload.Payload); + when 1 => DYNPD_R.DPL_P1_RW := Payload_To_Bit (Payload.Payload); + when 2 => DYNPD_R.DPL_P2_RW := Payload_To_Bit (Payload.Payload); + when 3 => DYNPD_R.DPL_P3_RW := Payload_To_Bit (Payload.Payload); + when 4 => DYNPD_R.DPL_P4_RW := Payload_To_Bit (Payload.Payload); + when 5 => DYNPD_R.DPL_P5_RW := Payload_To_Bit (Payload.Payload); + end case; + + Write_Register (This, DYNPD, Data); + + if Payload.Payload = Dynamic_Payload then + This.Holder.Payloads (Pipe) := (Payload => Dynamic_Payload); + return; + end if; + + -- Set static lenght + declare + Name : constant Register_Name := Pipe_To_RX_PW (Pipe); + Data : Register := Read_Register (This, Name); + RX_PW_PX_R : RX_PW_PX_Register + with Import, Address => Data'Address; + begin + RX_PW_PX_R.RX_PW_PX_RW := UInt6 (Payload.Size); + Write_Register (This, Name, Data); + + This.Holder.Payloads (Pipe) := + (Static_Payload, Payload.Size); + end; + end Set_Payload; + + begin + Set_Address; + Set_ACK; + Set_Payload; + Enable_RX_Pipe (This, Pipe); + end Configure_And_Enable_RX_Pipe; + + -------------------- + -- Configure_Chip -- + -------------------- + + procedure Configure_Chip + (This : in out NRF24L01P_Driver; + Config : Configuration) is + begin + CE_Low (This); + CSN_High (This); + + Set_Mode (This, Config.Mode); + Set_RF_Channel_Frequency (This, Config.Chanel); + Config_Power_Rate (This, Config.Power, Config.Rate); + Set_CRC (This, Config.CRC); + + Configure_IRQ + (This, + Config.RX_IRQ, + Config.TX_IRQ, + Config.MAX_TR_IRQ); + + Clear_RX_Buffer (This); + Clear_TX_Buffer (This); + Clear_Status (This); + end Configure_Chip; + + ------------------- + -- Configure_IRQ -- + ------------------- + + procedure Configure_IRQ + (This : in out NRF24L01P_Driver; + RX_IRQ : Boolean; + TX_IRQ : Boolean; + MAX_TR_IRQ : Boolean) + is + Data : Register := Read_Register (This, CONFIG); + CONFIG_R : CONFIG_Register + with Import, Address => Data'Address; + begin + CONFIG_R.MASK_RX_DR_RW := (if RX_IRQ then 0 else 1); + CONFIG_R.MASK_TX_DS_RW := (if TX_IRQ then 0 else 1); + CONFIG_R.MASK_MAX_RT_RW := (if MAX_TR_IRQ then 0 else 1); + Write_Register (This, CONFIG, Data); + end Configure_IRQ; + + --------------------- + -- Disable_RX_Pipe -- + --------------------- + + procedure Disable_RX_Pipe + (This : in out NRF24L01P_Driver; + Pipe : RX_Pipe) + is + Data : Register := Read_Register (This, EN_RXADDR); + En_Rxaddr_R : EN_RXADDR_Register + with Import, Address => Data'Address; + begin + Set_En_Rxaddr (En_Rxaddr_R, Pipe, 0); + Write_Register (This, EN_RXADDR, Data); + end Disable_RX_Pipe; + + -------------------- + -- Enable_RX_Pipe -- + -------------------- + + procedure Enable_RX_Pipe + (This : in out NRF24L01P_Driver; + Pipe : RX_Pipe) + is + Data : Register := Read_Register (This, EN_RXADDR); + En_Rxaddr_R : EN_RXADDR_Register + with Import, Address => Data'Address; + begin + Set_En_Rxaddr (En_Rxaddr_R, Pipe, 1); + Write_Register (This, EN_RXADDR, Data); + end Enable_RX_Pipe; + + ---------------------------- + -- Get_First_RX_Data_Size -- + ---------------------------- + + function Get_First_RX_Data_Size + (This : in out NRF24L01P_Driver) + return Payload_Length + is + Data : HAL.SPI.SPI_Data_8b (1 .. 1); + begin + Write_Command_And_Read (This, Cmd_R_RX_PL_WID, Data); + return Payload_Length (Data (Data'First)); + end Get_First_RX_Data_Size; + + + ---------------------- + -- Get_RX_Data_Size -- + ---------------------- + + function Get_RX_Data_Size + (This : in out NRF24L01P_Driver; + Pipe : RX_Pipe) + return Payload_Length + is + Data : Register := Read_Register (This, Pipe_To_RX_PW (Pipe)); + RX_PW_PX_R : RX_PW_PX_Register + with Import, Address => Data'Address; + begin + return Payload_Length (RX_PW_PX_R.RX_PW_PX_RW); + end Get_RX_Data_Size; + + ---------------- + -- Get_Status -- + ---------------- + + procedure Get_Status + (This : in out NRF24L01P_Driver; + TX_Full : out Boolean; + Max_TX_Retransmits : out Boolean; + TX_Sent : out Boolean; + RX_Resived : out Boolean; + Pipe : out RX_Pipe) + is + Data : Register := Read_Register (This, STATUS); + STATUS_R : STATUS_Register + with Import, Address => Data'Address; + begin + TX_Full := STATUS_R.TX_FULL_RO = 1; + Max_TX_Retransmits := STATUS_R.MAX_RT_RW = 1; + TX_Sent := STATUS_R.TX_DS_RW = 1; + RX_Resived := STATUS_R.RX_DR_RW = 1; + + Pipe := (if STATUS_R.RX_P_NO_RO <= UInt3 (RX_Pipe'Last) + then RX_Pipe (STATUS_R.RX_P_NO_RO) + else 0); + end Get_Status; + + ---------------------- + -- Have_ACK_Payload -- + ---------------------- + + function Have_ACK_Payload (This : in out NRF24L01P_Driver) return Boolean is + begin + return Is_Received (This); + end Have_ACK_Payload; + + ---------------- + -- Initialize -- + ---------------- + + procedure Initialize + (This : out NRF24L01P_Driver; + CE_Pin : HAL.GPIO.Any_GPIO_Point; + CSN_Pin : HAL.GPIO.Any_GPIO_Point; + SPI : HAL.SPI.Any_SPI_Port; + Config : Configuration) is + begin + This.Holder := Holder_Type' + (Kind => Software, + SPI => SPI, + CE_Pin => CE_Pin, + CSN_Pin => CSN_Pin, + Power_On => <>, + Mode => <>, + NO_ACK => <>, + Addr_Size => <>, + Payloads => <>); + + Configure_Chip (This, Config); + end Initialize; + + -------------------- + -- Is_ACK_Allowed -- + -------------------- + + function Is_ACK_Allowed + (ACK : Boolean; + Dynamic_Payload : Boolean) + return Boolean is + begin + return (if ACK then Dynamic_Payload else True); + end Is_ACK_Allowed; + + ------------------- + -- Is_In_RX_Mode -- + ------------------- + + function Is_In_RX_Mode (This : NRF24L01P_Driver) return Boolean is + begin + return This.Holder.Mode = PRX; + end Is_In_RX_Mode; + + ----------------------- + -- Is_No_ACK_Allowed -- + ----------------------- + + function Is_No_ACK_Allowed + (This : NRF24L01P_Driver; + No_ACK : Boolean) return Boolean is + begin + return (if No_ACK then This.Holder.NO_ACK else True); + end Is_No_ACK_Allowed; + + ----------------- + -- Is_Power_On -- + ----------------- + + function Is_Power_On (This : NRF24L01P_Driver) return Boolean is + begin + return This.Holder.Power_On; + end Is_Power_On; + + ----------------- + -- Is_Received -- + ----------------- + + function Is_Received (This : in out NRF24L01P_Driver) return Boolean + is + TX_Full : Boolean; + Max_TX : Boolean; + TX_Sent : Boolean; + RX_Resived : Boolean; + Pipe : RX_Pipe; + begin + Get_Status (This, TX_Full, Max_TX, TX_Sent, RX_Resived, Pipe); + return RX_Resived; + end Is_Received; + + -------------------- + -- Is_Transmitted -- + -------------------- + + function Is_Transmitted (This : in out NRF24L01P_Driver) return Boolean + is + TX_Full : Boolean; + Max_TX : Boolean; + TX_Sent : Boolean; + RX_Resived : Boolean; + Pipe : RX_Pipe; + begin + Get_Status (This, TX_Full, Max_TX, TX_Sent, RX_Resived, Pipe); + return TX_Sent; + end Is_Transmitted; + + --------------------------- + -- Is_Valid_Address_Size -- + --------------------------- + + function Is_Valid_Address_Size + (This : NRF24L01P_Driver; + Pipe : RX_Pipe; + Address : Pipe_Address) + return Boolean is + begin + case Pipe is + when 0 .. 1 => + return Address'Length = This.Holder.Addr_Size; + + when 2 .. 5 => + return Address'Length = 1; + end case; + end Is_Valid_Address_Size; + + ------------------------------ + -- Is_Valid_TX_Address_Size -- + ------------------------------ + + function Is_Valid_TX_Address_Size + (This : NRF24L01P_Driver; + Address : Pipe_Address) + return Boolean is + begin + return Address'Length = This.Holder.Addr_Size; + end Is_Valid_TX_Address_Size; + + ------------------ + -- Is_Wating_RX -- + ------------------ + + function Is_Wating_RX (This : NRF24L01P_Driver) return Boolean is + begin + return Is_In_RX_Mode (This) and then This.Holder.CE_Pin.Set; + end Is_Wating_RX; + + -------------------- + -- Payload_To_Bit -- + -------------------- + + function Payload_To_Bit (Value : Payload_Length_Type) return Bit is + begin + return (if Value = Static_Payload then 0 else 1); + end Payload_To_Bit; + + ------------------- + -- Pipe_To_RX_PW -- + ------------------- + + function Pipe_To_RX_PW (Pipe : RX_Pipe) return Register_Name is + begin + case Pipe is + when 0 => return RX_PW_P0; + when 1 => return RX_PW_P1; + when 2 => return RX_PW_P2; + when 3 => return RX_PW_P3; + when 4 => return RX_PW_P4; + when 5 => return RX_PW_P5; + end case; + end Pipe_To_RX_PW; + + ---------------- + -- Power_Down -- + ---------------- + + procedure Power_Down (This : in out NRF24L01P_Driver) is + begin + Stand_By (This); -- Appendix A, 6 + + declare + Data : Register := Read_Register (This, CONFIG); + RF_Config_R : CONFIG_Register + with Import, Address => Data'Address; + begin + RF_Config_R.PWR_UP_RW := 0; + Write_Register (This, CONFIG, Data); + end; + + This.Holder.Power_On := False; + end Power_Down; + + -------------- + -- Power_Up -- + -------------- + + procedure Power_Up (This : in out NRF24L01P_Driver) + is + Data : Register := Read_Register (This, CONFIG); + RF_Config_R : CONFIG_Register + with Import, Address => Data'Address; + begin + RF_Config_R.PWR_UP_RW := 1; + Write_Register (This, CONFIG, Data); + This.Holder.Power_On := True; + end Power_Up; + + ------------------- + -- Read_Register -- + ------------------- + + function Read_Register + (This : in out NRF24L01P_Driver; + Name : Register_Name) + return Register + is + Command : HAL.SPI.SPI_Data_8b (1 .. 1); + Cmd : Command_R_REGISTER := + (Address => Registers_Addressses (Name), + Command => <>) + with Address => Command (Command'First)'Address; + + Data : HAL.SPI.SPI_Data_8b (1 .. 1); + Result : Register + with Import, Address => Data (Data'First)'Address; + begin + Write_And_Read (This, Command, Data); + return Result; + end Read_Register; + + ------------- + -- Receive -- + ------------- + + function Receive + (This : in out NRF24L01P_Driver; + Pipe : out RX_Pipe; + Have_More : out Boolean) + return TX_RX_Data + is + TX_Full : Boolean; + Max_TX : Boolean; + TX_Data : Boolean; + + Payloads : Pipes_Payloads renames This.Holder.Payloads; + begin + Get_Status (This, TX_Full, Max_TX, TX_Data, Have_More, Pipe); + + if Have_More then + declare + Buffer : TX_RX_Data + (1 .. + (if Payloads (Pipe).Payload = Dynamic_Payload + then Get_First_RX_Data_Size (This) + else Payloads (Pipe).Size)); + begin + Receive (This, Buffer); + Get_Status (This, TX_Full, Max_TX, TX_Data, Have_More, Pipe); + return Buffer; + end; + + else + declare + Empty : TX_RX_Data (1 .. 0); + begin + return Empty; + end; + end if; + end Receive; + + ------------- + -- Receive -- + ------------- + + procedure Receive + (This : in out NRF24L01P_Driver; + Data : out TX_RX_Data) + is + Buf : SPI_Data_8b (Integer (Data'First) .. Integer (Data'Last)); + begin + Write_Command_And_Read (This, Cmd_R_RX_PAYLOAD, Buf); + Data := TX_RX_Data (Buf); + + Clear_Status + (This => This, + RX_Resived => True, + TX_Sent => False, + Max_TX_Retransmits => False); + end Receive; + + ----------------------- + -- Set_Air_Data_Rate -- + ----------------------- + + procedure Set_Air_Data_Rate + (This : in out NRF24L01P_Driver; + Rate : Air_Data_Rate) + is + Data : Register := Read_Register (This, RF_SETUP); + RF_Setup_R : RF_SETUP_Register + with Import, Address => Data'Address; + begin + Set_Rate (RF_Setup_R, Rate); + Write_Register (This, RF_SETUP, Data); + end Set_Air_Data_Rate; + + ------------------------- + -- Set_Amplifier_Power -- + ------------------------- + + procedure Set_Amplifier_Power + (This : in out NRF24L01P_Driver; + Power : Amplifier_Power) + is + Data : Register := Read_Register (This, RF_SETUP); + RF_Setup_R : RF_SETUP_Register + with Import, Address => Data'Address; + begin + Set_Power (RF_Setup_R, Power); + Write_Register (This, RF_SETUP, Data); + end Set_Amplifier_Power; + + ------------------------- + -- Set_Auto_Retransmit -- + ------------------------- + + procedure Set_Auto_Retransmit + (This : in out NRF24L01P_Driver; + Count : Auto_Retransmit_Count; + A_Delay : Auto_Retransmit_Delay) + is + Data : Register := Read_Register (This, SETUP_RETR); + Setup_Retr_R : SETUP_RETR_Register + with Import, Address => Data'Address; + begin + Setup_Retr_R.ARC_RW := Count; + Setup_Retr_R.ARD_RW := A_Delay; + Write_Register (This, SETUP_RETR, Data); + end Set_Auto_Retransmit; + + ------------------------------- + -- Set_Auto_Retransmit_Count -- + ------------------------------- + + procedure Set_Auto_Retransmit_Count + (This : in out NRF24L01P_Driver; + Count : Auto_Retransmit_Count) + is + Data : Register := Read_Register (This, SETUP_RETR); + Setup_Retr_R : SETUP_RETR_Register + with Import, Address => Data'Address; + begin + Setup_Retr_R.ARC_RW := Count; + Write_Register (This, SETUP_RETR, Data); + end Set_Auto_Retransmit_Count; + + ------------------------------- + -- Set_Auto_Retransmit_Delay -- + ------------------------------- + + procedure Set_Auto_Retransmit_Delay + (This : in out NRF24L01P_Driver; + A_Delay : Auto_Retransmit_Delay) + is + Data : Register := Read_Register (This, SETUP_RETR); + Setup_Retr_R : SETUP_RETR_Register + with Import, Address => Data'Address; + begin + Setup_Retr_R.ARD_RW := A_Delay; + Write_Register (This, SETUP_RETR, Data); + end Set_Auto_Retransmit_Delay; + + ------------- + -- Set_CRC -- + ------------- + + procedure Set_CRC + (This : in out NRF24L01P_Driver; + CRC : CRC_Length) + is + Data : Register := Read_Register (This, CONFIG); + RF_Config_R : CONFIG_Register + with Import, Address => Data'Address; + begin + case CRC is + when Disabled => + RF_Config_R.EN_CRC_RW := 0; + RF_Config_R.CRCO_RW := 0; + + when Enabled_1byte => + RF_Config_R.EN_CRC_RW := 1; + RF_Config_R.CRCO_RW := 0; + + when Enabled_2byte => + RF_Config_R.EN_CRC_RW := 1; + RF_Config_R.CRCO_RW := 1; + end case; + + Write_Register (This, CONFIG, Data); + end Set_CRC; + + ------------------ + -- Set_Features -- + ------------------ + + procedure Set_Features + (This : in out NRF24L01P_Driver; + Dynamic_Payload : Boolean; + ACK_Payload : Boolean; + NO_ACK_Allowed : Boolean) + is + Data : Register := Read_Register (This, FEATURE); + Feature_R : FEATURE_Register + with Import, Address => Data'Address; + begin + Feature_R.EN_DPL_RW := Boolean_To_Bit (Dynamic_Payload); + Feature_R.EN_ACK_PAY_RW := Boolean_To_Bit (ACK_Payload); + Feature_R.EN_DYN_ACK_RW := Boolean_To_Bit (NO_ACK_Allowed); + + Write_Register (This, FEATURE, Data); + This.Holder.NO_ACK := NO_ACK_Allowed; + end Set_Features; + + ------------------------------- + -- Set_Max_Pipe_Address_Size -- + ------------------------------- + + procedure Set_Max_Pipe_Address_Size + (This : in out NRF24L01P_Driver; + Size : Pipe_Address_Size) + is + Data : Register := Read_Register (This, SETUP_AW); + Setup_Aw_R : SETUP_AW_Register + with Import, Address => Data'Address; + begin + case Size is + when 3 => + Setup_Aw_R.AW_RW := 2#01#; + when 4 => + Setup_Aw_R.AW_RW := 2#10#; + when 5 => + Setup_Aw_R.AW_RW := 2#11#; + end case; + + Write_Register (This, SETUP_AW, Data); + This.Holder.Addr_Size := Size; + end Set_Max_Pipe_Address_Size; + + -------------- + -- Set_Mode -- + -------------- + + procedure Set_Mode + (This : in out NRF24L01P_Driver; + Mode : PX_Mode) + is + Data : Register := Read_Register (This, CONFIG); + CONFIG_R : CONFIG_Register + with Import, Address => Data'Address; + begin + CONFIG_R.PRIM_RX_RW := (if Mode = PRX then 1 else 0); + Write_Register (This, CONFIG, Data); + This.Holder.Mode := Mode; + end Set_Mode; + + -------------------- + -- Enable_RX_Pipe -- + -------------------- + + procedure Set_En_Rxaddr + (Reg : in out EN_RXADDR_Register; + Pipe : RX_Pipe; + Data : Bit) is + begin + case Pipe is + when 0 => + Reg.ERX_P0_RW := Data; + when 1 => + Reg.ERX_P1_RW := Data; + when 2 => + Reg.ERX_P2_RW := Data; + when 3 => + Reg.ERX_P3_RW := Data; + when 4 => + Reg.ERX_P4_RW := Data; + when 5 => + Reg.ERX_P5_RW := Data; + end case; + end Set_En_Rxaddr; + + ------------------------------ + -- Set_RF_Channel_Frequency -- + ------------------------------ + + procedure Set_RF_Channel_Frequency + (This : in out NRF24L01P_Driver; + Chanel : RF_Channel_Frequency) + is + Data : Register := Read_Register (This, RF_CH); + RF_CH_R : RF_CH_Register + with Import, Address => Data'Address; + begin + RF_CH_R.RF_CH_RW := Chanel; + + Write_Register (This, RF_CH, Data); + end Set_RF_Channel_Frequency; + + -------------------- + -- Set_TX_Address -- + -------------------- + + procedure Set_TX_Address + (This : in out NRF24L01P_Driver; + Addr : Pipe_Address) + is + Size : Pipe_Address_Size renames This.Holder.Addr_Size; + Data : HAL.SPI.SPI_Data_8b (1 .. 1 + Positive (Size)); + CMD : Command_W_REGISTER := + (Address => Registers_Addressses (TX_ADDR), + Command => <>) + with Address => Data (Data'First)'Address; + Local : Pipe_Address (1 .. Pipe_Address_Index (Size)) := + Addr (1 .. Pipe_Address_Index (Size)) + with Address => Data (Data'First + 1)'Address; + begin + Write (This, Data); + end Set_TX_Address; + + --------------------- + -- Start_Wating_RX -- + --------------------- + + procedure Start_Wating_RX (This : in out NRF24L01P_Driver) is + begin + Clear_RX_Buffer (This); + Clear_TX_Buffer (This); + Clear_Status (This); + + This.Holder.CE_Pin.Set; + end Start_Wating_RX; + + -------------- + -- Transmit -- + -------------- + + procedure Transmit + (This : in out NRF24L01P_Driver; + Data : TX_RX_Data; + No_ACK : Boolean := False) + is + Buffer : HAL.SPI.SPI_Data_8b (1 .. 1 + Data'Length); + + begin + Buffer (Buffer'First) := UInt8 + (if No_ACK then Cmd_W_TX_PAYLOAD_NO_ACK else Cmd_W_TX_PAYLOAD); + Buffer (Buffer'First + 1 .. Buffer'Last) := HAL.SPI.SPI_Data_8b (Data); + Write (This, Buffer); + + This.Holder.CE_Pin.Set; + end Transmit; + + -------------- + -- Stand_By -- + -------------- + + procedure Stand_By (This : in out NRF24L01P_Driver) is + begin + This.Holder.CE_Pin.Clear; + end Stand_By; + + --------------- + -- Set_Power -- + --------------- + + procedure Set_Power + (RF_Setup : in out RF_SETUP_Register; + Power : Amplifier_Power) is + begin + RF_Setup.RF_PWR_RW := Power; + end Set_Power; + + -------------- + -- Set_Rate -- + -------------- + + procedure Set_Rate + (RF_Setup : in out RF_SETUP_Register; + Rate : Air_Data_Rate) is + begin + case Rate is + when Rate_250kbps => + RF_Setup.RF_DR_LOW_RW := 1; + RF_Setup.RF_DR_HIGH_RW := 0; + + when Rate_1Mbps => + RF_Setup.RF_DR_LOW_RW := 0; + RF_Setup.RF_DR_HIGH_RW := 0; + + when Rate_2Mbps => + RF_Setup.RF_DR_LOW_RW := 0; + RF_Setup.RF_DR_HIGH_RW := 1; + end case; + end Set_Rate; + + ---------------- + -- To_RX_ADDR -- + ---------------- + + function To_RX_ADDR (Pipe : RX_Pipe) return Register_Address is + begin + case Pipe is + when 0 => return Registers_Addressses (RX_ADDR_P0); + when 1 => return Registers_Addressses (RX_ADDR_P1); + when 2 => return Registers_Addressses (RX_ADDR_P2); + when 3 => return Registers_Addressses (RX_ADDR_P3); + when 4 => return Registers_Addressses (RX_ADDR_P4); + when 5 => return Registers_Addressses (RX_ADDR_P5); + end case; + end To_RX_ADDR; + + ----------- + -- Write -- + ----------- + + procedure Write + (This : in out NRF24L01P_Driver; + Data : HAL.SPI.SPI_Data_8b) + is + Status : HAL.SPI.SPI_Status; + begin + CSN_Low (This); + This.Holder.SPI.Transmit (Data, Status); + CSN_High (This); + + if Status /= HAL.SPI.Ok then + raise Program_Error; + end if; + end Write; + + ----------------------- + -- Write_ACK_Payload -- + ----------------------- + + procedure Write_ACK_Payload + (This : in out NRF24L01P_Driver; + Pipe : RX_Pipe; + Data : TX_RX_Data) + is + Buffer : HAL.SPI.SPI_Data_8b (1 .. Data'Length + 1); + Command : Command_W_ACK_PAYLOAD := (Pipe => Pipe, Command => <>) + with Address => Buffer (Buffer'First)'Address; + Local : TX_RX_Data := Data + with Address => Buffer (Buffer'First + 1)'Address; + begin + Command.Pipe := Pipe; + Write (This, Buffer); + end Write_ACK_Payload; + + -------------------- + -- Write_And_Read -- + -------------------- + + procedure Write_And_Read + (This : in out NRF24L01P_Driver; + Command : HAL.SPI.SPI_Data_8b; + Data : out HAL.SPI.SPI_Data_8b) + is + Status : HAL.SPI.SPI_Status; + begin + CSN_Low (This); + This.Holder.SPI.Transmit (Command, Status); + if Status /= HAL.SPI.Ok then + CSN_High (This); + raise Program_Error; + end if; + + This.Holder.SPI.Receive (Data, Status); + CSN_High (This); + + if Status /= HAL.SPI.Ok then + raise Program_Error; + end if; + end Write_And_Read; + + ------------------- + -- Write_Command -- + ------------------- + + procedure Write_Command + (This : in out NRF24L01P_Driver; + Cmd : Command) + is + Buffer : HAL.SPI.SPI_Data_8b (1 .. 1); + Local : Command := Cmd + with Address => Buffer (Buffer'First)'Address; + begin + Write (This, Buffer); + end Write_Command; + + ---------------------------- + -- Write_Command_And_Read -- + ---------------------------- + + procedure Write_Command_And_Read + (This : in out NRF24L01P_Driver; + Cmd : Command; + Data : out HAL.SPI.SPI_Data_8b) + is + Buffer : HAL.SPI.SPI_Data_8b (1 .. 1); + Local : Command := Cmd + with Address => Buffer (Buffer'First)'Address; + begin + Write_And_Read (This, Buffer, Data); + end Write_Command_And_Read; + + -------------------- + -- Write_Register -- + -------------------- + + procedure Write_Register + (This : in out NRF24L01P_Driver; + Name : Register_Name; + Data : Register) + is + Buffer : HAL.SPI.SPI_Data_8b (1 .. 2); + Cmd : Command_W_REGISTER := + (Address => Registers_Addressses (Name), + Command => <>) + with Address => Buffer (Buffer'First)'Address; + + Local : Register := Data + with Address => Buffer (Buffer'First + 1)'Address; + begin + Write (This, Buffer); + end Write_Register; + +end NRF24L01P; diff --git a/components/src/radio/nrf24l01p/nrf24l01p.ads b/components/src/radio/nrf24l01p/nrf24l01p.ads new file mode 100644 index 000000000..19d5054e8 --- /dev/null +++ b/components/src/radio/nrf24l01p/nrf24l01p.ads @@ -0,0 +1,917 @@ +------------------------------------------------------------------------------ +-- -- +-- Copyright (C) 2025, 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. -- +-- -- +------------------------------------------------------------------------------ + +-- Driver for NRF24L01P chip + +with HAL; use HAL; +with HAL.GPIO; use HAL.GPIO; +with HAL.SPI; use HAL.SPI; + +package NRF24L01P is + pragma Extensions_Allowed (On); + + type NRF24L01P_Driver is limited private; + + type PX_Mode is (PTX, PRX); + -- Transiver or responser + + type Amplifier_Power is + (Power_Minimum, Power_Low, Power_High, Power_Maximum); + -- PA control is used to set the output power from the amplifier. + + type Air_Data_Rate is (Rate_250kbps, Rate_1Mbps, Rate_2Mbps); + -- The air data rate. A transmitter and a receiver must be + -- configured with the same air data rate to communicate with each other. + -- Should be set to 1Mbps or 250kbps for backward compatibility with the + -- nRF2401 nRF2401A, nRF2402, nRF24E1 and nRF24E2. + + type CRC_Length is (Disabled, Enabled_1byte, Enabled_2byte); + -- The CRC is the mandatory error detection mechanism in the packet. + -- It is either 1 or 2 bytes and is calculated over the address, + -- Packet Control Field and Payload. + + type RF_Channel_Frequency is range 0 .. 63 with Size => 7; + -- The RF channel frequency determines the center of the channel used by + -- the nRF24L01+ F0= 2400 + RF_CH [MHz] + + type Configuration is record + Mode : PX_Mode; + Power : Amplifier_Power; + Rate : Air_Data_Rate; + CRC : CRC_Length; + Chanel : RF_Channel_Frequency; + RX_IRQ : Boolean := True; + TX_IRQ : Boolean := True; + MAX_TR_IRQ : Boolean := True; + end record; + -- Holds configuration data which is used for initial chip configuraton. + -- RX_IRQ, TX_IRQ, MAX_TR_IRQ controls whether IRQ should be generated. + + procedure Initialize + (This : out NRF24L01P_Driver; + CE_Pin : HAL.GPIO.Any_GPIO_Point; + CSN_Pin : HAL.GPIO.Any_GPIO_Point; + SPI : HAL.SPI.Any_SPI_Port; + Config : Configuration) + with Pre => (CE_Pin.Mode = Output and then + CSN_Pin.Mode = Output and then + SPI.Data_Size = Data_Size_8b); + -- Creates a NRF24L01P_Driver instance for SPI with SOFTWARE slave + -- selection. Only 3 SPI wires will be used. NSS pin from board's SPI + -- is not used. CSN_Pin is used instead. It should be attached to + -- CSN (pin 2) on NRF24L01P. CSN_Pin should be initialized for OUTPUT + -- mode. CE_Pin will be used to control TX/RX and should be connected + -- to CE (pin 1) on NRF24L01P. Should be in OUTPUT mode. Config is used + -- for the initial configuration. SPI should have the following + -- configuration: Master, FullDuplex, Data_Size_8b, Low/P1Edge, + -- Software_Managed, 0-10Mbps, MSB. + -- IRQ pin should be configured as: + -- Mode_In, Floating, Interrupt_Falling_Edge. + + function Can_Be_Configured (This : NRF24L01P_Driver) return Boolean; + -- Returns True when the chip can be configured (power off or standby mode) + + procedure Set_Mode + (This : in out NRF24L01P_Driver; + Mode : PX_Mode) + with Pre => (Can_Be_Configured (This)); + -- Sets transiver or responser mode + + procedure Set_Amplifier_Power + (This : in out NRF24L01P_Driver; + Power : Amplifier_Power) + with Pre => (Can_Be_Configured (This)); + -- Sets the output power of the amplifier + + procedure Set_Air_Data_Rate + (This : in out NRF24L01P_Driver; + Rate : Air_Data_Rate) + with Pre => (Can_Be_Configured (This)); + -- Sets the air data rate. + -- Should be set to 1Mbps or 250kbps for backward compatibility with the + -- nRF2401 nRF2401A, nRF2402, nRF24E1 and nRF24E2. + + procedure Set_CRC + (This : in out NRF24L01P_Driver; + CRC : CRC_Length) + with Pre => (Can_Be_Configured (This)); + -- Enable/disable CRC. + -- Forced enabled when Enhanced ShockBurst is enabled. + + procedure Set_RF_Channel_Frequency + (This : in out NRF24L01P_Driver; + Chanel : RF_Channel_Frequency) + with Pre => (Can_Be_Configured (This)); + -- Sets the RF channel frequency. + + type Auto_Retransmit_Count is range 0 .. 15 with Size => 4; + -- Count of attempts to retransmit data when ACK packet is not recived back + + procedure Set_Auto_Retransmit_Count + (This : in out NRF24L01P_Driver; + Count : Auto_Retransmit_Count) + with Pre => (Can_Be_Configured (This)); + -- Sets count of attempts to retransmit data when ACK packet + -- is not recived back. Should be 0 for backward compatibility with the + -- nRF2401 nRF2401A, nRF2402, nRF24E1 and nRF24E2. + + type Auto_Retransmit_Delay is range 0 .. 15 with Size => 4; + -- Waiting for ACK packet time before retransmission + + procedure Set_Auto_Retransmit_Delay + (This : in out NRF24L01P_Driver; + A_Delay : Auto_Retransmit_Delay) + with Pre => (Can_Be_Configured (This)); + -- Set waiting for ACK packet time before retransmission + -- Please take care when setting this parameter. If the ACK payload + -- is more than 15 byte in 2Mbps mode the ARD must be 500µS or more, + -- if the ACK payload is more than 5byte in 1Mbps mode the ARD must be + -- 500µS or more. In 250kbps mode (even when the payload is not in ACK) + -- the ARD must be 500µS or more. + + procedure Set_Auto_Retransmit + (This : in out NRF24L01P_Driver; + Count : Auto_Retransmit_Count; + A_Delay : Auto_Retransmit_Delay) + with Pre => (Can_Be_Configured (This)); + -- The same as two above. + + type Pipe_Address_Index is range 1 .. 5; + -- Used as an index for address arrays + + type Pipe_Address_Size is range 3 .. 5; + -- Used to define address size for RX0, RX1 and TX pipes + + procedure Set_Max_Pipe_Address_Size + (This : in out NRF24L01P_Driver; + Size : Pipe_Address_Size) + with Pre => (Can_Be_Configured (This)); + -- Sets the address sizes for RX pipes 0, 1 and TX 0 + + type RX_Pipe is range 0 .. 5; + -- Number of the RX pipe + + type Pipe_Address is array (Pipe_Address_Index range <>) of UInt8; + -- Used to set address for pipes + + function Is_Valid_Address_Size + (This : NRF24L01P_Driver; + Pipe : RX_Pipe; + Address : Pipe_Address) + return Boolean; + -- Checks that Address'Length = Max_Pipe_Address_Size for RX0, RX1 + -- and 1 for RX2-5 + + type Payload_Length_Type is (Static_Payload, Dynamic_Payload); + + type Payload_Length is range 0 .. 32; + + type Pipe_Payload + (Payload : Payload_Length_Type := Static_Payload) + is record + case Payload is + when Static_Payload => + Size : Payload_Length := 0; + when Dynamic_Payload => + null; + end case; + end record; + + function Is_ACK_Allowed + (ACK : Boolean; + Dynamic_Payload : Boolean) + return Boolean; + -- Return True if ACK=False or both ACK & Dynamic_Payload are True + + procedure Configure_And_Enable_RX_Pipe + (This : in out NRF24L01P_Driver; + Pipe : RX_Pipe; + Addr : Pipe_Address; -- Address that will be used for pipe + ACK : Boolean; -- Enable ACK for pipe + Payload : Pipe_Payload) -- Payload size + with Pre => (Can_Be_Configured (This) + and then Is_Valid_Address_Size (This, Pipe, Addr) + and then Is_ACK_Allowed + (ACK, Payload.Payload = Dynamic_Payload)); + -- Configures and enables the RX pipe. + -- For PRX each pipe should have different address to accept a data from + -- different transmitters. + -- ACK_Payload should be enabled with Set_Features if ACK is True. + -- + -- !!! For PTX: + -- TX0 and RX0 pipes should have the same addresses. + -- RX0 pipe should have DPL enabled if receiver uses DPL/ACK + + function Is_Valid_TX_Address_Size + (This : NRF24L01P_Driver; + Address : Pipe_Address) + return Boolean; + -- Checks that Address'Length = Max_Pipe_Address_Size + + procedure Set_TX_Address + (This : in out NRF24L01P_Driver; + Addr : Pipe_Address) + with Pre => (Can_Be_Configured (This) and then + Is_Valid_TX_Address_Size (This, Addr)); + -- Sets address which will be used for TX pipe in PTX mode. + -- !!! The same address should be set for the TX(n) and RX0 pipe. + + procedure Enable_RX_Pipe + (This : in out NRF24L01P_Driver; + Pipe : RX_Pipe) + with Pre => (Can_Be_Configured (This)); + -- Enables the RX pipe, does not change its configuration + + procedure Disable_RX_Pipe + (This : in out NRF24L01P_Driver; + Pipe : RX_Pipe) + with Pre => (Can_Be_Configured (This)); + -- Disables the RX pipe + + procedure Set_Features + (This : in out NRF24L01P_Driver; + Dynamic_Payload : Boolean; -- Enables Dynamic Payload Length (DPL) + ACK_Payload : Boolean; -- Enables Payload with ACK + NO_ACK_Allowed : Boolean) -- Enables the W_TX_PAYLOAD_NOACK command + with Pre => (Can_Be_Configured (This) and then + Is_ACK_Allowed (ACK_Payload, Dynamic_Payload)); + -- Sets chip's features + -- + -- A PTX that transmits to a PRX with DPL enabled must have the DPL + -- enabled for RX0. + -- DPL should be off for backward compatibility with the nRF2401 nRF2401A, + -- nRF2402, nRF24E1 and nRF24E2. + -- + -- If ACK packet payload is activated the DPL feature should be enabled + -- for PTX and RTX. + -- On PTX side RX0 pipe should have DPL. + -- On PRX side DPL should be enabled for the pipe that will accept data + -- from transmitter with DPL. + -- + -- The PTX can send NO_ACK for PRX if NO_ACK_Allowed is True. + -- The PRX does not transmit an ACK packet back in this case. + + procedure Configure_IRQ + (This : in out NRF24L01P_Driver; + RX_IRQ : Boolean; + TX_IRQ : Boolean; + MAX_TR_IRQ : Boolean); + -- Enables/Disables IRQs + + function Is_Power_On (This : NRF24L01P_Driver) return Boolean; + -- Returns True when the chip is powered on + + procedure Power_Up (This : in out NRF24L01P_Driver) + with Pre => (not Is_Power_On (This)); + + procedure Power_Down (This : in out NRF24L01P_Driver) + with Pre => (Is_Power_On (This)); + + type TX_RX_Data is array (Payload_Length range 1 .. <>) of UInt8; + + function Is_No_ACK_Allowed + (This : NRF24L01P_Driver; + No_ACK : Boolean) return Boolean; + -- Returns True when No_ACK=False or FEATURE.EN_DYN_ACK is set + + procedure Transmit + (This : in out NRF24L01P_Driver; + Data : TX_RX_Data; + No_ACK : Boolean := False) + with Pre => (Is_Power_On (This) and then + not Is_In_RX_Mode (This) and then + Is_No_ACK_Allowed (This, No_ACK)); + -- Trasmits the data. No ACK will be send back if No_ACK=True + + function Is_Transmitted (This : in out NRF24L01P_Driver) return Boolean; + -- Returns True if TX has sent data. + -- Technically returns Status.TX_Sent + + function Have_ACK_Payload (This : in out NRF24L01P_Driver) return Boolean; + -- Returns True if RX side has sent data back with ACK. + -- Technically returns Status.RX_Resived + + function Is_In_RX_Mode (This : NRF24L01P_Driver) return Boolean; + -- Returns True when the chip is in RX mode + + function Is_Wating_RX (This : NRF24L01P_Driver) return Boolean; + -- Returns True when the chip is wait for RX data + + procedure Start_Wating_RX (This : in out NRF24L01P_Driver) + with Pre => (Is_Power_On (This) and then + Is_In_RX_Mode (This) and then + not Is_Wating_RX (This)); + -- Device will wait for data + + function Is_Received (This : in out NRF24L01P_Driver) return Boolean; + -- Returns True if RX has got data. + -- Technically returns Status.RX_Resived + + function Receive + (This : in out NRF24L01P_Driver; + Pipe : out RX_Pipe; + Have_More : out Boolean) + return TX_RX_Data; + -- Gets RX data size, read data from the buffer, clear RX_Resived status + -- flag. Fills Pipe with the pipe number where data was. + -- Set Have_More = True if RX buffer contains more data recived. + -- RX_Resived flag should not be cleared (by Clear_Status) before this + -- method called. + + procedure Write_ACK_Payload + (This : in out NRF24L01P_Driver; + Pipe : RX_Pipe; + Data : TX_RX_Data); + -- Upload data that will be send back to the TX when the next + -- packet is transived. + + procedure Stand_By (This : in out NRF24L01P_Driver) + with Pre => (Is_Power_On (This)); + -- Stops waiting for data or transmitting it + + ------------------------- + -- "Low level" methods -- + ------------------------- + + procedure Get_Status + (This : in out NRF24L01P_Driver; + TX_Full : out Boolean; + Max_TX_Retransmits : out Boolean; + TX_Sent : out Boolean; + RX_Resived : out Boolean; + Pipe : out RX_Pipe); + -- Returns current chip status + + procedure Clear_Status + (This : in out NRF24L01P_Driver; + RX_Resived : Boolean := True; + TX_Sent : Boolean := True; + Max_TX_Retransmits : Boolean := True); + + function Get_First_RX_Data_Size + (This : in out NRF24L01P_Driver) + return Payload_Length; + -- Returns the data's size which is the first in the buffer's queue + + procedure Receive + (This : in out NRF24L01P_Driver; + Data : out TX_RX_Data); + -- Gets data from the RX buffer and clear RX_Resived status flag + + procedure Clear_RX_Buffer (This : in out NRF24L01P_Driver); + -- Deletes all data from the RX buffer + + procedure Clear_TX_Buffer (This : in out NRF24L01P_Driver); + -- Deletes all data from the TX buffer + +private + + type Register is new UInt8; + type Register_Address is new UInt5; + + for Amplifier_Power use + (Power_Minimum => 2#00#, -- Power -18dBm, DC 7.0mA + Power_Low => 2#01#, -- Power -12dBm, DC 7.5mA + Power_High => 2#10#, -- Power -6dBm, DC 9.0mA + Power_Maximum => 2#11#); -- Power 0dBm, DC 11.3mA + for Amplifier_Power'Size use 2; + + ---------------- + -- Registers -- + ---------------- + + type CONFIG_Register is record + PRIM_RX_RW : Bit; + -- RX/TX control 1: PRX, 0: PTX + + PWR_UP_RW : Bit; + -- 1: POWER UP, 0:POWER DOWN + + CRCO_RW : Bit; + -- CRC encoding scheme. + -- '0' - 1 byte + -- '1' - 2 bytes + + EN_CRC_RW : Bit; + -- Enable CRC. + -- Forced high if one of the bits in the EN_AA is high + + MASK_MAX_RT_RW : Bit; + -- Mask interrupt caused by MAX_RT. + -- 1: Interrupt not reflected on the IRQ pin + -- 0: Reflect MAX_RT as active low interrupt on the IRQ pin + + MASK_TX_DS_RW : Bit; + -- Mask interrupt caused by TX_DS + -- 1: Interrupt not reflected on the IRQ pin + -- 0: Reflect TX_DS as active low interrupt on the IRQ pin + + MASK_RX_DR_RW : Bit; + -- Mask interrupt caused by RX_DR + -- 1: Interrupt not reflected on the IRQ pin + -- 0: Reflect RX_DR as active low interrupt on the pin + + Reserved_RW : Bit := 0; + end record with Size => Register'Size; + + for CONFIG_Register use record + PRIM_RX_RW at 0 range 0 .. 0; + PWR_UP_RW at 0 range 1 .. 1; + CRCO_RW at 0 range 2 .. 2; + EN_CRC_RW at 0 range 3 .. 3; + MASK_MAX_RT_RW at 0 range 4 .. 4; + MASK_TX_DS_RW at 0 range 5 .. 5; + MASK_RX_DR_RW at 0 range 6 .. 6; + Reserved_RW at 0 range 7 .. 7; + end record; + + -- EN_AA_Register -- + + type EN_AA_Register is record + ENAA_P0_RW : Bit; + ENAA_P1_RW : Bit; + ENAA_P2_RW : Bit; + ENAA_P3_RW : Bit; + ENAA_P4_RW : Bit; + ENAA_P5_RW : Bit; + Reserved_RW : UInt2 := 0; + end record with Size => Register'Size; + + for EN_AA_Register use record + ENAA_P0_RW at 0 range 0 .. 0; + ENAA_P1_RW at 0 range 1 .. 1; + ENAA_P2_RW at 0 range 2 .. 2; + ENAA_P3_RW at 0 range 3 .. 3; + ENAA_P4_RW at 0 range 4 .. 4; + ENAA_P5_RW at 0 range 5 .. 5; + Reserved_RW at 0 range 6 .. 7; + end record; + + -- EN_RXADDR_Register -- + + type EN_RXADDR_Register is record + ERX_P0_RW : Bit; + ERX_P1_RW : Bit; + ERX_P2_RW : Bit; + ERX_P3_RW : Bit; + ERX_P4_RW : Bit; + ERX_P5_RW : Bit; + Reserved_RW : UInt2 := 0; + end record with Size => Register'Size; + + for EN_RXADDR_Register use record + ERX_P0_RW at 0 range 0 .. 0; + ERX_P1_RW at 0 range 1 .. 1; + ERX_P2_RW at 0 range 2 .. 2; + ERX_P3_RW at 0 range 3 .. 3; + ERX_P4_RW at 0 range 4 .. 4; + ERX_P5_RW at 0 range 5 .. 5; + Reserved_RW at 0 range 6 .. 7; + end record; + + -- SETUP_AW_Register -- + + type SETUP_AW_Register is record + AW_RW : UInt2; + -- RX/TX Address field width + + Reserved_RW : UInt6 := 0; + end record with Size => Register'Size; + + for SETUP_AW_Register use record + AW_RW at 0 range 0 .. 1; + Reserved_RW at 0 range 2 .. 7; + end record; + + -- SETUP_RETR_Register -- + + type SETUP_RETR_Register is record + ARC_RW : Auto_Retransmit_Count; + -- Auto Retransmit Count + ARD_RW : Auto_Retransmit_Delay; + -- Auto Retransmit Delay + end record with Size => Register'Size; + + for SETUP_RETR_Register use record + ARC_RW at 0 range 0 .. 3; + ARD_RW at 0 range 4 .. 7; + end record; + + -- RF_CH_Register -- + + type RF_CH_Register is record + RF_CH_RW : RF_Channel_Frequency; + -- Sets the frequency channel device operates + + Reserved_RW : Bit := 0; + end record with Size => Register'Size; + + for RF_CH_Register use record + RF_CH_RW at 0 range 0 .. 6; + Reserved_RW at 0 range 7 .. 7; + end record; + + -- RF_SETUP_Register -- + + type RF_SETUP_Register is record + Obsolete_NA : Bit := 0; + -- Don't care + + RF_PWR_RW : Amplifier_Power; + -- Set RF output power in TX mode + + RF_DR_HIGH_RW : Bit; + -- Select between the high speed data rates. This bit is don't + -- care if RF_DR_LOW is set. + + PLL_LOCK_RW : Bit; + -- Force PLL lock signal. Only used in test + + RF_DR_LOW_RW : Bit; + -- Set RF Data Rate to 250kbps. + + Reserved_RW : Bit := 0; + -- Only '0' allowed + + CONT_WAVE_RW : Bit; + -- Enables continuous carrier transmit when high. + end record with Size => Register'Size; + + for RF_SETUP_Register use record + Obsolete_NA at 0 range 0 .. 0; + RF_PWR_RW at 0 range 1 .. 2; + RF_DR_HIGH_RW at 0 range 3 .. 3; + PLL_LOCK_RW at 0 range 4 .. 4; + RF_DR_LOW_RW at 0 range 5 .. 5; + Reserved_RW at 0 range 6 .. 6; + CONT_WAVE_RW at 0 range 7 .. 7; + end record; + + -- STATUS_Register -- + + type STATUS_Register is record + TX_FULL_RO : Bit; + -- TX FIFO full flag + + RX_P_NO_RO : UInt3; + -- Data pipe number for the payload available for reading from RX_FIFO + -- 000-101: Data Pipe Number + -- 110: Not Used + -- 111: RX FIFO Empty + + MAX_RT_RW : Bit; + -- Maximum number of TX retransmits interrupt. Write 1 to clear bit. + -- If is asserted it must be cleared to enable further communication. + + TX_DS_RW : Bit; + -- Data Sent TX FIFO interrupt. Asserted when packet transmitted on TX. + -- If AUTO_ACK is activated, this bit is set high only when ACK is + -- received. Write 1 to clear bit. + + RX_DR_RW : Bit; + -- Data Ready RX FIFO interrupt. Asserted when new data arrives RX + -- FIFOc. Write 1 to clear bit. + + Reserved_RW : Bit := 0; + -- Only '0' allowed. + end record with Size => Register'Size; + + for STATUS_Register use record + TX_FULL_RO at 0 range 0 .. 0; + RX_P_NO_RO at 0 range 1 .. 3; + MAX_RT_RW at 0 range 4 .. 4; + TX_DS_RW at 0 range 5 .. 5; + RX_DR_RW at 0 range 6 .. 6; + Reserved_RW at 0 range 7 .. 7; + end record; + + -- OBSERVE_TX_Register -- + + type OBSERVE_TX_Register is record + ARC_CNT_RO : UInt4; + -- Count retransmitted packets. The counter is reset + -- when transmission of a new packet starts + + PLOS_CNT_RO : UInt4; + -- Count lost packets. The counter is overflow protected to 15, + -- and discontinues at max until reset. The counter is reset by + -- writing to RF_CH. + end record with Size => Register'Size; + + for OBSERVE_TX_Register use record + ARC_CNT_RO at 0 range 0 .. 3; + PLOS_CNT_RO at 0 range 4 .. 7; + end record; + + -- RPD_Register -- + + type RPD_Register is record + RPD_RO : Bit; + -- Received Power Detector. This register is called CD (Carrier Detect) + -- in the nRF24L01. The name is different in nRF24L01+ due to the + -- different input power level threshold for this bit. + + Reserved_RO : UInt7 := 0; + end record with Size => Register'Size; + + for RPD_Register use record + RPD_RO at 0 range 0 .. 0; + Reserved_RO at 0 range 1 .. 7; + end record; + + -- RX_PW_PX_Register -- + + type RX_PW_PX_Register is record + RX_PW_PX_RW : UInt6; + -- Number of bytes in RX payload in data pipe X + Reserved_RW : UInt2 := 0; + end record with Size => Register'Size; + + for RX_PW_PX_Register use record + RX_PW_PX_RW at 0 range 0 .. 5; + Reserved_RW at 0 range 6 .. 7; + end record; + + -- FIFO_STATUS_Register -- + + type FIFO_STATUS_Register is record + RX_EMPTY_RO : Bit; -- 1: RX FIFO empty. + RX_FULL_RO : Bit; -- 1: RX FIFO full. + Reserved_RW : UInt2 := 0; + TX_EMPTY_RO : Bit; -- 1: TX FIFO empty. + TX_FULL_RO : Bit; -- 1: TX FIFO full. + TX_REUSE_RO : Bit; -- Reuse last transmitted payload + Reserved_RW2 : Bit := 0; + end record with Size => Register'Size; + + for FIFO_STATUS_Register use record + RX_EMPTY_RO at 0 range 0 .. 0; + RX_FULL_RO at 0 range 1 .. 1; + Reserved_RW at 0 range 2 .. 3; + TX_EMPTY_RO at 0 range 4 .. 4; + TX_FULL_RO at 0 range 5 .. 5; + TX_REUSE_RO at 0 range 6 .. 6; + Reserved_RW2 at 0 range 7 .. 7; + end record; + + -- DYNPD_Register -- + + type DYNPD_Register is record + DPL_P0_RW : Bit; + DPL_P1_RW : Bit; + DPL_P2_RW : Bit; + DPL_P3_RW : Bit; + DPL_P4_RW : Bit; + DPL_P5_RW : Bit; + Reserved_RW : UInt2 := 0; + end record with Size => Register'Size; + + for DYNPD_Register use record + DPL_P0_RW at 0 range 0 .. 0; + DPL_P1_RW at 0 range 1 .. 1; + DPL_P2_RW at 0 range 2 .. 2; + DPL_P3_RW at 0 range 3 .. 3; + DPL_P4_RW at 0 range 4 .. 4; + DPL_P5_RW at 0 range 5 .. 5; + Reserved_RW at 0 range 6 .. 7; + end record; + + -- FEATURE_Register -- + + type FEATURE_Register is record + EN_DYN_ACK_RW : Bit; + -- Enables the W_TX_PAYLOAD_NOACK command + + EN_ACK_PAY_RW : Bit; + -- Enables Payload with ACK + + EN_DPL_RW : Bit; + -- Enables Dynamic Payload Length + + Reserved_RW : UInt5 := 0; + end record with Size => Register'Size; + + for FEATURE_Register use record + EN_DYN_ACK_RW at 0 range 0 .. 0; + EN_ACK_PAY_RW at 0 range 1 .. 1; + EN_DPL_RW at 0 range 2 .. 2; + Reserved_RW at 0 range 3 .. 7; + end record; + + -- Register_Name -- + + type Register_Name is + (CONFIG, EN_AA, EN_RXADDR, SETUP_AW, SETUP_RETR, RF_CH, RF_SETUP, STATUS, + OBSERVE_TX, RPD, + RX_ADDR_P0, RX_ADDR_P1, RX_ADDR_P2, RX_ADDR_P3, RX_ADDR_P4, RX_ADDR_P5, + TX_ADDR, + RX_PW_P0, RX_PW_P1, RX_PW_P2, RX_PW_P3, RX_PW_P4, RX_PW_P5, + FIFO_STATUS, DYNPD, FEATURE); + + Registers_Addressses : constant array (Register_Name) of + Register_Address := + [CONFIG => 16#00#, + EN_AA => 16#01#, + EN_RXADDR => 16#02#, + SETUP_AW => 16#03#, + SETUP_RETR => 16#04#, + RF_CH => 16#05#, + RF_SETUP => 16#06#, + STATUS => 16#07#, + OBSERVE_TX => 16#08#, + RPD => 16#09#, + RX_ADDR_P0 => 16#0A#, + RX_ADDR_P1 => 16#0B#, + RX_ADDR_P2 => 16#0C#, + RX_ADDR_P3 => 16#0D#, + RX_ADDR_P4 => 16#0E#, + RX_ADDR_P5 => 16#0F#, + TX_ADDR => 16#10#, + RX_PW_P0 => 16#11#, + RX_PW_P1 => 16#12#, + RX_PW_P2 => 16#13#, + RX_PW_P3 => 16#14#, + RX_PW_P4 => 16#15#, + RX_PW_P5 => 16#16#, + FIFO_STATUS => 16#17#, + DYNPD => 16#1C#, + FEATURE => 16#1D#]; + + -------------- + -- Commands -- + -------------- + + type Command is new UInt8; + -- Base type for all commands + + type Command_R_REGISTER is record + Address : Register_Address; + Command : UInt3 := 0; + end record with Size => Command'Size; + -- Read registers command + + for Command_R_REGISTER use record + Address at 0 range 0 .. 4; + Command at 0 range 5 .. 7; + end record; + + type Command_W_REGISTER is record + Address : Register_Address; + Command : UInt3 := 1; + end record with Size => Command'Size; + -- Write registers command + + for Command_W_REGISTER use record + Address at 0 range 0 .. 4; + Command at 0 range 5 .. 7; + end record; + + type Command_W_ACK_PAYLOAD is record + Pipe : RX_Pipe; + Command : UInt5 := 2#10101#; + end record with Size => Command'Size; + -- Write ACK payload to be trasfered back to TX + + for Command_W_ACK_PAYLOAD use record + Pipe at 0 range 0 .. 2; + Command at 0 range 3 .. 7; + end record; + + Cmd_R_RX_PAYLOAD : constant Command := 2#0110_0001#; + Cmd_W_TX_PAYLOAD : constant Command := 2#1010_0000#; + Cmd_FLUSH_RX : constant Command := 2#1110_0010#; + Cmd_FLUSH_TX : constant Command := 2#1110_0001#; + Cmd_REUSE_TX_PL : constant Command := 2#1110_0011#; + Cmd_R_RX_PL_WID : constant Command := 2#0110_0000#; + Cmd_W_TX_PAYLOAD_NO_ACK : constant Command := 2#1011_0000#; + Cmd_NOP : constant Command := Command'Last; + + ---------------------- + -- NRF24L01P_Driver -- + ---------------------- + + type Pipes_Payloads is array (RX_Pipe) of Pipe_Payload; + + type Holder_Type_Kind is (Hardware, Software); + + type Holder_Type + (Kind : Holder_Type_Kind := Software) is + record + SPI : HAL.SPI.Any_SPI_Port; + CE_Pin : HAL.GPIO.Any_GPIO_Point; + -- Used to Enable/Disable chip + + -- Some current configuration data + Power_On : Boolean := False; + Mode : PX_Mode := PTX; + NO_ACK : Boolean := False; + Addr_Size : Pipe_Address_Size := 5; + Payloads : Pipes_Payloads := + [0 => (Payload => Dynamic_Payload), + 1 => (Payload => Dynamic_Payload), + 2 => <>, + 3 => <>, + 4 => <>, + 5 => <>]; + + case Kind is + when Hardware => + null; + when Software => + CSN_Pin : HAL.GPIO.Any_GPIO_Point; + -- Used for "selecting" chip vis SPI + end case; + end record; + + type NRF24L01P_Driver is limited record + Holder : Holder_Type; + end record; + + procedure Configure_Chip + (This : in out NRF24L01P_Driver; + Config : Configuration); + -- Configures microcircuit chip. + + function Read_Register + (This : in out NRF24L01P_Driver; + Name : Register_Name) + return Register; + -- Reads register from NRF24L01P_Driver chip by SPI + + procedure Write + (This : in out NRF24L01P_Driver; + Data : HAL.SPI.SPI_Data_8b); + -- Write data to chip + + procedure Write_And_Read + (This : in out NRF24L01P_Driver; + Command : HAL.SPI.SPI_Data_8b; + Data : out HAL.SPI.SPI_Data_8b); + -- Read data from chip + + procedure Write_Register + (This : in out NRF24L01P_Driver; + Name : Register_Name; + Data : Register); + -- Writes register to NRF24L01P_Driver chip by SPI + + procedure Write_Command + (This : in out NRF24L01P_Driver; + Cmd : Command); + -- Writes command to NRF24L01P_Driver chip by SPI + + procedure Write_Command_And_Read + (This : in out NRF24L01P_Driver; + Cmd : Command; + Data : out HAL.SPI.SPI_Data_8b); + -- Writes command to NRF24L01P_Driver chip by SPI and get the response + + procedure Config_Power_Rate + (This : in out NRF24L01P_Driver; + Power : Amplifier_Power; + Rate : Air_Data_Rate); + -- Sets amplifier power and air data rate + + function Get_RX_Data_Size + (This : in out NRF24L01P_Driver; + Pipe : RX_Pipe) + return Payload_Length; + -- Returns size we set for the pipe in non DPL mode + + procedure CE_High (This : NRF24L01P_Driver); + procedure CE_Low (This : NRF24L01P_Driver); + -- High/Low CE pin to Activate/Deactivate TX/RX operations + + procedure CSN_High (This : NRF24L01P_Driver); + procedure CSN_Low (This : NRF24L01P_Driver); + -- High/Low CSN pin in SOFTWARE slave selection mode. + +end NRF24L01P; diff --git a/examples/STM32F429_Discovery/nrf24l01p_f429disco.gpr b/examples/STM32F429_Discovery/nrf24l01p_f429disco.gpr new file mode 100644 index 000000000..5f4f84762 --- /dev/null +++ b/examples/STM32F429_Discovery/nrf24l01p_f429disco.gpr @@ -0,0 +1,15 @@ +with "../../boards/stm32f429_discovery/stm32f429_discovery_full.gpr"; + +project nRF24L01p_F429Disco extends "../shared/common/common.gpr" is + + for Runtime ("Ada") use STM32F429_Discovery_Full'Runtime("Ada"); + for Target use "arm-eabi"; + for Main use ("nrf24l01p_example.adb"); + for Languages use ("Ada"); + for Source_Dirs use ("../shared/nrf24l01p/src"); + for Object_Dir use "../shared/nrf24l01p/obj/stm32f429disco"; + for Create_Missing_Dirs use "True"; + + package Compiler renames STM32F429_Discovery_Full.Compiler; + +end nRF24L01p_F429Disco; diff --git a/examples/shared/nrf24l01p/.gdbinit b/examples/shared/nrf24l01p/.gdbinit new file mode 100644 index 000000000..618b9ce81 --- /dev/null +++ b/examples/shared/nrf24l01p/.gdbinit @@ -0,0 +1,24 @@ +# This command file will cause a Cortex-M3 or -M4 board to automatically +# reset immediately after a GDB "load" command executes. Note that GPS +# issues that command as part of the Debug->Init menu invocation. Manual +# "load" command invocations will also trigger the action. +# +# The reset is achieved by writing to the "Application Interrupt and Reset +# Control" register located at address 0xE000ED0C. +# +# Both the processor and the peripherals can be reset by writing a value +# of 0x05FA0004. That value will write to the SYSRESETREQ bit. If you want +# to avoid resetting the peripherals, change the value to 0x05FA0001. That +# value will write to the VECTRESET bit. Do *not* use a value that sets both +# bits. +# +# In both cases, any on-board debug hardware is not reset. +# +# See the book "The Definitive Guide to the ARM Cortex-M3 and Cortex-M4 +# Processors" by Joseph Yiu, 3rd edition, pp 262-263 for further details. + +define hookpost-load +echo Resetting the processor and peripherals...\n +set *0xE000ED0C := 0x05FA0004 +echo Reset complete\n +end \ No newline at end of file diff --git a/examples/shared/nrf24l01p/README.md b/examples/shared/nrf24l01p/README.md new file mode 100644 index 000000000..0c39fe329 --- /dev/null +++ b/examples/shared/nrf24l01p/README.md @@ -0,0 +1,22 @@ +This is a simple test/example for nRF24L01+ with +STM32F429Disco. Two nRF24L01+ should be connected +to the following board's pins: + +| TX: | | +|------|-----| +| CE | PB7 | +| CSN | PE3 | +| SCK | PE2 | +| MISO | PE5 | +| MOSI | PE6 | + +| RX: | | +|------|------| +| CE | PC11 | +| CSN | PC12 | +| IRQ | PB4 | +| SCK | PE2 | +| MISO | PE5 | +| MOSI | PE6 | + +to communicate with each other. diff --git a/examples/shared/nrf24l01p/src/nrf24l01p_example.adb b/examples/shared/nrf24l01p/src/nrf24l01p_example.adb new file mode 100644 index 000000000..968ea4abe --- /dev/null +++ b/examples/shared/nrf24l01p/src/nrf24l01p_example.adb @@ -0,0 +1,544 @@ +------------------------------------------------------------------------------ +-- -- +-- Copyright (C) 2025, 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. -- +-- -- +------------------------------------------------------------------------------ + +-- Simple test/example for STM32F429disco with two nrf24l01p attached + +with Last_Chance_Handler; pragma Unreferenced (Last_Chance_Handler); +-- The "last chance handler" is the user-defined routine that is called when +-- an exception is propagated. We need it in the executable, therefore it +-- must be somewhere in the closure of the context clauses. + +with HAL.SPI; +with STM32.Board; use STM32.Board; +with STM32.Device; +with STM32.GPIO; use STM32.GPIO; +with STM32.SPI; use STM32.SPI; +with STM32.EXTI; + +with NRF24L01P; use NRF24L01P; +with Watchdogs; use Watchdogs; + +-- Service +with Ada.Real_Time; use Ada.Real_Time; +with HAL.Bitmap; use HAL.Bitmap; +with BMP_Fonts; +with LCD_Std_Out; + +-- TX: +-- CE PB7 +-- CSN PE3 +-- SPI4 Pins: +-- PE2 SPI4_SCK +-- PE5 SPI4_MISO +-- PE6 SPI4_MOSI + + +-- RX: +-- CE PC11 +-- CSN PC12 +-- IRQ PB4 +-- SPI4 Pins: +-- PE2 SPI4_SCK +-- PE5 SPI4_MISO +-- PE6 SPI4_MOSI + +procedure nRF24L01P_Example is + + -- TX -- + TX_CE : GPIO_Point renames STM32.Device.PB7; + TX_CSN : GPIO_Point renames STM32.Device.PE3; + SPI4_Pins : constant GPIO_Points := + (STM32.Device.PE2, STM32.Device.PE5, STM32.Device.PE6); + TX_SPI : SPI_Port renames STM32.Device.SPI_4; + TX : NRF24L01P_Driver; + + TX_Config : constant Configuration := + (Mode => PTX, + Power => Power_Minimum, + Rate => Rate_1Mbps, + CRC => Enabled_2byte, + Chanel => 10, + RX_IRQ => True, + TX_IRQ => True, + MAX_TR_IRQ => True); + + -- RX -- + RX_CE : GPIO_Point renames STM32.Device.PC11; + RX_CSN : GPIO_Point renames STM32.Device.PC12; + RX_SPI : SPI_Port renames STM32.Device.SPI_4; + RX : NRF24L01P_Driver; + + RX_Config : constant Configuration := + (Mode => PRX, + Power => Power_Minimum, + Rate => Rate_1Mbps, + CRC => Enabled_2byte, + Chanel => 10, + RX_IRQ => True, -- Generate IRQ only when RX data arrived + TX_IRQ => False, + MAX_TR_IRQ => False); + + Address : constant Pipe_Address := (1, 2, 3, 4, 5); + -- Will be used for both TX/RX + + procedure Init_Pins; + procedure Init_SPI; + procedure On_RX_IRQ; + + --------------- + -- Init_Pins -- + --------------- + + procedure Init_Pins is + -- Initialize pins + begin + + -- TX -- + STM32.Device.Enable_Clock (GPIO_Points'(TX_CE, TX_CSN)); + + Configure_IO + (Points => (TX_CE, TX_CSN), + Config => + (Mode => Mode_Out, + Resistors => Floating, + Output_Type => Push_Pull, + Speed => Speed_Low)); + + TX_CE.Clear; + TX_CSN.Set; + + -- RX -- + STM32.Device.Enable_Clock (GPIO_Points'(RX_CE, RX_CSN, RX_IRQ)); + + Configure_IO + (Points => (RX_CE, RX_CSN), + Config => + (Mode => Mode_Out, + Resistors => Floating, + Output_Type => Push_Pull, + Speed => Speed_Low)); + + RX_CE.Clear; + RX_CSN.Set; + + -- RX IRQ pin + RX_IRQ.Configure_IO + (Config => + (Mode => Mode_In, + Resistors => Floating)); + + RX_Watchdog.Set_Callback (On_RX_IRQ'Unrestricted_Access); + RX_IRQ.Configure_Trigger (STM32.EXTI.Interrupt_Falling_Edge); + end Init_Pins; + + -------------- + -- Init_SPI -- + -------------- + + procedure Init_SPI + -- Initialize SPI + is + use STM32.Device; + + Conf : constant GPIO_Port_Configuration := + (Mode => Mode_AF, + Resistors => Floating, + AF_Output_Type => Push_Pull, + AF_Speed => Speed_Very_High, + AF => GPIO_AF_SPI4_5); + + SPI_Conf : constant SPI_Configuration := + (Direction => D2Lines_FullDuplex, + Mode => Master, + Data_Size => HAL.SPI.Data_Size_8b, + Clock_Polarity => Low, + Clock_Phase => P1Edge, + Slave_Management => Software_Managed, + Baud_Rate_Prescaler => BRP_16, + First_Bit => MSB, + CRC_Poly => 10); + begin + Enable_Clock (SPI4_Pins); + Enable_Clock (TX_SPI); + + Configure_IO (SPI4_Pins, Conf); + + Reset (TX_SPI); + if not TX_SPI.Enabled then + TX_SPI.Configure (SPI_Conf); + TX_SPI.Enable; + end if; + end Init_SPI; + + Period : constant Time_Span := Milliseconds (200); + Next_Release : Time := Clock; + BG : constant Bitmap_Color := (Alpha => 255, others => 64); + + -------------- + -- On_Error -- + -------------- + + procedure On_Error (Msg : String) is + begin + Power_Down (TX); + Power_Down (RX); + + LCD_Std_Out.Put_Line (Msg); + Display.Update_Layer (1, Copy_Back => True); + + loop + STM32.Board.Toggle (Red_LED); + Next_Release := Next_Release + Period; + delay until Next_Release; + end loop; + end On_Error; + + --------------- + -- On_RX_IRQ -- + --------------- + + Has_RX_IRQ : Boolean := False + with Volatile, Atomic; + -- Did we have RX IRQ + + procedure On_RX_IRQ is + -- Simple IRQ callback + begin + if Is_Received (RX) then + Has_RX_IRQ := True; + end if; + end On_RX_IRQ; + + ----------------- + -- Wait_For_RX -- + ----------------- + + procedure Wait_For_RX is + -- Waiting for data on the RX side + begin + loop + if Has_RX_IRQ then + Has_RX_IRQ := False; + exit; + end if; + delay 0.1; + end loop; + end Wait_For_RX; + + ------------------- + -- Ckeck_RX_Data -- + ------------------- + + procedure Ckeck_RX_Data + (Driver : in out NRF24L01P_Driver; + Value : String) + -- Checks data on the RX side + is + Pipe : RX_Pipe; + Have_More : Boolean; + Data : TX_RX_Data := Receive (Driver, Pipe, Have_More); + Str : String (Integer (Data'First) .. Integer (Data'Last)) + with Import, Address => Data'Address; + begin + LCD_Std_Out.Put_Line ("Get: '" & Str & "'"); + Display.Update_Layer (1, Copy_Back => True); + + if Pipe /= 0 then + On_Error ("RX Pipe FAILS" & Pipe'Img); + end if; + + if Have_More then + On_Error ("RX Have_More FAILS"); + end if; + + if Str /= Value then + On_Error ("RX DATA FAILS"); + end if; + end Ckeck_RX_Data; + +begin + delay 2.0; + + -- Initialize GPIO/SPI for NRF + Init_Pins; + Init_SPI; + delay 0.1; + + -- Init testing infrastructure + STM32.Board.Initialize_LEDs; + Display.Initialize; + Display.Initialize_Layer (1, ARGB_8888); + LCD_Std_Out.Set_Font (BMP_Fonts.Font12x12); + LCD_Std_Out.Current_Background_Color := BG; + Display.Hidden_Buffer (1).Set_Source (BG); + Display.Hidden_Buffer (1).Fill; + LCD_Std_Out.Clear_Screen; + + -- Static payload test -- + + -- Initialize TX -- + Initialize + (This => TX, + CE_Pin => TX_CE'Access, + CSN_Pin => TX_CSN'Access, + SPI => TX_SPI'Access, + Config => TX_Config); + delay 0.1; + + Set_Max_Pipe_Address_Size (TX, 5); + Set_TX_Address (TX, Address); + + Configure_And_Enable_RX_Pipe + (This => TX, + Pipe => 0, + Addr => Address, + ACK => False, + Payload => + (Payload => Static_Payload, + Size => 5)); + + Disable_RX_Pipe (TX, 1); + Disable_RX_Pipe (TX, 2); + Disable_RX_Pipe (TX, 3); + Disable_RX_Pipe (TX, 4); + Disable_RX_Pipe (TX, 5); + + Set_Features + (This => TX, + Dynamic_Payload => False, + ACK_Payload => False, + NO_ACK_Allowed => False); + + -- Initialize RX -- + Initialize + (This => RX, + CE_Pin => RX_CE'Access, + CSN_Pin => RX_CSN'Access, + SPI => RX_SPI'Access, + Config => RX_Config); + delay 0.1; + + Set_Max_Pipe_Address_Size (RX, 5); + + Configure_And_Enable_RX_Pipe + (This => RX, + Pipe => 0, + Addr => Address, + ACK => False, + Payload => + (Payload => Static_Payload, + Size => 5)); + + Disable_RX_Pipe (RX, 1); + Disable_RX_Pipe (RX, 2); + Disable_RX_Pipe (RX, 3); + Disable_RX_Pipe (RX, 4); + Disable_RX_Pipe (RX, 5); + + Set_Features + (This => RX, + Dynamic_Payload => False, + ACK_Payload => False, + NO_ACK_Allowed => False); + delay 0.1; + + -- Turn on RX -- + Power_Up (RX); + delay 0.0015; + Start_Wating_RX (RX); + + -- Transmit over TX -- + Power_Up (TX); + delay 0.0015; + + declare + Data : constant String := "Hello"; + Buf : TX_RX_Data + (Payload_Length (Data'First) .. Payload_Length (Data'Last)) + with Import, Address => Data'Address; + begin + Transmit (TX, Buf); + LCD_Std_Out.Put_Line ("Send: '" & Data & "'"); + Display.Update_Layer (1, Copy_Back => True); + end; + + -- Work with RX side + Wait_For_RX; + Ckeck_RX_Data (RX, "Hello"); + Stand_By (RX); + + if not Is_Transmitted (TX) then + On_Error ("TX_Data_Sent FAILS"); + end if; + Stand_By (TX); + Clear_Status (TX); + + Power_Down (TX); + Power_Down (RX); + delay 0.1; + + -- Dynamic payload test -- + + -- TX + Set_Features + (This => TX, + Dynamic_Payload => True, + ACK_Payload => False, + NO_ACK_Allowed => False); + + Configure_And_Enable_RX_Pipe + (This => TX, + Pipe => 0, + Addr => Address, + ACK => True, + Payload => (Payload => Dynamic_Payload)); + + -- RX + Set_Features + (This => RX, + Dynamic_Payload => True, + ACK_Payload => False, + NO_ACK_Allowed => False); + + Configure_And_Enable_RX_Pipe + (This => RX, + Pipe => 0, + Addr => Address, + ACK => True, + Payload => (Payload => Dynamic_Payload)); + + -- Prepare RX + Power_Up (RX); + delay 0.0015; + Start_Wating_RX (RX); + + -- Send over TX + Power_Up (TX); + delay 0.0015; + + declare + Data : constant String := "Hello world"; + Buf : TX_RX_Data + (Payload_Length (Data'First) .. Payload_Length (Data'Last)) + with Import, Address => Data'Address; + begin + Transmit (TX, Buf); + LCD_Std_Out.Put_Line ("Send: '" & Data & "'"); + Display.Update_Layer (1, Copy_Back => True); + end; + + -- Check on RX side + Wait_For_RX; + Ckeck_RX_Data (RX, "Hello world"); + Stand_By (RX); + + if not Is_Transmitted (TX) then + On_Error ("TX_Data_Sent FAILS"); + end if; + Stand_By (TX); + Clear_Status (TX); + + Power_Down (TX); + Power_Down (RX); + delay 0.1; + + -- ACK payload back test -- + + -- TX + Set_Features + (This => TX, + Dynamic_Payload => True, + ACK_Payload => True, + NO_ACK_Allowed => False); + + Set_Auto_Retransmit (This => TX, Count => 5, A_Delay => 5); + + -- RX + Set_Features + (This => RX, + Dynamic_Payload => True, + ACK_Payload => True, + NO_ACK_Allowed => False); + + -- Prepare RX + Power_Up (RX); + delay 0.0015; + Start_Wating_RX (RX); + + declare + Data : constant String := "ACK back"; + Buf : TX_RX_Data + (Payload_Length (Data'First) .. Payload_Length (Data'Last)) + with Import, Address => Data'Address; + begin + Write_ACK_Payload (This => RX, Pipe => 0, Data => Buf); + end; + + -- Send over TX + Power_Up (TX); + delay 0.0015; + + declare + Data : constant String := "With ACK"; + Buf : TX_RX_Data + (Payload_Length (Data'First) .. Payload_Length (Data'Last)) + with Import, Address => Data'Address; + begin + Transmit (TX, Buf); + LCD_Std_Out.Put_Line ("Send: '" & Data & "'"); + Display.Update_Layer (1, Copy_Back => True); + end; + + -- Check on RX side + Wait_For_RX; + Ckeck_RX_Data (RX, "With ACK"); + Stand_By (RX); + + loop + exit when Is_Transmitted (TX); + delay 0.1; + end loop; + Ckeck_RX_Data (TX, "ACK back"); + Stand_By (TX); + + Power_Down (TX); + Power_Down (RX); + + -- All is OK + LCD_Std_Out.Put_Line ("All tests passed"); + Display.Update_Layer (1, Copy_Back => True); + + loop + STM32.Board.Toggle (Green_LED); + Next_Release := Next_Release + Period; + delay until Next_Release; + end loop; +end nRF24L01P_Example; diff --git a/examples/shared/nrf24l01p/src/watchdogs.adb b/examples/shared/nrf24l01p/src/watchdogs.adb new file mode 100644 index 000000000..d6efefc2b --- /dev/null +++ b/examples/shared/nrf24l01p/src/watchdogs.adb @@ -0,0 +1,63 @@ +------------------------------------------------------------------------------ +-- -- +-- Copyright (C) 2025, 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. -- +-- -- +------------------------------------------------------------------------------ + +package body Watchdogs is + + ----------------- + -- RX_Watchdog -- + ----------------- + + protected body RX_Watchdog is + + ------------------ + -- Set_Callback -- + ------------------ + + procedure Set_Callback (Proc : Watchdog_Callback) is + begin + Callback := Proc; + end Set_Callback; + + --------------- + -- Interrupt -- + --------------- + + procedure Interrupt is + begin + STM32.EXTI.Clear_External_Interrupt (RX_EXTI_Line); + if Callback /= null then + Callback.all; + end if; + end Interrupt; + + end RX_Watchdog; + +end Watchdogs; diff --git a/examples/shared/nrf24l01p/src/watchdogs.ads b/examples/shared/nrf24l01p/src/watchdogs.ads new file mode 100644 index 000000000..68a734b5f --- /dev/null +++ b/examples/shared/nrf24l01p/src/watchdogs.ads @@ -0,0 +1,63 @@ +------------------------------------------------------------------------------ +-- -- +-- Copyright (C) 2025, 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. -- +-- -- +------------------------------------------------------------------------------ + +-- Watchdogs to catch IRQs + +with Ada.Interrupts.Names; + +with STM32.GPIO; use STM32.GPIO; +with STM32.Device; +with STM32.EXTI; + +package Watchdogs is + + RX_IRQ : GPIO_Point renames STM32.Device.PB4; + + type Watchdog_Callback is access procedure; + + -- RX_Watchdog -- + + protected RX_Watchdog is + pragma Interrupt_Priority; + + procedure Set_Callback (Proc : Watchdog_Callback); + + private + RX_EXTI_Line : STM32.EXTI.External_Line_Number := + RX_IRQ.Interrupt_Line_Number; + + procedure Interrupt; + pragma Attach_Handler (Interrupt, Ada.Interrupts.Names.EXTI4_Interrupt); + + Callback : Watchdog_Callback := null; + end RX_Watchdog; + +end Watchdogs;