이 포스팅에서는 군사 기밀이 있을 수 있습니다.

문제의 소지가 있다면 알려주시고, 바로 조치를 취하도록 하겠습니다.

 

추가로 이 포스팅은 저의 경험을 바탕으로 작성하였습니다.

전문연구요원이라도 다른 연대에 배치될 수 있고, 의경 중대에 배치될 수 있습니다.

저와 같은 연대 또는 중대가 아닐 경우 완전히 다른 이야기가 될 수 있는 점 참고해주세요.

 

 

2018/07/02 - [일상/전문연구요원] - 전문연구요원 훈련소 (군사교육소집) 준비물

 

위 포스팅에서 필요한 준비물을 잘 챙겨서 드디어 육군훈련소에 도착합니다.

사람들도 엄청 많고, 교통은 복잡하고, 혼동스럽습니다.

정신을 좀 차리고 아래 위치인 입영심사대로 가야합니다.

 

 

입구 정문에 "호 국 요 람" 이라고 써있습니다.

문제는 교통편인데... 혼자 왔으면 그냥 들어가면 되는데, 부모님이 데려다 주시는 경우에는 차 타고 들어가는게 나을 수 있습니다.

물론 부모님이 나오실 때 고생하시겠지만, 교통 안내는 잘 되는 편입니다.

 

문제는 늦게가면 입영식 행사 장소와 주차장 거리가 멀 수 있으니 1시간 전에 들어가서,

부모님과 시간보내고 친구들과 연락하며 최종 준비를 하면 될 것 같습니다.

 

 

입영식 행사 장소는 인조잔디 축구장입니다?

이제는 우리가 헤어져야 할 시간 ㅠㅠ

 

 

 

총 3제대가 모입니다.

3 제대에 전문연구요원과 의무소방이 있었고, 나머지는 다 의경인가요?

처음이라 그런지 경례가 어색하고 오와열도 정말... 개판입니다 ㅋㅋㅋ

 

황사마스크도 줍니다. 이 마스크를 훈련소에서 며칠동안 오래오래 사용하게 됩니다..;;

답답해요, 쓰다보면 줄도 끊어집니다...

 

경기장 한 바퀴를 돌으며 부모님께 정말 마지막 인사를 올립니다.

이후 신분 확인을 위한 어떤 건물로 들어가게 됩니다.

물론, 들어가는 길에 부모님을 다시 한 번 뵐 수 있습니다.

 

 

이제부터 전문연구요원끼리만 같이 있게 됩니다.

즉, 옆에 있는 전우들은 최소 같은 중대라는 것이죠.

 

신분을 확인하며 줄을 세우게 되는데... 분명 같이 온 친구들이 있을 수 있습니다.

다른 전문연구요원들과 어울리는 것도 중요하지만, 분명 친구와 같은 소대 또는 같은 분대에 배치된다면 큰 도움이 되는 것은 사실입니다.

 

중대에서 소대를 나눌 때는 생년월일로 나누고, 분대를 나눌 때는 키 순으로 나눕니다.

일단 같은 소대에 배치되야겠죠. 이건 노력해서 되는 일이 아닌 것 같습니다.

 

전문연구요원 한 중대에 183명, 3개 소대로 나뉘어집니다.

제가 있었던 소대는 4개 분대였고, 나머지는 5개 분대로 쪼개집니다.

 

먼저, 생년월일이 빠른 순으로 불려나가며 신분확인이 끝난 다음에 키 순으로 줄을 세우거든요.

줄을 차례대로 세운다음에, 같은 오가 같은 분대가 됩니다.

5개 분대로 쪼깨지는 소대라면, 5열로 줄을 세우겠죠.

 

또한, 생년월일이 지나치게 빠르거나 늦으면, 다른 중대로 넘어갈 수도 있습니다.

그 분들이 바로... 의경 중대에 배치되는 전문연구요원입니다.

 

그럼 왜 키 순으로 줄을 세우냐...?

훈련 받으면서 분대가 모여 매주 사진을 찍는데, 그래야 사진이 이쁘게 나오거든요 ㅋㅋㅋ

 

그래서 같은 생활관에 있으려면, 먼저 줄 선 친구가 사인을 보내주고, 다른 친구가 같은 줄에만 서 있으면 됩니다.

바로 뒤에 줄을 섰다면 같은 분대에서도 같이 활동하는 전우조가 될 수 있겠죠.

 

이제 명찰을 받으며, 연대 - 교육대 - 중대 - 소대 - 분대 - **번 훈련병 자기 관등성명이 정해집니다.

휴대폰을 아직 끄지 않았다면, 명찰에 적힌 자기 소속을 친구에게 알려주면 편지를 더 빨리 받을 수 있다고 하는데, 글쎄요... 하루 아님 이틀 차이입니다.

 

 

곧 이어, 정말 먹고 자고 훈련받을 곳으로 이동하게 됩니다.

진짜 오래... 또... 걷습니다.

 

 

지도가 뭔가 어색하죠...?? 티가 너무나 잘 납니다...

입영심사대가 맨 왼쪽 별이고, 육교로 바로 나오는 통로가 있습니다.

그리고 가운데 별이 훈련소 입구입니다. 정문은 아니죠.

 

물론 기수마다 배치되는 중대가 다를 수 있어 마지막 별 위치가 정확하지는 않겠지만,

저 위치가 훈련소에서 가장 멀리 있는 연대이지 싶습니다.. 저기까지 이동하면 약 3 km 정도 됩니다.

이게 무거운 가방을 들고 있으며. 햇빛이 강하고 마스크가 답답하니 정말 힘듭니다.

 

또한, 가운데 별을 지나 훈련소 안에 들어오면 구령에 따라 발을 맞춰 걸어가야 합니다.

목소리는 크게, 발을 최대한 맞춰야 서로 편합니다. 그대로 따라주세요.

 

바로 이런 느낌입니다.. ㅋㅋㅋ

https://youtu.be/Gy6N5kwIR7c?t=2m7s

 

 

걷고 또 걷다보면 중대 건물 앞에 도착하여 앉게 됩니다. 정식 명칭은 중대점호장입니다.

가방 검사를 하게 되는데, 앞에서 다 꺼내라고 말하지만 진짜 양심껏 꺼내도록 합니다.

 

금지 품목 따로 챙겼자나요..?

책과 처방 받아온 의약품은 물론, 꺼낼 수 있는 당당한 물품(레모나 또는 세면도구 등)을 꺼내서 보여줍니다.

혹시나, 금지 품목을 걸려서 제출해야 되는 경우. 넣는 곳이 따로 있는데 넣지 않는 방식으로 넘어가도록 합니다.

 

드디어 자신의 생활관에 들어왔습니다.

이제 진짜 훈련소 생활이 시작이군요.

목, 금, 토를 훈련소 동화기간이라고 부르고, 서류 작성 및 정리하느라 정신이 없는 기간이기도 하죠.

 

다음 포스팅에서 동화기간을 포함한 1주차 이야기를 다루도록 하겠습니다.

안녕하세요.

 

Project Cars 2 포스팅에서 말한대로 의자를 흔드는 모션 시뮬레이터를 만들기 위해,

현재 스텝 모터를 만지고 있고 소프트웨어 준비를 하고 있습니다.

 

2018/05/16 - [DIY/Project Cars 2] - Project Cars 2 SLI 완성!

 

 

무엇보다 차량의 서스펜션 값이 정말 궁금하더라구요.

그래서 그래프를 그려보기로 했습니다. 속도 그래프와 같이요.

그러면 풀 브레이킹을 할 때 앞으로 쏠리는 것이 보일 것이고, 기어 변속 충격도 눈으로 확인 할 수 있겠죠?

 

