DEV Community

Cover image for Ulanzi TC001 - ESP32 Programming / Custom Arduino firmware
Calum Knott
Calum Knott

Posted on

3 1

Ulanzi TC001 - ESP32 Programming / Custom Arduino firmware

I found quite a lot of tutorials on using the Ulanzi TC001 with a firmware like AWTRIX, but not much information on simply programming it from scratch.
It wasnt too dificult to figure out, but, i thought it would be nice to document a few things here, so that others might be encouraged to give this nice little unit a try.

So this is a collection of notes designed to assist anyone who wants to do some "from scratch" programming, with the help of a few libraries, of course.


Setup

You can program the Ulanzi TC001 using a standard Arduino IDE.

Install the ESP32 boards in the board manager.

ESP32 Espressif

Then select board time "NODEMCU-32S"

When you download to the board, you will get a high pitch noise. Im not sure why, but in order to fix this, you need to add the following lines to your setup function.

  pinMode(15, INPUT_PULLDOWN); //Stops the High pitch noise!
  pinMode(27, INPUT_PULLUP); // Does something with the buttons
  pinMode(26, INPUT_PULLUP); // Does something with the buttons
Enter fullscreen mode Exit fullscreen mode

Getting Started

A simple program can be written that shows how the pixels are arranged. They can be though of as essentially a strip of LEDs, that runs each row in alternating directions
001 -> 032
064 <- 033

Back and Forth

#include <FastLED.h>

#define NUM_LEDS 256
#define DATA_PIN 32

CRGB leds[NUM_LEDS];

void setup() {

  pinMode(15, INPUT_PULLDOWN); //Stops the High pitch noise!
  pinMode(27, INPUT_PULLUP);
  pinMode(26, INPUT_PULLUP);

  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(50);
}

void loop() {
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i] = CRGB::Red;
    FastLED.show();
    delay(50);
    leds[i] = CRGB::Black;
  }
}
Enter fullscreen mode Exit fullscreen mode

To fix this, we can use a helper function function "setLED" which will allow us to set any pixel simply by referring to its X,Y position, and will deal with the alternating direction of the rows.

void setLED(int x, int y, CRGB color) {
  if (x < 0 || x >= MATRIX_WIDTH || y < 0 || y >= MATRIX_HEIGHT) return;
  int index = (y % 2 == 0) ? y * MATRIX_WIDTH + x : (y + 1) * MATRIX_WIDTH - 1 - x;
  leds[index] = color;
}
Enter fullscreen mode Exit fullscreen mode

Correct

Here is an example of filling the display in the expected order:

#include <FastLED.h>

#define NUM_LEDS 256
#define DATA_PIN 32

#define MATRIX_WIDTH 32
#define MATRIX_HEIGHT 8

CRGB leds[NUM_LEDS];

void setup() {
  pinMode(15, INPUT_PULLDOWN);
  pinMode(27, INPUT_PULLUP);
  pinMode(26, INPUT_PULLUP);

  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(50);
}

void setLED(int x, int y, CRGB color) {
  if (x < 0 || x >= MATRIX_WIDTH || y < 0 || y >= MATRIX_HEIGHT) return;
  int index = (y % 2 == 0) ? y * MATRIX_WIDTH + x : (y + 1) * MATRIX_WIDTH - 1 - x;
  leds[index] = color;
}

void loop() {
  // Loop Through each pixel and turn it on
  for (int i = 0; i < MATRIX_HEIGHT; i++) {
    for (int j = 0; j < MATRIX_WIDTH; j++) {
        setLED(j,i,CRGB::Red);
        FastLED.show();
        delay(50);
    }
  }

  // Set all LEDs to black
  for(int i = 0; i < NUM_LEDS; i++) {
      leds[i] = CRGB::Black; 
  }
}


Enter fullscreen mode Exit fullscreen mode

Its then fairly simple to add extra features, and begin to build a usable system.

Time

The following code will allow you to display the time!

#include <FastLED.h>
#include <WiFi.h>
#include <time.h>

#define NUM_LEDS 256
#define DATA_PIN 32
#define MATRIX_WIDTH 32
#define MATRIX_HEIGHT 8

const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 0;
const int   daylightOffset_sec = 3600;

CRGB leds[NUM_LEDS];

// 3x5 font for digits
const uint8_t digits[10][5] = {
  {0b111, 0b101, 0b101, 0b101, 0b111}, // 0
  {0b010, 0b110, 0b010, 0b010, 0b111}, // 1
  {0b111, 0b001, 0b111, 0b100, 0b111}, // 2
  {0b111, 0b001, 0b111, 0b001, 0b111}, // 3
  {0b101, 0b101, 0b111, 0b001, 0b001}, // 4
  {0b111, 0b100, 0b111, 0b001, 0b111}, // 5
  {0b111, 0b100, 0b111, 0b101, 0b111}, // 6
  {0b111, 0b001, 0b010, 0b010, 0b010}, // 7
  {0b111, 0b101, 0b111, 0b101, 0b111}, // 8
  {0b111, 0b101, 0b111, 0b001, 0b111}  // 9
};

