반응형

Forward liveness analysis.

1. 위의 식을 기억하고 넘어감.

2. in[n] 먼저 하고 out[n] 진행

 

 


이런 명령어를 가진 그래프가 있다고 하자.

 

3. 초기 상태는 다음과 같다.

 

4. 공식에 넣기 위해 use, def 를 구한다.

use 는 명령어에서 보통 사용되는 변수 집합이라고 생각하면 되고

def 는 정의되는(값이 변하는) 변수라고 생각하면 된다.

 

5. 갱신

in[n] 먼저 구하는데, in[n] 은 use[n] U (out[n] - def[n]) 이다.

이후 out[n] 을 구하는데, out[n] 은 successor의 in 을 가져온다.

 

1st

명령어 1번 : in, out이 모두 공집합 상태이고, def 를 빼도 아무것도 남지 않는다.

in[1] = {}

out[1] =  in[2] = {}

이렇게 명령어 하나 지날 때 마다 업데이트 되는 개념임.

 

명령어 2번 

in[2] = {a} U ({} - {b}) = {a}

out[2] = in[3] = {}

 

명령어 3번 

in[3] = {bc} U ({} - {c}) = {bc}        -> {} - {c} = {}

out[3] = in[4] = {}

 

명령어 4번 

in[4] = {b} U ({} - {a}) = {b}

out[4] = in[5] = {}

 

명령어 5번 

in[5] = {a} U ({} - {}) = {a}

out[5] = in[2] U in[6] = {a} U {} = {a}

여기는 분기가 나뉘어 있어서, 현재까지 들어온 값에서 처리함.

1번 사이클에서 명렁어 2번에 의해 in[2] 가 이미 최신화되었기 때문에 그 값을 사용한다는 의미

 

명령어 6번

in[6] = {c} U ({} - {}) = {c}

out[6] = {}, 연결 X

 

1번 사이클을 지난 후 값 (inst# in/out)

inst1 : {} / {}

inst2 : {a} / {}

inst3 : {bc} / {}

inst4 : {b} / {}

inst5 : {a} / {a}

inst6 : {c} / {}

표의 1번째 사이클과 같은 것을 확인 가능함.

이것을 더 이상 최신화 되지 않을 때 까지 수행 (previous sets == current sets)

 

 

 

 

 


Backward liveness analysis.

 

1. 마찬가지로 위의 식을 기억하고 넘어감.

2. out[n] 을 먼저 계산하고 in[n] 을 계산한다.

 


이런 명령어를 가진 그래프가 있다고 하자. (Forward 와 동일)

 

3. 초기 상태는 다음과 같다. (Forward 와 동일)

 

4. 공식에 넣기 위해 use, def 를 구한다. (Forward 와 동일)

 


5. 1차 갱신

successor 를 파악해야 하므로 가장 끝 노드에서부터 처리를 시작한다.(backward 는 6번부터 1번으로 감)

out - in - out - in - ... 순서로 처리를 한다.

 

 

6. 2차 갱신

 

값이 변할 때 까지 업데이트

 

8. 최종 결과

 

반응형
반응형

모듈은 써 보고 싶은데 정보가 많지 않아서 글을 한 번 작성해 본다.

 

먼저, TF-LC02 LiDAR 모듈을 사용한다.

 

나는 Aliexpress 에서 개인 프로젝트 용으로 모듈을 구매하였다.

 

TF-LC02 ToF 레이저 라이더 거리 측정 센서 모듈, 통신 인터페이스, UART TDC VCSEL, Rang Robotics 스마트 홈, 940nm - AliExpress 502

 

6904.0₩ 25% OFF|TF LC02 ToF 레이저 라이더 거리 측정 센서 모듈, 통신 인터페이스, UART TDC VCSEL, Rang Robo

Smarter Shopping, Better Living! Aliexpress.com

ko.aliexpress.com

 

저렇게 생긴 모듈을 구매하였다. 앞서 말하는데, 나는 TTL to USB 케이블(정품) 을 사용할 수 있는 환경이다.

 

USB to TTL Serial Cable / 디바이스마트 (devicemart.co.kr)

 

USB to TTL Serial Cable

PL2303TA(PL2303HX) 칩셋을 이용한 USB to TTl 컨버터

www.devicemart.co.kr

정품이 아닌 버전은 최신 윈도우에서 포트에 자동으로 잡히지 않고 알 수 없는 장치라고 뜨는 이슈가 있다는데, 나는 이거까지는 잘 모르겠다.

 

 

기본적인 유저 가이드, 데이터시트는 다음과 같다.

 

TF-LC02 [BXDPTFLC0201] (youyeetoo.com)

 

사이트에 들어가게 되면 파일들이 존재한다.

 

유저 가이드의 경우 굉장히 간단한데 있을 것은 다 있다.

 


 

참고 코드는 해당 사이트의 아두이노 코드를 사용했다.

Benewake(北醒) TF-LC02 (TTL) 雷达使用TTL转USB转接板在Arduino Uno上的运用-CSDN博客

 

Benewake(北醒) TF-LC02 (TTL) 雷达使用TTL转USB转接板在Arduino Uno上的运用-CSDN博客

前言 本例程仅用作参考 Benewake(北醒) TF-LC02产品简要说明 性能参数 产品图片及尺寸 Arduino开发板介绍 参考链接:常用Arduino板介绍 Benewake(北醒) TF-LC02 接口及通讯协议说明 接口定义 串口协议说明

blog.csdn.net

 

연결은 다음과 같이 한다.

 

 

TF-LC02 는 다음 그림과 같은 구조를 가진다. 기본적으로 이 구조를 따르되, 조금 변경을 할 예정이다.

변경한 그림은 다음과 같다.

TTL to USB 로 들어가는 RX 단에 TF-LC02 의 TX 를 물려주는 것만 바꿨다. 직접 TX hex 값을 받아온다.

 

구조는 알아보기 힘들지만 이렇게 생겼다.

 

 

빨간색 부터 VCC, 검은색 GND, ...

 

그런데 TF-LC02 는 선이 짧게 오고, 이게 가장 큰 문제가 뭐냐면, 끝이 갈라진다.

 

갈라지는 케이블이라, 형태가 유지 되게끔 납을 살짝 칠해 주거나, 남는 아두이노 점퍼 케이블 헤더부분을 납땜해 사용해서 붙여야 한다.

 

아두이노 점퍼케이블

 

 


 

 

BasicReading.ino
0.00MB

 

python 코드는 다음과 같다. (일반적인 테스트 용이다.)

 

import serial # pip install pyserial
import binascii # pip install binascii

try:
    # Change Serial('COM Port to your environment')
    ser = serial.Serial('COM5', 115200)
except serial.SerialException as e:
    print(f"Error opening serial port: {e}")
    exit(1)

while True:
    if ser.readable():
        s = ser.read(1)  # Reading 1 byte at a time
        hex_string = binascii.hexlify(s).decode('utf-8')
        print(hex_string, end=" ")
        if hex_string == 'fa':
            print("\n")

 

 

Python 코드의 결과로 위 사진과 같은 hex 배열을 얻게 된다.

0x55 0xAA 는 헤더고, 0xFA 는 end sign 이다. 데이터시트를 보면 예시도 있으므로 참고하면 되겠다.

 

 

예시를 한 번 보자.

 

 

현재 아두이노 코드는 command 를 measuring distance 하나만 보내고 있다.

0x0155 가 왜 341이 나올까? 이는 hex 값이기 때문이다.

0 * 16^3 + 1 * 16^2 + 5 * 16^1 + 5 = 256 + 80 + 5 = 341 (단위 mm)

detect 되는 거리는 3cm ~ 2m 까지이기 때문에 최대 2000 mm 까지 감지 가능하다.

 

참고) 2000 은 0x07D0

