<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev_hoon.log</title>
        <link>https://velog.io/</link>
        <description>여러 방향으로 접근하는 개발자</description>
        <lastBuildDate>Thu, 16 Jun 2022 04:15:28 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev_hoon.log</title>
            <url>https://images.velog.io/images/dev-hoon/profile/3bc11922-9580-4141-ac36-2aaed19a212e/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev_hoon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev-hoon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[추가복습] Node.js / 스케줄러 / CICD]]></title>
            <link>https://velog.io/@dev-hoon/%EC%B6%94%EA%B0%80%ED%95%99%EC%8A%B5-Node.js-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%9F%AC-CICD</link>
            <guid>https://velog.io/@dev-hoon/%EC%B6%94%EA%B0%80%ED%95%99%EC%8A%B5-Node.js-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%9F%AC-CICD</guid>
            <pubDate>Thu, 16 Jun 2022 04:15:28 GMT</pubDate>
            <description><![CDATA[<h2 id="nodejs란">Node.js란?</h2>
<p><code>Node.js</code> : Chrome V8 JavaScript 엔진으로 빌드 된 JavaScript 런타임(실행기), 서버와 같은 네트워크 프로그램을 만들기 위한 S/W 플랫폼
<code>JavaScript</code> : Javascript를 실행하기 위해서는 <code>웹 브라우저</code>가 필요하다.
Javascript는 브라우저라는 특정한 프로그램 안에서 동작하기 때문에 브라우저 없이 javascript를 실행하려면 실행환경이 필요하다.</p>
<blockquote>
<p>Node.js를 쓰는 이유는 브라우저 없이 javascript를 실행하여 서버 프로그램을 만들기 위하여</p>
</blockquote>
<h3 id="특징">특징</h3>
<ol>
<li>단일 쓰레드, 이벤트 루프</li>
<li>비동기 Non-Blocking I/O 처리</li>
<li>이벤트 발생시 서버에 메세지 형태로 전달</li>
</ol>
<h3 id="장점">장점</h3>
<ol>
<li>javascript를 사용해서 서버를 만들 수 있다.</li>
<li>npm를 통해 다양한 모듈을 사용가능하다.</li>
</ol>
<h3 id="단점">단점</h3>
<ol>
<li>단일 쓰레드이기 때문에 작업 자체가 많이 걸리는 서비스 형태의 경우에는 사용불가</li>
</ol>
<h3 id="활용">활용</h3>
<ol>
<li>간단한 서버 로직</li>
<li>빠른 응답시간 요구</li>
<li>빠른 개발 요구</li>
<li>비동기 방식에 어울리는 스트리밍 서비스, 채팅 서비스에 적합</li>
</ol>
<h2 id="스케줄러">스케줄러</h2>
<p>특정 시점에 기능을 수행할 수 있도록 하기 위해 사용된다. 서버를 자주 운영되는 리눅스에서 사용된다.</p>
<h2 id="cron-이란">Cron 이란?</h2>
<p><code>Cron</code> : 유닉스 계열의 잡 스케줄러</p>
<h3 id="cron-표현식">Cron 표현식</h3>
<p><img src="https://velog.velcdn.com/images/dev-hoon/post/093d4c11-a844-4696-ab79-168fff8529c9/image.png" alt=""></p>
<h3 id="특수-문자-정리">특수 문자 정리</h3>
<pre><code>● * : 모든 값
● ? : 특정한 값이 없음
● - : 범위 (ex) 월요일에서 수요일까지는 MON-WED로 표현한다.
● , : 특별한 값일 때만 동작 (ex) 월,수,금 MON,WED,FRI 
● / : 시작시간 / 단위  (ex) 0분부터 매 5분 0/5
● L : 일에서 사용하면 마지막 일, 요일에서는 마지막 요일(토요일)
● W : 가장 가까운 평일 (ex) 15W는 15일에서 가장 가까운 평일 (월 ~ 금)을 찾음
● # : 몇째주의 무슨 요일을 표현 (ex) 3#2 : 2번째주 수요일</code></pre><p><img src="https://velog.velcdn.com/images/dev-hoon/post/f33e033b-d2fd-4021-bd25-90b8a1a76888/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev-hoon/post/15c14612-fcd9-4a98-9b82-c9a070b5f2e0/image.png" alt=""></p>
<h2 id="cicd-환경의-이해with-jenkins">CICD 환경의 이해(with Jenkins)</h2>
<p>CICD : 애플리케이션 개발 단계를 자동화하여 애플리케이션 개발을 보다 짧은 주기로 고객에게 제공하는 방법, 개발 과 운영팀에서 발생하는 소통 문제를 해결한다.</p>
<h2 id="test-case">Test Case</h2>
<p><code>Test Case</code> : 동작에 <code>조건</code>을 붙여 잘 실행되는지 테스트 하는 것</p>
<h3 id="테스트-기반">테스트 기반</h3>
<p><code>명세 기반</code> : 문장(text)로 이루어진 자료 ex) 기능설명서, 사용자 스토리
<code>구조 기반</code> : 구조적 데이터로 이루어진 자료  ex) 코드, 제어흐름</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[복습] DataBase 설계]]></title>
            <link>https://velog.io/@dev-hoon/%EA%B3%84%EC%A0%88%ED%95%99%EA%B8%B0-DataBase-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@dev-hoon/%EA%B3%84%EC%A0%88%ED%95%99%EA%B8%B0-DataBase-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Tue, 14 Jun 2022 01:47:53 GMT</pubDate>
            <description><![CDATA[<h2 id="db-설계">DB 설계</h2>
<p>DB 설계 : <code>효율적</code>으로 DB를 관리하기 위해서</p>
<ol>
<li>파급효과 : 백업을 하기 위해서 연결되어 있는 서비스 때문에, 먼 과거까지 백업을 해야할 수 있다.</li>
<li>데이터 품질(고급)</li>
<li>간결함 : 관련자들이 동일하게 작업을 진행하기 위한 <code>데이터 모델</code>, 의사소통이 목적</li>
</ol>
<h2 id="erd">ERD</h2>
<p>ERD(Entity Relationship Diagram) : tool이다. 테이블간의 관계를 설명해주는 다이어그램으로, DB 설계를 하기위해 효율적으로 사용되는 구조도이다. 의사소통이 주된 목적이다.</p>
<h2 id="index">Index</h2>
<p><code>인덱스</code> : 추가적인 쓰기 작업과 저장 공간을 활용하여 데이터베이스 테이블의 <code>검색속도</code>를 향상 시키기 위한 자료구조</p>
<h3 id="query가-index-타는지-확인-하는-방법">query가 index 타는지 확인 하는 방법</h3>
<p><code>explain</code>을 query문 앞에 붙여준다.</p>
<blockquote>
<p>인덱스를 탄다 = 인덱스를 걸은 컬럼 기준으로 데이터를 정렬시키고 정렬되어 있는 데이터를 트리구조로 빠르게 탐색한다</p>
</blockquote>
<h2 id="db-데이터-적재">DB 데이터 적재</h2>
<h3 id="실무-프로젝트시-데이터-이행">실무 프로젝트시 데이터 이행</h3>
<ol>
<li>실제 데이터를 운영 DB에 반영하는 작업</li>
<li>대량 데이터일 경우 <code>증분</code> 데이터만 반영<h3 id="데이터-적재-시점">데이터 적재 시점</h3>
</li>
<li>서버 Down 타임에 적재</li>
<li>사전 적재</li>
<li>서비스 운영 중에 적재</li>
</ol>
<h3 id="적재-방법">적재 방법</h3>
<ol>
<li>DB 백업본으로 이관</li>
<li>텍스트 데이터로 이관</li>
<li>DB to DB로 이관</li>
<li>Restful API로 이관</li>
</ol>
<p>주의) 필요한 데이터 가공 및 텍스트 속성값 확인할것</p>
<h3 id="참고-사이트">참고 사이트</h3>
<p><a href="https://shawn-choi.tistory.com/4">import</a>
<a href="https://dev.mysql.com/doc/workbench/en/wb-admin-export-import-table.html">export</a></p>
<h3 id="느낀점">느낀점</h3>
<ol>
<li>엑셀파일(.xlsx) 등을 import 할 수는 없고 .csv 파일로 변경해야한다.</li>
<li>.csv 변경시 연결 프로그램을 메모장으로 설정 후 다른 이름으로 저장으로 통해 utf-8로 저장해줘야 정상 저장이 된다.</li>
<li>column명을 .csv 파일과 table 과 동일시 해줘야한다.</li>
<li>import 중 column 명이 한글로 되어있을 경우 에러가 발생한다.</li>
<li>export 할 때 파일명 혹은 경로가 한글로 되어있을 경우 에러가 발생한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PJT] RPI-리모콘 연결]]></title>
            <link>https://velog.io/@dev-hoon/PJT-RPI-%EB%A6%AC%EB%AA%A8%EC%BD%98-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@dev-hoon/PJT-RPI-%EB%A6%AC%EB%AA%A8%EC%BD%98-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Tue, 24 May 2022 15:03:50 GMT</pubDate>
            <description><![CDATA[<h2 id="db-테스트">DB 테스트</h2>
<p>RC Car에서 작업 디렉토리 이동 후 <code>vi ./app.py</code>후 해당 코드를 테스트 해보자!</p>
<pre><code class="language-py">import mysql.connector

db = mysql.connector.connect(host=&#39;AWS IP 주소`, user=`관리자 계정 ID&#39;, password=&#39;관리자 PW&#39;, database=&#39;생성한 스키마명&#39;, auth_plugin=&#39;mysql_native_password&#39;)
cur = db.cursor()

#query
cur.execute(&quot;select * from command&quot;)

#print
for (id, time, cmd_string, arg_string, is_finish) in cur:
    print(id, time, cmd_string, arg_string, is_finish)

cur.close()
db.close()</code></pre>
<h2 id="timer-사용하기">Timer 사용하기</h2>
<blockquote>
<p>임베디드에서 Timer는 Thread가 아니라 Interrupt로 동작됩니다. 이와 달리, Python 에서 Timer는 Threading으로 동작되어, Main Thread와 Timer Thread가 동시에 동작 됩니다. </p>
</blockquote>
<pre><code class="language-py">import mysql.connector
from threading import Timer
from time import sleep

def polling():
    print(&quot;HI&quot;)

    timer = Timer(2, polling)
    timer.start()

polling()

while True: pass</code></pre>
<h2 id="명령어-미리-받기-위한-timer">명령어 미리 받기 위한 Timer</h2>
<p>다음 코드를 응용하여 </p>
<blockquote>
<p>Polling Thread : Command Query 로 받은 데이터를 “ready” 객체에 저장한다.
Main Thread : “ready” 객체에 저장된 명령을 수행한다. (Motor 제어)</p>
</blockquote>
<p>와 같이 구현할 예정입니다. </p>
<pre><code class="language-py">import mysql.connector
from threading import Timer
from time import sleep
import signal
import sys

def closeDB(signal, frame):
    print(&quot;BYE&quot;)
    timer.cancel()
    sys.exit(0)

def polling():
    print(&quot;HI&quot;)   
    global timer
    timer = Timer(2, polling)
    timer.start()

timer = None
signal.signal(signal.SIGINT, closeDB)
polling()

while True: 
    print(&quot;MAIN&quot;)
    sleep(0.5)
    pass</code></pre>
<h2 id="지속적으로-query-받기">지속적으로 Query 받기</h2>
<pre><code class="language-py"> import mysql.connector
from threading import Timer
from time import sleep
import signal
import sys

def closeDB(signal, frame):
    print(&quot;BYE&quot;)
    cur.close()
    db.close()
    timer.cancel()
    sys.exit(0)

def polling():
    global cur, db

    # 가장 최근의 레코드를 지속적으로 가져오는 소스코드
    cur.execute(&quot;select * from command order by time desc limit 1&quot;) 
    for (id, time, cmd_string, arg_string, is_finish) in cur:
        print(cmd_string, arg_string)

    db.commit()

    global timer
    timer = Timer(1, polling)
    timer.start()

#init
db = mysql.connector.connect(host=&#39;AWS IP 주소`, user=`관리자 계정 ID&#39;, password=&#39;관리자 PW&#39;, database=&#39;생성한 스키마명&#39;, auth_plugin=&#39;mysql_native_password&#39;)
cur = db.cursor()
timer = None
signal.signal(signal.SIGINT, closeDB)
polling()

#main thread
while True:
    print(&quot;KFC MAIN&quot;)
    sleep(0.5)
    pass</code></pre>
<h2 id="코드">코드</h2>
<pre><code class="language-py">import mysql.connector
from threading import Timer
from time import sleep
import signal
import sys

def closeDB(signal, frame): # 신호시 종료
    print(&quot;BYE&quot;)
    cur.close()
    db.close()
    timer.cancel()
    sys.exit(0)

def polling(): # 최근 명령어 가져오기
    global cur, db, ready

    cur.execute(&quot;select * from command order by time desc limit 1&quot;)
    for (id, time, cmd_string, arg_string, is_finish) in cur:
        if is_finish == 1 : break
        ready = (cmd_string, arg_string)
        cur.execute(&quot;update command set is_finish=1 where is_finish=0&quot;) # is_finish 1로 셋팅

    db.commit()

    global timer
    timer = Timer(0.1, polling)
    timer.start()

def go():
    print(&quot;go&quot;)

def back():
    print(&quot;back&quot;)

def stop():
    print(&quot;stop&quot;)

def left():
    print(&quot;left&quot;)

def mid():
    print(&quot;mid&quot;)

def right():
    print(&quot;right&quot;)

#init
db = mysql.connector.connect(host=&#39;13.125.225.160&#39;, user=&#39;mincoding&#39;, password=&#39;1234&#39;, database=&#39;minDB&#39;, auth_plugin=&#39;mysql_native_password&#39;)
cur = db.cursor()
ready = None
timer = None
signal.signal(signal.SIGINT, closeDB)
polling()

#main thread
while True:
    sleep(0.1)
    if ready == None : continue

    cmd, arg = ready
    ready = None

    if cmd == &quot;go&quot; : go()
    if cmd == &quot;back&quot; : back()
    if cmd == &quot;stop&quot; : stop()
    if cmd == &quot;left&quot; : left()
    if cmd == &quot;mid&quot; : mid()
    if cmd == &quot;right&quot; : right()</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PJT] RPI 셋팅 & 테스트]]></title>
            <link>https://velog.io/@dev-hoon/PJT-RPI-%EC%85%8B%ED%8C%85-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@dev-hoon/PJT-RPI-%EC%85%8B%ED%8C%85-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Tue, 24 May 2022 03:32:22 GMT</pubDate>
            <description><![CDATA[<h2 id="rpi-기본세팅">RPI 기본세팅</h2>
<p>RC카 라즈베리파이를 SSH로 접속합니다. 기본 도구인 <code>apt-update, vim과 rdate</code>를 설치합니다. 그리고 시간 세팅을 하여 <code>DB에 정확한 시간 값을 저장</code>할 수 있도록 합니다.</p>
<pre><code class="language-bash">sudo apt update
sudo apt install rdate vim -y
sudo rdate -s time.bora.net
sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
date</code></pre>
<h2 id="작업-디렉토리-생성-후-테스트">작업 디렉토리 생성 후 테스트</h2>
<p><code>mkdir</code>를 통해 작업 디렉토리 생성하기!
MySQL 접속을 위한 pip로 설치하기!</p>
<pre><code class="language-bash">python3 -m pip install mysql-connector
python3 -m pip install mysql-connector-python</code></pre>
<p>작업 디렉토리 접근후 테스트 해보기</p>
<h3 id="db-테스틑">DB 테스틑</h3>
<p><code>1_db_test.py</code></p>
<pre><code class="language-py">import mysql.connector

db = mysql.connector.connect(host=&#39;AWS IP 주소`, user=`관리자 계정 ID&#39;, password=&#39;관리자 PW&#39;, database=&#39;생성한 스키마명&#39;, auth_plugin=&#39;mysql_native_password&#39;)
cur = db.cursor()

#query
cur.execute(&quot;select * from command&quot;)

#print
for (id, time, cmd_string, arg_string, is_finish) in cur:
    print(id, time, cmd_string, arg_string, is_finish)

cur.close()
db.close()</code></pre>
<h3 id="sense-hat-test">Sense Hat Test</h3>
<p><code>sudo apt-get install sense-hat</code>을 통해 센스햇 Library
<code>2_sensehat_test.py</code></p>
<pre><code class="language-py">from sense_hat import SenseHat
sen = SenseHat()
sen.show_message(&#39;HELLO&#39;)</code></pre>
<h3 id="압력온도습도-센서-test">압력,온도,습도 센서 Test</h3>
<p><code>3_sensor_test.py</code></p>
<pre><code class="language-py">from sense_hat import SenseHat
from time import sleep

sense = SenseHat()

while True:
  pressure = sense.get_pressure()
  temp = sense.get_temperature()
  humidity = sense.get_humidity()

  p = round(pressure, 2)
  t = round(temp, 2)
  h = round(humidity, 2)

  msg = &quot;Press : &quot; + str(p) + &quot;  Temp : &quot; + str(t) + &quot;  Humid : &quot; + str(h)
  print(msg)
  sleep(0.1)</code></pre>
<h3 id="조이스틱-test">조이스틱 test</h3>
<p><code>4_eyebbong_test.py</code></p>
<pre><code class="language-py">from sense_hat import SenseHat

sense = SenseHat()
sense.clear(255, 255, 255) 

def red():
  sense.clear(255, 0, 0)

def blue():
  sense.clear(0, 0, 255)

def green():
  sense.clear(0, 255, 0)

def yellow():
  sense.clear(255, 255, 0)

sense.stick.direction_up = red
sense.stick.direction_down = blue
sense.stick.direction_left = green
sense.stick.direction_right = yellow
sense.stick.direction_middle = sense.clear

#infinity loop
while True:
  pass</code></pre>
<h2 id="모터-세팅하기">모터 세팅하기</h2>
<p><code>Motor Driver(Motor Hat)</code>을 사용하면, GPIO 로 간단히 DC Motor, Servo Motor를 제어할 수 있습니다.</p>
<p><code>Motor Library</code> 다운 후 압축 풀기</p>
<pre><code class="language-bash">cd ~/bbqcar
wget http://raspberrypiwiki.com/images/a/ac/Raspi-MotorHAT-python3.zip --no-check-certificate
unzip ./Raspi-MotorHAT-python3.zip</code></pre>
<p>필요한 라이브러리 작업 디렉토리로 복사하기</p>
<pre><code class="language-bash">cp ./DCTest.py ~/bbqcar/test/5_DCTest.py
cp ./Raspi_I2C.py ~/bbqcar/test/
cp ./Raspi_MotorHAT.py ~/bbqcar/test/
cp ./Raspi_PWM_Servo_Driver.py ~/bbqcar/test/</code></pre>
<h3 id="dc모터-테스트">DC모터 테스트</h3>
<p><code>vi 5_DCTest.py</code>에서 DC모터가 연결된 채널을 확인한 후 <code>myMotor = mh.getMotor(연결된 채널)</code>을 수정해줍니다!</p>
<h3 id="서보모터-테스트">서보모터 테스트</h3>
<p>```py
from Raspi_PWM_Servo_Driver import PWM
import time</p>
<p>pwm = PWM(0x6F)
pwm.setPWMFreq(60)</p>
<p>while True:
    value = int(input(&#39;PWM VALUE : &#39;))
    if value &lt;= 200 or value &gt;= 500 : 
        print(&quot;WARNING&quot;)
        continue
    pwm.setPWM(0, 0, value)
    ```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PJT] AWS & DB]]></title>
            <link>https://velog.io/@dev-hoon/PJT-icnzr476</link>
            <guid>https://velog.io/@dev-hoon/PJT-icnzr476</guid>
            <pubDate>Mon, 23 May 2022 06:43:07 GMT</pubDate>
            <description><![CDATA[<h2 id="aws-셋팅">AWS 셋팅</h2>
<p>이름, APP(Quick Start를 통한 생성), 키페어 생성(유출 조심), 스토리지 설정(프리 티어는 30GB까지 무료) 등을 모두 해줍니다.</p>
<p>인스턴스 ID를 클릭을 통해 추가 셋팅을 해줍니다.
<code>퍼블릭 IPv4 주소</code>에 접근가능한다.</p>
<h2 id="포트-개방">포트 개방</h2>
<p><code>DataBase</code>를 사용하기 위해 포트를 개방시켜줘야 합니다.</p>
<p><code>보안 &gt; 보안그룹 &gt; 인바운드 규칙 편집 &gt; 규칙 추가</code>에서 ICMP와 mySQL 포트 개방하기!
<code>인바운드</code> : 외부에서 서버로 들어오는 신호를 허용할지 말지 결정하는 보안 규칙
ICMP : 서버가 정상적으로 동작되는지 확인하기 위해 Ping을 날리는 행위
<code>0.0.0.0/0</code> : 어떠한 IP라도 접속 허용하겠다는 IP
mysql 포트 : 3306</p>
<h2 id="우분투-기본-세팅">우분투 기본 세팅</h2>
<p>apt 패키지 관리자 사용을 위한 업데이트 : <code>sudo apt update</code>
vim 설치 : <code>sudo apt install rdate vim -y</code></p>
<h3 id="데이터-센싱을-원활하게-하기-위한-시간-세팅">데이터 센싱을 원활하게 하기 위한 시간 세팅</h3>
<p>LG U+ 타임 서버에서 시간 값 가져오기 : <code>sudo rdate -s time.bora.net</code>
서울로 셋팅 : <code>sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime</code></p>
<h2 id="mysql-설치-및-세팅">MYSQL 설치 및 세팅</h2>
<p>MYSQL 설치 : <code>sudo apt install mysql-server -y</code></p>
<h3 id="관리자-계정-생성">관리자 계정 생성</h3>
<p><code>sudo mysql</code>
<code>use mysql;</code>
<code>create user &#39;아이디&#39;@&#39;%&#39; identified with mysql_native_password by &#39;비밀번호&#39;;</code></p>
<blockquote>
<p><code>%</code>는 어느 IP 접속 가능
<code>with mysql_native_password</code>는 키 인증 방식이 아닌 password 인증 방식을 사용하겠다는 의미</p>
</blockquote>
<h3 id="스키마-생성-및-권한-부여">스키마 생성 및 권한 부여</h3>
<p>스키마(엑셀 파일), Table(Sheet)
스키마 생성 : <code>create database [DB스키마이름];</code>
모든 권한 부여 : <code>grant all privileges on [DB스키마이름].* to &#39;아이디&#39;@&#39;%&#39;;</code></p>
<h3 id="테스트-및-추가-설정">테스트 및 추가 설정</h3>
<p><code>quit</code>으로 sql을 종료 후 <code>mysql -u &#39;아이디&#39; -p</code>을 통해 테스트하기
mysql을 ssh 뿐만 아니라 외부 접속도 가능케 하기 : <code>sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf</code>에서 bind-address를 <code>0.0.0.0</code>으로 설정하기! 
재시작하여 설정 내용 적용하기 : <code>sudo service mysql restart</code></p>
<h2 id="mysql-접속">MYSQL 접속</h2>
<p><a href="https://dev.mysql.com/downloads/workbench/">MYSQL Workbench 설치</a>후 <code>+</code>을 통해 계정 생성하기!
제목, AWS에서 생성한 IPv4 주소, 생성한 관리자 계정을 입력하여 접속 테스트 후 접속하기!
2개의 테이블을 생성할 것입니다. 클라우드 Sever로 부터 원격 조정기를 통해 보내온 명령어를 읽는 <code>명령어 Table</code> 과 RC Car가 센서로 부터 읽은 데이터를 클라우드 Server에 보낼 <code>sensing Table</code>을 생성합니다.</p>
<p>스키마 더블클릭하여 스키마에 접근 후 다음 SQL문을 입력하여 테이블 생성하기!</p>
<pre><code class="language-SQL">CREATE TABLE `command` (
    `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `time` DATETIME NULL,
    `cmd_string` VARCHAR(50) NULL DEFAULT NULL,
    `arg_string` VARCHAR(50) NULL DEFAULT NULL,
    `is_finish` INT NULL
);

CREATE TABLE `sensing` (
    `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `time` DATETIME NULL,
    `num1` DOUBLE NULL DEFAULT NULL,
    `num2` DOUBLE NULL DEFAULT NULL,
    `num3` DOUBLE NULL DEFAULT NULL,
    `meta_string` VARCHAR(50) NULL DEFAULT NULL,
    `is_finish` INT NULL
);</code></pre>
<h2 id="원격조정기">원격조정기</h2>
<p>원격조정기를 통해 제어 명령어를 클라우드 서버에 전송하면, RC카가 해당 명령어를 읽어 들여 명령어에 맞게 작동되게 설계하였습니다. </p>
<p><code>개발 PC</code>로 Windows 환경인 파이참에서 <code>PySide6</code>로 원격조정기를 개발합니다. 개발 및 Test가 완료 후에는 소스코드를 <code>Raspberry Pi</code> 에서 가져간 뒤, <code>PySide2</code>로 변경하여 App을 실행합니다.</p>
<h2 id="pyside-6-설치-및-셋팅">Pyside 6 설치 및 셋팅</h2>
<p>프로젝트를 하나 생성합니다. 폴더 경로에 <code>한글</code>이 포함되어있다면, 에러가 발생합니다.</p>
<p>파이썬에서는 외부 Library를 <code>패키지</code>라고 부릅니다. 먼저 PySide 패키지를 설치해봅시다. <code>설정에서 프로젝트 &gt; 인터프리터 &gt; 빈곳 더블클릭</code>하면 됩니다.
여기서 <code>pyside6</code> 를 선택하고 “설치” 를 누르면 패키지 설치가 진행됩니다.</p>
<p>테스트</p>
<pre><code class="language-python">from PySide6.QtWidgets import *

app = QApplication()
btn = QPushButton(&quot;Test Button&quot;)
btn.show()
app.exec()</code></pre>
<h2 id="gui-에디터-셋팅">GUI 에디터 셋팅</h2>
<p>PySide를 사용하여 GUI 개발을 하는데, 매번 venv &gt; lib &gt; site-package &gt; pyside &gt; designer.exe 를 수행하기에는 번거롭습니다.</p>
<h3 id="바로가기-제작">바로가기 제작</h3>
<p>따라서, <code>바로가기</code>를 만들어 편리하게 pyside를 수행합시다.</p>
<p><code>설정 &gt; 도구 &gt; 외부도구에 + 버튼</code>을 누릅니다.</p>
<blockquote>
<p>이름, 프로그램(경로: 생성된 프로젝트 명 &gt; venv &gt; Lib &gt; site-packages &gt; PySide6 &gt; desinger.exe) &gt; 고급옵션(도구 출력용 콘솔 열기 <code>끄기</code>) &gt; 확인</p>
</blockquote>
<p>설정 완료 후 좌측 탭 빈곳에서 마우스 <code>오른쪽 버튼 후 외부 도구</code>에서 실행시키기</p>
<h2 id="db-패키지-설치">DB 패키지 설치</h2>
<p>MySQL 공식 Connection 방법을 이용하여 DB와 Pyside 연결하기!
<code>설정에서 프로젝트 &gt; 인터프리터 &gt; 빈곳 더블클릭</code>하면 됩니다.
여기서 <code>mysql-connector</code>와 <code>mysql-connector-python</code>를 선택하고 “설치” 를 누르면 패키지 설치가 진행됩니다.</p>
<h3 id="테스트">테스트</h3>
<pre><code class="language-py">from PySide6.QtWidgets import *
import mysql.connector

db = mysql.connector.connect(host=&#39;AWS IPv4 주소&#39;, user=&#39;관리자 계정 아이디&#39;, password=&#39;관리자 계정 비밀번호&#39;, database=&#39;생성한 스키마명&#39;)
cur = db.cursor() # query의 결과를 저장하는 공간을 나타내는 Database 명령어

#query
cur.execute(&quot;select * from command&quot;)

#print : 생성한 행 이름에 맞게 설정
for (id, time, cmd_string, arg_string, is_finish) in cur:
    print(id, time, cmd_string, arg_string, is_finish)

cur.close()
db.close()</code></pre>
<h2 id="ui-제작">UI 제작</h2>
<p>아까 만들어둔 desginer 바로가기를 통해 designer을 실행시킵니다. 
<code>Main Window</code>에서 <code>PlainTextEdit</code>과 <code>Push Button</code> 위젯을 생성해줍니다.
PlainTextEdit의 객체명은 <code>logText</code>로 Push Button의 객체명은 <code>startButton</code>으로 설정해줍니다. 
버튼 클릭시 start()라는 함수가 실행되도록 Signal &amp; Slot을 연결해줍니다.</p>
<p>venv 폴더 밖 경로에 <code>mainUI.ui</code> 파일명으로 파일을 저장합니다. </p>
<h3 id="바로가기-제작-1">바로가기 제작</h3>
<p>ui 파일을 py로 convert 해줘야하는데, 이 또한 바로가기로 제작해봅니다!</p>
<p><code>설정 &gt; 도구 &gt; 외부도구에 + 버튼</code>을 누릅니다.</p>
<blockquote>
<p>이름, 프로그램(경로: 생성된 프로젝트 명 &gt; venv &gt; Scripts &gt; pyside6-uic.exe),인수($FileDir$\mainUI.ui -o $FileDir$\mainUI.py) , 고급옵션(도구 출력용 콘솔 열기 <code>끄기</code>) &gt; 확인</p>
</blockquote>
<p>설정 완료 후 좌측 탭 빈곳에서 마우스 <code>오른쪽 버튼 후 외부 도구</code>에서 실행시키기</p>
<h3 id="ui-테스트-코드">UI 테스트 코드</h3>
<pre><code class="language-py">from PySide6.QtWidgets import *
from mainUI import Ui_MainWindow

class MyApp(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.init()

    def init(self):
        print(&quot;INIT&quot;)

    def start(self):
        print(&quot;START&quot;)

app = QApplication()
win = MyApp()
win.show()
app.exec()</code></pre>
<h2 id="pyside6---db-연결-코드">Pyside6 - DB 연결 코드</h2>
<pre><code class="language-py">from PySide6.QtWidgets import *
from mainUI import Ui_MainWindow
import mysql.connector

class MyApp(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.init()

    def init(self):
        self.db = mysql.connector.connect(host=&#39;AWS IPv4 주소&#39;, user=&#39;관리자 계정 아이디&#39;, password=&#39;관리자 계정 비밀번호&#39;, database=&#39;생성한 스키마명&#39;)
        self.cur = self.db.cursor()


    def start(self):
        self.cur.execute(&quot;select * from command&quot;)
        for (id, time, cmd_string, arg_string, is_finish) in self.cur:
            print(id, time, cmd_string, arg_string, is_finish)

    def closeEvent(self, event):
        self.cur.close()
        self.db.close()

app = QApplication()
win = MyApp()
win.show()
app.exec()</code></pre>
<h2 id="qtimer을-이용하여-반복적으로-query하기">QTimer을 이용하여 반복적으로 Query하기</h2>
<pre><code class="language-py">from PySide6.QtWidgets import *
from PySide6.QtCore import *
from mainUI import Ui_MainWindow
import mysql.connector

class MyApp(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.init()

    def init(self):
        self.db = mysql.connector.connect(host=&#39;AWS IPv4 주소&#39;, user=&#39;관리자 계정 아이디&#39;, password=&#39;관리자 계정 비밀번호&#39;, database=&#39;생성한 스키마명&#39;)
        self.cur = self.db.cursor()

        #timer setting
        self.timer = QTimer()
        self.timer.setInterval(500) #500ms
        self.timer.timeout.connect(self.pollingQuery)

    def start(self):
        self.timer.start()

    def pollingQuery(self):
        self.cur.execute(&quot;select * from command&quot;)
        self.ui.logText.clear()
        for (id, time, cmd_string, arg_string, is_finish) in self.cur:
            str = &quot;%d | %s | %6s | %6s | %4d&quot; % (id, time.strftime(&quot;%Y%m%d %H:%M:%S&quot;), cmd_string, arg_string, is_finish)
            self.ui.logText.appendPlainText(str)

    def closeEvent(self, event):
        self.cur.close()
        self.db.close()

app = QApplication()
win = MyApp()
win.show()
app.exec()</code></pre>
<h2 id="버튼-추가">버튼 추가</h2>
<p>logText의 font를 <code>Consolas</code>로 수정하기
Go(객체명: goButton, slot: go()), Back(객체명: backButton, slot: back()), Mid(객체명: midButton, slot: mid()), Left(객체명: leftButton, slot: left()), Right(객체명: rightButton, slot: right()), STOP(객체명: stopButton, slot: stop()) 추가하기</p>
<h3 id="테스트-1">테스트</h3>
<pre><code class="language-py">from PySide6.QtWidgets import *
from PySide6.QtCore import *
from mainUI import Ui_MainWindow
import mysql.connector

class MyApp(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.init()

    def init(self):
        self.db = mysql.connector.connect(host=&#39;AWS IPv4 주소&#39;, user=&#39;관리자 계정 아이디&#39;, password=&#39;관리자 계정 비밀번호&#39;, database=&#39;생성한 스키마명&#39;)
        self.cur = self.db.cursor()

        #timer setting
        self.timer = QTimer()
        self.timer.setInterval(500) #500ms
        self.timer.timeout.connect(self.pollingQuery)

    def start(self):
        self.timer.start()

    def pollingQuery(self):
        self.cur.execute(&quot;select * from command&quot;)
        self.ui.logText.clear()
        for (id, time, cmd_string, arg_string, is_finish) in self.cur:
            str = &quot;%d | %s | %6s | %6s | %4d&quot; % (id, time.strftime(&quot;%Y%m%d %H:%M:%S&quot;), cmd_string, arg_string, is_finish)
            self.ui.logText.appendPlainText(str)

    def closeEvent(self, event):
        self.cur.close()
        self.db.close()

    def insertCommand(self, cmd_string, arg_string):
        time = QDateTime().currentDateTime().toPython()
        is_finish = 0

        query = &quot;insert into command(time, cmd_string, arg_string, is_finish) values (%s, %s, %s, %s)&quot;
        value = (time, cmd_string, arg_string, is_finish)

        self.cur.execute(query, value)
        self.db.commit()

    def go(self):
        self.insertCommand(&quot;go&quot;, &quot;5&quot;)

    def stop(self):
        self.insertCommand(&quot;stop&quot;, &quot;0&quot;)

    def back(self):
        self.insertCommand(&quot;back&quot;, &quot;0&quot;)

    def left(self):
        self.insertCommand(&quot;left&quot;, &quot;0&quot;)

    def mid(self):
        self.insertCommand(&quot;mid&quot;, &quot;0&quot;)

    def right(self):
        self.insertCommand(&quot;right&quot;, &quot;0&quot;)

app = QApplication()
win = MyApp()
win.show()
app.exec()</code></pre>
<h2 id="sensing-table-추가하기">Sensing Table 추가하기</h2>
<p>logText를 객체명: sensingText, Font: Consolas로 추가합니다.</p>
<h3 id="테스트-2">테스트</h3>
<pre><code class="language-py">from PySide6.QtWidgets import *
from PySide6.QtCore import *
from mainUI import Ui_MainWindow
import mysql.connector

class MyApp(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.init()

    def init(self):
        self.db = mysql.connector.connect(host=&#39;AWS IPv4 주소&#39;, user=&#39;관리자 계정 아이디&#39;, password=&#39;관리자 계정 비밀번호&#39;, database=&#39;생성한 스키마명&#39;)
        self.cur = self.db.cursor()

        #timer setting
        self.timer = QTimer()
        self.timer.setInterval(500) #500ms
        self.timer.timeout.connect(self.pollingQuery)

    def start(self):
        self.timer.start()

    def pollingQuery(self):
        self.cur.execute(&quot;select * from command order by time desc limit 15&quot;)
        self.ui.logText.clear()
        for (id, time, cmd_string, arg_string, is_finish) in self.cur:
            str = &quot;%d | %s | %6s | %6s | %4d&quot; % (id, time.strftime(&quot;%Y%m%d %H:%M:%S&quot;), cmd_string, arg_string, is_finish)
            self.ui.logText.appendPlainText(str)

        self.cur.execute(&quot;select * from sensing order by time desc limit 15&quot;)
        self.ui.sensingText.clear()
        for (id, time, num1, num2, num3, meta_string, is_finish) in self.cur:
            str = &quot;%d | %s | %6s | %6s | %6s | %10s | %4d&quot; % (id, time.strftime(&quot;%Y%m%d %H:%M:%S&quot;), num1, num2, num3, meta_string, is_finish)
            self.ui.sensingText.appendPlainText(str)

    def closeEvent(self, event):
        self.cur.close()
        self.db.close()

    def insertCommand(self, cmd_string, arg_string):
        time = QDateTime().currentDateTime().toPython()
        is_finish = 0

        query = &quot;insert into command(time, cmd_string, arg_string, is_finish) values (%s, %s, %s, %s)&quot;
        value = (time, cmd_string, arg_string, is_finish)

        self.cur.execute(query, value)
        self.db.commit()

    def go(self):
        self.insertCommand(&quot;go&quot;, &quot;0&quot;)

    def stop(self):
        self.insertCommand(&quot;stop&quot;, &quot;0&quot;)

    def back(self):
        self.insertCommand(&quot;back&quot;, &quot;0&quot;)

    def left(self):
        self.insertCommand(&quot;left&quot;, &quot;0&quot;)

    def mid(self):
        self.insertCommand(&quot;mid&quot;, &quot;0&quot;)

    def right(self):
        self.insertCommand(&quot;right&quot;, &quot;0&quot;)

app = QApplication()
win = MyApp()
win.show()
app.exec()</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PJT] AR Glass + RC 카]]></title>
            <link>https://velog.io/@dev-hoon/PJT</link>
            <guid>https://velog.io/@dev-hoon/PJT</guid>
            <pubDate>Fri, 20 May 2022 09:41:15 GMT</pubDate>
            <description><![CDATA[<h2 id="google-cloud-speech">Google Cloud Speech</h2>
<h3 id="셋팅">셋팅</h3>
<p><a href="https://cloud.google.com/gcp/?utm_source=google&amp;utm_medium=cpc&amp;utm_campaign=japac-AU-all-en-dr-bkws-all-super-trial-e-dr-1009882&amp;utm_content=ims_text-ad-none-none-DEV_c-CRE_540787626910-ADGP_Hybrid%20%7C%20BKWS%20-%20EXA%20%7C%20Txt%20~%20GCP%20~%20General_Business%20Services%20-%20google%20cloud-KWID_43700061080192030-aud-1640178259900%3Akwd-300427730551&amp;userloc_1030714-network_g&amp;utm_term=KW_%EA%B5%AC%EA%B8%80%20%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C&amp;gclid=Cj0KCQjw1ZeUBhDyARIsAOzAqQIVODbVxOdkqgFuy3ob7j2RoXLbwPw4foXY6sKKKXgyAtpybUh_NhMaAkqGEALw_wcB&amp;gclsrc=aw.ds">구글 클라우드 사이트</a>에서 로그인 후 <code>콘솔로 이동</code>으로 이동하기</p>
<p>상단에서 프로젝트를 새로 생성하고, <code>API 및 서비스</code>에서 <code>Cloud Speech-to-Text API</code>를 사용하기</p>
<p><code>사용자 인증정보</code> 탭 &gt; 사용자인증 정보 만들기 - 서비스 계정 &gt; 계정명 및 권한(소유자)로 설정 후 생성하기 &gt; 생성된 계정 클릭 &gt; 상단 탭 키 &gt; 키 추가 &gt; 새 키 만들기(JSON)를 통해 다운받기</p>
<p>다운 받은 키를 <code>마이크가 달린 AR Class 보드</code> 홈 디렉토리에 옮기기</p>
<p><code>export GOOGLE_APPLICATION_CREDENTIALS=&quot;/home/pi/xxxxxxx-xxxxxxxxx.json&quot;</code>를 통해 환경 변수 만들기</p>
<h3 id="구글-클라우드-스피치-api-설치">구글 클라우드 스피치 API 설치</h3>
<p>우선 오류 발생을 방지하여 <code>pip3 install --upgrade pip</code>로 pip를 업그레이드 시킨다.
<code>pip3 install --use-deprecated=backtrack-on-build-failures google-cloud-speech</code>를 통해 구글 클라우드 스피치 API 설치
참고) --use-deprecated=backtrack-on-build-failures 는 오류 발생시 이전 버전을 다운받는다는 설정</p>
<h2 id="구글-클라우드-예제">구글 클라우드 예제</h2>
<p>구글 클라우드 스피치가 음성 데이터를 텍스트로 변환하는 방법</p>
<ol>
<li>짧은 음성 파일을 보내 한번에 응답을 받는 방법</li>
<li>긴 음성 예제를 한번에 보내면 조각조각 응답 받는 방식</li>
<li>실시간으로 연속적으로 보내고 받는 방법<h2 id="1-짧은-음성-파일을-보내-한번에-응답을-받는-방법">1. 짧은 음성 파일을 보내 한번에 응답을 받는 방법</h2>
<h3 id="오디오-신호의-개념">오디오 신호의 개념</h3>
<code>오디오 신호</code> : 아주 빠르게 변경되는 아날로그 신호
<code>PCM(Pulse Code Modulation)</code> : Sampling rate(얼마의 주기로 데이터를 확인할지), Bit depth(데이터 값을 얼마만큼 쪼갤지)를 통해 아날로그 신호를 디지털로 변환한다. 잘게 쪼갤수록 더 정교한 데이터를 전송할 수 있다.</li>
</ol>
<h3 id="코드">코드</h3>
<pre><code class="language-py">from google.cloud import speech
import io
# 사용할 파일 위치
local_file_path = &#39;/home/pi/test.wav&#39;

client = speech.SpeechClient()

# PCM 구성 : 해당 음성 데이터가 다음과 같이 구성되어 있어야 정상 작동한다.
config = speech.RecognitionConfig(
    encoding = speech.RecognitionConfig.AudioEncoding.LINEAR16, # 세로축(데이터)을 2의 16승 만큼 쪼갬
    sample_rate_hertz = 48000, # 가로축(시간)을 48000만큼 쪼갬
    language_code = &quot;ko-KR&quot;
)
with io.open(local_file_path, &#39;rb&#39;) as f:
    content = f.read()
audio = speech.RecognitionAudio(content=content)

# 한번에 음성 데이터를 보내고 변환된 모든 텍스트 데이터를 한번에 받는 함수로 오디오 파일의 구성을 파라미터로 보낸다.
response = client.recognize(config=config, audio=audio)

# response는 여러개의 대안으로 전송되어 오는데, 그 중 confidence가 가장 높은 것을 체택한다.
for result in response.results:
    print(f&#39;Transcript: {result.alternatives[0].transcript}&#39;)</code></pre>
<p><code>arecord -r48000 -fS16_LE -d10 -vv -c1 test.wav</code>를 통해 데이터를 2의 16승만큼 쪼개고, 시간은 48000만큼 쪼개며, 10초동안 정상작동 되는지 보이고 채널은 mono로 제목을 test.wav로 녹음하겠다는 명령어</p>
<h2 id="3-실시간으로-연속적으로-보내고-받는-방법">3. 실시간으로 연속적으로 보내고 받는 방법</h2>
<p><code>구글 스피치</code>는 시간에 따라 Response를 계속 받아서 스스로 피드백을 내리게 되며, <code>is_final : true</code>이라는 문구와 함께 보내오는 데이터가 <code>완성된 문장</code>임을 의미한다.</p>
<pre><code class="language-py"># transcribe_streaming_mic.py

import re # 정규표현식 모듈
import sys

from google.cloud import speech
import pyaudio  # 파이썬에서 오디오 입력 사용
import queue

# Audio recording parameters
RATE = 16000
CHUNK = int(RATE / 10)  

class MicrophoneStream(object):
    def __init__(self, rate, chunk):
        self._rate = rate
        self._chunk = chunk

        # Create a thread-safe buffer of audio data
        self._buff = queue.Queue()  # pyaudio가 전달해주는 데이터를 담을 큐 
        self.closed = True

    # 파이썬 context manager사용. 여기에서는 실행중 문제가 발생해도 오디오장치를 제대로 닫도록 할 수 있기 위함.
    def __enter__(self):
        self._audio_interface = pyaudio.PyAudio()   # 시작할 때 pyaudio 데이터 스트림 열림
        self._audio_stream = self._audio_interface.open( # pyaudio.open()은 pyaudio.Stream object를 리턴.
            format=pyaudio.paInt16, # 16bit 다이나믹 레인지
            channels=1,
            rate=self._rate,
            input=True,     
            frames_per_buffer=self._chunk,
            stream_callback=self._fill_buffer,  # pyaudio에서 한 블록의 데이터가 들어올 때 호출되는 콜백
        )

        self.closed = False

        return self

    def __exit__(self, type, value, traceback):
        self._audio_stream.stop_stream()
        self._audio_stream.close()
        self.closed = True
        self._buff.put(None)
        self._audio_interface.terminate()   # 끝날 때 반드시 pyaudio 스트림 닫도록 한다.

    def _fill_buffer(self, in_data, frame_count, time_info, status_flags):  # pyaudio.Stream에서 호출되는 콜백은 4개 매개변수 갖고, 2개값 리턴한다. pyaudio문서 참고.
        self._buff.put(in_data) # 큐에 데이터 추가
        return None, pyaudio.paContinue

    # 한 라운드의 루프마다 현재 버퍼의 내용을 모아서 byte-stream을 yield함.
    def generator(self):
        while not self.closed:
            # Use a blocking get() to ensure there&#39;s at least one chunk of
            # data, and stop iteration if the chunk is None, indicating the
            # end of the audio stream.
            chunk = self._buff.get()
            if chunk is None:
                return
            data = [chunk]

            # Now consume whatever other data&#39;s still buffered.
            while True:
                try:
                    chunk = self._buff.get(block=False)  # 가장 오래된 데이터부터 순차적으로 data[]에 추가함.
                    if chunk is None:
                        return
                    data.append(chunk)
                except queue.Empty: # 큐에 더이상 데이터가 없을 때까지
                    break

            yield b&#39;&#39;.join(data) # byte-stream

# response  화면에 출력
def listen_print_loop(responses):
    num_chars_printed = 0
    for response in responses:
        if not response.results:
            continue

        # The `results` list is consecutive. For streaming, we only care about
        # the first result being considered, since once it&#39;s `is_final`, it
        # moves on to considering the next utterance.
        # 최종적인 결과값은 언제나 results[0]에 반영되므로 result[0]만 고려.
        result = response.results[0]
        if not result.alternatives:
            continue

        # 확실성 가장 높은 alternative의 해석
        transcript = result.alternatives[0].transcript

        # 완성된 문장이 intrim 문장보다 짧다면, 나머지 부분은 &#39; &#39;으로 overwrite해 가려준다.
        overwrite_chars = &#39; &#39; * (num_chars_printed - len(transcript))   

        if not result.is_final: # 확정된 transcript가 아니라면,
            sys.stdout.write(transcript + overwrite_chars + &#39;\r&#39;)   # &#39;\r&#39;로 줄바꿈은 하지 않고 맨 앞으로 돌아가 이전 문장위에 덧쓰도록 한다.
            sys.stdout.flush()

            num_chars_printed = len(transcript)

        else:   # 확정된 transcript라면
            print(transcript + overwrite_chars)

            # 문장중에 &#39;명령끝&#39;이라는 단어가 있다면 종료한다.
            if re.search(r&#39;\b(명령 끝)\b&#39;, transcript, re.I):
                print(&#39;Exiting..&#39;)
                break

            num_chars_printed = 0


def main():
    # 한국말 사용
    language_code = &#39;ko-KR&#39;  # a BCP-47 language tag

    client = speech.SpeechClient()
    config = speech.RecognitionConfig(
        encoding=&#39;LINEAR16&#39;, # enums.RecognitionConfig.AudioEncoding.LINEAR16
        sample_rate_hertz=RATE,
        max_alternatives=1, # 가장 가능성 높은 1개 alternative만 받음.
        language_code=language_code)
    streaming_config = speech.StreamingRecognitionConfig(
        config=config,
        interim_results=True) # 해석완료되지 않은(is_final=false) 중도값도 사용.

    with MicrophoneStream(RATE, CHUNK) as stream:   # 사운드 스트림 오브젝트 생성. 
                                                    # pyaudio가 terminate()되는 것을 보장하기 위해 python
                                                    # context manager  사용.
        audio_generator = stream.generator()
        requests = (speech.StreamingRecognizeRequest(audio_content=content)
                    for content in audio_generator) # 요청 생성

        responses = client.streaming_recognize(streaming_config, requests)  # 요청 전달 &amp; 응답 가져옴
        listen_print_loop(responses)    # 결과 출력. requests, responses 모두 iterable object


if __name__ == &#39;__main__&#39;:
    main()
</code></pre>
<h2 id="therading-vs-asyncio">Therading vs Asyncio</h2>
<h2 id="threading">Threading</h2>
<p>동시실행을 목적으로 사용한다.</p>
<pre><code class="language-py">import threading
import time

def sub_thread(start, end):
    for var in range(start, end):
        print(f&#39;we ar in SUB: {var}&#39;)
        var += 1
        time.sleep(0.6)

# 서브 thread가 동작되는 함수로 thread 오브젝트 생성
t = threading.Thread(target = sub_thread, args = (0,100)) 
t.daemon = True # 데몬 Therad : main Thread 종료시 sub Thread 자동 종료

t.start() # thread 실행, 이후 메인 쓰레드와 서브 쓰레드의 동시 실행

for i in range(20):
    print(&quot;메인 쓰레디 실행중 ...&quot;)
    time.sleep(1)</code></pre>
<h2 id="asyncio">Asyncio</h2>
<p>시간 효율성을 목적으로 사용한다.</p>
<h3 id="blocking-vs-non-blocking">Blocking vs Non-Blocking</h3>
<p><code>Blocking</code> : 메모리로부터 긴 시간동안 데이터를 가져와야하는 작업때 CPU는 쉬고 있는다.
<code>Non-Blocing</code> : 긴 시간이 필요한 작업의 응답을 기다리지 않고 다음 작업을 진행한다.
<code>blocking 방식</code></p>
<pre><code class="language-py">import time

def request1() :   # 시간이 오래걸리는 함수
    time.sleep(10)
    return time.perf_counter() # 프로그램 실행 시간을 반환

def request2() :
    time.sleep(5)
    return time.perf_counter()

def main():
    print(&quot;start&quot;)
    start_time = time.perf_counter()

    req1_time = request1() # 10초 걸리는 작업
    req2_time = request2() # 5초 걸리는 작업

    # print는 15초에 실행된다.
    print(f&#39;req1 takes {req1_time - start_time}&#39;)
    print(f&#39;req2 takes {req2_time - start_time}&#39;)

main() </code></pre>
<p><code>Non-blocking 방식</code>
잠깐 멈춰서 다른 작업을 할 수 있는 작업이 정해져있다.</p>
<pre><code class="language-py">import time
import asyncio

async def request1():  # await 구문을 포함하므로 함수를 async화 해줘야한다.
    await asyncio.sleep(10) # time은 잠시 정지 기능이 지원한되므로, asyncio를 사용한다.
    return time.perf_counter()  # 프로그램 실행 시간을 반환

async def request2():
    await asyncio.sleep(5)
    return time.perf_counter()

 main도 async로 만들어줌
async def main():
    print(&quot;start&quot;)
    start_time = time.perf_counter()

    # event loop라고 작업을 멈추지 않고 원할하게 관리해주는 곳에 task 형태로 등록한다.
    req1_time = asyncio.create_task(request1())
    req2_time = asyncio.create_task(request2())

    await req1_time
    await req2_time

    # task에서의 return 값을 사용하기 위한 result
    print(f&#39;req1 takes {req1_time.result() - start_time}&#39;)
    print(f&#39;req2 takes {req2_time.result() - start_time}&#39;)

asyncio.run(main())</code></pre>
<h2 id="websocket-통신">WebSocket 통신</h2>
<p>HTTP 통신 : 요청과 응답이 반복
Socket 통신 : 연결이 되었다면 양방향 실시간 통신</p>
<blockquote>
<p>websocket 통신 : 웹상에서도 tcp socket과 같이 실시간 양방향의 데이터 교환이 가능, 확장성 및 http의 보안성도 겸비하고 있다.</p>
</blockquote>
<h3 id="동작">동작</h3>
<p>서버와 클라이언트 모두 웹소켓 라이브러리 설치 : <code>pip3 install websockets</code>
<code>sever 코드</code></p>
<pre><code class="language-py">
import asyncio # non-blocking방식으로 네트웍 통신하기 위해 asyncio 모듈 필요.
import websockets # websockets 모듈 임포트

async def hello(websocket, path): # 아래의 websockets.serve()에 의해 호출되는 함수
    while True:
        name = await websocket.recv()  # 데이터 받기
        print(f&quot;{name}을 받았습니다.&quot;)    # 받은 데이터 출력

        greeting = f&quot;Hello {name}!&quot;
        await websocket.send(greeting)  # 데이터 보내기
        print(f&quot;{greeting}을 보냈습니다.&quot;)    # 보낸 데이터 출력

async def main():
    # 웹소켓 서버를 만들고, hello 함수를 실행시킨다.
    server = await websockets.serve(hello, host=&quot;xxx.xx&quot;, port=5678) # 본인 서버 IP 주소
    await server.wait_closed() # 서버를 명시적으로 종료할 때까지 대기

asyncio.run(main())</code></pre>
<p><code>client 코드</code></p>
<pre><code class="language-py">
import asyncio # non-blocking방식으로 네트웍 통신하기 위해 asyncio 모듈 필요.
import websockets # websockets 모듈 임포트

async def hello(websocket, path): # 아래의 websockets.serve()에 의해 호출되는 함수
    while True:
        name = await websocket.recv()  # 데이터 받기
        print(f&quot;{name}을 받았습니다.&quot;)    # 받은 데이터 출력

        greeting = f&quot;Hello {name}!&quot;
        await websocket.send(greeting)  # 데이터 보내기
        print(f&quot;{greeting}을 보냈습니다.&quot;)    # 보낸 데이터 출력

async def main():
    # 웹소켓 서버를 만들고, hello 함수를 실행시킨다.
    server = await websockets.serve(hello, host=&quot;xxx.xx&quot;, port=5678) # 본인 서버 IP 주소
    await server.wait_closed() # 서버를 명시적으로 종료할 때까지 대기

asyncio.run(main())</code></pre>
<h2 id="재부팅시-설정-고정">재부팅시 설정 고정</h2>
<p>IP 및 환경변수 및 부팅시 실행될 파일을 고정시켜보자!</p>
<h3 id="ip-고정">IP 고정</h3>
<p><code>ifconfig</code>를 통해 본인 보드의 연결형태와 ip주소를 기록해둔 후, <code>sudo vi /etc/dhcpcd.conf</code> 명령어를 통해 static IP configuration 부분을 수정하자!</p>
<p><img src="https://velog.velcdn.com/images/dev-hoon/post/5c40e482-d720-495c-ac6b-6a85fe3ffbd8/image.png" alt=""></p>
<h3 id="부팅시-실행할-파일-설정">부팅시 실행할 파일 설정</h3>
<p>부팅시 자동으로 가장 늦게 되는 파일은 <code>.bashrc</code>라는 파일입니다. 
해당 파일에서 마지막 줄에 실행할 파일을 추가해주자!
수정이 가능하도록 백그라운드 실행으로 파일을 실행시켜보자! 
<code>python3 [실행할 파일명]&amp;</code> : &amp; =&gt; 백그라운드 실행</p>
<h3 id="환경변수-고정">환경변수 고정</h3>
<p>실행 파일들을 오류없이 실행시키기 위한 환경변수는 파일 실행 명령어 이전에 선언되어야 합니다.
따라서, 부팅시 제일 첫번째로 실행되는 파일인 <code>/etc/profile</code>에 환경변수를 선언해줄 것입니다.
<code>sudo vi /etc/profile</code>에서 마지막 줄에 <code>export GOOGLE_APPLICATION_CREDENTIALS=&quot;/home/pi/xxxxxxx-xxxxxxxxx.json&quot;</code>를 포함시키자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[IoT] AWS 연동]]></title>
            <link>https://velog.io/@dev-hoon/IoT-AWS-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@dev-hoon/IoT-AWS-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Thu, 19 May 2022 07:50:52 GMT</pubDate>
            <description><![CDATA[<h2 id="aws-란">AWS 란?</h2>
<p><code>AWS</code> : 클라우드에서 Node 디바이스들을 관리해준다. 다른 디바이스 끼리 통신 중간다리 역할</p>
<p><code>AWS IoT Core</code> : Core 로 부터 받은 데이터를 처리해주는 서비스로 IoT 디바이스를 AWS 에서 관리해준다. </p>
<h2 id="aws-세팅">AWS 세팅</h2>
<p><a href="https://aws.amazon.com/ko/console/">AWS 홈페이지</a>에서 <code>IAM 사용자</code>로  로그인합니다.
<code>IoT Core</code> 서비스 검색 후 실행
관리 &gt; 사물 &gt; 사물생성 &gt; 단일 사물 생성 &gt; 사물이름 입력 &gt; 인증서 자동 생성 &gt; 정책 생성 &gt; 정책 이름, 정책 효과 : 허용, 정책 작업/정책 리소스 : * &gt; 생성된 정책으로 사물 생성 &gt; 인증서 및 키 다운로드</p>
<p>보안 &gt; 인증서 &gt; 인증서 클릭후 작업 &gt; 정책연결 &gt; 알맞은 정책과 연결하기
인증서 클릭후 작업 &gt; 사물연결 &gt; 알맞은 사물과 연결하기</p>
<p>설정 &gt; 엔드포인트의 ARN 정보를 복사해두자!</p>
<blockquote>
<p>ARN(Amazon Resource Name) : 리소스를 유일하게 식별하는 이름</p>
</blockquote>
<h2 id="aws--node-red-연동">AWS &amp; Node-red 연동</h2>
<p><code>mqtt-out</code>을 더블클릭하여 설정해주자!
서버 새로 만들기 &gt; 서버 : ARN 포트 : 8883, 사용 TLS 클릭 후 연필 &gt; 인증서, 비밀기(private), CA인증서(CA1) 추가하고 변경 후 완료하기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[IoT] Node-RED]]></title>
            <link>https://velog.io/@dev-hoon/IoT-Node-RED</link>
            <guid>https://velog.io/@dev-hoon/IoT-Node-RED</guid>
            <pubDate>Wed, 18 May 2022 06:38:03 GMT</pubDate>
            <description><![CDATA[<h2 id="node-red-란">Node-RED 란?</h2>
<p>IoT 연결을 시각화 도구로 연결가능한 개발 도구로 자바스크립트 언어를 지원, 플로 flow는 JSON 으로 저장한다. <a href="https://nodejs.org/en/">Node.js</a>를 설치해두어야 한다.</p>
<h3 id="node-red-설치">Node-RED 설치</h3>
<p><code>npm install -g node-red</code>를 cmd창에 입력하여 설치한다. 그 후 <code>node-red</code>를 통해 실행시켜보자! 그 후 브라우저 창에 <code>http://127.0.0.1:1880</code>로 접속해보자!</p>
<h2 id="dash-board">Dash board</h2>
<h3 id="dashboard-란">Dashboard 란?</h3>
<p><code>Dashboard</code> : 하나의 화면에 다양한 정보를 중앙 집중적으로 관리하고 찾을 수 있도록 하는 사용자 인터페이스 (UI) 기능을 포함하는 보드</p>
<h3 id="설치">설치</h3>
<p>cmd 창에 <code>npm install -g node-red-dashboard</code>를 통해 dashboard 메뉴 추가합니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[IoT] MQTT]]></title>
            <link>https://velog.io/@dev-hoon/IoT-MQTT</link>
            <guid>https://velog.io/@dev-hoon/IoT-MQTT</guid>
            <pubDate>Tue, 17 May 2022 08:37:49 GMT</pubDate>
            <description><![CDATA[<h2 id="mqtt-란">MQTT 란?</h2>
<p><code>MQTT</code>(Message Queing Telemetry Transport) : 서버로 데이터를 보낼때 자주 사용하는 프로토콜이다. <code>임베디드</code>에서의 저성능 환경에서 <code>작은 기기</code>들이 지연 및 손실을 방지하는 <code>안정적인 통신</code>이 가능토록 하는 프로토콜</p>
<h3 id="특징">특징</h3>
<p>실시간, 최소 전력, 신뢰성 있는 <code>비동기적</code> 메시징 방식, <code>다중 접속</code>을 지원한다.
<code>발행 구독 패턴</code> : 발행(Publish), 구독(Subscribe) 패턴의 TCP/IP 기반 경량 메시지 프로토콜</p>
<ul>
<li>발행자 : 센서노드 - 중개자로 데이터 전송한다.</li>
<li>구독자 : 중개자를 통해서 데이터를 받는다.</li>
<li>중개자 : 센서노드에서 주기적으로 데이터를 저장한다. ex) Mosquitto</li>
</ul>
<p>QoS : Quality of Service로 메세지 전달 방식의 품질을 결정한다. </p>
<h3 id="주제">주제</h3>
<p>주제는 무엇이든 가능하며, 계층적으로 나타낼수 있다. <code>/</code>를 이용하여 레벨별 표현 가능하다. </p>
<ol>
<li>단일 레벨 : +</li>
<li>다중 레벨 : #</li>
</ol>
<h2 id="mosquitto">Mosquitto</h2>
<p>중재자 서버 역할을 하는 프로그램
<a href="https://mosquitto.org/download/">설치</a>에서 윈도우 버전 다운받기!</p>
<p>고급 시스템 설정 &gt; 고급 &gt; 환경변수 &gt; 시스템 변수에서 Path &gt; 추가 후 <code>C:\Program Files\mosquitto</code> 붙여넣기</p>
<p>작업관리자 &gt; 서비스 &gt; mosquitto를 중지됨 -&gt; 시작 으로 변경</p>
<h3 id="실습">실습</h3>
<p>구독부터 하고 발행을 해야 구독한 사람이 받을 수 있다.
<code>발행자</code> : mosquitto_pub -t &quot;myhome/firstfloor/livingroom/temperature&quot; -m 23.4
<code>구독자</code> : mosquitto_sub -t &quot;myhome/firstfloor/livingroom/temperature&quot;</p>
<p>주제 구조를 변경하여 다른 구독자에게 데이터를 전송할 수 있다. </p>
<h3 id="주제-작성-팁">주제 작성 팁</h3>
<p>선행 슬래시 사용 x
공백 사용 x
주제는 짧고 간결하게
ASCll 문자만 사용 한글 x</p>
<h3 id="와일드-카드">와일드 카드</h3>
<p>+와 #을 활용해보자!
#은 항상 끝에서만 사용가능하다.</p>
<p>와일드 카드는 <code>구독자</code> 입장에서 사용가능하다.</p>
<p><code>+</code> : 단일레벨 - 1개의 주제를 대체한다.
<code>구독자</code> : mosquitto_sub -t &quot;myhome/firstfloor/+/temperature&quot;
<code>발행자</code> : mosquitto_pub -t &quot;myhome/firstfloor/livingroom/temperature&quot; -m 23.4</p>
<p><code>가능</code> : mosquitto_pub -t &quot;myhome/firstfloor/kitchen/temperature&quot; -m 23.4
<code>불가능</code> </p>
<pre><code class="language-bash">mosquitto_pub -t &quot;myhome/firstfloor/livingroom/brightness&quot; -m 23.4
mosquitto_pub -t &quot;myhome/secondfloor/livingroom/temperature&quot; -m 23.4
mosquitto_pub -t &quot;myhome/firstfloor/kitchen/livingroom/temperature&quot; -m 23.4
</code></pre>
<p><code>#</code> : 다중레벨 - 하위의 모든 주제를 포함한다.
<code>구독자</code> : mosquitto_sub -t &quot;myhome/firstfloor/#&quot;
<code>발행자</code> : mosquitto_pub -t &quot;myhome/firstfloor/livingroom/temperature&quot; -m 23.4</p>
<p><code>가능</code> : </p>
<pre><code class="language-bash">mosquitto_pub -t &quot;myhome/firstfloor/kitchen/temperature&quot; -m 23.4
mosquitto_pub -t &quot;myhome/firstfloor/livingroom&quot; -m 23.4</code></pre>
<p><code>불가능</code> :  mosquitto_pub -t &quot;myhome/secondfloor/kitchen/temperature&quot; -m 23.4</p>
<h2 id="라즈베리파이---mosquitto">라즈베리파이 - Mosquitto</h2>
<p>라즈베리파이 터미널 창에 <code>sudo apt install mosquitto</code>를 통해 모스키토를 설치하자!
<code>sudo apt install mosquitto-clients</code> : 브로커 동작 테스트 프로그램 설치 </p>
<p>데이터 구독 빛 발행 명령어가 윈도우와는 살짝 다르다.</p>
<h3 id="명령어">명령어</h3>
<p><code>구독</code> : mosquitto_sub -t &quot;everland/animals/tiger&quot; -h 127.0.0.1
<code>발행</code> : mosquitto_pub -t everland/animals/tiger&quot; -h 127.0.0.1 -m aaa</p>
<blockquote>
<p>-h는 IP주소로 동일한 PC에서 테스트하는거면 생략 해도 정상 작동한다.</p>
</blockquote>
<p>발행 입장에서 <code>-m 옵션 대신 -l 옵션</code>을 주면 채팅형식으로 계속 데이터를 전송할 수 있다.</p>
<h2 id="윈도우를-브로커로-사용하기">윈도우를 브로커로 사용하기</h2>
<ol>
<li><p>워드패드를 <code>관리자 권환</code>으로 실행 후 &gt; <code>mosquitto.conf</code> 파일 열기 &gt; 맨 하단에 다음 두줄 추가</p>
<pre><code>bind_address [본인 IP] =&gt; ip는 cmd &gt; ipconfig를 통해 확인하기 
allow_anonymous true</code></pre></li>
<li><p>윈도우 검색 &gt; 고급 보안이 포함된 ~ &gt; 인바운드 규칙 &gt; 새규칙 &gt; 포트 &gt; 특정 포트 &gt; 1883 &gt; 이름 : mosquitto로 설정</p>
</li>
<li><p>cd / &gt; cd &quot;Program Files&quot; /mosquitto &gt; <code>mosquitto -c mosquitto.conf -v</code>로 mosquitto 서버 실행시키기</p>
</li>
</ol>
<h3 id="구독-발행-명령어">구독 발행 명령어</h3>
<p><code>구독</code> : mosquitto_sub -h [설정한 브로커 IP] -t &quot;test&quot; -p 1883
<code>발행</code> : mosquitto_pub -h [설정한 브로커 IP] -t &quot;test&quot; -m hello -p 1883</p>
<p>브로커 서버를 살펴보면, 데이터 <code>용량</code>도 끝에 같이 표시된다.</p>
<h2 id="esp를-발행자로-사용하기">ESP를 발행자로 사용하기</h2>
<p>아두이노 IDE의 라이브러리 관리자에서 <code>espmqttclient</code> 설치 , install all</p>
<p>아두이노 IDE에서 해당 코드에서 <code>ssid, pw, ip</code>등을 변경하여 업로드 해보기</p>
<pre><code class="language-c">#include &quot;EspMQTTClient.h&quot;

EspMQTTClient client(
  &quot;본인 ssid&quot;,
  &quot;본인 pw&quot;,
  &quot;브로커 IP&quot;,   // MQTT Broker server ip
  &quot;MQTTUsername&quot;,   // Can be omitted if not needed
  &quot;MQTTPassword&quot;,   // Can be omitted if not needed
  &quot;TestClient&quot;,     // Client name that uniquely identify your device
  1883              // The MQTT port, default to 1883. this line can be omitted
);

int abc = 10;

void setup() {
  Serial.begin(9600);
  client.enableDebuggingMessages(); // Enable debugging messages sent to serial output
  client.enableHTTPWebUpdater(); // Enable the web updater. User and password default to values of MQTTUsername and MQTTPassword. These can be overrited with enableHTTPWebUpdater(&quot;user&quot;, &quot;password&quot;).
  client.enableLastWillMessage(&quot;TestClient/lastwill&quot;, &quot;I am going offline&quot;);  // You can activate the retain flag by setting the third parameter to true
}

// This function is called once everything is connected (Wifi and MQTT)
// WARNING : YOU MUST IMPLEMENT IT IF YOU USE EspMQTTClient
void onConnectionEstablished() {
  // Subscribe to &quot;mytopic/test&quot; and display received message to Serial
  client.subscribe(&quot;mytopic/test&quot;, [](const String &amp; payload) { Serial.println(payload); });

  // Subscribe to &quot;mytopic/wildcardtest/#&quot; and display received message to Serial
  client.subscribe(&quot;mytopic/wildcardtest/#&quot;, [](const String &amp; topic, const String &amp; payload) { Serial.println(topic + &quot;: &quot; + payload); });

  // Publish a message to &quot;mytopic/test&quot;
  client.publish(&quot;mytopic/test&quot;, &quot;This is a message&quot;); // You can activate the retain flag by setting the third parameter to true

  // Execute delayed instructions
  client.executeDelayed(5 * 1000, []() { client.publish(&quot;mytopic/test&quot;, &quot;This is a message sent 5 seconds later&quot;); });
}

void loop() {



  client.loop();

  // You can activate the retain flag by setting the third parameter to true  
  client.publish(&quot;mytopic/test&quot;, String(abc));

  abc++;

  delay(2000);
}
</code></pre>
<p>cmd 창에서 <code>mosquitto_sub -h [브로커 IP] -t &quot;mytopic/test&quot; -p 1883</code>로 구독하기</p>
<h2 id="라즈베리파이를-중재자로-사용하기">라즈베리파이를 중재자로 사용하기</h2>
<p><code>sudo vi /etc/mosquitto/mosquitto.conf</code>에서 하단에 다음 두줄 추가</p>
<pre><code class="language-c">port 1883
allow_anonymous true</code></pre>
<p><code>ps -ef | grep mosquitto</code> 에서 확인한 id(앞)을 기억하여 <code>sudo kill [id]</code> 후 <code>sudo mosquitto -v</code>를 통해 키자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[IoT] 클라우드 플랫폼]]></title>
            <link>https://velog.io/@dev-hoon/IoT-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%ED%94%8C%EB%9E%AB%ED%8F%BC</link>
            <guid>https://velog.io/@dev-hoon/IoT-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%ED%94%8C%EB%9E%AB%ED%8F%BC</guid>
            <pubDate>Tue, 17 May 2022 06:28:51 GMT</pubDate>
            <description><![CDATA[<h2 id="thingspeak-란">ThingSpeak 란?</h2>
<p><a href="https://thingspeak.com/">ThingSpeak</a>는 클라우드 기반으로 동작되는 데이터 분석 솔류션이다. 
채널생성 및 필드 생성하기
API Keys 탭에서 Write Key 복사하기
channel settings에서 channel ID도 복사하기</p>
<p>아두이노 IDE 라이브러리 매니저에서 <code>thingspeak</code> 설치
예제 &gt; thingspeak &gt; esp32 &gt; writesinglefield 예제 띄우기 </p>
<p>ssid, pw, channel ID, write API key를 헤더에 입력하여 esp32에 업로드 시켜보면, 웹사이트에서 그래프가 20초마다 생길 것이다.</p>
<blockquote>
<p>delay(20000) - 20초보다 짧게 주면 돈을 더 줘야하기 때문에, delay(20000)으로 고정하자!</p>
</blockquote>
<h2 id="firebase">Firebase</h2>
<p><a href="https://console.firebase.google.com/?pli=1">Firebase</a> : <code>구글</code>에서 제공하는 플랫폼 서비스로 <code>Cloud Database</code> 구축 용도이다. 개발자가 OS 에 관계없이 앱을 만들수 있으며 앱에서 필요로 하는 기능을 <code>API 형태로 제공</code>한다.</p>
<p><code>프로젝트</code> 생성 후 <code>빌드 &gt; Firestor Database</code>로 이동후 <code>데이터베이스 만들기 &gt; 테스트 모드에서 시작</code> 으로 데이터베이스 생성하기
<code>빌드 &gt; Realtime Database &gt; DB 만들기 &gt; 테스트 모드</code>로 생성하기
생성한 Realtime Database의 <code>주소</code> 복사하기
<code>프로젝트 설정 &gt; 서비스 계정 &gt; DB 비밀번호</code>에서 비밀번호 복사하기</p>
<p>아두이노 IDE 라이브러리 매니저에서 <code>Firebase ESP 32 Client</code> 설치
예제 &gt; Firebase ESP 32 Client &gt; esp32 &gt; writesinglefield 예제 띄우기 </p>
<pre><code class="language-c">#include &lt;WiFi.h&gt;
#include &lt;FirebaseESP32.h&gt;

#define FIREBASE_HOST &quot;haha&quot;       // 데이터베이스 주소
#define FIREBASE_AUTH &quot;hoho&quot;    // 데이터베이스 키

FirebaseData firebaseData;
FirebaseJson json;

#define WIFI_SSID &quot;huhu&quot;
#define WIFI_PASSWORD &quot;hehe&quot;

void setup()
{
  Serial.begin(115200);

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print(&quot;Connecting to Wi-Fi&quot;);
  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(&quot;.&quot;);
    delay(300);
  }
  Serial.println();
  Serial.print(&quot;Connected with IP: &quot;);
  Serial.println(WiFi.localIP());
  Serial.println();

  Serial.printf(&quot;Firebase Client v%s\n\n&quot;, FIREBASE_CLIENT_VERSION);
  Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
}

void loop()
{
    String str= &quot;hello world!&quot;;

    json.set(&quot;payload&quot;, str);
    Firebase.pushJSON(firebaseData, &quot;/response&quot;, json); // 보내기, 송신
    Serial.println(&quot;PATH: &quot; + firebaseData.dataPath());
    Serial.println(&quot;PUSH NAME: &quot; + firebaseData.pushName());    

    delay(2000);
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[IoT] RPI와 ESP32 연결]]></title>
            <link>https://velog.io/@dev-hoon/IoT-RPI%EC%99%80-ESP32-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@dev-hoon/IoT-RPI%EC%99%80-ESP32-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Tue, 17 May 2022 05:31:53 GMT</pubDate>
            <description><![CDATA[<h2 id="기성-프로그램을-이용하여-서버-클라이언트-연결">기성 프로그램을 이용하여 서버-클라이언트 연결</h2>
<p><a href="http://www.itdsn.kr/binfo/binfoview.asp?bcode=pds&amp;n=18">TCP/IP 테스트 프로그램 설치</a>
해당 프로그램은 서버에 <code>다중</code> 클라이언트 접속이 안된다.</p>
<p><code>cmd &gt; ipconfig &gt; 이더넷의 IPv4 주소 확인</code></p>
<p>다운 받은 프로그램에는 서버와 클라이언트로 나누어지는데, 서버 킨 후 확인한 <code>IP</code>는 클라이언트 주소에 입력한다. 서버에서 설정한 <code>포트</code>를 클라이언트에서 동일시하게 맞춰줘야 연결이 된다.
text등이 정상적으로 보내진다면, 연결성공이다!</p>
<p>아두이노 IDE에서 <code>예제 &gt; WIFI &gt; WIFICLINET</code> 에서 <code>ssid, pw, host 주소, 포트</code> 등을 수정하여 설치한 프로그램에서 작동시킨 서버와 연결시켜보자!</p>
<h2 id="라즈베리파이를-서버로">라즈베리파이를 서버로</h2>
<p>다중 접속을 위해, 라즈베리파이에 웹서버를 열어서 접속한다. 기존에 제작한 웹소켓 코드를 이용하여 빌드해보자!
<code>gcc ./chat_serv.c -D_REENTRANT -o ./cserv -lpthread</code> 후 <code>./cserv 6000</code>을 통해 서버 열기</p>
<h2 id="데이터-관리">데이터 관리</h2>
<p>임베디드에서 데이터 전송은 <code>EOF, SOF</code> 를 같이 보내면 데이터가 깨지지 않는다.
데이터의 <code>특성</code>에 따라 <code>물리적</code> 저장 장치를 선택하는것이 좋다.</p>
<blockquote>
<p>고속용 : SRAM<br>고속동작 X, 대용량 : Nand Flash, SD CARD
소용량, 영구저장, 고속 x : 내부 EEPROM</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[IoT] ESP32 보드 실습]]></title>
            <link>https://velog.io/@dev-hoon/IoT-ESP32-%EB%B3%B4%EB%93%9C-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@dev-hoon/IoT-ESP32-%EB%B3%B4%EB%93%9C-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Tue, 17 May 2022 02:54:45 GMT</pubDate>
            <description><![CDATA[<h2 id="iot란">IoT란?</h2>
<p><code>IoT(Internet of Things)</code> : 각종 사물에 센서와 무선 통신 기능을 내장하여 인터넷에 연결하는 기술</p>
<p>활용 사례 : 홈 IoT, 스마트 팜, 웨어러블
단점 : 인터넷 보안 위험, 정전시 동작 오류
해결책 : 안정적인 IoT 서버, 보안강화</p>
<h2 id="esp32-보드">ESP32 보드</h2>
<p>사용할 보드 : ESP32
모든 보드가 센서 노드가 될수 있지만 , IoT 를 위해서라면 어찌됐든지 간에 <code>통신 기능</code>이 있어야 한는데 ESP32는 무선통신인 Wi-Fi 및 BLE를 지원한다.</p>
<h3 id="보드-테스트">보드 테스트</h3>
<p><a href="https://www.arduino.cc/en/donate/">윈도우용 스케치 프로그램(아두이노) 다운</a></p>
<p><code>내부 LED 테스트 코드</code></p>
<pre><code class="language-c">void setup() {  // 1회성 호출 - init 함수 역할
  pinMode(LED_BUILTIN, OUTPUT); // 출력으로 설정
}

void loop() {  // while과 같은 무한 루프
  digitalWrite(LED_BUILTIN, HIGH); // LED 켜기
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW); // LED 끄기
  delay(1000);
}</code></pre>
<p>ESP32는 아두이노에서 공식적으로 지원하는 보드가 아니기 때문에, 보드를 따로 지정해줘야한다.</p>
<h3 id="보드-지정">보드 지정</h3>
<p><code>파일 &gt; 환경설정 &gt; 추가적인 보드 매니저 URLS</code>에 <code>https://dl.espressif.com/dl/package_esp32_index.json</code>추가하기
<img src="https://velog.velcdn.com/images/dev-hoon/post/36a7814f-ab18-40cd-88c9-0eabec890204/image.png" alt=""></p>
<p><code>툴 &gt; 보드 &gt; 보드 매니저 &gt; esp32</code> 겁색 후 설치
<img src="https://velog.velcdn.com/images/dev-hoon/post/54b3d198-df6d-4267-a874-143f0ad39b81/image.png" alt=""></p>
<p><code>툴&gt; 보드 &gt; ESP32 Arduino &gt; Node32S</code> 선택과 장치관리자에서 확인한 <code>포트</code>를 알맞게 설정 후 코드를 빌드 및 업로드를 해보면, 파란색 <code>LED</code>가 깜빡일 것입니다.</p>
<blockquote>
<p>빌드 단축키 : Crtl + R , 업로드 단축키 : Crtl + U</p>
</blockquote>
<h3 id="예전-버전의-esp32">예전 버전의 ESP32</h3>
<p>다음 이미지와 동일한 ESP32 보드를 사용한다면, <code>업로드할때 오류</code>가 발생한다.
해결책으로는 예전 버전의 ESP32에 업로드 할 때에는 <code>로그 발생시 IO0버튼</code>을 3초간 눌러줘야 한다.
<img src="https://velog.velcdn.com/images/dev-hoon/post/1163314a-41d6-48f9-bb31-3afe7bbc9bee/image.png" alt=""></p>
<h2 id="외부-io-연결하기">외부 I/O 연결하기</h2>
<p><img src="https://velog.velcdn.com/images/dev-hoon/post/419d86ef-788f-4383-a28a-2e5dee5a4afc/image.png" alt=""></p>
<h2 id="pwm">PWM</h2>
<p>모든 핀이 PWM을 지원하지 않는다. 데이터 시트를 확인해보자</p>
<h2 id="웹서버-제작">웹서버 제작</h2>
<p>ESP32로 웹서버도 제작할 수 있습니다. <code>파일 &gt; 예제 &gt; Webserver &gt; HelloSever</code> 코드에서 본인의 SSID와 PW를 입력하여 웹서버에 접속할 수 있습니다. 하단에 함수를 수정하여 API를 통해 추가적인 결과를 얻을 수 있습니다. 웹사이트 이미지 URL을 통해 이미지 삽입 또한 가능하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PJT] RC카]]></title>
            <link>https://velog.io/@dev-hoon/RC%EC%B9%B4</link>
            <guid>https://velog.io/@dev-hoon/RC%EC%B9%B4</guid>
            <pubDate>Fri, 13 May 2022 06:56:02 GMT</pubDate>
            <description><![CDATA[<h2 id="나사-치수">나사 치수</h2>
<p><code>나사산</code>의 크기는 직경의 크기와 비례된다. 나사산은 범용적으로 쓰이기 의해 직경의 크기에 비례하여 고정되었다. 같은 직경의 크기의 나사의 모든 나사산은 동일하다.
<img src="https://velog.velcdn.com/images/dev-hoon/post/ea6c9a3a-4aa0-4d64-b678-2304dc461886/image.png" alt=""></p>
<h2 id="나사-머리-모양">나사 머리 모양</h2>
<p><img src="https://velog.velcdn.com/images/dev-hoon/post/9765621d-c8c8-4979-927e-b696f6fc1669/image.png" alt=""></p>
<p>체결하려는 <code>장소</code>에 따라 사용하는 머리 모양이 달라집니다.
<code>접시머리 나사</code>는 홈이 나있는 곳에 써서 체결 시 튀어나오지 않아 평평하게 체결하기 위해 쓴다.</p>
<h2 id="큰기어">큰기어</h2>
<p>큰기어 -&gt; 작은기어 : 속도를 높인다.
1바퀴 -&gt; 3바퀴 : 힘이 1/3로 줄어들지만, 3배 만큼 속도가 높아진다.</p>
<p>키 : 축과 기어가 같이 돌어가게 고정시키는 나사 구멍</p>
<h2 id="베어링">베어링</h2>
<p>베어링 : 안쪽 원과 바깥 쪽 원 사이에 구슬같은 회전에 용이하게 하는 것이 포함되어 있다.
<img src="https://velog.velcdn.com/images/dev-hoon/post/aa459ed8-eda9-44a0-9227-75f4c16e0d9b/image.png" alt=""></p>
<h2 id="조향장치핸들">조향장치(핸들)</h2>
<p><code>서보모터</code>를 통해 조향장치를 구현할 것입니다. 포텐셜모터가 엔코더 역할을 한다. <code>180도</code> 범위를 갖는다.</p>
<h2 id="너클">너클</h2>
<p>바퀴의 방향을 바꾸는 역할</p>
<h2 id="충전형-배터리">충전형 배터리</h2>
<p>18650 : 직경 18mm 길이 650mm 3.6V 2600mAh 리튬 이온 배터리 
3개를 연결해서 11.8v로 약 12v를 전원어탭터에 전원을 인가합니다.
전원 어댑터에 인가된 전압은 라즈베리파이와 모터 드라이버에 각각 <code>5V</code>씩 인가된다.</p>
<h2 id="모터-드라이버">모터 드라이버</h2>
<h3 id="모터-드라이버-사용-이유">모터 드라이버 사용 이유</h3>
<p>라즈베리파이에서 공급할 수 있는 <code>전류</code>는 너무 한정되어있다. 하지만, 차량용 모터를 동작시키기 위해서는 전류가 턱없이 부족합니다. 또한, DC모터를 <code>역으로 회전</code>시키기위해서 배선을 일일히 바꿔줘야하는 번거롭습니다. </p>
<p><img src="https://velog.velcdn.com/images/dev-hoon/post/a18ed28d-0e50-4d5f-b206-c881b9ce4379/image.png" alt=""></p>
<h2 id="i2c-통신-연결">I2C 통신 연결</h2>
<p>모터 드라이버와 라즈베리파이를 I2C을 통해 연결합니다.</p>
<h3 id="i2c란">I2C란?</h3>
<p>I2C는 여러 장치를 동시에 연결하여 제어할 수 있지만, 장치 구분 및 송수신 판별로 인해 <code>속도 및 효율이 떨어진다</code>. 
클럭과 데이터 전송 선만으로 연결이 가능할만큼 <code>단순</code>하다.
장치 구분을 위해 각 Slave마다 <code>주소</code>를 갖는다. 데이터에 주소가 실린다.</p>
<blockquote>
<p><code>sudo i2cdetect -y 1</code> 명령어를 통해 사용할 모터드라이버의 주소는<code>0x6f</code>임을 확인할 수 있다.</p>
</blockquote>
<h3 id="i2c-설정-및-설치">I2C 설정 및 설치</h3>
<p><code>sudo raspi-config</code> 명령어를 통해 설정창을 열고, <code>interface options &gt; i2c &gt;켜기</code>로 I2C를 가능토록 설정합니다. </p>
<p>모터드라이버 라이브러리 설치 : 
<code>wget http://wiki.geekworm.com/images/a/ac/Raspi-MotorHAT-python3.zip</code>
압축해제 : <code>unzip Raspi-MotorHAT-python3.zip</code></p>
<p><code>cd Raspi-MotorHAT-python3</code>를 통해 확인해보자!
test 예제를 제외한 필수적인 라이브러리를 홈 디렉토리로 옮겨준다.  =&gt; <code>cp Raspi_* ~/</code></p>
<p>파이썬에서 I2C를 사용하기 위한 툴 설치 : <code>sudo apt-get install python-smbus i2c-tools</code></p>
<h2 id="rc카-이동-코드">RC카 이동 코드</h2>
<p><code>DC 모터 제어</code> : 이동 제어</p>
<pre><code class="language-py"># motor move
from Raspi_MotorHAT import Raspi_MotorHAT, Raspi_DCMotor

mh = Raspi_MotorHAT(addr = 0x6f)
motor = mh.getMotor(2)  # because of dc motor connected with M2

try:
    while True:
        motor.setSpeed(125) # speed limit : 0~255
        motor.run(Raspi_MotorHAT.FORWARD) # FORWARD
        # motor.run(Raspi_MotorHAT.BACKWARD) # BACKWARD

finally: 
    motor.run(Raspi_MotorHAT.RELEASE) # STOP
</code></pre>
<h2 id="pwm-제어">PWM 제어</h2>
<p>디지털 신호를 강약으로 나타내는 방법</p>
<h3 id="servo-제어">Servo 제어</h3>
<p>Pulse의 폭에 따라 특정 각도로 움직인다.
<img src="https://velog.velcdn.com/images/dev-hoon/post/67e53bf0-f9e0-4268-b9ba-b3e8225cdd78/image.png" alt=""></p>
<h3 id="코드">코드</h3>
<p><code>Servo 모터 제어</code> : 방향 제어</p>
<pre><code class="language-py"># servo motor control
from Raspi_MotorHAT import Raspi_MotorHAT

mh = Raspi_MotorHAT(0x6F) # motor-HAT i2c address
servo = mh._pwm

servo.setPWMFreq(60) # 사용할 서보모터의 주파수

# because of sevro motor is connected with #1
# 1주기 2^12 = 4096
# 0degree = 200, 90degree = 400, 180degree = 600 
servo.setPWM(1, 0, 400)  # 3개의 파라미터 : 장치번호, 시작, 끝</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[커널] 부트로더]]></title>
            <link>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-%EB%B6%80%ED%8A%B8%EB%A1%9C%EB%8D%94</link>
            <guid>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-%EB%B6%80%ED%8A%B8%EB%A1%9C%EB%8D%94</guid>
            <pubDate>Thu, 12 May 2022 00:51:53 GMT</pubDate>
            <description><![CDATA[<h2 id="bootloader">Bootloader</h2>
<h3 id="bootloader-란">Bootloader 란</h3>
<p><code>Bootloader</code>: 부팅시 운영체제 사용되기 이전에 동작되는 프로그램으로 Disk 에 저장되어 있는 <code>운영체제</code>를 실행 시키는 역할
커널이 동작되기 전 준비를 마치고, <code>커널을 메모리에 적재</code> 후 제어권을 넘기는 프로그램이므로, <code>메모리 직접 접근</code>이 가능하다.
부트 스트랩 로더 ” 를 줄여 부트스트랩 or 부트로더 라고 한다. 라즈베리파이는 자체 부트로더 사용 중이다.</p>
<p>리눅스에서는 <code>U-Boot</code>이라는 부트로더를 사용한다. </p>
<h3 id="수행-기능">수행 기능</h3>
<ol>
<li>다중 OS 부팅 기능</li>
<li>장치 제어 및 테스트 기능</li>
</ol>
<h2 id="kernel-image">Kernel Image</h2>
<p><code>Image</code> : 램에 그대로 올라가면 실행 가능해지도록 만들어진 <code>Binary File</code>
<code>Linux Kernel Image</code> : 리눅스 소스코드를 빌드 후 , 즉시 램에 올라가면 동작되도록 만들어진 Binary File</p>
<p>리눅스 커널 이미지는 <code>압축</code>되어 관리된다. 부트로더가 압축을 풀어 메모리에 적재한다</p>
<h3 id="커널-이미지-종류">커널 이미지 종류</h3>
<ol>
<li>zImage : gzip 으로 압축된 Kernel Image</li>
<li>bzImage : 빅 zImage, 파일 크기(약 1MB 이상)가 큰 Kernel Image 를 뜻함</li>
</ol>
<h2 id="vim-bootstrap">Vim-bootstrap</h2>
<p><a href="https://github.com/editor-bootstrap/vim-bootstrap">설치 가이드</a>
<a href="https://vim-bootstrap.com/#tagline">파일 다운 사이트</a> : 대표적인 vim 설정 파일
vim 설정 파일 vimrc에 복붙 후 vi를 통해 설치하자!</p>
<blockquote>
<p>라즈베리파이에서 사용가능하기 위해서는 snippets 부분을 삭제해주면 에러없이 사용가능하다.</p>
</blockquote>
<h2 id="cmos하드웨어">CMOS(하드웨어)</h2>
<p><code>CMOS Chipset</code> : 메모리 크기, 부팅순서, HW 구성 정보 등과 같은 CMOS Data(설정값)를 저장한다. </p>
<h2 id="bios프로그램">BIOS(프로그램)</h2>
<p><code>BIOS</code> : 기본적인 I/O 를 위한 Firmware (Basic I/O System), 컴퓨터 <code>부팅</code>시 바로 BIOS(Firmware) 가 동작을 시작한다. <code>CMOS의 설정값</code>들을 변경 가능하다</p>
<h2 id="post">POST</h2>
<p><code>POST</code>(Power-on Self-Test) : BIOS에서 Power 를 켜자마자 주변장치들을 검사하는 과정으로 Log Message 가 출력된다.</p>
<h2 id="uefi">UEFI</h2>
<p><code>UEFI</code>(유이파이) : Unified Extensible Firmware Interface로 BIOS 를 대체하는 그래픽 화려한 Firmware이다. <code>2.2TB 이상 디스크 사용</code>을 위한 GPT 지원을 한다.</p>
<p><code>GPT</code> : GUID 파티션 테이블로 <code>2.2TB 이상 디스크 사용</code>을 지원한다.</p>
<h2 id="grub2">GRUB2</h2>
<p><code>GRUB2</code> : 리눅스 배포판(우분투)에서 가장 자주 사용하는 GUI 부트로더</p>
<h2 id="u-boot">U-BOOT</h2>
<p><code>U-Boot(Universal Boot Loader)</code> : ARM(임베디드)에서 리눅스를 쓸 때 가장 자주 사용하는 부트로더로 USB, TCP/IP, Flash 제어 가능하다.</p>
<h2 id="임베디드-부팅-순서">임베디드 부팅 순서</h2>
<ul>
<li>0, 1 단계 : 칩셋 사 제공, 제어권을 넘긴다.</li>
<li>2 단계 : Bootloader (u-boot) : 메모리 직접 접근하여 장치 테스트 가능, 리눅스 커널을 적재하여 리눅스 부팅</li>
<li>Linux Kernel 실행</li>
</ul>
<h2 id="grub-다루기">GRUB 다루기</h2>
<p><code>cd /etc/default</code>에서 <code>grub</code>을 sudo vi로 열고 내용 살짝 바꾸기
style = menu를 변경하여 메뉴창을 띄우고, 10초동안 키 입력 없을 시 넘어가는 설정
<img src="https://velog.velcdn.com/images/dev-hoon/post/ef743ae4-9e73-4ffd-988c-840026026dd8/image.png" alt="">
<code>sudo update-grub</code>을 통해 수정 사항 저장시키면 <code>/boot/grup</code> 경로에 <code>grup.cfg</code>라는 환경설정 파일이 생성된다.</p>
<h3 id="추가-명령어">추가 명령어</h3>
<p><code>sudo reboot</code>를 통해 GRUB을 실행시킬 수 있다. GRUB이 실행된 후 입력을 가하면 다음 스텝으로 넘어가지 않고 고정된다.
e : grub.cnf 내용 확인
c : command모드로 진입</p>
<h3 id="command-모드">Command 모드</h3>
<p><code>help</code> : 내가 사용가능한 명령어 리스트 출력, Sroll 기능이 작동하지 않아 짤리는 부분은 확인하지 못한다.
<code>set pager=1</code>을 입력후 <code>help</code>명령어를 입력해보면 짤리는 부분도 <code>page down</code>으로 확인가능하다.</p>
<h3 id="해상도-변경하기">해상도 변경하기</h3>
<p><code>sudo vi /etc/default/grub</code>에서 GRUB_GFXMODE=1024x768로 주석을 풀고 변경해준다.
<code>sudo update-grub</code> 후 <code>sudo reboot</code>해준다.</p>
<h3 id="파일-시스템-탐색">파일 시스템 탐색</h3>
<p><code>cat /boot/grub/grub.cfg</code>을 통해 파일 열기 가능, <code>ls</code> 명령어도 사용가능</p>
<h3 id="직접-메모리에-접근">직접 메모리에 접근</h3>
<p><code>부트로더</code>이기 때문에, 직접 메모리 접근 가능하다.
쓰기 : <code>write_byte 0x0000 1</code> 
읽기 : <code>read_byte 0x0000</code> =&gt; 0x1</p>
<p><img src="https://velog.velcdn.com/images/dev-hoon/post/cd32b4fe-bdc7-41a7-8b00-6bc04ac855ec/image.png" alt=""></p>
<h2 id="ssh무선-vs-uartserial유선">SSH(무선) vs UART(serial,유선)</h2>
<p><code>SSH</code> : 랜카드 없으면 접근 불가능, 부팅 이후에 접근 가능하기 때문에 부트로더 제어 불가능
<code>UART</code> : 개발하기 불편할 정도로 느림</p>
<h2 id="u-boot-실습">U-Boot 실습</h2>
<p>우선적으로, 라즈베리파이의 Serial Enable로 설정 후 해당 <a href="https://www.ftdichip.com/Drivers/VCP.htm">드라이버</a>를 윈도우에 설치해줘야합니다.
전원을 끊고, 라즈베리파이와 <code>: FT232BL</code> 기기와 핀맵을 통해 연결합니다. 
5V 연결
GND 연결
RX 는 TX 에 연결하기
TX 는 RX 에 연결하기
<img src="https://velog.velcdn.com/images/dev-hoon/post/9e7daf3a-4e37-4fe2-8a64-968db53b757f/image.png" alt=""></p>
<p>그 후, 전원을 가하고, PC와 FT232BL 기기와 연결합니다.</p>
<p>Mobaxterm에서 세션을 생성합니다.
<img src="https://velog.velcdn.com/images/dev-hoon/post/855e7f23-2444-4075-9e57-8f44ee022492/image.png" alt="">
그 후 아이디와 비밀번호는 모두 라즈베리파이 기본 ID : pi, pw : raspberry 로 입력합니다.</p>
<h3 id="설치">설치</h3>
<ol>
<li><p>U-boot 빌드를 위한 필수 도구 설치 
<code>sudo apt install git vim gcc bc bison flex libssl-dev make libc6-dev libncurses5-dev crossbuild-essential-armhf</code></p>
</li>
<li><p>u-boot 소스코드 다운로드
<code>git clone https://github.com/u-boot/u-boot.git</code></p>
</li>
<li><p>config 파일 수정
<code>cd u-boot/</code> 후 <code>sudo vi config.mk</code>파일에 <code>CROSS_COMPILE := arm-linux-gnueabihf-</code> 변수를 추가한다.
<img src="https://velog.velcdn.com/images/dev-hoon/post/63bf83ef-773b-4e9c-a750-6ad07253a642/image.png" alt=""></p>
</li>
<li><p>.config 파일 생성
<code>sudo make rpi_4_32b_defconfig</code>를 입력한다.</p>
</li>
</ol>
<p><code>make menuconfig</code>로 편리하기 수정할 수 있는 창을 띄울 수 있다.</p>
<ol start="5">
<li><p>make로 빌드하기 -&gt; u-boot.bin 파일생성
<code>sudo make -j4</code> : 라즈베리파이 코어가 4개이므로 j4 사용</p>
</li>
<li><p>복사
<code>sudo cp u-boot.bin /boot</code></p>
</li>
</ol>
<p><code>cd /boot</code>에서 u-boot.bin이 있음을 확인해보자!</p>
<ol start="7">
<li><p>/boot 경로 내에 config.txt 수정
<code>sudo vi config.txt</code>에서 마지막 줄에 <code>kernel=u-boot.bin</code>를 추가하고 저장!</p>
</li>
<li><p>재부팅후 uart로 키기!</p>
</li>
</ol>
<h2 id="u-boot-command">U-Boot Command</h2>
<ol>
<li><p><code>echo</code> : 입력한 값이 그대로 출력된다.
enter을 계속 입력하면, 최근에 실행한 명령어가 실행된다.</p>
</li>
<li><p><code>printenv</code> : 환경변수들을 모두 출력한다.  변수 이름을 출력하려면 <code>${변수}</code> 입력하면 된다.
<img src="https://velog.velcdn.com/images/dev-hoon/post/eb47b240-bb2c-4033-a3d7-af95a2e3d9a2/image.png" alt="">
<code>botcmd 변수</code> : 특정 시간까지 입력이 되지 않으면 수행할 명령어들의 모음</p>
</li>
<li><p>ls 장치이름 [device 번호 ]]: 파티션 ] 경로 명령어 : <code>ls mmc 0:1 /</code>  특정 장치의 파일 내용을 볼 수 있다. </p>
</li>
<li><p>mmc 명령어 : Disk 제어 , R/W/Erase 가능</p>
</li>
</ol>
<ul>
<li>md 메모리 출력</li>
<li>nm : 메모리 수정 </li>
</ul>
<h2 id="dtb">dtb</h2>
<p>보드에 대한 <code>HW 정보</code>들을 Linux 커널에 전달하는 방법으로 <code>u-boot</code>에 의해 메모리에 Load 되어 Linux 커널에 전달되므로 상세 사항을 Linux 커널에 하드코딩할 필요 없다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[커널] interrupt]]></title>
            <link>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-interrupt</link>
            <guid>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-interrupt</guid>
            <pubDate>Wed, 11 May 2022 05:58:04 GMT</pubDate>
            <description><![CDATA[<h2 id="커널-헤더">커널 헤더</h2>
<p>app단에서 사용하는 헤더를 커널형으로 변환해서 사용할 수 있다.</p>
<blockquote>
<p>#include &lt;string.h&gt; =&gt; #include &lt;linux/string.h&gt;
와 같이 변경해서 사용하면, app에서 사용한 라이버러리와 동일하게 사용가능하다.</p>
</blockquote>
<h2 id="모듈-파라미터">모듈 파라미터</h2>
<p><code>insmod</code>를 통해 원하는 파라미터를 집어넣을 수 있다.
<code>kernel.c</code></p>
<pre><code class="language-c">static char *whom = &quot;world&quot;;
static int num = 1;    
module_param(num, int, S_IRUGO);    // 숫자형 변수에 파라미터를 받을 수 있다.
module_param(whom, charp, S_IRUGO);   // 문자형 변수에 파라미터를 받을 수 있다.</code></pre>
<p><code>insmod kernel c 100 0 num=10 whom = &quot;Jaehoon&quot;</code>으로 파라미터 받기!</p>
<h2 id="gpio-핀을-사용할때-메모리맵에-쉽게-접근하는-api">GPIO 핀을 사용할때 메모리맵에 쉽게 접근하는 API</h2>
<p>커널에서 사용 가능하며, GPIO에 쉽게 접근 가능한 API
헤더파일 : #include &lt;linux/gpio.h&gt;
사용 API :
gpio_direction_output(번호, 0)
gpio_set_value(번호, 0 또는 1)</p>
<h2 id="interrupt">Interrupt</h2>
<p><code>app.c</code></p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/ioctl.h&gt;
#include &lt;signal.h&gt;

void go_alert(int signum)
{
    for(int i=0; i&lt;5; i++)
    {
        printf(&quot;WARNING!!\n&quot;);
    }

}

int main()
{
    signal(SIGIO, go_alert);

    printf(&quot;OPEN FILE!!\n&quot;);
    int fd = open(&quot;/dev/nobrand&quot;, O_RDWR);
    if (fd == -1) {
        printf(&quot;FILE OPEN ERROR!!!\n&quot;);
        close(fd);
        return 0;
    }

    int pid = getpid();
    ioctl(fd, _IO(0,4), pid);

    while(1)
    {
        ioctl(fd, _IO(0,3), 0);
        usleep(300 * 1000);
    }


    // ioctl(fd, _IO(0, 3), 0);
    // ioctl(fd, _IO(0, 4), 0);

    printf(&quot;CLOSE FILE!!\n&quot;);
    close(fd);

    return 0;
}
</code></pre>
<p><code>kernel.c</code></p>
<pre><code class="language-c">#include &lt;linux/module.h&gt;
#include &lt;linux/fs.h&gt;
#include &lt;asm/io.h&gt;
#include &lt;linux/interrupt.h&gt;
#include &lt;linux/gpio.h&gt;

#define NOD_NAME &quot;nobrand&quot;
#define NOD_MAJOR 100

MODULE_LICENSE(&quot;GPL&quot;);

static uint32_t *BASE, *GPSEL2;
static uint32_t *GPIO_PUP_PDN_CNTRL_REG1;
static uint32_t *GPLEV0;

static int irq_num;
static int app_pid;

static struct kernel_siginfo sig_info;
static struct task_struct *task;

static int nobrand_open(struct inode *inode, struct file *filp) {
    printk( KERN_INFO &quot;OPEN!!!\n&quot;);
    return 0;
}

static int nobrand_release(struct inode *inode, struct file *filp) {
    printk( KERN_INFO &quot;CLOSE!!!\n&quot;);
    return 0;
}

static long nobrand_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int lev;
    switch (cmd)
    {
        case _IO(0, 3) :
            printk(KERN_INFO &quot;IO 3\n&quot;);
            lev = (*GPLEV0 &gt;&gt; 21) &amp; 0x1;
            printk( KERN_INFO &quot;GPIO21 LEV = %d\n&quot;, lev);
            break;
        case _IO(0, 4) :
            printk(KERN_INFO &quot;IO 4\n&quot;);
            app_pid = (int)arg;
            break;
    }

    printk( KERN_ALERT &quot;%d\n&quot;, cmd);
    return 0;
}
static struct file_operations fops = {
    .open = nobrand_open,
    .release = nobrand_release,
    .unlocked_ioctl = nobrand_ioctl
};

static irqreturn_t lets_go(int irq, void *data)
{
    printk( KERN_ALERT &quot;IT&#39;s ME\n&quot;);

    if(app_pid &gt; 0)
    {
        sig_info.si_signo = SIGIO;
        task = pid_task(find_vpid(app_pid), PIDTYPE_PID); // task 변수에 pid 정보 집어넣기
        send_sig_info(SIGIO, &amp;sig_info, task); // 신호를 app에 보낸다.
    }

    return IRQ_HANDLED;
}

static int nobrand_init(void) {

    int ret;

    BASE = (uint32_t *)ioremap(0xFE200000, 256);

    GPSEL2 = BASE + (0x08 / 4);
    *GPSEL2 = *GPSEL2 &amp; ~(0x7 &lt;&lt; 3);

    GPIO_PUP_PDN_CNTRL_REG1 = BASE + (0xe8 / 4);
    *GPIO_PUP_PDN_CNTRL_REG1 &amp;= ~(0x3 &lt;&lt; 10);
    *GPIO_PUP_PDN_CNTRL_REG1 |= (0x1 &lt;&lt; 10);

    GPLEV0 = BASE + (0x34 / 4);

    irq_num = gpio_to_irq(21); // 21번의 interrupt 감시
    // 버튼을 땔 때 lets_go라는 함수 콜백한다.
    ret = request_irq(irq_num, lets_go, IRQF_TRIGGER_RISING, &quot;interrupt&quot;, NULL);

    printk( KERN_INFO &quot;OK HELLO NOBRAND!!\n&quot;);
    if (register_chrdev(NOD_MAJOR, NOD_NAME, &amp;fops) &lt; 0) {
        printk( KERN_INFO &quot;ERROR!!! register error\n&quot;);
    }

    return 0;
}

static void nobrand_exit(void) {
    free_irq(irq_num, NULL);
    unregister_chrdev(NOD_MAJOR, NOD_NAME);
    printk( KERN_INFO &quot;BYE BYE\n\n&quot;);
}

module_init(nobrand_init);
module_exit(nobrand_exit);
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[커널] ioctl]]></title>
            <link>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-ioctl</link>
            <guid>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-ioctl</guid>
            <pubDate>Tue, 10 May 2022 06:49:57 GMT</pubDate>
            <description><![CDATA[<h2 id="ioctl">ioctl</h2>
<p><code>ioctl</code> : short for Input Output Control
ioctl(fd, cmd, argument) : 3 개의 파라미터</p>
<ol>
<li>File descriptor</li>
<li>cmd : 구분자</li>
<li>argument : 추가 정보</li>
</ol>
<h2 id="cmd">CMD</h2>
<p><code>CMD 구성</code> : 약속된 cmd 변수의 비트 단위 Format
하지만, 비트 연산하기 번거로워 Kernel 이 제공하는 <code>Macro 함수</code>를 사용하자!
<code>Macro 함수</code> : <code>_IO(type, number)</code> =&gt; type : 매직넘버 , number : 구분번호</p>
<p>ex) ioctl(fd, 3, 0) =&gt; ioctl(fd, _IO(3,0), 0)</p>
<h2 id="커널-로그-레벨">커널 로그 레벨</h2>
<p>커널 로그에 나타나는 메세지 색상을 바꿔줄 수 있다.
<img src="https://velog.velcdn.com/images/dev-hoon/post/52a084b7-c983-4f8e-a18a-0a8b564baf22/image.png" alt=""></p>
<h2 id="값-송수신">값 송수신</h2>
<h3 id="유저-스페이스-주소-커널에서-쓰면-위험하다">유저 스페이스 주소 커널에서 쓰면 위험하다</h3>
<p>app에서 데이터 전달</p>
<pre><code class="language-c">char buf[10] = &quot;Hi Jaehoon&quot;;
ioctl(fd, _IO(0, 3), buf);</code></pre>
<p>커널에서 데이터 수신</p>
<pre><code class="language-c">char *buf = (char *)arg;
printfk (KERN_INFO, &quot;%s\n&quot;, buf);</code></pre>
<p>커널 스페이스에서 유저 스페이스의 주소값을 건드리기 때문에 위험하다. 커널 패닉(=블루 스크린) 이 발생할 위험이 있다.</p>
<h3 id="값-복사">값 복사</h3>
<p>따라서, 보내오는 데이터를 복사 하여, 주소를 건드리지 않는 방법으로 사용해야한다.
<code>copy_to_user</code>와 <code>copy_from_user</code>을 사용한다.</p>
<p><code>app.c</code></p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/ioctl.h&gt;

int main()
{
    printf(&quot;OPEN FILE!!\n&quot;);
    int fd = open(&quot;/dev/nobrand&quot;, O_RDWR);
    if (fd == -1) {
        printf(&quot;FILE OPEN ERROR!!!\n&quot;);
        close(fd);
        return 0;
    }

    char buf[10] = &quot;HI Jaehoon&quot;;

    ioctl(fd, _IO(0, 3), buf); // kernel에 데이터 전송
    ioctl(fd, _IO(0, 4), buf); // kernel로부터 데이터 받기
    printf(&quot;from kernel : %s\n&quot;, buf);

    printf(&quot;CLOSE FILE!!\n&quot;);
    close(fd);

    return 0;
}
</code></pre>
<p><code>kernel.c</code></p>
<pre><code class="language-c">#include &lt;linux/module.h&gt;
#include &lt;linux/fs.h&gt;
#include &lt;asm/io.h&gt;
// 송수신 라이브러리
#include &lt;linux/uaccess.h&gt;
#include &lt;linux/string.h&gt;

#define NOD_NAME &quot;nobrand&quot;
#define NOD_MAJOR 100

MODULE_LICENSE(&quot;GPL&quot;);

static int nobrand_open(struct inode *inode, struct file *filp) {
    printk( KERN_INFO &quot;OPEN!!!\n&quot;);
    return 0;
}

static int nobrand_release(struct inode *inode, struct file *filp) {
    printk( KERN_INFO &quot;CLOSE!!!\n&quot;);
    return 0;
}

static long nobrand_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    // 변수 선언
    char buf[10] = { 0 };
    unsigned long ret;

    switch (cmd)
    {
        case _IO(0, 3) :
            printk(KERN_INFO &quot;IO 3\n&quot;);
            // app에서 보낸 문자열 10자리를 복사하여 수신한다.
            ret = copy_from_user((void*)buf, (void *)arg, 10);
            printk(KERN_INFO &quot;%s\n&quot;, buf);
            break;
        case _IO(0, 4) :
            printk(KERN_INFO &quot;IO 4\n&quot;);
            buf[0] = &#39;Z&#39;;
            // app 단에 &#39;Z&#39;를 전송한다.
            ret = copy_to_user((void *)arg, (void *)buf, 1); 
            printk(KERN_INFO &quot;kernel will send %s\n&quot;, buf);
            break;
    }

    printk( KERN_ALERT &quot;%d\n&quot;, cmd);
    return 0;
}
static struct file_operations fops = {
    .open = nobrand_open,
    .release = nobrand_release,
    .unlocked_ioctl = nobrand_ioctl
};

static int nobrand_init(void) {

    printk( KERN_INFO &quot;OK HELLO NOBRAND!!\n&quot;);
    if (register_chrdev(NOD_MAJOR, NOD_NAME, &amp;fops) &lt; 0) {
        printk( KERN_INFO &quot;ERROR!!! register error\n&quot;);
    }

    return 0;
}

static void nobrand_exit(void) {
    unregister_chrdev(NOD_MAJOR, NOD_NAME);
    printk( KERN_INFO &quot;BYE BYE\n\n&quot;);
}

module_init(nobrand_init);
module_exit(nobrand_exit);</code></pre>
<h2 id="구조체-송수신">구조체 송수신</h2>
<p><code>app.c</code></p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/ioctl.h&gt;

// 구조체 선언 : 계산하기 편하기 위해 모두 문자형으로 지정
struct Node{   
    char size;
    char buf[100];
};

int main()
{
    printf(&quot;OPEN FILE!!\n&quot;);
    int fd = open(&quot;/dev/nobrand&quot;, O_RDWR);
    if (fd == -1) {
        printf(&quot;FILE OPEN ERROR!!!\n&quot;);
        close(fd);
        return 0;
    }

    // 구조체 정의
    struct Node t = { 2, &quot;HI&quot; };    

    // 구조체 주소 전송
    ioctl(fd, _IO(0, 3), (void*)&amp;t);

    printf(&quot;CLOSE FILE!!\n&quot;);
    close(fd);

    return 0;
}</code></pre>
<p><code>kernel.c</code></p>
<pre><code class="language-c">#include &lt;linux/module.h&gt;
#include &lt;linux/fs.h&gt;
#include &lt;asm/io.h&gt;
#include &lt;linux/uaccess.h&gt;
#include&lt;linux/string.h&gt;

#define NOD_NAME &quot;nobrand&quot;
#define NOD_MAJOR 100


MODULE_LICENSE(&quot;GPL&quot;);

static int nobrand_open(struct inode *inode, struct file *filp) {
    printk( KERN_INFO &quot;OPEN!!!\n&quot;);
    return 0;
}

static int nobrand_release(struct inode *inode, struct file *filp) {
    printk( KERN_INFO &quot;CLOSE!!!\n&quot;);
    return 0;
}

// 구조체 선언
struct Node{
    char size;
    char buf[100];
};    

static long nobrand_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret;
    struct Node t; // 구조체 정의
    switch (cmd)
    {
        case _IO(0, 3) :
            // 구조체 중 특정 변수에 복사
            ret = copy_from_user(&amp;(t.size) ,(void*)arg,1);
            // 배열은 첫 주소값으로 반환하므로 &amp; 안붙임
            // 널문자까지  받아 문자열임을 인식하기 위해 SIZE + 1
            // 또한 size을 문자형으로 선언했기 때문에 다음 구조체 변수의 주소가 +1이 된거지, 숫자형이였으면 +4를 해줘야함
            ret = copy_from_user(t.buf, (void*)(arg+1), t.size+1); 
            printk(KERN_INFO &quot;t.size = %d\n&quot;, t.size);
            printk(KERN_INFO &quot;t.buf = %s\n&quot;, t.buf);
            break;
    }

    printk( KERN_ALERT &quot;%d\n&quot;, cmd);
    return 0;
}

static struct file_operations fops = {
    .open = nobrand_open,
    .release = nobrand_release,
    .unlocked_ioctl = nobrand_ioctl
};

static int nobrand_init(void) {

    printk( KERN_INFO &quot;OK HELLO NOBRAND!!\n&quot;);
    if (register_chrdev(NOD_MAJOR, NOD_NAME, &amp;fops) &lt; 0) {
        printk( KERN_INFO &quot;ERROR!!! register error\n&quot;);
    }

    return 0;
}

static void nobrand_exit(void) {
    unregister_chrdev(NOD_MAJOR, NOD_NAME);
    printk( KERN_INFO &quot;BYE BYE\n\n&quot;);
}

module_init(nobrand_init);
module_exit(nobrand_exit);
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[커널] GPIO 연결]]></title>
            <link>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-GPIO-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-GPIO-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Tue, 10 May 2022 02:31:11 GMT</pubDate>
            <description><![CDATA[<h2 id="라즈베리파이-데이터-시트-분석">라즈베리파이 데이터 시트 분석</h2>
<p><a href="https://datasheets.raspberrypi.com/bcm2711/bcm2711-peripherals.pdf">bcm2711 칩 라즈베리파이 데이터시트</a></p>
<p>일반적인 Memory Address는 <code>32bit</code>를 사용한다.
0x0000 0000 ~ 0xFFFF FFFF 까지 각 1Byte 공간을 저장하여 총 <code>4GByte</code> 저장 공간 Address를 가짐</p>
<p>하지만, 라즈베리파이의 ARM에 <code>LPAE</code> 기능을 사용하여 <code>35bit</code>까지 표현하고 최대 메모리는 <code>32GB</code>까지 저장 가능</p>
<p>C언어에서 35bit까지 <code>표기 불가능</code>, Low Peripheral 메모리 주소를 사용하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/dev-hoon/post/e2a13e33-53b0-428b-8a43-f80971d2430b/image.png" alt=""></p>
<blockquote>
<p>0x7C00 0000을 건드리고 싶다면, 0xFC00 0000을 건드리면 된다.</p>
</blockquote>
<h2 id="gpio-연결">GPIO 연결</h2>
<p><img src="https://velog.velcdn.com/images/dev-hoon/post/fc2a1484-a560-4b5e-8d8e-a4faa9d24465/image.png" alt="">
데이터시트를 살펴보면, <code>Base 주소</code> : 0x7e20 0000이다. 따라서, 실제 라즈베리파이에서 건드려야 하는 주소는 <code>0xFe20 0000</code>이다.</p>
<blockquote>
<p>참고) 임베디드에서 e는 <code>에코</code>라 읽는다. 2와 커뮤니케이션 혼동을 피하기 위해</p>
</blockquote>
<p><code>offset</code> : 얼마나 떨어져 있는지 표기</p>
<h3 id="led를-18번핀에-연결하여-출력한다고-하면">LED를 18번핀에 연결하여 출력한다고 하면</h3>
<p><img src="https://velog.velcdn.com/images/dev-hoon/post/0d4c9fd7-da1d-466a-97ba-2a8a6e60ea6b/image.png" alt=""></p>
<p>GPFSEL1 Register의 26:24에 001을 넣어야하고, 0xFE20 0004 주소 공간을 건드려야한다.</p>
<h3 id="set---clear">SET /  CLEAR</h3>
<p><img src="https://velog.velcdn.com/images/dev-hoon/post/aca9c4c6-8f86-4580-830b-5e13a8e39c3c/image.png" alt=""></p>
<p>18번핀 SET : <code>0xFE20 001C</code>의 18번 bit에다가 1 세팅 =&gt; LED ON
18번핀 Clear : <code>0xFE20 0028</code>의 18번 bit에다가 1 세팅 =&gt; LED OFF</p>
<h2 id="커널-모듈-붙이고-뗄때-led-키고-끄는-코드">커널 모듈 붙이고 뗄때 LED 키고 끄는 코드</h2>
<p><code>static</code> : 해당 파일 내에서만 사용가능한 전역변수
<code>nobrand.c</code> 에 추가</p>
<pre><code class="language-c">#include &lt;asm/io.h&gt;

static volatile uint32_t *BASE;
static volatile uint32_t *GPFSEL1;
static volatile uint32_t *GPSET0; 
static volatile uint32_t *GPCLR0; 

static int nobrand_init(void) {
    BASE = (uint32_t *)ioremap(0xFE200000, 256); // 해당 주소의 물리적 공간을 사용하기 위해 

    // offset을 해주는데, int의 자료형 크기가 4Byte이기 때문에 4를 나누어줘야 주소 공간 offset이 정상 작동된다.
    GPFSEL1 = BASE + (0x04 / 4); // `0xFE20 0004`
    GPSET0 = BASE + (0x1C / 4); // `0xFE20 001C`
    GPCLR0 = BASE + (0x28 / 4); // `0xFE20 0028`주소

    // GPFSEL1 Register의 26:24에 001을 넣는다.
    *GPFSEL1 &amp;= ~(0x7 &lt;&lt; 24);
    *GPFSEL1 |= (1 &lt;&lt; 24);

    *GPSET0 = (1 &lt;&lt; 18); // LED ON : `0xFE20 001C`의 18번 bit에다가 1 세팅

    if (register_chrdev(NOD_MAJOR, NOD_NAME, &amp;fops) &lt; 0) {
        printk( KERN_INFO &quot;ERROR!!! register error\n&quot;);
    }

    printk(KERN_INFO &quot;hi\n&quot;);
    return 0;
}

static void nobrand_exit(void) {
    *GPCLR0 = (1 &lt;&lt; 18); // LED OFF : `0xFE20 0028`의 18번 bit에다가 1 세팅
    iounmap(BASE);

    unregister_chrdev(NOD_MAJOR, NOD_NAME);
    printk( KERN_INFO &quot;BYE BYE\n\n&quot;);
}</code></pre>
<h2 id="버튼으로-led-제어하기">버튼으로 LED 제어하기</h2>
<p>버튼을 제어하기 위해서는 데이터 시트에서 다음 부분을 참고해야합니다. 
<img src="https://velog.velcdn.com/images/dev-hoon/post/9c5e9130-9eed-4c92-a1a8-8462a4a4f922/image.png" alt=""></p>
<p><code>app.c</code></p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/ioctl.h&gt;

int main()
{
    printf(&quot;OPEN FILE!!\n&quot;);
    int fd = open(&quot;/dev/nobrand&quot;, O_RDWR);
    if (fd == -1) {
        printf(&quot;FILE OPEN ERROR!!!\n&quot;);
        return 0;
    }

    //read
    char buf[100];
    while(1) {
        read(fd, buf, 2);  // 커널에서 buf값 읽어오기
        if (buf[0] == &#39;P&#39;)  // 만약 buf[0]이 &#39;P&#39;이면 LED ON
        {
            ioctl(fd, 3, 0);
        }
        else  // 만약 buf[0]이 &#39;P&#39;가 아니면 LED OFF
        {
            ioctl(fd, 4, 0);
        }

        usleep(100*1000);
    }

    printf(&quot;CLOSE FILE!!\n&quot;);
    close(fd);

    return 0;
}</code></pre>
<p><code>nobrand.c</code></p>
<pre><code class="language-c">#include &lt;linux/module.h&gt;
#include &lt;linux/fs.h&gt;
#include &lt;asm/io.h&gt;

#define NOD_NAME &quot;nobrand&quot;
#define NOD_MAJOR 100

MODULE_LICENSE(&quot;GPL&quot;);

static volatile uint32_t *BASE;
static volatile uint32_t *GPFSEL1;
static volatile uint32_t *GPSET0;
static volatile uint32_t *GPCLR0;

static volatile uint32_t *GPLEV0;
static volatile uint32_t *GPIO_PUP_PDN_CNTRL_REG0;

static void led(int isTurnOn);
static int isButtonPush(void);

static int nobrand_open(struct inode *inode, struct file *filp) {
    printk( KERN_INFO &quot;OPEN!!!\n&quot;);
    return 0;
}

static int nobrand_release(struct inode *inode, struct file *filp) {
    printk( KERN_INFO &quot;CLOSE!!!\n&quot;);
    return 0;
}

static ssize_t nobrand_read(struct file *filp, char *buf, size_t count, loff_t *ppos) {

    if (isButtonPush()) buf[0] = &#39;R&#39;;
    else buf[0] = &#39;P&#39;;
    buf[1] = &#39;\0&#39;;

    return count;
}

static long nobrand_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{

    if (cmd == 3) {
        led(1);
    }

    if (cmd == 4) {
        led(0);
    }

    printk( KERN_ALERT &quot;%d\n&quot;, cmd);
    return 0;
}

static struct file_operations fops = {
    .open = nobrand_open,
    .release = nobrand_release,
    .read = nobrand_read,
    .unlocked_ioctl = nobrand_ioctl
};

static int nobrand_init(void) {

    printk( KERN_INFO &quot;OK HELLO NOBRAND!!\n&quot;);
    if (register_chrdev(NOD_MAJOR, NOD_NAME, &amp;fops) &lt; 0) {
        printk( KERN_INFO &quot;ERROR!!! register error\n&quot;);
    }

    BASE = (uint32_t *)ioremap(0xFE200000, 256);

    GPFSEL1 = BASE + (0x04 / 4);
    GPSET0 = BASE + (0x1C / 4);
    GPCLR0 = BASE + (0x28 / 4);
    GPLEV0 = BASE + (0x34 / 4);
    GPIO_PUP_PDN_CNTRL_REG0 = BASE + (0xE4 / 4);

    //24~26bit 001 output
    *GPFSEL1 &amp;= ~(0x7 &lt;&lt; 21); //000
    *GPFSEL1 |= (1 &lt;&lt; 21); //001

    //using embedded pull up : 데이터 시트에서 초기값이 01이라 다음 코드 안해줘도 되긴 한다.
    *GPIO_PUP_PDN_CNTRL_REG0 &amp;= ~(0x3 &lt;&lt; 4);
    *GPIO_PUP_PDN_CNTRL_REG0 |= (0x1 &lt;&lt; 4); 

    return 0;
}

static void nobrand_exit(void) {
    unregister_chrdev(NOD_MAJOR, NOD_NAME);

    led(OFF);
    iounmap(BASE);

    printk( KERN_INFO &quot;BYE BYE\n\n&quot;);
}

static int isButtonPush(void) {
    int ret = 0;

    ret = (*GPLEV0 &gt;&gt; 2) &amp; 0x1;  // 버튼 2번핀에 연결되어 있다.
    printk(KERN_INFO &quot;BTN : %d\n&quot;, ret);
    return ret;
}

static void led(int isTurnOn) {

    if (isTurnOn)  // LED ON
    {
        *GPSET0 = (1 &lt;&lt; 18);
    }
    else  // LED OFF
    {
        *GPCLR0 = (1 &lt;&lt; 18);
    }
}


module_init(nobrand_init);
module_exit(nobrand_exit);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[커널] Read/Write/ioctl]]></title>
            <link>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-b9sqa2ep</link>
            <guid>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-b9sqa2ep</guid>
            <pubDate>Tue, 10 May 2022 01:41:06 GMT</pubDate>
            <description><![CDATA[<h2 id="read--write-추가하기">Read / Write 추가하기</h2>
<p>read : 커널 모듈로부터 <code>장문</code>의 데이터 받기 
write : 커널 모듈로에 <code>장문</code>의 데이터 보내기</p>
<p><code>app.c</code></p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;

int main()
{
    printf(&quot;OPEN FILE!!\n&quot;);
    int fd = open(&quot;/dev/nobrand&quot;, O_RDWR);  // 읽기 쓰기 둘가 가능 모드로 설정
    if (fd == -1) {
        printf(&quot;FILE OPEN ERROR!!!\n&quot;);
        return 0;
    }

    //write
    write(fd, &quot;Let&#39;s go\n&quot;, 7);

    //read
    char buf[100];
    read(fd, buf, 100);
    printf(&quot;%s\n&quot;, buf);
    printf(&quot;%s\n&quot;, buf);

    printf(&quot;CLOSE FILE!!\n&quot;);
    close(fd);

    return 0;
}</code></pre>
<p><code>nobrand.c</code></p>
<pre><code class="language-c">#include &lt;linux/module.h&gt;
#include &lt;linux/fs.h&gt;

#define NOD_NAME &quot;nobrand&quot;
#define NOD_MAJOR 100

MODULE_LICENSE(&quot;GPL&quot;);

static int nobrand_open(struct inode *inode, struct file *filp) {
    printk( KERN_INFO &quot;OPEN!!!\n&quot;);
    return 0;
}

static int nobrand_release(struct inode *inode, struct file *filp) {
    printk( KERN_INFO &quot;CLOSE!!!\n&quot;);
    return 0;
}

static ssize_t nobrand_read(struct file *filp, char *buf, size_t count, loff_t *ppos) {

    buf[0] = &#39;H&#39;;
    buf[1] = &#39;I&#39;;
    buf[2] = &#39;\0&#39;;

    return count;
}

static ssize_t nobrand_write(struct file *filp, const char *buf, size_t count, loff_t *ppos)
{
    printk( KERN_INFO &quot;HIHI\n\n&quot;);
    printk( KERN_INFO &quot;app message : %s\n&quot;, buf);
    return count;
}

static struct file_operations fops = {
    .open = nobrand_open,
    .release = nobrand_release,
    .read = nobrand_read,
    .write = nobrand_write
};

static int nobrand_init(void) {
    printk( KERN_INFO &quot;OK HELLO NOBRAND!!\n&quot;);
    if (register_chrdev(NOD_MAJOR, NOD_NAME, &amp;fops) &lt; 0) {
        printk( KERN_INFO &quot;ERROR!!! register error\n&quot;);
    }

    return 0;
}

static void nobrand_exit(void) {
    unregister_chrdev(NOD_MAJOR, NOD_NAME);
    printk( KERN_INFO &quot;BYE BYE\n\n&quot;);
}



module_init(nobrand_init);
module_exit(nobrand_exit);</code></pre>
<h2 id="ioctl-추가">ioctl 추가</h2>
<p>ioctl : 만능, read/write 둘다 가능, 대신 가독성 떨어짐 
또한 ioctl은 <code>값 하나</code>의 데이터를 보내고 받을 수 있다.
장문의 데이터를 송수신하려면 <code>copy_from_user()</code>와 같은 커널함수를 추가 작성해줘야 한다.</p>
<p><code>app.c</code>에 추가</p>
<pre><code class="language-c">#include &lt;sys/ioctl.h&gt;

ioctl(fd, 3, 0);  // 3 전송
ioctl(fd, 4, 0);  // 4 전송</code></pre>
<p><code>nobrand.c</code>에 추가</p>
<pre><code class="language-c">static long nobrand_ioctl(struct file *flip, unsigned int cmd, unsigned long arg)
{
    printk( KERN_ALERT &quot;%d\n&quot;, cmd);  // 3과 4 커널 로그에 찍음
    return 0;
}

static struct file_operations fops = {
    .open = nobrand_open,
    .release = nobrand_release,
    .read = nobrand_read,
    .write = nobrand_write,
    .unlocked_ioctl = nobrand_ioctl
};
</code></pre>
<h2 id="build-환경">Build 환경</h2>
<ol>
<li><code>Native 환경 개발</code> : 개발할 장치 OS에서 코딩하는 것으로, Device Driver와 App을 개발할 때는 유용하다.</li>
<li><code>Cross Compile</code> : 개발 PC 개발 후 Target 보드로 옮겨 테스트하며 개발하는 것으로, Kernel 혹은 규모가 큰 Device Driver와 App을 개발할 때 유용하다.</li>
</ol>
<blockquote>
<p>참고 : intel CPU 컴파일러를 통해 나온 실행파일은 ARM Core에서 작동되지 않는다. 따라서, ARM 컴파일러를 통해 실행파일을 만들면 된다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[커널] C언어에서만 사용가능한 문법들]]></title>
            <link>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-C%EC%96%B8%EC%96%B4%EC%97%90%EC%84%9C%EB%A7%8C-%EC%82%AC%EC%9A%A9%EA%B0%80%EB%8A%A5%ED%95%9C-%EB%AC%B8%EB%B2%95%EB%93%A4</link>
            <guid>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-C%EC%96%B8%EC%96%B4%EC%97%90%EC%84%9C%EB%A7%8C-%EC%82%AC%EC%9A%A9%EA%B0%80%EB%8A%A5%ED%95%9C-%EB%AC%B8%EB%B2%95%EB%93%A4</guid>
            <pubDate>Tue, 10 May 2022 00:22:49 GMT</pubDate>
            <description><![CDATA[<h2 id="특정-배열-인덱스-초기화">특정 배열 인덱스 초기화</h2>
<pre><code class="language-c">#include &lt;stdio.h&gt;

int main()
{
    int vect[5] = 
    {
        [0] = 4,
        [3] = 1,
    };

    return 0;
}</code></pre>
<h2 id="특정-구조체-초기화">특정 구조체 초기화</h2>
<pre><code class="language-c">#include &lt;stdio.h&gt;

struct Node
{
    int a;
    int b;
    int c;
};

int main()
{
    struct Node t = 
    { 
        .a = 100, 
        .c = 200 
    };

    return 0;
}</code></pre>
<h2 id="구조체-콜백함수와-연결">구조체 콜백함수와 연결</h2>
<pre><code class="language-c">#include &lt;stdio.h&gt;

void aaa()
{

}

void bbb()
{

}

void ccc()
{

}

struct MBC
{
    void(*one)();
    void(*two)();
    void(*three)();
};

struct TV {
    struct MBC go;
    struct MBC shoot;
};

int main()
{
    struct MBC mbc =
    {
        .one = aaa,
        .two = bbb,
        .three = ccc,
    };

    struct TV tv = {
        .go = {
            .one = aaa,
            .two = bbb,
        },
        .shoot = {
            .three = aaa,
        }
    };

    return 0;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[커널] App에서 커널 접근하기]]></title>
            <link>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-App%EC%97%90%EC%84%9C-%EC%BB%A4%EB%84%90-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev-hoon/%EC%BB%A4%EB%84%90-App%EC%97%90%EC%84%9C-%EC%BB%A4%EB%84%90-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 09 May 2022 11:03:15 GMT</pubDate>
            <description><![CDATA[<h2 id="device-node">Device Node</h2>
<h3 id="디바이스-드라이버-종류">디바이스 드라이버 종류</h3>
<ol>
<li>캐릭터 디바이스(chrdev) : Byte 단위로 값 전달</li>
<li>블록 디바이스(blkdev) : kb 이상의 블록단위로 값을 전달</li>
<li>네트워크 디바이스(netdev) : socket 을 열고 ioctl 이라는 System Call 로 장치를 제어</li>
</ol>
<h3 id="device-node란">Device Node란?</h3>
<p>Device Node는 <code>Device File</code>로 HW 컨트롤을 한다. <code>./dev</code>에서 장치들을 관리한다.</p>
<p><code>ls-al ./dev</code>를 하게 되면, 다음 이미지와 같이 확인할 수 있다.
<img src="https://velog.velcdn.com/images/dev-hoon/post/c54d08f2-41f3-4d4d-9e07-73c8b7bde0b6/image.png" alt=""></p>
<p>Major Number(주번호) : 디바이스 종류
Minor Number(부번호) : 구분하기 위한 용도</p>
<h3 id="device-파일-만들기">Device 파일 만들기</h3>
<p><code>sudo mknod ./[파일명] c 100 200</code> : chardev로 주번호 100 보조번호 200인 device file 생성</p>
<h2 id="app에서-커널-접근하기">App에서 커널 접근하기</h2>
<blockquote>
<p>커널 내부에서 디바이스 파일과 커널 모듈을 연결하여, app 단에서 디바이스 파일을 열어서 커널 모듈을 제어한다.</p>
</blockquote>
<h3 id="app에서-device-파일에-접근하기">app에서 Device 파일에 접근하기</h3>
<p><code>nobran.c</code> 기본형태</p>
<pre><code class="language-c">#include &lt;linux/module.h&gt;

MODULE_LICENSE(&quot;GPL&quot;);

static int nobrand_init(void) {
    printk( KERN_INFO &quot;OK HELLO NOBRAND!!\n&quot;);
    return 0;
}

static void nobrand_exit(void) {
    printk( KERN_INFO &quot;BYE BYE\n\n&quot;);
}

module_init(nobrand_init);
module_exit(nobrand_exit);</code></pre>
<p><code>Device File</code> 
<code>./dev</code>는 장치 파일을 제어해주는 디렉토리, 디바이스 파일은 ./dev에 저장되어 있어야 한다.
<code>sudo mknod /dev/nobrand c 100 0</code>
<code>sudo chmod 666 /dev/nobrand</code></p>
<p><code>app.c</code></p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;

int main()
{
    printf(&quot;OPEN FILE!!\n&quot;);
    int fd = open(&quot;/dev/nobrand&quot;, O_RDONLY); // 디바이스 파일에 접근하기
    if (fd == -1)
    {
        printf(&quot;FILE OPEN ERROR\n&quot;);
        return 0;
    }


    printf(&quot;CLOSE FILE!!\n&quot;);
    close(fd);

    return 0;
}</code></pre>
<h3 id="커널-모듈형태의-device-driver-제작">커널 모듈형태의 Device Driver 제작</h3>
<p><code>nobrand.c</code> 완성본</p>
<pre><code class="language-c">#include &lt;linux/module.h&gt;
#include &lt;linux/fs.h&gt;

#define NOD_NAME &quot;nobrand&quot;
#define NOD_MAJOR 100  // Device File의 Major Number

MODULE_LICENSE(&quot;GPL&quot;);

static int nobrand_open(struct inode *inode, struct file *filp) {
    printk( KERN_INFO &quot;OPEN!!!\n&quot;);
    return 0;
}

static int nobrand_release(struct inode *inode, struct file *filp) {
    printk( KERN_INFO &quot;CLOSE!!!\n&quot;);
    return 0;
}

static struct file_operations fops = {
    .open = nobrand_open,  // app.c에서 open 명령어 실행시 실행
    .release = nobrand_release // app.c에서 close 명령어 실행시 실행
};

static int nobrand_init(void) {  // insmod 명령어 입력시 실행
    printk( KERN_INFO &quot;OK HELLO NOBRAND!!\n&quot;);  // KERN_INFO : log에 print하기 위해
    if (register_chrdev(NOD_MAJOR, NOD_NAME, &amp;fops) &lt; 0) { // device file에 접근 실패시 실행
        printk( KERN_INFO &quot;ERROR!!! register error\n&quot;);
    }

    return 0;
}

static void nobrand_exit(void) { // rmmod 명령어 입력시 실행
    unregister_chrdev(NOD_MAJOR, NOD_NAME);
    printk( KERN_INFO &quot;BYE BYE\n\n&quot;);
}



module_init(nobrand_init);
module_exit(nobrand_exit);</code></pre>
<p><code>makefile</code></p>
<pre><code class="language-bash">KERNEL_SRC=/lib/modules/$(shell uname -r)/build
obj-m := nobrand.o

go:
    make -C $(KERNEL_SRC) M=$(PWD) modules
clean:
    make -C $(KERNEL_SRC) M=$(PWD) clean</code></pre>
<p><code>KERNEL_SRC</code> 경로에 <code>Makefile</code>이 존재한다. 해당 Makefile에 선언해준 <code>obj-m</code>이 있다.
PWD(현재 경로)의 모든 파일을 커널에 접근하여 make를 통해 나온 결과물을 다시 가져오게된다.</p>
<h2 id="실행">실행</h2>
<p><code>make</code>를 통해 커널 모듈 빌드
<code>sudo insmod ./nobrand.ko</code>를 통해 커널에 집어넣기 
<code>gcc ./app.c -o ./app</code>을 통해 app 파일 빌드
<code>./app</code>을 통해 app에서 커널 모듈에 접근
<code>sudo rmmod nobrand</code>를 통해 커널에서 빼기</p>
<blockquote>
<p>참고 : ko 파일은 test용으로 app단에 존재하는거지, 원래는 커널에 있어야한다.</p>
</blockquote>
<h2 id="재부팅-시-실행-순서">재부팅 시 실행 순서</h2>
<p>재부팅시 필요한 파일들을 만들어주는데, 상단에서 만든 nobrand 파일은 필수가 아니므로 다시 만들어줘야한다. 
$ make clean
$ rm app
$ sudo mknod /dev/nobrand c 100 0
$ sudo chmod 666 /dev/nobrand
$ make
$ gcc app.c -o app
터미널 새로 키고 $ dmesg -w
$ sudo insmod nobrand.ko
$ ./app
$ sudo rmmod nobrand
$ make clean
$ rm app</p>
<h2 id="좀더-간편화">좀더 간편화</h2>
<h3 id="makefile--수정">Makefile  수정</h3>
<p>app gcc를 추가</p>
<pre><code class="language-bash">KERNEL_SRC=/usr/src/linux
TARGET := app
obj-m := nobrand.o

all: driver app

driver:
        make -C $(KERNEL_SRC) M=$(PWD) modules

app:
     $(CC) -o $@ $@.c

clean:
     make -C $(KERNEL_SRC) M=$(PWD) clean
     rm -f *.o $(TARGET)
</code></pre>
<h3 id="aliassh-파일-만들기">alias.sh 파일 만들기</h3>
<p><code>source ./alias.sh</code>로 실행가능
매크로 함수로, <code>sd</code>로 커널 진입 , <code>sc</code>로 커널 빠져나오기</p>
<pre><code class="language-bash">alias sc=&#39;sudo insmod ./nobrand.ko&#39;
alias sd=&#39;sudo rmmod nobrand&#39;
</code></pre>
]]></description>
        </item>
    </channel>
</rss>