Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/lib_tm1637_rpi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
push:
branches:
- main
- dev

release:
types: ['released', 'prereleased']
Expand Down
5 changes: 3 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.9)

set(lib-name TM1637Pi)
set(lib-version 1.4)
set(lib-version 1.5)
set(say-name tm1637_say)

# Project
Expand All @@ -10,7 +10,7 @@ project(${lib-name} VERSION ${lib-version} DESCRIPTION "TM1637 driver for Raspbe
include(GNUInstallDirs)

# We require C++11
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD 14)

# Default to building shared lib (.so) instead of static lib (.a) if not set on cmd line
option(BUILD_SHARED_LIBS "Build shared library (instead of static)" ON)
Expand Down Expand Up @@ -44,6 +44,7 @@ file(GLOB_RECURSE say_src_files "${say_cpp_base}/*.cpp")
# Say executable target
add_executable(${say-name} ${say_src_files})
# find required libs
set(Boost_USE_STATIC_LIBS ON) # Statically link to Boost (no SOVERSION support in Debian?)
find_package(Boost COMPONENTS program_options REQUIRED)
# Link say executable with library and Boost boost_program_options
target_link_libraries(${say-name} ${lib-name} dl Boost::program_options)
Expand Down
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,22 @@ to any GPIO library at build time. The GPIO library is dynamically loaded at run
## Installation

