概述:此处,我以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
/*-----版权声明----- * 艾米电子工作室——让开发变得更简单 * 网站:http://www.amy-studio.com * 淘宝:http://amy-studio.taobao.com * QQ(邮箱):amy-studio@qq.com *-----文件信息----- * 文件名称: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
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
/*-----版权声明-----
* 艾米电子工作室——让开发变得更简单
* 网站:http://www.amy-studio.com
* 淘宝:http://amy-studio.taobao.com
* QQ(邮箱):amy-studio@qq.com
*-----文件信息-----
* 文件名称: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__ */
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方式的寄存器访问方式,譬如
#define CS *(volatile unsigned *) CS_BASE // 片选信号 --低有效
这种貌似更好操作。由于我没有深入研究这种寄存器访问方式,这里就不多说了。
下面的几行和各种MCU大同小异。
2.2 C源文件
2.2.1 Amy_S_lcd12864.c
/*-----版权声明-----
* 艾米电子工作室——让开发变得更简单
* 网站:http://www.amy-studio.com
* 淘宝:http://amy-studio.taobao.com
* QQ(邮箱):amy-studio@qq.com
*-----文件信息-----
* 文件名称: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]); // 写字符数据
}
}
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
/*-----版权声明-----
* 艾米电子工作室——让开发变得更简单
* 网站:http://www.amy-studio.com
* 淘宝:http://amy-studio.taobao.com
* QQ(邮箱):amy-studio@qq.com
*-----文件信息-----
* 文件名称: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;
}
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