Home

Awesome

AceTMI

Validation

Interface classes for communicating with a TM1637 or TM1638 LED controller chip on Arduino platforms. The code was initially part of the AceSegment library, but was extracted into a separate library for consistency with the AceWire and AceSPI libraries. It provides the following implementations:

The "TMI" acronym was invented by this library to name the protocol used by the TM1637 or the TM1638 chip. It stands for "Titan Micro Interface", named after Titan Micro Electronics which manufactures these chips. The TM1637 protocol is electrically similar to I2C, but different enough that we cannot use the <Wire.h> I2C library. The TM1638 protocol is electrically similar to SPI but different enough that we cannot use the <SPI.h> library.

The protocol implemented by this library works only for the TM1637 or the TM1638 chips as far as I know. Most people will want to use the Tm1637Module or the Tm1638Module class in the AceSegment library instead of using this low-level library.

The library uses C++ templates to achieve minimal runtime overhead. In more technical terms, the library provides compile-time polymorphism instead of runtime polymorphism to avoid the overhead of the virtual keyword.

Version: 0.6 (2022-03-01)

Changelog: CHANGELOG.md

See Also:

Table of Contents

<a name="Installation"></a>

Installation

The latest stable release is available in the Arduino IDE Library Manager. Search for "AceTMI". Click install.

The development version can be installed by cloning the GitHub repository, checking out the default develop branch, then manually copying over to or symlinking from the ./libraries directory used by the Arduino IDE. (The result is a directory or link named ./libraries/AceTMI.)

The master branch contains the stable releases.

<a name="SourceCode"></a>

Source Code

The source files are organized as follows:

<a name="Dependencies"></a>

Dependencies

The main AceTMI.h does not depend any external libraries.

The "Fast" version (SimpleTmi1637FastInterface.h) depends on one of the digitalWriteFast libraries, for example:

<a name="Documentation"></a>

Documentation

<a name="Usage"></a>

Usage

<a name="HeaderAndNamespace"></a>

Include Header and Namespace

In many cases, only a single header file AceTMI.h is required to use this library. To prevent name clashes with other libraries that the calling code may use, all classes are defined in the ace_tmi namespace. To use the code without prepending the ace_tmi:: prefix, use the using directive:

#include <Arduino.h>
#include <AceTMI.h>
using ace_tmi::SimpleTmi1637Interface;

The "Fast" versions are not included automatically by AceTMI.h because they work only on AVR processors and they depend on a <digitalWriteFast.h> library. To use the "Fast" versions, use something like the following:'

#include <Arduino.h>
#include <AceTMI.h>

#if defined(ARDUINO_ARCH_AVR)
  #include <digitalWriteFast.h>
  #include <ace_tmi/SimpleTmi1637FastInterface.h>
  using ace_tmi::SimpleTmi1637FastInterface;
#endif

<a name="UnifiedInterface"></a>

Unified Interface

The classes in this library provide the following unified interface for handling communication with the TM1637 chip. Downstream classes can code against this generic API using C++ templates so that different implementations can be selected at compile-time.

class XxxInterface {
  public:
    void begin() const;
    void end() const;

    void startCondition() const;
    void stopCondition() const;
    uint8_t write(uint8_t data) const;
    uint8_t read() const;
};

The TM1638 interface classes follow a similar pattern:

class Xxx1638Interface {
  public:
    void begin() const;
    void end() const;

    void beginTransaction() const;
    void endTransaction() const;
    uint8_t write(uint8_t data) const;
    uint8_t read() const;
};

Notice that the classes in this library do not inherit from a common interface with virtual functions. This saves several hundred bytes of flash memory on 8-bit AVR processors by avoiding the dynamic dispatch, and often allows the compiler to optimize away the overhead of calling the methods in this library so that the function call is made directly to the underlying implementation. The reduction of flash memory consumption is especially large for classes that use the digitalWriteFast libraries which use compile-time constants for pin numbers. The disadvantage is that this library is harder to use because these classes require the downstream classes to be implemented using C++ templates.

The read() method reads the keypad scan data from the controller chip:

<a name="SimpleTmi1637Interface"></a>

SimpleTmi1637Interface

