Saturday, June 07, 2008

The Analog Clock

Collage of the Digital Photo Frame

……秒針急急忙忙的去撥動每一根短棒,使它們產生意義。然後分針慢吞吞的做同樣的事,使那些短棒產生另一種意義。三種針的位置和關係不斷變更,在錶面上切割出許多角來,夾住那不可捉摸的時間。……(摘自作文七巧:P86)

算一算日子,在現任公司混吃也有九個月了。很幸運的,一進來就參與一顆 ASIC 的開發,從一開始的寫 tools 測試 FPGA 功能,後來的寫 f/w 測試 ASIC ,到最後的參與產品開發。照規劃,一開始只打算拿來秀秀圖,偶爾也秀秀時間日期。後來為了把這顆小 MCU 的能耐完全壓榨出來,前些日子我還幫它加了類比鐘(Analog Clock)。自此,相框就不再僅僅只是相框了:

The Analog Clocks

想起專科的畢業專題,我實作過一組函式庫,用來執行 3D 投影及相關的座標轉換。一晃眼已經十多年了,最近為了完成的這個類比鐘,竟然連描點畫線的程式都得自己手寫……

這也沒辦法,先前那支專題程式是在 16 bit 的 286 CPU 上跑的,硬體上有浮點運算器可用,軟體方面也有 DOS 版的 Borland C++ 提供的 BGI 繪圖函式庫。而這次的類比鐘卻要運作於 8 bit 的 8051 MCU。

首先,我寫了常用於光柵繪圖的點(pixel)運算及畫線程式。既然可以描繪出線段了,接著就一段一段的,拿來描出時鐘刻度及各式指針:

The Analog Clocks

由於底層光柵繪圖的座標系統(直角座標)實在不適合拿來描繪鐘面及指針,所以我只好祭出極座標。

雖然極座標到直角座標的轉換式是長成這樣的:

x = x0 + r⋅cos(θ)
y = y0 + r⋅sin(θ)

我實際採用的轉換式子卻是:

x = x0 + r⋅sin(minute)
y = y0 - r⋅cos(minute)

這是因為

  1. 數學上慣用的極座標以逆時針方向為正,順時針方向為負,把 sin 跟 cos 互換後,角度的正負才符合時針的轉向。
  2. 螢幕的 y 座標是往下長的,所以補個負號,讓它迷途知返。
  3. 最後,時鐘的角度單位只要有「一分鐘轉角」的精度就夠了。

值得一提的是,整個換算過程只用到整數運算,因為我不但採用了定點數運算技巧,還以爬說語產生了正弦、餘弦表格:

>>> from numpy import *
>>> scale = 127
>>> array([round(x) for x in scale*sin(arange(0, 2*pi, 2*pi/60))], dtype=int)
array([   0,   13,   26,   39,   52,   63,   75,   85,   94,  103,  110,
        116,  121,  124,  126,  127,  126,  124,  121,  116,  110,  103,
         94,   85,   75,   64,   52,   39,   26,   13,    0,  -13,  -26,
        -39,  -52,  -63,  -75,  -85,  -94, -103, -110, -116, -121, -124,
       -126, -127, -126, -124, -121, -116, -110, -103,  -94,  -85,  -75,
        -64,  -52,  -39,  -26,  -13])

>>> array([round(x) for x in scale*cos(arange(0, 2*pi, 2*pi/60))], dtype=int)
array([ 127,  126,  124,  121,  116,  110,  103,   94,   85,   75,   64,
         52,   39,   26,   13,    0,  -13,  -26,  -39,  -52,  -63,  -75,
        -85,  -94, -103, -110, -116, -121, -124, -126, -127, -126, -124,
       -121, -116, -110, -103,  -94,  -85,  -75,  -64,  -52,  -39,  -26,
        -13,    0,   13,   26,   39,   52,   63,   75,   85,   94,  103,
        110,  116,  121,  124,  126])
Tags: [] [] [] []

20 comments:

HuaHua said...

很酷的設計

Anonymous said...

請問為什麼要叫 python 為爬說語呢?

York said...

To 無名氏:

1. Python 是以動物命名的語言
2. Python 就是大蟒蛇
3. 蛇類說的話,當然是爬說語

Anonymous said...

哈哈哈,真有創意

Wallace said...

時鐘只有分成六十分,可以利用三角關係中的合角公式來簡化。
A=cos(2pi/60)
B=sin(2pi/60)
cos(k+1)=Acos(k)-Bsin(k)
sin(k+1)=Asin(k)+Bcos(k)
這樣就不用去使用大的sin表格了。但若是其他地方有重覆使用就另當別論。

York said...

To Wallace,

受教了,我壓根兒不知有合角公式可用。

還好,只套用合角公式無法滿足這個應用,因為指針是一條條線描出來的 :)

Wallace said...