일단 자주 사용하는 Qt에서 그래프를 그려보도록 합니다.

정말 고맙게도 Qt에서 그래프를 그릴 수 있는 라이브러리는 오픈소스로 공개되어 있습니다.

 

기능이 막강한 만큼 사용법이 참 어려운데요,

라이브러리를 가져다가 메인 그래프 1개와 서브 그래프 4개를 같이 그리는 프로젝트를 올려보겠습니다.

 

이번 기회에 Git을 공부하고 있어요... 파일이 너무 많아서 여기에 올리는 것은 관리도 힘들 것 같고 복잡하겠죠.

제 Git 첫 프로젝트 주소는 https://github.com/LocoField/LocoGraph 입니다!

 

 

Start 버튼을 눌러 스레드를 시작하면 이렇게 그래프가 실시간으로 그려집니다.

마우스 드래그로 그래프 범위를 이동할 수도 있는데, 그래프 새로운 값을 그리는 중에는 자동으로 움직여서 의미 없을 것 같네요.

 

아직 버그가 많이 있을 수 있어요... 또한 데이터 저장을 포함한 다양한 기능이 있으면 좋겠군요.

계속 기능을 추가하여 개선해나가도록 하겠습니다.

 

Project Cars 2 데이터 뽑는 것은 branch를 따야겠네요.

그래프 기능 추가 안하고 프카 먼저 하고 싶은데...ㅋㅋㅋ

 

제 프로젝트를 포함하여 QCustomPlot 라이브러리 질문은 댓글로 달아주세요.

최대한 도와드리도록 하겠습니다!

 

 

참고 웹페이지:

[1] http://www.qcustomplot.com/

 

'코딩 > Qt' 카테고리의 다른 글

Qt Graph 그리기  (0) 2018.07.22
Qt PDF Viewer  (1) 2018.03.15

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

 

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

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

 

 

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

 

가장 큰 문제는 배터리!!

저기에 들어간 배터리는 드론용 배터리거든요. 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 신고

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

참 많은 시간이 흘렀습니다.

블로그에 후기와 팁을 올리겠다고 훈련소에서 매일 일기를 썼는데

막상 나오니 하고 싶은 것도 먹고 싶은 것고 많고 그냥 귀찮네요.

 

차근차근 정리해봅니다. 우선 준비물이겠죠.

저도 구글에 검색하면서 많은 도움이 되었습니다.

하지만 시기가 많이 지난 만큼, 이 글을 보는 여러분께 도움을 드리고자 포스팅하게 되었습니다.

 

저는 2018년 5월 24일에 입소하여, 6월 21일에 수료했습니다.

육군훈련소(논산) 496(18-19)기 입니다.

 

 

네, 이런 우편을 학교나 직장에서 받을겁니다.

이 위에는 통지서가 있고, 4월 5일 소집 통지한다고 합니다.

이유는 잘 모르겠지만, 저는 4월 16일 정도? 입소 약 한 달 전에 받았습니다.

 

아래와 같이 나라사랑카드로 여비도 챙겨줍니다.

저같이 나라사랑카드 계좌 자체가 없어졌다면, 수료 후 병무청에서 따로 연락하여 계좌를 물어보고 익일 입금해줍니다.

 

 

뒷면을 보시면, 소집대상자가 알아야 할 사항이 나와있습니다.

 

1. 소집 통지서와 신분증, 나라사랑카드 지참해야 합니다.

실제 소집 통지서는 필요없었네요.

신분증과 나라사랑카드 둘 중 하나는 꼭 있어야 합니다. 군에서는 나라사랑카드로도 신분 확인이 가능하죠.

둘 다 있으면 좋습니다. 나라사랑카드가 필요할 때가 있을 수 있거든요. 저는 훈련소에서 나라사랑카드를 만들었습니다.

그 말은, 둘 다 챙겨가면 그만큼 시간을 절약하고 더 쉴 수 있습니다.

 

2. 의약품 지참 가능합니다. 아니 꼭 지참해야 하고 그게 편합니다.

상비약 처방약 모두 가능하고 상처에 바르는 약이나 의약품도 포함합니다.

 

3. 네, 부대 주변에서 구입할 품목은 없습니다.

구입할 품목이 없는 것(품질이 매우 좋지 않아요.)이지, 준비할 품목은 많습니다.

 

보급품을 정리해보면,

칫솔과 치약, 면도기, 비누, 두루마리 휴지 2개, 수건 2개, 양말 4쌍, 속옷 3쌍을 줍니다.

바늘은 1인 1개를 빌려주고, 실 뭉치는 생활관마다 줍니다.

속옷은 런닝이 아닌 셔츠와 트렁크 사각 팬티입니다.

 

4. 복장은 간소복. 운동복도 괜찮습니다. 운동화 색깔 상관없습니다. 가방은 필수입니다.

머리는 자르고 오시는게 좋습니다. 대부분 밀고 오며, 훈련소에서 밀면 매우 짧게 밀립니다.

9 mm 정도로 밀고 옆머리는 더 미시는게 좋습니다.


5. 폰 쓸 수 없는 것은 당연하지만, 가지고 와서 맡기면 됩니다.

현금과 카드는 내부에서 사용할 일이 없습니다. 그러나, 필요없는게 아닙니다, PX에서 쓸 수도 있습니다.

나라사랑카드 계좌에 충분한 금액을 넣어오시거나, 현금을 들고 가면 따로 걷어서 계좌에 입금해줍니다.

 

6. 생략.

 

 

이 밑으로는, 소집일자연기 등 안내가 있는데... 역시나 도움이 되지 않습니다.

저는 원래 작년 10월이었으나 한 번 연기했고, 중요 프로젝트 또는 과제에 참여하는 것을 증명하면 연기가 쉽게 가능합니다.

여러 번은 당연히 안되고, 병무청에서 제공하는 서식을 조금 꼼꼼히 작성하면 됩니다.

 

 

나온 안내는 이게 끝인데, 정말 도움이 안되네요.

이메일로도 군사교육소집 안내문이 오는데, 조금 흥미로울 뿐 준비물에 대한 자세한 정보는 부족합니다.

 

그럼 준비물을 제대로 정리해봅시다.

 

 

1. 군인정신

 

우리는 전문연구요원이 되기 위해 수많은 노력과 공부를 했죠. 남들보다 덜 놀고 효율적으로 살아왔을거고요.

이유는 잘 모르겠는데, 군대는 정말 비효율적인 곳입니다.

할 일을 불필요하게 반복적으로 시키고 그 순서와 이유도 이해가 되지 않을 때가 많습니다.

또한 소대별로 전달되는 명령이 달라서 해도 되지 않을 일을 해야하는 경우도 있습니다.

 

불필요한 청소를 반복적으로 시킨다던지, 배식이 급하다며 소대간 배식 집합 간격이 5분이거나...

제가 훈련소에서 제일 불만이 많았던 것은 배식을 포함하여 대기하는 시간이 너무 많았는데 시간을 너무 날리는 것이었습니다.

 

근데, 병영생활 행동 강령에도 나오는 말이지만, 상관의 명령에는 절대 복종하도록 합시다.

불평 불만 토로한다고 해서 결과는 달라지지 않고 본인만 괴롭습니다.

딱 4주만 참아줘요. 다시 가고 싶어도 못가는 곳입니다.

 

 

2. 가방

 

입소 할 때 가져갈 준비물도 정말 많습니다. 우리는 현역이 아니기 때문에 가방을 집으로 보내지 않습니다.

수료 할 때 군복, 전투화를 포함한 지급 받는 보급품을 다 가지고 가야합니다.

 

무조건 큰 가방이 좋습니다!!

수료할 때 가방 무게가 9 kg, 입소 할 때는 더 무거웠습니다.

무거워서 힘들었지만 훈련하면서 부족함이 없었습니다.

 

 

 

제 가방은 스노우보드 시즌 때 사용하는데, 무려 60리터 입니다.

정말 클수록 좋고, 수납공간도 많아야 금지 품목을 숨기기에 아주 좋습니다?

 

물론 수납 공간이 보장된다면 백팩이 훨씬 편합니다. (저는 쇄골이 아파서 백팩은 안됩니다.)

 

추가로, 입 퇴소시에 부모님이 안오신다면(자가용이 없다면) 캐리어를 권장합니다.

부대 내에서 캐리어를 끌어도 되는지는 모르겠는데, 적어도 터미널에서는 편하겠죠.

많은 가방들을 봤는데, 캐리어를 백팩 형태로 맬 수 있는 가방도 있었습니다.

 

가방에 준비물을 담는 방법은 '금지품목' 인 것과 아닌 것을 잘 분류하면 됩니다.

중대에 도착해서 가방에 있는 모든 물품을 밖에 꺼내고 검사를 합니다.

여러 분대장들이 검사를 하는데, 가방 속을 뒤지지는 않습니다.

작은 수납공간에 금지품목 등을 넣고 꺼내지 않으면 됩니다.

 

약들은 오남용을 방지하고자 모두 제출해야 하니, 제출하지 않을 의약품(대표적으로 용각산)은 따로 넣어야 합니다.

책도 일단 모두 제출합니다. 불온서적을 거른다고 하는데? ...

 

 

3. 화장실과 세면장에서 필요한 준비물

 

1) 샤워타올

샤워타올은 거의 필수겠네요. 전우애로 굳게 단결하여 ㅋㅋㅋ 서로 등을 밀어줘도 되지만...

땀을 엄청 많이 흘리기 때문에 등을 매일 시원하게 밀기 위해서는 정말 필요합니다!

 

2) 스킨 및 로션

밖에서도 쓰던거자나요. 금지 품목 아닙니다. 많이 건조하기 때문에 로션도 필요하구요. 립글로스도 필요하려나요?

대신 유리병 제품은 안됩니다. 자해 또는 흉기가 될 수 있기 때문이죠.

 

3) 면도 크림

면도는 막사 내 세면장에서 특정 시간에만 할 수 있는데, 면도 크림이 있다면 상처없이 면도를 부드럽게 할 수 있겠죠.

대놓고 썼는데 제재한 적은 한 번도 없습니다. 같은 생활관 전우와 즐겁게 면도 타임을 가졌네요.

 

4) 샴푸와 바디워시

머리가 짧아서 그런지 먼지가 두피에 많이 쌓이는 것 같고 많이 간지럽더라구요.

시원하게 샴푸로 머리를 감으면 좋겠습니다.

전 바디워시는 안챙겼지만, 금지 품목은 아니니 쓰시던 분이면 가져가면 되겠네요.

 

5) 세면 바구니 (손잡이 있는 것)

세면백을 하나 주긴 하는데, 이게 정말 불편합니다.

세면백에 옷을 넣어가고 별도로 가져가는 바구니에 위 준비물을 담도록 합시다.

 

6) 치실

필요한 채소는 정말 나오지 않고 육류 배식은 거의 매일입니다.

이에 참 잘 끼어요... 치실 필요합니다.

 

 

4. 생활관에서 필요한 준비물

 

1) 시계

몇 분 후 집합이 있을 수도 있는데...

보통은 이런 경우가 많았습니다. "분대장 시계로는 몇 분인데, 몇 분까지 미비된 동작 마무리 합니다."

그리고 불침번 근무를 설 때 시계가 있으면 보는 경우도 있습니다.

절대 훈련소 앞에서 구입하지 않도록 합니다. 방수가 안되거든요 ㅋㅋㅋ

 

2) 비닐 봉투

생활관 내에는 쓰레기통이 없습니다. 하나 있으면 매번 쓰레기 버리려고 막사 끝까지 이동하지 않아도 됩니다. (당연 걸리면 안되죠.)

또한, 퇴소 때 빨래와 여러 물품들을 정리해서 가방에 넣을 때 필요합니다. 10매 정도면 충분합니다.

 

3) 이어플러그

옆 전우가 코를 골 확률이 매우 큽니다. 평소에 조용히 자는 사람도 하루 일과가 매우 힘든 날에는 코를 골기 마련이죠.

사격 전에 이어플러그를 주긴 하지만, 더 일찍 줄 수도 있지만 최소 1개는 꼭 챙겨가도록 합시다.

잃어버리지는 않았는데, 여유분을 챙겨간 저는 사격할 때도 사제 이어플러그를 써서 더 편했습니다.

 

4) 안대

밤에 조그만한 불빛이라도 있으면 잠을 못주무시는 분들에게는 필수템이네요.

복도 불 일부를 소등하긴 하는데, 어느 정도 불빛은 있습니다.

추가로, 오침시간이 주어지는 경우도 있고, 개인 정비 시간에 낮잠을 잘 수도 있어 필요합니다.

 

5) 편지지와 봉투, 우표

일반 편지지에는 군사우편 도장을 찍을 수 없습니다.

우표는 420원 스티커로 된 20매 1장 사시면 됩니다.

꼭 420원 우표로 사가세요.

 

6) 네임펜

생활관(한 분대, 10-15명)에서 나오는 모든 빨래를 여러 개의 망에 넣고 한번에 돌리기 때문에 옷과 양말에 주기를 해야합니다.

낙서 방지를 위해 금지 품목이라고 들었는데, 꼭 필요합니다.

 

7) 수첩과 볼펜

책 형태로 된 병영일기장도 주고 볼펜도 지급합니다.

수첩은 적을 것이 많지는 않으나, 저처럼 일기를 쓰면 재미도 있더라구요. 전화번호도 여기에 적어가면 되겠네요.

볼펜은 부서질 수도 있으니 라이트펜을 포함하여 2개 정도 더 챙겨가도록 합니다.

제가 가져간 수첩은 100 mm * 120 mm 되는 80매 큰 수첩인데도, 전투복 휴대주머니 모든 곳에 다 들어갑니다.

수첩을 2개나 챙겨갔는데, 1개면 충분합니다. 일기를 매일 썼는데도, 반 정도 쓴 것 같아요.

 

8) 물티슈

간단한 청소를 포함한 총, 여러 보급품을 닦을 때 물티슈가 짱이죠.

30매 * 3개를 사갔는데 부족했습니다. 여기에 100매 1개 더 사가면 여유롭게 막 쓸 수 있겠네요.

 

9) 라이트펜

밤에 책을 읽을 일은 없으나, 불침번 근무를 포함하여 불꺼진 생활관에서 필요할 때가 생깁니다.

많이 사용할 일은 없으니 다이소에서 1개 사면 됩니다.

 

10) 책

평소에 저는 책을 보지 않아서 가져갔던 1개 책도 다 읽지 못했습니다. (물론 전공책이어서...)

책은 돌려보면 더욱 재밌으니 1권 정도만 가져가면 좋겠네요.

많이 가져가면 좋겠지만, 무게를 줄여야 합니다.

 

 

5. 훈련 받을 때 필요한 준비물

 

1) 보호대

사격 및 각개전투 때 있으면 좋습니다.

PRI(Preliminary Rifle Instruction, 사격술 예비훈련)는 안했지만, 여름일 때는 전투복 소매를 걷어서 입게 됩니다.

팔꿈치가 노출이 되는데, 사격 시 아프겠죠. 물론 소매를 내리라고 합니다.

무엇보다 있으면 편합니다. 보호대가 있다면 내리지 않아도 되고 푹신해서 더 좋겠죠.

 

