Harman 세미콘 아카데미/Verilog

2024.7.10 [Verilog]17 - PWM으로 servo 모터 제어하기, ADC

U_Pong 2024. 7. 10. 21:38

2024.7.10 수업날


< PWM으로 servo_motor 제어하기 >

pwm_256step_freq 소스코드
///////////////////////////////////////////////// 2024.7.10
//////////////////////////pwm 제어하기, 256분주기(100MHz로 나눈)
// 서브모터는 50Hz
module pwm_256step_freq 
#(  parameter sys_clk_freq = 100_000_000,           //시스템 클록,  100MHz
      parameter pwm_freq = 50,                                   //pwm 출력, 50Hz
      parameter duty_step = 256,                                // 듀티 싸이클 256로 설정--> 많으면 많을수록 모터의 움직임이 부드러워짐
      parameter temp = sys_clk_freq / pwm_freq / duty_step,         //  타이머 카운트 주기, parameter 상수는 그냥 수이기 때문에 회로 만들때는 나누기 회로가 만들어지지 않음
      parameter temp_half = temp / 2)
(
      input clk, reset_p,
      input [7:0]duty,             
      output reg pwm );     
      
      integer cnt;                        // 카운터 변수 선언
      reg pwm_freqX256;          // 128배 주파수를 가지는 PWM 신호를 저장하는 레지스터 선언
      always @ (posedge clk or posedge reset_p) begin
            if(reset_p) begin
                  pwm_freqX256 = 0;
                  cnt = 0;
            end
            else begin
                  if(cnt >= (temp - 1))cnt = 0;               //78분주-->10,000/128
                  else cnt = cnt + 1;
                  
                  //if(cnt < (pwmXduty_steps / 2)) pwm_freqX128 = 0;                  // 마찬가지로 상수이기 때문에 상관 없음
                  if(cnt < temp_half) pwm_freqX256 = 0;
                  else pwm_freqX256 = 1;
            end
      end
      
      wire pwm_freqX256_nedge;
       edge_detector_n ed(
                  .clk(clk), .reset_p(reset_p), 
                  .cp(pwm_freqX256), .n_edge(pwm_freqX256_nedge));
      
      reg [7:0] cnt_duty;                        // 2^8=256분주기           
      always @ (posedge clk or posedge reset_p)begin
            if(reset_p) begin
                  cnt_duty = 0;
                  pwm = 0;
            end
            else if(pwm_freqX256_nedge)begin
                  cnt_duty = cnt_duty + 1;                        
                  if(cnt_duty < duty)pwm = 1;
                  else pwm = 0;
            end
      end
      
endmodule

 

servo_motor_top 소스코드
//////////////////////////////////////////////////////////////////////2024.7.10
//////////////// 서브모터 pwm 제어
module servo_motor_top(
      input clk, reset_p,
      output servo_pwm,
      output [3:0] com,
      output [7:0] seg_7);
      
      integer clk_div;
      always @(posedge clk) clk_div = clk_div + 1;
      
      wire clk_div_nedge;
      edge_detector_n ed(
            .clk(clk), .reset_p(reset_p), .cp(clk_div[26]),             // clk_div[26]-->1.3s 
            .n_edge(clk_div_nedge));
      
      integer cnt;                  // integer는 32비트
      always @(posedge clk or posedge reset_p) begin
            if(reset_p)cnt = 0;
            else if(clk_div_nedge)begin
                  //if(cnt >= 12)cnt = 6;              // 6에서 12까지 증가하는 카운터 만들기--> 90도 움직일 수 있음
                  if(cnt >= 16)cnt = 3;                     // 3부터 16까지 카운트됨-->180도 움직일 수 있음
                  else cnt = cnt + 1;
            end
      end
            
      pwm_128step_freq #(.pwm_freq(50)) pwm_motor(           // #으로 해놓으면 모듈마다 수정할 필요없음
            .clk(clk), .reset_p(reset_p),
            .duty(cnt),
            .pwm(servo_pwm));
      
      wire [15:0] duty_bcd;
      bin_to_dec btd_duty(.bin({cnt}), .bcd(duty_bcd));
      
      fnd_4digit_cntr FND_duty(.clk(clk), .value(duty_bcd), .com(com), .seg_7(seg_7));   