The SimpleTmi1637Interface can be used like this to communicate with a TM1637 controller chip. It looks like this:

class SimpleTmi1637Interface {
  public:
    explicit SimpleTmi1637Interface(
        uint8_t dioPin,
        uint8_t clkPin,
        uint8_t delayMicros
    );

    void begin() const;
    void end() const;

    void startCondition() const;
    void stopCondition() const;
    uint8_t write(uint8_t data) const;
    uint8_t read() const;
};

It is configured and used by the calling code MyClass like this:

#include <Arduino.h>
#include <AceTMI.h>
using ace_tmi::SimpleTmi1637Interface;

template <typename T_TMII>
class MyClass {
  public:
    explicit MyClass(T_TMII& tmiInterface)
        : mTmiInterface(tmiInterface)
    {...}

    void sendData() {
      // Set addressing mode.
      mTmiInterface.startCondition();
      mTmiInterface.write(addressMode);
      mTmiInterface.stopCondition();

      // Send data bytes.
      mTmiInterface.startCondition();
      mTmiInterface.write(otherCommand);
      [...]
      mTmiInterface.stopCondition();

      // Set brightness.
      mTmiInterface.startCondition();
      mTmiInterface.write(brightness);
      mTmiInterface.stopCondition();
    }

  private:
    T_TMII mTmiInterface; // copied by value
};

const uint8_t CLK_PIN = 8;
const uint8_t DIO_PIN = 9;
const uint8_t DELAY_MICROS = 100;

using TmiInterface = SimpleTmi1637Interface;
TmiInterface tmiInterface(DIO_PIN, CLK_PIN, DELAY_MICROS);
MyClass<TmiInterface> myClass(tmiInterface);

void setup() {
  tmiInterface.begin();
  ...
}

The using statement is the C++11 version of a typedef that defines TmiInterface. It is not strictly necessary here, but it allows the same pattern to be used for the more complicated examples below.

The T_TMII template parameter contains a T_ prefix to avoid name collisions with too many #define macros defined in the global namespace on Arduino platforms. The double II contains 2 Interface, the first referring to the TM1637 protocol, and the second referring to classes in this library.

<a name="SimpleTmi1637FastInterface"></a>

SimpleTmi1637FastInterface

The SimpleTmi1637FastInterface is identical to SimpleTmi1637Interface except that it uses digitalWriteFast(). It looks like this:

template <
    uint8_t T_DIO_PIN,
    uint8_t T_CLK_PIN,
    uint8_t T_DELAY_MICROS
>
class SimpleTmi1637FastInterface {
  public:
    explicit SimpleTmi1637FastInterface() = default;

    void begin() const;
    void end() const;

    void startCondition() const;
    void stopCondition() const;
    uint8_t write(uint8_t data) const;
    uint8_t read() const;
};

It is configured and used by the calling code MyClass like this:

#include <Arduino.h>
#include <AceTMI.h>
#if defined(ARDUINO_ARCH_AVR)
  #include <digitalWriteFast.h>
  #include <ace_tmi/SimpleTmi1637FastInterface.h>
  using ace_tmi::SimpleTmi1637Interface;
#endif

const uint8_t CLK_PIN = 8;
const uint8_t DIO_PIN = 9;
const uint8_t DELAY_MICROS = 100;

template <typename T_TMII>
class MyClass {
  // Exactly same as above.
};

using TmiInterface = SimpleTmi1637FastInterface<DIO_PIN, CLK_PIN, DELAY_MICROS>;
TmiInterface tmiInterface;
MyClass<TmiInterface> myClass(tmiInterface);

void setup() {
  tmiInterface.begin();
  ...
}

<a name="SimpleTmi1638Interface"></a>

SimpleTmi1638Interface

The SimpleTmi1638Interface can be used like this to communicate with a TM1637 controller chip. It looks like this:

class SimpleTmi1638Interface {
  public:
    explicit SimpleTmi1638Interface(
        uint8_t dioPin,
        uint8_t clkPin,
        uint8_t stbPin,
        uint8_t delayMicros
    );

    void begin() const;
    void end() const;

    void beginTransaction() const;
    void endTransaction() const;
    uint8_t write(uint8_t data) const;
    uint8_t read() const;
};

