거북이처럼 천천히

Verilog RTL 설계(8월 2일 - 2, PWM을 통한 Servo-motor 제어 - 2) 본문

RTL Design/Verilog RTL 설계

Verilog RTL 설계(8월 2일 - 2, PWM을 통한 Servo-motor 제어 - 2)

유로 청년 2024. 8. 16. 16:52

1. 시간에 따라 Servo-motor가 이동하도록 설계

  • 이전 게시글까지 Servo-motor에 대해서 살펴보고, -90도, 0도, 90도에 위치시키도록 하기 위해서는 얼마만큼의 duty ratio가 필요한지를 실험을 통해 확인할 수 있었다.
  • 이번 게시글에는 이를 토대로 servo-motor를 활용하도록 하겠다.
  • Servo-motor에 대해서 궁금하거나 자세히 알고 싶다면 아래 게시글을 참고하길 바란다.
    https://jbhdeve.tistory.com/306
 

Verilog RTL 설계(8월 2일 - 1, PWM을 통한 Servo-motor 제어 - 1)

1. Servo - motorServo는 "노예, 추종하다."를 의미하며, 라틴어의 servus에서 유래했다.Servo motor는  주로 기기를 시스템이 요구하는 위치로 이동하거나 특정 속도 및 토크로 가동시킬 때, 정확하게 제어

jbhdeve.tistory.com

 

 

 

 

  • 이번에는 시간에 따라 Servo-motor를 -90도 ▶ 0도 ▶ 90도 ▶ 0도 ▶ -90도 ▶ .... 로 위치시키도록 설계하겠다.
  • Servo-motor가 -90도, 0도, 90도에 위치시키기 위해서 이전 게시글을 통해 얻은 duty ratio을 활용하도록 하겠다.

 

< Source, PWM Control Module >

// PWM Control module
module pwm_cntr_1 #(
    parameter sys_clk = 100_000_000,
    parameter duty_step = 40,
    parameter pwm_freq = 50,
    
    parameter temp = sys_clk / duty_step / pwm_freq,
    parameter temp_half = temp/2 )(
    input clk, reset_p,
    input [31:0] duty,
    output pwm );
    
    // temp 분주화
    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
    
    wire temp_pwm, temp_pwm_nedge;
    assign temp_pwm = (cnt_temp < temp_half) ? 0 : 1;
    edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p),                       
         .cp(temp_pwm), .n_edge(temp_pwm_nedge));
         
    // 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
    
    assign pwm = (cnt_duty < duty) ? 1 : 0;
endmodule
  • 첫 번째 분주화 작업은 pwm_freq 주파수를 갖는 PWM을 생성하기 위한 작업이며, 이를 위해 10ns Clock Pulse를 temp 분주화 작업을 한다. 이를 통해 얻어진 PWM은 sys_clk / temp 주파수를 갖는 PWM이 된다.
  • 두 번째 분주화 작업은 PWM의 Duty ratio을 duty_step 단계로 나누어 컨트롤하기 위한 작업이며, 이를 위해 첫 번째 분주화 작업을 통해 얻어진 PWM에서 duty_step 분주화 작업을 한다. 이를 통해 얻어진 PWM은 sys_clk / temp / duty_step == pwm_freq 주파수를 갖는 PWM이 된다.

 

 

< Source, Top module of Servo motor >

// Top module of Servo-Motor
module servo_motor_cntr_1(
    input clk, reset_p,
    output [3:0] com,
    output [7:0] seg_7,
    output pwm );
    
    // Declare Parameter
    reg up_down;
    
    // Get 1sec One Cycle Pulse
    wire clk_1usec, clk_1msec, clk_1sec;
    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(clk_1msec));
    clk_div_1000 clk_div_1000_1 (.clk(clk), .reset_p(reset_p), .clk_source(clk_1msec), 
                                  .clk_div_1000_nedge(clk_1sec));
                                  
    reg [7:0] counter;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) begin counter = 6; up_down = 1; end
        else if(clk_1sec) begin
            if(counter >= 25) begin counter = 24; up_down = 0; end
            else if(counter <= 5) begin counter = 6; up_down = 1; end
            else begin
                if(up_down) counter = counter + 1;
                else counter = counter - 1;
            end
        end
    end       
    
    // PWM Control 
    pwm_cntr_1 #(.duty_step(200), .pwm_freq(50)) pwm_cntr_0 (.clk(clk), .reset_p(reset_p), .duty(counter), .pwm(pwm));                       
    
    // Convert from binary to BCD.
    wire [15:0] bcd_duty;
    bin_to_dec bin_to_dec_0 (.bin(counter), .bcd(bcd_duty));
    
    // FND Control
    fnd_cntr fnd_cntr_0 (.clk(clk), .reset_p(reset_p), .hex_value(bcd_duty), .com(com),  .seg_7(seg_7));
