https://insoobaik.tistory.com/652
기본적인 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
'Semiconductor > 0. FPGA' 카테고리의 다른 글
FPGA를 이용한 시계 만들기 (AXI4-Lite를 이용한 입력에 따른 Freq 조절) (0) | 2024.10.04 |
---|---|
FPGA - IP 생성 및 Vitis를 이용한 Switch, LED 제어 (0) | 2024.09.19 |
FPGA - SPI 통신을 이용한 DC Motor 제어하기 (0) | 2024.07.15 |
FPGA - CHARACTER LCD에 문자 입력하기 (0) | 2024.06.18 |
FPGA - FPGA를 이용한 DC 모터 구동 (0) | 2024.06.11 |