项目简介

本项目基于Altera公司的Cyclone IV型芯片,利用NIOS II软核,2-port RAM与时序控制模块,实现64*48分辨率的显存(再大的显存板载资源m9k不够用)

实现效果如下:

VGA时序控制模块

VGA时序简介

网络上针对VGA时序的讲解已经非常多了,简单的理解,VGA主要有H_syncV_sync这两个坐标同步信号,与RGB这三个色彩信号。当H_syncV_sync达到特定的值的时候,对应一个特别的坐标(x,y)上的颜色为RGBVGA上的RGB信号是模拟信号,FPGA通常通过接入一个DA模块来实现数字信号到模拟信号的转换。我所用的板子上便是16位的RGB数模转换的解决方案。

HSYNC的时序如下

VSYNC的时序如下

针对图中的不同段的长度,可以参考以下常用的参数配置表:

在我的IP核中,通过参数定义以增加核心的通用性:

    //1024*758 60HZ
parameter H_SyncPulse=136; //HSYNC_A
parameter H_BackPorch=160; //HSYNC_B
parameter H_ActivePix=1024; //HSYNC_C
parameter H_FrontPorch=24; //HSYNC_D
parameter LinePeriod =1344; //HSYNC_E
parameter Hde_start=H_SyncPulse+H_BackPorch;
//HSYNC_A+HSYNC_B
parameter Hde_end=Hde_start+H_ActivePix;
//HSYNC_A+HSYNC_B+HSYNC_C
//1024*758 60HZ
parameter V_SyncPulse=6; //VSYNC_O
parameter V_BackPorch=29; //VSYNC_P
parameter V_ActivePix=768; //VSYNC_Q
parameter V_FrontPorch=3; //VSYNC_R
parameter FramePeriod =806; //VSYNC_S
parameter Vde_start=V_BackPorch+V_SyncPulse;
parameter Vde_end=Vde_start+V_ActivePix+V_FrontPorch;

由于板载时钟信号是一个50MHZ,利用PLL超频至65MHZ,接入板子时钟:

    pllvga mypll(.inclk0(clk),.c0(vga_clk),.areset(~rstn),.locked());

根据上面所述VGA的基本时序,可以书写VGA的时序模块:

always @ (posedge vga_clk)
if(~rstn)
x_cnt <= 1;
else if(x_cnt == LinePeriod)
x_cnt <= 1;
else
x_cnt <= x_cnt+ 1; always @ (posedge vga_clk)
begin
if(~rstn)
hsync_r <= 1'b1;
else if(x_cnt == 1)
hsync_r <= 1'b0;
else if(x_cnt == H_SyncPulse)
hsync_r <= 1'b1; if(~rstn)
hsync_de <= 1'b0;
else if(x_cnt == Hde_start)
hsync_de <= 1'b1;
else if(x_cnt == Hde_end)
hsync_de <= 1'b0;
end always @ (posedge vga_clk)
if(~rstn)
y_cnt <= 1;
else if(y_cnt == FramePeriod)
y_cnt <= 1;
else if(x_cnt == LinePeriod)
y_cnt <= y_cnt+1; always @ (posedge vga_clk)
begin
if(~rstn)
vsync_r <= 1'b1;
else if(y_cnt == 1)
vsync_r <= 1'b0;
else if(y_cnt == V_SyncPulse)
vsync_r <= 1'b1; if(~rstn)
vsync_de <= 1'b0;
else if(y_cnt == Vde_start)
vsync_de <= 1'b1;
else if(y_cnt == Vde_end)
vsync_de <= 1'b0;
end

并且可以从x_cnty_cnt中得知当前对应的xy的坐标的值:

    assign display_x = x_cnt>Hde_start?x_cnt-Hde_start:12'd0;
assign display_y = y_cnt>Vde_start?y_cnt-Vde_start:12'd0;

利用iVerilogVGA基本时序进行仿真,并利用GTKwave查看仿真结果:

可以通过仿真结果对VGA时序做进一步的理解。

VGA显存模块

上面已经分析了最基本的VGA时序,对应一个VGA显示模块来说,需要做的,就是在对应的display_xdisplay_y,通过RGB线输出该像素的颜色信息。

若想通过NIOS II核控制VGA显示内容,需要一个显存来保存整个屏幕的显示信息。但是对于1024*768的屏幕,需要的显存太大,无法在板子上实现,这里退而求其次,选择64*48的一个显存。并选择Altera2-port Memory实现这一个显存。选择存储深度为4096,字为16bit