각개 전투 때에는 무릎 팔꿈치 모두 필요합니다.

빠른 자세 전환(슬라이딩 및 엎드리기)이나 여러 포복 자세를 할 때 아프지 않습니다.

쓸리는 것도 방지하는 목적도 있지만, 각개 전투 훈련장에 있는 돌이 매우 날카롭습니다.

 

저는 무릎 보호대만 가져갔고, 팔꿈치 보호대는 양말 목 부분을 잘라서 그냥 차기만 했습니다.

쿠션이 필요한 정도는 아닙니다. 다만, 혹시 모르니 대놓고 착용하지 않도록 합니다.

아들에게 각개전투가 뭔지 알려주려면 없이 해야 한다면서 빼라고 합니다...?

 

2) 황사마스크 (KF80 이상)

황사마스크를 주긴 하지만, 계속 재활용하며 쓰게 됩니다. 아니 마스크가 일회용품 아니었나요? 이해는 안갑니다.

전 다이소에서 마스크10개짜리 2개를 사갔지만, 후회됩니다. 비용이 많이 들겠지만 KF 마스크 20개 사가도 될 것 같습니다.

그만큼 먼지가 많아요. 막사에서도 KF 마스크를 쓰면 감기에 더 오래 저항할 수 있을 것 같습니다.

거의 매일 하는 점호를 연병장에서 하는데 가는 길은 무조건 흙먼지 먹습니다.

부대 밖은 더욱 심하구요.

 

3) 선크림과 선스틱 3개

선크림과 선스틱 둘 다 챙기도록 합니다. 선크림은 쓰던 걸 가져갔지만 남았고, 선스틱은 2주도 지나지 않아 다 썼습니다.

선크림은 훈련 나가기 전, 막사에서 바르도록 하구요. 선스틱은 항상 소지하여 훈련 중 쉬는 시간에 팔과 목에 계속 발라줍니다.

계절에 따라 다르겠지만, 선크림은 SPF가 높은 제품(SPF 50), 선스틱은 PA가 높은 제품(PA++++)을 사시면 더 좋을 것 같습니다.

전 많이 챙겨 발랐는데도 까맣게 그을렸어요. 얼굴은 확실히 덜 탔는데, 목 뒤와 팔이 심각하더군요.

 

4) 물집 방지 패드

필요없다고 하는 사람이 있는데, 저는 필요했습니다. 챙기지 못했는데 전우한테 얻었네요.

전투화가 아무리 편해도 1시간 이상 걷다보면 물집은 잡히기 마련입니다.

터지면 정말 아프니깐... 물집 잡힌 뒤라도 붙이면 좋습니다.

 

5) 위장크림

맨 마지막 주에 있는 각개전투 훈련은 총 3개로 구성되어 있습니다. 기초 - 숙달 - 종합.

숙달과 종합 훈련 때 위장을 해야 하는데, 물론 줍니다. 주는데 사제 위장 크림을 꼭 사가세요.

 

보급품은 잘 지워지지도 않고, 사제 크림은 선크림 효과도 있더군요. 심지어 거울도 달려 있으니 물건!

보급되는 위장크림이 많이 좋아졌다고 하는데, 아직 사제품에 비하면 부족한 것 같습니다.

전 안사갔는데, 전우와 같이 사용했습니다. 근데 거울 볼 일이 참 많더라구요.

 

검은색만 사용하여 위장을 하게 되는데,

대부분 이니스프리에서 파는 위장크림을 많이 사오더군요.

쉽게 바르고 싶다면 토니모리에서 사가시면 됩니다.

 

 

6. 건강식품, 의약품

 

취식류는 안됩니다. 초콜렛과 사탕 등이요.

전문연구요원은 책상 앞에서 단 게 필요한 것 잘 알고 있고 소대장님들도 잘 알고 있더군요.

그러나 먹고 싶어도 어쩔 수 없습니다. 그래도 부식은 꽤 잘 나오는 편이고, 심지어 쌓이기도 합니다.

 

근데 포카리 분말은 정말 짱입니다.

사실 전 인터넷에 파는 그대로 다 들고 갔습니다. (5팩 * 4EA)

거래도 하고 전우들에게 나눠주기도 하고 남겨온 건 2팩 뿐입니다.

훈련 때 500 ml 생수를 나눠주기 때문에 1팩으로 2개의 포카리를 만들 수 있죠!

 

건강식품은 숨기지 않아도 됩니다.

특히 레모나요. 레모나는 거의 필수 아이템이 되어버렸습니다. (더이상 화폐가치가 아닙니다.)

하지만 부식으로? 고려은단 비타민 1000도 나오더군요.

그래도 레모나는 맛으로 먹는 것도 있답니다.

 

저는 BCAA 아미노바이탈도 들고 갔는데 미리 숨기지 못하여 제출하게 되었는데(이것도 건강식품 아닌가?), 있으면 좋을 것 같습니다.

체력검정 때 먹고 뛰면 좋은 성적 나올 것 같아요.

물론 저희는 회사나 대학원으로 복귀하기 때문에 성적은 전혀 필요 없죠.

근데 체력검정 다음 날, 근육통이 오더라구요... 3일 절뚝거리며 고생했습니다.

맨날 컴퓨터 앞에만 있었지, 런닝을 이렇게 빡세게 한 적은 없었으니깐요.

근손실 방지 목적으로 하나 먹으면 좋을 것 같습니다.

 

문제는 의약품인데, 잘 준비하도록 합니다.

 

1) 이부프로펜 (아세트아미노펜)

전 아세트아미노펜을 자주 먹어서 챙겨갔는데요,

감기에 심하게 걸려서 체온이 높다면 의무실에 끌려갑니다. 10시 이전에는 연대 응급 환자도 받습니다.

그래서 해열보다는 소염 진통에 더 효능 좋은? 이부프로펜이 더 효율적인 것 같습니다.

 

2) 종합감기약 (유리병 안됩니다.)

감기는 정말 안 걸릴 수가 없어요...

뭔가 몸 낌새가 좋지 않다? 그냥 바로 드세요.

 

3) 용각산

용각산. 저는 처음 들어보는 약이었는데, 정말 물건입니다.

저는 1팩에 7포 들어있는 짝퉁(왼쪽 이미지)을 사갔습니다. 같은 제약사인데 이것보다는 스푼으로 떠먹는게(오른쪽 이미지) 진리입니다.

먼지도 많이 먹고 목이 칼칼한데, 이것만 먹으면 시원하게 내려갑니다. 진해거담제 대용으로도 먹을 수 있습니다.

 

용각산에 대한 이미지 검색결과용각산에 대한 이미지 검색결과

 

4) 의약품

대부분은 생활관 밖 당직 책상 앞에 있긴 합니다만 혹시 모르니 챙겨봅니다.

1회용 소독약(요오드)과 거즈는 있는 것을 직접 확인 했고, 메디폼 같은 상처에 붙이는 밴드, 대일 밴드와 같은 작은 상처에 붙이는 밴드는 가져가세요.

저는 물이 들어가지 않는 큰 방수 밴드도 챙겨갔고, 트러블 났을 때 얼굴에 붙이는 밴드도 가져갔습니다.

 

5) 필요에 따라 준비

개인에 따라 다른 것은 비염과 같은 알러지나 우울증 등의 처방약이구요.

소화제나 변비약도 먹는 편이라면 챙기시면 됩니다.

연대 의무실에서 처방 받을 수 있으므로 굳이 필요 없습니다.

 

 

7. 기타

 

1) 진단서 및 초진기록지

몸이 건강하지 않다면 훈련은 최대한 덜 힘들게 받는게 좋겠죠.

아무래도 나이도 있고 밤새 책상에서 많이 일하다보니 디스크 있는 인원도 꽤 많습니다.