endmodule

 

.xdc 수정해야하는 곳(serco_motor와 관련된 코드에는 모두 해당됨)

 

servo _motor _top 결과

 

 

servo_motor_down_up_top 소스코드
//////////////////////////////////// 서브모터 pwm 제어, 최대값에 도달하면 다시 다운카운트가 되도록
module servo_motor_down_up_top(
      input clk, reset_p,
      output servo_pwm,
      output [3:0] com,
      output [7:0] seg_7);
      
      integer clk_div;
      always @(posedge clk) clk_div = clk_div + 1;
      
      wire clk_div_nedge;
      edge_detector_n ed(
            .clk(clk), .reset_p(reset_p), .cp(clk_div[25]),             // clk_div[21]-->0.3s 
            .n_edge(clk_div_nedge));
      
      integer cnt;                  // integer는 32비트
      reg down_up;
      // 3~16카운터--> 총 12단계, 180도 움직임--> 한번에 15도씩 움직이는 것
      always @(posedge clk or posedge reset_p) begin
            if(reset_p) begin
                  cnt = 3;
                  down_up = 0;
            end     
            else if(clk_div_nedge)begin
                  if(down_up) begin
                        if(cnt <= 3) begin
                              down_up = 0;
                        end
                        else cnt = cnt - 1;
                  end
                  else begin
                        if(cnt >= 16)begin
                              down_up = 1;
                        end
                        else cnt = cnt + 1;
                  end
            end
      end
            
      pwm_128step_freq #(.pwm_freq(50)) pwm_motor(           // #으로 해놓으면 모듈마다 수정할 필요없음
            .clk(clk), .reset_p(reset_p),
            .duty(cnt),
            .pwm(servo_pwm));
      
      wire [15:0] duty_bcd;
      bin_to_dec btd_duty(.bin({cnt}), .bcd(duty_bcd));
      
      fnd_4digit_cntr FND_duty(.clk(clk), .value(duty_bcd), .com(com), .seg_7(seg_7));   
endmodule

 

servo_motor_down_up_top 결과

 

 

servo_motor_down_up_256_top 소스코드
//////////////////////////////////// 서브모터 pwm 제어, 256주기, 최대값에 도달하면 다시 다운카운트가 되도록
module servo_motor_down_up_256_top(
      input clk, reset_p,
      output servo_pwm,
      output [3:0] com,
      output [7:0] seg_7);
      
      integer clk_div;
      always @(posedge clk) clk_div = clk_div + 1;
      
      wire clk_div_nedge;
      edge_detector_n ed(
            .clk(clk), .reset_p(reset_p), .cp(clk_div[24]), 
            .n_edge(clk_div_nedge));
      
      integer cnt;                  // integer는 32비트
      reg down_up;
      always @(posedge clk or posedge reset_p) begin
            if(reset_p) begin
                  cnt = 6;
                  down_up = 0;
            end     
            else if(clk_div_nedge)begin
                  if(down_up) begin
                        if(cnt <= 6) down_up = 0;
                        else cnt = cnt - 1;
                  end
                  else begin
                        if(cnt >= 32)down_up = 1;
                        else cnt = cnt + 1;
                  end
            end
      end
            
      pwm_256step_freq #(.pwm_freq(50)) pwm_motor(           // #으로 해놓으면 모듈마다 수정할 필요없음
            .clk(clk), .reset_p(reset_p),
            .duty(cnt),
            .pwm(servo_pwm));
      
      wire [15:0] duty_bcd;
      bin_to_dec btd_duty(.bin({cnt}), .bcd(duty_bcd));
      
      fnd_4digit_cntr FND_duty(.clk(clk), .value(duty_bcd), .com(com), .seg_7(seg_7));   
