2025年12月6日 星期六

I2C LCD with ESP32 using ESP-IDF

I2C LCD with ESP32 using ESP-IDF





main.c

#include <stdio.h>
#include "driver/i2c.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "HD44780.h"

#define LCD_ADDR 0x27
// *** 修正: 確保這裡只有標準空格 ***
#define SDA_PIN 21
#define SCL_PIN 22
// ********************************
#define LCD_COLS 16
#define LCD_ROWS 2

void LCD_DemoTask(void* param)
{
    char num[20];
    while (true) {
        LCD_home();
        LCD_clearScreen();
        LCD_writeStr("16x2 I2C LCD");
        // *** 修正: 替換為 portTICK_PERIOD_MS ***
        vTaskDelay(3000 / portTICK_PERIOD_MS);
       
        LCD_clearScreen();
        LCD_writeStr("Lets Count 0-10!");
        // *** 修正: 替換為 portTICK_PERIOD_MS ***
        vTaskDelay(3000 / portTICK_PERIOD_MS);
       
        LCD_clearScreen();
        for (int i = 0; i <= 10; i++) {
            // 游標設定在 (col, row)。假設 row 從 0 開始,1 為第二行
            LCD_setCursor(7, 1);
            sprintf(num, "%2d", i);
            LCD_writeStr(num);
            // *** 修正: 替換為 portTICK_PERIOD_MS ***
            vTaskDelay(1000 / portTICK_PERIOD_MS);
        }
       
        // 額外的延遲,讓計數結果顯示得久一點
        vTaskDelay(3000 / portTICK_PERIOD_MS);
    }
}

void app_main(void)
{
    LCD_init(LCD_ADDR, SDA_PIN, SCL_PIN, LCD_COLS, LCD_ROWS);
    // 增加堆疊大小以提供更多的安全空間 (2048 * 2)
    xTaskCreate(&LCD_DemoTask, "Demo Task", 4096, NULL, 5, NULL);
}


hd44780.h

#pragma once
#include <stdint.h>
void LCD_init(uint8_t addr, uint8_t dataPin, uint8_t clockPin, uint8_t cols, uint8_t rows);
void LCD_setCursor(uint8_t col, uint8_t row);
void LCD_home(void);
void LCD_clearScreen(void);
void LCD_writeChar(char c);
void LCD_writeStr(char* str);

hd44780.c

#include <driver/i2c.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <stdio.h>
#include "sdkconfig.h"
#include "rom/ets_sys.h"
#include <esp_log.h>

// LCD module defines
#define LCD_LINEONE             0x00        // start of line 1
#define LCD_LINETWO             0x40        // start of line 2
#define LCD_LINETHREE           0x14        // start of line 3
#define LCD_LINEFOUR            0x54        // start of line 4

#define LCD_BACKLIGHT           0x08
#define LCD_ENABLE              0x04              
#define LCD_COMMAND             0x00
#define LCD_WRITE               0x01

#define LCD_SET_DDRAM_ADDR      0x80
#define LCD_READ_BF             0x40

// LCD instructions
#define LCD_CLEAR               0x01        // replace all characters with ASCII 'space'
#define LCD_HOME                0x02        // return cursor to first position on first line
#define LCD_ENTRY_MODE          0x06        // shift cursor from left to right on read/write
#define LCD_DISPLAY_OFF         0x08        // turn display off
#define LCD_DISPLAY_ON          0x0C        // display on, cursor off, don't blink character
#define LCD_FUNCTION_RESET      0x30        // reset the LCD
#define LCD_FUNCTION_SET_4BIT   0x28        // 4-bit data, 2-line display, 5 x 7 font
#define LCD_SET_CURSOR          0x80        // set cursor position

// Pin mappings
// P0 -> RS
// P1 -> RW
// P2 -> E
// P3 -> Backlight
// P4 -> D4
// P5 -> D5
// P6 -> D6
// P7 -> D7

