(原創) 如何在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的目錄架構應該如下所示:

de2_lcm_ccd_sram_00
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

1 #ifndef __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行

#ifndef __CCD_CONTROLLER_REGS_H
#define __CCD_CONTROLLER_REGS_H


#endif

為header guard,為C/C++常用的技巧,可避免header file重複載入。

第6行

#define CCD_CONTROLLER_PIO_KEY_REG               0
#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

1 #ifndef  CCD_CONTROLLER_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

1 #include "CCD_Controller_API.h"
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

1 C_LIB_SRCS   += CCD_Controller_API.c
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。

de2_lcm_ccd_sram_01

加上μ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

1 #include "includes.h"
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 "includes.h"
#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行

void task1(void* pdata) {
 
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行

void task2(void* pdata) {
 
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

posted on 2008-02-01 09:53  真 OO无双  阅读(11702)  评论(62编辑  收藏  举报

导航