import sys
from math import floor
from random import randint
import pygame
from pygame.locals import QUIT, MOUSEBUTTONDOWN

WIDTH = 20
HEIGHT = 15
SIZE = 50
NUM_OF_BOMBS = 20
EMPTY = 0
BOMB = 1
OPENED = 2
OPENED_COUNT = 0
CHECKED = [
    [
        0 for _ in range(WIDTH)
    ]
    for _ in range(HEIGHT)
]

pygame.init()
SURFACE = pygame.display.set_mode(([WIDTH * SIZE, HEIGHT * SIZE]))
FPSCLOCK = pygame.time.Clock()

def num_of_bomb(field, x_pos, y_pos):
    cnt = 0
    for y_off in range(-1, 2):
        for x_off in range(-1, 2):
            xpos, ypos = (x_pos + x_off, y_pos + y_off)
            if 0 <= xpos < WIDTH and 0 <= ypos < HEIGHT and\
                field[ypos][xpos] == BOMB:
                cnt +=1
    return cnt

def open_tile(field, x_pos, y_pos):
    global OPENED_COUNT
    if CHECKED[y_pos][x_pos]:
        return
    CHECKED[y_pos][x_pos] = True

    for y_off in range(-1, 2):
        for x_off in range(-1, 2):
            xpos, ypos = (x_pos + x_off, y_pos + y_off)
            if 0 <= xpos < WIDTH and 0 <= ypos < HEIGHT and\
                field[ypos][xpos] == EMPTY:
                field[ypos][xpos] = OPENED
                OPENED_COUNT += 1
                cnt = num_of_bomb(field, xpos, ypos)
                if cnt == 0 and not (xpos == x_pos and ypos == y_pos):
                    open_tile(field, xpos, ypos)

def main():
    small_font = pygame.font.SysFont(None, 36)
    large_font = pygame.font.SysFont(None, 72)
    msg_clear = large_font.render("!!CLEARED!!", True, (0, 255, 255))
    msg_over = large_font.render("GAME OVER!!", True, (0, 255, 255))
    msg_rect = msg_clear.get_rect()
    msg_rect.center = (WIDTH*SIZE/2, HEIGHT*SIZE/2)
    game_over = False

    field = [
        [
            EMPTY for x_pos in range(WIDTH)
        ]
        for y_pos in range(HEIGHT)
        ]
    cnt = 0
    while cnt < NUM_OF_BOMBS:
        x_pos, y_pos = randint(0, WIDTH-1), randint(0, HEIGHT - 1)
        if field[y_pos][x_pos] == EMPTY:
            field[y_pos][x_pos] = BOMB
            cnt +=1
    
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            if event.type == MOUSEBUTTONDOWN and event.button == 1:
                x_pos, y_pos = floor(event.pos[0] / SIZE),\
                            floor(event.pos[1] / SIZE)
                if field[y_pos][x_pos] == BOMB:
                    game_over = True
                else:
                    open_tile(field, x_pos, y_pos)
        

        SURFACE.fill((0, 0, 0))
        for y_pos in range(HEIGHT):
            for x_pos in range(WIDTH):
                tile = field[y_pos][x_pos]
                rect = (x_pos * SIZE, y_pos * SIZE, SIZE, SIZE)
                if tile == EMPTY or tile == BOMB:
                    pygame.draw.rect(SURFACE, (192, 192, 192), rect)
                    if game_over and tile == BOMB:
                        pygame.draw.ellipse(SURFACE, (225, 225, 0), rect)
                elif tile == OPENED:
                    cnt = num_of_bomb(field, x_pos, y_pos)
                    if cnt > 0:
                        num_img = small_font.render(
                            "{}".format(cnt), True, (255, 255, 0)
                        )
                        SURFACE.blit(num_img, (x_pos * SIZE + 10, y_pos * SIZE + 10))

        for idx in range(0, WIDTH*SIZE, SIZE):
            pygame.draw.line(SURFACE, (96, 96, 96), (idx, 0), (idx, HEIGHT * SIZE))
        for idx in range(0, HEIGHT*SIZE, SIZE):
            pygame.draw.line(SURFACE, (96, 96, 96), (0, idx), (WIDTH*SIZE, idx))
        
        if OPENED_COUNT == WIDTH*HEIGHT - NUM_OF_BOMBS:
            SURFACE.blit(msg_clear, msg_rect.topleft)
        elif game_over:
            SURFACE.blit(msg_over, msg_rect.topleft)
        
        pygame.display.update()
        FPSCLOCK.tick(15)
    
if __name__ == "__main__":
    main()

계속 손에 안잡히고 시작 못해서

간단하게 파이게임 진행해보려고함.

 

 

 

 

파이게임 단순 화면띄우기 예제

import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
pygame.display.set_caption("just window")

def main():
    while True:
        SURFACE.fill((255, 255, 255))

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
        
        pygame.display.update()

if __name__ == "__main__":
    main()

 

 

이벤트

- 이벤트 큐에 저장

- 큐 맨앞에 이벤트 꺼내 처리

 

 

 

간단한 글자 띄우기

import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))

def main():
    sysfont = pygame.font.SysFont(None, 36)
    counter = 0
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
        counter += 1
        SURFACE.fill((0, 0, 0))
        count_image = sysfont.render("count is {}".format(counter), True, (255, 255, 255))
        SURFACE.blit(count_image, (50, 50))
        pygame.display.update()

if __name__ == "__main__":
    main()

 

 

fps 설정 1

- pygame.time.Clock() tick(10) 1초에 10번 루프

 

import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


def main():
    sysfont = pygame.font.SysFont(None, 36)
    counter = 0
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
        counter += 1
        SURFACE.fill((0, 0, 0))
        count_image = sysfont.render("count is {}".format(counter), True, (255, 255, 255))
        SURFACE.blit(count_image, (50, 50))
        pygame.display.update()

        FPSCLOCK.tick(10)

if __name__ == "__main__":
    main()

 

 

 

상자 그리기

 

import sys
import pygame
from pygame.locals import QUIT, Rect

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


def main():
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit
        SURFACE.fill((255, 255, 255))

        pygame.draw.rect(SURFACE, (255, 0, 0), (10, 20, 100, 50))
        pygame.draw.rect(SURFACE, (255, 0, 0), (150, 10, 100, 30), 3)
        pygame.draw.rect(SURFACE, (0, 255, 0), ((100, 80), (80, 50)))

        rect0 = Rect((200, 60), (140, 80))
        pygame.draw.rect(SURFACE, (0, 0, 255), rect0)
        rect1 = Rect((30, 160), (100, 50))
        pygame.draw.rect(SURFACE, (255, 255, 0), rect1)

        pygame.display.update()
        FPSCLOCK.tick(3)

if __name__ == "__main__":
    main()

 

 

import sys
import pygame
from pygame.locals import QUIT, Rect, Circle

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


def main():
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit
        SURFACE.fill((255, 255, 255))

        pygame.draw.circle(SURFACE, (255, 0, 0), (50, 50), 20)
        pygame.draw.circle(SURFACE, (255, 0, 0), (150, 50), 20, 10)


        pygame.draw.circle(SURFACE, (0, 255, 0), (50, 150), 10)
        pygame.draw.circle(SURFACE, (0, 255, 0), (150, 150), 20)
        pygame.draw.circle(SURFACE, (0, 255, 0), (250, 150), 30)

        pygame.draw.ellipse(SURFACE, (255, 0, 0), (50, 50, 140, 60))
        pygame.draw.ellipse(SURFACE, (255, 0, 0), (250, 30, 90, 90))

        pygame.display.update()
        FPSCLOCK.tick(3)

if __name__ == "__main__":
    main()

 

 

 

원, 타원 그리기

import sys
import pygame
from pygame.locals import QUIT, Rect

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