참고) 앞에 나왔던 55 AA 81 03 에서 03 은 다음과 같음

03은 뒤에 나오는 parameter 길이

 

그럼 길이를 한번 다시 재본다.

코드는 다음과 같다.

 

import serial  # pip install pyserial
import binascii  # pip install binascii

idx = 0
distance = 0

try:
    # Change Serial('COM Port to your environment')
    ser = serial.Serial('COM5', 115200)
except serial.SerialException as e:
    print(f"Error opening serial port: {e}")
    exit(1)

while True:
    if ser.readable():
        s = ser.read(1)  # Reading 1 byte at a time
        hex_string = binascii.hexlify(s).decode('utf-8')

        if idx == 4 or idx == 5:
            # Append incoming hex values and convert to decimal
            if idx == 4:
                distance = int(hex_string, 16) << 8  # Shift left by 8 bits for the high byte
            elif idx == 5:
                distance += int(hex_string, 16)  # Add the low byte value
                print(f"Distance: {distance} mm")  # Print the distance

        if idx == 6 and hex_string != '00':
            print("WARNING: Out of range!")
        # Index increment
        idx += 1
        
        if hex_string == 'fa':
            # Reset packet on end signal
            idx = 0
            distance = 0
            print("\n")

 

 

정상적으로 출력되는 것을 볼 수 있다.

time.sleep() 을 사용해서 딜레이도 줘 봤는데, UART 통신이 뭔가 이상해져서 다시 뺐다.

 

반응형
반응형

딥러닝을 한 번이라도 맛본 사람이라면, 

이미지가 텐서 라는 형태로 변환되고 처리가 이루어진다는 것은 자명한 사실이다.

 

 

그럼 이 텐서라는 형태에서 다시 이미지 파일로 형태를 바꾸려면, 어떻게 해야 할까?

 

보기 쉬운 이미지를 위해, 그림판으로 28x28 사이즈의 이미지를 하나 색칠했다.

 

회색 부분은 #999999 이고, 줄이 쳐진 부분은 #555595 이다.

 

 

대충 이미지 하나를 생성했다. (28x28x3) 

 

그럼 이 이미지를 이제 텐서 형태로 변환한다.

 

image_path 는 알아서 잘 지정하자. 절대경로로 하면 편하다.

 

값을 알아보기 위함이므로 normalize 는 진행하지 않는다.

 

출력은 넘파이 배열로 출력한다.

from PIL import Image
import torchvision.transforms as transforms
import torch

image_path = "img.png"
image = Image.open(image_path)

transform = transforms.Compose([
    transforms.Resize((28, 28)),
    transforms.ToTensor()
])

image_tensor = transform(image)
image_tensor = image_tensor.unsqueeze(0)

tensor_values = image_tensor.numpy()

with open("tensor_values.txt", "w") as f:
    for layer in tensor_values:
        for row in layer:
            for value in row:
                f.write(f"{value} ")
            f.write("\n")
        f.write("\n")

 

 

tensor_value.txt

 

텍스트 파일을 보면 알 수 있는 점이 있다.

 

다시 그림을 한 번 보자.

 

보면 오른쪽으로 1픽셀 정도 치우쳐져 있는 그림이다.

값을 한 번 보자.

 

 

0.33 .. 값은 첫 번째를 1번이라고 했을 때 14번부터 16번의 값을 가진다!

28x28 의 정 중앙이 14 라는 점을 보았을 때, 딱 1픽셀 밀려 있는 것을 확인 가능하다.

 

 

이번에는 

0.33 ... 값이 가득한 부분을 한 번 본다.

 

위에서 부터 1번 row 라고 했을 때 7번에 위치한다.

 

잘 보면 첫 번째만 값이 이상한데, 이는 의도된 그림이다. 그림을 다시 보면 가장 처음 픽셀만 살짝 연한 것을 볼 수 있다.

 

따라서, 이 정보들을 보면, 텐서는 

 

저 방향으로 하나의 row 를 끝날 때까지 읽고, 끝나면 다음 row를 출력한다고 보면 되겠다.

 

 

그리고 총 형태를 보면,

 

이다. 우리가 지금까지 봤었던 것은 한 차원의 값이다. 아마 RGB 중 R 값을 본 것 같다.

 

이는 B의 0.33 ... 값을 보면 더 잘 알 수 있다.

 

 

이쪽 부분만 0.58431375 로 값이 같은 것을 알 수 있다.

 

처음에 값 지정할 때 #555595 로 설정했기 때문에 혼자만 다르게 나오는 것을 볼 수 있다.

 

 

커널이 있으면 위 사진처럼 계산도 가능하겠다.


 

이제 다시 넘파이 배열를 이미지로 변환해 볼 차례이다.

 

하나 예시를 들어보면, #999999의 RGB 값은 (153, 153, 153) 이다.

153 을 RGB 범위인 255로 나누게 되면 0.6 이라는 값이 나오게 된다.

 

#555595 는 어떨까? (85, 85, 149) 의 값을 가진다.

85를 RGB 범위인 255로 나누게 되면 0.33333... 의 값이 나온다.

컴퓨터에서는 32비트 범위에서 잘리게 되므로 약간의 소수점 변동이 생길 수 있다.

텐서의 경우 0.33333334 의 값을 가지게 되었다.

 

149를 255로 나눠보면? 

0.5843137254 ... 값이 나온다. 이도 소수점 7번째 자리까지 동일한 값이 나오는 것을 확인 가능하다.

 

다르게 생각해 보면 R 채널에서는 회색 범위는 153의 값을 가지고, 보라색 범위는 85의 값을 가지는 텐서들로 이루어져 있다.

 

G 채널에서도 같다.

 

B 채널에서만 회색 범위가 153이라는 값을 가지고, 보라색 범위가 149의 값을 가지게 된다. 그래서 값 차이가 크게 나지 않는다.

 

아무튼 다시 이미지로 변경을 한다.

 

import torchvision.transforms as transforms
import torch
from PIL import Image

# Load the image
image_path = "./img.png"
image = Image.open(image_path)

# Define the transformation
transform = transforms.Compose([
    transforms.ToTensor()
])

# Convert the image to a tensor
image_tensor = transform(image)

print(image_tensor.shape)

tf = transforms.ToPILImage()(image_tensor).show()

 

변형한 것이 없기 때문에 깔끔하게 다시 나온다.


이건 기존에 했던 이미지 - > 바이트 배열 -> 텐서 -> 넘파이 배열 -> 텐서 -> 이미지 변환이다.

 

from PIL import Image
import torchvision.transforms as transforms
import torch
import numpy as np

# 이미지 불러오기
image_path = "./img.png"
image = Image.open(image_path)

# 이미지 전처리
transform = transforms.Compose([
    transforms.Resize((28, 28)),  # 모델의 입력 크기로 리사이즈
    transforms.ToTensor()  # 텐서로 변환
])

image_tensor = transform(image)  # 텐서 변환
image_tensor = image_tensor.unsqueeze(0)  # 배치 차원 추가

# 텐서를 바이트 값으로 변환하여 바이트 파일로 저장
tensor_bytes = image_tensor.numpy().tobytes()  # 텐서를 numpy 배열로 변환 후 바이트로 변환

# 바이트 파일 저장
with open("D:/tensor_values.bytes", "wb") as f:
    f.write(tensor_bytes)

