Little project using ESP32-C6 1.47in display and LD2410c presence sensor

I’ve seen a few posts about setting up the ESP32-C6 1.47in display (this one), available for about $20 on Amazon, and the mmwave LD2410c presence sensor, about $12 on Amazon. But I haven’t seen anything about getting the two of them set up together (which was a bit of grief, possibly related to my poor pin-soldering skills), and I haven’t seen anything about LVGL graphics on this display in ESPHome.

My goal was to hook up the presence sensor to the display, and have the display graph the moving and still energy levels every 2 seconds for each of the 8 default distance gates on the LD2410c. Nothing fancy; definitely a work in progress:

I had a lot of trouble getting the LD2410c to communicate using the dedicated TX/RX pins on the ESP32-C6; the ESP32 TX pin worked OK (I could tell it was transmitting by monitoring the LD2410c on the Android app connected via Bluetooth), but the ESP32 RX pin wouldn’t function. It is possible that my pin-soldering job caused this, but I solved it by moving the ESP32 RX lead to a GPIO pin (GPIO13 in my case).

I also had trouble getting LVGL graphics to work on the display, using the (deprecated?) ST7789v ESPHome display component, which most posts I’ve seen seem to mention. Instead, everything worked fine using the ili9xxx display component.

My hookup is
ESP32-C6 LD2410c
Pin 5v → Pin VCC
Pin Gnd → Pin Gnd
Pin Tx → Pin Rx
Pin 13 → Pin Tx

The “out” pin on the LD2410c is unnecessary (but can be used); it functions as a simple GPIO on/off binary sensor for presence.

In my code, I chose to include all of the sensors and switches available; obviously, they’re not all necessary and certainly don’t all have to be made accessible (to Home Assistant, for instance). You can monitor them via the web server. In order to get the graph to work on the display (in order for the LD2410c to output the individual gate data), Engineering Mode on the LD2410c has to be turned on (which I do programmatically on boot).

The code I’m pasting below should work out of the box, just inserting your encryption, ota, and fallback AP passwords. Here’s the ESPHome yaml code:

substitutions:
  gate0: "10"
  gate1: "48"
  gate2: "85"
  gate3: "123"
  gate4: "160"
  gate5: "198"
  gate6: "235"
  gate7: "273"
  gate8: "310"
# gates (in meters) * 100/2 + 10; actual gates are 0, 0.75, 1.5, 2.25, 3, 3.75, 4.5, 5.25, 6 (meters)

esphome:
  name: small-screen
  friendly_name: Small screen
  on_boot:
    then:
      - switch.turn_on: engineering_mode

esp32:
  board: esp32-c6-devkitm-1
  variant: esp32c6
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_ESPTOOLPY_FLASHSIZE_4MB: y

spi:
  clk_pin: GPIO7
  mosi_pin: GPIO6

display:
  - platform: ili9xxx
    id: my_display
    model: ST7789V
    dc_pin: GPIO15
    cs_pin: GPIO14
    reset_pin: GPIO21
    invert_colors: true
    show_test_card: false
    dimensions:
      height: 172
      width: 320
      offset_height: 34
      offset_width: 0
    transform:
      swap_xy: true
      mirror_x: false
      mirror_y: true
    color_order: bgr

# Define a PWM output on the ESP32
output:
  - platform: ledc
    pin: GPIO22
    id: backlight_pwm

# Define a monochromatic, dimmable light for the backlight
light:
  - platform: monochromatic
    output: backlight_pwm
    name: "Display Backlight"
    id: back_light
    restore_mode: ALWAYS_ON

interval:
  - interval: 2sec
    then:
      - lvgl.line.update:
          id: move_line
          points:
            - x: $gate0
              y: !lambda return 172-int(id(g0_move).state);
            - x: $gate1
              y: !lambda return 172-int(id(g1_move).state);
            - x: $gate2
              y: !lambda return 172-int(id(g2_move).state);
            - x: $gate3
              y: !lambda return 172-int(id(g3_move).state);
            - x: $gate4
              y: !lambda return 172-int(id(g4_move).state);
            - x: $gate5
              y: !lambda return 172-int(id(g5_move).state);
            - x: $gate6
              y: !lambda return 172-int(id(g6_move).state);
            - x: $gate7
              y: !lambda return 172-int(id(g7_move).state);
            - x: $gate8
              y: !lambda return 172-int(id(g8_move).state);
      - lvgl.line.update:
          id: still_line
          points:
            - x: $gate0
              y: !lambda return 172-int(id(g0_still).state);
            - x: $gate1
              y: !lambda return 172-int(id(g1_still).state);
            - x: $gate2
              y: !lambda return 172-int(id(g2_still).state);
            - x: $gate3
              y: !lambda return 172-int(id(g3_still).state);
            - x: $gate4
              y: !lambda return 172-int(id(g4_still).state);
            - x: $gate5
              y: !lambda return 172-int(id(g5_still).state);
            - x: $gate6
              y: !lambda return 172-int(id(g6_still).state);
            - x: $gate7
              y: !lambda return 172-int(id(g7_still).state);
            - x: $gate8
              y: !lambda return 172-int(id(g8_still).state);

