2010年5月29日 星期六

linux USB hid driver cypress_m8.c

參考 USB Device Class Definition for Human Interface Devices(HID)
firmware spec version 1.11,範例 driver 是 USB Cypress M8 driver
~linux/drivers/usb/serial/cypress_m8.c,source code 中說有
文件就在 ~linux/Documentation/usb/usb-serial.txt。

主要是看 DeLorme Earthmate USB 的 usb hid 如何設定,她使用的
是 feature report 的方式,在 control pipe 傳送 5 bytes 資料。
對應到 USB HID spec 的 7.2 Class-specific Requests 裡面的 7.2.1
Get_Report Request 跟 7.2.2 Set_Report Request,透過 control
pipe 傳送 host <-> device 資料。

呼叫 usb_control_msg(),定義在 USB core 的實作中
~linux/drivers/usb/core/message.c:


/**
* usb_control_msg - Builds a control urb, sends it off and waits for completion
* @dev: pointer to the usb device to send the message to
* @pipe: endpoint "pipe" to send the message to
* @request: USB message request value
* @requesttype: USB message request type value
* @value: USB message value
* @index: USB message index value
* @data: pointer to the data to send
* @size: length in bytes of the data to send
* @timeout: time in msecs to wait for the message to complete before timing
* out (if 0 the wait is forever)
*
* Context: !in_interrupt ()
*
...
*/
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
__u8 requesttype, __u16 value, __u16 index, void *data,
__u16 size, int timeout)



有兩種用法,差異在於傳輸方向,是以 host 為主體而有 in/out, set/get 不同。
一個是 host 傳到 device(out/set),另一個是 device 到 host(in/get)。

host->device 是 USB_DIR_OUT,使用 HID_REQ_SET_REPORT request
建立 send endpoint pipe 是用 usb_sndctrlpipe()

device->host 是 USB_DIR_IN,使用 HID_REQ_GET_REPORT request
建立 receive endpoint pipe 是用 usb_rcvctrlpipe()


retval = usb_control_msg(port->serial->dev,
usb_sndctrlpipe(port->serial->dev, 0),
HID_REQ_SET_REPORT,
USB_DIR_OUT | USB_RECIP_INTERFACE | USB_TYPE_CLASS,
0x0300, 0, feature_buffer,
feature_len, 500);

retval = usb_control_msg(port->serial->dev,
usb_rcvctrlpipe(port->serial->dev, 0),
HID_REQ_GET_REPORT,
USB_DIR_IN | USB_RECIP_INTERFACE | USB_TYPE_CLASS,
0x0300, 0, feature_buffer,
feature_len, 500);


第一個參數就是 struct usb_device *dev,probe 時就可以取到
第二個是利用 MACRO 從 usb device 找到 usb device 的 endpoint pipe
第三個參數是 message request value,HID_REQ_SET_REPORT 或
HID_REQ_GET_REPORT
第四個是 message request type value,
USB_DIR_OUT | USB_RECIP_INTERFACE | USB_TYPE_CLASS, 或
USB_DIR_IN | USB_RECIP_INTERFACE | USB_TYPE_CLASS,

第五個參數是 message value 看 device hard code: 0x0300
第六個參數是 message index value hard code: 0

因為採用的是 feature report 的方式傳送 5 bytes 資料存在
第七個參數 feature_buffer 中,
第八個參數是 feature_len 是資料長度。
第九個參數是 500 micro seconds 的 timeout。

相關文章:

TI OMAP3 DM3730 USB host controller high full low speed?



相關連結:

http://www.usb.org/developers/devclass_docs/HID1_11.pdf

~linux/drivers/usb/serial/cypress_m8.c

~linux/drivers/usb/core/message.c

2010年5月28日 星期五

linux kernel read_proc() in procfs

在 ~linux/fs/proc/generic.c 中找 __proc_file_read() 的實作可以看到相關說明:
/*
* How to be a proc read function
* ------------------------------
* Prototype:
* int f(char *buffer, char **start, off_t offset,
* int count, int *peof, void *dat)
*
* Assume that the buffer is "count" bytes in size.
*
裡面有三種讀 read_proc 的方式說明,底下就是說明的程式碼,
也可以搜尋其他 driver使用 read_proc 的範例。
比如 ~linux/drivers/char/efirtc.c。
*/
n = dp->read_proc(page, &start, *ppos,
count, &eof, dp->data);
...
這邊是當 kernel 讀取 proc 檔呼叫 driver read_proc callback 的地方,也就是用法。因此可以倒推回去我們的 callback function 如何實作:

第一種是 driver 要回傳的資料量小於 read_proc 中的 buffer size(PAGE_SIZE)。
此時不管 *start,將 driver 要傳給 filesystem 的資料擺在 buffer 的 offset 位置,我們只看 offset 跟上次 callback 讀到的 buffer size, n 的差值,在接到的 offset 跟剩餘要傳的 n 差值(n - offset) 為零時,告知 filesystem 我們的 driver 已經傳完資料,才設定 *peof = 1; 否則繼續 callback。

第二種是當你有大量資料要傳輸時使用。
將 *start 設定為介於 buffer 跟零 之間的值,資料是從 buffer 起始位址開始填,return 每次要填的資料長度,如果你的資料還沒填完,第二次進入 read_proc 時 offset 值會增加 *start 的長度,直到你填完資料後再設定 *peof = 1; 就可以完成傳輸大量資料了。

第三種跟第一種一樣,只是把 offset 代換成 *start

參考文獻:
  1. procfs-guide.pdf 3.1 Reading data
  2. linux kernel source: ~linux/fs/proc/generic.c, ~linux/drivers/char/efirtc.c
  3. Linux Modules (3) - procfs

2010年5月27日 星期四

Google 的十項工作哲學

據說 Google 在面對未來的展望時,會以下列十項原則做為行動基準,摘譯如下。

我們奉為真理的十項行動準則:
一、 專注在使用者經驗上,接下來該做的事情自然清楚明白
二、最好是聚焦在一件事情上,然後做到最、最、最好
三、快比慢好,當有技術突破時,我們的搜尋速度每每創新紀錄。
四、民主機制在網路上行得通
五、不一定要在辦公桌才能得到答案,這是行動計算的時代。
六、不一定要黑心才能賺錢
七、永遠有更多的資訊供你去發現
八、對於資訊的渴求沒有 國界 之分
九、不一定要穿西裝才能認真。成功不是因為表面功夫做得好,強調團隊成果與個人成就感、自尊心才是重點。
十、 偉大仍不夠好。把某件事情做得偉大隻是起點,而非終點。我們總是設定超出目前能力範圍的工作目標,如此我們才能讓自己永續成長茁壯。

參考文獻:

boot code, bootloader, and Linux Kernel entry points

菠蘿麵包整理了 Android 開機流程 boot sequence
給個 boot rom, bootloader, Linux kernel, Android Init, Zygote, Dalvik,, System Server, 最後發出
ACTION_BOOT_COMPLETED 的 braodcast intent 觸發需要知道開機啟動的服務,其中從
硬體到使用者接觸到的應用程式的完整流程。

我們關注的是前面這段觀念:
硬體平台上的 boot ROM 儲存了 boot code,boot rom 本身也是在記憶體上執行,arm cpu
藉由 reset 0x0000, 0000 由 chip select 線路連結的開機媒介決定到哪一種 storage (Nor flash,
 Nand flash) 去讀取 boot loader。 將 boot loader 載入到實體記憶體後開始執行 boot loader ...

booting sequence documentation 則提到在 bootloader 到 linux kernel 端的開機介面
與流程:

ARM Linux kernel 對 bootloader 所需要的,就是藉由 bootloader 將 Linux kernel 從
storage 讀出載入到記憶體,設定某些暫存器,並呼叫 Linux kernel entry point,此
時還不需要啟動 MMU。

 Linux kernel 的 zImage 壓縮檔,就必須要跳到 arch/arm/boot/compressed/head.S
會使用  arch/arm/boot/compressed/misc.c (抄自 gzip 的某些函式介面) 裡面的
decompress_kernel() 其中會呼叫 arch_decomp_setup() 設定 debug FF/ST uart port,
或特殊的 Chip Select memory mapping,接著在開機印出 "Uncompressing Linux..."
字串後進行解壓縮。

ARM Linux boot sequence

另外關於 arm linux 的開機流程也有人寫了程式碼分析文:
ARM Linux boot sequence

zImage 解壓縮階段:
從跟 boot loader 串接,關閉 cpu cache, MMU, 設定 stack, kernel entry point 在實體
記憶體位置,設定 cpu 型號辨識,啟用 cpu cache, MMU, 設定 MMU page table
將 kernel zImage 解壓縮並載入到 RAM,進入 kernel start...

ARM 相關的 kernel code:
查詢 cpu 型號,啟用多核心支援,查詢(主機)板子的 machine 型號,MACHINE_DESC
macro 的定義。建立 MMU 的 page table,enable MMU,初始化 cache, write buffer。
繼續 enable MMU,設定 page table pointer (TTB),找到 page table 起始點,切換
MMU 進入 virtual address space, 回到已經過 mmap 過的 switch data。

複製 data segment 到 RAM
清空 BSS(寫零)
跳到 start_kernel 開始執行 Linux kernel 開機程序。也就是接 Intel 文件中的 C 程式碼
起始點。 

Linux kernel entry points

Intel 的 device driver debugging 可以找到幾個 Linux kernel source entry point。

開發新平台(OS Adaptation, OS bring up, customize core components)
其中一項重要的工作就是寫驅動程式,有些多媒體編解碼的最佳化,
也是在驅動程式端實作。

一些有用的 kernel 資訊如:active kernel threads, loaded kernel modules

x86 開機流程中 BIOS->OS boot loader --start_kernel()-->OS

sched_init() 是 scheduler initialization

mwait_idle() 是 OS 主要的迴圈(就像 micro controller 的 main loop)


相關連結:

Device Driver Debugging on Intel Atom processor based devices (pdf)

2010年5月24日 星期一

電影:超速先生

週末尾聲全家看著 MOD 的電影,超速先生,轉到時已經開播不知多久,但因為是安東尼霍普金斯主演,才繼續看下去。結局也不出所望的是部好電影,並且是根據真人真事改編而成的電影。

要說劇中主角的人生觀,可以套用安東尼霍普金斯在幕後花絮的一句話代表:現在的我可以掌控自己的人生,並且是一個非常快樂的人。

在完全無預期下看完本片,只能用驚奇連連來形容本片的劇情鋪設,每每以為伯特蒙羅即將因為某些挫折、困難而打道回府,但都在他的堅持、毅力,與樂觀不懈的態度、實際嘗試後迎刃而解,也因為他不斷的累積並一步一步改進自己的愛車,而在年屆 68 歲,騎著 47 年車齡的 Indian 達到世界最速車的紀錄頭銜,他在 1967 年創下的 1000cc 最速紀錄至今無人能破。

伯特蒙羅在家鄉期間,花了 20 年針對在 1920 年買的 Indian 重機進行改裝提升性能,1938 年起就陸續在紐西蘭創下八項紀錄,並在後續遠渡重洋到 Bonneville Salt Flats 位在美國猶它州的波尼維鹽原創下世界紀錄。他在鹽原十次參賽中,總共創下三項紀錄,其中的 1000cc 最速紀錄仍然高掛在榜首位置。

參考文獻:
  1. 超速先生 電影中文網站
  2. Burt Munro 維基百科

2010年5月8日 星期六

cross compile Android native C code

當需要建立一些指令列工具,需要自行 build code 在 Android
command line 上執行,或供應用程式透過 JNI 呼叫的函式庫時,
需要 cross compile android native C program

1. agcc, 一個幫你搞定 cross compile 所需複雜參數的 perl script,依照
Compiling for Android wiki 上的步驟,應該就可以成功

2. 如果幸運的話,你可以不用往下看,不然就繼續吧,假如 agcc
不能達要你的需求,網路上也有神人已經寫了 Makefile 再加上 1. 裡
面的其他設定設好,以及你所使用平台的 C runtime library,在
~android/bionic/libc/arch-arm/bionic 底下 copy crtbegin_dynamic.S,
crtend.S 到你的 utility source 目錄,一起 link 就可以從正確的 entry symbol
__start 執行。否則直接拿 x86 cross compile 給 arm 平台的程式時
會有類似以下的警告訊息:

warning: cannot find entry symbol _start; defaulting to 000082c8

3. add scripts to ease rebuild native C code and installation,要將 utility
包 到 system.img 時需要先移除 ~android/out/target/product/arm/system.img
再將新加的 utility 放到 ~android/out/target/product/arm/system/bin
重新 build android,因為前面已經將其他 object files build 完,所以只剩下
archive system.img 的工作。

因此再借用神人的 my_command_line utility Makefile 改成如下:


# ripped from http://forum.xda-developers.com/archive/index.php/t-623976.html
AR = arm-eabi-ar
AS = arm-eabi-as
CC = arm-eabi-gcc
CXX = arm-eabi-c++
LD = arm-eabi-ld

ANDROID_SOURCE_ROOT = /path/to/your/androidsource
NDK_KIT = $(ANDROID_SOURCE_ROOT)/ndk/
PLATF_KIT = build/platforms/android-4

ARM_INC = $(NDK_KIT)/$(PLATF_KIT)/arch-arm/usr/include
ARM_LIB = $(NDK_KIT)/$(PLATF_KIT)/arch-arm/usr/lib

PLATF_INC = $(NDK_KIT)/$(PLATF_KIT)/common/include

OUT_DIR = $(ANDROID_SOURCE_ROOT)/out/target/product/arm
OUT_SYSTEM_BIN = $(OUT_DIR)/system/bin/
ALL_SYSTEM_IMG = $(OUT_DIR)/system.img $(OUT_DIR)/obj/PACKAGING/systemimage_unopt_intermediates/system.img

PROJECT = my_command_line
SRCS = $(PROJECT).c util.c
OBJS = $(PROJECT).o util.o crtbegin_dynamic.o crtend.o
EXES = $(PROJECT)

all: $(EXES)
file $(EXES)

install: $(EXES)
cp -afv my_command_line $(OUT_SYSTEM_BIN)
cd ../; rm -fv $(ALL_SYSTEM_IMG)
@echo "Please re-run build_android.sh to rebuild system.img"

$(EXES): $(OBJS)
$(LD) \
--entry=_start \
--dynamic-linker /system/bin/linker -nostdlib \
-rpath /system/lib -rpath $(ARM_LIB) \
-L $(ARM_LIB) -lc $(OBJS) -o $(EXES)

$(OBJS): $(SRCS)
$(CC) -I $(ARM_INC) -I $(PLATF_INC) -c $(SRCS)
$(CC) -mthumb-interwork -o crtbegin_dynamic.o -c crtbegin_dynamic.S
$(CC) -mthumb-interwork -o crtend.o -c crtend.S
#crtbegin_dynamic.S and crtend.S copy from bionic/libc/arch-arm/bionic

clean:
rm -f $(OBJS) $(EXES)


另外寫程式要注意的是 ~android/bionic/libc/README 提到跟 standard C
library 差異處與特性:
- no support for locales
- no support for wide chars (i.e. multi-byte characters)
- its own smallish implementation of pthreads based on Linux futexes
- support for x86, ARM and ARM thumb CPU instruction sets and kernel interfaces

4. Thinker 發表的 Android Build System 分析一文相當值得細讀,善用
Android makefile 的 build system 可以省掉去瞭解這些編譯的細節。

5. Android Source Code 中的 NDK 也詳細介紹了使用 Android Java VM
的 JNI 介面與實作 shared library 的建議,在
~android/ndk/docs/OVERVIEW.TXT 中有說明,Google 一下也可以找到
中文翻譯資料,我就不翻了。 :P

結論是網路上神人很多,Google 搜尋跟這些神人也很熟。:P

相關連結:

Android 原生(Native) C 開發之八: Toolchain 環境搭建篇

從 gpio & I2C 硬體線路對應到檔案讀寫

0. 硬體電路的 schema
找到電路圖上的 gpio, i2c 線路圖上的 block 是接到 datasheet 上哪一個 slot

1. datasheet 中的 gpio chapter
針對 gpio controller 的 register 值依照 in/out, pull up/down, data 值設定

2. board initial entry point,比如 Samsung s3c6410
~linux/arch/arm/mach-s3c64xx/mach-smdk6410.c
MACHINE_START macro 定義 board information
可以看到註冊的 .init_machine 進入點 function 是 smdk6410_machine_init

Qualcomm 的 arm board initial 檔案則是在
~linux/arch/arm/mach-msm/board-msm7x27.c
定義了 QCT MSM7x2x (7225)
~linux/arch/arm/mach-msm/board-msm7x30.c
定義了 QCT MSM7x30
~linux/arch/arm/mach-msm/board-qsd8x50.c
定義了 QCT QSD8X50 (8250)

3. i2c
關於 i2c 參考 Documentation/i2c/instantiating-devices,可以看到 mach-smdk6410.c 定義兩個 i2c slot 0, 1 為 struct i2c_board_info,裡面定義這個 bus 上接的 i2c slave device(s) 名稱與 slave address 資訊,透過 S3C_EINT 對應到外部中斷 12,然後在 smdk6410_machine_init():

i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));

註冊兩個 i2c bus,0 跟 1。接下來 i2c device 的具體化由 i2c core 完成。

kernel driver 可以在 ~linux/drivers/i2c 目錄找到,可以使用 i2c-dev,直接 sudo modprobe -a i2c-dev,就可以在 /dev/i2c-0 存取 i2c bus。

參考 ~linux/Documentation/i2c/dev-interface 可以針對 /dev/i2c-0[1] 的 device file 使用 user space driver 的 ioctl 控制 i2c 設定與傳輸。

最簡單的動作是 ioctl(file, I2C_SLAVE, addr),就直接對 file read(), write() 就可以讀寫 i2c device 資料。

4. gpio
關於取得需要使用的 gpio 方式如下:

gpio_request(S3C64XX_GPN(5), "LCD power");
gpio_request(S3C64XX_GPF(13), "LCD power");
gpio_request(S3C64XX_GPF(15), "LCD power");

這是指取用 GPN pin 5 跟 GPF pin 13, pin 15 操作 LCD power

~linux/Documentation/gpio.txt 可以用 /sys/class/gpio/ 底下針對 gpiochip (controller) 或是 gpioXX (pin) 進行 user driver 的控制 in/out, high/low, trigger mode /sys/class/gpio/gpiochip0/ 中的 label 可以對應到 datasheet 的 controller 名稱,參考 arch/arm/mach-s3c64xx/include/mach/gpio.h 可以找到 S3C64XX_GPA, S3C64XX_GPB 等 gpio controller

S3C64XX_GPIO_A_NR (8) 定義 Bank A 有 8 根 gpio 腳位,依此類推對於 /sys/class/gpio/gpioXX/value read/write 就可以讀取 gpio 腳位high/low 或設定 high/low


2010年5月7日 星期五

Keil C C51 語法

1. 加上記憶體型態的變數宣告順序:

變數型態 記憶體型態 變數名稱;

變數型態指一般 C 語言的 int, char 等

記憶體型態指 code, data, bdata, xdata, pdata 等六種記憶體位置
,各型態記憶體位置,以 Silicon Labs C8051F320/1 為例:

內部記憶體:
0x00~0xFF 都可使用間接定址存取資料的 data 記憶體

data 記憶體內:
0x00 ~ 0x1F 是一般暫存器 0x20~0x2F 給 bit/bytes 直接定址 bdata
0x30~0x7F 是直接定址記憶體
0x80~0xFF 是給 sfr(special function register) 只能間接定址 Stack
Pointer 可以是在 data 記憶體內定址的 256 bytes 區塊

外部記憶體:pdata
0x0000~0x03FF 是 1K 的 pdata
0x0400~0x07FF 是 1K 的 USB FIFO

16K Flash 記憶體:xdata, code

0x0000~0x3DFF 是 16K 的 In-System Programmable 記憶體
也是 firmware update 使用區,參考 PSCTL 暫存器的 PSWE、
PSEE 位元保護寫入與讀取的動作。

0x3DFF~0x3E00 是 Security Lock Byte,理論上可以鎖定 64K
bytes 的連續記憶體

Security Lock Byte 可以指定要鎖定的 512 bytes
(page 0 = 0x0000~0x01FF)

鎖定的區塊比未鎖定區塊有較高優先權,可以讀、寫、刪鎖定與未鎖定的記憶體區塊。鎖定是防止透過 C2 interface 讀寫 flash 的燒錄器讀取韌體機密,但無法防止其他介面的讀寫,因此韌體在其他介面溝通時,如果有機密資料需要做加密傳輸。

鎖定區如果要強制讀取會透過電路先將 flash erase 才可以讀寫,可以做到保護鎖定區資料機密性的功能。在鎖定區的 loader,在進行韌體更新時,檢查是否是合法的 firmware,才進行更新,以防止燒錯 firmware,或是被 crack 修改到鎖定區內資料。

檢查是否是合法 firmware 可以用公開金鑰機制防護,在鎖定區內的 loader 存 private key,要燒錄到 flash 上的韌體存 public key。可以做到韌體加密與驗證合法韌體的功能。

0x3E00~0x3FFF 保留未使用

比如宣告

char xdata GPIO1 _at_ 0x100;

表示宣告為型態 char,在 external data memory 位置 0x100 的 GPIO1 變數

2. 宣告 register 與位元

sfr P0 = 0x80; // 宣告 P0 是 special function register,
// Port 0 位址在 0x80,8 bit constant, 位在 IDATA 型態的
// 0x80~0xFF 直接定址區
// 0x00 ~ 0x7F 位在 DATA 型態記憶體的直接定址區

sfr SCON = 0x98;
// 宣告 SCON 為 register,SCON register 值為 0x98

sbit B1 = SCON ^ 0;
//宣告B1 為 SCON sfr 的第0個位元( 1 byte = 0~7 bits)


3. 中斷函式宣告、註冊與定義

timer0_int() interrupt 1
{
// C codes
}


宣告函式名稱 timer0_int(),註冊在中斷 1,中斷 n 的向量(編號)

公式為 (中斷大小*n +3) + 中斷向量基底位址,中斷向量由 C51 compiler 計算。若一般中斷大小為 8,中斷向量基底位址為 0x4000,則宣告 timer0_int() 程式碼放在 code memory 位址 0x400B。

4. 在固定記憶體位置宣告 struct, memory mapped struct

struct MyStruct { char data;};

struct MyStruct xdata *sPtr;
// C51 依 xdata 配置視 sPtr 為指向一個 memory mapped struct

sPtr = (void xdata*) 0x8000;
// 這個 struct pointer map 到 0x8000 實體記憶體
// 類比 standard C lib 的 malloc heap memory


相關連結:

Silicon Labs C8051F320, C8051F321 Datasheet (pdf)

The C51 Primer (pdf)

SDCC small device C compiler Open Source 8051 C compiler

SDCC Open Knowledge Resource Open Source C libraries for 8051

Keil C51 第一家在 8051 平台推出 C compiler 的公司

8051 market in 2008

8051 C Compilers

http://o.keil.com/forum/docs/thread356.asp

2010年5月5日 星期三

2010宜蘭綠色博覽會

趕在這周末宜蘭綠博結束前,我帶著SSH去體驗一下大地之美。原來2010宜蘭綠色博覽會3/27就已開始,我根本不知道有這個活動,要不是老爸看電視得知且提議去走走,我可能就這麼錯過了。

非假日去的好處就是不塞車,門票又有優惠,刷中信卡還可打九折。如果你以為平常日遊客少,那可就大錯特錯。我們去的這一天,人還挺多的,很多幼稚園、小學生都是平常日到綠博來戶外教學。我覺得人多比較有玩的感覺。當天天氣晴空萬里,氣溫飆到33度,簡直熱到炸,才買好門票準備進園,就被烈陽曬得頭昏。

2010宜蘭綠色博覽會位於蘇澳鎮武荖坑風景區,依官網園區展示資訊來看,共九個主題館,若照著順序走,第一個是「水上人家」,以休閒農業傳遞出宜蘭的農村美,入口處有全台最大綠雕水車,園區還有一群鴨子悠遊水間,我這個天兵竟然鵝跟鴨子搞不清楚,大聲地叫SSH過來看鵝,後來聽到身旁小學生說:「怎麼鴨子這麼白?!」,我才知道我鬧了大笑話。本來買一包飼料要餵鴨子,希望把鴨子吸引過來靠近我們,結果SSH一拿到飼料就整袋丟進水裡,一群鴨子全嚇跑了,希望鴨子不要連塑膠袋也吃下去。

才逛完第一館,我們這群大人就熱到懶得走,只有SSH依然熱情不減,不畏酷陽,在大草坪上狂奔,後來看到有遊園車可坐,乾脆花個20元坐車直接殺到最後面,從後面玩回來。遊園車是直接開到最後面,中間的館不停,單程每人20元。但後來發現其實每個主題館之間距離並不遠,用走的當作散步應該還好,只是天氣太熱,我們偷懶不想走罷了。

坐車到最後區,一下車就有二個主題館-「綠色奧林匹克館」與「森活林場」,我都以視線帶過就當作參觀過,因為我們最有興趣的是「夢的迷宮」,這也是我們坐遊園車的主因。因為「夢的迷宮」位在最後區,這座以綠雕及花牆方式設計而成的大型花園迷宮,是將畢卡索的名畫-The Dream《夢》,放大4100倍,用各種花材布置,讓遊客走在花叢裡,也走進畢卡索的想像世界裡。剛開始我們一行人團體行動,後來漸漸走散,我跟著SSH後面走,看他走在迷宮裡,即使遇到叉路,也不加思索地憑直覺選定其中一條走,一下子就走到出口。倒是其他人真的迷失在花園裡,還靠手機聯絡才找到我們。原來畢卡索的抽象藝術,純真的二歲兒才懂。在迷宮前,有一以廢棄貨櫃改置成的觀景台,可讓遊客於平台上,欣賞整幅畫作。我們只在觀景台一樓買便當吃,因為當時不知道爬到最上層可以看整幅畫,以為那只是讓遊客休息吃東西的歇腳處,後來在綠博官網才知道,觀景台是主辦單位的用心,在最上層可以把整個「夢的迷宮」拍下來。

走完「夢的迷宮」,就跟烈陽投降,直接又坐遊園車回去,「開心牧場」與「蒲公英的悠活」就沒去參觀,以為這兩館很遠,其實後來看地圖,就在「夢的迷宮」附近,「開心牧場」是我很想去的其中一館,現場有擠羊乳活動,SSH很愛看動物,沒去成真的很可惜。

坐遊園車回到最前面,前區除了參觀過的「水上人家」,還有「幸福單車館」及「靓水旅域」,單車館展示各種年代單車,充分傳達綠博理念,講求環保,以騎單車代替開汽車。

「靓水旅域」則是以愛護水資源,認識水再生利用為主題。SSH應該是最不懂得水環保的小遊客,他在家超愛玩水,無形中很多水就被浪費掉。在SSH腦裡,水就是用來洗手、洗澡,他也已經會講水這個字,所以他在「靓水旅域」這個館倒是玩得挺愉快,看到水就把手伸進去洗一洗。

回家前去逛逛「綠色市集」,本來想買網友推薦的三星蔥,聽說一大把才80元,可惜沒看到在賣。市集裡有幾攤賣蘭花的,一小盆50元,喜歡園藝的倒是不錯選擇。

整個遊園時間大約2-3小時,看了六個主題館。我覺得有帶小朋友去要4小時才夠,因為總共九個館,我們有三個館沒去-「開心牧場」、「蒲公英的悠活」、「綠野舞台」。2010宜蘭綠博活動只到 5/9母親節,想去要快,母親節當天,媽媽免費喔!


藍天白雲正是遊綠博的好日子


2010宜蘭綠博3/27 ~ 5/9


第一館- 「水上人家」


綠雕水車


園區內處處可見花海


「水上人家」館內大水車


藍天 白雲 綠地


「綠色奧林匹克館」


「夢的迷宮」


「夢的迷宮」靈感來源


SSH努力走出迷宮