[原创].怎样制作一个简单ip,以方便在Quartus II和Nios II中使用?.[Avalon][C][Nios II][Quartus II][SOPC Builder][Verilog]
0赞概述:此处,我以Lcd12864(ST7920)作为范例,进行粗浅讲解,望各位网友踊跃拍砖。
0 软硬件环境
软件:Altera Quartus II 9.1 + Nios II 9.1 Software Build Tools for Eclipse
硬件:艾米电子EP2C8开发板(EP2C8Q208C8 + 16 bit SDRAM 64MB + EPCS4)
1 硬件部分
1.1 Avalon-MM接口(读作:阿窝龙妹妹接口)
Avalon Memory-Mapped接口,简称为 Avalon-MM接口,用于在存储映射系统中描述主从元件(component)的读/写接口。
图1.1 Amy_S_lcd12864 IP与System Interconnect Fabric的连线框图
图1.2 某带有Amy_S_lcd12864 IP的Avalon系统框图
1.2 从设备读写时序
请参考手册《Avalon Interface Specification》,此处略去。
1.3 HDL模块及说明
1.3.1 模块介绍
表1.1 Amy_S_lcd12864 IP的HDL源代码模块介绍
文件名称 | 功能描述 |
Amy_S_lcd12864_avalon_interface.v | Amy_S_lcd12864 Avalon接口文件 |
1.3.2 源代码
注:本文所涉及verilog代码,是按照Verilog 2001规范编写的。Avalon信号类型命名参考图1.3。
图1.3 Naming Convention for Avalon Signal Type
1.3.2.1 Amy_S_lcd12864_avalon_interface.v
001 |
/*-----版权声明----- |
002 |
* 艾米电子工作室——让开发变得更简单 |
003 |
* 网站:http://www.amy-studio.com |
004 |
* 淘宝:http://amy-studio.taobao.com |
005 |
* QQ(邮箱):amy-studio@qq.com |
006 |
*-----文件信息----- |
007 |
* 文件名称:Amy_S_lcd12864_avalon_interface.v |
008 |
* 最后修改日期:3.20, 2010 |
009 |
* 描述:Lcd12864的Avalon接口描述文件 |
010 |
*------------------ |
011 |
* 创建者:张亚峰 |
012 |
* 创建日期:3.20, 2009 |
013 |
* 版本:1.0 |
014 |
* 描述:原始版本 |
015 |
*------------------ |
016 |
* 修改者: |
017 |
* 修改日期: |
018 |
* 版本: |
019 |
* 描述: |
020 |
*------------------- |
021 |
*/ |
022 |
023 |
module Amy_S_lcd12864_avalon_interface( |
024 |
// Clcok Input |
025 |
input csi_clk, |
026 |
input csi_reset_n, |
027 |
// Avalon-MM Slave |
028 |
input avs_chipselect, |
029 |
input [1:0] avs_address, |
030 |
input avs_write, |
031 |
input [31:0] avs_writedata, |
032 |
input avs_read, |
033 |
output [31:0] avs_readdata, |
034 |
// Conduit End |
035 |
// lcd12864 interface |
036 |
output reg coe_e, |
037 |
output reg coe_rw, |
038 |
output reg coe_rs, |
039 |
inout [7:0] coe_data_io |
040 |
); |
041 |
042 |
//++++++++++++++++++++++++++++++++++++++ |
043 |
// 写 开始 |
044 |
//++++++++++++++++++++++++++++++++++++++ |
045 |
reg [7:0] coe_data_o; |
046 |
047 |
always @( posedge csi_clk, negedge csi_reset_n) |
048 |
begin |
049 |
if (!csi_reset_n) |
050 |
begin |
051 |
coe_e <= 1'b0 ; |
052 |
coe_rw <= 1'b0 ; |
053 |
coe_rs <= 1'b0 ; |
054 |
coe_data_o <= 8'b0 ; |
055 |
end |
056 |
else if (avs_chipselect & avs_write) |
057 |
begin |
058 |
case (avs_address) |
059 |
0: coe_e <= avs_writedata[0]; |
060 |
1: coe_rw <= avs_writedata[0]; |
061 |
2: coe_rs <= avs_writedata[0]; |
062 |
3: coe_data_o <= avs_writedata[7:0]; |
063 |
endcase |
064 |
end |
065 |
end |
066 |
//-------------------------------------- |
067 |
// 写 结束 |
068 |
//-------------------------------------- |
069 |
070 |
//++++++++++++++++++++++++++++++++++++++ |
071 |
// 读 开始 |
072 |
//++++++++++++++++++++++++++++++++++++++ |
073 |
reg [7:0] readdata_r; |
074 |
wire [7:0] coe_data_i; |
075 |
076 |
always @( posedge csi_clk) |
077 |
if (avs_chipselect & avs_read) |
078 |
begin |
079 |
if (avs_address == 3) |
080 |
readdata_r <= coe_data_i; |
081 |
else |
082 |
readdata_r <= 8'b0 ; |
083 |
end |
084 |
else |
085 |
readdata_r <= 8'b0 ; |
086 |
|
087 |
assign avs_readdata = { 24'b0 , readdata_r}; |
088 |
//-------------------------------------- |
089 |
// 读 结束 |
090 |
//-------------------------------------- |
091 |
092 |
093 |
//++++++++++++++++++++++++++++++++++++++ |
094 |
// 双向口 开始 |
095 |
//++++++++++++++++++++++++++++++++++++++ |
096 |
reg coe_data_o_en; |
097 |
098 |
always @( posedge csi_clk) |
099 |
if (avs_chipselect & avs_write) |
100 |
coe_data_o_en <= 1'b0 ; |
101 |
else if (avs_chipselect & avs_read) |
102 |
coe_data_o_en <= 1'b1 ; |
103 |
104 |
assign coe_data_i = coe_data_io; |
105 |
assign coe_data_io = coe_data_o_en ? 8'bz : coe_data_o; |
106 |
//-------------------------------------- |
107 |
// 双向口 结束 |
108 |
//-------------------------------------- |
109 |
|
110 |
endmodule |
1.3.3 一些说明
ST7920的E、RW和RS都是单向的,而DATA总线是双向的;故在此处nios既需要写数据给ST7920,又需要从ST7920读数据。
从42行到68行,即nios向ST7920写数据。注意,谁给nios写数据呢?请看图1.4。
图1.4 NII、nios cpu和ST7920通信框图
从70行到90行,是nios从ST7920读数据。由于只有DATA总线需要读,其他的管脚就不写了,呵呵。
从93行到108行,是对DATA双向总线的处理。读或写只是简单由Avalon的读、写信号来控制的。这个技巧是我从open-cores里面的 基于wishbone总线的IIC从设备的IP上学到的。注意:ST7920是低速设备,此处只做简单处理;高速设备请大家自行斟酌。
还有一点需要说明,chipselect在Nios II 9.0之后就不是必须的信号,此处加上,只为和以前的版本兼容。
2 软件部分
表2.1 Amy_S_lcd12864 IP的C源代码模块介绍
文件名称 | 功能描述 |
Amy_S_lcd12864.h | Amy_S_lcd12864 的C头文件 |
Amy_S_lcd12864.c | Amy_S_lcd12864 的C源文件 |
2.1 C头文件
2.1.1 Amy_S_lcd12864.h
01 |
/*-----版权声明----- |
02 |
* 艾米电子工作室——让开发变得更简单 |
03 |
* 网站:http://www.amy-studio.com |
04 |
* 淘宝:http://amy-studio.taobao.com |
05 |
* QQ(邮箱):amy-studio@qq.com |
06 |
*----- 文件信息----- |
07 |
* 文件名称:Amy_S_lcd12864.h |
08 |
* 最后修改日期:3.20, 2009 |
09 |
* 描述:Lcd12864驱动宏文件 |
10 |
*------------------ |
11 |
* 创建者:张亚峰 |
12 |
* 创建日期:3.20, 2009 |
13 |
* 版本:1.0 |
14 |
* 描述:原始版本 |
15 |
*------------------ |
16 |
* 修改者: |
17 |
* 修改日期: |
18 |
* 版本: |
19 |
* 描述: |
20 |
*------------------- |
21 |
*/ |
22 |
23 |
24 |
#ifndef __Amy_S_LCD12864_H__ |
25 |
#define __Amy_S_LCD12864_H__ |
26 |
27 |
28 |
//++++++++++++++++++++++++++++++++++++++ |
29 |
// 基地址 开始 |
30 |
// 根据SOPC Builder设置编写 |
31 |
//++++++++++++++++++++++++++++++++++++++ |
32 |
#include "system.h" |
33 |
34 |
#define lcd12864_addr LCD12864_BASE |
35 |
//-------------------------------------- |
36 |
// 基地址 开始 |
37 |
//-------------------------------------- |
38 |
39 |
40 |
//++++++++++++++++++++++++++++++++++++++ |
41 |
// 寄存器映射 开始 |
42 |
// 根据HDL编写 |
43 |
//++++++++++++++++++++++++++++++++++++++ |
44 |
#include <io.h> |
45 |
46 |
#define IOWR_LCD12864_E(base, data) IOWR(base, 0, data) |
47 |
#define IOWR_LCD12864_RW(base, data) IOWR(base, 1, data) |
48 |
#define IOWR_LCD12864_RS(base, data) IOWR(base, 2, data) |
49 |
#define IOWR_LCD12864_DATA(base, data) IOWR(base, 3, data) |
50 |
#define IORD_LCD12864_DATA(base) IORD(base, 3) |
51 |
//-------------------------------------- |
52 |
// 寄存器映射 结束 |
53 |
//-------------------------------------- |
54 |
55 |
56 |
//++++++++++++++++++++++++++++++++++++++ |
57 |
// 管脚操作 开始 |
58 |
//++++++++++++++++++++++++++++++++++++++ |
59 |
#define SET_E IOWR_LCD12864_E(lcd12864_addr, 1) |
60 |
#define CLR_E IOWR_LCD12864_E(lcd12864_addr, 0) |
61 |
#define SET_RW IOWR_LCD12864_RW(lcd12864_addr, 1) |
62 |
#define CLR_RW IOWR_LCD12864_RW(lcd12864_addr, 0) |
63 |
#define SET_RS IOWR_LCD12864_RS(lcd12864_addr, 1) |
64 |
#define CLR_RS IOWR_LCD12864_RS(lcd12864_addr, 0) |
65 |
#define WR_DATA(data) IOWR_LCD12864_DATA(lcd12864_addr, data) |
66 |
#define RD_DATA IORD_LCD12864_DATA(lcd12864_addr) |
67 |
//-------------------------------------- |
68 |
// 管脚操作 结束 |
69 |
//-------------------------------------- |
70 |
71 |
72 |
//++++++++++++++++++++++++++++++++++++++ |
73 |
// 函数声明 开始 |
74 |
//++++++++++++++++++++++++++++++++++++++ |
75 |
extern void LCD12864_CheckBusy( void ); |
76 |
extern void Lcd12864_WrCmd(alt_u8 cmd); |
77 |
extern void Lcd12864_WrData(alt_u8 data); |
78 |
extern void Lcd12864_Init( void ); |
79 |
extern void Lcd12864_WrChar(alt_u8 row, alt_u8 col, alt_u8 *pCN, alt_u8 n); |
80 |
//-------------------------------------- |
81 |
// 函数声明 结束 |
82 |
//-------------------------------------- |
83 |
84 |
85 |
#endif /* __Amy_S_LCD12864_H__ */</io.h> |
2.1.2 一些说明
最后面的那个</io.h>是发布博客的时候带出来的,不属于头文件。
从28行到37行,是根据SOPC Builder设置编写的lcd12864的基地址,需要system.h的支持。注:system.h就是和SOPC Builder设置一一对应的;当在NII中建立工程时,system.h就根据sopcinfo(Nios II 9.1 Software Build Tools for Eclipse使用,不是Nios II 9.1 IDE)文件自动生产。
第40行到第53行,是自己编写的一些宏,这个叫Register Map(寄存器映射),以前都是单独放在一个头文件里(如xxx_regs.h)。由于NII 9.1貌似不支持HAL的自动初始化(我研究的结果是不行,不知道Altera公司有没有相关的变动声明),因此就没有向8.1那样书写HAL。注 意,0、1~3是OFFSET(偏移地址),请参考HDL代码编写。
从56行到69行,是一些管脚操作的宏,这样写,主要是方便移植。大家也可以不写寄存器映射,直接写管脚操作的宏也行,注意替换哟。
实际上大家也可以使用ARM方式的寄存器访问方式,譬如
1 |
#define CS *(volatile unsigned *) CS_BASE // 片选信号 --低有效 |
这种貌似更好操作。由于我没有深入研究这种寄存器访问方式,这里就不多说了。
下面的几行和各种MCU大同小异。
2.2 C源文件
2.2.1 Amy_S_lcd12864.c
01 |
/*-----版权声明----- |
02 |
* 艾米电子工作室——让开发变得更简单 |
03 |
* 网站:http://www.amy-studio.com |
04 |
* 淘宝:http://amy-studio.taobao.com |
05 |
* QQ(邮箱):amy-studio@qq.com |
06 |
*----- 文件信息----- |
07 |
* 文件名称:Amy_S_lcd12864.c |
08 |
* 最后修改日期:3.20, 2009 |
09 |
* 描述:Lcd12864驱动源文件 |
10 |
*------------------ |
11 |
* 创建者:张亚峰 |
12 |
* 创建日期:3.20, 2009 |
13 |
* 版本:1.0 |
14 |
* 描述:原始版本 |
15 |
*------------------ |
16 |
* 修改者: |
17 |
* 修改日期: |
18 |
* 版本: |
19 |
* 描述: |
20 |
*------------------- |
21 |
*/ |
22 |
23 |
#include "Amy_S_lcd12864.h" |
24 |
#include "alt_types.h" |
25 |
#include "unistd.h" |
26 |
27 |
void LCD12864_CheckBusy( void ) |
28 |
{ |
29 |
CLR_RS; // 指令 |
30 |
SET_RW; // 读 |
31 |
SET_E; |
32 |
while ((RD_DATA&0x80) == 0x80); // 检测busy flag |
33 |
CLR_E; |
34 |
usleep(72); // 72us |
35 |
} |
36 |
37 |
void Lcd12864_WrCmd(alt_u8 cmd) |
38 |
{ |
39 |
LCD12864_CheckBusy(); |
40 |
CLR_RS; // 指令 |
41 |
CLR_RW; // 写 |
42 |
SET_E; |
43 |
WR_DATA(cmd); |
44 |
CLR_E; |
45 |
usleep(72); // 72us |
46 |
} |
47 |
48 |
void Lcd12864_WrData(alt_u8 data) |
49 |
{ |
50 |
LCD12864_CheckBusy(); |
51 |
SET_RS; // 数据 |
52 |
CLR_RW; // 写 |
53 |
SET_E; |
54 |
WR_DATA(data); |
55 |
CLR_E; |
56 |
usleep(72); // 72us |
57 |
} |
58 |
59 |
void Lcd12864_Init( void ) |
60 |
{ |
61 |
usleep(40*1000); |
62 |
Lcd12864_WrCmd(0x30); // 8bit |
63 |
usleep(100); |
64 |
Lcd12864_WrCmd(0x30); // basic function |
65 |
usleep(37); |
66 |
Lcd12864_WrCmd(0x0F); // 整体显示开 游标开 反白 |
67 |
usleep(100); |
68 |
Lcd12864_WrCmd(0x10); // 游标左移 |
69 |
usleep(100); |
70 |
Lcd12864_WrCmd(0x01); |
71 |
usleep(10*1000); |
72 |
Lcd12864_WrCmd(0x06); // 画面整体右移 |
73 |
} |
74 |
75 |
void Lcd12864_WrChar(alt_u8 row, alt_u8 col, alt_u8 *pCN, alt_u8 n) |
76 |
{ |
77 |
alt_u8 i, addr; |
78 |
row &= 0x03; // row < 4 |
79 |
col &= 0x07; // col < 8 |
80 |
switch (row) |
81 |
{ |
82 |
case 0: addr = 0x80; break ; |
83 |
case 1: addr = 0x90; break ; |
84 |
case 2: addr = 0x88; break ; |
85 |
case 3: addr = 0x98; break ; |
86 |
} |
87 |
addr += col; |
88 |
Lcd12864_WrCmd(addr); |
89 |
for (i=0; i<2*n; i++) |
90 |
{ |
91 |
Lcd12864_WrData(pCN[i]); // 写字符数据 |
92 |
} |
93 |
} |
2.2.2 一些说明
呵呵,这个就不说明了,大家自己看。
3 使用举例
注意:9.1环境。
3.1 在SOPC Builder中添加组件
从File-New Component.. 打开Component Editor,单击HDL Files标签,添加所编写的HDL文件。如图3.1所示。
图3.1 添加HDL文件
由于所编写的HDL完全是按照规范的,因此直接单击Component Wizard即可,编写IP信息,如图3.2所示。
图3.2 编写IP信息
单击Finish,IP添加成功,如图 3.3所示。
图3.4 添加成功后的IP
3.2 在SOPC Builder中例化
这个就不多说了,直接上图,请看图3.5。
图3.5 例化后的IP 1
注意lcd12864是随便起的名字哟,只要不叫IP的名字(Ams_S_lcd12864)就行。
图3.6 例化后的IP 2
3.3 在Nios II中的使用范例
先将Amy_S_lcd12864.h和Amy_S_lcd12864.c拷贝到软件工程内。
3.3.1 使用范例main.c
01 |
/*-----版权声明----- |
02 |
* 艾米电子工作室——让开发变得更简单 |
03 |
* 网站:http://www.amy-studio.com |
04 |
* 淘宝:http://amy-studio.taobao.com |
05 |
* QQ(邮箱):amy-studio@qq.com |
06 |
*----- 文件信息----- |
07 |
* 文件名称:main.c |
08 |
* 最后修改日期:3.20, 2009 |
09 |
* 描述:Lcd12864测试文件 |
10 |
*------------------ |
11 |
* 创建者:张亚峰 |
12 |
* 创建日期:3.20, 2009 |
13 |
* 版本:1.0 |
14 |
* 描述:原始版本 |
15 |
*------------------ |
16 |
* 修改者: |
17 |
* 修改日期: |
18 |
* 版本: |
19 |
* 描述: |
20 |
*------------------- |
21 |
*/ |
22 |
23 |
24 |
#include <string.h> // strlen() |
25 |
#include "Amy_S_lcd12864.h" // 根据SOPC Builder的设置,修改该头文件中的Lcd12864基地址 |
26 |
27 |
int main() |
28 |
{ |
29 |
Lcd12864_Init(); // 初始化Lcd12864 |
30 |
Lcd12864_WrChar(0, 0, "这不是单片机吗?" , strlen ( "这不是单片机吗?" )>>1); |
31 |
Lcd12864_WrChar(1, 0, "这就是单片机呀。" , strlen ( "这就是单片机呀。" )>>1); |
32 |
Lcd12864_WrChar(2, 0, "艾米电子出品。" , strlen ( "艾米电子出品。" )>>1); |
33 |
Lcd12864_WrChar(2, 0, "艾米电子出品。" , strlen ( "艾米电子出品。" )>>1); |
34 |
Lcd12864_WrChar(3, 0, "Amy-studio Pub." , strlen ( "Amy-studio Pub." )>>1); |
35 |
|
36 |
return 0; |
37 |
}</string.h> |
3.3.2 一些说明
最后面的那个</string.h>是发布博客的时候带出来的,不属于头文件。
3.4 使用效果
图3.7 Amy_S_lcd12864 IP使用效果
4 参考资料
1.李兰英等.Nios II嵌入式软核SOPC设计原理及应用.北京航空航天大学出版社.2006
2.周立功等.SOPC嵌入式系统实验教程(一).北京航空航天大学出版社.2006
3.蔡伟刚.Nios II软件架构解析.西安电子科技大学出版社.2007
4.Altera Handbook.Quartus II Handbook Volume4: SOPC Builder.2009
5.Altera Handbook.Avalon Interface Specifications.2009
6.Altera Handbook.HAL API Reference.2009
7.Altera Website.Avalon Component Interfaces Supported in the Component Editor Version 7.2 and Later
9.萧鸿森.如 何让Nios II自动抓到自己写的IP的HAL? .2008