RTL, Synthesis, P&R

32-bit SPI Interface - (3) Arduino로 FPGA Motor 제어

MiddleJo 2024. 9. 3. 19:27

- 이전 글

(1) RTL : https://chonh0531.tistory.com/2

(2) Synthesis, P&R : https://chonh0531.tistory.com/3

목차

1. 과제 개요

2. 소스코드

3. 실습 결과

 

 

1. 과제 개요

이전에 구현해 본 SPI_Slave를 테스트해 보기로 하였고,

Master 역할로 Arduino Uno 보드를 이용하여

FPGA에서 Motor를 제어해보도록 합니다.

 

1. 컴퓨터에서 UART 통신으로 Arduino Uno 보드에 명령어를 전달
2. Arduino Uno는 FPGA에 SPI 통신으로 명령을 전달
3. FPGA는 data 부분의 마지막 4비트를 모터 속도제어에 이용

 

 

 

2. 소스코드

- SPI_Slave

`timescale 1ns / 1ps

module top(
    input i_clk, i_SCLK, i_CS, i_rst_H, i_MOSI,
    output o_out, o_nsleep, o_MISO 
    );
    
    wire [3:0] w_duty;
    wire [4:0] w_bitcnt;
                                          
    SPI_Slave U0 (.i_clk(i_clk), .i_SCLK(i_SCLK), .i_CS(i_CS), .i_rst(i_rst_H), .i_MOSI(i_MOSI), .o_MISO(o_MISO), .o_duty(w_duty), .o_bitcnt(w_bitcnt));
    motor U1 (.i_clk(i_clk), .i_rst(i_rst), .i_duty(w_duty), .o_out(o_out), .o_nsleep(o_nsleep));

endmodule

 

module motor(
    input i_clk,
    input i_rst_H,
    input [3:0] i_duty,
    output reg o_out,
    output reg o_nsleep
    );

    reg [18:0] r_cnt;
    reg [4:0] r_duty;
    reg r_clk2;

    always @(posedge i_clk or posedge i_rst_H) begin
        if (i_rst_H)
            o_nsleep <= 0;
        else 
            o_nsleep <= 1;
    end

    always @(posedge i_clk or posedge i_rst_H) begin
        if (i_rst_H) begin
            r_cnt <= 0;
            r_clk2 <= 0;
        end
        else if (r_cnt == 500_000) begin
            r_clk2 <= ~r_clk2;
            r_cnt <= 0;
        end
        else
            r_cnt <= r_cnt + 1;
    end
    
    always @(posedge r_clk2 or posedge i_rst_H) begin
        if (i_rst_H)
            r_duty <= 0;
        else if (r_duty >= 16)
            r_duty <= 0;
        else
            r_duty <= r_duty + 1;
    end
    
    always @(posedge i_clk or posedge i_rst_H) begin
        if (i_rst_H)
            o_out <= 0;
        else if (r_duty < i_duty)
            o_out <= 1;
        else
            o_out <= 0;
    end
endmodule

 

`timescale 1ns / 1ps

