diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 7de7324..9a24fbd 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(communicator) add_subdirectory(pinephone-communicator) -add_subdirectory(usb-communicator) \ No newline at end of file +add_subdirectory(usb-communicator) +add_subdirectory(gpio) \ No newline at end of file diff --git a/apps/communicator/include/PineDio/LoRa/Communicator.h b/apps/communicator/include/PineDio/LoRa/Communicator.h index fc02705..88bb460 100644 --- a/apps/communicator/include/PineDio/LoRa/Communicator.h +++ b/apps/communicator/include/PineDio/LoRa/Communicator.h @@ -6,8 +6,9 @@ class PinedioLoraRadio; class Communicator { public: explicit Communicator(PineDio::LoRa::PinedioLoraRadio &radio); - + ~Communicator(); void Run(); + void Stop(); private: PineDio::LoRa::PinedioLoraRadio &radio; diff --git a/apps/communicator/src/Communicator.cpp b/apps/communicator/src/Communicator.cpp index 77dfc73..f549f57 100644 --- a/apps/communicator/src/Communicator.cpp +++ b/apps/communicator/src/Communicator.cpp @@ -17,6 +17,12 @@ Communicator::Communicator(PineDio::LoRa::PinedioLoraRadio &radio) : radio{radio receiveTask.reset(new std::thread([this](){Receive();})); } +Communicator::~Communicator() { + running = false; + receiveTask->join(); +} + + void Communicator::Run() { running = true; @@ -39,8 +45,10 @@ void Communicator::Run() { } void Communicator::Receive() { - while(true){ - auto data = radio.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) { @@ -55,3 +63,7 @@ void Communicator::Receive() { } } +void Communicator::Stop() { + running = false; +} + diff --git a/apps/gpio/CMakeLists.txt b/apps/gpio/CMakeLists.txt new file mode 100644 index 0000000..4d13764 --- /dev/null +++ b/apps/gpio/CMakeLists.txt @@ -0,0 +1,5 @@ +project(gpio) +cmake_minimum_required(VERSION 3.21) + +add_executable(gpio + main.cpp) diff --git a/apps/gpio/main.cpp b/apps/gpio/main.cpp new file mode 100644 index 0000000..2051cd3 --- /dev/null +++ b/apps/gpio/main.cpp @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include +#include + +struct FileDescriptor { + int fd; +}; + +void PrintChipInfo(const std::string& chip); +void PrintLineInfo(FileDescriptor chip, int line); +std::string GetCh341ChipFilename(); +std::vector GetChipChipNames(); + +int main() { + auto chips = GetChipChipNames(); + std::cout << "Number of GPIO chips : " << chips.size() << std::endl; + + for(const auto& chip : chips) { + PrintChipInfo(chip); + } + + try { + auto ch341Index = GetCh341ChipFilename(); + std::cout << std::endl << std::endl << " --> CH341 GPIO chip detected at index " << ch341Index << std::endl; + } catch(const std::runtime_error& error) { + std::cerr << "Error : " << error.what(); + } + + return 0; +} + +std::vector GetChipChipNames() { + auto gpioFilenameFilter = [](const struct dirent *entry) -> int { + std::string filename = entry->d_name; + return filename.rfind("gpiochip", 0) == 0; + }; + static const char* devString = "/dev/"; + + std::vector filenames; + struct dirent **entries; + auto nb = scandir(devString, &entries, gpioFilenameFilter, alphasort); + for(int i = 0; i < nb; i++) { + std::string name = std::string{devString} + std::string{entries[i]->d_name}; + filenames.push_back(name); + } + return filenames; +} + +void PrintChipInfo(const std::string& chip) { + struct gpiochip_info info; + auto fd = open(chip.c_str(), O_RDWR); + + auto res = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info); + if(res == 0) { + std::cout << chip << " : " << info.name << " (" << info.label + << ") : " << info.lines << " lines" << std::endl; + for(int i = 0; i < info.lines; i++) { + PrintLineInfo({fd}, i); + } + } else { + std::cerr << "Error while getting chip info for " << chip << std::endl; + } +} + +void PrintLineInfo(FileDescriptor fileDescriptor, int line) { + struct gpioline_info info; + info.line_offset = line; + + auto res = ioctl(fileDescriptor.fd, GPIO_GET_LINEINFO_IOCTL, &info); + if(res == 0) { + std::cout << "\t[" << line << "] " << info.name << " - " << info.consumer << " - " << info.flags << std::endl; + } else { + std::cerr << "Error while getting line info for line " << line; + } +} + +bool GetChipInfo(const std::string chip, struct gpiochip_info& info) { + auto fd = open(chip.c_str(), O_RDWR); + auto res = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info); + return res == 0; +} + +std::string GetCh341ChipFilename() { + static const char* ch341ChipLabel = "ch341"; + static const size_t ch341NbLines = 16; + + auto chips = GetChipChipNames(); + for(auto& chip : chips) { + struct gpiochip_info info; + if(GetChipInfo(chip, info)) { + if(std::string{info.label} == std::string{ch341ChipLabel} && info.lines == ch341NbLines) + return chip; + } + } + throw std::runtime_error("Cannot find a GPIO chip corresponding to the CH341"); +} diff --git a/apps/usb-communicator/main.cpp b/apps/usb-communicator/main.cpp index 7d27157..0e8c9a4 100644 --- a/apps/usb-communicator/main.cpp +++ b/apps/usb-communicator/main.cpp @@ -2,12 +2,21 @@ #include #include "PineDio/LoRa/UsbAdapter.h" #include "PineDio/LoRa/Exceptions.h" +#include + +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 { - 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; diff --git a/include/PineDio/LoRa/PinedioLoraRadio.h b/include/PineDio/LoRa/PinedioLoraRadio.h index 04f6904..624e524 100644 --- a/include/PineDio/LoRa/PinedioLoraRadio.h +++ b/include/PineDio/LoRa/PinedioLoraRadio.h @@ -12,7 +12,7 @@ public: virtual void Initialize(); virtual void Send(std::vector data); - virtual std::vector Receive(); + virtual std::vector Receive(std::chrono::milliseconds timeout); private: SX126x& radio; bool dataReceived {false}; diff --git a/include/PineDio/LoRa/UsbAdapter.h b/include/PineDio/LoRa/UsbAdapter.h index 07b71f1..85925b3 100644 --- a/include/PineDio/LoRa/UsbAdapter.h +++ b/include/PineDio/LoRa/UsbAdapter.h @@ -1,19 +1,25 @@ #pragma once #include #include +#include namespace PineDio::LoRa { class UsbAdapter : public SX126x { public: UsbAdapter(); + ~UsbAdapter() override; 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; + void OpenSpi(); + void OpenGpio(); int handle; + struct gpiohandle_request resetGpio; + struct gpiohandle_request busyGpio; void RxDone(); }; diff --git a/src/PinedioLoraRadio.cpp b/src/PinedioLoraRadio.cpp index 2303fdb..a71f9da 100644 --- a/src/PinedioLoraRadio.cpp +++ b/src/PinedioLoraRadio.cpp @@ -43,7 +43,7 @@ void PinedioLoraRadio::Initialize() { - static char* message = "Hello, I'm a Pinephone!"; + static const char* message = "Hello, I'm a Pinephone!"; auto s = strlen(message); SX126x::PacketParams_t packetParams; @@ -63,9 +63,10 @@ void PinedioLoraRadio::Send(const std::vector data) { transmitBuffer = data; //copy } -std::vector PinedioLoraRadio::Receive() { - std::cout << "[PinedioLoraRadio] Receive()" << std::endl; +std::vector PinedioLoraRadio::Receive(std::chrono::milliseconds timeout) { bool running = true; + auto startTime = std::chrono::steady_clock::now(); + std::vector buffer; while(running) { radio.ProcessIrqs(); @@ -87,13 +88,18 @@ std::vector PinedioLoraRadio::Receive() { } 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 receivedBuffer; //copy + return buffer; //copy } void PinedioLoraRadio::OnDataReceived() { diff --git a/src/usb-adapter/UsbAdapter.cpp b/src/usb-adapter/UsbAdapter.cpp index 6360310..64802b9 100644 --- a/src/usb-adapter/UsbAdapter.cpp +++ b/src/usb-adapter/UsbAdapter.cpp @@ -3,97 +3,121 @@ #include #include -#include #include #include #include +#include +#include using namespace PineDio::LoRa; +namespace { +const char *spiDriverOverrideFilePath ="/sys/class/spi_master/spi0/spi0.0/driver_override"; +const char *spidevOverride = "spidev"; + +const char *spiBindFilePath = "/sys/bus/spi/drivers/spidev/bind"; +const char *spiUnbindFilePath = "/sys/bus/spi/drivers/spidev/unbind"; +const char *spiBind = "spi0.0"; + +std::vector GetChipChipNames() { + auto gpioFilenameFilter = [](const struct dirent *entry) -> int { + std::string filename = entry->d_name; + return filename.rfind("gpiochip", 0) == 0; + }; + static const char *devString = "/dev/"; + + std::vector filenames; + struct dirent **entries; + auto nb = scandir(devString, &entries, gpioFilenameFilter, alphasort); + for (int i = 0; i < nb; i++) { + std::string name = std::string{devString} + std::string{entries[i]->d_name}; + filenames.push_back(name); + } + return filenames; +} + +bool GetChipInfo(const std::string chip, struct gpiochip_info &info) { + auto fd = open(chip.c_str(), O_RDWR); + auto res = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info); + return res == 0; +} + +std::string GetCh341ChipFilename() { + static const char *ch341ChipLabel = "ch341"; + static const size_t ch341NbLines = 16; + + auto chips = GetChipChipNames(); + for (auto &chip : chips) { + struct gpiochip_info info; + if (GetChipInfo(chip, info)) { + if (std::string{info.label} == std::string{ch341ChipLabel} && + info.lines == ch341NbLines) + return chip; + } + } + throw std::runtime_error("Cannot find a GPIO chip corresponding to the CH341"); +} + +int GetGpioLineIndex(const std::string &chip, const char *name) { + struct gpiochip_info info; + auto fd = open(chip.c_str(), O_RDWR); + auto res = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info); + if(res != 0) + throw std::runtime_error("IOCTL error (GPIO_GET_CHIPINFO_IOCTL failed)"); + + for (int i = 0; i < info.lines; i++) { + struct gpioline_info infoLine; + infoLine.line_offset = i; + res = ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &infoLine); + if(res != 0) + throw std::runtime_error("IOCTL error (GPIO_GET_LINEINFO_IOCTL failed)"); + + if (strcmp(name, infoLine.name) == 0) + return i; + } + throw std::runtime_error("Cannot find GPIO line"); +} +} + UsbAdapter::UsbAdapter() { std::cout << "[UsbAdapter] ctor()" << std::endl; + OpenGpio(); + OpenSpi(); +} - // 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); - } +UsbAdapter::~UsbAdapter() { + close(handle); + auto fd = open(spiUnbindFilePath, O_WRONLY); + write(fd, spiBind, strlen(spiBind)); - 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); + close(fd); } uint8_t UsbAdapter::HalGpioRead(SX126x::GpioPinFunction_t func) { - if(func != GpioPinFunction_t::GPIO_PIN_BUSY) { - std::cout << "ERROR" << std::endl; - throw; - } + if(func != GpioPinFunction_t::GPIO_PIN_BUSY) + throw std::runtime_error("Invalid call to HalGpioRead()"); - int fd; - if ((fd = open("/sys/class/gpio/slct/value", O_RDWR)) == -1) { - perror("Error while opening GPIO \"busy\""); - return -1; - } + struct gpiohandle_data data; + memset(&data, 0, sizeof(data)); + auto res = ioctl(busyGpio.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data); + if(res != 0) + throw std::runtime_error("IOCTL error (GPIOHANDLE_GET_LINE_VALUES_IOCTL failed)"); - char buf; - if (read(fd, &buf, 1) == -1) { - perror("Error while reading GPIO \"busy\""); - return -1; - } - close(fd); - - int value = (buf == '0') ? 0 : 1; + int value = data.values[0]; 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; - } + if(func != GpioPinFunction_t::GPIO_PIN_RESET) + throw std::runtime_error("Invalid call to HalGpioWrite()"); - 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\""); - } + struct gpiohandle_data data; + data.values[0] = value; + auto res = ioctl(resetGpio.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); + if(res != 0) + throw std::runtime_error("IOCTL error (GPIOHANDLE_SET_LINE_VALUES_IOCTL failed)"); } void UsbAdapter::HalSpiTransfer(uint8_t *buffer_in, const uint8_t *buffer_out, uint16_t size) { @@ -108,6 +132,68 @@ void UsbAdapter::HalSpiTransfer(uint8_t *buffer_in, const uint8_t *buffer_out, u spi_trans.cs_change = true; spi_trans.len = size; - int status = ioctl (handle, SPI_IOC_MESSAGE(1), &spi_trans); + int res = ioctl(handle, SPI_IOC_MESSAGE(1), &spi_trans); + if(res < 0) + throw std::runtime_error("IOCTL error (SPI_IOC_MESSAGE failed)"); } +void UsbAdapter::OpenSpi() { + auto fd = open(spiDriverOverrideFilePath, O_WRONLY); + if(fd == -1) + throw std::runtime_error("Cannot open SPI device (driver_override open error)"); + + auto res = write(fd, spidevOverride, strlen(spidevOverride)); + if(res <= 0) + throw std::runtime_error("Cannot open SPI device (driver_override write error)"); + + close(fd); + + 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) + throw std::runtime_error("Cannot open SPI device (bind write error)"); + + close(fd); + + handle = open("/dev/spidev0.0", O_RDWR); + if(handle == -1) + throw std::runtime_error("Cannot open SPI device (spidev open error)"); + + 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); +} + +void UsbAdapter::OpenGpio() { + auto chipName = GetCh341ChipFilename(); + std::cout << "CH341 detected at " << chipName << std::endl; + + auto resetlineIndex = GetGpioLineIndex(chipName, "ini"); + std::cout << "GPIO RESET detected at index " << resetlineIndex << std::endl; + + struct gpiochip_info info; + auto fd = open(chipName.c_str(), O_RDWR); + + resetGpio.flags = GPIOHANDLE_REQUEST_OUTPUT; + resetGpio.lines = 1; + resetGpio.lineoffsets[0] = resetlineIndex; + resetGpio.default_values[0] = 0; + strcpy(resetGpio.consumer_label, "LoRa SPI driver"); + ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &resetGpio); + + auto busylineIndex = GetGpioLineIndex(chipName, "slct"); + std::cout << "GPIO BUSY detected at index " << busylineIndex << std::endl; + + busyGpio.flags = GPIOHANDLE_REQUEST_INPUT; + busyGpio.lines = 1; + busyGpio.lineoffsets[0] = busylineIndex; + busyGpio.default_values[0] = 0; + strcpy(busyGpio.consumer_label, "LoRa SPI driver"); + ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &busyGpio); +}