注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

火车的家

Put first thing first

 
 
 

日志

 
 

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘)  

2012-05-29 18:52:12|  分类: 技术博客 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

注:很好的文章,对于怎么配置寄存器接口说得很清楚,可惜找不到出处了。
link

NIOS II是一个建立在FPGA上的嵌入式软核处理器,除了可以根据需要任意添加已经提供的外设外,用户还可以通过定制用户逻辑外设和定制用户指令来实现各种应用要求。这节我们就来研究如何定制基于Avalon总线的用户外设。

SOPC Builder提供了一个元件编辑器,通过这个元件编辑器我们就可以将我们自己写的逻辑封装成一个SOPC Builder元件了。下面,我们就以PWM实验为例,详细介绍一下定制基于Avalon总线的用户外设的过程。

我们要将的PWM是基于Avalon总线中的Avalon Memory Mapped Interface (Avalon-MM),而Avalon总线还有其他类型的设备,比如Avalon Streaming Interface (Avalon-ST)、Avalon Memory Mapped Tristate Interface等等,在这里我就不详细叙述了,需要进一步了解的请参考ALTERA公司的《Avalon Interface Specifications》(mnl_avalon_spec.pdf)。

Avalon-MM接口是内存映射系统下的用于主从设备之间的读写的接口,下图就是一个基于Avalon-MM的主从设备系统。而我们这节需要做的就是下图高亮部分。他的地位与UART,RAM Controller等模块并驾齐驱的。

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
Avalon-MM接口有很多特点,其中最大的特点就是根据自己的需求自由选择信号线,不过里面还是有一些要求的。建议大家在看本文之前,先看一遍《Avalon Interface Specifications》,这样就能对Avalon-MM接口有一个整体的了解。

下图为Avalon-MM外设的一个结构图,

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
理论的就说这些,下面我们举例来具体说明,让大家可以更好的理解。

构建HDL

我们这一节是PWM为例,所以首先,我们要构建一个符合Avalon-MM Slave接口规范的可以实现PWM功能的时序逻辑,在这里,我们利用Verilog语言来编写。在程序中会涉及到Avalon信号,在这里,我们说明一 下这些信号(其中,方向以从设备为基准)

HDL中的信号 

Avalon信号类型 

宽度 

方向 

描述 

clk

clk

1

input

同步时钟信号

reset_n

reset_n

1

input

复位信号,低电平有效

chipselect

chipselect

1

input

片选信号

address

address

2

input

2位地址,译码后确定寄存器offset

write

write

1

input

写使能信号

writedata

writedata

32

input

32位写数据值

read

read

1

input

读时能信号

byteenable

byteenable

1

input

字节使能信号

readdata

readdata

32

output

32位读数据值

此外,程序中还包括一个PWM_out信号,这个信号是PWM输出,不属于Avalon接口信号。

PWM内部还包括使能控制寄存器、周期设定寄存器以及占空比设置寄存器。设计中将各寄存器映射成Avalon Slave端口地址空间内一个单独的偏移地址。没个寄存器都可以进行读写访问,软件可以读回寄存器中的当前值。寄存器及偏移地址如下:

寄存器名 

偏移量 

访问属性 

描述 

clock_divide_reg

00

读/写

设定PWM输出周期的时钟数

duty_cycle_reg

01

读/写

设定一个周期内PWM输出低电平的始终个数

control_reg

10

读/写

使能和关闭PWM输出,为1时使能PWM输出


代码如下:

