(原創) 如何在DE2將CCD影像顯示在彩色LCD? (Nios II軟體篇 + μC/OS-II + SRAM + 驅動程式) (IC Design) (DE2) (Nios II) (μC/OS-II) (SOPC Builder) (TRDB-LCM)
Abstract
前一篇(原創) 如何在DE2將CCD影像顯示在彩色LCD上? (Nios II軟體篇 + onchip memory) (IC Design) (DE2) (Nios II) (SOPC Builder),討論了透過Nios II軟體控制CCD和彩色LCD,實作出簡易的數位相機,本文將以其為基礎,繼續加上OS和驅動程式,並且執行在SRAM上。
使用環境:Quartus II 7.2 SP1 + Nios II 7.2 SP1 + DE2(Cyclone II EP2C35F627C6) + TRDB_LCM + TRDB_DC2
Introduction
前一篇(原創) 如何在DE2將CCD影像顯示在彩色LCD上? (Nios II軟體篇 + onchip memory) (IC Design) (DE2) (Nios II) (SOPC Builder),雖然已經達到軟硬體整合,由軟體控制硬體,但仍有幾個缺憾:
1.仍使用on-chip memory
實務上很少on-chip memory,在DE2,若選擇功能最少、速度最慢的Nios II/e,配合最少的controller,不寫任何Verilog,可以壓榨出49K的on-chip memory,但49K仍然非常的小,軟體再稍微大一點,或者加上作業系統,on-chip memory就不夠用了,所以實務上一定得使用DE2上的SRAM、SDRAM或Flash。
2.尚未提供驅動程式(Device Driver)
Nios II軟體直接透過IORD()與IOWR()直接存取register,嚴格來說還不算驅動程式,一般來說,軟體應用程式、軟體驅動程式、硬體程式會由三個不同的工程師負責,理論上負責寫軟體驅動程式的工程師應該提供register map,然後再提供高階的API給軟體應用程式工程師使用,而不是由軟體應用程式工程師直接存取register。
3.尚未加上作業系統(OS)
為了實現多工,有些人可能想在DE2上跑μC/OS-II或μCLinux,而Nios II EDS已經直接支援了μC/OS-II。
所以本文將在前一篇的基礎下,繼續補足這三個缺憾。
開發CCD驅動程式
Step 1:
建立DE2_LCM_CCD_sram_ucosii目錄
將前一個DE2_LCM_CCD_onchip目錄下所有檔案複製到此目錄下,若你沒有DE2_LCM_CCD_onchip專案,請下載DE2_LCM_CCD_onchip.7z。
Step2:
為CCD_Controller建立子目錄
根據Altera對驅動程式的規定[1],一個controller的目錄架構應該如下所示:
Fig.1 SOPC controller directory structure
altera_avalon_jtag_uart為controller名稱,外層的inc目錄放的是C的.h檔,作為register map,硬體的Verilog程式將放在HAL子目錄下,而HAL下的inc將放驅動程式的.h檔,src則為驅動程式實際的.c檔。
為什麼要依照這個目錄架構呢?
在Quartus II 6.x的Component Editor,還可以手動指定.c與.h的檔案位置,但Quartus II 7.2的Component Editor已經無此設定,而是依賴制式的目錄架構,由Nios II EDS自動幫你抓.c和.h, 若不這樣寫,Nios II EDS會抓不到你自己寫的驅動程式路徑。
事實上在C:\altera\72\ip\sopc_builder_ip\目錄下所有的controller,都是依照這個目錄架構,而且皆為open source,Altera也鼓勵你研究內建controller的source code。
請依照上圖的目錄架構為CCD_Controller加上HAL、inc、src等子目錄。
Step 3:
撰寫register map
DE2_LCM_CCD_sram_ucosii\ip\CCD_Controller\inc\CCD_Controller_regs.h
2 #define __CCD_CONTROLLER_REGS_H
3
4 #include <io.h>
5
6 #define CCD_CONTROLLER_PIO_KEY_REG 0
7 #define IOADDR_CCD_CONTROLLER_PIO_KEY(base) __IO_CALC_ADDRESS_NATIVE(base, CCD_CONTROLLER_PIO_KEY_REG)
8 #define IORD_CCD_CONTROLLER_PIO_KEY(base) IORD(base, CCD_CONTROLLER_PIO_KEY_REG)
9 #define IOWR_CCD_CONTROLLER_PIO_KEY(base, data) IOWR(base, CCD_CONTROLLER_PIO_KEY_REG, data)
10
11
12 #define CCD_CONTROLLER_PIO_SW_REG 0
13 #define IOADDR_CCD_CONTROLLER_PIO_SW(base) __IO_CALC_ADDRESS_NATIVE(base, CCD_CONTROLLER_PIO_SW_REG)
14 #define IORD_CCD_CONTROLLER_PIO_SW(base) IORD(base, CCD_CONTROLLER_PIO_SW_REG)
15 #define IOWR_CCD_CONTROLLER_PIO_SW(base, data) IOWR(base, CCD_CONTROLLER_PIO_SW_REG, data)
16
17 #define CCD_CONTROLLER_CCD_KEY_REG 0
18 #define IOADDR_CCD_CONTROLLER_CCD_KEY(base) __IO_CALC_ADDRESS_NATIVE(base, CCD_CONTROLLER_CCD_KEY_REG)
19 #define IORD_CCD_CONTROLLER_CCD_KEY(base) IORD(base, CCD_CONTROLLER_CCD_KEY_REG)
20 #define IOWR_CCD_CONTROLLER_CCD_KEY(base, data) IOWR(base, CCD_CONTROLLER_CCD_KEY_REG, data)
21
22 #define CCD_CONTROLLER_CCD_SW_REG 1
23 #define IOADDR_CCD_CONTROLLER_CCD_SW(base) __IO_CALC_ADDRESS_NATIVE(base, CCD_CONTROLLER_CCD_SW_REG)
24 #define IORD_CCD_CONTROLLER_CCD_SW(base) IORD(base, CCD_CONTROLLER_CCD_SW_REG)
25 #define IOWR_CCD_CONTROLLER_CCD_SW(base, data) IOWR(base, CCD_CONTROLLER_CCD_SW_REG, data)
26
27 #endif
第1行
#define __CCD_CONTROLLER_REGS_H
#endif
為header guard,為C/C++常用的技巧,可避免header file重複載入。
第6行
#define IOADDR_CCD_CONTROLLER_PIO_KEY(base) __IO_CALC_ADDRESS_NATIVE(base, CCD_CONTROLLER_PIO_KEY_REG)
#define IORD_CCD_CONTROLLER_PIO_KEY(base) IORD(base, CCD_CONTROLLER_PIO_KEY_REG)
#define IOWR_CCD_CONTROLLER_PIO_KEY(base, data) IOWR(base, CCD_CONTROLLER_PIO_KEY_REG, data)
根據Altera建議[2],register map應該以<component name>_regs.h的方式命名,所以命名為CCD_Controller_regs.h,並且應該提供以下macro。
1.讀取register功能,並使用以下命名方式:
IORD_<component name>_<register name> (component base address)
IOWR_<component name>_<register name> (component base address, data)
2.提供register位址,並使用以下命名方式:
IOADDR_<component name>_<register name> (component base address)
3.register個別bit的mask和offset,並使用以下命名方式:
<component name>_<register name>_<name of field>_MSK
<component name>_<register name>_<name of field>_OFST
由於這次並沒有需要存取register個別bit,所以沒有提供mask和offset。
或許你會問,為什麼要提供register map呢?[3]
這是Computer Science最常用的一個手法:abstraction,如TCP/IP七層架構,Web Application的N-Tier,Web Application的CSS,用的都是這種手法,多了一個register map後,使驅動程式和硬體多了一層緩衝,讓驅動程式不必與硬體綁死,不必直接將register位址寫死在驅動程式中,若將來硬體換了,只需更改register map即可,驅動程式完全不需修改。
Step 4:
撰寫API
寫軟體應用程式的人都知道,API是他們與硬體溝通的終極方式,透過API才能與硬體溝通,現在我們就要撰寫API供應用程式存取。
DE2_LCM_CCD_sram_ucosii\ip\CCD_Controller\HAL\inc\CCD_Conotroller_API.h
2 #define CCD_CONTROLLER_API_H
3
4 unsigned char read_pio_key();
5 unsigned int read_pio_sw();
6 void write_ccd_key(unsigned char key);
7 void write_ccd_sw(unsigned int sw);
8
9 #endif
定義了高階的API,讓軟體程式開發者將來只需#include "CCD_Controller_API.h"就可用簡單的方式存取KEY、SW與CCD。
DE2_LCM_CCD_sram_ucosii\ip\CCD_Controller\HAL\src\CCD_Controller_API.c
2 #include "system.h"
3 #include "CCD_Controller_regs.h"
4
5 unsigned char read_pio_key() {
6 return IORD_CCD_CONTROLLER_PIO_KEY(KEY_PIO_BASE);
7 }
8
9 unsigned int read_pio_sw() {
10 return IORD_CCD_CONTROLLER_PIO_SW(SW_PIO_BASE);
11 }
12
13 void write_ccd_key(unsigned char key) {
14 IOWR_CCD_CONTROLLER_CCD_KEY(CCD_CONTROLLER_INST_BASE, key);
15 }
16
17 void write_ccd_sw(unsigned int sw) {
18 IOWR_CCD_CONTROLLER_CCD_SW(CCD_CONTROLLER_INST_BASE, sw);
19 }
實作CCD_Controller_API.h所定義的API,直接使用CCD_Controller_regs.h所定義的register map,並且使用根據nios_ii_system.ptf所產生的system.h,KEY_PIO_BASE, CCD_CONTROLLER_INST_BASE等都是定義在system.h中。
Step 5:
撰寫makefile
DE2_LCM_CCD_sram_ucosii\ip\CCD_Controller\HAL\src\component.mk
2 ASM_LIB_SRCS +=
3 INCLUDE_PATH +=
Nios II EDS在編譯時自動將component.mk整合至top-level makefile,各變數意義如下:[4]
C_LIB_SRCS | 列出需要被編譯至system library的.c檔,若有多的.c檔,須自己指定之。 |
ASM_LIB_SRCS | 列出需要被編億至system library的組合語言檔。 |
INCLUDE_PATH | 列出需include的目錄,預設會加入<component>/HAL/inc目錄。 |
這樣就完成了驅動程式的撰寫。
使用SRAM
詳細的加入步驟請參考(原創) 如何自己用SOPC Builder建立一個能在DE2上跑μC/OS-II的Nios II系統? (IC Design) (DE2) (Quartus II) (Nios II) (SOPC Builder) (μC/OS-II),這裡僅列出SOPC Builder所加入的controller。
加上μC/OS-II
詳細的加入步驟請參考(原創) 如何自己用SOPC Builder建立一個能在DE2上跑μC/OS-II的Nios II系統? (IC Design) (DE2) (Quartus II) (Nios II) (SOPC Builder) (μC/OS-II),這裡僅列出程式碼。
hello_world.c
2 #include <stdio.h>
3 #include "CCD_Controller_API.h"
4
5 /* Definition of Task Stacks */
6 #define TASK_STACKSIZE 2048
7 OS_STK task1_stk[TASK_STACKSIZE];
8 OS_STK task2_stk[TASK_STACKSIZE];
9
10 /* Definition of Task Priorities */
11 #define TASK1_PRIORITY 1
12 #define TASK2_PRIORITY 2
13
14 /* Detect KEY & SW input */
15 void task1(void* pdata) {
16 while (1) {
17 unsigned char key = read_pio_key();
18 unsigned int sw = read_pio_sw();
19
20 write_ccd_sw(sw);
21 write_ccd_key(key);
22
23 OSTimeDlyHMSM(0, 0, 0, 1);
24 }
25 }
26
27 /* Prints "Hello DE2_CCD_LCM" and sleeps for three seconds */
28 void task2(void* pdata) {
29 while (1) {
30 printf("Hello DE2_LCM_CCD\n");
31 OSTimeDlyHMSM(0, 0, 3, 0);
32 }
33 }
34
35 /* The main function creates two task and starts multi-tasking */
36 int main(void) {
37 OSTaskCreateExt(task1,
38 NULL,
39 (void *)&task1_stk[TASK_STACKSIZE],
40 TASK1_PRIORITY,
41 TASK1_PRIORITY,
42 task1_stk,
43 TASK_STACKSIZE,
44 NULL,
45 0);
46
47
48 OSTaskCreateExt(task2,
49 NULL,
50 (void *)&task2_stk[TASK_STACKSIZE],
51 TASK2_PRIORITY,
52 TASK2_PRIORITY,
53 task2_stk,
54 TASK_STACKSIZE,
55 NULL,
56 0);
57
58 OSStart();
59 return 0;
60 }
第1行
#include <stdio.h>
#include "CCD_Controller_API.h"
可以發現#include變得很精簡,除了"includes.h"是μC/OS-II要用,而<stdio.h>為ANSI C lib,另外只需在加上"CCD_Controller_API.h"即可,應用程式開發者不再需要其他更低階的.h檔(system.h、io.h、CCD_Controller_regs.h皆不需要)。
為什麼Nios II EDS找的到"CCD_Controller_API.h"呢?完全依賴之前所寫的makefile component.mk,和精心安排的目錄架構所賜。
15行
while (1) {
unsigned char key = read_pio_key();
unsigned int sw = read_pio_sw();
write_ccd_sw(sw);
write_ccd_key(key);
OSTimeDlyHMSM(0, 0, 0, 1);
}
}
應用程式可以使用較高階的read_pio_key() API存取硬體,也不用在理會base address是多少,這才是驅動程式所該做的。
28行
while (1) {
printf("Hello DE2_LCM_CCD\n");
OSTimeDlyHMSM(0, 0, 3, 0);
}
}
使用另外一個task顯示Hello DE2_LCM_CCD,展現μC/OS-II的能力,多工的應用其實還很多,例如可以結合友晶另外一個SD_PLAYER範例,讓數位相機邊拍照還可以邊聽音樂。
完整程式碼下載
DE2_LCM_CCD_sram.7z (不含μC/OS-II版本)
DE2_LCM_CCD_sram_ucosii.7z (含μC/OS-II 版本)
Conclusion
『如何在DE2將CCD影像顯示在彩色LCD』系列將告一段落,從純Verilog硬體 -> C軟體 -> μC/OS-II一步一步的深入探討,也是我幾個月來研究Nios II與SOPC的心得報告,其中心路歷程在(原創) 程式生涯最艱苦的戰役:開發DE2上CCD驅動程式 (IC Design) (DE2) (Nios II)有詳細的記載,透過這個小小的範例,讓我們體會到如何自己寫Verilog硬體,並用C撰寫驅動程式,由應用程式端呼叫驅動程式控制硬體,這是一個很神奇的旅程,若你也在研究Nios II,希望我的經驗對你有所幫助。
See Also
(原創) 程式生涯最艱苦的戰役:開發DE2上CCD驅動程式 (IC Design) (DE2) (Nios II)
原創) 如何自己用SOPC Builder建立一個能在DE2上跑μC/OS-II的Nios II系統? (IC Design) (DE2) (Quartus II) (Nios II) (SOPC Builder) (μC/OS-II)
(原創) 如何在DE2將CCD影像顯示在彩色LCD上? (純硬體篇) (IC Design) (DE2)
(原創) 如何在DE2將CCD影像顯示在彩色LCD上? (Nios II軟體篇 + onchip memory) (IC Design) (DE2) (Nios II) (SOPC Builder)
Reference
[1] Nios II Software Developer's Handbook P.7-19
[2] Nios II Software Developer's Handbook P.7-4
[3] Nios II Software Developer's Handbook P.7-5
[4] Nios II Software Developer's Handbook P.7-22