# 바이트 파일을 읽어 텐서로 변환
with open("D:/tensor_values.bytes", "rb") as f:
    tensor_bytes = f.read()

# 바이트 데이터를 numpy 배열로 변환
tensor_array = np.frombuffer(tensor_bytes, dtype=np.float32)
tensor_array = tensor_array.reshape((1, 3, 28, 28))  # 원래 텐서의 형태로 변환

# numpy 배열을 텐서로 변환
reconstructed_tensor = torch.tensor(tensor_array)

# 텐서를 이미지로 변환
reconstructed_image = transforms.ToPILImage()(reconstructed_tensor.squeeze(0))

# 이미지 저장
reconstructed_image.save("D:/reconstructed_image.jpg")

 

 

 

이미지에 잡음이 좀 생긴 것을 확인할 수 있었다.

 

 

반응형
반응형

GPT 의 힘을 빌려서 완성했다.

command 창 내 현재 위치에 파일이 생성되니 주의할 것.

import torch
from ultralytics import YOLO

# ------------ 모델 경로 및 파일 수정 시간은 수정하기 --------------
model_path = '../best.pt' 
model_generated_time = "  24-06-04, 20:02"
# --------------------------------------------------------------

model = YOLO(model_path)

# 모델의 가중치 확인
model_weights = model.model.state_dict()
torch.set_printoptions(threshold=torch.inf)
idx = 0
while idx < len(model_weights):
    name = list(model_weights.keys())[idx]
    param = model_weights[name]
    if "model." in name:
        output_file = f"{name}.txt" 
        with open(output_file, "w") as f:
            f.write("Weights Information:\n")
            f.write("# weights ver.2\n\n")
            f.write(f"Model Path: {model_path}\n")
            f.write(f"Model Generated Time: {model_generated_time}\n\n")
            f.write(f"Layer: {name} | Size: {param.size()}\n")
            f.write(str(param) + "\n")  
    idx += 1

 

 

다음은 레이어 정보만 가져오는 코드임.

 

# 레이어 정보만 가져오기
import torch
from ultralytics import YOLO

# 모델 경로 및 파일 수정 시간 설정
model_path = '../best.pt' 
model_generated_time = "24-06-04, 20:02"

# YOLO 모델 로드
model = YOLO(model_path)

# 모델의 가중치 확인
model_weights = model.model.state_dict()

# 레이어 정보를 저장할 파일 생성
with open("layers.txt", "w") as f:
    f.write("Layer Information:\n")
    f.write(f"Model Path: {model_path}\n")
    f.write(f"Model Generated Time: {model_generated_time}\n\n")

# 각 레이어 정보를 파일에 기록
with open("layers.txt", "a") as f:
    for idx, (name, param) in enumerate(model_weights.items()):
        f.write(f"Layer {idx}:\n")
        f.write(f"Name: {name}\n")
        f.write(f"Size: {param.size()}\n")
반응형
반응형

알게 된 점 위주로 작성하는 개인적인 글

 

먼저 ultralytics 레포지토리를 클론한다.

 

GitHub - ultralytics/ultralytics: NEW - YOLOv8 🚀 in PyTorch > ONNX > OpenVINO > CoreML > TFLite

 

GitHub - ultralytics/ultralytics: NEW - YOLOv8 🚀 in PyTorch > ONNX > OpenVINO > CoreML > TFLite

NEW - YOLOv8 🚀 in PyTorch > ONNX > OpenVINO > CoreML > TFLite - ultralytics/ultralytics

github.com

 

커스텀 데이터셋을 받아오기 위해 pretrain 된 모델을 받아온다.(굳이 안해도 되긴 한다)

YOLOv8 - Ultralytics YOLO 문서

 

YOLOv8

Discover YOLOv8, the latest advancement in real-time object detection, optimizing performance with an array of pre-trained models for diverse tasks.

docs.ultralytics.com

 

모델은 YOLOv8(n~x) 까지 있는데, 원하는 것으로 다운받으면 된다.

parameter 가 많을수록 속도가 느리지만, 정확도가 상승한다.

 

나는 가장 가벼운 YOLOv8n 을 사용한다.

 

다운받은 YOLOv8n.pt 파일을 ultralytics 폴더 안에 넣는다.

 

커스텀 데이터셋을 만들기 위해 여러가지가 가능한데, 나는 이미지를 수동으로 라벨링하고 roboflow 사이트에서 yaml 파일을 생성하는 방법으로 해 보았다. 아마 roboflow 에서 자동으로도 지원하는 것 같다.

 

수동 라벨링 툴은 labelImg 를 사용하였다.

 

YOLO 이미지 라벨링을 위한 labelImg 사용법 (velog.io)

 

YOLO 이미지 라벨링을 위한 labelImg 사용법

labelImg를 이용해 YOLO 이미지 라벨링하기

velog.io

 

잘 정리된 글을 따라 가면 된다.

 

포맷을 pascalVOC 에서 YOLO 로 바꾸는 것을 까먹지 말자.

 

나는 커스텀 데이터셋으로 사과 사진을 사용하였다.

 

apple#.jpg 사진을 라벨링을 끝내고 난 파일들인데, 이를 roboflow 에 넣으면 커스텀 데이터셋을 만들어 준다.

모델을 따로 지정하지는 않고 export dataset 으로 데이터셋만 가져와 준다.

 

형식은 YOLOv8 로 지정하고 export 해 주면 위와 같이 파일들이 있다.

 

data.yaml 을 한 번 본다.

 

train: ../train/images
val: ../valid/images
test: ../test/images

nc: 1
names: ['15']

roboflow:
  workspace: hipensan
  project: apple-finder-ar2zu
  version: 1
  license: CC BY 4.0
  url: https://universe.roboflow.com/hipensan/apple-finder-ar2zu/dataset/1

 

names 에 '15' 라고 되어 있는데, 이를 객체 이름으로 바꿔준다. 나는 apple finder 를 사용하기 때문에 Apple 라고 붙여주도록 하겠다.

 

데이터셋을 준비했으니 모델에 학습시킬 차례이다.

 

ultralytics 폴더에 main.py 를 생성한다.

[main.py]

from ultralytics import YOLO

model = YOLO('yolov8n.pt')

model.train(data="../Apple Finder/data.yaml", epochs=20)

 

model.train 의 data 경로는 yaml 파일의 경로로 해 줘야 한다. 절대 경로로 하는게 정신건강에 이롭다.

 

코드 시작전 현재 command 창의 위치를 잘 확인하자. 현재 위치에 runs/.. 파일들이 생성된다.

 

+) 데이터셋이 있는 곳에서 실행시키는 것이 편할 수도 있다.

 

 

20에포크를 지나면 다음과 같이 내가 설정한 경로/runs/detect/train# 하위에 best.pt, last.pt 가 생성되었다.

 

학습시킨 best.pt의 내부 구조를 한번 본다.

 

경로 설정을 쉽게 하기 위해 ultralytics 폴더로 best.pt 파일을 복사해 온다.

 

from ultralytics import YOLO
import torch
# model = YOLO('yolov8n.pt')

# model.train(data="C:/Users/admin/Desktop/github/Apple Finder/data.yaml", epochs=20)

model = torch.load("./best.pt")
print(model)

 

레이어 0 ~ 레이어 22까지 내부 구조가 나오는 것을 확인 가능하다.

 

train 시켰으니 예측을 시켜 보는 것도 필요하겠다.

 

사과와 비슷해 보이는 복숭아, 자두, 사과 사진을 각각 준비한다.

아, 사진의 사이즈는 640*640이다. roboflow 를 사용하면 resize 도 지원한다.

 