001     module PWM(
002         clk,
003         reset_n,
004         chipselect,
005         address,
006         write,
007         writedata,
008         read,
009         byteenable,
010         readdata,
011         PWM_out);
012      
013     input clk;
014     input reset_n;
015     input chipselect;
016     input [1:0]address;
017     input write;
018     input [31:0] writedata;
019     input read;
020     input [3:0] byteenable;
021     output [31:0] readdata;
022     output PWM_out;
023      
024     reg [31:0] clock_divide_reg;
025     reg [31:0] duty_cycle_reg;
026     reg control_reg;
027     reg clock_divide_reg_selected;
028     reg duty_cycle_reg_selected;
029     reg control_reg_selected;
030     reg [31:0] PWM_counter;
031     reg [31:0] readdata;
032     reg PWM_out;
033     wire pwm_enable;
034      
035     //地址译码
036     always @ (address)
037     begin
038         clock_divide_reg_selected<=0;
039         duty_cycle_reg_selected<=0;
040         control_reg_selected<=0;
041         case(address)
042             2'b00:clock_divide_reg_selected<=1;
043             2'b01:duty_cycle_reg_selected<=1;
044             2'b10:control_reg_selected<=1;
045             default:
046             begin
047                 clock_divide_reg_selected<=0;
048                 duty_cycle_reg_selected<=0;
049                 control_reg_selected<=0;
050             end
051         endcase
052     end          
053      
054     //写PWM输出周期的时钟数寄存器
055     always @ (posedge clk or negedge reset_n)
056     begin
057         if(reset_n==1'b0)
058             clock_divide_reg=0;
059         else
060         begin
061             if(write & chipselect & clock_divide_reg_selected)
062             begin
063                 if(byteenable[0])
064                     clock_divide_reg[7:0]=writedata[7:0];
065                 if(byteenable[1])
066                     clock_divide_reg[15:8]=writedata[15:8];
067                 if(byteenable[2])
068                     clock_divide_reg[23:16]=writedata[23:16];
069                 if(byteenable[3])
070                     clock_divide_reg[31:24]=writedata[31:24];
071             end
072         end
073     end
074      
075     //写PWM周期占空比寄存器
076     always @ (posedge clk or negedge reset_n)
077     begin
078         if(reset_n==1'b0)
079             duty_cycle_reg=0;
080         else
081         begin
082             if(write & chipselect & duty_cycle_reg_selected)
083             begin
084                 if(byteenable[0])
085                     duty_cycle_reg[7:0]=writedata[7:0];
086                 if(byteenable[1])
087                     duty_cycle_reg[15:8]=writedata[15:8];
088                 if(byteenable[2])
089                     duty_cycle_reg[23:16]=writedata[23:16];
090                 if(byteenable[3])
091                     duty_cycle_reg[31:24]=writedata[31:24];
092             end
093         end
094     end
095      
096     //写控制寄存器
097     always @ (posedge clk or negedge reset_n)
098     begin
099         if(reset_n==1'b0)
100             control_reg=0;
101         else
102         begin
103             if(write & chipselect & control_reg_selected)
104             begin
105                 if(byteenable[0])
106                     control_reg=writedata[0];
107             end
108         end
109     end
110      
111     //读寄存器
112     always @ (address or read or clock_divide_reg or duty_cycle_reg or control_reg or chipselect)
113     begin
114         if(read & chipselect)
115             case(address)
116                 2'b00:readdata<=clock_divide_reg;
117                 2'b01:readdata<=duty_cycle_reg;
118                 2'b10:readdata<=control_reg;
119                 default:readdata=32'h8888;
120             endcase
121     end
122      
123     //控制寄存器
124     assign pwm_enable=control_reg;
125      
126     //PWM功能部分
127     always @ (posedge clk or negedge reset_n)
128     begin
129         if(reset_n==1'b0)
130             PWM_counter=0;
131         else
132         begin
133             if(pwm_enable)
134             begin
135                 if(PWM_counter>=clock_divide_reg)
136                     PWM_counter<=0;
137                 else
138                     PWM_counter<=PWM_counter+1;
139             end
140             else
141                 PWM_counter<=0;
142         end
143     end     
144      
145     always @ (posedge clk or negedge reset_n)
146     begin
147         if(reset_n==1'b0)
148             PWM_out<=1'b0;
149         else
150         begin
151             if(pwm_enable)
152             begin
153                 if(PWM_counter<=duty_cycle_reg)
154                     PWM_out<=1'b1;
155                 else
156                     PWM_out<=1'b0;
157             end
158             else
159                 PWM_out<=1'b0;
160         end
161     end
162      
163     endmodule

硬件设置

接下来,我们就通过SOPC Builder,来建立PWM模块了。首先,打开Quartus软件,进入SOPC Builder。进入后,点击下图红圈处


2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
点击后,如下图所示,点击Next,

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
点击后,如下图所示,点击下图红圈处,将我们刚才建立的PWM.v加进来。(我将PWM。v放到了工程目录下的pwm文件夹下)

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
加入后,系统会对PWM.v文件进行分析,如下图所示,出现红圈处的文字,说明分析成功,点击close,关闭对话框。
2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
然后点击Next,如下图所示,通过下图,我们可以看到,PWM.v中的信号都出现在这里面了。我们可以根据我们的功能要求来配置这些信号,其 中,Interface是Avalon接口类型 ,它包括Avalon-MM、Avalon-ST、Avalon Memory Mapped Tristate Interface等等。Signal Type指的是各个Avalon接口类型下的信号类型。PWM.v中的信号我们已经在前面都介绍过了,大家按照上面的要求设置就可以了。默认情况只有 PWM_out需要改动,如下图示红圈处设置,

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
其中,Interface在下拉菜单中选择下图红圈处所示的选项。

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
上面的选项都设置好以后,点击Next,如下图所示,我们通过下图红圈处的下拉条向下拉

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
拉到下图所示位置停止,我们将红圈处的改选为NATIVE,这个地方就是地址对齐的选项,我们选择为静态地址对齐。其他的地方都默认,不需要改动。

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
这里面还有很多选项,其中Timing部分需要说明一下,PWM的Avalon Slave端口与Avalon Slave端口时钟信号同步,读/写时的建立很保持时间为0,因为读、写寄存器仅需要一个时钟周期,所以读/写时为0等待切不需要读延时。

接着点击Next,如下图所示,其中红圈处需要注意,这个地方需要可以建立新组,然后在SOPC Builder中体现出来。


2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
点击Finish后,会出现下面的对话框,点击Yes,就会生成一个PWM_hw.tcl脚本文件,大家可以打开看一下,里面放置的是刚才我们配置PWM时候的配置信息。

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
上面都完成以后,我们回到了SOPC Builder界面,我们在左侧边栏中可以找到下图所示的红圈处

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
大家看到了吧,MyIP就是我们刚才建立的group。双击PWM,我们建立PWM模块,如下图

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 

点击Finish,完成建立。

这里还需要设置一步,点击下图红圈处

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
点击后,如下图所示,点击IP Serarch Path,然后点击Add,添加PWM.v所在位置的路径

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
添加后,如下图所示

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 
点击Finish完成。设置这个选项是为了让SOPC Builder可以找到PWM.v的位置。不然就会出现下次你进入SOPC Builder的时候PWM模块无效的问题。

接下来的工作就是自动分配地址,分配中断,编译,等待......

编译好以后,我们回到Quartus软件界面,我们可以看到,PWM出现了,我将它接到了一个LED上了,我们可以通过PWM改变LED的亮度,实现LED渐亮渐灭的过程。

2012.05.28 [转] Avalon总线IP核的定制-----(深入了解软件编程的奥秘) - tassardge - 火车的家
 

接下来又是编译,等待.....

做好硬件部分工作以后,我们打开NIOS IDE,开始软件编程部分。

软件开发

首先对工程重新编译一次,Ctril+B,等待......

编译好以后,我们来看一下system.h的变化情况,我们可以发现,多出来PWM部分了。

下面是PWM测试代码,

01     #include <unistd.h>
02     #include "system.h"
03      
04     //根据寄存器的偏移量,我们定义一个结构体PWM
05     typedef struct{
06         volatile unsigned int divi;
07         volatile unsigned int duty;
08         volatile unsigned int enable;
09     }PWM;
10      
11     int main()
12     {
13     int dir = 1;
14      
15         //将pwm指向PWM_0_BASE首地址
16     PWM *pwm = (PWM *)PWM_0_BASE;
17     //对pwm进行初始化,divi最大值为232-1。
18         pwm->divi = 1000;
19         pwm->duty = 0;
20         pwm->enable = 1;
21       
22         //通过不断的改变duty值来改变LED一个周期亮灯的时间长短
23         while(1){
24             if(dir > 0){
25                 if(pwm->duty < pwm->divi)
26                     pwm->duty += 100;
27                 else
28                     dir = 0;
29             }
30             else{
31                 if(pwm->duty > 0)
32                     pwm->duty -= 100;
33                 else
34                     dir = 1;
35             }
36              
37             usleep(100000);
38         }
39          
40         return 0;  
41     }

  评论这张
 
阅读(597)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018