static char tag[] = "LCD Driver";
static uint8_t LCD_addr;
static uint8_t SDA_pin;
static uint8_t SCL_pin;
static uint8_t LCD_cols;
static uint8_t LCD_rows;

static void LCD_writeNibble(uint8_t nibble, uint8_t mode);
static void LCD_writeByte(uint8_t data, uint8_t mode);
static void LCD_pulseEnable(uint8_t nibble);

static esp_err_t I2C_init(void)
{
    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = SDA_pin,
        .scl_io_num = SCL_pin,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = 100000
    };
  i2c_param_config(I2C_NUM_0, &conf);
  i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0);
    return ESP_OK;
}

void LCD_init(uint8_t addr, uint8_t dataPin, uint8_t clockPin, uint8_t cols, uint8_t rows)
{
    LCD_addr = addr;
    SDA_pin = dataPin;
    SCL_pin = clockPin;
    LCD_cols = cols;
    LCD_rows = rows;
    I2C_init();
    vTaskDelay(100 / portTICK_PERIOD_MS);                                 // Initial 40 mSec delay

    // Reset the LCD controller
    LCD_writeNibble(LCD_FUNCTION_RESET, LCD_COMMAND);                   // First part of reset sequence
    vTaskDelay(10 / portTICK_PERIOD_MS);                                  // 4.1 mS delay (min)
    LCD_writeNibble(LCD_FUNCTION_RESET, LCD_COMMAND);                   // second part of reset sequence
    ets_delay_us(200);                                                  // 100 uS delay (min)
    LCD_writeNibble(LCD_FUNCTION_RESET, LCD_COMMAND);                   // Third time's a charm
    LCD_writeNibble(LCD_FUNCTION_SET_4BIT, LCD_COMMAND);                // Activate 4-bit mode
    ets_delay_us(80);                                                   // 40 uS delay (min)

    // --- Busy flag now available ---
    // Function Set instruction
    LCD_writeByte(LCD_FUNCTION_SET_4BIT, LCD_COMMAND);                  // Set mode, lines, and font
    ets_delay_us(80);

    // Clear Display instruction
    LCD_writeByte(LCD_CLEAR, LCD_COMMAND);                              // clear display RAM
    vTaskDelay(2 / portTICK_PERIOD_MS);                                   // Clearing memory takes a bit longer
   
    // Entry Mode Set instruction
    LCD_writeByte(LCD_ENTRY_MODE, LCD_COMMAND);                         // Set desired shift characteristics
    ets_delay_us(80);

    LCD_writeByte(LCD_DISPLAY_ON, LCD_COMMAND);                         // Ensure LCD is set to on
}

void LCD_setCursor(uint8_t col, uint8_t row)
{
    if (row > LCD_rows - 1) {
        ESP_LOGE(tag, "Cannot write to row %d. Please select a row in the range (0, %d)", row, LCD_rows-1);
        row = LCD_rows - 1;
    }
    uint8_t row_offsets[] = {LCD_LINEONE, LCD_LINETWO, LCD_LINETHREE, LCD_LINEFOUR};
    LCD_writeByte(LCD_SET_DDRAM_ADDR | (col + row_offsets[row]), LCD_COMMAND);
}

void LCD_writeChar(char c)
{
    LCD_writeByte(c, LCD_WRITE);                                        // Write data to DDRAM
}

void LCD_writeStr(char* str)
{
    while (*str) {
        LCD_writeChar(*str++);
    }
}

void LCD_home(void)
{
    LCD_writeByte(LCD_HOME, LCD_COMMAND);
    vTaskDelay(2 / portTICK_PERIOD_MS);                                   // This command takes a while to complete
}

void LCD_clearScreen(void)
{
    LCD_writeByte(LCD_CLEAR, LCD_COMMAND);
    vTaskDelay(2 / portTICK_PERIOD_MS);                                   // This command takes a while to complete
}