def main():
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit
        SURFACE.fill((255, 255, 255))

        pygame.draw.circle(SURFACE, (255, 0, 0), (50, 50), 20)
        pygame.draw.circle(SURFACE, (255, 0, 0), (150, 50), 20, 10)


        pygame.draw.circle(SURFACE, (0, 255, 0), (50, 150), 10)
        pygame.draw.circle(SURFACE, (0, 255, 0), (150, 150), 20)
        pygame.draw.circle(SURFACE, (0, 255, 0), (250, 150), 30)

        pygame.draw.ellipse(SURFACE, (255, 0, 0), (50, 50, 140, 60))
        pygame.draw.ellipse(SURFACE, (255, 0, 0), (250, 30, 90, 90))

        pygame.display.update()
        FPSCLOCK.tick(3)

if __name__ == "__main__":
    main()

 

선긋기

 

import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 220))
FPSCLOCK = pygame.time.Clock()

def main():
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
        
        SURFACE.fill((255, 255, 255))
        pygame.draw.line(SURFACE, (255, 0, 0), (10, 80), (200, 80))
        pygame.draw.line(SURFACE, (255, 0, 0), (10, 150), (200, 150), 15)
        pygame.draw.line(SURFACE, (0, 255, 0), (250, 30), (250, 200))

        start_pos = (300, 30)
        end_pos = (380, 200)
        pygame.draw.line(SURFACE, (0, 0, 255), start_pos, end_pos, 10)

        pygame.display.update()
        FPSCLOCK.tick(3)

if __name__ == "__main__":
    main()

 

 

 

 

그리드 만들기

 

import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()

def main():
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        SURFACE.fill((0, 0, 0))

        for xpos in range(0, 400, 25):
            pygame.draw.line(SURFACE, 0xFFFFFF, (xpos, 0), (xpos,300))
        for ypos in range(0, 300, 25):
            pygame.draw.line(SURFACE, 0xFFFFFF, (0, ypos), (400, ypos))
        pygame.display.update()
        FPSCLOCK.tick(3)
    

if __name__ == "__main__":
    main()

 

 

 

 

 

 

이은선

 

import sys
import random
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()

def main():
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        SURFACE.fill((0, 0, 0))

        pointlist = []
        for _ in range(10):
            xpos = random.randint(0, 400)
            ypos = random.randint(0, 300)
            pointlist.append((xpos, ypos))
        
        pygame.draw.lines(SURFACE, (255, 255, 255), True, pointlist, 5)
        pygame.display.update()
        FPSCLOCK.tick(3)

if __name__ == "__main__":
    main()

 

 

 

텍스트 쓰기

import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 200))
FPSCLOCK = pygame.time.Clock()

def main():
    sysfont = pygame.font.SysFont(None, 72)
    msg = sysfont.render("hello python", True, (0, 128, 128))
    msg_rect = msg.get_rect()
    msg_rect.center = (200, 100)


    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
        SURFACE.fill((255, 255, 255))
        SURFACE.blit(msg, msg_rect)
        pygame.display.update()
        FPSCLOCK.tick(3)

if __name__ == "__main__":
    main()

 

 

 

 

 

 

텍스트 줌 회전

import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 200))
FPSCLOCK = pygame.time.Clock()

def main():
    sysfont = pygame.font.SysFont(None, 72)
    msg = sysfont.render("hello python", True, (0, 128, 128))
    msg_rect = msg.get_rect()
    theta = 0
    scale = 1


    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
        SURFACE.fill((255, 255, 255))

        theta += 5
        scale = (theta % 360) / 180
        tmp_msg = pygame.transform.rotozoom(msg, theta, scale)
        tmp_rect = tmp_msg.get_rect()
        tmp_rect.center = (200,150)


        SURFACE.blit(tmp_msg, tmp_rect)
        pygame.display.update()
        FPSCLOCK.tick(30)

if __name__ == "__main__":
    main()

 

 

 

마우스 클릭 이벤트

import sys
import pygame
from pygame.locals import QUIT, MOUSEBUTTONDOWN

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()

def main():
    mousepos = []
    while True:
        SURFACE.fill((255, 255, 255))
        
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == MOUSEBUTTONDOWN:
                mousepos.append(event.pos)
        
        for pos in mousepos:
            pygame.draw.circle(SURFACE, (0, 255, 0), pos, 5)
        
        pygame.display.update()
        FPSCLOCK.tick(10)

if __name__ == "__main__":
    main()

 

이 책같은 경우에는

평소 그림 연출 같은게 궁금했던 편이라 

개발 서적만 보면 재미도 없어서 보게됨

내용 자체가 만화로 구성되어 있어서 가볍게 읽을수 있엇고

 

컴퓨터 쪽만 다루다가

만화 작가 입장에서 어떻게 만화 세상을 만들고 사람들에게 보여줄지 고민한 흔적들을 보는게 꽤 재밋게 볼수 있었음.

 

 

이 책 중간에 기술 체득에 대해 언급한 부분이 가장 인상적이었음.

"기술을 익힌다는 것은 어떤 것인가? 기술의 체득이란 비유하자면 뇌에 고속도로를 뚫는 것과 같다."

"처음 고속도로 뚫을 때는 노력과 수고가 많이든다. 하지만 서서히 개통 구간이 늘어나고 어느 시점에 도달하면 그 이후로는 몸이 알아서 기술을 발휘해 주게 된다. 이 도로는 뚫기 힘들지만 한번 뚤으면 어지간해선 쇠퇴하지 않습니다."

"다만 이 고속도로는 까다로운 성질이 있는데, 하루에 개통 가능한 한도가 정해져 있다는 것이다."

이 책을 왜 봤더라..

 

평소 가끔 널널한 개발자님유튜브 영상 보곤하는데

 

대부분의 프로그래밍 책들이 기본서 이상의 내용이 부실해서 아쉽게 생각하는편이라

그 중에서 그나마 기본서가 다루지 않는 부분도 긁어주는 책이라 보게되었음.

 

이 책 머릿말에서 부터 이 부분을 언급하고 시작한다.

 

 

 

" 언어 입문서 대부분이 함수 포인터를 비롯한 소위 '고급 이론,들은 대부분 언급하지 않습니다. 다 이유가 있겠지만 이런 고급 이론들이 생략된 채로 C 언어 책들이 만들어지는 것은 정말 아쉬운 일입니다. 실무자에게는 너무도 중요한 이론들이기 때문입니다. 정확히 말해서 모르고는 제대로 살아남기 어려운 이론들이라 해야 할 것입니다. 많은 개발자가 10년 이내에 조기 은퇴하는 이유도 꼭 알아야 할 것들을 제대로 배운 적이 없기 때문이 아닐까 조심스럽 게 추측해봅니다."

 

"어려운 이론들을 늘어놓을수록 대중성과 멀어진다는 사실을 알고 있습니다. 하지만 적당히 타협해서 책을 쓰고 싶지는 않았습니다. 아쉬움이 없다고 할 수는 없지만. C 언어를 이용하는 실무 프로그래머 라면 반드시 알아야 할 이론들을 충분히 담았다고 자부합니다."

 

 

해외 번역서로 넘어가거나 잘 찾아보면 C 언어 고급 내용도 포함된 책도 찾아볼수야 있긴한데,

나처럼 머리가 굳은 사람 입장에서는 잘 이해하기가 힘들어서 보기 부담스러울때가 많다.

하지만 지금도 유튜브를 운영하시면서 오랫동안 고민하고 강의해오신 분 답게

기본서 이상 내용을 가장 깔끔하게 설명해준 책이라 생각된다.

 

국내 프로그래밍 기본서 중에

직접 메모리, 어셈블리를 보여주거나

스택 프레임을 그려준다거나

유니코드

형한정어

전처리기(조건부, 빌드모드)

최적화, 함수규칙 등을

 

이렇게 쉽게 설명해주는 책을 거의 보지 못했는데 

보통 기본서 땐 시점에서 오픈소스 살펴보면