제가 속해있던 중대의 경우에는 진단서를 요구하지 않고 그냥 구두로 조사했지만, 혹시 모르니 꼭 챙겨갑시다.

 

2) 보조 배터리

한 달 동안 폰을 꺼놓아도 자연 방전이 됩니다.

4주 동안 막사에서 폰 보고 싶어 죽겠는데, 퇴소 후에도 못보면 정말 슬프겠죠.

 

3) 왕복교통편 결정

부모님이 오시는 경우가 아니라면 꼭 예매까지 하고 입소하도록 합니다.

그렇지 않으면 아까운 전화 포상?을 사용하여 친구를 통해 예매를 해야합니다.

수료식은 10시 30분부터 진행되며 11시에 끝나며, 아무리 늦어도 12시에는 훈련소 밖을 나가게 되니, 13시 버스는 충분히 탈 수 있습니다.

 

4) 불알 친구 전화번호

그래도 훈련소 밖에 있는 사람과 전화를 해야 힘도 나더라구요. 전화번호를 외우고 있어도 혹시 모르니 꼭 적어갑니다.

전 많이 적어갔는데, 부모님과 여자친구한테 밖에 안했네요 ㅋㅋㅋ

 

5) ...

 

 

8. 있으면 좋은 준비물

 

1) 팬티 1장 (면 100%)

보급품에 팬티가 포함되어 있지만, 보통 목요일에 입소합니다.

그 다음 날인 금요일에 팬티를 받기 전까지 입을 팬티를 준비해가면 찝찝하지 않겠더라구요.

 

2) 비닐장갑

더러운 곳 청소나 총기손질을 아주 깨끗히 해야할 때 있으면 좋습니다.

 

3) 면봉

대부분 가져옵니다. 총기손질 할 때 있으면 좋습니다.

그러나 총을 깨끗히 닦지 않는다고 기능 고장이 안나는 것은 아닙니다.

고장날 총은 뭘 해도 고장납니다. 이미 많이 노후화 된 M16 총이니깐요.

 

4) 탈취제

생활관이 많이 건조하기 때문에 옷에서 냄새가 나지는 않습니다.

근데 방독면 두건이나 판초우의는 모든 기수가 재활용하기 때문에 냄새가 많이 납니다.

네, 당연히 제대로 말리지 않아서죠. 시간날 때 말리긴 하는데 고유의 똥 냄새는 절대 사라지지 않습니다.

 

5) 안경 착용자는 안경닦이와 예비 안경 또는 데일리 소프트 렌즈

안경닦이는 거의 필수죠.

근데 제가 부주의 한 탓인지, 안경 떨어뜨릴 일이 상당히 많더라구요.

저는 렌즈가 빠진 적도 있었습니다. 혹시 모르니 예비 안경을 가져가서 문제가 되는 일이 없도록 합니다.

 

화생방 훈련 때 방독면을 뒤집어 쓰게 되는데, 원래는 저시력자용 내부 안경이 있으나...

보충역인 저희는 그런걸 줄까요? 렌즈가 있으면 그 날만 끼면 좋을 것 같습니다.

한 달용은 세척할 시간과 귀찮음이 있고, 먼지가 많으므로 비추천이 되겠네요.

 

6) 비누 받침 스펀지

감기 예방을 위해 손을 잘 씻도록 합니다. 물론 손소독제도 있지만요.

아무튼 파란색 비누 케이스를 주는데, 비누를 쓰고 세면백이나 바구니에 넣으면 비눗물이 줄줄 흐르겠죠.

있으면 훨씬 편할 것 같습니다.

 

7) 개인 머그컵

식후 물 먹는 쇠컵 있자나요? 같은 걸 하나씩 줍니다. 그리고 관물대에 올려놓고 쓰게 되는데, 그거로는 물만 먹는게 좋겠죠.

분명히 금지 품목이긴 하나, 카누같은 커피를 타 먹을 때 개인 컵이 있으면 정말 좋습니다.

냄새나지 않도록 씻기 쉽고 깨지지 않는 것으로 준비하면 되겠네요.

 

8) ...

 

 

9. 필요없는 준비물

 

1) 깔창

보급받는 전투화는 진짜 A급일뿐만 아니라 깔창을 하나씩 더 줍니다. 필요하다면 깔창을 2개 넣어서 푹신하게 만들 수 있습니다.

 

2) 면도기

면도기와 날은 보급품에 있습니다. 저품질은 아니고 5날이기도 합니다. 전 가져갔는데 필요가 없었습니다.

 

3) ...

 

 

 

이렇게 정리해봤는데, 생각나는대로 추가하도록 할게요.

그럼 가방에 담는 방법을 생각하며 차곡차곡 잘 정리하면 되겠습니다.

 

질문은 언제든지 댓글로 환영합니다.

감사합니다.

  1. rageworx 2018.07.04 11:09 신고

    감동의 쓰나미가 몰려오는 전문연구원의 병영경험생활 잘 보았습니다.
    아무래도 기간병 생활을 하기 위해 입소했던 과거 생활을 고려 해 보면, 이것은 정말 다른 차원의 모습이 아닌가 할 정도로 신기한 이야기들이 많군요 - 나의 기간병 생활이 이러 했으면 아마 장미빛 추억이 되어 있었을 텐데 ... - 그런이유로 앞으로 더 좋아진 병영환경이 되어가면 좋겠군요.
    어딜 갔다 오던간에 건강히 잘 다녀 오는 것이 조국과민족, 가족들을 위한것일 터 - 부디 locofield 님의 글을 보고 많은 다른 전문연구원님들이 건강히 잘 병영생활을 경험하고 돌아 오면 좋겠습니다.

    • 로코필드 2018.07.04 22:57 신고

      군대가 많이 바뀌었다고 다들 그러더라구요, 28일간의 일기도 잘 정리해보겠습니다 :) 좋은 말씀해주셔서 감사해요!

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

OpenCV에 cv::applyColorMap 함수가 있습니다.

함수 사용 방법을 읽어보면, grayscale 뿐만 아니라 색상 영상도 제공되는 칼라맵을 적용하여 색감을 완전히 변화시킬 수 있습니다.

 

기본적으로 제공되는 칼라맵은 아래와 같습니다.

 

Class

Scale

 

COLORMAP_AUTUMN

../../../../_images/colorscale_autumn.jpg

 

COLORMAP_BONE

../../../../_images/colorscale_bone.jpg

 

COLORMAP_COOL

../../../../_images/colorscale_cool.jpg

 

COLORMAP_HOT

../../../../_images/colorscale_hot.jpg

 

COLORMAP_HSV

../../../../_images/colorscale_hsv.jpg

 

COLORMAP_JET

../../../../_images/colorscale_jet.jpg

 

COLORMAP_OCEAN

../../../../_images/colorscale_ocean.jpg

 

COLORMAP_PINK

../../../../_images/colorscale_pink.jpg

 

COLORMAP_RAINBOW

../../../../_images/colorscale_rainbow.jpg

 

COLORMAP_SPRING

../../../../_images/colorscale_spring.jpg

 

COLORMAP_SUMMER

../../../../_images/colorscale_summer.jpg

 

COLORMAP_WINTER

../../../../_images/colorscale_winter.jpg

 

 

저는 색상 영상보다는 grayscale 영상을 주로 다루는데, 이 함수는 비교적 쓰임새가 많습니다.

픽셀값이 0-255 또는 0-65535, 심지어 0.0f-1.0f 범위의 grayscale 영상을 많이 다루는데,

이 함수를 적용하면 어떤 값이든 눈에 쉽게 보이므로 논문 및 프레젠테이션 작성 시 아주 유용합니다.

 

