아두이노 동작 스케치 = 영점 조정 값설정, 주기적 색 감지, RGB 갑스올 바꾸고 시리얼 통신 통해 PC 전달
PC 동작 프로세싱 IDE 스케치 = 전달받은 데이터에서 3개 정수 값 분리해 실행 창 배경색으로 설정
아두이노 스케치
/*
TCS230 Color Sensing
색 감지 & PC 전달 & Processing 스케치 배경색 예제
Calibrate 스케치로 얻어낸 영점 조정 데이터 사용 = 먼저 Calibrate 스케치 실행
*/
#include <MD_TCS230.h>
#include <FreqCount.h>
// 연결된 핀 번호들: S0_OUT <-> 8, S1_OUT <-> 9
#define S2_OUT 12
#define S3_OUT 11
#define LED 13 // HIGH = ENABLED
// TCS230 객체인 CS 사용 핀 선언
MD_TCS230 CS(S2_OUT, S3_OUT, /* S0_OUT, S1_OUT, */ LED);
// Calibrate 스케치 실행 얻은 영점 조정 데이터.
// TCS3200 컬러 감지 센서 1 게시물에서 나온 본인 결과값 적어야함.
sensorData sdBlack = { 40, 50, 50 };
sensorData sdWhite = { 2640, 1370, 1980 };
// 전역 변수들:
colorData rgb;
void setup()
{
// 시리얼 포트 초기화
Serial.begin(115200);
// 컬러 센서 초기화 begin()
// 영점 조정 위한 검정색, 흰색 값 설정
CS.begin();
CS.setDarkCal(&sdBlack);
CS.setWhiteCal(&sdWhite);
}
void loop()
{
// 색 감지, RGB 값 읽기
CS.read();
// 읽을 데이터 준비 때까지 기다림
while(!CS.available()) ;
// RGB 데이터 값 읽기
CS.getRGB(&rgb);
// 쉼표 분리된 값 출력
Serial.print(rgb.value[TCS230_RGB_R]);
Serial.print(",");
Serial.print(rgb.value[TCS230_RGB_G]);
Serial.print(",");
Serial.println(rgb.value[TCS230_RGB_B]);
delay(100);
}
프로세싱 스케치
import processing.serial.*;
int redValue = 0; // Red 컬러 값
int greenValue = 0; // Green 컬러 값
int blueValue = 0; // Blue 컬러 값
Serial myPort;
void setup() {
size(200, 200);
// 사용 가능 모든 시리얼 포트 출력
println(Serial.list());
// PC 두번째 시리얼 포트가 아두이노와 연결된 시리얼 포트라 Serial.list()[2] 사용
// 다른 포트를 사용 시 그 포트 숫자 작성
myPort = new Serial(this, Serial.list()[6], 115200);
// '\n' 받을 때까지 serialEvent() 발생 X
myPort.bufferUntil('\n');
}
void draw() {
// 아두이노 RGB 데이터 배경색 설정
background(redValue, greenValue, blueValue);
}
void serialEvent(Serial myPort) {
// 시리얼 포트 ~ 새 줄문자 = inString 문자열 받음
String inString = myPort.readStringUntil('\n');
if (inString != null) {
// 문자열 있다면 앞 뒤 공백 문자 삭제
inString = trim(inString);
// 쉼표 분리 integer 값 만듦
int[] colors = int(split(inString, ","));
// 3개의 값 받으면 RGB 모든 값 받아 들인 것
// 각 redValue, greenValue, blueValue 넣고 콘솔 출력
if (colors.length >=3) {
redValue = colors[0];
greenValue = colors[1];
blueValue = colors[2];
System.out.println(String.format("RGB(%d, %d, %d)", redValue, greenValue, blueValue));
}
}
}
int LED_T = 10; // 위 LED
int LED_B = 9; // 아래 LED
int CLK = 2; // CLK
int DT = 3; // DT
int SW = 4; // SW
static int oldCLK = LOW; // CLK 값 저장 변수
static int oldDT = LOW; // DT 값 저장 변수
int brightness = 143; // LED 밝기 값(=중간(0~255))
int location = 0; // 켜진 LED 위치 저장 변수 (0(=위) 초기화)
void setup() {
pinMode(CLK, INPUT_PULLUP); // 내장 풀업 저항 사용하는 입력
pinMode(DT, INPUT_PULLUP);
pinMode(SW, INPUT_PULLUP);
pinMode(LED_T, OUTPUT);
pinMode(LED_B, OUTPUT);
}
void loop() {
if (digitalRead(SW) == LOW) { // 노브 눌렸을 때
changeLED(); // changeLED 함수 호출, LED 켜기
location++; // LED 위치 바꾸기
delay(300); // 중복 입력 방지
}
else { // 노브 돌렸을 때
brightness += (getDirection()*5); // 밝기 조절
if(brightness < 0) // LED 밝기 최솟값(0)이하 떨어지는 것 방지
brightness = 0;
if(brightness > 255) // LED 밝기 최댓값(255)이상 올라가는 것 방지
brightness = 255;
changeLED(); // 변화한 밝기값 적용
}
}
//방향 반환 함수
int getDirection() {
int direct = 0; // 방향 초기화
int newCLK = digitalRead(CLK); // 현재 CLK 값 저장 변수
int newDT = digitalRead(DT); // 현재 DT 값 저장 변수
if (newCLK != oldCLK) { // CLK 값 변한 경우
if (oldCLK == LOW) { // LOW에서 HIGH 경우
direct = oldDT * 2 - 1; // direct 값 변경
}
}
oldCLK = newCLK; // 갱신
oldDT = newDT;
return direct; // 시계방향 = -1, 반시계방향 = +1 반환
}
// LED 위치 조절 함수
void changeLED() {
if(location%2 == 1) { // 변수를 2로 나눈 나머지 = 1
analogWrite(LED_T, brightness); // 위 LED만 ON
analogWrite(LED_B, LOW);
}
else { // 나머지 = 0
analogWrite(LED_T, LOW);
analogWrite(LED_B, brightness); // 아래 LED만 ON
}
}
int main(int argc, char **argv) #Receive constant at program startup
// 프로그램 시작 시 변수 받기 위한 것
{
ros::init(argc, argv, "talker");
// 노드 이름 talker
ros::NodeHandle n;
// n이라는 핸들러
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
// cahtter_pub(Publisher name) = string형태 ("topic name", Buffer num);
// publisher name & string 변경
ros::Rate loop_rate(10);
// 1초에 10번 수행
int count = 0;
while (ros::ok()) #ok = ctrl C 들어올 때 까지 반복
{
chatter_pub.publish(msg); //msg publish
ros::spinOnce(); //spinOnce = 다음에 나오는 call back 함수 1번만 실행
loop_rate.sleep(); //1초 10번 제어 = sleep
++count;
}
return 0;
}
실행
1. talker 변경
2. rosrun my_first_pkg talker_node & listener_node 실행
3. rqt graph 실행
talker -> talker1 바뀐 것 확인
4. chatter -> chatter1
5. rosrun talker & listener 재실행
6. listener = 받지 않음
why? = chatter1으로 해서 연결 불가
Answer = listener.cpp에 가서 똑같이 chatter->chatter1으로 바꿔줘야함
7. 다시 make -> rosrun listener 재실행
연결 완료
8. rate 바꿈 (1초 2번)
9. talker_node 터미널 부분 재실행
rate10일 때보다 속도 느리게 나옴
listener.cpp
//string형 topic 받으면 실행되는 함수
void chatterCallback(const std_msgs::String::ConstPtr& msg)
//std_mags String 받음, 메시지 포인트 받아서 표시
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
//받은 topic 출력
}
ros::Subscriber sub = n.subscribe("chatter1", 1000, chatterCallback);
//subscribe 함수 등록, chatter topic 입력 = cahtterCallback 실행 ("topic name", buffer, Callback함수)
ros::spin();
//spin = 계속 호출
return 0;
$ source /opt/ros/melodic/setup.sh
$ mkdir -p ~/test_catkin_ws/src
// -p = 하위 폴더까지 다 만듦
$ cd ~/test_catkin_ws/src
// cd = 이동
$ catkin_init_workspace
// scr 폴더에 CMakeLists.txt 파일 생성
//간단한 메시지 전송 시연
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
int main(int argc, char **argv)
{
ros::init(argc, argv, "talker");
//The ros::init() = Command line에 제공된 모든 ROS 인수 & 이름 재매핑 수행 위해 argc & argv 확인.
//프로그래밍 방식 재매핑 = init() 다른 버전도 가능하나 argc & argv 전달=가장 쉬운 방법
//init() 3번째 인수 = 노드 이름
//ROS 시스템 다른 부분 사용 전 ros::init()버전 中 하나 호출 필요
ros::NodeHandle n;
//NodeHandle = ROS 통신 기본 액세스 지점
//첫 번째 NodeHandle = 이 노드를 초기화, 마지막 NodeHandle 파괴 시 노드 종료
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
//advertise()=해당 토픽으로 publish(송신) 가능 객체 ros::Publisher 클래스 반환, 객체 publish() 이용=원하는 메시지 발행
//ros 마스터 노드 호출, 게시/구독 중 사용자 레지스트리 유지
//호출 후 마스터 노드 = 구독 사용자에게 알림 →
//반환된 게시자 개체 모든 복사본 삭제 → 자동 알림 해제
//advertise() 2번 파라미터 = 메시지 게시 사용, 메시지 대기열 크기
//게시 속도 >> 가능 전송 속도 = 메시지 버리긴 전, 버퍼링 메시지 수 지정
ros::Rate loop_rate(10); //루프 10Hz 실행 설정
//전송 메시지 개수, 고유 문자열 제작 시 사용
int count = 0;
while (ros::ok())
{
//메시지 개체. 데이터 채운 후 게시
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
ROS_INFO("%s", msg.data.c_str());
chatter_pub.publish(msg);
//publish() = 메시지 보내는 방법
//parameter = 메시지 개체
//이 개체 유형 = advertise<>() 호출 템플릿 매개 변수 유형과 일치 필요
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
→ ctrl S → ampt/test_catkin_ws/src/my_first_pkg/src → 이름 설정 (talker.cpp)
int arge = main() 전달 데이터 개수
char* argv[] = main() 전달 실제 데이터, char형 포인터 배열 구성. 첫 문자열=프로그램 실행경로
하나 더 생성 ( 이번엔 2.1 복붙)
#include "ros/ros.h"
#include "std_msgs/String.h"
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "listener");
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
//subscribe()=주어진 topic에 대한 메시지 받고 싶을 때 ros에게 말하는 방법
//ros 마스터 노드 호출, 게시/구독 사용자 레지스트리 유지
//메시지 → chatterCallback
//subscribe() = 구독 취소 까지 보류해야 하는 구독자 개체 반환
//구독자 개체 모든 복사본 범위 이탈 시 콜백 자동 구독 취소
//2번째 매개 변수 = 메시지 Q 크기
//처리 속도<<도착 속도 = 가장 오래된 메시지 버리기 전에 버퍼링되는 메시지 수
ros::spin();
return 0;
}
같은 위치 listener.cpp 생성
컴파일 위해 생성한 CMakeLists.txt 파일 수정
주석 # 풀어주기 (매번 패키지 만들때마다 해야함)
줄 152~ (Install & Testing) 삭제
줄 136 복사
add_executable(${PROJECT_NAME}_node src/my_first_pkg_node.cpp)
//빌드 대상 바이너리 추가
줄 128 붙여넣기 & 이름변경
add_executable(talker_node src/talker.cpp)
줄 135 복사
add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
//Target간 의존성 정의
//Top-level Target간 의존성 지장
//Top-levl Target = ADD_~ 명령으로 정의한 Target 의미