짐벌 조립을 완료하여 테스트 영상을 찍어봤습니다.

 

물론 조립 완료한지 몇 달이 지났지만, 계속 생각하고 테스트해봐도 개선점이 크게 없었습니다.

설계 미스인거죠... 다시 제작해보려고 합니다 ㅠㅠ

 

 

그럼 무엇이 문제냐? 디자인은 괜찮은데, 역시 케이블이 복잡하네요.

 

가장 큰 문제는 배터리!!

저기에 들어간 배터리는 드론용 배터리거든요. 1000 mAh 이면서 방전율이 10C.

 

짐벌 모터는 고속으로 회전할 필요가 없고 전류를 크게 소모하지 않습니다.

이 배터리보다 18650 3개를 묶어 만든 배터리를 쓰는 것이 크기와 그립면에서도 더 좋겠더라구요.

 

케이블도 두껍다보니, 케이블 장력이 yaw 모터의 안정화에 영향을 주는 것 같습니다.

yaw 축 동작을 제거하니 위 영상처럼 잘 작동했습니다.

 

 

아날로그 조이스틱 모듈도 제대로 작동하네요.

얼른 안정화를 마치고 돌아오는 스노우보드 시즌 때는 꼭 써봐야겠습니다.

 

추가 계획으로는, 핸드폰에서 고프로 영상을 받아다가 저 또는 다른 인물을 인식하여 tracking 하려고 합니다.

모터 회전은 블루투스로 짐벌 보드에 명령을 날리면 되거든요!

그러면 파크에 삼각대 세워놓고 트릭 장면을 모두 담을 수 있겠죠? 설렌다.. >.<

 

'DIY > Gimbal' 카테고리의 다른 글

Gimbal 테스트  (1) 2018.07.13
Gimbal 직접 만들기  (0) 2018.05.16
  1. rageworx 2018.10.03 14:03 신고

    그래서 다음 개선은 언제 ... ???

Gimbal, 짐벌은 원래 그 뜻이 있습니다.

한 물체가 여러 축으로 움직일 수 있도록 하는 그 장치라 할까요?

 

이 때, 한 물체가 카메라가 된다면?

카메라는 렌즈가 바라보는 방향을 유지할 수 있게 됩니다.

그럼 사람이 카메라를 들고 뛰던지 방향을 바꿔도 흔들림 없는 영상을 얻을 수 있습니다.

 

원래 명칭은 camera stabilizer system 정도? 라고 할 수 있는데, 보통은 짐벌이라고 부릅니다.

 

운동을 좋아하는 저는 고프로를 자주 쓰니 고프로에 맞는 짐벌 하나 있으면 정말 좋겠다라는 생각이 들었죠.

아 그런데 너무 비싸네요. 특히 안정적인 영상과 휴대성이 좋은 짐벌은 말이죠.

 

그래서 제 특기이자 흥미로, 하나 만들어보기로 합니다.

 

처음엔 참 삽질 많이 했습니다.

브러시리스 모터에 대한 지식도 없었으니 말이죠.

 

개발에 착수한 지, 몇 달만에 드디어 완성했네요.

물론 안정화가 부족하여 한 축은 어디 도망가버렸습니다. ㅋㅋㅋ

 

 

 

아, 개발비가 시중에서 파는 저렴한 짐벌 값보다 한참 더 많이 든 것은 비밀이 아닙니다...^^

그래도 전 많은 것을 배웠죠.

 

현재 yaw와 pitch 축을 돌릴 수 있고, 조이스틱으로도 마음대로 회전시킬 수 있습니다.

배터리 커버인 동시에 손잡이 밑 부분에는 삼각대에 고정시킬 수 있도록 마운트도 붙어있습니다!

 

 

한 축이 빠졌더라도, 짐벌 역할을 수행할 수 있는 것을 확인했으니 이제 제대로 된 3축 짐벌을 만들어보려고 합니다.

