这一次,我将选择一个PicoBlaze的参考设计来详细讲解一下,并会在其上稍作修改,以实现我所想要的效果。
从如下地址下载PicoBlaze的Reference Design:
http://www.xilinx.com/products/boards/s3estarter/files/s3esk_picoblaze_pwm_control.zip
将此文件夹解包,得到如下图所示文件:
图 1 PicoBlaze PWM参考设计文件
首先阅读一下PicoBlaze_PWM_control_rev1.pdf这份文档,这份文档里已经将该PWM的参考设计讲解的很详细了,稍后将对它作一番讲解。为了建立对该参考设计的感性认识,就先按照文档里的步骤,重现一下该设计:
- 给Spartan3E Starter Kit开发板接通电源,连接USB Cable到Host PC上;
- 建立一个波特率为9600,数据位为8bit,无奇偶校验,无控制流的超级终端连接;
- 双击install_picoblaze_pwm_control.bat这个批处理文件,它将自动执行:初始化Scan-chain,下载bitstream文件等操作。
当弹出的DOS对话框一闪而过后,表示该批处理操作已经完成并自动退出了。同时在超级终端的界面上应该可以看到如下图所示的提示符号:
这时就可以看到位于FPGA开发板上右边的8个绿色LED灯以从左到右的顺序亮度依次变暗。这说明每个给LED供电的电压等级依次“变弱”。这种不同程度的亮度便是由PicoBlaze输出的PWM信号来实现的。
我们知道,从XC3S500E输出的是数字信号,只有0或者1两种状态;而这种代表不同亮度的“模拟信号”是从何而来的呢?采用PWM(Pulse
Width
Modulation,脉冲宽度调制)的机制就能够实现。PWM的原理简单说来就是将具有一定频率的数字信号按照固定的脉冲宽度分成若干份,那么通过输出不同占空比(也就是一个周期T内该信号为有效1的时间占整个周期的比值)的数字信号来表示不同的“模拟信号”。比如一个频率为1KHz(即1ms为一个周期)的脉冲,电压等级为3.3V。若该信号的周期分成32个脉冲的宽度(一个脉冲的宽度就是15.625us),那么占空比为46.875%(即15/32)的该信号代表的电压水平就是46.875%×3.3V
≈ 1.55V。
说到这里,大家应该明白了该参考设计中产生这些不同亮度的信号的原理了。下面就让我对这个例子讲解一下(其实Ken
Chapman在参考设计中给出的文档以及在psm文件中添加的注释已经十分详细了——这就是Xilinx的Coding
Style,在这里我仅对关键的几个地方作一下解释):
这个Reference
Design实现了对12个通道的PWM信号的控制,其中每个通道的信号频率为1KHz,精度为256阶(即输出的占空比以1/256为步进单位)。
在程序的开头,定义了8个LED端口以及4个扩展输出口,同时还有一些描述UART中收发器状态的寄存器。
接着,该程序便定义了一大堆关于通过UART终端显示命令以及提示符的状态函数,包括输出字符串“PicoBlaze PWM
Control”,“KCPSM3>”,“Error”,响应“LD”和“IO”命令的状态函数。
下图便是与pwm_ctrl.psm相等效的状态转换图(为简化略去终端串口收发字符的状态转换细节):
ISR中的内部计数器值会在每次中断例程中自动加1,并且是从0~255循环计数;
当前中断中内部的计数器的值会与每个通道的PWM的值进行比较,若前者小于后者,则该通道的输出值为1;反之则为0(这里是通过比较结果产生的Carry位是否被置1来实现的)。并且,4个扩展口的电平高低将先并行输出,然后8个LED端口的电平高低再在当前ISR结束前并行输出(每次比较都会有一个Carry位被串行左移进入寄存器s2)。
关于“3.92us”
由于产生的信号频率为1KHz,PWM精度为8bit——1/256,鉴于前述PWM信号的产生机理——即在1ms的时间里要进行256次比较输出,因此所需的终端信号的产生频率就为1ms/256
= 3.90625ms ≈ 3.91us
。与3.92us仍相差0.01us,对不对?这里,每3.92us产生一个中断信号是因为:这样更方便计算指令数。对于PicoBlaze的指令集,指令的执行时间是固定的:两个时钟周期(原因在《PicoBlaze介绍(二)已经提到过)。而参考设计所使用的Spartan3E
Starter Kits开发板上的时钟源是50MHz的,因此每条指令的执行时间就是40ns。3.91us/40ns =
97.75,如果是3.92us/40ns =
98。基于这个原因,相信大家就不会对PicoBlaze_PWM_control_rev1.pdf中3.92us的来由产生疑惑了。事实上,也正因为如此,在示波器上看到的PWM信号也并不严格为1KHz,而是约为997Hz。
关于“196”
由于每3.92us就会产生一个中断信号,而开发板上的时钟源为50MHz,因此需要设计一个状态数为3.92us/20ns =
196的计数器,当它从0开始计数,每当计数到195时便产生一个中断信号,来激活PicoBlaze的ISR。
关于“326”
在PicoBlaze_PWM_control_rev1.pdf文档中第11页还有一个状态数为326的计数器,这个326是如何得到的呢?注意到这个计数器是和UART模块连接起来的,UART的设置为波特率为9600,8位数据位,1位停止位,无奇偶校验和硬件控制流。而在该PicoBlaze的UART模块对clock的要求是时钟必须为波特率的16倍(我猜测是由于UART模块中用到的16Byte的FIFO使然)。这样一来UART模块的时钟频率就为9600×16
= 153600Hz。如何得到呢?采用状态数为326的计数器,每计数到325时便产生一个高电平信号,这样就能够得到50MHz/326
≈ 153374近似于153600频率的时钟信号了。
关于“the Limits”
在PicoBlaze_PWM_control_rev1.pdf文档的第12页提到了该参考设计的局限性“the Limits of
Software”。当时我对其中关于该设计中的PicoBlaze还剩余约50%的计算能力来处理其他上层任务这一点不理解。(原文是这样的:However,
97 instructions are adequate to drive the PWM signals for all 12
channels and still have ~50% of the processor bandwidth available
for the higher level control
tasks)经过我在Xilinx的PicoBlaze官方论坛提问求助以及Ken
Chapman的亲自回答的情况下,我终于明白了这样写的原因:在该PWM Reference
Design中是每3.92us产生一个中断,在此期间,PicoBlaze能够执行的指令数为98条(3.92us/40ns =
98)。中断例程ISR代码段的指令数为48条。这样一来,还剩下大约一半的指令空间。而这些指令空间即是被用来执行输出命令行“KCPSM3>”,“PicoBlaze
PWM Control”,UART输入以及ASCII码到Hex码转换等任务(并且执行一次执行单个这样的任务,50条指令绰绰有余!)
另外,该设计中能够实现12个通道的PWM控制信号输出,性能是相当不错。但要注意的是这是一种折中,因为每个信号的频率不是很高,只有1KHz。如果提高PWM信号的频率,可控的通道数和PicoBlaze处理能力的带宽将大大降低,原因请参考上面解释的几条。好在一个不够就多用几个,众人拾柴火焰高!(前面不是提到了一个用到1000多个PicoBlaze的案例了吗?)
PWM的参考设计介绍完了。现在我将在此设计的基础上进行一下简单的修改来实现一个输出8KHz,精度为64阶的PWM信号。以备后用
按照前面介绍的计算方法,在该PWM参考设计的基础上,要产生8KHz,64阶精度的PWM信号,每个中断的产生时间为1/(8K×64)≈
1.96us 。这样的话中断计数器的状态数就为1.96us/20ns = 98
。而由于UART的波特率不变,因此另外一个产生UART时钟的计数器的状态数也不变。
上图就是read_duty_value函数段的其中一部分。在接收占空比的第一个字符时,对其判断是否大于或者等于4就会跳转到Error状态。
而在ISR段中,也需要进行修改,因为原来的ISR中的PWM_duty_counter是从0~255(即00~FF)来循环增一计数的,刚好是8位的长度。而现在只需要计数到63(3F)然后就回复到0状态重新计数,怎么实现?用一个值为3F(0011
1111)的Mask来与当前计数的PWM_duty_counter相与来实现从0~63的循环计数,这样PWM_duty_counter依然是从0~255来循环增一计数,但却将它分为了4个0~63的循环计数,修改的程度最小,又实现了各通道的PWM_channel与duty_counter值比较输出高低电平来构成PWM信号的目的。
至此,PicoBlaz介绍的系列告一段落,在这里非常感谢Ken Chapman在Xilinx的PicoBlaze论坛的详细解答。各位有什么问题也非常欢迎在这里提出!
稍后将放上全部修改后的PWM控制的psm文件。