+) 다시 위치 바꾸기 귀찮아서 데이터셋이 있는 위치에서 모두 진행하였다. best.pt 도 옮겼다.

 

파일구성은 위 사진과 같다.

 

코드는 다음과 같다. Predict - Ultralytics YOLO 문서 에서 제공해주는 코드에서 모델과 input image 만 바꿨다.

from ultralytics import YOLO

# Load a model
model = YOLO("best.pt")  # pretrained YOLOv8n model

# Run batched inference on a list of images
results = model(["peaches1.jpg", "peaches2.jpg", "peaches3.jpg", "plum1.jpg", "plum2.jpg"])  # return a list of Results objects

# Process results list
for result in results:
    boxes = result.boxes  # Boxes object for bounding box outputs
    masks = result.masks  # Masks object for segmentation masks outputs
    keypoints = result.keypoints  # Keypoints object for pose outputs
    probs = result.probs  # Probs object for classification outputs
    obb = result.obb  # Oriented boxes object for OBB outputs
    result.show()  # display to screen
    result.save(filename="result.jpg")  # save to disk

 

 

우리가 train 시킨 사과가 없으므로, no detection 이 뜨는 것이 당연하다. 

다른 사과 이미지를 한 번 넣어본다.

train 시킨 사과 이미지와 다르면 된다.

 

 

학습 데이터가 적어서? detect 되지 않는 문제가 발생한다.

val 데이터를 한 번 넣어본다.

 

* val 데이터, train 데이터도 제대로 출력되지 않는다(?)

* yolov8n 모델로 돌리면 제대로 나온다.

 

* 데이터 학습이 제대로 안 되었다는 소리

 

아무튼 yolov8n 모델로 돌려 보았다.

yolov8n.pt

 

test 데이터는 더 있는데, 학습되지 않은 자두와 복숭아에 대해서 사과와 구분을 잘 못하는 모습을 보였다.

그래서, 다른 모델을 적용시켜 보기로 했다.

small, medium, large 까지만 사용해 보기로 했다.

 

yolov8s.pt

small 도 이상하다.

coco dataset 에 복숭아와 자두가 없는게 문제가 큰 것 같다.

 

yolov8m.pt

 

 

yolov8l.pt

 

train 시키지 않은 항목에 대해서 이미 있는것을 바탕으로 추측하고, 사과와 비슷하게 생겼기 때문에 사과로 인지하는 것 같다. 해당 사진은 '자두' 이다.

 

커스텀 데이터셋을 다시 train 시킬 필요가 있겠다.

 

 

 

epoch 를 60으로 늘렸다. train 데이터도 제대로 검출이 안 되기 때문.

아니면 항목이 하나밖에 없어서 그럴 수도 있다.

 

last.pt (epochs=60)

 

best.pt (epochs=60)

 

last 와 best 가 동일한 결과를 보였다.

이제 사과 비슷한 모양은 비슷하게 검출되는 것을 확인 가능하다. (best.pt 사용했다.)

 

 

Full code 도 첨부하도록 하겠다.

필요한 대로 수정하면서 사용하도록 한다.

# --------------------------------------------------
# from ultralytics import YOLO
# model = YOLO('yolov8n.pt')
# model.train(data="C:/Users/admin/Desktop/github/Apple Finder/data.yaml", epochs=60)
# --------------------------------------------------

# from ultralytics import YOLO
# import torch

# model = torch.load("./best.pt")
# print(model)


#----------------------------------------------------
### image predict

from ultralytics import YOLO

# Load a model
model = YOLO("best.pt")  # pretrained YOLOv8n model

# Run batched inference on a list of images
# results = model(["app.jpg"])  # return a list of Results objects
results = model(["apple1.jpg",
                 "apple2.jpg",
                 "apple3.jpg",
                 "peaches1.jpg",
                 "peaches2.jpg",
                 "peaches3.jpg",
                 "plum1.jpg"])

# Process results list
for result in results:
    boxes = result.boxes  # Boxes object for bounding box outputs
    masks = result.masks  # Masks object for segmentation masks outputs
    keypoints = result.keypoints  # Keypoints object for pose outputs
    probs = result.probs  # Probs object for classification outputs
    obb = result.obb  # Oriented boxes object for OBB outputs
    result.show()  # display to screen
    result.save(filename="result.jpg")  # save to disk
#----------------------------------------------------------
반응형
반응형

기존 파일에서 이어서 간다.

GitHub - westonb/OV7670-Verilog: Verilog modules required to get the OV7670 camera working

 

GitHub - westonb/OV7670-Verilog: Verilog modules required to get the OV7670 camera working

Verilog modules required to get the OV7670 camera working - westonb/OV7670-Verilog

github.com

 

SCCB 인터페이스 동작에 대해 전에 봤고, 이번에는 카메라를 볼 차례이다.

 

camera_config_tb.v 를 보도록 한다.

 

마찬가지로 modelsim 으로 파형을 보는데, continue 를 눌러 끝까지 실행시킨다.

 

 

내부 플래그 이름을 보면 버스와 클럭에 관계되는

전체를 관리하는 부분 같다.

 

어,,

조금 이따 보고 read 먼저 본다.

 

camera_read_tb.v 파일은 다음과 같은 구조다.

 

module camera_read_tb;

// inputs 
reg p_clock;
reg vsync;
reg href;
reg [7:0] p_data;

//outputs 
wire [15:0] pixel_data;
wire pixel_valid;
wire frame_done;

 camera_read camera_read_1 (
        .p_clock(p_clock), 
        .vsync(vsync), 
        .href(href),
        .p_data(p_data), 
        .pixel_data(pixel_data), 
        .pixel_valid(pixel_valid),
        .frame_done(frame_done)
    );
    
    always #5 p_clock = ~p_clock;
    
    initial begin 
    
    p_clock = 0;
    vsync = 0;
    href = 0;
    p_data = 0;
    #5;
    #100;
    
    vsync = 1;
    #10;
    vsync = 0;
    #10;
    href = 1; 
    p_data = 8'hFF;
    #10; 
    p_data = 8'h0;
    #10;
    href = 0;
    #10;
    vsync = 0; 
    
    
    end
    
    
endmodule

 

일단 이것만 봐서는 좀 이해가 안 된다.

 

파형을 한 번 살펴본다.

 

 

105ns 에서 Vsync가 1로 뜨고, 115ns 에서 vsync 가 0으로 내려간다.

125ns에서 href가 1로 뜨고, p_data가 0xFF 값이 들어간다.

135ns 에서 p_data 가 0x00 값이 들어간다.

145ns 에서 href 가 다시 0으로 내려간다.

155ns 에서 vsync 가 다시 0으로 내려가는(아까 전에 0이였음) 흐름으로 테스트벤치가 구성되어 있다.

 

camera_read.v 본문 내용을 한 번 살펴본다.

 

module camera_read(
	input wire p_clock,
	input wire vsync,
	input wire href,
	input wire [7:0] p_data,
	output reg [15:0] pixel_data =0,
	output reg pixel_valid = 0,
	output reg frame_done = 0
    );
	 
	
	reg [1:0] FSM_state = 0;
    reg pixel_half = 0;
	
	localparam WAIT_FRAME_START = 0;
	localparam ROW_CAPTURE = 1;
	
	
	always@(posedge p_clock)
	begin 
	
	case(FSM_state)
	
	WAIT_FRAME_START: begin //wait for VSYNC
	   FSM_state <= (!vsync) ? ROW_CAPTURE : WAIT_FRAME_START;
	   frame_done <= 0;
	   pixel_half <= 0;
	end
	
	ROW_CAPTURE: begin 
	   FSM_state <= vsync ? WAIT_FRAME_START : ROW_CAPTURE;
	   frame_done <= vsync ? 1 : 0;
	   pixel_valid <= (href && pixel_half) ? 1 : 0; 
	   if (href) begin
	       pixel_half <= ~ pixel_half;
	       if (pixel_half) pixel_data[7:0] <= p_data;
	       else pixel_data[15:8] <= p_data;
	   end
	end
	
	
	endcase
	end
	