전처리나 최적화, 라이브러리 등 내용이 부실해서 이해하기힘드나

 

다 긁어주니 좋더라.

특히 VS 쓰면서 빌드 에러날떼 cdecl이니 fastcall이니 뭔가 뜨는데 귀찬아서 제대로 안보던게

함수 규칙인걸 이제서야 처음알았다.

 

처음 개발 공부하는 입장에서는 힘든 책일지는 몰라도

어중간한 기본서보고 헛발질하느니 첫 C언어 기본서로서는 최고인듯.

https://www.yes24.com/Product/Goods/18732021

 

독하게 시작하는 C 프로그래밍 - 예스24

아프리카 TV에서 생방송 강의 진행!!!기초 문법은 물론 실무 활용 코딩 기법까지 코드 한 줄 한 줄을 독하게 파고든다!C는 정말 독하게 마음먹지 않고서는 숙달하기 어려운 프로그래밍 언어이다.

www.yes24.com

 

 

요 며칠새 블랜더도 손에 안잡히고 

센서 다루는것도 손에 안잡혀서 계속 놀고 있었다.

 

 

mpu6050 3개에서 ypr 가져오도록 하긴했는데

평평한 곳에 같은 방향으로 놓아도 yaw값이 다 다르게 나와서

mpu6050에 가속조 자이로는 없어도 지자기가 없다길래

mpu9250이라도 새로 사서 해야하나 좌절하고 있었다.

 

 

오랜만에 imu 가지고 트래킹하는걸 찾다가

 

내가 생각한거랑 비슷한걸 한 사람을 찾았다.

이 사람의 경우 블랜더에서 하는걸 보여주는데

 

계속 센서 위치를 어떻게 놓는게 좋을까 고민하던중에 보게되었다.

 

 

 

https://www.youtube.com/watch?v=F1IdRtIDdIs

 

 

더불어 소스코드도 같이 올려줬는데

이 친구는 ypr이 아닌 쿼터니언을 뽑아오도록 하더라

 

근데 찝찝한게 내 mpu6050은 같은 방향을 보도록 해도 yaw가 다르던데

쿼터니언으로 안한게 문제였을까?

https://github.com/T-K-233/Dual-MPU6050-Motion-Sync/blob/main/sensors/Arduino_dual_MPU6050_joint_capture/Arduino_dual_MPU6050_joint_capture.ino

 

mpu6050_dmp 코드를 보면

오일러, 쿼터니언, ypr, 가속도 등 출력해내도록 있던데

ypr이나 오일러각이랑 비슷한거 아닌가?

https://github.com/jrowberg/i2cdevlib/blob/master/Arduino/MPU6050/examples/MPU6050_DMP6/MPU6050_DMP6.ino

 

 

 

 

 

싶어서 구글링 좀하면서 해매다가

이글도 처음에 대충 지나가면서  이해가 안갔는데

 

https://taeyoung96.github.io/mathematics/Euler/

 

Euler Angle 과 Roll Pitch Yaw

Euler Angle(오일러 각) 과 Roll Pitch Yaw에 대해서 알아보자.

taeyoung96.github.io

 

 

이 글이랑 영상 몇개보고

이해갔다.

roll pitch yaw는 고정된 초기 축에 따라서 회전학 각도고

오일러각은 회전하면서 회전기준축도 이동한다.

 

위 국내 글에 그림을 잘보면 이 내용이 있는데 제대로 안봐서 못보고 지나갓엇다.

https://analyticalgraphics.my.site.com/faqs/articles/Knowledge/What-is-the-difference-between-YPR-and-Euler-attitude-in-STK

 

 

 

 

아무튼 내 mpu6050도 쿼터니언으로 뽑으면 좀 나으려나?

 

안그래도 이전에 만들면서도 오프셋 고쳐야할탠대 싶었는데

위 영상 만든사람도 오프셋을 고쳐서 하라고한다.

 

그런데 보다보니까

내가 갖고잇는 dmp 코드랑 뭔가 조금 다르더라.

  // supply your own gyro offsets here, scaled for min sensitivity
  mpu68.setXGyroOffset(0);
  mpu68.setYGyroOffset(0);
  mpu68.setZGyroOffset(0);
  mpu68.setXAccelOffset(0);
  mpu68.setYAccelOffset(0);
  mpu68.setZAccelOffset(0);
  
  mpu69.setXGyroOffset(0);
  mpu69.setYGyroOffset(0);
  mpu69.setZGyroOffset(0);
  mpu69.setXAccelOffset(0);
  mpu69.setYAccelOffset(0);
  mpu69.setZAccelOffset(0);

  
  // Calibration Time: generate offsets and calibrate our MPU6050
  mpu68.CalibrateAccel(6);
  mpu68.CalibrateGyro(6);
  
  mpu69.CalibrateAccel(6);
  mpu69.CalibrateGyro(6);

 

 

 

 

 

 

이게 2019년도에 업데이트된 최신 코드같은데

https://github.com/jrowberg/i2cdevlib/blob/master/Arduino/MPU6050/examples/MPU6050_DMP6_using_DMP_V6.12/MPU6050_DMP6_using_DMP_V6.12.ino

 

 

 

 

 

 

 

내가 사용한 mpu6050 라이브러리는

2013년에 만들어진 아주 오래된 코드를 기반으로 하고있었다 --..

// I2C device class (I2Cdev) demonstration Arduino sketch for MPU6050 class using DMP (MotionApps v2.0)
// 6/21/2012 by Jeff Rowberg <jeff@rowberg.net>
// Updates should (hopefully) always be available at https://github.com/jrowberg/i2cdevlib
//
// Changelog:
//      2013-05-08 - added seamless Fastwire support
//                 - added note about gyro calibration
//      2012-06-21 - added note about Arduino 1.0.1 + Leonardo compatibility error
//      2012-06-20 - improved FIFO overflow handling and simplified read process
//      2012-06-19 - completely rearranged DMP initialization code and simplification
//      2012-06-13 - pull gyro and accel data from FIFO packet instead of reading directly
//      2012-06-09 - fix broken FIFO read sequence and change interrupt detection to RISING
//      2012-06-05 - add gravity-compensated initial reference frame acceleration output
//                 - add 3D math helper file to DMP6 example sketch
//                 - add Euler output and Yaw/Pitch/Roll output formats
//      2012-06-04 - remove accel offset clearing for better results (thanks Sungon Lee)
//      2012-06-01 - fixed gyro sensitivity to be 2000 deg/sec instead of 250
//      2012-05-30 - basic DMP initialization working