endmodule

 

servo_motor_down_up_256_top 결과

 

 

servo_down_up_256_comparator 소스코드
//////////////////////////////////// 서브모터 pwm 제어, 256주기, 최대값에 도달하면 다시 다운카운트가 되도록
/////////////////비교기 사용--> 부드럽게 움직이도록 함
module servo_down_up_256_comparator_top(
      input clk, reset_p,
      output servo_pwm,
      output [3:0] com,
      output [7:0] seg_7);
      
      integer clk_div;
      always @(posedge clk) clk_div = clk_div + 1;
      
      wire clk_div_nedge;
      edge_detector_n ed(
            .clk(clk), .reset_p(reset_p), .cp(clk_div[21]), 
            .n_edge(clk_div_nedge));
      
      integer cnt;                  // integer는 32비트
      reg down_up;
      always @(posedge clk or posedge reset_p) begin
            if(reset_p) begin
                  cnt = 6;
                  down_up = 0;
            end     
            else if(clk_div_nedge)begin
                  if(down_up) begin
                        if(cnt <= 6) down_up = 0;
                        else cnt = cnt - 1;
                  end
                  else begin
                        if(cnt >= 32)down_up = 1;
                        else cnt = cnt + 1;
                  end
            end
      end
            
      pwm_256step_comparator_freq #(.pwm_freq(50)) pwm_motor(           // #으로 해놓으면 모듈마다 수정할 필요없음
            .clk(clk), .reset_p(reset_p),
            .duty(cnt),
            .pwm(servo_pwm));
      
      wire [15:0] duty_bcd;
      bin_to_dec btd_duty(.bin({cnt}), .bcd(duty_bcd));
      
      fnd_4digit_cntr FND_duty(.clk(clk), .value(duty_bcd), .com(com), .seg_7(seg_7));   
endmodule

 

servo_down_up_256_comparator 결과

 

 

servo_down_up_256_btn_top 소스코드
//////////////////////////////////// 서브모터 pwm 제어, 256주기, 최대값에 도달하면 다시 다운카운트가 되도록
/////////////////비교기 사용--> 부드럽게 움직이도록 함, 버튼을 누르면 반대로 토글되게 제어하기
module servo_down_up_256_btn_top(
      input clk, reset_p,
      input btn,
      output servo_pwm,
      output [3:0] com,
      output [7:0] seg_7);
      
      wire btn_nedge;
      button_cntr_reg_n_p btn_t(
            .clk(clk), .reset_p(reset_p), 
            .btn(btn), .btn_nedge(btn_nedge));
      
      integer clk_div;
      always @(posedge clk) clk_div = clk_div + 1;
      
      wire clk_div_nedge;
      edge_detector_n ed(
            .clk(clk), .reset_p(reset_p), .cp(clk_div[21]), 
            .n_edge(clk_div_nedge));
      
      integer cnt;                  // integer는 32비트
      reg down_up;
      always @(posedge clk or posedge reset_p) begin
            if(reset_p) begin
                  cnt = 6;
                  down_up = 0;
            end     
            else if(clk_div_nedge)begin
                  if(down_up) begin
                        if(cnt <= 6) down_up = 0;
                        else cnt = cnt - 1;
                  end
                  else begin
                        if(cnt >= 32)down_up = 1;
                        else cnt = cnt + 1;
                  end 
            end
            else if(btn_nedge)down_up = ~down_up;
      end
            
      pwm_256step_comparator_freq #(.pwm_freq(50)) pwm_motor(           // #으로 해놓으면 모듈마다 수정할 필요없음
            .clk(clk), .reset_p(reset_p),
            .duty(cnt),
            .pwm(servo_pwm));
      
      wire [15:0] duty_bcd;
      bin_to_dec btd_duty(.bin({cnt}), .bcd(duty_bcd));
      
      fnd_4digit_cntr FND_duty(.clk(clk), .value(duty_bcd), .com(com), .seg_7(seg_7));   