물론 만드는 과정과 저만의 짐벌이 업데이트 되는 내용도 포스팅하겠습니다.

 

짐벌 리비전 모델을 디자인 해봤는데, 정말 제가 생각해도 잘 만든 것 같아요 ㅋㅋㅋㅋ

 

 

사용하는 부품은 아래와 같습니다.

 

보드: BaseCam SimpleBGC 32-bit Tiny Rev. B [2]

모터: Turnigy HD 2212 BLDC motor [3]

모터: Turnigy HD 3508 BLDC motor

프레임: 3D Printing (PLA)

조이스틱: Analog joystick module with arduino

 

참고 웹페이지:

[1] https://en.wikipedia.org/wiki/Gimbal

[2] https://www.basecamelectronics.com/

[3] https://hobbyking.com/en_us/turnigy-hd-2212-brushless-gimbal-motor-bldc.html

'DIY > Gimbal' 카테고리의 다른 글

Gimbal 테스트  (1) 2018.07.13
Gimbal 직접 만들기  (0) 2018.05.16

 

 

고프로로 촬영하여 그런지, 왜곡이 상당하네요 ^^;

 

랩 타임이 나오도록 8-digit 모듈을 하나 더 얹으려고 했는데, 다른 재미난 것을 만드느라 정신이 없군요.

재미난 것을 만들다가 한계가 오면 다시 업데이트 하도록 하겠습니다.

 

아! SLI 다음 프로젝트로는 VR 오락실에 가면 만날 수 있는 모션 체어를 만드려고 해요.

리얼리티한 레이싱 게임을 위해 모터를 사용하여 차량의 서스펜션 상태에 따라 의자를 흔드는거죠.

과연 잘 완성할 수 있을지 모르겠네요.

 

 

SLI를 만들기 위해서 MAX7219 제어 방법이 궁금하다면?

2018/03/16 - [코딩/Raspberry PI] - Raspberry Pi에서 MAX7219 제어하기

 

 

동영상에서 동작한 SLI 메인 코드를 올려드려요!

 

main.cpp

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
 
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
 
#include <wiringPi.h>
 
#include "max7219.h"
#include "SMS_UDP_Definitions.hpp"
 
int sockfd_udp_cli;
 
void intHandler(int dummy)
{
    close(sockfd_udp_cli);
    send_MAX7219(SHUTDOWN, 00);
    exit(0);
}
 
