음성 인식은 사물 인터넷(IoT)의 필수적인 요소로 자리 잡고 있으며, 이를 통해 우리는 손을 대지 않고도 기기를 제어, 모니터링 및 상호 작용할 수 있습니다. ESP32 음성-텍스트 변환은 마이크를 통해 오디오를 캡처하고 처리하여 기기가 이해할 수 있는 텍스트로 변환하는 방식으로 작동합니다. 이 텍스트는 Alexa나 Google Assistant가 음성에 응답하는 방식처럼 명령을 실행하는 데 사용됩니다. 흥미롭게도 ESP32 오프라인 음성 인식 시스템은 엣지 컴퓨팅과 클라우드 컴퓨팅을 혼합하여 사용하는 경우가 많으며, 일부 처리는 기기에서 직접 수행하고 나머지는 클라우드에서 처리합니다. 소리의 방향을 감지하는 프로젝트에 관심이 있다면 Arduino 를 사용하여 소리의 방향을 감지하는 방법 에 대한 자세한 가이드를 참조하세요 . 자동화에 관심 있는 분들을 위해 Smart Desk Bot 프로젝트는 IoT 및 AI 기능을 사용하여 음성으로 제어되는 책상 도우미를 만드는 방법을 보여줍니다.
자, 그럼 오늘의 프로젝트로 넘어가 보겠습니다. ESP32를 메인 마이크로컨트롤러로 사용하여 Edge Impulse로 확장 가능한 소형 ESP32 음성 비서를 만들어 볼 거예요. ESP32를 이용한 오프라인 음성 인식의 목표는 단순함을 유지하면서도 향후 업그레이드 가능성을 열어두는 것입니다. 그럼 바로 시작해 볼까요!
목차
프로젝트 참조
프로젝트 개요
주요 특징
ESP32 음성 인식 모듈 학습시키기
필수 구성 요소
INMP441과 ESP32 핀 연결
코드 구현
수정된 코드
데이터 구조, 상수 및 설정 변수
설정 기능
테스트
GitHub 저장소
ESP32 음성 인식 프로젝트 참조
| 요소 | 사양 | 목적 |
| 마이크로컨트롤러 | ESP32 (240MHz) | 오프라인 음성 인식용 메인 처리 장치 |
| 마이크 모듈 | INMP441 MEMS | 고정밀 오디오 캡처 |
| ML 플랫폼 | 엣지 임펄스 | ESP32 음성 인식 모델 학습 |
| 샘플링 속도 | 16kHz | 오디오 샘플링 주파수 |
| 인식 유형 | 오프라인/기기 내 | 인터넷 연결 없이도 작동합니다. |
ESP32 음성 인식 프로젝트 개요: ESP32 음성 비서 구축하기
이 프로젝트의 주요 목표는 ESP32를 사용하여 오프라인 음성 인식을 구현하는 것입니다. 인터넷상의 대부분의 프로젝트는 Google TTS와 같은 타사 API에 의존하지만, 여기서는 Edge Impulse를 사용하는 다른 접근 방식을 취합니다. 클라우드 기반 솔루션 대신, 온디바이스 머신 러닝을 위해 Edge Impulse를 활용한 ESP32 음성 인식 모듈을 구현합니다. 사실, 원한다면 TensorFlow Lite를 직접 사용할 수도 있지만, 조금 더 복잡합니다. 이 부분은 다른 글에서 다루겠습니다. 지금은 Edge Impulse 방식에 집중해 보겠습니다. 이 접근 방식을 통해 ESP32 음성 비서는 인터넷 연결 없이도 독립적으로 작동하므로 개인 정보 보호가 중요한 애플리케이션이나 원격 IoT 환경에 이상적입니다.

ESP32 음성-텍스트 변환 시스템의 주요 특징 :
✓ 완전 오프라인 작동 - ESP32 오프라인 음성 인식에 클라우드 의존성 없음
✓ 낮은 응답 속도 - 기기 내 처리로 빠른 인식 보장
✓ 사용자 지정 웨이크 워드 - 사용자 지정 명령으로 ESP32 음성 비서 학습 가능
✓ 확장 가능한 아키텍처 - 음성 명령을 쉽게 추가 가능
✓ 개인 정보 보호 - 오디오는 기기를 벗어나지 않음
✓ 비용 효율적인 솔루션 - 저렴한 ESP32 하드웨어 사용
또한, 아두이노 기반 텍스트 음성 변환 프로젝트를 통해 마이크로컨트롤러가 말하는 방법을 배울 수 있습니다 . 이 프로젝트는 입력된 텍스트를 음성으로 변환합니다.
ESP32 음성 인식 워크플로우 - 단계별 설명
이 구성에서 대부분의 핵심 작업은 머신 러닝 부분을 담당하는 Edge Impulse가 수행합니다. 그 외의 작업은 주로 데이터셋 수집과 회로 조립입니다.
ESP32 음성 인식 오프라인 시스템 구축은 다음과 같은 6단계로 간소화됩니다.
- 데이터셋을 수집하세요
- Edge Impulse에서 모델을 학습시키세요
- Edge Impulse에서 Arduino 라이브러리로 내보내기
- 회로를 조립하세요
- 코드를 약간 수정하고 ESP32에 업로드하세요.
- 모든 것을 실시간으로 테스트하세요
자, 이제 본격적인 프로젝트로 넘어가 데이터셋 수집부터 시작해 보겠습니다.
ESP32 음성 인식 오프라인 학습을 위한 데이터셋 수집 방법
ESP32를 이용한 오프라인 음성 인식의 성공은 양질의 학습 데이터에서 시작됩니다. ESP32 음성 인식 모듈의 정확도에 영향을 미치는 데이터셋을 수집하는 방법은 여러 가지가 있습니다. 가장 쉬운 방법은 오픈 소스 데이터셋을 활용하는 것입니다. 어떤 데이터셋을 사용할지는 필요한 데이터셋의 종류나 범주에 따라 달라집니다. 일반적인 프로젝트의 경우, 원하는 데이터셋을 쉽게 찾을 수 있으며 다양한 옵션이 제공됩니다. 크게 두 가지 접근 방식이 있습니다. 기존의 오픈 소스 데이터셋을 사용하는 방법과 특정 ESP32 음성 비서 애플리케이션에 맞춰 직접 녹음한 데이터를 만드는 방법입니다.
ESP32 음성-텍스트 변환 프로젝트에 가장 적합한 오픈 소스 데이터셋
ESP32 음성 인식 오프라인 시스템의 신속한 프로토타이핑을 위해 엄선된 다음 데이터 세트는 훌륭한 시작점이 될 수 있습니다.
Google 음성 명령 데이터셋 - "켜짐", "꺼짐", "예", "아니오"와 같은 키워드를 찾는 데 유용합니다.
Mozilla Common Voice – 오픈 소스 다국어 음성 데이터 세트.
Kaggle 데이터셋 – 이미지, 오디오, 텍스트 데이터셋 등 매우 다양한 데이터셋을 제공합니다.
구글에서 제공하는 방대한 데이터셋인 오픈 이미지 데이터셋 .
Hugging Face 데이터셋 – 자연어 처리, 음성 및 영상 처리에 바로 사용할 수 있는 데이터셋입니다.
물론 더 많은 옵션이 있지만, 가장 일반적인 것들만 언급했습니다. 그럼, 이 프로젝트에서 사용한 데이터셋을 보여드리겠습니다.
이 프로젝트에서 사용한 데이터셋: Google 음성 명령 데이터셋 V2
또 다른 방법은 여러 가지 목소리, 톤, 속도로 필요한 오디오를 1~3초 분량의 짧은 클립으로 직접 녹음하여 데이터를 수집하는 것입니다. 수집하는 데이터가 많을수록 학습 결과가 향상됩니다. 특히 ESP32 음성 비서 애플리케이션의 경우, 사용자 지정 녹음을 통해 최적의 성능을 확보할 수 있습니다. 음성 인식을 사무 자동화에 통합하는 방법을 알아보려면 DigiKey 음성 인식 사무 도우미를 살펴보세요.
Edge Impulse에서 ESP32 음성 인식 모듈 학습시키기
학습용 데이터셋을 수집했으면 이제 Edge Impulse 단계로 넘어갈 수 있습니다. Edge Impulse는 ESP32 음성 인식 모델을 오프라인으로 학습할 수 있는 강력한 플랫폼을 제공합니다.
아래에서는 새 프로젝트 생성부터 학습된 모델을 아두이노 라이브러리로 내보내는 단계까지 자세한 안내를 확인할 수 있습니다. 이 단계별 가이드를 통해 맞춤형 ESP32 음성 비서 모델을 생성, 학습 및 배포하는 방법을 알아보세요.
⇒ 1단계: ESP32 음성-텍스트 변환을 위한 Edge Impulse 계정 생성 Google에서 Edge Impulse를
검색하면 [1] 상단에 사이트 링크가 표시됩니다 [2]. 해당 링크를 클릭하면 Edge Impulse 홈페이지로 이동합니다. 여기에서 ESP32 음성 인식 모듈의 지능을 개발할 수 있습니다.

ESP32 음성 인식 오프라인 모델 학습 플랫폼용 Edge Impulse 로그인 페이지입니다.
여기서 오른쪽 상단 모서리에 있는 로그인 [3] 버튼을 클릭합니다. 편의상 Google로 계속 [4] 옵션을 선택했습니다. 필요한 단계를 완료하면 Edge Impulse의 메인 페이지로 이동합니다.
⇒ 2단계: ESP32 음성 인식 프로젝트 초기화
Edge Impulse의 홈 페이지에서 오른쪽 상단 모서리에 있는 새 프로젝트 생성 버튼[5]을 클릭합니다. ESP32 음성 인식 오프라인 시스템 구성을 시작합니다.

ESP32 음성 비서용 Edge Impulse 프로젝트를 프로젝트 이름 지정 인터페이스를 사용하여 새로 생성합니다.
프로젝트 이름을 입력하세요[6]. 이 이름은 최종 Arduino 라이브러리 내보내기까지 유지됩니다. 프로젝트 유형은 기본적으로 개인용 이고 설정은 기본적으로 비공개입니다. 마지막으로 새 프로젝트 만들기 [7]를 클릭하세요.
프로젝트 생성이 완료되면 프로젝트 홈페이지가 표시됩니다.
⇒ 3단계: 음성 인식을 위한 ESP32 타겟 장치 구성
프로젝트 페이지에서 가장 먼저 해야 할 일은 대상 장치와 애플리케이션 예산을 설정하는 것입니다. 올바른 장치 설정은 ESP32 음성-텍스트 변환 모델의 최적 성능을 보장합니다.

Edge Impulse에서 ESP32 장치 구성 화면을 보여주며 오프라인 음성 인식을 위한 ESP-EYE 타겟 선택이 표시됩니다.
오른쪽 상단에는 [8] Target: Cortex-M4F 80 MHz 버튼이 있습니다 . 이 버튼을 클릭하면 구성 메뉴가 열립니다. 첫 번째 단계는 대상 장치를 선택하는 것입니다. [9] 드롭다운 메뉴에서 ESP32 오프라인 음성 인식에 최적화하기 위해 [10] Espressif ESP-EYE (ESP32 240 MHz)를 선택합니다. 마지막으로 오른쪽 하단의 [11] 저장 버튼을 클릭하여 ESP32 음성 인식 모듈 구성을 확인합니다.
⇒ 4단계: ESP32 음성 인식 오프라인용 학습 데이터 업로드 이제 워크플로의 다음 단계인 ESP32 음성 비서의 데이터 수집
[12] 으로 넘어가겠습니다 .

Edge Impulse에서 센서 데이터 수집 및 업로드
데이터 추가 [13] 버튼을 클릭합니다 . 그러면 데이터 업로드, 프로젝트에서 가져오기, 스토리지 버킷 추가의 세 가지 옵션이 열립니다 . 이 인터페이스를 사용하면 ESP32 음성-텍스트 변환 학습을 위한 오디오 파일을 대량으로 업로드할 수 있습니다.
여기서는 데이터 업로드 [14]를 클릭합니다.

ESP32 음성 인식 모듈 학습을 위한 오디오 샘플에 명령어 범주를 라벨링합니다.
새로운 메뉴가 나타나고 몇 가지 옵션이 표시됩니다. 데이터셋을 개별 폴더에 제대로 저장했으므로 [15] 폴더 선택 옵션을 사용했습니다. 다음으로 [16][17][18] 파일 선택을 통해 폴더를 선택했습니다 . 폴더를 선택하면 선택된 파일 수가 표시됩니다. 중요한 단계: ESP32 음성 인식 모듈의 정확한 학습을 위해 각 데이터셋에 적절한 레이블을 지정하세요[20](예: "noise", "marvin", "on", "off"). 그런 다음 [19] 업로드를 클릭하여 ESP32 음성 인식 오프라인 모델의 데이터 수집을 완료합니다.
여기서 가장 중요한 단계는 데이터 레이블링입니다. 데이터셋을 개별적으로 업로드했기 때문에 나중에 수고를 덜기 위해 바로 레이블을 지정했습니다. 레이블 옵션에서 노이즈 데이터셋에 대해 noise [20]를 선택했습니다. 마지막으로 업로드 [21]를 클릭하면 데이터가 성공적으로 추가됩니다[22].

ESP32 음성 비서에 대한 여러 레이블이 지정된 범주를 보여주는 전체 데이터 세트가 Edge Impulse에 업로드되었습니다.
마찬가지로, 위 이미지에서처럼 Marvin, off, on에 대한 데이터도 추가했습니다. 이 ESP32 오프라인 음성 인식 프로젝트에서는 노이즈(배경 오디오), Marvin(웨이크 워드), off, on(제어 명령)에 대한 데이터셋을 추가했습니다.
⇒ 5단계: ESP32 음성 비서용 신경망 아키텍처 설계
왼쪽 패널에서 Impulse Design을 볼 수 있습니다 . 그 아래에서 Create Impulse [23]를 클릭합니다. 여기에서 Processing Block [24]과 Learning Block[25]을 선택한 다음 Save Impulse [26]를 클릭하여 ESP32 음성 인식 오프라인 모델을 저장하고 Save Impulse[26]를 클릭합니다. 옵션에 대해서는 너무 걱정하지 마세요. 기본 설정으로도 충분히 작동합니다.

Edge Impulse에서 ESP32 음성-텍스트 변환 신경망 아키텍처를 위한 임펄스 설계 생성
저장 후에는 임펄스가 성공적으로 저장되었다는 알림이 표시됩니다.
⇒ 6단계: ESP32 음성 인식 오프라인 모델을 위한 특징 추출
임펄스를 저장한 후에는 파라미터 메뉴 가 표시됩니다 . 여기에서 자동 튜닝 파라미터 [27] 옵션은 ESP32 음성 인식 모듈의 설정을 자동으로 최적화하는 데 유용합니다. 이를 클릭하고 잠시 기다린 다음 파라미터 저장 [28]을 클릭합니다.

ESP32 오프라인 음성 인식 모델 최적화를 위한 자동 튜닝 매개변수
이제 [29]에서 특징 생성을 클릭합니다 . 아래 이미지에서 데이터셋이 다양한 색상으로 표현되는 것을 볼 수 있습니다. 색상 클러스터가 더 뚜렷하고 분리될수록 결과가 더 좋아집니다.
ESP32 음성 비서 학습을 위한 데이터 분리를 보여주는 특징 생성 시각화
제 경우에는 클러스터들이 서로 너무 가까이 붙어 있어서 분리가 잘 되지 않았습니다. 색상으로 구분된 클러스터는 ESP32 오프라인 음성 인식 시스템에서 사용되는 다양한 음성 명령을 나타냅니다. 클러스터들이 잘 분리되어 있을수록(겹치는 부분이 거의 없고 서로 다른 색상일수록) 학습 데이터의 품질이 우수하고 ESP32 음성 인식 모듈의 정확도가 높을 것으로 예상됩니다.
⇒ 7단계: ESP32 음성 인식 모듈 신경망 학습 다음으로 신경망 설정
페이지 로 이동합니다 . 여기에서 학습 주기 [30], 학습률 등과 같은 매개변수를 조정하여 ESP32 음성 인식 오프라인 모델의 성능을 최적화할 수 있습니다. 대부분의 기본 설정은 잘 작동합니다.

ESP32 음성 비서 모델의 신경망 학습 설정 및 구성 옵션
기본 설정을 유지하고 저장 및 학습 [31]을 클릭했습니다. 이제 편안히 기다리세요. 데이터셋 크기에 따라 시간이 다소 걸릴 것입니다. 완료되면 모델의 학습 성능을 확인할 수 있습니다.
⇒ 8단계: ESP32 음성-텍스트 변환 모델 정확도 검증
테스트 모델 창 에서 Classify All [32] 버튼을 클릭하기만 하면 됩니다. 그러면 예약된 테스트 데이터를 사용하여 ESP32 음성 인식 모듈을 자동으로 평가하는 테스트가 실행됩니다. 이 검증 단계는 ESP32 음성 인식 오프라인 시스템에 대한 편향되지 않은 정확도 측정값을 제공합니다.

