<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
	<channel>
		<title><![CDATA[Smart Home Automation Forum - CO16]]></title>
		<link>https://www.kincony.com/forum/</link>
		<description><![CDATA[Smart Home Automation Forum - https://www.kincony.com/forum]]></description>
		<pubDate>Wed, 04 Nov 2026 20:29:38 +0000</pubDate>
		<generator>MyBB</generator>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-13 ST7789 TFT LCD Flash MJPEG Player]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9935</link>
			<pubDate>Tue, 03 Nov 2026 11:04:25 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9935</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 * CO16 Flash MJPEG Player: 320x240, 10 fps target, 120 frames, silent loop.<br />
 * No SD card and no filesystem upload required. Remove SD card before use.<br />
 * Video is embedded in KinConyVideo.h and uploaded with this sketch.<br />
 * Libraries: Adafruit GFX Library, Adafruit BusIO, JPEGDEC by Larry Bank.<br />
 * Board: ESP32S3 Dev Module, Flash 16MB, OPI PSRAM, Arduino-ESP32 3.x.<br />
 * Choose an application partition &gt;= 3MB (e.g. Huge APP).<br />
 * Serial 115200: p=pause/resume, r=restart. Relays commanded OFF at startup.<br />
 */<br />
#include &lt;Arduino.h&gt;<br />
#include &lt;Wire.h&gt;<br />
#include &lt;JPEGDEC.h&gt;<br />
#include &lt;cstring&gt;<br />
#include "KinConyVideo.h"<br />
<br />
#include &lt;Adafruit_GFX.h&gt;<br />
#include &lt;driver/gpio.h&gt;<br />
#include &lt;driver/spi_master.h&gt;<br />
#include &lt;esp_heap_caps.h&gt;<br />
<br />
<br />
// CO16 ST7789 (GMT020-02-7P) pin mapping from kcs_co16.h.<br />
constexpr gpio_num_t kTftSclk = GPIO_NUM_11;<br />
constexpr gpio_num_t kTftMosi = GPIO_NUM_10;<br />
constexpr gpio_num_t kTftMiso = GPIO_NUM_12;<br />
constexpr gpio_num_t kTftCs = GPIO_NUM_4;<br />
constexpr gpio_num_t kTftDc = GPIO_NUM_0;<br />
constexpr gpio_num_t kTftReset = GPIO_NUM_5;<br />
constexpr gpio_num_t kTftBacklight = GPIO_NUM_40;<br />
<br />
constexpr int16_t kDisplayWidth = 320;<br />
constexpr int16_t kDisplayHeight = 240;<br />
constexpr int16_t kTransferChunkHeight = 10;<br />
constexpr uint32_t kDisplaySpiFrequency = 20 * 1000 * 1000;<br />
constexpr uint8_t kCo16Madctl = 0x68;  // MX | MV | BGR.<br />
<br />
constexpr uint16_t kBlack = 0x0000;<br />
constexpr uint16_t kBlue = 0x001F;<br />
constexpr uint16_t kRed = 0xF800;<br />
constexpr uint16_t kGreen = 0x07E0;<br />
constexpr uint16_t kCyan = 0x07FF;<br />
constexpr uint16_t kMagenta = 0xF81F;<br />
constexpr uint16_t kYellow = 0xFFE0;<br />
constexpr uint16_t kWhite = 0xFFFF;<br />
<br />
class Co16St7789 : public Adafruit_GFX {<br />
 public:<br />
  Co16St7789() : Adafruit_GFX(kDisplayWidth, kDisplayHeight) {}<br />
<br />
  bool begin() {<br />
    gpio_reset_pin(kTftCs);<br />
    gpio_set_direction(kTftCs, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftCs, 1);<br />
<br />
    gpio_reset_pin(kTftDc);<br />
    gpio_set_direction(kTftDc, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftDc, 1);<br />
<br />
    gpio_reset_pin(kTftBacklight);<br />
    gpio_set_direction(kTftBacklight, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftBacklight, 0);<br />
<br />
    // SPI3 is initialized once by setup(); only the display uses it.<br />
    esp_err_t err;<br />
<br />
    spi_device_interface_config_t deviceConfig = {};<br />
    deviceConfig.clock_speed_hz = kDisplaySpiFrequency;<br />
    deviceConfig.mode = 0;<br />
    deviceConfig.spics_io_num = -1;<br />
    deviceConfig.queue_size = 1;<br />
<br />
    err = spi_bus_add_device(SPI3_HOST, &amp;deviceConfig, &amp;spiDevice_);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI device setup failed: %s&#92;n",<br />
                    esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    drawBuffer_ = static_cast&lt;uint8_t*&gt;(heap_caps_malloc(<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t),<br />
        MALLOC_CAP_DMA));<br />
    if (drawBuffer_ == nullptr) {<br />
      Serial.println("ST7789 DMA buffer allocation failed");<br />
      return false;<br />
    }<br />
<br />
    // GPIO5 is shared with the optional LoRa module. Match the working KCS<br />
    // firmware and issue one reset pulse before initializing the controller.<br />
    gpio_reset_pin(kTftReset);<br />
    gpio_set_direction(kTftReset, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 0);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(10);<br />
<br />
    if (!initializeController()) {<br />
      return false;<br />
    }<br />
<br />
    fillScreen(kBlack);<br />
    delay(120);<br />
    if (!writeCommand(0x29)) {  // Display on.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    gpio_set_level(kTftBacklight, 1);<br />
    initialized_ = true;<br />
    return true;<br />
  }<br />
<br />
  void drawPixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void writePixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void fillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                uint16_t color) override {<br />
    if (x &lt; 0) {<br />
      width += x;<br />
      x = 0;<br />
    }<br />
    if (y &lt; 0) {<br />
      height += y;<br />
      y = 0;<br />
    }<br />
    if (x + width &gt; kDisplayWidth) {<br />
      width = kDisplayWidth - x;<br />
    }<br />
    if (y + height &gt; kDisplayHeight) {<br />
      height = kDisplayHeight - y;<br />
    }<br />
    if (drawBuffer_ == nullptr || width &lt;= 0 || height &lt;= 0) {<br />
      return;<br />
    }<br />
<br />
    const uint8_t high = color &gt;&gt; 8;<br />
    const uint8_t low = color;<br />
    for (int16_t row = y; row &lt; y + height;<br />
       &nbsp;&nbsp;row += kTransferChunkHeight) {<br />
      const int16_t chunkHeight =<br />
          min&lt;int16_t&gt;(kTransferChunkHeight, y + height - row);<br />
      const size_t pixelCount = width * chunkHeight;<br />
      for (size_t index = 0; index &lt; pixelCount; ++index) {<br />
        drawBuffer_[index * 2] = high;<br />
        drawBuffer_[index * 2 + 1] = low;<br />
      }<br />
<br />
      if (!setWindow(x, row, x + width, row + chunkHeight) ||<br />
          !writePixels(drawBuffer_, pixelCount * sizeof(uint16_t))) {<br />
        Serial.println("ST7789 pixel transfer failed");<br />
        return;<br />
      }<br />
    }<br />
  }<br />
<br />
  void writeFillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                   &nbsp;&nbsp;uint16_t color) override {<br />
    fillRect(x, y, width, height, color);<br />
  }<br />
<br />
  // Copy a fully rendered RGB565 frame into the DMA staging buffer.<br />
  bool present(const uint16_t* pixels) {<br />
    if (!initialized_ || pixels == nullptr) return false;<br />
    for (int y = 0; y &lt; kDisplayHeight; y += kTransferChunkHeight) {<br />
      const int rows = min&lt;int&gt;(kTransferChunkHeight, kDisplayHeight - y);<br />
      const int count = kDisplayWidth * rows;<br />
      for (int i = 0; i &lt; count; ++i) {<br />
        uint16_t c = pixels[y * kDisplayWidth + i];<br />
        drawBuffer_[2 * i] = c &gt;&gt; 8;<br />
        drawBuffer_[2 * i + 1] = c &amp; 0xFF;<br />
      }<br />
      if (!setWindow(0, y, kDisplayWidth, y + rows) ||<br />
          !writePixels(drawBuffer_, count * 2)) return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool initialized() const { return initialized_; }<br />
<br />
 private:<br />
  bool transmit(const void* data, size_t length, int dcLevel) {<br />
    if (data == nullptr || length == 0) {<br />
      return true;<br />
    }<br />
<br />
    gpio_set_level(kTftDc, dcLevel);<br />
    spi_transaction_t transaction = {};<br />
    transaction.length = length * 8;<br />
    transaction.tx_buffer = data;<br />
    const esp_err_t err = spi_device_polling_transmit(spiDevice_, &amp;transaction);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI transfer failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool writeCommand(uint8_t command) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool writeCommandData(uint8_t command, const uint8_t* data, size_t length) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0) &amp;&amp;<br />
                       &nbsp;&nbsp;transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool setWindow(int16_t x1, int16_t y1, int16_t x2, int16_t y2) {<br />
    const uint8_t columns[] = {<br />
        static_cast&lt;uint8_t&gt;(x1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x1),<br />
        static_cast&lt;uint8_t&gt;((x2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x2 - 1),<br />
    };<br />
    const uint8_t rows[] = {<br />
        static_cast&lt;uint8_t&gt;(y1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y1),<br />
        static_cast&lt;uint8_t&gt;((y2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y2 - 1),<br />
    };<br />
    return writeCommandData(0x2A, columns, sizeof(columns)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0x2B, rows, sizeof(rows));<br />
  }<br />
<br />
  bool writePixels(const uint8_t* data, size_t length) {<br />
    constexpr uint8_t kRamWrite = 0x2C;<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;kRamWrite, sizeof(kRamWrite), 0) &amp;&amp;<br />
                       &nbsp;&nbsp;transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool initializeController() {<br />
    const uint8_t madctl[] = {kCo16Madctl};<br />
    const uint8_t displayFunction[] = {0x0A, 0x82};<br />
    const uint8_t pixelFormat[] = {0x55};<br />
    const uint8_t porchControl[] = {0x0C, 0x0C, 0x00, 0x33, 0x33};<br />
    const uint8_t gateControl[] = {0x35};<br />
    const uint8_t vcom[] = {0x28};<br />
    const uint8_t lcmControl[] = {0x0C};<br />
    const uint8_t vdvVrhEnable[] = {0x01, 0xFF};<br />
    const uint8_t vrh[] = {0x10};<br />
    const uint8_t vdv[] = {0x20};<br />
    const uint8_t frameRate[] = {0x0F};<br />
    const uint8_t powerControl[] = {0xA4, 0xA1};<br />
    const uint8_t positiveGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32,<br />
        0x44, 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17,<br />
    };<br />
    const uint8_t negativeGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31,<br />
        0x54, 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E,<br />
    };<br />
<br />
    if (!writeCommand(0x11)) {  // Sleep out.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    return writeCommand(0x13) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0x36, madctl, sizeof(madctl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xB6, displayFunction, sizeof(displayFunction)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0x3A, pixelFormat, sizeof(pixelFormat)) &amp;&amp;<br />
         &nbsp;&nbsp;(delay(10), true) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xB2, porchControl, sizeof(porchControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xB7, gateControl, sizeof(gateControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xBB, vcom, sizeof(vcom)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC0, lcmControl, sizeof(lcmControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC2, vdvVrhEnable, sizeof(vdvVrhEnable)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC3, vrh, sizeof(vrh)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC4, vdv, sizeof(vdv)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC6, frameRate, sizeof(frameRate)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xD0, powerControl, sizeof(powerControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xE0, positiveGamma, sizeof(positiveGamma)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xE1, negativeGamma, sizeof(negativeGamma)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommand(0x21);  // Display inversion on.<br />
  }<br />
<br />
  spi_device_handle_t spiDevice_ = nullptr;<br />
  uint8_t* drawBuffer_ = nullptr;<br />
  bool initialized_ = false;<br />
};<br />
<br />
constexpr uint32_t VIDEO_FPS = 10;<br />
constexpr uint32_t FRAME_INTERVAL_MS = 1000 / VIDEO_FPS;<br />
static_assert(VIDEO_FPS &gt; 0 &amp;&amp; VIDEO_FPS &lt;= 30, "Choose FPS between 1 and 30");<br />
Co16St7789 display;<br />
JPEGDEC jpeg;<br />
uint16_t* frameBuffer = nullptr;<br />
bool paused = false;<br />
uint32_t frameCount = 0, reportStart = 0, reportFrames = 0;<br />
<br />
void haltPlayer(const char* title, const char* hint) {<br />
  Serial.printf("%s: %s&#92;n", title, hint);<br />
  if (display.initialized()) {<br />
    display.fillScreen(kBlack);<br />
    display.setTextWrap(true);<br />
    display.setTextSize(2);<br />
    display.setTextColor(kRed);<br />
    display.setCursor(8, 30);<br />
    display.print(title);<br />
    display.setTextSize(1);<br />
    display.setTextColor(kWhite);<br />
    display.setCursor(8, 85);<br />
    display.print(hint);<br />
  }<br />
  while (true) delay(1000);<br />
}<br />
<br />
void* allocateVideoMemory(size_t size) {<br />
  void* p = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);<br />
  if (!p) p = heap_caps_malloc(size, MALLOC_CAP_8BIT);<br />
  return p;<br />
}<br />
<br />
int decodedPixels(JPEGDRAW* block) {<br />
  if (!frameBuffer || !block || block-&gt;iBpp != 16 || block-&gt;x &lt; 0 || block-&gt;y &lt; 0)<br />
    return 0;<br />
  if (block-&gt;x &gt;= 320 || block-&gt;y &gt;= 240) return 0;<br />
  int width = min&lt;int&gt;(block-&gt;iWidthUsed, 320 - block-&gt;x);<br />
  int height = min&lt;int&gt;(block-&gt;iHeight, 240 - block-&gt;y);<br />
  if (width &lt;= 0 || height &lt;= 0) return 0;<br />
  for (int row = 0; row &lt; height; ++row) {<br />
    memcpy(frameBuffer + (block-&gt;y + row) * 320 + block-&gt;x,<br />
         &nbsp;&nbsp;block-&gt;pPixels + row * block-&gt;iWidth, width * sizeof(uint16_t));<br />
  }<br />
  return 1;<br />
}<br />
<br />
void setup() {<br />
  Serial.begin(115200);<br />
  Wire.begin(8, 18);<br />
  Wire.setClock(100000);<br />
  Wire.setTimeOut(20);<br />
  Wire.beginTransmission(0x22);<br />
  Wire.write(uint8_t(0xFF));<br />
  Wire.write(uint8_t(0xFF));<br />
  if (Wire.endTransmission() != 0) Serial.println("Warning: relay OFF command not acknowledged.");<br />
<br />
  // Keep unused SPI peripherals deselected. Remove the SD card for this demo.<br />
  const int csPins[] = {4, 9, 13, 14, 42};<br />
  for (int pin : csPins) { digitalWrite(pin, HIGH); pinMode(pin, OUTPUT); }<br />
  pinMode(40, OUTPUT);<br />
  digitalWrite(40, LOW);<br />
  spi_bus_config_t bus = {};<br />
  bus.sclk_io_num = 11;<br />
  bus.mosi_io_num = 10;<br />
  bus.miso_io_num = 12;<br />
  bus.quadwp_io_num = -1;<br />
  bus.quadhd_io_num = -1;<br />
  bus.max_transfer_sz = 320 * kTransferChunkHeight * 2;<br />
  esp_err_t err = spi_bus_initialize(SPI3_HOST, &amp;bus, SPI_DMA_CH_AUTO);<br />
  if (err != ESP_OK) haltPlayer("SPI init failed", esp_err_to_name(err));<br />
<br />
  if (!display.begin()) haltPlayer("LCD init failed", "Check serial output and CO16 board selection.");<br />
  display.setTextWrap(false);<br />
  display.setTextSize(2);<br />
  display.setTextColor(kCyan);<br />
  display.setCursor(30, 90);<br />
  display.print("KinCony MJPEG");<br />
  display.setTextSize(1);<br />
  display.setCursor(30, 122);<br />
  display.print("Loading slogan video ...");<br />
<br />
  frameBuffer = static_cast&lt;uint16_t*&gt;(allocateVideoMemory(320 * 240 * 2));<br />
  if (!frameBuffer)<br />
    haltPlayer("Not enough RAM", "Enable OPI PSRAM for ESP32-S3 N16R8 and upload again.");<br />
  Serial.printf("Embedded video: %u bytes, %lu frames&#92;n",<br />
                unsigned(sizeof(kVideoData)), static_cast&lt;unsigned long&gt;(kVideoFrameCount));<br />
  reportStart = millis();<br />
  Serial.println("Player ready. 10 fps target; p=pause/resume, r=restart.");<br />
}<br />
<br />
void loop() {<br />
  while (Serial.available()) {<br />
    char c = Serial.read();<br />
    if (c == 'p') paused = !paused;<br />
    if (c == 'r') {<br />
      frameCount = 0;<br />
      paused = false;<br />
    }<br />
  }<br />
  if (paused) {<br />
    reportStart = millis(); reportFrames = 0;<br />
    delay(10); return;<br />
  }<br />
  uint32_t started = millis();<br />
  if (frameCount &gt;= kVideoFrameCount) frameCount = 0;<br />
  uint32_t offset = kFrameOffsets[frameCount];<br />
  uint32_t end = kFrameOffsets[frameCount + 1];<br />
  if (end &lt;= offset || end &gt; sizeof(kVideoData))<br />
    haltPlayer("Bad frame index", "Re-upload the complete firmware package.");<br />
  if (!jpeg.openFLASH(kVideoData + offset, int(end - offset), decodedPixels))<br />
    haltPlayer("JPEG open failed", "Check JPEGDEC library and embedded video data.");<br />
  if (jpeg.getWidth() != 320 || jpeg.getHeight() != 240) {<br />
    jpeg.close();<br />
    haltPlayer("Wrong frame size", "Convert the video to exactly 320x240 pixels.");<br />
  }<br />
  jpeg.setPixelType(RGB565_LITTLE_ENDIAN);<br />
  memset(frameBuffer, 0, 320 * 240 * 2);<br />
  int decoded = jpeg.decode(0, 0, 0);<br />
  int jpegError = jpeg.getLastError();<br />
  jpeg.close();<br />
  if (!decoded) {<br />
    Serial.printf("JPEG decode error: %d&#92;n", jpegError);<br />
    haltPlayer("JPEG decode failed", "Check video integrity and JPEGDEC library.");<br />
  }<br />
  // Present the decoded full frame to the display.<br />
  if (!display.present(frameBuffer)) haltPlayer("LCD write failed", "Check power and SPI connection.");<br />
  ++frameCount;<br />
  ++reportFrames;<br />
  uint32_t elapsed = millis() - started;<br />
  if (elapsed &lt; FRAME_INTERVAL_MS) delay(FRAME_INTERVAL_MS - elapsed);<br />
  else delay(1); // Yield; do not skip frames or run catch-up bursts.<br />
  uint32_t now = millis();<br />
  if (now - reportStart &gt;= 5000) {<br />
    Serial.printf("Playback %.1f fps, read/decode/display %lu ms, frame %lu&#92;n",<br />
                  reportFrames * 1000.0f / (now - reportStart),<br />
                  static_cast&lt;unsigned long&gt;(elapsed), static_cast&lt;unsigned long&gt;(frameCount));<br />
    reportStart = now; reportFrames = 0;<br />
  }<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10744" target="_blank" title="">13-CO16_Flash_MJPEG_Player.zip</a> (Size: 8.21 MB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10745" target="_blank" title="">13-CO16_Flash_MJPEG_Player.ino.merged.zip</a> (Size: 1.05 MB / Downloads: 7)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 * CO16 Flash MJPEG Player: 320x240, 10 fps target, 120 frames, silent loop.<br />
 * No SD card and no filesystem upload required. Remove SD card before use.<br />
 * Video is embedded in KinConyVideo.h and uploaded with this sketch.<br />
 * Libraries: Adafruit GFX Library, Adafruit BusIO, JPEGDEC by Larry Bank.<br />
 * Board: ESP32S3 Dev Module, Flash 16MB, OPI PSRAM, Arduino-ESP32 3.x.<br />
 * Choose an application partition &gt;= 3MB (e.g. Huge APP).<br />
 * Serial 115200: p=pause/resume, r=restart. Relays commanded OFF at startup.<br />
 */<br />
#include &lt;Arduino.h&gt;<br />
#include &lt;Wire.h&gt;<br />
#include &lt;JPEGDEC.h&gt;<br />
#include &lt;cstring&gt;<br />
#include "KinConyVideo.h"<br />
<br />
#include &lt;Adafruit_GFX.h&gt;<br />
#include &lt;driver/gpio.h&gt;<br />
#include &lt;driver/spi_master.h&gt;<br />
#include &lt;esp_heap_caps.h&gt;<br />
<br />
<br />
// CO16 ST7789 (GMT020-02-7P) pin mapping from kcs_co16.h.<br />
constexpr gpio_num_t kTftSclk = GPIO_NUM_11;<br />
constexpr gpio_num_t kTftMosi = GPIO_NUM_10;<br />
constexpr gpio_num_t kTftMiso = GPIO_NUM_12;<br />
constexpr gpio_num_t kTftCs = GPIO_NUM_4;<br />
constexpr gpio_num_t kTftDc = GPIO_NUM_0;<br />
constexpr gpio_num_t kTftReset = GPIO_NUM_5;<br />
constexpr gpio_num_t kTftBacklight = GPIO_NUM_40;<br />
<br />
constexpr int16_t kDisplayWidth = 320;<br />
constexpr int16_t kDisplayHeight = 240;<br />
constexpr int16_t kTransferChunkHeight = 10;<br />
constexpr uint32_t kDisplaySpiFrequency = 20 * 1000 * 1000;<br />
constexpr uint8_t kCo16Madctl = 0x68;  // MX | MV | BGR.<br />
<br />
constexpr uint16_t kBlack = 0x0000;<br />
constexpr uint16_t kBlue = 0x001F;<br />
constexpr uint16_t kRed = 0xF800;<br />
constexpr uint16_t kGreen = 0x07E0;<br />
constexpr uint16_t kCyan = 0x07FF;<br />
constexpr uint16_t kMagenta = 0xF81F;<br />
constexpr uint16_t kYellow = 0xFFE0;<br />
constexpr uint16_t kWhite = 0xFFFF;<br />
<br />
class Co16St7789 : public Adafruit_GFX {<br />
 public:<br />
  Co16St7789() : Adafruit_GFX(kDisplayWidth, kDisplayHeight) {}<br />
<br />
  bool begin() {<br />
    gpio_reset_pin(kTftCs);<br />
    gpio_set_direction(kTftCs, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftCs, 1);<br />
<br />
    gpio_reset_pin(kTftDc);<br />
    gpio_set_direction(kTftDc, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftDc, 1);<br />
<br />
    gpio_reset_pin(kTftBacklight);<br />
    gpio_set_direction(kTftBacklight, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftBacklight, 0);<br />
<br />
    // SPI3 is initialized once by setup(); only the display uses it.<br />
    esp_err_t err;<br />
<br />
    spi_device_interface_config_t deviceConfig = {};<br />
    deviceConfig.clock_speed_hz = kDisplaySpiFrequency;<br />
    deviceConfig.mode = 0;<br />
    deviceConfig.spics_io_num = -1;<br />
    deviceConfig.queue_size = 1;<br />
<br />
    err = spi_bus_add_device(SPI3_HOST, &amp;deviceConfig, &amp;spiDevice_);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI device setup failed: %s&#92;n",<br />
                    esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    drawBuffer_ = static_cast&lt;uint8_t*&gt;(heap_caps_malloc(<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t),<br />
        MALLOC_CAP_DMA));<br />
    if (drawBuffer_ == nullptr) {<br />
      Serial.println("ST7789 DMA buffer allocation failed");<br />
      return false;<br />
    }<br />
<br />
    // GPIO5 is shared with the optional LoRa module. Match the working KCS<br />
    // firmware and issue one reset pulse before initializing the controller.<br />
    gpio_reset_pin(kTftReset);<br />
    gpio_set_direction(kTftReset, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 0);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(10);<br />
<br />
    if (!initializeController()) {<br />
      return false;<br />
    }<br />
<br />
    fillScreen(kBlack);<br />
    delay(120);<br />
    if (!writeCommand(0x29)) {  // Display on.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    gpio_set_level(kTftBacklight, 1);<br />
    initialized_ = true;<br />
    return true;<br />
  }<br />
<br />
  void drawPixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void writePixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void fillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                uint16_t color) override {<br />
    if (x &lt; 0) {<br />
      width += x;<br />
      x = 0;<br />
    }<br />
    if (y &lt; 0) {<br />
      height += y;<br />
      y = 0;<br />
    }<br />
    if (x + width &gt; kDisplayWidth) {<br />
      width = kDisplayWidth - x;<br />
    }<br />
    if (y + height &gt; kDisplayHeight) {<br />
      height = kDisplayHeight - y;<br />
    }<br />
    if (drawBuffer_ == nullptr || width &lt;= 0 || height &lt;= 0) {<br />
      return;<br />
    }<br />
<br />
    const uint8_t high = color &gt;&gt; 8;<br />
    const uint8_t low = color;<br />
    for (int16_t row = y; row &lt; y + height;<br />
       &nbsp;&nbsp;row += kTransferChunkHeight) {<br />
      const int16_t chunkHeight =<br />
          min&lt;int16_t&gt;(kTransferChunkHeight, y + height - row);<br />
      const size_t pixelCount = width * chunkHeight;<br />
      for (size_t index = 0; index &lt; pixelCount; ++index) {<br />
        drawBuffer_[index * 2] = high;<br />
        drawBuffer_[index * 2 + 1] = low;<br />
      }<br />
<br />
      if (!setWindow(x, row, x + width, row + chunkHeight) ||<br />
          !writePixels(drawBuffer_, pixelCount * sizeof(uint16_t))) {<br />
        Serial.println("ST7789 pixel transfer failed");<br />
        return;<br />
      }<br />
    }<br />
  }<br />
<br />
  void writeFillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                   &nbsp;&nbsp;uint16_t color) override {<br />
    fillRect(x, y, width, height, color);<br />
  }<br />
<br />
  // Copy a fully rendered RGB565 frame into the DMA staging buffer.<br />
  bool present(const uint16_t* pixels) {<br />
    if (!initialized_ || pixels == nullptr) return false;<br />
    for (int y = 0; y &lt; kDisplayHeight; y += kTransferChunkHeight) {<br />
      const int rows = min&lt;int&gt;(kTransferChunkHeight, kDisplayHeight - y);<br />
      const int count = kDisplayWidth * rows;<br />
      for (int i = 0; i &lt; count; ++i) {<br />
        uint16_t c = pixels[y * kDisplayWidth + i];<br />
        drawBuffer_[2 * i] = c &gt;&gt; 8;<br />
        drawBuffer_[2 * i + 1] = c &amp; 0xFF;<br />
      }<br />
      if (!setWindow(0, y, kDisplayWidth, y + rows) ||<br />
          !writePixels(drawBuffer_, count * 2)) return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool initialized() const { return initialized_; }<br />
<br />
 private:<br />
  bool transmit(const void* data, size_t length, int dcLevel) {<br />
    if (data == nullptr || length == 0) {<br />
      return true;<br />
    }<br />
<br />
    gpio_set_level(kTftDc, dcLevel);<br />
    spi_transaction_t transaction = {};<br />
    transaction.length = length * 8;<br />
    transaction.tx_buffer = data;<br />
    const esp_err_t err = spi_device_polling_transmit(spiDevice_, &amp;transaction);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI transfer failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool writeCommand(uint8_t command) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool writeCommandData(uint8_t command, const uint8_t* data, size_t length) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0) &amp;&amp;<br />
                       &nbsp;&nbsp;transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool setWindow(int16_t x1, int16_t y1, int16_t x2, int16_t y2) {<br />
    const uint8_t columns[] = {<br />
        static_cast&lt;uint8_t&gt;(x1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x1),<br />
        static_cast&lt;uint8_t&gt;((x2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x2 - 1),<br />
    };<br />
    const uint8_t rows[] = {<br />
        static_cast&lt;uint8_t&gt;(y1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y1),<br />
        static_cast&lt;uint8_t&gt;((y2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y2 - 1),<br />
    };<br />
    return writeCommandData(0x2A, columns, sizeof(columns)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0x2B, rows, sizeof(rows));<br />
  }<br />
<br />
  bool writePixels(const uint8_t* data, size_t length) {<br />
    constexpr uint8_t kRamWrite = 0x2C;<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;kRamWrite, sizeof(kRamWrite), 0) &amp;&amp;<br />
                       &nbsp;&nbsp;transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool initializeController() {<br />
    const uint8_t madctl[] = {kCo16Madctl};<br />
    const uint8_t displayFunction[] = {0x0A, 0x82};<br />
    const uint8_t pixelFormat[] = {0x55};<br />
    const uint8_t porchControl[] = {0x0C, 0x0C, 0x00, 0x33, 0x33};<br />
    const uint8_t gateControl[] = {0x35};<br />
    const uint8_t vcom[] = {0x28};<br />
    const uint8_t lcmControl[] = {0x0C};<br />
    const uint8_t vdvVrhEnable[] = {0x01, 0xFF};<br />
    const uint8_t vrh[] = {0x10};<br />
    const uint8_t vdv[] = {0x20};<br />
    const uint8_t frameRate[] = {0x0F};<br />
    const uint8_t powerControl[] = {0xA4, 0xA1};<br />
    const uint8_t positiveGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32,<br />
        0x44, 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17,<br />
    };<br />
    const uint8_t negativeGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31,<br />
        0x54, 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E,<br />
    };<br />
<br />
    if (!writeCommand(0x11)) {  // Sleep out.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    return writeCommand(0x13) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0x36, madctl, sizeof(madctl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xB6, displayFunction, sizeof(displayFunction)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0x3A, pixelFormat, sizeof(pixelFormat)) &amp;&amp;<br />
         &nbsp;&nbsp;(delay(10), true) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xB2, porchControl, sizeof(porchControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xB7, gateControl, sizeof(gateControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xBB, vcom, sizeof(vcom)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC0, lcmControl, sizeof(lcmControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC2, vdvVrhEnable, sizeof(vdvVrhEnable)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC3, vrh, sizeof(vrh)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC4, vdv, sizeof(vdv)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC6, frameRate, sizeof(frameRate)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xD0, powerControl, sizeof(powerControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xE0, positiveGamma, sizeof(positiveGamma)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xE1, negativeGamma, sizeof(negativeGamma)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommand(0x21);  // Display inversion on.<br />
  }<br />
<br />
  spi_device_handle_t spiDevice_ = nullptr;<br />
  uint8_t* drawBuffer_ = nullptr;<br />
  bool initialized_ = false;<br />
};<br />
<br />
constexpr uint32_t VIDEO_FPS = 10;<br />
constexpr uint32_t FRAME_INTERVAL_MS = 1000 / VIDEO_FPS;<br />
static_assert(VIDEO_FPS &gt; 0 &amp;&amp; VIDEO_FPS &lt;= 30, "Choose FPS between 1 and 30");<br />
Co16St7789 display;<br />
JPEGDEC jpeg;<br />
uint16_t* frameBuffer = nullptr;<br />
bool paused = false;<br />
uint32_t frameCount = 0, reportStart = 0, reportFrames = 0;<br />
<br />
void haltPlayer(const char* title, const char* hint) {<br />
  Serial.printf("%s: %s&#92;n", title, hint);<br />
  if (display.initialized()) {<br />
    display.fillScreen(kBlack);<br />
    display.setTextWrap(true);<br />
    display.setTextSize(2);<br />
    display.setTextColor(kRed);<br />
    display.setCursor(8, 30);<br />
    display.print(title);<br />
    display.setTextSize(1);<br />
    display.setTextColor(kWhite);<br />
    display.setCursor(8, 85);<br />
    display.print(hint);<br />
  }<br />
  while (true) delay(1000);<br />
}<br />
<br />
void* allocateVideoMemory(size_t size) {<br />
  void* p = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);<br />
  if (!p) p = heap_caps_malloc(size, MALLOC_CAP_8BIT);<br />
  return p;<br />
}<br />
<br />
int decodedPixels(JPEGDRAW* block) {<br />
  if (!frameBuffer || !block || block-&gt;iBpp != 16 || block-&gt;x &lt; 0 || block-&gt;y &lt; 0)<br />
    return 0;<br />
  if (block-&gt;x &gt;= 320 || block-&gt;y &gt;= 240) return 0;<br />
  int width = min&lt;int&gt;(block-&gt;iWidthUsed, 320 - block-&gt;x);<br />
  int height = min&lt;int&gt;(block-&gt;iHeight, 240 - block-&gt;y);<br />
  if (width &lt;= 0 || height &lt;= 0) return 0;<br />
  for (int row = 0; row &lt; height; ++row) {<br />
    memcpy(frameBuffer + (block-&gt;y + row) * 320 + block-&gt;x,<br />
         &nbsp;&nbsp;block-&gt;pPixels + row * block-&gt;iWidth, width * sizeof(uint16_t));<br />
  }<br />
  return 1;<br />
}<br />
<br />
void setup() {<br />
  Serial.begin(115200);<br />
  Wire.begin(8, 18);<br />
  Wire.setClock(100000);<br />
  Wire.setTimeOut(20);<br />
  Wire.beginTransmission(0x22);<br />
  Wire.write(uint8_t(0xFF));<br />
  Wire.write(uint8_t(0xFF));<br />
  if (Wire.endTransmission() != 0) Serial.println("Warning: relay OFF command not acknowledged.");<br />
<br />
  // Keep unused SPI peripherals deselected. Remove the SD card for this demo.<br />
  const int csPins[] = {4, 9, 13, 14, 42};<br />
  for (int pin : csPins) { digitalWrite(pin, HIGH); pinMode(pin, OUTPUT); }<br />
  pinMode(40, OUTPUT);<br />
  digitalWrite(40, LOW);<br />
  spi_bus_config_t bus = {};<br />
  bus.sclk_io_num = 11;<br />
  bus.mosi_io_num = 10;<br />
  bus.miso_io_num = 12;<br />
  bus.quadwp_io_num = -1;<br />
  bus.quadhd_io_num = -1;<br />
  bus.max_transfer_sz = 320 * kTransferChunkHeight * 2;<br />
  esp_err_t err = spi_bus_initialize(SPI3_HOST, &amp;bus, SPI_DMA_CH_AUTO);<br />
  if (err != ESP_OK) haltPlayer("SPI init failed", esp_err_to_name(err));<br />
<br />
  if (!display.begin()) haltPlayer("LCD init failed", "Check serial output and CO16 board selection.");<br />
  display.setTextWrap(false);<br />
  display.setTextSize(2);<br />
  display.setTextColor(kCyan);<br />
  display.setCursor(30, 90);<br />
  display.print("KinCony MJPEG");<br />
  display.setTextSize(1);<br />
  display.setCursor(30, 122);<br />
  display.print("Loading slogan video ...");<br />
<br />
  frameBuffer = static_cast&lt;uint16_t*&gt;(allocateVideoMemory(320 * 240 * 2));<br />
  if (!frameBuffer)<br />
    haltPlayer("Not enough RAM", "Enable OPI PSRAM for ESP32-S3 N16R8 and upload again.");<br />
  Serial.printf("Embedded video: %u bytes, %lu frames&#92;n",<br />
                unsigned(sizeof(kVideoData)), static_cast&lt;unsigned long&gt;(kVideoFrameCount));<br />
  reportStart = millis();<br />
  Serial.println("Player ready. 10 fps target; p=pause/resume, r=restart.");<br />
}<br />
<br />
void loop() {<br />
  while (Serial.available()) {<br />
    char c = Serial.read();<br />
    if (c == 'p') paused = !paused;<br />
    if (c == 'r') {<br />
      frameCount = 0;<br />
      paused = false;<br />
    }<br />
  }<br />
  if (paused) {<br />
    reportStart = millis(); reportFrames = 0;<br />
    delay(10); return;<br />
  }<br />
  uint32_t started = millis();<br />
  if (frameCount &gt;= kVideoFrameCount) frameCount = 0;<br />
  uint32_t offset = kFrameOffsets[frameCount];<br />
  uint32_t end = kFrameOffsets[frameCount + 1];<br />
  if (end &lt;= offset || end &gt; sizeof(kVideoData))<br />
    haltPlayer("Bad frame index", "Re-upload the complete firmware package.");<br />
  if (!jpeg.openFLASH(kVideoData + offset, int(end - offset), decodedPixels))<br />
    haltPlayer("JPEG open failed", "Check JPEGDEC library and embedded video data.");<br />
  if (jpeg.getWidth() != 320 || jpeg.getHeight() != 240) {<br />
    jpeg.close();<br />
    haltPlayer("Wrong frame size", "Convert the video to exactly 320x240 pixels.");<br />
  }<br />
  jpeg.setPixelType(RGB565_LITTLE_ENDIAN);<br />
  memset(frameBuffer, 0, 320 * 240 * 2);<br />
  int decoded = jpeg.decode(0, 0, 0);<br />
  int jpegError = jpeg.getLastError();<br />
  jpeg.close();<br />
  if (!decoded) {<br />
    Serial.printf("JPEG decode error: %d&#92;n", jpegError);<br />
    haltPlayer("JPEG decode failed", "Check video integrity and JPEGDEC library.");<br />
  }<br />
  // Present the decoded full frame to the display.<br />
  if (!display.present(frameBuffer)) haltPlayer("LCD write failed", "Check power and SPI connection.");<br />
  ++frameCount;<br />
  ++reportFrames;<br />
  uint32_t elapsed = millis() - started;<br />
  if (elapsed &lt; FRAME_INTERVAL_MS) delay(FRAME_INTERVAL_MS - elapsed);<br />
  else delay(1); // Yield; do not skip frames or run catch-up bursts.<br />
  uint32_t now = millis();<br />
  if (now - reportStart &gt;= 5000) {<br />
    Serial.printf("Playback %.1f fps, read/decode/display %lu ms, frame %lu&#92;n",<br />
                  reportFrames * 1000.0f / (now - reportStart),<br />
                  static_cast&lt;unsigned long&gt;(elapsed), static_cast&lt;unsigned long&gt;(frameCount));<br />
    reportStart = now; reportFrames = 0;<br />
  }<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10744" target="_blank" title="">13-CO16_Flash_MJPEG_Player.zip</a> (Size: 8.21 MB / Downloads: 4)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10745" target="_blank" title="">13-CO16_Flash_MJPEG_Player.ino.merged.zip</a> (Size: 1.05 MB / Downloads: 7)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-12 ST7789 TFT LCD image display]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9934</link>
			<pubDate>Tue, 03 Nov 2026 09:34:38 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9934</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 * CO16 Image Display - show the supplied photograph on the onboard LCD.<br />
 * Target: ESP32-S3 CO16, ST7789 320x240 landscape.<br />
 * Uses the display initialization from the validated CO16 Cyber Show sketch.<br />
 * Install Adafruit GFX Library and its Adafruit BusIO dependency.<br />
 * Select ESP32S3 Dev Module; retain your working CO16 board settings.<br />
 * Keep image_data.h in the same folder as this sketch.<br />
 * Image is stored in flash as RGB565; no SD card, JPEG decoder or Wi-Fi.<br />
 * Full image is resized to 320x240 with no margins; aspect ratio changes.<br />
 * PCF8575 relay outputs at 0x22 are commanded OFF at startup.<br />
 */<br />
#include &lt;Arduino.h&gt;<br />
#include &lt;Wire.h&gt;<br />
#include "image_data.h"<br />
<br />
#include &lt;Adafruit_GFX.h&gt;<br />
#include &lt;driver/gpio.h&gt;<br />
#include &lt;driver/spi_master.h&gt;<br />
#include &lt;esp_heap_caps.h&gt;<br />
<br />
<br />
// CO16 ST7789 (GMT020-02-7P) pin mapping from kcs_co16.h.<br />
constexpr gpio_num_t kTftSclk = GPIO_NUM_11;<br />
constexpr gpio_num_t kTftMosi = GPIO_NUM_10;<br />
constexpr gpio_num_t kTftMiso = GPIO_NUM_12;<br />
constexpr gpio_num_t kTftCs = GPIO_NUM_4;<br />
constexpr gpio_num_t kTftDc = GPIO_NUM_0;<br />
constexpr gpio_num_t kTftReset = GPIO_NUM_5;<br />
constexpr gpio_num_t kTftBacklight = GPIO_NUM_40;<br />
<br />
constexpr int16_t kDisplayWidth = 320;<br />
constexpr int16_t kDisplayHeight = 240;<br />
constexpr int16_t kTransferChunkHeight = 10;<br />
constexpr uint32_t kDisplaySpiFrequency = 20 * 1000 * 1000;<br />
constexpr uint8_t kCo16Madctl = 0x68;  // MX | MV | BGR.<br />
<br />
constexpr uint16_t kBlack = 0x0000;<br />
constexpr uint16_t kBlue = 0x001F;<br />
constexpr uint16_t kRed = 0xF800;<br />
constexpr uint16_t kGreen = 0x07E0;<br />
constexpr uint16_t kCyan = 0x07FF;<br />
constexpr uint16_t kMagenta = 0xF81F;<br />
constexpr uint16_t kYellow = 0xFFE0;<br />
constexpr uint16_t kWhite = 0xFFFF;<br />
<br />
class Co16St7789 : public Adafruit_GFX {<br />
 public:<br />
  Co16St7789() : Adafruit_GFX(kDisplayWidth, kDisplayHeight) {}<br />
<br />
  bool begin() {<br />
    gpio_reset_pin(kTftCs);<br />
    gpio_set_direction(kTftCs, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftCs, 1);<br />
<br />
    gpio_reset_pin(kTftDc);<br />
    gpio_set_direction(kTftDc, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftDc, 1);<br />
<br />
    gpio_reset_pin(kTftBacklight);<br />
    gpio_set_direction(kTftBacklight, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftBacklight, 0);<br />
<br />
    spi_bus_config_t busConfig = {};<br />
    busConfig.sclk_io_num = kTftSclk;<br />
    busConfig.mosi_io_num = kTftMosi;<br />
    busConfig.miso_io_num = kTftMiso;<br />
    busConfig.quadwp_io_num = -1;<br />
    busConfig.quadhd_io_num = -1;<br />
    busConfig.max_transfer_sz =<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t);<br />
<br />
    esp_err_t err =<br />
        spi_bus_initialize(SPI3_HOST, &amp;busConfig, SPI_DMA_CH_AUTO);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("SPI3 initialization failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    spi_device_interface_config_t deviceConfig = {};<br />
    deviceConfig.clock_speed_hz = kDisplaySpiFrequency;<br />
    deviceConfig.mode = 0;<br />
    deviceConfig.spics_io_num = -1;<br />
    deviceConfig.queue_size = 1;<br />
<br />
    err = spi_bus_add_device(SPI3_HOST, &amp;deviceConfig, &amp;spiDevice_);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI device setup failed: %s&#92;n",<br />
                    esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    drawBuffer_ = static_cast&lt;uint8_t*&gt;(heap_caps_malloc(<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t),<br />
        MALLOC_CAP_DMA));<br />
    if (drawBuffer_ == nullptr) {<br />
      Serial.println("ST7789 DMA buffer allocation failed");<br />
      return false;<br />
    }<br />
<br />
    // GPIO5 is shared with the optional LoRa module. Match the working KCS<br />
    // firmware and issue one reset pulse before initializing the controller.<br />
    gpio_reset_pin(kTftReset);<br />
    gpio_set_direction(kTftReset, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 0);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(10);<br />
<br />
    if (!initializeController()) {<br />
      return false;<br />
    }<br />
<br />
    fillScreen(kBlack);<br />
    delay(120);<br />
    if (!writeCommand(0x29)) {  // Display on.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    gpio_set_level(kTftBacklight, 1);<br />
    initialized_ = true;<br />
    return true;<br />
  }<br />
<br />
  void drawPixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void writePixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void fillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                uint16_t color) override {<br />
    if (x &lt; 0) {<br />
      width += x;<br />
      x = 0;<br />
    }<br />
    if (y &lt; 0) {<br />
      height += y;<br />
      y = 0;<br />
    }<br />
    if (x + width &gt; kDisplayWidth) {<br />
      width = kDisplayWidth - x;<br />
    }<br />
    if (y + height &gt; kDisplayHeight) {<br />
      height = kDisplayHeight - y;<br />
    }<br />
    if (drawBuffer_ == nullptr || width &lt;= 0 || height &lt;= 0) {<br />
      return;<br />
    }<br />
<br />
    const uint8_t high = color &gt;&gt; 8;<br />
    const uint8_t low = color;<br />
    for (int16_t row = y; row &lt; y + height;<br />
         row += kTransferChunkHeight) {<br />
      const int16_t chunkHeight =<br />
          min&lt;int16_t&gt;(kTransferChunkHeight, y + height - row);<br />
      const size_t pixelCount = width * chunkHeight;<br />
      for (size_t index = 0; index &lt; pixelCount; ++index) {<br />
        drawBuffer_[index * 2] = high;<br />
        drawBuffer_[index * 2 + 1] = low;<br />
      }<br />
<br />
      if (!setWindow(x, row, x + width, row + chunkHeight) ||<br />
          !writePixels(drawBuffer_, pixelCount * sizeof(uint16_t))) {<br />
        Serial.println("ST7789 pixel transfer failed");<br />
        return;<br />
      }<br />
    }<br />
  }<br />
<br />
  void writeFillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                     uint16_t color) override {<br />
    fillRect(x, y, width, height, color);<br />
  }<br />
<br />
  // Copy a fully rendered RGB565 frame into the DMA staging buffer.<br />
  bool present(const uint16_t* pixels) {<br />
    if (!initialized_ || pixels == nullptr) return false;<br />
    for (int y = 0; y &lt; kDisplayHeight; y += kTransferChunkHeight) {<br />
      const int rows = min&lt;int&gt;(kTransferChunkHeight, kDisplayHeight - y);<br />
      const int count = kDisplayWidth * rows;<br />
      for (int i = 0; i &lt; count; ++i) {<br />
        uint16_t c = pixels[y * kDisplayWidth + i];<br />
        drawBuffer_[2 * i] = c &gt;&gt; 8;<br />
        drawBuffer_[2 * i + 1] = c &amp; 0xFF;<br />
      }<br />
      if (!setWindow(0, y, kDisplayWidth, y + rows) ||<br />
          !writePixels(drawBuffer_, count * 2)) return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool initialized() const { return initialized_; }<br />
<br />
 private:<br />
  bool transmit(const void* data, size_t length, int dcLevel) {<br />
    if (data == nullptr || length == 0) {<br />
      return true;<br />
    }<br />
<br />
    gpio_set_level(kTftDc, dcLevel);<br />
    spi_transaction_t transaction = {};<br />
    transaction.length = length * 8;<br />
    transaction.tx_buffer = data;<br />
    const esp_err_t err = spi_device_polling_transmit(spiDevice_, &amp;transaction);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI transfer failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool writeCommand(uint8_t command) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool writeCommandData(uint8_t command, const uint8_t* data, size_t length) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0) &amp;&amp;<br />
                         transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool setWindow(int16_t x1, int16_t y1, int16_t x2, int16_t y2) {<br />
    const uint8_t columns[] = {<br />
        static_cast&lt;uint8_t&gt;(x1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x1),<br />
        static_cast&lt;uint8_t&gt;((x2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x2 - 1),<br />
    };<br />
    const uint8_t rows[] = {<br />
        static_cast&lt;uint8_t&gt;(y1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y1),<br />
        static_cast&lt;uint8_t&gt;((y2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y2 - 1),<br />
    };<br />
    return writeCommandData(0x2A, columns, sizeof(columns)) &amp;&amp;<br />
           writeCommandData(0x2B, rows, sizeof(rows));<br />
  }<br />
<br />
  bool writePixels(const uint8_t* data, size_t length) {<br />
    constexpr uint8_t kRamWrite = 0x2C;<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;kRamWrite, sizeof(kRamWrite), 0) &amp;&amp;<br />
                         transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool initializeController() {<br />
    const uint8_t madctl[] = {kCo16Madctl};<br />
    const uint8_t displayFunction[] = {0x0A, 0x82};<br />
    const uint8_t pixelFormat[] = {0x55};<br />
    const uint8_t porchControl[] = {0x0C, 0x0C, 0x00, 0x33, 0x33};<br />
    const uint8_t gateControl[] = {0x35};<br />
    const uint8_t vcom[] = {0x28};<br />
    const uint8_t lcmControl[] = {0x0C};<br />
    const uint8_t vdvVrhEnable[] = {0x01, 0xFF};<br />
    const uint8_t vrh[] = {0x10};<br />
    const uint8_t vdv[] = {0x20};<br />
    const uint8_t frameRate[] = {0x0F};<br />
    const uint8_t powerControl[] = {0xA4, 0xA1};<br />
    const uint8_t positiveGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32,<br />
        0x44, 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17,<br />
    };<br />
    const uint8_t negativeGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31,<br />
        0x54, 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E,<br />
    };<br />
<br />
    if (!writeCommand(0x11)) {  // Sleep out.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    return writeCommand(0x13) &amp;&amp;<br />
           writeCommandData(0x36, madctl, sizeof(madctl)) &amp;&amp;<br />
           writeCommandData(0xB6, displayFunction, sizeof(displayFunction)) &amp;&amp;<br />
           writeCommandData(0x3A, pixelFormat, sizeof(pixelFormat)) &amp;&amp;<br />
           (delay(10), true) &amp;&amp;<br />
           writeCommandData(0xB2, porchControl, sizeof(porchControl)) &amp;&amp;<br />
           writeCommandData(0xB7, gateControl, sizeof(gateControl)) &amp;&amp;<br />
           writeCommandData(0xBB, vcom, sizeof(vcom)) &amp;&amp;<br />
           writeCommandData(0xC0, lcmControl, sizeof(lcmControl)) &amp;&amp;<br />
           writeCommandData(0xC2, vdvVrhEnable, sizeof(vdvVrhEnable)) &amp;&amp;<br />
           writeCommandData(0xC3, vrh, sizeof(vrh)) &amp;&amp;<br />
           writeCommandData(0xC4, vdv, sizeof(vdv)) &amp;&amp;<br />
           writeCommandData(0xC6, frameRate, sizeof(frameRate)) &amp;&amp;<br />
           writeCommandData(0xD0, powerControl, sizeof(powerControl)) &amp;&amp;<br />
           writeCommandData(0xE0, positiveGamma, sizeof(positiveGamma)) &amp;&amp;<br />
           writeCommandData(0xE1, negativeGamma, sizeof(negativeGamma)) &amp;&amp;<br />
           writeCommand(0x21);  // Display inversion on.<br />
  }<br />
<br />
  spi_device_handle_t spiDevice_ = nullptr;<br />
  uint8_t* drawBuffer_ = nullptr;<br />
  bool initialized_ = false;<br />
};<br />
<br />
Co16St7789 display;<br />
<br />
void setup() {<br />
  Serial.begin(115200);<br />
  Wire.begin(8, 18);<br />
  Wire.setClock(100000);<br />
  Wire.setTimeOut(20);<br />
  // Active-low PCF8575 relay outputs: HIGH on all channels means OFF.<br />
  Wire.beginTransmission(0x22);<br />
  Wire.write(uint8_t(0xFF));<br />
  Wire.write(uint8_t(0xFF));<br />
  if (Wire.endTransmission() != 0) {<br />
    Serial.println("Warning: relay OFF command not acknowledged at 0x22.");<br />
  }<br />
<br />
  // Deselect all other SPI peripherals before initializing the LCD bus.<br />
  const int inactiveCs[] = {9, 13, 14, 42};<br />
  for (int pin : inactiveCs) {<br />
    digitalWrite(pin, HIGH);<br />
    pinMode(pin, OUTPUT);<br />
  }<br />
  if (!display.begin()) {<br />
    Serial.println("ST7789 initialization failed.");<br />
    while (true) delay(1000);<br />
  }<br />
  // ESP32 flash is memory mapped. present() reads the constant image into<br />
  // a small DMA-capable staging buffer, then sends it to the ST7789.<br />
  if (!display.present(kImageRgb565)) {<br />
    Serial.println("Image transfer failed.");<br />
    while (true) delay(1000);<br />
  }<br />
  Serial.println("Image displayed: 320x240 RGB565, full-screen image.");<br />
}<br />
<br />
void loop() {<br />
  // The LCD retains its image; no continuous redraw is necessary.<br />
  delay(1000);<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10743" target="_blank" title="">12-CO16_Image_Display.zip</a> (Size: 729.92 KB / Downloads: 9)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10740" target="_blank" title="">12-CO16_Image_Display.ino.merged.zip</a> (Size: 273.66 KB / Downloads: 8)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10741" target="_blank" title="">CO16-image-1.jpg</a> (Size: 262.26 KB / Downloads: 16)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10742" target="_blank" title="">CO16-image-2.jpg</a> (Size: 259.66 KB / Downloads: 19)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 * CO16 Image Display - show the supplied photograph on the onboard LCD.<br />
 * Target: ESP32-S3 CO16, ST7789 320x240 landscape.<br />
 * Uses the display initialization from the validated CO16 Cyber Show sketch.<br />
 * Install Adafruit GFX Library and its Adafruit BusIO dependency.<br />
 * Select ESP32S3 Dev Module; retain your working CO16 board settings.<br />
 * Keep image_data.h in the same folder as this sketch.<br />
 * Image is stored in flash as RGB565; no SD card, JPEG decoder or Wi-Fi.<br />
 * Full image is resized to 320x240 with no margins; aspect ratio changes.<br />
 * PCF8575 relay outputs at 0x22 are commanded OFF at startup.<br />
 */<br />
#include &lt;Arduino.h&gt;<br />
#include &lt;Wire.h&gt;<br />
#include "image_data.h"<br />
<br />
#include &lt;Adafruit_GFX.h&gt;<br />
#include &lt;driver/gpio.h&gt;<br />
#include &lt;driver/spi_master.h&gt;<br />
#include &lt;esp_heap_caps.h&gt;<br />
<br />
<br />
// CO16 ST7789 (GMT020-02-7P) pin mapping from kcs_co16.h.<br />
constexpr gpio_num_t kTftSclk = GPIO_NUM_11;<br />
constexpr gpio_num_t kTftMosi = GPIO_NUM_10;<br />
constexpr gpio_num_t kTftMiso = GPIO_NUM_12;<br />
constexpr gpio_num_t kTftCs = GPIO_NUM_4;<br />
constexpr gpio_num_t kTftDc = GPIO_NUM_0;<br />
constexpr gpio_num_t kTftReset = GPIO_NUM_5;<br />
constexpr gpio_num_t kTftBacklight = GPIO_NUM_40;<br />
<br />
constexpr int16_t kDisplayWidth = 320;<br />
constexpr int16_t kDisplayHeight = 240;<br />
constexpr int16_t kTransferChunkHeight = 10;<br />
constexpr uint32_t kDisplaySpiFrequency = 20 * 1000 * 1000;<br />
constexpr uint8_t kCo16Madctl = 0x68;  // MX | MV | BGR.<br />
<br />
constexpr uint16_t kBlack = 0x0000;<br />
constexpr uint16_t kBlue = 0x001F;<br />
constexpr uint16_t kRed = 0xF800;<br />
constexpr uint16_t kGreen = 0x07E0;<br />
constexpr uint16_t kCyan = 0x07FF;<br />
constexpr uint16_t kMagenta = 0xF81F;<br />
constexpr uint16_t kYellow = 0xFFE0;<br />
constexpr uint16_t kWhite = 0xFFFF;<br />
<br />
class Co16St7789 : public Adafruit_GFX {<br />
 public:<br />
  Co16St7789() : Adafruit_GFX(kDisplayWidth, kDisplayHeight) {}<br />
<br />
  bool begin() {<br />
    gpio_reset_pin(kTftCs);<br />
    gpio_set_direction(kTftCs, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftCs, 1);<br />
<br />
    gpio_reset_pin(kTftDc);<br />
    gpio_set_direction(kTftDc, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftDc, 1);<br />
<br />
    gpio_reset_pin(kTftBacklight);<br />
    gpio_set_direction(kTftBacklight, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftBacklight, 0);<br />
<br />
    spi_bus_config_t busConfig = {};<br />
    busConfig.sclk_io_num = kTftSclk;<br />
    busConfig.mosi_io_num = kTftMosi;<br />
    busConfig.miso_io_num = kTftMiso;<br />
    busConfig.quadwp_io_num = -1;<br />
    busConfig.quadhd_io_num = -1;<br />
    busConfig.max_transfer_sz =<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t);<br />
<br />
    esp_err_t err =<br />
        spi_bus_initialize(SPI3_HOST, &amp;busConfig, SPI_DMA_CH_AUTO);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("SPI3 initialization failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    spi_device_interface_config_t deviceConfig = {};<br />
    deviceConfig.clock_speed_hz = kDisplaySpiFrequency;<br />
    deviceConfig.mode = 0;<br />
    deviceConfig.spics_io_num = -1;<br />
    deviceConfig.queue_size = 1;<br />
<br />
    err = spi_bus_add_device(SPI3_HOST, &amp;deviceConfig, &amp;spiDevice_);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI device setup failed: %s&#92;n",<br />
                    esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    drawBuffer_ = static_cast&lt;uint8_t*&gt;(heap_caps_malloc(<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t),<br />
        MALLOC_CAP_DMA));<br />
    if (drawBuffer_ == nullptr) {<br />
      Serial.println("ST7789 DMA buffer allocation failed");<br />
      return false;<br />
    }<br />
<br />
    // GPIO5 is shared with the optional LoRa module. Match the working KCS<br />
    // firmware and issue one reset pulse before initializing the controller.<br />
    gpio_reset_pin(kTftReset);<br />
    gpio_set_direction(kTftReset, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 0);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(10);<br />
<br />
    if (!initializeController()) {<br />
      return false;<br />
    }<br />
<br />
    fillScreen(kBlack);<br />
    delay(120);<br />
    if (!writeCommand(0x29)) {  // Display on.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    gpio_set_level(kTftBacklight, 1);<br />
    initialized_ = true;<br />
    return true;<br />
  }<br />
<br />
  void drawPixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void writePixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void fillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                uint16_t color) override {<br />
    if (x &lt; 0) {<br />
      width += x;<br />
      x = 0;<br />
    }<br />
    if (y &lt; 0) {<br />
      height += y;<br />
      y = 0;<br />
    }<br />
    if (x + width &gt; kDisplayWidth) {<br />
      width = kDisplayWidth - x;<br />
    }<br />
    if (y + height &gt; kDisplayHeight) {<br />
      height = kDisplayHeight - y;<br />
    }<br />
    if (drawBuffer_ == nullptr || width &lt;= 0 || height &lt;= 0) {<br />
      return;<br />
    }<br />
<br />
    const uint8_t high = color &gt;&gt; 8;<br />
    const uint8_t low = color;<br />
    for (int16_t row = y; row &lt; y + height;<br />
         row += kTransferChunkHeight) {<br />
      const int16_t chunkHeight =<br />
          min&lt;int16_t&gt;(kTransferChunkHeight, y + height - row);<br />
      const size_t pixelCount = width * chunkHeight;<br />
      for (size_t index = 0; index &lt; pixelCount; ++index) {<br />
        drawBuffer_[index * 2] = high;<br />
        drawBuffer_[index * 2 + 1] = low;<br />
      }<br />
<br />
      if (!setWindow(x, row, x + width, row + chunkHeight) ||<br />
          !writePixels(drawBuffer_, pixelCount * sizeof(uint16_t))) {<br />
        Serial.println("ST7789 pixel transfer failed");<br />
        return;<br />
      }<br />
    }<br />
  }<br />
<br />
  void writeFillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                     uint16_t color) override {<br />
    fillRect(x, y, width, height, color);<br />
  }<br />
<br />
  // Copy a fully rendered RGB565 frame into the DMA staging buffer.<br />
  bool present(const uint16_t* pixels) {<br />
    if (!initialized_ || pixels == nullptr) return false;<br />
    for (int y = 0; y &lt; kDisplayHeight; y += kTransferChunkHeight) {<br />
      const int rows = min&lt;int&gt;(kTransferChunkHeight, kDisplayHeight - y);<br />
      const int count = kDisplayWidth * rows;<br />
      for (int i = 0; i &lt; count; ++i) {<br />
        uint16_t c = pixels[y * kDisplayWidth + i];<br />
        drawBuffer_[2 * i] = c &gt;&gt; 8;<br />
        drawBuffer_[2 * i + 1] = c &amp; 0xFF;<br />
      }<br />
      if (!setWindow(0, y, kDisplayWidth, y + rows) ||<br />
          !writePixels(drawBuffer_, count * 2)) return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool initialized() const { return initialized_; }<br />
<br />
 private:<br />
  bool transmit(const void* data, size_t length, int dcLevel) {<br />
    if (data == nullptr || length == 0) {<br />
      return true;<br />
    }<br />
<br />
    gpio_set_level(kTftDc, dcLevel);<br />
    spi_transaction_t transaction = {};<br />
    transaction.length = length * 8;<br />
    transaction.tx_buffer = data;<br />
    const esp_err_t err = spi_device_polling_transmit(spiDevice_, &amp;transaction);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI transfer failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool writeCommand(uint8_t command) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool writeCommandData(uint8_t command, const uint8_t* data, size_t length) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0) &amp;&amp;<br />
                         transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool setWindow(int16_t x1, int16_t y1, int16_t x2, int16_t y2) {<br />
    const uint8_t columns[] = {<br />
        static_cast&lt;uint8_t&gt;(x1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x1),<br />
        static_cast&lt;uint8_t&gt;((x2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x2 - 1),<br />
    };<br />
    const uint8_t rows[] = {<br />
        static_cast&lt;uint8_t&gt;(y1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y1),<br />
        static_cast&lt;uint8_t&gt;((y2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y2 - 1),<br />
    };<br />
    return writeCommandData(0x2A, columns, sizeof(columns)) &amp;&amp;<br />
           writeCommandData(0x2B, rows, sizeof(rows));<br />
  }<br />
<br />
  bool writePixels(const uint8_t* data, size_t length) {<br />
    constexpr uint8_t kRamWrite = 0x2C;<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;kRamWrite, sizeof(kRamWrite), 0) &amp;&amp;<br />
                         transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool initializeController() {<br />
    const uint8_t madctl[] = {kCo16Madctl};<br />
    const uint8_t displayFunction[] = {0x0A, 0x82};<br />
    const uint8_t pixelFormat[] = {0x55};<br />
    const uint8_t porchControl[] = {0x0C, 0x0C, 0x00, 0x33, 0x33};<br />
    const uint8_t gateControl[] = {0x35};<br />
    const uint8_t vcom[] = {0x28};<br />
    const uint8_t lcmControl[] = {0x0C};<br />
    const uint8_t vdvVrhEnable[] = {0x01, 0xFF};<br />
    const uint8_t vrh[] = {0x10};<br />
    const uint8_t vdv[] = {0x20};<br />
    const uint8_t frameRate[] = {0x0F};<br />
    const uint8_t powerControl[] = {0xA4, 0xA1};<br />
    const uint8_t positiveGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32,<br />
        0x44, 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17,<br />
    };<br />
    const uint8_t negativeGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31,<br />
        0x54, 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E,<br />
    };<br />
<br />
    if (!writeCommand(0x11)) {  // Sleep out.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    return writeCommand(0x13) &amp;&amp;<br />
           writeCommandData(0x36, madctl, sizeof(madctl)) &amp;&amp;<br />
           writeCommandData(0xB6, displayFunction, sizeof(displayFunction)) &amp;&amp;<br />
           writeCommandData(0x3A, pixelFormat, sizeof(pixelFormat)) &amp;&amp;<br />
           (delay(10), true) &amp;&amp;<br />
           writeCommandData(0xB2, porchControl, sizeof(porchControl)) &amp;&amp;<br />
           writeCommandData(0xB7, gateControl, sizeof(gateControl)) &amp;&amp;<br />
           writeCommandData(0xBB, vcom, sizeof(vcom)) &amp;&amp;<br />
           writeCommandData(0xC0, lcmControl, sizeof(lcmControl)) &amp;&amp;<br />
           writeCommandData(0xC2, vdvVrhEnable, sizeof(vdvVrhEnable)) &amp;&amp;<br />
           writeCommandData(0xC3, vrh, sizeof(vrh)) &amp;&amp;<br />
           writeCommandData(0xC4, vdv, sizeof(vdv)) &amp;&amp;<br />
           writeCommandData(0xC6, frameRate, sizeof(frameRate)) &amp;&amp;<br />
           writeCommandData(0xD0, powerControl, sizeof(powerControl)) &amp;&amp;<br />
           writeCommandData(0xE0, positiveGamma, sizeof(positiveGamma)) &amp;&amp;<br />
           writeCommandData(0xE1, negativeGamma, sizeof(negativeGamma)) &amp;&amp;<br />
           writeCommand(0x21);  // Display inversion on.<br />
  }<br />
<br />
  spi_device_handle_t spiDevice_ = nullptr;<br />
  uint8_t* drawBuffer_ = nullptr;<br />
  bool initialized_ = false;<br />
};<br />
<br />
Co16St7789 display;<br />
<br />
void setup() {<br />
  Serial.begin(115200);<br />
  Wire.begin(8, 18);<br />
  Wire.setClock(100000);<br />
  Wire.setTimeOut(20);<br />
  // Active-low PCF8575 relay outputs: HIGH on all channels means OFF.<br />
  Wire.beginTransmission(0x22);<br />
  Wire.write(uint8_t(0xFF));<br />
  Wire.write(uint8_t(0xFF));<br />
  if (Wire.endTransmission() != 0) {<br />
    Serial.println("Warning: relay OFF command not acknowledged at 0x22.");<br />
  }<br />
<br />
  // Deselect all other SPI peripherals before initializing the LCD bus.<br />
  const int inactiveCs[] = {9, 13, 14, 42};<br />
  for (int pin : inactiveCs) {<br />
    digitalWrite(pin, HIGH);<br />
    pinMode(pin, OUTPUT);<br />
  }<br />
  if (!display.begin()) {<br />
    Serial.println("ST7789 initialization failed.");<br />
    while (true) delay(1000);<br />
  }<br />
  // ESP32 flash is memory mapped. present() reads the constant image into<br />
  // a small DMA-capable staging buffer, then sends it to the ST7789.<br />
  if (!display.present(kImageRgb565)) {<br />
    Serial.println("Image transfer failed.");<br />
    while (true) delay(1000);<br />
  }<br />
  Serial.println("Image displayed: 320x240 RGB565, full-screen image.");<br />
}<br />
<br />
void loop() {<br />
  // The LCD retains its image; no continuous redraw is necessary.<br />
  delay(1000);<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10743" target="_blank" title="">12-CO16_Image_Display.zip</a> (Size: 729.92 KB / Downloads: 9)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10740" target="_blank" title="">12-CO16_Image_Display.ino.merged.zip</a> (Size: 273.66 KB / Downloads: 8)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10741" target="_blank" title="">CO16-image-1.jpg</a> (Size: 262.26 KB / Downloads: 16)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10742" target="_blank" title="">CO16-image-2.jpg</a> (Size: 259.66 KB / Downloads: 19)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-11 ST7789 TFT LCD Cyber Show With Relay]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9933</link>
			<pubDate>Tue, 03 Nov 2026 08:15:17 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9933</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 * CO16 CYBER SHOW - standalone Shorts demo, 32-second automatic loop.<br />
 * Target: ESP32-S3, CO16 with PCF8575 relay outputs at 0x22.<br />
 * Display: 320x240 ST7789; native SPI3 initialization from CO16-pcb-test.ino.<br />
 * Install Adafruit GFX Library and its Adafruit BusIO dependency.<br />
 * Board: ESP32S3 Dev Module; retain the settings used for the factory sketch.<br />
 * No PCF8575, Ethernet, SD, RTC or ADC libraries required.<br />
 *<br />
 * IMPORTANT: disconnect all loads from relay terminals before filming.<br />
 * These are mechanical relays: use this as a short filming demo, not a<br />
 * continuous endurance test. The graphics are synthetic, not sensor data.<br />
 * Relay tiles show acknowledged output commands, NOT contact feedback.<br />
 *<br />
 * Serial Monitor, 115200 baud (no line ending needed):<br />
 *   0 = automatic show from beginning; 1..5 = hold a scene<br />
 *   p = pause/resume (pause switches relays OFF); r = restart auto show<br />
 *   x = disable relay motion; e = enable relay motion<br />
 * Frame rendering uses 153600 bytes of RAM plus a 6400-byte DMA buffer.<br />
 * No EEPROM writes, networking, SD operations or other peripheral tests.<br />
 */<br />
#include &lt;Arduino.h&gt;<br />
#include &lt;Wire.h&gt;<br />
#include &lt;math.h&gt;<br />
#include &lt;string.h&gt;<br />
<br />
#include &lt;Adafruit_GFX.h&gt;<br />
#include &lt;driver/gpio.h&gt;<br />
#include &lt;driver/spi_master.h&gt;<br />
#include &lt;esp_heap_caps.h&gt;<br />
<br />
<br />
// CO16 ST7789 (GMT020-02-7P) pin mapping from kcs_co16.h.<br />
constexpr gpio_num_t kTftSclk = GPIO_NUM_11;<br />
constexpr gpio_num_t kTftMosi = GPIO_NUM_10;<br />
constexpr gpio_num_t kTftMiso = GPIO_NUM_12;<br />
constexpr gpio_num_t kTftCs = GPIO_NUM_4;<br />
constexpr gpio_num_t kTftDc = GPIO_NUM_0;<br />
constexpr gpio_num_t kTftReset = GPIO_NUM_5;<br />
constexpr gpio_num_t kTftBacklight = GPIO_NUM_40;<br />
<br />
constexpr int16_t kDisplayWidth = 320;<br />
constexpr int16_t kDisplayHeight = 240;<br />
constexpr int16_t kTransferChunkHeight = 10;<br />
constexpr uint32_t kDisplaySpiFrequency = 20 * 1000 * 1000;<br />
constexpr uint8_t kCo16Madctl = 0x68;  // MX | MV | BGR.<br />
<br />
constexpr uint16_t kBlack = 0x0000;<br />
constexpr uint16_t kBlue = 0x001F;<br />
constexpr uint16_t kRed = 0xF800;<br />
constexpr uint16_t kGreen = 0x07E0;<br />
constexpr uint16_t kCyan = 0x07FF;<br />
constexpr uint16_t kMagenta = 0xF81F;<br />
constexpr uint16_t kYellow = 0xFFE0;<br />
constexpr uint16_t kWhite = 0xFFFF;<br />
<br />
class Co16St7789 : public Adafruit_GFX {<br />
 public:<br />
  Co16St7789() : Adafruit_GFX(kDisplayWidth, kDisplayHeight) {}<br />
<br />
  bool begin() {<br />
    gpio_reset_pin(kTftCs);<br />
    gpio_set_direction(kTftCs, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftCs, 1);<br />
<br />
    gpio_reset_pin(kTftDc);<br />
    gpio_set_direction(kTftDc, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftDc, 1);<br />
<br />
    gpio_reset_pin(kTftBacklight);<br />
    gpio_set_direction(kTftBacklight, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftBacklight, 0);<br />
<br />
    spi_bus_config_t busConfig = {};<br />
    busConfig.sclk_io_num = kTftSclk;<br />
    busConfig.mosi_io_num = kTftMosi;<br />
    busConfig.miso_io_num = kTftMiso;<br />
    busConfig.quadwp_io_num = -1;<br />
    busConfig.quadhd_io_num = -1;<br />
    busConfig.max_transfer_sz =<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t);<br />
<br />
    esp_err_t err =<br />
        spi_bus_initialize(SPI3_HOST, &amp;busConfig, SPI_DMA_CH_AUTO);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("SPI3 initialization failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    spi_device_interface_config_t deviceConfig = {};<br />
    deviceConfig.clock_speed_hz = kDisplaySpiFrequency;<br />
    deviceConfig.mode = 0;<br />
    deviceConfig.spics_io_num = -1;<br />
    deviceConfig.queue_size = 1;<br />
<br />
    err = spi_bus_add_device(SPI3_HOST, &amp;deviceConfig, &amp;spiDevice_);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI device setup failed: %s&#92;n",<br />
                    esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    drawBuffer_ = static_cast&lt;uint8_t*&gt;(heap_caps_malloc(<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t),<br />
        MALLOC_CAP_DMA));<br />
    if (drawBuffer_ == nullptr) {<br />
      Serial.println("ST7789 DMA buffer allocation failed");<br />
      return false;<br />
    }<br />
<br />
    // GPIO5 is shared with the optional LoRa module. Match the working KCS<br />
    // firmware and issue one reset pulse before initializing the controller.<br />
    gpio_reset_pin(kTftReset);<br />
    gpio_set_direction(kTftReset, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 0);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(10);<br />
<br />
    if (!initializeController()) {<br />
      return false;<br />
    }<br />
<br />
    fillScreen(kBlack);<br />
    delay(120);<br />
    if (!writeCommand(0x29)) {  // Display on.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    gpio_set_level(kTftBacklight, 1);<br />
    initialized_ = true;<br />
    return true;<br />
  }<br />
<br />
  void drawPixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void writePixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void fillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                uint16_t color) override {<br />
    if (x &lt; 0) {<br />
      width += x;<br />
      x = 0;<br />
    }<br />
    if (y &lt; 0) {<br />
      height += y;<br />
      y = 0;<br />
    }<br />
    if (x + width &gt; kDisplayWidth) {<br />
      width = kDisplayWidth - x;<br />
    }<br />
    if (y + height &gt; kDisplayHeight) {<br />
      height = kDisplayHeight - y;<br />
    }<br />
    if (drawBuffer_ == nullptr || width &lt;= 0 || height &lt;= 0) {<br />
      return;<br />
    }<br />
<br />
    const uint8_t high = color &gt;&gt; 8;<br />
    const uint8_t low = color;<br />
    for (int16_t row = y; row &lt; y + height;<br />
         row += kTransferChunkHeight) {<br />
      const int16_t chunkHeight =<br />
          min&lt;int16_t&gt;(kTransferChunkHeight, y + height - row);<br />
      const size_t pixelCount = width * chunkHeight;<br />
      for (size_t index = 0; index &lt; pixelCount; ++index) {<br />
        drawBuffer_[index * 2] = high;<br />
        drawBuffer_[index * 2 + 1] = low;<br />
      }<br />
<br />
      if (!setWindow(x, row, x + width, row + chunkHeight) ||<br />
          !writePixels(drawBuffer_, pixelCount * sizeof(uint16_t))) {<br />
        Serial.println("ST7789 pixel transfer failed");<br />
        return;<br />
      }<br />
    }<br />
  }<br />
<br />
  void writeFillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                     uint16_t color) override {<br />
    fillRect(x, y, width, height, color);<br />
  }<br />
<br />
  // Copy a fully rendered RGB565 frame into the DMA staging buffer.<br />
  bool present(const uint16_t* pixels) {<br />
    if (!initialized_ || pixels == nullptr) return false;<br />
    for (int y = 0; y &lt; kDisplayHeight; y += kTransferChunkHeight) {<br />
      const int rows = min&lt;int&gt;(kTransferChunkHeight, kDisplayHeight - y);<br />
      const int count = kDisplayWidth * rows;<br />
      for (int i = 0; i &lt; count; ++i) {<br />
        uint16_t c = pixels[y * kDisplayWidth + i];<br />
        drawBuffer_[2 * i] = c &gt;&gt; 8;<br />
        drawBuffer_[2 * i + 1] = c &amp; 0xFF;<br />
      }<br />
      if (!setWindow(0, y, kDisplayWidth, y + rows) ||<br />
          !writePixels(drawBuffer_, count * 2)) return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool initialized() const { return initialized_; }<br />
<br />
 private:<br />
  bool transmit(const void* data, size_t length, int dcLevel) {<br />
    if (data == nullptr || length == 0) {<br />
      return true;<br />
    }<br />
<br />
    gpio_set_level(kTftDc, dcLevel);<br />
    spi_transaction_t transaction = {};<br />
    transaction.length = length * 8;<br />
    transaction.tx_buffer = data;<br />
    const esp_err_t err = spi_device_polling_transmit(spiDevice_, &amp;transaction);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI transfer failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool writeCommand(uint8_t command) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool writeCommandData(uint8_t command, const uint8_t* data, size_t length) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0) &amp;&amp;<br />
                         transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool setWindow(int16_t x1, int16_t y1, int16_t x2, int16_t y2) {<br />
    const uint8_t columns[] = {<br />
        static_cast&lt;uint8_t&gt;(x1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x1),<br />
        static_cast&lt;uint8_t&gt;((x2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x2 - 1),<br />
    };<br />
    const uint8_t rows[] = {<br />
        static_cast&lt;uint8_t&gt;(y1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y1),<br />
        static_cast&lt;uint8_t&gt;((y2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y2 - 1),<br />
    };<br />
    return writeCommandData(0x2A, columns, sizeof(columns)) &amp;&amp;<br />
           writeCommandData(0x2B, rows, sizeof(rows));<br />
  }<br />
<br />
  bool writePixels(const uint8_t* data, size_t length) {<br />
    constexpr uint8_t kRamWrite = 0x2C;<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;kRamWrite, sizeof(kRamWrite), 0) &amp;&amp;<br />
                         transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool initializeController() {<br />
    const uint8_t madctl[] = {kCo16Madctl};<br />
    const uint8_t displayFunction[] = {0x0A, 0x82};<br />
    const uint8_t pixelFormat[] = {0x55};<br />
    const uint8_t porchControl[] = {0x0C, 0x0C, 0x00, 0x33, 0x33};<br />
    const uint8_t gateControl[] = {0x35};<br />
    const uint8_t vcom[] = {0x28};<br />
    const uint8_t lcmControl[] = {0x0C};<br />
    const uint8_t vdvVrhEnable[] = {0x01, 0xFF};<br />
    const uint8_t vrh[] = {0x10};<br />
    const uint8_t vdv[] = {0x20};<br />
    const uint8_t frameRate[] = {0x0F};<br />
    const uint8_t powerControl[] = {0xA4, 0xA1};<br />
    const uint8_t positiveGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32,<br />
        0x44, 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17,<br />
    };<br />
    const uint8_t negativeGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31,<br />
        0x54, 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E,<br />
    };<br />
<br />
    if (!writeCommand(0x11)) {  // Sleep out.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    return writeCommand(0x13) &amp;&amp;<br />
           writeCommandData(0x36, madctl, sizeof(madctl)) &amp;&amp;<br />
           writeCommandData(0xB6, displayFunction, sizeof(displayFunction)) &amp;&amp;<br />
           writeCommandData(0x3A, pixelFormat, sizeof(pixelFormat)) &amp;&amp;<br />
           (delay(10), true) &amp;&amp;<br />
           writeCommandData(0xB2, porchControl, sizeof(porchControl)) &amp;&amp;<br />
           writeCommandData(0xB7, gateControl, sizeof(gateControl)) &amp;&amp;<br />
           writeCommandData(0xBB, vcom, sizeof(vcom)) &amp;&amp;<br />
           writeCommandData(0xC0, lcmControl, sizeof(lcmControl)) &amp;&amp;<br />
           writeCommandData(0xC2, vdvVrhEnable, sizeof(vdvVrhEnable)) &amp;&amp;<br />
           writeCommandData(0xC3, vrh, sizeof(vrh)) &amp;&amp;<br />
           writeCommandData(0xC4, vdv, sizeof(vdv)) &amp;&amp;<br />
           writeCommandData(0xC6, frameRate, sizeof(frameRate)) &amp;&amp;<br />
           writeCommandData(0xD0, powerControl, sizeof(powerControl)) &amp;&amp;<br />
           writeCommandData(0xE0, positiveGamma, sizeof(positiveGamma)) &amp;&amp;<br />
           writeCommandData(0xE1, negativeGamma, sizeof(negativeGamma)) &amp;&amp;<br />
           writeCommand(0x21);  // Display inversion on.<br />
  }<br />
<br />
  spi_device_handle_t spiDevice_ = nullptr;<br />
  uint8_t* drawBuffer_ = nullptr;<br />
  bool initialized_ = false;<br />
};<br />
<br />
Co16St7789 display;<br />
GFXcanvas16 canvas(320, 240);<br />
constexpr uint32_t FRAME_MS = 80;   // Target ~12.5 FPS; actual rate depends on SPI.<br />
constexpr uint32_t RELAY_STEP_MS = 250;<br />
constexpr uint32_t SHOW_MS = 32000;<br />
constexpr uint8_t RELAY_ADDRESS = 0x22;<br />
constexpr float TAU = 6.28318530718f;<br />
constexpr uint16_t BG = 0x0001, GRID = 0x0843, CYAN = 0x07FF;<br />
constexpr uint16_t PINK = 0xF81F, WHITE = 0xFFFF, DIM = 0x5B6D;<br />
constexpr uint16_t GREEN = 0x07E0, GOLD = 0xFDE0;<br />
const char* const SCENE_NAMES[] = {<br />
  "SYSTEM BOOT", "RELAY RADAR", "ENERGY CORE", "BEAT MATRIX", "ONE BRAIN"<br />
};<br />
uint32_t showTime = 0, lastTick = 0, lastFrame = 0, lastRelay = 0;<br />
uint16_t relayMask = 0;<br />
bool relayEnabled = true, paused = false, relayFault = false;<br />
int manualScene = -1;<br />
<br />
// A single two-byte write changes all 16 PCF8575 outputs together.<br />
// P0 is channel 1. HIGH = OFF, LOW = ON, as in the supplied test.<br />
bool writeRelays(uint16_t mask) {<br />
  uint16_t raw = uint16_t(~mask);<br />
  Wire.beginTransmission(RELAY_ADDRESS);<br />
  Wire.write(uint8_t(raw));<br />
  Wire.write(uint8_t(raw &gt;&gt; 8));<br />
  bool ok = Wire.endTransmission() == 0;<br />
  if (ok) relayMask = mask;<br />
  return ok;<br />
}<br />
<br />
uint16_t channelBit(int i) { return uint16_t(1u &lt;&lt; (i &amp; 15)); }<br />
<br />
int sceneAt(uint32_t t) {<br />
  if (t &lt; 4000) return 0;<br />
  if (t &lt; 12000) return 1;<br />
  if (t &lt; 20000) return 2;<br />
  if (t &lt; 28000) return 3;<br />
  return 4;<br />
}<br />
uint32_t sceneStart(int s) {<br />
  const uint32_t starts[] = {0, 4000, 12000, 20000, 28000};<br />
  return starts[s];<br />
}<br />
<br />
uint16_t patternFor(int s, uint32_t t) {<br />
  uint32_t step = t / RELAY_STEP_MS;<br />
  switch (s) {<br />
    case 0: return channelBit(step % 16);<br />
    case 1: return channelBit(step % 16);<br />
    case 2: {<br />
      int p = step % 14;<br />
      if (p &gt; 7) p = 14 - p;<br />
      return channelBit(p) | channelBit(15 - p);<br />
    }<br />
    case 3: {<br />
      const uint16_t beats[] = {0x1111, 0, 0x2222, 0, 0x4444, 0, 0x8888, 0};<br />
      return beats[step % 8];<br />
    }<br />
    default: return t &lt; 3000 ? channelBit(15 - (step % 16)) : 0;<br />
  }<br />
}<br />
<br />
void label(int x, int y, const char* text, int size, uint16_t color) {<br />
  canvas.setTextSize(size);<br />
  canvas.setTextColor(color);<br />
  canvas.setCursor(x, y);<br />
  canvas.print(text);<br />
}<br />
void center(int y, const char* text, int size, uint16_t color) {<br />
  label((320 - int(strlen(text)) * 6 * size) / 2, y, text, size, color);<br />
}<br />
<br />
void background(uint32_t t) {<br />
  canvas.fillScreen(BG);<br />
  for (int x = 0; x &lt; 320; x += 20) canvas.drawFastVLine(x, 30, 158, GRID);<br />
  for (int y = 30; y &lt; 188; y += 20) canvas.drawFastHLine(0, y, 320, GRID);<br />
  // Deterministic drifting stars, without allocations or random-state changes.<br />
  for (int i = 0; i &lt; 36; ++i) {<br />
    int x = (i * 73 + t / (15 + i % 7)) % 320;<br />
    int y = 34 + (i * 47) % 149;<br />
    canvas.drawPixel(x, y, i % 3 ? DIM : CYAN);<br />
  }<br />
}<br />
<br />
void drawBoot(uint32_t t) {<br />
  center(42, "KinCony", 4, CYAN);<br />
  center(80, "CO16", 3, WHITE);<br />
  center(112, "INITIALIZING THE SHOW", 1, DIM);<br />
  canvas.drawRoundRect(36, 132, 248, 18, 4, CYAN);<br />
  int progress = int((t % 4000) * 242 / 3999);<br />
  canvas.fillRect(39, 135, progress, 12, PINK);<br />
  char line[32];<br />
  snprintf(line, sizeof(line), "16 CHANNELS / %02lu%%", (unsigned long)((t % 4000) * 100 / 3999));<br />
  center(163, line, 1, WHITE);<br />
}<br />
<br />
void drawRadar(uint32_t t) {<br />
  const int cx = 96, cy = 108;<br />
  for (int r = 20; r &lt;= 64; r += 22) canvas.drawCircle(cx, cy, r, 0x0329);<br />
  canvas.drawFastHLine(29, cy, 135, 0x0329);<br />
  canvas.drawFastVLine(cx, 41, 135, 0x0329);<br />
  float angle = (t % 4000) * TAU / 4000.0f - TAU / 4;<br />
  for (int j = 12; j &gt;= 0; --j) {<br />
    float a = angle - j * 0.035f;<br />
    canvas.drawLine(cx, cy, cx + int(cosf(a) * 63), cy + int(sinf(a) * 63),<br />
                    j == 0 ? CYAN : uint16_t((12 - j + 3) &lt;&lt; 5));<br />
  }<br />
  for (int i = 0; i &lt; 16; ++i) {<br />
    float a = i * TAU / 16 - TAU / 4;<br />
    int x = cx + int(cosf(a) * 54), y = cy + int(sinf(a) * 54);<br />
    bool on = relayMask &amp; channelBit(i);<br />
    canvas.fillCircle(x, y, on ? 5 : 2, on ? GOLD : DIM);<br />
    if (on) canvas.drawCircle(x, y, 8, CYAN);<br />
  }<br />
  label(181, 51, "SCAN", 3, CYAN);<br />
  label(182, 83, "16 RELAY NODES", 1, WHITE);<br />
  char buf[16];<br />
  snprintf(buf, sizeof(buf), "CH %02lu", (unsigned long)((t / RELAY_STEP_MS) % 16 + 1));<br />
  label(182, 110, buf, 2, GOLD);<br />
  label(182, 144, "COMMAND TRACKER", 1, DIM);<br />
  label(182, 160, "250ms / STEP", 1, DIM);<br />
}<br />
<br />
void drawCore(uint32_t t) {<br />
  float phase = t * 0.002f;<br />
  for (int i = 0; i &lt; 4; ++i) {<br />
    int r = 24 + i * 13;<br />
    for (int k = 0; k &lt; 46; ++k) {<br />
      float a = phase * (i % 2 ? -1 : 1) + k * TAU / 60;<br />
      float b = a + TAU / 65;<br />
      canvas.drawLine(160 + int(cosf(a) * r), 109 + int(sinf(a) * r),<br />
                      160 + int(cosf(b) * r), 109 + int(sinf(b) * r), i % 2 ? PINK : CYAN);<br />
    }<br />
  }<br />
  canvas.fillCircle(160, 109, 18 + int(3 * sinf(phase * 3)), 0x0410);<br />
  label(148, 102, "16", 2, WHITE);<br />
  for (int i = 0; i &lt; 8; ++i) {<br />
    int h = 5 + int((sinf(phase * 2 + i * 0.6f) + 1) * 24);<br />
    canvas.fillRect(14 + i * 6, 139 - h, 4, h, CYAN);<br />
    canvas.fillRect(260 + i * 6, 139 - h, 4, h, PINK);<br />
  }<br />
  label(12, 155, "CH 1-8", 1, DIM);<br />
  label(255, 155, "CH 9-16", 1, DIM);<br />
  center(176, "DUAL-WAVE SYNCHRONIZATION", 1, WHITE);<br />
}<br />
<br />
void drawBeat(uint32_t t) {<br />
  // Synthetic frequency bars. No microphone, FFT, or audio synchronization.<br />
  float phase = t * 0.004f;<br />
  for (int i = 0; i &lt; 16; ++i) {<br />
    float v = (sinf(phase + i * 0.67f) + sinf(phase * 0.63f - i * 0.41f) + 2) / 4;<br />
    int levels = 2 + int(v * 14);<br />
    for (int j = 0; j &lt; levels; ++j)<br />
      canvas.fillRect(9 + i * 19, 166 - j * 6, 13, 4, j &gt; 11 ? PINK : (j &gt; 6 ? CYAN : 0x0410));<br />
  }<br />
  center(43, "BEAT MATRIX", 2, WHITE);<br />
  center(65, "120 BPM / SYNTHETIC VISUALIZER", 1, DIM);<br />
  canvas.fillCircle(299, 45, 4, (t % 500 &lt; 250) ? GOLD : DIM);<br />
}<br />
<br />
void drawFinale(uint32_t t) {<br />
  for (int i = 0; i &lt; 32; ++i) {<br />
    float a = i * TAU / 32;<br />
    int r = 20 + (int(t / 12) + i * 17) % 130;<br />
    canvas.drawLine(160 + int(cosf(a) * r), 108 + int(sinf(a) * r * 0.50f),<br />
                    160 + int(cosf(a) * (r + 7)), 108 + int(sinf(a) * (r + 7) * 0.50f), GRID);<br />
  }<br />
  center(56, "16 CHANNELS", 3, CYAN);<br />
  center(94, "ONE BRAIN.", 3, WHITE);<br />
  center(136, "KinCony CO16 / ESP32-S3", 1, PINK);<br />
  center(165, "www.kincony.com", 1, DIM);<br />
}<br />
<br />
void render(int scene, uint32_t t) {<br />
  background(t);<br />
  switch (scene) {<br />
    case 0: drawBoot(t); break;<br />
    case 1: drawRadar(t); break;<br />
    case 2: drawCore(t); break;<br />
    case 3: drawBeat(t); break;<br />
    default: drawFinale(t); break;<br />
  }<br />
  canvas.fillRect(0, 0, 320, 29, BG);<br />
  label(8, 9, "CO16", 2, CYAN);<br />
  label(80, 12, SCENE_NAMES[scene], 1, WHITE);<br />
  label(271, 12, manualScene &lt; 0 ? "AUTO" : "HOLD", 1, PINK);<br />
  canvas.drawFastHLine(8, 28, 304, CYAN);<br />
  // Two rows of numbered output tiles, all drawn from the last ACKed command.<br />
  canvas.fillRect(0, 190, 320, 50, BG);<br />
  for (int i = 0; i &lt; 16; ++i) {<br />
    int x = 8 + (i % 8) * 39, y = 191 + (i / 8) * 18;<br />
    bool on = relayMask &amp; channelBit(i);<br />
    canvas.fillRoundRect(x, y, 32, 15, 3, on ? CYAN : GRID);<br />
    char number[4];<br />
    snprintf(number, sizeof(number), "%02d", i + 1);<br />
    label(x + 10, y + 4, number, 1, on ? BG : DIM);<br />
  }<br />
  const char* status = relayFault ? "I2C ERROR / OUTPUT UNKNOWN" :<br />
    paused ? "PAUSED / RELAYS OFF" : !relayEnabled ? "SCREEN ONLY / RELAYS OFF" : "RELAY COMMANDS / VISUAL DEMO";<br />
  center(231, status, 1, relayFault ? GOLD : DIM);<br />
  if (!display.present(canvas.getBuffer())) {<br />
    writeRelays(0);<br />
    Serial.println("Display transfer failed. Demo halted; OFF command attempted.");<br />
    while (true) { writeRelays(0); delay(1000); }<br />
  }<br />
}<br />
<br />
void handleSerial() {<br />
  while (Serial.available()) {<br />
    char c = Serial.read();<br />
    if (c == 'p') paused = !paused;<br />
    if (c == 'x') relayEnabled = false;<br />
    if (c == 'e') relayEnabled = true;<br />
    if (c == '0' || c == 'r') { manualScene = -1; showTime = 0; paused = false; }<br />
    if (c &gt;= '1' &amp;&amp; c &lt;= '5') { manualScene = c - '1'; showTime = 0; paused = false; }<br />
    if (c == 'p' || c == 'x' || c == '0' || c == 'r' || (c &gt;= '1' &amp;&amp; c &lt;= '5')) {<br />
      if (!writeRelays(0)) relayFault = true;<br />
      lastRelay = millis();<br />
    }<br />
  }<br />
}<br />
<br />
void setup() {<br />
  Serial.begin(115200);<br />
  Wire.begin(8, 18);<br />
  Wire.setClock(100000);<br />
  Wire.setTimeOut(20);<br />
  relayFault = !writeRelays(0);<br />
  // Deselect other devices sharing the TFT wires BEFORE enabling SPI3.<br />
  // Do not initialize SD, LoRa, or software SPI on this bus in this demo.<br />
  const int inactiveCs[] = {9, 13, 14, 42};<br />
  for (int pin : inactiveCs) { digitalWrite(pin, HIGH); pinMode(pin, OUTPUT); }<br />
  if (!canvas.getBuffer() || !display.begin()) {<br />
    Serial.println("Display/buffer initialization failed. Demo halted.");<br />
    while (true) { writeRelays(0); delay(1000); }<br />
  }<br />
  canvas.setTextWrap(false);<br />
  Serial.println("CO16 CYBER SHOW: 0=auto 1..5=scene p=pause x=relays off e=relays on r=restart");<br />
  lastTick = lastRelay = millis();<br />
}<br />
<br />
void loop() {<br />
  handleSerial();<br />
  uint32_t now = millis();<br />
  uint32_t dt = now - lastTick;<br />
  lastTick = now;<br />
  if (!paused) showTime = (showTime + dt) % SHOW_MS;<br />
  int scene = manualScene &lt; 0 ? sceneAt(showTime) : manualScene;<br />
  uint32_t localTime = manualScene &lt; 0 ? showTime - sceneStart(scene) : showTime;<br />
  // Limit real relay updates independently of display rendering.<br />
  if (now - lastRelay &gt;= RELAY_STEP_MS) {<br />
    lastRelay = now;<br />
    uint16_t wanted = (paused || !relayEnabled || relayFault) ? 0 : patternFor(scene, localTime);<br />
    if (relayFault || wanted != relayMask) {<br />
      if (!writeRelays(wanted)) {<br />
        relayFault = true;  // Latched until reset. Retry OFF, never resume motion.<br />
        Serial.println("Relay I2C error: motion disabled; retrying OFF.");<br />
        writeRelays(0);<br />
      }<br />
    }<br />
  }<br />
  if (now - lastFrame &gt;= FRAME_MS) {<br />
    lastFrame = now;<br />
    render(scene, localTime);<br />
  }<br />
  delay(1);<br />
}</code></div></div>arduino ino file download: <br />
[attachment=10732]<br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10733" target="_blank" title="">11-CO16_Cyber_Show.ino.merged.zip</a> (Size: 218.48 KB / Downloads: 9)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10734" target="_blank" title="">CO16_Cyber_Show-1.JPG</a> (Size: 347.55 KB / Downloads: 19)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10735" target="_blank" title="">CO16_Cyber_Show-2.JPG</a> (Size: 486.6 KB / Downloads: 21)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10736" target="_blank" title="">CO16_Cyber_Show-3.JPG</a> (Size: 553.72 KB / Downloads: 20)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10737" target="_blank" title="">CO16_Cyber_Show-4.JPG</a> (Size: 371.06 KB / Downloads: 20)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10738" target="_blank" title="">CO16_Cyber_Show-5.JPG</a> (Size: 381.15 KB / Downloads: 19)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 * CO16 CYBER SHOW - standalone Shorts demo, 32-second automatic loop.<br />
 * Target: ESP32-S3, CO16 with PCF8575 relay outputs at 0x22.<br />
 * Display: 320x240 ST7789; native SPI3 initialization from CO16-pcb-test.ino.<br />
 * Install Adafruit GFX Library and its Adafruit BusIO dependency.<br />
 * Board: ESP32S3 Dev Module; retain the settings used for the factory sketch.<br />
 * No PCF8575, Ethernet, SD, RTC or ADC libraries required.<br />
 *<br />
 * IMPORTANT: disconnect all loads from relay terminals before filming.<br />
 * These are mechanical relays: use this as a short filming demo, not a<br />
 * continuous endurance test. The graphics are synthetic, not sensor data.<br />
 * Relay tiles show acknowledged output commands, NOT contact feedback.<br />
 *<br />
 * Serial Monitor, 115200 baud (no line ending needed):<br />
 *   0 = automatic show from beginning; 1..5 = hold a scene<br />
 *   p = pause/resume (pause switches relays OFF); r = restart auto show<br />
 *   x = disable relay motion; e = enable relay motion<br />
 * Frame rendering uses 153600 bytes of RAM plus a 6400-byte DMA buffer.<br />
 * No EEPROM writes, networking, SD operations or other peripheral tests.<br />
 */<br />
#include &lt;Arduino.h&gt;<br />
#include &lt;Wire.h&gt;<br />
#include &lt;math.h&gt;<br />
#include &lt;string.h&gt;<br />
<br />
#include &lt;Adafruit_GFX.h&gt;<br />
#include &lt;driver/gpio.h&gt;<br />
#include &lt;driver/spi_master.h&gt;<br />
#include &lt;esp_heap_caps.h&gt;<br />
<br />
<br />
// CO16 ST7789 (GMT020-02-7P) pin mapping from kcs_co16.h.<br />
constexpr gpio_num_t kTftSclk = GPIO_NUM_11;<br />
constexpr gpio_num_t kTftMosi = GPIO_NUM_10;<br />
constexpr gpio_num_t kTftMiso = GPIO_NUM_12;<br />
constexpr gpio_num_t kTftCs = GPIO_NUM_4;<br />
constexpr gpio_num_t kTftDc = GPIO_NUM_0;<br />
constexpr gpio_num_t kTftReset = GPIO_NUM_5;<br />
constexpr gpio_num_t kTftBacklight = GPIO_NUM_40;<br />
<br />
constexpr int16_t kDisplayWidth = 320;<br />
constexpr int16_t kDisplayHeight = 240;<br />
constexpr int16_t kTransferChunkHeight = 10;<br />
constexpr uint32_t kDisplaySpiFrequency = 20 * 1000 * 1000;<br />
constexpr uint8_t kCo16Madctl = 0x68;  // MX | MV | BGR.<br />
<br />
constexpr uint16_t kBlack = 0x0000;<br />
constexpr uint16_t kBlue = 0x001F;<br />
constexpr uint16_t kRed = 0xF800;<br />
constexpr uint16_t kGreen = 0x07E0;<br />
constexpr uint16_t kCyan = 0x07FF;<br />
constexpr uint16_t kMagenta = 0xF81F;<br />
constexpr uint16_t kYellow = 0xFFE0;<br />
constexpr uint16_t kWhite = 0xFFFF;<br />
<br />
class Co16St7789 : public Adafruit_GFX {<br />
 public:<br />
  Co16St7789() : Adafruit_GFX(kDisplayWidth, kDisplayHeight) {}<br />
<br />
  bool begin() {<br />
    gpio_reset_pin(kTftCs);<br />
    gpio_set_direction(kTftCs, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftCs, 1);<br />
<br />
    gpio_reset_pin(kTftDc);<br />
    gpio_set_direction(kTftDc, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftDc, 1);<br />
<br />
    gpio_reset_pin(kTftBacklight);<br />
    gpio_set_direction(kTftBacklight, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftBacklight, 0);<br />
<br />
    spi_bus_config_t busConfig = {};<br />
    busConfig.sclk_io_num = kTftSclk;<br />
    busConfig.mosi_io_num = kTftMosi;<br />
    busConfig.miso_io_num = kTftMiso;<br />
    busConfig.quadwp_io_num = -1;<br />
    busConfig.quadhd_io_num = -1;<br />
    busConfig.max_transfer_sz =<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t);<br />
<br />
    esp_err_t err =<br />
        spi_bus_initialize(SPI3_HOST, &amp;busConfig, SPI_DMA_CH_AUTO);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("SPI3 initialization failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    spi_device_interface_config_t deviceConfig = {};<br />
    deviceConfig.clock_speed_hz = kDisplaySpiFrequency;<br />
    deviceConfig.mode = 0;<br />
    deviceConfig.spics_io_num = -1;<br />
    deviceConfig.queue_size = 1;<br />
<br />
    err = spi_bus_add_device(SPI3_HOST, &amp;deviceConfig, &amp;spiDevice_);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI device setup failed: %s&#92;n",<br />
                    esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    drawBuffer_ = static_cast&lt;uint8_t*&gt;(heap_caps_malloc(<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t),<br />
        MALLOC_CAP_DMA));<br />
    if (drawBuffer_ == nullptr) {<br />
      Serial.println("ST7789 DMA buffer allocation failed");<br />
      return false;<br />
    }<br />
<br />
    // GPIO5 is shared with the optional LoRa module. Match the working KCS<br />
    // firmware and issue one reset pulse before initializing the controller.<br />
    gpio_reset_pin(kTftReset);<br />
    gpio_set_direction(kTftReset, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 0);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(10);<br />
<br />
    if (!initializeController()) {<br />
      return false;<br />
    }<br />
<br />
    fillScreen(kBlack);<br />
    delay(120);<br />
    if (!writeCommand(0x29)) {  // Display on.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    gpio_set_level(kTftBacklight, 1);<br />
    initialized_ = true;<br />
    return true;<br />
  }<br />
<br />
  void drawPixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void writePixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void fillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                uint16_t color) override {<br />
    if (x &lt; 0) {<br />
      width += x;<br />
      x = 0;<br />
    }<br />
    if (y &lt; 0) {<br />
      height += y;<br />
      y = 0;<br />
    }<br />
    if (x + width &gt; kDisplayWidth) {<br />
      width = kDisplayWidth - x;<br />
    }<br />
    if (y + height &gt; kDisplayHeight) {<br />
      height = kDisplayHeight - y;<br />
    }<br />
    if (drawBuffer_ == nullptr || width &lt;= 0 || height &lt;= 0) {<br />
      return;<br />
    }<br />
<br />
    const uint8_t high = color &gt;&gt; 8;<br />
    const uint8_t low = color;<br />
    for (int16_t row = y; row &lt; y + height;<br />
         row += kTransferChunkHeight) {<br />
      const int16_t chunkHeight =<br />
          min&lt;int16_t&gt;(kTransferChunkHeight, y + height - row);<br />
      const size_t pixelCount = width * chunkHeight;<br />
      for (size_t index = 0; index &lt; pixelCount; ++index) {<br />
        drawBuffer_[index * 2] = high;<br />
        drawBuffer_[index * 2 + 1] = low;<br />
      }<br />
<br />
      if (!setWindow(x, row, x + width, row + chunkHeight) ||<br />
          !writePixels(drawBuffer_, pixelCount * sizeof(uint16_t))) {<br />
        Serial.println("ST7789 pixel transfer failed");<br />
        return;<br />
      }<br />
    }<br />
  }<br />
<br />
  void writeFillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                     uint16_t color) override {<br />
    fillRect(x, y, width, height, color);<br />
  }<br />
<br />
  // Copy a fully rendered RGB565 frame into the DMA staging buffer.<br />
  bool present(const uint16_t* pixels) {<br />
    if (!initialized_ || pixels == nullptr) return false;<br />
    for (int y = 0; y &lt; kDisplayHeight; y += kTransferChunkHeight) {<br />
      const int rows = min&lt;int&gt;(kTransferChunkHeight, kDisplayHeight - y);<br />
      const int count = kDisplayWidth * rows;<br />
      for (int i = 0; i &lt; count; ++i) {<br />
        uint16_t c = pixels[y * kDisplayWidth + i];<br />
        drawBuffer_[2 * i] = c &gt;&gt; 8;<br />
        drawBuffer_[2 * i + 1] = c &amp; 0xFF;<br />
      }<br />
      if (!setWindow(0, y, kDisplayWidth, y + rows) ||<br />
          !writePixels(drawBuffer_, count * 2)) return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool initialized() const { return initialized_; }<br />
<br />
 private:<br />
  bool transmit(const void* data, size_t length, int dcLevel) {<br />
    if (data == nullptr || length == 0) {<br />
      return true;<br />
    }<br />
<br />
    gpio_set_level(kTftDc, dcLevel);<br />
    spi_transaction_t transaction = {};<br />
    transaction.length = length * 8;<br />
    transaction.tx_buffer = data;<br />
    const esp_err_t err = spi_device_polling_transmit(spiDevice_, &amp;transaction);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI transfer failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool writeCommand(uint8_t command) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool writeCommandData(uint8_t command, const uint8_t* data, size_t length) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0) &amp;&amp;<br />
                         transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool setWindow(int16_t x1, int16_t y1, int16_t x2, int16_t y2) {<br />
    const uint8_t columns[] = {<br />
        static_cast&lt;uint8_t&gt;(x1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x1),<br />
        static_cast&lt;uint8_t&gt;((x2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x2 - 1),<br />
    };<br />
    const uint8_t rows[] = {<br />
        static_cast&lt;uint8_t&gt;(y1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y1),<br />
        static_cast&lt;uint8_t&gt;((y2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y2 - 1),<br />
    };<br />
    return writeCommandData(0x2A, columns, sizeof(columns)) &amp;&amp;<br />
           writeCommandData(0x2B, rows, sizeof(rows));<br />
  }<br />
<br />
  bool writePixels(const uint8_t* data, size_t length) {<br />
    constexpr uint8_t kRamWrite = 0x2C;<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;kRamWrite, sizeof(kRamWrite), 0) &amp;&amp;<br />
                         transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool initializeController() {<br />
    const uint8_t madctl[] = {kCo16Madctl};<br />
    const uint8_t displayFunction[] = {0x0A, 0x82};<br />
    const uint8_t pixelFormat[] = {0x55};<br />
    const uint8_t porchControl[] = {0x0C, 0x0C, 0x00, 0x33, 0x33};<br />
    const uint8_t gateControl[] = {0x35};<br />
    const uint8_t vcom[] = {0x28};<br />
    const uint8_t lcmControl[] = {0x0C};<br />
    const uint8_t vdvVrhEnable[] = {0x01, 0xFF};<br />
    const uint8_t vrh[] = {0x10};<br />
    const uint8_t vdv[] = {0x20};<br />
    const uint8_t frameRate[] = {0x0F};<br />
    const uint8_t powerControl[] = {0xA4, 0xA1};<br />
    const uint8_t positiveGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32,<br />
        0x44, 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17,<br />
    };<br />
    const uint8_t negativeGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31,<br />
        0x54, 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E,<br />
    };<br />
<br />
    if (!writeCommand(0x11)) {  // Sleep out.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    return writeCommand(0x13) &amp;&amp;<br />
           writeCommandData(0x36, madctl, sizeof(madctl)) &amp;&amp;<br />
           writeCommandData(0xB6, displayFunction, sizeof(displayFunction)) &amp;&amp;<br />
           writeCommandData(0x3A, pixelFormat, sizeof(pixelFormat)) &amp;&amp;<br />
           (delay(10), true) &amp;&amp;<br />
           writeCommandData(0xB2, porchControl, sizeof(porchControl)) &amp;&amp;<br />
           writeCommandData(0xB7, gateControl, sizeof(gateControl)) &amp;&amp;<br />
           writeCommandData(0xBB, vcom, sizeof(vcom)) &amp;&amp;<br />
           writeCommandData(0xC0, lcmControl, sizeof(lcmControl)) &amp;&amp;<br />
           writeCommandData(0xC2, vdvVrhEnable, sizeof(vdvVrhEnable)) &amp;&amp;<br />
           writeCommandData(0xC3, vrh, sizeof(vrh)) &amp;&amp;<br />
           writeCommandData(0xC4, vdv, sizeof(vdv)) &amp;&amp;<br />
           writeCommandData(0xC6, frameRate, sizeof(frameRate)) &amp;&amp;<br />
           writeCommandData(0xD0, powerControl, sizeof(powerControl)) &amp;&amp;<br />
           writeCommandData(0xE0, positiveGamma, sizeof(positiveGamma)) &amp;&amp;<br />
           writeCommandData(0xE1, negativeGamma, sizeof(negativeGamma)) &amp;&amp;<br />
           writeCommand(0x21);  // Display inversion on.<br />
  }<br />
<br />
  spi_device_handle_t spiDevice_ = nullptr;<br />
  uint8_t* drawBuffer_ = nullptr;<br />
  bool initialized_ = false;<br />
};<br />
<br />
Co16St7789 display;<br />
GFXcanvas16 canvas(320, 240);<br />
constexpr uint32_t FRAME_MS = 80;   // Target ~12.5 FPS; actual rate depends on SPI.<br />
constexpr uint32_t RELAY_STEP_MS = 250;<br />
constexpr uint32_t SHOW_MS = 32000;<br />
constexpr uint8_t RELAY_ADDRESS = 0x22;<br />
constexpr float TAU = 6.28318530718f;<br />
constexpr uint16_t BG = 0x0001, GRID = 0x0843, CYAN = 0x07FF;<br />
constexpr uint16_t PINK = 0xF81F, WHITE = 0xFFFF, DIM = 0x5B6D;<br />
constexpr uint16_t GREEN = 0x07E0, GOLD = 0xFDE0;<br />
const char* const SCENE_NAMES[] = {<br />
  "SYSTEM BOOT", "RELAY RADAR", "ENERGY CORE", "BEAT MATRIX", "ONE BRAIN"<br />
};<br />
uint32_t showTime = 0, lastTick = 0, lastFrame = 0, lastRelay = 0;<br />
uint16_t relayMask = 0;<br />
bool relayEnabled = true, paused = false, relayFault = false;<br />
int manualScene = -1;<br />
<br />
// A single two-byte write changes all 16 PCF8575 outputs together.<br />
// P0 is channel 1. HIGH = OFF, LOW = ON, as in the supplied test.<br />
bool writeRelays(uint16_t mask) {<br />
  uint16_t raw = uint16_t(~mask);<br />
  Wire.beginTransmission(RELAY_ADDRESS);<br />
  Wire.write(uint8_t(raw));<br />
  Wire.write(uint8_t(raw &gt;&gt; 8));<br />
  bool ok = Wire.endTransmission() == 0;<br />
  if (ok) relayMask = mask;<br />
  return ok;<br />
}<br />
<br />
uint16_t channelBit(int i) { return uint16_t(1u &lt;&lt; (i &amp; 15)); }<br />
<br />
int sceneAt(uint32_t t) {<br />
  if (t &lt; 4000) return 0;<br />
  if (t &lt; 12000) return 1;<br />
  if (t &lt; 20000) return 2;<br />
  if (t &lt; 28000) return 3;<br />
  return 4;<br />
}<br />
uint32_t sceneStart(int s) {<br />
  const uint32_t starts[] = {0, 4000, 12000, 20000, 28000};<br />
  return starts[s];<br />
}<br />
<br />
uint16_t patternFor(int s, uint32_t t) {<br />
  uint32_t step = t / RELAY_STEP_MS;<br />
  switch (s) {<br />
    case 0: return channelBit(step % 16);<br />
    case 1: return channelBit(step % 16);<br />
    case 2: {<br />
      int p = step % 14;<br />
      if (p &gt; 7) p = 14 - p;<br />
      return channelBit(p) | channelBit(15 - p);<br />
    }<br />
    case 3: {<br />
      const uint16_t beats[] = {0x1111, 0, 0x2222, 0, 0x4444, 0, 0x8888, 0};<br />
      return beats[step % 8];<br />
    }<br />
    default: return t &lt; 3000 ? channelBit(15 - (step % 16)) : 0;<br />
  }<br />
}<br />
<br />
void label(int x, int y, const char* text, int size, uint16_t color) {<br />
  canvas.setTextSize(size);<br />
  canvas.setTextColor(color);<br />
  canvas.setCursor(x, y);<br />
  canvas.print(text);<br />
}<br />
void center(int y, const char* text, int size, uint16_t color) {<br />
  label((320 - int(strlen(text)) * 6 * size) / 2, y, text, size, color);<br />
}<br />
<br />
void background(uint32_t t) {<br />
  canvas.fillScreen(BG);<br />
  for (int x = 0; x &lt; 320; x += 20) canvas.drawFastVLine(x, 30, 158, GRID);<br />
  for (int y = 30; y &lt; 188; y += 20) canvas.drawFastHLine(0, y, 320, GRID);<br />
  // Deterministic drifting stars, without allocations or random-state changes.<br />
  for (int i = 0; i &lt; 36; ++i) {<br />
    int x = (i * 73 + t / (15 + i % 7)) % 320;<br />
    int y = 34 + (i * 47) % 149;<br />
    canvas.drawPixel(x, y, i % 3 ? DIM : CYAN);<br />
  }<br />
}<br />
<br />
void drawBoot(uint32_t t) {<br />
  center(42, "KinCony", 4, CYAN);<br />
  center(80, "CO16", 3, WHITE);<br />
  center(112, "INITIALIZING THE SHOW", 1, DIM);<br />
  canvas.drawRoundRect(36, 132, 248, 18, 4, CYAN);<br />
  int progress = int((t % 4000) * 242 / 3999);<br />
  canvas.fillRect(39, 135, progress, 12, PINK);<br />
  char line[32];<br />
  snprintf(line, sizeof(line), "16 CHANNELS / %02lu%%", (unsigned long)((t % 4000) * 100 / 3999));<br />
  center(163, line, 1, WHITE);<br />
}<br />
<br />
void drawRadar(uint32_t t) {<br />
  const int cx = 96, cy = 108;<br />
  for (int r = 20; r &lt;= 64; r += 22) canvas.drawCircle(cx, cy, r, 0x0329);<br />
  canvas.drawFastHLine(29, cy, 135, 0x0329);<br />
  canvas.drawFastVLine(cx, 41, 135, 0x0329);<br />
  float angle = (t % 4000) * TAU / 4000.0f - TAU / 4;<br />
  for (int j = 12; j &gt;= 0; --j) {<br />
    float a = angle - j * 0.035f;<br />
    canvas.drawLine(cx, cy, cx + int(cosf(a) * 63), cy + int(sinf(a) * 63),<br />
                    j == 0 ? CYAN : uint16_t((12 - j + 3) &lt;&lt; 5));<br />
  }<br />
  for (int i = 0; i &lt; 16; ++i) {<br />
    float a = i * TAU / 16 - TAU / 4;<br />
    int x = cx + int(cosf(a) * 54), y = cy + int(sinf(a) * 54);<br />
    bool on = relayMask &amp; channelBit(i);<br />
    canvas.fillCircle(x, y, on ? 5 : 2, on ? GOLD : DIM);<br />
    if (on) canvas.drawCircle(x, y, 8, CYAN);<br />
  }<br />
  label(181, 51, "SCAN", 3, CYAN);<br />
  label(182, 83, "16 RELAY NODES", 1, WHITE);<br />
  char buf[16];<br />
  snprintf(buf, sizeof(buf), "CH %02lu", (unsigned long)((t / RELAY_STEP_MS) % 16 + 1));<br />
  label(182, 110, buf, 2, GOLD);<br />
  label(182, 144, "COMMAND TRACKER", 1, DIM);<br />
  label(182, 160, "250ms / STEP", 1, DIM);<br />
}<br />
<br />
void drawCore(uint32_t t) {<br />
  float phase = t * 0.002f;<br />
  for (int i = 0; i &lt; 4; ++i) {<br />
    int r = 24 + i * 13;<br />
    for (int k = 0; k &lt; 46; ++k) {<br />
      float a = phase * (i % 2 ? -1 : 1) + k * TAU / 60;<br />
      float b = a + TAU / 65;<br />
      canvas.drawLine(160 + int(cosf(a) * r), 109 + int(sinf(a) * r),<br />
                      160 + int(cosf(b) * r), 109 + int(sinf(b) * r), i % 2 ? PINK : CYAN);<br />
    }<br />
  }<br />
  canvas.fillCircle(160, 109, 18 + int(3 * sinf(phase * 3)), 0x0410);<br />
  label(148, 102, "16", 2, WHITE);<br />
  for (int i = 0; i &lt; 8; ++i) {<br />
    int h = 5 + int((sinf(phase * 2 + i * 0.6f) + 1) * 24);<br />
    canvas.fillRect(14 + i * 6, 139 - h, 4, h, CYAN);<br />
    canvas.fillRect(260 + i * 6, 139 - h, 4, h, PINK);<br />
  }<br />
  label(12, 155, "CH 1-8", 1, DIM);<br />
  label(255, 155, "CH 9-16", 1, DIM);<br />
  center(176, "DUAL-WAVE SYNCHRONIZATION", 1, WHITE);<br />
}<br />
<br />
void drawBeat(uint32_t t) {<br />
  // Synthetic frequency bars. No microphone, FFT, or audio synchronization.<br />
  float phase = t * 0.004f;<br />
  for (int i = 0; i &lt; 16; ++i) {<br />
    float v = (sinf(phase + i * 0.67f) + sinf(phase * 0.63f - i * 0.41f) + 2) / 4;<br />
    int levels = 2 + int(v * 14);<br />
    for (int j = 0; j &lt; levels; ++j)<br />
      canvas.fillRect(9 + i * 19, 166 - j * 6, 13, 4, j &gt; 11 ? PINK : (j &gt; 6 ? CYAN : 0x0410));<br />
  }<br />
  center(43, "BEAT MATRIX", 2, WHITE);<br />
  center(65, "120 BPM / SYNTHETIC VISUALIZER", 1, DIM);<br />
  canvas.fillCircle(299, 45, 4, (t % 500 &lt; 250) ? GOLD : DIM);<br />
}<br />
<br />
void drawFinale(uint32_t t) {<br />
  for (int i = 0; i &lt; 32; ++i) {<br />
    float a = i * TAU / 32;<br />
    int r = 20 + (int(t / 12) + i * 17) % 130;<br />
    canvas.drawLine(160 + int(cosf(a) * r), 108 + int(sinf(a) * r * 0.50f),<br />
                    160 + int(cosf(a) * (r + 7)), 108 + int(sinf(a) * (r + 7) * 0.50f), GRID);<br />
  }<br />
  center(56, "16 CHANNELS", 3, CYAN);<br />
  center(94, "ONE BRAIN.", 3, WHITE);<br />
  center(136, "KinCony CO16 / ESP32-S3", 1, PINK);<br />
  center(165, "www.kincony.com", 1, DIM);<br />
}<br />
<br />
void render(int scene, uint32_t t) {<br />
  background(t);<br />
  switch (scene) {<br />
    case 0: drawBoot(t); break;<br />
    case 1: drawRadar(t); break;<br />
    case 2: drawCore(t); break;<br />
    case 3: drawBeat(t); break;<br />
    default: drawFinale(t); break;<br />
  }<br />
  canvas.fillRect(0, 0, 320, 29, BG);<br />
  label(8, 9, "CO16", 2, CYAN);<br />
  label(80, 12, SCENE_NAMES[scene], 1, WHITE);<br />
  label(271, 12, manualScene &lt; 0 ? "AUTO" : "HOLD", 1, PINK);<br />
  canvas.drawFastHLine(8, 28, 304, CYAN);<br />
  // Two rows of numbered output tiles, all drawn from the last ACKed command.<br />
  canvas.fillRect(0, 190, 320, 50, BG);<br />
  for (int i = 0; i &lt; 16; ++i) {<br />
    int x = 8 + (i % 8) * 39, y = 191 + (i / 8) * 18;<br />
    bool on = relayMask &amp; channelBit(i);<br />
    canvas.fillRoundRect(x, y, 32, 15, 3, on ? CYAN : GRID);<br />
    char number[4];<br />
    snprintf(number, sizeof(number), "%02d", i + 1);<br />
    label(x + 10, y + 4, number, 1, on ? BG : DIM);<br />
  }<br />
  const char* status = relayFault ? "I2C ERROR / OUTPUT UNKNOWN" :<br />
    paused ? "PAUSED / RELAYS OFF" : !relayEnabled ? "SCREEN ONLY / RELAYS OFF" : "RELAY COMMANDS / VISUAL DEMO";<br />
  center(231, status, 1, relayFault ? GOLD : DIM);<br />
  if (!display.present(canvas.getBuffer())) {<br />
    writeRelays(0);<br />
    Serial.println("Display transfer failed. Demo halted; OFF command attempted.");<br />
    while (true) { writeRelays(0); delay(1000); }<br />
  }<br />
}<br />
<br />
void handleSerial() {<br />
  while (Serial.available()) {<br />
    char c = Serial.read();<br />
    if (c == 'p') paused = !paused;<br />
    if (c == 'x') relayEnabled = false;<br />
    if (c == 'e') relayEnabled = true;<br />
    if (c == '0' || c == 'r') { manualScene = -1; showTime = 0; paused = false; }<br />
    if (c &gt;= '1' &amp;&amp; c &lt;= '5') { manualScene = c - '1'; showTime = 0; paused = false; }<br />
    if (c == 'p' || c == 'x' || c == '0' || c == 'r' || (c &gt;= '1' &amp;&amp; c &lt;= '5')) {<br />
      if (!writeRelays(0)) relayFault = true;<br />
      lastRelay = millis();<br />
    }<br />
  }<br />
}<br />
<br />
void setup() {<br />
  Serial.begin(115200);<br />
  Wire.begin(8, 18);<br />
  Wire.setClock(100000);<br />
  Wire.setTimeOut(20);<br />
  relayFault = !writeRelays(0);<br />
  // Deselect other devices sharing the TFT wires BEFORE enabling SPI3.<br />
  // Do not initialize SD, LoRa, or software SPI on this bus in this demo.<br />
  const int inactiveCs[] = {9, 13, 14, 42};<br />
  for (int pin : inactiveCs) { digitalWrite(pin, HIGH); pinMode(pin, OUTPUT); }<br />
  if (!canvas.getBuffer() || !display.begin()) {<br />
    Serial.println("Display/buffer initialization failed. Demo halted.");<br />
    while (true) { writeRelays(0); delay(1000); }<br />
  }<br />
  canvas.setTextWrap(false);<br />
  Serial.println("CO16 CYBER SHOW: 0=auto 1..5=scene p=pause x=relays off e=relays on r=restart");<br />
  lastTick = lastRelay = millis();<br />
}<br />
<br />
void loop() {<br />
  handleSerial();<br />
  uint32_t now = millis();<br />
  uint32_t dt = now - lastTick;<br />
  lastTick = now;<br />
  if (!paused) showTime = (showTime + dt) % SHOW_MS;<br />
  int scene = manualScene &lt; 0 ? sceneAt(showTime) : manualScene;<br />
  uint32_t localTime = manualScene &lt; 0 ? showTime - sceneStart(scene) : showTime;<br />
  // Limit real relay updates independently of display rendering.<br />
  if (now - lastRelay &gt;= RELAY_STEP_MS) {<br />
    lastRelay = now;<br />
    uint16_t wanted = (paused || !relayEnabled || relayFault) ? 0 : patternFor(scene, localTime);<br />
    if (relayFault || wanted != relayMask) {<br />
      if (!writeRelays(wanted)) {<br />
        relayFault = true;  // Latched until reset. Retry OFF, never resume motion.<br />
        Serial.println("Relay I2C error: motion disabled; retrying OFF.");<br />
        writeRelays(0);<br />
      }<br />
    }<br />
  }<br />
  if (now - lastFrame &gt;= FRAME_MS) {<br />
    lastFrame = now;<br />
    render(scene, localTime);<br />
  }<br />
  delay(1);<br />
}</code></div></div>arduino ino file download: <br />
[attachment=10732]<br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10733" target="_blank" title="">11-CO16_Cyber_Show.ino.merged.zip</a> (Size: 218.48 KB / Downloads: 9)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10734" target="_blank" title="">CO16_Cyber_Show-1.JPG</a> (Size: 347.55 KB / Downloads: 19)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10735" target="_blank" title="">CO16_Cyber_Show-2.JPG</a> (Size: 486.6 KB / Downloads: 21)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10736" target="_blank" title="">CO16_Cyber_Show-3.JPG</a> (Size: 553.72 KB / Downloads: 20)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10737" target="_blank" title="">CO16_Cyber_Show-4.JPG</a> (Size: 371.06 KB / Downloads: 20)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10738" target="_blank" title="">CO16_Cyber_Show-5.JPG</a> (Size: 381.15 KB / Downloads: 19)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[Questions on CO16]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9828</link>
			<pubDate>Sun, 13 Sep 2026 17:38:48 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=11770">philj77</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9828</guid>
			<description><![CDATA[Hi,<br />
<br />
I have several questions on CO16 that I would like to use to replace my alarm system :-)<br />
<br />
1/ Are there free GPIO that can be accessed (I need to add Wiegand input)<br />
2/ Is there a socket to expand I2C bus ? <br />
3/ Which GPIO are used for KNX ? <br />
4/ With ESPHome can we send KNX messages from CO16<br />
5/ Regarding the SIM7600E expansion, what is the wiring (I already have such a module and I would like to know if it will be compatible) <br />
6/ Any idea on how to generate sound for a remote loundspeaker (the one that tells alarm in service / door left open / ....) ?<br />
7/ Is there any electronic between GPIO38/39 and RS485 socket ?<br />
<br />
Regards<br />
Philippe]]></description>
			<content:encoded><![CDATA[Hi,<br />
<br />
I have several questions on CO16 that I would like to use to replace my alarm system :-)<br />
<br />
1/ Are there free GPIO that can be accessed (I need to add Wiegand input)<br />
2/ Is there a socket to expand I2C bus ? <br />
3/ Which GPIO are used for KNX ? <br />
4/ With ESPHome can we send KNX messages from CO16<br />
5/ Regarding the SIM7600E expansion, what is the wiring (I already have such a module and I would like to know if it will be compatible) <br />
6/ Any idea on how to generate sound for a remote loundspeaker (the one that tells alarm in service / door left open / ....) ?<br />
7/ Is there any electronic between GPIO38/39 and RS485 socket ?<br />
<br />
Regards<br />
Philippe]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[KNX ETS software project file for CO16]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9809</link>
			<pubDate>Tue, 08 Sep 2026 11:44:37 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9809</guid>
			<description><![CDATA[<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10483" target="_blank" title="">KNX-test.jpg</a> (Size: 191.09 KB / Downloads: 129)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10481" target="_blank" title="">ETS.png</a> (Size: 163.54 KB / Downloads: 143)
<!-- end: postbit_attachments_attachment --><br />
ETS project download:<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10482" target="_blank" title="">CO16.zip</a> (Size: 103.09 KB / Downloads: 126)
<!-- end: postbit_attachments_attachment --><br />
<br />
<iframe width="560" height="315" src="//www.youtube-nocookie.com/embed/hIavN5e9Ais" frameborder="0" allowfullscreen="true"></iframe>]]></description>
			<content:encoded><![CDATA[<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10483" target="_blank" title="">KNX-test.jpg</a> (Size: 191.09 KB / Downloads: 129)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10481" target="_blank" title="">ETS.png</a> (Size: 163.54 KB / Downloads: 143)
<!-- end: postbit_attachments_attachment --><br />
ETS project download:<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10482" target="_blank" title="">CO16.zip</a> (Size: 103.09 KB / Downloads: 126)
<!-- end: postbit_attachments_attachment --><br />
<br />
<iframe width="560" height="315" src="//www.youtube-nocookie.com/embed/hIavN5e9Ais" frameborder="0" allowfullscreen="true"></iframe>]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-10 ST7789 TFT color display]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9791</link>
			<pubDate>Mon, 31 Aug 2026 08:16:23 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9791</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;Adafruit_GFX.h&gt;<br />
#include &lt;driver/gpio.h&gt;<br />
#include &lt;driver/spi_master.h&gt;<br />
#include &lt;esp_heap_caps.h&gt;<br />
<br />
namespace {<br />
<br />
// CO16 ST7789 (GMT020-02-7P) pin mapping from kcs_co16.h.<br />
constexpr gpio_num_t kTftSclk = GPIO_NUM_11;<br />
constexpr gpio_num_t kTftMosi = GPIO_NUM_10;<br />
constexpr gpio_num_t kTftMiso = GPIO_NUM_12;<br />
constexpr gpio_num_t kTftCs = GPIO_NUM_4;<br />
constexpr gpio_num_t kTftDc = GPIO_NUM_0;<br />
constexpr gpio_num_t kTftReset = GPIO_NUM_5;<br />
constexpr gpio_num_t kTftBacklight = GPIO_NUM_40;<br />
<br />
constexpr int16_t kDisplayWidth = 320;<br />
constexpr int16_t kDisplayHeight = 240;<br />
constexpr int16_t kTransferChunkHeight = 10;<br />
constexpr uint32_t kDisplaySpiFrequency = 20 * 1000 * 1000;<br />
constexpr uint8_t kCo16Madctl = 0x68;  // MX | MV | BGR.<br />
<br />
constexpr uint16_t kBlack = 0x0000;<br />
constexpr uint16_t kBlue = 0x001F;<br />
constexpr uint16_t kRed = 0xF800;<br />
constexpr uint16_t kGreen = 0x07E0;<br />
constexpr uint16_t kCyan = 0x07FF;<br />
constexpr uint16_t kMagenta = 0xF81F;<br />
constexpr uint16_t kYellow = 0xFFE0;<br />
constexpr uint16_t kWhite = 0xFFFF;<br />
<br />
class Co16St7789 : public Adafruit_GFX {<br />
 public:<br />
  Co16St7789() : Adafruit_GFX(kDisplayWidth, kDisplayHeight) {}<br />
<br />
  bool begin() {<br />
    gpio_reset_pin(kTftCs);<br />
    gpio_set_direction(kTftCs, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftCs, 1);<br />
<br />
    gpio_reset_pin(kTftDc);<br />
    gpio_set_direction(kTftDc, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftDc, 1);<br />
<br />
    gpio_reset_pin(kTftBacklight);<br />
    gpio_set_direction(kTftBacklight, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftBacklight, 0);<br />
<br />
    spi_bus_config_t busConfig = {};<br />
    busConfig.sclk_io_num = kTftSclk;<br />
    busConfig.mosi_io_num = kTftMosi;<br />
    busConfig.miso_io_num = kTftMiso;<br />
    busConfig.quadwp_io_num = -1;<br />
    busConfig.quadhd_io_num = -1;<br />
    busConfig.max_transfer_sz =<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t);<br />
<br />
    esp_err_t err =<br />
        spi_bus_initialize(SPI3_HOST, &amp;busConfig, SPI_DMA_CH_AUTO);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("SPI3 initialization failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    spi_device_interface_config_t deviceConfig = {};<br />
    deviceConfig.clock_speed_hz = kDisplaySpiFrequency;<br />
    deviceConfig.mode = 0;<br />
    deviceConfig.spics_io_num = -1;<br />
    deviceConfig.queue_size = 1;<br />
<br />
    err = spi_bus_add_device(SPI3_HOST, &amp;deviceConfig, &amp;spiDevice_);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI device setup failed: %s&#92;n",<br />
                    esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    drawBuffer_ = static_cast&lt;uint8_t*&gt;(heap_caps_malloc(<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t),<br />
        MALLOC_CAP_DMA));<br />
    if (drawBuffer_ == nullptr) {<br />
      Serial.println("ST7789 DMA buffer allocation failed");<br />
      return false;<br />
    }<br />
<br />
    // GPIO5 is shared with the optional LoRa module. Match the working KCS<br />
    // firmware and issue one reset pulse before initializing the controller.<br />
    gpio_reset_pin(kTftReset);<br />
    gpio_set_direction(kTftReset, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 0);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(10);<br />
<br />
    if (!initializeController()) {<br />
      return false;<br />
    }<br />
<br />
    fillScreen(kBlack);<br />
    delay(120);<br />
    if (!writeCommand(0x29)) {  // Display on.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    gpio_set_level(kTftBacklight, 1);<br />
    initialized_ = true;<br />
    return true;<br />
  }<br />
<br />
  void drawPixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void writePixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void fillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                uint16_t color) override {<br />
    if (x &lt; 0) {<br />
      width += x;<br />
      x = 0;<br />
    }<br />
    if (y &lt; 0) {<br />
      height += y;<br />
      y = 0;<br />
    }<br />
    if (x + width &gt; kDisplayWidth) {<br />
      width = kDisplayWidth - x;<br />
    }<br />
    if (y + height &gt; kDisplayHeight) {<br />
      height = kDisplayHeight - y;<br />
    }<br />
    if (drawBuffer_ == nullptr || width &lt;= 0 || height &lt;= 0) {<br />
      return;<br />
    }<br />
<br />
    const uint8_t high = color &gt;&gt; 8;<br />
    const uint8_t low = color;<br />
    for (int16_t row = y; row &lt; y + height;<br />
       &nbsp;&nbsp;row += kTransferChunkHeight) {<br />
      const int16_t chunkHeight =<br />
          min&lt;int16_t&gt;(kTransferChunkHeight, y + height - row);<br />
      const size_t pixelCount = width * chunkHeight;<br />
      for (size_t index = 0; index &lt; pixelCount; ++index) {<br />
        drawBuffer_[index * 2] = high;<br />
        drawBuffer_[index * 2 + 1] = low;<br />
      }<br />
<br />
      if (!setWindow(x, row, x + width, row + chunkHeight) ||<br />
          !writePixels(drawBuffer_, pixelCount * sizeof(uint16_t))) {<br />
        Serial.println("ST7789 pixel transfer failed");<br />
        return;<br />
      }<br />
    }<br />
  }<br />
<br />
  void writeFillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                   &nbsp;&nbsp;uint16_t color) override {<br />
    fillRect(x, y, width, height, color);<br />
  }<br />
<br />
  bool initialized() const { return initialized_; }<br />
<br />
 private:<br />
  bool transmit(const void* data, size_t length, int dcLevel) {<br />
    if (data == nullptr || length == 0) {<br />
      return true;<br />
    }<br />
<br />
    gpio_set_level(kTftDc, dcLevel);<br />
    spi_transaction_t transaction = {};<br />
    transaction.length = length * 8;<br />
    transaction.tx_buffer = data;<br />
    const esp_err_t err = spi_device_polling_transmit(spiDevice_, &amp;transaction);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI transfer failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool writeCommand(uint8_t command) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool writeCommandData(uint8_t command, const uint8_t* data, size_t length) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0) &amp;&amp;<br />
                       &nbsp;&nbsp;transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool setWindow(int16_t x1, int16_t y1, int16_t x2, int16_t y2) {<br />
    const uint8_t columns[] = {<br />
        static_cast&lt;uint8_t&gt;(x1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x1),<br />
        static_cast&lt;uint8_t&gt;((x2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x2 - 1),<br />
    };<br />
    const uint8_t rows[] = {<br />
        static_cast&lt;uint8_t&gt;(y1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y1),<br />
        static_cast&lt;uint8_t&gt;((y2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y2 - 1),<br />
    };<br />
    return writeCommandData(0x2A, columns, sizeof(columns)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0x2B, rows, sizeof(rows));<br />
  }<br />
<br />
  bool writePixels(const uint8_t* data, size_t length) {<br />
    constexpr uint8_t kRamWrite = 0x2C;<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;kRamWrite, sizeof(kRamWrite), 0) &amp;&amp;<br />
                       &nbsp;&nbsp;transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool initializeController() {<br />
    const uint8_t madctl[] = {kCo16Madctl};<br />
    const uint8_t displayFunction[] = {0x0A, 0x82};<br />
    const uint8_t pixelFormat[] = {0x55};<br />
    const uint8_t porchControl[] = {0x0C, 0x0C, 0x00, 0x33, 0x33};<br />
    const uint8_t gateControl[] = {0x35};<br />
    const uint8_t vcom[] = {0x28};<br />
    const uint8_t lcmControl[] = {0x0C};<br />
    const uint8_t vdvVrhEnable[] = {0x01, 0xFF};<br />
    const uint8_t vrh[] = {0x10};<br />
    const uint8_t vdv[] = {0x20};<br />
    const uint8_t frameRate[] = {0x0F};<br />
    const uint8_t powerControl[] = {0xA4, 0xA1};<br />
    const uint8_t positiveGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32,<br />
        0x44, 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17,<br />
    };<br />
    const uint8_t negativeGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31,<br />
        0x54, 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E,<br />
    };<br />
<br />
    if (!writeCommand(0x11)) {  // Sleep out.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    return writeCommand(0x13) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0x36, madctl, sizeof(madctl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xB6, displayFunction, sizeof(displayFunction)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0x3A, pixelFormat, sizeof(pixelFormat)) &amp;&amp;<br />
         &nbsp;&nbsp;(delay(10), true) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xB2, porchControl, sizeof(porchControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xB7, gateControl, sizeof(gateControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xBB, vcom, sizeof(vcom)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC0, lcmControl, sizeof(lcmControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC2, vdvVrhEnable, sizeof(vdvVrhEnable)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC3, vrh, sizeof(vrh)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC4, vdv, sizeof(vdv)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC6, frameRate, sizeof(frameRate)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xD0, powerControl, sizeof(powerControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xE0, positiveGamma, sizeof(positiveGamma)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xE1, negativeGamma, sizeof(negativeGamma)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommand(0x21);  // Display inversion on.<br />
  }<br />
<br />
  spi_device_handle_t spiDevice_ = nullptr;<br />
  uint8_t* drawBuffer_ = nullptr;<br />
  bool initialized_ = false;<br />
};<br />
<br />
Co16St7789 display;<br />
uint32_t frameCount = 0;<br />
<br />
void drawColorBars() {<br />
  constexpr uint16_t colors[] = {<br />
      kRed,<br />
      kGreen,<br />
      kBlue,<br />
      kCyan,<br />
      kMagenta,<br />
      kYellow,<br />
  };<br />
  constexpr size_t colorCount = sizeof(colors) / sizeof(colors[0]);<br />
  const int barWidth = kDisplayWidth / colorCount;<br />
<br />
  for (size_t index = 0; index &lt; colorCount; ++index) {<br />
    const int x = index * barWidth;<br />
    const int width =<br />
        (index == colorCount - 1) ? kDisplayWidth - x : barWidth;<br />
    display.fillRect(x, 132, width, 44, colors[index]);<br />
  }<br />
}<br />
<br />
void drawStaticScreen() {<br />
  display.fillScreen(kBlack);<br />
  display.drawRect(0, 0, kDisplayWidth, kDisplayHeight, kWhite);<br />
<br />
  display.setTextWrap(false);<br />
  display.setTextColor(kCyan);<br />
  display.setTextSize(3);<br />
  display.setCursor(34, 20);<br />
  display.print("KinCony CO16");<br />
<br />
  display.setTextColor(kWhite);<br />
  display.setTextSize(2);<br />
  display.setCursor(24, 64);<br />
  display.print("ST7789 display test");<br />
  display.setCursor(24, 92);<br />
  display.print("SPI3 native driver");<br />
<br />
  drawColorBars();<br />
<br />
  display.setTextColor(kGreen);<br />
  display.setCursor(24, 194);<br />
  display.print("Frame:");<br />
}<br />
<br />
void updateFrameCounter() {<br />
  display.fillRect(108, 190, 190, 28, kBlack);<br />
  display.setTextColor(kGreen);<br />
  display.setTextSize(2);<br />
  display.setCursor(108, 194);<br />
  display.print(frameCount++);<br />
}<br />
<br />
}  // namespace<br />
<br />
void setup() {<br />
  Serial.begin(115200);<br />
  delay(1000);<br />
<br />
  Serial.println();<br />
  Serial.println("KinCony CO16 standalone ST7789 example");<br />
  Serial.printf("SPI3 SCLK=%d MOSI=%d MISO=%d CS=%d mode=0 clock=%u Hz&#92;n",<br />
                kTftSclk, kTftMosi, kTftMiso, kTftCs,<br />
                kDisplaySpiFrequency);<br />
  Serial.printf("LCD DC=%d RESET=%d BACKLIGHT=%d&#92;n", kTftDc, kTftReset,<br />
                kTftBacklight);<br />
<br />
  if (!display.begin()) {<br />
    Serial.println("Display initialization failed");<br />
    while (true) {<br />
      delay(1000);<br />
    }<br />
  }<br />
<br />
  drawStaticScreen();<br />
  updateFrameCounter();<br />
<br />
  Serial.printf("Display initialized: %dx%d, MADCTL=0x%02X, invert=on&#92;n",<br />
                display.width(), display.height(), kCo16Madctl);<br />
}<br />
<br />
void loop() {<br />
  <br />
  updateFrameCounter();<br />
  delay(1000);<br />
}</code></div></div><!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10356" target="_blank" title="">ST7789.jpg</a> (Size: 262.38 KB / Downloads: 152)
<!-- end: postbit_attachments_attachment --><br />
arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10352" target="_blank" title="">10-TFT-LCD-ST7789.zip</a> (Size: 3.05 KB / Downloads: 132)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10353" target="_blank" title="">10-TFT-LCD-ST7789.ino.merged.zip</a> (Size: 211.33 KB / Downloads: 134)
<!-- end: postbit_attachments_attachment --><br />
before run code , need install these arduino library:<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10354" target="_blank" title="">Adafruit-GFX-Library.png</a> (Size: 122.69 KB / Downloads: 149)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10355" target="_blank" title="">Adafruit-ST7735-and-ST7789-Library.png</a> (Size: 118.05 KB / Downloads: 134)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;Adafruit_GFX.h&gt;<br />
#include &lt;driver/gpio.h&gt;<br />
#include &lt;driver/spi_master.h&gt;<br />
#include &lt;esp_heap_caps.h&gt;<br />
<br />
namespace {<br />
<br />
// CO16 ST7789 (GMT020-02-7P) pin mapping from kcs_co16.h.<br />
constexpr gpio_num_t kTftSclk = GPIO_NUM_11;<br />
constexpr gpio_num_t kTftMosi = GPIO_NUM_10;<br />
constexpr gpio_num_t kTftMiso = GPIO_NUM_12;<br />
constexpr gpio_num_t kTftCs = GPIO_NUM_4;<br />
constexpr gpio_num_t kTftDc = GPIO_NUM_0;<br />
constexpr gpio_num_t kTftReset = GPIO_NUM_5;<br />
constexpr gpio_num_t kTftBacklight = GPIO_NUM_40;<br />
<br />
constexpr int16_t kDisplayWidth = 320;<br />
constexpr int16_t kDisplayHeight = 240;<br />
constexpr int16_t kTransferChunkHeight = 10;<br />
constexpr uint32_t kDisplaySpiFrequency = 20 * 1000 * 1000;<br />
constexpr uint8_t kCo16Madctl = 0x68;  // MX | MV | BGR.<br />
<br />
constexpr uint16_t kBlack = 0x0000;<br />
constexpr uint16_t kBlue = 0x001F;<br />
constexpr uint16_t kRed = 0xF800;<br />
constexpr uint16_t kGreen = 0x07E0;<br />
constexpr uint16_t kCyan = 0x07FF;<br />
constexpr uint16_t kMagenta = 0xF81F;<br />
constexpr uint16_t kYellow = 0xFFE0;<br />
constexpr uint16_t kWhite = 0xFFFF;<br />
<br />
class Co16St7789 : public Adafruit_GFX {<br />
 public:<br />
  Co16St7789() : Adafruit_GFX(kDisplayWidth, kDisplayHeight) {}<br />
<br />
  bool begin() {<br />
    gpio_reset_pin(kTftCs);<br />
    gpio_set_direction(kTftCs, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftCs, 1);<br />
<br />
    gpio_reset_pin(kTftDc);<br />
    gpio_set_direction(kTftDc, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftDc, 1);<br />
<br />
    gpio_reset_pin(kTftBacklight);<br />
    gpio_set_direction(kTftBacklight, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftBacklight, 0);<br />
<br />
    spi_bus_config_t busConfig = {};<br />
    busConfig.sclk_io_num = kTftSclk;<br />
    busConfig.mosi_io_num = kTftMosi;<br />
    busConfig.miso_io_num = kTftMiso;<br />
    busConfig.quadwp_io_num = -1;<br />
    busConfig.quadhd_io_num = -1;<br />
    busConfig.max_transfer_sz =<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t);<br />
<br />
    esp_err_t err =<br />
        spi_bus_initialize(SPI3_HOST, &amp;busConfig, SPI_DMA_CH_AUTO);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("SPI3 initialization failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    spi_device_interface_config_t deviceConfig = {};<br />
    deviceConfig.clock_speed_hz = kDisplaySpiFrequency;<br />
    deviceConfig.mode = 0;<br />
    deviceConfig.spics_io_num = -1;<br />
    deviceConfig.queue_size = 1;<br />
<br />
    err = spi_bus_add_device(SPI3_HOST, &amp;deviceConfig, &amp;spiDevice_);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI device setup failed: %s&#92;n",<br />
                    esp_err_to_name(err));<br />
      return false;<br />
    }<br />
<br />
    drawBuffer_ = static_cast&lt;uint8_t*&gt;(heap_caps_malloc(<br />
        kDisplayWidth * kTransferChunkHeight * sizeof(uint16_t),<br />
        MALLOC_CAP_DMA));<br />
    if (drawBuffer_ == nullptr) {<br />
      Serial.println("ST7789 DMA buffer allocation failed");<br />
      return false;<br />
    }<br />
<br />
    // GPIO5 is shared with the optional LoRa module. Match the working KCS<br />
    // firmware and issue one reset pulse before initializing the controller.<br />
    gpio_reset_pin(kTftReset);<br />
    gpio_set_direction(kTftReset, GPIO_MODE_OUTPUT);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 0);<br />
    delay(1);<br />
    gpio_set_level(kTftReset, 1);<br />
    delay(10);<br />
<br />
    if (!initializeController()) {<br />
      return false;<br />
    }<br />
<br />
    fillScreen(kBlack);<br />
    delay(120);<br />
    if (!writeCommand(0x29)) {  // Display on.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    gpio_set_level(kTftBacklight, 1);<br />
    initialized_ = true;<br />
    return true;<br />
  }<br />
<br />
  void drawPixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void writePixel(int16_t x, int16_t y, uint16_t color) override {<br />
    fillRect(x, y, 1, 1, color);<br />
  }<br />
<br />
  void fillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                uint16_t color) override {<br />
    if (x &lt; 0) {<br />
      width += x;<br />
      x = 0;<br />
    }<br />
    if (y &lt; 0) {<br />
      height += y;<br />
      y = 0;<br />
    }<br />
    if (x + width &gt; kDisplayWidth) {<br />
      width = kDisplayWidth - x;<br />
    }<br />
    if (y + height &gt; kDisplayHeight) {<br />
      height = kDisplayHeight - y;<br />
    }<br />
    if (drawBuffer_ == nullptr || width &lt;= 0 || height &lt;= 0) {<br />
      return;<br />
    }<br />
<br />
    const uint8_t high = color &gt;&gt; 8;<br />
    const uint8_t low = color;<br />
    for (int16_t row = y; row &lt; y + height;<br />
       &nbsp;&nbsp;row += kTransferChunkHeight) {<br />
      const int16_t chunkHeight =<br />
          min&lt;int16_t&gt;(kTransferChunkHeight, y + height - row);<br />
      const size_t pixelCount = width * chunkHeight;<br />
      for (size_t index = 0; index &lt; pixelCount; ++index) {<br />
        drawBuffer_[index * 2] = high;<br />
        drawBuffer_[index * 2 + 1] = low;<br />
      }<br />
<br />
      if (!setWindow(x, row, x + width, row + chunkHeight) ||<br />
          !writePixels(drawBuffer_, pixelCount * sizeof(uint16_t))) {<br />
        Serial.println("ST7789 pixel transfer failed");<br />
        return;<br />
      }<br />
    }<br />
  }<br />
<br />
  void writeFillRect(int16_t x, int16_t y, int16_t width, int16_t height,<br />
                   &nbsp;&nbsp;uint16_t color) override {<br />
    fillRect(x, y, width, height, color);<br />
  }<br />
<br />
  bool initialized() const { return initialized_; }<br />
<br />
 private:<br />
  bool transmit(const void* data, size_t length, int dcLevel) {<br />
    if (data == nullptr || length == 0) {<br />
      return true;<br />
    }<br />
<br />
    gpio_set_level(kTftDc, dcLevel);<br />
    spi_transaction_t transaction = {};<br />
    transaction.length = length * 8;<br />
    transaction.tx_buffer = data;<br />
    const esp_err_t err = spi_device_polling_transmit(spiDevice_, &amp;transaction);<br />
    if (err != ESP_OK) {<br />
      Serial.printf("ST7789 SPI transfer failed: %s&#92;n", esp_err_to_name(err));<br />
      return false;<br />
    }<br />
    return true;<br />
  }<br />
<br />
  bool writeCommand(uint8_t command) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool writeCommandData(uint8_t command, const uint8_t* data, size_t length) {<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;command, sizeof(command), 0) &amp;&amp;<br />
                       &nbsp;&nbsp;transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool setWindow(int16_t x1, int16_t y1, int16_t x2, int16_t y2) {<br />
    const uint8_t columns[] = {<br />
        static_cast&lt;uint8_t&gt;(x1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x1),<br />
        static_cast&lt;uint8_t&gt;((x2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(x2 - 1),<br />
    };<br />
    const uint8_t rows[] = {<br />
        static_cast&lt;uint8_t&gt;(y1 &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y1),<br />
        static_cast&lt;uint8_t&gt;((y2 - 1) &gt;&gt; 8), static_cast&lt;uint8_t&gt;(y2 - 1),<br />
    };<br />
    return writeCommandData(0x2A, columns, sizeof(columns)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0x2B, rows, sizeof(rows));<br />
  }<br />
<br />
  bool writePixels(const uint8_t* data, size_t length) {<br />
    constexpr uint8_t kRamWrite = 0x2C;<br />
    gpio_set_level(kTftCs, 0);<br />
    const bool success = transmit(&amp;kRamWrite, sizeof(kRamWrite), 0) &amp;&amp;<br />
                       &nbsp;&nbsp;transmit(data, length, 1);<br />
    gpio_set_level(kTftCs, 1);<br />
    return success;<br />
  }<br />
<br />
  bool initializeController() {<br />
    const uint8_t madctl[] = {kCo16Madctl};<br />
    const uint8_t displayFunction[] = {0x0A, 0x82};<br />
    const uint8_t pixelFormat[] = {0x55};<br />
    const uint8_t porchControl[] = {0x0C, 0x0C, 0x00, 0x33, 0x33};<br />
    const uint8_t gateControl[] = {0x35};<br />
    const uint8_t vcom[] = {0x28};<br />
    const uint8_t lcmControl[] = {0x0C};<br />
    const uint8_t vdvVrhEnable[] = {0x01, 0xFF};<br />
    const uint8_t vrh[] = {0x10};<br />
    const uint8_t vdv[] = {0x20};<br />
    const uint8_t frameRate[] = {0x0F};<br />
    const uint8_t powerControl[] = {0xA4, 0xA1};<br />
    const uint8_t positiveGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32,<br />
        0x44, 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17,<br />
    };<br />
    const uint8_t negativeGamma[] = {<br />
        0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31,<br />
        0x54, 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E,<br />
    };<br />
<br />
    if (!writeCommand(0x11)) {  // Sleep out.<br />
      return false;<br />
    }<br />
    delay(120);<br />
<br />
    return writeCommand(0x13) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0x36, madctl, sizeof(madctl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xB6, displayFunction, sizeof(displayFunction)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0x3A, pixelFormat, sizeof(pixelFormat)) &amp;&amp;<br />
         &nbsp;&nbsp;(delay(10), true) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xB2, porchControl, sizeof(porchControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xB7, gateControl, sizeof(gateControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xBB, vcom, sizeof(vcom)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC0, lcmControl, sizeof(lcmControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC2, vdvVrhEnable, sizeof(vdvVrhEnable)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC3, vrh, sizeof(vrh)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC4, vdv, sizeof(vdv)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xC6, frameRate, sizeof(frameRate)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xD0, powerControl, sizeof(powerControl)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xE0, positiveGamma, sizeof(positiveGamma)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommandData(0xE1, negativeGamma, sizeof(negativeGamma)) &amp;&amp;<br />
         &nbsp;&nbsp;writeCommand(0x21);  // Display inversion on.<br />
  }<br />
<br />
  spi_device_handle_t spiDevice_ = nullptr;<br />
  uint8_t* drawBuffer_ = nullptr;<br />
  bool initialized_ = false;<br />
};<br />
<br />
Co16St7789 display;<br />
uint32_t frameCount = 0;<br />
<br />
void drawColorBars() {<br />
  constexpr uint16_t colors[] = {<br />
      kRed,<br />
      kGreen,<br />
      kBlue,<br />
      kCyan,<br />
      kMagenta,<br />
      kYellow,<br />
  };<br />
  constexpr size_t colorCount = sizeof(colors) / sizeof(colors[0]);<br />
  const int barWidth = kDisplayWidth / colorCount;<br />
<br />
  for (size_t index = 0; index &lt; colorCount; ++index) {<br />
    const int x = index * barWidth;<br />
    const int width =<br />
        (index == colorCount - 1) ? kDisplayWidth - x : barWidth;<br />
    display.fillRect(x, 132, width, 44, colors[index]);<br />
  }<br />
}<br />
<br />
void drawStaticScreen() {<br />
  display.fillScreen(kBlack);<br />
  display.drawRect(0, 0, kDisplayWidth, kDisplayHeight, kWhite);<br />
<br />
  display.setTextWrap(false);<br />
  display.setTextColor(kCyan);<br />
  display.setTextSize(3);<br />
  display.setCursor(34, 20);<br />
  display.print("KinCony CO16");<br />
<br />
  display.setTextColor(kWhite);<br />
  display.setTextSize(2);<br />
  display.setCursor(24, 64);<br />
  display.print("ST7789 display test");<br />
  display.setCursor(24, 92);<br />
  display.print("SPI3 native driver");<br />
<br />
  drawColorBars();<br />
<br />
  display.setTextColor(kGreen);<br />
  display.setCursor(24, 194);<br />
  display.print("Frame:");<br />
}<br />
<br />
void updateFrameCounter() {<br />
  display.fillRect(108, 190, 190, 28, kBlack);<br />
  display.setTextColor(kGreen);<br />
  display.setTextSize(2);<br />
  display.setCursor(108, 194);<br />
  display.print(frameCount++);<br />
}<br />
<br />
}  // namespace<br />
<br />
void setup() {<br />
  Serial.begin(115200);<br />
  delay(1000);<br />
<br />
  Serial.println();<br />
  Serial.println("KinCony CO16 standalone ST7789 example");<br />
  Serial.printf("SPI3 SCLK=%d MOSI=%d MISO=%d CS=%d mode=0 clock=%u Hz&#92;n",<br />
                kTftSclk, kTftMosi, kTftMiso, kTftCs,<br />
                kDisplaySpiFrequency);<br />
  Serial.printf("LCD DC=%d RESET=%d BACKLIGHT=%d&#92;n", kTftDc, kTftReset,<br />
                kTftBacklight);<br />
<br />
  if (!display.begin()) {<br />
    Serial.println("Display initialization failed");<br />
    while (true) {<br />
      delay(1000);<br />
    }<br />
  }<br />
<br />
  drawStaticScreen();<br />
  updateFrameCounter();<br />
<br />
  Serial.printf("Display initialized: %dx%d, MADCTL=0x%02X, invert=on&#92;n",<br />
                display.width(), display.height(), kCo16Madctl);<br />
}<br />
<br />
void loop() {<br />
  <br />
  updateFrameCounter();<br />
  delay(1000);<br />
}</code></div></div><!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10356" target="_blank" title="">ST7789.jpg</a> (Size: 262.38 KB / Downloads: 152)
<!-- end: postbit_attachments_attachment --><br />
arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10352" target="_blank" title="">10-TFT-LCD-ST7789.zip</a> (Size: 3.05 KB / Downloads: 132)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10353" target="_blank" title="">10-TFT-LCD-ST7789.ino.merged.zip</a> (Size: 211.33 KB / Downloads: 134)
<!-- end: postbit_attachments_attachment --><br />
before run code , need install these arduino library:<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10354" target="_blank" title="">Adafruit-GFX-Library.png</a> (Size: 122.69 KB / Downloads: 149)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10355" target="_blank" title="">Adafruit-ST7735-and-ST7789-Library.png</a> (Size: 118.05 KB / Downloads: 134)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-09 digital INPUT trigger OUTPUT directly]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9790</link>
			<pubDate>Mon, 31 Aug 2026 08:11:58 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9790</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
  Made by KinCony IoT: https://www.kincony.com<br />
<br />
  Program functionality:<br />
  This program uses ESP32-S3 to read inputs from PCA9555 I/O expander chip (I2C address 0x24)<br />
  for channels 1-16, and control corresponding relays using PCF8575 I/O expander chip (I2C address 0x22)<br />
  for controlling 16 relays (1-16). When input (DI) is triggered (LOW), corresponding relay (OUTPUT) is activated (LOW).<br />
*/<br />
<br />
#include &lt;PCA95x5.h&gt;<br />
#include &lt;Wire.h&gt;<br />
#include &lt;PCF8575.h&gt;<br />
<br />
// Initialize the PCA9555 objects for reading inputs (channels 1-16)<br />
PCA9555 input_ioex1;  // For channels 1-16 (I2C address 0x24)<br />
<br />
// Set I2C address of the PCF8575 module for output relays<br />
#define I2C_ADDRESS 0x22<br />
PCF8575 pcf8575_R1(I2C_ADDRESS);<br />
<br />
void setup() {<br />
    Serial.begin(115200);<br />
    delay(10);<br />
<br />
    // Initialize I2C bus: SDA=GPIO8, SCL=GPIO18, 100kHz<br />
    Wire.begin(8, 18, 100000);<br />
<br />
    // Initialize PCF8575 for outputs<br />
    pcf8575_R1.begin();<br />
<br />
    // Turn off all relays initially (set all pins HIGH - relay OFF)<br />
    for (int i = 0; i &lt; 16; i++) {<br />
        pcf8575_R1.write(i, HIGH);<br />
    }<br />
<br />
    // Configure input PCA9555 (for inputs 1-16)<br />
    input_ioex1.attach(Wire, 0x24);<br />
    input_ioex1.polarity(PCA95x5::Polarity::ORIGINAL_ALL);<br />
    input_ioex1.direction(PCA95x5::Direction::IN_ALL);<br />
<br />
    delay(50);<br />
}<br />
<br />
void loop() {<br />
    // Read input states from XL9535 (inputs 1-16)<br />
    uint16_t inputs_1_16 = input_ioex1.read();<br />
    <br />
    // Control outputs based on inputs<br />
    // When input is LOW, set corresponding output to LOW (activate relay)<br />
    for (int channel = 0; channel &lt; 16; channel++) {<br />
        if (!(inputs_1_16 &amp; (1 &lt;&lt; channel))) {<br />
            pcf8575_R1.write(channel, LOW); &nbsp;&nbsp;// Input triggered, activate relay<br />
        } else {<br />
            pcf8575_R1.write(channel, HIGH);  // Input not triggered, deactivate relay<br />
        }<br />
    }<br />
    <br />
    delay(100);<br />
}</code></div></div> arduino ino file download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10350" target="_blank" title="">9-input-trigger-output.zip</a> (Size: 997 bytes / Downloads: 134)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10351" target="_blank" title="">9-input-trigger-output.ino.merged.zip</a> (Size: 197.75 KB / Downloads: 140)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
  Made by KinCony IoT: https://www.kincony.com<br />
<br />
  Program functionality:<br />
  This program uses ESP32-S3 to read inputs from PCA9555 I/O expander chip (I2C address 0x24)<br />
  for channels 1-16, and control corresponding relays using PCF8575 I/O expander chip (I2C address 0x22)<br />
  for controlling 16 relays (1-16). When input (DI) is triggered (LOW), corresponding relay (OUTPUT) is activated (LOW).<br />
*/<br />
<br />
#include &lt;PCA95x5.h&gt;<br />
#include &lt;Wire.h&gt;<br />
#include &lt;PCF8575.h&gt;<br />
<br />
// Initialize the PCA9555 objects for reading inputs (channels 1-16)<br />
PCA9555 input_ioex1;  // For channels 1-16 (I2C address 0x24)<br />
<br />
// Set I2C address of the PCF8575 module for output relays<br />
#define I2C_ADDRESS 0x22<br />
PCF8575 pcf8575_R1(I2C_ADDRESS);<br />
<br />
void setup() {<br />
    Serial.begin(115200);<br />
    delay(10);<br />
<br />
    // Initialize I2C bus: SDA=GPIO8, SCL=GPIO18, 100kHz<br />
    Wire.begin(8, 18, 100000);<br />
<br />
    // Initialize PCF8575 for outputs<br />
    pcf8575_R1.begin();<br />
<br />
    // Turn off all relays initially (set all pins HIGH - relay OFF)<br />
    for (int i = 0; i &lt; 16; i++) {<br />
        pcf8575_R1.write(i, HIGH);<br />
    }<br />
<br />
    // Configure input PCA9555 (for inputs 1-16)<br />
    input_ioex1.attach(Wire, 0x24);<br />
    input_ioex1.polarity(PCA95x5::Polarity::ORIGINAL_ALL);<br />
    input_ioex1.direction(PCA95x5::Direction::IN_ALL);<br />
<br />
    delay(50);<br />
}<br />
<br />
void loop() {<br />
    // Read input states from XL9535 (inputs 1-16)<br />
    uint16_t inputs_1_16 = input_ioex1.read();<br />
    <br />
    // Control outputs based on inputs<br />
    // When input is LOW, set corresponding output to LOW (activate relay)<br />
    for (int channel = 0; channel &lt; 16; channel++) {<br />
        if (!(inputs_1_16 &amp; (1 &lt;&lt; channel))) {<br />
            pcf8575_R1.write(channel, LOW); &nbsp;&nbsp;// Input triggered, activate relay<br />
        } else {<br />
            pcf8575_R1.write(channel, HIGH);  // Input not triggered, deactivate relay<br />
        }<br />
    }<br />
    <br />
    delay(100);<br />
}</code></div></div> arduino ino file download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10350" target="_blank" title="">9-input-trigger-output.zip</a> (Size: 997 bytes / Downloads: 134)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10351" target="_blank" title="">9-input-trigger-output.ino.merged.zip</a> (Size: 197.75 KB / Downloads: 140)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-08 Ethernet W5500 chip work with TCP Server mode]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9789</link>
			<pubDate>Mon, 31 Aug 2026 08:09:39 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9789</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 *<br />
 * This Arduino program sets up an ESP32-S3 with a W5500 Ethernet module<br />
 * as a TCP server. It listens on port 4196 and echoes back any string <br />
 * received from a client.<br />
 *<br />
 * Hardware connections:<br />
 * - CLK: GPIO1<br />
 * - MOSI: GPIO2<br />
 * - MISO: GPIO41<br />
 * - CS: GPIO42<br />
 * - RST: GPIO44<br />
 * - INT: GPIO43<br />
 *<br />
 * Static IP address: 192.168.3.55<br />
 * Subnet Mask: 255.255.255.0<br />
 * Gateway: 192.168.3.1<br />
 * DNS: 192.168.3.1<br />
 */<br />
<br />
#include &lt;SPI.h&gt;<br />
#include &lt;Ethernet.h&gt;<br />
<br />
// Define the W5500 Ethernet module pins<br />
#define W5500_CS_PIN  42<br />
#define W5500_RST_PIN 44<br />
#define W5500_INT_PIN 43<br />
#define W5500_CLK_PIN 1<br />
#define W5500_MOSI_PIN 2<br />
#define W5500_MISO_PIN 41<br />
<br />
// MAC address for your Ethernet shield (must be unique on your network)<br />
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };<br />
<br />
// Static IP address configuration<br />
IPAddress ip(192, 168, 3, 55);     &nbsp;&nbsp;// Static IP address<br />
IPAddress subnet(255, 255, 255, 0); &nbsp;&nbsp;// Subnet mask<br />
IPAddress gateway(192, 168, 3, 1);    // Default gateway<br />
IPAddress dns(192, 168, 3, 1);        // DNS server address<br />
<br />
// Create an EthernetServer object to handle TCP connections<br />
EthernetServer server(4196);<br />
<br />
void setup() {<br />
  // Initialize serial communication<br />
  Serial.begin(115200);<br />
  while (!Serial) {<br />
    ; // Wait for serial port to connect<br />
  }<br />
<br />
  // Initialize the W5500 module<br />
  pinMode(W5500_RST_PIN, OUTPUT);<br />
  pinMode(W5500_INT_PIN, INPUT);<br />
  digitalWrite(W5500_RST_PIN, LOW);  // Reset the W5500 module<br />
  delay(100);                     &nbsp;&nbsp;// Wait for reset to complete<br />
  digitalWrite(W5500_RST_PIN, HIGH); // Release reset<br />
<br />
  // Initialize SPI with the correct pin definitions<br />
  SPI.begin(W5500_CLK_PIN, W5500_MISO_PIN, W5500_MOSI_PIN);<br />
<br />
  // Set up the Ethernet library with W5500-specific pins<br />
  Ethernet.init(W5500_CS_PIN);<br />
<br />
  // Start the Ethernet connection with static IP configuration<br />
  Ethernet.begin(mac, ip, dns, gateway, subnet);<br />
<br />
  // Print the IP address to the serial monitor<br />
  Serial.print("IP Address: ");<br />
  Serial.println(Ethernet.localIP());<br />
<br />
  // Start listening for incoming TCP connections<br />
  server.begin();<br />
}<br />
<br />
void loop() {<br />
  // Check for incoming client connections<br />
  EthernetClient client = server.available();<br />
  if (client) {<br />
    Serial.println("New client connected");<br />
<br />
    // Read data from the client and echo it back<br />
    while (client.connected()) {<br />
      if (client.available()) {<br />
        char c = client.read();<br />
        server.write(c);<br />
      }<br />
    }<br />
<br />
    // Close the connection when done<br />
    client.stop();<br />
    Serial.println("Client disconnected");<br />
  }<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10348" target="_blank" title="">8-Ethernet-W5500.zip</a> (Size: 1.23 KB / Downloads: 131)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10349" target="_blank" title="">8-Ethernet-W5500.ino.merged.zip</a> (Size: 188.93 KB / Downloads: 139)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 *<br />
 * This Arduino program sets up an ESP32-S3 with a W5500 Ethernet module<br />
 * as a TCP server. It listens on port 4196 and echoes back any string <br />
 * received from a client.<br />
 *<br />
 * Hardware connections:<br />
 * - CLK: GPIO1<br />
 * - MOSI: GPIO2<br />
 * - MISO: GPIO41<br />
 * - CS: GPIO42<br />
 * - RST: GPIO44<br />
 * - INT: GPIO43<br />
 *<br />
 * Static IP address: 192.168.3.55<br />
 * Subnet Mask: 255.255.255.0<br />
 * Gateway: 192.168.3.1<br />
 * DNS: 192.168.3.1<br />
 */<br />
<br />
#include &lt;SPI.h&gt;<br />
#include &lt;Ethernet.h&gt;<br />
<br />
// Define the W5500 Ethernet module pins<br />
#define W5500_CS_PIN  42<br />
#define W5500_RST_PIN 44<br />
#define W5500_INT_PIN 43<br />
#define W5500_CLK_PIN 1<br />
#define W5500_MOSI_PIN 2<br />
#define W5500_MISO_PIN 41<br />
<br />
// MAC address for your Ethernet shield (must be unique on your network)<br />
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };<br />
<br />
// Static IP address configuration<br />
IPAddress ip(192, 168, 3, 55);     &nbsp;&nbsp;// Static IP address<br />
IPAddress subnet(255, 255, 255, 0); &nbsp;&nbsp;// Subnet mask<br />
IPAddress gateway(192, 168, 3, 1);    // Default gateway<br />
IPAddress dns(192, 168, 3, 1);        // DNS server address<br />
<br />
// Create an EthernetServer object to handle TCP connections<br />
EthernetServer server(4196);<br />
<br />
void setup() {<br />
  // Initialize serial communication<br />
  Serial.begin(115200);<br />
  while (!Serial) {<br />
    ; // Wait for serial port to connect<br />
  }<br />
<br />
  // Initialize the W5500 module<br />
  pinMode(W5500_RST_PIN, OUTPUT);<br />
  pinMode(W5500_INT_PIN, INPUT);<br />
  digitalWrite(W5500_RST_PIN, LOW);  // Reset the W5500 module<br />
  delay(100);                     &nbsp;&nbsp;// Wait for reset to complete<br />
  digitalWrite(W5500_RST_PIN, HIGH); // Release reset<br />
<br />
  // Initialize SPI with the correct pin definitions<br />
  SPI.begin(W5500_CLK_PIN, W5500_MISO_PIN, W5500_MOSI_PIN);<br />
<br />
  // Set up the Ethernet library with W5500-specific pins<br />
  Ethernet.init(W5500_CS_PIN);<br />
<br />
  // Start the Ethernet connection with static IP configuration<br />
  Ethernet.begin(mac, ip, dns, gateway, subnet);<br />
<br />
  // Print the IP address to the serial monitor<br />
  Serial.print("IP Address: ");<br />
  Serial.println(Ethernet.localIP());<br />
<br />
  // Start listening for incoming TCP connections<br />
  server.begin();<br />
}<br />
<br />
void loop() {<br />
  // Check for incoming client connections<br />
  EthernetClient client = server.available();<br />
  if (client) {<br />
    Serial.println("New client connected");<br />
<br />
    // Read data from the client and echo it back<br />
    while (client.connected()) {<br />
      if (client.available()) {<br />
        char c = client.read();<br />
        server.write(c);<br />
      }<br />
    }<br />
<br />
    // Close the connection when done<br />
    client.stop();<br />
    Serial.println("Client disconnected");<br />
  }<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10348" target="_blank" title="">8-Ethernet-W5500.zip</a> (Size: 1.23 KB / Downloads: 131)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10349" target="_blank" title="">8-Ethernet-W5500.ino.merged.zip</a> (Size: 188.93 KB / Downloads: 139)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-07 how to DS3231 RTC clock]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9788</link>
			<pubDate>Mon, 31 Aug 2026 08:08:28 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9788</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 *<br />
 * DS3231 RTC with Arduino<br />
 *<br />
 * This program demonstrates how to use the DS3231 RTC (Real-Time Clock) module with the Arduino.<br />
 * It includes functionality to:<br />
 * - Initialize the DS3231 RTC module<br />
 * - Read the current date and time from the RTC<br />
 * - Set the RTC time based on a serial command:Command format: DYYYY-MM-DDTHH:MM:SS<br />
*    Set date and time command example: D2024-09-19T11:50:22<br />
*    print current date and time command: current time<br />
*<br />
 *<br />
 * Hardware Connections:<br />
 * - SDA: GPIO 8<br />
 * - SCL: GPIO 18<br />
 */<br />
<br />
#include &lt;DS3231.h&gt;<br />
#include &lt;Wire.h&gt;<br />
<br />
String serial_cmd_rcv = ""; // Serial port receiver<br />
<br />
typedef struct<br />
{<br />
  byte year;    // Last two digits of the year, library adds 2000.<br />
  byte month;<br />
  byte day;<br />
  byte hour;<br />
  byte minute;<br />
  byte second;<br />
} MY_DATE_STR;<br />
<br />
MY_DATE_STR my_date_str = {0};<br />
<br />
// Define constants for relay control<br />
#define OPEN_RLY_DATA    26<br />
#define OPEN_RLY_MONTH &nbsp;&nbsp;4<br />
#define CLOSE_RLY_DATA &nbsp;&nbsp;2<br />
#define CLOSE_RLY_MONTH  5<br />
<br />
// Define pin connections<br />
#define SDA_PIN &nbsp;&nbsp;8<br />
#define SCL_PIN &nbsp;&nbsp;18<br />
<br />
DS3231 rtc; // Create an instance of the DS3231 RTC<br />
bool h12Flag;<br />
bool pmFlag;<br />
static bool bCentury = false;<br />
static bool old_level_high = false;<br />
static bool old_level_low = false;<br />
<br />
<br />
/**<br />
 * @brief Print the current time from the RTC to the Serial Monitor.<br />
 */<br />
static void PrintfCurTime()<br />
{<br />
  Serial.print("Current time is: ");<br />
  int year = rtc.getYear() + 2000;<br />
  Serial.print(year);<br />
  Serial.print("-");<br />
<br />
  Serial.print(rtc.getMonth(bCentury), DEC);<br />
  Serial.print("-");<br />
<br />
  Serial.print(rtc.getDate(), DEC);<br />
  Serial.print(" ");<br />
<br />
  Serial.print(rtc.getHour(h12Flag, pmFlag), DEC);<br />
  Serial.print(":");<br />
  Serial.print(rtc.getMinute(), DEC);<br />
  Serial.print(":");<br />
  Serial.println(rtc.getSecond(), DEC);<br />
}<br />
<br />
/**<br />
 * @brief Process serial commands to set the RTC time.<br />
 * Command format: DYYYY-MM-DDTHH:MM:SS<br />
 */<br />
static void GetSerialCmd()<br />
{<br />
  if (Serial.available() &gt; 0)<br />
  {<br />
    delay(100);<br />
    int num_read = Serial.available();<br />
    while (num_read--)<br />
      serial_cmd_rcv += char(Serial.read());<br />
  }<br />
  else return;<br />
<br />
  serial_cmd_rcv.trim();<br />
<br />
  if (serial_cmd_rcv == "current time")<br />
  {<br />
    PrintfCurTime();<br />
    serial_cmd_rcv = "";<br />
    return;<br />
  }<br />
<br />
  Serial.print("Received length: ");<br />
  Serial.println(serial_cmd_rcv.length());<br />
<br />
  int indexof_d = serial_cmd_rcv.indexOf('D');<br />
  int indexof_t = serial_cmd_rcv.indexOf('T');<br />
<br />
  Serial.print("D index: ");<br />
  Serial.print(indexof_d);<br />
  Serial.print(" T index: ");<br />
  Serial.println(indexof_t);<br />
<br />
  if (serial_cmd_rcv.length() != 20 || <br />
      serial_cmd_rcv.substring(0, 1) != "D" ||<br />
      serial_cmd_rcv.substring(11, 12) != "T")  <br />
  {<br />
    Serial.println(serial_cmd_rcv);<br />
    serial_cmd_rcv = "";<br />
    return;<br />
  }<br />
<br />
  Serial.println("Setting time...");<br />
<br />
  my_date_str.year = (byte)serial_cmd_rcv.substring(3, 5).toInt();<br />
  my_date_str.month = (byte)serial_cmd_rcv.substring(6, 8).toInt();<br />
  my_date_str.day = (byte)serial_cmd_rcv.substring(9, 11).toInt();<br />
  my_date_str.hour = (byte)serial_cmd_rcv.substring(12, 14).toInt();<br />
  my_date_str.minute = (byte)serial_cmd_rcv.substring(15, 17).toInt();<br />
  my_date_str.second = (byte)serial_cmd_rcv.substring(18).toInt();<br />
<br />
  rtc.setYear(my_date_str.year);<br />
  rtc.setMonth(my_date_str.month);<br />
  rtc.setDate(my_date_str.day);<br />
  rtc.setHour(my_date_str.hour);<br />
  rtc.setMinute(my_date_str.minute);<br />
  rtc.setSecond(my_date_str.second);<br />
<br />
  serial_cmd_rcv = "";<br />
<br />
  Serial.println("Time set.");<br />
}<br />
<br />
void setup() {<br />
  // Initialize the I2C interface<br />
  Wire.begin(SDA_PIN, SCL_PIN, 40000);<br />
  <br />
  // Initialize Serial communication<br />
  Serial.begin(115200);<br />
 &nbsp;&nbsp;<br />
  // Set the RTC to 24-hour mode<br />
  rtc.setClockMode(false); // 24-hour format<br />
<br />
  // Print current time to Serial Monitor<br />
  PrintfCurTime();<br />
<br />
  // Clear any remaining serial data<br />
  while (Serial.read() &gt;= 0) {}<br />
}<br />
<br />
void loop() {<br />
  // Process incoming serial commands<br />
  GetSerialCmd(); <br />
  delay(1000); // Delay for 1 second<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10346" target="_blank" title="">7-DS3231-RTC.zip</a> (Size: 1.56 KB / Downloads: 132)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10347" target="_blank" title="">7-DS3231-RTC.ino.merged.zip</a> (Size: 191.08 KB / Downloads: 147)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 *<br />
 * DS3231 RTC with Arduino<br />
 *<br />
 * This program demonstrates how to use the DS3231 RTC (Real-Time Clock) module with the Arduino.<br />
 * It includes functionality to:<br />
 * - Initialize the DS3231 RTC module<br />
 * - Read the current date and time from the RTC<br />
 * - Set the RTC time based on a serial command:Command format: DYYYY-MM-DDTHH:MM:SS<br />
*    Set date and time command example: D2024-09-19T11:50:22<br />
*    print current date and time command: current time<br />
*<br />
 *<br />
 * Hardware Connections:<br />
 * - SDA: GPIO 8<br />
 * - SCL: GPIO 18<br />
 */<br />
<br />
#include &lt;DS3231.h&gt;<br />
#include &lt;Wire.h&gt;<br />
<br />
String serial_cmd_rcv = ""; // Serial port receiver<br />
<br />
typedef struct<br />
{<br />
  byte year;    // Last two digits of the year, library adds 2000.<br />
  byte month;<br />
  byte day;<br />
  byte hour;<br />
  byte minute;<br />
  byte second;<br />
} MY_DATE_STR;<br />
<br />
MY_DATE_STR my_date_str = {0};<br />
<br />
// Define constants for relay control<br />
#define OPEN_RLY_DATA    26<br />
#define OPEN_RLY_MONTH &nbsp;&nbsp;4<br />
#define CLOSE_RLY_DATA &nbsp;&nbsp;2<br />
#define CLOSE_RLY_MONTH  5<br />
<br />
// Define pin connections<br />
#define SDA_PIN &nbsp;&nbsp;8<br />
#define SCL_PIN &nbsp;&nbsp;18<br />
<br />
DS3231 rtc; // Create an instance of the DS3231 RTC<br />
bool h12Flag;<br />
bool pmFlag;<br />
static bool bCentury = false;<br />
static bool old_level_high = false;<br />
static bool old_level_low = false;<br />
<br />
<br />
/**<br />
 * @brief Print the current time from the RTC to the Serial Monitor.<br />
 */<br />
static void PrintfCurTime()<br />
{<br />
  Serial.print("Current time is: ");<br />
  int year = rtc.getYear() + 2000;<br />
  Serial.print(year);<br />
  Serial.print("-");<br />
<br />
  Serial.print(rtc.getMonth(bCentury), DEC);<br />
  Serial.print("-");<br />
<br />
  Serial.print(rtc.getDate(), DEC);<br />
  Serial.print(" ");<br />
<br />
  Serial.print(rtc.getHour(h12Flag, pmFlag), DEC);<br />
  Serial.print(":");<br />
  Serial.print(rtc.getMinute(), DEC);<br />
  Serial.print(":");<br />
  Serial.println(rtc.getSecond(), DEC);<br />
}<br />
<br />
/**<br />
 * @brief Process serial commands to set the RTC time.<br />
 * Command format: DYYYY-MM-DDTHH:MM:SS<br />
 */<br />
static void GetSerialCmd()<br />
{<br />
  if (Serial.available() &gt; 0)<br />
  {<br />
    delay(100);<br />
    int num_read = Serial.available();<br />
    while (num_read--)<br />
      serial_cmd_rcv += char(Serial.read());<br />
  }<br />
  else return;<br />
<br />
  serial_cmd_rcv.trim();<br />
<br />
  if (serial_cmd_rcv == "current time")<br />
  {<br />
    PrintfCurTime();<br />
    serial_cmd_rcv = "";<br />
    return;<br />
  }<br />
<br />
  Serial.print("Received length: ");<br />
  Serial.println(serial_cmd_rcv.length());<br />
<br />
  int indexof_d = serial_cmd_rcv.indexOf('D');<br />
  int indexof_t = serial_cmd_rcv.indexOf('T');<br />
<br />
  Serial.print("D index: ");<br />
  Serial.print(indexof_d);<br />
  Serial.print(" T index: ");<br />
  Serial.println(indexof_t);<br />
<br />
  if (serial_cmd_rcv.length() != 20 || <br />
      serial_cmd_rcv.substring(0, 1) != "D" ||<br />
      serial_cmd_rcv.substring(11, 12) != "T")  <br />
  {<br />
    Serial.println(serial_cmd_rcv);<br />
    serial_cmd_rcv = "";<br />
    return;<br />
  }<br />
<br />
  Serial.println("Setting time...");<br />
<br />
  my_date_str.year = (byte)serial_cmd_rcv.substring(3, 5).toInt();<br />
  my_date_str.month = (byte)serial_cmd_rcv.substring(6, 8).toInt();<br />
  my_date_str.day = (byte)serial_cmd_rcv.substring(9, 11).toInt();<br />
  my_date_str.hour = (byte)serial_cmd_rcv.substring(12, 14).toInt();<br />
  my_date_str.minute = (byte)serial_cmd_rcv.substring(15, 17).toInt();<br />
  my_date_str.second = (byte)serial_cmd_rcv.substring(18).toInt();<br />
<br />
  rtc.setYear(my_date_str.year);<br />
  rtc.setMonth(my_date_str.month);<br />
  rtc.setDate(my_date_str.day);<br />
  rtc.setHour(my_date_str.hour);<br />
  rtc.setMinute(my_date_str.minute);<br />
  rtc.setSecond(my_date_str.second);<br />
<br />
  serial_cmd_rcv = "";<br />
<br />
  Serial.println("Time set.");<br />
}<br />
<br />
void setup() {<br />
  // Initialize the I2C interface<br />
  Wire.begin(SDA_PIN, SCL_PIN, 40000);<br />
  <br />
  // Initialize Serial communication<br />
  Serial.begin(115200);<br />
 &nbsp;&nbsp;<br />
  // Set the RTC to 24-hour mode<br />
  rtc.setClockMode(false); // 24-hour format<br />
<br />
  // Print current time to Serial Monitor<br />
  PrintfCurTime();<br />
<br />
  // Clear any remaining serial data<br />
  while (Serial.read() &gt;= 0) {}<br />
}<br />
<br />
void loop() {<br />
  // Process incoming serial commands<br />
  GetSerialCmd(); <br />
  delay(1000); // Delay for 1 second<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10346" target="_blank" title="">7-DS3231-RTC.zip</a> (Size: 1.56 KB / Downloads: 132)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10347" target="_blank" title="">7-DS3231-RTC.ino.merged.zip</a> (Size: 191.08 KB / Downloads: 147)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-06 How to use SD Card]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9787</link>
			<pubDate>Mon, 31 Aug 2026 08:06:31 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9787</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 *<br />
 * SD Card File Operations<br />
 *<br />
 * This program demonstrates basic file operations on an SD card using the ESP32.<br />
 * It includes functionality to:<br />
 * - Initialize and test the SD card<br />
 * - Read from, write to, append to, and delete files on the SD card<br />
 * - Measure file read and write performance<br />
 *<br />
 * Hardware Connections:<br />
 * - SCK: GPIO 11<br />
 * - MISO: GPIO 12<br />
 * - MOSI: GPIO 10<br />
 * - CS: GPIO 9<br />
 */<br />
<br />
#include "FS.h"<br />
#include "SD.h"<br />
#include "SPI.h"<br />
<br />
// Pin definitions for SD card<br />
#define SCK  11<br />
#define MISO 12<br />
#define MOSI 10<br />
#define CS &nbsp;&nbsp;9<br />
<br />
/**<br />
 * @brief Reads the contents of a file from the SD card and prints it to the serial monitor.<br />
 * <br />
 * @param fs File system to use (in this case, SD).<br />
 * @param path Path of the file to read.<br />
 */<br />
void readFile(fs::FS &amp;fs, const char * path) {<br />
  Serial.printf("Reading file: %s&#92;n", path);<br />
<br />
  File file = fs.open(path);<br />
  if (!file) {<br />
    Serial.println("Failed to open file for reading");<br />
    return;<br />
  }<br />
<br />
  Serial.print("Read from file: ");<br />
  while (file.available()) {<br />
    Serial.print((char)file.read());<br />
  }<br />
  file.close();<br />
}<br />
<br />
/**<br />
 * @brief Writes a message to a file on the SD card.<br />
 * <br />
 * @param fs File system to use (in this case, SD).<br />
 * @param path Path of the file to write.<br />
 * @param message Message to write to the file.<br />
 */<br />
void writeFile(fs::FS &amp;fs, const char * path, const char * message) {<br />
  Serial.printf("Writing file: %s&#92;n", path);<br />
<br />
  File file = fs.open(path, FILE_WRITE);<br />
  if (!file) {<br />
    Serial.println("Failed to open file for writing");<br />
    return;<br />
  }<br />
  if (file.print(message)) {<br />
    Serial.println("File written");<br />
  } else {<br />
    Serial.println("Write failed");<br />
  }<br />
  file.close();<br />
}<br />
<br />
/**<br />
 * @brief Appends a message to a file on the SD card.<br />
 * <br />
 * @param fs File system to use (in this case, SD).<br />
 * @param path Path of the file to append.<br />
 * @param message Message to append to the file.<br />
 */<br />
void appendFile(fs::FS &amp;fs, const char * path, const char * message) {<br />
  Serial.printf("Appending to file: %s&#92;n", path);<br />
<br />
  File file = fs.open(path, FILE_APPEND);<br />
  if (!file) {<br />
    Serial.println("Failed to open file for appending");<br />
    return;<br />
  }<br />
  if (file.print(message)) {<br />
    Serial.println("Message appended");<br />
  } else {<br />
    Serial.println("Append failed");<br />
  }<br />
  file.close();<br />
}<br />
<br />
/**<br />
 * @brief Deletes a file from the SD card.<br />
 * <br />
 * @param fs File system to use (in this case, SD).<br />
 * @param path Path of the file to delete.<br />
 */<br />
void deleteFile(fs::FS &amp;fs, const char * path) {<br />
  Serial.printf("Deleting file: %s&#92;n", path);<br />
  if (fs.remove(path)) {<br />
    Serial.println("File deleted");<br />
  } else {<br />
    Serial.println("Delete failed");<br />
  }<br />
}<br />
<br />
/**<br />
 * @brief Tests file read and write performance.<br />
 * <br />
 * @param fs File system to use (in this case, SD).<br />
 * @param path Path of the file to test.<br />
 */<br />
void testFileIO(fs::FS &amp;fs, const char * path) {<br />
  File file = fs.open(path);<br />
  static uint8_t buf[512];<br />
  size_t len = 0;<br />
  uint32_t start = millis();<br />
  uint32_t end = start;<br />
<br />
  if (file) {<br />
    len = file.size();<br />
    size_t flen = len;<br />
    start = millis();<br />
    while (len) {<br />
      size_t toRead = len;<br />
      if (toRead &gt; 512) {<br />
        toRead = 512;<br />
      }<br />
      file.read(buf, toRead);<br />
      len -= toRead;<br />
    }<br />
    end = millis() - start;<br />
    Serial.printf("%u bytes read for %u ms&#92;n", flen, end);<br />
    file.close();<br />
  } else {<br />
    Serial.println("Failed to open file for reading");<br />
  }<br />
<br />
  file = fs.open(path, FILE_WRITE);<br />
  if (!file) {<br />
    Serial.println("Failed to open file for writing");<br />
    return;<br />
  }<br />
<br />
  size_t i;<br />
  start = millis();<br />
  for (i = 0; i &lt; 2048; i++) {<br />
    file.write(buf, 512);<br />
  }<br />
  end = millis() - start;<br />
  Serial.printf("%u bytes written for %u ms&#92;n", 2048 * 512, end);<br />
  file.close();<br />
}<br />
<br />
void setup() {<br />
  // Initialize serial communication<br />
  Serial.begin(115200);<br />
  <br />
  // Initialize SPI and SD card<br />
  SPIClass spi = SPIClass(HSPI);<br />
  spi.begin(SCK, MISO, MOSI, CS);<br />
<br />
  if (!SD.begin(CS, spi, 80000000)) {<br />
    Serial.println("Card Mount Failed");<br />
    return;<br />
  }<br />
<br />
  uint8_t cardType = SD.cardType();<br />
<br />
  if (cardType == CARD_NONE) {<br />
    Serial.println("No SD card attached");<br />
    return;<br />
  }<br />
<br />
  Serial.print("SD Card Type: ");<br />
  if (cardType == CARD_MMC) {<br />
    Serial.println("MMC");<br />
  } else if (cardType == CARD_SD) {<br />
    Serial.println("SDSC");<br />
  } else if (cardType == CARD_SDHC) {<br />
    Serial.println("SDHC");<br />
  } else {<br />
    Serial.println("UNKNOWN");<br />
  }<br />
<br />
  uint64_t cardSize = SD.cardSize() / (1024 * 1024);<br />
  Serial.printf("SD Card Size: %lluMB&#92;n", cardSize);<br />
  delay(2000);<br />
<br />
  // Perform file operations<br />
  deleteFile(SD, "/hello.txt");<br />
  writeFile(SD, "/hello.txt", "Hello ");<br />
  appendFile(SD, "/hello.txt", "World!&#92;n");<br />
  readFile(SD, "/hello.txt");<br />
  testFileIO(SD, "/test.txt");<br />
  Serial.printf("Total space: %lluMB&#92;n", SD.totalBytes() / (1024 * 1024));<br />
  Serial.printf("Used space: %lluMB&#92;n", SD.usedBytes() / (1024 * 1024));<br />
}<br />
<br />
void loop() {<br />
  // No operation in loop<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10344" target="_blank" title="">6-SD.zip</a> (Size: 1.53 KB / Downloads: 128)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10345" target="_blank" title="">6-SD.ino.merged.zip</a> (Size: 221.25 KB / Downloads: 136)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 *<br />
 * SD Card File Operations<br />
 *<br />
 * This program demonstrates basic file operations on an SD card using the ESP32.<br />
 * It includes functionality to:<br />
 * - Initialize and test the SD card<br />
 * - Read from, write to, append to, and delete files on the SD card<br />
 * - Measure file read and write performance<br />
 *<br />
 * Hardware Connections:<br />
 * - SCK: GPIO 11<br />
 * - MISO: GPIO 12<br />
 * - MOSI: GPIO 10<br />
 * - CS: GPIO 9<br />
 */<br />
<br />
#include "FS.h"<br />
#include "SD.h"<br />
#include "SPI.h"<br />
<br />
// Pin definitions for SD card<br />
#define SCK  11<br />
#define MISO 12<br />
#define MOSI 10<br />
#define CS &nbsp;&nbsp;9<br />
<br />
/**<br />
 * @brief Reads the contents of a file from the SD card and prints it to the serial monitor.<br />
 * <br />
 * @param fs File system to use (in this case, SD).<br />
 * @param path Path of the file to read.<br />
 */<br />
void readFile(fs::FS &amp;fs, const char * path) {<br />
  Serial.printf("Reading file: %s&#92;n", path);<br />
<br />
  File file = fs.open(path);<br />
  if (!file) {<br />
    Serial.println("Failed to open file for reading");<br />
    return;<br />
  }<br />
<br />
  Serial.print("Read from file: ");<br />
  while (file.available()) {<br />
    Serial.print((char)file.read());<br />
  }<br />
  file.close();<br />
}<br />
<br />
/**<br />
 * @brief Writes a message to a file on the SD card.<br />
 * <br />
 * @param fs File system to use (in this case, SD).<br />
 * @param path Path of the file to write.<br />
 * @param message Message to write to the file.<br />
 */<br />
void writeFile(fs::FS &amp;fs, const char * path, const char * message) {<br />
  Serial.printf("Writing file: %s&#92;n", path);<br />
<br />
  File file = fs.open(path, FILE_WRITE);<br />
  if (!file) {<br />
    Serial.println("Failed to open file for writing");<br />
    return;<br />
  }<br />
  if (file.print(message)) {<br />
    Serial.println("File written");<br />
  } else {<br />
    Serial.println("Write failed");<br />
  }<br />
  file.close();<br />
}<br />
<br />
/**<br />
 * @brief Appends a message to a file on the SD card.<br />
 * <br />
 * @param fs File system to use (in this case, SD).<br />
 * @param path Path of the file to append.<br />
 * @param message Message to append to the file.<br />
 */<br />
void appendFile(fs::FS &amp;fs, const char * path, const char * message) {<br />
  Serial.printf("Appending to file: %s&#92;n", path);<br />
<br />
  File file = fs.open(path, FILE_APPEND);<br />
  if (!file) {<br />
    Serial.println("Failed to open file for appending");<br />
    return;<br />
  }<br />
  if (file.print(message)) {<br />
    Serial.println("Message appended");<br />
  } else {<br />
    Serial.println("Append failed");<br />
  }<br />
  file.close();<br />
}<br />
<br />
/**<br />
 * @brief Deletes a file from the SD card.<br />
 * <br />
 * @param fs File system to use (in this case, SD).<br />
 * @param path Path of the file to delete.<br />
 */<br />
void deleteFile(fs::FS &amp;fs, const char * path) {<br />
  Serial.printf("Deleting file: %s&#92;n", path);<br />
  if (fs.remove(path)) {<br />
    Serial.println("File deleted");<br />
  } else {<br />
    Serial.println("Delete failed");<br />
  }<br />
}<br />
<br />
/**<br />
 * @brief Tests file read and write performance.<br />
 * <br />
 * @param fs File system to use (in this case, SD).<br />
 * @param path Path of the file to test.<br />
 */<br />
void testFileIO(fs::FS &amp;fs, const char * path) {<br />
  File file = fs.open(path);<br />
  static uint8_t buf[512];<br />
  size_t len = 0;<br />
  uint32_t start = millis();<br />
  uint32_t end = start;<br />
<br />
  if (file) {<br />
    len = file.size();<br />
    size_t flen = len;<br />
    start = millis();<br />
    while (len) {<br />
      size_t toRead = len;<br />
      if (toRead &gt; 512) {<br />
        toRead = 512;<br />
      }<br />
      file.read(buf, toRead);<br />
      len -= toRead;<br />
    }<br />
    end = millis() - start;<br />
    Serial.printf("%u bytes read for %u ms&#92;n", flen, end);<br />
    file.close();<br />
  } else {<br />
    Serial.println("Failed to open file for reading");<br />
  }<br />
<br />
  file = fs.open(path, FILE_WRITE);<br />
  if (!file) {<br />
    Serial.println("Failed to open file for writing");<br />
    return;<br />
  }<br />
<br />
  size_t i;<br />
  start = millis();<br />
  for (i = 0; i &lt; 2048; i++) {<br />
    file.write(buf, 512);<br />
  }<br />
  end = millis() - start;<br />
  Serial.printf("%u bytes written for %u ms&#92;n", 2048 * 512, end);<br />
  file.close();<br />
}<br />
<br />
void setup() {<br />
  // Initialize serial communication<br />
  Serial.begin(115200);<br />
  <br />
  // Initialize SPI and SD card<br />
  SPIClass spi = SPIClass(HSPI);<br />
  spi.begin(SCK, MISO, MOSI, CS);<br />
<br />
  if (!SD.begin(CS, spi, 80000000)) {<br />
    Serial.println("Card Mount Failed");<br />
    return;<br />
  }<br />
<br />
  uint8_t cardType = SD.cardType();<br />
<br />
  if (cardType == CARD_NONE) {<br />
    Serial.println("No SD card attached");<br />
    return;<br />
  }<br />
<br />
  Serial.print("SD Card Type: ");<br />
  if (cardType == CARD_MMC) {<br />
    Serial.println("MMC");<br />
  } else if (cardType == CARD_SD) {<br />
    Serial.println("SDSC");<br />
  } else if (cardType == CARD_SDHC) {<br />
    Serial.println("SDHC");<br />
  } else {<br />
    Serial.println("UNKNOWN");<br />
  }<br />
<br />
  uint64_t cardSize = SD.cardSize() / (1024 * 1024);<br />
  Serial.printf("SD Card Size: %lluMB&#92;n", cardSize);<br />
  delay(2000);<br />
<br />
  // Perform file operations<br />
  deleteFile(SD, "/hello.txt");<br />
  writeFile(SD, "/hello.txt", "Hello ");<br />
  appendFile(SD, "/hello.txt", "World!&#92;n");<br />
  readFile(SD, "/hello.txt");<br />
  testFileIO(SD, "/test.txt");<br />
  Serial.printf("Total space: %lluMB&#92;n", SD.totalBytes() / (1024 * 1024));<br />
  Serial.printf("Used space: %lluMB&#92;n", SD.usedBytes() / (1024 * 1024));<br />
}<br />
<br />
void loop() {<br />
  // No operation in loop<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10344" target="_blank" title="">6-SD.zip</a> (Size: 1.53 KB / Downloads: 128)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10345" target="_blank" title="">6-SD.ino.merged.zip</a> (Size: 221.25 KB / Downloads: 136)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-05 Read PT100 temperature sensor]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9786</link>
			<pubDate>Mon, 31 Aug 2026 08:04:58 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9786</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;Adafruit_MAX31865.h&gt;<br />
<br />
namespace {<br />
<br />
// CO16 MAX31865 software-SPI pins.<br />
constexpr int kPt100Sclk = 11;<br />
constexpr int kPt100Mosi = 10;<br />
constexpr int kPt100Miso = 12;<br />
constexpr int kPt100Cs = 14;<br />
<br />
// NX3L4051PW channel select pins. S3 is fixed LOW on CO16.<br />
constexpr int kMuxS1 = 7;<br />
constexpr int kMuxS2 = 21;<br />
constexpr uint8_t kPt100ChannelCount = 4;<br />
constexpr uint32_t kMuxSettlingTimeMs = 20;<br />
<br />
constexpr float kRtdNominalOhms = 100.0F;<br />
constexpr float kReferenceResistorOhms = 400.0F;<br />
<br />
Adafruit_MAX31865 pt100(kPt100Cs, kPt100Mosi, kPt100Miso, kPt100Sclk);<br />
<br />
void selectPt100Channel(uint8_t channel) {<br />
  const uint8_t muxAddress = channel - 1;<br />
<br />
  // S3 is fixed LOW, so S2/S1 select PT100 channels 1-4.<br />
  digitalWrite(kMuxS1, muxAddress &amp; 0x01);<br />
  digitalWrite(kMuxS2, (muxAddress &gt;&gt; 1) &amp; 0x01);<br />
  delay(kMuxSettlingTimeMs);<br />
}<br />
<br />
void printFault(uint8_t fault) {<br />
  Serial.printf(" fault=0x%02X", fault);<br />
  if (fault &amp; MAX31865_FAULT_HIGHTHRESH) {<br />
    Serial.print(" RTD_HIGH_THRESHOLD");<br />
  }<br />
  if (fault &amp; MAX31865_FAULT_LOWTHRESH) {<br />
    Serial.print(" RTD_LOW_THRESHOLD");<br />
  }<br />
  if (fault &amp; MAX31865_FAULT_REFINLOW) {<br />
    Serial.print(" REFIN_HIGH");<br />
  }<br />
  if (fault &amp; MAX31865_FAULT_REFINHIGH) {<br />
    Serial.print(" REFIN_LOW_OR_FORCE_OPEN");<br />
  }<br />
  if (fault &amp; MAX31865_FAULT_RTDINLOW) {<br />
    Serial.print(" RTDIN_LOW_OR_FORCE_OPEN");<br />
  }<br />
  if (fault &amp; MAX31865_FAULT_OVUV) {<br />
    Serial.print(" OVER_UNDERVOLTAGE");<br />
  }<br />
}<br />
<br />
}  // namespace<br />
<br />
void setup() {<br />
  Serial.begin(115200);<br />
  delay(1000);<br />
<br />
  pinMode(kMuxS1, OUTPUT);<br />
  pinMode(kMuxS2, OUTPUT);<br />
  selectPt100Channel(1);<br />
<br />
  Serial.println();<br />
  Serial.println("KinCony CO16 four-channel PT100 example");<br />
  Serial.printf("MUX S3=LOW S2=%d S1=%d; SPI SCLK=%d MOSI=%d MISO=%d CS=%d&#92;n",<br />
                kMuxS2, kMuxS1, kPt100Sclk, kPt100Mosi, kPt100Miso,<br />
                kPt100Cs);<br />
<br />
  pt100.begin(MAX31865_3WIRE);<br />
  pt100.enable50Hz(true);<br />
}<br />
<br />
void loop() {<br />
  for (uint8_t channel = 1; channel &lt;= kPt100ChannelCount; ++channel) {<br />
    selectPt100Channel(channel);<br />
<br />
    const uint16_t raw = pt100.readRTD();<br />
    const float resistance =<br />
        static_cast&lt;float&gt;(raw) * kReferenceResistorOhms / 32768.0F;<br />
    const float temperature = pt100.calculateTemperature(<br />
        raw, kRtdNominalOhms, kReferenceResistorOhms);<br />
    const uint8_t fault = pt100.readFault(MAX31865_FAULT_NONE);<br />
<br />
    Serial.printf("CH%u raw=%u resistance=%.3f ohm temperature=%.2f C",<br />
                  channel, raw, resistance, temperature);<br />
    if (fault != 0) {<br />
      printFault(fault);<br />
      pt100.clearFault();<br />
    } else {<br />
      Serial.print(" fault=0x00");<br />
    }<br />
    Serial.println();<br />
  }<br />
<br />
  Serial.println();<br />
  delay(1000);<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10341" target="_blank" title="">5-PT100.zip</a> (Size: 1.11 KB / Downloads: 133)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10342" target="_blank" title="">5-PT100.ino.merged.zip</a> (Size: 198.5 KB / Downloads: 132)
<!-- end: postbit_attachments_attachment --><br />
before run code , need install max31865 arduino library<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10343" target="_blank" title="">max31865-library.png</a> (Size: 122.09 KB / Downloads: 127)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10556" target="_blank" title="">pt100-test.jpg</a> (Size: 593.56 KB / Downloads: 98)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>#include &lt;Adafruit_MAX31865.h&gt;<br />
<br />
namespace {<br />
<br />
// CO16 MAX31865 software-SPI pins.<br />
constexpr int kPt100Sclk = 11;<br />
constexpr int kPt100Mosi = 10;<br />
constexpr int kPt100Miso = 12;<br />
constexpr int kPt100Cs = 14;<br />
<br />
// NX3L4051PW channel select pins. S3 is fixed LOW on CO16.<br />
constexpr int kMuxS1 = 7;<br />
constexpr int kMuxS2 = 21;<br />
constexpr uint8_t kPt100ChannelCount = 4;<br />
constexpr uint32_t kMuxSettlingTimeMs = 20;<br />
<br />
constexpr float kRtdNominalOhms = 100.0F;<br />
constexpr float kReferenceResistorOhms = 400.0F;<br />
<br />
Adafruit_MAX31865 pt100(kPt100Cs, kPt100Mosi, kPt100Miso, kPt100Sclk);<br />
<br />
void selectPt100Channel(uint8_t channel) {<br />
  const uint8_t muxAddress = channel - 1;<br />
<br />
  // S3 is fixed LOW, so S2/S1 select PT100 channels 1-4.<br />
  digitalWrite(kMuxS1, muxAddress &amp; 0x01);<br />
  digitalWrite(kMuxS2, (muxAddress &gt;&gt; 1) &amp; 0x01);<br />
  delay(kMuxSettlingTimeMs);<br />
}<br />
<br />
void printFault(uint8_t fault) {<br />
  Serial.printf(" fault=0x%02X", fault);<br />
  if (fault &amp; MAX31865_FAULT_HIGHTHRESH) {<br />
    Serial.print(" RTD_HIGH_THRESHOLD");<br />
  }<br />
  if (fault &amp; MAX31865_FAULT_LOWTHRESH) {<br />
    Serial.print(" RTD_LOW_THRESHOLD");<br />
  }<br />
  if (fault &amp; MAX31865_FAULT_REFINLOW) {<br />
    Serial.print(" REFIN_HIGH");<br />
  }<br />
  if (fault &amp; MAX31865_FAULT_REFINHIGH) {<br />
    Serial.print(" REFIN_LOW_OR_FORCE_OPEN");<br />
  }<br />
  if (fault &amp; MAX31865_FAULT_RTDINLOW) {<br />
    Serial.print(" RTDIN_LOW_OR_FORCE_OPEN");<br />
  }<br />
  if (fault &amp; MAX31865_FAULT_OVUV) {<br />
    Serial.print(" OVER_UNDERVOLTAGE");<br />
  }<br />
}<br />
<br />
}  // namespace<br />
<br />
void setup() {<br />
  Serial.begin(115200);<br />
  delay(1000);<br />
<br />
  pinMode(kMuxS1, OUTPUT);<br />
  pinMode(kMuxS2, OUTPUT);<br />
  selectPt100Channel(1);<br />
<br />
  Serial.println();<br />
  Serial.println("KinCony CO16 four-channel PT100 example");<br />
  Serial.printf("MUX S3=LOW S2=%d S1=%d; SPI SCLK=%d MOSI=%d MISO=%d CS=%d&#92;n",<br />
                kMuxS2, kMuxS1, kPt100Sclk, kPt100Mosi, kPt100Miso,<br />
                kPt100Cs);<br />
<br />
  pt100.begin(MAX31865_3WIRE);<br />
  pt100.enable50Hz(true);<br />
}<br />
<br />
void loop() {<br />
  for (uint8_t channel = 1; channel &lt;= kPt100ChannelCount; ++channel) {<br />
    selectPt100Channel(channel);<br />
<br />
    const uint16_t raw = pt100.readRTD();<br />
    const float resistance =<br />
        static_cast&lt;float&gt;(raw) * kReferenceResistorOhms / 32768.0F;<br />
    const float temperature = pt100.calculateTemperature(<br />
        raw, kRtdNominalOhms, kReferenceResistorOhms);<br />
    const uint8_t fault = pt100.readFault(MAX31865_FAULT_NONE);<br />
<br />
    Serial.printf("CH%u raw=%u resistance=%.3f ohm temperature=%.2f C",<br />
                  channel, raw, resistance, temperature);<br />
    if (fault != 0) {<br />
      printFault(fault);<br />
      pt100.clearFault();<br />
    } else {<br />
      Serial.print(" fault=0x00");<br />
    }<br />
    Serial.println();<br />
  }<br />
<br />
  Serial.println();<br />
  delay(1000);<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10341" target="_blank" title="">5-PT100.zip</a> (Size: 1.11 KB / Downloads: 133)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10342" target="_blank" title="">5-PT100.ino.merged.zip</a> (Size: 198.5 KB / Downloads: 132)
<!-- end: postbit_attachments_attachment --><br />
before run code , need install max31865 arduino library<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="PNG Image" border="0" alt=".png" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10343" target="_blank" title="">max31865-library.png</a> (Size: 122.09 KB / Downloads: 127)
<!-- end: postbit_attachments_attachment --><br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/image.png" title="JPG Image" border="0" alt=".jpg" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10556" target="_blank" title="">pt100-test.jpg</a> (Size: 593.56 KB / Downloads: 98)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-04 RS485 communication test]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9785</link>
			<pubDate>Mon, 31 Aug 2026 08:02:27 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9785</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 *<br />
 * RS485 Communication Test<br />
 *<br />
 * This program is a simple test for RS485 communication using ESP32-S3.<br />
 * It will send a message over RS485 and then read incoming messages.<br />
 * The TXD pin is defined as GPIO 18 and RXD pin is defined as GPIO 8.<br />
 */<br />
<br />
#include &lt;HardwareSerial.h&gt;<br />
<br />
// Define RS485 pins<br />
#define RS485_RXD 38<br />
#define RS485_TXD 39<br />
<br />
// Create a hardware serial object<br />
HardwareSerial rs485Serial(1);<br />
<br />
void setup() {<br />
  // Start serial communication for debugging<br />
  Serial.begin(115200);<br />
  while (!Serial);<br />
<br />
  // Initialize RS485 Serial communication<br />
  rs485Serial.begin(9600, SERIAL_8N1, RS485_RXD, RS485_TXD);<br />
  <br />
  Serial.println("RS485 Test Start");<br />
}<br />
<br />
void loop() {<br />
  // Send a test message<br />
  rs485Serial.println("Hello from KinCony!");<br />
<br />
  // Wait for a short period<br />
  delay(1000);<br />
<br />
  // Check if data is available to read<br />
  if (rs485Serial.available()) {<br />
    String receivedMessage = "";<br />
    while (rs485Serial.available()) {<br />
      char c = rs485Serial.read();<br />
      receivedMessage += c;<br />
    }<br />
    // Print the received message<br />
    Serial.print("Received: ");<br />
    Serial.println(receivedMessage);<br />
  }<br />
<br />
  // Wait before sending the next message<br />
  delay(2000);<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10339" target="_blank" title="">4-RS485-Test.zip</a> (Size: 760 bytes / Downloads: 135)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10340" target="_blank" title="">4-RS485-Test.ino.merged.zip</a> (Size: 190.1 KB / Downloads: 119)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 *<br />
 * RS485 Communication Test<br />
 *<br />
 * This program is a simple test for RS485 communication using ESP32-S3.<br />
 * It will send a message over RS485 and then read incoming messages.<br />
 * The TXD pin is defined as GPIO 18 and RXD pin is defined as GPIO 8.<br />
 */<br />
<br />
#include &lt;HardwareSerial.h&gt;<br />
<br />
// Define RS485 pins<br />
#define RS485_RXD 38<br />
#define RS485_TXD 39<br />
<br />
// Create a hardware serial object<br />
HardwareSerial rs485Serial(1);<br />
<br />
void setup() {<br />
  // Start serial communication for debugging<br />
  Serial.begin(115200);<br />
  while (!Serial);<br />
<br />
  // Initialize RS485 Serial communication<br />
  rs485Serial.begin(9600, SERIAL_8N1, RS485_RXD, RS485_TXD);<br />
  <br />
  Serial.println("RS485 Test Start");<br />
}<br />
<br />
void loop() {<br />
  // Send a test message<br />
  rs485Serial.println("Hello from KinCony!");<br />
<br />
  // Wait for a short period<br />
  delay(1000);<br />
<br />
  // Check if data is available to read<br />
  if (rs485Serial.available()) {<br />
    String receivedMessage = "";<br />
    while (rs485Serial.available()) {<br />
      char c = rs485Serial.read();<br />
      receivedMessage += c;<br />
    }<br />
    // Print the received message<br />
    Serial.print("Received: ");<br />
    Serial.println(receivedMessage);<br />
  }<br />
<br />
  // Wait before sending the next message<br />
  delay(2000);<br />
}</code></div></div> arduino ino file download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10339" target="_blank" title="">4-RS485-Test.zip</a> (Size: 760 bytes / Downloads: 135)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download:  <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10340" target="_blank" title="">4-RS485-Test.ino.merged.zip</a> (Size: 190.1 KB / Downloads: 119)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-03 Read analog input ports value]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9784</link>
			<pubDate>Mon, 31 Aug 2026 08:00:50 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9784</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * KinCony 16-Channel Analog Input Monitor<br />
 *<br />
 * Four ADS1115 ADC modules are used:<br />
 *<br />
 * ADS1115-1 (U35): CH1-CH4 &nbsp;&nbsp;I2C address: 0x48<br />
 * ADS1115-2 (U7):  CH5-CH8 &nbsp;&nbsp;I2C address: 0x49<br />
 * ADS1115-3 (U15): CH9-CH12  I2C address: 0x4B<br />
 * ADS1115-4 (U19): CH13-CH16 I2C address: 0x4A<br />
 *<br />
 * ESPHome equivalent configuration:<br />
 *<br />
 * gain: 4.096<br />
 * filters:<br />
 * &nbsp;&nbsp;- lambda: 'return x &lt;= 0.0025f ? 0.0f : x;'<br />
 * &nbsp;&nbsp;- multiply: 5.16696<br />
 * &nbsp;&nbsp;- clamp:<br />
 *     &nbsp;&nbsp;min_value: 0.0<br />
 *     &nbsp;&nbsp;max_value: 10.0<br />
 *<br />
 * Only channels with an input voltage greater than 0.5V<br />
 * will be printed.<br />
 *<br />
 * Copyright: Made by KinCony IoT: https://www.kincony.com<br />
 */<br />
<br />
#include &lt;Wire.h&gt;<br />
#include &lt;DFRobot_ADS1115.h&gt;<br />
<br />
// I2C pins<br />
#define SDA_PIN 8<br />
#define SCL_PIN 18<br />
<br />
// ADS1115 I2C addresses<br />
#define ADS1115_1_ADDR 0x48<br />
#define ADS1115_2_ADDR 0x49<br />
#define ADS1115_3_ADDR 0x4B<br />
#define ADS1115_4_ADDR 0x4A<br />
<br />
// ESPHome calibration multiplier<br />
#define VOLTAGE_MULTIPLIER 5.16696f<br />
<br />
// ESPHome zero threshold<br />
#define ADC_ZERO_THRESHOLD 0.0025f<br />
<br />
// Print threshold<br />
#define PRINT_THRESHOLD 0.5f<br />
<br />
// Maximum input voltage<br />
#define MAX_INPUT_VOLTAGE 10.0f<br />
<br />
<br />
// Create ADS1115 objects<br />
DFRobot_ADS1115 ads1(&amp;Wire);<br />
DFRobot_ADS1115 ads2(&amp;Wire);<br />
DFRobot_ADS1115 ads3(&amp;Wire);<br />
DFRobot_ADS1115 ads4(&amp;Wire);<br />
<br />
<br />
/*<br />
 * Read ADS1115 channel and convert to actual input voltage.<br />
 *<br />
 * DFRobot readVoltage() returns voltage in mV.<br />
 *<br />
 * ESPHome:<br />
 *   &nbsp;&nbsp;x &lt;= 0.0025V -&gt; 0V<br />
 *   &nbsp;&nbsp;x * 5.16696<br />
 *   &nbsp;&nbsp;clamp 0~10V<br />
 */<br />
float readInputVoltage(DFRobot_ADS1115 &amp;ads, uint8_t channel)<br />
{<br />
    // Read ADC voltage in mV<br />
    uint16_t adc_mV = ads.readVoltage(channel);<br />
<br />
    // Convert mV to V<br />
    float adcVoltage = adc_mV / 1000.0f;<br />
<br />
    // ESPHome lambda filter<br />
    if (adcVoltage &lt;= ADC_ZERO_THRESHOLD)<br />
    {<br />
        return 0.0f;<br />
    }<br />
<br />
    // ESPHome multiply filter<br />
    float inputVoltage = adcVoltage * VOLTAGE_MULTIPLIER;<br />
<br />
    // ESPHome clamp filter<br />
    if (inputVoltage &lt; 0.0f)<br />
    {<br />
        inputVoltage = 0.0f;<br />
    }<br />
<br />
    if (inputVoltage &gt; MAX_INPUT_VOLTAGE)<br />
    {<br />
        inputVoltage = MAX_INPUT_VOLTAGE;<br />
    }<br />
<br />
    return inputVoltage;<br />
}<br />
<br />
<br />
/*<br />
 * Check one analog channel.<br />
 *<br />
 * If the actual input voltage is greater than 0.5V,<br />
 * print the channel number and voltage.<br />
 */<br />
void checkChannel(<br />
    DFRobot_ADS1115 &amp;ads,<br />
    uint8_t adcChannel,<br />
    uint8_t channelNumber)<br />
{<br />
    float voltage = readInputVoltage(ads, adcChannel);<br />
<br />
    if (voltage &gt; PRINT_THRESHOLD)<br />
    {<br />
        Serial.print("CH");<br />
        Serial.print(channelNumber);<br />
        Serial.print(": ");<br />
<br />
        Serial.print(voltage, 2);<br />
<br />
        Serial.println(" V");<br />
    }<br />
}<br />
<br />
<br />
void setup(void)<br />
{<br />
    // Initialize serial communication<br />
    Serial.begin(115200);<br />
<br />
    // Initialize I2C<br />
    Wire.begin(SDA_PIN, SCL_PIN);<br />
<br />
    /*<br />
   &nbsp;&nbsp;* ADS1115 gain = 4.096V<br />
   &nbsp;&nbsp;*<br />
   &nbsp;&nbsp;* This corresponds to ESPHome:<br />
   &nbsp;&nbsp;*<br />
   &nbsp;&nbsp;* gain: 4.096<br />
   &nbsp;&nbsp;*<br />
   &nbsp;&nbsp;* DFRobot library:<br />
   &nbsp;&nbsp;* eGAIN_ONE = 4.096V<br />
   &nbsp;&nbsp;*/<br />
<br />
    // ADS1115-1<br />
    ads1.setAddr_ADS1115(ADS1115_1_ADDR);<br />
    ads1.setGain(eGAIN_ONE);<br />
    ads1.setMode(eMODE_SINGLE);<br />
    ads1.setRate(eRATE_128);<br />
    ads1.setOSMode(eOSMODE_SINGLE);<br />
    ads1.init();<br />
<br />
    // ADS1115-2<br />
    ads2.setAddr_ADS1115(ADS1115_2_ADDR);<br />
    ads2.setGain(eGAIN_ONE);<br />
    ads2.setMode(eMODE_SINGLE);<br />
    ads2.setRate(eRATE_128);<br />
    ads2.setOSMode(eOSMODE_SINGLE);<br />
    ads2.init();<br />
<br />
    // ADS1115-3<br />
    ads3.setAddr_ADS1115(ADS1115_3_ADDR);<br />
    ads3.setGain(eGAIN_ONE);<br />
    ads3.setMode(eMODE_SINGLE);<br />
    ads3.setRate(eRATE_128);<br />
    ads3.setOSMode(eOSMODE_SINGLE);<br />
    ads3.init();<br />
<br />
    // ADS1115-4<br />
    ads4.setAddr_ADS1115(ADS1115_4_ADDR);<br />
    ads4.setGain(eGAIN_ONE);<br />
    ads4.setMode(eMODE_SINGLE);<br />
    ads4.setRate(eRATE_128);<br />
    ads4.setOSMode(eOSMODE_SINGLE);<br />
    ads4.init();<br />
<br />
    Serial.println();<br />
    Serial.println("KinCony 16-Channel Analog Input Test");<br />
    Serial.println("--------------------------------------");<br />
    Serial.println("ADS1115 Gain: 4.096V");<br />
    Serial.println("Multiplier: 5.16696");<br />
    Serial.println("Print threshold: 0.50V");<br />
    Serial.println();<br />
}<br />
<br />
<br />
void loop(void)<br />
{<br />
    // ADS1115-1: CH1 ~ CH4<br />
    if (ads1.checkADS1115())<br />
    {<br />
        checkChannel(ads1, 0, 1);<br />
        checkChannel(ads1, 1, 2);<br />
        checkChannel(ads1, 2, 3);<br />
        checkChannel(ads1, 3, 4);<br />
    }<br />
    else<br />
    {<br />
        Serial.println("ADS1115-1 (0x48) Disconnected!");<br />
    }<br />
<br />
<br />
    // ADS1115-2: CH5 ~ CH8<br />
    if (ads2.checkADS1115())<br />
    {<br />
        checkChannel(ads2, 0, 5);<br />
        checkChannel(ads2, 1, 6);<br />
        checkChannel(ads2, 2, 7);<br />
        checkChannel(ads2, 3, 8);<br />
    }<br />
    else<br />
    {<br />
        Serial.println("ADS1115-2 (0x49) Disconnected!");<br />
    }<br />
<br />
<br />
    // ADS1115-3: CH9 ~ CH12<br />
    if (ads3.checkADS1115())<br />
    {<br />
        checkChannel(ads3, 0, 9);<br />
        checkChannel(ads3, 1, 10);<br />
        checkChannel(ads3, 2, 11);<br />
        checkChannel(ads3, 3, 12);<br />
    }<br />
    else<br />
    {<br />
        Serial.println("ADS1115-3 (0x4B) Disconnected!");<br />
    }<br />
<br />
<br />
    // ADS1115-4: CH13 ~ CH16<br />
    if (ads4.checkADS1115())<br />
    {<br />
        checkChannel(ads4, 0, 13);<br />
        checkChannel(ads4, 1, 14);<br />
        checkChannel(ads4, 2, 15);<br />
        checkChannel(ads4, 3, 16);<br />
    }<br />
    else<br />
    {<br />
        Serial.println("ADS1115-4 (0x4A) Disconnected!");<br />
    }<br />
<br />
<br />
    // Scan every 1 second<br />
    delay(1000);<br />
}</code></div></div> arduino ino file download:<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10337" target="_blank" title="">3-ads1115_adc.zip</a> (Size: 1.68 KB / Downloads: 124)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10338" target="_blank" title="">3-ads1115_adc.ino.merged.zip</a> (Size: 195.04 KB / Downloads: 120)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * KinCony 16-Channel Analog Input Monitor<br />
 *<br />
 * Four ADS1115 ADC modules are used:<br />
 *<br />
 * ADS1115-1 (U35): CH1-CH4 &nbsp;&nbsp;I2C address: 0x48<br />
 * ADS1115-2 (U7):  CH5-CH8 &nbsp;&nbsp;I2C address: 0x49<br />
 * ADS1115-3 (U15): CH9-CH12  I2C address: 0x4B<br />
 * ADS1115-4 (U19): CH13-CH16 I2C address: 0x4A<br />
 *<br />
 * ESPHome equivalent configuration:<br />
 *<br />
 * gain: 4.096<br />
 * filters:<br />
 * &nbsp;&nbsp;- lambda: 'return x &lt;= 0.0025f ? 0.0f : x;'<br />
 * &nbsp;&nbsp;- multiply: 5.16696<br />
 * &nbsp;&nbsp;- clamp:<br />
 *     &nbsp;&nbsp;min_value: 0.0<br />
 *     &nbsp;&nbsp;max_value: 10.0<br />
 *<br />
 * Only channels with an input voltage greater than 0.5V<br />
 * will be printed.<br />
 *<br />
 * Copyright: Made by KinCony IoT: https://www.kincony.com<br />
 */<br />
<br />
#include &lt;Wire.h&gt;<br />
#include &lt;DFRobot_ADS1115.h&gt;<br />
<br />
// I2C pins<br />
#define SDA_PIN 8<br />
#define SCL_PIN 18<br />
<br />
// ADS1115 I2C addresses<br />
#define ADS1115_1_ADDR 0x48<br />
#define ADS1115_2_ADDR 0x49<br />
#define ADS1115_3_ADDR 0x4B<br />
#define ADS1115_4_ADDR 0x4A<br />
<br />
// ESPHome calibration multiplier<br />
#define VOLTAGE_MULTIPLIER 5.16696f<br />
<br />
// ESPHome zero threshold<br />
#define ADC_ZERO_THRESHOLD 0.0025f<br />
<br />
// Print threshold<br />
#define PRINT_THRESHOLD 0.5f<br />
<br />
// Maximum input voltage<br />
#define MAX_INPUT_VOLTAGE 10.0f<br />
<br />
<br />
// Create ADS1115 objects<br />
DFRobot_ADS1115 ads1(&amp;Wire);<br />
DFRobot_ADS1115 ads2(&amp;Wire);<br />
DFRobot_ADS1115 ads3(&amp;Wire);<br />
DFRobot_ADS1115 ads4(&amp;Wire);<br />
<br />
<br />
/*<br />
 * Read ADS1115 channel and convert to actual input voltage.<br />
 *<br />
 * DFRobot readVoltage() returns voltage in mV.<br />
 *<br />
 * ESPHome:<br />
 *   &nbsp;&nbsp;x &lt;= 0.0025V -&gt; 0V<br />
 *   &nbsp;&nbsp;x * 5.16696<br />
 *   &nbsp;&nbsp;clamp 0~10V<br />
 */<br />
float readInputVoltage(DFRobot_ADS1115 &amp;ads, uint8_t channel)<br />
{<br />
    // Read ADC voltage in mV<br />
    uint16_t adc_mV = ads.readVoltage(channel);<br />
<br />
    // Convert mV to V<br />
    float adcVoltage = adc_mV / 1000.0f;<br />
<br />
    // ESPHome lambda filter<br />
    if (adcVoltage &lt;= ADC_ZERO_THRESHOLD)<br />
    {<br />
        return 0.0f;<br />
    }<br />
<br />
    // ESPHome multiply filter<br />
    float inputVoltage = adcVoltage * VOLTAGE_MULTIPLIER;<br />
<br />
    // ESPHome clamp filter<br />
    if (inputVoltage &lt; 0.0f)<br />
    {<br />
        inputVoltage = 0.0f;<br />
    }<br />
<br />
    if (inputVoltage &gt; MAX_INPUT_VOLTAGE)<br />
    {<br />
        inputVoltage = MAX_INPUT_VOLTAGE;<br />
    }<br />
<br />
    return inputVoltage;<br />
}<br />
<br />
<br />
/*<br />
 * Check one analog channel.<br />
 *<br />
 * If the actual input voltage is greater than 0.5V,<br />
 * print the channel number and voltage.<br />
 */<br />
void checkChannel(<br />
    DFRobot_ADS1115 &amp;ads,<br />
    uint8_t adcChannel,<br />
    uint8_t channelNumber)<br />
{<br />
    float voltage = readInputVoltage(ads, adcChannel);<br />
<br />
    if (voltage &gt; PRINT_THRESHOLD)<br />
    {<br />
        Serial.print("CH");<br />
        Serial.print(channelNumber);<br />
        Serial.print(": ");<br />
<br />
        Serial.print(voltage, 2);<br />
<br />
        Serial.println(" V");<br />
    }<br />
}<br />
<br />
<br />
void setup(void)<br />
{<br />
    // Initialize serial communication<br />
    Serial.begin(115200);<br />
<br />
    // Initialize I2C<br />
    Wire.begin(SDA_PIN, SCL_PIN);<br />
<br />
    /*<br />
   &nbsp;&nbsp;* ADS1115 gain = 4.096V<br />
   &nbsp;&nbsp;*<br />
   &nbsp;&nbsp;* This corresponds to ESPHome:<br />
   &nbsp;&nbsp;*<br />
   &nbsp;&nbsp;* gain: 4.096<br />
   &nbsp;&nbsp;*<br />
   &nbsp;&nbsp;* DFRobot library:<br />
   &nbsp;&nbsp;* eGAIN_ONE = 4.096V<br />
   &nbsp;&nbsp;*/<br />
<br />
    // ADS1115-1<br />
    ads1.setAddr_ADS1115(ADS1115_1_ADDR);<br />
    ads1.setGain(eGAIN_ONE);<br />
    ads1.setMode(eMODE_SINGLE);<br />
    ads1.setRate(eRATE_128);<br />
    ads1.setOSMode(eOSMODE_SINGLE);<br />
    ads1.init();<br />
<br />
    // ADS1115-2<br />
    ads2.setAddr_ADS1115(ADS1115_2_ADDR);<br />
    ads2.setGain(eGAIN_ONE);<br />
    ads2.setMode(eMODE_SINGLE);<br />
    ads2.setRate(eRATE_128);<br />
    ads2.setOSMode(eOSMODE_SINGLE);<br />
    ads2.init();<br />
<br />
    // ADS1115-3<br />
    ads3.setAddr_ADS1115(ADS1115_3_ADDR);<br />
    ads3.setGain(eGAIN_ONE);<br />
    ads3.setMode(eMODE_SINGLE);<br />
    ads3.setRate(eRATE_128);<br />
    ads3.setOSMode(eOSMODE_SINGLE);<br />
    ads3.init();<br />
<br />
    // ADS1115-4<br />
    ads4.setAddr_ADS1115(ADS1115_4_ADDR);<br />
    ads4.setGain(eGAIN_ONE);<br />
    ads4.setMode(eMODE_SINGLE);<br />
    ads4.setRate(eRATE_128);<br />
    ads4.setOSMode(eOSMODE_SINGLE);<br />
    ads4.init();<br />
<br />
    Serial.println();<br />
    Serial.println("KinCony 16-Channel Analog Input Test");<br />
    Serial.println("--------------------------------------");<br />
    Serial.println("ADS1115 Gain: 4.096V");<br />
    Serial.println("Multiplier: 5.16696");<br />
    Serial.println("Print threshold: 0.50V");<br />
    Serial.println();<br />
}<br />
<br />
<br />
void loop(void)<br />
{<br />
    // ADS1115-1: CH1 ~ CH4<br />
    if (ads1.checkADS1115())<br />
    {<br />
        checkChannel(ads1, 0, 1);<br />
        checkChannel(ads1, 1, 2);<br />
        checkChannel(ads1, 2, 3);<br />
        checkChannel(ads1, 3, 4);<br />
    }<br />
    else<br />
    {<br />
        Serial.println("ADS1115-1 (0x48) Disconnected!");<br />
    }<br />
<br />
<br />
    // ADS1115-2: CH5 ~ CH8<br />
    if (ads2.checkADS1115())<br />
    {<br />
        checkChannel(ads2, 0, 5);<br />
        checkChannel(ads2, 1, 6);<br />
        checkChannel(ads2, 2, 7);<br />
        checkChannel(ads2, 3, 8);<br />
    }<br />
    else<br />
    {<br />
        Serial.println("ADS1115-2 (0x49) Disconnected!");<br />
    }<br />
<br />
<br />
    // ADS1115-3: CH9 ~ CH12<br />
    if (ads3.checkADS1115())<br />
    {<br />
        checkChannel(ads3, 0, 9);<br />
        checkChannel(ads3, 1, 10);<br />
        checkChannel(ads3, 2, 11);<br />
        checkChannel(ads3, 3, 12);<br />
    }<br />
    else<br />
    {<br />
        Serial.println("ADS1115-3 (0x4B) Disconnected!");<br />
    }<br />
<br />
<br />
    // ADS1115-4: CH13 ~ CH16<br />
    if (ads4.checkADS1115())<br />
    {<br />
        checkChannel(ads4, 0, 13);<br />
        checkChannel(ads4, 1, 14);<br />
        checkChannel(ads4, 2, 15);<br />
        checkChannel(ads4, 3, 16);<br />
    }<br />
    else<br />
    {<br />
        Serial.println("ADS1115-4 (0x4A) Disconnected!");<br />
    }<br />
<br />
<br />
    // Scan every 1 second<br />
    delay(1000);<br />
}</code></div></div> arduino ino file download:<br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10337" target="_blank" title="">3-ads1115_adc.zip</a> (Size: 1.68 KB / Downloads: 124)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10338" target="_blank" title="">3-ads1115_adc.ino.merged.zip</a> (Size: 195.04 KB / Downloads: 120)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-02 Read digital input ports state]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9783</link>
			<pubDate>Mon, 31 Aug 2026 07:58:42 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9783</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
  Made by KinCony IoT: https://www.kincony.com<br />
<br />
  Program functionality:<br />
  This program uses an ESP32-S3 to read inputs from an XL9535 I/O expander chip <br />
  (I2C address 0x24) via I2C. The XL9535 chip handles channels 1-16. <br />
  The program reads the state of all these inputs and prints them in binary format <br />
  to the serial monitor.<br />
<br />
  Key points:<br />
  - The I2C bus is initialized on GPIO pins 8 (SDA) and 18 (SCL) with a frequency of 40kHz.<br />
  - The XL9535 chip reads the state of input pins for channels 1-16.<br />
  - The state of the inputs is printed to the serial monitor in binary format every second.<br />
  - Note: The original description mentioned three expander chips, but this implementation<br />
    currently uses only one XL9535 (PCA9555 compatible) at address 0x24.<br />
*/<br />
<br />
#include &lt;PCA95x5.h&gt;<br />
#include &lt;Wire.h&gt;<br />
<br />
// Initialize the PCA9555 object for XL9535 chip (channels 1-16)<br />
PCA9555 ioex1;  // XL9535 I/O expander at I2C address 0x24, handles channels 1-16<br />
<br />
void setup() {<br />
    // Start serial communication for debugging<br />
    Serial.begin(115200);<br />
    delay(2000);  // Wait for 2 seconds to ensure serial monitor is ready<br />
<br />
    // Initialize the I2C bus with GPIO 8 as SDA and GPIO 18 as SCL, 40kHz frequency<br />
    Wire.begin(8, 18, 40000);<br />
<br />
    // Attach the PCA9555 device at I2C address 0x24<br />
    ioex1.attach(Wire, 0x24);<br />
    <br />
    // Set polarity to original (no inversion)<br />
    ioex1.polarity(PCA95x5::Polarity::ORIGINAL_ALL);<br />
    <br />
    // Configure all 16 pins as inputs<br />
    ioex1.direction(PCA95x5::Direction::IN_ALL);<br />
}<br />
<br />
void loop() {<br />
    // Read and print the state of inputs from the XL9535 (channels 1-16)<br />
    Serial.print("1-16 input states: ");<br />
    // Read all 16 pins and print as 16-bit binary value<br />
    Serial.println(ioex1.read(), BIN);<br />
<br />
    delay(1000);  // Wait for 1 second before the next reading<br />
}</code></div></div> arduino ino file download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10335" target="_blank" title="">2-digital-input.zip</a> (Size: 1,013 bytes / Downloads: 132)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10336" target="_blank" title="">2-digital-input.ino.merged.zip</a> (Size: 197.84 KB / Downloads: 140)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
  Made by KinCony IoT: https://www.kincony.com<br />
<br />
  Program functionality:<br />
  This program uses an ESP32-S3 to read inputs from an XL9535 I/O expander chip <br />
  (I2C address 0x24) via I2C. The XL9535 chip handles channels 1-16. <br />
  The program reads the state of all these inputs and prints them in binary format <br />
  to the serial monitor.<br />
<br />
  Key points:<br />
  - The I2C bus is initialized on GPIO pins 8 (SDA) and 18 (SCL) with a frequency of 40kHz.<br />
  - The XL9535 chip reads the state of input pins for channels 1-16.<br />
  - The state of the inputs is printed to the serial monitor in binary format every second.<br />
  - Note: The original description mentioned three expander chips, but this implementation<br />
    currently uses only one XL9535 (PCA9555 compatible) at address 0x24.<br />
*/<br />
<br />
#include &lt;PCA95x5.h&gt;<br />
#include &lt;Wire.h&gt;<br />
<br />
// Initialize the PCA9555 object for XL9535 chip (channels 1-16)<br />
PCA9555 ioex1;  // XL9535 I/O expander at I2C address 0x24, handles channels 1-16<br />
<br />
void setup() {<br />
    // Start serial communication for debugging<br />
    Serial.begin(115200);<br />
    delay(2000);  // Wait for 2 seconds to ensure serial monitor is ready<br />
<br />
    // Initialize the I2C bus with GPIO 8 as SDA and GPIO 18 as SCL, 40kHz frequency<br />
    Wire.begin(8, 18, 40000);<br />
<br />
    // Attach the PCA9555 device at I2C address 0x24<br />
    ioex1.attach(Wire, 0x24);<br />
    <br />
    // Set polarity to original (no inversion)<br />
    ioex1.polarity(PCA95x5::Polarity::ORIGINAL_ALL);<br />
    <br />
    // Configure all 16 pins as inputs<br />
    ioex1.direction(PCA95x5::Direction::IN_ALL);<br />
}<br />
<br />
void loop() {<br />
    // Read and print the state of inputs from the XL9535 (channels 1-16)<br />
    Serial.print("1-16 input states: ");<br />
    // Read all 16 pins and print as 16-bit binary value<br />
    Serial.println(ioex1.read(), BIN);<br />
<br />
    delay(1000);  // Wait for 1 second before the next reading<br />
}</code></div></div> arduino ino file download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10335" target="_blank" title="">2-digital-input.zip</a> (Size: 1,013 bytes / Downloads: 132)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10336" target="_blank" title="">2-digital-input.ino.merged.zip</a> (Size: 197.84 KB / Downloads: 140)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
		<item>
			<title><![CDATA[[arduino code examples for CO16]-01 Turn ON/OFF relay]]></title>
			<link>https://www.kincony.com/forum/showthread.php?tid=9782</link>
			<pubDate>Mon, 31 Aug 2026 07:56:48 +0800</pubDate>
			<dc:creator><![CDATA[<a href="https://www.kincony.com/forum/member.php?action=profile&uid=1">admin</a>]]></dc:creator>
			<guid isPermaLink="false">https://www.kincony.com/forum/showthread.php?tid=9782</guid>
			<description><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 *<br />
 * This program controls a 16-channel relay board via a PCF8575 I/O expander.<br />
 * It sequentially turns on each relay and then turns them off in a loop.<br />
 * <br />
 * Pin Definitions:<br />
 * - SDA: GPIO 8<br />
 * - SCL: GPIO 18<br />
 * <br />
 * Delay Time:<br />
 * - 200 milliseconds between switching relays<br />
 */<br />
<br />
#include &lt;Wire.h&gt;        // Include the Wire library for I2C communication<br />
#include &lt;PCF8575.h&gt;   &nbsp;&nbsp;// Include the PCF8575 library to control the I/O expander<br />
<br />
#define SDA 8         &nbsp;&nbsp;// Define the SDA pin<br />
#define SCL 18         &nbsp;&nbsp;// Define the SCL pin<br />
#define DELAY_TIME 200 &nbsp;&nbsp;// Define the delay time in milliseconds<br />
<br />
// Set I2C address of the PCF8575 module<br />
#define I2C_ADDRESS 0x22 // I2C address of the PCF8575 module<br />
<br />
PCF8575 pcf8575_R1(I2C_ADDRESS); // Create a PCF8575 object with the specified I2C address<br />
<br />
void setup() {<br />
  // Initialize I2C communication<br />
  Wire.begin(SDA, SCL); // SDA on GPIO 8, SCL on GPIO 18 (according to your board's configuration)<br />
  <br />
  // Initialize serial communication for debugging (optional)<br />
  Serial.begin(115200);<br />
  Serial.println("PCF8575 Relay Control: Starting...");<br />
<br />
  // Initialize the PCF8575 module<br />
  pcf8575_R1.begin();<br />
<br />
  // Turn off all relays initially (set all pins HIGH)<br />
  for (int i = 0; i &lt; 16; i++) {<br />
    pcf8575_R1.write(i, HIGH); // Set all relays to OFF (assuming HIGH means OFF for relays)<br />
  }<br />
}<br />
<br />
void loop() {<br />
  // Sequentially turn on each relay<br />
  for (int i = 0; i &lt; 16; i++) {<br />
    pcf8575_R1.write(i, LOW); &nbsp;&nbsp;// Turn on the relay at pin i (LOW means ON for the relay)<br />
    delay(DELAY_TIME);          // Wait for DELAY_TIME milliseconds<br />
  }<br />
<br />
  // Sequentially turn off each relay<br />
  for (int i = 0; i &lt; 16; i++) {<br />
    pcf8575_R1.write(i, HIGH);  // Turn off the relay at pin i (HIGH means OFF for the relay)<br />
    delay(DELAY_TIME);          // Wait for DELAY_TIME milliseconds<br />
  }<br />
}</code></div></div> arduino ino file download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10333" target="_blank" title="">1-output.zip</a> (Size: 926 bytes / Downloads: 131)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10334" target="_blank" title="">1-output.ino.merged.zip</a> (Size: 197.66 KB / Downloads: 135)
<!-- end: postbit_attachments_attachment -->]]></description>
			<content:encoded><![CDATA[<div class="codeblock"><div class="title">Code:</div><div class="body" dir="ltr"><code>/*<br />
 * Made by KinCony IoT: https://www.kincony.com<br />
 *<br />
 * This program controls a 16-channel relay board via a PCF8575 I/O expander.<br />
 * It sequentially turns on each relay and then turns them off in a loop.<br />
 * <br />
 * Pin Definitions:<br />
 * - SDA: GPIO 8<br />
 * - SCL: GPIO 18<br />
 * <br />
 * Delay Time:<br />
 * - 200 milliseconds between switching relays<br />
 */<br />
<br />
#include &lt;Wire.h&gt;        // Include the Wire library for I2C communication<br />
#include &lt;PCF8575.h&gt;   &nbsp;&nbsp;// Include the PCF8575 library to control the I/O expander<br />
<br />
#define SDA 8         &nbsp;&nbsp;// Define the SDA pin<br />
#define SCL 18         &nbsp;&nbsp;// Define the SCL pin<br />
#define DELAY_TIME 200 &nbsp;&nbsp;// Define the delay time in milliseconds<br />
<br />
// Set I2C address of the PCF8575 module<br />
#define I2C_ADDRESS 0x22 // I2C address of the PCF8575 module<br />
<br />
PCF8575 pcf8575_R1(I2C_ADDRESS); // Create a PCF8575 object with the specified I2C address<br />
<br />
void setup() {<br />
  // Initialize I2C communication<br />
  Wire.begin(SDA, SCL); // SDA on GPIO 8, SCL on GPIO 18 (according to your board's configuration)<br />
  <br />
  // Initialize serial communication for debugging (optional)<br />
  Serial.begin(115200);<br />
  Serial.println("PCF8575 Relay Control: Starting...");<br />
<br />
  // Initialize the PCF8575 module<br />
  pcf8575_R1.begin();<br />
<br />
  // Turn off all relays initially (set all pins HIGH)<br />
  for (int i = 0; i &lt; 16; i++) {<br />
    pcf8575_R1.write(i, HIGH); // Set all relays to OFF (assuming HIGH means OFF for relays)<br />
  }<br />
}<br />
<br />
void loop() {<br />
  // Sequentially turn on each relay<br />
  for (int i = 0; i &lt; 16; i++) {<br />
    pcf8575_R1.write(i, LOW); &nbsp;&nbsp;// Turn on the relay at pin i (LOW means ON for the relay)<br />
    delay(DELAY_TIME);          // Wait for DELAY_TIME milliseconds<br />
  }<br />
<br />
  // Sequentially turn off each relay<br />
  for (int i = 0; i &lt; 16; i++) {<br />
    pcf8575_R1.write(i, HIGH);  // Turn off the relay at pin i (HIGH means OFF for the relay)<br />
    delay(DELAY_TIME);          // Wait for DELAY_TIME milliseconds<br />
  }<br />
}</code></div></div> arduino ino file download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10333" target="_blank" title="">1-output.zip</a> (Size: 926 bytes / Downloads: 131)
<!-- end: postbit_attachments_attachment --><br />
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download: <br />
<!-- start: postbit_attachments_attachment -->
<br /><!-- start: attachment_icon -->
<img src="https://www.kincony.com/forum/images/attachtypes/zip.png" title="ZIP File" border="0" alt=".zip" />
<!-- end: attachment_icon -->&nbsp;&nbsp;<a href="attachment.php?aid=10334" target="_blank" title="">1-output.ino.merged.zip</a> (Size: 197.66 KB / Downloads: 135)
<!-- end: postbit_attachments_attachment -->]]></content:encoded>
		</item>
	</channel>
</rss>