int main(int argc, char** argv)
{
    signal(SIGINT, intHandler);
    
    if( wiringPiSetup() < )
    {
        return 1;
    }
    
    puts("\nLocoField SLI\n");
 
    char udp_recvbuff[SMS_UDP_MAX_PACKETSIZE];
    PacketBase packetHeader;
    memset(&packetHeader, 0sizeof(packetHeader));
 
    sockfd_udp_cli = socket(AF_INET, SOCK_DGRAM, 0);
    if( sockfd_udp_cli < )
    {
        puts("ERROR: create socket.\n");
        return 1;
    }
    
    //int flags = fcntl(sockfd_udp_cli, F_GETFL, 0);
    //fcntl(sockfd_udp_cli, F_SETFL, flags | O_NONBLOCK);
    
    struct timeval timeout;      
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    setsockopt(sockfd_udp_cli, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
 
    
    sockaddr_in receiver;
    receiver.sin_family = AF_INET;
    receiver.sin_addr.s_addr = INADDR_ANY;
    receiver.sin_port = htons(SMS_UDP_PORT);
 
    int retval = bind(sockfd_udp_cli, (sockaddr*&receiver, sizeof(receiver));
    if( retval < )
    {
        puts("ERROR: bind socket.\n");
        return 1;
    }
    
    
    pinMode(DATA, OUTPUT);
    pinMode(CLOCK, OUTPUT);
    pinMode(LOAD, OUTPUT);
 
    send_MAX7219(SCAN_LIMIT, 77);
    send_MAX7219(DECODE_MODE, 00);
    send_MAX7219(INTENSITY, 88);
    send_MAX7219(SHUTDOWN, 11);
    
    int digit_table[11= { 12648109121519195114127123};
    int dot_table[9][9=
    { // 128 64 32 16 8 4 2 1
    { 000015000}, // N
    { 01111111}, // 1
    { 01511158815}, // 2
    { 01511151115}, // 3
    { 099915111}, // 4
    { 01588151115}, // 5
    { 01588159915}, // 6
    { 015999111}, // 7
    { 000015888}, // R
    };
    
    send_MAX7219(1, digit_table[0], dot_table[0][1]);
    send_MAX7219(2, digit_table[0], dot_table[0][2]);
    send_MAX7219(3, digit_table[0], dot_table[0][3]);
    send_MAX7219(4, digit_table[0], dot_table[0][4]);
    send_MAX7219(5, digit_table[0], dot_table[0][5]);
    send_MAX7219(6, digit_table[0], dot_table[0][6]);
    send_MAX7219(7, digit_table[0], dot_table[0][7]);
    send_MAX7219(8, digit_table[0], dot_table[0][8]);
    
    
    sTelemetryData data1;
    memset(&data1, 0sizeof(data1));
    
    while)
    {
        retval = recvfrom(sockfd_udp_cli, (char*)&udp_recvbuff, sizeof(udp_recvbuff), 0, NULL, 0);    
        if( retval > )
        {
            memcpy(&packetHeader, udp_recvbuff, sizeof(PacketBase));
            switch( packetHeader.mPacketType )
            {
            case eCarPhysics:
            {
                memcpy(&data1, udp_recvbuff, sizeof(data1));
                
                // speed
                int speed = data1.sSpeed / 1000 * 3600;
                int s3 = speed / 100; speed -= s3 * 100;
                int s2 = speed / 10; speed -= s2 * 10;
                int s1 = speed;
                
                // rpm
                int rpm = data1.sRpm;
                int r5 = 10;
                int r4 = rpm / 1000; rpm -= r4 * 1000;
                int r3 = rpm / 100; rpm -= r3 * 100;
                int r2 = rpm / 10; rpm -= r2 * 10;
                int r1 = rpm;
                
                if( r4 >= 10 )
                {
                    r4 -= 10;
                    r5 = 1;
                }
                
                int gear = data1.sGearNumGears;
                int maxGear = gear >> 4;
                int curGear = gear & 15;
                
                if( curGear == 15 // reverse gear;
                    curGear = 8;
                
                float rpm_per = (float) data1.sRpm / data1.sMaxRpm - 0.8;
                int rpmL = 0;
                
                if( rpm_per > )
                    rpmL = rpm_per /= 0.025;
                
                int led[9=
                {
                    0,
                    digit_table[r1],
                    digit_table[r2],
                    digit_table[r3],
                    digit_table[r4],
                    digit_table[r5],
                    digit_table[s1] | 128,
                    digit_table[s2],
                    digit_table[s3],
                };
                
                forint i = 8; i > 0; i-- )
                {
                    int dot = dot_table[curGear][i];
                    
                    if( rpmL != )
                    {
                        dot |= 128;
                        rpmL--;
                    }
                    
                    send_MAX7219(i, led[i], dot);
                }
 
                usleep(1);
            }
            default:
                continue;
            }
        }
    }
 
    intHandler(0);
    return 0;
}
 
cs

 

 

Project Cars 2에서 제공하는 헤더 파일과 위 소스를 포함하는 프로젝트를 압축하여 업로드합니다.

자유롭게 사용하세요!

 

질문도 언제든지 환영입니다.

 

sli.zip

 

'DIY > Project Cars 2' 카테고리의 다른 글

Project Cars 2 SLI 완성!  (0) 2018.05.16
Raspberry Pi에서 Project Cars 2 UDP 데이터 받기  (2) 2018.03.20

하나 만들어보고 싶은게 생겼습니다.

 

Shift Lighting Indicator의 줄임말인 SLI는 게임 속 차량 정보와 랩 타임 등의 여러 데이터를 가독성있게 출력해주는 일종의 계기판입니다.

SLI가 있으면 게임 속 렌더링 되는 화면 일부를 없애서 시야를 넓히고 레이싱에 더 몰입할 수도 있죠.

특히, 헬멧 시점으로 레이싱 할 때 가려지지 않아서 좋고, 더욱 현실감을 더해줍니다.

 

 

기본 시야에서는 이렇게 보이지가 않아요........ 물론 VR을 사서 밑을 쳐다보면 보이겠죠?^^

아 SLI가 기본 장착된 스티어링 휠도 있습니다!!

 

 

맙소사... 가격이....... 너무합니다 ㅠㅠ 모션기어 사의 SLI 모듈도 13만원이 넘습니다 ㅠㅠ

그래서 직접 만들어보기로 합니다.

우선은 Project Cars 2 게임에서 제공하는 데이터를 받아서 모듈로 전송이 필요하겠죠?

 

 

 

이렇게 게임 옵션에 들어가면 어떤 방식을 사용할지 설정할 수 있습니다.

 

1. 직접 개발한 PC 소프트웨어에서 공유 메모리를 열어 데이터 가공하고 모듈로 다시 전송하는 방법

2. 모듈에서 직접 데이터를 받아 출력하는 방법

 

이 두 가지 방법에서 크게 달라지지 않습니다.

2번 같은 경우는 데이터를 직접 처리해므로 컴퓨팅이 필요하겠지요.

 

하지만 라즈베리 파이는 정말 물건입니다. 이 쪼그만한게 별 걸 다하지요.

그래서 라즈베리에서 UDP 데이터를 직접 받고 직접 LED를 컨트롤하도록 개발하고자 합니다.

 

지금 필요한 준비물은.

라즈베리 파이와 이더넷을 micro usb로 바꿔주는 어댑터 입니다.

 

이더넷을 usb로 바꿔주는 제품을 참 많을텐데, 라즈베리에서 사용하기 가장 적절한 제품은 USB 허브와 이더넷이 합쳐진 게 아닐까요?

 

 

https://www.adafruit.com/product/2992

 

 

이 제품으로 PC와 라즈베리를 같은 네트워크에 묶어주면 UDP 패킷을 받을 수 있습니다.

와이어샤크로 패킷을 찍어본 결과, 브로드캐스트(255.255.255.255) 패킷이기 때문에 네트워크로 연결된 모든 장비에서 데이터를 받을 수 있습니다.

 

 

 

그러면 이제 Project Cars 2 데이터를 라즈베리에서 받을 수 있도록 코딩해봅시다!

공식 홈페이지에서 UDP 패킷 구조가 담겨있는 헤더 파일과 패킷을 받을 수 있는 Windows console 응용프로그램 샘플소스를 제공하지만,

그 샘플을 gcc 등의 Windows가 아닌 환경에서 컴파일하려고 했다가는 엄청난 에러를 만나겠죠 :)

라즈베리파이에서 돌아가는 소스를 올려드립니다.

 

main.cpp

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
 
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
 
#include <wiringPi.h>
 
#include "SMS_UDP_Definitions.hpp"
 
int sockfd_udp_cli;
 
void intHandler(int dummy)
{
    close(sockfd_udp_cli);
    exit(0);
}
 
int main(int argc, char** argv)
{
    signal(SIGINT, intHandler);
    
    if( wiringPiSetup() < )
    {
        return 1;
    }
    
    puts("\nLocoField SLI\n");
 
    char udp_recvbuff[SMS_UDP_MAX_PACKETSIZE];
    PacketBase packetHeader;
    memset(&packetHeader, 0sizeof(packetHeader));
 
    sockfd_udp_cli = socket(AF_INET, SOCK_DGRAM, 0);
    if( sockfd_udp_cli < )
    {
        puts("ERROR: create socket.\n");
        return 1;
    }
    
    //int flags = fcntl(sockfd_udp_cli, F_GETFL, 0);
    //fcntl(sockfd_udp_cli, F_SETFL, flags | O_NONBLOCK);
    
    struct timeval timeout;      
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    setsockopt(sockfd_udp_cli, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
 
    
    sockaddr_in receiver;
    receiver.sin_family = AF_INET;
    receiver.sin_addr.s_addr = INADDR_ANY;
    receiver.sin_port = htons(SMS_UDP_PORT);
 
    int retval = bind(sockfd_udp_cli, (sockaddr*&receiver, sizeof(receiver));
    if( retval < )
    {
        puts("ERROR: bind socket.\n");
        return 1;
    }
    
    
    sTelemetryData data1;
    memset(&data1, 0sizeof(data1));
    
    while)
    {
        retval = recvfrom(sockfd_udp_cli, (char*)&udp_recvbuff, sizeof(udp_recvbuff), 0, NULL, 0);    
        if( retval > )
        {
            memcpy(&packetHeader, udp_recvbuff, sizeof(PacketBase));
            switch( packetHeader.mPacketType )
            {
            case eCarPhysics:
            {
                memcpy(&data1, udp_recvbuff, sizeof(data1));
            }
            default:
                continue;
            }
        }
    }
 
    intHandler(0);
    return 0;
}
 
cs

 

프로젝트의 종속성은 WiringPi 뿐이네요.

사실 이 프로젝트에서는 전혀 필요가 없어요. 라즈베리에서 GPIO 다룰 일이 많기 때문에 습관적으로 넣는 코드인거죠.

 

코드 자체는 정말 간단합니다.

 

signal 함수로 Ctrl+C로 종료할 때 소켓을 닫구요. 나중에 LED를 사용하게 되면 LED 끄는 코드를 넣으면 됩니다.

 

패킷은 UDP이므로 소켓은 데이터그램으로 만들고, 타임아웃을 설정합니다.

사실 논블록 소켓을 만들고 싶었는데 잘 되지는 않았네요...

 

그리고 recvfrom 함수로 데이터를 받았을 때 Project Cars 2에서 제공하는 구조체에 여러 값들을 복사해줍니다.

제가 소켓에 타임아웃을 설정했으니 받을 데이터가 없으면(게임에서 레이싱 상태가 아니라면)

조건문에서 계속 검사하며 while 문에서 계속 돌고 있겠지요.

 

라즈베리에서 바로 사용하실 수 있게 프로젝트 파일(CMakeLists.txt, MakeFile)도 올려드려요.

질문은 언제든지 환영입니다!

 

project.zip

 

 

참고 웹페이지:

[1] https://www.projectcarsgame.com/project-cars-2-api/

 

'DIY > Project Cars 2' 카테고리의 다른 글

Project Cars 2 SLI 완성!  (0) 2018.05.16
Raspberry Pi에서 Project Cars 2 UDP 데이터 받기  (2) 2018.03.20
  1. Parkseokhyeon 2018.10.24 20:28 신고

    프로젝트 카스2에서 라즈베리 파이로 속도와 RPM 변수를 받아와서 아날로그 계기판을 움직여

    속도 계기판을 만들고 싶습니다.

    가능할까요? 가능하다면 준비해야하는것은 무었일까요?

    • 로코필드 2018.10.26 16:18 신고

      결국엔 아날로그 계기판을 컨트롤 할 수 있어야겠죠? 차에 들어가는 계기판을 떼서 쓸 수 있을지는 모르겠네요.
      제가 당장 만들어야 된다면 스텝 모터를 이용해서 직접 계기판을 만들고 컨트롤하면 되겠네요.

+ Recent posts