zoukankan      html  css  js  c++  java
  • (转载)Avalon总线IP核的定制(深入了解软件编程的奥秘)

      本篇博客转载来源于http://www.cnblogs.com/kingst/,仅供学习。里面软件编程中结构体的定义是一个亮点,让我更加深入的了解软件编程的乐趣所在。

    简介

          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等模块并驾齐驱的。

    clip_image002

          Avalon-MM接口有很多特点,其中最大的特点就是根据自己的需求自由选择信号线,不过里面还是有一些要求的。建议大家在看本文之前,先看一遍《Avalon Interface Specifications》,这样就能对Avalon-MM接口有一个整体的了解。

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

    clip_image004

          理论的就说这些,下面我们举例来具体说明,让大家可以更好的理解。

    构建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

    上面的程序保存好以后,命名为PWM.v,并将其存放到工程目录下。

    硬件设置

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

    clip_image006

    点击后,如下图所示,点击Next,

    clip_image008

    点击后,如下图所示,点击下图红圈处,将我们刚才建立的PWM.v加进来。(我将PWM。v放到了工程目录下的pwm文件夹下)

    clip_image010

    加入后,系统会对PWM.v文件进行分析,如下图所示,出现红圈处的文字,说明分析成功,点击close,关闭对话框。

    clip_image012

    然后点击Next,如下图所示,通过下图,我们可以看到,PWM.v中的信号都出现在这里面了。我们可以根据我们的功能要求来配置这些信号,其中,Interface是Avalon接口类型 ,它包括Avalon-MM、Avalon-ST、Avalon Memory Mapped Tristate Interface等等。Signal Type指的是各个Avalon接口类型下的信号类型。PWM.v中的信号我们已经在前面都介绍过了,大家按照上面的要求设置就可以了。默认情况只有PWM_out需要改动,如下图示红圈处设置,

    clip_image014

    其中,Interface在下拉菜单中选择下图红圈处所示的选项。

    clip_image016

    上面的选项都设置好以后,点击Next,如下图所示,我们通过下图红圈处的下拉条向下拉

    clip_image018

    拉到下图所示位置停止,我们将红圈处的改选为NATIVE,这个地方就是地址对齐的选项,我们选择为静态地址对齐。其他的地方都默认,不需要改动。

    clip_image020

          这里面还有很多选项,其中Timing部分需要说明一下,PWM的Avalon Slave端口与Avalon Slave端口时钟信号同步,读/写时的建立很保持时间为0,因为读、写寄存器仅需要一个时钟周期,所以读/写时为0等待切不需要读延时。

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

    clip_image022

    点击Finish后,会出现下面的对话框,点击Yes,就会生成一个PWM_hw.tcl脚本文件,大家可以打开看一下,里面放置的是刚才我们配置PWM时候的配置信息。

    clip_image024

    上面都完成以后,我们回到了SOPC Builder界面,我们在左侧边栏中可以找到下图所示的红圈处

    clip_image026

    大家看到了吧,MyIP就是我们刚才建立的group。双击PWM,我们建立PWM模块,如下图

    clip_image028

    点击Finish,完成建立。

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

    clip_image030

    点击后,如下图所示,点击IP Serarch Path,然后点击Add,添加PWM.v所在位置的路径

    clip_image032

    添加后,如下图所示

    clip_image034

          点击Finish完成。设置这个选项是为了让SOPC Builder可以找到PWM.v的位置。不然就会出现下次你进入SOPC Builder的时候PWM模块无效的问题。

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

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

    clip_image036

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

          做好硬件部分工作以后,我们打开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 }
  • 相关阅读:
    [编织消息框架][netty源码分析]13 ByteBuf 实现类CompositeByteBuf职责与实现
    [编织消息框架][netty源码分析]12 ByteBuf 实现类UnpooledDirectByteBuf职责与实现
    [编织消息框架][netty源码分析]11 ByteBuf 实现类UnpooledHeapByteBuf职责与实现
    [编织消息框架][netty源码分析]10 ByteBuf 与 ByteBuffer
    [编织消息框架][netty源码分析]9 Promise 实现类DefaultPromise职责与实现
    [编织消息框架][netty源码分析]8 Channel 实现类NioSocketChannel职责与实现
    [编织消息框架][netty源码分析]7 Unsafe 实现类NioSocketChannelUnsafe职责与实现
    浅谈如何在SQL Server中生成脚本
    word中如何将空格变成换行
    win2003从组策略关闭端口(445/135/137/138/139/3389等)教程
  • 原文地址:https://www.cnblogs.com/yingfang18/p/1896957.html
Copyright © 2011-2022 走看看