/* ============================================

 

 

 

 

최신 코드

체인지로그

// I2C device class (I2Cdev) demonstration Arduino sketch for MPU6050 class using DMP (MotionApps v6.12)
// 6/21/2012 by Jeff Rowberg <jeff@rowberg.net>
// Updates should (hopefully) always be available at https://github.com/jrowberg/i2cdevlib
//
// Changelog:
//      2019-07-10 - Uses the new version of the DMP Firmware V6.12
//                 - Note: I believe the Teapot demo is broken with this versin as
//                 - the fifo buffer structure has changed
//      2016-04-18 - Eliminated a potential infinite loop
//      2013-05-08 - added seamless Fastwire support
//                 - added note about gyro calibration
//      2012-06-21 - added note about Arduino 1.0.1 + Leonardo compatibility error
//      2012-06-20 - improved FIFO overflow handling and simplified read process
//      2012-06-19 - completely rearranged DMP initialization code and simplification
//      2012-06-13 - pull gyro and accel data from FIFO packet instead of reading directly
//      2012-06-09 - fix broken FIFO read sequence and change interrupt detection to RISING
//      2012-06-05 - add gravity-compensated initial reference frame acceleration output
//                 - add 3D math helper file to DMP6 example sketch
//                 - add Euler output and Yaw/Pitch/Roll output formats
//      2012-06-04 - remove accel offset clearing for better results (thanks Sungon Lee)
//      2012-06-01 - fixed gyro sensitivity to be 2000 deg/sec instead of 250
//      2012-05-30 - basic DMP initialization working

/* ============================================

 

 

3/6 작성

---------------------------------

3/12 작성

 

계속 개으름 피우다가 다시 시작

 

이 mpu6050 ypr 추출 코드 작성한 사람 깃헙을 보면

https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050

 

모션앱뒤에 20, 41, 612가 붙어있는데

이게 뭔가 보니

 

 

 

 

DMP 펌웨어 버전을 말하는듯하다.

 

https://invensense.tdk.com/products/motion-tracking/6-axis/mpu-6050/

 

MPU-6050 | TDK InvenSense

NOT RECOMMENDED FOR NEW DESIGNS MPU-6050 Six-Axis (Gyro + Accelerometer) MEMS MotionTracking™ Devices   The MPU-6050™ parts are the world’s first MotionTracking devices designed for the low power, low cost, and high-performance requirements of smart

invensense.tdk.com

 

 

// I2C device class (I2Cdev) demonstration Arduino sketch for MPU6050 class using DMP (MotionApps v6.12)
// 6/21/2012 by Jeff Rowberg <jeff@rowberg.net>
// Updates should (hopefully) always be available at https://github.com/jrowberg/i2cdevlib
//
// Changelog:
//      2019-07-10 - Uses the new version of the DMP Firmware V6.12
//                 - Note: I believe the Teapot demo is broken with this versin as
//                 - the fifo buffer structure has changed

 

 

 

일단 이 예제코드로 동작시켜봄

 

https://github.com/jrowberg/i2cdevlib/blob/master/Arduino/MPU6050/examples/MPU6050_DMP6_using_DMP_V6.12/MPU6050_DMP6_using_DMP_V6.12.ino

#include "I2Cdev.h"

#include "MPU6050_6Axis_MotionApps612.h"
//#include "MPU6050.h" // not necessary if using MotionApps include file

#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
#include "Wire.h"
#endif


MPU6050 mpu;

#define OUTPUT_READABLE_YAWPITCHROLL



#define INTERRUPT_PIN 2  // use pin 2 on Arduino Uno & most boards
#define LED_PIN 13 // (Arduino is 13, Teensy is 11, Teensy++ is 6)
bool blinkState = false;

// MPU control/status vars
bool dmpReady = false;  // set true if DMP init was successful
uint8_t mpuIntStatus;   // holds actual interrupt status byte from MPU
uint8_t devStatus;      // return status after each device operation (0 = success, !0 = error)
uint16_t packetSize;    // expected DMP packet size (default is 42 bytes)
uint16_t fifoCount;     // count of all bytes currently in FIFO
uint8_t fifoBuffer[64]; // FIFO storage buffer

// orientation/motion vars
Quaternion q;           // [w, x, y, z]         quaternion container
VectorInt16 aa;         // [x, y, z]            accel sensor measurements
VectorInt16 gy;         // [x, y, z]            gyro sensor measurements
VectorInt16 aaReal;     // [x, y, z]            gravity-free accel sensor measurements
VectorInt16 aaWorld;    // [x, y, z]            world-frame accel sensor measurements
VectorFloat gravity;    // [x, y, z]            gravity vector
float euler[3];         // [psi, theta, phi]    Euler angle container
float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector

// packet structure for InvenSense teapot demo
uint8_t teapotPacket[14] = { '$', 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, '\r', '\n' };



// ================================================================
// ===               INTERRUPT DETECTION ROUTINE                ===
// ================================================================

volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
void dmpDataReady() {
  mpuInterrupt = true;
}



// ================================================================
// ===                      INITIAL SETUP                       ===
// ================================================================

void setup() {
  // join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
  Wire.begin();
  Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
  Fastwire::setup(400, true);
#endif

  // initialize serial communication
  // (115200 chosen because it is required for Teapot Demo output, but it's
  // really up to you depending on your project)
  Serial.begin(115200);
  while (!Serial); // wait for Leonardo enumeration, others continue immediately

  // NOTE: 8MHz or slower host processors, like the Teensy @ 3.3V or Arduino
  // Pro Mini running at 3.3V, cannot handle this baud rate reliably due to
  // the baud timing being too misaligned with processor ticks. You must use
  // 38400 or slower in these cases, or use some kind of external separate
  // crystal solution for the UART timer.

  // initialize device
  Serial.println(F("Initializing I2C devices..."));
  mpu.initialize();
  pinMode(INTERRUPT_PIN, INPUT);

  // verify connection
  Serial.println(F("Testing device connections..."));
  Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));

  // wait for ready
  Serial.println(F("\nSend any character to begin DMP programming and demo: "));
  while (Serial.available() && Serial.read()); // empty buffer
  while (!Serial.available());                 // wait for data
  while (Serial.available() && Serial.read()); // empty buffer again

  // load and configure the DMP
  Serial.println(F("Initializing DMP..."));
  devStatus = mpu.dmpInitialize();

  // supply your own gyro offsets here, scaled for min sensitivity
  mpu.setXGyroOffset(51);
  mpu.setYGyroOffset(8);
  mpu.setZGyroOffset(21);
  mpu.setXAccelOffset(1150);
  mpu.setYAccelOffset(-50);
  mpu.setZAccelOffset(1060);
  // make sure it worked (returns 0 if so)
  if (devStatus == 0) {
    // Calibration Time: generate offsets and calibrate our MPU6050
    mpu.CalibrateAccel(6);
    mpu.CalibrateGyro(6);
    Serial.println();
    mpu.PrintActiveOffsets();
    // turn on the DMP, now that it's ready
    Serial.println(F("Enabling DMP..."));
    mpu.setDMPEnabled(true);

    // enable Arduino interrupt detection
    Serial.print(F("Enabling interrupt detection (Arduino external interrupt "));
    Serial.print(digitalPinToInterrupt(INTERRUPT_PIN));
    Serial.println(F(")..."));
    attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);
    mpuIntStatus = mpu.getIntStatus();

    // set our DMP Ready flag so the main loop() function knows it's okay to use it
    Serial.println(F("DMP ready! Waiting for first interrupt..."));
    dmpReady = true;

    // get expected DMP packet size for later comparison
    packetSize = mpu.dmpGetFIFOPacketSize();
  } else {
    // ERROR!
    // 1 = initial memory load failed
    // 2 = DMP configuration updates failed
    // (if it's going to break, usually the code will be 1)
    Serial.print(F("DMP Initialization failed (code "));
    Serial.print(devStatus);
    Serial.println(F(")"));
  }

  // configure LED for output
  pinMode(LED_PIN, OUTPUT);
}



// ================================================================
// ===                    MAIN PROGRAM LOOP                     ===
// ================================================================

void loop() {
  // if programming failed, don't try to do anything
  if (!dmpReady) return;
  // read a packet from FIFO
  if (mpu.dmpGetCurrentFIFOPacket(fifoBuffer)) { // Get the Latest packet 

#ifdef OUTPUT_READABLE_QUATERNION
    // display quaternion values in easy matrix form: w x y z
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    Serial.print("quat\t");
    Serial.print(q.w);
    Serial.print("\t");
    Serial.print(q.x);
    Serial.print("\t");
    Serial.print(q.y);
    Serial.print("\t");
    Serial.println(q.z);
#endif

#ifdef OUTPUT_READABLE_EULER
    // display Euler angles in degrees
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetEuler(euler, &q);
    Serial.print("euler\t");
    Serial.print(euler[0] * 180 / M_PI);
    Serial.print("\t");
    Serial.print(euler[1] * 180 / M_PI);
    Serial.print("\t");
    Serial.println(euler[2] * 180 / M_PI);
#endif

#ifdef OUTPUT_READABLE_YAWPITCHROLL
    // display Euler angles in degrees
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
    Serial.print("ypr\t");
    Serial.print(ypr[0] * 180 / M_PI);
    Serial.print("\t");
    Serial.print(ypr[1] * 180 / M_PI);
    Serial.print("\t");
    Serial.print(ypr[2] * 180 / M_PI);
    /*
      mpu.dmpGetAccel(&aa, fifoBuffer);
      Serial.print("\tRaw Accl XYZ\t");
      Serial.print(aa.x);
      Serial.print("\t");
      Serial.print(aa.y);
      Serial.print("\t");
      Serial.print(aa.z);
      mpu.dmpGetGyro(&gy, fifoBuffer);
      Serial.print("\tRaw Gyro XYZ\t");
      Serial.print(gy.x);
      Serial.print("\t");
      Serial.print(gy.y);
      Serial.print("\t");
      Serial.print(gy.z);
    */
    Serial.println();