endmodule

 

굉장히 짧게 구성된 코드이다.

다른것 볼 필요 없이 state machine 만 보도록 한다.

 

WAIT_FRAME_START: begin //wait for VSYNC
   FSM_state <= (!vsync) ? ROW_CAPTURE : WAIT_FRAME_START;
   frame_done <= 0;
   pixel_half <= 0;
end

 

VSYNC 가 1일 때 frame_start(현재 state) 를 유지하고, 0이면 ROW_CAPTURE state 로 넘어간다.

frame_done, pixel_half 는 아직 나온것이 없기 때문에 초기화 해 주는 내용이라고 생각하고 넘어간다.

 

ROW_CAPTURE: begin 
   FSM_state <= vsync ? WAIT_FRAME_START : ROW_CAPTURE;
   frame_done <= vsync ? 1 : 0;
   pixel_valid <= (href && pixel_half) ? 1 : 0; 
   if (href) begin
       pixel_half <= ~ pixel_half;
       if (pixel_half) pixel_data[7:0] <= p_data;
       else pixel_data[15:8] <= p_data;
   end
end

 

ROW_CAPTURE state 에서

VSYNC가 1이면 WAIT_FRAME_START, 0이면 ROW_CAPTURE(현재 유지)가 된다. 아까의 state 와 반대이다.

그리고, frame_done 도 VSYNC 에 따라 바뀐다.

VSYNC 가 1이 되면 state 를 프레임 시작을 기다리고, 현재 프레임을 끝났다 라고 생각하게 만든다. 라고 볼 수 있다.

 

href 가 활성화 된 경우

pixel_half 값 반전,

pixel_half 가 1이면 하위 8비트에 픽셀 데이터를 넣음.

pixel_half 가 0이면 상위 8비트에 픽셀 데이터를 넣음.

 

역시나 이것만으로는 동작에 대한 설명이 좀 부족하다.

vsync와 href 의 동작을 알아야 카메라에서 어떤 값을 어떤 방식으로 받아올 수 있는지 알 수 있을 것이다.

 

 

 

 

 


 

다음으로 ov7670_config.v 를 보도록 한다.(camera_configure.v 가 아니다)

 

module OV7670_config
#(
    parameter CLK_FREQ = 25000000
)
(
    input wire clk,
    input wire SCCB_interface_ready,
    input wire [15:0] rom_data,
    input wire start,
    output reg [7:0] rom_addr,
    output reg done,
    output reg [7:0] SCCB_interface_addr,
    output reg [7:0] SCCB_interface_data,
    output reg SCCB_interface_start
    );
    
    initial begin
        rom_addr = 0;
        done = 0;
        SCCB_interface_addr = 0;
        SCCB_interface_data = 0;
        SCCB_interface_start = 0;
    end
    
    localparam FSM_IDLE = 0;
    localparam FSM_SEND_CMD = 1;
    localparam FSM_DONE = 2;
    localparam FSM_TIMER = 3;
    
    reg [2:0] FSM_state = FSM_IDLE;
    reg [2:0] FSM_return_state;
    reg [31:0] timer = 0; 
    
    always@(posedge clk) begin
    
        case(FSM_state)
            
            FSM_IDLE: begin 
                FSM_state <= start ? FSM_SEND_CMD : FSM_IDLE;
                rom_addr <= 0;
                done <= start ? 0 : done;
            end
            
            FSM_SEND_CMD: begin 
                case(rom_data)
                    16'hFFFF: begin //end of ROM
                        FSM_state <= FSM_DONE;
                    end
                    
                    16'hFFF0: begin //delay state 
                        timer <= (CLK_FREQ/100); //10 ms delay
                        FSM_state <= FSM_TIMER;
                        FSM_return_state <= FSM_SEND_CMD;
                        rom_addr <= rom_addr + 1;
                    end
                    
                    default: begin //normal rom commands
                        if (SCCB_interface_ready) begin
                            FSM_state <= FSM_TIMER;
                            FSM_return_state <= FSM_SEND_CMD;
                            timer <= 0; //one cycle delay gives ready chance to deassert
                            rom_addr <= rom_addr + 1;
                            SCCB_interface_addr <= rom_data[15:8];
                            SCCB_interface_data <= rom_data[7:0];
                            SCCB_interface_start <= 1;
                        end
                    end
                endcase
            end
                        
            FSM_DONE: begin //signal done 
                FSM_state <= FSM_IDLE;
                done <= 1;
            end
                           
                
            FSM_TIMER: begin //count down and jump to next state
                FSM_state <= (timer == 0) ? FSM_return_state : FSM_TIMER;
                timer <= (timer==0) ? 0 : timer - 1;
                SCCB_interface_start <= 0;
            end
        endcase
    end
endmodule

 

처음 부분에서 초기화를 시키고 있다.

 

initial begin
    rom_addr = 0;
    done = 0;
    SCCB_interface_addr = 0;
    SCCB_interface_data = 0;
    SCCB_interface_start = 0;
end

 

State 로 넘어간다.

 

FSM_IDLE: begin 
    FSM_state <= start ? FSM_SEND_CMD : FSM_IDLE;
    rom_addr <= 0;
    done <= start ? 0 : done;
end

 

start 입력이 들어오면 다음 state 인 FSM_SEND_CMD 로 넘어가고, 아니면 유지한다.

특이하게 done 값을 유지하고 있다. start 가 시작되면 다시 done 은 0으로 초기화된다.

 

FSM_SEND_CMD: begin 
    case(rom_data)
        16'hFFFF: begin //end of ROM
            FSM_state <= FSM_DONE;
        end
        
        16'hFFF0: begin //delay state 
            timer <= (CLK_FREQ/100); //10 ms delay
            FSM_state <= FSM_TIMER;
            FSM_return_state <= FSM_SEND_CMD;
            rom_addr <= rom_addr + 1;
        end
        
        default: begin //normal rom commands
            if (SCCB_interface_ready) begin
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_SEND_CMD;
                timer <= 0; //one cycle delay gives ready chance to deassert
                rom_addr <= rom_addr + 1;
                SCCB_interface_addr <= rom_data[15:8];
                SCCB_interface_data <= rom_data[7:0];
                SCCB_interface_start <= 1;
            end
        end
    endcase
end

 

rom_data 를 보고 FFFF signal(롬 종료 시그널) 이 오면 init command 보내는 것을 종료한다.

이 부분은 OV7670 내부 레지스터 초기 세팅을 하는 부분이라고 봐도 좋다.

 

여기도 따로 delay state 를 생성해 유지한다. FFF0 signal 이 오면 delay 10ms 만큼 delay 시킨다.

 

기본적으로는 SCCB interface ready flag가 뜨면 command 를 계속 보낸다.

rom 파일을 보게 되면 이상하게도 16비트가 나오는데, 앞은 reg address, 뒤는 value 로 나뉘는 것을 여기서 볼 수 있다.

 

결론은 rom 파일 내부 init command 를 모두 버스로 보내기 위해 사용하는 모듈이라고 보면 되겠다.

 

Done 상태는 뭐 없으니 넘어가도록 한다.

 


 

