Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Stackchan Dance

English | 日本語

Warning

Defining motions in motion.json with excessive speed or intensity (short duration_ms, large angle changes, high speed values for turnX(), etc.) can damage the servo motor or its gears/mounting. Tune motion parameters carefully and test at low intensity first. The author takes no responsibility for any servo damage, injury, or other loss resulting from custom motion data.

Please do not run this on a Takao-version Stack-chan, especially one using SG90-series servos.

⚠️ The 7-pin cable is likely to break if intense movements are repeated continuously. Please avoid running intense motions back-to-back.

The Repair Kit for Stackchan includes a replacement 7-pin cable. We recommend picking one up in case it breaks.

Firmware for an M5Stack CoreS3 based Stack-chan style robot.

This project does the following:

  • Plays /dance/dance1.mp3 from the SD card
  • Shows an M5Stack-Avatar face on the display
  • Moves the mouth based on audio level
  • Runs scheduled servo motions defined in JSON files
  • Switches motions at absolute times from the schedule
  • Shows battery level / charging status as an icon on the avatar

Environment

  • Board: M5Stack CoreS3
  • Framework: Arduino via PlatformIO
  • Motion servo driver: SCServo
  • Display avatar: M5Stack-Avatar
  • Audio playback: ESP8266Audio

File Layout

Files in data/ are copied to the SD card.

On the device, these files are read from the SD card root as:

  • /dance/dance1.mp3
  • /dance/dance1.json
  • /dance/motion.json
  • /dance/dance_list.yaml
  • /yaml/SC_BasicConfig.yaml

Motion Files

dance/dance<N>.json (e.g. dance1.json)

This file defines when each motion starts for one song.

Example:

{
  "motions": [
    { "motion": "intro", "start_ms": 2000 },
    { "motion": "shakehead", "start_ms": 15000 },
    { "motion": "v", "start_ms": 40000 }
  ]
}

Fields:

  • motion: Motion name defined in motion.json
  • start_ms: Absolute start time from playback start

Important behavior:

  • start_ms has priority
  • When a new motion starts, the previous motion is stopped
  • Each motion repeats automatically until the next motion's start_ms
  • The last scheduled motion runs once

motion.json

This file defines named motion patterns.

Example:

{
  "Motion": {
    "intro": [
      { "x": 0, "y": 45, "duration_ms": 500 },
      { "y": 30, "speed": 300, "is_cw": true, "millis_for_move": 700 },
      { "x": 20, "y": 45, "duration_ms": 500 },
      { "x": 0, "y": 45, "duration_ms": 500 }
    ]
  }
}

Fields for each step:

  • x: X-axis position from -100% to 100%. -100% maps to lower_limit, 0% to the midpoint, and 100% to upper_limit
  • y: Y-axis position from 0% to 100%. 0% maps to lower_limit and 100% to upper_limit. For rotation steps, if specified, Y starts moving over millis_for_move and turnX() starts immediately without waiting for the move to complete
  • duration_ms: Duration of this step
  • speed: Optional. If speed, is_cw, and millis_for_move are all present, execute stackchanSERVO::turnX()
  • is_cw: Optional clockwise flag for turnX()
  • millis_for_move: Optional rotation duration for turnX(). If duration_ms is omitted, this value is used as the step duration

Notes:

  • t_ms is no longer used
  • Step timing is calculated by accumulating duration_ms
  • Rotation is ignored unless all of speed, is_cw, and millis_for_move are specified
  • For rotation steps, x is not used
  • Out-of-range x and y values are clamped to their respective minimum or maximum

dance_list.yaml

Defines the song list. Up to 10 mp3 / dance (motion schedule) filename pairs can be specified. Filenames are relative to the data/dance/ folder.

Example:

dance_list:
  - mp3: "dance1.mp3"
    dance: "dance1.json"
  - mp3: "dance2.mp3"
    dance: "dance2.json"

