本篇文章由 VeriMake 旧版论坛中备份出的原帖的 Markdown 源码生成
原帖标题为:wujian100 的 PWM 周期问题
原帖网址为:https://verimake.com/topics/122 (旧版论坛网址,已失效)
原帖作者为:Edwin(旧版论坛 id = 48,注册于 2020-05-21 13:58:16)
原帖由作者初次发表于 2020-07-28 11:02:50,最后编辑于 2020-07-28 11:02:50(编辑时间可能不准确)
截至 2021-12-18 14:27:30 备份数据库时,原帖已获得 1460 次浏览、1 个点赞、0 条回复
1、pwm波形发生器实验结果
在wujian100的样例程序代码如下:
int32_t pwm_signal_test(uint32_t pwm_idx, uint8_t pwm_ch)
{
int32_t ret;
pwm_handle_t pwm_handle;
example_pin_pwm_init();
pwm_handle = csi_pwm_initialize(pwm_idx);
if (pwm_handle == NULL) {
printf("csi_pwm_initialize error\n");
return -1;
}
ret = csi_pwm_config(pwm_handle, pwm_ch, 3000, 1500); //设置pwm周期3ms
if (ret < 0) {
printf("csi_pwm_config error\n");
return -1;
}
csi_pwm_start(pwm_handle, pwm_ch);
mdelay(20);
ret = csi_pwm_config(pwm_handle, pwm_ch, 200, 150); //设置pwm周期200us
if (ret < 0) {
printf("csi_pwm_config error\n");
return -1;
}
mdelay(20);
csi_pwm_stop(pwm_handle, pwm_ch);
csi_pwm_uninitialize(pwm_handle);
return 0;
}
其中有两句配置函数
ret = csi_pwm_config(pwm_handle, pwm_ch, 3000, 1500); //设置pwm周期3ms,占空比1500/3000
ret = csi_pwm_config(pwm_handle, pwm_ch, 200, 150); //设置pwm周期200us占空比50/200
这两句都是用来配置pwm周期的,是实际测试中pwm周期和占空比都没有问题。但是如果设置pwm周期为5ms时,需要修改这句话为:
ret = csi_pwm_config(pwm_handle, pwm_ch, 5000, 2500);
在实际测试过程中会发现,他的周期实际是2.5ms并不是预期的5ms,如果设置为100ms会发现pwm的实际输出周期是2.5ms。实际输出是有问题的。
2、查找问题
1、软件问题:
跟踪csi_pwm_config
函数,发现在函数中会根据设置的周期进行时钟配置:
drv_pwm_config_clockdiv(handle, channel, cnt_div[count_div]);
由于pwm计数器是16位,所以软件会根据设置的周期值计算出要配置的数值,如果该数值超过0xffff,就会设置分频系数,直到需要计数的值小于0xffff为止。
继续跟踪
drv_pwm_config_clockdiv(handle, channel, cnt_div[count_div]);
我们可以看到
void drv_pwm_config_clockdiv(pwm_handle_t handle, uint8_t channel, uint32_t div)
{
PWM_NULL_PARAM_CHK_NORETVAL(handle);
wj_pwm_priv_t *pwm_priv = handle;
wj_pwm_reg_t *addr = (wj_pwm_reg_t *)(pwm_priv->base);
addr->PWMCFG &= ~(7 << 24);
switch (div) {
case 1:
addr->PWMCFG &= ~(PWM_CFG_CNTDIV_EN);
break;
case 2:
addr->PWMCFG |= PWM_CFG_CNTDIV_EN | PWM_CFG_CNTDIV_2;
break;
case 5:
addr->PWMCFG |= PWM_CFG_CNTDIV_EN | PWM_CFG_CNTDIV_5;
break;
........
这个函数会对寄存器PWMCFG的第25位到第27位进行了赋值操作,查找数据手册发现

第28位是分频使能,第26到24位是分频系数设置。
在csi库里提供了一个函数可以读取这几位的值来查看分频的设置:
/**
\brief get pwm clock division.
\param[in] handle pwm handle to operate.
\param[in] channel channel num.
\return div clock div.
*/
uint33_t drv_pwm_get_clockdiv(pwm_handle_t handle, uint8_t channel)
通过这个函数可以得到PWMCFG寄存器里的分频系数cntdiv的值。通过实验我们发现分频系数的值配置到寄存器里了,并且读回来的值也是配置的值。
总结:所以软件上对于pwm的配置是没有问题的,配置pwm周期不是预期值不是软件问题。
3、硬件问题
排除软件问题,那么出现pwm周期非预期值他的问题就只可能是pwm外设在设计时的硬件问题了。我们查找wujian100内部设计的问题,为了方便查看我们使用verdi来查看跟踪模块设计,这样效率高。
首先第一步配置wujian100工作路径:在linux系统中利用source将wujian100工作目录添加到系统环境变量。
第二步tb目录下的tb_file.list文件,这个文件里加载的顶层文件是wujian100_open_top.v,并不是我们生成bit文件时的wujian100_open_fpga_top.v文件(该文件在fpga目录下),我们首先赋值fpga目录下的wujian100_open_fpga_top.v到soc目录下。修改文件tb_file.list里的第3行,将wujian100_open_top.v替换为wujian100_open_fpga_top.v
第三步进入tb目录。使用verdi -f tb_file.list打开软件verdi并加载tb_file.list里列出的文件。(前提是你的linux系统安装了verdi软件)。
以上三步正确就会打开wujian100设计的模块图。

查看选中打开该文件,查看文件名是不是wujian100_open_fpga_top.v

然后打开原理图。点击如图按钮:

打开wujian100的设计如图:

接下来可以按文件查找相应模块,也可以双击原理图上的模块一层一层进入。
pwm模块位于PDU下的apb1上。一层一层进入查看下,定位到pwm:

继续进入pwm_sec_top,再进入pwm就是pwm外设的内部了。

在这里有两部分,一个是aphif负责总线,ctrl就是pwm的实际实现了。
进入pwm_ctrl:

我们发现有六个pwm_gen。和数据手册上介绍的一样。

我们现在定位到左下角,放大看,这部分就是pwm的时钟部分:

我们在view菜单下打开端口名称显示和模块内部端口名称显示。在图上我们可以看到cntdiv[2:0]控制了分频系数,分频系数会通过分频器(图上的f)对pclk系统时钟进行分频,然后通过gated_clk_cell控制时钟的通过然后通过clk_mux2选择器送到后续的pwm发生器上作为发生器的时钟。我们双击模块就能够查看各自对应的verilog代码。我们在逐个查找时发现gated_clk_cell 它的结构有问题
双击它,查看它内部结构:

它内部是一个很奇怪的结构,直接就是clk_in输入,直接送到clk_out输出上去了。结合上一张图我们可以看出clk_in就是pclk系统时钟,这样导致前面的分频没有任何作用,pclk将会直接送到pwm发生器上作为pwm的时钟。这样不论你有没有设置分频,pwm就只有系统时钟pclk(21M)。这样pwm的信号周期无法修改。
反过来推到下,我们之前的实验设置周期6ms是得到的结果是2.5ms,其实我们的分频系数是2,由于时钟没有分频,导致我们的输出周期是2.5ms,如果分频成功我们的周期就会是2.5*2=5ms,同理,10ms时的分频系数是4。有兴趣的可以根据sdk提供的代码推到下,也可以用
uint33_t drv_pwm_get_clockdiv(pwm_handle_t handle, uint8_t channel)
这个函数查看分频系数,然后分析下。
我们直到了问题出在模块gated_clk_cell上了,我们定位到模块对应的verilog文件,查找到代码位于pwm.v文件的4186行例化了一个叫做gated_clk_cell的模块,我们双击gated_clk_cell进入内部,双击模块定位到了common.v文件的66行,代码如下:
`ifdef FPGA
assign clk_out = clk_in;
`else
Standard_Cell_CLK_GATE x_gated_clk_cell(
.CK (clk_in),
.SE (SE),
.EN (clk_en_bf_latch),
.Q (clk_out)
);
`endif
这下一目了然了,由于定义了FPGA这个量,导致assign clk_out=clk_in;而下面的模块没有实现,所以最主要原因就是定义了FPGA这个量,这个量在哪里定义的呢,就是在wujian100_open_fpga_top.v这个文件的最开头定义的(第37行):

将这行用//注释,然后在verdi中点击file下选择reload设计,重新加载文件,我们再查看geted_clk_cell模块,得到如下图:

这样clk_in不会直接送到clk_out输出了。
修改后用vivado重新分析综合生成bit,下载到开发板上,我们发现输出的pwm的周期输出正确了。6ms,10ms的周期也能生成了。
这里有一点注意,修改后源码用vivado建立工程,用vivado去Synthesis,不要用官方方法,用Synplify_pro去Synthesis。不然生成的bit下载开发板,输出的pwm周期会小一半。例如10ms周期只能输出5ms。有兴趣的可以试下。
4、后续问题:
查看这张结构图

仔细查看,不难发现,它的六个pwm_gen都是用的一个时钟源,都是pclk通过分频之后的时钟直接连接在了每一个pwn_gen的时钟上,六个时钟都是一个源,那么就会造成一个问题,他的六组pwm发生器只能同时产生一个频率(周期)的信号,例如ch1产生了5ms,那个这时ch2也只能产生5ms,没法产生10ms周期的pwm,所有通道的周期都会被最后那个设置改成同一个频率,就是因为他们的时钟是同一个。枯😥。这将怎么解决,有一个思路,就是每一个pwm_gen有各自的分频模块。怎么解决下一篇介绍。
有问题欢迎留言指出,一起讨论。