此处以我所写的MAX7219为范例,从HDL接口描述到C语言软件编程,分析两种表面不一样、但实质是一样的寄存器映射方法,找出其中联系与区别。
方法1 使用Altera提供的API
1. 使用HDL描述Avalon-MM接口
代码1 Amy_S_max7219_avalon_interface.v
01
/*-----版权声明-----
02
* 艾米电子工作室——让开发变得更简单
05
* QQ(邮箱):amy-studio@qq.com
06
*-----文件信息-----
07
* 文件名称:Amy_S_max7219_avalon_interface.v
08
* 最后修改日期:3.20, 2010
09
* 描述:Max7219的Avalon接口描述文件
10
*------------------
11
* 创建者:张亚峰
12
* 创建日期:3.20, 2009
13
* 版本:1.0
14
* 描述:原始版本
15
*------------------
16
* 修改者:
17
* 修改日期:
18
* 版本:
19
* 描述:
20
*-------------------
21
*/
22
23
module Amy_S_max7219_avalon_interface(
24
// Clcok Input
25
input csi_clk,
26
input csi_reset_n,
27
// Avalon-MM Slave
28
input avs_chipselect,
29
input [1:0] avs_address,
30
input avs_write,
31
input [31:0] avs_writedata,
32
// Conduit End
33
output reg coe_din,
34
output reg coe_cs,
35
output reg coe_clk
36
);
37
38
// write
39
always@(posedge csi_clk, negedge csi_reset_n)
40
begin
41
if (!csi_reset_n)
42
begin
43
coe_din <= 1'b0;
44
coe_cs <= 1'b0;
45
coe_clk <= 1'b0;
46
end
47
else if (avs_chipselect & avs_write)
48
begin
49
case (avs_address)
50
0: coe_din <= avs_writedata[0];
51
1: coe_cs <= avs_writedata[0];
52
2: coe_clk <= avs_writedata[0];
53
endcase
54
end
55
end
56
57
endmodule
<
;p>在这里,使用了3个寄存器,并通过avs_address来寻址。从50~52行,可以看出,这三个寄存器的偏移地址(Offset)分别是0、1和2。
2. 使用C语言编写寄存器映射文件
代码2 Amy_S_max7219.h 片段
01
//++++++++++++++++++++++++++++++++++++++
02
// 寄存器映射 开始
03
// 根据HDL编写
04
//++++++++++++++++++++++++++++++++++++++
05
#include
06
07
#define IOWR_MAX7219_DIN(base, data) IOWR(base, 0, data)
08
#define IOWR_MAX7219_CS(base, data) IOWR(base, 1, data)
09
#define IOWR_MAX7219_CLK(base, data) IOWR(base, 2, data)
10
//--------------------------------------
11
// 寄存器映射 结束
12
//--------------------------------------
注意:结尾那个是发博客发出来的,不属于代码。
由于是使用ALtera的API——IOWR(),因此第5行,就得加上#include 。IOWR(base, offset, data)的3个输入参数,分别是IP的基地址,所使用寄存器的偏移地址,欲给所使用寄存器赋的值。寄存器的存储映射所使用的偏移地址,是有HDL中avs_address决定的。(avs avalon slave 阿窝龙从设备)
代码3 Amy_S_max7219.h 片段
代码描述:使用上面的已经映射好的函数
01
//++++++++++++++++++++++++++++++++++++++
02
// 基地址 开始
03
// 根据SOPC Builder设置编写
04
//++++++++++++++++++++++++++++++++++++++
05
#include "system.h"
06
07
#define max7219_addr MAX7219_BASE
08
//--------------------------------------
09
// 基地址 结束
10
//--------------------------------------
11
12
13
//++++++++++++++++++++++++++++++++++++++
14
// 寄存器映射 开始
15
// 根据HDL编写
16
//++++++++++++++++++++++++++++++++++++++
17
#include
18
19
#define IOWR_MAX7219_DIN(base, data) IOWR(base, 0, data)
20
#define IOWR_MAX7219_CS(base, data) IOWR(base, 1, data)
21
#define IOWR_MAX7219_CLK(base, data) IOWR(base, 2, data)
22
//--------------------------------------
23
// 寄存器映射 结束
24
//--------------------------------------
25
26
27
//++++++++++++++++++++++++++++++++++++++
28
// 管脚操作 开始
29
//++++++++++++++++++++++++++++++++++++++
30
#define SET_DIN IOWR_MAX7219_DIN(max7219_addr, 1)
31
#define CLR_DIN IOWR_MAX7219_DIN(max7219_addr, 0)
32
#define SET_CS IOWR_MAX7219_CS(max7219_addr, 1)
33
#define CLR_CS IOWR_MAX7219_CS(max7219_addr, 0)
34
#define SET_CLK IOWR_MAX7219_CLK(max7219_addr, 1)
35
#define CLR_CLK IOWR_MAX7219_CLK(max7219_addr, 0)
36
//--------------------------------------
37
// 管脚操作 结束
38
//--------------------------------------
注意:结尾那个是发博客发出来的,不属于代码
代码4 Amy_S_max7219.c代码片段
代码描述:使用Altera API的具体操作
01
#include "Amy_S_max7219.h"
02
03
/*
04
* 发送一个字节的子程序:
05
* 上升沿发送数据,
06
* MSB first
07
*/
08
void Max7219_WriteByte(alt_u8 byte)
09
{
10
alt_u8 i;
11
for (i=0; i<8; i++)
12
{
13
CLR_CLK;
14
if(byte & 0x80)
15
SET_DIN;
16
else
17
CLR_DIN;
18
byte <<= 1;
19
SET_CLK;
20
}
21
}
至此,使用Altera的API来描述寄存器存储映射的方法,告一段落。
方法2 使用位域或结构体
其实这种方法,Altera的API的源代码有时也会用到。但是有一个地方需要注意,后面会提到。
1. 使用HDL描述Avalon-MM接口
如上。
2. 使用C语言编写寄存器映射文件
代码4 Amy_S_max7219.h 片段
01
//++++++++++++++++++++++++++++++++++++++
02
// 寄存器映射 开始
03
// 根据HDL编写
04
//++++++++++++++++++++++++++++++++++++++
05
#include "system.h"
06
#include "alt_types.h"
07
08
typedef struct
09
{
10
alt_u32 DIN : 32;
11
alt_u32 CS : 32;
12
alt_u32 CLK : 32;
13
}MAX7219_T;
14
15
#define m7219 ((MAX7219_T *)(MAX7219_BASE))
16
//--------------------------------------
17
// 寄存器映射 结束
18
//--------------------------------------
因为Nios II是32位的处理
器,所以之前定义了3个寄存器,都是32位的。此处为了表达这种关系,我们使用了位域。将这个位域(或结构体)重定义为一个类型,然后定义一个该类型的指针变量,起始地址是所需的基地址。这样做,就可以很好地为从基地址开始的连续的3x32位数据寻址(此处为3个寄存器,故数据总长3x32)。
代码5 Amy_S_max7219.c代码片段
代码描述:使用结构体指针寻址示例
01
/*
02
* 发送一个字节的子程序:
03
* 上升沿发送数据,
04
* MSB first
05
*/
06
void Max7219_WriteByte(alt_u8 byte)
07
{
08
alt_u8 i;
09
for (i=0; i<8; i++)
10
{
11
m7219->CLK = 0;
12
if(byte & 0x80)
13
m7219->DIN = 1;
14
else
15
m7219->DIN = 0;
16
byte <<= 1;
17
m7219->CLK = 1;
18
}
19
}
哈哈,是不是可以直接赋值了,更加像单片机了吧。其实Nios II就是单片机,32位的单片机。
做到这里,有些实验者在开发板上演练时,确实成功了;然而有些没有成功?这是为什么呢?我们先看参考资料1。
代码6 两种寄存器存储映射所对应的汇编
01
IOWR=32DIRECT(GPIO_LED_BASE, 0, 1);
02
0x04000234 : movhi r3,2048
03
0x04000238 : addi r3,r3,6144
04
0x0400023c : movi r2,1
05
0x04000240 : stwio r2,0(r3)
06
07
LED = 1;
08
0x04000224 : movhi r3,2048
09
0x04000228 : addi r3,r3,6144
10
0x0400022c : movi r2,1
11
0x04000230 : stw r2,0(r3)
看到没有,两种寄存器存储映射所对应的汇编不一样。看关键字,一个是stwio,一个是stw。接下来打开手册,Table 3-36。
表1 宽数据传输指令
手册上清楚地写到,I/O外设的数据传输应该使用ldwio和stwio;这两条指令在传输时,是没有cache和buffer的。那怎样让结构体指针的寄存器映射方式也能使用ldwio和stwio呢。接着看手册,在98页,Cache Memory小节,写到 。那还有其他方法来实现cache bypass吗?第38页写到:
图1 Cache Bypass Method
图2 The Bit-31 Cache Bypass Method
好的,看代码。
代码7 system.h片段
1
#define ALT_MODULE_CLASS_max7219 Amy_S_max7219
2
#define MAX7219_BASE 0x1002020
3
#define MAX7219_IRQ -1
4
#define MAX7219_IRQ_INTERRUPT_CONTROLLER_ID -1
5
#define MAX7219_NAME "/dev/max7219"
6
#define MAX7219_SPAN 16
7
#define MAX7219_TYPE "Amy_S_max7219"
MAX7219_BASE=0x1002020,第31位是0;因此我们用结构体指针来寄存器存储映射时,传输的数据因数据缓存而出错。那我们就把这个Cache给Bypass(旁路)了。怎么办?把地址总线的第31位置一。
代码8 修改后的Amy_S_max7219.h片段
01
//++++++++++++++++++++++++++++++++++++++
02
// 寄存器映射 开始
03
// 根据HDL编写
04
//++++++++++++++++++++++++++++++++++++++
05
#include "system.h"
06
#include "alt_types.h"
07
08
typedef struct
09
{
10
alt_u32 DIN : 32;
11
alt_u32 CS : 32;
12
alt_u32 CLK : 32;
13
}MAX7219_T;
14
15
#define m7219 ((MAX7219_T *)(MAX7219_BASE | 1<<31))
16
//--------------------------------------
17
// 寄存器映射 结束
18
//--------------------------------------
在这里,我们直接通过或(1<<31)的方式,把第31位给置一了;这样从该基地址传输的数据就把数据缓存给旁路了;因为是I/O外设传输嘛。
好了,至此大功告成。我们可以自由切换喜欢的寄存器映射方式。
3. 一点其他内容
有时候我们所使用的寄存器并不一定都是32位的,这时要使用嵌套结构体来凑足32位,以防止寄存器寻址错误。
代码9 嵌套结构体示例
01
typedef struct{
02
struct{
03
alt_u8 DIN : 8;
04
alt_u32 NC : 24;
05
}offset_0;
06
struct{
07
alt_u8 CS : 1;
08
alt_u32 NC : 31;
09
}offset_1;
10
struct{
11
alt_u8 CLK : 1;
12
alt_u32 NC : 31;
13
}offset_2;
14
}MAX7219_T;
关于嵌套结构体,此处不解析,请读者自行分析。
对比
两种方法都不错,大家爱用什么就用什么。
参考
2. Altera.Nios II Processor Reference Handbook