endmodule

 

.xdc 파일 수정해야하는 곳

 

servo_down_up_btn_top 결과

 

 

< ADC , 아날로그 디지털 변환 회로(Analog-to-digital converter) >

이제 아날로그 입력을 받아서 디지털로 출력하는 adc에 대해서 알아보자

아래와 같이 fpga에는 아날로그 입력을 받을 수 있도록 adc가 있다.

 

아래와 같은 순서로 진행하면 된다.

 

 

XADC Wizard를 더블클릭하면 이렇게 새로운 창이 뜨게 된다.

결과 창 모습

 

OK를 누른 후 생기는 다음 창

 

그러면   xadc_wiz_0 소스코드 가 자동으로 추가되는데, 아래와 같이 수정해준다.

xadc_wiz_0 소스코드
// file: xadc_wiz_0.v
// (c) Copyright 2009 - 2013 Xilinx, Inc. All rights reserved.
// 
// This file contains confidential and proprietary information
// of Xilinx, Inc. and is protected under U.S. and
// international copyright and other intellectual property
// laws.
// 
// DISCLAIMER
// This disclaimer is not a license and does not grant any
// rights to the materials distributed herewith. Except as
// otherwise provided in a valid license issued to you by
// Xilinx, and to the maximum extent permitted by applicable
// law: (1) THESE MATERIALS ARE MADE AVAILABLE "AS IS" AND
// WITH ALL FAULTS, AND XILINX HEREBY DISCLAIMS ALL WARRANTIES
// AND CONDITIONS, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING
// BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON-
// INFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE; and
// (2) Xilinx shall not be liable (whether in contract or tort,
// including negligence, or under any other theory of
// liability) for any loss or damage of any kind or nature
// related to, arising under or in connection with these
// materials, including for any direct, or any indirect,
// special, incidental, or consequential loss or damage
// (including loss of data, profits, goodwill, or any type of
// loss or damage suffered as a result of any action brought
// by a third party) even if such damage or loss was
// reasonably foreseeable or Xilinx had been advised of the
// possibility of the same.
// 
// CRITICAL APPLICATIONS
// Xilinx products are not designed or intended to be fail-
// safe, or for use in any application requiring fail-safe
// performance, such as life-support or safety devices or
// systems, Class III medical devices, nuclear facilities,
// applications related to the deployment of airbags, or any
// other applications that could lead to death, personal
// injury, or severe property or environmental damage
// (individually and collectively, "Critical
// Applications"). Customer assumes the sole risk and
// liability of any use of Xilinx products in Critical
// Applications, subject only to applicable laws and
// regulations governing limitations on product liability.
// 
// THIS COPYRIGHT NOTICE AND DISCLAIMER MUST BE RETAINED AS
// PART OF THIS FILE AT ALL TIMES.
`timescale 1ns / 1 ps

(* CORE_GENERATION_INFO = "xadc_wiz_0,xadc_wiz_v3_3_7,{component_name=xadc_wiz_0,enable_axi=false,enable_axi4stream=false,dclk_frequency=100,enable_busy=true,enable_convst=false,enable_convstclk=false,enable_dclk=true,enable_drp=true,enable_eoc=true,enable_eos=true,enable_vbram_alaram=false,enable_vccddro_alaram=false,enable_Vccint_Alaram=false,enable_Vccaux_alaram=false,enable_vccpaux_alaram=false,enable_vccpint_alaram=false,ot_alaram=false,user_temp_alaram=false,timing_mode=continuous,channel_averaging=256,sequencer_mode=off,startup_channel_selection=single_channel}" *)


module xadc_wiz_0
          (
          daddr_in,            // Address bus for the dynamic reconfiguration port
          dclk_in,             // Clock input for the dynamic reconfiguration port
          den_in,              // Enable Signal for the dynamic reconfiguration port
          di_in,               // Input data bus for the dynamic reconfiguration port
          dwe_in,              // Write Enable for the dynamic reconfiguration port
          reset_in,            // Reset signal for the System Monitor control logic
          vauxp6,              // Auxiliary channel 6
          vauxn6,
          busy_out,            // ADC Busy signal
          channel_out,         // Channel Selection Outputs
          do_out,              // Output data bus for dynamic reconfiguration port
          drdy_out,            // Data ready signal for the dynamic reconfiguration port
          eoc_out,             // End of Conversion Signal
          eos_out,             // End of Sequence Signal
          alarm_out,           // OR'ed output of all the Alarms    
          vp_in,               // Dedicated Analog Input Pair
          vn_in);

          input [6:0] daddr_in;
          input dclk_in;
          input den_in;
          input [15:0] di_in;
          input dwe_in;
          input reset_in;
          input vauxp6;
          input vauxn6;
          input vp_in;
          input vn_in;

          output busy_out;
          output [4:0] channel_out;
          output [15:0] do_out;
          output drdy_out;
          output eoc_out;
          output eos_out;
          output alarm_out;

        wire FLOAT_VCCAUX;
        wire FLOAT_VCCINT;
        wire FLOAT_TEMP;
          wire GND_BIT;
    wire [2:0] GND_BUS3;
          assign GND_BIT = 0;
    assign GND_BUS3 = 3'b000;
          wire [15:0] aux_channel_p;
          wire [15:0] aux_channel_n;
          wire [7:0]  alm_int;
          assign alarm_out = alm_int[7];
          assign aux_channel_p[0] = 1'b0;
          assign aux_channel_n[0] = 1'b0;

          assign aux_channel_p[1] = 1'b0;
          assign aux_channel_n[1] = 1'b0;

          assign aux_channel_p[2] = 1'b0;
          assign aux_channel_n[2] = 1'b0;

          assign aux_channel_p[3] = 1'b0;
          assign aux_channel_n[3] = 1'b0;

          assign aux_channel_p[4] = 1'b0;
          assign aux_channel_n[4] = 1'b0;

          assign aux_channel_p[5] = 1'b0;
          assign aux_channel_n[5] = 1'b0;

          assign aux_channel_p[6] = vauxp6;
          assign aux_channel_n[6] = vauxn6;

          assign aux_channel_p[7] = 1'b0;
          assign aux_channel_n[7] = 1'b0;

          assign aux_channel_p[8] = 1'b0;
          assign aux_channel_n[8] = 1'b0;

          assign aux_channel_p[9] = 1'b0;
          assign aux_channel_n[9] = 1'b0;

          assign aux_channel_p[10] = 1'b0;
          assign aux_channel_n[10] = 1'b0;

          assign aux_channel_p[11] = 1'b0;
          assign aux_channel_n[11] = 1'b0;

          assign aux_channel_p[12] = 1'b0;
          assign aux_channel_n[12] = 1'b0;

          assign aux_channel_p[13] = 1'b0;
          assign aux_channel_n[13] = 1'b0;

          assign aux_channel_p[14] = 1'b0;
          assign aux_channel_n[14] = 1'b0;

          assign aux_channel_p[15] = 1'b0;
          assign aux_channel_n[15] = 1'b0;
XADC #(
        .INIT_40(16'h3016), // config reg 0
        .INIT_41(16'h310F), // config reg 1
        .INIT_42(16'h0400), // config reg 2
        .INIT_48(16'h0100), // Sequencer channel selection
        .INIT_49(16'h0000), // Sequencer channel selection
        .INIT_4A(16'h0000), // Sequencer Average selection
        .INIT_4B(16'h0000), // Sequencer Average selection
        .INIT_4C(16'h0000), // Sequencer Bipolar selection
        .INIT_4D(16'h0000), // Sequencer Bipolar selection
        .INIT_4E(16'h0000), // Sequencer Acq time selection
        .INIT_4F(16'h0000), // Sequencer Acq time selection
        .INIT_50(16'hB5ED), // Temp alarm trigger
        .INIT_51(16'h57E4), // Vccint upper alarm limit
        .INIT_52(16'hA147), // Vccaux upper alarm limit
        .INIT_53(16'hCA33),  // Temp alarm OT upper
        .INIT_54(16'hA93A), // Temp alarm reset
        .INIT_55(16'h52C6), // Vccint lower alarm limit
        .INIT_56(16'h9555), // Vccaux lower alarm limit
        .INIT_57(16'hAE4E),  // Temp alarm OT reset
        .INIT_58(16'h5999), // VCCBRAM upper alarm limit
        .INIT_5C(16'h5111),  //  VCCBRAM lower alarm limit
        .SIM_DEVICE("7SERIES"),
        .SIM_MONITOR_FILE("design.txt")
)

inst (
        .CONVST(GND_BIT),
        .CONVSTCLK(GND_BIT),
        .DADDR(daddr_in[6:0]),
        .DCLK(dclk_in),
        .DEN(den_in),
        .DI(di_in[15:0]),
        .DWE(dwe_in),
        .RESET(reset_in),
        .VAUXN(aux_channel_n[15:0]),
        .VAUXP(aux_channel_p[15:0]),
        .ALM(alm_int),
        .BUSY(busy_out),
        .CHANNEL(channel_out[4:0]),
        .DO(do_out[15:0]),
        .DRDY(drdy_out),
        .EOC(eoc_out),
        .EOS(eos_out),
        .JTAGBUSY(),
        .JTAGLOCKED(),
        .JTAGMODIFIED(),
        .OT(),
        .MUXADDR(),
        .VP(vp_in),
        .VN(vn_in)
          );

endmodule

 

그리고 test_top에 adc_top 소스코드를 아래와 같이 추가한다.

adc_top 소스코드
//////////////////////////////////////////////////////// adc
module adc_top(
      input clk, reset_p,
      input vauxp6, vauxn6,
      output [3:0] com,
      output [7:0] seg_7);
      
      wire [4:0] channel_out;
      wire eoc_out;
      wire [15:0] do_out;
      xadc_wiz_0 adc_ch6
          (
          .daddr_in({2'b0, channel_out}),            // Address bus for the dynamic reconfiguration port
          .dclk_in(clk),                                          // Clock input for the dynamic reconfiguration port
          .den_in(eoc_out),                                 // Enable Signal for the dynamic reconfiguration port
          .reset_in(reset_p),                               // Reset signal for the System Monitor control logic
          .vauxp6(vauxp6),                               // Auxiliary channel 6
          .vauxn6(vauxn6),
          .channel_out(channel_out),            // Channel Selection Outputs
          .do_out(do_out),                            // Output data bus for dynamic reconfiguration port
          .eoc_out(eoc_out)                           // End of Conversion Signal
          );
          
      wire eoc_out_pedge;
      edge_detector_n ed(
            .clk(clk), .reset_p(reset_p), .cp(eoc_out), 
            .n_edge(eoc_out_pedge));
      
      reg [11:0] adc_value;               // 2^12=4096, 비트수가 많을수록 정밀도가 높다
      always @(posedge clk or posedge reset_p)begin
            if(reset_p)adc_value = 0;
            else if(eoc_out_pedge)begin
                  adc_value = do_out[15:4];           // [15:0] do_out에서 상위 12비트만 측정값으로 한다고 정했기 때문 
            end
      end
      
      wire [15:0] adc_value_bcd;
      bin_to_dec btd_duty(.bin({adc_value}), .bcd(adc_value_bcd));
      
      fnd_4digit_cntr FND_duty(.clk(clk), .value(adc_value_bcd), .com(com), .seg_7(seg_7)); 
          
endmodule

 

.xdc 파일 수정해야하는 곳


PWM으로 servo 모터 제어하기, ADC 끝!