Compare commits

...

17 Commits

Author SHA1 Message Date
Erki
9e5ecdf1b9 memmove -> memcpy 2022-07-28 15:10:27 +03:00
Erki
6a9697124e IEEE 802.15.4-2006 protocol implementation 2022-07-28 14:50:10 +03:00
Erki
c994e63478 Initial implementation of app state machine 2022-07-17 18:20:45 +03:00
Erki
1c695fbaa8 Implement millis() function 2022-07-16 12:23:07 +03:00
Erki
00810009ec A lot of WIP state 2022-07-15 15:12:13 +03:00
Erki
e25b54add4 Update to use skullc for enums 2022-07-15 14:23:19 +03:00
Erki
c67c41f166 Fix and implement RF interrupt handling 2022-07-15 13:52:34 +03:00
Erki
671abd2f7f Rework state transition logic to be more complete 2022-07-14 18:01:52 +03:00
Erki
e86a5a1cbe Initial client application skeleton 2022-07-14 00:26:09 +03:00
Erki
8cddb07ee4 Configuration tooling added 2022-07-13 23:45:12 +03:00
Erki
11baa1c185 Initial state transitions 2022-07-12 18:04:40 +03:00
Erki
c27f7a9450 Enable EXTINT0 for PB00 (hopefully) 2022-07-11 18:54:26 +03:00
Erki
bbe259c29a Add nano.specs to the project 2022-07-11 01:29:31 +03:00
Erki
1bbe29b5d9 Bring up to date with skullc 2022-06-30 19:58:56 +03:00
Erki
bb1a446f77 Basic radio boot-up code + initial state transition 2022-06-30 19:35:05 +03:00
Erki
94368b458a Remove new-ing from radio instance handling 2022-06-29 16:22:02 +03:00
Erki
798d529b20 Merge branch 'feature/skullc_hal' 2022-06-29 00:30:59 +03:00
28 changed files with 1196 additions and 100 deletions

View File