It is configured and used by the calling code MyClass like this:

#include <Arduino.h>
#include <AceTMI.h>
using ace_tmi::SimpleTmi1638Interface;

template <typename T_TMII>
class MyClass {
  public:
    explicit MyClass(T_TMII& tmiInterface)
        : mTmiInterface(tmiInterface)
    {...}

    void sendData() {
      // Set addressing mode.
      mTmiInterface.beginTransaction();
      mTmiInterface.write(dataCommand);
      mTmiInterface.endTransaction();

      // Send data bytes.
      mTmiInterface.beginTransaction();
      mTmiInterface.write(addressCommand);
      [...]
      mTmiInterface.endTransaction();

      // Set brightness.
      mTmiInterface.beginTransaction();
      mTmiInterface.write(brightness);
      mTmiInterface.endTransaction();
    }

  private:
    T_TMII mTmiInterface; // copied by value
};

const uint8_t CLK_PIN = 8;
const uint8_t DIO_PIN = 9;
const uint8_t STB_PIN = 10;
const uint8_t DELAY_MICROS = 1;

using TmiInterface = SimpleTmi1638Interface;
TmiInterface tmiInterface(DIO_PIN, CLK_PIN, STB_PIN, DELAY_MICROS);
MyClass<TmiInterface> myClass(tmiInterface);

void setup() {
  tmiInterface.begin();
  ...
}

The using statement is the C++11 version of a typedef that defines TmiInterface. It is not strictly necessary here, but it allows the same pattern to be used for the more complicated examples below.

The T_TMII template parameter contains a T_ prefix to avoid name collisions with too many #define macros defined in the global namespace on Arduino platforms. The double II contains 2 Interface, the first referring to the TM1637 protocol, and the second referring to classes in this library.

<a name="SimpleTmi1638FastInterface"></a>

SimpleTmi1638FastInterface

The SimpleTmi1638FastInterface is identical to SimpleTmi1638Interface except that it uses digitalWriteFast(). It looks like this:

template <
    uint8_t T_DIO_PIN,
    uint8_t T_CLK_PIN,
    uint8_t T_STB_PIN,
    uint8_t T_DELAY_MICROS
>
class SimpleTmi1638FastInterface {
  public:
    explicit SimpleTmi1638FastInterface() = default;

    void begin() const;
    void end() const;

    void beginTransaction() const;
    void endTransaction() const;
    uint8_t write(uint8_t data) const;
    uint8_t read() const;
};

It is configured and used by the calling code MyClass like this:

#include <Arduino.h>
#include <AceTMI.h>
#if defined(ARDUINO_ARCH_AVR)
  #include <digitalWriteFast.h>
  #include <ace_tmi/SimpleTmi1638FastInterface.h>
  using ace_tmi::SimpleTmi1638Interface;
#endif

const uint8_t CLK_PIN = 8;
const uint8_t DIO_PIN = 9;
const uint8_t STB_PIN = 10;
const uint8_t DELAY_MICROS = 1;

template <typename T_TMII>
class MyClass {
  // Exactly same as above.
};

using TmiInterface = SimpleTmi1638FastInterface<
    DIO_PIN, CLK_PIN, STB_PIN, DELAY_MICROS>;
TmiInterface tmiInterface;
MyClass<TmiInterface> myClass(tmiInterface);

void setup() {
  tmiInterface.begin();
  ...
}

<a name="StoringInterfaceObjects"></a>

Storing Interface Objects

In the above examples, the MyClass object holds the T_TMII interface object by value. In other words, the interface object is copied into the MyClass object. This is efficient because interface objects are very small in size, and copying them by-value avoids an extra level of indirection when they are used inside the MyClass object.

The alternative is to save the T_TMII object by reference like this:

template <typename T_TMII>
class MyClass {
  public:
    explicit MyClass(T_TMII& tmiInterface)
        : mTmiInterface(tmiInterface)
    {...}

    [...]

  private:
    T_TMII& mTmiInterface; // copied by reference
};

