概述:此处,我以Lcd12864(ST7920)作为范例,进行粗浅讲解,望各位网友踊跃拍砖。
软件:Altera Quartus II 9.1 + Nios II 9.1 Software Build Tools for Eclipse
硬件:艾米电子EP2C8开发板(EP2C8Q208C8 + 16 bit SDRAM 64MB + EPCS4)
Avalon Memory-Mapped接口,简称为 Avalon-MM接口,用于在存储映射系统中描述主从元件(component)的读/写接口。
图1.1 Amy_S_lcd12864 IP与System Interconnect Fabric的连线框图
图1.2 某带有Amy_S_lcd12864 IP的Avalon系统框图
请参考手册《Avalon Interface Specification》,此处略去。
表1.1 Amy_S_lcd12864 IP的HDL源代码模块介绍
文件名称 | 功能描述 |
Amy_S_lcd12864_avalon_interface.v | Amy_S_lcd12864 Avalon接口文件 |
注:本文所涉及verilog代码,是按照Verilog 2001规范编写的。Avalon信号类型命名参考图1.3。
图1.3 Naming Convention for Avalon Signal Type
/*-----版权声明----- * 艾米电子工作室——让开发变得更简单 * 网站:http://www.amy-studio.com * 淘宝:http://amy-studio.taobao.com * QQ(邮箱):[email protected] *-----文件信息----- * 文件名称:Amy_S_lcd12864_avalon_interface.v * 最后修改日期:3.20, 2010 * 描述:Lcd12864的Avalon接口描述文件 *------------------ * 创建者:张亚峰 * 创建日期:3.20, 2009 * 版本:1.0 * 描述:原始版本 *------------------ * 修改者: * 修改日期: * 版本: * 描述: *------------------- */ module Amy_S_lcd12864_avalon_interface( // Clcok Input input csi_clk, input csi_reset_n, // Avalon-MM Slave input avs_chipselect, input [1:0] avs_address, input avs_write, input [31:0] avs_writedata, input avs_read, output [31:0] avs_readdata, // Conduit End // lcd12864 interface output reg coe_e, output reg coe_rw, output reg coe_rs, inout [7:0] coe_data_io ); //++++++++++++++++++++++++++++++++++++++ // 写 开始 //++++++++++++++++++++++++++++++++++++++ reg [7:0] coe_data_o; always@(posedge csi_clk, negedge csi_reset_n) begin if (!csi_reset_n) begin coe_e <= 1'b0; coe_rw <= 1'b0; coe_rs <= 1'b0; coe_data_o <= 8'b0; end else if (avs_chipselect & avs_write) begin case (avs_address) 0: coe_e <= avs_writedata[0]; 1: coe_rw <= avs_writedata[0]; 2: coe_rs <= avs_writedata[0]; 3: coe_data_o <= avs_writedata[7:0]; endcase end end //-------------------------------------- // 写 结束 //-------------------------------------- //++++++++++++++++++++++++++++++++++++++ // 读 开始 //++++++++++++++++++++++++++++++++++++++ reg [7:0] readdata_r; wire [7:0] coe_data_i; always@(posedge csi_clk) if (avs_chipselect & avs_read) begin if (avs_address == 3) readdata_r <= coe_data_i; else readdata_r <= 8'b0; end else readdata_r <= 8'b0; assign avs_readdata = {24'b0, readdata_r}; //-------------------------------------- // 读 结束 //-------------------------------------- //++++++++++++++++++++++++++++++++++++++ // 双向口 开始 //++++++++++++++++++++++++++++++++++++++ reg coe_data_o_en; always@(posedge csi_clk) if (avs_chipselect & avs_write) coe_data_o_en <= 1'b0; else if (avs_chipselect & avs_read) coe_data_o_en <= 1'b1; assign coe_data_i = coe_data_io; assign coe_data_io = coe_data_o_en ? 8'bz : coe_data_o; //-------------------------------------- // 双向口 结束 //-------------------------------------- endmodule
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.1 Amy_S_lcd12864 IP的C源代码模块介绍
文件名称 | 功能描述 |
Amy_S_lcd12864.h | Amy_S_lcd12864 的C头文件 |
Amy_S_lcd12864.c | Amy_S_lcd12864 的C源文件 |
/*-----版权声明----- * 艾米电子工作室——让开发变得更简单 * 网站:http://www.amy-studio.com * 淘宝:http://amy-studio.taobao.com * QQ(邮箱):[email protected] *-----文件信息----- * 文件名称:Amy_S_lcd12864.h * 最后修改日期:3.20, 2009 * 描述:Lcd12864驱动宏文件 *------------------ * 创建者:张亚峰 * 创建日期:3.20, 2009 * 版本:1.0 * 描述:原始版本 *------------------ * 修改者: * 修改日期: * 版本: * 描述: *------------------- */ #ifndef __Amy_S_LCD12864_H__ #define __Amy_S_LCD12864_H__ //++++++++++++++++++++++++++++++++++++++ // 基地址 开始 // 根据SOPC Builder设置编写 //++++++++++++++++++++++++++++++++++++++ #include "system.h" #define lcd12864_addr LCD12864_BASE //-------------------------------------- // 基地址 开始 //-------------------------------------- //++++++++++++++++++++++++++++++++++++++ // 寄存器映射 开始 // 根据HDL编写 //++++++++++++++++++++++++++++++++++++++ #include #define IOWR_LCD12864_E(base, data) IOWR(base, 0, data) #define IOWR_LCD12864_RW(base, data) IOWR(base, 1, data) #define IOWR_LCD12864_RS(base, data) IOWR(base, 2, data) #define IOWR_LCD12864_DATA(base, data) IOWR(base, 3, data) #define IORD_LCD12864_DATA(base) IORD(base, 3) //-------------------------------------- // 寄存器映射 结束 //-------------------------------------- //++++++++++++++++++++++++++++++++++++++ // 管脚操作 开始 //++++++++++++++++++++++++++++++++++++++ #define SET_E IOWR_LCD12864_E(lcd12864_addr, 1) #define CLR_E IOWR_LCD12864_E(lcd12864_addr, 0) #define SET_RW IOWR_LCD12864_RW(lcd12864_addr, 1) #define CLR_RW IOWR_LCD12864_RW(lcd12864_addr, 0) #define SET_RS IOWR_LCD12864_RS(lcd12864_addr, 1) #define CLR_RS IOWR_LCD12864_RS(lcd12864_addr, 0) #define WR_DATA(data) IOWR_LCD12864_DATA(lcd12864_addr, data) #define RD_DATA IORD_LCD12864_DATA(lcd12864_addr) //-------------------------------------- // 管脚操作 结束 //-------------------------------------- //++++++++++++++++++++++++++++++++++++++ // 函数声明 开始 //++++++++++++++++++++++++++++++++++++++ extern void LCD12864_CheckBusy(void); extern void Lcd12864_WrCmd(alt_u8 cmd); extern void Lcd12864_WrData(alt_u8 data); extern void Lcd12864_Init(void); extern void Lcd12864_WrChar(alt_u8 row, alt_u8 col, alt_u8 *pCN, alt_u8 n); //-------------------------------------- // 函数声明 结束 //-------------------------------------- #endif /* __Amy_S_LCD12864_H__ */
最后面的那个</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方式的寄存器访问方式,譬如
#define CS *(volatile unsigned *) CS_BASE // 片选信号 --低有效
这种貌似更好操作。由于我没有深入研究这种寄存器访问方式,这里就不多说了。
下面的几行和各种MCU大同小异。
/*-----版权声明----- * 艾米电子工作室——让开发变得更简单 * 网站:http://www.amy-studio.com * 淘宝:http://amy-studio.taobao.com * QQ(邮箱):[email protected] *-----文件信息----- * 文件名称:Amy_S_lcd12864.c * 最后修改日期:3.20, 2009 * 描述:Lcd12864驱动源文件 *------------------ * 创建者:张亚峰 * 创建日期:3.20, 2009 * 版本:1.0 * 描述:原始版本 *------------------ * 修改者: * 修改日期: * 版本: * 描述: *------------------- */ #include "Amy_S_lcd12864.h" #include "alt_types.h" #include "unistd.h" void LCD12864_CheckBusy(void) { CLR_RS; // 指令 SET_RW; // 读 SET_E; while((RD_DATA&0x80) == 0x80); // 检测busy flag CLR_E; usleep(72); // 72us } void Lcd12864_WrCmd(alt_u8 cmd) { LCD12864_CheckBusy(); CLR_RS; // 指令 CLR_RW; // 写 SET_E; WR_DATA(cmd); CLR_E; usleep(72); // 72us } void Lcd12864_WrData(alt_u8 data) { LCD12864_CheckBusy(); SET_RS; // 数据 CLR_RW; // 写 SET_E; WR_DATA(data); CLR_E; usleep(72); // 72us } void Lcd12864_Init(void) { usleep(40*1000); Lcd12864_WrCmd(0x30); // 8bit usleep(100); Lcd12864_WrCmd(0x30); // basic function usleep(37); Lcd12864_WrCmd(0x0F); // 整体显示开 游标开 反白 usleep(100); Lcd12864_WrCmd(0x10); // 游标左移 usleep(100); Lcd12864_WrCmd(0x01); usleep(10*1000); Lcd12864_WrCmd(0x06); // 画面整体右移 } void Lcd12864_WrChar(alt_u8 row, alt_u8 col, alt_u8 *pCN, alt_u8 n) { alt_u8 i, addr; row &= 0x03; // row < 4 col &= 0x07; // col < 8 switch(row) { case 0: addr = 0x80; break; case 1: addr = 0x90; break; case 2: addr = 0x88; break; case 3: addr = 0x98; break; } addr += col; Lcd12864_WrCmd(addr); for(i=0; i<2*n; i++) { Lcd12864_WrData(pCN[i]); // 写字符数据 } }
呵呵,这个就不说明了,大家自己看。
注意:9.1环境。
从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.5。
图3.5 例化后的IP 1
注意lcd12864是随便起的名字哟,只要不叫IP的名字(Ams_S_lcd12864)就行。
图3.6 例化后的IP 2
先将Amy_S_lcd12864.h和Amy_S_lcd12864.c拷贝到软件工程内。
/*-----版权声明----- * 艾米电子工作室——让开发变得更简单 * 网站:http://www.amy-studio.com * 淘宝:http://amy-studio.taobao.com * QQ(邮箱):[email protected] *-----文件信息----- * 文件名称:main.c * 最后修改日期:3.20, 2009 * 描述:Lcd12864测试文件 *------------------ * 创建者:张亚峰 * 创建日期:3.20, 2009 * 版本:1.0 * 描述:原始版本 *------------------ * 修改者: * 修改日期: * 版本: * 描述: *------------------- */ #include // strlen() #include "Amy_S_lcd12864.h" // 根据SOPC Builder的设置,修改该头文件中的Lcd12864基地址 int main() { Lcd12864_Init(); // 初始化Lcd12864 Lcd12864_WrChar(0, 0, "这不是单片机吗?", strlen("这不是单片机吗?")>>1); Lcd12864_WrChar(1, 0, "这就是单片机呀。", strlen("这就是单片机呀。")>>1); Lcd12864_WrChar(2, 0, "艾米电子出品。", strlen("艾米电子出品。")>>1); Lcd12864_WrChar(2, 0, "艾米电子出品。", strlen("艾米电子出品。")>>1); Lcd12864_WrChar(3, 0, "Amy-studio Pub.", strlen("Amy-studio Pub.")>>1); return 0; }
最后面的那个</string.h>是发布博客的时候带出来的,不属于头文件。
图3.7 Amy_S_lcd12864 IP使用效果
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