다시 camera_configure 로 온다.

tb 먼저 보도록 한다.

 

module camera_configure_tb;

    // inputs

    reg clk;
    reg start;
    
    //outputs 
    
    wire done;
    wire sioc;
    wire siod;
    
    camera_configure dut1
        (
        .clk(clk),
        .start(start),
        .sioc(sioc),
        .siod(siod),
        .done(done)
        );
        
        always #5 clk = ~ clk;
        
        initial begin
        clk = 0;
        start = 0;
        #100;
        start = 1;
        #10;
        start = 0;
        @(posedge done) $finish;
        end
endmodule

 

그냥 start 만 넣어주는 코드라고 봐도 무방하겠다.

FPGA 에 구현했다면, 마치 버튼을 눌러 동작 시작을 시켰다와 같다.

 

camera_configure.v 로 가 본다.

 

module camera_configure
    #(
    parameter CLK_FREQ=25000000
    )
    (
    input wire clk,
    input wire start,
    output wire sioc,
    output wire siod,
    output wire done
    );
    
    wire [7:0] rom_addr;
    wire [15:0] rom_dout;
    wire [7:0] SCCB_addr;
    wire [7:0] SCCB_data;
    wire SCCB_start;
    wire SCCB_ready;
    wire SCCB_SIOC_oe;
    wire SCCB_SIOD_oe;
    
    assign sioc = SCCB_SIOC_oe ? 1'b0 : 1'bZ;
    assign siod = SCCB_SIOD_oe ? 1'b0 : 1'bZ;
    
    OV7670_config_rom rom1(
        .clk(clk),
        .addr(rom_addr),
        .dout(rom_dout)
        );
        
    OV7670_config #(.CLK_FREQ(CLK_FREQ)) config_1(
        .clk(clk),
        .SCCB_interface_ready(SCCB_ready),
        .rom_data(rom_dout),
        .start(start),
        .rom_addr(rom_addr),
        .done(done),
        .SCCB_interface_addr(SCCB_addr),
        .SCCB_interface_data(SCCB_data),
        .SCCB_interface_start(SCCB_start)
        );
    
    SCCB_interface #( .CLK_FREQ(CLK_FREQ)) SCCB1(
        .clk(clk),
        .start(SCCB_start),
        .address(SCCB_addr),
        .data(SCCB_data),
        .ready(SCCB_ready),
        .SIOC_oe(SCCB_SIOC_oe),
        .SIOD_oe(SCCB_SIOD_oe)
        );
    
endmodule

 

기본적으로 지금까지 봤던 모듈들을 재사용하는데, 한 가지 read 가 없다.

여기서는 SCCB interface 를 통해 동작시키는 것 까지가 메인이 되겠다.

 

전체 파형은 다음과 같다.

 

 

read 는 이 이후, SCCB init register setting 이 끝난 이후의 vsync, href 값을 판단해서 들고 와서 hdmi, vga 등 화면을 띄울 수 있는 인터페이스에 픽셀 데이터를 넘겨주면 될 것 같다.

반응형

'Verilog' 카테고리의 다른 글

[Verilog] SCCB & OV7670 - more1  (0) 2024.05.28
[Verilog] SCCB Interface & OV7670  (0) 2024.04.16
반응형

GitHub - westonb/OV7670-Verilog: Verilog modules required to get the OV7670 camera working

 

GitHub - westonb/OV7670-Verilog: Verilog modules required to get the OV7670 camera working

Verilog modules required to get the OV7670 camera working - westonb/OV7670-Verilog

github.com

를 참고했다.

 

SCCB가 어떻게 움직이는지 보기 위해 modelsim 으로 SCCB_interface_tb.v 를 시뮬레이션 돌려 보았다.

 

봐야 할 코드 부분은 다음과 같다.

 

SCCB_interface 
    #(.CLK_FREQ(800), .SCCB_FREQ(20)) 
    dut1
    (
    .clk(clk),
    .start(start),
    .address(address),
    .data(data),
    .ready(ready),
    .SIOC_oe(SIOC_oe),
    .SIOD_oe(SIOD_oe)
    );
    
    always #5 clk=~clk;
    
    initial begin
        clk = 0;
        start = 0;
        address = 0;
        data = 0;
        
        #100;
        address = 8'hAA;
        data = 8'h77;
        start = 1;
        
        #10;
        start = 0;
        address = 0;
        data = 0;
    end

 

시뮬레이션용으로 Clock freq를 800, bus freq를 20으로 설정해 뒀다.

클럭은 10ns 마다 pos edge 를 가진다.

 

처음 clk, start, address, data 가 0이고

 

100 ns 가 지나면 address에 AA, data에 77, 시작 값을 가진다.

 

그리고 그 후 10ns 가 지나면 start, address, data를 0으로 돌린다.

 

 이런 경우 아마 내부에 ready 플래그를 둬서 start 입력에도 내부 동작이 끝나지 않았다면 동작하지 않게 만들 가능성이 크다.

 

SCCB interface 코드를 한 번 보도록 한다.

 

module SCCB_interface
#(
    parameter CLK_FREQ = 25000000,
    parameter SCCB_FREQ = 100000
)
(
    input wire clk,
    input wire start,
    input wire [7:0] address,
    input wire [7:0] data,
    output reg ready,
    output reg SIOC_oe,
    output reg SIOD_oe
    );
    
    localparam CAMERA_ADDR = 8'h42;
    localparam FSM_IDLE = 0;
    localparam FSM_START_SIGNAL = 1;
    localparam FSM_LOAD_BYTE = 2;
    localparam FSM_TX_BYTE_1 = 3;
    localparam FSM_TX_BYTE_2 = 4;
    localparam FSM_TX_BYTE_3 = 5;
    localparam FSM_TX_BYTE_4 = 6;
    localparam FSM_END_SIGNAL_1 = 7;
    localparam FSM_END_SIGNAL_2 = 8;
    localparam FSM_END_SIGNAL_3 = 9;
    localparam FSM_END_SIGNAL_4 = 10;
    localparam FSM_DONE = 11;
    localparam FSM_TIMER = 12;
    
    
    initial begin 
        SIOC_oe = 0;
        SIOD_oe = 0;
        ready = 1;
    end
    
    reg [3:0] FSM_state = 0;
    reg [3:0] FSM_return_state = 0;
    reg [31:0] timer = 0;
    reg [7:0] latched_address;
    reg [7:0] latched_data; 
    reg [1:0] byte_counter = 0;
    reg [7:0] tx_byte = 0;
    reg [3:0] byte_index = 0;
    
    
    always@(posedge clk) begin
        
        case(FSM_state) 
        
            FSM_IDLE: begin
                byte_index <= 0;
                byte_counter <= 0;
                if (start) begin 
                    FSM_state <= FSM_START_SIGNAL;
                    latched_address <= address;
                    latched_data <= data;
                    ready <= 0;
                end
                else begin
                    ready <= 1;
                end
            end
            
            FSM_START_SIGNAL: begin //comunication interface start signal, bring SIOD low
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_LOAD_BYTE;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 0;
                SIOD_oe <= 1;
            end
            
            FSM_LOAD_BYTE: begin //load next byte to be transmitted
                FSM_state <= (byte_counter == 3) ? FSM_END_SIGNAL_1 : FSM_TX_BYTE_1;
                byte_counter <= byte_counter + 1;
                byte_index <= 0; //clear byte index
                case(byte_counter)
                    0: tx_byte <= CAMERA_ADDR;
                    1: tx_byte <= latched_address;
                    2: tx_byte <= latched_data;
                    default: tx_byte <= latched_data;
                endcase
            end
            
            FSM_TX_BYTE_1: begin //bring SIOC low and and delay for next state 
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_TX_BYTE_2;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 1; 
            end
            
            FSM_TX_BYTE_2: begin //assign output data, 
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_TX_BYTE_3;
                timer <= (CLK_FREQ/(4*SCCB_FREQ)); //delay for SIOD to stabilize
                SIOD_oe <= (byte_index == 8) ? 0 : ~tx_byte[7]; //allow for 9 cycle ack, output enable signal is inverting
            end
            
            FSM_TX_BYTE_3: begin // bring SIOC high 
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_TX_BYTE_4;
                timer <= (CLK_FREQ/(2*SCCB_FREQ));
                SIOC_oe <= 0; //output enable is an inverting pulldown
            end
            
            FSM_TX_BYTE_4: begin //check for end of byte, incriment counter
                FSM_state <= (byte_index == 8) ? FSM_LOAD_BYTE : FSM_TX_BYTE_1;
                tx_byte <= tx_byte<<1; //shift in next data bit
                byte_index <= byte_index + 1;
            end
            
            FSM_END_SIGNAL_1: begin //state is entered with SIOC high, SIOD high. Start by bringing SIOC low
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_END_SIGNAL_2;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 1;
            end
            
            FSM_END_SIGNAL_2: begin // while SIOC is low, bring SIOD low 
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_END_SIGNAL_3;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOD_oe <= 1;
            end
            
            FSM_END_SIGNAL_3: begin // bring SIOC high
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_END_SIGNAL_4;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 0;
            end
            
            FSM_END_SIGNAL_4: begin // bring SIOD high when SIOC is high
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_DONE;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOD_oe <= 0;
            end
            
            FSM_DONE: begin //add delay between transactions
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_IDLE;
                timer <= (2*CLK_FREQ/(SCCB_FREQ));
                byte_counter <= 0;
            end
            
            FSM_TIMER: begin //count down and jump to next state
                FSM_state <= (timer == 0) ? FSM_return_state : FSM_TIMER;
                timer <= (timer==0) ? 0 : timer - 1;
            end
        endcase
    end
    
    
