Abstract
在上一篇blog,我們學會了將wav檔放在SD卡上,實做出一個SD卡wav player,第一次體會出軟硬體設計的威力。由於FAT16格式的讀取,必須牽涉到軟體的動作,所以必須引入Nios II與Avalon Bus,不能再靠純硬體的方式設計。這次我們將圖片放在SD卡上,在DE2-70實做出一個簡易的數位相框。

Introduction
使用環境:Quartus II 8.1 + Nios II EDS 8.1 + DE2-70 (Cyclone II EP2C70F896C6N) + TRDB-LTM

這4篇原本是設計在一起的lab,適合初學者從0開始慢慢熟悉Quartus II、SOPC Builder、Nios II EDS、Avalon Bus Slave、Avalon Bus Master。

(原創) 如何自己用SOPC Builder建立一個能在DE2-70上跑μC/OS-II的Nios II系統? (SOC) (Quartus II) (SOPC Builder) (Nios II) (μC/OS-II) (DE2-70)
(原創) 如何設計一個七段顯示器Controller? (SOC) (Quartus II) (SOPC Builder) (Nios II) (DE2-70)
(原創) 如何設計一個SD卡Wav Player? (SOC) (Quartus II) (SOPC Builder) (Nios II) (DE2-70)
(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (DE2-70)

面臨的挑戰
要在DE2-70設計一個數位相框,面臨了幾個挑戰:
1.因為圖片是放在SD卡上,用的是FAT16格式,無論開檔、讀檔都必須透過軟體,關於SD卡使用與FAT16的讀取,在Lab 3我們已經學會,所以已經解決。

2.Altera並沒有提供LTM (touch panel) controller,所以我們必須依照Homework 2的model自己開發一個LTM Controller,這也是本次Lab的重點。

『在DE2-70 CD的範例中,是否有接近的範例可以參考而加以修改呢?』

DE2_70_D5M_LTM範例中,提供了一個相近的範例,不過他的input是500萬像素cmos,而不是SD卡。

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

SDRAM之前屬於CMOS部分,與本Lab無關,可以不用理他,SDRAM為frame buffer,影像就是存在這裡。LTM Controller與Data Request就是LCD Timing Generator部分,這部分可以保留下來繼續用。

由於讀取SD卡與FAT16需要軟體配合,勢必加上Nios II CPU與Avalon Bus,此時這裡的Multi-Port SDRAM Controller與LTM Controller就很尷尬了,因為他是個純硬體的Controller,無法掛在Avalon Bus上,所以必須將這2個controller換掉。

Altera有提供SDRAM Controller可用,在Lab1 ~ Lab3我們都使用過Altera SOPC版的SDRAM Controller,但Altera並沒有提供SOPC版的LTM Controller,這就得靠我們自己寫了。最後架構如下圖所示:

系統架構圖

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

 

Master Interface
Slave Interface已經在Lab 2 七段顯示器Controller練習過,主要提供C語言透過Nios II對controller設定register。LTM Controller雖也提供slave interface,但主要的影像傳輸使用的是Master Interface

為什麼要使用Master Interface呢?
由於影像放在SDRAM,當成LTM的frame buffer,LTM必須以33MHz不斷對SDRAM要資料,對LTM做掃描顯示,在Lab 2我曾經說過,master與slave的差別在於Master能夠自己發起傳輸,Slave則必須由Master控制而被動的發起傳輸,若影像傳輸使用Slave Interface,則Nios II CPU必須不斷的介入,命令資料從SDRAM搬到LTM,如此Nios II CPU將非常忙碌,所以比較理想的方式是LTM Controller支援Master Interface,這樣LTM Controller就能主動地讀取SDRAM,不需Nios II CPU介入

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

硬體部分
Step 1:
使用lab4_files.7z內的DE2_70_LTM_NIOS project

將DE2_70_LTM_NIOS複製到c:\DE2-70下

Step 2:
使用Lab4_files.zip內的LTM_Controller

將LTM_Controller複製到c:\DE2-70\DE2_70_LTM_NIOS\ip目錄下

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

Step 3:
開發LTM_Controller.v

57行,請補上FIFODEPTH參數,這是設定FIFO的大小
59行,請補上FIFOFULLVALUE參數,這是設定當FIFO還剩下多少時,就認為FIFO已經滿了。
109行,請補上fifo_full的條件。

最後完整程式如下所示:
LTM_Controller.v / Verlilog

1 /*
2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3
4 Filename : LTM_Controller.v
5 Compiler : Quartus II 8.1
6 Description : LTM Controller for Avalon Bus
7 Release : 12/18/2008 1.0
8  */
9
10 `default_nettype none
11
12  module LTM_Controller (
13 // Avalon clock interface siganals
14   input csi_clockreset_clk, // avalon-mm clk
15   input csi_clockreset_reset_n, // avalon-mm reset_n
16 // Signals for Avalon-MM master port
17   output [DATAWIDTH-1:0] avm_m1_address, // avalon-mm master address
18   output [BYTEENABLEWIDTH-1:0] avm_m1_byteenable, // avalon-mm master byteenable
19   output avm_m1_read, // avalon-mm master read
20   input avm_m1_readdatavalid, // avalon-mm master readdatavalid
21   input [DATAWIDTH-1:0] avm_m1_readdata, // avalon-mm master readdata
22 input avm_m1_waitrequest, // avalon-mm master waitrequest
23 // Signals for Avalon-MM slave port
24 input [ADDRWIDTH-1:0] avs_s1_address, // avalon-mm slave address
25 input [BYTEENABLEWIDTH-1:0] avs_s1_byteenable, // avalon-mm slave byteenable
26 input avs_s1_write, // avalon-mm slave write
27 input [DATAWIDTH-1:0] avs_s1_writedata, // avalon-mm slave writedata
28 input avs_s1_read, // avalon-mm slave read
29 output [DATAWIDTH-1:0] avs_s1_readdata, // avalon-mm slave readdata
30 // LTM couduit input
31 input coe_ltm_export_iCLK_50, // 50MHz
32 input coe_ltm_export_iRST0, // reset delay 0
33 input coe_ltm_export_iRST2, // reset delay 2
34 // LTM couduit output
35 output coe_ltm_export_oLTM_CLK, // ltm clk
36 output [LTM_DATAWIDTH-1:0] coe_ltm_export_oR, // ltm R
37 output [LTM_DATAWIDTH-1:0] coe_ltm_export_oG, // ltm G
38 output [LTM_DATAWIDTH-1:0] coe_ltm_export_oB, // ltm B
39 output coe_ltm_export_oHD, // ltm h.sync
40 output coe_ltm_export_oVD, // ltm v.sync
41 output coe_ltm_export_oDEN, // ltm data enable
42 output coe_ltm_export_oSCLK, // ltm I2S clk
43 inout coe_ltm_export_ioSDAT, // ltm I2S data
44 output coe_ltm_export_oSCEN, // ltm I2S clk enable
45 output coe_ltm_export_oFIFO_FULL, // for debug use only (ltm fifo empty)
46 output coe_ltm_export_oFIFO_EMPTY // for debug use only (ltm fifo full)
47 );
48
49 // LTM parameter
50 parameter LTM_DATAWIDTH = 8; // ltm data width
51
52 // Avalon-MM parameter
53 parameter DATAWIDTH = 32; // width of databus
54 parameter BYTEENABLEWIDTH = 4; // width of byteenable
55 parameter ADDRESSBASE = 32'h0080_0000; // SDRAM frame buffer start address
56 parameter LENGTH = 800 * 480; // LTM width : 800, height = 480
57 parameter FIFODEPTH = 8192; // FIFO depth : number of words
58 parameter FIFODEPTHLOG2 = 13; // FIFO width of depth
59 parameter FIFOFULLVALUE = 195; // FIFO full value
60 parameter ADDRWIDTH = 3; // avalon-mm slave address width
61
62 localparam FIFOALMOSTFULL = FIFODEPTH - FIFOFULLVALUE; // FIFO almost full value
63
64 // LTM wire
65 wire ltm_nclk; // ltm clk
66 wire read; // sdram read request
67
68 // fifo wire
69 wire fifo_wrclk; // fifo write clk
70 wire fifo_aclr; // fifo asynchronous clk
71 wire fifo_wrreq; // fifo write request
72 wire [DATAWIDTH-1:0] fifo_wrdata; // fifo write data
73 wire fifo_rdclk; // fifo read clk
74 wire fifo_rereq; // fifo read request
75 wire [DATAWIDTH-1:0] fifo_rddata; // fifo read data
76 wire fifo_empty; // fifo empty
77 wire fifo_full; // fifo full
78 wire [FIFODEPTHLOG2-1:0] fifo_used; // fifo data used
79
80 // internal logic
81 reg [DATAWIDTH-1:0] address; // address of sdram
82 wire [DATAWIDTH-1:0] end_address; // end address of sdram
83 wire is_address_sload; // is sload of address?
84 wire is_increment_address; // is increment address of sdram?
85 reg [FIFODEPTHLOG2-1:0] reads_pending; // pending reads
86
87 // master output
88 assign avm_m1_address = address; // avalon-mm master address
89 assign avm_m1_byteenable = 4'b1111; // avalon-mm master byteenable (32 bit)
90 assign avm_m1_read = (!fifo_full); // avalon-mm master read
91
92 // export output
93 assign coe_ltm_export_oLTM_CLK = ltm_nclk; // ltm clk
94 assign coe_ltm_export_oFIFO_FULL = fifo_full; // for debug use only (ltm fifo full)
95 assign coe_ltm_export_oFIFO_EMPTY = fifo_empty; // for debug use only (ltm fifo empty)
96
97 // fifo input
98 assign fifo_wrclk = csi_clockreset_clk; // fifo write clk
99 assign fifo_aclr = ~csi_clockreset_reset_n; // fifo asynchronous clear
100 assign fifo_wrreq = avm_m1_readdatavalid; // fifo write request
101 assign fifo_wrdata = avm_m1_readdata; // fifo write data
102 assign fifo_rdclk = ltm_nclk; // fifo read clk
103 assign fifo_rereq = read; // fifo read request
104
105 // internal logic
106 assign end_address = ADDRESSBASE + LENGTH * BYTEENABLEWIDTH; // end address of sdram
107 assign is_address_sload = (address == end_address); // is sload of address?
108 assign is_increment_address = (!fifo_full) && (!avm_m1_waitrequest); // is increment address of sdram?
109 assign fifo_full = (fifo_used + reads_pending) > (FIFOALMOSTFULL); // is fifo full
110
111 // address register
112 always@(posedge csi_clockreset_clk, negedge csi_clockreset_reset_n) begin
113 if (!csi_clockreset_reset_n)
114 address <= 0;
115 else begin
116 if (address == 0) // initailize
117 address <= ADDRESSBASE;
118 else if (is_address_sload) // is sload of address?
119 address <= ADDRESSBASE;
120 else if (is_increment_address) // is increment address of sdram?
121 address <= address + BYTEENABLEWIDTH;
122 end
123 end
124
125 // reads_pending register
126 always@(posedge csi_clockreset_clk, negedge csi_clockreset_reset_n) begin
127 if (!csi_clockreset_reset_n)
128 reads_pending <= 0;
129 else begin
130 if (is_increment_address) begin // is increment address of sdram?
131 if (!avm_m1_readdatavalid)
132 reads_pending <= reads_pending + 1'b1;
133 end
134 else begin
135 if (avm_m1_readdatavalid) // read data valid
136 reads_pending <= reads_pending - 1'b1;
137 end
138 end
139 end
140
141 // ltm clk
142 ltm_pll ltm_pll0 (
143 .inclk0(coe_ltm_export_iCLK_50), // 50MHz
144 .c0(ltm_nclk) // 33MHz
145 );
146
147 // I2S ltm config
148 lcd_3wire_config wire0 (
149 // Host Side
150 .iCLK(coe_ltm_export_iCLK_50), // 50MHz
151 .iRST_n(coe_ltm_export_iRST0), // reset delay 0
152 // 3 wire Side
153 .o3WIRE_SCLK(coe_ltm_export_oSCLK), // I2S clk
154 .io3WIRE_SDAT(coe_ltm_export_ioSDAT),// I2S data
155 .o3WIRE_SCEN(coe_ltm_export_oSCEN) // I2s clk enable
156 );
157
158 // tcon controller
159 touch_tcon tcon0 (
160 .iCLK(ltm_nclk), // 33MHz
161 .iRST_n(coe_ltm_export_iRST2), // reset delay 2
162 // sdram side
163 .iREAD_DATA1({1'b0, fifo_rddata[15:11], fifo_rddata[7:0], 2'b00}), // G[9:5]B[9:0]
164 .iREAD_DATA2({1'b0, fifo_rddata[10:8], 2'b00, fifo_rddata[23:16], 2'b00}), // G[4:0]R[9:0]
165 .oREAD_SDRAM_EN(read), // sdram read request
166 // lcd side
167 .oLCD_R(coe_ltm_export_oR), // ltm R
168 .oLCD_G(coe_ltm_export_oG), // ltm G
169 .oLCD_B(coe_ltm_export_oB), // ltm B
170 .oHD(coe_ltm_export_oHD), // ltm h. sync
171 .oVD(coe_ltm_export_oVD), // ltm v.sync
172 .oDEN(coe_ltm_export_oDEN) // ltm data enable
173 );
174
175 // ltm fifo
176 dcfifo # (
177 .lpm_width(DATAWIDTH), // data width of fifo
178 .lpm_numwords(FIFODEPTH), // fifo depth of fifo
179 .lpm_widthu(FIFODEPTHLOG2) // fifo width of depth
180 ) fifo0 (
181 .wrclk(fifo_wrclk), // fifo write clk
182 .aclr(fifo_aclr), // fifo asynchronous clear
183 .wrreq(fifo_wrreq), // fifo write request
184 .data(fifo_wrdata), // fifo write data
185 .rdclk(fifo_rdclk), // fifo read clk
186 .rdreq(fifo_rereq), // fifo read request
187 .q(fifo_rddata), // fifo read data
188 .rdempty(fifo_empty), // fifo empty
189 .wrusedw(fifo_used) // fifo used data
190 );
191
192 endmodule

LTM Controller使用的是效率較佳的Master Pipelined Read Transfer

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

(1) 此時Master開始對Avalon Bus做讀取的動作,因此對Avalon Bus發出addr1信號並將read信號拉high。此時剛好碰到Avalon Bus給master的waitrequest為high,表示Avalon Bus正在忙碌中,因此addr1與read必須再多delay 1個clk。

(2) 此時Avalon Bus已經不忙碌,所以Avalon Bus將waitrequest拉low,此時Avalon Bus接受了Master所發出的addr1與read信號。

(3) 此時Avalon Bus不忙碌,所以Avalon Bus的waitrequest為low,此時Avalon Bus接受了下一筆Master所發出的addr2與read信號。在此同時,Avalon Bus發給Master的readdatavalid為high信號,表示Master可從Avalon Bus去讀取addr1的數據data1。

(4) 此時Avalon Bus不忙碌,所以Avalon Bus的waitrequest為low,此時Avalon Bus接受了下一筆Master所發出的addr3與read信號,所以在此時已經有兩筆數據pending在Avalon Bus還未傳輸。

(5) 此時Avalon Bus發給Master的readdatavalid為high,所以Master從Avalon Bus讀取數據 (data2)。

(6) 此時Avalon Bus發給Master的readdatavalid為low,所以Master無法從Avalon Bus讀取數據。在此同時,Master對Avalon Bus發出addr1、read與flush為high信號,告訴Avalon Bus讀取第4筆數據並且放棄讀取pending在Avalon Bus而尚未讀取的數據(data3)

(7) 此時Avalon Bus發給Master的readdatavalid為high,所以Master從Avalon Bus讀取數據(data4),因為data3以前被放棄。

Step 4:
開發HAL

目前僅提供一個HAL,就是讓C語言可以指定x, y座標與該pixel的RGB,如此才能將讀取的bmp圖片寫進SDRAM frame buffer。

第9行,請補上offset的計算方式。

最後完整程式如下所示:

ltm.c / C

1 /*
2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3
4 Filename : ltm.c
5 Compiler : Nios II 8.1
6 Description : LTM API for LTM Controller
7 Release : 12/18/2008 1.0
8 */
9
10 #include "alt_types.h" // alt_u32, alt_u8
11 #include "ltm.h"
12 #include "ltm_regs.h" // register map of ltm controller
13
14 void ltm_write_rgb_to_pixel(alt_u32 base, alt_u16 x, alt_u16 y, alt_u8 r, alt_u8 g, alt_u8 b) {
15 alt_u32 offset;
16 alt_u32 data;
17
18 offset = ((y * LTM_WIDTH)+(x)) * BYTEENABLEWIDTH;
19
20 data = g & 0xff;
21 data = (data << 8) | (r & 0xff);
22 data = (data << 8) | (g & 0xff);
23 data = (data << 8) | (b & 0xff);
24
25 IOWR_LTM_RGB(base + offset, data);
26 }

Step 5:
使用SOPC Builder將IP打包

在Lab 2我們已經學會將七段顯示器controller打包的方法,使用相同的方式將LTM controller打包成ip。唯一不同的是,因為LTM Controller的module較多,所以必須加入多個.v檔。注意Top Level Module是LTM_Controller.v

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70) 

建立成功會在左上角顯示LTM_Controller。

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70) 

Step 6:
加入Pipeline Bridge

在Quartus II 7.1之後,提出了Pipeline Bridge架構,可以將master與slave間的address、write、writedata、read、readdata等信號加上pipeline stage,可以提高整個系統的Fmax,更詳細的解釋請參考: (原創) 如何使用Pipeline Bridge增進Nios II系統的Fmax? (SOC) (Quartus II) (Nios II) (SOPC Builder) (DE2-70)

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

Step 7:
加入LTM Controller

接受原來LTM_Controller.v預設的parameter即可。

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

由於ltm是透過pipeline_bridge與sdram相連。所以將

ltm的master與pipeline_bridge_ltm的slave相連。
pipeline_bridge_ltm的master與sdram的slave相連。
ltm的slave與CPU的data_master相連。

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

設定LTM Controller的Bus Arbitration Rule
由於LTM的更新速度很快(33MHz),所以LTM Contoller需要不斷的對SDRAM要資料顯示,因此預設的bus佔有率已經無法滿足LTM的正常顯示,必須加以調整。

顯示Arbitration

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

將Arbitration Rule調85,表示LTM將占據SDRAM 85%的頻寬。

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

事實上,LTM要能穩定的顯示,有三個重要的參數彼此影響:

1.LTM Controller的Bus Arbitration Rule
2.LTM Controller的FIFO depth
3.LTM Controller的FIFO almost full value

我花了很多時間去tune這三個參數讓LTM的顯示穩定,若3個參數沒調好,可能會造成LTM影像上下shift或者上下左右不斷的輪播..等等問題。

最後Auto-Assign Base Address,解決address overlap的錯誤。

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

按Generate開始產生SOPC System。

Step 8:
Quartus II編譯與Programmer燒入

top module部分我就沒要求同學修改,Quartus II直接編譯即可,不過還是建議同學花時間看看top module是怎麼寫的。

軟體部分
Step 9:
Import hello_world_0與hello_world_0_syslib
Run As Hardware

整個程式的架構與Lab 3的DE2_70_SD_Card_Audio_Player類似,只是Wav Lib改成Bmp Lib,Audio HAL改成LTM HAL。程式難度不高,同學可自行研究。

唯一要同學加上的是140行,使用我們自己寫的ltm_write_rgb_to_pixel()將RGB載入SDRAM。

最後完整程式如下所示:

hello_world.c / C

1 /*
2 (C) OOMusou 2008 http://oomusou.cnblogs.com
3
4 Filename : hello_world.c
5 Compiler : Nios II 8.1
6 Description : main() for photo frame
7 Release : 12/18/2008 1.0
8 */
9 #include <stdio.h>
10 #include "my_includes.h"
11 #include "my_types.h"
12 #include "FatFileSystem.h" // FAT16 lib
13 #include "SDCardDriver.h" // SDCard HAL
14 #include "bitmap.h" // bmp lib
15 #include "ltm.h" // LTM HAL
16
17 #define WAITING_SEC 5
18 // bmp file list parameter
19 #define MAX_FILE_NUM 128 // maximum file number in file list
20 #define FILENAME_LEN 32 // length of file name
21
22 typedef struct {
23 int filenum;
24 char filename[MAX_FILE_NUM][FILENAME_LEN];
25 } BmpFileList;
26
27 static BmpFileList bmp_file_list;
28
29 // bmp parameter
30 #define BMP_WIDTH 800
31 #define BMP_HEIGHT 480
32 #define BMP_RGB_OFST 54
33 #define NUM_RGB 3
34 #define OFST_R 2
35 #define OFST_G 1
36 #define OFST_B 0
37
38 // wait sdcard insert into socket
39 void wait_sdcard_insert(void) {
40 bool bFirstTime2Detect = TRUE;
41
42 while(!SD_card_init()) {
43 if (bFirstTime2Detect){
44 printf("Please insert SD card.\n");
45 bFirstTime2Detect = FALSE;
46 }
47 }
48
49 printf("Find SD card.\n");
50 }
51
52 // build bmp file list
53 int build_bmp_play_list(void) {
54 int filecnt = 0; // number of bmp
55 FAT_BROWSE_HANDLE hFileBrowse; // FAT browse handle
56 FAT_DIRECTORY Directory; // FAT directory
57 FAT_FILE_HANDLE hFile; // FAT file handle
58 alt_u8 header[BMP_RGB_OFST];
59 char filename[FILENAME_LEN];
60
61 bmp_file_list.filenum = 0;
62
63 // FatFileSystem.h
64 if (!Fat_FileBrowseBegin(&hFileBrowse)){
65 printf("browse file fail.\n");
66 return 0;
67 }
68
69 // FatFileSystem.h
70 while (Fat_FileBrowseNext(&hFileBrowse, &Directory)) {
71 // only bmp in file list
72 if (strncmpi(Directory.Extension, "BMP", 3))
73 continue;
74
75 // compose filename
76 Fat_ComposeFilename(&Directory, filename);
77
78 // fopen() (FatFileSystem.h)
79 if (!Fat_FileOpen(&hFile, filename)) {
80 printf("bmp file open fail.\n");
81 continue;
82 }
83
84 // fread() (FatFileSystem.h)
85 if (!Fat_FileRead(&hFile, header, sizeof(header))) {
86 printf("bmp file read fail.\n");
87 continue;
88 }
89
90 // fclose() (FatFileSystem.h)
91 Fat_FileClose(&hFile);
92
93 // convert to bmp header (bitmap.h)
94 BitmapHeader *bmp_header = get_bmp_header(header);
95
96 // check valid bmp format
97 if (!chk_valid_bmp(bmp_header)) {
98 printf("%s is invalid bmp for DE2-70 LTM.\n", filename);
99 continue;
100 }
101
102 // copy filename into file list
103 strcpy(bmp_file_list.filename[filecnt], filename);
104 filecnt++;
105 } // while
106
107 bmp_file_list.filenum = filecnt;
108
109 return filecnt;
110 }
111
112 // play bmp by filename
113 bool play_bmp(char *filename){
114 FAT_FILE_HANDLE hFile; // FAT file handle
115
116 // fopen() (FatFileSystem.h)
117 if (!Fat_FileOpen(&hFile, filename)){
118 printf("Fat_FileOpen fail.\n");
119 return FALSE;
120 }
121
122 printf("BMP file name is %s.\n", filename);
123
124 if (!Fat_FileSeek(&hFile, FILE_SEEK_BEGIN, BMP_RGB_OFST)) {
125 printf("Fat_FileSeek fail.\n");
126 return FALSE;
127 }
128
129 // bmp RGB array
130 alt_u8 buff_bmp[BMP_WIDTH * BMP_HEIGHT * NUM_RGB];
131
132 // fread() (FatFileSystem.h)
133 if (!Fat_FileRead(&hFile, buff_bmp, sizeof(buff_bmp))) {
134 printf("Fat_FileRead fail.\n");
135 return FALSE;
136 }
137
138 // move to sdram frame buffer
139 int x, y;
140 for(x=0; x < BMP_WIDTH; x++) {
141 for(y=0; y < BMP_HEIGHT; y++) {
142 // get RGB
143 int r = buff_bmp[(y * BMP_WIDTH + x) * NUM_RGB + OFST_R];
144 int g = buff_bmp[(y * BMP_WIDTH + x) * NUM_RGB + OFST_G];
145 int b = buff_bmp[(y * BMP_WIDTH + x) * NUM_RGB + OFST_B];
146
147 // write rgb to sdram frame buffer (ltm.h)
148 ltm_write_rgb_to_pixel(SDRAM_BASE, x,y, r, g, b);
149 }
150 }
151
152 printf("Finsh reading FAT16\n");
153 // file close
154 Fat_FileClose(&hFile);
155
156 return TRUE;
157 }
158
159 int main() {
160 int play_index = 0;
161 alt_u8 filename[FILENAME_LEN];
162
163 // check SD card
164 wait_sdcard_insert();
165
166 // Mount SD-CARD
167 if (!Fat_Mount(FAT_SD_CARD)) {
168 printf("SD card mount fail.\n");
169 return -1;
170 }
171
172 // build wave list in gWavePlayList
173 if (build_bmp_play_list() == 0) {
174 printf("There is no bmp file in the root directory of SD card.\n");
175 return -1;
176 }
177
178 while(1) {
179 strcpy(filename, bmp_file_list.filename[play_index]);
180
181 if (!play_bmp(filename)){
182 printf("bmp play fail.\n");
183 return -1;
184 }
185
186 play_index++;
187
188 // repeat
189 if (play_index >= bmp_file_list.filenum)
190 play_index = 0;
191
192 // waiting 5 sec between bmp
193 printf("Waiting %d sec...\n", WAITING_SEC);
194 usleep(WAITING_SEC * 1000 * 1000);
195 }
196
197 return 0;
198 }
199

lab4_files.7z提供了3張bmp圖檔,請將這3個圖檔copy到SD卡根目錄,即可開始測試。每張照片中顯示會間格5秒鐘。

希志あいの

 (原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

七海なな

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

瑤瑤

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

若你要使用自己的bmp也可以,但有幾個限制:
1.使用24位元的bmp且不能壓縮。
2.大小為800 * 480,配合LTM的解析度。

Step 10:
將軟體與硬體存到Flash

從Lab 1到Lab 3,每次要執行Nios II程式,一定得經過Quartus II燒入sof,並且用Nios II EDS Run As Hardware,才能將elf載入到SDRAM或SRAM執行,對於一個完整的嵌入式產品,不可能每次都要靠PC將軟體與硬體傳入DE2-70。所幸DE2-70提供了兩個Flash,儘管斷電之後,資料仍然存在,如此只要一Power on後,就可以執行Nios II程式,不需要PC的介入。

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

接下來我們會將硬體sof放到epcs_flash,將軟體elf放到cfi_flash,這樣以後只要電源按鈕按下,就可以執行我們的數位相框,不再需要PC了。

Step 11:
使用Nios II EDS的Flash Programmer

Nios II EDS
Tools -> Flash Programmer

新增一個configuration

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70)

若成功會出現以下訊息: 

#!/bin/sh
#
# This file was automatically generated by the Nios II IDE Flash Programmer.
#
# It will be overwritten when the flash programmer options change.
#

cd C:
/DE2-70/DE2_70_LTM_NIOS/software/hello_world_0/Debug

# Creating .flash file
for the FPGA configuration
"$SOPC_KIT_NIOS2/bin/sof2flash" --epcs --input="C:/DE2-70/DE2_70_LTM_NIOS/DE2_70
.sof" --output="DE2_70.flash"
Info: *******************************************************************
Info: Running Quartus II Convert_programming_file
Info: Command: quartus_cpf
--no_banner --convert --device=EPCS128 --option=DE2_7
0.opt C:/DE2-70/DE2_70_LTM_NIOS/DE2_70.sof DE2_70.pof
Info: Quartus II Convert_programming_file was successful.
0 errors, 0 warnings
Info: Peak
virtual memory: 72 megabytes
Info: Processing ended: Wed Dec
24 01:00:31 2008
Info: Elapsed time:
00:00:03
Info: Total CPU time (on all processors):
00:00:03
Info:
*******************************************************************
Info: Running Quartus II Convert_programming_file
Info: Command: quartus_cpf
--no_banner --convert DE2_70.pof DE2_70.rpd
Info: Quartus II Convert_programming_file was successful.
0 errors, 0 warnings
Info: Peak
virtual memory: 66 megabytes
Info: Processing ended: Wed Dec
24 01:00:33 2008
Info: Elapsed time:
00:00:02
Info: Total CPU time (on all processors):
00:00:02

# Programming flash with the FPGA configuration
"$SOPC_KIT_NIOS2/bin/nios2-flash-programmer" --epcs --base=0x09000000 --sidp=0x0
90008a8
--id=1669606734 --timestamp=1230048124 "DE2_70.flash"
Using cable
"USB-Blaster [USB-0]", device 1, instance 0x00
Resetting and pausing target processor: OK
Reading System ID at address
0x090008A8: verified

: Checksumming existing contents

00000000 : Verifying existing contents

00010000 : Verifying existing contents

00020000 : Verifying existing contents

00030000 : Verifying existing contents

00040000 : Verifying existing contents

00050000 : Verifying existing contents

00060000 : Verifying existing contents

00070000 : Verifying existing contents

00080000 : Verifying existing contents

00090000 : Verifying existing contents

00000000 : Reading existing contents

00010000 : Reading existing contents

00020000 : Reading existing contents

00030000 : Reading existing contents

00040000 : Reading existing contents

00050000 : Reading existing contents

00060000 : Reading existing contents

00070000 : Reading existing contents

00080000 : Reading existing contents

00090000 : Reading existing contents

Checksummed
/read 640kB in 15.4s

00000000 ( 0%): Erasing

00010000 (10%): Erasing

00020000 (20%): Erasing

00030000 (30%): Erasing

00040000 (40%): Erasing

00050000 (50%): Erasing

00060000 (60%): Erasing

00070000 (70%): Erasing

00080000 (80%): Erasing

00090000 (90%): Erasing

Erased 640kB
in 5.9s (108.4kB/s)

00000000 ( 0%): Programming

00010000 (10%): Programming

00020000 (20%): Programming

00030000 (30%): Programming

00040000 (40%): Programming

00050000 (50%): Programming

00060000 (60%): Programming

00070000 (70%): Programming

00080000 (80%): Programming

00090000 (90%): Programming

Programmed 589KB
+51KB in 13.5s (47.4KB/s)
Did not attempt to verify device contents
Leaving target processor paused

# Creating .flash file
for the project
"$SOPC_KIT_NIOS2/bin/elf2flash" --base=0x0a800000 --end=0xaffffff --reset=0xa800
000 --input="hello_world_0.elf" --output="cfi_flash.flash" --boot="C:/altera/81/
ip/altera/nios2_ip/altera_nios2/boot_loader_cfi.srec"

# Programming flash with the project
"$SOPC_KIT_NIOS2/bin/nios2-flash-programmer" --base=0x0a800000 --sidp=0x090008a8
--id=1669606734 --timestamp=1230048124 "cfi_flash.flash"
Using cable
"USB-Blaster [USB-0]", device 1, instance 0x00
Resetting and pausing target processor: OK
Reading System ID at address
0x090008A8: verified

: Checksumming existing contents

00000000 : Verifying existing contents

00002000 : Verifying existing contents

00004000 : Verifying existing contents

00006000 : Verifying existing contents

00008000 : Verifying existing contents

0000A000 : Verifying existing contents

0000C000 : Verifying existing contents

0000E000 : Verifying existing contents

00010000 : Verifying existing contents

Checksummed
/read 81kB in 1.9s
Erase not required

00000000 ( 0%): Programming

00002000 ( 0%): Programming

00004000 ( 0%): Programming

00006000 ( 0%): Programming

00008000 ( 0%): Programming

0000A000 (
0%): Programming

0000C000 (
0%): Programming

0000E000 (
0%): Programming

00010000 ( 0%): Programming

Programmed 81KB
in 0.0s
No change to device contents
Leaving target processor paused

重新Power On,就可以看到數位相框自動執行了!!

(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (TRDB-LTM) (DE2-70) 

完整程式碼下載
lab4_files.7z (一個未完成的半成品,可以根著本文一步一步完成)
DE2_70_LTM_NIOS.7z (最後完整的結果)

Question
(這是我當時給學生的homework,各位有興趣可以自己自做做看)

1.雖然設定每張圖片間隔5秒鐘顯示,為什麼實際執行時,圖片與圖片的間格時間超過5秒鐘呢?

2.目前LTM controller的SDRAM base address使用的是Verilog的parameter,請改用register的方式,讓Nios II的C語言可以透過Slave Interface動態更改SDRAM的base address。

3.目前每一次都要重新從SD卡讀取像片至SDRAM,很花時間,請試著改成只有第一次需從SD卡讀取相片至SDRAM,以後就不必從SD卡讀取。(有很多種方法,請發揮你的創意)。

4.請結合(原創) 如何設計一個SD卡Wav Player? (SOC) (Quartus II) (SOPC Builder) (Nios II) (DE2-70)並搭配μC/OS-II,讓DE2-70可以同時當數位像框,並可播放音樂。

Conclusion
很多人想將原本純硬體的設計加上Nios II CPU變成HW/SW Co-Design,最常見的問題就是SDRAM要怎麼讓軟硬體共用,若你還是一直執著於使用SDRAM_Control_4Port那條路,將永遠無法解決,既然打算掛上Nios II CPU,就要引進Avalon Bus概念,我歸納出3點:

1.使用Avalon Bus將整個系統的Nios II CPU與SDRAM切開。

2.使用Altera為Avalon Bus所提供的SDRAM Controller,而不要使用SDRAM_Control_4Port。

3.為你自己的硬體週邊寫Master或Slave Controller。

如此整個系統才會變成SOPC Enable,整體的效能才會好,當然所付出的代價就是你要去了解Avalon-MM Interface Specification ,並且慢慢的調試。

See Also
(原創) 如何自己用SOPC Builder建立一個能在DE2-70上跑μC/OS-II的Nios II系統? (SOC) (Quartus II) (SOPC Builder) (Nios II) (μC/OS-II) (DE2-70)
(原創) 如何設計一個七段顯示器Controller? (SOC) (Quartus II) (SOPC Builder) (Nios II) (DE2-70)
(原創) 如何設計一個SD卡Wav Player? (SOC) (Quartus II) (SOPC Builder) (Nios II) (DE2-70)
(原創) 如何設計一個數位相框? (SOC) (Quartus II) (SOPC Builder) (Nios II) (DE2-70)
(原創) 如何使用Pipeline Bridge增進Nios II系統的Fmax? (SOC) (Quartus II) (Nios II) (SOPC Builder) (DE2-70)

Reference
Avalon-MM Interface Specification

相关文章: