8/01 pwm, 모터, 서보모터

정유석·2024년 8월 1일

교육 - 베릴로그

목록 보기
20/28

duty_step, temp, pwm_freq 설명

주기와 주파수는 역수 관계이다
10ns를 1us로 만들기 위해선 100분주를 했는데 이것은 10ns가 100번 지나면 1000ns=1us이기 때문에 100을 곱해준것과 같이 받아들이면 됬다
주파수의 관점에서 생각해 봤을때 10ns는 1억Hz이다 이것을 임의로 원하는 주파수가 10,000Hz라고 설정했을때, 10,000Hz로 만들기 위해선 1억/10,000=10,000Hz이다, 그런데 여기서 한번에 10,000을 나누는것이 아니라 duty_step을 만들기 위해 두번에 걸처서 나눗셈을 한다 따라서 나누는값 10,000은 (duty_step x temp)인데 여기서 temp는 임의의 duty_step값에 따라 변화하는 변수이다. 이것은 duty_step의값 만큼을 한 덩어리로 묶어서 이 묶여진 덩어리들이 temp값만큼 있으면 원하는 주파수(여기선 10,000)을 만들 수 있게된다. 이제 1억Hz를 temp갯수 만큼의 나누어 각각의 덩어리로 만들고 한덩어리가 duty_step으로 나눠져있는 것을 만들었다 여기서 duty_step을 일정 갯수 만큼을 1과 0을 주면(아래 코드에서 'duty'값) 원하는 듀티폭을 만들 수 있다.


duty_step, temp로 나눈 덩어리들을 주파수에서 나누면 원하는 주파수로 줄이는 분주, 주기에 곱하면 원하는 주기로 늘려주는 분주


결국 주파수 관점에서의 분주를 구하면 아래의 temp식이 나오는 것이고 duty_step, temp둘다 분주비 이기때문에 아래 코드에서는 구해놓은 분주비를 주기의 관점에서 작성한다.

RGB 변화

module led_pwm_top(
    input clk, reset_p,
//    input [6:0] duty,
    output pwm, led_r, led_g, led_b);

//    pwm_100step pwm_inst(.clk(clk), .reset_p(reset_p), .duty(duty), .pwm(pwm));
    
    reg [31:0] clk_div;
    always @(posedge clk)clk_div = clk_div + 1;
    
    pwm_100step pwm_inst(.clk(clk), .reset_p(reset_p), .duty(clk_div[27:21]), .pwm(pwm));
    
    pwm_100step pwm_r(.clk(clk), .reset_p(reset_p), .duty(clk_div[26:20]), .pwm(led_r));
    pwm_100step pwm_g(.clk(clk), .reset_p(reset_p), .duty(clk_div[25:19]), .pwm(led_g));
    pwm_100step pwm_b(.clk(clk), .reset_p(reset_p), .duty(clk_div[24:18]), .pwm(led_b));
endmodule 

100단계 컨트롤 모듈

module pwm_100step(
    input clk, reset_p,
    input [6:0] duty, //100단계 //듀티값은 시뮬레이션에서 정해주었다
    output pwm);
    
    parameter sys_clk_freq = 100_000_000;
    parameter pwm_freq = 10_000;
    parameter duty_step = 100;
    parameter temp = sys_clk_freq / duty_step / pwm_freq;  
    parameter temp_half = temp / 2;
    
    integer cnt_sysclk;   //100까지 세는 카운터,7비트 필요
    
    wire pwm_freqX100;
    
    always @(negedge clk or posedge reset_p)begin //100분주, 10ns->1us
        if(reset_p)cnt_sysclk = 0;
        else begin
                if(cnt_sysclk >= temp-1) cnt_sysclk = 0;
                else cnt_sysclk = cnt_sysclk + 1;
        end
    end
    
    assign pwm_freqX100 = (cnt_sysclk < temp_half) ? 1 : 0;
    
    wire pwm_freqX100_nedge;
    
    edge_detector_n ed(
        .clk(clk), .reset_p(reset_p), .cp(pwm_freqX100),
        .n_edge(pwm_freqX100_nedge));
    
    reg [6:0] cnt_duty;   //100까지 세는 카운터,7비트 필요
    
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)cnt_duty = 0;
        else if(pwm_freqX100_nedge)begin
                if(cnt_duty >= 99) cnt_duty = 0; //cnt_duty가 99를넘어 다시 0되면 100us
                else cnt_duty = cnt_duty + 1; 
        end
    end
    
    assign pwm = (cnt_duty < duty) ? 1 : 0; //duty값은 시뮬레이션에서 넣어줌

endmodule

XDC-JC 2,3,4 사용

set_property -dict { PACKAGE_PIN M18   IOSTANDARD LVCMOS33 } [get_ports {led_r}];#Sch name = JC2
set_property -dict { PACKAGE_PIN N17   IOSTANDARD LVCMOS33 } [get_ports {led_g}];#Sch name = JC3
set_property -dict { PACKAGE_PIN P18   IOSTANDARD LVCMOS33 } [get_ports {led_b}];#Sch name = JC4

파라미터화

module pwm_Nstep_freq
    #(  parameter sys_clk_freq = 100_000_000,
        parameter pwm_freq = 10_000,
        parameter duty_step = 100,
        parameter temp = sys_clk_freq / duty_step / pwm_freq,  
        parameter temp_half = temp / 2
    )
(
    input clk, reset_p,
    input [31:0] duty,
    output pwm);
       
    
    integer cnt_sysclk;   //100까지 세는 카운터,7비트 필요
    
    wire clk_freqXstep;
    
    always @(negedge clk or posedge reset_p)begin //100분주, 10ns->1us
        if(reset_p)cnt_sysclk = 0;
        else begin
                if(cnt_sysclk >= temp-1) cnt_sysclk = 0;
                else cnt_sysclk = cnt_sysclk + 1;
        end
    end
    
    assign clk_freqXstep = (cnt_sysclk < temp_half) ? 1 : 0;
    
    wire clk_freqXstep_nedge;
    
    edge_detector_n ed(
        .clk(clk), .reset_p(reset_p), .cp(clk_freqXstep),
        .n_edge(clk_freqXstep_nedge));
    
    integer cnt_duty;   
    
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)cnt_duty = 0;
        else if(clk_freqXstep_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; //duty값은 시뮬레이션에서 넣어줌

endmodule 

탑모듈

module led_pwm_top(
    input clk, reset_p,
//    input [6:0] duty,
    output pwm, led_r, led_g, led_b);

//    pwm_100step pwm_inst(.clk(clk), .reset_p(reset_p), .duty(duty), .pwm(pwm));
    
    reg [31:0] clk_div;
    always @(posedge clk)clk_div = clk_div + 1;
    
    pwm_100step pwm_inst(.clk(clk), .reset_p(reset_p), .duty(clk_div[27:21]), .pwm(pwm));
    
    pwm_Nstep_freq #(.duty_step(77)) pwm_r(.clk(clk), .reset_p(reset_p), .duty(clk_div[28:23]), .pwm(led_r));
    pwm_Nstep_freq #(.duty_step(93)) pwm_g(.clk(clk), .reset_p(reset_p), .duty(clk_div[27:22]), .pwm(led_g));
    pwm_Nstep_freq #(.duty_step(103)) pwm_b(.clk(clk), .reset_p(reset_p), .duty(clk_div[26:21]), .pwm(led_b));
endmodule  

듀티스텝은 펄스폭(펄스의 두께)을 몇단계로 제어 할것이냐
led 문턱 2v
3.3v를 인가하여 led가 켜지긴하지만 50%의 듀티폭으로 인가하면 1.65v의 힘으로 킨거과 같은 밝기가 되는 것

모터


입력에 5v인가, 출력에 넣지 않게 주의!, 전원핀은 모터의 방향차이라 Vcc,GND 방향 상관없음

모터 동작을 위한 탑모듈

module dc_motor_pwm_top(
    input clk, reset_p,
    output motor_pwm);
    
    reg [31:0] clk_div;
    always @(posedge clk)clk_div = clk_div + 1;
    
    pwm_Nstep_freq #(
        .duty_step(4),
        .pwm_freq(100))
    pwm_b(
        .clk(clk), 
        .reset_p(reset_p), 
        .duty(clk_div[31:30]), //0번째 비트에서 부터 1씩 증가하며 채워오다가 30번째 비트가 1이 되면 duty_step 4단계중에 1단계
        .pwm(motor_pwm)); //그러다가 (31번째 비트, 30번째 비트)가 (1,0)이 되면 duty_step 4단계 중에 2단계
        				//즉 duty_step 4단계를 clk_div의 상위 2비트를 잘라서 duty에 넣으므로서 duty값이 계속해서 변화하게 시킴
endmodule 			//시스템클락마다 증가하는 clk_div레지스터중 가장 늦게 변화하는 최상위 2비트를 사용

FND출력을 위한 탑 모듈

module dc_motor_pwm_top(
    input clk, reset_p,
    output motor_pwm,
    output [3:0] com,
    output [7:0] seg_7);
    
    reg [31:0] clk_div;
    always @(posedge clk)clk_div = clk_div + 1;
    
    pwm_Nstep_freq #(
        .duty_step(4),
        .pwm_freq(100))
    pwm_b(
        .clk(clk), 
        .reset_p(reset_p), 
        .duty(clk_div[31:30]), 
        .pwm(motor_pwm));
    
    wire [15:0] duty_bcd;
    bin_to_dec bcd_humi(.bin({10'b0, clk_div[31:30]}), .bcd(duty_bcd));
    fnd_cntr fnd (.clk(clk), .reset_p(reset_p), .value(duty_bcd), .com(com), .seg_7(seg_7));
endmodule

100단계로 나눈

module dc_motor_pwm_top(
    input clk, reset_p,
    output motor_pwm,
    output [3:0] com,
    output [7:0] seg_7);
    
    reg [31:0] clk_div;
    always @(posedge clk)clk_div = clk_div + 1;
    
    pwm_Nstep_freq #(
        .duty_step(100),
        .pwm_freq(100))
    pwm_b(
        .clk(clk), 
        .reset_p(reset_p), 
        .duty(clk_div[31:26]), 
        .pwm(motor_pwm));
    
    wire [15:0] duty_bcd;
    bin_to_dec bcd_humi(.bin({6'b0, clk_div[31:26]}), .bcd(duty_bcd));
    fnd_cntr fnd (.clk(clk), .reset_p(reset_p), .value(duty_bcd), .com(com), .seg_7(seg_7));
endmodule  
module dc_motor_pwm_top(
    input clk, reset_p,
    output motor_pwm,
    output [3:0] com,
    output [7:0] seg_7);
    
    reg [31:0] clk_div;
    always @(posedge clk or posedge reset_p)begin
        if(reset_p) clk_div = 0;
        else clk_div = clk_div + 1;
    end
    
    wire clk_div_26_nedge;
    edge_detector_n ed(
        .clk(clk), .reset_p(reset_p), .cp(clk_div[26]),
        .n_edge(clk_div_26_nedge));
    
    reg [6:0] duty;
    always @(posedge clk or posedge reset_p)begin
        if(reset_p) duty = 20;
        else if(clk_div_26_nedge)begin
            if(duty >= 50) duty = 20;
            else duty = duty + 1;
        end
    end
    
    pwm_Nstep_freq #(
        .duty_step(100),
        .pwm_freq(100))
    pwm_b(
        .clk(clk), 
        .reset_p(reset_p), 
        .duty(duty), 
        .pwm(motor_pwm));
    
    wire [15:0] duty_bcd;
    bin_to_dec bcd_humi(.bin({6'b0, duty}), .bcd(duty_bcd));
    fnd_cntr fnd (.clk(clk), .reset_p(reset_p), .value(duty_bcd), .com(com), .seg_7(seg_7));
endmodule 

서보 모터


전체 20ms 주기중 1ms는 5%의 펄스폭, 1.5ms는 7.5%의 펄스폭 2ms는 10%의 펄스폭을 갖는다
데이터 시트가 정확한 수치를 표시하지 않을수있어서 FND에 듀티값을 표시해 실제 어떤 듀티값에서 어떤 각도가 되는지 알아볼 필요가 있다. 저 세밀한 각도를 알기 위해서는 duty_step을 늘릴 필요가 있다

module dc_motor_pwm_top(
    input clk, reset_p,
    output motor_pwm,
    output [3:0] com,
    output [7:0] seg_7);
    
    reg [31:0] clk_div;
    always @(posedge clk or posedge reset_p)begin
        if(reset_p) clk_div = 0;
        else clk_div = clk_div + 1;
    end
    
    wire clk_div_26_nedge;
    edge_detector_n ed(
        .clk(clk), .reset_p(reset_p), .cp(clk_div[25]), //2배 빠르게 진행하기 위해 변경
        .n_edge(clk_div_26_nedge));
    
    reg [6:0] duty;
    always @(posedge clk or posedge reset_p)begin
        if(reset_p) duty = 0;
        else if(clk_div_26_nedge)begin
            if(duty >= 20) duty = 0; //듀티값 변경
            else duty = duty + 1;
        end
    end
    
    pwm_Nstep_freq #(
        .duty_step(100), //펄스폭 100단계로 나눔
        .pwm_freq(50)) //데이터시트에 표기된 서보모터가 필요한 주파수
    pwm_b(
        .clk(clk), 
        .reset_p(reset_p), 
        .duty(duty), 
        .pwm(motor_pwm));
    
    wire [15:0] duty_bcd;
    bin_to_dec bcd_humi(.bin({6'b0, duty}), .bcd(duty_bcd));
    fnd_cntr fnd (.clk(clk), .reset_p(reset_p), .value(duty_bcd), .com(com), .seg_7(seg_7));
endmodule 

버튼에 따른 각도 변화(버튼 하나 사용)

module servo_pwm_top(
    input clk, reset_p,
    input [3:0] btn,
    output motor_pwm,
    output [3:0] com,
    output [7:0] seg_7);
    
    reg [31:0] clk_div;
    always @(posedge clk or posedge reset_p)begin
        if(reset_p) clk_div = 0;
        else clk_div = clk_div + 1;
    end
    
//    wire clk_div_26_nedge;
//    edge_detector_n ed(
//        .clk(clk), .reset_p(reset_p), .cp(clk_div[25]),
//        .n_edge(clk_div_26_nedge));
    wire btn_ctr;      
    button_cntr btn1(.clk(clk), .reset_p(reset_p), .btn(btn[3]), .btn_pedge(btn_ctr)); //버튼 채터링 방지
   
    wire btn_nedge;
    edge_detector_n ed1(
        .clk(clk), .reset_p(reset_p), .cp(btn_ctr),
        .n_edge(btn_nedge));               
    
    reg [1:0] cnt_btn;                    
    always @(negedge clk or posedge reset_p)begin
        if(reset_p)begin
            cnt_btn = 1;
        end
        else if(btn_nedge)begin
                if(cnt_btn>=3)begin
                    cnt_btn = 1;
                end
            cnt_btn = cnt_btn + 1;
        end
    end
    
    pwm_Nstep_freq #(
        .duty_step(200), //듀티폭을 100단계로 나눔
        .pwm_freq(50)) //연결한 센서에 따라다름, 데이터시트에 필요한 주파수 있음
    pwm_b(
        .clk(clk), 
        .reset_p(reset_p), 
        .duty(duty), 
        .pwm(motor_pwm));
    
    wire [15:0] duty_bcd;
    bin_to_dec bcd_humi(.bin({6'b0, duty}), .bcd(duty_bcd));
    fnd_cntr fnd (.clk(clk), .reset_p(reset_p), .value(duty_bcd), .com(com), .seg_7(seg_7));
endmodule

버튼 분리

module servo_pwm_top(
    input clk, reset_p,
    input [3:0] btn,
    output motor_pwm,
    output [3:0] com,
    output [7:0] seg_7);
    
    reg [31:0] clk_div;
    always @(posedge clk or posedge reset_p)begin
        if(reset_p) clk_div = 0;
        else clk_div = clk_div + 1;
    end
    
//    wire clk_div_26_nedge;
//    edge_detector_n ed(
//        .clk(clk), .reset_p(reset_p), .cp(clk_div[25]),
//        .n_edge(clk_div_26_nedge));
    wire btn_ctr1, btn_ctr2, btn_ctr3;      
    button_cntr btn1(.clk(clk), .reset_p(reset_p), .btn(btn[1]), .btn_pedge(btn_ctr1)); //버튼 채터링 방지
    button_cntr btn2(.clk(clk), .reset_p(reset_p), .btn(btn[2]), .btn_pedge(btn_ctr2)); //버튼 채터링 방지
    button_cntr btn3(.clk(clk), .reset_p(reset_p), .btn(btn[3]), .btn_pedge(btn_ctr3)); //버튼 채터링 방지
    
    wire btn_nedge1, btn_nedge2, btn_nedge3;
    edge_detector_n ed1(
        .clk(clk), .reset_p(reset_p), .cp(btn_ctr1),
        .n_edge(btn_nedge1));
    edge_detector_n ed2(
        .clk(clk), .reset_p(reset_p), .cp(btn_ctr2),
        .n_edge(btn_nedge2));
    edge_detector_n ed3(
        .clk(clk), .reset_p(reset_p), .cp(btn_ctr3),
        .n_edge(btn_nedge3));                
    
//    reg [1:0] cnt_btn;                    
//    always @(negedge clk or posedge reset_p)begin
//        if(reset_p)begin
//            cnt_btn = 1;
//        end
//        else if(btn_nedge)begin
//                if(cnt_btn>=3)begin
//                    cnt_btn = 1;
//                end
//            cnt_btn = cnt_btn + 1;
//        end
//    end
    reg [6:0] duty;
    always @(posedge clk or posedge reset_p)begin
        if(reset_p) duty = 1;
        else if(btn_nedge1)begin
            duty = 5;
        end
        else if(btn_nedge2)begin
            duty = 14;
        end
        else if(btn_nedge3)begin
            duty = 24;
        end                         
    end
    
    pwm_Nstep_freq #(
        .duty_step(200), //듀티폭을 100단계로 나눔
        .pwm_freq(50)) //연결한 센서에 따라다름, 데이터시트에 필요한 주파수 있음
    pwm_b(
        .clk(clk), 
        .reset_p(reset_p), 
        .duty(duty), 
        .pwm(motor_pwm));
    
    wire [15:0] duty_bcd;
    bin_to_dec bcd_humi(.bin({6'b0, duty}), .bcd(duty_bcd));
    fnd_cntr fnd (.clk(clk), .reset_p(reset_p), .value(duty_bcd), .com(com), .seg_7(seg_7));
endmodule
module pwm_100step(
    input clk, reset_p,
    input [6:0] duty, //100단계 //듀티값은 시뮬레이션에서 정해주었다
    output pwm);
    
    parameter sys_clk_freq = 100_000_000;
    parameter pwm_freq = 10_000;
    parameter duty_step = 100;
    parameter temp = sys_clk_freq / duty_step / pwm_freq;  
    parameter temp_half = temp / 2;
    
    integer cnt_sysclk;   //100까지 세는 카운터,7비트 필요
    
    wire pwm_freqX100;
    
    always @(negedge clk or posedge reset_p)begin //100분주, 10ns->1us
        if(reset_p)cnt_sysclk = 0;
        else begin
                if(cnt_sysclk >= temp-1) cnt_sysclk = 0;
                else cnt_sysclk = cnt_sysclk + 1;
        end
    end
    
    assign pwm_freqX100 = (cnt_sysclk < temp_half) ? 1 : 0;
    
    wire pwm_freqX100_nedge;
    
    e
profile
개인 기록공간

0개의 댓글