不是很懂,使用合角方法取代sin表格和繪線有何關係。可能是我描述不清。用合角來產生sin/cos表格只需要sin(6度)及cos(6度)的值。sin(12度)/cos(12度)則由二個sin/cos(6度)合成。sin/cos(18度)則由上次推出之sin/cos(12度)再加上sin/cos(6度)再合成。一路推出整個360度。而時鐘大部分狀況只會前進6度,是可能不需用到sin/cos表格。但指針若是使用多種角度畫的,基於座標旋轉6度將上次的座標再加入6度旋轉公式,也只用到sin/cos(6度)的值。只有初始時會用到6*n度,其餘時間都只有用到旋轉6度。
不過,ROM多時查表法是比較容易讀。很符合版主的style。

York said...

To Wallace

瞭解了,講得非常精彩詳盡 ^______^

昨天我有意會到可以一次轉六度,一路推出 360 度;但想到指針要有不同角度畫法,直覺無法只利用這個達成目的。

現在經你說明,已打通思路了,非常感謝。果然數學的東西還是得讓專業的來 :)

如果下次要針對這段重構(Refactoring),我會換成你這個合角公式的作法 ^____^

HuaHua said...

很有趣的討論-

a. 將六十個所需的sin/cos off-line求出存進ROM中. 讀取時需要
1.求出所需資料在ROM中的位址
2.讀取ROM一次

b. 存一組sin(6)/cos(6)值. 讀取時需要
1.讀取ROM兩次 -- 取得先前的sin(6n)/cos(6n)
2.以和角公式求出所需角度

空間需求: a > b
計算需求: b > a -- 我的問題是: 以和角公式推出來的數值, 它的誤差是不是會越來越大? 畢竟定點運算得捨棄部份精確度.

另外, Wallace是"猴子靈藥"裡面回文想做機器人的那個嗎?

York said...

To HuaHua,

由於之前被廣告費文入侵,不堪其擾。所以現在大家的評註,我改成人工過濾後,再決定要不要顯示。

言歸正傳,
合角公式的作法確實可能有誤插累積的問題,但這個問題可輕易避掉:
1) 改用 2byte 存放六度角正弦餘弦值,以提高精度
2) 每轉一週矯正一次,回到初始值

Wallace said...

確實在實做上要考慮精度及誤差累積問題,所以版主的做法是對的。也給了我建議方法一個完整的實做方法。
至於Huahua問的問題,我的回答是:Yes。
因為我在機器人實驗室做研究,故會和你有許多意見上的溝通。其實在那裏已發表太多和文章不太相關的東西,已經有"喧賓奪主"之相。所以不好意思要發表下去了。

York said...

To Wallace and HuaHua,

哪個實驗室?哪個猴子靈藥?

大家都沒附連結,不是好習慣,實在不可取 :p

Wallace said...

我的教授實驗室是醫療機器人實驗室,因為我還沒有良好供獻,不想打壞名聲,所以不太想說清楚。而且我不是全職研究生是在職,能準時畢業就要偷笑了。
至於討論在
http://blog.monkeypotion.net/gamedev/career/how-to-prepare-for-entering-game-industry
只我沒想到HuaHua一直問,我就一直答,把人家版面搞成這樣也不是我想見到的。

Hua-Hua said...

To Wallace:

這個blogger的主人York懂得很多關於機器人, 如果他允許的話, 把那個討論串移到此繼續, 應該會讓你我收穫更多. 只是在討論之前, 我會建議你訂一個主題, 先前與你討論的過程會有失焦的情況, 我想是因為我沒有抓到你想討論的問題核心.

To York:

很抱歉沒有附上連結, 原因是我覺得是同一個人的機會實在不大.

Wallace said...

我會在機器人文章下貼文,請期待。

Wallace said...

我的新文章發表:8051簡單多工核心
http://www.haifeng.idv.tw/leo/cgi-bin/topic.cgi?forum=56&topic=441&show=0

Hua-Hua said...

(聊天文)

To Wallace:
雖然還沒有仔細看, 不過應該是個好東西!

Wallace said...

To:Hua Hua
謝謝你的讚美。它雖是我的作品,卻是前公司廢棄品。大概只能用在8051、PIC等小型MCU。所以我就公開了,算是讓人學習及教育用的材料。

Hua-Hua said...

To Wallace:

還記得你想做一個lua debugger. 以你這個例子再加些修改: 與外界溝通的command channel(硬體:bus; 軟體:command parser), 以及一個function queue與function map.
執行流程-
1. command channel接收command並且做parsing得到function代號與function參數
2. 以function代號查詢function map得到function address, 將address與function參數填入function queue
3. 執行function queue中的function (這是你多工的wile loop部分)

會遇到的問題在command channel的handshake設計, 以及不定個數的function參數傳遞(甚至是return).

如果有這樣的軟硬體系統, 你只要傳function代號與function參數就可以改變mcu執行流程了. (就可以不用lua了)

Wallace said...

To HuaHua:
很好的想法,參數就放在Stack中就可以了,結果也放回Stack,這樣就省了參數傳遞。
這樣和Lua的VM層做法就一樣了,也和Forth語言做一樣的事。
所以,還是用別人做好的語言,就不用寫說明書或應用例。只要解決移植的問題,成果會快很多。
至於我那個多工核心,就是沒看到別人寫,才會自己搞。