#endif

#ifdef OUTPUT_READABLE_REALACCEL
    // display real acceleration, adjusted to remove gravity
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetAccel(&aa, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity);
    Serial.print("areal\t");
    Serial.print(aaReal.x);
    Serial.print("\t");
    Serial.print(aaReal.y);
    Serial.print("\t");
    Serial.println(aaReal.z);
#endif

#ifdef OUTPUT_READABLE_WORLDACCEL
    // display initial world-frame acceleration, adjusted to remove gravity
    // and rotated based on known orientation from quaternion
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetAccel(&aa, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity);
    mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &q);
    Serial.print("aworld\t");
    Serial.print(aaWorld.x);
    Serial.print("\t");
    Serial.print(aaWorld.y);
    Serial.print("\t");
    Serial.println(aaWorld.z);
#endif

#ifdef OUTPUT_TEAPOT
    // display quaternion values in InvenSense Teapot demo format:
    teapotPacket[2] = fifoBuffer[0];
    teapotPacket[3] = fifoBuffer[1];
    teapotPacket[4] = fifoBuffer[4];
    teapotPacket[5] = fifoBuffer[5];
    teapotPacket[6] = fifoBuffer[8];
    teapotPacket[7] = fifoBuffer[9];
    teapotPacket[8] = fifoBuffer[12];
    teapotPacket[9] = fifoBuffer[13];
    Serial.write(teapotPacket, 14);
    teapotPacket[11]++; // packetCount, loops at 0xFF on purpose
#endif

    // blink LED to indicate activity
    blinkState = !blinkState;
    digitalWrite(LED_PIN, blinkState);
  }
}

 

 

잘 나오기는 한데 euler에서는 중력을 안쓰고 ypr 계산시에 중력을 사용하는 이유가 뭘까

실제 코드 dmpgetypr 들어가면 gravity 사용해서 계산하긴한다.

#ifdef OUTPUT_READABLE_QUATERNION
    // display quaternion values in easy matrix form: w x y z
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    Serial.print("quat\t");
    Serial.print(q.w);
    Serial.print("\t");
    Serial.print(q.x);
    Serial.print("\t");
    Serial.print(q.y);
    Serial.print("\t");
    Serial.println(q.z);
#endif

#ifdef OUTPUT_READABLE_EULER
    // display Euler angles in degrees
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetEuler(euler, &q);
    Serial.print("euler\t");
    Serial.print(euler[0] * 180 / M_PI);
    Serial.print("\t");
    Serial.print(euler[1] * 180 / M_PI);
    Serial.print("\t");
    Serial.println(euler[2] * 180 / M_PI);
#endif

#ifdef OUTPUT_READABLE_YAWPITCHROLL
    // display Euler angles in degrees
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
uint8_t MPU6050::dmpGetQuaternion(int16_t *data, const uint8_t* packet) {
    // TODO: accommodate different arrangements of sent data (ONLY default supported now)
    if (packet == 0) packet = dmpPacketBuffer;
    data[0] = ((packet[0] << 8) | packet[1]);
    data[1] = ((packet[4] << 8) | packet[5]);
    data[2] = ((packet[8] << 8) | packet[9]);
    data[3] = ((packet[12] << 8) | packet[13]);
    return 0;
}
uint8_t MPU6050::dmpGetQuaternion(Quaternion *q, const uint8_t* packet) {
    // TODO: accommodate different arrangements of sent data (ONLY default supported now)
    int16_t qI[4];
    uint8_t status = dmpGetQuaternion(qI, packet);
    if (status == 0) {
        q -> w = (float)qI[0] / 16384.0f;
        q -> x = (float)qI[1] / 16384.0f;
        q -> y = (float)qI[2] / 16384.0f;
        q -> z = (float)qI[3] / 16384.0f;
        return 0;
    }
    return status; // int16 return value, indicates error if this line is reached
}


uint8_t MPU6050::dmpGetEuler(float *data, Quaternion *q) {
    data[0] = atan2(2*q -> x*q -> y - 2*q -> w*q -> z, 2*q -> w*q -> w + 2*q -> x*q -> x - 1);   // psi
    data[1] = -asin(2*q -> x*q -> z + 2*q -> w*q -> y);                              // theta
    data[2] = atan2(2*q -> y*q -> z - 2*q -> w*q -> x, 2*q -> w*q -> w + 2*q -> z*q -> z - 1);   // phi
    return 0;
}

#ifdef USE_OLD_DMPGETYAWPITCHROLL
uint8_t MPU6050::dmpGetYawPitchRoll(float *data, Quaternion *q, VectorFloat *gravity) {
    // yaw: (about Z axis)
    data[0] = atan2(2*q -> x*q -> y - 2*q -> w*q -> z, 2*q -> w*q -> w + 2*q -> x*q -> x - 1);
    // pitch: (nose up/down, about Y axis)
    data[1] = atan(gravity -> x / sqrt(gravity -> y*gravity -> y + gravity -> z*gravity -> z));
    // roll: (tilt left/right, about X axis)
    data[2] = atan(gravity -> y / sqrt(gravity -> x*gravity -> x + gravity -> z*gravity -> z));
    return 0;
}
#else 
uint8_t MPU6050::dmpGetYawPitchRoll(float *data, Quaternion *q, VectorFloat *gravity) {
    // yaw: (about Z axis)
    data[0] = atan2(2*q -> x*q -> y - 2*q -> w*q -> z, 2*q -> w*q -> w + 2*q -> x*q -> x - 1);
    // pitch: (nose up/down, about Y axis)
    data[1] = atan2(gravity -> x , sqrt(gravity -> y*gravity -> y + gravity -> z*gravity -> z));
    // roll: (tilt left/right, about X axis)
    data[2] = atan2(gravity -> y , gravity -> z);
    if (gravity -> z < 0) {
        if(data[1] > 0) {
            data[1] = PI - data[1]; 
        } else { 
            data[1] = -PI - data[1];
        }
    }
    return 0;
}
#endif

 

 

 

위 계산에 대한 해설은 다음 링크에 있다.

https://oduerr.github.io/gesture/ypr_calculations.html

 

YawPitchRollCalculation

This document describes how the orientation of the IMU in terms of yaw,pitch, and roll is calculated from the information provided by the IMU (we use the MPU-9250). Calculations as done on the Arduino The result of the calculations in the Digital Motion Pr

oduerr.github.io

 

 

ypr 계산시 왜 gravity 백터를 사용하는지 궁금해서 구글링하다

국내에 mpu 사용한 글들 몇가지 찾아봄

 

https://blog.naver.com/ysahn2k/221410391235

 

[MEMS] MPU6050 + DMP6 기본동작

이번 블로그에서는 지난 상보필터에 대한 블로그에서 잠깐 언급한 Jeff Rowberg님의 DMP6 프로그램에 ...

blog.naver.com

 

https://m.blog.naver.com/yuninjae1234/220935189584

 

자이로센서 roll, pitch, yaw 데이터 처리 구조 (AHRS)

