[Arduino] 스마트 쓰레기통 제작기(2) - 스피커모듈 및 터치센서 구현
이전 내용은 "스마트 쓰레기통 제작기(1)"에서 확인할 수 있다.
https://jy-develop.tistory.com/2
[Arduino] 스마트 쓰레기통 제작기(1) - 기획 및 물품 구매
2024년 1학기, 대학교 2학년이 되자마자 첫 프로젝트 강의를 듣게 되었다.전공필수 과목이라 들을 수밖에 없었지만, '만드는 건 재밌으니까'라는 막연한 기대감을 안고 1학기를 시작했다. OT부터
jy-develop.tistory.com
구매한 부품이 12주차에 왔다. 그동안 팅커캐드로 작성했던 코드를 직접 실행시켜볼 수 있다는 생각에 다들 들떠있었다. 그렇게 로드셀을 연결하고 난 뒤, 첫 업로드를 진행했다. 결과는 대.실.패.
로드셀을 해보는데 2주가 걸렸다. 자그마치 2주가.. 그동안 시간이 너무 아까웠고 촉박한 일정이라 터치센서와 기울기 센서만 따로 다른 아두이노에서 작동시켜야겠다고 팀원들에게 말한 뒤, 그 작업을 돌입했다.
1. 기울기 센서 & 스피커
쓰레기통이 넘어질 때, 도움을 요청하는 음성을 출력하는 기능을 구현하기 위해 기울기 센서와 스피커모듈을 먼저 작업했다. 아두이노로 음성을 어떻게 출력할 수 있을까가 고민이었고, 자료를 찾기 시작했다.
그때 발견한 것이 바로 "talkie.h" 라이브러리였다.
http://www.iamamaker.kr/ko/tutorials/arduino_voice_out/
Talkie 라이브러리를 사용한 아두이노 음성 출력 – 나는 메이커다! (iamamaker.kr)
아두이노 브릭보드로 음성을 출력할 수 있을까요? 조금만 검색해 보면 MP3쉴드 또는 음성 디코더 디 …
www.iamamaker.kr
그때 당시에도 이 글을 참고해서 talkie 라이브러리를 설치했었다. 설치만 하면 테스트 코드를 실행할 수 있다. 예제에서 제공하는 문장은 "안녕하세요. Talkie 라이브러리를 사용한 음성 출력 예제 입니다."와 같은 문장이다. 하지만, 필요한건 "Help"나 "도와주세요"와 같은 문장. 그렇게 다시 한번 찾아낸 것이 바로 이 글이었다.
https://blog.naver.com/soohan530/221281818870
아두이노 음성출력하기(Talkie) - 음성 데이터 만들기
개요 본 강의는 I am a maker 사이트를 참고하여 만들었습니다. Talkie 라이브러리 사용을 위한 세팅은 ...
blog.naver.com
나를 구제해주신 수많은 블로그... 정말 너무 감사했다. 하지만, TTS를 딸 수 있는 사이트가 404 오류가 뜨는 바람에 다시 찾아야 했다. 요즘에는 많은 TTS 서비스가 있기 때문에 어떤 것을 사용해도 되지만, 로그인 없이 할 수 있는 사이트를 발견해 해당 사이트에서 음성 추출을 하였다.
https://ttsmaker.com/ko#google_vignette
여기서 각각 "도와주세요"와 "Help" 음성을 다운받아 wav 변환 및 FreeMat 프로그램으로 음성을 수치화해 넣을 수 있었다. 진짜 수치가 너무 길어서 잘라내는 것도 힘들었다. 두 음성 모두 테스트 해봤는데 한국어 음성은 너무 뭉게져서 더 짧은 "Help"가 채택되었다.
이렇게 Help 음성을 출력하는 스피커 모듈 작업을 끝냈다. 이제 기울기 센서랑 연결해서 음성 출력이 될 수 있게 코드를 바꿔주면 된다. 이때, 기울기 센서는 Ball switch라고 불리는 센서로 센서 안에 있는 구슬(Ball)이 움직이면서 스위치가 열리고 닫힌다. 이 센서는 스위치가 열렸는지 닫혔는지 2가지로만 기울었는지 안기울었는지를 판단하는 센서라 정확도나 기울기 정도를 설정할 수는 없다.
넘어짐만을 감지하면 되는 간단한 센싱이라 이 기울기 센서와 스피커모듈을 함께 사용해 기울어짐 감지 시, 음성을 출력할 수 있는 코드를 작성했다.
//speaker_fin.ino
#include "talkie.h"
// C++ code
//S -> digital pin : 3
//+(중앙핀) -> 5V 연결
//- : gnd(아날로그 핀)
int ball_switch = 4;
int speaker = 3;
Talkie voice;
/* 도와주세요 */
const uint8_t korean_help[] PROGMEM = {0x00, 0x00, 0xa8, 0x0f, 0xd6, 0x51, 0x53, 0x9c, 0xb4, 0x92, 0x3b, 0x25, 0x0b, 0x89, 0x1b, 0x0c, 0x60, 0x2f, 0x7c, 0x4d, 0x6a, 0x31, 0x41, 0xfb, 0xe6, 0x64, 0x6a, 0xcd, 0x02, 0x6b, 0x53, 0x5b, 0xa8, 0x0c, 0x03, 0x8c, 0xf1, 0x28, 0xa6, 0xb3, 0x34, 0xd0, 0xc2, 0xbb, 0xd0, 0xd1, 0x91, 0x41, 0x71, 0xef, 0xc4, 0x5a, 0x47, 0x80, 0xd5, 0xbd, 0x15, 0x4b, 0x1f, 0x0e, 0xe4, 0xd0, 0x76, 0xaa, 0xbd, 0x58, 0xa0, 0xda, 0xb3, 0xd4, 0xee, 0x51, 0x96, 0x60, 0x06, 0x52, 0x67, 0x87, 0xeb, 0x54, 0x04, 0x54, 0xd3, 0x1e, 0x16, 0xc8, 0x08, 0x6b, 0x77, 0x7a, 0x18, 0xa0, 0xcb, 0x7d, 0x34, 0xd9, 0xe1, 0x18, 0xb6, 0x8c, 0x8e, 0x38, 0x47, 0xde, 0x56, 0x19, 0x59, 0x62, 0x27, 0xe0, 0x94, 0xf2, 0x00, 0x4c, 0x2d, 0xba, 0x14, 0x20, 0x42, 0xad, 0xcc, 0xd9, 0xd0, 0x80, 0x0f, 0xc9, 0xa2, 0x55, 0xcd, 0x01, 0xbe, 0x39, 0x93, 0xe6, 0x16, 0x0f, 0xb8, 0xe1, 0x48, 0x9e, 0x9d, 0x02, 0x10, 0x86, 0x3d, 0x78, 0x6a, 0x0a, 0x41, 0x0b, 0x0b, 0xf6, 0x70, 0x29, 0x04, 0xdb, 0x92, 0xc5, 0xe5, 0xa6, 0x00, 0x5c, 0x2b, 0x16, 0x95, 0x1b, 0x3c, 0xf0, 0x34, 0x89, 0x45, 0x6e, 0x70, 0xc1, 0xd3, 0x22, 0x52, 0xaa, 0xc1, 0x01, 0xcf, 0x52, 0x80, 0xcd, 0x19, 0x17, 0xdc, 0x72, 0x17, 0x08, 0x27, 0x7c, 0xb0, 0x3b, 0xd3, 0xd0, 0x2a, 0x0a, 0x41, 0x9e, 0x30, 0x21, 0xc9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xff};
/* Help */
const uint8_t english_help[] PROGMEM = {0x00, 0x00, 0x00, 0x04, 0x88, 0x45, 0xbc, 0xc9, 0x3e, 0x89, 0x87, 0xb8, 0x94, 0x25, 0xc2, 0x6e, 0xc9, 0x52, 0x96, 0x97, 0x88, 0x9f, 0x04, 0x5b, 0x5a, 0x19, 0x12, 0x7a, 0x5c, 0x9a, 0x26, 0xa5, 0xab, 0xc9, 0x06, 0x4f, 0x9b, 0x47, 0x21, 0x3b, 0x02, 0x7c, 0x1d, 0x19, 0x01, 0x2d, 0x08, 0xf0, 0x25, 0x5d, 0x95, 0x34, 0xd2, 0x41, 0x59, 0x4f, 0x55, 0x89, 0x60, 0x48, 0xeb, 0x38, 0xcc, 0xcc, 0x71, 0xc0, 0xb8, 0x93, 0xf4, 0xd0, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff};
void setup() {
Serial.begin(9600);
pinMode(ball_switch, INPUT_PULLUP);
}
void loop() {
int val = digitalRead(ball_switch);
Serial.println(val);
if(val == 1){
voice.say(english_help);
delay(500);
}
}
2. 터치센서 & 서보모터
터치센서로 서보모터를 제어하는 것은 스피커를 해결한 나에겐 그다지 어려운 문제가 아니었다. 그냥 터치 인식이 되면, 서보모터를 제어하면 되는 구조였다. 원래는 더 강력한 서보모터를 사용하려 했으나 라이브러리가 문제였는지 서보모터 자체의 문제였는지 제대로 작동하지 않아 파란색 마이크로 서보모터로 제어했다.
//servo_cover.ino
#include <Servo.h>
#define touchSensorPin 7
Servo myServo;
void setup() {
// put your setup code here, to run once:
myServo.attach(12);
pinMode(touchSensorPin, INPUT);
Serial.begin(9600);
}
void loop() {
myServo.write(0);
// 터치센서의 입력 값을 읽어옴
int touchValue = digitalRead(touchSensorPin);
// 터치센서를 터치하면
Serial.print(touchValue);
if (touchValue == HIGH) {
myServo.write(90);
// 모터를 정지시킴
digitalWrite(12, LOW);
delay(3000);
}
else{
myServo.write(0);
delay(500);
// 모터를 정지시킴
digitalWrite(12, LOW);
}
}
3. 두 기능 통합시키기
원래는 다 다른 아두이노에 연결시켜서 각각의 기능들을 모듈화 시키려고 했으나 작은 쓰레기통 안에 배터리와 dc모터, 모터드라이버 등 각종 부품을 넣을 수 있을까 싶었다. 그래서 하나의 아두이노에는 스피커 모듈과 터치 센서를, 다른 하나의 아두이노에는 주행에 관련된 기능들을 구현하기로 했고, 통합 코드를 구현하였다.
//open_and_speaker.ino
#include <Servo.h>
#include "talkie.h"
#define touchSensorPin 7
Servo myServo;
// C++ code
//S -> digital pin : 3
//+(중앙핀) -> 5V 연결
int ball_switch = 4;
int speaker = 3;
Talkie voice;
/* 도와주세요 */
const uint8_t korean_help[] PROGMEM = {0x00, 0x00, 0xa8, 0x0f, 0xd6, 0x51, 0x53, 0x9c, 0xb4, 0x92, 0x3b, 0x25, 0x0b, 0x89, 0x1b, 0x0c, 0x60, 0x2f, 0x7c, 0x4d, 0x6a, 0x31, 0x41, 0xfb, 0xe6, 0x64, 0x6a, 0xcd, 0x02, 0x6b, 0x53, 0x5b, 0xa8, 0x0c, 0x03, 0x8c, 0xf1, 0x28, 0xa6, 0xb3, 0x34, 0xd0, 0xc2, 0xbb, 0xd0, 0xd1, 0x91, 0x41, 0x71, 0xef, 0xc4, 0x5a, 0x47, 0x80, 0xd5, 0xbd, 0x15, 0x4b, 0x1f, 0x0e, 0xe4, 0xd0, 0x76, 0xaa, 0xbd, 0x58, 0xa0, 0xda, 0xb3, 0xd4, 0xee, 0x51, 0x96, 0x60, 0x06, 0x52, 0x67, 0x87, 0xeb, 0x54, 0x04, 0x54, 0xd3, 0x1e, 0x16, 0xc8, 0x08, 0x6b, 0x77, 0x7a, 0x18, 0xa0, 0xcb, 0x7d, 0x34, 0xd9, 0xe1, 0x18, 0xb6, 0x8c, 0x8e, 0x38, 0x47, 0xde, 0x56, 0x19, 0x59, 0x62, 0x27, 0xe0, 0x94, 0xf2, 0x00, 0x4c, 0x2d, 0xba, 0x14, 0x20, 0x42, 0xad, 0xcc, 0xd9, 0xd0, 0x80, 0x0f, 0xc9, 0xa2, 0x55, 0xcd, 0x01, 0xbe, 0x39, 0x93, 0xe6, 0x16, 0x0f, 0xb8, 0xe1, 0x48, 0x9e, 0x9d, 0x02, 0x10, 0x86, 0x3d, 0x78, 0x6a, 0x0a, 0x41, 0x0b, 0x0b, 0xf6, 0x70, 0x29, 0x04, 0xdb, 0x92, 0xc5, 0xe5, 0xa6, 0x00, 0x5c, 0x2b, 0x16, 0x95, 0x1b, 0x3c, 0xf0, 0x34, 0x89, 0x45, 0x6e, 0x70, 0xc1, 0xd3, 0x22, 0x52, 0xaa, 0xc1, 0x01, 0xcf, 0x52, 0x80, 0xcd, 0x19, 0x17, 0xdc, 0x72, 0x17, 0x08, 0x27, 0x7c, 0xb0, 0x3b, 0xd3, 0xd0, 0x2a, 0x0a, 0x41, 0x9e, 0x30, 0x21, 0xc9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xff};
/* Help */
const uint8_t english_help[] PROGMEM = {0x00, 0x00, 0x00, 0x04, 0x88, 0x45, 0xbc, 0xc9, 0x3e, 0x89, 0x87, 0xb8, 0x94, 0x25, 0xc2, 0x6e, 0xc9, 0x52, 0x96, 0x97, 0x88, 0x9f, 0x04, 0x5b, 0x5a, 0x19, 0x12, 0x7a, 0x5c, 0x9a, 0x26, 0xa5, 0xab, 0xc9, 0x06, 0x4f, 0x9b, 0x47, 0x21, 0x3b, 0x02, 0x7c, 0x1d, 0x19, 0x01, 0x2d, 0x08, 0xf0, 0x25, 0x5d, 0x95, 0x34, 0xd2, 0x41, 0x59, 0x4f, 0x55, 0x89, 0x60, 0x48, 0xeb, 0x38, 0xcc, 0xcc, 0x71, 0xc0, 0xb8, 0x93, 0xf4, 0xd0, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff};
void setup() {
myServo.attach(12);
pinMode(touchSensorPin, INPUT);
pinMode(ball_switch, INPUT_PULLUP);
}
void loop() {
myServo.write(0);
// 터치센서의 입력 값을 읽어옴
int touchValue = digitalRead(touchSensorPin);
int val = digitalRead(ball_switch);
if (touchValue == HIGH) {
myServo.write(90);
// 모터를 정지시킴
digitalWrite(12, LOW);
delay(3000);
}
else{
myServo.write(0);
delay(500);
// 모터를 정지시킴
digitalWrite(12, LOW);
}
if(val == 0)
for(int i = 0 ; i<3; i++){
voice.say(english_help);
delay(500);
}
}
이때까지만 해도 주행기능이 어느정도 된다는 팀장님의 말에 아 진짜 마지막으로 모여서 선정리랑 실제 쓰레기통 안에 붙이면서 테스트 및 발표 준비만 하면 되겠다고 생각했다. 현실은 녹록치 않았지만... 마지막 조립 및 결과는 다음 글에서 마저 작성하겠다.