Display knob

The device(s) work fine in esphome, problem is that for unknown reasons they put audio out on the esp32 and mic + display on the esp32-s3, makes it hard to use it as i voice assistant. so i put it back in the drawer :laughing:

you can flash both esp’s from esphome, just flip the usb-c plug.

here is what i got for the esp32-s3 side before i put it back in the drawer:

substitutions:
  name: esphome-web-d1e9b4
  friendly_name: Xiaozhi Knob

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2025.5.0
  name_add_mac_suffix: false

esp32:
  board: esp32-s3-devkitc-1
  flash_size: 16MB
  cpu_frequency: 240MHz
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
      CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
      CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"

psram:
  mode: octal
  speed: 80MHz

api:

ota:
  - platform: esphome
    id: ota_esphome

logger:
  level: DEBUG

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "Xiaozhi 3.5 Hotspot"
    password: "RZ7D3EzJdPM6"

captive_portal:

uart:
  id: audio_uart
  tx_pin: GPIO41
  rx_pin: GPIO40
  baud_rate: 115200

i2c:
  - id: bus_a
    sda: GPIO11
    scl: GPIO12
    scan: true

sensor:
  - platform: adc
    pin: 1
    name: "Battery Voltage"
    id: battery_voltage
    attenuation: 12db
    accuracy_decimals: 2
    update_interval: 1s
    unit_of_measurement: "V"
    icon: mdi:battery-medium
    filters:
      - multiply: 2.0
      - median:
          window_size: 7
          send_every: 7
          send_first_at: 7
      - throttle: 1min
    on_value:
      then:
        - component.update: battery_percentage

  - platform: rotary_encoder
    id: knob
    name: "Rotary Knob Raw"
    pin_a:
      number: GPIO8
      mode: INPUT_PULLUP
    pin_b:
      number: GPIO7
      mode: INPUT_PULLUP
    resolution: 2
    on_clockwise:
      then:
        - logger.log: "Knob turned → CLOCKWISE"
    on_anticlockwise:
      then:
        - logger.log: "Knob turned → COUNTER-CLOCKWISE"


  - platform: template
    id: battery_percentage
    name: "Battery Percentage"
    lambda: return id(battery_voltage).state;
    accuracy_decimals: 0
    unit_of_measurement: "%"
    icon: mdi:battery-medium
    filters:
      - calibrate_linear:
         method: exact
         datapoints:
          - 2.80 -> 0.0
          - 3.10 -> 10.0
          - 3.30 -> 20.0
          - 3.45 -> 30.0
          - 3.60 -> 40.0
          - 3.70 -> 50.0
          - 3.75 -> 60.0
          - 3.80 -> 70.0
          - 3.90 -> 80.0
          - 4.00 -> 90.0
          - 4.20 -> 100.0
      - lambda: |-
          if (x > 100) return 100;
          if (x < 0) return 0;
          return x;

switch:
  - platform: gpio
    name: "PA CTRL"
    pin: 48
    restore_mode: RESTORE_DEFAULT_ON

i2s_audio:
  - id: i2s_in
    i2s_lrclk_pin: GPIO45
    i2s_bclk_pin: GPIO42


microphone:
  - platform: i2s_audio
    id: i2s_microphone
    i2s_audio_id: i2s_in
    i2s_din_pin: GPIO46
    adc_type: external
    pdm: false
    channel: left
    sample_rate: 16000
    bits_per_sample: 32bit

time:
  - platform: sntp
    id: sntp_time
    timezone: "Europe/Berlin"

font:
  - file: "gfonts://Roboto"
    id: my_font
    size: 28
  - file: "gfonts://Roboto"
    id: roboto_52
    size: 52

binary_sensor:
  - platform: template
    name: "Touch Button"
    id: touch_input

# SCREEN & TOUCH

output:
  - platform: ledc
    pin: 47
    id: backlight_output

light:
  - platform: monochromatic
    id: Sled
    name: Screen
    icon: "mdi:television"
    entity_category: config
    output: backlight_output
    restore_mode: ALWAYS_ON
    default_transition_length: 250ms

external_components:
  - source: github://pr#10392
    components: [mipi_spi]
    refresh: 1d

spi:
  id: display_qspi
  type: quad
  clk_pin: 13
  data_pins: [15, 16, 17, 18]

touchscreen:
  - platform: cst816
    id: my_touchscreen
    interrupt_pin: GPIO9
    reset_pin: GPIO10
    display: main_display
    on_touch:
      then:
        - logger.log:
            format: "Touch at (%d, %d)"
            args: [touch.x, touch.y]
        - binary_sensor.template.publish:
            id: touch_input
            state: ON
    on_release:
      then:
        - binary_sensor.template.publish:
            id: touch_input
            state: OFF

display:
  - platform: mipi_spi
    id: main_display
    model: JC3636W518V2
    rotation: 180
    cs_pin: 14
    reset_pin: 21
    update_interval: 1s

    lambda: |-
      auto black = Color(0,0,0), red = Color(255,0,0), green = Color(0,255,0), blue = Color(0,0,255), yellow = Color(255,255,0), white = Color(255,255, 255), cyberlightgreen = Color(0,255,159), cyberlightblue = Color(0,184,255), cyberpink = Color(214,0,255);
      it.fill(black);
      it.strftime(180, 180, id(roboto_52), cyberlightgreen, TextAlign::CENTER, "%H:%M:%S", id(sntp_time).now());


3 Likes