카테고리 없음
Verilog RTL 설계(8월 1일 - 2, PWM을 통한 Motor 제어 - 2)
유로 청년
2024. 8. 13. 22:07
1. Switch를 통해 Motor PWM의 duty ratio를 컨트롤하기
- 이전 게시글에서는 100Hz 주파수를 갖는 PWM를 만든 뒤, Motor의 속도를 제어하였다.
- 이 때, 생성된 PWM은 Parameter를 통해 다양한 PWM의 주파수와 Duty ratio step을 갖는 모듈을 통해 만들었다.
- 이번에는 Parameter를 통해 다양한 PWM의 주파수와 Duty ratio를 갖는 PWM을 생성할 수 있는 모듈을 통해 100Hz 주파수를 가지며, Duty ratio를 128단계로 나누어 컨트롤 할 수 있는 PWM을 만든 뒤, Switch을 통해 Duty ratio을 컨트롤할 것이다.
- Parameter를 통해 다양한 PWM의 주파수와 Duty ratio을 만들 수 있는 모듈 설계에 대해서 궁금하다면 아래 게시글을 참고하길 바란다.
https://jbhdeve.tistory.com/304
Verilog RTL 설계(8월 1일 - 2, PWM을 통한 Motor 제어 - 1)
1. PWM을 통한 Motor 제어이전까지는 PWM을 통해 LED diode의 밝기를 제어를 해보았으며, LED diode 외에도 PWM을 통해 Motor 제어가 가능하다.따라서 이번에는 PWM을 통해 Motor의 속도를 제어해보도록 하겠다
jbhdeve.tistory.com
2. Switch를 통해 PWM의 duty ratio를 컨트롤 할 수 있도록 설계하기.
- Motor를 구동하기 위해 100Hz, Duty ratio step = 128단계인 PWM을 생성하여 Motor를 구동시키도록 하겠다.
- 7개의 Switch를 통해 현재 인가되는 PWM의 Duty ratio를 컨트롤 하겠으며, 현재 설정된 Duty ratio은 FND로 출력되도록 설계하겠다.
< Source, Edge detector >
// Edge detector
module edge_detector (
input clk, reset_p,
input cp,
output n_edge, p_edge );
reg flip_flop_current, flip_flop_old;
always @(posedge clk or posedge reset_p) begin
if(reset_p) begin
flip_flop_current <= 0;
flip_flop_old <= 0;
end
else begin
flip_flop_current <= cp;
flip_flop_old <= flip_flop_current;
end
end
assign p_edge = ({flip_flop_current, flip_flop_old} == 2'b10) ? 1 : 0;
assign n_edge = ({flip_flop_current, flip_flop_old} == 2'b01) ? 1 : 0;
endmodule
- 두 개의 D Flip-Flop을 병렬 연결하여 cp 펄스파의 엣지를 감지하는 모듈이다.
< Source, Convert from binary to decimal >
// Convert from binary to bcd.
module bin_to_dec(
input [11:0] bin,
output reg [15:0] bcd
);
reg [3:0] i;
always @(bin) begin
bcd = 0;
for (i=0;i<12;i=i+1)begin
bcd = {bcd[14:0], bin[11-i]};
if(i < 11 && bcd[3:0] > 4) bcd[3:0] = bcd[3:0] + 3;
if(i < 11 && bcd[7:4] > 4) bcd[7:4] = bcd[7:4] + 3;
if(i < 11 && bcd[11:8] > 4) bcd[11:8] = bcd[11:8] + 3;
if(i < 11 && bcd[15:12] > 4) bcd[15:12] = bcd[15:12] + 3;
end
end
endmodule
- Double dabble 알고리즘을 이용한 이진수를 BCD 코드로 변환하는 모듈이다.
< Source, Clock divider 100 >
// Clock divider 100
module clk_div_100 (
input clk, reset_p,
output clk_div_100,
output clk_div_100_nedge, clk_div_100_edge );
reg [6:0] cnt_div;
always @(posedge clk or posedge reset_p) begin
if(reset_p) cnt_div = 0;
else begin
if(cnt_div >= 99) cnt_div = 0;
else cnt_div = cnt_div + 1;
end
end
assign clk_div_100 = (cnt_div < 50) ? 0 : 1;
edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(clk_div_100), .n_edge(clk_div_100_nedge), .p_edge(clk_div_100_pedge));
endmodule
- 10ns 주기를 갖는 Clock Pulse를 100분주하여 주기가 1usec인 Pulse wave와 edge detect에 대한 One Cycle Pulse를 반환하는 모듈이다.
< Source, Clock Divider 1000 >
// Clock divider 1000
module clk_div_1000 (
input clk, reset_p,
input clk_source,
output clk_div_1000,
output clk_div_1000_nedge, clk_div_1000_pedge );
wire clk_source_nedge;
edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(clk_source), .n_edge(clk_source_nedge));
reg [9:0] cnt_div;
always @(posedge clk or posedge reset_p) begin
if(reset_p) cnt_div = 0;
else if(clk_source_nedge) begin
if(cnt_div >= 999) cnt_div = 0;
else cnt_div = cnt_div + 1;
end
end
assign clk_div_1000 = (cnt_div < 500) ? 0 : 1;
edge_detector edge_detector_1 (.clk(clk), .reset_p(reset_p), .cp(clk_div_1000), .n_edge(clk_div_1000_nedge), .p_edge(clk_div_1000_pedge));
endmodule
- clk_source 를 1000분주하여 pulse wave와 pulse wave의 edge에 대한 one cycle pulse를 출력으로 내보낸다.
< Source, Ring Counter of com value >
// Ring Counter of com
module ring_counter (
input clk, reset_p,
output reg [3:0] com );
// Get 1sec one cycle pulse.
wire clk_1usec, clk_1msec;
clk_div_100 clk_div_100_0 (.clk(clk), .reset_p(reset_p), .clk_div_100(clk_1usec));
clk_div_1000 clk_div_1000_0 (.clk(clk), .reset_p(reset_p), .clk_source(clk_1usec), .clk_div_1000_nedge(clk_1msec));
// Ring Counter
always @(posedge clk or posedge reset_p) begin
if(reset_p) com = 4'b1110;
else if(clk_1msec) com = {com[2:0], com[3]};
end
endmodule
- FND의 common anode인 com값을 1msec를 주기로 Shifting하여 동시 각 자리에서 서로 다른 숫자들이 출력되도록 하기 위한 Ring Counter이다.
< Source, Decoder of 7-segmnet >
// Decoder of 7-segment,
module decoder_seg7 (
input [3:0] hex_value,
output reg [7:0] seg_7 );
always @(hex_value) begin
case(hex_value)
0 : seg_7 = 8'b0000_0011; //common anode, 0이 켜지는거임
1 : seg_7 = 8'b1001_1111; // 1
2 : seg_7 = 8'b0010_0101; // 2
3 : seg_7 = 8'b0000_1101; // 3
4 : seg_7 = 8'b1001_1001; // 4
5 : seg_7 = 8'b0100_1001; // 5
6 : seg_7 = 8'b0100_0001; // 6
7 : seg_7 = 8'b0001_1111; // 7
8 : seg_7 = 8'b0000_0001; // 8
9 : seg_7 = 8'b0000_1001; // 9
10 : seg_7 = 8'b0001_0001; // A
11 : seg_7 = 8'b1100_0001; // b
12 : seg_7 = 8'b0110_0011; // C
13 : seg_7 = 8'b1000_0101; // d
14 : seg_7 = 8'b0110_0001; // E
15 : seg_7 = 8'b0111_0001; // F
endcase
end
endmodule
- 이진수 값을 입력받은 뒤, 해당 값을 FND로 출력하기 위해 필요한 7-segment 값을 반환해주는 모듈이다.
< Source, Control FND >
// Control FND
module fnd_cntr (
input clk, reset_p,
input [15:0] hex_value,
output [3:0] com,
output [7:0] seg_7 );
// Ring Counter of com
ring_counter ring_counter_0 (.clk(clk), .reset_p(reset_p), .com(com));
// select hex_value
reg [3:0] value;
always @(posedge clk or posedge reset_p) begin
if(reset_p) value = 4'b0;
else begin
case(com)
4'b1110 : value = hex_value[3:0];
4'b1101 : value = hex_value[7:4];
4'b1011 : value = hex_value[11:8];
4'b0111 : value = hex_value[15:12];
endcase
end
end
// Get seg_7 value
decoder_seg7 decoder_seg7_0 (.hex_value(value), .seg_7(seg_7));
endmodule
< Source, Parameter를 통해 범용성있는 PWM 생성 모듈 >
// PWM Control Module
module pwm_cntr #(
parameter sys_clk = 100_000_000,
parameter duty_step = 128,
parameter pwm_freq = 100,
parameter temp = sys_clk / duty_step / pwm_freq,
parameter temp_half = temp / 2)
(
input clk, reset_p,
input [31:0] duty,
output pwm );
// pwm_freq 주파수를 갖는 PWM을 만들기 위한 Prescaling 작업
integer cnt_temp;
always @(posedge clk or posedge reset_p) begin
if(reset_p) cnt_temp = 0;
else begin
if(cnt_temp >= temp - 1) cnt_temp = 0;
else cnt_temp = cnt_temp + 1;
end
end
// Get pwm_freq PWM
wire temp_pwm;
assign temp_pwm = (cnt_temp < temp_half) ? 0 : 1;
// Get one cycle pulse of temp_pwm
wire temp_pwm_nedge;
edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p), .cp(temp_pwm), .n_edge(temp_pwm_nedge));
// PWM의 duty ratio를 duty_step 단계로 나누어 컨트롤하기 위해
// temp_pwm을 duty_step 분주화 작업 시행
integer cnt_duty;
always @(posedge clk or posedge reset_p) begin
if(reset_p) cnt_duty = 0;
else if(temp_pwm_nedge) begin
if(cnt_duty >= duty_step-1) cnt_duty = 0;
else cnt_duty = cnt_duty + 1;
end
end
// Get PWM
assign pwm = (cnt_duty < duty) ? 1 : 0;
endmodule
- parameter 값으로 system clock (sys_clk) 과 만들고자 하는 PWM의 주파수 (pwm_freq), Duty ratio의 Step (duty_step) 을 설정함으로서 다양한 PWM의 주파수와 다양한 Duty ratio step을 갖는 PWM을 만들어 낼 수 있다.
< Source, Top module of control Motor PWM >
// Top module of PWM Motor control
module top_moudle_pwm_motor (
input clk, reset_p,
input [6:0] duty,
output [3:0] com,
output [7:0] seg_7,
output pwm );
// PWM Control module
pwm_cntr #(.duty_step(128), .pwm_freq(100)) pwm_control_0 (.clk(clk), .reset_p(reset_p), .duty(duty), .pwm(pwm));
// print duty to FND.
wire [15:0] bcd_duty;
bin_to_dec bin_to_dec_0 (.bin(duty), .bcd(bcd_duty));
fnd_cntr fnd_cntr_0 (.clk(clk), .reset_p(reset_p), .hex_value(bcd_duty), .com(com), .seg_7(seg_7));
endmodule
- pwm_cntr 모듈을 통해 100Hz 주파수를 가지며, Duty ratio step을 128단계로 나뉘어진 PWM을 생성할 수 있었으며, 해당 PWM을 motor에 인가하여 PWM의 duty ratio을 통해 Motor의 속력을 제어할 수 있다.
- PWM의 duty ratio를 컨트롤 하기 위해 Basys3의 7개 Switch를 xdc파일로 설정한 뒤, 해당 Switch들을 통해 PWM의 Duty ratio을 컨트롤 할 수 있었다.
3. 구현 영상
- Switch로 PWM의 duty ratio 값을 이진수로 표현하여 PWM의 Duty ratio를 컨트롤 할 수 있었으며, 현재 Motor에 인가되는 PWM의 duty ratio를 FND로 출력시켜 확인할 수 있었다.
- 실험을 통해 PWM의 Duty ratio가 활성화되는 비율 및 시작이 적을 경우, 모터 구동에 필요한 전력을 충분하게 공급하지 못해 모터가 동작하지 못하는 것을 확인할 수 있다.
- 또한, PWM의 Duty ratio가 활성화되는 비율 및 시작이 높을수록, 모터가 구동하는 동시에 모터의 회전 속도도 점차 빨라짐을 확인할 수 있었다.