(原創) 如何在DE2將CCD影像顯示在彩色LCD? (Nios II軟體篇 + μC/OS-II + SRAM + 驅動程式) (IC Design) (DE2) (Nios II) (μC/OS-...

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行

None.gif #ifndef __CCD_CONTROLLER_REGS_H
None.gif
#define __CCD_CONTROLLER_REGS_H
None.gif
None.gif
None.gif
#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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值