Edge Impulse에서 모든 기능을 분류하여 ESP32 음성 인식 모듈의 정확도를 테스트합니다.
보시다시피 결과가 만족스러웠기 때문에 배포 단계로 넘어갔습니다. ESP32 음성 비서 애플리케이션을 상용으로 출시할 경우 90% 이상의 정확도를 목표로 해야 합니다. 프로토타입 및 개발 목적이라면 85% 이상의 정확도도 괜찮습니다.
⇒ 9단계: ESP32 음성 인식 오프라인 모델을 아두이노 라이브러리로 배포
배포 창 으로 이동합니다 . 검색 창[33]에 Arduino Library[34]를 입력합니다. 그런 다음 빌드[35] 버튼을 클릭하여 ESP32 음성 인식 모듈을 Arduino 호환 라이브러리로 컴파일을 시작합니다.

Edge Impulse 플랫폼에서 ESP32 음성-텍스트 변환 모델을 아두이노 라이브러리로 배포하기
빌드가 완료되면 라이브러리를 저장하라는 메시지가 표시됩니다[36]. 라이브러리는 .zip 파일 로 다운로드됩니다 .
⇒ 10단계: 라이브러리 설치 및 ESP32 음성 비서 코드 구성
Arduino IDE를 열고 스케치 → 라이브러리 포함 → .ZIP 라이브러리 추가 [37]로 이동합니다. 다운로드한 라이브러리[38]를 선택하고 열기 [39]를 클릭하여 ESP32 음성 인식 모듈 프레임워크를 설치합니다.

ESP32 오프라인 음성 인식 개발을 위해 Arduino IDE에 Edge Impulse 라이브러리 설치하기
이제 파일 → 예제 → [프로젝트 이름]_추론 → esp32 → esp32_microphone으로 이동하여 예제 프로그램 [40]을 엽니다. 이것은 ESP32 보드에 업로드할 코드입니다. 이 사전 구성된 스케치는 ESP32 음성 비서의 오디오 캡처, 전처리 및 ESP32 음성-텍스트 변환에 최적화된 추론 코드를 포함하여 ESP32 음성 비서의 완전한 시작점을 제공합니다.
자, 이제 하드웨어로 넘어가 볼까요!
ESP32 음성 비서 하드웨어 설정: ESP32 음성 인식 모듈 배선하기
이 ESP32 음성 인식 오프라인 프로젝트의 하드웨어 구성은 간단하며 최소한의 부품만 필요합니다. 회로는 LED 두 개, 마이크 하나, 그리고 ESP32 메인 모듈로 구성됩니다. 아래는 구현해야 할 회로도입니다. 이 구성을 통해 웨이크 워드 감지 및 명령 실행이 가능한 완벽한 ESP32 음성 비서를 만들 수 있습니다.
ESP32 음성 인식 모듈에 필요한 구성 요소
| 요소 | 수량 | 기능 |
| ESP32 개발 보드 | 1 | ESP32 오프라인 음성 인식용 메인 프로세서 |
| INMP441 MEMS 마이크 | 1 | ESP32 음성-텍스트 변환을 위한 고정밀 오디오 입력 |
| LED(표시등) | 1 | 웨이크 워드 감지를 위한 시각적 피드백 |
| LED (제어) | 1 | 음성 명령을 통한 출력 제어 가능 |
| 저항(220Ω) | 2 | LED 전류 제한 |
| 브레드보드 및 점퍼선 | 필요에 따라 | 프로토타이핑 연결 |

ESP32를 이용한 오프라인 음성 인식 회로도 (INMP441 마이크 연결 및 LED와 ESP32 GPIO 핀 연결 포함)
ESP32 음성-텍스트 변환용 INMP441 마이크 모듈 핀 배치도
여기에는 각각 다른 용도로 사용되는 두 개의 LED가 있습니다. 하나는 웨이크워드 감지 및 음성 인식 상태를 나타내는 표시 LED[23]이고, 다른 하나는 명령을 통해 켜거나 끌 수 있는 제어 LED[GPIO22]입니다. INMP441 MEMS 마이크는 ESP32 음성 비서로 고품질 오디오를 전송하기 위해 I²S(Inter-IC Sound) 프로토콜을 사용합니다. 두 개의 LED는 ESP32 오프라인 음성 인식 시스템에 필수적인 시각적 피드백을 제공합니다. 안정적인 ESP32 오프라인 음성 인식을 위해서는 올바른 핀 연결이 매우 중요합니다.
주요 구성 요소는 마이크 모듈인 INMP441 MEMS 고정밀 무지향성 마이크 모듈입니다. 이 모듈에는 6개의 핀이 있습니다.
- 좌/우 [3V3]
- WS [25]
- SCK [26]
- SD [33]
- VDD [3V3]
- GND [GND]
회로에 대한 설명은 여기까지입니다.
연결이 성공적으로 완료되면 다음과 같이 표시됩니다.
실제 하드웨어에서는 최소한의 요소로 최대한 단순하게 만들기 위해 약간 다른 접근 방식을 사용했습니다. 회로도는 참고용이며 제작 과정을 안내하기 위한 것입니다.
Edge Impulse에서 다운로드한 Arduino 라이브러리의 기본 I²S 핀은 다음과 같습니다.
- 좌/우 [3V3]
- WS [32]
- SCK [26]
- SD [33]
- VDD [3V3]
- GND [GND]
이러한 기본 핀을 사용하면 예제 프로그램을 코드 수정 없이 실행할 수 있습니다. 위 회로도는 수정된 코드에 사용된 핀을 보여주지만, 필요에 따라 핀을 변경하는 것도 간단합니다.
음성 인식 모듈용 INMP441과 ESP32 핀 연결
| INMP441 Pin | ESP32 GPIO | Signal Description |
| L/R | 3V3 | Channel selection (3.3V = Right channel) |
| WS (Word Select) | GPIO 25 | Left/Right clock for I²S audio |
| SCK (Serial Clock) | GPIO 26 | Bit clock for audio data |
| SD (Serial Data) | GPIO 33 | Audio data input to ESP32 |
| VDD | 3V3 | Power supply (3.3V) |
| GND | GND | Ground reference |
ESP32 음성-텍스트 변환 코드 구현: ESP32 음성 인식 모듈 프로그래밍
ESP32 오프라인 음성 인식 예제 코드 테스트
고급 기능을 살펴보기 전에 Edge Impulse 예제 프로그램을 사용하여 ESP32 음성 비서의 기본 기능을 테스트해 보세요.
파일 → 예제 → “[프로젝트 이름]”_ 추론 → esp32 → esp32_microphone[40] . 이 원시 예제를 사용하면 하드웨어로 모델을 실시간으로 테스트할 수 있습니다.