endmodule
  • PWM의 duty 값은 5 ~ 25까지 값을 가지며, duty 값은 1초 One Cycle Pulse에 의해 1초마다 변화한다.
  • up_down 변수에 의해 서보 모터에 전달되는 PWM의 Duty 값이 up counting이 되는가? down counting이 되는가? 가 결정된다.
  • up_down 변수 값이 1이면 up-counting하며, up_down 변수 값이 0이면 down-counting한다.

 

 

 

< 구현 영상 >

  • 50Hz 주파수를 갖는 PWM의 duty 값을 5 ~ 25까지 범위를 갖고, 서보 모터에 인가함으로서 1초마다 duty 값이 변화하면서 이에 따른 서보 모터는 위치 값이 변화함을 확인할 수 있다.

 

 

 

 

 

 

 

2. 버튼 값에 따라 Servo-motor가 이동하도록 설계

  • 이번에는 3개의 버튼을 이용하여 각각의 버튼 값에 따라 서보 모터의 위치 값이 -90도, 0도, 90도에 위치시키도록 설계해보도록 하겠다.
  • pwm_cntr 모듈은 그대로 사용하되, Top module만 변경하여 설계할 것이기 때문에 pwm_cntr 모듈에 대한 설명은 생략하도록 하겠다.

 

< Source, PWM Control >

// PWM Control
module pwm_cntr_2 # (
    parameter sys_clk = 100_000_000,
    parameter duty_step = 100,
    parameter pwm_freq = 50, 
    
    parameter temp = sys_clk / duty_step / pwm_freq,
    parameter temp_half = temp / 2 )(
    input clk, reset_p,
    input [31:0] duty,
    output pwm );
    
    // temp 분주화
    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
    
    wire pwm_temp, pwm_temp_nedge;
    assign pwm_temp = (cnt_temp < temp_half) ? 0 : 1;
    edge_detector edge_detector_0 (.clk(clk), .reset_p(reset_p),                       
         .cp(pwm_temp), .n_edge(pwm_temp_nedge));
    
    // duty_step 분주화
    integer cnt_duty;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) cnt_duty = 0;
        else if(pwm_temp_nedge) begin
            if(cnt_duty >= duty_step-1) cnt_duty = 0;
            else cnt_duty = cnt_duty + 1;
        end
    end
    
    assign pwm = (cnt_duty < duty) ? 1 : 0;
    
endmodule

 

 

 

< Top module of servo motor >

// Top module of Servo-motor
module servo_motor_cntr_2(
    input clk, reset_p,
    input [2:0] btn,
    output pwm,
    output [3:0] com,
    output [7:0] seg_7);
    
    // Get one cycle pulse of button.
    wire btn_mid, btn_left, btn_right;
    btn_cntr btn_cntr_mid(.clk(clk), .reset_p(reset_p), .btn(btn[0]), .btn_pedge(btn_mid));
    btn_cntr btn_cntr_left(.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pedge(btn_left));
    btn_cntr btn_cntr_right(.clk(clk), .reset_p(reset_p), .btn(btn[2]), .btn_pedge(btn_right));
    
    // Select duty ratio.
    reg [31:0] duty;
    always @(posedge clk or posedge reset_p) begin
        if(reset_p) duty = 5;
        else begin
            if(btn_mid) duty = 32'd15;
            else if(btn_left) duty = 32'd5;
            else if(btn_right) duty = 32'd25;
        end  
    end
    
    // PWM Control
    pwm_cntr_2 #(.duty_step(200), .pwm_freq(50)) pwm_cntr (.clk(clk), .reset_p(reset_p), .duty(duty), .pwm(pwm));
    
    // Convert from binary to BCD.
    wire [15:0] bcd_duty;
    bin_to_dec bin_to_dec_0 (.bin(duty), .bcd(bcd_duty));
    
    // FND Control
    fnd_cntr fnd_cntr_0 (.clk(clk), .reset_p(reset_p), .hex_value(bcd_duty), .com(com),  .seg_7(seg_7));
    
endmodule
  • button chattering 문제를 해결하기 위해 btn_cntr 모듈을 설계하였으며, btn_cntr 모듈내에서는 D-Flip Flop을 이용하여 button 값을 바로 입력 받는 것이 아닌 1msec delay time을 갖고, button 값을 받는다.
  • 이를 통해 chattering 현상으로 인한 불안정한 버튼 값의 변화가 지나고 난 뒤, 안정된 버튼 값을 입력 받아 chattering 문제를 소프트웨어 적으로 해결하게 된다.
  • 3개의 버튼을 통해 서보 모토의 위치를 3단계로 나누어 컨트롤 하게 된다.

 

 

 

< 구현 영상 >