`define IDLE 2'b00
`define READ 2'b10
`define WRITE 2'b11

module SPI_Slave(
    // Internal input
    input i_clk,
    input i_rst_H,
    input [1:0] i_ID,

    // SPI Interface
    input i_SCLK,
    input i_CS,
    input i_MOSI,
    output reg o_MISO,

    // Additional output for duty cycle
    output reg [3:0] o_duty
    );

    reg [5:0] bitcnt = 6'd0;
    reg [1:0] current_state = 2'b00;
    reg [1:0] next_state = 2'b00;
    
    reg [15:0] D0;
    reg [15:0] D1;

    reg addr;    
    reg [31:0] data_RX;
    reg id_ok;

// FSM part
    always @(negedge i_SCLK or posedge i_CS) begin
        if (i_CS) begin
            current_state <= `IDLE;
            next_state <= `IDLE;
        end
        else begin
            current_state <= next_state;
            case(current_state)
                `IDLE : begin
                    if (bitcnt == 6'd3 && id_ok == 1 && data_RX[0] == 1)
                        next_state <= `READ;
                    else if (bitcnt == 6'd3 && id_ok == 1 && data_RX[0] == 0)
                        next_state <= `WRITE;
                    else
                        next_state <= next_state;
                end
                `READ : begin
                    if (bitcnt == 6'd0)
                        next_state <= `IDLE;
                    else
                        next_state <= next_state;
                end
                `WRITE : begin
                    if (bitcnt == 6'd0)
                        next_state <= `IDLE;
                    else
                        next_state <= next_state;
                end
                default : next_state <= next_state; // no cases. just for synthesis
            endcase
        end
    end

    always @(posedge i_SCLK or posedge i_CS) begin
        if(i_CS) begin
            id_ok <= 0;
        end
        else if (bitcnt == 6'd2 && data_RX[1:0] == i_ID) begin
            id_ok <= 1;
        end
        else
            id_ok <= id_ok;
    end

// Receive data
    always @(negedge i_SCLK or posedge i_CS) begin
        if(i_CS) begin
            bitcnt <= 6'd0;
            data_RX <= 32'd0;
        end
        else begin
            bitcnt <= bitcnt + 6'd1;
            data_RX <= {data_RX[30:0], i_MOSI};
        end
    end

// Read address
    always @(negedge i_SCLK or posedge i_CS) begin
        if(i_CS) begin
            addr <= 1'd0;
        end
        else if (current_state == `IDLE && bitcnt == 6'd4) begin
            addr <= data_RX[0];
        end
        else
            addr <= addr;
    end

// Write data
    always @(posedge i_SCLK or posedge i_rst_H) begin
        if(i_rst_H) begin
            D0 <= 16'd0;
            D1 <= 16'd0;
        end
        else if(current_state == `WRITE && addr == 1'b0 && bitcnt == 6'd32) begin
            D0 <= data_RX[15:0];
        end
        else if(current_state == `WRITE && addr == 1'b1 && bitcnt == 6'd32) begin
            D1 <= data_RX[15:0];
        end
        else begin
            D0 <= D0;
            D1 <= D1;
        end
    end

// Update o_duty from D0 or D1
always @(posedge i_SCLK or posedge i_rst_H) begin
    if (i_rst_H)
        o_duty <= 4'd0;
    else if (current_state == `WRITE && bitcnt == 6'd32) begin
        if (addr == 1'b0)
            o_duty <= D0[3:0]; // Update duty cycle from the lower 4 bits of D0
        else
            o_duty <= D1[3:0]; // Update duty cycle from the lower 4 bits of D1
    end
    else
        o_duty <= o_duty;
end

// READ Mode: data transfer
    always @(posedge i_SCLK) begin
        if(current_state == `READ && addr == 1'b0) begin
            case(bitcnt)
                6'd16 : o_MISO <= D0[15];
                6'd17 : o_MISO <= D0[14];
                6'd18 : o_MISO <= D0[13];
                6'd19 : o_MISO <= D0[12];
                6'd20 : o_MISO <= D0[11];
                6'd21 : o_MISO <= D0[10];
                6'd22 : o_MISO <= D0[9];
                6'd23 : o_MISO <= D0[8];
                6'd24 : o_MISO <= D0[7];
                6'd25 : o_MISO <= D0[6];
                6'd26 : o_MISO <= D0[5];
                6'd27 : o_MISO <= D0[4];
                6'd28 : o_MISO <= D0[3];
                6'd29 : o_MISO <= D0[2];
                6'd30 : o_MISO <= D0[1];
                6'd31 : o_MISO <= D0[0];
                default : o_MISO <= 0;
            endcase
        end else if(current_state == `READ && addr == 1'b1) begin
            case(bitcnt)
                6'd16 : o_MISO <= D1[15];
                6'd17 : o_MISO <= D1[14];
                6'd18 : o_MISO <= D1[13];
                6'd19 : o_MISO <= D1[12];
                6'd20 : o_MISO <= D1[11];
                6'd21 : o_MISO <= D1[10];
                6'd22 : o_MISO <= D1[9];
                6'd23 : o_MISO <= D1[8];
                6'd24 : o_MISO <= D1[7];
                6'd25 : o_MISO <= D1[6];
                6'd26 : o_MISO <= D1[5];
                6'd27 : o_MISO <= D1[4];
                6'd28 : o_MISO <= D1[3];
                6'd29 : o_MISO <= D1[2];
                6'd30 : o_MISO <= D1[1];
                6'd31 : o_MISO <= D1[0];
                default : o_MISO <= 0;
            endcase
        end else
            o_MISO <= 1'b0;
    end

endmodule

 

기존 코드에서, 입력으로 받은 데이터중 하위 4비트 데이터를 duty로 전달하였습니다.

motor 제어 부분은 간단하므로, 추가로 설명하지 않았습니다.

 

 

- SPI_Master에 사용할 Arduino 코드

#include <SPI.h>


enum InputState {
  WAITING_FOR_MODE,
  WAITING_FOR_ID,  // ID 입력 대기 상태 추가
  WAITING_FOR_UPPER_LOWER,
  WAITING_FOR_ADDRESS,
  WAITING_FOR_DATA
};

InputState currentState = WAITING_FOR_MODE;

bool writeMode;
bool upperData; // 4번째 비트 (Upper/Lower)
bool address;
uint16_t addr; // 주소
uint16_t data; // 데이터
uint8_t id; // ID 값 (2비트)

const int CS_PIN = 10; // SS 핀 (CS 핀) 고정값 변경 못함
const int SCLK_PIN = 13; // SCLK 핀
const int ID_PIN_1 = 40; // ID 핀 1
const int ID_PIN_2 = 42; // ID 핀 2

void setup() {
  Serial.begin(9600); // 통신속도
  SPI.begin(); // SPI 초기화
  SPI.setClockDivider(SPI_CLOCK_DIV2); // 클럭 속도 설정
  SPI.setDataMode(SPI_MODE2); // SPI 모드 설정 (모드 2)

  // SPI 핀 모드 설정
  pinMode(CS_PIN, OUTPUT); // SS 핀을 출력 모드로 설정
  digitalWrite(CS_PIN, HIGH); // 슬레이브 비선택 상태
  pinMode(SCLK_PIN, OUTPUT); // SCLK 핀을 출력 모드로 설정
  digitalWrite(SCLK_PIN, LOW); // 초기 상태 LOW

  // ID 핀 모드 설정
  pinMode(ID_PIN_1, OUTPUT);
  pinMode(ID_PIN_2, OUTPUT);
  digitalWrite(ID_PIN_1, LOW);
  digitalWrite(ID_PIN_2, LOW);

  // 시작 프롬프트 출력
  Serial.println("Enter read/write mode (1 for read, 0 for write):"); // 콘솔에 값 출력
}

void loop() { // 반복문 
  if (Serial.available() > 0) { //시리얼 통신에 값이 있으면
    String input = Serial.readStringUntil('\n'); // 엔터까지 읽기
    input.trim(); // 공백 제거

    if (currentState == WAITING_FOR_MODE) {
      // 모드 입력 받기
      if (input.equals("1") || input.equals("0")) {
        writeMode = input.equals("0"); // 수정: 0을 입력하면 true(0), 1을 입력하면 false(1) 0이 WRITE니까 입력해야 하니까 상태 변화
        currentState = WAITING_FOR_ID; // ID 입력 대기 상태로 전환
        Serial.println("Enter ID value (0 to 3):");  // WRITE일 경우 ID 값 받기
      } else {
        Serial.println("Invalid input. Enter 1 for read, 0 for write:"); //READ는 그냥 넘어가고 0, 1 이외의 값은 그냥 끝내기
      }
    } else if (currentState == WAITING_FOR_ID) { // WRITE 입력하면 상태가 얘로 바뀌니까 반복문에 의해서 얘가 실행
      // ID 입력 받기
      int idValue = input.toInt();
      if (idValue >= 0 && idValue <= 3) {
        id = idValue;
        digitalWrite(ID_PIN_1, id & 0x01); // id가 입력한 값으로 들어온 만약 id로 3을 입력하면 0000 0011이랑 0000 0001이란 &연산 = 0000 0001
        digitalWrite(ID_PIN_2, (id >> 1) & 0x01); // 0000 0011 >> 1 => 0000 0001 & 0000 00001
        currentState = WAITING_FOR_UPPER_LOWER;
        Serial.println("Enter upper or lower (1 for upper, 0 for lower):");
      } else {
        Serial.println("Invalid input. Enter ID value (0 to 3):");
      }
    } else if (currentState == WAITING_FOR_UPPER_LOWER) {
      // upper/lower 입력 받기
      upperData = input.toInt();
      // if (input.equals("1") || input.equals("0")) {
        //upperData = input.equals("1");
      if(upperData == 1 || upperData == 0) {
        currentState = WAITING_FOR_ADDRESS;
        Serial.println("Enter address (in hex):");
      } else {
        Serial.println("Invalid input. Enter 1 for upper, 0 for lower:");
      }
    } else if (currentState == WAITING_FOR_ADDRESS) { // ADDRESS인데 그냥 그 빈공간임
      // 주소 입력 받기
      addr = strtol(input.c_str(), NULL, 16);
      currentState = WAITING_FOR_DATA;
      Serial.println("Enter data (in hex):");
    } else if (currentState == WAITING_FOR_DATA) {
      // 데이터 입력 받기
      data = strtol(input.c_str(), NULL, 16);

      // 32비트 데이터 구성
      uint32_t dataToSend = ((uint32_t)id << 30) | ((uint32_t)(!writeMode) << 29) | ((uint32_t)upperData << 28) | ((uint32_t)addr << 16) | data;

      // SPI 데이터 전송
      SPI.beginTransaction(SPISettings(800, MSBFIRST, SPI_MODE2)); // SPI 설정 (모드 2)
      digitalWrite(CS_PIN, LOW); // 슬레이브 선택

      byte bytesToSend[4];
      bytesToSend[0] = (dataToSend >> 24) & 0xFF;
      bytesToSend[1] = (dataToSend >> 16) & 0xFF;
      bytesToSend[2] = (dataToSend >> 8) & 0xFF;
      bytesToSend[3] = dataToSend & 0xFF;
      
      uint32_t receivedData = 0;
      for (int i = 0; i < 4; ++i) {
        receivedData = (receivedData << 8) | SPI.transfer(bytesToSend[i]);
      }

      digitalWrite(CS_PIN, HIGH); // 슬레이브 비선택
      SPI.endTransaction(); // SPI 트랜잭션 종료

      // 결과 출력
      Serial.print("Sent: ID(");
      Serial.print(id, BIN); // ID 값을 이진수로 출력
      Serial.print("), ");
      Serial.print(writeMode ? "Write" : "Read");
      Serial.print(", ");
      Serial.print(upperData ? "Upper" : "Lower");
      Serial.print(", Address: 16'b");
      Serial.print(addr, BIN);
      Serial.print(", Data: 16'b");
      Serial.print(data, BIN);
      Serial.println();

      // 읽기 모드일 때 수신된 데이터 출력
      if (!writeMode) {
        Serial.print("Received: 0x");
        Serial.println(receivedData, HEX);
      }

      // 다시 모드 입력 상태로 전환
      currentState = WAITING_FOR_MODE;
      Serial.println("Enter read/write mode (1 for read, 0 for write):");
    }
  }
}

 

upper / lower는 각각 D1, D0 데이터에 넣는다는 의미입니다.

 

3. 실습결과

FPGA는 ZYNQ-7000의 것을 사용하였고,

Tool은 Vivado를 사용하였습니다.

Arduino의 제어 Tool은 Arduino IDE를 사용하였습니다.

 

 

데이터 "1000_0000_0000_0000_0000_0000_1001_1001" 이 전송되며,

10은 ID, 0은 writemode, 0이므로 D0에 0000_0000_1001_1001이 저장됩니다.

또한 최하위 1001은 duty로 전달되며, 모터 동작 속도를 결정하게 됩니다.

 

결과적으로, SPI_Slave 설계 프로젝트는 약식이지만 모든 테스트를 통과하였고,

Tape-out 단계 전까지 완료되었습니다.

 

'RTL, Synthesis, P&R' 카테고리의 다른 글

UART 통신 - (2) Arm보드로 FPGA LCD 제어  (0) 2024.09.03
UART 통신 - (1) RTL  (0) 2024.09.03
32-bit SPI Interface - (2) Synthesis, P&R  (0) 2024.09.03
32-bit SPI Interface - (1) RTL  (0) 2024.09.03
4-digit Counter with FPGA  (0) 2024.08.30