lvgl:
  pages:
    bg_color: 0x000000
    bg_opa: COVER
    widgets:
      - line:
          id: move_line
          points:
            - $gate0,1
          line_width: 3
          line_color: 0x00FF00 # green
          line_rounded: true
          bg_opa: TRANSP
      - line:
          id: still_line
          points:
            - $gate0,1
          line_width: 3
          line_color: 0xFF0000 # red
          line_rounded: true
          bg_opa: TRANSP


###########################################################

uart:
  tx_pin: TX
  rx_pin: GPIO13 # I used this because I couldn't get the RX pin to work
  parity: NONE
  data_bits: 8
  stop_bits: 1
  baud_rate: 256000
  

ld2410:
  id: ld2410_radar

binary_sensor:
  - platform: ld2410
    has_target:
      name: Presence
    has_moving_target:
      name: Moving Target
    has_still_target:
      name: Still Target
    out_pin_presence_status:
      name: out pin presence status

sensor:
  - platform: ld2410
    light:
      name: light
    moving_distance:
      name : Moving Distance
    still_distance:
      name: Still Distance
    moving_energy:
      name: Move Energy
    still_energy:
      name: Still Energy
    detection_distance:
      name: Detection Distance
    g0:
      move_energy:
        name: g0 move energy
        id: g0_move
      still_energy:
        name: g0 still energy
        id: g0_still
    g1:
      move_energy:
        name: g1 move energy
        id: g1_move
      still_energy:
        name: g1 still energy
        id: g1_still
    g2:
      move_energy:
        name: g2 move energy
        id: g2_move
      still_energy:
        name: g2 still energy
        id: g2_still
    g3:
      move_energy:
        name: g3 move energy
        id: g3_move
      still_energy:
        name: g3 still energy
        id: g3_still
    g4:
      move_energy:
        name: g4 move energy
        id: g4_move
      still_energy:
        name: g4 still energy
        id: g4_still
    g5:
      move_energy:
        name: g5 move energy
        id: g5_move
      still_energy:
        name: g5 still energy
        id: g5_still
    g6:
      move_energy:
        name: g6 move energy
        id: g6_move
      still_energy:
        name: g6 still energy
        id: g6_still
    g7:
      move_energy:
        name: g7 move energy
        id: g7_move
      still_energy:
        name: g7 still energy
        id: g7_still
    g8:
      move_energy:
        name: g8 move energy
        id: g8_move
      still_energy:
        name: g8 still energy
        id: g8_still

switch:
  - platform: ld2410
    engineering_mode:
      name: "engineering mode"
      id: engineering_mode

number:
  - platform: ld2410
    timeout:
      name: timeout
    light_threshold:
      name: light threshold
    max_move_distance_gate:
      name: max move distance gate
    max_still_distance_gate:
      name: max still distance gate
    g0:
      move_threshold:
        name: g0 move threshold
      still_threshold:
        name: g0 still threshold
    g1:
      move_threshold:
        name: g1 move threshold
      still_threshold:
        name: g1 still threshold
    g2:
      move_threshold:
        name: g2 move threshold
      still_threshold:
        name: g2 still threshold
    g3:
      move_threshold:
        name: g3 move threshold
      still_threshold:
        name: g3 still threshold
    g4:
      move_threshold:
        name: g4 move threshold
      still_threshold:
        name: g4 still threshold
    g5:
      move_threshold:
        name: g5 move threshold
      still_threshold:
        name: g5 still threshold
    g6:
      move_threshold:
        name: g6 move threshold
      still_threshold:
        name: g6 still threshold
    g7:
      move_threshold:
        name: g7 move threshold
      still_threshold:
        name: g7 still threshold
    g8:
      move_threshold:
        name: g8 move threshold
      still_threshold:
        name: g8 still threshold

text_sensor:
  - platform: ld2410
    version:
      name: "firmware version"
    mac_address:
      name: "mac address"

select:
  - platform: ld2410
    distance_resolution:
      name: "distance resolution"
    light_function:
      name: light function
    out_pin_level:
      name: out pin level


button:
  - platform: restart
    icon: mdi:power-cycle
    name: "ESP Reboot"   
  - platform: ld2410
    restart:
      name: "restart"
    query_params:
      name: query params
    factory_reset:
      name: "factory reset"

# Enable logging
logger:
  baud_rate: 0

# Enable Home Assistant API
api:
  encryption:
    key: "your encryption key"  # your encryption key has to be entered here

ota:
  - platform: esphome
    password: "your ota password"  # your ota password has to be entered here

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Small screen Fallback Hotspot"
    password: "your fallback password"  # your fallback password has to be entered here
  
captive_portal:

web_server:
  port: 80
1 Like

Hi NYZack,

thanks for sharing. I have made my unit with very similar code…until your post, I did not think I could do it with my small WS ESP32C6 board.

Just for users like me, who want to see different information on the screen, I’m including my code. I added the reading of my light meter via HA and now change the backlight accordingly.

esphome:
  name: another-presensce-display-ws
  friendly_name: Another presensce display ws