注意在Altera的存储器中,选择对于正在赋值的块若同时读取,直接输出老的数据。

至于为何选择64*48的显存,是因为这样更好做内存的映射:

    reg[11:0] addrx;
reg[11:0] addry;
always @ (posedge vga_clk)
begin
if(~rstn)
readaddr = 0;
else if(hsync_de & vsync_de)
begin
addrx = display_x >> 4;
addry = display_y >> 4;
addry = addry << 6;
readaddr = addrx + addry;
end
else
begin
readaddr = 0;
addrx = 12'd0;
addry = 12'd0;
end
end

对于(x,y),每个坐标均向右移4位,纵轴坐标再*64 => <<6,即可得到该像素点对应显存的地址。

读出的内容,通过color_buff缓冲后输出即可。

    assign vga_r = (hsync_de & vsync_de)?vga_r_reg:5'b00000;
assign vga_g = (hsync_de & vsync_de)?vga_g_reg:6'b000000;
assign vga_b = (hsync_de & vsync_de)?vga_b_reg:5'b00000;
always @(negedge vga_clk)
begin
color_buff<=readdata;
end always @(negedge vga_clk)
if(~rstn)
begin
vga_r_reg<=0;
vga_g_reg<=0;
vga_b_reg<=0;
end
else
begin
vga_r_reg<=color_buff[15:11];
vga_g_reg<=color_buff[10:5];
vga_b_reg<=color_buff[4:0];
end

测试模块

单独测试模块选择自己初始化一个mif进行读取测试:

利用python书写生成测试填充显存的mif

f = open("test.mif",'w')
address = 0
f.write("WIDTH=16;\nDEPTH=4096;\nADDRESS_RADIX=HEX;\nDATA_RADIX=HEX;\nCONTENT BEGIN\n")
for i in range(64):
for j in range(64):
if(i < 48):
if( i % 2 == 1 and j %2 == 1):
f.write("%x:ffff;\n"%(address))
else:
f.write("%x:0000;\n"%(address))
else:
f.write("%x:0000;\n"%(address))
address = address + 1
f.write("END;\n")
f.close()

NIOSii软核接入

在通过了基本的测试模块之后,就可以接入NIOS ii软核进行写入显存测试了。这里配置软核非常非常简单,只需要留出PIO口,接入VGA的IP核即可,完成后的顶层图如下:

图中还有留有进行查错的口子没有删除,对最后工程没有影响。

NIOSii软核显示驱动

驱动大体思路

针对我设计的IP核,驱动非常好写,以图片驱动的代码作为示例:

void print_img() {
int i, j;
int addr, data;
for (i = 0; i < 48; i++) {
for (j = 0; j < 64; j++) {
IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0);
addr = i * 64 + j;
data = gImage_test[addr * 2] << 8 & 0xff00;
data |= gImage_test[addr * 2 + 1];
IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr);
IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, data);
IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1);
}
}
}

先通过WRITEADDR将显存的地址传入,再通过WRITEDATA16位的颜色数据传入,最后给WREN1即可完成一个像素的写入。通过循环对每个点进行书写便可完成一帧的画面的写入。

显示字符驱动

下面通过对一个C语言的ascii显示库调用完成字符显示驱动

void display_ascii(unsigned int x, unsigned int y,unsigned int w_color, unsigned int b_color) {
unsigned int i, j,k;
unsigned char str;
unsigned int OffSet;
unsigned int addr;
for(k = 0;lcd_buff[k]!='\0';k++){
OffSet = lcd_buff[k] * 11;
printf("%c",lcd_buff[k]);
for (i = 0; i < 11; i++) {
str = word_lib[OffSet + i];
for (j = 0; j < 8; j++) {
addr = (x + j)+k*8 + (y + i) * 64;
IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0);
IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr);
if (str & 0x80) {
IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, w_color);
} else {
IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, b_color);
}
IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1);
str <<= 1;
}
}
}
}

总结

  • 这次的显存非常小,只能显示很少的内容,但是基本设计思路是相同的。
  • Altera提供了Framebuffer,可以缓存多帧数据,还没有研究是利用什么资源进行存储的。

附:VGA模块完整代码