This file is loaded at startup and used for button-triggered playback:

  • Button A: Plays every song in dance_list.yaml, in list order
  • Button A (long-press, 10 seconds): Powers off this unit. If any peers are configured (see ESP-NOW Multi-Device Sync), it also broadcasts a power-off command so every configured peer shuts down too.
  • Button B: Plays every song in dance_list.yaml, in shuffled (random) order
  • Button C: Always plays the first song in dance_list.yaml
  • Button C (long-press, 2 seconds): Enters random motion mode — plays every song in dance_list.yaml in order, but instead of following that song's dance schedule file, picks a random motion from motion.json every 2 seconds and repeats it until the next pick. When a song ends, the currently running motion is cut short and the head returns to front over 1 second before the next song starts.

For each song, the shared motion.json motion library is reused, but that song's dance (motion schedule) file is (re)loaded before playback (except in random motion mode, which never loads a dance schedule file). Button presses are ignored while playback is in progress; the firmware blocks until playback finishes (the whole list for Button A/B/long-press-C, a single song for a short Button C press). If dance_list.yaml is missing or empty, it falls back to playing dance1.mp3 alone (with dance1.json for normal playback, or random motions for random motion mode). Each button press plays a short confirmation tone.

ESP-NOW Multi-Device Sync

Multiple Stack-chan units can play the same song and start dancing at the same time, using ESP-NOW (no Wi-Fi access point required).

  • Whichever unit's button is pressed becomes the leader for that song: it broadcasts the mp3/dance filenames to its configured peers.
  • Every peer that receives the command loads the same-named files from its own SD card into PSRAM, then replies with a "ready" message.
  • The leader waits for all configured peers to become ready (or a 5-second timeout), then broadcasts a "start" signal so every unit begins playback and dancing at the same moment.
  • If a peer doesn't respond in time, the leader proceeds with whichever peers are ready.
  • This also applies to the random motion mode (Button C long-press): the leader still broadcasts which song to play, and additionally broadcasts the name of each randomly-picked motion every 2 seconds, so peers execute the exact same motion at the exact same time instead of picking their own.

Setup

  1. Copy data/yaml/SC_SecConfig.yaml.sample to data/yaml/SC_SecConfig.yaml.
  2. Flash each unit once and check the Serial log for a line like Own MAC: AA:BB:CC:DD:EE:FF to find its MAC address.
  3. List the MAC addresses of all units (including each unit itself) under espnow.peers in SC_SecConfig.yaml. Each unit automatically excludes its own address, so you can copy the exact same file onto every unit's SD card.
  4. Copy SC_SecConfig.yaml to /yaml/SC_SecConfig.yaml on every unit's SD card, alongside the same data/dance/ files (same filenames on every unit).

SC_SecConfig.yaml identifies your own hardware, so it's excluded via .gitignore and never committed — only the .sample template is tracked.

If SC_SecConfig.yaml is missing or has no peers, the firmware runs standalone exactly as before.

Known limitations

  • No arbitration if two units are used as leader at the same time (e.g. two buttons pressed simultaneously).
  • A unit already playing a song ignores incoming play commands from other leaders.
  • Synchronization accuracy depends on Wi-Fi channel conditions; this is a best-effort sync, not a hard real-time guarantee.

Build

Build for CoreS3:

~/.platformio/penv/bin/pio run -e m5stack-cores3

Upload firmware:

~/.platformio/penv/bin/pio run -e m5stack-cores3 -t upload

Copy Files To SD

The JSON and MP3 files in data/ must be copied to the SD card root.

Copy these files to the SD card:

  • dance/dance1.mp3
  • dance/dance1.json
  • dance/dance_list.yaml
  • dance/motion.json
  • yaml/SC_BasicConfig.yaml
  • yaml/SC_SecConfig.yaml (optional, only needed for ESP-NOW multi-device sync)

Recommended update order:

  1. Update files in data/
  2. Copy the changed files to the SD card root
  3. Upload firmware if code changed

If only JSON or MP3 files changed, updating the SD card is enough.

Runtime Logs

During playback, the firmware prints the motion name only when a motion starts.

Example:

intro
shakehead
v

Notes

  • This project reads data from the SD card, not from SPIFFS
  • If you rename or modify files in data/, copy them to the SD card again
  • If a scheduled motion name does not exist in motion.json, startup fails
  • Before playback, the whole MP3 file is loaded into PSRAM, then played from there. This avoids audio glitches caused by SD card read latency. If the PSRAM allocation fails (e.g. not enough free PSRAM), that song is skipped

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages