From 89ef1b3938699a13d6c226bca0214c9ab590f188 Mon Sep 17 00:00:00 2001 From: NaifBanana <30419422+NaifBanana@users.noreply.github.com> Date: Thu, 22 Oct 2026 14:41:48 -0500 Subject: [PATCH] Now builds with RadioLib! --- .gitignore | 2 + CMakeLists.txt | 44 ++++---- Common/CMakeLists.txt | 19 ++++ Common/FreeRTOSConfig.h | 132 ++++++++++++++++++++++++ Common/PicoHal.cpp | 49 +++++++++ Common/PicoHal.h | 221 ++++++++++++++++++++++++++++++++++++++++ Yeller/CMakeLists.txt | 2 +- Yeller/main.cpp | 25 +++-- 8 files changed, 461 insertions(+), 33 deletions(-) create mode 100644 Common/CMakeLists.txt create mode 100644 Common/FreeRTOSConfig.h create mode 100644 Common/PicoHal.cpp create mode 100644 Common/PicoHal.h diff --git a/.gitignore b/.gitignore index 7d09426..d1d7a5a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ build .nvim .git !.git/hooks +FreeRTOS_Kernel_import.cmake +pico_sdk_import.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index f7b6bf2..826f20c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,52 +6,44 @@ set(PROJECT_NAME "GateOpener") set(PICO_SDK_PATH "C:/Users/naifa/Documents/Projects/Hardware/Microcontroller/SDK/PicoC++/pico-sdk") set(PICO_TOOLCHAIN_PATH "C:/Users/naifa/Documents/Projects/Hardware/Microcontroller/SDK/arm-gnu-toolchain-15.3.rel2-mingw-w64-x86_64-arm-none-eabi" ) set(FREERTOS_KERNEL_PATH "C:/Users/naifa/Documents/Projects/Hardware/FreeRTOS-Kernel") -set(FREERTOS_CFG_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/Config") -set(RADIOLIB_PATH "C:/Program Files (x86)/radiolib") +set(COMMON_FILES "${CMAKE_CURRENT_LIST_DIR}/Common") +# set(RADIOLIB_PATH "C:/Program Files (x86)/radiolib") +set(RADIOLIB_PATH "C:/Users/naifa/Documents/Projects/Hardware/libs/RadioLib") - -set(CMAKE_PREFIX_PATH - ${CMAKE_PREFIX_PATH} - ${FREERTOS_KERNEL_PATH} - ${RADIOLIB_PATH} -) set(FREERTOS_PICO ${FREERTOS_KERNEL_PATH}/portable/ThirdParty/GCC/RP2040) +set(CMAKE_LIBRARY_PATH + ${CMAKE_LIBRARY_PATH} + ${FREERTOS_KERNEL_PATH} + ${RADIOLIB_PATH}/lib +) include(./pico_sdk_import.cmake) project(${PROJECT_NAME} VERSION ${VERSION} LANGUAGES CXX C ASM) + include(./FreeRTOS_Kernel_import.cmake) pico_sdk_init() +add_subdirectory(${RADIOLIB_PATH} ./radiolib) + message(STATUS "Pico SDK dir is ${PICO_SDK_PATH}") message(STATUS "FreeRTOS root is ${FREERTOS_KERNEL_PATH}") +message(STATUS "Libraries are ${CMAKE_LIBRARY_PATH}") -include_directories(${FREERTOS_CFG_DIRECTORY}/) +include_directories( + ${COMMON_FILES}/ +) include_directories(SYSTEM AFTER # ${FREERTOS_PICO} ${FREERTOS_PICO}/include ${FREERTOS_KERNEL_PATH}/include - ${FREERTOS_KERNEL_PATH}/portable/GCC/ARM_CM0 - ${RADIOLIB_PATH}/include + ${FREERTOS_KERNEL_PATH}/portable/GCC/ARM_CM0 ) add_compile_definitions(configTICK_CORE=0) -# include(${FREERTOS_PICO}/FreeRTOS_Kernel_import.cmake) -# add_library(FreeRTOS STATIC -# ${FREERTOS_KERNEL_PATH}/event_groups.c -# ${FREERTOS_KERNEL_PATH}/list.c -# ${FREERTOS_KERNEL_PATH}/queue.c -# ${FREERTOS_KERNEL_PATH}/stream_buffer.c -# ${FREERTOS_KERNEL_PATH}/tasks.c -# ${FREERTOS_KERNEL_PATH}/timers.c -# ${FREERTOS_KERNEL_PATH}/portable/MemMang/heap_3.c -# # ${FREERTOS_SRC_DIRECTORY}/portable/GCC/ARM_CM0/port.c -# ${FREERTOS_PICO}/port.c -# ) - - +add_subdirectory(./Common) add_subdirectory(./Talky) +add_subdirectory(./Yeller) # add_subdirectory(./Listener) -# add_subdirectory(./Yeller) diff --git a/Common/CMakeLists.txt b/Common/CMakeLists.txt new file mode 100644 index 0000000..a4abb46 --- /dev/null +++ b/Common/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.5.0) + +add_library(RadioLibPico + ${COMMON_FILES}/PicoHal.cpp +) +target_link_libraries(RadioLibPico + pico_stdlib + hardware_spi + hardware_pwm + RadioLib +) +target_include_directories(RadioLibPico + PUBLIC ${PICO_SDK_PATH}/src/rp2_common/hardware_spi/include + PUBLIC ${PICO_SDK_PATH}/src/rp2_common/hardware_pwm/include +) +target_compile_definitions(RadioLibPico + PUBLIC RADIOLIB_BUILD_RPI_PICO +) + diff --git a/Common/FreeRTOSConfig.h b/Common/FreeRTOSConfig.h new file mode 100644 index 0000000..034def1 --- /dev/null +++ b/Common/FreeRTOSConfig.h @@ -0,0 +1,132 @@ +/* + * FreeRTOS + * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * https://www.FreeRTOS.org + * https://aws.amazon.com/freertos + * + */ +#ifndef FREERTOS_CONFIG_H +#define FREERTOS_CONFIG_H + +#define PICO_LORA_SPI_SPEED 500*1000 +#define PICO_LORA_SPI_CS 5 +#define PICO_LORA_SPI_DIO1 26 +#define PICO_LORA_RST 20 +#define PICO_LORA_MISO 16 +#define PICO_LORA_MOSI 15 +#define PICO_LORA_SCK 14 +#define PICO_LORA_BUSY 4 + +/* Use Pico SDK ISR handlers */ +#define vPortSVCHandler isr_svcall +#define xPortPendSVHandler isr_pendsv +#define xPortSysTickHandler isr_systick + +/* Scheduler Related */ +#define configUSE_PREEMPTION 1 // Allow tasks to be pre-empted +#define configUSE_TIME_SLICING 1 // Allow FreeRTOS to switch tasks at each tick +#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 +#define configUSE_TICKLESS_IDLE 0 +#define configCPU_CLOCK_HZ 133000000 // 133MHz for RP2040 +#define configTICK_RATE_HZ (TickType_t)1000 // FreeRTOS beats per second +#define configMAX_PRIORITIES 24 // Max number of priority values (0-24) +#define configMINIMAL_STACK_SIZE (configSTACK_DEPTH_TYPE)512 +#define configUSE_16_BIT_TICKS 0 +#define configIDLE_SHOULD_YIELD 1 +#define configMAX_TASK_NAME_LEN 16 +#define configTASK_NOTIFICATION_ARRAY_ENTRIES 3 + +/* Synchronization Related */ +#define configUSE_MUTEXES 0 +#define configUSE_RECURSIVE_MUTEXES 0 +#define configUSE_COUNTING_SEMAPHORES 0 +#define configUSE_TASK_NOTIFICATIONS 1 +#define configUSE_QUEUE_SETS 0 +#define configQUEUE_REGISTRY_SIZE 10 +#define configUSE_NEWLIB_REENTRANT 0 +#define configENABLE_BACKWARD_COMPATIBILITY 0 +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 + +/* System */ +#define configSTACK_DEPTH_TYPE uint32_t +#define configMESSAGE_BUFFER_LENGTH_TYPE size_t // Defaults to size_t for backward compatibility, + // but can be changed if lengths will always be less + // than the number of bytes in a size_t. + +/* Memory allocation related definitions. */ +#define configSUPPORT_STATIC_ALLOCATION 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 // Get FreeRTOS to allocation task memory +#define configAPPLICATION_ALLOCATED_HEAP 0 +#define configTOTAL_HEAP_SIZE (128*1024) + +/* Hook function related definitions. */ +#define configUSE_IDLE_HOOK 0 +#define configUSE_TICK_HOOK 0 +#define configCHECK_FOR_STACK_OVERFLOW 0 +#define configUSE_MALLOC_FAILED_HOOK 0 +#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 + +/* Run time and task stats gathering related definitions. */ +#define configGENERATE_RUN_TIME_STATS 0 +#define configUSE_TRACE_FACILITY 0 +#define configUSE_STATS_FORMATTING_FUNCTIONS 0 + +/* Co-routine related definitions. */ +#define configUSE_CO_ROUTINES 0 +#define configMAX_CO_ROUTINE_PRIORITIES 1 + +/* Software timer related definitions. */ +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) +#define configTIMER_QUEUE_LENGTH 10 +#define configTIMER_TASK_STACK_DEPTH 1024 + +/* RP2040 specific */ +#define configSUPPORT_PICO_SYNC_INTEROP 1 +#define configSUPPORT_PICO_TIME_INTEROP 1 + +/* Define to trap errors during development. */ +#include +#define configASSERT( x ) + +/* Optional functions - most linkers will remove unused functions anyway. */ +#define INCLUDE_vTaskPrioritySet 1 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_vTaskDelayUntil 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_xTaskGetSchedulerState 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 +#define INCLUDE_uxTaskGetStackHighWaterMark 1 +#define INCLUDE_xTaskGetIdleTaskHandle 1 +#define INCLUDE_eTaskGetState 1 +#define INCLUDE_xTaskAbortDelay 1 +#define INCLUDE_xTaskGetHandle 1 +#define INCLUDE_xTaskResumeFromISR 1 +#define INCLUDE_xQueueGetMutexHolder 1 +#define INCLUDE_xResumeFromISR 1 +#define INCLUDE_xEventGroupSetBitFromISR 1 +#define INCLUDE_xTimerPendFunctionCall 1 + +/* A header file that defines trace macro can be included here. */ + +#endif /* FREERTOS_CONFIG_H */ diff --git a/Common/PicoHal.cpp b/Common/PicoHal.cpp new file mode 100644 index 0000000..03e6733 --- /dev/null +++ b/Common/PicoHal.cpp @@ -0,0 +1,49 @@ +#include "PicoHal.h" + +#if defined(RADIOLIB_BUILD_RPI_PICO) + +// pre-calculated pulse-widths for 1200 and 2200Hz +// we do this to save calculation time (see https://github.com/khoih-prog/RP2040_PWM/issues/6) +#define SLEEP_1200 416.666 +#define SLEEP_2200 227.272 + +static uint32_t toneLoopPin; +static unsigned int toneLoopFrequency; +static unsigned long toneLoopDuration; + +// === NOTE === +// The tone(...) implementation uses the second core on the RPi Pico. This is to diminish as much +// jitter in the output tones as possible. + +static void toneLoop() { + gpio_set_dir(toneLoopPin, GPIO_OUT); + + uint32_t sleep_dur; + if(toneLoopFrequency == 1200) { + sleep_dur = SLEEP_1200; + } else if(toneLoopFrequency == 2200) { + sleep_dur = SLEEP_2200; + } else { + sleep_dur = 500000 / toneLoopFrequency; + } + + // tone bitbang + while(1) { + gpio_put(toneLoopPin, 1); + sleep_us(sleep_dur); + gpio_put(toneLoopPin, 0); + sleep_us(sleep_dur); + tight_loop_contents(); + } +} + +void PicoHal::tone(uint32_t pin, unsigned int frequency, unsigned long duration) { + // tones on the Pico are generated using bitbanging. This process is offloaded to the Pico's second core + multicore_reset_core1(); + toneLoopPin = pin; + toneLoopFrequency = frequency; + toneLoopDuration = duration; + multicore_launch_core1(toneLoop); +} + +#endif diff --git a/Common/PicoHal.h b/Common/PicoHal.h new file mode 100644 index 0000000..b00ff2a --- /dev/null +++ b/Common/PicoHal.h @@ -0,0 +1,221 @@ +#ifndef PICO_HAL_H +#define PICO_HAL_H + +#if defined(RADIOLIB_BUILD_RPI_PICO) + +// include RadioLib +#include + +// include the necessary Pico libraries +#include +#include "hardware/spi.h" +#include "hardware/timer.h" +#include "hardware/pwm.h" +#include "hardware/clocks.h" +#include "hardware/gpio.h" +#include "pico/multicore.h" + +#define PI_PICO_MAX_USER_GPIO (NUM_BANK0_GPIOS) + +// because the Pico SDK does not allow to pass user data into interrupt handlers, +// we keep it as a global here. This is hacky and means that multiple PicoHal +// instances share the same interrupts, which is weird and will probably break. +// However, there seems to be no real use case for creating multiple intances of the HAL +static irq_handler_t picoHalUserCallbacks[PI_PICO_MAX_USER_GPIO] = { 0 }; +static uint32_t picoHalIrqEventMasks[PI_PICO_MAX_USER_GPIO] = { 0 }; +static uint64_t picoHalIrqMask = 0; + +static void picoInterruptHandler(void) { + for(unsigned int gpio = 0; gpio < PI_PICO_MAX_USER_GPIO; gpio++) { + // Skip pins that are not managed by us. + if(picoHalIrqEventMasks[gpio] == 0) { + continue; + } + uint32_t activeEvents = gpio_get_irq_event_mask(gpio); + if(activeEvents) { + gpio_acknowledge_irq(gpio, activeEvents); + if((activeEvents & picoHalIrqEventMasks[gpio]) && picoHalUserCallbacks[gpio]) { + picoHalUserCallbacks[gpio](); + } + } + } +} + +// create a new Raspberry Pi Pico hardware abstraction +// layer using the Pico SDK +// the HAL must inherit from the base RadioLibHal class +// and implement all of its virtual methods +class PicoHal : public RadioLibHal { +public: + PicoHal(spi_inst_t *spiChannel, uint32_t misoPin, uint32_t mosiPin, uint32_t sckPin, uint32_t spiSpeed = 500 * 1000) + : RadioLibHal(GPIO_IN, GPIO_OUT, 0, 1, GPIO_IRQ_EDGE_RISE, GPIO_IRQ_EDGE_FALL), + _spiChannel(spiChannel), + _spiSpeed(spiSpeed), + _misoPin(misoPin), + _mosiPin(mosiPin), + _sckPin(sckPin){} + + void init() override { + stdio_init_all(); + spiBegin(); + } + + void term() override { + spiEnd(); + } + + // GPIO-related methods (pinMode, digitalWrite etc.) should check + // RADIOLIB_NC as an alias for non-connected pins + void pinMode(uint32_t pin, uint32_t mode) override { + if (pin == RADIOLIB_NC) { + return; + } + + gpio_init(pin); + gpio_set_dir(pin, mode); + } + + void digitalWrite(uint32_t pin, uint32_t value) override { + if (pin == RADIOLIB_NC) { + return; + } + + gpio_put(pin, (bool)value); + } + + uint32_t digitalRead(uint32_t pin) override { + if (pin == RADIOLIB_NC) { + return 0; + } + + return gpio_get(pin); + } + + void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override { + if (interruptNum == RADIOLIB_NC) { + return; + } + + // set callbacks + picoHalUserCallbacks[interruptNum] = (irq_handler_t)interruptCb; + picoHalIrqEventMasks[interruptNum] = mode; + + // is it a new interrupt for us to grab ? + if (!(picoHalIrqMask & (1ULL << interruptNum))) { + // if we have a handler in place, we must remove it to avoid 'assert' in the PDK + // and we must disable interrupt to make sure we don't miss anything during that + // shuffling + if (picoHalIrqMask) { + irq_set_enabled(IO_IRQ_BANK0, false); + gpio_remove_raw_irq_handler_masked64(picoHalIrqMask, picoInterruptHandler); + } + + // (re-)add shared handler with the new mask + picoHalIrqMask |= (1ULL << interruptNum); + gpio_add_raw_irq_handler_masked64(picoHalIrqMask, picoInterruptHandler); + irq_set_enabled(IO_IRQ_BANK0, true); + } + + // enable GPIO to generate interrupt + gpio_set_irq_enabled(interruptNum, mode, true); + } + + void detachInterrupt(uint32_t interruptNum) override { + if (interruptNum == RADIOLIB_NC) { + return; + } + + // disable the IRQ + gpio_set_irq_enabled(interruptNum, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, false); + + // clear callbacks + picoHalUserCallbacks[interruptNum] = NULL; + picoHalIrqEventMasks[interruptNum] = 0; + } + + void delay(unsigned long ms) override { + sleep_ms(ms); + } + + void delayMicroseconds(unsigned long us) override { + sleep_us(us); + } + + unsigned long millis() override { + return to_ms_since_boot(get_absolute_time()); + } + + unsigned long micros() override { + return to_us_since_boot(get_absolute_time()); + } + + long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override { + if (pin == RADIOLIB_NC) { + return 0; + } + + this->pinMode(pin, GPIO_IN); + uint32_t start = this->micros(); + uint32_t curtick = this->micros(); + + while (this->digitalRead(pin) == state) { + if ((this->micros() - curtick) > timeout) { + return 0; + } + } + + return (this->micros() - start); + } + + void tone(uint32_t pin, unsigned int frequency, unsigned long duration = 0) override; + + void noTone(uint32_t pin) override { + (void)pin; // avoid unused parameter warning + multicore_reset_core1(); + } + + void spiBegin() override { + spi_init(_spiChannel, _spiSpeed); + spi_set_format(_spiChannel, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); + + gpio_set_function(_sckPin, GPIO_FUNC_SPI); + gpio_set_function(_mosiPin, GPIO_FUNC_SPI); + gpio_set_function(_misoPin, GPIO_FUNC_SPI); + } + + void spiBeginTransaction() override {} + + void spiTransfer(uint8_t *out, size_t len, uint8_t *in) override { + spi_write_read_blocking(_spiChannel, out, in, len); + } + + void yield() override { + tight_loop_contents(); + } + + void spiEndTransaction() override {} + + void spiEnd() override { + spi_deinit(_spiChannel); + } + + void pullUpDown(uint32_t pin, bool enable, bool up) override { + if (up) { + gpio_pull_up(pin); + } else { + gpio_pull_down(pin); + } + } + +private: + // the HAL can contain any additional private members + spi_inst_t *_spiChannel; + uint32_t _spiSpeed; + uint32_t _misoPin; + uint32_t _mosiPin; + uint32_t _sckPin; +}; + +#endif + +#endif diff --git a/Yeller/CMakeLists.txt b/Yeller/CMakeLists.txt index 296c96a..4cae60d 100644 --- a/Yeller/CMakeLists.txt +++ b/Yeller/CMakeLists.txt @@ -7,7 +7,7 @@ add_executable(${APP_NAME} ) target_link_libraries(${APP_NAME} LINK_PUBLIC - pico_stdlib + RadioLibPico FreeRTOS-Kernel FreeRTOS-Kernel-Heap4 ) diff --git a/Yeller/main.cpp b/Yeller/main.cpp index 8e58418..269122a 100644 --- a/Yeller/main.cpp +++ b/Yeller/main.cpp @@ -1,16 +1,29 @@ -#include "FreeRTOSConfig.h" -#include "FreeRTOS.h" #include -#include -#include +#include +#include +#include +#include +#include int main() { + PicoHal* pico = new PicoHal( + spi0, + PICO_LORA_MISO, + PICO_LORA_MOSI, + PICO_LORA_SCK + ); // Currently written as numbered 1-40th pin on Pico - SX1262 radio = new Module(5, 26, 20, 4); + SX1262 radio = new Module( + pico, + PICO_LORA_SPI_CS, + PICO_LORA_SPI_DIO1, + PICO_LORA_RST, + PICO_LORA_BUSY + ); ConfigLoRa_t config; config.frequency = 915; int state = radio.begin(config); - if (state == RADIOLIB_ERR_NONE) { + if (state == RADIOLIB_ERR_NONE){ printf("LoRa module initialized\n"); while(true) { state = radio.startTransmit("Howdiddly doo");