endmodule

 

위의 테스트벤치에서 CLK_FREQ를 800, SCCB_FREQ를 20으로 제한했다.

 

initial begin 
        SIOC_oe = 0;
        SIOD_oe = 0;
        ready = 1;
    end

내부 파라미터는 넘어가고, SIOC_oe, SIOD_oe, ready 를 init 에서 할당해주고 있다. 순차적으로 안 해도 될 것 같은 부분이다.

 

State machine 을 한 번 본다.

FSM_IDLE: begin
                byte_index <= 0;
                byte_counter <= 0;
                if (start) begin 
                    FSM_state <= FSM_START_SIGNAL;
                    latched_address <= address;
                    latched_data <= data;
                    ready <= 0;
                end
                else begin
                    ready <= 1;
                end
            end

 

IDLE 상태에서 입력(start)이 들어오면 다음 상태로 변화시키는 코드다.

기본 값들은 초기화 혹은 리셋 시켜주면서, 입력이 들어오면 현재 address, data 를 레지스터에 저장한다.

뒤에서 이 값들(latched_address, latched_data)을 변화시키는 부분이 없었으므로 계속 값을 가져갈 것으로 보인다.

 

FSM_START_SIGNAL: begin //comunication interface start signal, bring SIOD low
                FSM_state <= FSM_TIMER;
                FSM_return_state <= FSM_LOAD_BYTE;
                timer <= (CLK_FREQ/(4*SCCB_FREQ));
                SIOC_oe <= 0;
                SIOD_oe <= 1;
            end

 

START_SIGNAL 부분은 IDLE에서 start 입력이 들어오면 다음 클럭에서 state가 변화되는 부분이다.

특이하게 return state 를 지정하고 있는데, 이는 다음 state 인 TIMER 로 인해 필요한 부분이다.

또, timer 는 FPGA의 클럭과 버스를 동기화하기 위해 사용하고, 

SIOC_oe(SCL) 은 0을 유지하고, SIOD_oe(SDA) 는 1로 올라간다. 이는 SCCB의 start signal이다.

I2C 와 다른 인터페이스인 이유이기도 하다.

 FSM_TIMER: begin //count down and jump to next state
                FSM_state <= (timer == 0) ? FSM_return_state : FSM_TIMER;
                timer <= (timer==0) ? 0 : timer - 1;
            end

 

자주 등장하는 TIMER state 를 본다.

다른 건 없고, 시간 때우기 용으로 넣어 놓은 state 라고 보면 좋다.

timer 가 0이 될 때 까지 현재 상태를 유지하는데, 이 때 SCL, SDA 값도 유지된다.

위의 waveform 에서 FSM_state 가 1100(decimal 값으로 12)에서 길게 유지하는 것을 볼 수 있다.

 실제로 파형을 보면 timer 이외에는 FSM_state 가 1100(binary) 인 때는 움직이지 않고 유지하는 것을 볼 수 있음.

 

timer가 0이 되어 return_state 로 돌아가도록 한다.

현재 return_state 는 FSM_LOAD_BYTE 로 지정되어 있다.

FSM_LOAD_BYTE: begin //load next byte to be transmitted
    FSM_state <= (byte_counter == 3) ? FSM_END_SIGNAL_1 : FSM_TX_BYTE_1;
    byte_counter <= byte_counter + 1;
    byte_index <= 0; //clear byte index
    case(byte_counter)
        0: tx_byte <= CAMERA_ADDR;
        1: tx_byte <= latched_address;
        2: tx_byte <= latched_data;
        default: tx_byte <= latched_data;
    endcase
end

 

FSM_state 가 byte_counter 가 3이면 분기하는 조건이 있는걸 봐선 여기서 바이트 카운터를 올리는 것이 있던지, 다른 state 에서 바이트 카운터를 올리는 옵션이 있을 것이다.

실제로 다음 코드에서 바이트코드를 증가시키는 코드가 있다.

그리고 뭔지 모르겠지만 바이트 인덱스를 0으로 만든다. 다른 곳에서 바이트 인덱스를 사용하고 여기 다시 돌아와서 뭔가 한다는 의미일 확률이 높겠다.

그리고 바이트카운터에 따라서 tx_byte 의 값을 조정한다.

 

CAMERA_ADDR 은 localparam 에서 42로 지정했다.

 

현재 LOAD_BYTE 에서의 값은 다음과 같다.

 

현재 막 LOAD_BYTE에 도착했다.

다음 클럭에서 바뀌는 값들을 보도록 한다.

FSM_state 는 byte_counter 가 3이 아니므로(0임) FSM_TX_BYTE_1

byte_counter 은 다음 클럭에 1로

byte_index 는 다음 클럭에 0으로

tx_byte 는 다음 클럭에 CAMERA_ADDR 의 값을 가짐.

non-blocking(<=) 이기 때문에 순차적 처리가 아닌 병렬 처리를 함.

 

 

실제 다음 클럭의 값임.

 

이번에 온 TX_BYTE_1 을 살펴본다.

 

FSM_TX_BYTE_1: begin //bring SIOC low and and delay for next state 
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_TX_BYTE_2;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOC_oe <= 1; 
end

 

다음 클럭에 SCL 을 변화시킨다. 실제로 통신을 시작하려는 부분임.

이 부분에서 SCL = 1, SDA = 1 이 되므로 통신 시작, 이제 8비트의 데이터를 보내고 1비트의 ACK 를 수신해야 함.

하지만 다음 state 가 TIMER 로 다시 간다.

