Semiconductor/0. FPGA

FPGA - STM32 UART 이용한 DC Motor 제어 및 신호 확인

잇(IT) 2024. 7. 30. 16:12

https://insoobaik.tistory.com/652

 

통신 프로토콜 UART 동작 원리

보호되어 있는 글입니다. 내용을 보시려면 비밀번호를 입력하세요.

insoobaik.tistory.com

기본적인 UART 이론은 위 글을 참고한다.

 

* 전체 실행 관련 코드는 글 아래 첨부되어 있다.


UART 통신 규칙 설정

Baud_Rate 9600
Data Bit 8
Parity x
Stop Bit 1

 

FPGA Clock 50MHz

 

FPGA Board의 Clock은 50MHz(20ns)다.

Baud Rate 9600은 104166ns에 해당한다.

Clock Count를 5208번으로 설정하게 되면 Baud Rate와 1bit당 속도를 맞출 수 있다.

Clock이 5208번 반복하게 되면 104160ns를 소요하게 되고 6ns의 차이는 UART 오차 범위를 허용한다.


 

블록 다이어그램

1. STM32에서 UART 프로토콜을 통해 Data를 전송하게 되면, uart_rx 모듈을 통해 해당 데이터를 전달 받아 8비트 플립플롭에 저장된다.

2. 저장된 8비트 중 뒤에 4bit를 Duty Cycle로 사용하며 Top 모듈을 통해 DC_Motor 모듈로 해당 4bit Duty Cycle을 전달한다.

3. DC_Motor에서는 4bit Duty Cycle을 전달받아 o_Pwm_Out을 핀을 통해 Duty Cycle에 맞게 0 or 1의 신호를 전달한다.

4. FPGA Board에 있는 DC_Motor는 전달받은 신호에 맞게 Motor를 동작 시킨다.


아래와 같이 STM32 Nucleo Board와 FPGA board를 연결한다.


STM32 Nucleo Board

STM32429F Nucleo Board의 경우 ST-Link를 통해 PC와 UART 통신이 가능하다.

CubeIDE를 통해 UART 설정을 할경우 PD9 = UART_TX(송신) / PD8 = UART_RX(수신) 핀이 설정되고, 해당 Pin은 ST-Link를 통해 PC와 연결된 USB COM Port를 통해 디버깅을 하여 통신이 가능해진다.

Data Sheet를 보게 되면 STLK_TX, RX 핀이 존재하는 것을 확인할 수 있다.

PD9 Port의 경우 UART_TX 핀이기 때문에 해당 Port에 선을 연결하면 STM32에서 UART 프로토콜을 통해 전달되는 신호를 확인할 수 있다. (위 연결 사진을 보면 해당 Port에 선을 연결한 것을 확인할 수 있다.)

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance==USART3)
	{
		HAL_UART_Receive_IT(&huart3, (uint8_t *)&rx_data, 1);
		HAL_UART_Transmit(&huart3, (uint8_t *)&rx_data, 1, 1000);
	}
}

CubeIDE의 코드 일부다. PC와 ST-Link가 연결된 Port로 키보드 입력을 통해 입력이 전달되면 HAL_UART_Receive_IT 함수를 통해 해당 신호를 전달 받아 rx_data 변수에 값을 저장한다.