void setup() {
  Serial.begin(115200);
  pinMode(15, INPUT_PULLDOWN);
  pinMode(27, INPUT_PULLUP);
  pinMode(26, INPUT_PULLUP);

  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(50);

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nConnected to WiFi");

  // Init and get the time
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}

void setLED(int x, int y, CRGB color) {
  if (x < 0 || x >= MATRIX_WIDTH || y < 0 || y >= MATRIX_HEIGHT) return;
  int index = (y % 2 == 0) ? y * MATRIX_WIDTH + x : (y + 1) * MATRIX_WIDTH - 1 - x;
  leds[index] = color;
}

void drawDigit(int x, int y, int digit, CRGB color) {
  for (int row = 0; row < 5; row++) {
    for (int col = 0; col < 3; col++) {
      if (digits[digit][row] & (1 << (2 - col))) {
        setLED(x + col, y + row, color);
      }
    }
  }
}

void drawTime(int hours, int minutes, int seconds, CRGB color) {
  drawDigit(0, 1, hours / 10, color);
  drawDigit(4, 1, hours % 10, color);
  setLED(8, 2, color);
  setLED(8, 4, color);
  drawDigit(10, 1, minutes / 10, color);
  drawDigit(14, 1, minutes % 10, color);
  setLED(18, 2, color);
  setLED(18, 4, color);
  drawDigit(20, 1, seconds / 10, color);
  drawDigit(24, 1, seconds % 10, color);
}

void loop() {
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return;
  }

  FastLED.clear();
  drawTime(timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec, CRGB::White);
  FastLED.show();
  delay(1000);
}
Enter fullscreen mode Exit fullscreen mode

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (7)

Collapse
 
reinout_roels_4ca4a460b83 profile image
Reinout Roels • Edited

Very useful, thanks! Is there any public info on how to interface with for instance the sensor or the buzzer? I can't seem to find even the pinout.

I would prefer not to take apart my brand new clock just yet :D

EDIT: never mind, found something useful:
github.com/Blueforcer/awtrix3/blob...
templates.blakadder.com/ulanzi_TC0...

Collapse
 
calumk profile image
Calum Knott

I wasnt aware there was a buzzer! Thanks thats usefull

Collapse
 
reinout_roels_4ca4a460b83 profile image
Reinout Roels

There's also a dedicated RTC and temp/humidity sensor connected to the I2C bus. I will try to get these working via Arduino in the coming week.

Collapse
 
reinout_roels_4ca4a460b83 profile image
Reinout Roels

In case anyone wants to make use of the other peripherals (temp sensor, light sensor, buzzer, RTC, ...), from a custom Arduino firmware:

github.com/rroels/ulanzi_tc001_har...

I did a quick write-up with working examples for each peripheral in the Ulanzi TC001.

Collapse
 
maxxh profile image
Max H

Hey Calum,

Thanks for the post. I'm trying to do the same, but I'm new to arduino. How did you manage to connect your TC001 to Arduino IDE? I don't see mine in the port list

Collapse
 
reinout_roels_4ca4a460b83 profile image
Reinout Roels

For some operating systems, you have to install a driver to communicate with the CH340. This is a usb-to-serial chip that is found on many microcontroller boards, including the Ulanzi TC001.

learn.sparkfun.com/tutorials/how-t...

Also make sure the Ulanzi is turned on. It won't be detected if it's off.

If that doesn't work, try a different USB cable (not all USB cables have data lines, some are only for power).

Collapse
 
calumk profile image
Calum Knott

Yup - I didnt run into this issue probably because i already had CH340 driver installed from a few years ago

Image of AssemblyAI

Automatic Speech Recognition with AssemblyAI

Experience near-human accuracy, low-latency performance, and advanced Speech AI capabilities with AssemblyAI's Speech-to-Text API. Sign up today and get $50 in API credit. No credit card required.

Try the API

👋 Kindness is contagious

Immerse yourself in a wealth of knowledge with this piece, supported by the inclusive DEV Community—every developer, no matter where they are in their journey, is invited to contribute to our collective wisdom.

A simple “thank you” goes a long way—express your gratitude below in the comments!

Gathering insights enriches our journey on DEV and fortifies our community ties. Did you find this article valuable? Taking a moment to thank the author can have a significant impact.

Okay