한 가지 예를 들자면, 저는 한 3D 모델에서 레퍼런스 모델과 편차(deviation)를 구하여 그 정밀도(accuracy, not precision)를 보여주고 있습니다.

쉽게, 3D 포인트마다 정밀도 값에 따라 colormap을 입히면 정밀도가 좋지 않은 부분을 한 눈에 찾을 수 있고, 그 형태를 쉽게 알 수 있죠.

 

 

보통 정밀도를 색으로 표현할 때, 정확한 부분을 초록색, 약간 벗어나는 부분을 노란색, -로 벗어나면 파란색, +로 벗어나면 빨간색으로 표시를 합니다.

 

OpenCV에서 제공하는 Jet 클래스를 사용할 수 있지만, 초록색 범위가 너무 적어 결과가 부정적으로 보이더군요.

Rainbow 클래스는 보라색을 포함하고 있어, 바로 가져다 쓸 수도 없네요.

 

게다가, 표현하고자 하는 정밀도 값의 step이 얼마나 될 지 모르니, lookup table 방법으로는 구현이 어렵습니다.

base color 맵을 lookup table로 정의하고 표현하고자 하는 크기에 따라 유동적으로 colormap 만드는 것이 필요합니다.

 

그냥 쉽게 만들어보죠. OpenCV는 오픈소스라구요!

 

 

ColorMap.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
namespace colormap
{
    using namespace cv;
 
    static Mat linspace(float x0, float x1, int n)
    {
        Mat pts(n, 1, CV_32FC1);
        float step = (x1 - x0) / (n - 1);
        forint i = 0; i < n; i++ )
            pts.at<float>(i, 0= x0 + i*step;
        return pts;
    }
 
    //------------------------------------------------------------------------------
    // cv::sortMatrixRowsByIndices
    //------------------------------------------------------------------------------
    static void sortMatrixRowsByIndices(InputArray _src, InputArray _indices, OutputArray _dst)
    {
        if( _indices.getMat().type() != CV_32SC1 )
            CV_Error(Error::StsUnsupportedFormat, "cv::sortRowsByIndices only works on integer indices!");
        Mat src = _src.getMat();
        std::vector<int> indices = _indices.getMat();
        _dst.create(src.rows, src.cols, src.type());
        Mat dst = _dst.getMat();
        for( size_t idx = 0; idx < indices.size(); idx++ ) {
            Mat originalRow = src.row(indices[idx]);
            Mat sortedRow = dst.row((int) idx);
            originalRow.copyTo(sortedRow);
        }
    }
 
    static Mat sortMatrixRowsByIndices(InputArray src, InputArray indices)
    {
        Mat dst;
        sortMatrixRowsByIndices(src, indices, dst);
        return dst;
    }
 
 
    static Mat argsort(InputArray _src, bool ascending = true)
    {
        Mat src = _src.getMat();
        if( src.rows != && src.cols != )
            CV_Error(Error::StsBadArg, "cv::argsort only sorts 1D matrices.");
        int flags = SORT_EVERY_ROW | (ascending ? SORT_ASCENDING : SORT_DESCENDING);
        Mat sorted_indices;
        sortIdx(src.reshape(11), sorted_indices, flags);
        return sorted_indices;
    }
 
