일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- automation
- c#
- 크롬드라이버
- 엠블록
- 아두이노
- QA
- testautomation
- mysql4
- arduino
- TP4056
- 업로드모드
- hutc
- CM-50
- 로보티즈
- 테스트자동화
- 코딩봇
- Ollo
- 효용감
- 테스트 자동화
- Dynamixel
- 무명의개발자
- 다이나믹셀
- Robotis
- 아두이노코딩봇
- NocoDB
- chromedriver
- 절벽아래은둔자
- ChatGPT
- 올로
- MBLOCK
- Today
- Total
Hermit Under the Cliff
[Arduino] 아두이노 코딩봇 만들기 (6) - 코딩봇 펌웨어 작성 본문
[Arduino] 아두이노 코딩봇 만들기 (6) - 코딩봇 펌웨어 작성
AnonymousDeveloper 2022. 2. 22. 12:45이제 코딩봇의 펌웨어를 만들 차례입니다.
그러기에 앞서 제품의 이름을 먼저 지어주어야 합니다.
회사에서 사내 테스트 툴을 만들때에는 별별 이상한 이름으로 지어서
사용하는 사람들이 이름을 말할 때 부끄러워 하게 하는 편이었는데,
이 녀석은 따님이 쓸꺼라 평범하게 이름을 붙여줍니다.
간단히 올로와 따님의 이름중 한 글자씩 따서 올빈봇으로 지었습니다.
펌웨어는 블록코딩 프로그램 (Mblock)에서 시리얼을 통해 받는 명령어를
CM-50 모듈에 전달하는 역할 및 각 센서의 값들을 시리얼을 통해 전송해주는 역할을 하게 됩니다.
먼저 CM-50에 전달할 Instruction packet을 만들어 주는 것 부터 시작해 봅니다.
Instuction packet은 지난번 포스트에서 살펴본 바 아래와 같은 구조로 구성이 됩니다.
그냥 간단하게 만들 수 있으면 좋겠지만, CRC 계산하는데 로직이 들어가 주어야 합니다.
CRC 계산하는 방법 역시 Dynamixel Protocol 메뉴얼에 자세히 나와 있긴 하지만
C/C++을 손 놓은지가 10년은 훌쩍 넘어가다 보니 머리가 아파와서 이미 있는 걸 사용하기로 합니다.
이전 포스트에서 살짝 소개한 Dynamixel2Arduino 코드에서 필요한 부분을 가져 옵니다.
https://github.com/ROBOTIS-GIT/Dynamixel2Arduino/blob/master/src/dxl_c/protocol.h
Instruction packet에 대한 구조체가 아래와 같이 선언이 되어 있고
이 구조체의 내용을 채우는 함수들이 아래와 같이 정의되어 있습니다.
이제 펌웨어를 본격적으로 작성합니다.
아래에 나오는 코드들의 완성본은 아래 깃허브에서 확인 가능합니다.
https://github.com/reitn/OlbinBot
GitHub - reitn/OlbinBot: Olbin Coding bot based on Arduino and Mblock
Olbin Coding bot based on Arduino and Mblock. Contribute to reitn/OlbinBot development by creating an account on GitHub.
github.com
protocl.h 를 이용하여 아두이노 스케치 코드에 명령어를 전달해 주는 클래스를 하나 만듭니다.
우선은 헤더파을을 아래와 같이 작성을 하고,
//// Olbin.h
#ifndef _OLBIN_H_
#define _OLBIN_H_
#include "protocol.h"
enum MOTOR_DIRECTION
{
CW,
CCW
};
enum ADDRESS
{
MOTOR1 = 136,
MOTOR2 = 138,
};
class Olbin
{
public:
Olbin(int protocol_version = 2, uint16_t malloc_buf_size = 256);
InfoToMakeDXLPacket_t get_command(uint8_t id, uint16_t addr, const uint8_t *p_data, uint16_t data_length);
InfoToMakeDXLPacket_t command_set_motor_speed(int motorIdx, MOTOR_DIRECTION direction, int32_t speed);
private:
int protocol_version;
bool is_buf_malloced_;
uint8_t *p_packet_buf_;
uint16_t packet_buf_capacity_;
InfoToMakeDXLPacket_t info_tx_packet_;
InfoToParseDXLPacket_t info_rx_packet_;
DXLLibErrorCode_t last_lib_err_;
};
#endif
내용을 아래와 같이 채워 줍니다.
//// olbin.cpp
#include "olbin.h"
Olbin::Olbin(int protocol_version, uint16_t malloc_buf_size)
{
this->protocol_version = protocol_version;
if(malloc_buf_size > 0)
{
p_packet_buf_ = new uint8_t[malloc_buf_size];
if(p_packet_buf_ != nullptr)
{
packet_buf_capacity_ = malloc_buf_size;
is_buf_malloced_ = true;
}
}
info_tx_packet_.is_init = true;
info_rx_packet_.is_init = true;
}
InfoToMakeDXLPacket_t
Olbin::get_command(uint8_t id, uint16_t addr, const uint8_t *p_data, uint16_t data_length)
{
bool ret = false;
DXLLibErrorCode_t err = DXL_LIB_OK;
uint8_t param_len = 0;
param_len = 2;
begin_make_dxl_packet(&info_tx_packet_, id, protocol_version,
DXL_INST_WRITE, 0, p_packet_buf_, packet_buf_capacity_);
add_param_to_dxl_packet(&info_tx_packet_, (uint8_t*)&addr, param_len);
param_len = data_length;
add_param_to_dxl_packet(&info_tx_packet_, (uint8_t*)p_data, param_len);
err = end_make_dxl_packet(&info_tx_packet_);
return info_tx_packet_;
}
InfoToMakeDXLPacket_t
Olbin::command_set_motor_speed(int motorIdx, MOTOR_DIRECTION direction, int32_t speed)
{
if(direction == MOTOR_DIRECTION::CCW)
{
speed+= 1024;
}
uint16_t addr;
if(motorIdx == 1)
{
addr = ADDRESS::MOTOR1;
}
else
{
addr = ADDRESS::MOTOR2;
}
return get_command(200, addr, (uint8_t*)&speed, 2);
}
오랜만에 써보는 C/C++이라서 그런지 여기까지 오는데에도 한참 걸렸습니다. (C# 만세!!!)
get_command 함수는 id(다이나믹셀 부품들의 고유 id), addr(write 할 주소), *p_data(setting 값), data_length를 받아서
Write의 Instruction packet을 retrun 해 줍니다.
command_set_motor_speed의 경우는 motorIdx (1번/2번 모터), direction(CW/CCW), speed를 받아
get_command 함수를 통해 모터를 움직일 수 있는 instruction packet을 받아 옵니다.
여기서 CM-50만을 생각한 관계로 ID 값은 200으로 고정하였습니다.
return type은 InfoToMakeDXLPacket_t라는 구조체인데,
p_packet_buf에 실제 byte가 저장이 되고 generated_packet_length에 instruction packet의 길이가 저장이 됩니다.
이 파일들을 아두이노 폴더 아래 libraries/Olbin_Protocol2.0 폴더아래 위치를 시켜주면
아두이노 IDE에서 해당 파일들을 참조할 수 있습니다.
그럼 이제 아두이노 IDE를 열어 간단한 펌웨어 코드를 만들어 주면 됩니다.
돈 받고 팔아될 코드는 아니어서 대충 돌아가기만 하면 되니 마음 편하게 작성을 합니다.
#include "SoftwareSerial.h"
#include "olbin.h"
SoftwareSerial mySerial(2,3);
int distance =0;
/* Control protocol
@[Target][Length][Data]#
- Target:
M : Motor
L : Led
U : Ultra Sonic
- Data:
Motor :
MF : Move forward
MB : Move backward
TR : Turn Right
TL : Turn Left
ST : Stop
SL : Set speed to slow (200)
SM : Set speed to medium (256)
SH : Set speed to fast (300)
LED :
BN : Blue LED On
BF : Blue LED Off
RN : Red LED On
RF : Red LED Off
Ultra Sonic :
Data None.
Return [distance in cm]
*/
// CM-50 Commands (Control bytes)
int32_t robot_speed;
Olbin olbin;
InfoToMakeDXLPacket_t _CM50_command;
void setup() {
mySerial.begin(57600);
Serial.begin(115200);
while (!Serial) {
; // wait for serial port to connect. Needed for Native USB only
}
// Generate CM-50 Commands
olbin = Olbin();
change_speed(256);
Serial.println("Start");
}
우선, 올빈봇만의 시리얼 프로토콜을 정의하여 명령어를 주고 받을 수 있게 합니다.
위 코드의 주석에서 보듯 간단하게 @로 시작해서 #으로 끝나면 명령어라고 인식을 하게 하고
첫번째 string은 Target으로 [M]otor, [U]ltrasonicSensor, [L]ed 등으로 정의 합니다.
다음 string은 숫자형식으로 다음에 올 data의 length를 적어주고
length만큼의 data를 붙여주는 것으로 간단히 정의 하였습니다.
setup에서는 mySerial(Software Serial)을 57600 bps로 시작하여 CM-50과 통신을 할 수 있게 하고
Serial을 115200 bps로 명령어를 주고 받을 수 있도록 하였습니다.
그리고 command를 생성하기 위한 Olbin 객체를 하나 생성해 주면 됩니다.
void loop() {
// CM-50 Control code
String recv_data=Serial.readStringUntil('#');
if(recv_data.charAt(0) == '@')
{
char target = recv_data.charAt(1);
int data_len = recv_data.charAt(2) - '0';
String data = recv_data.substring(3, 3 + data_len);
switch(target){
case 'M': // Motor control
if(data.equals("MF")) {
moveForward();
}
else if(data.equals("MB")){
moveBackward();
}
else if(data.equals("ST")){
stopMoving();
}
else if(data.equals("TR")) {
turnRight();
}
else if(data.equals("TL")) {
turnLeft();
}
else if(data.equals("SL")) {
change_speed(200);
}
else if(data.equals("SM")) {
change_speed(256);
}
else if(data.equals("SH")) {
change_speed(300);
}
break;
case 'L':
break;
case 'U': // Ultrasonic sensor control
distance = getDistance();
Serial.println(distance);
break;
}
}
}
loop에서는 Serial로 들어온 string을 읽어서 Serial.readStringUntil 을 이용하여 명령어의 마지막 char (#)를 확인하고
위에서 정의된 프로토콜에 따라 명령어를 분석하여, 명령어에 따라 알맞은 함수를 호출해 줍니다.
void write_command(InfoToMakeDXLPacket_t command)
{
mySerial.write(command.p_packet_buf, command.generated_packet_length);
delay(100);
}
/// CM-50 Motor
void moveForward()
{
_CM50_command = olbin.command_set_motor_speed(1, MOTOR_DIRECTION::CCW, robot_speed);
write_command(_CM50_command);
_CM50_command = olbin.command_set_motor_speed(2, MOTOR_DIRECTION::CW, robot_speed);
write_command(_CM50_command);
}
write_command 함수는 InfoToMakeDXLPacket_t 객체를 받아
p_packet_buf의 데이터를 generated_packet_length 만큼 software serial 포트에 write 해 줍니다.
moveForward 함수는 첫 번째 모터는 CCW, 두 번째 모터는 CW 방향으로 회전하게 하여
로봇을 앞으로 이동시키는 역할을 합니다.
위에서 설명한 command_set_motor_speed 함수를 사용하여 packet을 생성하였습니다.
이와 같은 식으로 펌웨어 작성을 마친 뒤, 아두이노 IDE에서 업로드를 하고 테스트를 해볼 수 있습니다.
IDE에서 업로드 후 시리얼 모니터를 열어 명령어를 실행해 보면
원하는 대로 바퀴가 움직이는 것을 확인 할 수 있습니다.
CM-50을 사용하시거나, 아님 다른 Dynamixel 을 사용하시는 분들도 아래 깃허브에 있는 코드들을
마음껏 수정해 가시면서 사용할 수 있습니다.
https://github.com/reitn/OlbinBot
이제 펌웨어 준비까지 마쳤으니,
블록코딩으로 가서 로봇의 동작을 블록코딩 툴에서 제어를 할 수 있게 만들어 주면 됩니다.
'Personal Projects > 아두이노 코딩봇' 카테고리의 다른 글
[Arduino] 아두이노 코딩봇 만들기 (8) - mBlock의 라이브 모드와 업로드 모드 (0) | 2022.02.23 |
---|---|
[Arduino] 아두이노 코딩봇 만들기 (7) - mBlock Device Extension (0) | 2022.02.23 |
[Arduino] 아두이노 코딩봇 만들기 (5) - 아두이노로 CM-50 제어하기 (0) | 2022.02.21 |
[Arduino] 아두이노 코딩봇 만들기 (4) - 로보티즈 올로 CM-50 컨트롤 하기 (0) | 2022.02.20 |
[Arduino] 아두이노 코딩봇 만들기 (3) - 코딩봇 설계 with 로보티즈 올로 (0) | 2022.02.18 |