이후 HAL_UART_Transmit 함수를 통해 해당 변수에 있는 값을 전달하게 된다. (전달은 위에서 설정한 UART_TX (PD9번 Pin을 통해 전달된다.)


FPGA (전체 코드는 아래 첨부)

 

uart_rx 코드

1. IDLE, START_BIT, RX_DATA, STOP_BIT, CLEAN 총 4개의 상태를 가지고 있다. Uart 통신 프로토콜에 맞게 START_BIT, RX_DATA, STOP_BIT가 동작하게 만들었으며, CLEAN 상태의 경우 STOP_BIT 상태가 끝난 뒤 UART 통신의 원활함을 위해 잠깐의 시간동안 신호를 정리하는 상태에 해당한다.

2. UART 통신의 데이터를 확인하는 방법은 여러가지가 있다.

(1) 4번 이상의 샘플링을 통한 데이터 확인

(2) Buad Rate보다 충분히 빠른 Clock을 통해 Baud Rate 1bit의 중간 값을 확인

위 2가지 방식 외에 여러가지 방법이 있겠지만 (2)의 방식을 사용할 것이다.

3. Baud Rate를 통해 전달되는 1bit의 중간 값을 확인하여 데이터를 받아오고 8비트 플립플롭에 저장하여 STOP_BIT 상태에 도달하게 되면 전달된 8bit Data 중 하위 4비트를 Duty Cycle 값으로 전달한다.

 

motor 코드

1. uart_rx를 통해 전달 받은 Duty 값을 사용하여 PWM 주기를 조절하여 Motor에 신호를 전달한다.

 

Schematic

위에서 구상한 블록 다이어그램과 동일한 Schematic이 생성된 것을 확인할 수 있다.

 

uart_rx Schematic

motor Schematic

 


Simulation

(Test Bench 코드는 하단에 첨부된 코드를 확인한다.)

Baud Rate의 절반 시간동안 START_BIT 상태를 유지하며 START_BIT로 0의 값이 정상적으로 들어오게 되면 RX_DATA 상태로 전환한다.

8bit의 데이터가 전부 들어온 이후 8비트 플립플롭에 Test Bench로 전달한 값(af)이 정상적으로 들어가는 것이 확인되었으며, 전달된 Data의 하위 4bit가 Duty Cycle에 해당하는 값(f)에 정상적으로 저장된 것을 확인할 수 있다.


IMPLEMENTATION

위 사진을 보게 되면 Setup Time은 충분하지만 Hold Time이 매우 짧은 것을 볼 수 있다. Hold Time 마진이 너무 짧은 추후에 문제가 발생할 수 있기 때문에 추후 경로 수정, 버퍼 추가 등 여러 방법을 통해 고쳐야 할 것 같다.


ILA

 

0x60 Data 전송

0x60 Data를 전송하게 되면 하위 4bit 즉, 0의 값이 Duty Cycle 0%가 되기 때문에 DC_Motor가 동작하지 않게 된다.

 

 

0x6F Data 전송

0x6F Data를 전달하게 되면 하위 4bit F(1111) 값인Duty Cycle 100%이 되기 때문에 DC_Motor가 최고 속도로 돌아가는 것을 확인할 수 있다.

 


전체 코드

 

top.v

`timescale 1ns / 1ps

//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/07/30 14:55:58
// Design Name: 
// Module Name: top
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module top 
    #(parameter TOP_CLK_DIV = 5028)(
    input i_Clock,
    input i_Rx_Data,
    input i_Reset,
    output [7:0] o_Rx_Byte,
    output o_Rx_DV,
    output o_Nsleep,
    output o_Pwm_Out
    );
    
    wire [3:0] w_Duty;
    
    uart_rx #(.CLK_DIV(TOP_CLK_DIV)) uart_rx_inst(
        .i_Clock(i_Clock),
        .i_Rx_Data(i_Rx_Data),
        .o_Rx_DV(o_Rx_DV),
        .o_Rx_Byte(o_Rx_Byte),
        .o_Duty(w_Duty)
    );
    
    motor motor_inst(
        .i_Duty(w_Duty),
        .i_Clock(i_Clock),
        .i_Reset(i_Reset),
        .o_Pwm_Out(o_Pwm_Out),
        .o_Nsleep(o_Nsleep)
    );
    
    ila_0 (
        .clk(i_Clock),
        .probe0(i_Rx_Data),
        .probe1(i_Reset),
        .probe2(o_Rx_Byte),
        .probe3(o_Rx_DV),
        .probe4(o_Nsleep),
        .probe5(o_Pwm_Out)
    );
    
endmodule

 

uart_rx.v

`timescale 1ns / 1ps

//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/07/30 13:48:41
// Design Name: 
// Module Name: uart_rx
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module uart_rx 
    #(parameter CLK_DIV = 5208)(
    input i_Clock,
    input i_Rx_Data,
    output o_Rx_DV,
    output [7:0] o_Rx_Byte,
    output [3:0] o_Duty
    );
    
    parameter s_IDLE = 3'b000;
    parameter s_START_BIT = 3'b001;
    parameter s_RX_DATA = 3'b010;
    parameter s_STOP_BIT = 3'b100;
    parameter s_CLEAN = 3'b111;
    
    //Data Valid
    reg r_Rx_DV;
    //8bit Data
    reg [7:0] r_Rx_Byte;
    //State
    reg [2:0] r_State = 0;
    //Metastability
    reg r_Rx_Data_R = 0;
    reg r_Rx_Data = 0;
    //Clock Count
    reg [15:0] r_Clock_Count = 0;
    //8bit Index
    reg [2:0] r_Index = 0;
    //PWM Duty
    reg [3:0] r_Duty = 4'b0000;
    
    //Metastability
    always @(posedge i_Clock) begin
        r_Rx_Data_R <= i_Rx_Data;
        r_Rx_Data <= r_Rx_Data_R;
    end
    
    //FSM
    always @(posedge i_Clock) begin
        case(r_State)
            s_IDLE : begin
                r_Rx_DV <= 1'b0;
                r_Clock_Count <= 0;
                r_Index <= 0;
                
                if(r_Rx_Data == 1'b0)
                    r_State <= s_START_BIT;
                else
                    r_State <= s_IDLE;
            end
            
            s_START_BIT : begin
                if(r_Clock_Count == (CLK_DIV-1)/2) begin
                    if(r_Rx_Data == 1'b0) begin
                        r_State <= s_RX_DATA;
                        r_Clock_Count <= 0;
                    end
                    else begin
                        r_State <= s_IDLE;
                    end
                end
                else begin
                    r_Clock_Count <= r_Clock_Count + 1;
                    r_State <= s_START_BIT;
                end
            end
            
            s_RX_DATA : begin
                if(r_Clock_Count < CLK_DIV - 1) begin
                    r_Clock_Count <= r_Clock_Count + 1;
                    r_State <= s_RX_DATA;
                end
                else begin
                    r_Clock_Count <= 0;
                    r_Rx_Byte[r_Index] <= r_Rx_Data;
                    if(r_Index < 7) begin
                        r_Index <= r_Index + 1;
                        r_State <= s_RX_DATA;
                    end
                    else begin
                        r_Index <= 0;
                        r_State <= s_STOP_BIT;
                    end
                end
            end
            
            s_STOP_BIT : begin
                if(r_Clock_Count < CLK_DIV - 1) begin
                    r_Clock_Count <= r_Clock_Count + 1;
                    r_State <= s_STOP_BIT;    
                end
                else begin
                    r_Rx_DV <= 1'b1;
                    r_Clock_Count <= 0;
                    r_State <= s_CLEAN;
                    r_Duty <= r_Rx_Byte[3:0];
                end
            end
            
            s_CLEAN : begin
                r_State <= s_IDLE;
                r_Rx_DV <= 1'b0;
            end
            
            default : begin
                r_State <= s_IDLE;
            end
        endcase
    end
    
    assign o_Rx_DV  = r_Rx_DV;
    assign o_Rx_Byte = r_Rx_Byte;
    assign o_Duty = r_Duty;
    
endmodule

 

motor.v

`timescale 1ns / 1ps

//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/07/30 14:46:12
// Design Name: 
// Module Name: motor
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module motor(
    input [3:0] i_Duty,
    input i_Clock,
    input i_Reset,
    output o_Pwm_Out,
    output o_Nsleep
    );
    
    reg r_Pwm_Out;
    
    assign o_Pwm_Out = r_Pwm_Out;
    
    reg [25:0] r_Counter;
    reg [3:0] r_Pwm_Counter;
    
    assign o_Nsleep = 1;
    
    always @(posedge i_Clock) begin
        if(i_Reset) begin
            r_Pwm_Out <= 0;
            r_Counter <= 0;
            r_Pwm_Counter <= 0;
        end
        else begin
            if(r_Counter >= 499_999) begin
                r_Counter <= 0;
                r_Pwm_Counter <= r_Pwm_Counter + 1;
                
                if(r_Pwm_Counter >= 15) begin
                    r_Pwm_Counter <= 0;
                end
            end
            else begin
                r_Counter <= r_Counter + 1;
            end
        end
        
        if(r_Pwm_Counter < i_Duty) begin
            r_Pwm_Out <= 1;
        end
        else begin
            r_Pwm_Out <= 0;
        end
    end
    
endmodule

 

tb_top.v

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/07/30 15:16:35
// Design Name: 
// Module Name: tb_top
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module tb_top;

    parameter TB_TOP_CLK_DIV = 5208;
    parameter BAUD_RATE = 104100;
        
    reg r_Clock;
    reg r_Rx_Data;
    reg r_Reset;
    wire [7:0] w_Rx_Byte;
    wire w_Rx_DV;
    wire w_Nsleep;
    wire w_Pwm_Out;
    
    top #(.TOP_CLK_DIV(TB_TOP_CLK_DIV)) top_inst(
        .i_Clock(r_Clock),
        .i_Rx_Data(r_Rx_Data),
        .i_Reset(r_Reset),
        .o_Rx_Byte(w_Rx_Byte),
        .o_Rx_DV(w_Rx_DV),
        .o_Nsleep(w_Nsleep),
        .o_Pwm_Out(w_Pwm_Out)
    );
    
    task UART_RX;
        input [7:0] i_Data;
        integer ii;
        begin
            r_Rx_Data <= 1'b0;
            #(BAUD_RATE);
            #1000;
            
            for(ii = 0; ii<8; ii=ii+1) begin
                r_Rx_Data <= i_Data[ii];
                #(BAUD_RATE);
            end
            
            r_Rx_Data <= 1'b1;
            #(BAUD_RATE);
        end
    endtask
    
    always #10 r_Clock <= !r_Clock;
    
    initial begin
        r_Clock = 0;
        r_Rx_Data = 0;
        r_Reset = 0;
        #10 
        r_Reset = 1;
        #10
        r_Reset = 0;
    end
    
    initial begin
        @(posedge r_Clock);
        UART_RX(8'haf);
        @(posedge r_Clock);
    end


endmodule
728x90