With [releases](https://github.com/neildavis/lib_tm1637_rpi/releases) from **v1.4.0** onwards
I am making available prebuilt Debian (`.deb`) packages for both 32-bit (`armhf`) & 64-bit (`arm64`/`aarch64`) versions of Raspberry Pi OS ('Bookworm').
I am making available prebuilt Debian (`.deb`) packages for both
32-bit (`armhf`) & 64-bit (`arm64`/`aarch64`) versions of Raspberry Pi OS
(Debian versions 12 '_Bookworm_' / 13 '_Trixie_').

Grab them from [here]([releases](https://github.com/neildavis/lib_tm1637_rpi/releases)) and install using `dpkg -i`: e.g.
Download them from the [releases]([releases](https://github.com/neildavis/lib_tm1637_rpi/releases)) and install using `dpkg -i`: e.g.

```sh
# 64-bit
sudo dpkpg -i libtm1637pi_1.4_arm64.deb`
sudo dpkpg -i libtm1637pi_1.5_arm64.deb`
```

OR

```sh
# 32-bit
sudo dpkpg -i libtm1637pi_1.4_armhf.deb`
sudo dpkpg -i libtm1637pi_1.5_armhf.deb`
```

Alternatively, you can follow the instructions in this README to
Expand All @@ -61,12 +63,18 @@ If you wish to use `libgpiod` for GPIO access, you will need to install the `gpi
sudo apt install gpiod
```

The library supports both SO versions of `libgpiod3` - as included in Debian 13 'Trixie'-
and the older `libgpiod2` - as included in previous OS versions.

Although these versions are neither source nor binary compatible (API & ABI change),
this library will identify and dynamically load the installed version and call the appropriate API.

### pigpio / pigpiod

#### \*\* IMPORTANT - Raspberry Pi 5 \*\*

Due to hardware architecture changes in the Raspberry Pi 5, ***`pigpio`***
**does not currently wok on the Raspberry Pi 5**. See / track
**does not currently work on the Raspberry Pi 5**. See / track
[these](https://github.com/joan2937/pigpio/issues/586)
[issues](https://github.com/joan2937/pigpio/issues/589)
for more details.
Expand Down Expand Up @@ -160,7 +168,7 @@ pkg-config --libs --cflags libTM1637Pi
An example of how to compile & link your program against the library:

```sh
g++ -Wall -std=c++11 myprogram.cpp -o myprogram $(pkg-config --libs --cflags libTM1637Pi)
g++ -Wall -std=c++14 myprogram.cpp -o myprogram $(pkg-config --libs --cflags libTM1637Pi)
```

### API
Expand Down
3 changes: 2 additions & 1 deletion inc/tm1637.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once
#include <inttypes.h>
#include <memory>

namespace tm1637 {

Expand Down Expand Up @@ -85,7 +86,7 @@ namespace tm1637 {


protected:
MGPIO *m_gpio;
std::unique_ptr<MGPIO> m_gpio;
uint8_t m_data[4];
uint8_t m_brightness;
bool m_showColon;
Expand Down
94 changes: 51 additions & 43 deletions src/gpioGPIOD.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,81 @@
#include <dlfcn.h>
#include <stdexcept>
#include <vector>
#include <sstream>
#include <string>

using namespace tm1637;
#include "gpioGPIODv1.h"
#include "gpioGPIODv2.h"

const char *kGpiodLibSoName = "libgpiod.so.2";
const char *kLineConsumer = "libTM1637Pi";
using namespace tm1637;

GPIOD::GPIOD(int pinClk, int pinData) :
m_pinClk(pinClk),
m_pinData(pinData) {
}


void GPIOD::initialize() {
dynLoadGpiodLib();

std::vector<std::string> chip_names = {
"gpiochip4", // For RPi5 - try first
"gpiochip0"
};

for (std::vector<std::string>::iterator it = chip_names.begin();
it != chip_names.end(); it++) {
m_chip = m_gpiod_chip_open_by_name(it->c_str());
if (m_chip) {
break;
}
int soVersion = dynLoadGpiodLib(); // throws if unsuccessful
// Initialize the correct MGPIO proxy for the libgpiod SO version loaded
// Note: Due to legacy reasons, SO.3 is lib version >= 2.1 and SO.2 is >= v1.5.1
switch (soVersion)
{
case 3:
m_gpio = std::make_unique<GPIODv2>(m_pinClk, m_pinData, m_libHandle);
break;
case 2:
m_gpio = std::make_unique<GPIODv1>(m_pinClk, m_pinData, m_libHandle);
break;
default:
// Should never get here since dynLoadGpiodLib() will throw if not returning
// supported SO version on a successful dyn lib load
throw std::runtime_error("Error: Unsupported libgpiod SO version");
break;
}

m_lineClk = m_gpiod_chip_get_line(m_chip, m_pinClk);
m_lineData = m_gpiod_chip_get_line(m_chip, m_pinData);
m_gpiod_line_request_output(m_lineClk, kLineConsumer, 0); // open CLK as LOW
m_gpiod_line_request_output(m_lineData, kLineConsumer, 0); // open DIO as LOW

// If we get here, m_gpio is assumed to be valid since dynLoadGpiodLib will throw if not.
m_gpio->initialize();
}

void GPIOD::deinitialize() {
m_gpiod_line_release(m_lineClk);
m_gpiod_line_release(m_lineData);
m_gpiod_chip_close(m_chip);
m_gpio->deinitialize();
dlclose(m_libHandle);
}

void GPIOD::setClock(PinDigitalState state) {
m_gpiod_line_set_value(m_lineClk, state);
m_gpio->setClock(state);
}

void GPIOD::setData(PinDigitalState state) {
m_gpiod_line_set_value(m_lineData, state);
m_gpio->setData(state);
}

void GPIOD::delayMicroseconds(int usecs) {
std::this_thread::sleep_for(std::chrono::microseconds(usecs));
}

void GPIOD::dynLoadGpiodLib() {
m_libHandle = dlopen(kGpiodLibSoName, RTLD_LAZY);
if (NULL == m_libHandle) { throw std::runtime_error(dlerror()); }
m_gpiod_chip_open_by_name = reinterpret_cast<gpiod_chip* (*)(const char*)>(dlsym(m_libHandle, "gpiod_chip_open_by_name"));
if (NULL == m_gpiod_chip_open_by_name) { throw std::runtime_error(dlerror()); }
m_gpiod_chip_close = reinterpret_cast<void (*)(gpiod_chip*)>(dlsym(m_libHandle, "gpiod_chip_close"));
if (NULL == m_gpiod_chip_close) { throw std::runtime_error(dlerror()); }
m_gpiod_chip_get_line = reinterpret_cast<gpiod_line* (*)(gpiod_chip*, unsigned int)>(dlsym(m_libHandle, "gpiod_chip_get_line"));
if (NULL == m_gpiod_chip_get_line) { throw std::runtime_error(dlerror()); }
m_gpiod_line_release = reinterpret_cast<void (*)(gpiod_line*)>(dlsym(m_libHandle, "gpiod_line_release"));
if (NULL == m_gpiod_line_release) { throw std::runtime_error(dlerror()); }
m_gpiod_line_request_output = reinterpret_cast<int (*)(gpiod_line*, const char*, int)>(dlsym(m_libHandle, "gpiod_line_request_output"));
if (NULL == m_gpiod_line_request_output) { throw std::runtime_error(dlerror()); }
m_gpiod_line_set_value = reinterpret_cast<int (*)(gpiod_line*, int)>(dlsym(m_libHandle, "gpiod_line_set_value"));
if (NULL == m_gpiod_line_set_value) { throw std::runtime_error(dlerror()); }
int GPIOD::dynLoadGpiodLib() {
// Attempt to open libgpiod.so.N in this order of 'N'
std::vector<int> soVersions = {
3, // libgpiod3 in Debian 13 'Trixie' +
2 // libgpiod2 in Debian 12 'Bookworm' (and earlier)
};
for (auto it = soVersions.begin(); it != soVersions.end(); it++) {
std::ostringstream oss;
oss << "libgpiod.so." << *it;
m_libHandle = dlopen(oss.str().c_str(), RTLD_LAZY);
if (m_libHandle) {
return *it;
}
}
std::ostringstream oss;
oss << "Unable to load any supported SO version of libgpiod.so. Tried versions: ";
for (auto it = soVersions.begin(); it != soVersions.end(); it++) {
oss << *it;
if (it < soVersions.end() - 1) {
oss << ", ";
}
}

throw std::runtime_error(oss.str().c_str());
return -1; // never get here due to exception thrown, but satisifes the compiler
}
20 changes: 4 additions & 16 deletions src/gpioGPIOD.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
#pragma once

#include "gpio.h"

struct gpiod_line;
struct gpiod_chip;
#include <memory>

namespace tm1637 {

Expand All @@ -17,21 +15,11 @@ namespace tm1637 {
virtual void setData(PinDigitalState state) override;
virtual void delayMicroseconds(int usecs) override;
private:
void dynLoadGpiodLib();
private:
// These are func pointers we assign from the symbols in libgpiod.so.2
gpiod_chip* (*m_gpiod_chip_open_by_name)(const char *name);
void (*m_gpiod_chip_close)(gpiod_chip *chip);
gpiod_line* (*m_gpiod_chip_get_line)(gpiod_chip *chip, unsigned int offset);
void (*m_gpiod_line_release)(gpiod_line *line);
int (*m_gpiod_line_request_output)(gpiod_line *line, const char *consumer, int default_val);
int (*m_gpiod_line_set_value)(gpiod_line *line, int value);
int dynLoadGpiodLib();
private:
int m_pinClk;
int m_pinData;
void *m_libHandle;
gpiod_line *m_lineClk;
gpiod_line *m_lineData;
gpiod_chip *m_chip;
};
std::unique_ptr<MGPIO> m_gpio; // proxied for GPIODv1 or GPIODv2
};
}
74 changes: 74 additions & 0 deletions src/gpioGPIODv1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "gpioGPIODv1.h"

#include <chrono>
#include <thread>
#include <dlfcn.h>
#include <stdexcept>
#include <vector>

using namespace tm1637;

const char *kGPIODLineConsumer1 = "libTM1637Pi";

GPIODv1::GPIODv1(int pinClk, int pinData, void *libHandle) :
m_pinClk(pinClk),
m_pinData(pinData),
m_libHandle(libHandle) {
}


void GPIODv1::initialize() {
dynLoadGpiodLib();

std::vector<std::string> chip_names = {
"gpiochip4", // For RPi5 - try first
"gpiochip0"
};

for (std::vector<std::string>::iterator it = chip_names.begin();
it != chip_names.end(); it++) {
m_chip = m_gpiod_chip_open_by_name(it->c_str());
if (m_chip) {
break;
}
}

m_lineClk = m_gpiod_chip_get_line(m_chip, m_pinClk);
m_lineData = m_gpiod_chip_get_line(m_chip, m_pinData);
m_gpiod_line_request_output(m_lineClk, kGPIODLineConsumer1, 0); // open CLK as LOW
m_gpiod_line_request_output(m_lineData, kGPIODLineConsumer1, 0); // open DIO as LOW

}

void GPIODv1::deinitialize() {
m_gpiod_line_release(m_lineClk);
m_gpiod_line_release(m_lineData);
m_gpiod_chip_close(m_chip);
}

void GPIODv1::setClock(PinDigitalState state) {
m_gpiod_line_set_value(m_lineClk, state);
}

void GPIODv1::setData(PinDigitalState state) {
m_gpiod_line_set_value(m_lineData, state);
}

void GPIODv1::delayMicroseconds(int usecs) {
std::this_thread::sleep_for(std::chrono::microseconds(usecs));
}

void GPIODv1::dynLoadGpiodLib() {
m_gpiod_chip_open_by_name = reinterpret_cast<gpiod_chip* (*)(const char*)>(dlsym(m_libHandle, "gpiod_chip_open_by_name"));
if (NULL == m_gpiod_chip_open_by_name) { throw std::runtime_error(dlerror()); }
m_gpiod_chip_close = reinterpret_cast<void (*)(gpiod_chip*)>(dlsym(m_libHandle, "gpiod_chip_close"));
if (NULL == m_gpiod_chip_close) { throw std::runtime_error(dlerror()); }
m_gpiod_chip_get_line = reinterpret_cast<gpiod_line* (*)(gpiod_chip*, unsigned int)>(dlsym(m_libHandle, "gpiod_chip_get_line"));
if (NULL == m_gpiod_chip_get_line) { throw std::runtime_error(dlerror()); }
m_gpiod_line_release = reinterpret_cast<void (*)(gpiod_line*)>(dlsym(m_libHandle, "gpiod_line_release"));
if (NULL == m_gpiod_line_release) { throw std::runtime_error(dlerror()); }
m_gpiod_line_request_output = reinterpret_cast<int (*)(gpiod_line*, const char*, int)>(dlsym(m_libHandle, "gpiod_line_request_output"));
if (NULL == m_gpiod_line_request_output) { throw std::runtime_error(dlerror()); }
m_gpiod_line_set_value = reinterpret_cast<int (*)(gpiod_line*, int)>(dlsym(m_libHandle, "gpiod_line_set_value"));
if (NULL == m_gpiod_line_set_value) { throw std::runtime_error(dlerror()); }
}
37 changes: 37 additions & 0 deletions src/gpioGPIODv1.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#pragma once

#include "gpio.h"

struct gpiod_line;
struct gpiod_chip;

namespace tm1637 {

class GPIODv1 : public MGPIO {
public:
GPIODv1(int pinClk, int pinData, void *libHandle);
// API
virtual void initialize() override;
virtual void deinitialize() override;
virtual void setClock(PinDigitalState state) override;
virtual void setData(PinDigitalState state) override;
virtual void delayMicroseconds(int usecs) override;
private:
void dynLoadGpiodLib();
private:
// These are func pointers we assign from the symbols in libgpiod.so.2
gpiod_chip* (*m_gpiod_chip_open_by_name)(const char *name);
void (*m_gpiod_chip_close)(gpiod_chip *chip);
gpiod_line* (*m_gpiod_chip_get_line)(gpiod_chip *chip, unsigned int offset);
void (*m_gpiod_line_release)(gpiod_line *line);
int (*m_gpiod_line_request_output)(gpiod_line *line, const char *consumer, int default_val);
int (*m_gpiod_line_set_value)(gpiod_line *line, int value);
private:
int m_pinClk;
int m_pinData;
void *m_libHandle;
gpiod_line *m_lineClk;
gpiod_line *m_lineData;
gpiod_chip *m_chip;
};
}
Loading