转自http://www.cnblogs.com/yuphone/archive/2010/04/22/1717779.html
此处以我所写的MAX7219为范例,从HDL接口描述到C语言软件编程,分析两种表面不一样、但实质是一样的寄存器映射方法,找出其中联系与区别。
方法1 使用Altera提供的API
1. 使用HDL描述Avalon-MM接口
代码1 Amy_S_max7219_avalon_interface.v
01 | /*-----版权声明----- |
02 | * 艾米电子工作室——让开发变得更简单 |
03 | * 网站:http://www.amy-studio.com |
04 | * 淘宝:http://amy-studio.taobao.com |
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 <IO.H> |
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 | //-------------------------------------- |
注意:结尾那个</io.h>是发博客发出来的,不属于代码。
由于是使用ALtera的API——IOWR(),因此第5行,就得加上#include <io.h>。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 <IO.H> |
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 | //-------------------------------------- |
注意:结尾那个</io.h>是发博客发出来的,不属于代码
代码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 <MAIN +36>: movhi r3,2048 |
03 | 0x04000238 <MAIN +40>: addi r3,r3,6144 |
04 | 0x0400023c <MAIN +44>: movi r2,1 |
05 | 0x04000240 <MAIN +48>: stwio r2,0(r3) |
06 | |
07 | LED = 1; |
08 | 0x04000224 <MAIN +20>: movhi r3,2048 |
09 | 0x04000228 <MAIN +24>: addi r3,r3,6144 |
10 | 0x0400022c <MAIN +28>: movi r2,1 |
11 | 0x04000230 <MAIN +32>: 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; |
关于嵌套结构体,此处不解析,请读者自行分析。
对比
两种方法都不错,大家爱用什么就用什么。
参考
1. http://www.edaboard.com/ftopic354136.html
2. Altera.Nios II Processor Reference Handbook