精华内容
下载资源
问答
  • 多路 PWM 原理分析以及实现试验 基本原理(参考 http͗//www͘fpga4fun͘com/PWM_D_C͘html) PWM_(Pulse_Width_Modulation) A PWM takes an input value of any width and creates an output that is just one-bit ...

    多路 PWM 原理分析以及实现试验

    基本原理(参考 http͗//www͘fpga4fun͘com/PWM_D_C͘html)

    PWM_(Pulse_Width_Modulation)

    A PWM takes an input value of any width and creates an output that is just
    one-bit wide.
    PWM using a free-running counter

    That’s the simplest PWM we can make.

    module PWM( 
    input clk, 
    input [3:0] PWM_in, 
    output PWM_out 
    ); 
    
    reg [3:0] cnt; 
    always @(posedge clk) cnt <= cnt + 1'b1;  // free-running 
    counter 
    
    assign PWM_out = (PWM_in > cnt);  // comparator 
    endmodule 
    
    

    We choose a 4bit PWM here so the PWM period is 16. The input
    can go from 0 to 15 so the PWM output ratio goes from 0% to
    15/16=93%. If you need to be able to go up to 100%, the input needs
    to have an extra bit.

    The code works fine, although it is a bit naive in its current form
    because the input must be fixed (or change only when the counter
    overflows = goes back to 0). Otherwise the output will glitch. So most
    likely a bit of extra logic is required (usually in the form of a latch
    capturing the input at the right time).

    PWM using a loadable up-down counter

    That’s a slightly more sophisticated design.

    module PWM( 
    input clk, 
    input [3:0] PWM_in, 
    output PWM_out 
    ); 
    reg [3:0] cnt; 
    reg cnt_dir;  // 0 to count up, 1 to count down 
    wire [3:0] cnt_next = cnt_dir ? cnt-1'b1 : cnt+1'b1; 
    wire cnt_end = cnt_dir ? cnt==4'b0000 : cnt==4'b1111; 
    always @(posedge clk) cnt <= cnt_end ? PWM_in : cnt_next; 
    always @(posedge clk) cnt_dir <= cnt_dir ^ cnt_end; 
    assign PWM_out = cnt_dir; 
    endmodule 
    

    First-order sigma-delta modulator

    A first-order sigma-delta modulator resembles a PWM, but with a
    better frequency response if you need to filter it because of its higher
    frequency output content.

    The simplest way to create a first-order sigma-delta modulator is
    to use an hardware accumulator… every time the accumulator
    overflows, output a ‘1’. Otherwise output a ‘0’. That’s very easily done
    in an FPGA.

    Verilog 
    
    module PWM(clk, PWM_in, PWM_out); 
    input clk; 
    input [7:0] PWM_in; 
    output PWM_out; 
    
    reg [8:0] PWM_accumulator; 
    always @(posedge clk) PWM_accumulator <= 
    PWM_accumulator[7:0] + PWM_in; 
    
    assign PWM_out = PWM_accumulator[8]; 
    endmodule 
    
    

    One-bit DAC

    Take one pin of an FPGA, connect a speaker and listen to an MP3?
    Easy. Here, we’ll use a PC to decode an MP3, and then send the
    decoded data to an FPGA that is configured as a one-bit DAC.

    Audio output

    We require a DAC (digital-to-analog converter) to connect the FPGA
    (digital) to a speaker (analog).
    The conventional approach would be to use a resistor ladder
    (see here), or use a dedicated DAC IC, like the venerable DAC-08.

    Since the frequency at which FPGAs run is so fast compared to the
    frequencies required in the audio domain (MHZ’s compared to KHz’s),
    a one-bit DAC is a better choice.

    Basically, to create the analog output, we smooth out the PWM or
    sigma-delta modulator output pulses using a low-pass filter. A
    sigma-delta modulator is better because of its higher-frequency
    output content, with which a single-order low-pass RC filter is usually
    enough.

    Playing an MP3

    The first step is to decode the MP3. The decoded MP3 data is called
    “PCM” data. To keep things simple, we send the PCM data through the
    serial port of the PC to the FPGA. The maximum rate possible through
    the serial port is 115200 baud (i.e. about 11.5 KBytes per second), so
    the music has to be down-sampled to 11KHz 8 bits. All that is easily
    done by a PC. Here’s the software used (with source code).
    And for the HDL code, we simply modify the sigma-delta modulator so
    that the PWM data input comes from the serial port.

    module PWM(input clk, input RxD, output PWM_out); 
    wire RxD_data_ready; 
    wire [7:0] RxD_data; 
    async_receiver 
    deserializer(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_d 
    ata(RxD_data)); 
    
    reg [7:0] RxD_data_reg; 
    always @(posedge clk) if(RxD_data_ready) RxD_data_reg <= RxD_data; 
    
     
    reg [8:0] PWM_accumulator; 
    always @(posedge clk)   PWM_accumulator   <=   PWM_accumulator[7:0]   + 
    RxD_data_reg; 
    
    assign PWM_out = PWM_accumulator[8]; 
    endmodule 
    
    

    Now is time to connect a speaker to the FPGA. There are 3 basic ways
    to do it.
    在这里插入图片描述

    使用创建和封装 IP 向导创建自定义 IP

    1.使用提供的 axi_lite 从属外设模板和自定义 IP 源代码来创建自定义 IP。

    打开 Vivado 软件,单击 Manage IP,然后选择 New IP Location,然后在新
    建 IP 位置窗口中单击 Next。
    选择 Verilog 作为 Target Language,Mixed 作为 Simulator language,对于
    IP 位置,请键入 D:/IP Core,然后单击 Finish(将其他设置保留为默认值,如果
    提示创建目录,请单击确定)。
    在这里插入图片描述

    2.运行创建和封装 IP向导 选择 Tools > Create and Package New IP…

    在这里插入图片描述
    在下一个窗口中,
    单击 Next
    在这里插入图片描述
    由于我们需要挂在到总线上,因此创建一个带
    AXI 总线的用户 IP,故选择 Create a new AXI4 peripheral。 点击 Next.
    在这里插入图片描述
    设置
    IP 的名字为 ADAU1761,版本号默认,并且记住 IP的位置 ,单击 Next.
    在这里插入图片描述
    设置总线形式为
    Lite 总线, Lite 总线是简化的 AXI 总线消耗的资源少,当然性
    能也是比完全版的 AXI 总线差一点,但是由于音频的速度并不高,因此采用 Lite 总线就够了,设置寄存器数量为 8,因为后面我们需要用到 8个寄存器。 单击
    Next.
    在这里插入图片描述
    选择
    edit IP 单击 Finish 完成
    在这里插入图片描述
    完成后的界面如下图所示
    在这里插入图片描述

    3.用户 IP 的修改

    IP创建完成后,并不能立马使用,还需要做一些修改。

    1. 打开PWM_IP_v1_0.v 文件在以下位置修改
      在这里插入图片描述
      在这里插入图片描述
    2. 打开PWM_IP_v1_0_S00_AXI.v 修改PWM_IP_v1_0_S00_AXI.v 的端口部分
      在这里插入图片描述
    3. slv_reg0、slv_reg1、slv_reg2、slv_reg3、slv_reg4、slv_reg5、slv_reg6、slv_reg7 为PS 部分写入PL 的寄存器。通过这个8 个寄存器的值,我们可以控制PWM 的占空比。
      在这里插入图片描述
    4. 下面这段代码就是PS 写PL 部分的寄存器,一共有8 个寄存器
    
    always @( posedge S_AXI_ACLK )
    begin
    if ( S_AXI_ARESETN == 1'b0 )
    begin
    slv_reg0 <= 0;
    slv_reg1 <= 0;
    slv_reg2 <= 0;
    slv_reg3 <= 0;
    slv_reg4 <= 0;
    slv_reg5 <= 0;
    slv_reg6 <= 0;
    slv_reg7 <= 0;
    end
    else begin
    if (slv_reg_wren)
    begin
    case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
    3'h0:
    for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
    if ( S_AXI_WSTRB[byte_index] == 1 ) begin
    // Respective byte enables are asserted as per write strobes
    // Slave register 0
    slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
    end
    3'h1:
    for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
    if ( S_AXI_WSTRB[byte_index] == 1 ) begin
    // Respective byte enables are asserted as per write strobes
    // Slave register 1
    slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
    end
    3'h2:
    for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
    if ( S_AXI_WSTRB[byte_index] == 1 ) begin
    // Respective byte enables are asserted as per write strobes
    // Slave register 2
    slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
    end
    3'h3:
    for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
    if ( S_AXI_WSTRB[byte_index] == 1 ) begin
    // Respective byte enables are asserted as per write strobes
    // Slave register 3
    slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
    end
    3'h4:
    for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
    if ( S_AXI_WSTRB[byte_index] == 1 ) begin
    // Respective byte enables are asserted as per write strobes
    // Slave register 4
    slv_reg4[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
    end
    3'h5:
    for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
    if ( S_AXI_WSTRB[byte_index] == 1 ) begin
    // Respective byte enables are asserted as per write strobes
    // Slave register 5
    slv_reg5[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
    end
    3'h6:
    for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
    if ( S_AXI_WSTRB[byte_index] == 1 ) begin
    // Respective byte enables are asserted as per write strobes
    // Slave register 6
    slv_reg6[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
    end
    3'h7:
    for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
    if ( S_AXI_WSTRB[byte_index] == 1 ) begin
    // Respective byte enables are asserted as per write strobes
    // Slave register 7
    slv_reg7[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
    end
    default : begin
    slv_reg0 <= slv_reg0;
    slv_reg1 <= slv_reg1;
    slv_reg2 <= slv_reg2;
    slv_reg3 <= slv_reg3;
    slv_reg4 <= slv_reg4;
    slv_reg5 <= slv_reg5;
    slv_reg6 <= slv_reg6;
    slv_reg7 <= slv_reg7;
    end
    endcase
    end
    end
    end
    

    5新建一个PWM_driver.v 文件实现8 路PWM 并行输出然后保存到PWM_IP_1.0/hdl 文件夹,并添加进来。
    在这里插入图片描述
    在这里插入图片描述

    PWM_driver.v具体内容为:
    module PWM_driver(
    input clk_i,
    input rst_n_i,
    input [31:0]pwm_reg0_i,
    input [31:0]pwm_reg1_i,
    input [31:0]pwm_reg2_i,
    input [31:0]pwm_reg3_i,
    input [31:0]pwm_reg4_i,
    input [31:0]pwm_reg5_i,
    input [31:0]pwm_reg6_i,
    input [31:0]pwm_reg7_i,
    output reg [7:0] pwm_o
    );
    reg[31:0]pwm_cnt0;
    reg [31:0]pwm_cnt1;
    reg [31:0]pwm_cnt2;
    reg [31:0]pwm_cnt3;
    reg [31:0]pwm_cnt4;
    reg [31:0]pwm_cnt5;
    reg [31:0]pwm_cnt6;
    reg [31:0]pwm_cnt7;
    //pwm0
    always @(posedge clk_i)begin
    if(!rst_n_i)begin
    pwm_cnt0 <= 32'd0;
    pwm_o[0] <= 1'b0;
    end
    else begin
    if(pwm_cnt0<pwm_reg0_i)begin
    pwm_cnt0 <= pwm_cnt0 +1'b1;
    end
    else begin
    pwm_cnt0<=32'D0;
    pwm_o[0]<=~pwm_o[0];
    end
    end
    end
    //pwm1
    always @(posedge clk_i)begin
    if(!rst_n_i)begin
    pwm_cnt1 <= 32'd0;
    pwm_o[1] <= 1'b0;
    end
    else begin
    if(pwm_cnt1<pwm_reg1_i)begin
    pwm_cnt1 <= pwm_cnt1 +1'b1;
    end
    else begin
    pwm_cnt1<=32'D0;
    pwm_o[1]<=~pwm_o[1];
    end
    end
    end
    //pwm2
    always @(posedge clk_i)begin
    if(!rst_n_i)begin
    pwm_cnt2 <= 32'd0;
    pwm_o[2] <= 1'b0;
    end
    else begin
    if(pwm_cnt2<pwm_reg2_i)begin
    pwm_cnt2 <= pwm_cnt2 +1'b1;
    end
    else begin
    pwm_cnt2<=32'D0;
    pwm_o[2]<=~pwm_o[2];
    end
    end
    end
    //pwm3
    always @(posedge clk_i)begin
    if(!rst_n_i)begin
    pwm_cnt3 <= 32'd0;
    pwm_o[3] <= 1'b0;
    end
    else begin
    if(pwm_cnt3<pwm_reg3_i)begin
    pwm_cnt3 <= pwm_cnt3 +1'b1;
    end
    else begin
    pwm_cnt3<=32'D0;
    pwm_o[3]<=~pwm_o[3];
    end
    end
    end
    //pwm4
    always @(posedge clk_i)begin
    if(!rst_n_i)begin
    pwm_cnt4 <= 32'd0;
    pwm_o[4] <= 1'b0;
    end
    else begin
    if(pwm_cnt4<pwm_reg4_i)begin
    pwm_cnt4 <= pwm_cnt4 +1'b1;
    end
    else begin
    pwm_cnt4<=32'D0;
    pwm_o[4]<=~pwm_o[4];
    end
    end
    end
    //pwm5
    always @(posedge clk_i)begin
    if(!rst_n_i)begin
    pwm_cnt5 <= 32'd0;
    pwm_o[5] <= 1'b0;
    end
    else begin
    if(pwm_cnt5<pwm_reg5_i)begin
    pwm_cnt5 <= pwm_cnt5 +1'b1;
    end
    else begin
    pwm_cnt5<=32'D0;
    pwm_o[5]<=~pwm_o[5];
    end
    end
    end
    //pwm5
    always @(posedge clk_i)begin
    if(!rst_n_i)begin
    pwm_cnt6 <= 32'd0;
    pwm_o[6] <= 1'b0;
    end
    else begin
    if(pwm_cnt6<pwm_reg6_i)begin
    pwm_cnt6 <= pwm_cnt6 +1'b1;
    end
    else begin
    pwm_cnt6<=32'D0;
    pwm_o[6]<=~pwm_o[6];
    end
    end
    end
    //pwm7
    always @(posedge clk_i)begin
    if(!rst_n_i)begin
    pwm_cnt7 <= 32'd0;
    pwm_o[7] <= 1'b0;
    end
    else begin
    if(pwm_cnt7<pwm_reg7_i)begin
    pwm_cnt7 <= pwm_cnt7 +1'b1;
    end
    else begin
    pwm_cnt7<=32'D0;
    pwm_o[7]<=~pwm_o[7];
    end
    end
    end
    endmodule
    
    

    6.点击File–>点击 Save all files,最终如下
    在这里插入图片描述
    4.修改完成后还要重新打包
    1 选择 tool–>Create and Package New Ip…
    2. 选择 package your current project 选择 next
    在这里插入图片描述
    3.保持默认设置,不做任何修改,点击 Next
    在这里插入图片描述
    4.点击 Next 选择 Overwrite
    在这里插入图片描述
    5.点击 Finish,完成。
    在这里插入图片描述
    6.执行以下操作检查 IP是否封装完成,展开 IP XACT(1)>双击
    component.xml,展开 Ports and Interface,可以看到封装 IP完成。
    在这里插入图片描述
    至此,创建用户
    IP完成。
    5添加 PWM_IP_v1_0 IP
    重新建立一个新的空的工程。
    Create Block Design 直接添加 zynq7 processing system。这个前面的教程内容部分已经重复很多次了,这里不重复。
    1.在进程导航窗格中,单击 Project Setting选项,选择 IP,然后单击 Add Repository 按钮。 浏览窗口打开,浏览到 IP核的位置

    D:/IP Core/ip_repo/PWM_IP_1.0 然后单击“ Select”,单击 Ok。
    在这里插入图片描述
    2.注意工具如何在目录中检测到新的 IP,点击 Apply,然后 OK。
    在这里插入图片描述
    3.这样做后,就可以将 PWM_IP_v1.0添加到当前项目的 IP库中,下一步是
    将其添加到块设计中,并将其从一侧连接到 Zynq处理系统,并从另一侧通过使
    用外部端口连接到 LED板块 。
    4.由于前面的过程非常详细,很多步骤省略,搭建完成后的系统如下图所示
    在这里插入图片描述
    5.添加 管脚约束 文件,之后编译工程,导出到 SDK,这个步骤前面的教程中
    也是重复过,不再过多重复,这里只给出 管脚约束 文件 PWM_pin.xdc

    set_property PACKAGE_PIN T22 [get_ports {pwm_o[0]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[0]}]
    set_property PACKAGE_PIN T21 [get_ports {pwm_o[1]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[1]}]
    set_property PACKAGE_PIN U22 [get_ports {pwm_o[2]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[2]}]
    set_property PACKAGE_PIN U21 [get_ports {pwm_o[3]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[3]}]
    set_property PACKAGE_PIN V22 [get_ports {pwm_o[4]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[4]}]
    set_property PACKAGE_PIN W22 [get_ports {pwm_o[5]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[5]}]
    set_property PACKAGE_PIN U19 [get_ports {pwm_o[6]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[6]}]
    set_property PACKAGE_PIN U14 [get_ports {pwm_o[7]}]
    set_property IOSTANDARD LVCMOS33 [get_ports {pwm_o[7]}]
    

    6.SDK 工程源码
    SDK 工程部分的 C 工程新建工程也不详细讲解,前面已经重复很多次了,
    这里只给出 C 代码略作分析。

    #include "xparameters.h"
    #include "stdio.h"
    #include "xil_io.h"
    #define PWM_REG0 XPAR_PWM_IP_V1_0_0_BASEADDR + 0
    #define PWM_REG1 XPAR_PWM_IP_V1_0_0_BASEADDR + 4
    #define PWM_REG2 XPAR_PWM_IP_V1_0_0_BASEADDR + 8
    #define PWM_REG3 XPAR_PWM_IP_V1_0_0_BASEADDR + 12
    #define PWM_REG4 XPAR_PWM_IP_V1_0_0_BASEADDR + 16
    #define PWM_REG5 XPAR_PWM_IP_V1_0_0_BASEADDR + 20
    #define PWM_REG6 XPAR_PWM_IP_V1_0_0_BASEADDR + 24
    #define PWM_REG7 XPAR_PWM_IP_V1_0_0_BASEADDR + 28
    int main()
    {
    Xil_Out32(PWM_REG0,100000000);
    Xil_Out32(PWM_REG1,100000000/2);
    Xil_Out32(PWM_REG2,100000000/4);
    Xil_Out32(PWM_REG3,100000000/8);
    Xil_Out32(PWM_REG4,100000000/10);
    Xil_Out32(PWM_REG5,100000000/16);
    Xil_Out32(PWM_REG6,100000000/20);
    Xil_Out32(PWM_REG7,100000000/32);
    return 0;
    }
    

    以上函数中
    我们自定一点 PWM IP 寄存器中写 数据,实现了 100MHZ AXI4 总线的 2 分
    频、 4分频、 8 分频、 10 分频、 16 分频、 20 分频、 32 分频。
    测试完成,现象:
    8个 灯 流水 亮起。

    展开全文
  • 本文讲述怎么利用单片机的一个定时器生成多路PWM波形。 一般的,PWM的周期t1是一个固定值,如1ms,10ms,100ms等,在一个周期中包含了高电平t2和低电平t3,它们的关系是t2+t3=t1。改变一个周期中高电平的时间,就能...

    在很多工程应用中,需要使用到PWM波(脉宽调制),例如电机调速、温度控制调整功率等。本文讲述怎么利用单片机的一个定时器生成多路PWM波形。
    一般的,PWM的周期t1是一个固定值,如1ms,10ms,100ms等,在一个周期中包含了高电平t2和低电平t3,它们的关系是t2+t3=t1。改变一个周期中高电平的时间,就能达到速度或者功率调整的目的。PWM适用于高速开关器件的控制,不适合于继电器等低速开关元件的控制,因为继电器等低速元件达不到如此快的开关速度。
    PWM控制的关键是控制改变PWM的高电平时间t2,这个时间在其他子程序中由控制算法中进行修改,如PID控制算法。

    在微处理器,如单片机中,实现一个定时器生成多路PWM的方法如下。
    首先来看看用一个定时器实现一路PWM输出的方法:
    >>定义一个时间基准刻度t,这个也是计时器中断周期,一般在程序中t不改变。PWM的周期、高低电平时间由若干个基准时间t组成;
    >>定义一个全局计时变量n,n表示这段时间经过了多少个基准时间t;
    >>定义一个全局计时变量n1,n1表示一个PWM的周期包含多少个基准时间t,n1=t1/t。一般在程序中n1,t1不改变。
    >>定义一个全局计时变量n2,n2表示一个PWM周期中高电平包含多少个基准时间t,n2=t2/t。由于高电平的时间受其他算法控制改变的,n2也是随时在改变。
    程序的流程如下:
    ①初始化定时器,定时器中断时间设置为t,如设置t为1ms或其他;
    ②初始化变量:n=0;//计时变量n清零
    n1=t1/t;//一个PWM的周期t1包含了多少个基准时间t.
    //假如设PWM周期为100ms,则n1=100;
                      n2=t2/t;//计算一个PWM中高电平时间t2由多少个基准时间//t组成。t2由其他控制算法改变。
    ③ 开始计时,打开定时器中断。定时器中断程序中n++;
    当n<n2&& n<n1时,IO口输出高电平;
    当n>=n2&& n<n1时,IO口输出低电平;

    当n>n1时,返回步骤①进行下一个周期的PWM.

    程序流程图如下:


    如果在同一个定时器中需要多路PWM,再定义多组类似的变量,按照图2 中流程处理即可。

    全文完。

     

    展开全文
  • 不用定时器,使用特殊的软件延时方法可以达到多路PWM控制舵机,即使不能并行输出的C51也可以做到
  • PWM的周期t1是一个固定值,如1ms,10ms,100ms等,在一个周期中包含了高电平t2和低电平t3,它们的关系是t2+t3=t1。改变一个周期中高电平的时间,就能达到速度或者功率调整的目的。

    PWM产生原理

    PWM的周期t1是一个固定值,如1ms,10ms,100ms等,在一个周期中包含了高电平t2和低电平t3,它们的关系是t2+t3=t1。改变一个周期中高电平的时间,就能达到速度或者功率调整的目的。

    定时器生成一路PWM

    基于STM32用一个定时器实现一路PWM输出的方法:

    1. 定义一个时间基准刻度t,这个也是计时器中断周期,一般在程序中t不改变。PWM的周期、高低电平时间由若干个基准时间t组成;

    我们用的单片机是STM32F103VBT6,我们选用TIM2作为基本定时器,CobeMX中的配置如图所示:

    TIM2的时钟频率是64MHZ,计数模式选择向上计数,计满之后产生定时中断,中断一次的时间是1毫秒,故基准刻度t是1毫秒。

    并开启定时中断

    2. 定义一个全局计时变量n,n表示这段时间经过了多少个基准时间t;后期我们在中断中定义一个全局变量CountTimer_2,每终端一次加1,即可得知定时时间。

    3. 定义一个全局计时变量n1,n1表示一个PWM的周期包含多少个基准时间t,n1=t1/t。一般在程序中n1,t1是计时周期不改变。

    4. 定义一个全局计时变量n2,n2表示一个PWM周期中高电平包含多少个基准时间t,n2=t2/t。由于高电平的时间受其他算法控制改变的,n2也是随时在改变。

    程序的流程如下:

    ①初始化定时器,定时器中断时间设置为t,如设置t为1ms或其他;

    ②初始化变量:n=0;//计时变量n清零 

    uint32_t CountTimer_2 = 0; 

    n1=t1/t;     //一个PWM的周期t1包含了多少个基准时间t,假如设PWM周期为1000ms=1s,则n1=1000;

      if(CountTimer_2==1000)

    n2=t2/t;     //计算一个PWM中高电平时间t2由多少个基准时间t组成。t2由其他控制算法改变。

    ③开始计时,打开定时器中断。定时器中断程序中n++;

    /*
    **********************************************************************************************************
    *	函 数 名: void PIDCalc(int32_t NextTemp)
    *	功能说明: 增量式闭环计算
    *	输入参数: 无
    *	返 回 值: 无
    **********************************************************************************************************
    */
    int32_t PIDCalc_IRQHandler(PID_TypeDef *sPidChannel,float CurrentTemp)
    {
        float iError,iIncpid;
        iError = sPidChannel->SetTemp - CurrentTemp;
        if(iError >= 0 && iError <= 2)
        {
           iIncpid = (int32_t)((sPidChannel->Proportion * (float)iError)
                     - (sPidChannel->Integral * (float)sPidChannel->LastError)
                     + (sPidChannel->Derivative * (float)sPidChannel->PrevError))*0.2;
           sPidChannel->PrevError = sPidChannel->LastError;
           sPidChannel->LastError = iError;
        }
        else if(iError > 2 && iError <= 10)
        {
           iIncpid = (int32_t)((sPidChannel->Proportion * (float)iError)
                     - (sPidChannel->Integral * (float)sPidChannel->LastError)
                     + (sPidChannel->Derivative * (float)sPidChannel->PrevError))*1.5;
           sPidChannel->PrevError = sPidChannel->LastError;
           sPidChannel->LastError = iError;        
        }    
        else if(iError > 10)
        {
           iIncpid = (int32_t)((sPidChannel->Proportion * (float)iError)
                     - (sPidChannel->Integral * (float)sPidChannel->LastError)
                     + (sPidChannel->Derivative * (float)sPidChannel->PrevError))*10;
           sPidChannel->PrevError = sPidChannel->LastError;
           sPidChannel->LastError = iError;        
        }
        else if(iError < 0)
        {
           iIncpid = (int32_t)((sPidChannel->Proportion * (float)iError)
                     - (sPidChannel->Integral * (float)sPidChannel->LastError)
                     + (sPidChannel->Derivative * (float)sPidChannel->PrevError))*0;
           sPidChannel->PrevError = sPidChannel->LastError;
           sPidChannel->LastError = iError;        
        }     
        return(iIncpid);      
    }
    

     

     

     

     

     

     

     

     

     

    展开全文
  • 该控制系统依据由可编程逻辑器件FPGA实现多路PWM控制系统的原理和方法,选择新型FPGA芯片MAX10作为主控芯片,该芯片集成了AD采样控制、控制算法和PWM 波形生成等电路,大大降低了电路的复杂程度。仿真和实验结果验证...
  • STM32之使用PWM控制多路舵机

    万次阅读 多人点赞 2018-05-29 10:51:12
    前言 最近在玩一个6自由度...舵机控制原理 舵机的控制原理还是比较简单的,而且控制的角度和精度能够比较好的按照开发者的意愿来进行,因此经常被应用与一些控制类器械中,如机械手、云台、2自由度摄像头等产品中。...

     

     

    前言

     

        最近在玩一个6自由度的机械臂,我手上这台机械臂的核心控制器件就是那六个能够180度旋转的舵机了。想想之前在学校还没有系统性的把舵机给玩明白,所以就索性拿手上的STM32来自己写驱动代码,将6个舵机给驱动起来。

     

    舵机控制原理

     

     

        舵机的控制原理还是比较简单的,而且控制的角度和精度能够比较好的按照开发者的意愿来进行,因此经常被应用与一些控制类器械中,如机械手、云台、2自由度摄像头等产品中。

     

        舵机的外接线一般分为3根线,电源线、地线和信号线,而控制舵机转动,就是通过信号线给舵机发送一系列的周期信号(一般的舵机的能接收的信号周期为20ms),然后通过控制周期信号的高电平的持续时间来达到控制舵机转动的目的。我手上的舵机就是根据高电平持续时间(0.5ms~2.5ms)来实现0~180的转动的。下面附上一张舵机周期信号控制和转动角度的图片说明。

     

     

     

     

     

        当然了,周期信号的产生可以使用很多方式,但是使用PWM来控制高电平的占空比不失为一种最好的应用方式。在STM32中,STM32的定时器也都提供有PWM的功能。下面就说明一下STM32输出PWM的具体实现方式。

     

    使用STM32控制单个舵机

     

     

        在STM32中控制舵机,实际上就是开发STM32上的PWM功能,这部分功能需要配置STM32的定时器和GPIO复用共功能,然后就是通过修改定时器计数器的比较寄存器的数值来达到控制PWM的高电平占空比的目的。

     

     

     

        这里以STM32F767为例,说明一下具体的实现。

     

     

     

     

       下面使用到了STM32F7中的定时器3作为主定时器,使用通道4来产生PWM。需要注意的定时器3的通道4是通过GPIOB1接口输出的,因此还需要将GPIOB1配置为复用输出功能。PWM的输出比较极性设置为高。

    TIM_HandleTypeDef TIM3_Handler;
    TIM_OC_InitTypeDef TIM3_CH4Handler;
    //arr 为自动重装值
    //psc 为时钟预分频数
    void TIM3_PWM_Init(u16 arr,u16 psc)
    { 
        TIM3_Handler.Instance=TIM3;    
        TIM3_Handler.Init.Prescaler=psc;      
        TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;
        TIM3_Handler.Init.Period=arr;         
        TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
        HAL_TIM_PWM_Init(&TIM3_Handler);      
        
        TIM3_CH4Handler.OCMode=TIM_OCMODE_PWM1; 
        TIM3_CH4Handler.Pulse=arr/2; 
        TIM3_CH4Handler.OCPolarity=TIM_OCPOLARITY_HIGH; //设置输出比较极性
        HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4);
    	
        HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_4);
    }
    //htim 为定时器句柄
    
    void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
    {
        GPIO_InitTypeDef GPIO_Initure;
     __HAL_RCC_TIM3_CLK_ENABLE();  
        __HAL_RCC_GPIOB_CLK_ENABLE();   
     
        GPIO_Initure.Pin=GPIO_PIN_1;
        GPIO_Initure.Mode=GPIO_MODE_AF_PP;  
        GPIO_Initure.Pull=GPIO_PULLUP;        
        GPIO_Initure.Speed=GPIO_SPEED_HIGH;     
     GPIO_Initure.Alternate= GPIO_AF2_TIM3;
        HAL_GPIO_Init(GPIOB,&GPIO_Initure);
    }
    //设置定时器的比较寄存器的值
    void TIM_SetTIM3Compare4(u32 compare)
    {
    	TIM3->CCR4=compare; 
    }
    //arr 为自动重装值
    //psc 为时钟预分频数
    void TIM3_PWM_Init(u16 arr,u16 psc)
    { 
        TIM3_Handler.Instance=TIM3;    
        TIM3_Handler.Init.Prescaler=psc;      
        TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;
        TIM3_Handler.Init.Period=arr;         
        TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
        HAL_TIM_PWM_Init(&TIM3_Handler);      
        
        TIM3_CH4Handler.OCMode=TIM_OCMODE_PWM1; 
        TIM3_CH4Handler.Pulse=arr/2; 
        TIM3_CH4Handler.OCPolarity=TIM_OCPOLARITY_HIGH; //设置输出比较极性
        HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4);
    	
        HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_4);
    }
    //htim 为定时器句柄
    
    void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
    {
        GPIO_InitTypeDef GPIO_Initure;
     __HAL_RCC_TIM3_CLK_ENABLE();  
        __HAL_RCC_GPIOB_CLK_ENABLE();   
     
        GPIO_Initure.Pin=GPIO_PIN_1;
        GPIO_Initure.Mode=GPIO_MODE_AF_PP;  
        GPIO_Initure.Pull=GPIO_PULLUP;        
        GPIO_Initure.Speed=GPIO_SPEED_HIGH;     
     GPIO_Initure.Alternate= GPIO_AF2_TIM3;
        HAL_GPIO_Init(GPIOB,&GPIO_Initure);
    }
    //设置定时器的比较寄存器的值
    void TIM_SetTIM3Compare4(u32 compare)
    {
    	TIM3->CCR4=compare; 
    }
    
    

        以上的配置代码完成之后,就可以在主函数里面进行调用,并且使用PWM功能了。

    //配置定时器3的自动重装值和时钟预分频数,定时器3使用的时钟为108MHz,这里进行108分频,定时器的预分频设置为1MHz
    //自动重装值设置为20000,既该定时器每计数20000次触发一次中断,预分频为1MHz,则周期为20ms
    TIM3_PWM_Init(20000-1,108-1)

     

     

        定时器初始化配置完之后,在主循环之前要对舵机进行一次复位操作,从上面的图中可以看出,在高电平持续时间为1.5ms的情况下,舵机能够回中,因此需要调用以下函数,设置定时器的初始比较寄存器的值为1500,即可以使得定时器复位回中。

    
    TIM_SetTIM3Compare4(1500);

     

        需要说明一下,在修改定时器的比较寄存器的值的过程中,要有一些短暂的延时,以保证舵机将动作执行完成。

     

    使用STM32控制多路舵机

     

     

     

        单个舵机控制能够完成之后,就可以尝试使用STM32的定时器控制多路舵机了。STM32的定时器中通用定时器能够产生多达4路PWM输出,高级定时器TIM1和TIM8还能够产生多达7路定时器。因此,我们完全可以使用STM32的一个定时器外设控制多路舵机,通过修改不同通道的比较寄存器的值,来达到控制舵机运动的目的。

        下面就以STM32F7控制4路舵机为例,贴出来定时器配置相关代码。

    TIM_HandleTypeDef TIM3_Handler;         //定时器句柄 
    TIM_OC_InitTypeDef TIM3_CH1Handler;     //定时器3通道1句柄
    TIM_OC_InitTypeDef TIM3_CH2Handler;     //定时器3通道2句柄
    
    TIM_OC_InitTypeDef TIM3_CH3Handler;     //定时器3通道3句柄
    TIM_OC_InitTypeDef TIM3_CH4Handler;     //定时器3通道4句柄
    
    //arr:自动重装值
    //psc:时钟预分频数
    void TIM3_PWM_Init(u16 arr,u16 psc)
    { 
        TIM3_Handler.Instance=TIM3;            //定时器3
        TIM3_Handler.Init.Prescaler=psc;       //定时器分频
    	TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;//向上计数模式
        TIM3_Handler.Init.Period=arr;          //自动重装载值
        TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
        HAL_TIM_PWM_Init(&TIM3_Handler);       //初始化PWM
        
    	
    	TIM3_CH1Handler.OCMode=TIM_OCMODE_PWM1; 
    	TIM3_CH1Handler.Pulse=arr/2;
    	TIM3_CH1Handler.OCPolarity=TIM_OCPOLARITY_HIGH;
    	HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH1Handler,TIM_CHANNEL_1);//配置TIM3通道1
    	HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_1);//开启PWM通道1
    	
    	TIM3_CH2Handler.OCMode=TIM_OCMODE_PWM1; 
    	TIM3_CH2Handler.Pulse=arr/2;
    	TIM3_CH2Handler.OCPolarity=TIM_OCPOLARITY_HIGH;
    	HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH2Handler,TIM_CHANNEL_2);//配置TIM3通道2
    	HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_2);//开启PWM通道2
    	
    	
    	TIM3_CH3Handler.OCMode=TIM_OCMODE_PWM1; //模式选择PWM1
    	TIM3_CH3Handler.Pulse=arr/2;
    	TIM3_CH3Handler.OCPolarity=TIM_OCPOLARITY_HIGH; 
    	HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH3Handler,TIM_CHANNEL_3);//配置TIM3通道3
    	HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_3);//开启PWM通道3
    
        TIM3_CH4Handler.OCMode=TIM_OCMODE_PWM1; //模式选择PWM1
        TIM3_CH4Handler.Pulse=arr/2;            //设置比较值,此值用来确定占空比,
                                                //默认比较值为自动重装载值的一半,即占空比为50%
        TIM3_CH4Handler.OCPolarity=TIM_OCPOLARITY_HIGH; 
        HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4);//配置TIM3通道4
        HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_4);//开启PWM通道4
    }
    
    
    //此函数会被HAL_TIM_PWM_Init()调用 
    //htim:定时器句柄 
    void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) {     
    GPIO_InitTypeDef GPIO_Initure;  
    __HAL_RCC_TIM3_CLK_ENABLE();   //使能定时器3     
    __HAL_RCC_GPIOB_CLK_ENABLE();   //开启GPIOB时钟  
    
    __HAL_RCC_GPIOA_CLK_ENABLE();   //开启GPIOA时钟  
    
    GPIO_Initure.Pin=GPIO_PIN_6|GPIO_PIN_7; //PA6 PA7     
    GPIO_Initure.Mode=GPIO_MODE_AF_PP;   //复用推完输出  
    
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉     
    
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速 
    
    GPIO_Initure.Alternate=GPIO_AF2_TIM3; //PB1复用为TIM3_CH4   
    
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);       
    
    GPIO_Initure.Pin=GPIO_PIN_1;            //PB1     
    
    GPIO_Initure.Mode=GPIO_MODE_AF_PP;   //复用推完输出     
    
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉     
    
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速 
    
    GPIO_Initure.Alternate=GPIO_AF2_TIM3; //PB1复用为TIM3_CH4   
    
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);       
    
    GPIO_Initure.Pin=GPIO_PIN_0 ;           //PB0     
    
    GPIO_Initure.Mode=GPIO_MODE_AF_PP;   //复用推完输出     
    
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉     
    
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速 
    
    GPIO_Initure.Alternate=GPIO_AF2_TIM3; //PB1复用为TIM3_CH3    
    
    
    HAL_GPIO_Init(GPIOB,&GPIO_Initure); 
    }
    
    
    
    //设置TIM3通道1的占空比 //compare:比较值 
    void TIM_SetTIM3Compare1(u32 compare) 
    {  TIM3->CCR1=compare; }
    
    //设置TIM3通道2的占空比 //compare:比较值 
    void TIM_SetTIM3Compare2(u32 compare) 
    {  TIM3->CCR2=compare; }
    //设置TIM通道4的占空比 //compare:比较值 
    void TIM_SetTIM3Compare4(u32 compare) 
    {  TIM3->CCR4=compare; }
    
    //设置TIM通道3的占空比 //compare:比较值 
    void TIM_SetTIM3Compare3(u32 compare) 
    {  TIM3->CCR3=compare; }

    总结

        善于利用STM32单片机的外设资源,能够让开发效率大大提高。

       后续就是要对这些函数进行封装,用来作为舵机控制的统一接口,直接传递舵机控制的角度,来达到控制舵机的目的。

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 最近需要做一个舵机实验,需要四路pwm输出,只需要一个定时器的四个通道就行了,但是正点原子的教程里的pwm输出是使用的TIM14,只有两个通道,上网搜了一下输出多路pwm的博客,发现大多数都是介绍原理,或者都是打着...
  • 最近买了块16路PWM舵机驱动板,测试后做个总结。 舵机原理网上资料很就不详细介绍了,一般以9g舵机为例,一个20ms的周期内通过0.5ms到2.5ms的脉冲宽度控制舵机角度。 板子为16通道12bit PWM舵机驱动,用2个引脚...
  • 提出了一种基于PWM的新型移动通信光纤多路中继传输系统。该系统可以排除现有模拟光纤中继传输系统中的非线性和噪声干扰,而要求的传输码率又比一般数字系统低近10倍,成本比相同传距的模拟光纤中继器低十多倍。
  • 该设计使用STM32F407为主要控制芯片,使用不同的PWM输出口,分别各自单独控制电机,使得电机互相直接独立工作互不干涉,可以个电机同时运动,提高设备运动性能,通过加减速算法,电机运行S型曲线。本设计同时设计...
  • 单片机的PWM控制技术

    2012-04-27 12:45:42
    在单片机中实现PWM 控制技术的方法及其原理,分析单片机实现PWM 功能几种方法的优缺点,分别介绍用单片机的PWM 模块实现PWM 功能、用程序模拟实现多路PWM 功能、用定时器资源实现高分辩率PWM 功能和用机外硬件实现 ...
  • S3C6410 PWM驱动(二) --- 原理分析

    千次阅读 2012-09-24 15:50:15
    (2)TCFG1:时钟多路复用器和 DMA 模式的选择。 (3)TCON:定时器控制寄存器。 (4)TCNTB0:定时器 0 计数缓冲寄存器。 (5)TCMPB0:定时器 0 比较缓冲寄存器。 (6)TCNTO0:定时器 0 计数观察寄存器。 (7)TCNTB1:定时器 ...
  • 该设计使用STM32F407为主要控制芯片,使用不同的PWM输出口,分别各自单独控制电机,使得电机互相直接独立工作互不干涉,可以个电机同时运动,提高设备运动性能,通过加减速算法,电机运行S型曲线。本设计同时设计...
  • STM32-PWM输出

    2017-07-29 10:43:23
    脉冲宽度调制,简称PWM。... 在STM32中拥有多路PWM信号。每一个定时器的输出引脚都可以设置成相应的PWM信号的输出。PWM信号主要的参数有两个,周期的占空比。周期主要是由对应的定时器来确定。占空比
  • 其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 PWM 输出。而通用定时器也能同时产生多达 4PWM 输出! 下图就是定时器各路输出对应的IO口。从图中可以看出,一路有可能对应个IO。 二.工作原理 由上...
  • 8 50-400 Hz PWM 舵机输出 兼容Futaba S.Bus 舵机输出 兼容PPM, Spektrum 和 Futaba S.Bus 接收机输入 (信号叠加形式的PPM, 很兼容接收机, 一路传送所有通道的信号) 2 0-40 V, 1 A的固态继电器 (MOSFET) 2 5...
  • 目前,多路照明LED 正在被广泛的应用,但每个LED 都是配有一个单独的驱动源,...通过实验研究,阐述了多路调光控制的原理,论证了两种调光方法的可行性,得出了整个电路输入输出关系,从而可以具体控制多路LED 的亮度。
  • 设计集成有两MAX1169以及PWM输出的测试模块可以为很实验提供测量和驱动功能。 比如: 使用STC8H1K28控制微型磁悬浮 磁悬浮地球仪控制初步测试 磁悬浮地球仪拆解 电磁铁的磁芯实验   01电路模块设计 ...
  • 1标题说不清楚,简单点说就是一个定时器输出4路可调节频率的pwm。 2这个功能能干嘛? 一般是用于控制个步进电机。这样做一个定时器就可以控制4个电机了。 先上代码吧 u16 capture = 0; vu16 CCR1_Val = 32768...
  • 像大家看到的七色彩灯其原理也类似,只是用3路PWM分别控制红、绿、蓝三种颜色的灯输出亮度,再结合混色原理表现出丰富多彩的炫光效果~   写在前面:前十几篇介绍了CC2530的一些外设的基本用法,接下来几篇拿几个...
  • 介绍了开关电源电流型PWM控制技术的原理和优点,并基于UC3842芯片设计了+5V/4A,±12V/1A三输出的反激式电源,具体分析了电路的工作原理和高频变压器参数的计算。
  • 反激式转换器的电路特点是 电路简单,所用元器件的数量最少,一般多用于小功率(如100W)和多路输出的场合。  反激式转换器的工作原理是:当主开关管导通时变压器次级侧的二极管关断,变压器储能;在主开关管关...

空空如也

空空如也

1 2 3 4 5 6
收藏数 114
精华内容 45
关键字:

多路pwm原理