diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c795b054e5ade51b7031abab1581a5b7e2d2f5ba --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 8704dedb898f8dd6f53b3bc2db0cf7c9f7223449..e2c975a8ec3f6c2f432088e4059802df800afca8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,13 @@ project(pinedio-lora-driver-project) -cmake_minimum_required(VERSION 3.21) +cmake_minimum_required(VERSION 3.10) +if(POLICY CMP0012) + cmake_policy(SET CMP0012 NEW) +endif() set(CMAKE_CXX_STANDARD 20) -option(BUILD_FOR_PINEPHONE "Build PinePhone support" FALSE) -option(BUILD_FOR_USB "Build support for the USB adapter" FALSE) add_subdirectory(src) add_subdirectory(libs) -add_subdirectory(apps) +add_subdirectory(testapp) diff --git a/README.md b/README.md index 4cb4c6e2e49fe9ad5eb41a5298d1ad993cd8d79a..12fd688b176062264e0b7b2ff790330b64927452 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,34 @@ -# PineDio LoRa drivers -PineDio is the new LoRa-based product range at [Pine64](https://pine64.org). This project implements a C++ driver for the [LoRa USB adapter](https://wiki.pine64.org/wiki/Pinedio#USB_adapter) and the [Pinephone backplate](https://wiki.pine64.org/wiki/Pinedio#Pinephone_backplate). - -## Build -Build this project like any CMake project: - - Clone the project from Git - - Initialize/update the Git submodules - - Create a build directory and invoke CMake with command line parameters to choose the target hardware - - Build - - Enjoy +# Board-Docs +[LoRa USB adapter](https://wiki.pine64.org/wiki/Pinedio#USB_adapter) +[Pinephone backplate](https://wiki.pine64.org/wiki/Pinedio#Pinephone_backplate). +# Build ```bash git clone xxx cd pinedio-lora-driver git submodule update --init +``` +In the SX-Library, the workaround-patch needs to be applied due to a not yet debugged bug in the kernel driver. Afterwards: +```bash mkdir build && cd build -cmake -DBUILD_FOR_PINEPHONE=0 -DBUILD_FOR_USB=1 .. +cmake .. make -j ``` -2 CMake options are available : -- `BUILD_FOR_PINEPHONE` : build the driver for the pinephone backplate and the `pinephone-communicator` test app. -- `BUILD_FOR_USB` : build the driver for the USB adapter and the `usb-communicator` test app. - -## Dependencies +# Dependencies You need to install and load [this driver](https://github.com/rogerjames99/spi-ch341-usb) to be able to use the USB adapter. This spi-ch341-usb is a driver that configures the CH341 chip (USB <-> serial converter) mounted on the USB adapter and exposes it as a `spidev` (userland SPI API) device. -## Run the test applications -The test applications (one for the Pinephone, one for the USB adapter) provide a very basic chat application : it prints all the data received on the LoRa radio and allows sending messages to other LoRa devices. - -The *communicator* test app are available in the directory `apps/` : - -```bash -./apps/pinephone-communicator/pinephone-communicator -``` -or -```bash -./apps/usb-communicator/usb-communicator -``` - -## Demo - - -[Here's a video showcasing this project](https://video.codingfield.com/videos/watch/5a68be9e-01a2-43aa-af60-595366619553). - -## Overview of the project structure -The goal of this implementation is to provide a common interface for both devices (Pinephone add-on and USB adapter) as they are both based on the same LoRa module (Semtech SX1262). - -The implementation specific for the Pinephone is located in the class `PineDio::LoRa::PinephoneBackplate`. It initializes the I²C port to communicate with the LoRa backplate. The LoRa backplate PCB is based on the SX1262 and uses a simple ATtiny84 MCU to convert the I²C bus from the Pinephone into the SPI bus for the SX1262 (it run [this firmware](https://github.com/zschroeder6212/tiny-i2c-spi)). +# Current state of this project +This is only a PoC currently. -The implementation specific for the USB adapter is located in the class `PineDio::LoRa::UsbAdapter`. It uses the `spidev` API to access the SPI bus from the Linux userland. - -Both classes derive from `SX126x`, the generic driver from [this repo](https://github.com/YukiWorkshop/sx126x_driver). - -The generic abstraction is provided by the class `PineDio::LoRa::PinedioLoraRadio`, which takes a reference to a `SX126x`-derived object (which can be either `PinephoneBackplate` or `UsbAdapter`). - -At the application level, all you have to do is instantiate the driver for the targeted device and configure it if necessary, pass it to an instance of PineDioLoraRadio and then use this instance to send and receive data: - -```c++ -PineDio::LoRa::PinephoneBackplate pinephoneBackplate("/dev/i2c-2"); -pinephoneBackplate.Initialize(); -PineDio::LoRa::PinedioLoraRadio radio(pinephoneBackplate); -radio.Send({...}); -auto data = radio.Receive(); -``` -or -```c++ -PineDio::LoRa::UsbAdapter usbAdapter; -PineDio::LoRa::PinedioLoraRadio radio(usbAdapter); -radio.Send({...}); -auto data = radio.Receive(); -``` - -Linking with this driver in an external application should be as easy as linking (`target_link_libraries`) with `pinedio-lora-driver` and `pinedio-lora-driver-usb` or `pinedio-lora-driver-pinephone`. See *communicator* demo applications in the folder `apps`. - -## Current state of this project -This project is at its very beginning. There are still a lot of unnecessary `sleep()`, and most of the configuration is hard-coded. - -The test application (usb-communicator and pinephone-communicator) are also in a very early stage of development. They are already able to print all data received on the LoRa radio and to send messages from the standard input. - -### TODO +## TODO - [ ] Remove unnecessary `sleep()` - - [ ] Remove hard-coded configuration in the driver, add API to configure the driver - - [ ] Communicator test app: configure the driver parameters (frequency, LoRa settings,..) via command line options and/or configuration file - - [ ] Improve the implementation to provide synchronous and asynchronous API - - [ ] Probably many other things + - [ ] migrate to https://github.com/dimich-dmb/spi-ch341-usb and test if it works better (eg. without the patch). + - [ ] Expose LoRa-Config to be able to fed into the library + - [ ] replace some mutex-hacks with proper notify + - [ ] Make a file that works on PineDio as well as the PinePhone LoRa-Backplate + - [ ] ... -## License +# License This project is released under the terms of the **LGPLv3 license**. -## Acknowledgements -This project is based on [this C++ SX126x driver](https://github.com/YukiWorkshop/sx126x_driver) from [YukiWorkshop](https://github.com/YukiWorkshop). - -The implementation of the driver for the USB adapter is possible thanks to [rogerjames99's](https://github.com/rogerjames99) [fork](https://github.com/rogerjames99/spi-ch341-usb) of the [spi-ch341-usb module](https://github.com/gschorcht/spi-ch341-usb) from [Gunar Schorcht](https://github.com/gschorcht). - -The LoRa add-on board works thanks to a ATtiny MCU running [this firmware](https://github.com/zschroeder6212/tiny-i2c-spi) from [Zachary Schroeder](https://github.com/zschroeder6212). - -This project wouldn't be possible without the good work from [Pine64](https://pine64.org), which works hands in hands with the open source community to design and build nice and open devices! \ No newline at end of file diff --git a/SX126x_driver_workaround_big_pkgs.patch b/SX126x_driver_workaround_big_pkgs.patch new file mode 100644 index 0000000000000000000000000000000000000000..a712202f7d289aac2bed02d8665ccf705a076ea7 --- /dev/null +++ b/SX126x_driver_workaround_big_pkgs.patch @@ -0,0 +1,81 @@ +diff --git a/SX126x.cpp b/SX126x.cpp +index 5636d97..0531720 100644 +--- a/SX126x.cpp ++++ b/SX126x.cpp +@@ -22,6 +22,7 @@ + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + ++#define MAX_BLOCK_SIZE (uint8_t)10 + + #include "SX126x.hpp" + +@@ -1048,36 +1049,52 @@ uint8_t SX126x::ReadReg(uint16_t address) { + void SX126x::WriteBuffer(uint8_t offset, uint8_t *buffer, uint8_t size) { + std::lock_guard<std::mutex> lg(IOLock); + +- WaitOnBusy(); ++ while(size) { ++ uint8_t block_size = std::min(size,MAX_BLOCK_SIZE); + +- auto total_transfer_size = 2+size; +- auto *buf_out = (uint8_t *)alloca(total_transfer_size); ++ WaitOnBusy(); + +- buf_out[0] = RADIO_WRITE_BUFFER; +- buf_out[1] = offset; ++ auto total_transfer_size = 2+block_size; ++ auto *buf_out = (uint8_t *)alloca(total_transfer_size); + +- memcpy(buf_out+2, buffer, size); ++ buf_out[0] = RADIO_WRITE_BUFFER; ++ buf_out[1] = offset; + +- HalSpiWrite(buf_out, total_transfer_size); ++ memcpy(buf_out+2, buffer, block_size); ++ ++ HalSpiWrite(buf_out, total_transfer_size); ++ ++ size -= block_size; ++ offset += block_size; ++ buffer += block_size; ++ } + } + + void SX126x::ReadBuffer(uint8_t offset, uint8_t *buffer, uint8_t size) { + std::lock_guard<std::mutex> lg(IOLock); + +- WaitOnBusy(); ++ while(size) { ++ uint8_t block_size = std::min(size,MAX_BLOCK_SIZE); + +- auto total_transfer_size = 3+size; +- auto *buf_out = (uint8_t *)alloca(total_transfer_size); +- auto *buf_in = (uint8_t *)alloca(total_transfer_size); ++ WaitOnBusy(); + +- memset(buf_out, 0, total_transfer_size); ++ auto total_transfer_size = 3+block_size; ++ auto *buf_out = (uint8_t *)alloca(total_transfer_size); ++ auto *buf_in = (uint8_t *)alloca(total_transfer_size); + +- buf_out[0] = RADIO_READ_BUFFER; +- buf_out[1] = offset; ++ memset(buf_out, 0, total_transfer_size); + +- HalSpiTransfer(buf_in, buf_out, total_transfer_size); ++ buf_out[0] = RADIO_READ_BUFFER; ++ buf_out[1] = offset; ++ ++ HalSpiTransfer(buf_in, buf_out, total_transfer_size); + +- memcpy(buffer, buf_in+3, size); ++ memcpy(buffer, buf_in+3, block_size); ++ ++ size -= block_size; ++ offset += block_size; ++ buffer += block_size; ++ } + } + + void SX126x::SetDeviceType(SX126x::DeviceType_t devtype) { diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt deleted file mode 100644 index 9a24fbd838c1c6c9799241fe4ffaa3d23325f6de..0000000000000000000000000000000000000000 --- a/apps/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_subdirectory(communicator) -add_subdirectory(pinephone-communicator) -add_subdirectory(usb-communicator) -add_subdirectory(gpio) \ No newline at end of file diff --git a/apps/communicator/CMakeLists.txt b/apps/communicator/CMakeLists.txt deleted file mode 100644 index 2a63372ee211741d46684a5ba731a040b4a35886..0000000000000000000000000000000000000000 --- a/apps/communicator/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -project(communicator) -cmake_minimum_required(VERSION 3.21) - -add_library(communicator - src/Communicator.cpp - include/PineDio/LoRa/Communicator.h) - -target_link_libraries(communicator - pinedio-lora-driver - pthread -) - -target_include_directories(communicator PUBLIC - $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/apps/communicator/include> - ) \ No newline at end of file diff --git a/apps/communicator/include/PineDio/LoRa/Communicator.h b/apps/communicator/include/PineDio/LoRa/Communicator.h deleted file mode 100644 index 88bb46010de0cbae2ea7da09a522f0d19c091639..0000000000000000000000000000000000000000 --- a/apps/communicator/include/PineDio/LoRa/Communicator.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "PineDio/LoRa/PinedioLoraRadio.h" -namespace PineDio::LoRa { -class PinedioLoraRadio; -class Communicator { -public: - explicit Communicator(PineDio::LoRa::PinedioLoraRadio &radio); - ~Communicator(); - void Run(); - void Stop(); - -private: - PineDio::LoRa::PinedioLoraRadio &radio; - std::atomic<bool> running{false}; - std::unique_ptr<std::thread> receiveTask; - void Receive(); -}; -} - - diff --git a/apps/communicator/src/Communicator.cpp b/apps/communicator/src/Communicator.cpp deleted file mode 100644 index f549f57a785dcfcfb8ec6a86e3806db2a27cd106..0000000000000000000000000000000000000000 --- a/apps/communicator/src/Communicator.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include <PineDio/LoRa/Communicator.h> -#include <iostream> -#include <future> -#include <algorithm> -using namespace PineDio::LoRa; - -namespace { -std::string GetString() { - std::string input; - std::getline(std::cin, input); - return input; -} -} - -Communicator::Communicator(PineDio::LoRa::PinedioLoraRadio &radio) : radio{radio} { - radio.Initialize(); - receiveTask.reset(new std::thread([this](){Receive();})); -} - -Communicator::~Communicator() { - running = false; - receiveTask->join(); -} - - -void Communicator::Run() { - running = true; - - std::future<std::string> futureString = std::async(std::launch::async, GetString); - - - while(running) { - if(futureString.wait_for(std::chrono::milliseconds {0}) == std::future_status::ready) { - auto msgStr = futureString.get(); - std::vector<uint8_t> msg; - for(auto c : msgStr) - msg.push_back(c); - msg.push_back('\0'); - radio.Send(msg); - futureString = std::async(std::launch::async, GetString); - } - - std::this_thread::sleep_for(std::chrono::milliseconds{100}); - } - -} -void Communicator::Receive() { - while(running){ - auto data = radio.Receive(std::chrono::milliseconds{100}); - if(data.empty()) - continue; - std::cout << "Data received on LoRa radio : " << std::endl; - std::cout << "\tHEX: "; - for(auto d : data) { - int dd = d; - std::cout << std::hex << "0x" << dd << " "; - } - std::cout << std::endl << "\tSTR: "; - for(auto d : data) { - std::cout << d; - } - std::cout << std::endl; - } -} - -void Communicator::Stop() { - running = false; -} - diff --git a/apps/pinephone-communicator/CMakeLists.txt b/apps/pinephone-communicator/CMakeLists.txt deleted file mode 100644 index e028dd8057c96bc5bfb939797283762d5ee3a601..0000000000000000000000000000000000000000 --- a/apps/pinephone-communicator/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -project(pinephone-communicator) -cmake_minimum_required(VERSION 3.21) - -add_executable(pinephone-communicator - main.cpp) - -target_link_libraries(pinephone-communicator - communicator - pinedio-lora-driver-pinephone - ) \ No newline at end of file diff --git a/apps/pinephone-communicator/main.cpp b/apps/pinephone-communicator/main.cpp deleted file mode 100644 index da940a497a138b64ac7bf42819892239a9bc82a2..0000000000000000000000000000000000000000 --- a/apps/pinephone-communicator/main.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include <PineDio/LoRa/PinedioLoraRadio.h> -#include <PineDio/LoRa/PinephoneBackplate.h> -#include <PineDio/LoRa/Communicator.h> -#include <PineDio/LoRa/Exceptions.h> -#include <iostream> - -int main() { - try { - PineDio::LoRa::PinephoneBackplate pinephoneBackplate("/dev/i2c-2"); - pinephoneBackplate.Initialize(); - PineDio::LoRa::PinedioLoraRadio radio(pinephoneBackplate); - PineDio::LoRa::Communicator communicator(radio); - communicator.Run(); - } catch(const PineDio::LoRa::InitializationException& ex) { - std::cerr << "Initialization error : " << ex.what() << std::endl; - } - - return 0; -} diff --git a/apps/usb-communicator/CMakeLists.txt b/apps/usb-communicator/CMakeLists.txt deleted file mode 100644 index 34ca49fba0f988694d600ee770dc7ee354a89a70..0000000000000000000000000000000000000000 --- a/apps/usb-communicator/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -project(usb-communicator) -cmake_minimum_required(VERSION 3.21) - -add_executable(usb-communicator - main.cpp) - -target_link_libraries(usb-communicator - communicator - pinedio-lora-driver-usb - ) \ No newline at end of file diff --git a/apps/usb-communicator/main.cpp b/apps/usb-communicator/main.cpp deleted file mode 100644 index 0e8c9a4973c9db483e17dbea9dcd104cbbdd0b5c..0000000000000000000000000000000000000000 --- a/apps/usb-communicator/main.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include <PineDio/LoRa/Communicator.h> -#include <iostream> -#include "PineDio/LoRa/UsbAdapter.h" -#include "PineDio/LoRa/Exceptions.h" -#include <signal.h> - -PineDio::LoRa::UsbAdapter usbAdapter; -PineDio::LoRa::PinedioLoraRadio radio(usbAdapter); -PineDio::LoRa::Communicator communicator(radio); - -void sig_handler(int signum) { - std::cout << "Press ENTER to exit..." << std::endl; - communicator.Stop(); -} - -int main() { - signal(SIGINT, sig_handler); - - try { - communicator.Run(); - } catch(const PineDio::LoRa::InitializationException& ex) { - std::cerr << "Initialization error : " << ex.what() << std::endl; - } - - return 0; -} \ No newline at end of file diff --git a/doc/pinedio-communicator-demo2.jpg b/doc/pinedio-communicator-demo2.jpg deleted file mode 100644 index 54aa810a59b60a2d25f21449b30d238d0d204c4f..0000000000000000000000000000000000000000 Binary files a/doc/pinedio-communicator-demo2.jpg and /dev/null differ diff --git a/include/PineDio/LoRa/PinedioLoraRadio.h b/include/PineDio/LoRa/PinedioLoraRadio.h deleted file mode 100644 index 624e524d8456300368c05b284d6b020c6ec267f0..0000000000000000000000000000000000000000 --- a/include/PineDio/LoRa/PinedioLoraRadio.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include <string> -#include <memory> -#include <sx126x_driver/SX126x.hpp> - -namespace PineDio { -namespace LoRa { -class PinedioLoraRadio { -public: - explicit PinedioLoraRadio(SX126x& radio); - - virtual void Initialize(); - virtual void Send(std::vector<uint8_t> data); - virtual std::vector<uint8_t> Receive(std::chrono::milliseconds timeout); -private: - SX126x& radio; - bool dataReceived {false}; - std::vector<uint8_t> receivedBuffer; - - bool dataToSend {false}; - std::vector<uint8_t> transmitBuffer; - - void OnDataReceived(); -}; -} -} \ No newline at end of file diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index baa18eeb63b8f29388df30e92294b760597c98e1..c255b6820061d9b00d211cc0ae3793d71f55a336 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -1,5 +1,6 @@ -cmake_minimum_required(VERSION 3.17) +#cmake_minimum_required(VERSION 3.17) project(sx126x_driver) +set(CMAKE_BUILD_TYPE Debug) set(CMAKE_CXX_STANDARD 14) @@ -7,4 +8,4 @@ add_library(sx126x_driver sx126x_driver/SX126x.cpp sx126x_driver/SX126x.hpp) target_include_directories(sx126x_driver PUBLIC $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/libs> - ) \ No newline at end of file + ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 96a6530739e20bfe3eefa47b4add30d420ce88e9..ddefb32a31db6c402f366bb5c975d45a705c29da 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,31 +1,19 @@ project(pinedio-lora-driver CXX) +set(CMAKE_BUILD_TYPE Debug) +set(THREADS_PREFER_PTHREAD_FLAG ON) +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") +add_subdirectory(usb-adapter) -add_library(pinedio-lora-driver - ../include/PineDio/LoRa/PinedioLoraRadio.h - ../include/PineDio/LoRa/Exceptions.h +find_package(Threads REQUIRED) + +add_library(pinedio-lora-driver SHARED + PinedioLoraRadio.h + Exceptions.h + ../libs/sx126x_driver/SX126x.hpp + UsbAdapter.h PinedioLoraRadio.cpp ) target_link_libraries(pinedio-lora-driver PUBLIC - sx126x_driver - ) - -target_include_directories(pinedio-lora-driver PUBLIC - $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include/> - ) - - -set(AT_LEAST_ONE_BUILD_ENABLED FALSE) -if(${BUILD_FOR_PINEPHONE}) - add_subdirectory(pinephone) - set(AT_LEAST_ONE_BUILD_ENABLED TRUE) -endif() - -if(${BUILD_FOR_USB}) - add_subdirectory(usb-adapter) - set(AT_LEAST_ONE_BUILD_ENABLED TRUE) -endif() - -if(NOT ${AT_LEAST_ONE_BUILD_ENABLED}) - message(FATAL_ERROR "Please select at least one build (-DBUILD_FOR_PINEPHONE or -DBUILD_FOR_USB)") -endif() \ No newline at end of file + sx126x_driver pinedio-lora-driver-usb "pthread" ${CMAKE_THREAD_LIBS_INIT} + ) \ No newline at end of file diff --git a/include/PineDio/LoRa/Exceptions.h b/src/Exceptions.h similarity index 100% rename from include/PineDio/LoRa/Exceptions.h rename to src/Exceptions.h diff --git a/src/PinedioLoraRadio.cpp b/src/PinedioLoraRadio.cpp index a71f9daae45f13ecdb8f571e419fbcd86bcf58f0..05c595bc39d1c903428e7010cf875ba25f25d1db 100644 --- a/src/PinedioLoraRadio.cpp +++ b/src/PinedioLoraRadio.cpp @@ -1,115 +1,169 @@ -#include <PineDio/LoRa/PinedioLoraRadio.h> +#include "PinedioLoraRadio.h" +#include "UsbAdapter.h" + +#include <queue> #include <sx126x_driver/SX126x.hpp> #include <iostream> #include <unistd.h> +#include <atomic> +#include <thread> +#include <vector> +#include <memory> +#include "mutexed_queue.class.cpp" +#include <functional> using namespace PineDio::LoRa; -PinedioLoraRadio::PinedioLoraRadio(SX126x &radio) : radio{radio} {} - -void PinedioLoraRadio::Initialize() { - std::cout << "[PinedioLoraRadio] Initialize()" << std::endl; - - radio.callbacks.rxDone = [this](){OnDataReceived();}; - radio.callbacks.txDone = [this](){ - std::cout << "TX DONE" << std::endl; - usleep(10000); - radio.SetRx(0xffffffff); - usleep(10000); - }; - - radio.SetDeviceType(SX126x::SX1262); - radio.Init(); - radio.SetDio2AsRfSwitchCtrl(true); - radio.SetStandby(SX126x::RadioStandbyModes_t::STDBY_RC); - radio.SetRegulatorMode(SX126x::RadioRegulatorMode_t::USE_DCDC); - radio.SetBufferBaseAddresses(0,127); - - radio.SetTxParams(22, SX126x::RadioRampTimes_t::RADIO_RAMP_3400_US); - radio.SetDioIrqParams(0xffff, 0x0001, 0x0000, 0x0000); - radio.SetRfFrequency(868000000); - - radio.SetPacketType(SX126x::RadioPacketTypes_t::PACKET_TYPE_LORA); - - radio.SetStopRxTimerOnPreambleDetect(false); - - SX126x::ModulationParams_t modulationParams; - modulationParams.PacketType = SX126x::RadioPacketTypes_t::PACKET_TYPE_LORA; - modulationParams.Params.LoRa.LowDatarateOptimize = 0; - modulationParams.Params.LoRa.Bandwidth = SX126x::RadioLoRaBandwidths_t::LORA_BW_500; - modulationParams.Params.LoRa.CodingRate = SX126x::RadioLoRaCodingRates_t::LORA_CR_4_5; - modulationParams.Params.LoRa.SpreadingFactor = SX126x::RadioLoRaSpreadingFactors_t::LORA_SF12; - radio.SetModulationParams(modulationParams); - - - - static const char* message = "Hello, I'm a Pinephone!"; - auto s = strlen(message); - - SX126x::PacketParams_t packetParams; - packetParams.PacketType = SX126x::RadioPacketTypes_t::PACKET_TYPE_LORA;; - packetParams.Params.LoRa.HeaderType = SX126x::RadioLoRaPacketLengthsMode_t::LORA_PACKET_VARIABLE_LENGTH; - packetParams.Params.LoRa.InvertIQ = SX126x::RadioLoRaIQModes_t::LORA_IQ_INVERTED; - packetParams.Params.LoRa.CrcMode = SX126x::RadioLoRaCrcModes_t::LORA_CRC_ON; - packetParams.Params.LoRa.PayloadLength = s; - packetParams.Params.LoRa.PreambleLength = 8; - radio.SetPacketParams(packetParams); - radio.ClearIrqStatus(0xffff); - radio.SetRx(0xffffffff); +std::unique_ptr<SX126x> radio; +SX126x::ModulationParams_t modulationParams; //WON'T be modifyable during runtime +SX126x::PacketParams_t packetParams; //WON'T be modifyable during runtime + +mutexed_queue<std::vector<uint8_t>> transmitBuffer; +std::atomic<bool> stop(true); +std::atomic<bool> txInProgress(false); +std::thread loop; +std::function<void(std::vector<uint8_t>)> appRxCallback; //will be called from another thread +std::thread RxConsumerThread; +mutexed_queue<std::vector<uint8_t>> rxBuffer; + +/* + I am doing something very crazy here: I don't quite understand the wait¬ify-mechanism here, so I use a mutex. locking is waiting (the mutex stays locked during the whole execution and while waiting for the next execution), unlocking is wakeup. In my head I found no possible race condition ... Should probably be done properly for the final version +*/ +std::mutex wakeupRxConsumer; + +void RxConsumer() { + while(!stop.load()) { + //I'm doing really hacky stuff here - see comment for wakeupRxConsumer + wakeupRxConsumer.lock(); //wait for wakeup + + while(!rxBuffer.empty()) { + appRxCallback(rxBuffer.pop()); //handles all the code in the callback-function + } + } } -void PinedioLoraRadio::Send(const std::vector<uint8_t> data) { - std::cout << "[PinedioLoraRadio] Send()" << std::endl; - dataToSend = true; - transmitBuffer = data; //copy + +void PinedioLoraRadioInitialize(std::function<void(std::vector<uint8_t>)> appRxCallback_) { + std::cout << "[PinedioLoraRadio] Initialize()" << std::endl; + + appRxCallback = appRxCallback_; + + modulationParams.PacketType = SX126x::RadioPacketTypes_t::PACKET_TYPE_LORA; + modulationParams.Params.LoRa.LowDatarateOptimize = 0; + modulationParams.Params.LoRa.Bandwidth = SX126x::RadioLoRaBandwidths_t::LORA_BW_125; + modulationParams.Params.LoRa.CodingRate = SX126x::RadioLoRaCodingRates_t::LORA_CR_4_7; + modulationParams.Params.LoRa.SpreadingFactor = SX126x::RadioLoRaSpreadingFactors_t::LORA_SF9; + + packetParams.PacketType = SX126x::RadioPacketTypes_t::PACKET_TYPE_LORA;; + packetParams.Params.LoRa.HeaderType = SX126x::RadioLoRaPacketLengthsMode_t::LORA_PACKET_IMPLICIT; + packetParams.Params.LoRa.InvertIQ = SX126x::RadioLoRaIQModes_t::LORA_IQ_INVERTED; + packetParams.Params.LoRa.CrcMode = SX126x::RadioLoRaCrcModes_t::LORA_CRC_OFF; + packetParams.Params.LoRa.PayloadLength = 255; //only implemented for 255 at the moment - otherwise send and receive-Length needs to be alternated; 255 seems to be the max even though the buffer seems to be 256 Bytes big + packetParams.Params.LoRa.PreambleLength = 8; + + //seems to be radio.reset(new pinephoneBackplate("/dev/i2c-2")); for the PInePhone - untested + radio.reset(new PineDio::LoRa::UsbAdapter()); + + radio->SetDeviceType(SX126x::SX1262); + radio->Init(); + radio->SetDio2AsRfSwitchCtrl(true); + radio->SetStandby(SX126x::RadioStandbyModes_t::STDBY_RC); + radio->SetRegulatorMode(SX126x::RadioRegulatorMode_t::USE_DCDC); + radio->SetBufferBaseAddresses(0,0); + + radio->SetTxParams(22, SX126x::RadioRampTimes_t::RADIO_RAMP_3400_US); + radio->SetDioIrqParams(0xffff, 0x0001, 0x0000, 0x0000); + radio->SetRfFrequency(433500000); + + radio->SetPacketType(SX126x::RadioPacketTypes_t::PACKET_TYPE_LORA); + + radio->SetStopRxTimerOnPreambleDetect(false); + + radio->SetModulationParams(modulationParams); + + radio->SetPacketParams(packetParams); + radio->ClearIrqStatus(0xffff); + radio->SetRx(0xffffffff); + + radio->callbacks.rxDone = [](){ + uint8_t size = 255; //make it dfinitly big enough + + std::vector<uint8_t> pkg(size); //a to big array isn't a problem :) + pkg.resize(size); + radio->GetPayload(&pkg[0], &size, size); + rxBuffer.push(pkg); + + //I'm doing really hacky stuff here - see comment for wakeupRxConsumer + wakeupRxConsumer.try_lock(); //make sure the mutex is locked - it should be + wakeupRxConsumer.unlock(); //wakeup consumer + }; + radio->callbacks.rxError = [](SX126x::IrqErrorCode_t dummy){radio->callbacks.rxDone;}; //we don't care about CRC + + radio->callbacks.txDone = [](){ + std::cout << "TX DONE" << std::endl; + usleep(10000); + radio->SetRx(0xffffffff); + usleep(10000); + txInProgress.store(false); + }; + + //Don't ask me how that should happen ... + radio->callbacks.txTimeout = []{ + std::cout << "TX TIMEOUT" << std::endl; + radio->SetRx(0xffffffff); + txInProgress.store(false); + }; + + stop.store(false); } -std::vector<uint8_t> PinedioLoraRadio::Receive(std::chrono::milliseconds timeout) { - bool running = true; - auto startTime = std::chrono::steady_clock::now(); - std::vector<uint8_t> buffer; - while(running) { - radio.ProcessIrqs(); - - if(dataToSend) { - std::cout << "SEND " << std::to_string(transmitBuffer.size()) << std::endl; - - SX126x::PacketParams_t packetParams; - packetParams.PacketType = SX126x::RadioPacketTypes_t::PACKET_TYPE_LORA;; - packetParams.Params.LoRa.HeaderType = SX126x::RadioLoRaPacketLengthsMode_t::LORA_PACKET_VARIABLE_LENGTH; - packetParams.Params.LoRa.InvertIQ = SX126x::RadioLoRaIQModes_t::LORA_IQ_INVERTED; - packetParams.Params.LoRa.CrcMode = SX126x::RadioLoRaCrcModes_t::LORA_CRC_ON; - packetParams.Params.LoRa.PayloadLength = transmitBuffer.size(); - packetParams.Params.LoRa.PreambleLength = 8; - radio.SetPacketParams(packetParams); - - radio.SendPayload(transmitBuffer.data(), transmitBuffer.size(), 0xffffffff); - usleep(3000000); - dataToSend = false; - } - - if(dataReceived) { - buffer = receivedBuffer; - break; - } - - if(std::chrono::steady_clock::now() - startTime > timeout) - break; - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - dataReceived = false; - return buffer; //copy +void PinedioLoraRadioInitialize(void (*appRxCallback_)(uint8_t[])) { + PinedioLoraRadioInitialize([appRxCallback_](std::vector<uint8_t> param){ + appRxCallback_(param.data()); + //The content of param.data() will be deleted here + }); } -void PinedioLoraRadio::OnDataReceived() { - uint8_t size = 0; - uint8_t offset = 0; - radio.GetRxBufferStatus(&size, &offset); +void PinedioLoraRadioSend(const std::vector<uint8_t> data) { + transmitBuffer.push(data); +} - receivedBuffer.clear(); - receivedBuffer.resize(size); - radio.GetPayload(receivedBuffer.data(), &size, size); +void PinedioLoraRadioSend(const uint8_t data[]) { + PinedioLoraRadioSend(std::vector<uint8_t>(data, data+255)); +} - dataReceived = true; +void PinedioLoraRadioStop() { + stop.store(true); + if(loop.joinable()) //if the radio is even started ... + loop.join(); + + //as said above really hacky stufff here + wakeupRxConsumer.try_lock(); + wakeupRxConsumer.unlock(); + RxConsumerThread.join(); + if(RxConsumerThread.joinable()) //if the radio is even started ... + RxConsumerThread.join(); } + +void LoRaStateMachine() { + while(!stop.load()) { + radio->ProcessIrqs(); //calls received-callback if something is received + + while(!txInProgress.load() && !transmitBuffer.empty()) { + std::vector<uint8_t> pkg = transmitBuffer.pop(); //mutexed_queue::pop differs from STLs queue::pop + std::cout << "SEND " << std::to_string(pkg.size()) << std::endl; + + // packetParams.Params.LoRa.PayloadLength = pkg.size(); + // radio->SetPacketParams(packetParams); + + txInProgress.store(true); + radio->SendPayload(pkg.data(), pkg.size(), 60*64000); //64000: In case of {what??} failure, abort transmission after 1 minute + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + +void PinedioLoraRadioStart() { + loop = std::thread(LoRaStateMachine); + RxConsumerThread = std::thread(RxConsumer); +} \ No newline at end of file diff --git a/src/PinedioLoraRadio.h b/src/PinedioLoraRadio.h new file mode 100644 index 0000000000000000000000000000000000000000..5e321e16adf494d322959bd2dd706cc347e4a061 --- /dev/null +++ b/src/PinedioLoraRadio.h @@ -0,0 +1,19 @@ +#pragma once + +#include <string> +#include <memory> +#include <vector> +#include <atomic> +#include <queue> +#include <functional> + +#include <sx126x_driver/SX126x.hpp> + +//void PinedioLoraRadioInitialize(std::function<void(std::vector<uint8_t>)> appRxCallback_); +//void PinedioLoraRadioSend(const std::vector<uint8_t> data); +extern "C" { + void PinedioLoraRadioInitialize(void (*appRxCallback_)(uint8_t[])); //callback: needs to be thread-safe and immideately deepcopy the byte array + void PinedioLoraRadioSend(const uint8_t[]); + void PinedioLoraRadioStart(); + void PinedioLoraRadioStop() __attribute__((destructor (9999))); +} \ No newline at end of file diff --git a/include/PineDio/LoRa/PinephoneBackplate.h b/src/PinephoneBackplate.h similarity index 91% rename from include/PineDio/LoRa/PinephoneBackplate.h rename to src/PinephoneBackplate.h index 1bdf609e91ef8750459b8fb0b0fe115fc0624cea..2b03651acfebb178ddf778e846e6ec3939d71720 100644 --- a/include/PineDio/LoRa/PinephoneBackplate.h +++ b/src/PinephoneBackplate.h @@ -1,5 +1,5 @@ #pragma once -#include <PineDio/LoRa/PinedioLoraRadio.h> +#include "PinedioLoraRadio.h" #include <memory> namespace PineDio::LoRa { diff --git a/include/PineDio/LoRa/UsbAdapter.h b/src/UsbAdapter.h similarity index 92% rename from include/PineDio/LoRa/UsbAdapter.h rename to src/UsbAdapter.h index 85925b33b5188237f0f67e465b6464fce7fa7306..0f6e588cb50ed5e7029425e2cee3c874d41026c1 100644 --- a/include/PineDio/LoRa/UsbAdapter.h +++ b/src/UsbAdapter.h @@ -1,5 +1,5 @@ #pragma once -#include <PineDio/LoRa/PinedioLoraRadio.h> +#include "PinedioLoraRadio.h" #include <memory> #include <linux/gpio.h> diff --git a/apps/gpio/CMakeLists.txt b/src/gpio/CMakeLists.txt similarity index 56% rename from apps/gpio/CMakeLists.txt rename to src/gpio/CMakeLists.txt index 4d137640007b0595c3210f7571dc96f2f3a3c1e8..87ed8b5b5204ff2939c6661a48062c364d123aa3 100644 --- a/apps/gpio/CMakeLists.txt +++ b/src/gpio/CMakeLists.txt @@ -1,5 +1,5 @@ project(gpio) -cmake_minimum_required(VERSION 3.21) +#cmake_minimum_required(VERSION 3.21) add_executable(gpio main.cpp) diff --git a/apps/gpio/main.cpp b/src/gpio/main.cpp similarity index 100% rename from apps/gpio/main.cpp rename to src/gpio/main.cpp diff --git a/src/mutexed_queue.class.cpp b/src/mutexed_queue.class.cpp new file mode 100644 index 0000000000000000000000000000000000000000..476bebe7e446da0736040ec1e3d65c9923617f4f --- /dev/null +++ b/src/mutexed_queue.class.cpp @@ -0,0 +1,81 @@ +#pragma once +#include <mutex> +#include <queue> +#include <string> + +/** + * This class provides a class which aims to be compatible to std::queue in the essential functions while maintaining thread-safety. For good documentation look at the documentation of the original class. Remember that this only supports the main functionality! + * + * WARNING: Construction goes like this: mutexed_queue<...> varname; + * NOT LIKE: mutexed_queue<...> varname = mutexed_queue<...>(); + * On the second example, the obejct gets created on the right and then is copied to the left. But the object contains Mutex and Mutex can't be copied. So the object can't be copied, too! This took me a couple of hours to figure out! + * + * @license CC0 + * @author Thomas + */ +template<typename _Tp> +class mutexed_queue { + private: + std::queue<_Tp> q; + std::mutex m; + + public: + mutexed_queue() { + q = std::queue<_Tp>(); + } + + bool empty() { + bool tmp; + m.lock(); + tmp = q.empty(); + m.unlock(); + + return tmp; + } + + size_t size() { + size_t tmp; + m.lock(); + tmp = q.size(); + m.unlock(); + + return tmp; + } + + _Tp front() { + _Tp tmp; + m.lock(); + tmp = q.front(); + m.unlock(); + + return tmp; + } + + _Tp back() { + _Tp tmp; + m.lock(); + tmp = q.back(); + m.unlock(); + + return tmp; + } + + /** + * Note that for comfort (and atomic operations), there is made a little change in comparison to the std::queue::pop(): It returns the removed element before removing it + */ + _Tp pop() { + _Tp tmp; + m.lock(); + tmp = q.front(); + q.pop(); + m.unlock(); + + return tmp; + } + + void push(_Tp tmp) { + m.lock(); + q.push(tmp); + m.unlock(); + } +}; diff --git a/src/pinephone/CMakeLists.txt b/src/pinephone/CMakeLists.txt index 4761acb67fef6a6c924682135fbe4292654e4ddb..d32f0b8ddf268bb18e51368caf90be35363a4c4e 100644 --- a/src/pinephone/CMakeLists.txt +++ b/src/pinephone/CMakeLists.txt @@ -5,8 +5,8 @@ set(PINEDIO_LORA_DRIVER_SOURCE_FILES ) set(PINEDIO_LORA_DRIVER_PUBLIC_HEADERS - ../../include/PineDio/LoRa/PinedioLoraRadio.h - ../../include/PineDio/LoRa/PinephoneBackplate.h + ../PinedioLoraRadio.h + ../PinephoneBackplate.h ) add_library(pinedio-lora-driver-pinephone @@ -16,8 +16,4 @@ add_library(pinedio-lora-driver-pinephone target_link_libraries(pinedio-lora-driver-pinephone PUBLIC pinedio-lora-driver - ) - -target_include_directories(pinedio-lora-driver-pinephone PUBLIC - $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include/> -) \ No newline at end of file + ) \ No newline at end of file diff --git a/src/pinephone/PinephoneBackplate.cpp b/src/pinephone/PinephoneBackplate.cpp index 077ff605f2a4c1e451fe07712949a9be4c10423f..5e49eeb4daf9b66293469ad64441a8c24e4aef0f 100644 --- a/src/pinephone/PinephoneBackplate.cpp +++ b/src/pinephone/PinephoneBackplate.cpp @@ -1,5 +1,5 @@ -#include <PineDio/LoRa/PinephoneBackplate.h> -#include <PineDio/LoRa/Exceptions.h> +#include "../PinephoneBackplate.h" +#include "../Exceptions.h" #include <iostream> #include <linux/i2c-dev.h> diff --git a/src/usb-adapter/CMakeLists.txt b/src/usb-adapter/CMakeLists.txt index cd46237ae427996d78f910674b31bc02f48f52a4..2d63daf31016956e0a9610ab78a3c27934c77a8a 100644 --- a/src/usb-adapter/CMakeLists.txt +++ b/src/usb-adapter/CMakeLists.txt @@ -4,11 +4,6 @@ set(PINEDIO_LORA_DRIVER_SOURCE_FILES UsbAdapter.cpp ) -set(PINEDIO_LORA_DRIVER_PUBLIC_HEADERS - ../../include/PineDio/LoRa/PinedioLoraRadio.h - ../../include/PineDio/LoRa/UsbAdapter.h - ) - add_library(pinedio-lora-driver-usb ${PINEDIO_LORA_DRIVER_PUBLIC_HEADERS} ${PINEDIO_LORA_DRIVER_SOURCE_FILES} @@ -16,8 +11,4 @@ add_library(pinedio-lora-driver-usb target_link_libraries(pinedio-lora-driver-usb sx126x_driver - ) - -target_include_directories(pinedio-lora-driver-usb PUBLIC - $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include/> -) \ No newline at end of file + ) \ No newline at end of file diff --git a/src/usb-adapter/UsbAdapter.cpp b/src/usb-adapter/UsbAdapter.cpp index 64802b9af676402b1ced85e9269aaa3dbf2e94ef..8bbbd8b63e9287972148a43765356957dbca14af 100644 --- a/src/usb-adapter/UsbAdapter.cpp +++ b/src/usb-adapter/UsbAdapter.cpp @@ -1,4 +1,4 @@ -#include <PineDio/LoRa/UsbAdapter.h> +#include "../UsbAdapter.h" #include <iostream> #include <unistd.h> @@ -12,6 +12,8 @@ using namespace PineDio::LoRa; namespace { +//TODO: this files need udev-care ... - use file from driver as example +//const char *spiDriverOverrideFilePath ="/dev/null"; const char *spiDriverOverrideFilePath ="/sys/class/spi_master/spi0/spi0.0/driver_override"; const char *spidevOverride = "spidev"; @@ -148,11 +150,12 @@ void UsbAdapter::OpenSpi() { close(fd); + //TODO: only if not yet boundd fd = open(spiBindFilePath, O_WRONLY); if(fd == -1) throw std::runtime_error("Cannot open SPI device (bind open error)"); - res = write(fd, spiBind, strlen(spiBind)); +if(res > 0) if(res <= 0) throw std::runtime_error("Cannot open SPI device (bind write error)"); diff --git a/testapp/CMakeLists.txt b/testapp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..0a238ed4f822885389c6f3e558d5e615d136c8ba --- /dev/null +++ b/testapp/CMakeLists.txt @@ -0,0 +1,13 @@ +project(testapp) +#cmake_minimum_required(VERSION 3.21) +set(CMAKE_BUILD_TYPE Debug) + +find_package(Threads REQUIRED) +set(THREADS_PREFER_PTHREAD_FLAG ON) + +add_executable(testapp + main.cpp) + +target_link_libraries(testapp + "pthread" ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} + ) \ No newline at end of file diff --git a/testapp/main.cpp b/testapp/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9e07cebc0e41a8ee949c6c5a39859f5046ea9d29 --- /dev/null +++ b/testapp/main.cpp @@ -0,0 +1,57 @@ +//#include "../src/UsbAdapter.h" +//#include "../src/Exceptions.h" +//#include "../src/PinedioLoraRadio.h" +#include <iostream> +#include <signal.h> +#include <future> +#include <algorithm> +#include <atomic> +#include <vector> +#include <thread> +#include <dlfcn.h> + +void (*PinedioLoraRadioInitialize)(void (*appRxCallback_)(uint8_t[])); +void (*PinedioLoraRadioStart)(); +void (*PinedioLoraRadioSend)(const uint8_t[]); +void (*PinedioLoraRadioStop)(); + +int main() { + void* lib_handle = dlopen("libpinedio-lora-driver.so", RTLD_LAZY); + PinedioLoraRadioInitialize = (void (*)(void (*)(uint8_t[])))dlsym(lib_handle, "PinedioLoraRadioInitialize"); + PinedioLoraRadioStart = (void (*)())dlsym(lib_handle, "PinedioLoraRadioStart"); + PinedioLoraRadioSend = (void (*)(const uint8_t[]))dlsym(lib_handle, "PinedioLoraRadioSend"); + PinedioLoraRadioStop = (void (*)())dlsym(lib_handle, "PinedioLoraRadioStop"); + + PinedioLoraRadioInitialize([](uint8_t data[]) {for(int i=0;i<255;i++)printf("%x",data[i]);}); + PinedioLoraRadioStart(); + std::vector<uint8_t> data({'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4'}); + PinedioLoraRadioSend(data.data()); + std::this_thread::sleep_for(std::chrono::milliseconds(1000000)); + PinedioLoraRadioStop();//*/ + return 0; +} \ No newline at end of file