드론을 만들기위해 자이로 센서를 공부하면서 많은 예제들을 보고 공부하였지만 드문드문 있는 자료들을 머...

blog.naver.com

 

 

 

ypr 대신 그냥 쿼터니언과 오일러만 출력시켜보니

초기 시점 기준으로 회전한 만큼 각이 바뀐다.

중력벡터를 사용하면서 실제 중력 기준으로 각도를 변환해주는 거구나.

중력벡터를 안쓰고 하면 초기 위치 기준으로 값들이 출력된다.

 

왜 맨 위에 블렌더에서 실험한 사람은 yaw pitch roll을 안쓰고 바로 쿼터니언을 사용한것인지 이제 이해가 간다.

아래는 필요없는 부분 제거해서 좀더 간략하게 줄인 코드

#include "I2Cdev.h"

#include "MPU6050_6Axis_MotionApps612.h"
//#include "MPU6050.h" // not necessary if using MotionApps include file

#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
#include "Wire.h"
#endif


MPU6050 mpu;

//#define OUTPUT_READABLE_YAWPITCHROLL
#define OUTPUT_READABLE_EULER

// MPU control/status vars
bool dmpReady = false;  // set true if DMP init was successful
uint8_t mpuIntStatus;   // holds actual interrupt status byte from MPU
uint8_t devStatus;      // return status after each device operation (0 = success, !0 = error)
uint16_t packetSize;    // expected DMP packet size (default is 42 bytes)
uint16_t fifoCount;     // count of all bytes currently in FIFO
uint8_t fifoBuffer[64]; // FIFO storage buffer

// orientation/motion vars
Quaternion q;           // [w, x, y, z]         quaternion container
VectorInt16 aa;         // [x, y, z]            accel sensor measurements
VectorInt16 gy;         // [x, y, z]            gyro sensor measurements
VectorInt16 aaReal;     // [x, y, z]            gravity-free accel sensor measurements
VectorInt16 aaWorld;    // [x, y, z]            world-frame accel sensor measurements
VectorFloat gravity;    // [x, y, z]            gravity vector
float euler[3];         // [psi, theta, phi]    Euler angle container
float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector


// ================================================================
// ===                      INITIAL SETUP                       ===
// ================================================================

void setup() {


#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
  Wire.begin();
  Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
  Fastwire::setup(400, true);
#endif

  Serial.begin(115200);
  while (!Serial); // wait for Leonardo enumeration, others continue immediately


  // initialize device
  Serial.println(F("Initializing I2C devices..."));
  mpu.initialize();

  // verify connection
  Serial.println(F("Testing device connections..."));
  Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));

  // load and configure the DMP
  Serial.println(F("Initializing DMP..."));
  devStatus = mpu.dmpInitialize();

  // supply your own gyro offsets here, scaled for min sensitivity
  mpu.setXGyroOffset(51);
  mpu.setYGyroOffset(8);
  mpu.setZGyroOffset(21);
  mpu.setXAccelOffset(1150);
  mpu.setYAccelOffset(-50);
  mpu.setZAccelOffset(1060);
  // make sure it worked (returns 0 if so)
  if (devStatus == 0) {
    // Calibration Time: generate offsets and calibrate our MPU6050
    mpu.CalibrateAccel(6);
    mpu.CalibrateGyro(6);
    Serial.println();
    mpu.PrintActiveOffsets();
    // turn on the DMP, now that it's ready
    Serial.println(F("Enabling DMP..."));
    mpu.setDMPEnabled(true);

    mpuIntStatus = mpu.getIntStatus();

    // set our DMP Ready flag so the main loop() function knows it's okay to use it
    Serial.println(F("DMP ready! Waiting for first interrupt..."));
    dmpReady = true;

    // get expected DMP packet size for later comparison
    packetSize = mpu.dmpGetFIFOPacketSize();
  } else {
    Serial.print(F("DMP Initialization failed (code "));
    Serial.print(devStatus);
    Serial.println(F(")"));
  }

}



// ================================================================
// ===                    MAIN PROGRAM LOOP                     ===
// ================================================================

void loop() {
  // if programming failed, don't try to do anything
  if (!dmpReady) return;
  // read a packet from FIFO
  if (mpu.dmpGetCurrentFIFOPacket(fifoBuffer)) { // Get the Latest packet 

#ifdef OUTPUT_READABLE_QUATERNION
    // display quaternion values in easy matrix form: w x y z
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    Serial.print("quat\t");
    Serial.print(q.w);
    Serial.print("\t");
    Serial.print(q.x);
    Serial.print("\t");
    Serial.print(q.y);
    Serial.print("\t");
    Serial.println(q.z);
#endif

#ifdef OUTPUT_READABLE_EULER
    // display Euler angles in degrees
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetEuler(euler, &q);
    Serial.print("euler\t");
    Serial.print(euler[0] * 180 / M_PI);
    Serial.print("\t");
    Serial.print(euler[1] * 180 / M_PI);
    Serial.print("\t");
    Serial.println(euler[2] * 180 / M_PI);
#endif

#ifdef OUTPUT_READABLE_YAWPITCHROLL
    // display Euler angles in degrees
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
    Serial.print("ypr\t");
    Serial.print(ypr[0] * 180 / M_PI);
    Serial.print("\t");
    Serial.print(ypr[1] * 180 / M_PI);
    Serial.print("\t");
    Serial.print(ypr[2] * 180 / M_PI);
    Serial.println();

#endif
  }
}

 

 

 

 

 

내가 본 최고의 컴퓨터 역사 책

 

중간에 관심없는 내용들도 많고, 이 책 분량 자체가 많아서 제대로 보지는 못했지만

전자 정복이 전기 전자 역사를 다룬 최고의 책이었다면

이 책은 컴퓨터 역사를 다룬 책들 중에선 가장 잘쓴것 같다.

 

전자 정복만큼 재밌게 보지는 못했다만

벨 연구소 이야기, 전자 정복, 인터넷의 기원 등의 책에서 다룬 내용들이 다 언급될 뿐더러

내가 아직까지 책으로 보지 못했던 게임과 웹의 기원에 대해서도 나온다.

 

책 구매 페이지를 보면 왜 이런 수식어가 붙는지 이해할만하다.

 

아마존 베스트 1위, 『뉴욕타임스』, 『워싱턴포스트』, 『파이낸셜타임』, 『월스트리트저널』, 『포브스』 등이 꼽은 최고의 책

초특급 베스트셀러 『스티브 잡스』에 이은 월터 아이작슨의 놀라운 역작

 

 

https://www.yes24.com/Product/Goods/23691334

 

이노베이터 - 예스24

아마존 베스트 1위, 『뉴욕타임스』, 『워싱턴포스트』, 『파이낸셜타임』, 『월스트리트저널』, 『포브스』 등이 꼽은 최고의 책초특급 베스트셀러 『스티브 잡스』에 이은 월터 아이작슨의

www.yes24.com

 

 

이 책에서 기억에 남는 구문 몇가지

 

"혁신이란 타이밍의 문제기도 하다. 아이디어가 나올때 실현할 기술이 존재하느냐이다. 인간을 달에 보낸다는 아이디어는 마이크로칩의 발전으로 컴퓨터 유도 장치를 로켓에 장착할 시점에 제시되었다. 타이밍에 맞지 않는 경우도 있다. 찰스 배비지는 1837년에 컴퓨터 논문을 발표했지만, 제작하는데 필요한 기술 진전이 이루어지는데 100년이 걸렸다."

 

 

최초의 집적회로

"잭 킬비는 규소를 여러 방식으로 처리해 어떠한 전자 소자를 만들 수 있었다. 킬비는 노벨상 수상 시에 인용하게 될 문장을 실험실 일지에 기록했다. '하나의 덩어리로 저항, 캐패시터, 분산 커퍼시터, 트랜지스터와 같은 회로 소자를 만들 수 있다.' "

