在Avalon总线协议(一)和Avalon总线协议(二)中大概了解Avalon总线的几种类型,目前比较常用到的就是Avalon-MM接口了,虽然在概念中有那么多的属性,但是具体使用起来还是非常简单的。
一、Avalon-MM
前面提到过Avalon总线常用于 用户自定义的逻辑 与 NIOS Ⅱ处理器 之间进行通信,再通俗一点的理解就是硬件(Verilog代码)和软件(Nios Ⅱ处理器)进行数据交互。Nios Ⅱ作为主端口(Master),而Verilog代码模块实现了从端口(Slave),比如在Verilog代码中写了一个计数器,而Nios想要知道这个计数器的值,那么就可以在Verilog代码中定义一个寄存器,该寄存器具有与之相对应的地址,Nios Ⅱ可以根据地址对这个寄存器的值进行读取或者写入,这就是Avalon-MM协议。
如图,Verilog代码中定义了三个寄存器,分别是REG1、REG2、REG3,偏移量分别是OFFSET1、OFFSET2、OFFSET3;
当这一部分Verilog代码作为自定义组件加入到NIOS中并进行编译后,会自动产生BASE;
这时候寄存器的地址和偏移量就都有了,那么NIOS就可以对寄存器中的数据进行读写,从而实现通信!
二、NIOS常用函数
在NIOS中有一些已经定义好的函数方便去对数据进行操作
最常用的肯定是IORD()、IOWR()以及对PIO操作的IORD_ALTERA_AVALON_PIO_DATA()、IOWR_ALTERA_AVALON_PIO_DATA()
其实都一样>-<,IORD_ALTERA_AVALON_PIO_DATA()还是调用的IORD()
IORD(BASE, OFFSET)
//BASE为寄存器的基地址,OFFSET为寄存器的偏移量
//从基地址为BASE的设备中读取寄存器中偏移量为OFFSET的单元里面的值
IOWR(BASE, OFFSET, DATA)
//BASE为寄存器的基地址,OFFSET为寄存器的偏移量,DATA为要写入的数据
//向基地址为BASE的设备偏移量为OFFSET寄存器中写入数据DATA
IORD_ALTERA_AVALON_PIO_DATA(BASE)
//BASE为寄存器的基地址
//向基地址为BASE的设备中读取数
IOWR_ALTERA_AVALON_PIO_DATA(BASE, DATA)
//BASE为寄存器的基地址,DATA为要写入的数据
//向基地址为BASE的设备中写入数据DATA
其他NIOS函数可以参考:NIOS常用函数详解-CSDN博客
三、Avalon-MM实现
其实已经在前面的文章中实现过了,只不过没有较为详细的解释:
SOPC之NIOS Ⅱ实现电机转速PID控制_STATEABC的博客-CSDN博客
就用其中的电机PWM控制模块作为例子
3.1 硬件部分
module MOTOR_PWM(
input clk,
input reset_n,
//Avalon-MM输入输出
input avalon_cs, // 片选信号,进行数据操作时自动置为1
input [2:0] avalon_address, // 基地址,位宽根据要定义的寄存器个数
input avalon_write, // 写入信号
input [31:0] avalon_writedata, // 写入数据
input avalon_read, // 读取信号
output reg [31:0] avalon_readdata, // 读取数据
input signed [31:0] Speed,
output reg PWM,
output reg IN1,
output reg IN2
);
reg pwm_tem;
reg [31:0] total; // 总时间
reg [31:0] high; // 高位时间
reg [31:0] count; // 计数器
/
// 定义寄存器的偏移量,两种写法都可以
localparam REGISTER_TOTAL_DUR = 0;
`define REGISTER_HIGH_DUR 2'd1
/
// Avalon-MM通信
always @(posedge clock or negedge reset_n)
begin
if (~reset_n)
begin
high <= 0;
total <= 0;
end
// 当片选信号和写入信号有效,反映在软件上就是执行了IOWR()
else if (avalon_cs & avalon_write)
begin
if (avalon_address == REGISTER_TOTAL_DUR) // 当地址等于0,即REGISTER_TOTAL_DUR
total <= avalon_writedata; // avalon_writedata为IOWR()写入的DATA值
else if (avalon_address == `REGISTER_HIGH_DUR)
high <= avalon_writedata;
end
// 当片选信号和读取信号有效,反映在软件上就是执行了IORD()
else if (select_cs & select_read)
begin
if (avalon_address == `REGISTER_TOTAL_DUR) // 当地址等于0,即REGISTER_TOTAL_DUR
select_readdata <= total; // avalon_writedata为IORD()的返回值
else if (avalon_address == `REGISTER_HIGH_DUR)
select_readdata <= high;
end
end
/
// 进行PWM输出
always @(*)
begin
if (Speed>0) begin
{IN1, IN2, PWM} <= {1'b1, 1'b0, pwm_tem};
end
else begin
{IN1, IN2, PWM} <= {1'b1, 1'b0, pwm_tem};
end
end
always @(posedge clock or negedge reset_n)
begin
if (~reset_n)
begin
count <= 1;
end
else if (count >= total)
begin
count <= 1;
end
else
count <= count + 1;
end
always @(posedge clock)
begin
pwm_tem <= (count <= high) ? 1'b1 : 1'b0;
end
endmodule
将其作为自定义组件加入NIOS系统中并进行编译
3.2 软件部分
硬件部分进行全编译后,生成的systm.h文件中会包含其BASE信息
然后就可以根据BASE和OFFSET进行数据的读取
#include <stdio.h>
#include <stdlib.h>
#include "system.h"
#include "altera_avalon_pio_regs.h" //IOWR_ALTERA_AVALON_PIO_DATA
int main()
{
int high,total;
//写入数据
IOWR(MOTOR_PWM_BASE,0,2000);
IOWR(MOTOR_PWM_BASE,1,1000);
//读取数据
total = IORD(MOTOR_PWM_BASE,0);
high = IORD(MOTOR_PWM_BASE,1);
printf("total= %d\r\n", total);
printf("high = %d\r\n", hight);
return 0;
}