static void LCD_writeNibble(uint8_t nibble, uint8_t mode)
{
    uint8_t data = (nibble & 0xF0) | mode | LCD_BACKLIGHT;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    ESP_ERROR_CHECK(i2c_master_start(cmd));
    ESP_ERROR_CHECK(i2c_master_write_byte(cmd, (LCD_addr << 1) | I2C_MASTER_WRITE, 1));
    ESP_ERROR_CHECK(i2c_master_write_byte(cmd, data, 1));
    ESP_ERROR_CHECK(i2c_master_stop(cmd));
    ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000/portTICK_PERIOD_MS));
    i2c_cmd_link_delete(cmd);  

    LCD_pulseEnable(data);                                              // Clock data into LCD
}

static void LCD_writeByte(uint8_t data, uint8_t mode)
{
    LCD_writeNibble(data & 0xF0, mode);
    LCD_writeNibble((data << 4) & 0xF0, mode);
}

static void LCD_pulseEnable(uint8_t data)
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    ESP_ERROR_CHECK(i2c_master_start(cmd));
    ESP_ERROR_CHECK(i2c_master_write_byte(cmd, (LCD_addr << 1) | I2C_MASTER_WRITE, 1));
    ESP_ERROR_CHECK(i2c_master_write_byte(cmd, data | LCD_ENABLE, 1));
    ESP_ERROR_CHECK(i2c_master_stop(cmd));
    ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000/portTICK_PERIOD_MS));
    i2c_cmd_link_delete(cmd);  
    ets_delay_us(1);

    cmd = i2c_cmd_link_create();
    ESP_ERROR_CHECK(i2c_master_start(cmd));
    ESP_ERROR_CHECK(i2c_master_write_byte(cmd, (LCD_addr << 1) | I2C_MASTER_WRITE, 1));
    ESP_ERROR_CHECK(i2c_master_write_byte(cmd, (data & ~LCD_ENABLE), 1));
    ESP_ERROR_CHECK(i2c_master_stop(cmd));
    ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000/portTICK_PERIOD_MS));
    i2c_cmd_link_delete(cmd);
    ets_delay_us(500);
}



diagram.json


{
  "version": 1,
  "author": "alex wu",
  "editor": "wokwi",
  "parts": [
    {
      "type": "board-esp32-devkit-c-v4",
      "id": "esp",
      "top": -153.6,
      "left": -167.96,
      "attrs": { "builder": "esp-idf" }
    },
    { "type": "wokwi-lcd1602", "id": "lcd1", "top": -128, "left": -4, "attrs": { "pins": "i2c" } }
  ],
  "connections": [
    [ "esp:TX", "$serialMonitor:RX", "", [] ],
    [ "esp:RX", "$serialMonitor:TX", "", [] ],
    [ "esp:21", "lcd1:SDA", "green", [ "h48", "v-28.8" ] ],
    [
      "esp:22",
      "lcd1:SCL",
      "green",
      [ "h19.2", "v-57.6", "h-134.4", "v240", "h144", "v-144", "h-9.6" ]
    ],
    [ "esp:3V3", "lcd1:VCC", "red", [ "h0.15", "v-48", "h144", "v86.4" ] ],
    [ "esp:GND.2", "lcd1:GND", "black", [ "v0", "h57.6", "v28.8" ] ]
  ],
  "dependencies": {}
}



VS Code LED Blink Project with ESP-IDF

 VS Code LED Blink Project with ESP-IDF

源自於 https://esp32tutorials.com/esp32-gpio-esp-idf-led-blinking-example/

main.c

#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define LED_PIN 13