이로 인해 SCCB 인터페이스와 FPGA 사이의 클럭 차이가 굉장히 많이 나기 때문에 클럭 손실이 크다.

따라서 I2C 를 느려터진 인터페이스라고 하는 이유다.

 

현재 코드 상에서는 시뮬레이션이 아닌, 기기에서의 클럭을 25Mhz, SCCB freq 를 100Khz 로 정의해 놓았다.

timer 에서 SCCB 주파수에 4를 곱하는 이유는 아마 I2C 처럼 고속 모드(400Khz) 지원을 위함이지 싶다.

아무튼 저 값을 나누면 62.5 가 나오는데 소수 버림으로 62 가 나온다.

아무것도 안하고 벌써 62 클럭을 2번이나 낭비했다.

 

아무튼 return state 를 TX_BYTE_2 로 정의하고 다시 timer 에 갔다 온다.

 

FSM_TX_BYTE_2: begin //assign output data, 
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_TX_BYTE_3;
    timer <= (CLK_FREQ/(4*SCCB_FREQ)); //delay for SIOD to stabilize
    SIOD_oe <= (byte_index == 8) ? 0 : ~tx_byte[7]; //allow for 9 cycle ack, output enable signal is inverting
end

 

timer 에 갔다 다시 돌아온 state 다.

근데 다음 state 가 다시 timer 이다.

여기서는 잘 보니, 한 바이트에 대해 처리하고 있는 것을 볼 수 있다.

SDA 에 byte index 가 8(9번째 비트, ACK) 가 아니면 tx_byte[7]을 반전시켜 넣고 있다.

 

return state 를 TX_BYTE_3으로 지정하고 다시 timer 로 간다. (62*3)

 

FSM_TX_BYTE_3: begin // bring SIOC high 
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_TX_BYTE_4;
    timer <= (CLK_FREQ/(2*SCCB_FREQ));
    SIOC_oe <= 0; //output enable is an inverting pulldown
end

 

이번에도 다음 state 는 TIMER 이다.

여기서는 SCL 을 0으로 바꾼다. 

다른 건 없다.

return state 를 TX_BYTE_4 로 지정하고 다음 timer 로 간다. (62*4)

 

 

 

FSM_TX_BYTE_4: begin //check for end of byte, incriment counter
    FSM_state <= (byte_index == 8) ? FSM_LOAD_BYTE : FSM_TX_BYTE_1;
    tx_byte <= tx_byte<<1; //shift in next data bit
    byte_index <= byte_index + 1;
end

 

byte index 가 8이 아니면(ack) 다시 TX_BYTE_1 로 돌아간다. 아니면 LOAD_BYTE 로 간다.

tx_byte 는 하나 shift 하는데, 이는 이미 우리가 반전시킨 tx_byte[7] 을 SDA 로 보냈기 때문이다.

그리고 byte_index 를 1 증가시킨다.

 

대략적으로 다음과 같은 구조가 되었다.

 

결국 TX_BYTE_1 ~ TX_BYTE_4 를 돌면서 1 Bit 를 보낸 것과 같다.

근데 TX_BYTE 에 사용한 클럭은 

LOAD, TX1, TX2, TX3, TX4, TIMER*3 = 5 + 186 = 191 clock 이다.

25Mhz cpu 를 사용하면 7,640 ns = 7.64us 가 소요된 것이다.

이걸 이제 byte_index 가 8이 될 때 까지, 총 9번 실행해 준다.

그럼 이제 CAMERA_ADDRESS 를 SCCB interface 로 보내는 것이 완료 된 것이다.

 

그리고 다시 LOAD_BYTE 로 오게 된다.

byte_counter 에 따라, 3이 될 때 까지 반복한다. (기기 address, 내부 레지스터 address, 쓸 값)

 

3이 되었으면, END_SIGNAL_1 로 간다.

 

FSM_END_SIGNAL_1: begin //state is entered with SIOC high, SIOD high. Start by bringing SIOC low
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_END_SIGNAL_2;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOC_oe <= 1;
end

 

여기서는 통신을 종료하기 위해 STOP 시그널을 만드려는 쪽 같다.

SCL 을 1로 만든다.

return state 를 END_SIGNAL_2 로 지정하고, timer 로 간다.

파형으로 보면 저 위치이다.

 

자세하게 보면 이렇다.

 

FSM_END_SIGNAL_2: begin // while SIOC is low, bring SIOD low 
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_END_SIGNAL_3;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOD_oe <= 1;
end

 

이번엔 SDA를 1로 만든다.

return state 를 END_SIGNAL_3 으로 지정하고 timer 로 간다.

파형은 다음과 같다.

 

FSM_END_SIGNAL_3: begin // bring SIOC high
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_END_SIGNAL_4;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOC_oe <= 0;
end

 

이번에 SCL 을 다시 0으로 만든다.

return state 를 END_SIGNAL_4 로 지정하고 timer 로 간다.

파형은 다음과 같다.

 

 

FSM_END_SIGNAL_4: begin // bring SIOD high when SIOC is high
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_DONE;
    timer <= (CLK_FREQ/(4*SCCB_FREQ));
    SIOD_oe <= 0;
end

 

마지막으로 SDA 를 0으로 내린다.

주석 적어놓은 거 보면 I2C 를 완전히 반대로 구현해 놓은 것과 동일한 것 같다.

timer 를 지나고, 마지막 done state 로 간다.

 

I2C를 보게 되면 마지막이 모두 HIGH로 끝나고, SCCB 는 정 반대이다.

 

FSM_DONE: begin //add delay between transactions
    FSM_state <= FSM_TIMER;
    FSM_return_state <= FSM_IDLE;
    timer <= (2*CLK_FREQ/(SCCB_FREQ));
    byte_counter <= 0;
end

 

Done state 는 다음 트랜잭션이 오기 전의 딜레이 역할을 한다.

딱히 byte_counter 를 초기화 안 해도 동작에는 지장이 없을 것 같다.(IDLE에서 초기화를 하기 때문)

시간이 저렇게 표기되어 있는데 작동에 준수해야 되는 최소 시간인지는 잘 모르겠다.

 

 

반응형

'Verilog' 카테고리의 다른 글

[Verilog] OV7670 - more2  (0) 2024.05.28
[Verilog] SCCB Interface & OV7670  (0) 2024.04.16
반응형

취미로 빌드한 지 2년 정도가 지난, TES68을 한 번 청소해 봤다.

 

 

하우징? 으로 Nuphy 의 TES68 orange 를 사용했다.

 

스위치는 Aliexpress에서 공장윤활된 gateron yellow pro 스위치를 사용했고

 

키캡은 피카츄 XDA 프로파일 키캡을 사용하고 있다. 개인적으로 백스페이스가 흰 색이였으면 좋았을 것 같다.

 

 

스위치가 milky yellow 축이라고는 하는데 그냥 황축인 것 같다..

 

 

 

하우징은 Nuphy 공식 사이트에서 구매했다. 

 

최근에 다시 스위치 & 스테빌 간이윤활을 한 번 진행했는데, 확실히 스테빌 쪽은 소리가 조금 잡혔다. 전에는 철심 소리가 강하게 났었는데 그게 좀 줄어든 것 같다.

 

 

키보드를 강하게 누르는 편이라 소리가 좀 나는데, 그래도 한번 쯤 커스텀 빌드는 해 볼만한 것 같다.

 

 

타건 소리도 한 번 첨부해 봤다!..

 

다음에는 어떤 키보드를 만들어 볼 지 고민이 되는구만,,

반응형

+ Recent posts