The internal size of the SimpleTmi1637Interface object is just 3 bytes, and the size of the SimpleTmi1637FastInterface is even smaller at 0 bytes, so we do not save much memory by storing these by reference. But storing the mTmiInterface as a reference causes an unnecessary extra layer of indirection every time the mTmiInterface object is called. In almost every case, I recommend storing the XxxInterface object by value into the MyClass object.

<a name="ResourceConsumption"></a>

Resource Consumption

<a name="FlashAndStaticMemory"></a>

Flash And Static Memory

The Memory benchmark numbers can be seen in examples/MemoryBenchmark. Here are 2 samples:

Arduino Nano

+--------------------------------------------------------------+
| functionality                   |  flash/  ram |       delta |
|---------------------------------+--------------+-------------|
| baseline                        |    456/   11 |     0/    0 |
|---------------------------------+--------------+-------------|
| SimpleTmi1637Interface          |   1200/   14 |   744/    3 |
| SimpleTmi1637FastInterface      |    638/   11 |   182/    0 |
| SimpleTmi1638Interface          |   1108/   15 |   652/    4 |
| SimpleTmi1638FastInterface      |    590/   11 |   134/    0 |
+--------------------------------------------------------------+

ESP8266

+--------------------------------------------------------------+
| functionality                   |  flash/  ram |       delta |
|---------------------------------+--------------+-------------|
| baseline                        | 260089/27892 |     0/    0 |
|---------------------------------+--------------+-------------|
| SimpleTmi1637Interface          | 261361/27992 |  1272/  100 |
| SimpleTmi1638Interface          | 261217/27992 |  1128/  100 |
+--------------------------------------------------------------+

<a name="CpuCycles"></a>

CPU Cycles

The CPU benchmark numbers can be seen in examples/AutoBenchmark. Here are 2 samples:

Arduino Nano

+-----------------------------------------+-------------------+----------+
| Functionality                           |   min/  avg/  max | eff kbps |
|-----------------------------------------+-------------------+----------|
| SimpleTmi1637Interface,1us              |   752/  781/  836 |     41.0 |
| SimpleTmi1637FastInterface,1us          |    92/   95/  104 |    336.8 |
| SimpleTmi1638Interface,1us              |   616/  635/  688 |     50.4 |
| SimpleTmi1638FastInterface,1us          |    84/   86/   92 |    372.1 |
+-----------------------------------------+-------------------+----------+

ESP8266

+-----------------------------------------+-------------------+----------+
| Functionality                           |   min/  avg/  max | eff kbps |
|-----------------------------------------+-------------------+----------|
| SimpleTmi1637Interface,1us              |   375/  377/  415 |     84.9 |
| SimpleTmi1638Interface,1us              |   334/  335/  354 |     95.5 |
+-----------------------------------------+-------------------+----------+

<a name="SystemRequirements"></a>

System Requirements

<a name="Hardware"></a>

Hardware

Tier 1: Fully Supported

These boards are tested on each release:

Tier 2: Should work

These boards should work but I don't test them as often:

Tier 3: May work, but not supported

Tier Blacklisted

The following boards are not supported and are explicitly blacklisted to allow the compiler to print useful error messages instead of hundreds of lines of compiler errors:

<a name="ToolChain"></a>

Tool Chain

This library is not compatible with:

It should work with PlatformIO but I have not tested it.

The library can be compiled under Linux or MacOS (using both g++ and clang++ compilers) using the EpoxyDuino (https://github.com/bxparks/EpoxyDuino) emulation layer.

<a name="OperatingSystem"></a>

Operating System

I use Ubuntu 20.04 for the vast majority of my development. I expect that the library will work fine under MacOS and Windows, but I have not explicitly tested them.

<a name="License"></a>

License

MIT License

<a name="FeedbackAndSupport"></a>

Feedback and Support

If you have any questions, comments, or feature requests for this library, please use the GitHub Discussions for this project. If you have bug reports, please file a ticket in GitHub Issues. Feature requests should go into Discussions first because they often have alternative solutions which are useful to remain visible, instead of disappearing from the default view of the Issue tracker after the ticket is closed.

Please refrain from emailing me directly unless the content is sensitive. The problem with email is that I cannot reference the email conversation when other people ask similar questions later.

<a name="Authors"></a>

Authors

Created by Brian T. Park (brian@xparks.net).