Arduino IDE에서 ESP32 음성 인식 모듈 테스트를 위한 예제 코드를 보여줍니다.
코드에서 다음 코드 조각을 찾을 수 있습니다. 이를 참고하여 회로를 적절히 조정하거나 코드의 핀 설정을 수정하여 작동하도록 할 수 있습니다. 이 예제는 ESP32를 이용한 실시간 음성-텍스트 변환 추론을 보여줍니다. 시리얼 모니터에는 학습된 각 단어에 대한 신뢰도 수준이 표시되어 ESP32 오프라인 음성 인식 모델이 오디오 입력을 어떻게 해석하는지 확인할 수 있습니다.
i2s_pin_config_t pin_config = {
.bck_io_num = 26, // IIS_SCLK
.ws_io_num = 32, // IIS_LCLK
.data_out_num = -1, // IIS_DSIN
.data_in_num = 33, // IIS_DOUT
};
시리얼 모니터에서 실시간 추론 과정을 확인할 수 있으며, 학습된 각 단어에 대한 신뢰도 수준이 표시됩니다. 이러한 유연성을 통해 ESP32 음성 인식 모듈을 특정 핀 레이아웃에 맞게 조정할 수 있습니다.
이 신뢰 수준을 사용하여 코드를 수정하고 더 고급 기능을 추가할 수 있습니다.
ESP32 음성 제어 프로젝트용 수정된 코드
예제 코드는 웨이크 워드 감지, LED 제어 등의 기능을 포함하도록 수정되었습니다. 간단하게 코드를 살펴보겠습니다.
개요
이 코드는 ESP32와 에지 임펄스 머신러닝을 활용한 고급 음성 제어 LED 시스템 구현을 위한 것입니다. 음성 인식 기능은 "마빈"과 같은 웨이크 워드 감지와 "켜짐"/"꺼짐"과 같은 명령 인식을 포함합니다. 이 시스템은 이중 신뢰도 임계값을 사용하며, 포괄적인 시각적 피드백을 제공합니다. 개선된 코드는 기본 예제를 웨이크 워드 감지, 명령 처리 및 시각적 피드백 기능을 갖춘 상용 ESP32 음성 인식 오프라인 시스템으로 변환합니다. 이 ESP32 음성 비서를 지능적이고 반응성이 뛰어나게 만드는 핵심 구성 요소를 살펴보겠습니다.
포함된 라이브러리
- ESP32_STT_Recognition_inferencing.h – Edge Impulse의 배포용 라이브러리입니다. 제 코드에는 ESP32_STT_Recognition_inferencing으로 호출됩니다. 사용자의 경우 [프로젝트 이름]_inferencing이 될 것입니다. 설치된 라이브러리의 예제 프로그램을 열어 확인할 수 있습니다.
- freertos/FreeRTOS.h, freertos/task.h – 병렬 오디오 캡처 및 처리를 위한 멀티태스킹을 가능하게 하는 실시간 운영 체제 구성 요소입니다.
- driver/i2s.h – ESP32용 I2S 오디오 드라이버로, 오디오 입력을 위해 디지털 MEMS 마이크와 인터페이스하는 데 사용됩니다.
데이터 구조, 상수 및 설정 변수
1. ESP32 음성 인식 모듈용 핵심 라이브러리
typedef struct {
int16_t *buffer; // Audio sample storage
uint8_t buf_ready; // Flag: buffer ready for processing
uint32_t buf_count; // Current write position
uint32_t n_samples; // Total buffer capacity
} inference_t;
이 라이브러리는 실시간 오디오 캡처 및 머신러닝 처리를 위한 순환 버퍼를 관리합니다. 이를 통해 병렬 오디오 캡처 및 머신러닝 추론을 위한 멀티태스킹이 가능하며, 이는 반응성이 뛰어난 ESP32 오프라인 음성 인식에 필수적입니다. FreeRTOS는 ESP32 음성 비서가 블로킹 없이 오디오를 녹음하고, 음성을 처리하고, 출력을 제어할 수 있도록 보장합니다.
2. ESP32 음성 인식을 위한 정확한 오프라인 구현을 위한 이중 신뢰도 임계값 시스템
const float COMMAND_CONFIDENCE_THRESHOLD = 0.80;
const float RECOGNITION_CONFIDENCE_THRESHOLD = 0.50;
COMMAND_CONFIDENCE_THRESHOLD는 명령 실행에 사용되는 반면, RECOGNITION_CONFIDENCE_THRESHOLD는 표시용으로만 사용됩니다. 임계값이 높을수록 잘못된 명령 실행을 방지할 수 있습니다.
3. 타이밍 구성
const unsigned long LISTENING_DURATION_MS = 10000;
이는 웨이크 워드 이후 명령이 수락되는 시간 범위입니다.
4. ESP32 음성-텍스트 변환용 하드웨어 핀 정의
const int CONTROL_LED_PIN = 22;
const int INDICATOR_LED_PIN = 23;
CONTROL_LED_PIN은 제어 대상인 메인 LED이고, INDICATOR_LED_PIN은 상태 피드백을 제공합니다.
5. 오디오 처리
static const uint32_t sample_buffer_size = 2048;
이는 오디오 캡처를 위한 I2S DMA 버퍼 크기입니다.
6. ESP32 오프라인 음성 인식을 위한 상태 관리
static bool wake_word_detected = false;
static unsigned long wake_word_timestamp = 0;
static bool listening_mode = false;
static bool led_state = false;
static inference_t inference;
static signed short sampleBuffer[2048];
static bool debug_nn = false;
static bool record_status = true;
이 변수들은 감지 상태, 타이밍, 청취 모드, LED 상태, 녹음 상태, 오디오 버퍼 및 디버그 플래그를 관리합니다. 이러한 변수들은 ESP32 음성 인식 오프라인 시스템의 전체 상태를 추적하여 웨이크 워드 감지, 명령 타이밍, LED 상태 및 오디오 버퍼 관리를 담당합니다.
setup 함수
1. 직렬 통신
ESP32 음성 인식 오프라인 시스템에서 실시간 추론 데이터를 보려면 Arduino IDE 시리얼 모니터(115200bps)를 여십시오.
Serial.begin(115200);
while (!Serial); // optional
디버깅 및 모니터링을 위해 115200 보드 속도로 직렬 인터페이스를 설정합니다.
2. 하드웨어 핀 구성
pinMode(CONTROL_LED_PIN, OUTPUT);
digitalWrite(CONTROL_LED_PIN, LOW);
pinMode(INDICATOR_LED_PIN, OUTPUT);
digitalWrite(INDICATOR_LED_PIN, LOW);
LED 제어를 위해 GPIO 핀을 구성하고 초기화하여 꺼짐 상태로 만듭니다.
3. 오디오 시스템 초기화
if (microphone_inference_start(EI_CLASSIFIER_RAW_SAMPLE_COUNT) == false) {
ei_printf("ERR: Could not allocate audio buffer\n");
Return;
}
오디오 버퍼를 할당하고, I2S 마이크를 초기화한 후, 캡처 작업을 시작합니다.
Main Loop 함수
loop() 함수는 지속적으로 실행되며 세 가지 주요 작업을 수행합니다.
1. 오디오 캡처
bool m = microphone_inference_record();
if (!m) {
ei_printf("ERR: Failed to record audio...\n");
return;
}
새로운 샘플로 오디오 버퍼가 채워질 때까지 기다립니다.
2. 머신러닝 추론
signal_t signal;
signal.total_length = EI_CLASSIFIER_RAW_SAMPLE_COUNT;
signal.get_data = µphone_audio_signal_get_data;
EI_IMPULSE_ERROR r = run_classifier(&signal, &result, debug_nn);
에지 임펄스 분류기를 실행하여 음성을 인식하고 학습된 각 단어에 대한 신뢰도 수준을 제공합니다.
3. 명령 처리
handle_wake_word_and_commands(result);
머신러닝 결과를 처리하고 적절한 조치를 실행합니다.
4. 디버그 출력
ei_printf("Predictions (DSP: %d ms., Classification: %d ms.): \n", ...);
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
ei_printf(" %s: %.2f\n", result.classification[ix].label, ...);
}
모든 클래스에 대한 추론 시간 및 신뢰도 점수를 표시합니다.
사용자 정의 함수
사용자 정의 함수는 크게 네 가지 범주로 나뉩니다.
1. LED 시각 피드백 시스템
show_wake_word_pattern()
show_listening_mode()
show_turning_on_pattern()
show_turning_off_pattern()
show_general_recognition_pattern()
stop_listening_mode()
이것들은 시스템의 현재 상태를 보여주는 표시등 LED를 제어합니다. 예를 들면 다음과 같습니다.
- "마빈"이라고 말하면 LED가 빠르게 두 번 깜빡입니다.
- 명령을 기다리는 동안 LED가 켜져 있습니다.
- "켜짐"이라고 말하면 LED가 깜빡여 확인됩니다.
- "끄다"라고 말하면 서서히 사라지는 효과가 나타납니다.
- 다른 단어들은 눈을 한 번 빠르게 깜빡이게 합니다.
- stop_listening_mode() 함수는 청취 창이 종료되면 표시기를 끕니다.
2. 명령 처리 기능
handle_wake_word_and_commands(ei_impulse_result_t &result)
이것이 시스템의 핵심입니다. 가장 높은 신뢰도 점수를 받은 단어를 찾아냅니다. 신뢰도가 50% 이상인 단어는 LED를 한 번 깜빡여 피드백을 제공합니다. 명령은 신뢰도가 80% 이상일 때만 실행됩니다. "marvin"이 감지되면 청취 모드가 시작되고, "켜기"/"끄기" 명령으로 메인 LED가 제어됩니다. 이 두 단계 과정은 오작동을 방지합니다.
3. 오디오 캡처 및 처리 시스템
기능
audio_inference_callback(uint32_t n_bytes)
capture_samples(void* arg)
microphone_inference_start(uint32_t n_samples)
microphone_inference_record(void)
microphone_audio_signal_get_data(size_t offset, size_t length, float *out_ptr)
microphone_inference_end(void)
이 함수들은 연속적인 오디오 입력을 처리합니다. `capture_samples` 함수는 백그라운드에서 실행되어 I2S 마이크에서 오디오를 읽고 증폭합니다. 데이터는 버퍼에 복사되고, 버퍼가 가득 차면 처리를 위해 전송됩니다. 다른 함수들은 오디오를 머신러닝 모델에 필요한 형식으로 변환하고 메모리 및 백그라운드 작업을 관리합니다.
4. I2S 하드웨어 인터페이스
함수,
i2s_init(uint32_t sampling_rate)
i2s_deinit(void)
이 함수들은 ESP32 I2S 하드웨어를 구성합니다. i2s_init 함수는 샘플링 속도(16kHz), DMA 버퍼, 그리고 GPIO 핀(비트 클럭용 26번 핀, 워드 선택용 25번 핀, 데이터 입력용 33번 핀)을 설정합니다. 오디오는 CPU 오버헤드 없이 지속적으로 캡처됩니다. i2s_deinit 함수는 I2S를 깔끔하게 종료하지만, 이 상시 작동 설정에서는 사용되지 않습니다.
이 설명을 통해 이제 이 프로젝트의 코드가 어떻게 작동하는지 명확하게 이해하셨을 것입니다. 다음으로 실제 구현 사례를 살펴보겠습니다 .
ESP32 음성 인식 오프라인 시스템 테스트하기
코드 업로드가 완료되면 ESP32 음성 비서의 작동 방식을 확인할 수 있습니다. 작동 방식은 간단합니다. "마빈"이라는 웨이크 워드를 사용합니다. 웨이크 워드를 인식하면 10초 동안 명령어를 기다립니다. 명령어가 입력되면 이를 인식하여 조명을 켜거나 끄는 등의 특정 기능을 실행합니다. 이 시스템은 안정적인 ESP32 오프라인 음성 인식을 위해 최적화된 간단한 2단계 명령 구조로 작동합니다.
위 GIF 이미지에서 볼 수 있듯이, 한 사람이 "마빈"이라고 말한 후 "켜기"라고 하면 파란색 불빛이 켜지고, "끄기"라고 하면 꺼집니다.
이로써 프로젝트 전체에 대한 명확한 이해가 되셨을 겁니다. 이 프로젝트는 여러분의 상상력에 따라 기능을 추가하여 확장할 수 있으며, 이것은 단지 제가 구현한 버전일 뿐입니다. 한 가지 과제는 적절한 데이터셋을 찾는 것입니다. 적합한 데이터셋만 있다면 모든 것이 정상적으로 작동할 것입니다. 마지막으로, 아두이노에 음성 명령 인식 기능을 추가하는 데 관심이 있다면, 이 아두이노 음성 인식 튜토리얼을 참고해 보세요 .
ESP32 음성 인식 프로젝트의 전체 코드ESP32 음성 인식 프로젝트의 전체 코드
GitHub 저장소를 방문하여 소스 코드를 다운로드하고, 수정하고, 프로젝트를 손쉽게 배포하세요.
자주 묻는 질문: ESP32 음성 인식 및 ESP32 음성-텍스트 변환
⇥ 1. ESP32 음성 인식과 ESP32를 이용한 텍스트 음성 변환(TTS)의 차이점은 무엇인가요?
ESP32 음성 인식(STT)은 음성을 텍스트 명령으로 변환하여 기기를 제어합니다. 텍스트 음성 변환(TTS)은 ESP32를 사용하여 텍스트에서 음성 출력을 생성합니다. 이 프로젝트는 음성 제어를 위한 STT에 초점을 맞추고 있습니다. TTS를 구현하려면 추가적인 오디오 출력 하드웨어와 합성 라이브러리가 필요하며, 이러한 요소들은 음성 응답 기능을 위해 별도로 통합할 수 있습니다.
⇥ 2. Google TTS 대신 Edge Impulse를 사용하여 ESP32 음성 비서를 구동해야 하는 이유는 무엇입니까?
Edge Impulse는 인터넷 연결, API 비용 또는 개인 정보 보호 문제에 대한 걱정 없이 진정한 오프라인 ESP32 음성 인식을 지원합니다. Google TTS는 인터넷 연결이 필요하고 요청 시마다 요금이 부과되는 반면, Edge Impulse를 사용하면 기기에서 로컬로 실행되는 독립형 모델을 만들 수 있어 응답 속도가 빠르고 사용량에 제한이 없으며 개인 정보 보호가 완벽하고 사물 인터넷(IoT) 애플리케이션에 일반적인 오프라인 환경 및 상황에서도 작동합니다.
⇥ 3. ESP32를 이용한 오프라인 다중 음성 감지에 가장 적합한 마이크는 무엇인가요?
ESP32 음성 인식 모듈 애플리케이션에는 INMP441 MEMS 무지향성 마이크가 최적의 선택입니다. I²S 디지털 출력(아날로그 노이즈 없음), 61dB SNR, 무지향성 픽업 패턴, 3.3V 직접 인터페이스 호환성을 갖추고 있습니다. 프로젝트 사양 및 예산에 따라 SPH0645(I²S MEMS와 유사) 또는 MAX9814(AGC 기능이 있는 아날로그)와 같은 다른 옵션도 고려해 볼 수 있습니다.
⇥ 4. ESP32 음성 비서는 전력을 얼마나 소모합니까?
ESP32 음성 인식 오프라인 시스템은 연속 청취 시 약 160~260mA의 전력을 소모합니다(ESP32: 80~160mA, INMP441: 1.4mA, LED: 각 20~40mA). 웨이크 워드 감지 기능과 활성화 간 딥 슬립 모드를 사용하면 평균 전력 소모를 10~50mA까지 낮출 수 있습니다. 따라서 배터리로 직접 작동이 가능합니다. 초저전력 애플리케이션의 경우, 외부 웨이크 워드 감지 IC를 사용하여 필요할 때만 ESP32 음성 인식을 실행할 수 있습니다.
⇥ 5. ESP32 하나로 다국어 오프라인 음성 인식 시스템을 구축하는 것이 가능할까요?
네, 가능합니다. Edge Impulse는 ESP32와 함께 사용되는 음성 인식 모듈에 적합한 다국어 학습 기술을 보유하고 있습니다. 데이터셋을 수집할 때 필요한 모든 언어의 음성 샘플을 추출하면 신경망이 특정 언어에 국한되지 않는 음향 패턴을 학습할 수 있습니다. 마찬가지로, 필요한 각 언어별로 별도의 모델을 구축하고 모델 간에 전환할 수도 있습니다. 모델의 성능과 추론 결과는 언어 간 유사성 및 ESP32가 더 큰 다국어 모델을 실행하는 데 사용할 수 있는 메모리 용량에 따라 달라질 수 있습니다.
⇥ 6. ESP32 음성-텍스트 변환 시스템의 인식 지연 시간은 얼마입니까?
일반적인 ESP32 음성 비서의 지연 시간은 총 약 200~500ms입니다. 오디오 캡처(1000ms 윈도우), DSP 전처리(50~100ms), 신경망 추론(100~300ms), 명령 실행(<50ms)으로 구성됩니다. Edge Impulse는 ESP32의 240MHz 듀얼 코어 프로세서에 최적화된 추론을 제공합니다. 모델 복잡성이 증가함에 따라 지연 시간도 증가하지만, 클라우드 기반 서비스의 지연 시간이 1~2초 정도인 점을 고려하면 대부분의 음성 제어 애플리케이션에서는 거의 눈에 띄지 않습니다.
⇥ 7. 배경 소음이 있는 환경에서 ESP32 음성 인식 모듈의 정확도를 향상시키는 방법은 무엇입니까?
ESP32의 오프라인 음성 인식을 개선하기 위한 방법은 다음과 같습니다. (1) 훈련 데이터에 현실적인 배경 소음 추가, (2) 전처리 단계에서 소음 감소, (3) 방향성 마이크 배치, (4) 웨이크 워드에 대한 신뢰도 임계값 증가, (5) INMP441 주변에 음향 격리 장치 설치, (6) 다양한 거리에서 음성을 사용하여 재훈련, (7) 중요 명령에 대한 다중 확인을 통해 오탐을 방지합니다.
결론: ESP32 음성 인식 오프라인 마스터하기
이제 클라우드에 의존하지 않고 오프라인에서도 작동 가능한 완전 독립형 ESP32 음성 인식 모듈을 완성했습니다. 이 ESP32 음성-텍스트 변환 시스템은 머신 러닝, 임베디드 시스템, 실시간 오디오 처리를 결합하여 실현 가능한 IoT 시스템으로 구현할 수 있는 음성 인터페이스를 위한 엣지 컴퓨팅의 개념 증명입니다. 여러분이 구축한 ESP32 음성 비서 프레임워크는 단순한 LED 제어부터 홈 자동화, 산업 제어 시스템, 심지어 접근성 장치까지 제어할 수 있는 다양한 음성 명령 통합에 이르기까지 무한한 확장성을 제공합니다. ESP32 오프라인 음성 인식은 무궁무진한 가능성을 열어줍니다. 저희 인공지능 프로젝트 모음에서는 ESP32 음성 제어, 스마트 주차 시스템, 혁신적인 로봇 공학 응용 프로그램 등 따라 하기 쉬운 AI 및 머신 러닝 프로젝트 아이디어를 제공합니다.
이 프로젝트는 Circuit Digest 엔지니어링 팀에서 제작했습니다. 저희 전문가들은 메이커와 엔지니어들이 라즈베리 파이, 아두이노, ESP32 및 IoT 개발 프로젝트를 마스터할 수 있도록 실용적이고 실제적인 튜토리얼을 만드는 데 집중하고 있습니다 .
위 튜토리얼의 워논 문서는 다음 링크를 참고하세요. 늘 유용한 자료를 제공하는 저자에게 감사합니다.
전체 코드를 아래에 포함합니다.
/*
* ===============================================================================
* ESP32 ENHANCED VOICE CONTROL SYSTEM WITH EDGE IMPULSE
* ===============================================================================
*
* Author: CircuitDigest/RithikKrisna[me_RK]
* Date: August 2025
*
* DESCRIPTION:
* Advanced voice-controlled LED system using ESP32 microcontroller with Edge Impulse
* machine learning for wake word detection and command recognition. Features dual
* confidence thresholds for reliable command execution and enhanced user feedback.
*
* FEATURES:
* --------
* • Wake Word Detection: "marvin" activates listening mode
* • Voice Commands: "on" and "off" to control LED
* • Dual Confidence System:
* - 80% threshold for reliable command execution
* - 50% threshold for recognition feedback
* • Visual Feedback System:
* - Control LED (Pin 22): Main device being controlled
* - Indicator LED (Pin 23): Status and pattern feedback
* • Listening Window: 10-second timeout after wake word
* • Real-time Audio Processing: Continuous inference with I2S microphone
*
* LED PATTERNS:
* ------------
* • Wake Word ("marvin"): 2 quick pulses + steady listening indicator
* • Turn On Command: Triple flash + confirmation
* • Turn Off Command: Fade pattern + confirmation
* • General Recognition: Single quick blink for detected words
* • Listening Mode: Steady indicator light
*
* HARDWARE REQUIREMENTS:
* ---------------------
* • ESP32 Development Board
* • I2S Digital Microphone (INMP441 or similar)
* • 2x LEDs with appropriate resistors
* • Breadboard and connecting wires
*
* PIN CONFIGURATION:
* -----------------
* • I2S Microphone:
* - BCK (Bit Clock): Pin 26
* - WS (Word Select): Pin 25
* - Data In: Pin 33
* • LEDs:
* - Control LED: Pin 22
* - Indicator LED: Pin 23 (optional, set to -1 to disable)
*
* SOFTWARE DEPENDENCIES:
* ---------------------
* • Edge Impulse Arduino Library
* • ESP32 Arduino Core
* • FreeRTOS (included with ESP32 core)
*
* USAGE:
* ------
* 1. Say "marvin" to activate listening mode (indicator LED turns on)
* 2. Within 10 seconds, say "on" or "off" to control the main LED
* 3. System provides visual feedback for all recognized words
*
* ===============================================================================
*/
// ===============================================================================
// PREPROCESSOR DIRECTIVES AND MEMORY OPTIMIZATION
// ===============================================================================
// Memory optimization: Remove this macro to save 10K RAM if target is memory-limited
#define EIDSP_QUANTIZE_FILTERBANK 0
/*
** MEMORY ALLOCATION NOTE:
** If you encounter TFLite arena allocation issues due to dynamic memory fragmentation,
** try defining "-DEI_CLASSIFIER_ALLOCATION_STATIC" in boards.local.txt and copy to:
** <ARDUINO_CORE_INSTALL_PATH>/arduino/hardware/<mbed_core>/<core_version>/
**
** Reference: https://support.arduino.cc/hc/en-us/articles/360012076960-Where-are-the…-
** If issues persist, insufficient memory for this model and application.
*/
// ===============================================================================
// SYSTEM INCLUDES
// ===============================================================================
#include <"YOUR_PROJECT_TITLE"_inferencing.h> // Edge Impulse generated library
#include "freertos/FreeRTOS.h" // Real-time operating system
#include "freertos/task.h" // Task management
#include "driver/i2s.h" // I2S audio driver
// ===============================================================================
// AUDIO PROCESSING DATA STRUCTURES
// ===============================================================================
/**
* Audio inference buffer structure
* Manages circular buffer for real-time audio processing
*/
typedef struct {
int16_t *buffer; // Audio sample buffer
uint8_t buf_ready; // Buffer ready flag
uint32_t buf_count; // Current buffer position
uint32_t n_samples; // Total number of samples
} inference_t;
// ===============================================================================
// SYSTEM CONFIGURATION CONSTANTS
// ===============================================================================
// AI Model Confidence Thresholds
const float COMMAND_CONFIDENCE_THRESHOLD = 0.80; // 80% for reliable command execution
const float RECOGNITION_CONFIDENCE_THRESHOLD = 0.50; // 50% for recognition feedback
// Timing Configuration
const unsigned long LISTENING_DURATION_MS = 10000; // 10-second listening window after wake word
// Hardware Pin Configuration
const int CONTROL_LED_PIN = 22; // Main LED being controlled
const int INDICATOR_LED_PIN = 23; // Status indicator LED (-1 to disable)
// Audio Processing Configuration
static const uint32_t sample_buffer_size = 2048; // I2S sample buffer size
// ===============================================================================
// GLOBAL STATE VARIABLES
// ===============================================================================
// Wake Word System State
static bool wake_word_detected = false; // Wake word detection flag
static unsigned long wake_word_timestamp = 0; // Timestamp of last wake word
static bool listening_mode = false; // Active listening state
static bool led_state = false; // Current LED state
// Audio Processing State
static inference_t inference; // Audio inference structure
static signed short sampleBuffer[sample_buffer_size]; // Raw audio sample buffer
static bool debug_nn = false; // Neural network debug output
static bool record_status = true; // Recording status flag
// ===============================================================================
// LED VISUAL FEEDBACK SYSTEM
// ===============================================================================
/**
* @brief Create wake word detection pattern
* Shows 2 quick pulses to indicate "marvin" was detected
*/
void show_wake_word_pattern() {
if (INDICATOR_LED_PIN == -1) return;
// Wake pattern - 2 quick pulses to indicate wake word detected
for (int cycle = 0; cycle < 2; cycle++) {
digitalWrite(INDICATOR_LED_PIN, HIGH);
delay(150);
digitalWrite(INDICATOR_LED_PIN, LOW);
delay(100);
}
}
/**
* @brief Activate listening mode indicator
* Shows steady light during command listening period
*/
void show_listening_mode() {
if (INDICATOR_LED_PIN == -1) return;
digitalWrite(INDICATOR_LED_PIN, HIGH);
}
/**
* @brief Show "turning on" confirmation pattern
* Triple flash followed by confirmation sequence
*/
void show_turning_on_pattern() {
if (INDICATOR_LED_PIN == -1) return;
// Quick triple flash then steady
for (int i = 0; i < 1; i++) {
digitalWrite(INDICATOR_LED_PIN, HIGH);
delay(100);
digitalWrite(INDICATOR_LED_PIN, LOW);
delay(100);
}
// Brief pause then steady on for confirmation
delay(200);
digitalWrite(INDICATOR_LED_PIN, HIGH);
delay(800);
digitalWrite(INDICATOR_LED_PIN, LOW);
}
/**
* @brief Show "turning off" confirmation pattern
* Fade-like pattern to indicate device turning off
*/
void show_turning_off_pattern() {
if (INDICATOR_LED_PIN == -1) return;
// Start bright, then fade to off with pauses
digitalWrite(INDICATOR_LED_PIN, HIGH);
delay(300);
digitalWrite(INDICATOR_LED_PIN, LOW);
delay(150);
digitalWrite(INDICATOR_LED_PIN, HIGH);
delay(200);
digitalWrite(INDICATOR_LED_PIN, LOW);
delay(150);
digitalWrite(INDICATOR_LED_PIN, HIGH);
delay(100);
digitalWrite(INDICATOR_LED_PIN, LOW);
// Final confirmation that it's off
delay(500);
}
/**
* @brief Show general word recognition pattern
* Single blink for words detected above 50% confidence (non-commands)
*/
void show_general_recognition_pattern() {
if (INDICATOR_LED_PIN == -1) return;
// Single quick blink for general recognition
digitalWrite(INDICATOR_LED_PIN, HIGH);
delay(150);
digitalWrite(INDICATOR_LED_PIN, LOW);
}
/**
* @brief Deactivate listening mode
* Turns off indicator LED and resets listening state
*/
void stop_listening_mode() {
if (INDICATOR_LED_PIN == -1) return;
listening_mode = false;
digitalWrite(INDICATOR_LED_PIN, LOW);
}
// ===============================================================================
// VOICE COMMAND PROCESSING ENGINE
// ===============================================================================
/**
* @brief Enhanced wake word detection and command processing system
*
* Processes inference results with dual confidence thresholds:
* - High threshold (80%) for reliable command execution
* - Lower threshold (50%) for user feedback and recognition
*
* @param result Edge Impulse inference result containing classification data
*/
void handle_wake_word_and_commands(ei_impulse_result_t &result) {
// Find the classification label with highest confidence
float max_confidence = 0.0;
String detected_word = "";
// Iterate through all possible classifications
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
if (result.classification[ix].value > max_confidence) {
max_confidence = result.classification[ix].value;
detected_word = String(result.classification[ix].label);
}
}
float max_confidence_percentage = max_confidence * 100.0;
// ENHANCED FEATURE: Recognition feedback for any detected word above 50%
// Provides user feedback even for words below command threshold
if (max_confidence >= RECOGNITION_CONFIDENCE_THRESHOLD && !detected_word.equals("noise")) {
// Only show general recognition if not in command listening mode
if (!listening_mode && !detected_word.equals("marvin") &&
!detected_word.equals("on") && !detected_word.equals("off")) {
show_general_recognition_pattern();
}
ei_printf("Recognized: %s (%.1f%%)\n", detected_word.c_str(), max_confidence_percentage);
}
// Check if listening window has expired (10-second timeout)
if (wake_word_detected) {
unsigned long current_time = millis();
if (current_time - wake_word_timestamp > LISTENING_DURATION_MS) {
wake_word_detected = false;
listening_mode = false;
stop_listening_mode();
ei_printf("Listening window expired.\n");
return;
}
}
// COMMAND PROCESSING: Only execute commands above 80% confidence threshold
if (max_confidence_percentage < (COMMAND_CONFIDENCE_THRESHOLD * 100)) {
return;
}
// Ignore background noise classifications
if (detected_word.equals("noise")) {
return;
}
ei_printf("Detected: %s (%.2f%%)\n", detected_word.c_str(), max_confidence_percentage);
// WAKE WORD PROCESSING: Activate listening mode
if (detected_word.equals("marvin")) {
wake_word_detected = true;
wake_word_timestamp = millis();
listening_mode = true;
ei_printf("Wake word detected! Listening for commands for %d seconds...\n", LISTENING_DURATION_MS / 1000);
// Visual feedback: Show wake pattern then enter listening mode
show_wake_word_pattern();
delay(200); // Brief pause between patterns
show_listening_mode();
return;
}
// COMMAND PROCESSING: Execute LED control commands during active listening window
if (wake_word_detected && listening_mode) {
if (detected_word.equals("on")) {
// Execute LED ON command sequence
stop_listening_mode(); // End listening mode
show_turning_on_pattern(); // Visual confirmation
digitalWrite(CONTROL_LED_PIN, HIGH); // Turn on main LED
led_state = true;
ei_printf("LED turned ON\n");
wake_word_detected = false; // Reset wake word state
}
else if (detected_word.equals("off")) {
// Execute LED OFF command sequence
stop_listening_mode(); // End listening mode
show_turning_off_pattern(); // Visual confirmation
digitalWrite(CONTROL_LED_PIN, LOW); // Turn off main LED
led_state = false;
ei_printf("LED turned OFF\n");
wake_word_detected = false; // Reset wake word state
}
}
}
// ===============================================================================
// SYSTEM INITIALIZATION
// ===============================================================================
/**
* @brief Arduino setup function - System initialization
* Configures hardware, displays system information, and starts audio processing
*/
void setup()
{
// -------------------------------------------------------------------------
// Serial Communication Setup
// -------------------------------------------------------------------------
Serial.begin(115200);
while (!Serial); // Wait for USB connection (comment out for standalone operation)
Serial.println("Enhanced Voice Control Demo");
// -------------------------------------------------------------------------
// Hardware Pin Initialization
// -------------------------------------------------------------------------
// Configure control LED (main device being controlled)
pinMode(CONTROL_LED_PIN, OUTPUT);
digitalWrite(CONTROL_LED_PIN, LOW);
led_state = false;
// Configure indicator LED if enabled (status feedback)
if (INDICATOR_LED_PIN != -1) {
pinMode(INDICATOR_LED_PIN, OUTPUT);
digitalWrite(INDICATOR_LED_PIN, LOW);
}
// -------------------------------------------------------------------------
// System Configuration Display
// -------------------------------------------------------------------------
ei_printf("Voice control ready. Say 'marvin' then 'on' or 'off'\n");
ei_printf("Config: Command threshold=%.0f%%, Recognition threshold=%.0f%%\n",
COMMAND_CONFIDENCE_THRESHOLD*100, RECOGNITION_CONFIDENCE_THRESHOLD*100);
ei_printf("LEDs: Control=Pin%d, Indicator=Pin%d\n", CONTROL_LED_PIN, INDICATOR_LED_PIN);
ei_printf("LED Patterns:\n");
ei_printf(" - Wake word 'marvin': 2 quick pulses + listening mode (steady on)\n");
ei_printf(" - Command 'on': Triple flash + confirmation\n");
ei_printf(" - Command 'off': Fade pattern + confirmation\n");
ei_printf(" - Other words: Single blink (when not listening)\n");
// -------------------------------------------------------------------------
// Edge Impulse Model Information Display
// -------------------------------------------------------------------------
ei_printf("Inferencing settings:\n");
ei_printf("\tInterval: ");
ei_printf_float((float)EI_CLASSIFIER_INTERVAL_MS);
ei_printf(" ms.\n");
ei_printf("\tFrame size: %d\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
ei_printf("\tSample length: %d ms.\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT / 16);
ei_printf("\tNo. of classes: %d\n", sizeof(ei_classifier_inferencing_categories) / sizeof(ei_classifier_inferencing_categories[0]));
// -------------------------------------------------------------------------
// Audio System Initialization
// -------------------------------------------------------------------------
ei_printf("\nStarting continuous inference in 2 seconds...\n");
ei_sleep(2000);
// Initialize microphone and audio buffer
if (microphone_inference_start(EI_CLASSIFIER_RAW_SAMPLE_COUNT) == false) {
ei_printf("ERR: Could not allocate audio buffer (size %d), this could be due to the window length of your model\r\n", EI_CLASSIFIER_RAW_SAMPLE_COUNT);
return;
}
ei_printf("Recording...\n");
}
// ===============================================================================
// MAIN PROCESSING LOOP
// ===============================================================================
/**
* @brief Arduino main loop - Continuous voice processing
* Handles real-time audio capture, ML inference, and command processing
*/
void loop()
{
// -------------------------------------------------------------------------
// Audio Capture
// -------------------------------------------------------------------------
bool m = microphone_inference_record();
if (!m) {
ei_printf("ERR: Failed to record audio...\n");
return;
}
// -------------------------------------------------------------------------
// Machine Learning Inference
// -------------------------------------------------------------------------
// Prepare signal structure for Edge Impulse classifier
signal_t signal;
signal.total_length = EI_CLASSIFIER_RAW_SAMPLE_COUNT;
signal.get_data = µphone_audio_signal_get_data;
ei_impulse_result_t result = { 0 };
// Run ML classification on audio data
EI_IMPULSE_ERROR r = run_classifier(&signal, &result, debug_nn);
if (r != EI_IMPULSE_OK) {
ei_printf("ERR: Failed to run classifier (%d)\n", r);
return;
}
// -------------------------------------------------------------------------
// Command Processing and LED Control
// -------------------------------------------------------------------------
handle_wake_word_and_commands(result);
// -------------------------------------------------------------------------
// Debug Information Output
// -------------------------------------------------------------------------
// Display inference timing and classification results
ei_printf("Predictions ");
ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
result.timing.dsp, result.timing.classification, result.timing.anomaly);
ei_printf(": \n");
// Print all classification probabilities
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
ei_printf(" %s: ", result.classification[ix].label);
ei_printf_float(result.classification[ix].value);
ei_printf("\n");
}
// Print anomaly score if available
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf(" anomaly score: ");
ei_printf_float(result.anomaly);
ei_printf("\n");
#endif
}
// ===============================================================================
// AUDIO PROCESSING SUBSYSTEM
// ===============================================================================
/**
* @brief Audio data callback function
* Called when new audio samples are available from I2S
*
* @param n_bytes Number of bytes received
*/
static void audio_inference_callback(uint32_t n_bytes)
{
// Copy samples from I2S buffer to inference buffer
for(int i = 0; i < n_bytes>>1; i++) {
inference.buffer[inference.buf_count++] = sampleBuffer[i];
// Check if buffer is full
if(inference.buf_count >= inference.n_samples) {
inference.buf_count = 0; // Reset buffer position
inference.buf_ready = 1; // Signal buffer ready for processing
}
}
}
/**
* @brief FreeRTOS task for continuous audio sample capture
* Runs on separate core for real-time audio processing
*
* @param arg I2S bytes to read per iteration
*/
static void capture_samples(void* arg) {
const int32_t i2s_bytes_to_read = (uint32_t)arg;
size_t bytes_read = i2s_bytes_to_read;
// Continuous audio capture loop
while (record_status) {
// Read audio data from I2S microphone
i2s_read((i2s_port_t)1, (void*)sampleBuffer, i2s_bytes_to_read, &bytes_read, 100);
// Error handling for I2S read operations
if (bytes_read <= 0) {
ei_printf("Error in I2S read : %d", bytes_read);
}
else {
// Handle partial reads
if (bytes_read < i2s_bytes_to_read) {
ei_printf("Partial I2S read");
}
// Audio signal amplification (scale by 8x for better sensitivity)
for (int x = 0; x < i2s_bytes_to_read/2; x++) {
sampleBuffer[x] = (int16_t)(sampleBuffer[x]) * 8;
}
// Forward samples to inference callback if still recording
if (record_status) {
audio_inference_callback(i2s_bytes_to_read);
}
else {
break;
}
}
}
// Clean up FreeRTOS task when done
vTaskDelete(NULL);
}
// ===============================================================================
// MICROPHONE CONTROL FUNCTIONS
// ===============================================================================
/**
* @brief Initialize microphone inference system
* Allocates buffers and starts audio capture task
*
* @param n_samples Number of audio samples in buffer
* @return true if successful, false if memory allocation failed
*/
static bool microphone_inference_start(uint32_t n_samples)
{
// Allocate memory for audio sample buffer
inference.buffer = (int16_t *)malloc(n_samples * sizeof(int16_t));
if(inference.buffer == NULL) {
return false;
}
// Initialize inference structure
inference.buf_count = 0; // Reset buffer position
inference.n_samples = n_samples; // Set buffer size
inference.buf_ready = 0; // Buffer not ready initially
// Initialize I2S audio interface
if (i2s_init(EI_CLASSIFIER_FREQUENCY)) {
ei_printf("Failed to start I2S!");
}
ei_sleep(100); // Allow I2S to stabilize
record_status = true; // Enable recording
// Create FreeRTOS task for audio capture (high priority task)
xTaskCreate(capture_samples, "CaptureSamples", 1024 * 32, (void*)sample_buffer_size, 10, NULL);
return true;
}
/**
* @brief Wait for microphone inference buffer to be ready
* Blocks until audio buffer contains new samples for processing
*
* @return true when buffer is ready
*/
static bool microphone_inference_record(void)
{
bool ret = true;
// Wait for buffer to be filled by capture task
while (inference.buf_ready == 0) {
delay(10);
}
inference.buf_ready = 0; // Reset ready flag
return ret;
}
/**
* @brief Convert audio samples to float format for ML processing
* Edge Impulse callback function for accessing audio data
*
* @param offset Starting position in buffer
* @param length Number of samples to convert
* @param out_ptr Output buffer for float samples
* @return 0 on success
*/
static int microphone_audio_signal_get_data(size_t offset, size_t length, float *out_ptr)
{
numpy::int16_to_float(&inference.buffer[offset], out_ptr, length);
return 0;
}
/**
* @brief Clean up microphone inference resources
* Stops I2S and frees allocated memory
*/
static void microphone_inference_end(void)
{
i2s_deinit();
ei_free(inference.buffer);
}
// ===============================================================================
// I2S AUDIO INTERFACE CONFIGURATION
// ===============================================================================
/**
* @brief Initialize I2S audio interface for microphone input
* Configures ESP32 I2S peripheral for digital microphone communication
*
* @param sampling_rate Audio sampling frequency (typically 8kHz or 16kHz)
* @return 0 on success, error code on failure
*/
static int i2s_init(uint32_t sampling_rate) {
// I2S Configuration Structure
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX), // Master mode with RX
.sample_rate = sampling_rate, // Set by Edge Impulse model
.bits_per_sample = (i2s_bits_per_sample_t)16, // 16-bit audio samples
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // Mono audio (right channel)
.communication_format = I2S_COMM_FORMAT_I2S, // Standard I2S protocol
.intr_alloc_flags = 0, // Default interrupt allocation
.dma_buf_count = 8, // Number of DMA buffers
.dma_buf_len = 512, // DMA buffer length
.use_apll = false, // Use PLL for clock generation
.tx_desc_auto_clear = false, // Don't auto-clear TX descriptors
.fixed_mclk = -1, // Auto-calculate master clock
};
// I2S Pin Configuration for INMP441 Digital Microphone
i2s_pin_config_t pin_config = {
.bck_io_num = 26, // Bit Clock (SCLK)
.ws_io_num = 25, // Word Select (LRCLK)
.data_out_num = -1, // Data Output (not used for input-only)
.data_in_num = 33, // Data Input (SD pin from microphone)
};
esp_err_t ret = 0;
// Install I2S driver
ret = i2s_driver_install((i2s_port_t)1, &i2s_config, 0, NULL);
if (ret != ESP_OK) {
ei_printf("Error in i2s_driver_install");
}
// Configure I2S pins
ret = i2s_set_pin((i2s_port_t)1, &pin_config);
if (ret != ESP_OK) {
ei_printf("Error in i2s_set_pin");
}
// Clear DMA buffers
ret = i2s_zero_dma_buffer((i2s_port_t)1);
if (ret != ESP_OK) {
ei_printf("Error in initializing dma buffer with 0");
}
return int(ret);
}
/**
* @brief Deinitialize I2S audio interface
* Stops and uninstalls I2S driver
*
* @return 0 on success
*/
static int i2s_deinit(void) {
i2s_driver_uninstall((i2s_port_t)1); // Stop and destroy I2S driver
return 0;
}
// ===============================================================================
// COMPILE-TIME VALIDATION
// ===============================================================================
// Ensure correct sensor type is configured in Edge Impulse model
#if !defined(EI_CLASSIFIER_SENSOR) || EI_CLASSIFIER_SENSOR != EI_CLASSIFIER_SENSOR_MICROPHONE
#error "Invalid model for current sensor."
#endif
// ===============================================================================
// END OF FILE
// ==============================================================================='ESP32' 카테고리의 다른 글
| esp32 sd 카드 spi 인터페이스와 i2s 인터페이스 충돌 (0) | 2026.05.27 |
|---|---|
| ESP-Claw 시작 가이드 1 (0) | 2026.05.27 |
| 실내에서 센티미터 수준의 정밀도로 모든 것을 추적 (0) | 2026.05.26 |
| 저전력 ESP32 회로 제작기 (0) | 2026.05.25 |
| esp32-s3 보드 정보 출력으로 Flash와 PSRAM 크기 알아내자 (0) | 2026.05.21 |
| ESP32 SoftAP 프로비저닝 방법 (0) | 2026.05.21 |
| ESP32-S3 개발 보드 RGB LED 테스트 (0) | 2026.05.21 |
| ESP32 웹 업데이트 OTA(Over The Air) 프로그래밍 (0) | 2026.05.21 |
취업, 창업의 막막함, 외주 관리, 제품 부재!
당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약,
아이디어는 있지만 구현할 기술이 없는 막막함.
우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.
이제 고민을 멈추고, 캐어랩을 만나세요!
코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.
제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!
귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.
캐어랩