이 부분같은 경우 평소 실리콘 반도체로 어떻게 집적회로나 센서들을 만들어내는지 궁금해서 몇번 찾아봤지만 잘 이해할수 없는  와중에 보면서 꽤 기억에 남았다.

 

최초의 마이크로 프로세서 intel 4004

"테드 호프는 비지컴이 요구한 대부분의 작업을 수행 할 수 있는 단일 논리칩을 설계하자 제안했다. 로버트 노이스는 명목상 부하직원인 앤디 그로브를 설득하는게 우선 이라 생각했다. 그로브는 인텔 기강을 잡는 것이 의무라 생각했다..."

초기 인텔에서 intel 4004를 만들게 된 일화를 설명하는 부분인데 이외로 꽤 위트있었음.

 

가장 인상적인 구문

 

로버트 노이스가 마이크로프로세서를 만들때를 회상하면서

"어느 순간 갑자기 전구가 켜지고 완벽한 아이디어가 떠오른 것은 이나디. '이걸 할수 있으면 저것도 할수 있을거야.'는 식으로 매일 조금씩 전진해나간 결과 종국에는 개념이 정리된 것이다."

 

뉴턴이 나무에서 사과가 떨어지는 걸보고 만유인력을 생각해냇다는 이야기나

아르키메데스가 목욕을 하다가 유레카를 외친 이야기는

아이디어의 신화처럼 여겨지고 있지만

책 이노베이션에서 말하였듯이 진짜 있엇던 일을 왜곡, 과장하거나 매우 축약시켜 각인되었는데

 

만화처럼 갑자기 불켜진 전구가 아닌 우리가 아이디어를 어떻게 떠올리게 되는지 잘 찔러주는 문구라 생각된다.

어제 이노베이터 책을 보다가

문뜩 내가 어떤 컴퓨터 역사 책들을 봤는지 정리해보고 싶어졌다.

모든 책들을 정리할수는 없고 컴퓨터 과학 역사에 대해서 어떤 흐름으로 봤는지 나열해보려고함.

 

 

 

1. the elements of computing systems

 역사 책은 아니지만 내가 컴퓨터 과학 역사에 관심을 갖게되는 시발점이 된 책이다. 이 책을 어떻게 알게됫느냐 2017년도경에 사이토 고키의 밑바닥부터 시작하는 딥러닝 중간에 이 책을 보면 컴퓨터 구조를 알수있다는 내용보고 원서 사서 보게된 책이었다. 처음 볼땐 이 책 한 페이지 이해하는데만 30분이 걸려서 2장? 3장까지만 보고 포기했던 기억이 난다.

 

 이후에 3~4번 다시 보려고 시도한 결과 2022년 경 이 블로그에 올렸지만 hdl로 컴퓨터 하드웨어 구현하는것까지 진행할수 있었다. (2021년쯤에 번역서가 나왔었는데 원서 보면서 블로그에 다 정리하고나서 번역서가 나온걸 알았다.) 컴퓨터 하면 8비트 16비트니 하는데 8비트 RISC 컴퓨터 동작 과정을 이해할수 있게 해준 고마운 책이다.

 

2. 유닉스의 탄생

 실제로 내가 처음 본 컴퓨터 과학 역사책이라 할만한건 이 책인것 같다. 2020년 말인가 2021년인가쯤에 너무 의욕이 없어서 역사 책을 보면 조금 의욕적으로 할수 있지 않을까 싶어서 고른 책이다. 내용은 켄톰슨과 데니스 리치가 pdp-8 인가 하는 컴터에서 게임 돌리려고 뭐하다가 c언어를 만들고 유닉스를 만들어냈으며 거기에 사용되는 툴들을 설명했던것같다.

 

3. 세상을 여는 컴퓨터 이야기

 이 책을 본게 2023년 쯤인것 같은데 컴퓨터 비전 공부한다고 오일석 교수님의 책을 한창 봤었는데 이 분이 다른 책을 쓴게 있 찾아보다가 컴퓨터 역사 책을 쓰신걸 알게되어 보게 된 책이다. 이 책 자체는 청소년들을 대상으로 쓰여진 책인만큼 딱딱하지도 않고 가볍게 컴퓨터가 어떻게 만들어졌는지 설명한다. 다 보긴 했는데 모클리와 에커트 이야기, 에이다 이야기 외에는 잘 기억에 남진 않음.

 

4. 계산기는 어떻게 인공지능이 되었을까?

 이 책을 보기 앞서 세상을 여는 컴퓨터 이야기를 보면서 컴퓨터 역사에 대해 가볍게 관심이 커졌는데, 여전히 막연한 개념이 많았다. 그러다가 집근처 도서관에 이 책이 있길래 대출해서 보게 되었다. 깊이는 세상을 여는 컴퓨터 이야기처럼 어렵지 않고 얕게 나온다. 하지만 그 책에서 아쉬운 부분들을 꽤 잘 보충해주고, 다른 책들과 달리 사진들이 많이 첨부되 있어서 글만 있는 책들보다는 꽤 읽기 좋았던 편.

 

5. 벨 연구소 이야기

 이전에 유닉스 탄생을 보고 컴퓨터 역사에 대해 조금 알게 된 상태에서 그기술들의 고향인 벨 연구소에서 어떻게 만들어졌는지 궁금해 보게 된 책이다. 벨 연구소가 만들어지고 전쟁과 전후 나온 기술들 그리고 연구소가 사라지기 까지 과정이 나온다. 이 책을 꽤 감정 이입하면서 봣는지 끝에는 참 아쉽더라.

 

6. 저글러, 땜장이, 놀이꾼, 디지털 세상을 설계하다.

 벨 연구소 이야기를 보다보면 중간에 천재 클로드 섀넌에 대한 이야기가 나온다. 클로드 새년 컴퓨터 과학을 공부하면서 엔트로피 개념을 잠깐 볼때 나온 사람이었는데, 벨 연구소 이야기에서 섀넌의 활약을 보고 궁금해서 본 책이다.

 섀넌이 어떻게 가장 위대한 석사 논문을 쓰게 되었는지, 이후 연구와 취미 활동, 노년까지 이야기를 꽤 재밋게 볼수 있었음.

 

7. 전자정복

 내가 본 최고의 역사 책, 이 책이 얼마나 좋은지 이전에 책 리뷰 글을 쓸때 적어놔서 또 쓸게 없다. 현대 사회 전기전자 기술이 어떻게 만들어 지고 발전했는지 큰 흐름을 파악하기엔 최고. 

 

8. 이노베이션 신화의 진실과 오해

 이 책을 어쩌다 봣더라. 평소 막힌 문제를 열심히 해매다가 해결했을때의 희열감 이 단어가 궁금해서 찾아보다가 에피파니 라는 용어를 찾았는데 이와 관련해서 사람들은 어떤 재밋는 글을 썻을까 구글링해보다가 한빛 미디어인가 아카데미에서 올린 글을 봤었다.

 혁신은 에피파니로부터 나온다기 보다는 다른 것들을 설명해주는 책이었는데 지금 와서는 내용이 잘 기억나진 않는다. 마우스를 만든 엥겔바트 이야기가 나온것같긴한데 소개 글만 감탄하면서 보고 내용은 그렇게 와닿지는 않은편인듯.

 

9. 모스에서 잡스까지

 전자 컴퓨터 역사 다루다가 네트워크 쪽도 궁금해서 보게 된 책인것 같은데 꽤 가볍게 볼수 있는 책이었던걸로 기억한다.

 

10. 인터넷의 기원

 앞서 본 모스에서 잡스까지는 너무 가볍기도 했고, 인터넷에 대해 궁금해서 봤던 책이다. 알파가 어떻게 만들어지고 네트워크를 만들게 되었는지 장비나 통신 규약들이 만들어지고, 알파넷이 어떻게 사라졌는지 흐름이 나온다. 나는 이 책을 팀버너스리의 www가 어떻게 만들어졋는지 다루는줄 알았는데 알파넷 내용이 주라 좀 허탈감을 느꼇었다.

 

