commit a1ccddba7af84d74b5921adfe8842e1c059898b5 Author: Jean-François Milants Date: Sat Jan 1 15:43:07 2022 +0100 Initial commit diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e41b7b8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libs/sx126x_driver"] + path = libs/sx126x_driver + url = git@github.com:YukiWorkshop/sx126x_driver.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8704ded --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,12 @@ +project(pinedio-lora-driver-project) +cmake_minimum_required(VERSION 3.21) + +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) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4cb4c6e --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ +# 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 + +```bash +git clone xxx +cd pinedio-lora-driver +git submodule update --init +mkdir build && cd build +cmake -DBUILD_FOR_PINEPHONE=0 -DBUILD_FOR_USB=1 .. +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 +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 +![PineDio communbicator demo](doc/pinedio-communicator-demo2.jpg) + +[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)). + +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 + - [ ] 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 + +## 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/apps/CMakeLists.txt b/apps/CMakeLists.txt new file mode 100644 index 0000000..7de7324 --- /dev/null +++ b/apps/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(communicator) +add_subdirectory(pinephone-communicator) +add_subdirectory(usb-communicator) \ No newline at end of file diff --git a/apps/communicator/CMakeLists.txt b/apps/communicator/CMakeLists.txt new file mode 100644 index 0000000..2a63372 --- /dev/null +++ b/apps/communicator/CMakeLists.txt @@ -0,0 +1,15 @@ +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 + $ + ) \ No newline at end of file diff --git a/apps/communicator/include/PineDio/LoRa/Communicator.h b/apps/communicator/include/PineDio/LoRa/Communicator.h new file mode 100644 index 0000000..fc02705 --- /dev/null +++ b/apps/communicator/include/PineDio/LoRa/Communicator.h @@ -0,0 +1,20 @@ +#pragma once + +#include "PineDio/LoRa/PinedioLoraRadio.h" +namespace PineDio::LoRa { +class PinedioLoraRadio; +class Communicator { +public: + explicit Communicator(PineDio::LoRa::PinedioLoraRadio &radio); + + void Run(); + +private: + PineDio::LoRa::PinedioLoraRadio &radio; + std::atomic running{false}; + std::unique_ptr receiveTask; + void Receive(); +}; +} + + diff --git a/apps/communicator/src/Communicator.cpp b/apps/communicator/src/Communicator.cpp new file mode 100644 index 0000000..77dfc73 --- /dev/null +++ b/apps/communicator/src/Communicator.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include +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();})); +} + +void Communicator::Run() { + running = true; + + std::future 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 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(true){ + auto data = radio.Receive(); + 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; + } +} + diff --git a/apps/pinephone-communicator/CMakeLists.txt b/apps/pinephone-communicator/CMakeLists.txt new file mode 100644 index 0000000..e028dd8 --- /dev/null +++ b/apps/pinephone-communicator/CMakeLists.txt @@ -0,0 +1,10 @@ +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 new file mode 100644 index 0000000..da940a4 --- /dev/null +++ b/apps/pinephone-communicator/main.cpp @@ -0,0 +1,19 @@ +#include +#include +#include +#include +#include + +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 new file mode 100644 index 0000000..34ca49f --- /dev/null +++ b/apps/usb-communicator/CMakeLists.txt @@ -0,0 +1,10 @@ +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 new file mode 100644 index 0000000..7d27157 --- /dev/null +++ b/apps/usb-communicator/main.cpp @@ -0,0 +1,17 @@ +#include +#include +#include "PineDio/LoRa/UsbAdapter.h" +#include "PineDio/LoRa/Exceptions.h" + +int main() { + try { + PineDio::LoRa::UsbAdapter usbAdapter; + PineDio::LoRa::PinedioLoraRadio radio(usbAdapter); + PineDio::LoRa::Communicator communicator(radio); + 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 new file mode 100644 index 0000000..54aa810 Binary files /dev/null and b/doc/pinedio-communicator-demo2.jpg differ diff --git a/include/PineDio/LoRa/Exceptions.h b/include/PineDio/LoRa/Exceptions.h new file mode 100644 index 0000000..5e553c9 --- /dev/null +++ b/include/PineDio/LoRa/Exceptions.h @@ -0,0 +1,9 @@ +#pragma once + +namespace PineDio::LoRa { +class InitializationException : public std::runtime_error { +public: + explicit InitializationException(const std::string& message) : runtime_error(message) {} +}; + +} diff --git a/include/PineDio/LoRa/PinedioLoraRadio.h b/include/PineDio/LoRa/PinedioLoraRadio.h new file mode 100644 index 0000000..04f6904 --- /dev/null +++ b/include/PineDio/LoRa/PinedioLoraRadio.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +namespace PineDio { +namespace LoRa { +class PinedioLoraRadio { +public: + explicit PinedioLoraRadio(SX126x& radio); + + virtual void Initialize(); + virtual void Send(std::vector data); + virtual std::vector Receive(); +private: + SX126x& radio; + bool dataReceived {false}; + std::vector receivedBuffer; + + bool dataToSend {false}; + std::vector transmitBuffer; + + void OnDataReceived(); +}; +} +} \ No newline at end of file diff --git a/include/PineDio/LoRa/PinephoneBackplate.h b/include/PineDio/LoRa/PinephoneBackplate.h new file mode 100644 index 0000000..1bdf609 --- /dev/null +++ b/include/PineDio/LoRa/PinephoneBackplate.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include + +namespace PineDio::LoRa { + +class PinephoneBackplate : public SX126x { +public: + explicit PinephoneBackplate(const std::string& i2cFilename); + void Initialize(); + +private: + uint8_t HalGpioRead(GpioPinFunction_t func) override; + void HalGpioWrite(GpioPinFunction_t func, uint8_t value) override; + void HalSpiTransfer(uint8_t *buffer_in, const uint8_t *buffer_out, uint16_t size) override; + + int handle; + void RxDone(); + void SyncI2CBuffer(); +}; + +} diff --git a/include/PineDio/LoRa/UsbAdapter.h b/include/PineDio/LoRa/UsbAdapter.h new file mode 100644 index 0000000..07b71f1 --- /dev/null +++ b/include/PineDio/LoRa/UsbAdapter.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include + +namespace PineDio::LoRa { + +class UsbAdapter : public SX126x { +public: + UsbAdapter(); + +private: + uint8_t HalGpioRead(GpioPinFunction_t func) override; + void HalGpioWrite(GpioPinFunction_t func, uint8_t value) override; + void HalSpiTransfer(uint8_t *buffer_in, const uint8_t *buffer_out, uint16_t size) override; + + int handle; + + void RxDone(); +}; + +} diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt new file mode 100644 index 0000000..baa18ee --- /dev/null +++ b/libs/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.17) +project(sx126x_driver) + +set(CMAKE_CXX_STANDARD 14) + +add_library(sx126x_driver sx126x_driver/SX126x.cpp sx126x_driver/SX126x.hpp) + +target_include_directories(sx126x_driver PUBLIC + $ + ) \ No newline at end of file diff --git a/libs/sx126x_driver b/libs/sx126x_driver new file mode 160000 index 0000000..42a75df --- /dev/null +++ b/libs/sx126x_driver @@ -0,0 +1 @@ +Subproject commit 42a75dfbeb6a4df8bb4e77d10d135fba878b88d4 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..96a6530 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,31 @@ +project(pinedio-lora-driver CXX) + +add_library(pinedio-lora-driver + ../include/PineDio/LoRa/PinedioLoraRadio.h + ../include/PineDio/LoRa/Exceptions.h + PinedioLoraRadio.cpp + ) + +target_link_libraries(pinedio-lora-driver PUBLIC + sx126x_driver + ) + +target_include_directories(pinedio-lora-driver PUBLIC + $ + ) + + +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 diff --git a/src/PinedioLoraRadio.cpp b/src/PinedioLoraRadio.cpp new file mode 100644 index 0000000..2303fdb --- /dev/null +++ b/src/PinedioLoraRadio.cpp @@ -0,0 +1,109 @@ +#include +#include +#include +#include + +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 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); +} +void PinedioLoraRadio::Send(const std::vector data) { + std::cout << "[PinedioLoraRadio] Send()" << std::endl; + dataToSend = true; + transmitBuffer = data; //copy +} + +std::vector PinedioLoraRadio::Receive() { + std::cout << "[PinedioLoraRadio] Receive()" << std::endl; + bool running = true; + 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) { + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + dataReceived = false; + return receivedBuffer; //copy +} + +void PinedioLoraRadio::OnDataReceived() { + uint8_t size = 0; + uint8_t offset = 0; + radio.GetRxBufferStatus(&size, &offset); + + receivedBuffer.clear(); + receivedBuffer.resize(size); + radio.GetPayload(receivedBuffer.data(), &size, size); + + dataReceived = true; +} diff --git a/src/pinephone/CMakeLists.txt b/src/pinephone/CMakeLists.txt new file mode 100644 index 0000000..4761acb --- /dev/null +++ b/src/pinephone/CMakeLists.txt @@ -0,0 +1,23 @@ +project(pinedio-lora-driver-pinephone CXX) + +set(PINEDIO_LORA_DRIVER_SOURCE_FILES + PinephoneBackplate.cpp +) + +set(PINEDIO_LORA_DRIVER_PUBLIC_HEADERS + ../../include/PineDio/LoRa/PinedioLoraRadio.h + ../../include/PineDio/LoRa/PinephoneBackplate.h + ) + +add_library(pinedio-lora-driver-pinephone + ${PINEDIO_LORA_DRIVER_PUBLIC_HEADERS} + ${PINEDIO_LORA_DRIVER_SOURCE_FILES} + ) + +target_link_libraries(pinedio-lora-driver-pinephone PUBLIC + pinedio-lora-driver + ) + +target_include_directories(pinedio-lora-driver-pinephone PUBLIC + $ +) \ No newline at end of file diff --git a/src/pinephone/PinephoneBackplate.cpp b/src/pinephone/PinephoneBackplate.cpp new file mode 100644 index 0000000..077ff60 --- /dev/null +++ b/src/pinephone/PinephoneBackplate.cpp @@ -0,0 +1,142 @@ +#include +#include +#include + +#include +#include +#include +#include +using namespace PineDio::LoRa; + +PinephoneBackplate::PinephoneBackplate(const std::string &i2cFilename) { + std::cout << "[PinephoneBackplate] ctor()" << std::endl; + + if ((handle = open(i2cFilename.c_str(), O_RDWR)) < 0) { + /* ERROR HANDLING: you can check errno to see what went wrong */ + perror("Failed to open the i2c bus"); + exit(1); + } + + int addr = 0x28; // The I2C address of the ADC + if (ioctl(handle, I2C_SLAVE, addr) < 0) { + printf("Failed to acquire bus access and/or talk to slave.\n"); + /* ERROR HANDLING; you can check errno to see what went wrong */ + exit(1); + } +} + +void PinephoneBackplate::Initialize() { + SyncI2CBuffer(); + /* + uint8_t bufferWrite = 4; + if (write(handle, &bufferWrite,1) != 1) { + printf("Failed to write to the i2c bus.\n"); + printf("\n\n"); + exit(1); + } + */ +} + + +uint8_t PinephoneBackplate::HalGpioRead(SX126x::GpioPinFunction_t func) { + usleep(10000); + return 0; +} + +void PinephoneBackplate::HalGpioWrite(SX126x::GpioPinFunction_t func, uint8_t value) { + usleep(10000); +} + +void PinephoneBackplate::HalSpiTransfer(uint8_t *buffer_in, + const uint8_t *buffer_out, + uint16_t size) { + //std::cout << "Start transfer " << size +1<< " bytes\n\t"; + uint8_t bufferWrite[size+1]; + uint8_t bufferRead[size+1]; + bufferWrite[0] = 1; // CMD TRANSMIT + std::memcpy(bufferWrite+1, buffer_out, size); + + //for(int i = 0; i< size+1; i++) { + // printf("%x ", bufferWrite[i]); + // } + // std::cout << std::endl; + + if (write(handle, bufferWrite,size+1) != size+1) { + /* ERROR HANDLING: i2c transaction failed */ + printf("Failed to write to the i2c bus.\n"); + printf("\n\n"); + return; + } + //std::cout << "\t Write OK\n"; + + //std::cout << "Reading " << size << " bytes from the bus \n"; + for(int i = 0; i < size; i++) { + if (read(handle, buffer_in + i, 1) != 1) { + /* ERROR HANDLING: i2c transaction failed */ + printf("Failed to read from the i2c bus.\n"); + printf("\n\n"); + return; + } + } +} + +void PinephoneBackplate::SyncI2CBuffer() { + std::cout << "[PinephoneBackplate] Sync buffer..." << std::endl; + + std::cout << "\tInit LoRa module..." << std::endl; + // Set radio to idle + uint8_t dd[3] = {0x01, 0x80, 0x00}; + write(handle, &dd, 3); + usleep(1000); + + // Set RX/TX index to 0 + uint8_t d[4] = {0x01, 0x8f, 0x00, 0x00}; + write(handle, &d, 4); + usleep(1000); + + std::cout << "\tSend verification data..." << std::endl; + uint8_t cmd1[] = {0x01, 0x0E, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0xAA, 0x55, 0x00, 0xFF}; + write(handle, cmd1, 12); + usleep(1000); + + uint8_t cmd2[] = {0x01, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + usleep(1000); + write(handle, cmd2, 13); + usleep(1000); + + std::cout << "\tSync'ing..." << std::endl; + bool done = false; + bool sequenceStarted = false; + size_t sequenceIndex = 0; + std::array sequence = {0x10, 0x20, 0x30, 0x40, 0x50, 0xAA, 0x55, 0x00, 0xFF}; + int count = 0; + while(!done && count < 256) { + int d = 0; + read(handle, &d, 1); + + if(!sequenceStarted) { + for(int i = 0; i < 9; i++) { + if(d == sequence[i]){ + sequenceStarted = true; + sequenceIndex = i; + } + } + } else { + if(d == sequence[sequenceIndex+1]) { + sequenceIndex++; + if(sequenceIndex == 8) { + done = true; + std::cout << "\tSync done after " << count << " bytes!" << std::endl; + + } + } else { + sequenceStarted = false; + } + } + count ++; + } + if(count >= 256) { + throw InitializationException("Internal buffer synchronization failed"); + } + +} diff --git a/src/usb-adapter/CMakeLists.txt b/src/usb-adapter/CMakeLists.txt new file mode 100644 index 0000000..cd46237 --- /dev/null +++ b/src/usb-adapter/CMakeLists.txt @@ -0,0 +1,23 @@ +project(pinedio-lora-driver-usb CXX) + +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} + ) + +target_link_libraries(pinedio-lora-driver-usb + sx126x_driver + ) + +target_include_directories(pinedio-lora-driver-usb PUBLIC + $ +) \ No newline at end of file diff --git a/src/usb-adapter/UsbAdapter.cpp b/src/usb-adapter/UsbAdapter.cpp new file mode 100644 index 0000000..6360310 --- /dev/null +++ b/src/usb-adapter/UsbAdapter.cpp @@ -0,0 +1,113 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace PineDio::LoRa; + +UsbAdapter::UsbAdapter() { + std::cout << "[UsbAdapter] ctor()" << std::endl; + + // TODO remove code duplication for exporting GPIO + if(access("/sys/class/gpio/ini", F_OK) != 0 ) { + int fd; + if ((fd = open("/sys/class/gpio/export", O_WRONLY)) == -1) { + perror("Error while opening GPIO export file"); + // TODO ERROR + } + if (write(fd, "240", 3) == -1) { // TODO how to find the GPIO number programatically? + perror("Error while exporting GPIO \"ini\""); + // TODO ERROR + } + close(fd); + } + + if(access("/sys/class/gpio/slct", F_OK) != 0 ) { + int fd; + if ((fd = open("/sys/class/gpio/export", O_WRONLY)) == -1) { + perror("Error while opening GPIO export file"); + // TODO ERROR + } + if (write(fd, "244", 3) == -1) { // TODO how to find the GPIO number programatically? + perror("Error while exporting GPIO \"sclt\""); + // TODO ERROR + } + close(fd); + } + + handle = open("/dev/spidev0.0", O_RDWR); + if(handle == -1) { + // TODO error + printf("SPI IOCTL error %s\n", strerror(errno)); + } + + uint8_t mmode = SPI_MODE_0; + uint8_t lsb = 0; + uint8_t bitsperword = 8; + ioctl(handle, SPI_IOC_RD_BITS_PER_WORD, &bitsperword); + ioctl(handle, SPI_IOC_WR_MODE, &mmode); + ioctl(handle, SPI_IOC_WR_LSB_FIRST, &lsb); +} + +uint8_t UsbAdapter::HalGpioRead(SX126x::GpioPinFunction_t func) { + if(func != GpioPinFunction_t::GPIO_PIN_BUSY) { + std::cout << "ERROR" << std::endl; + throw; + } + + int fd; + if ((fd = open("/sys/class/gpio/slct/value", O_RDWR)) == -1) { + perror("Error while opening GPIO \"busy\""); + return -1; + } + + char buf; + if (read(fd, &buf, 1) == -1) { + perror("Error while reading GPIO \"busy\""); + return -1; + } + close(fd); + + int value = (buf == '0') ? 0 : 1; + + std::this_thread::sleep_for(std::chrono::milliseconds{1}); // Why do I need this sleep()? + return value; +} + +void UsbAdapter::HalGpioWrite(SX126x::GpioPinFunction_t func, uint8_t value) { + if(func != GpioPinFunction_t::GPIO_PIN_RESET) { + std::cout << "ERROR" << std::endl; + throw; + } + + int fd; + + if ((fd = open("/sys/class/gpio/ini/value", O_RDWR)) == -1) { + perror("Error while opening GPIO \"reset\""); + } + + if (write(fd, value ? "1" : "0", 1) == -1) { + perror ("Error while writing GPIO \"reset\""); + } +} + +void UsbAdapter::HalSpiTransfer(uint8_t *buffer_in, const uint8_t *buffer_out, uint16_t size) { + const uint8_t *mosi = buffer_out; // output data + uint8_t *miso = buffer_in; // input data + + struct spi_ioc_transfer spi_trans; + memset(&spi_trans, 0, sizeof(spi_trans)); + + spi_trans.tx_buf = (unsigned long) mosi; + spi_trans.rx_buf = (unsigned long) miso; + spi_trans.cs_change = true; + spi_trans.len = size; + + int status = ioctl (handle, SPI_IOC_MESSAGE(1), &spi_trans); +} +