样例源码
链接:https://pan.baidu.com/s/1lx7KyPAijTRS1Nc0fHwxuw 提取码:9v56
SF1的RISC-V的内部架构
在第一讲中介绍了SF1芯片的内部架构,SF1 是集成了三级流水线的 RISC-V MCU 内核以及包含有 5824 LUTs 的 FPGA。 RISC-V MCU 的 APB与 DSC, DSI 等硬核模块通过硬线直连, 用户可以通过 MCU 其进行初始化配置。 RISC-V MCU 预留 AHB端口, 可通过 AHB 总线访问 FPGA 用户逻辑, 也可访问 PSRAM 等存储单元。 此外, 在 SF1 RISC-V MCU所有 IO 都与 FPGA 相连, 因此, MCU 系统时钟、 中断、 调试等接口, 需通过用户配置 FPGA 来实现。 当MCU 处于 Bypass Mode 时(关闭 MCU Enable), DSC/DSI 的模块也可以通过用户逻辑的 APB master 来配置, 此时 MCU 不能工作。
从其中的介绍可以直到,RISC-V内核和FPGA部分通过AHB可以连接,而APB端口是固定用来连接DSI/DSC模块。所以我们如果设计了一个模块需要通过RISC-V内核来控制的话,可以通过AHB总线来连接。例如内核里没有PWM外设,如果自己设计一个PWM模块就可以通过AHB接口连接到RISC-V内核上,然后通过FutureDynasty软件进行编程控制PWM模块输出相应的PWM波形。本讲就以设计一个PWM模块并将它通过AHB接口连接到RISC-V内核上,编程输出不同的PWM波形。
PWM模块设计
本教程假设需要设计一个8路输出的PWM波形发生器,为了突出外设的挂载方法和过程,PWM模块的功能将尽量简化设计,只输出PWM,可以设置周期、占空比、可以启动和停止PWM输出这些简单功能,至于其他高级功能例如死区控制、PWM输入,正交解码等等功能不在设计范围。有兴趣的可以在本教程基础上添加设计,希望大家将自己的设计能够在本论坛发布出来以供交流。
PWM——脉宽调制信号(Pulse Width Modulation),它利用微处理器的数字输出来实现,是对模拟电路控制的一种非常有效的技术,广泛应用于测量、通信、功率控制与变化等许多领域。实现方法很简单,使用一个计数器一直计数,然后和两个值进行比较,一个值是高电平时间h_time,一个值是周期period,在小于h_time期间,输出高电平;大于h_time期间,输出低电平,到达周期period时,计数器清零。
简单的PWM发生器的代码如下:
always @(posedge clk_div) begin
if(en == 1)begin
if(contper >= period-1) begin // Period is registered in ms.
contper = 0;
end else begin
contper = contper + 1;
end
end
end
always @(posedge clk_div) begin
if(en == 1)begin
if(contper <= duty-1)begin
state = 1;
end else begin
state = 0;
end
end
end
第一个always中计数器contper的值大于设置的周期值,则会清零,第二个always中判断周期控制里的计数值和占空比值duty比较,来控制“1”的输出时间。这样就可以得到一个占空比可调的PWM波形。
但是这样一个电路怎么接到RISC-V上呢,通过RISC-V编程来控制这个PWM模块呢?接下来需要设计PWM模块的AHB接口,设计ROSC-V通过AHB总线访问PWM模块的寄存器,通过修改寄存器的值来控制PWM的输出。
首先为了能够控制PWM的输出频率范围更广,添加一个PWM输入时钟预分频模块,通过一个32位寄存器clkdiv来控制8路PWM的时钟预分频系数。每路PWM的预分频值为4位二进制,即0-255。接下来对PWM模块的寄存器进行设计:
表一、PWM寄存器配置表
地址 | 寄存器名 | 说明 |
0x00 | en | 使能低8位分别控制8个PWM,“1”表示启动 |
0x04 | clkdiv | 输入时钟预分频,每路4位 |
0x08 | period0 | PWM0 周期值 32位 |
0x0C | period1 | PWM1 周期值 32位 |
0x10 | period2 | PWM2 周期值 32位 |
0x14 | period3 | PWM3 周期值 32位 |
0x18 | period4 | PWM4 周期值 32位 |
0x1C | period5 | PWM5 周期值 32位 |
0x20 | period6 | PWM6 周期值 32位 |
0x24 | period7 | PWM7 周期值 32位 |
0x28 | duty0 | PWM0 占空比值 32位 |
0x2C | duty1 | PWM1 占空比值 32位 |
0x30 | duty2 | PWM2 占空比值 32位 |
0x34 | duty3 | PWM3 占空比值 32位 |
0x38 | duty4 | PWM4 占空比值 32位 |
0x3c | duty5 | PWM5 占空比值 32位 |
0x40 | duty6 | PWM6 占空比值 32位 |
0x44 | duty7 | PWM7 占空比值 32位 |
PWM模块设计如下,命名为ahb_pwm.v
module ahb_pwm(
input I_ahb_clk,
input I_rst,
input [1:0] I_ahb_htrans,
input I_ahb_hwrite,
input [31:0] I_ahb_haddr, //synthesis keep
input [2:0] I_ahb_hsize,
input [2:0] I_ahb_hburst,
input [3:0] I_ahb_hprot,
input I_ahb_hmastlock,
input [31:0] I_ahb_hwdata, //synthesis keep
output reg [31:0] O_ahb_hrdata, //synthesis keep
output wire[1:0] O_ahb_hresp,
output reg O_ahb_hready,
output [7:0] pwmo
);
reg S_ahb_wr_trig;
reg S_ahb_wr_trig_1d;
reg[31:0] S_ahb_wr_addr;
reg[31:0] S_ahb_wr_data;
reg S_ahb_rd_trig;
reg S_ahb_rd_trig_1d;
reg[31:0] S_ahb_rd_addr;
reg[31:0] S_ahb_rd_data;
// Registers
reg [31:00] clkdiv;
reg [31:00] period0;
reg [31:00] period1;
reg [31:00] period2;
reg [31:00] period3;
reg [31:00] period4;
reg [31:00] period5;
reg [31:00] period6;
reg [31:00] period7;
reg [31:00] duty0;
reg [31:00] duty1;
reg [31:00] duty2;
reg [31:00] duty3;
reg [31:00] duty4;
reg [31:00] duty5;
reg [31:00] duty6;
reg [31:00] duty7;
reg [7:0] en;
always @(posedge I_ahb_clk or posedge I_rst) begin
if(I_rst)
O_ahb_hready <= 1'b1;
else
if(I_ahb_htrans == 2'b10)
O_ahb_hready <= 1'b0;
else if(S_ahb_wr_trig_1d || S_ahb_rd_trig_1d)
O_ahb_hready <= 1'b1;
else
O_ahb_hready <= O_ahb_hready;
end
always @(posedge I_ahb_clk or posedge I_rst) begin
if(I_rst)
S_ahb_wr_trig <= 1'b0;
else
if(I_ahb_htrans == 2'b10 && I_ahb_hwrite)
S_ahb_wr_trig <= 1'b1;
else
S_ahb_wr_trig <= 1'b0;
end
always @(posedge I_ahb_clk) begin
S_ahb_wr_trig_1d <= S_ahb_wr_trig;
end
always @(posedge I_ahb_clk or posedge I_rst) begin
if(I_rst)
S_ahb_wr_addr <= 'd0;
else
if(S_ahb_wr_trig)
S_ahb_wr_addr <= I_ahb_haddr;
else
S_ahb_wr_addr <= S_ahb_wr_addr;
end
always @(posedge I_ahb_clk or posedge I_rst) begin
if(I_rst)
S_ahb_wr_data <= 'd0;
else
if(S_ahb_wr_trig)
S_ahb_wr_data <= I_ahb_hwdata;
else
S_ahb_wr_data <= S_ahb_wr_data;
end
//总线写寄存器
always @(posedge I_ahb_clk or posedge I_rst) begin
if(I_rst)
begin
en <= 'd0;
clkdiv <= 'd0;
period0 <= 'd0;
period1 <= 'd0;
period2 <= 'd0;
period3 <= 'd0;
period4 <= 'd0;
period5 <= 'd0;
period6 <= 'd0;
period7 <= 'd0;
duty0 <= 'd0;
duty1 <= 'd0;
duty2 <= 'd0;
duty3 <= 'd0;
duty4 <= 'd0;
duty5 <= 'd0;
duty6 <= 'd0;
duty7 <= 'd0;
end
else
begin
if(S_ahb_wr_trig_1d)
begin
case(S_ahb_wr_addr[7:0])
8'h00: en <= S_ahb_wr_data;
8'h04: clkdiv <= S_ahb_wr_data;
8'h08: period0 <= S_ahb_wr_data;
8'h0C: period1 <= S_ahb_wr_data;
8'h10: period2 <= S_ahb_wr_data;
8'h14: period3 <= S_ahb_wr_data;
8'h18: period4 <= S_ahb_wr_data;
8'h1C: period5 <= S_ahb_wr_data;
8'h20: period6 <= S_ahb_wr_data;
8'h24: period7 <= S_ahb_wr_data;
8'h28: duty0 <= S_ahb_wr_data;
8'h2C: duty1 <= S_ahb_wr_data;
8'h30: duty2 <= S_ahb_wr_data;
8'h34: duty3 <= S_ahb_wr_data;
8'h38: duty4 <= S_ahb_wr_data;
8'h3c: duty5 <= S_ahb_wr_data;
8'h40: duty6 <= S_ahb_wr_data;
8'h44: duty7 <= S_ahb_wr_data;
endcase
end
else
begin
en <= en ;
clkdiv <= clkdiv ;
period0 <= period0 ;
period1 <= period1 ;
period2 <= period2 ;
period3 <= period3 ;
period4 <= period4 ;
period5 <= period5 ;
period6 <= period6 ;
period7 <= period7 ;
duty0 <= duty0 ;
duty1 <= duty1 ;
duty2 <= duty2 ;
duty3 <= duty3 ;
duty4 <= duty4 ;
duty5 <= duty5 ;
duty6 <= duty6 ;
duty7 <= duty7 ;
end
end
end
always @(posedge I_ahb_clk or posedge I_rst) begin
if(I_rst)
S_ahb_rd_trig <= 1'b0;
else
if(I_ahb_htrans == 2'b10 && (!I_ahb_hwrite))
S_ahb_rd_trig <= 1'b1;
else
S_ahb_rd_trig <= 1'b0;
end
always @(posedge I_ahb_clk) begin
S_ahb_rd_trig_1d <= S_ahb_rd_trig;
end
always @(posedge I_ahb_clk or posedge I_rst) begin
if(I_rst)
S_ahb_rd_addr <= 'd0;
else
if(S_ahb_rd_trig)
S_ahb_rd_addr <= I_ahb_haddr;
else
S_ahb_rd_addr <= S_ahb_rd_addr;
end
//总线读寄存器
always @(posedge I_ahb_clk or posedge I_rst) begin
if(I_rst)
O_ahb_hrdata <= 'd0;
else
begin
if(S_ahb_rd_trig_1d)
begin
case(S_ahb_rd_addr[7:0])
8'h00: O_ahb_hrdata <=en ;
8'h04: O_ahb_hrdata <=clkdiv ;
8'h08: O_ahb_hrdata <=period0 ;
8'h0C: O_ahb_hrdata <=period1 ;
8'h10: O_ahb_hrdata <=period2 ;
8'h14: O_ahb_hrdata <=period3 ;
8'h18: O_ahb_hrdata <=period4 ;
8'h1C: O_ahb_hrdata <=period5 ;
8'h20: O_ahb_hrdata <=period6 ;
8'h24: O_ahb_hrdata <=period7 ;
8'h28: O_ahb_hrdata <=duty0 ;
8'h2C: O_ahb_hrdata <=duty1 ;
8'h30: O_ahb_hrdata <=duty2 ;
8'h34: O_ahb_hrdata <=duty3 ;
8'h38: O_ahb_hrdata <=duty4 ;
8'h3c: O_ahb_hrdata <=duty5 ;
8'h40: O_ahb_hrdata <=duty6 ;
8'h44: O_ahb_hrdata <=duty7 ;
endcase
end
else
O_ahb_hrdata <= O_ahb_hrdata;
end
end
/* Counter */
//例化8个PWM发送器
counter pwm0(.clk(I_ahb_clk), .clkdiv(clkdiv[3:0] ), .period(period0), .duty(duty0), .state(pwmo[0]), .en(en[0]));
counter pwm1(.clk(I_ahb_clk), .clkdiv(clkdiv[7:4] ), .period(period1), .duty(duty1), .state(pwmo[1]), .en(en[1]));
counter pwm2(.clk(I_ahb_clk), .clkdiv(clkdiv[11:8] ), .period(period2), .duty(duty2), .state(pwmo[2]), .en(en[2]));
counter pwm3(.clk(I_ahb_clk), .clkdiv(clkdiv[15:12]), .period(period3), .duty(duty3), .state(pwmo[3]), .en(en[3]));
counter pwm4(.clk(I_ahb_clk), .clkdiv(clkdiv[19:16]), .period(period4), .duty(duty4), .state(pwmo[4]), .en(en[4]));
counter pwm5(.clk(I_ahb_clk), .clkdiv(clkdiv[23:20]), .period(period5), .duty(duty5), .state(pwmo[5]), .en(en[5]));
counter pwm6(.clk(I_ahb_clk), .clkdiv(clkdiv[27:24]), .period(period6), .duty(duty6), .state(pwmo[6]), .en(en[6]));
counter pwm7(.clk(I_ahb_clk), .clkdiv(clkdiv[31:28]), .period(period7), .duty(duty7), .state(pwmo[7]), .en(en[7]));
endmodule
//PWM核心模块
module counter(
input clk,
input [3:0] clkdiv,
input en,
input [31:0] period,
input [31:0] duty,
output reg state
);
// Registers
wire clk_div ;
reg clk_div1 ;
reg clk_div2 ;
reg [4:0] contclk = 0;
reg [32:0] contper = 0;
//分频器
always @(posedge clk) begin
if(en == 1)begin
if (contclk == clkdiv) begin //clkdiv+1 为分频系数
contclk = 0;
clk_div1 = ~clk_div1;
end else begin
contclk = contclk+1;
end
end
end
always @(negedge clk) begin
if(en == 1) begin
if (contclk == (clkdiv)/2) begin
clk_div2 = ~clk_div2;
end
end
end
assign clk_div = clk_div1^clk_div2;
// 周期设置
always @(posedge clk_div) begin
if(en == 1)begin
if(contper >= period-1) begin // Period is registered in ms.
contper = 0;
end else begin
contper = contper + 1;
end
end
end
// 占空比设置
always @(posedge clk_div) begin
if(en == 1)begin
if(contper <= duty-1)begin
state = 1;
end else begin
state = 0;
end
end
end
endmodule
构建MCU
参照第一讲的方法构建MCU,其中注意一定要勾选AHB Enable,gpio部分根据需要勾选,如果勾选后面添加第一讲中的gpio_controler即可。
创建时钟
参照第一讲的方法构建PLL,为MCU和ahb_pwm模块提供时钟。
构建SoC
将创建的MCU、ahb_pwm、PLL模块连接起来构建一个有8路输出的SoC。创建SF1_SoC.v文件,代码如下:
module SF1_SOC(
input wire I_clk_25m,
input wire I_rst_n,
input wire I_jtag_tck,
output wire O_jtag_tdo,
input wire I_jtag_tms,
input wire I_jtag_tdi,
input wire I_uart_rx,
output wire O_uart_tx,
output wire O_led0,
output wire O_led1,
output wire O_led2,
output wire [7:0] O_pwm
);
wire S_sys_clk_100m;
wire S_rst;
assign S_rst = ~I_rst_n;
pll u_pll(
.refclk ( I_clk_25m ),
.reset ( S_rst ),
.extlock ( ),
.clk0_out ( S_sys_clk_100m )
);
wire S_gpio0_out;
wire S_gpio0_dir;
wire S_gpio0_in ;
wire S_gpio1_out;
wire S_gpio1_dir;
wire S_gpio1_in ;
wire S_gpio2_out;
wire S_gpio2_dir;
wire S_gpio2_in ;
//AHB接口连线
wire[1:0] S_ahb_htrans_master;
wire S_ahb_hwrite_master;
wire[31:0] S_ahb_haddr_master;
wire[2:0] S_ahb_hsize_master;
wire[2:0] S_ahb_hburst_master;
wire[3:0] S_ahb_hprot_master;
wire S_ahb_hmastlock_master;
wire[31:0] S_ahb_hwdata_master;
wire[31:0] S_ahb_hrdata_master;
wire[1:0] S_ahb_hresp_master;
wire S_ahb_hready_master;
gpio_controler u0_gpio_controler(
.O_gpio_in ( S_gpio0_in ),
.I_gpio_dir ( S_gpio0_dir ),
.I_gpio_out ( S_gpio0_out ),
.IO_gpio ( O_led0 )
);
gpio_controler u1_gpio_controler(
.O_gpio_in ( S_gpio1_in ),
.I_gpio_dir ( S_gpio1_dir ),
.I_gpio_out ( S_gpio1_out ),
.IO_gpio ( O_led1 )
);
gpio_controler u2_gpio_controler(
.O_gpio_in ( S_gpio2_in ),
.I_gpio_dir ( S_gpio2_dir ),
.I_gpio_out ( S_gpio2_out ),
.IO_gpio ( O_led2 )
);
SF1_MCU u_SF1_MCU(
.core_clk ( S_sys_clk_100m ),
.timer_clk ( I_clk_25m ),
.core_reset ( S_rst ),
.jtag_tck ( I_jtag_tck ),
.jtag_tdo ( O_jtag_tdo ),
.jtag_tms ( I_jtag_tms ),
.jtag_tdi ( I_jtag_tdi ),
.soft_ip_apbm_en ( 1'b0 ),
.qspi0cfg1_mode ( 1'b1 ),
.qspi0cfg2_mode ( 1'b1 ),
.uart_tx ( O_uart_tx ),
.uart_rx ( I_uart_rx ),
.gpio0_out ( S_gpio0_out ),
.gpio0_dir ( S_gpio0_dir ),
.gpio0_in ( S_gpio0_in ),
.gpio1_out ( S_gpio1_out ),
.gpio1_dir ( S_gpio1_dir ),
.gpio1_in ( S_gpio1_in ),
.gpio2_out ( S_gpio2_out ),
.gpio2_dir ( S_gpio2_dir ),
.gpio2_in ( S_gpio2_in ),
.htrans (S_ahb_htrans_master ),
.hwrite (S_ahb_hwrite_master ),
.haddr (S_ahb_haddr_master ),
.hsize (S_ahb_hsize_master ),
.hburst (S_ahb_hburst_master ),
.hprot (S_ahb_hprot_master ),
.hmastlock (S_ahb_hmastlock_master ),
.hwdata (S_ahb_hwdata_master ),
.hclk (S_sys_clk_100m ), //AHB时钟选择系统时钟100MHz
.hrdata (S_ahb_hrdata_master ),
.hresp (S_ahb_hresp_master ),
.hready (S_ahb_hready_master ),
.nmi ( ),
.clic_irq ( ),
.sysrstreq ( ),
.apb_clk_down ( ),
.apb_paddr_down ( ),
.apb_penable_down ( ),
.apb_pprot_down ( ),
.apb_prdata_down ( ),
.apb_pready_down ( ),
.apb_pslverr_down ( ),
.apb_pstrobe_down ( ),
.apb_pwdata_down ( ),
.apb_pwrite_down ( ),
.apb_psel0_down ( ),
.apb_psel1_down ( ),
.apb_psel2_down ( )
);
ahb_pwm u_ahb_pwm(
.I_ahb_clk ( S_sys_clk_100m ), //pwm的时钟选择100MHz
.I_rst ( S_rst ),
.I_ahb_htrans ( S_ahb_htrans_master ),
.I_ahb_hwrite ( S_ahb_hwrite_master ),
.I_ahb_haddr ( S_ahb_haddr_master ),
.I_ahb_hsize ( S_ahb_hsize_master ),
.I_ahb_hburst ( S_ahb_hburst_master ),
.I_ahb_hprot ( S_ahb_hprot_master ),
.I_ahb_hmastlock( S_ahb_hmastlock_master ),
.I_ahb_hwdata ( S_ahb_hwdata_master ),
.O_ahb_hrdata ( S_ahb_hrdata_master ),
.O_ahb_hresp ( S_ahb_hresp_master ),
.O_ahb_hready ( S_ahb_hready_master ),
.pwmo ( O_pwm )
);
endmodule
创建管脚配置
新建文件,为SoC配置管脚。
set_pin_assignment { I_clk_25m } { LOCATION = D7; }
set_pin_assignment { I_jtag_tck } { LOCATION = C7; }
set_pin_assignment { I_jtag_tdi } { LOCATION = D5; }
set_pin_assignment { I_jtag_tms } { LOCATION = D6; }
set_pin_assignment { I_rst_n } { LOCATION = J2; }
set_pin_assignment { I_uart_rx } { LOCATION = E4; }
set_pin_assignment { O_jtag_tdo } { LOCATION = C6; }
set_pin_assignment { O_led0 } { LOCATION = J5; }
set_pin_assignment { O_led1 } { LOCATION = H5; }
set_pin_assignment { O_led2 } { LOCATION = G4; }
set_pin_assignment { O_pwm[0] } { LOCATION = C10; }
set_pin_assignment { O_pwm[1] } { LOCATION = C11; }
set_pin_assignment { O_pwm[2] } { LOCATION = E11; }
set_pin_assignment { O_pwm[3] } { LOCATION = F11; }
set_pin_assignment { O_pwm[4] } { LOCATION = A10; }
set_pin_assignment { O_pwm[5] } { LOCATION = A11; }
set_pin_assignment { O_pwm[6] } { LOCATION = B10; }
set_pin_assignment { O_pwm[7] } { LOCATION = B11; }
set_pin_assignment { O_uart_tx } { LOCATION = A4; }
RISC-V代码编写
硬件构成完成后,生成bit文件后就是需要利用FutureDynasty软件为硬件编写驱动代码,编程的核心思想是通过AHB总线访问PWM模块的寄存器,通过配置表一中的偏移地址的寄存器来控制PWM的输出周期和占空比。在安路的TN817_SF1 RISC-V 用户指南.pdf文件中可以查询到AHB的基地址为0x40000000。所以例如想要访问PWM0的周期寄存器就是访问地址0x40000080即可。在C语言中对该地址地址读取时即对 ((volatile unsigned int)(0x40000080)操作即可。编写pwm驱动文件,在驱动中编写了简单的函数如下:
void pwm_cmd(uint8_t ch , uint8_t status); //启动PWM,ch为通道号,status 可为ENABLE启动或DISABLE停止
void set_pwm_clkdiv(uint8_t ch , uint8_t div); //设置预分频值
void set_pwm_period(uint8_t ch , uint32_t period); //设置周期值
void set_pwm_duty(uint8_t ch , uint32_t duty); //设置占空比值
//以下两个函数体没有编写,留给读者尝试添加
uint32_t get_pwm_period(uint8_t ch); //读取周期值
uint32_t get_pwm_duty(uint8_t ch); //读取占空比值
pwm.h
#include <stdio.h>
#include "nuclei.h"
#ifndef APPLICATION_DEVICE_PWM_H_
#define APPLICATION_DEVICE_PWM_H_
typedef struct {
uint16_t PWM_CH;
uint32_t PWM_Period;
uint32_t PWM_Duty;
}PWM_InitTypeDef;
#define ENABLE 1
#define DISABLE 0
#define REGP(x) ((volatile unsigned int*)(x))
#define REG(x) (*((volatile unsigned int*)(x)))
#define REGP_8(x) (((volatile uint8_t*)(x)))
#define AHB_REGISTER_BASE_ADDR 0x40000000
#define PWM_EN AHB_REGISTER_BASE_ADDR + 0x0
#define PWM_DIV AHB_REGISTER_BASE_ADDR + 0x4
#define PWM_PERIOD0 AHB_REGISTER_BASE_ADDR + 0x8
#define PWM_PERIOD1 AHB_REGISTER_BASE_ADDR + 0xC
#define PWM_PERIOD2 AHB_REGISTER_BASE_ADDR + 0x10
#define PWM_PERIOD3 AHB_REGISTER_BASE_ADDR + 0x14
#define PWM_PERIOD4 AHB_REGISTER_BASE_ADDR + 0x18
#define PWM_PERIOD5 AHB_REGISTER_BASE_ADDR + 0x1C
#define PWM_PERIOD6 AHB_REGISTER_BASE_ADDR + 0x20
#define PWM_PERIOD7 AHB_REGISTER_BASE_ADDR + 0x24
#define PWM_DUTY0 AHB_REGISTER_BASE_ADDR + 0x28
#define PWM_DUTY1 AHB_REGISTER_BASE_ADDR + 0x2C
#define PWM_DUTY2 AHB_REGISTER_BASE_ADDR + 0x30
#define PWM_DUTY3 AHB_REGISTER_BASE_ADDR + 0x34
#define PWM_DUTY4 AHB_REGISTER_BASE_ADDR + 0x38
#define PWM_DUTY5 AHB_REGISTER_BASE_ADDR + 0x3C
#define PWM_DUTY6 AHB_REGISTER_BASE_ADDR + 0x40
#define PWM_DUTY7 AHB_REGISTER_BASE_ADDR + 0x44
#define pwm_en REG(PWM_EN)
#define pwm_div REG(PWM_DIV)
#define pwm_period0 REG(PWM_PERIOD0)
#define pwm_period1 REG(PWM_PERIOD1)
#define pwm_period2 REG(PWM_PERIOD2)
#define pwm_period3 REG(PWM_PERIOD3)
#define pwm_period4 REG(PWM_PERIOD4)
#define pwm_period5 REG(PWM_PERIOD5)
#define pwm_period6 REG(PWM_PERIOD6)
#define pwm_period7 REG(PWM_PERIOD7)
#define pwm_duty0 REG(PWM_DUTY0)
#define pwm_duty1 REG(PWM_DUTY1)
#define pwm_duty2 REG(PWM_DUTY2)
#define pwm_duty3 REG(PWM_DUTY3)
#define pwm_duty4 REG(PWM_DUTY4)
#define pwm_duty5 REG(PWM_DUTY5)
#define pwm_duty6 REG(PWM_DUTY6)
#define pwm_duty7 REG(PWM_DUTY7)
void pwm_cmd(uint8_t ch , uint8_t status);
void set_pwm_clkdiv(uint8_t ch , uint8_t div);
void set_pwm_period(uint8_t ch , uint32_t period);
void set_pwm_duty(uint8_t ch , uint32_t duty);
uint32_t get_pwm_period(uint8_t ch);
uint32_t get_pwm_duty(uint8_t ch);
#endif /* APPLICATION_DEVICE_PWM_H_ */
pwm.c
/*
* pwm.c
*
* Created on: 2022年9月19日
* Author: zhang
*/
#include <stdio.h>
#include "pwm.h"
void pwm_cmd(uint8_t ch , uint8_t status){
if (status) {
pwm_en |= 1<<ch;
}
else
{
pwm_en &= ~(1<<ch);
}
}
void set_pwm_clkdiv(uint8_t ch , uint8_t div){
pwm_div |= div<<(ch*4);
}
void set_pwm_period(uint8_t ch , uint32_t period){
switch (ch) {
case 0:
pwm_period0 = period;
break;
case 1:
pwm_period1 = period;
break;
case 2:
pwm_period2 = period;
break;
case 3:
pwm_period3 = period;
break;
case 4:
pwm_period4 = period;
break;
case 5:
pwm_period5 = period;
break;
case 6:
pwm_period6 = period;
break;
case 7:
pwm_period7 = period;
break;
default:
break;
}
}
void set_pwm_duty(uint8_t ch , uint32_t duty){
switch (ch) {
case 0:
pwm_duty0 = duty;
break;
case 1:
pwm_duty1 = duty;
break;
case 2:
pwm_duty2 = duty;
break;
case 3:
pwm_duty3 = duty;
break;
case 4:
pwm_duty4 = duty;
break;
case 5:
pwm_duty5 = duty;
break;
case 6:
pwm_duty6 = duty;
break;
case 7:
pwm_duty7 = duty;
break;
default:
break;
}
}
uint32_t get_pwm_period(uint8_t ch){
}
uint32_t get_pwm_duty(uint8_t ch){
}
通过主函数来来配置pwm输出。例如希望输出10KHz,占空比50%的pwm0波形。只需要在main函数中进行如下配置:
set_pwm_clkdiv(0, 1); // 设置预分频为2分频 100MHz输入,则pwm的时钟为50MHz
set_pwm_period(0, 5000); //设置周期为0.1ms 50MHz/5000= 10KHz
set_pwm_duty(0, 2500); // 设置占空比为2500/5000*100%=50%
pwm_cmd(0, ENABLE);
从代码中不难看出PWM波形的输出周期取决于预分频值和period的值。PWM频率 = (PWM主频/(div+1))/period
占空比 = (duty/period)*100%
通过示波器或者逻辑分析仪可以观察不同配置下的pwm输出效果。例如如下的main.c对应的波形图。
main.c
#include <stdio.h>
#include "pwm.h"
#include "../../../../SoC/anlogic/Board/sf1_eval/Include/nuclei_sdk_hal.h"
#define LEDn 3
static uint32_t LED_CLORK[] = {SOC_LED_RED_GPIO_MASK, SOC_LED_GREEN_GPIO_MASK,SOC_LED_BLUE_GPIO_MASK};
void led_config(void)
{
for(int i=0; i<LEDn; i++)
{
//gpio_iof_config(GPIO,LED_CLORK[i],IOF_SEL_DEF);
gpio_enable_output(GPIO,LED_CLORK[i]);
gpio_write(GPIO,LED_CLORK[i],1);
}
}
int main(void)
{
led_config();
int a,b,c;
c=a+b;
set_pwm_clkdiv(0, 2);
set_pwm_period(0, 33);
set_pwm_duty(0, 17);
pwm_cmd(0, ENABLE);
set_pwm_clkdiv(1, 0);
set_pwm_period(1, 10000);
set_pwm_duty(1, 5000);
pwm_cmd(1, ENABLE);
set_pwm_clkdiv(2, 1);
set_pwm_period(2, 500000);
set_pwm_duty(2, 250000);
pwm_cmd(2, ENABLE);
set_pwm_clkdiv(3, 9);
set_pwm_period(3, 20000);
set_pwm_duty(3, 2000);
pwm_cmd(3, ENABLE);
while(1)
{
for(int i=0; i<LEDn; i++)
{
gpio_toggle(GPIO,LED_CLORK[i]);
delay_1ms(1000);
anl_printf("gpio_toggle\r\n");
}
}
}
配置了4路PWM输出。
留下的问题
由于SF1的AHB总线是1主接在RISC-V内核上,1从就是接在本教程PWM模块上的,那么如果需要接多个不同外设怎么办?
那么就需要通过Bridge将AHB接在APB上,APB总线上按自己需求设计多个接口连接多个外设,当然Bridge和APB都需要自行设计,没有IP哦。下一讲我们就试试通过这样的结构挂载多个外设。