11. 이노베이터

 이 책을 왜 보게 되었는지 잘 기억나지는 않는데 기술 혁신은 개인이 아닌 집단의 힘으로 나온다는 주제로 컴퓨터 역사를 다루는게 인상적이어서 보게 됬던것같다. 아직 다 읽지 않고 조금씩 보는 중인 상태

 

 

내가 컴퓨터 역사 책을 꽤 많이 봤다 싶어 한번 정리해봤다.

도서 사이트에서 컴퓨터 역사라고 적어도 검색해도 제대로 된 역사책들이 잘 나오지는 않는데

이 글을 볼 사람이 있을랑가 몰라도

우리나라에 나온 컴퓨터 역사책은 거의 다 본것 같아 도서명들을 정리해서 올린다.

C++ 코딩의 기술을 보면서

마지막에 통신 프로토콜이 어떻게 만드는지까지 대충 보고

 

다음으로 본 책은 무인항공기 드론 소프트웨어를 만나다.

 

https://www.yes24.com/Product/Goods/53219638

 

 

 

 

이 책을 왜 보게됬냐

 

최근까지 언리얼 mpu6050 다루다가

각 센서마다 yaw 오프셋이 다르고 초기 위치나 오프셋 조정하는걸 해매야하는데 하기싫어서

최근 책보기만 하다가

혹시나 참고할 내용이 있을까 찾아보다가 보게 된 책이다.

 

이 책에선 mpu6050 로 ypr 뽑아내는 코드 만든사람껄 쓰는게 아니라

raw 가속도 자이로 데이터로 간단한 필터링들 상보 필터 같은걸 구현해서 써보고

pid 제어기 설계하는 내용도 나온다.

 

유익하긴 한데 직접 돌려볼 장비도 없고

책이 얇다보니 설명이 조금 적은게 아쉽다 ㅜㅜ

 

다른 드론책에서 자세 제어하는걸 좀더 찾아볼까 싶다.

 

오랜만에 책 보면서 약간 감동받은 책

 

책을볼때마다 나는 머릿말 보는걸 가장 좋아하는데

이 책은 머릿말부터 내가 개발 공부해오면서 가져온 문제 의식을 정확하게 찌른다.

 

내가 감명깊게 본 머릿말

 

"c++만 20년 동안 다뤘습니다. 어떤 경우 회사 일정에 쫓기는 와중에 2주나 걸려 간신히 해결한 문제도 있었고, 무려 5년만에 알게된 것도 있습니다. 그렇다고 어청나게 어려운 고급 개념이 아니라 기본적인 c++의 동작 원리나 문법들이었다는게 저를 더 맥빠지게 했습니다."

 

"다른 사람들은 이미 알고있을텐데 왜 안알려준거지?라며 보이지 않는 누군가에게 원망도 많이 했습니다. 시간이 더 흘러 나중에 다시 시중의 책을 봤더니 이거 안 알려준게 아니라 이 사람들도 몰랐던 건가?"

 

 

지금 만큼 책에 대한 관심과 접근성이 떨어졌을 때

오픈 소스 오픈소스 많이 얘길하지만 정작 이걸 어떻게 쓰는지 

알려주는 책이나 사람들이 많지 않았다.

 

지금보다 시야기 좁을때 대충 5,6년 전 만해도

내 눈에 보이는 책들은 열혈 강의같은 기본서만 있었지

기본서 다음의 중고급의 지식을 제시하는 책들을 잘 찾지 못했다.

원서 범위로 넘어가면 낫긴하지만 지금도 그타시피 원서 보기가 너무힘들기도하고

 

오픈 소스 사용하는 방법에 대해서

그냥 파이썬 pip으로 받거나

opencv 빌드하는 방법 검색질하고 사용하면서

포함 디렉터리나 라이브러리 개념이나 간단한 프로젝트 설정에 대해서 익숙해지는 것 정도가 한계였었는데

 

국내에는 뭐 볼만한 자료가 많지 않아서

없는 영어실력으로 내가 필요하다 싶은 원문 자료들을 번역하면서 블로그에 올리곤 했었다.

그러면서 좀 잘하는 사람이 보통 개발서적에서 잘 설명하지 않는(VS 사용법이나 오픈소스, 라이브러리 등)

간지러운 부분을 좀 긁어주는 자료좀 만들어주지 계속 생각했는데

최근에 찾은 책이 이책이었고 내가 한창 책찾아볼떄가 아닌 나온지 1년도 안된 책이더라.

 

책의 내용은 정말 감동이었다.

기본 문법가르치는 책이거나

한권에 1000페이지나 되지만 개발문서 번역한듯한 윈도우 API 같은 책들도 많으나

 

c++ 20년 진짜 고수가 병맛을 잘 섞어서 만든 c++의 얕고 넓은 백과사전 같은 느낌이었다.

국내에 비주얼 스튜디오 설정에 대해 알려주는 책이 얼마나 되는가?

오픈소스 빌드하는 방법을 알려주는 책은?

메모리나 통신에 대해 기본서 이상을 알려주는 책은?

 

해외 번역서로 넘어서 찾아보면 이런 내용들이 단편적으로 나오지만 

에이콘 번역서를 좀 보면 번역이 이상하거나 밋밋하다는 느낌을 많이 받는데

이 책은 깊이 강약 조절을 너무 잘했다.

 

 

이 책 거의 다 읽어가는 중인데

책 중간에 저자가

이걸 알려주는 사람이 어딧냐고 하는 부분을 보면서

정말 인정할수 밖에 없다.

 

나도 책까진 아니더라도 이런 걸 쓸수있을만큼 역량이 되면 좋을텐데 말이다.

 

계속 블렌더해야지

생각만하고 있는데

 

여전히 하기 귀찬아서 개으름피우고 있다.

그나마 최근 기쁜일은 youtube 올린 영상이

조금씩 조회수가 오르고 있는건데 

그렇게 조회수가 많은건 아니지만

생각한 데로만 만들어진다면 꽤 괜찬을것같다.

 

 

하지만 막막한 부분이 많고

하기는 귀찬다.

 

그러다가 본 책이 그림으로 배우는 네트워크 프로토콜

 

이전에도 네트워크 관련 책은 여러번 봤었는데

나온지 얼마 안된 책은 어떨가 싶어서 보게되었다.

 

내가 본 네트워크 책 중에서

얕고 넓게 다룬다면 가장 잘 쓴편이라 생각들었다.

 

 

와이어샤크 쓰는 책이 있는가하면

네트워크 책에 와이어샤크 소개가 짤막하게 붙어있는 경우는 봤지만

 

실제 네트워크 프로토콜을

와이어 샤크로 잡아서 보여준건 이 책이 처음이다.

 

네트워크 내용을 그림으로 묘사한 책들은 참 많지만

와이어샤크 써봐야지 생각만하고 안하다가

이 책에서는 옆에 붙어있으니 좀더 와닿긴하더라.

 

 

아주 깊게 파고들지는 않지만

전에 봤던 수학 대백과 처럼 

적당히 얕으면서 넓게

실제 활용 예시도 붙여 현실감있게 보여준 책이라 생각든다.

 

이전에 개발자를 위한 해석학인가 보고 잠깐 실망하긴했지만

역시 일본 사람들이 책을 잘만든다.

 

https://m.yes24.com/Goods/Detail/124766379

 

그림으로 배우는 네트워크 프로토콜 - 예스24

그림으로 쉽게 파악하는 네트워크 프로토콜의 세계네트워크는 물리적인 케이블, 케이블을 통해 전달되는 전기 및 광신호, 그 신호들이 변환된 다양한 데이터, 그리고 데이터를 주고받기 위한

m.yes24.com

 

+ Recent posts