`timescale 1ns / 1ps
module vga_test(clk,rstn,vga_hs,vga_vs,vga_r,vga_g,vga_b,readaddr,writeaddr,writedata,wren);
input clk;
input rstn;
input [11:0]writeaddr;
input [15:0]writedata;
input wren;
output vga_hs;
output vga_vs;
output [4:0] vga_r;
output [5:0] vga_g;
output [4:0] vga_b;
output reg [11:0] readaddr; //1024*758 60HZ
parameter H_SyncPulse=136; //HSYNC_A
parameter H_BackPorch=160; //HSYNC_B
parameter H_ActivePix=1024; //HSYNC_C
parameter H_FrontPorch=24; //HSYNC_D
parameter LinePeriod =1344; //HSYNC_E
parameter Hde_start=H_SyncPulse+H_BackPorch; //HSYNC_A+HSYNC_B
parameter Hde_end=Hde_start+H_ActivePix; //HSYNC_A+HSYNC_B+HSYNC_C //1024*758 60HZ
parameter V_SyncPulse=6; //VSYNC_O
parameter V_BackPorch=29; //VSYNC_P
parameter V_ActivePix=768; //VSYNC_Q
parameter V_FrontPorch=3; //VSYNC_R
parameter FramePeriod =806; //VSYNC_S
parameter Vde_start=V_BackPorch+V_SyncPulse;
parameter Vde_end=Vde_start+V_ActivePix+V_FrontPorch; reg[10 : 0] x_cnt;
reg[9 : 0] y_cnt;
reg[15 : 0] color_buff;
reg[4 : 0] vga_r_reg;
reg[5 : 0] vga_g_reg;
reg[4 : 0] vga_b_reg;
reg hsync_r;
reg vsync_r;
reg hsync_de;
reg vsync_de;
wire[15:0] readdata;
wire[31:0] display_x;
wire[31:0] display_y;
wire vga_clk; assign vga_hs = hsync_r;
assign vga_vs = vsync_r;
assign vga_r = (hsync_de & vsync_de)?vga_r_reg:5'b00000;
assign vga_g = (hsync_de & vsync_de)?vga_g_reg:6'b000000;
assign vga_b = (hsync_de & vsync_de)?vga_b_reg:5'b00000;
pllvga mypll(.inclk0(clk),.c0(vga_clk),.areset(~rstn),.locked()); assign display_x = x_cnt>Hde_start?x_cnt-Hde_start:12'd0;
assign display_y = y_cnt>Vde_start?y_cnt-Vde_start:12'd0; vga_ram vga_buff(.data(writedata),.inclock(vga_clk),.outclock(vga_clk),.rdaddress(readaddr),.wraddress(writeaddr),.wren(wren),.q(readdata));
//vga_rom vga_rom(.address(readaddr),.clock(vga_clk),.q(readdata)); always @ (posedge vga_clk)
if(~rstn)
x_cnt <= 1;
else if(x_cnt == LinePeriod)
x_cnt <= 1;
else
x_cnt <= x_cnt+ 1; always @ (posedge vga_clk)
begin
if(~rstn)
hsync_r <= 1'b1;
else if(x_cnt == 1)
hsync_r <= 1'b0;
else if(x_cnt == H_SyncPulse)
hsync_r <= 1'b1; if(~rstn)
hsync_de <= 1'b0;
else if(x_cnt == Hde_start)
hsync_de <= 1'b1;
else if(x_cnt == Hde_end)
hsync_de <= 1'b0;
end always @ (posedge vga_clk)
if(~rstn)
y_cnt <= 1;
else if(y_cnt == FramePeriod)
y_cnt <= 1;
else if(x_cnt == LinePeriod)
y_cnt <= y_cnt+1; always @ (posedge vga_clk)
begin
if(~rstn)
vsync_r <= 1'b1;
else if(y_cnt == 1)
vsync_r <= 1'b0;
else if(y_cnt == V_SyncPulse)
vsync_r <= 1'b1; if(~rstn)
vsync_de <= 1'b0;
else if(y_cnt == Vde_start)
vsync_de <= 1'b1;
else if(y_cnt == Vde_end)
vsync_de <= 1'b0;
end always @(negedge vga_clk)
begin
color_buff<=readdata;
end always @(negedge vga_clk)
if(~rstn)
begin
vga_r_reg<=0;
vga_g_reg<=0;
vga_b_reg<=0;
end
else
begin
vga_r_reg<=color_buff[15:11];
vga_g_reg<=color_buff[10:5];
vga_b_reg<=color_buff[4:0];
end reg[11:0] addrx;
reg[11:0] addry;
always @ (posedge vga_clk)
begin
if(~rstn)
readaddr = 0;
else if(hsync_de & vsync_de)
begin
addrx = display_x >> 4;
addry = display_y >> 4;
addry = addry << 6;
readaddr = addrx + addry;
end
else
begin
readaddr = 0;
addrx = 12'd0;
addry = 12'd0;
end
end
endmodule

附:Nios ii完整代码

#include <stdio.h>
#include "system.h"
#include "altera_avalon_pio_regs.h"
#include "img.h"
#include "ascii_lib.h" unsigned char lcd_buff[256];
void display_ascii(unsigned int x, unsigned int y,unsigned int w_color, unsigned int b_color);
void clean_screen();
void print_img();
void shine();
void delay_ms(unsigned int i); int main() { clean_screen();
shine();
delay_ms(2000); clean_screen();
print_img();
delay_ms(2000); clean_screen();
sprintf((char*)lcd_buff,"Hello");
display_ascii(0,0,0xffff,0);
sprintf((char*)lcd_buff,"World");
display_ascii(0,11,0xffff,0);
sprintf((char*)lcd_buff,"VGATest");
display_ascii(0,22,0xffff,0);
delay_ms(2000);
return 0;
} void shine() {
int data, addr;
int i = 0;
int j = 0;
int k = 0;
int color = 0x238f;
for (k = 0; k < 100; k++) {
for (i = 0; i < 48; i++) {
for (j = 0; j < 64; j++) {
IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0);
addr = i * 64 + j;
if (i % 2 && j % 2)
data = color;
else
data = 0x0;
IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr);
IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, data);
IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1);
}
}
color += 0x10;
}
} void print_img() {
int i, j;
int addr, data;
for (i = 0; i < 48; i++) {
for (j = 0; j < 64; j++) {
IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0);
addr = i * 64 + j;
data = gImage_test[addr * 2] << 8 & 0xff00;
data |= gImage_test[addr * 2 + 1];
IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr);
IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, data);
IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1);
}
}
} void display_ascii(unsigned int x, unsigned int y,unsigned int w_color, unsigned int b_color) {
unsigned int i, j,k;
unsigned char str;
unsigned int OffSet;
unsigned int addr;
for(k = 0;lcd_buff[k]!='\0';k++){
OffSet = lcd_buff[k] * 11;
printf("%c",lcd_buff[k]);
for (i = 0; i < 11; i++) {
str = word_lib[OffSet + i];
for (j = 0; j < 8; j++) {
addr = (x + j)+k*8 + (y + i) * 64;
IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0);
IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr);
if (str & 0x80) {
IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, w_color);
} else {
IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, b_color);
}
IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1);
str <<= 1;
}
}
}
} void clean_screen(){
int data, addr;
int i = 0;
int j = 0;
int color = 0x0000;
for (i = 0; i < 48; i++) {
for (j = 0; j < 64; j++) {
IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 0);
addr = i * 64 + j;
data = 0x0;
IOWR_ALTERA_AVALON_PIO_DATA(WRITEADDR_BASE, addr);
IOWR_ALTERA_AVALON_PIO_DATA(WRITEDATA_BASE, data);
IOWR_ALTERA_AVALON_PIO_DATA(WREN_BASE, 1);
}
}
color += 0x10;
} void delay_ms(unsigned int i) {
unsigned int j, k;
for (j = 0; j < i; j++)
for (k = 0; k < 1000; k++);
}

[置顶] 基于FPGA的VGA简易显存设计&NIOS ii软核接入的更多相关文章

  1. 基于FPGA的VGA可移植模块终极设计【转】

    本文转载自:http://www.cnblogs.com/lueguo/p/3373643.html 略过天涯   基于FPGA的VGA可移植模块终极设计 一.VGA的诱惑 首先,VGA的驱动,这事, ...

  2. 基于FPGA的VGA可移植模块终极设计

    一.VGA的诱惑 首先,VGA的驱动,这事,一般的单片机是办不到的:由于FPGA的速度,以及并行的优势,加上可现场配置的优势,VGA的配置,只有俺们FPGA可以胜任,也只有FPGA可以随心所欲地配置( ...

  3. 基于FPGA的VGA显示设计(二)

    上一篇:基于FPGA的VGA显示设计(一)     参照 CrazyBingo 的 基于FPGA的VGA可移植模块终极设计代码  的工程代码风格,模块化处理了上一篇的代码,并增加了一点其它图形. 顶层 ...

  4. 基于FPGA的VGA显示实验设计

    基于FPGA的VGA显示实验设计 成果展示(优酷视频): 视频: 基于FPGA的VGA显示技术(手机控制) http://v.youku.com/v_show/id_XNjk4ODE3ODUy.htm ...

  5. 基于FPGA的VGA显示静态图片

    终于熬到暑假了,记过三四周的突击带考试,终于为我的大二画上了一个完整的句号,接下来终于可以静心去做自己想做的事情了,前一阵子报了一个线上培训班,学学Sobel边缘检测,之前一直在学习图像处理,但是因为 ...

  6. 基于FPGA的VGA显示设计(一)

    前言 FPGA主要运用于芯片验证.通信.图像处理.显示VGA接口的显示器是最基本的要求了. 原理 首先需要了解 : (1)VGA接口协议:VGA端子_维基百科 .VGA视频传输标准_百度 引脚1 RE ...

  7. [置顶] 基于.NET的快速信息化系统开发框架 — RDIFramework.NET — 系统目录

    基于.NET的快速信息化系统开发整合框架 —RDIFramework.NET—系统目录 框架简单介绍 .NET快速开发整合框架(RDIFramework.NET),基于.NET的快速信息化系统开发.整 ...

  8. 基于fpga的vga学习(3)

    本次学习如何通过vga发送数字.文字.字母, 首先利用建模软件,将想要发送的数据通过数学建模转换,这里我用的软件是PCtoLCD,具体效果如下 这里可以看出,建模将数据装换成0和1,一个字母用16x8 ...

  9. [置顶] 基于视频采集卡驱动的错误修改CX26828

    基于视频采集卡驱动的错误修改CX26828 1. 设置root密码 command:sudo passwd root 2.查看系统状态 输入命令:lsmod root@ubuntu:/home/yu# ...

随机推荐

  1. NOIP 2000 提高组 题解

    NOIP2000 提高组 题解 No 1. 进制转换 https://www.rqnoj.cn/problem/295 水题 对于n和基数r, 每次用n mod r, 把余数按照逆序排列 注意 mod ...

  2. Node.js原生及Express方法实现注册登录原理

    由于本文只是实现其原理,所以没有使用数据库,只是在js里面模拟数据库,当然是种中还是需要用数据库的. 1.node.js原生方法 ①html页面,非常简单,没有一丝美化~我们叫它user.html & ...

  3. LoadRunner入门(一)

    以LR自带的web系统为例(前提条件:已安装好lordrunner 11 ): 一.WebTours系统 是lordrunner自带一个飞机订票系统网站,支持IE浏览器 1. WebTours服务启动 ...

  4. iOS tableview group时头尾视图间隔大小

    解决: 一,当使用tableview的格式为group时 1.先设置 tableview.sectionHeaderHeight = 0.00001; tableview.sectionFooterH ...

  5. 【有意思的BUG】需要停止的进程

    用户操作的目的:将某网站的歌曲分享到朋友圈 用户遇到的问题:在朋友圈内,有2个入口可以播放该第三方的歌曲.但是这两个入口可以同时播放,音乐重叠了. 操作步骤: [1] 将歌曲分享到朋友圈 [2] 在朋 ...

  6. Jquery ztree树插件

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  7. Java开发工具(Eclipse中内容辅助键的使用)

    * A:Alt+/ 起提示作用* B:main+alt+/,syso+alt+/,给出其他提示* C:补充输出语句,选中需要输出的部分,alt+/选择最后一项即可* C:定义自己的alt + / * ...

  8. 字符串拼接data-id时注意事项

    今天测试下一个ajax请求,结果后台接收不到data-id的数据,导致后台无法进行正确的数据库查询. 我的评论页面是使用字符串拼接后,再放到页面里的,其中有关data-id的部分是这样的: '< ...

  9. iOS之UIWebView无法获取web标题

    最近遇到了一个问题,就是在UIWebView的代理方法里,执行document.title的js代码无法获取网页标题,代码如下: - (void)webViewDidFinishLoad:(UIWeb ...

  10. ubuntu修改grub,修改开机顺序,配置grub启动顺序

    方法一: 1.进入Ubuntu系统后打开终端 2.终端中输入以下命令 sudo gedit /boot/grub/grub.cfg 3.找到Windows的配置将其剪切到Ubuntu的前面 把下面 # ...