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

 

랩 타임이 나오도록 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