esp32:
  board: esp32-c6-devkitc-1
  variant: esp32c6
  framework:
    type: esp-idf
    #sdkconfig_options:
    #  CONFIG_ESPTOOLPY_FLASHSIZE_4MB: y
    
# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: ""

ota:
  - platform: esphome
    password: ""

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Another-Presensce-Display-Ws"
    password: ""


  
captive_portal:

web_server:
  port: 80

# SPI display


spi:
  clk_pin: GPIO7
  mosi_pin: GPIO6
 
 
output:  
  - platform: ledc
    pin: GPIO22
    id: backlight_pwm

light:
  - platform: monochromatic
    output: backlight_pwm
    name: "Display Backlight"
    id: backlight
    restore_mode: ALWAYS_ON






font:
  - file: "gfonts://Roboto"
    id: my_font_small
    size: 24
  - file: "gfonts://Roboto"
    id: my_font_med
    size: 36
  - file: "gfonts://Roboto"
    id: my_font_large
    size: 48

display:
  - platform: ili9xxx
    model: ST7789V
    id: my_display

    cs_pin: GPIO14
    dc_pin: GPIO15
    reset_pin: GPIO21
    dimensions:
      width: 172
      height: 320
      offset_width: 34
      offset_height: 0
    rotation: 0
   
    invert_colors: true
    
    update_interval: 3s 
    lambda: |-
      it.fill(Color(0, 0, 0));

      auto blue = Color(0, 0, 255);
      auto red = Color(255, 0, 0);
      auto green = Color(0, 255, 0);
      auto white = Color(255, 255, 255);

      it.print(5, 10, id(my_font_small), white, "Target");

      if (id(ld_has_target).state) {
        it.print(85, 40, id(my_font_med), green, "YES");
      } else {
        it.print(85, 40, id(my_font_med), red, "NO");
      }

      it.print(5, 80, id(my_font_small), white, "Moving:");
      it.printf(5, 111, id(my_font_med), white, "%.0f cm", id(ld_moving_distance).state);

      it.print(5, 155, id(my_font_small), blue, "Still:");
      it.printf(5, 188, id(my_font_med), white, "%.0f cm", id(ld_still_distance).state);

      it.print(5, 230, id(my_font_small), blue, "Lux:");
      it.printf(5, 265, id(my_font_med), white, "%.0f lx", id(ha_lux).state);



uart:
  id: uart_ld2410
  tx_pin: GPIO19
  rx_pin: GPIO13
  baud_rate: 256000

ld2410:
  uart_id: uart_ld2410

sensor:
  - platform: ld2410
    moving_distance:
      name: "LD2410 Moving Distance"
      id: ld_moving_distance

    still_distance:
      name: "LD2410 Still Distance"
      id: ld_still_distance

  - platform: homeassistant
    id: ha_lux
    entity_id: sensor.lichtsensorkh_bh1750_illuminance



binary_sensor:
  - platform: ld2410
    has_target:
      name: "LD2410 Target"
      id: ld_has_target

switch:
  - platform: ld2410
    engineering_mode:
      id: engineering_mode


interval:
  - interval: 10s
    then:
      - lambda: |-
          float lux = id(ha_lux).state;
          float brightness = 0.2;

          if (lux >= 800.0) {
            brightness = 1.0;
          } else if (lux >= 100.0) {
            brightness = 0.5;
          } else if (lux >= 30.0) {
            brightness = 0.3;
          }

          auto call = id(backlight).turn_on();
          call.set_brightness(brightness);
          call.perform();


I have two of those little WS modules, but one does not work properly. As you can see on my video, it starts flashing right after powering on and after some seconds switches off…it’s not the backlight, as this is still on. If somebody got an idea howe to fix it, it would be great to know about. I hate to let the module go…if I can rescue it :slight_smile:
Here is a link to a short video:

Video ws blinking

Thanks for watching and again, thanks for posting the idea!

ATB KH

2 Likes

I can’t say I know why your ESP32 module flashes. But I will say that, when I hook up my LD2410c to my ESP32-C6 video module and connect the ESP32 to my computer’s USB, I get a warning saying that the device has malfunctioned, and sometimes the warning fluctuates on and off. I don’t have this problem with the ESP device alone (without the LD2410c). I’ve wondered whether it’s a power issue.

I think the LD2410c draws a lot of power (based on my experience when I tried to run it off of a battery).

This sounds like you are using the UART on pins 1 and 3. Those are often used for USB serial communication. You cant use that UART while it is communicating over USB. Try the one on 17 and 16 instead (if this board is not using those for other things too).

1 Like

I was using the TX pin (UART0 TX). I took your advice and moved to different pins (GPIO04 and 05); the pinout diagram for this particular device doesn’t show a GPIO 16 or 17. But this solved it.

I really wonder whether some of my grief is due to the fact that I did a very sloppy soldering job. Some of the pins may not be making good contact, and I did spill some solder elsewhere, which I couldn’t adequately clean up; I wonder whether it’s causing a short.

Good candidate as your problem. If you have two identic boards and only one is misbehaving like that, overheating of chip (or some component) would make sense. Just inspect with magnification.