Week 7: Input Devices

Assignment

The EECS group assignment is linked here.

This week, our task is:

Board

The Vision

This week, I focused on designing a board with input devices relevant to my final project. Specifically, I planned to use two types of input: one to detect motion and another to detect voice. For motion detection, I wanted my flower to respond when it senses human presence nearby. I decided to use a time-of-flight (ToF) distance sensor to detect motion in front of it. I briefly reviewed the datasheets for the available options and chose one that balanced range—about two meters—with cost. Ideally, I wanted a sensor in the low single-digit dollar range. Ultimately, I selected the VL53L0X. For voice detection, my goal was for the flower to recognize a joke. While no sensor can directly interpret humor, I planned to integrate a microphone (ICS-43434) with my ESP32-S3 microcontroller. The microphone would feed audio data into the microcontroller, which could then process it. The idea was for the microcontroller to periodically record short clips (every second or so, up to 10 seconds, depending on storage constraints) and send them over Wi-Fi as .wav files. These files could then be uploaded to a speech-to-text API or machine learning model to determine whether the audio contained a joke. This approach relies on cloud processing because local TinyML techniques are generally limited to detecting specific keywords. Detecting a joke is significantly more complex, and even advanced AI models do not always interpret humor reliably. While there are many potential points of failure in this plan, this represents the ideal workflow for achieving the desired functionality.

Some Notes

How to Make

  1. Make your schematic in Eagle.
    1. Use an ESP32-S3 as your microcontroller.
    2. Add an LED/1k Ohm resistor combination for debugging.
    3. Connect a 6-pin header set for ICS-43434:
      • 3V to 3.3V
      • GND and SEL to GND
      • DOUT to D8
      • LRCL to D7
    4. Connect a 4-pin header set for VL53L0X:
      • Vin to 3.3V
      • GND to GND
      • SCL to SCL
      • SDA to SDA
  2. Draw your layout in Eagle.
    1. I used 0 Ohm resistors for jumpers to make the geometry make sense.
  3. Mill your PCB, according to the instructions in Week 5’s page.
  4. Solder your board. I used wire solder for everything.
    • The VL53L0X module is supposed to face sensor up.
    • The ICS-43434 sensor is supposed to face sensor down (I did not know this).
  5. Program your board.

Eagle Design

Board Layout
Board Schematic

I had a previous design, which was my second board I made during the Week 4 electronics design unit. This week, I made several changes. For example, I replaced the sensor footprints with header pins, as I was now using breakout modules. I also realized that I had misread much of the documentation initially—I had placed resistors where they weren’t supposed to be—so I corrected those mistakes. After rereading the documentation, I added a 100 kΩ resistor, a 0.1 µF capacitor, a 100 nF capacitor, and 5 kΩ resistors, following the guidance (for purposes such as decoupling AC signals, pull-up resistors, etc.). At the time, I didn’t fully understand this, since I had never seen a breakout board in real life, but these extra components are often already included on the breakout boards, so adding them wasn’t strictly necessary. Most of these components, like resistors in parallel, could effectively be treated as open circuits if left unconnected, so it was fine to add them just in case; others I left open.

Soldering

Milled & Soldered PCB Board Front
Milled & Soldered PCB Board Back (sensors)
What happens when you have to pull off a breakout module and you don't notice the solder lifted up on the other side because you were only looking down on it.
\

I made several mistakes while soldering, including installing the VL53L0X module upside down since I was kind of in a rush. I had to use hot air to remove it, which may have slightly damaged the board. I also learned that a short to an unused pin can still cause problems. A key lesson I learned is to check every connection as you go and use a multimeter to ensure there are no shorts to nearby pins.

Programming

Raw audio data (integers) from me singing a very high note
\
Programming the board was quite a journey. Starting with the microphone, I originally wanted to write the code myself. However, I realized that every sensor uses a slightly different library or communication protocol, and I didn’t feel confident writing code that I could also debug effectively. Since I was already worried about my embedded programming skills—especially because I had previously managed to clear the memory of a board earlier this semester—I decided to let ChatGPT handle most of the coding. I focused more on the structure and architecture, acting as a kind of “puppeteer” guiding the overall process. I first had ChatGPT generate code to test if my microphone was working. The code output the decibel levels of the sound it captured, but no matter whether the environment was loud or silent, it kept outputting around 40 dB—which I knew couldn’t be correct. At first, I assumed something was wrong with the microphone. This became a recurring theme: trying to figure out whether a problem was hardware or software. Eventually, after checking all the connections and knowing also I was actually receiving data, I concluded that the microphone was likely fine. The issue was probably in how the code was calculating the decibels. After a few rounds of creative prompt engineering with ChatGPT, I finally got it to work. The process is as follows: you first upload code to the board through the Arduino IDE, which sets the microphone’s operating rate and establishes the I2S communication protocol. Then you have to close the Arduino IDE because the serial port can only be accessed by one program at a time. Next, you switch to a Python environment, where you read the serial data and output it as a .wav file. This approach worked successfully for me. Below is an example audio recording from the mic!