    template <typename _Tp> static
        Mat interp1_(InputArray X_, InputArray Y_, InputArray XI_)
    {
        // sort input table
        std::vector<int> sort_indices = argsort(X_);
 
        Mat X = sortMatrixRowsByIndices(X_, sort_indices);
        Mat Y = sortMatrixRowsByIndices(Y_, sort_indices);
        Mat XI = XI_.getMat();
        Mat yi = Mat::zeros(XI.size(), XI.type()); // interpolated values
 
        forint i = 0; i < XI.rows; i++ )
        {
            int low = 0;
            int high = X.rows - 1;
 
            // set bounds
            if( XI.at<_Tp>(i, 0< X.at<_Tp>(low, 0) )
                high = 1;
            if( XI.at<_Tp>(i, 0> X.at<_Tp>(high, 0) )
                low = high - 1;
 
            // binary search
            while( (high - low) > )
            {
                const int c = low + ((high - low) >> 1);
                if( XI.at<_Tp>(i, 0> X.at<_Tp>(c, 0) ) {
                    low = c;
                }
                else {
                    high = c;
                }
            }
 
            // linear interpolation
            yi.at<_Tp>(i, 0+= Y.at<_Tp>(low, 0)
                + (XI.at<_Tp>(i, 0- X.at<_Tp>(low, 0))
                * (Y.at<_Tp>(high, 0- Y.at<_Tp>(low, 0))
                / (X.at<_Tp>(high, 0- X.at<_Tp>(low, 0));
        }
 
        return yi;
    }
 
    class ColorMap
    {
    public:
        virtual ~ColorMap() {}
 
        // Setup base map to interpolate from.
        virtual void init(int n) = 0;
 
        // Interpolates from a base colormap.
        static Mat linear_colormap(InputArray X,
                                   InputArray r, InputArray g, InputArray b,
                                   int n) {
            return linear_colormap(X, r, g, b, linspace(01, n));
        }
 
        // Interpolates from a base colormap.
        static Mat linear_colormap(InputArray X,
                                   InputArray r, InputArray g, InputArray b,
                                   float begin, float end, float n) {
            return linear_colormap(X, r, g, b, linspace(begin, end, cvRound(n)));
        }
 
        // Interpolates from a base colormap.
        static Mat ColorMap::linear_colormap(InputArray X,
                                             InputArray r, InputArray g, InputArray b,
                                             InputArray xi) {
            Mat lut;
            Mat planes[] = {
                    interp1_<float>(X, b, xi),
                    interp1_<float>(X, g, xi),
                    interp1_<float>(X, r, xi) };
            merge(planes, 3, lut);
            return lut; // for OpenGL renderer
        }
    };
 
    class Rainbow2 : public ColorMap
    {
    public:
        Rainbow2(int n) : ColorMap()
        {
            init(n);
        }
 
        void init(int n)
        {
            // breakpoints
            Mat X = linspace(01171);
            
            // define the basemap
            float r[] = { 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000,
            0.02352940.04705880.07058820.09411770.1176470.1411760.1647060.1882350.2117650.2352940.2588240.2823530.3058820.3294120.3529410.3764710.40.4235290.4470590.4705880.4941180.5176470.5411760.5647060.5882350.6117650.6352940.6588240.6823530.7058820.7294120.7529410.7764710.80.8235290.8470590.8705880.8941180.9176470.9411770.9647060.988235,
            111111111111111111111111111111111111111111};
            
            float g[] = { 00.02352940.04705880.07058820.09411770.1176470.1411760.1647060.1882350.2117650.2352940.2588240.2823530.3058820.3294120.3529410.3764710.40.4235290.4470590.4705880.4941180.5176470.5411760.5647060.5882350.6117650.6352940.6588240.6823530.7058820.7294120.7529410.7764710.80.8235290.8470590.8705880.8941180.9176470.9411770.9647060.988235,
            1111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
            0.9882350.9647060.9411770.9176470.8941180.8705880.8470590.8235290.80.7764710.7529410.7294120.7058820.6823530.6588240.6352940.6117650.5882350.5647060.5411760.5176470.4941180.4705880.4470590.4235290.40.3764710.3529410.3294120.3058820.2823530.2588240.2352940.2117650.1882350.1647060.1411760.1176470.09411770.07058820.04705880.0235294};
            
            float b[] = { 1111111111111111111111111111111111111111111,
            0.9882350.9647060.9411770.9176470.8941180.8705880.8470590.8235290.80.7764710.7529410.7294120.7058820.6823530.6588240.6352940.6117650.5882350.5647060.5411760.5176470.4941180.4705880.4470590.4235290.40.3764710.3529410.3294120.3058820.2823530.2588240.2352940.2117650.1882350.1647060.1411760.1176470.09411770.07058820.04705880.0235294,
            0000000000000000000000000000000000000000000000000000000000000000000000000000000000000};
 
            // now build lookup table
            this->image_colorMap = ColorMap::linear_colormap(X,
                                                             Mat(1711, CV_32FC1, r).clone(), // red
                                                             Mat(1711, CV_32FC1, g).clone(), // green
                                                             Mat(1711, CV_32FC1, b).clone(), // blue
                                                             n);
        }
 
    public:
        cv::Mat image_colorMap;
    };
}
cs

 

 

Rainbow2 라는 클래스를 새로 만들고 초기화 함수에 원하는 색상 값을 base color로 정의합니다.

그리고 상속받은 ColorMap 클래스의 메서드를 이용하여 파라미터 n에 따라 lookup table을 만들면 끝입니다.

ColorMap 클래스와 미리 정의한 static 함수는 OpenCV 소스입니다. (필요없는 부분은 제거했습니다.)

 

저는 OpenGL 등의 renderer에서 사용할 목적이니, 127 라인대로 Rainbow2 클래스의 멤버 변수는 float 형식으로 r g b 값을 (CV_32FC3) 가지고 있습니다.

멤버 변수에 직접 인덱스로 엑세스하여 필요한 색상 값을 쉽게 얻어올 수 있겠네요.

 

아래는 400개의 Rainbow 색상을 만든 결과입니다.

 

 

 

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

 

 

참고자료:

[1] https://docs.opencv.org/2.4/modules/contrib/doc/facerec/colormaps.html

 

'코딩 > OpenCV' 카테고리의 다른 글

OpenCV Custom Color Map  (0) 2018.04.17

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

 

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 신고

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

라즈베리파이나 아두이노를 다루면서 숫자 출력할 일은 꼭 있습니다.

그럴 때 엘리베이터에서 쉽게 볼 수 있는 'seven-segment display' 또는 'flexible numeric display (FND)'를 이용합니다.

 

 

이렇게 무식하게 생겼고, 10개 핀이 달렸습니다.

그리고 이 모듈을 여러 개 달아서 대량의 정보를 표시하자니, 배선이 지옥입니다.

그래서 multiplexer를 이용하거나 Multiplexed 4 digit display 모듈을 판매하고 있습니다.

 

하지만, 4자리도 보여주고 싶은 정보를 보여주기에 부족하다면요?

MAX7219 칩이 달린 제품을 이용하시면 됩니다.

 

두 제품을 소개합니다.

1) MAX7219 dot matrix module

2) MAX7219 8-digit 7-segment LED display

 

앞에 MAX7219 이름이 붙는 이유는 이 이름의 칩을 사용한 모듈이라는 것입니다.

LED 형태에 따라 쉽게 제어를 할 수 있도록 만들어놨죠.

중국산(?)이라 그런지 자유롭게 사용가능한 제품 이미지도 구하기 힘들지만, 국내에서도 쉽게 구할 수 있는 좋은 제품입니다.

 

통신 방식은 DATA(MOSI), CS(chip select), CLK(clock) 3핀을 사용하는 SPI입니다.

그리고 MAX7219 제품끼리 여러 개를 물려서 사용할 수 있는 큰 장점이 있습니다.

 

저는 소개한 두 제품을 가지고 Project Cars 2 전용 SLI 제작하려고 합니다.

이번 포스팅에서는 라즈베리 파이에서 라이브러리 없이 C++로 MAX7219 모듈을 제어하는 방법을 소개합니다.

 

SLI가 무엇인지 궁금하다면?

2018/03/20 - [DIY/Project Cars 2] - Raspberry Pi에서 Project Cars 2 UDP 데이터 받기

 

 

 

MAX7219와 라즈베리 파이를 연결

 

라즈베리 파이에서 SPI 통신하는 핀은 정해져있기 때문에 핀맵은 달라질 수가 없겠네요.

WiringPI 기준으로 12 14 10번. 물리적인 핀 번호는 19 23 24번 입니다.

나머지 2개인 5v와 GND만 빠지지 않게 잘 연결해주면 되겠습니다.

 

 

 

MAX7219.h

 

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
// define pins for Raspberry PI
#define DATA        12
#define CLOCK       14
#define LOAD        10
 
// MAX7219 Registers
#define DECODE_MODE   0x09
#define INTENSITY     0x0a
#define SCAN_LIMIT    0x0b
#define SHUTDOWN      0x0c
#define DISPLAY_TEST  0x0f
 
void send_SPI_16bits(unsigned short data)
{
    for (int i = 16; i > 0; i--)
    {
        /* bitmask */
        unsigned short mask = << (i - 1);
 
        /* send data */
        digitalWrite(CLOCK, 0);
        digitalWrite(DATA, (data & mask) ? 0);
        digitalWrite(CLOCK, 1);
    
        /* no receive data */
        //digitalWrite(CLOCK, 0);
    }
}
 
void send_MAX7219(unsigned short reg_number, unsigned short data1, unsigned short data2)
{
    digitalWrite(LOAD, 1);
    send_SPI_16bits((reg_number << 8+ data2);
    send_SPI_16bits((reg_number << 8+ data1);
    digitalWrite(LOAD, 0); // to latch
    digitalWrite(LOAD, 1);
}
 
cs

 

MAX7219를 제어하기 위한 함수 2개가 구현된 헤더 파일입니다.

네, 알고 있어요... 억지로 2개를 제어하겠다는 막코드죠 ㅋㅋㅋㅋ

 

MAX7219를 제어하기 위한 함수가 있구요, 모듈이 2개이니 data1과 data2를 같이 입력받습니다.

언젠가..는 초기화 과정에서 모듈 개수를 입력받아 유연하게 데이터를 입력받는 클래스로 구현해야겠죠?

 

다른 함수는 SPI 프로토콜에 맞게 16비트 데이터를 보내는 함수입니다.

MAX7219에 맞게 구현했으니 당연히 데이터를 받지 않고 보내기만 하고 있습니다. 16번 반복하면서요.

 

그럼 이 함수를 사용하는 예제를 살펴볼까요?

 

 

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
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
 
#include <wiringPi.h>
 
#include "max7219.h"
 
void intHandler(int dummy)
{
    send_MAX7219(SHUTDOWN, 00);
    exit(0);
}
 
int main(int argc, char** argv)
{
    signal(SIGINT, intHandler);
    
    if( wiringPiSetup() < )
    {
        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]);
 
    //intHandler(0);
    return 0;
}
cs

 

25-27 줄에서 SPI 통신에 사용할 3핀을 WiringPI로 초기화해줍니다.

그 다음으로, MAX7219 모듈을 초기화하는 코드가 나옵니다.

 

1) SCAN_LIMIT 명령어로 초기화를 해주는데요, 값을 바꿀 이유는 없을 것 같습니다.

 

2) DECODE_MODE는 제가 작성한 예제에서는 사용하지 않습니다.

디코드 모드를 사용하면 FND 모듈에서 출력 가능한 숫자를 포함한 문자를 그대로 출력할 수 있습니다.

0부터 15까지 데이터를 보내서 7 segment display 답게 0-9 숫자 또는 A B C D E F 문자를 출력할 수 있습니다

라고 알고 있는데, 그럼 .은 어떻게 출력하지?

디코드 모드는 자세히 살펴보지 않아 정확한 내용은 datasheet를 봐야할 것 같네요.

 

디코드 모드를 사용하지 않는다면, 8비트 마스크를 OR 비트 연산한 값인 (비트가 겹치지 않으니 더하면 됩니다.) 0-255 사이의 데이터를 보내야 합니다.

필요에 따라 출력할 숫자의 비트 마스크를 미리 계산하고 테이블로 만들어두고 배열로 출력하면 됩니다. 저처럼 말이죠, 이게 더 편해요.

 

3) INTENSITY는 말그대로 밝기입니다.

15까지 올릴 수 있는데, 너무 밝으니 눈이 아프더라구요.

 

4) SHUTDOWN은 ON OFF 입니다.

define 이름이 이상하게 보이는데, 1을 보내서 키고 0을 보내서 쉽게 끌 수 있습니다.

 

 

34 라인은 FND 모듈의 숫자 테이블입니다. 0-9 숫자에다가 부분적으로 끄기 위해 맨 마지막에 0을 추가했습니다.

. 을 찍고 싶으면 테이블 값에 128을 더하면 됩니다.

 

35 라인은 도트 매트릭스에서 자동차 기어를 표시하기 위해 ---- (Neutral), 1-7, ┌ (Reverse) 를 추가했습니다.

8x8 도트이니 당연히 2차원 배열이죠.

숫자를 예쁘게 출력하기 위해 오른쪽에 치우쳐 출력했고, 왼쪽 빈 공간에는 다른 정보를 출력할 수 있겠군요.

 

48-55 라인은 초기화가 완료되었음을 표시하기 위해

위에서 만들어놓은 테이블을 참조하여 FND 모듈과 도트 매트릭스 모듈에 각각 00000000 과 ---- 를 표시하는 코드입니다.

 

그리고 LED는 켜진 채 종료됩니다.

종료를 시키지 않는 경우에는 57 라인의 주석을 해제하여 메인함수가 종료되기 전 LED를 끌 수 있습니다.

물론, 핸들러 덕분에 Ctrl+C로 종료할 때 역시 LED가 꺼집니다.

 

 

지금까지 두 개의 MAX7219 모듈에 제가 원하는 정보를 출력할 수 있었습니다.

그리고, 아주 간단했습니다. 제 주관적인 생각으로는 라이브러리를 사용하여 이 모듈을 다루면 너무 무거워지는 것 같네요.

물론 테이블을 만들 때 약간의 노가다가 필요하죠.

여러분은 어떤 정보를 출력하시나요? 유용한 테이블은 공유해요~

 

 

참고자료:

[1] https://en.wikipedia.org/wiki/Seven-segment_display

[2] https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus

 

'코딩 > Raspberry PI' 카테고리의 다른 글

Raspberry Pi에서 MAX7219 제어하기  (0) 2018.03.16

Qt는 메타데이터에서만 문제가 생기지 않는다면... 쉽고 빠르게 GUI 소프트웨어를 작성할 수 있는 프레임워크이죠!

소프트웨어 안에서 PDF 문서를 보여주고자 할 때도 쉽게 해결 할 수 있습니다.

 

PDF Viewer를 만들기 위한 클래스 생성 예제를 올려봅니다.

 

Qt로 작성된 poppler 라이브러리를 사용하였고, 이것은 XpdfReader 라이브러리를 기반으로 한다고 합니다.

저는 서브클래스를 만들어 확대/축소, 페이지 이동 기능만 추가하였습니다.

 

라이브러리는 https://poppler.freedesktop.org/ 에서 라이브러리를 다운받을 수 있습니다.

또는 아래에서 Visual Studio 2015 x64로 제가 빌드한 라이브러리를 다운받으실 수 있습니다.

 

poppler.zip

 

 

PDF 핸들링은 라이브러리에서 다 해주기 때문에 서브클래스는 많이 복잡하지 않습니다.

스케일 비율이 커지면 문서가 점점 짤릴텐데, 아직 마우스 드래그로 뷰를 움직이는 기능은 구현하지 않았습니다.

근데 마우스 드래그 처리가 귀찮을 뿐, Page::renderToImage 함수에서 x, y 파라미터에 값만 전달해주면 쉽게 구현할 수 있겠네요.

 

질문은 얼마든지 환영합니다.

그럼 뿅!

 

 

QDocumentWidget.h

 

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
#pragma once
 
#include <QWidget>
#include <QLabel>
#include <QImage>
#include <QWheelEvent>
 
#include <poppler-qt5.h>
 
 
class QDocumentWidget : public QLabel
{
    Q_OBJECT
 
public:
    QDocumentWidget(QWidget* pParent = NULL);
    ~QDocumentWidget();
 
protected:
    Poppler::Document* m_pDocument;
    Poppler::Page* m_pDocPage;
    int m_page;
    int m_scale; // pdf 문서 렌더링 크기. 단위는 %.
 
protected:
    void wheelEvent(QWheelEvent* event);
    void mouseDoubleClickEvent(QMouseEvent* event);
 
public:
    bool loadDocument(std::string path);
    bool setPage(int page);
    void showDocument();
    void closeDocument();
 
};
 
cs

 

 

 

QDocumentWidget.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
#include "QDocumentWidget.h"
 
#include <QGuiApplication>
 
 
QDocumentWidget::QDocumentWidget(QWidget* pParent)
    : m_pDocument(nullptr)
    , m_pDocPage(nullptr)
    , m_page(0)
    , m_scale(150)
{
    setParent(pParent);
 
    setBackgroundRole(QPalette::Dark);
    setAlignment(Qt::AlignCenter);
    setAutoFillBackground(true);
    setScaledContents(false);
    setMouseTracking(true);
}
 
QDocumentWidget::~QDocumentWidget()
{
    closeDocument();
}
 
void QDocumentWidget::wheelEvent(QWheelEvent* event)
{
    Qt::KeyboardModifiers k = QGuiApplication::keyboardModifiers();
    if( k == Qt::ControlModifier )
    {
        if( event->angleDelta().y() < )
            m_scale = max(m_scale - 550);
        else
            m_scale = min(m_scale + 5500);
 
        showDocument();
    }
    else
    {
        if( event->angleDelta().y() > // 위로
            setPage(m_page - 1);
        else
            setPage(m_page + 1);
 
        showDocument();
    }
}
 
void QDocumentWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
    if( event->button() == Qt::LeftButton )
    {
        m_scale = 100;
        showDocument();
    }
}
 
bool QDocumentWidget::loadDocument(std::string path)
{
    if( m_pDocument != nullptr )
        closeDocument();
 
    m_pDocument = Poppler::Document::load(path.c_str());
 
    if( m_pDocument == nullptr )
        return false;
 
    return setPage(m_page);
}
 
bool QDocumentWidget::setPage(int page)
{
    if( page < return false;
 
    if( m_pDocument )
    {
        int nPages = m_pDocument->numPages();
 
        if( page >= nPages )
            return false;
 
        m_page = page;
        m_pDocPage = m_pDocument->page(m_page);
        return m_pDocPage != nullptr;
    }
 
    return false;
}
 
void QDocumentWidget::showDocument()
{
    if!m_pDocPage )
        setPage(m_page);
 
    if( m_pDocPage )
    {
        QImage image = m_pDocPage->renderToImage(m_scale, m_scale);
        setPixmap(QPixmap::fromImage(image));
    }
}
 
void QDocumentWidget::closeDocument()
{
    if( m_pDocument != nullptr )
    {
        delete m_pDocPage;
        m_pDocPage = nullptr;
 
        delete m_pDocument;
        m_pDocument = nullptr;
    }
}
 
cs

 

 

참고 웹페이지:

[1] https://github.com/danigm/poppler

[2] http://www.xpdfreader.com/

 

'코딩 > Qt' 카테고리의 다른 글

Qt Graph 그리기  (0) 2018.07.22
Qt PDF Viewer  (1) 2018.03.15
  1. 2018.09.19 14:06

    비밀댓글입니다

+ Recent posts