void app_main(void)
{
    // 配置 GPIO
    gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
   
    // 使用一個整數來追蹤 LED 的狀態 (1: ON, 0: OFF)
    int led_state = 0;

    while(true)
    {
        // 1. 切換 LED 狀態
        // 使用 XOR 運算符 '!' 來切換 0 和 1 (0 -> 1, 1 -> 0)
        led_state = !led_state;
       
        // 2. 設定 GPIO 輸出電平
        gpio_set_level(LED_PIN, led_state);
       
        // 3. 透過序列埠顯示狀態
        if (led_state == 1) {
            printf("LED=ON\n");
        } else {
            printf("LED=OFF\n");
        }
       
        // 4. 延遲 1000 毫秒 (1 秒)
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}



diagram.json


{
  "version": 1,
  "author": "alex wu",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-breadboard-mini", "id": "bb1", "top": -59, "left": -184.8, "attrs": {} },
    {
      "type": "board-esp32-devkit-c-v4",
      "id": "esp",
      "top": -153.6,
      "left": 33.64,
      "attrs": { "builder": "esp-idf" }
    },
    {
      "type": "wokwi-resistor",
      "id": "r2",
      "top": -24.85,
      "left": -105.6,
      "attrs": { "value": "1000" }
    },
    {
      "type": "wokwi-led",
      "id": "led1",
      "top": -70.8,
      "left": -130.6,
      "attrs": { "color": "red" }
    }
  ],
  "connections": [
    [ "esp:TX", "$serialMonitor:RX", "", [] ],
    [ "esp:RX", "$serialMonitor:TX", "", [] ],
    [ "r2:1", "bb1:8t.d", "", [ "$bb" ] ],
    [ "r2:2", "bb1:14t.d", "", [ "$bb" ] ],
    [ "led1:A", "bb1:8t.c", "", [ "$bb" ] ],
    [ "led1:C", "bb1:7t.c", "", [ "$bb" ] ],
    [ "esp:13", "bb1:14t.e", "green", [ "h0" ] ],
    [ "esp:GND.1", "bb1:7t.d", "black", [ "h-38.25", "v-96", "h-134.4", "v9.6" ] ]
  ],
  "dependencies": {}
}

ESP32 Control LED with Push button (ESP-IDF)

ESP32 Control LED with Push button (ESP-IDF)

源自於 https://esp32tutorials.com/esp32-push-button-esp-idf-digital-input/#more-168





#define LED_PIN         27
#define PUSH_BUTTON_PIN 33



 main.c

#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

#define LED_PIN         27
#define PUSH_BUTTON_PIN 33

// 使用一個靜態變數來追蹤按鈕的前一個狀態
static int last_pb_state = -1; // 初始化為 -1 確保程式碼一開始就會印出狀態

void app_main(void)
{
    // 配置 GPIO
    gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
    gpio_set_direction(PUSH_BUTTON_PIN, GPIO_MODE_INPUT);

    // 啟動時偵測並顯示初始狀態
    int current_pb_state = gpio_get_level(PUSH_BUTTON_PIN);
    last_pb_state = current_pb_state; // 儲存初始狀態

    // 啟動時,LED 狀態應與按鈕初始狀態一致
    gpio_set_level(LED_PIN, current_pb_state);
   
    // 透過序列埠顯示初始狀態
    if (current_pb_state == 1) {
        printf("啟動時按鈕狀態: High (已按下或上拉) \n");
    } else {
        printf("啟動時按鈕狀態: Low (未按下或下拉) \n");
    }

    // 主迴圈
    while(1) {
        current_pb_state = gpio_get_level(PUSH_BUTTON_PIN);

        // 偵測按鈕狀態是否改變
        if (current_pb_state != last_pb_state) {
            // 狀態已改變,透過序列埠印出新狀態
            if (current_pb_state == 1) {
                printf("按鈕狀態改變: High\n");
            } else {
                printf("按鈕狀態改變: Low\n");
            }

            // 更新 LED 狀態
            gpio_set_level(LED_PIN, current_pb_state);

            // 更新 last_pb_state 以追蹤新的狀態
            last_pb_state = current_pb_state;
        }
        // 如果狀態未改變,則不做任何事 (LED 狀態保持不變)

        vTaskDelay(pdMS_TO_TICKS(10)); // 延遲 10ms 進行消抖和降低 CPU 負載
    }
}


diagram.json

{
  "version": 1,
  "author": "alex wu",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-breadboard-mini", "id": "bb1", "top": -59, "left": -184.8, "attrs": {} },
    {
      "type": "board-esp32-devkit-c-v4",
      "id": "esp",
      "top": -153.6,
      "left": 33.64,
      "attrs": { "builder": "esp-idf" }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn1",
      "top": 44.6,
      "left": -240,
      "attrs": { "color": "green", "xray": "1" }
    },
    {
      "type": "wokwi-resistor",
      "id": "r1",
      "top": 13.55,
      "left": -105.6,
      "attrs": { "value": "1000" }
    },
    {
      "type": "wokwi-resistor",
      "id": "r2",
      "top": -24.85,
      "left": -105.6,
      "attrs": { "value": "1000" }
    },
    {
      "type": "wokwi-led",
      "id": "led1",
      "top": -70.8,
      "left": -130.6,
      "attrs": { "color": "red" }
    }
  ],
  "connections": [
    [ "esp:TX", "$serialMonitor:RX", "", [] ],
    [ "esp:RX", "$serialMonitor:TX", "", [] ],
    [ "esp:27", "bb1:14t.a", "green", [ "h-47.85", "v-19.2" ] ],
    [ "esp:GND.1", "bb1:7t.e", "black", [ "h-95.85", "v0", "h-57.6" ] ],
    [ "esp:GND.1", "bb1:14b.g", "black", [ "h-19.05", "v105.6" ] ],
    [ "r1:1", "bb1:8b.f", "", [ "$bb" ] ],
    [ "r1:2", "bb1:14b.f", "", [ "$bb" ] ],
    [ "r2:1", "bb1:8t.d", "", [ "$bb" ] ],
    [ "r2:2", "bb1:14t.d", "", [ "$bb" ] ],
    [ "led1:A", "bb1:8t.c", "", [ "$bb" ] ],
    [ "led1:C", "bb1:7t.c", "", [ "$bb" ] ],
    [ "esp:33", "bb1:8b.i", "violet", [ "h-38.25", "v105.6" ] ],
    [ "esp:3V3", "bb1:1b.f", "red", [ "h-239.85", "v28.8" ] ],
    [ "btn1:1.r", "bb1:1b.j", "", [ "$bb" ] ],
    [ "bb1:8b.j", "btn1:2.r", "green", [ "v28.8" ] ]
  ],
  "dependencies": {}
}


原來程式

#include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #define LED_PIN 27 #define PUSH_BUTTON_PIN 33 void app_main(void) { gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT); gpio_set_direction(PUSH_BUTTON_PIN, GPIO_MODE_INPUT); while(1) { if (gpio_get_level(PUSH_BUTTON_PIN) == 1) { gpio_set_level(LED_PIN, 1); } else { gpio_set_level(LED_PIN, 0); } vTaskDelay(1); } }


ESP-IDF GPIO APIs for Digital Input & Digital Output

#include "driver/gpio.h"


Set Direction of GPIO Pins

gpio_set_direction(GPIO_NUM_27, GPIO_MODE_OUTPUT);
gpio_set_direction(GPIO_NUM_33, GPIO_MODE_INPUT);

Set Output Level of Digital Pin

gpio_set_level(GPIO_NUM_27, 1);


Read Digital Input (Get Output Level of Input Pin)

int gpio_get_level(GPIO_NUM_33);




  清除項目編譯快取 執行 flutter clean 重新取得套件依賴 執行 flutter pub get 完整重新編譯並執行 flutter run 1. 新增專案套件 (pubspec.yaml) 在 Flutter 專案根目錄下的 pubspec.yaml 中,新增 ...