What I struggled with the most was the time-of-flight sensor. It was particularly challenging because I had originally soldered it upside down. Even though the connections were technically correct, the sensor wasn’t facing the right way, which caused a huge problem. I tried to remove the module using a hot air soldering tool, but I think this may have caused some damage—it definitely melted the header pins, and there’s one spot where plastic seems deformed, where it shouldn’t be. In the end, I found myself trying to fix both software and hardware issues at the same time, which was not ideal. I could never really pinpoint what was going wrong. For example, when I was programming, I noticed a short, so I would go back and fix it, then try running the program again. Even after that, I ran into persistent issues. I spent about five hours trying to fix this, including using an I2C scan to detect devices, but no matter what I did, I couldn’t find the sensor. I even tried using my friend Eghosa’s code—she had successfully used a similar time-of-flight sensor—but her code couldn’t detect my sensor either. The expected device address for this sensor was something like 0x29, but mine was never found. Her sensor’s identiy was 0xeeaa, but mine showed up as 0xFFFF, which online searches suggested might indicate a communication error. I tried so many approaches—looking at prior students’ code, consulting online forums, scanning through every possible I²C address—but nothing worked. I still don’t know exactly what went wrong. It’s possible that I damaged the sensor while trying different code or soldering fixes. Regardless, I think the safest approach is to redesign my board. I’m hopeful that as I integrate it with the output devices in the coming weeks, the issues will become clearer. The next step would have been trying an oscilloscope, but since I'm still sick and it was quite late, I decided to leave it for the future.


            // to be ran in Arduino IDE 
            // Prompt: i have a ics-43434 soldered to a xiao esp 32 s3. How do I get audio information and turn it into speech?
            #include 
                #include 
                
                #define I2S_WS  44  // Word Select (LRCLK)
                #define I2S_SD  7   // Serial Data
                #define I2S_SCK 8  // Serial Clock (BCLK)
                
                #define SAMPLE_RATE 16000
                #define I2S_PORT I2S_NUM_0
                #define BUFFER_LEN 512
                
                int32_t buffer[BUFFER_LEN];
                
                void setup() {
                  Serial.begin(115200);
                  
                  i2s_config_t i2s_config = {
                    .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
                    .sample_rate = SAMPLE_RATE,
                    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
                    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
                    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S),
                    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
                    .dma_buf_count = 8,
                    .dma_buf_len = BUFFER_LEN,
                    .use_apll = false,
                  };
                
                  i2s_pin_config_t pin_config = {
                    .bck_io_num = I2S_SCK,
                    .ws_io_num = I2S_WS,
                    .data_out_num = I2S_PIN_NO_CHANGE,
                    .data_in_num = I2S_SD
                  };
                
                  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
                  i2s_set_pin(I2S_PORT, &pin_config);
                  i2s_set_clk(I2S_PORT, SAMPLE_RATE, I2S_BITS_PER_SAMPLE_32BIT, I2S_CHANNEL_MONO);
                
                  Serial.println("I2S microphone started!");
                }
                
                void loop() {
                  size_t bytesRead;
                  i2s_read(I2S_PORT, (void*)buffer, BUFFER_LEN * sizeof(int32_t), &bytesRead, portMAX_DELAY);
                  
                  // Convert samples to 16-bit signed
                  int samples = bytesRead / 4;
                  for (int i = 0; i < samples; i++) {
                    int16_t s = buffer[i] >> 14;  // Downscale 32-bit to 16-bit
                    Serial.println(s); // You’ll see audio values here (use Plotter)
                  }
                }
                
        

        // to be ran in Python IDE 
        // Prompt: how to save samples via serial as a .wav file?

        import serial
        import wave
        import struct
        import time

        # === CONFIGURATION ===
        SERIAL_PORT = "/dev/tty.usbmodem14101"  # change this for your computer
        BAUD_RATE = 115200
        SAMPLE_RATE = 16000       # must match your ESP32 code
        RECORD_SECONDS = 5        # how long to record
        OUTPUT_FILENAME = "output.wav"

        # === OPEN SERIAL PORT ===
        ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
        time.sleep(2)  # give ESP32 time to reset

        # === OPEN WAV FILE ===
        wav_file = wave.open(OUTPUT_FILENAME, 'wb')
        wav_file.setnchannels(1)          # mono
        wav_file.setsampwidth(2)          # 16-bit samples
        wav_file.setframerate(SAMPLE_RATE)

        print(f"Recording {RECORD_SECONDS} seconds...")
        start = time.time()

        # === READ SAMPLES FROM SERIAL ===
        while (time.time() - start) < RECORD_SECONDS:
            try:
                line = ser.readline().decode('utf-8').strip()
                if line:
                    sample = int(line)
                    data = struct.pack('<h', sample) # 16-bit little-endian
                    wav_file.writeframesraw(data)
            except ValueError:
                pass  # skip malformed lines
            except KeyboardInterrupt:
                break

        print('Done recording.')
        wav_file.close()
        ser.close()

    
    

For the Future

Design Files

Project Considerations

Acknowledgements

Resources linked above, Eghosa for letting me use her code to debug, Anthony as always!