@ -48,9 +48,13 @@ add_executable(skl_tunnel
radio/src/radio_spi.c radio/src/radio_spi.c
radio/src/radio_gpio.c radio/src/radio_gpio.c
radio/src/radio_hw_instance.cpp radio/src/radio_hw_instance.cpp
radio/src/radio_protocol_frame.cpp
radio/src/radio_protocol.cpp
app/src/app_logging.cpp app/src/app_board.cpp
app/src/app_transparent_client.cpp
main.cpp main.cpp
syscalls.c
) )
target_include_directories(skl_tunnel target_include_directories(skl_tunnel

19
app/include/app_board.hpp Normal file
View File

@ -0,0 +1,19 @@
//
// Created by erki on 15.07.22.
//
#ifndef SKL_TUNNEL_APP_BOARD_HPP
#define SKL_TUNNEL_APP_BOARD_HPP
#include <cstdint>
namespace App::Board
{
void setup();
std::uint32_t systickGet();
}
#endif //SKL_TUNNEL_APP_BOARD_HPP

View File

@ -1,15 +0,0 @@
//
// Created by erki on 28.06.22.
//
#ifndef SKL_TUNNEL_APP_LOGGING_HPP
#define SKL_TUNNEL_APP_LOGGING_HPP
namespace App::Logging
{
void setup();
}
#endif //SKL_TUNNEL_APP_LOGGING_HPP

View File

@ -0,0 +1,25 @@
//
// Created by erki on 14.07.22.
//
#ifndef SKL_TUNNEL_APP_SETTINGS_HPP
#define SKL_TUNNEL_APP_SETTINGS_HPP
#include <cstdint>
namespace App
{
struct RadioSettings
{
std::uint16_t short_address = 0x0230;
std::uint64_t long_address = 0x1222334455667788;
std::uint8_t channel = 11;
std::uint16_t pan_id = 0x0023;
std::int16_t tx_power_dbm = 0;
std::uint8_t retries = 3;
};
}
#endif //SKL_TUNNEL_APP_SETTINGS_HPP

View File

@ -0,0 +1,60 @@
//
// Created by erki on 13.07.22.
//
#ifndef SKL_TUNNEL_APP_TRANSPARENT_CLIENT_HPP
#define SKL_TUNNEL_APP_TRANSPARENT_CLIENT_HPP
#include <optional>
#include <utility_function.hpp>
#include "app_settings.hpp"
#include "radio_interrupts.hpp"
namespace radio
{
class HwInstance;
}
namespace App
{
class TransparentClient
{
public:
TransparentClient(const RadioSettings& initial_settings);
TransparentClient(const TransparentClient&) = delete;
TransparentClient(TransparentClient&&) = delete;
TransparentClient& operator=(const TransparentClient&) = delete;
TransparentClient& operator=(TransparentClient&&) = delete;
void apply_settings(const RadioSettings& settings);
void process();
private:
radio::HwInstance* m_radio;
Utility::FunctionOwned<TransparentClient, void (radio::HwInstance*)> m_isr_cb_pointer;
std::optional<radio::Interrupts> m_pending_irqs = std::nullopt;
enum class AppState
{
STARTUP,
PASSIVE,
ACTIVE_RX,
ACTIVE_TX,
RX_FRAME_READY
};
AppState m_state = AppState::STARTUP;
void m_cbRadioIrqHandler(radio::HwInstance*);
std::optional<AppState> m_hwInterruptToNewState();
void m_transitionToState(const AppState& new_state);
void m_processState();
void m_initiateTx();
bool m_txBufferIsReady();
};
}
#endif //SKL_TUNNEL_APP_TRANSPARENT_CLIENT_HPP

View File

@ -5,10 +5,6 @@
#ifndef SKL_TUNNEL_SKULLC_SAMD21_HAL_HPP #ifndef SKL_TUNNEL_SKULLC_SAMD21_HAL_HPP
#define SKL_TUNNEL_SKULLC_SAMD21_HAL_HPP #define SKL_TUNNEL_SKULLC_SAMD21_HAL_HPP
#include <array>
#include <cstdint>
#include <cassert>
#include <hal_delay.h> #include <hal_delay.h>
#include <hal_io.h> #include <hal_io.h>
#include <hal_usart_async.h> #include <hal_usart_async.h>
@ -16,6 +12,12 @@
#include <utility_function.hpp> #include <utility_function.hpp>
#include <utility_tag.hpp> #include <utility_tag.hpp>
#include <array>
#include <cstdint>
#include <cassert>
#include "app_board.hpp"
namespace Peripherals namespace Peripherals
{ {
namespace Hal namespace Hal
@ -26,7 +28,14 @@ namespace Samd
struct StaticHal struct StaticHal
{ {
static void initialize() static void initialize()
{ } {
App::Board::setup();
}
static std::uint32_t getMillis()
{
return App::Board::systickGet();
}
static void delay(const std::uint32_t milliseconds) static void delay(const std::uint32_t milliseconds)
{ {

View File

@ -1,16 +1,17 @@
// //
// Created by erki on 28.06.22. // Created by erki on 15.07.22.
// //
#include "app_logging.hpp"
#include "driver_init.h" #include "driver_init.h"
#include "app_board.hpp"
#include "skullc_samd21_hal.hpp"
#include <utility_logging.hpp> #include <utility_logging.hpp>
#include <utility_asynclogger.hpp> #include <utility_asynclogger.hpp>
#include <utility_staticpointer.hpp> #include <utility_staticpointer.hpp>
#include "skullc_samd21_hal.hpp"
namespace Hal = Peripherals::Hal::Samd; namespace Hal = Peripherals::Hal::Samd;
@ -20,6 +21,7 @@ namespace
{ {
Utility::StaticPointer<Logger> m_logger; Utility::StaticPointer<Logger> m_logger;
std::uint32_t m_systick_counter = 0;
void m_txCompleteCb(const usart_async_descriptor* const) void m_txCompleteCb(const usart_async_descriptor* const)
{ {
@ -28,16 +30,29 @@ void m_txCompleteCb(const usart_async_descriptor* const)
} }
namespace App::Logging extern "C" void SysTick_Handler()
{
m_systick_counter++;
}
namespace App::Board
{ {
void setup() void setup()
{ {
// Logging
Hal::SerialInterfaceAsync<usart_async_descriptor> usart0{&USART_0}; Hal::SerialInterfaceAsync<usart_async_descriptor> usart0{&USART_0};
usart0.registerTxCallback(m_txCompleteCb); usart0.registerTxCallback(m_txCompleteCb);
m_logger.setup(usart0); m_logger.setup(usart0);
Utility::setLogger(*m_logger); Utility::setLogger(*m_logger);
SysTick_Config(F_CPU / 1000u);
}
std::uint32_t systickGet()
{
return m_systick_counter;
} }
} }

View File

@ -0,0 +1,145 @@
//
// Created by erki on 14.07.22.
//
#include "radio_hw_instance.hpp"
#include "app_transparent_client.hpp"
#include "skullc_samd21_hal.hpp"
#include "utility_logging.hpp"
#include <utility_atomicscopeguard.hpp>
namespace App
{
TransparentClient::TransparentClient(const RadioSettings& initial_settings)
: m_radio(radio::HwInstance::instance())
, m_isr_cb_pointer(*this, &TransparentClient::m_cbRadioIrqHandler)
{
apply_settings(initial_settings);
m_radio->set_irq_handler(&m_isr_cb_pointer);
m_radio->set_current_state(radio::HwInstance::States::PLL_ON);
}
void TransparentClient::apply_settings(const RadioSettings& settings)
{
m_radio->set_address_short(settings.short_address);
m_radio->set_address_long(settings.long_address);
m_radio->set_channel(settings.channel);
m_radio->set_pan_id(settings.pan_id);
m_radio->set_tx_power(settings.tx_power_dbm);
m_radio->set_max_retries(settings.retries);
}
void TransparentClient::process()
{
std::optional<AppState> new_state_request = std::nullopt;
{
Utility::AtomicScopeGuard<Peripherals::Hal::Samd::StaticHal> irq_guard;
if (m_pending_irqs)
new_state_request = m_hwInterruptToNewState();
}
if (!new_state_request && m_txBufferIsReady())
new_state_request = AppState::ACTIVE_TX;
if (new_state_request)
m_transitionToState(*new_state_request);
m_processState();
}
void TransparentClient::m_cbRadioIrqHandler(radio::HwInstance*)
{
const radio::Interrupts new_irqs = m_radio->get_pending_irq();
if (m_pending_irqs)
m_pending_irqs = (*m_pending_irqs) | new_irqs;
else
m_pending_irqs = new_irqs;
}
std::optional<TransparentClient::AppState> TransparentClient::m_hwInterruptToNewState()
{
const radio::Interrupts current_irqs = *m_pending_irqs;
m_pending_irqs = std::nullopt;
SKULLC_LOG_DEBUG("APP: Processing IRQs: %d.", current_irqs);
const auto flag_is_set = [current_irqs](const radio::Interrupts& flag) -> bool
{
return std::underlying_type_t<radio::Interrupts>(current_irqs & flag);
};
switch (m_state)
{
case AppState::STARTUP:
if (flag_is_set(radio::Interrupts::PLL_LOCK))
return AppState::PASSIVE;
break;
case AppState::PASSIVE:
if (flag_is_set(radio::Interrupts::RX_START))
return AppState::ACTIVE_RX;
else if (flag_is_set(radio::Interrupts::AMI))
return AppState::RX_FRAME_READY;
break;
case AppState::ACTIVE_RX:
[[fallthrough]];
case AppState::ACTIVE_TX:
if (flag_is_set(radio::Interrupts::TRX_END))
return AppState::PASSIVE;
break;
default:
break;
}
return std::nullopt;
}
void TransparentClient::m_transitionToState(const AppState& new_state)
{
SKULLC_LOG_DEBUG("APP: Trans. to state. %d -> %d.", m_state, new_state);
switch (m_state)
{
case AppState::STARTUP:
if (new_state == AppState::PASSIVE)
m_radio->set_current_state(radio::HwInstance::States::RX_ON);
break;
case AppState::PASSIVE:
// new_state == AppState::ACTIVE_RX is a HW transition. State machine simply locks itself.
// new_state == AppState::RX_FRAME_READY is handled in process state.
if (new_state == AppState::ACTIVE_TX)
m_initiateTx();
break;
case AppState::ACTIVE_RX:
// new_state == AppState::PASSIVE is a HW transition. State machine simply unlocks itself.
break;
case AppState::ACTIVE_TX:
if (new_state == AppState::PASSIVE)
m_radio->set_current_state(radio::HwInstance::States::RX_ON);
break;
case AppState::RX_FRAME_READY:
break;
}
m_state = new_state;
}
void TransparentClient::m_processState()
{
return;
}
void TransparentClient::m_initiateTx()
{
return;
}
bool TransparentClient::m_txBufferIsReady()
{
return false;
}
}

View File

@ -34,9 +34,9 @@ set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-as) set(CMAKE_ASM_COMPILER arm-none-eabi-as)
set(CMAKE_RANLIB arm-none-eabi-ranlib) set(CMAKE_RANLIB arm-none-eabi-ranlib)
set(COMMON_C_FLAGS "-mcpu=cortex-m0plus -mfloat-abi=soft -mthumb -mlong-calls -fdata-sections -ffunction-sections -Wall -Wextra -O2") set(COMMON_C_FLAGS "-mcpu=cortex-m0plus -mfloat-abi=soft -mthumb -mlong-calls -fdata-sections -ffunction-sections -Wall -Wextra -Wswitch-enum -Og")
set(CMAKE_C_FLAGS_INIT "${COMMON_C_FLAGS}") set(CMAKE_C_FLAGS_INIT "${COMMON_C_FLAGS}")
set(CMAKE_CXX_FLAGS_INIT "${COMMON_C_FLAGS} -fno-exceptions -fno-rtti") set(CMAKE_CXX_FLAGS_INIT "${COMMON_C_FLAGS} -fno-exceptions -fno-rtti -fno-use-cxa-atexit")
set(CMAKE_ASM_FLAGS_INIT "${COMMON_C_FLAGS}") set(CMAKE_ASM_FLAGS_INIT "${COMMON_C_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS_INIT "${COMMON_C_FLAGS} -specs=nosys.specs") set(CMAKE_EXE_LINKER_FLAGS_INIT "${COMMON_C_FLAGS} -specs=nano.specs -specs=nosys.specs")

View File

@ -6,4 +6,6 @@
void atmel_start_init(void) void atmel_start_init(void)
{ {
system_init(); system_init();
SysTick_Config(1000);
} }

View File

@ -108,7 +108,14 @@ drivers:
eic_arch_wakeupen7: false eic_arch_wakeupen7: false
eic_arch_wakeupen8: false eic_arch_wakeupen8: false
eic_arch_wakeupen9: false eic_arch_wakeupen9: false
optional_signals: [] optional_signals:
- identifier: EXTERNAL_IRQ_0:EXTINT/0
pad: PA00
mode: Enabled
configuration: null
definition: Atmel:SAMD21_Drivers:0.0.1::SAMD21E17A-MF::optional_signal_definition::EIC.EXTINT.0
name: EIC/EXTINT/0
label: EXTINT/0
variant: null variant: null
clocks: clocks:
domain_group: domain_group:
@ -709,6 +716,12 @@ drivers:
clocks: clocks:
domain_group: null domain_group: null
pads: pads:
PA00:
name: PA00
definition: Atmel:SAMD21_Drivers:0.0.1::SAMD21E17A-MF::pad::PA00
mode: Digital input
user_label: PA00
configuration: null
OUT_LED_TX: OUT_LED_TX:
name: PA06 name: PA06
definition: Atmel:SAMD21_Drivers:0.0.1::SAMD21E17A-MF::pad::PA06 definition: Atmel:SAMD21_Drivers:0.0.1::SAMD21E17A-MF::pad::PA06

View File

@ -21,6 +21,7 @@
#define GPIO_PIN_FUNCTION_G 6 #define GPIO_PIN_FUNCTION_G 6
#define GPIO_PIN_FUNCTION_H 7 #define GPIO_PIN_FUNCTION_H 7
#define PA00 GPIO(GPIO_PORTA, 0)
#define OUT_LED_TX GPIO(GPIO_PORTA, 6) #define OUT_LED_TX GPIO(GPIO_PORTA, 6)
#define OUT_XBEE_REMOTE_RESET GPIO(GPIO_PORTA, 7) #define OUT_XBEE_REMOTE_RESET GPIO(GPIO_PORTA, 7)
#define IN_UART_TX GPIO(GPIO_PORTA, 8) #define IN_UART_TX GPIO(GPIO_PORTA, 8)

View File

@ -682,6 +682,10 @@
#endif #endif
// </e> // </e>
#ifndef CONFIG_EIC_EXTINT_MAP
#define CONFIG_EIC_EXTINT_MAP {0, PIN_PA00}, {0, 32},
#endif
// <<< end of configuration section >>> // <<< end of configuration section >>>
#endif // HPL_EIC_CONFIG_H #endif // HPL_EIC_CONFIG_H

View File

@ -25,6 +25,19 @@ void EXTERNAL_IRQ_0_init(void)
{ {
_gclk_enable_channel(EIC_GCLK_ID, CONF_GCLK_EIC_SRC); _gclk_enable_channel(EIC_GCLK_ID, CONF_GCLK_EIC_SRC);
// Set pin direction to input
gpio_set_pin_direction(PA00, GPIO_DIRECTION_IN);
gpio_set_pin_pull_mode(PA00,
// <y> Pull configuration
// <id> pad_pull_config
// <GPIO_PULL_OFF"> Off
// <GPIO_PULL_UP"> Pull-up
// <GPIO_PULL_DOWN"> Pull-down
GPIO_PULL_OFF);
gpio_set_pin_function(PA00, PINMUX_PA00A_EIC_EXTINT0);
ext_irq_init(); ext_irq_init();
} }

View File

@ -10,11 +10,17 @@
#include "driver_init.h" #include "driver_init.h"
#include "utils.h" #include "utils.h"
static void button_on_PA00_pressed(void)
{
}
/** /**
* Example of using EXTERNAL_IRQ_0 * Example of using EXTERNAL_IRQ_0
*/ */
void EXTERNAL_IRQ_0_example(void) void EXTERNAL_IRQ_0_example(void)
{ {
ext_irq_register(PIN_PA00, button_on_PA00_pressed);
} }
/** /**

View File

@ -33,7 +33,7 @@
#include "hal_ext_irq.h" #include "hal_ext_irq.h"
#define EXT_IRQ_AMOUNT 0 #define EXT_IRQ_AMOUNT 2
/** /**
* \brief Driver version * \brief Driver version

View File

@ -69,7 +69,7 @@ static int ffs(int v)
} }
#endif #endif
#define EXT_IRQ_AMOUNT 0 #define EXT_IRQ_AMOUNT 2
/** /**
* \brief EXTINTx and pin number map * \brief EXTINTx and pin number map

View File

@ -1,40 +1,60 @@
#include <atmel_start.h> #include <atmel_start.h>
#include <utility_logging.hpp> #include <utility_logging.hpp>
#include <utility_assert.hpp>
#include <utility_staticpointer.hpp>
#include <utility_function.hpp>
#include "radio_hw_instance.hpp" #include "radio_hw_instance.hpp"
#include "app_logging.hpp"
#include "skullc_samd21_hal.hpp" #include "skullc_samd21_hal.hpp"
#include "app_transparent_client.hpp"
namespace Hal = Peripherals::Hal::Samd; namespace Hal = Peripherals::Hal::Samd;
namespace namespace
{ {
Utility::StaticPointer<App::TransparentClient> m_app;
[[noreturn]] void m_faultHandler(const char* expression, const char* file, const int line)
{
SKULLC_LOG_FATAL("Expression failed: (%s), source: %s:%d", expression, file, line);
__asm__("BKPT");
while (true);
} }
int main(void) }
int main()
{ {
/* Initializes MCU, drivers and middleware */ /* Initializes MCU, drivers and middleware */
atmel_start_init(); atmel_start_init();
Utility::Assert::setHandler(m_faultHandler);
Hal::StaticHal::initialize();
gpio_set_pin_level(OUT_LED_TX, false); gpio_set_pin_level(OUT_LED_TX, false);
App::Logging::setup(); SKULLC_LOG_DEBUG("Begin.");
radio::HwInstance* radio_hw = radio::HwInstance::create_instance(); const App::RadioSettings settings;
m_app.setup(settings);
std::uint32_t counter = 0;
/* Replace with your application code */ /* Replace with your application code */
while (1) while (true)
{
if (counter++ == 1000)
{ {
gpio_toggle_pin_level(OUT_LED_RX);
const int16_t radio_num = radio_hw->register_read(0x1C);
SKULLC_LOG_INFO("Reg 0x1C: %d", radio_num);
gpio_toggle_pin_level(OUT_LED_TX); gpio_toggle_pin_level(OUT_LED_TX);
counter = 0;
}
delay_ms(1000); m_app->process();
Hal::StaticHal::delay(1);
} }
} }

View File

@ -6,23 +6,85 @@
#define SKL_TUNNEL_RADIO_HW_INSTANCE_HPP #define SKL_TUNNEL_RADIO_HW_INSTANCE_HPP
#include <hal_spi_m_sync.h> #include <hal_spi_m_sync.h>
#include <type_traits>
#include <array>
#include <cstring>
#include <optional>
#include <utility_function.hpp>
#include "radio_hw_registers.hpp"
#include "radio_interrupts.hpp"
namespace radio namespace radio
{ {
struct HwInstance struct HwInstance
{ {
static HwInstance* create_instance(); enum class States : std::uint8_t
{
P_ON = 0,
BUSY_RX = 0x1,
BUSY_TX = 0x2,
RX_ON = 0x06,
TRX_OFF = 0x08,
PLL_ON = 0x09,
SLEEP = 0x0F,
PREP_DEEP_SLEEP = 0x10,
BUSY_RX_AACK = 0x11,
BUSY_TX_ARET = 0x12,
RX_AACK_ON = 0x16,
TX_ARET_ON = 0x19,
TRANSITION_IN_PROGRESS = 0x1F
};
void irq_handler(); static HwInstance* instance();
HwInstance();
HwInstance(const HwInstance&) = delete;
HwInstance(HwInstance&&) = delete;
HwInstance& operator=(const HwInstance&) = delete;
HwInstance& operator=(HwInstance&&) = delete;
void set_irq_handler(Utility::IFunction<void (HwInstance*)>* cb);
Interrupts get_pending_irq();
uint8_t register_read(const Registers& address);
void register_write(const Registers& address, const uint8_t value);
template<typename T>
void register_write(const Registers& initial_address, const T value)
{
static_assert(std::is_integral_v<T>, "T must be an integral type.");
std::array<std::uint8_t, sizeof(T)> data_array;
std::memcpy(data_array.data(), &value, sizeof(T));
for (std::uint8_t i = 0; i < sizeof(T); i++)
{
const std::uint8_t address = std::uint8_t(initial_address) + i;
register_write(Registers(address), std::uint8_t(data_array[i]));
}
}
States current_state() const;
bool set_current_state(const States& new_state);
void set_address_short(const std::uint16_t address);
void set_address_long(const std::uint64_t address);
void set_channel(const std::uint8_t channel);
void set_pan_id(const std::uint16_t pan_id);
void set_tx_power(std::int16_t power);
void set_max_retries(std::uint8_t retries);
void set_csma_max_retries(const std::optional<std::uint8_t>& retries);
void set_csma_backoff_exponential(std::uint8_t max, std::uint8_t min);
void set_csma_seed(const std::uint16_t entropy);
uint8_t register_read(uint8_t address);
void register_write(uint8_t address, const uint8_t value);
private: private:
spi_m_sync_descriptor* m_spi = nullptr; spi_m_sync_descriptor* m_spi = nullptr;
io_descriptor* m_spi_io = nullptr; io_descriptor* m_spi_io = nullptr;
States m_current_state = States::P_ON;
HwInstance(); void m_wait_can_transition();
}; };
} }

View File

@ -0,0 +1,64 @@
//
// Created by erki on 30.06.22.
//
#ifndef SKL_TUNNEL_RADIO_HW_REGISTERS_HPP
#define SKL_TUNNEL_RADIO_HW_REGISTERS_HPP
#include <cstdint>
namespace radio
{
enum class Registers : std::uint8_t
{
TRX_STATUS = (0x01),
TRX_STATE = (0x02),
TRX_CTRL_0 = (0x03),
TRX_CTRL_1 = (0x04),
PHY_TX_PWR = (0x05),
PHY_RSSI = (0x06),
PHY_ED_LEVEL = (0x07),
PHY_CC_CCA = (0x08),
CCA_THRES = (0x09),
RX_CTRL = (0x0A),
SFD_VALUE = (0x0B),
TRX_CTRL_2 = (0x0C),
ANT_DIV = (0x0D),
IRQ_MASK = (0x0E),
IRQ_STATUS = (0x0F),
VREG_CTRL = (0x10),
BATMON = (0x11),
XOSC_CTRL = (0x12),
CC_CTRL_1 = (0x14),
RX_SYN = (0x15),
XAH_CTRL_1 = (0x17),
FTN_CTRL = (0x18),
PLL_CF = (0x1A),
PLL_DCU = (0x1B),
PART_NUM = (0x1C),
VERSION_NUM = (0x1D),
MAN_ID_0 = (0x1E),
MAN_ID_1 = (0x1F),
SHORT_ADDR_0 = (0x20),
SHORT_ADDR_1 = (0x21),
PAN_ID_0 = (0x22),
PAN_ID_1 = (0x23),
IEEE_ADDR_0 = (0x24),
IEEE_ADDR_1 = (0x25),
IEEE_ADDR_2 = (0x26),
IEEE_ADDR_3 = (0x27),
IEEE_ADDR_4 = (0x28),
IEEE_ADDR_5 = (0x29),
IEEE_ADDR_6 = (0x2A),
IEEE_ADDR_7 = (0x2B),
XAH_CTRL_0 = (0x2C),
CSMA_SEED_0 = (0x2D),
CSMA_SEED_1 = (0x2E),
CSMA_BE = (0x2F),
TST_CTRL_DIGI = (0x36)
};
}
#endif //SKL_TUNNEL_RADIO_HW_REGISTERS_HPP

View File

@ -0,0 +1,30 @@
//
// Created by erki on 15.07.22.
//
#ifndef SKL_TUNNEL_RADIO_INTERRUPTS_HPP
#define SKL_TUNNEL_RADIO_INTERRUPTS_HPP
#include <cstdint>
#include <utility_enum_helpers.hpp>
namespace radio
{
enum class Interrupts : std::uint8_t
{
PLL_LOCK = (1 << 0),
PLL_UNLOCK = (1 << 1),
RX_START = (1 << 2),
TRX_END = (1 << 3),
CCA_ED_DONE = (1 << 4),
AMI = (1 << 5),
TRX_UR = (1 << 6),
BAT_LOW = (1 << 7)
};
SKULLC_ENUM_DECLARE_BITFLAG_OPERATORS(Interrupts)
}
#endif //SKL_TUNNEL_RADIO_INTERRUPTS_HPP

View File

@ -0,0 +1,18 @@
//
// Created by erki on 28.07.22.
//
#ifndef SKL_TUNNEL_RADIO_PROTOCOL_HPP
#define SKL_TUNNEL_RADIO_PROTOCOL_HPP
#include "radio_protocol_frame.hpp"
namespace radio::protocol
{
std::size_t composeFrameBuffer(const FrameStructure& frame);
FrameStructure decomposeFrameBuffer(const std::uint8_t* data);
}
#endif //SKL_TUNNEL_RADIO_PROTOCOL_HPP

View File

@ -0,0 +1,135 @@
//
// Created by erki on 18.07.22.
//
#ifndef SKL_TUNNEL_RADIO_FRAME_HPP
#define SKL_TUNNEL_RADIO_FRAME_HPP
#include <array>
#include <cstdint>
#include <type_traits>
#include <optional>
#include <variant>
#include <utility_assert.hpp>
/**
* Reference section 37.1 of the SAMR21 datasheet for these structures.
*/
namespace radio::protocol
{
struct PhyHeader
{
std::uint8_t frame_length : 7;
std::uint8_t reserved : 1;
} __attribute__((packed));
struct FrameControlField
{
enum FrameType
{
FRAME_TYPE_BEACON = 0b000,
FRAME_TYPE_DATA = 0b001,
FRAME_TYPE_ACKNOWLEDGE = 0b010,
FRAME_TYPE_MAC_COMMAND = 0b011
};
enum AddressingMode
{
ADDRESSING_MODE_NOT_SPECIFIED = 0b00,
ADDRESSING_MODE_SHORT = 0b10,
ADDRESSING_MODE_LONG = 0b11
};
FrameType type : 3;
std::uint8_t security_enabled : 1;
std::uint8_t frame_pending : 1;
std::uint8_t ack_requested : 1;
std::uint8_t pan_id_compression : 1;
std::uint8_t : 3;
AddressingMode destination_addressing_mode : 2;
std::uint8_t frame_version : 2;
AddressingMode source_addressing_mode : 2;
} __attribute__((packed));
struct Address
{
std::variant<std::uint16_t, std::uint64_t> mac = 0ull;
std::uint16_t pan_id = 0;
FrameControlField::AddressingMode getAddressingMode() const
{
if (std::holds_alternative<std::uint16_t>(mac))
return FrameControlField::ADDRESSING_MODE_SHORT;
else if (std::holds_alternative<std::uint64_t>(mac))
return FrameControlField::ADDRESSING_MODE_LONG;
else
return FrameControlField::ADDRESSING_MODE_NOT_SPECIFIED;
}
void setShortAddress(const std::uint16_t address)
{
mac = address;
}
void setLongAddress(const std::uint64_t address)
{
mac = address;
}
std::uint16_t getShortAddress() const
{
const std::uint16_t* addr = std::get_if<std::uint16_t>(&mac);
SKULLC_ASSERT_DEBUG(addr != nullptr);
return *addr;
}
std::uint64_t getLongAddress() const
{
const std::uint64_t* addr = std::get_if<std::uint64_t>(&mac);
SKULLC_ASSERT_DEBUG(addr != nullptr);
return *addr;
}
};
struct FrameStructure
{
FrameControlField frame_control_field;
std::uint8_t sequence_number;
Address destination_address;
Address source_address;
std::uint16_t frame_checksum;
static constexpr std::size_t max_frame_size = 127;
static constexpr std::size_t max_payload_size
= max_frame_size - sizeof(FrameControlField) - sizeof(frame_checksum)
- sizeof(sequence_number) - (2 * (sizeof(std::uint16_t) + sizeof(std::uint64_t)));
std::uint8_t payload_length;
std::array<std::uint8_t, max_payload_size> payload;
FrameStructure();
static FrameStructure createDataFrame();
FrameStructure& setPayload(const std::uint8_t* data, const std::uint8_t length);
template<std::size_t N>
FrameStructure& setPayload(const std::array<std::uint8_t, N>& data)
{
static_assert(N <= max_payload_size, "Data length N exceeds max_payload_size.");
return setPayload(data.data(), std::uint8_t(N));
}
FrameStructure& setSourceAddress(const Address& addr);
FrameStructure& setDestinationAddress(const Address& addr);
std::uint8_t getTotalFrameLength() const;
std::uint8_t calculatePayloadLength(const std::uint8_t total_frame_length) const;
};
}
#endif //SKL_TUNNEL_RADIO_FRAME_HPP

View File

@ -23,6 +23,11 @@ void radio_gpio_init()
gpio_set_pin_direction(IN_RADIO_IRQ, GPIO_DIRECTION_IN); gpio_set_pin_direction(IN_RADIO_IRQ, GPIO_DIRECTION_IN);
gpio_set_pin_pull_mode(IN_RADIO_IRQ, GPIO_PULL_OFF); gpio_set_pin_pull_mode(IN_RADIO_IRQ, GPIO_PULL_OFF);
EIC->CONFIG[0].reg |= (1 << 0);
EIC->INTENSET.reg |= (1 << 0);
EIC->CTRL.reg |= (1 << 1);
gpio_set_pin_function(IN_RADIO_IRQ, PINMUX_PB00A_EXTINT0); gpio_set_pin_function(IN_RADIO_IRQ, PINMUX_PB00A_EXTINT0);
#undef PINMUX_PB00A_EXTINT0 #undef PINMUX_PB00A_EXTINT0

View File

@ -10,28 +10,24 @@
#include <hal_delay.h> #include <hal_delay.h>
#include <hal_ext_irq.h> #include <hal_ext_irq.h>
#include <utility_assert.hpp>
#include <utility_staticpointer.hpp>
#include <utility_logging.hpp>
#include <optional>
#include <algorithm>
namespace namespace
{ {
radio::HwInstance* _INSTANCE = nullptr; Utility::StaticPointer<radio::HwInstance> _INSTANCE;
Utility::IFunction<void (radio::HwInstance*)>* _CALLBACK;
void _irq_handler() void _irq_handler()
{ {
_INSTANCE->irq_handler(); SKULLC_ASSERT_DEBUG(_INSTANCE);
}
void _startup() if (_CALLBACK)
{ (*_CALLBACK)(_INSTANCE.get());
gpio_set_pin_level(OUT_RADIO_RST, false);
gpio_set_pin_level(OUT_RADIO_SLP_TR, false);
delay_ms(10);
ext_irq_enable(IN_RADIO_IRQ);
ext_irq_register(IN_RADIO_IRQ, _irq_handler);
gpio_set_pin_level(OUT_RADIO_RST, true);
delay_ms(10);
} }
} }
@ -39,43 +35,14 @@ void _startup()
namespace radio namespace radio
{ {
HwInstance* HwInstance::create_instance() HwInstance* HwInstance::instance()
{ {
return new HwInstance{}; if (!_INSTANCE.isInitialized())
{
_INSTANCE.setup();
} }
void HwInstance::irq_handler() return _INSTANCE.get();
{
}
uint8_t HwInstance::register_read(uint8_t address)
{
address = (address & 0x3F) | (1 << 7);
gpio_set_pin_level(OUT_RADIO_CS, false);
delay_us(1);
io_write(m_spi_io, &address, 1);
delay_us(1);
uint8_t data = 0xFF;
io_read(m_spi_io, &data, 1);
gpio_set_pin_level(OUT_RADIO_CS, true);
return data;
}
void HwInstance::register_write(const uint8_t address, const uint8_t value)
{
uint8_t data[2] = {
uint8_t((address & 0x3F) | (1 << 7) | (1 << 6)),
uint8_t(value & 0xEF)
};
gpio_set_pin_level(OUT_RADIO_CS, false);
io_write(m_spi_io, data, 2);
gpio_set_pin_level(OUT_RADIO_CS, true);
} }
HwInstance::HwInstance() HwInstance::HwInstance()
@ -86,7 +53,218 @@ HwInstance::HwInstance()
spi_m_sync_get_io_descriptor(m_spi, &m_spi_io); spi_m_sync_get_io_descriptor(m_spi, &m_spi_io);
spi_m_sync_enable(m_spi); spi_m_sync_enable(m_spi);
_startup(); gpio_set_pin_level(OUT_RADIO_RST, false);
gpio_set_pin_level(OUT_RADIO_SLP_TR, false);
delay_ms(10);
ext_irq_register(IN_RADIO_IRQ, _irq_handler);
gpio_set_pin_level(OUT_RADIO_RST, true);
delay_ms(10);
// Enable safe mode for TX.
register_write(Registers::TRX_CTRL_2, 0x80);
// Disable external clock output.
std::uint8_t trx_ctrl = register_read(Registers::TRX_CTRL_0);
trx_ctrl &= ~(0x07);
trx_ctrl &= ~(0x08);
register_write(Registers::TRX_CTRL_0, trx_ctrl);
// Enable interrupts.
register_write(Registers::IRQ_MASK, 0xFF);
// clear interrupts.
register_read(Registers::IRQ_STATUS);
set_current_state(States::TRX_OFF);
}
void HwInstance::set_irq_handler(Utility::IFunction<void (HwInstance*)>* cb)
{
_CALLBACK = cb;
}
Interrupts HwInstance::get_pending_irq()
{
return Interrupts(register_read(Registers::IRQ_STATUS));
}
uint8_t HwInstance::register_read(const Registers& address)
{
const uint8_t address_to_write = (uint8_t(address) & 0x3F) | (1 << 7);
gpio_set_pin_level(OUT_RADIO_CS, false);
delay_us(1);
io_write(m_spi_io, &address_to_write, 1);
delay_us(1);
uint8_t data = 0xFF;
io_read(m_spi_io, &data, 1);
gpio_set_pin_level(OUT_RADIO_CS, true);
return data;
}
void HwInstance::register_write(const Registers& address, const uint8_t value)
{
uint8_t data[2] = {
uint8_t((uint8_t(address) & 0x3F) | (1 << 7) | (1 << 6)),
uint8_t(value & 0xEF)
};
gpio_set_pin_level(OUT_RADIO_CS, false);
io_write(m_spi_io, data, 2);
gpio_set_pin_level(OUT_RADIO_CS, true);
}
HwInstance::States HwInstance::current_state() const
{
return m_current_state;
}
bool HwInstance::set_current_state(const States& new_state)
{
if (new_state == m_current_state)
return true;
m_wait_can_transition();
auto is_rx_state = [](const States& s) -> bool
{
return s == States::RX_ON || s == States::RX_AACK_ON;
};
auto is_tx_state = [](const States& s) -> bool
{
return s == States::TX_ARET_ON;
};
if ((is_rx_state(m_current_state) && is_tx_state(new_state))
|| (is_tx_state(m_current_state) && is_rx_state(new_state)))
{
set_current_state(States::PLL_ON);
}
register_write(Registers::TRX_STATE, std::uint8_t(new_state));
m_wait_can_transition();
SKULLC_LOG_DEBUG("HW: New state. %d -> %d.", m_current_state, new_state);
m_current_state = new_state;
return true;
}
void HwInstance::set_address_short(const std::uint16_t address)
{
register_write(Registers::SHORT_ADDR_0, address);
}
void HwInstance::set_address_long(const std::uint64_t address)
{
register_write(Registers::IEEE_ADDR_0, address);
}
void HwInstance::set_channel(const std::uint8_t channel)
{
static constexpr std::uint8_t channel_min = 11;
static constexpr std::uint8_t channel_max = 26;
SKULLC_ASSERT_DEBUG(channel >= channel_min && channel <= channel_max);
if (channel > channel_max || channel < channel_min)
{
return;
}
std::uint8_t reg_value = register_read(Registers::PHY_CC_CCA);
reg_value &= ~0x1F;
reg_value |= channel & 0x1F;
register_write(Registers::PHY_CC_CCA, reg_value);
}
void HwInstance::set_pan_id(const std::uint16_t pan_id)
{
register_write(Registers::PAN_ID_0, pan_id);
}
void HwInstance::set_tx_power(std::int16_t power)
{
static constexpr std::array<std::uint8_t, 21> dbm_to_tx_power{
0x0F, 0x0F, 0x0F, 0x0E, 0x0E, 0x0E, 0x0E, 0x0D, 0x0D, 0x0C, 0x0C,
0x0B, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x03, 0x00
};
power += 17;
power = std::clamp(power, std::int16_t(0), std::int16_t(21));
register_write(Registers::PHY_TX_PWR, dbm_to_tx_power[power]);
}
void HwInstance::set_max_retries(std::uint8_t retries)
{
retries = std::clamp<std::uint8_t>(retries, 0, 7);
constexpr std::uint8_t mask = 0xF0;
std::uint8_t reg_value = register_read(Registers::XAH_CTRL_0);
reg_value &= ~mask;
reg_value |= (retries << 4) & mask;
register_write(Registers::XAH_CTRL_0, reg_value);
}
void HwInstance::set_csma_max_retries(const std::optional<std::uint8_t>& retries)
{
std::uint8_t value_to_write = 0;
if (!retries)
value_to_write = 7;
else
value_to_write = std::clamp<std::uint8_t>(*retries, 0, 5);
constexpr std::uint8_t mask = 0x0E;
std::uint8_t reg_value = register_read(Registers::XAH_CTRL_0);
reg_value &= ~mask;
reg_value |= (value_to_write << 1) & mask;
register_write(Registers::XAH_CTRL_0, reg_value);
}
void HwInstance::set_csma_backoff_exponential(std::uint8_t max, std::uint8_t min)
{
max = std::clamp<std::uint8_t>(max, 0, 8);
min = std::clamp<std::uint8_t>(min, 0, max);
const std::uint8_t reg_value = (max << 4) | min;
register_write(Registers::CSMA_BE, reg_value);
}
void HwInstance::set_csma_seed(const std::uint16_t entropy)
{
std::array<std::uint8_t, 2> entropy_data{ 0 };
std::memcpy(entropy_data.data(), &entropy, 2);
register_write(Registers::CSMA_SEED_0, entropy_data[0]);
constexpr std::uint8_t mask = 0x07;
std::uint8_t reg_value = register_read(Registers::CSMA_SEED_1);
reg_value &= ~mask;
reg_value |= entropy_data[1] & mask;
register_write(Registers::CSMA_SEED_1, reg_value);
}
/**
* @brief Blocks until a state transition can be initiated.
*/
void HwInstance::m_wait_can_transition()
{
States radio_status = States::TRANSITION_IN_PROGRESS;
do
{
radio_status = States(register_read(Registers::TRX_STATUS) & 0x1F);
} while (radio_status == States::BUSY_RX || radio_status == States::BUSY_TX ||
radio_status == States::BUSY_RX_AACK || radio_status == States::BUSY_TX_ARET ||
radio_status == States::TRANSITION_IN_PROGRESS);
} }
} }

View File

@ -0,0 +1,146 @@
//
// Created by erki on 28.07.22.
//
#include "radio_protocol.hpp"
#include <cstring>
namespace
{
using namespace radio::protocol;
[[nodiscard]] std::uint8_t* serializeAddress(std::uint8_t* data, const Address& address)
{
std::memcpy(data, &address.pan_id, sizeof(address.pan_id));
data += sizeof(address.pan_id);
if (address.getAddressingMode() == FrameControlField::ADDRESSING_MODE_SHORT)
{
const std::uint16_t mac = address.getShortAddress();
std::memcpy(data, &mac, sizeof(mac));
data += sizeof(mac);
}
else
{
const std::uint64_t mac = address.getLongAddress();
std::memcpy(data, &mac, sizeof(mac));
data += sizeof(mac);
}
return data;
}
[[nodiscard]] std::uint8_t* serializePhyHeader(std::uint8_t* data, const PhyHeader& header)
{
static_assert(sizeof(PhyHeader) == 1, "PHY Header must be 1 byte/octet.");
std::memcpy(data, &header, sizeof(PhyHeader));
data += sizeof(PhyHeader);
return data;
}
[[nodiscard]] std::uint8_t* serializeMacProtocolDataUnit(std::uint8_t* data, const FrameStructure& frame)
{
static_assert(sizeof(frame.frame_control_field) == 2, "FCF must be 2 bytes/octets.");
std::memcpy(data, &frame.frame_control_field, sizeof(frame.frame_control_field));
data += sizeof(FrameControlField);
std::memcpy(data, &frame.sequence_number, sizeof(frame.sequence_number));
data += sizeof(frame.sequence_number);
data = serializeAddress(data, frame.destination_address);
data = serializeAddress(data, frame.source_address);
std::memcpy(data, frame.payload.data(), frame.payload_length);
data += frame.payload_length;
return data;
}
[[nodiscard]] const std::uint8_t* deserializeAddress(const std::uint8_t* data, const FrameControlField::AddressingMode& addressing_mode, Address& address)
{
std::memcpy(&address.pan_id, data, sizeof(address.pan_id));
data += sizeof(address.pan_id);
if (addressing_mode == FrameControlField::ADDRESSING_MODE_SHORT)
{
std::uint16_t mac = 0;
std::memcpy(&mac, data, sizeof(mac));
data += sizeof(mac);
address.setShortAddress(mac);
}
else
{
std::uint64_t mac = 0;
std::memcpy(&mac, data, sizeof(mac));
data += sizeof(mac);
address.setLongAddress(mac);
}
return data;
}
[[nodiscard]] const std::uint8_t* deserializePhyHeader(const std::uint8_t* data, PhyHeader& header)
{
std::memcpy(&header, data, sizeof(PhyHeader));
data += sizeof(PhyHeader);
return data;
}
[[nodiscard]] FrameStructure deserializeMacProtocolDataUnit(const std::uint8_t* data, const std::uint8_t total_frame_length)
{
FrameStructure frame;
std::memcpy(&frame.frame_control_field, data, sizeof(frame.frame_control_field));
data += sizeof(frame.frame_control_field);
std::memcpy(&frame.sequence_number, data, sizeof(frame.sequence_number));
data += sizeof(frame.sequence_number);
data = deserializeAddress(data, frame.frame_control_field.destination_addressing_mode, frame.destination_address);
data = deserializeAddress(data, frame.frame_control_field.source_addressing_mode, frame.source_address);
frame.payload_length = frame.calculatePayloadLength(total_frame_length);
std::memcpy(frame.payload.data(), data, frame.payload_length);
data += frame.payload_length;
std::memcpy(&frame.frame_checksum, data, sizeof(frame.frame_checksum));
return frame;
}
}
namespace radio::protocol
{
std::size_t composeFrameBuffer(std::uint8_t* data, const FrameStructure& frame)
{
std::uint8_t* const data_start = data;
PhyHeader header;
std::memset(&header, 0, sizeof(PhyHeader));
header.frame_length = frame.getTotalFrameLength();
data = serializePhyHeader(data, header);
data = serializeMacProtocolDataUnit(data, frame);
return data - data_start;
}
FrameStructure decomposeFrameBuffer(const std::uint8_t* data)
{
PhyHeader header;
data = deserializePhyHeader(data, header);
const std::uint8_t total_frame_length = header.frame_length;
const FrameStructure frame = deserializeMacProtocolDataUnit(data, total_frame_length);
return frame;
}
}

View File

@ -0,0 +1,99 @@
//
// Created by erki on 28.07.22.
//
#include "radio_protocol_frame.hpp"
#include <cstring>
namespace
{
using namespace radio::protocol;
template<typename T>
constexpr T zeroInitialized()
{
static_assert(std::is_trivially_default_constructible<T>::value, "Struct is not trivially default constructible.");
T t;
std::memset(&t, 0, sizeof(T));
return t;
}
std::uint8_t get_address_size(const Address& address)
{
std::uint8_t size = sizeof(address.pan_id);
if (address.getAddressingMode() == FrameControlField::ADDRESSING_MODE_SHORT)
size += sizeof(std::uint16_t);
else
size += sizeof(std::uint64_t);
return size;
};
}
namespace radio::protocol
{
FrameStructure::FrameStructure()
: frame_control_field(zeroInitialized<FrameControlField>())
, sequence_number(0)
, frame_checksum(0)
, payload_length(0)
{ }
FrameStructure FrameStructure::createDataFrame()
{
FrameStructure structure;
structure.frame_control_field.type = FrameControlField::FRAME_TYPE_DATA;
structure.frame_control_field.frame_version = 1;
return structure;
}
FrameStructure& FrameStructure::setPayload(const std::uint8_t* data, const std::uint8_t length)
{
std::memmove(payload.data(), data, length);
return *this;
}
FrameStructure& FrameStructure::setSourceAddress(const Address& addr)
{
source_address = addr;
frame_control_field.source_addressing_mode = addr.getAddressingMode();
return *this;
}
FrameStructure& FrameStructure::setDestinationAddress(const Address& addr)
{
destination_address = addr;
frame_control_field.destination_addressing_mode = addr.getAddressingMode();
return *this;
}
std::uint8_t FrameStructure::getTotalFrameLength() const
{
std::uint8_t size = sizeof(frame_control_field) + sizeof(sequence_number);
size += get_address_size(destination_address);
size += get_address_size(source_address);
size += payload_length;
size += sizeof(frame_checksum);
return size;
}
std::uint8_t FrameStructure::calculatePayloadLength(const std::uint8_t total_frame_length) const
{
std::uint8_t payload_length = total_frame_length - sizeof(frame_control_field) - sizeof(sequence_number);
payload_length -= get_address_size(destination_address);
payload_length -= get_address_size(source_address);
payload_length -= sizeof(frame_checksum);
return payload_length;
}
}

38
syscalls.c Normal file
View File

@ -0,0 +1,38 @@
//
// Created by erki on 10.07.22.
//
#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>
extern int __io_putchar(int ch) __attribute__((weak));
extern int __io_getchar(void) __attribute__((weak));
__attribute__((weak)) int _read(int file, char *ptr, int len)
{
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
*ptr++ = __io_getchar();
}
return len;
}
__attribute__((weak)) int _write(int file, char *ptr, int len)
{
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
__io_putchar(*ptr++);
}
return len;
}