Monday, April 12, 2010

[OpenCV] Face Detection (人臉偵測)


何謂OpenCV?
Open代表Open Source,CV為Computer Vision,Computer Vision被廣泛應用在計算機科學及醫學等領域,OpenCV是由Intel公司參與及開發出來的,感謝Intel的Open Source :)。OpenCV支援Windows跟Linux兩種平台,並具有強大的圖形跟矩陣的運算。然而,近幾年OpenCV為人垢病的是,它並不再更新,因此無法提供一些新的演算法。But,這個問題似乎得到了回應,最近OpenCV已經開始在改版了,加入了一些新的函數,像是近年來在電腦視覺領域上,極常被使用及提及的Scale-invariant feature transform(SIFT)

下載及安裝OpenCV
最新版的OpenCV可至以下網址下載:
http://sourceforge.net/projects/opencvlibrary/

Face Detection
There are numerous researches making efforts toward how to correctly detect faces in digital images. 而OpenCV所使用的方法是由Viola & Jones」所發表的AdaBoost Learning with Haar-Like Features來實現人臉偵測。下面就簡單介紹一下這個方法:
  1. 首先定義一些Haar-Like Features
  2. 再來,我們必須給定一些sample例如: 假如我們要偵測的是人臉,那麼我們就要輸入一些人臉的sample有了這些sample我們將會利用AdaBoost learning algorithm來挑出某幾個代表性的Haar-Like Features,以這個例子來講,我們可以想成這些被挑選出來的feature就是代表人臉的feature
  3. 每個被挑選出來的feature都代表一種classifier,許多種被挑選出來的feature因此構成了一連串的classifier,我們稱為strong classifier。而每個classifier皆用來判斷所輸入的圖片是否為人臉,並且回傳,最後,通過所有classifier的圖片將被判定是一張人臉
以上就是Viola與Jones所提出的Object Detection方法,除了人臉當然它還可以拿來用在其它的物件偵測,只要我們改變一開始輸入訓練用的sample型態即可。

「Source Code
OpenCV 有提供Face Detection的source code,不過它註解很少,並且有點難懂,所以我試著修改它,並加上一些註解

/*  
 * Face Detection in Digital Photographs  
 *  
 * We leverage the AdaBoost learning-based face detection method with Haar-like features to detect faces in photos.  
 * However, OpenCV supplies Haar Cascade classifier for object (face) detection.
 * 
 * (C) Matt Swanson & TrekLee 
 * http://seeyababy.blogspot.com/  
 *  
*/ 

// Path setting for OpenCV Library
#pragma comment(lib,"../OpenCV/lib/cv.lib")
#pragma comment(lib,"../OpenCV/lib/cxcore.lib")
#pragma comment(lib,"../OpenCV/lib/highgui.lib")

// Path setting for OpenCV Header files
#include "../OpenCV/include/cv.h"
#include "../OpenCV/include/cxcore.h"
#include "../OpenCV/include/highgui.h"
  
#include <stdio.h>
#include <iostream>

using namespace std;

// the minimum object size
int min_face_height    = 20;
int min_face_width    = 10;

//Face Detection
int runFaceDetection(IplImage* image_detect);

void main()
{    
    IplImage* pImg = cvLoadImage( "image.jpg", 1);    
    runFaceDetection(pImg);
    cvReleaseImage( &pImg ); 
}

int runFaceDetection(IplImage* image_detect)
{ 
    // Load the pre-trained Haar classifier data.
    CvHaarClassifierCascade* classifier = (CvHaarClassifierCascade*)cvLoad("haarcascade_frontalface_alt.xml", 0, 0, 0);
 
    // Quit the application if the input source or the classifier data failed to load properly.
    if( !classifier)
    {
        cerr << "ERROR: Could not load classifier cascade." << endl;
        return -1;
    }

    // Create a CvMemStorage object for use by the face detection function.
    CvMemStorage* facesMemStorage = cvCreateMemStorage(0);

    IplImage* tempFrame = cvCreateImage(cvSize(image_detect->width, 
        image_detect->height), IPL_DEPTH_8U, image_detect->nChannels);

    // Copy the current frame into the temporary image.  Also, make 
    // sure the images have the same orientation.
    if(image_detect->origin == IPL_ORIGIN_TL)
    {
        cvCopy(image_detect, tempFrame, 0);
    }
    else
    {
        cvFlip(image_detect, tempFrame, 0);
    }

    /* Perform face detection on the temporary image, adding a rectangle around the detected face. */

    // "Resets" the memory but does not deallocate it.
    cvClearMemStorage(facesMemStorage);
 
    // Run the main object recognition function.  The arguments are: 
    // 1. the image to use
    // 2. the pre-trained Haar classifier cascade data
    // 3. memory storage for rectangles around recognized objects
    // 4. a scale factor "by which the search window is scaled between the 
    //    subsequent scans, for example, 1.1 means increasing window by 10%"
    // 5. the "minimum number (minus 1) of neighbor rectangles that makes up 
    //    an object. All the groups of a smaller number of rectangles than 
    //    min_neighbors-1 are rejected. If min_neighbors is 0, the function 
    //    does not any grouping at all and returns all the detected candidate 
    //    rectangles, which may be useful if the user wants to apply a 
    //    customized grouping procedure."
    // 6. flags which determine the mode of operation
    // 7. the minimum object size (if possible, increasing this will 
    //    really speed up the process)
    CvSeq* faces = cvHaarDetectObjects(tempFrame, classifier, facesMemStorage, 1.1, 
        2, CV_HAAR_DO_CANNY_PRUNING, cvSize(min_face_width, min_face_height));

    // If any faces were detected, draw rectangles around them.
    if (faces)
    {    
        for(int i = 0; i < faces->total; ++i)
        {
            // Setup two points that define the extremes of the rectangle, 
            // then draw it to the image..
            CvPoint point1, point2;
            CvRect* rectangle = (CvRect*)cvGetSeqElem(faces, i);
            
            point1.x = rectangle->x;
            point2.x = rectangle->x + rectangle->width;
            point1.y = rectangle->y;
            point2.y = rectangle->y + rectangle->height;

            cvRectangle(tempFrame, point1, point2, CV_RGB(255,0,0), 3, 8, 0);
        }
    }
    
    // Show the result in the window.
    cvNamedWindow("Face Detection Result", 1);
    cvShowImage("Face Detection Result", tempFrame);
    cvWaitKey(0);
    cvDestroyWindow("Face Detection Result");

    // Clean up allocated OpenCV objects.
    cvReleaseMemStorage(&facesMemStorage);
    cvReleaseImage(&tempFrame);
    cvReleaseHaarClassifierCascade( &classifier );

    return 0;
}


「Experiment
接下來我挑張照片來做個簡單的實驗,下圖是做完人臉偵測,並框出系統判斷為人臉的結果



















我們可以發現接近右下角的地方有個誤判的情況發生,但不得不說的是,仔細看它真的長的很像一張邪惡的人臉...囧|||...,回歸正題,我們該如何解決這個誤判? 除了加入膚色判斷,或是讓face detector更準確之外,其實有個立即就可以針對這個問題來解決的方法,當我們觀察以下這個函式

 CvSeq* faces = cvHaarDetectObjects(tempFrame, classifier, facesMemStorage, 1.1, 
        2, CV_HAAR_DO_CANNY_PRUNING, cvSize(min_face_width, min_face_height));

其中cvSize(min_face_width, min_face_height)設定被偵測人臉的最小size,上面被誤判為人臉的圖片明顯小於其它正常的人臉,所以我們可以考慮將size放大,看看是否可以解決這個問題,因此我將min_face_heightmin_face_width由原本的size各加上10個pixel,重新偵測所得到的結果如下


















我們發現誤判果然被解決了



「Conclusion
本篇文章首先介紹了Viola與Jones所提出的利用AdaBoost Learning with Haar-Like Features來做人臉偵測的方法,並且提供了source code,這份程式改進了OpenCV所提供的source code其難讀(for me)、註解少的缺失,最後,做了一個簡單的實驗及討論


Coming soon...
接下來,我希望有機會及時間可以介紹人臉辨識(Face Recognition)的部份,也就是判斷某張人臉是哪個人,如此,加上Face Detection,便可以讓我們很方便、很快的從數以千或萬計的數位照片當中挑出某個人出現過的照片了

Saturday, March 27, 2010

How to program the Pololu 3pi Robot using C language - "Motor Control"

Pololu 3pi Robot


如上圖 [Ref.1] Pololu 3pi Robot是一台小型的Robot, 主要依靠一顆AVR微處理器(AVR Atmel ATmega328P)來控制LCD LEDMotors...等各項硬體設備 ATmega328P提供了ISP(In-System Programming)的功能, 我們可以利用C語言來控制Pololu 3pi Robot,例如在Robot的LCD上show出文字或是讓Robot直線、走迷宮。 下面兩張圖分別是Pololu 3pi Robot的正反面, 圖上並且標記一些硬體設備的名稱 [Ref.1]。 



Motor
關於Motor的部份,它負責提供能量給3pi並使用TB6612FNG dual motor driver [Ref.2]。當然Pololu AVR Library已經提供一些Library來控制motor,但這邊想探討的是比較low-level的motor control [Ref.3]。由上圖我們可以看到3pi有兩個motor,Motor 1連接接腳PD5跟PD6Motor 2連接接腳PD3跟PB3。這些接腳會連接ATmega328p的4個8-bit hardware PWM outputs (PD5=OC0B, PD6=OC0A, PD3=OC2B, and PB3=OC2A)我們可以透過這些硬體上的timers來控制3pi的速度,而不需經由軟體來控制它的好處是可以讓CPU有多點心思可以花在其它任務上,至於怎麼去控制在稍後會討論

I/O-Ports
要控制motor前,我們需要先知道它的硬體是怎麼規劃及怎麼接線的不同的AVR Microcontroller型號有其不同的I/O Port數目及I/O接腳。AVR的I/O Port皆有3個8-bit I/O暫存器(在datasheet裏稱之為I/O memory address locations),分別為PORTxDDRxPINx,其中x代表I/O Port的編號,而其意義分別簡述如下:
  1. Data Register – PORTx



    • I/O Port被設定為output,則PORTx會用來輸出資料
    • I/O Port被設定為input,則PORTx另有它用(可參考datasheet [Ref.4])

  2. Data Direction Register – DDRx
    此暫存器用來設定
    I/O Port為input或output,所有I/O Port的每個接腳皆可以獨立設定



    • 當接腳設為0,則I/O Port設定為input
    • 當接腳設為1,則I/O Port設為output

  3. Port Input Pins – PINx
    此暫存器只能read
    ,不能write。當DDRx設定為input,可以從此暫存器讀取外部接腳信號

Variable Speed Control Using Hardware PWMs
ATmega328p有兩個8-bit timers: Timer0與Timer2
Each of these timers has two PWM output pins. The Timer0 PWM output pins are OC0A on PD6 and OC0B on PD5; these are the control lines for motor 1. The Timer2 PWM output pins are OC2A on PB3 and OC2B on PD3; these are the control lines for motor 2. ~by [Reference 3]
那麼到底怎麼利用PWM來控制motor的速度?太細部的電子電路相關理論在這恐怕沒辦法說得太細,而在ATmega328p是支援PWM的前提下整體觀念應該可以這樣來說:
  • 首先,我們應該先瞭解何謂PWM,它是Pulse-Width Modulation的縮寫,它長得像下圖[Ref.5]



                _      __     ___    _____  _      _____  __     _     
               | |    |  |   |   |  |     || |    |     ||  |   | |    
    PWM Signal | |    |  |   |   |  |     || |    |     ||  |   | |    
             __| |____|  |___|   |__|     || |____|     ||  |___| |____
    


    而PWM的其中一個應用,就是其對電流的控制 
  • 接著我們必須瞭解Duty cycle簡單來講它的意思就是在PWM波的一個時間周期中高電位所佔的比例,即稱為Duty cycle
  • Duty cycle越高,其速度是越快的,因此我們控制速度的主要依據就是兩個8-bit timers,它可以計時高低電位持續的時間
  • 至於motor的轉動方向所依據的是H-bridge的原理,H-bridge是一個常被用在robot的電子電路,它有四個switch(S1、S2、S3、S4)可以控制motor的轉動方向,如下面兩圖示[Ref.6]



    • 當S1及S4為連接的,而S2及S3為非連接的,則motor方向會向右(順時針轉動)
    • 當S2及S3為連接的,而S1及S4為非連接的,則motor方向會向左(逆時針轉動)



Demo
利用Variable Speed Control Using Hardware PWMs,加上H-bridge的原理我們可以控制3pi的方向速度,這邊我測試了一個簡單的motor control也就是以一個中心為原點3pi以順時針的方向,來回的行走於各個角度,再回到原中心點這個demo的目的只是要簡單show出3pi的前進後退及轉彎下圖中是我跟我妹借的一些Hello Kitty當Model:

然後這是可愛的Hello Kitty們跟3pi的合照XD
 
Hello Kitty說穿了其實只是裝飾用的(真是辛苦她們了XD)切入正題,我想做的是讓Hello Kitty將3pi圍成一圈3pi將順時針方向來回拜訪每個Hello Kitty,如下圖示
 
接著,來看看成果吧 :)



Source Code
下面是Source Code,首先是標頭檔

// Modified by Trek Lee to work with the Pololu 3pi Robot

#ifndef _DEVICE_
#define _DEVICE_

#ifndef F_CPU
#define F_CPU 20000000 // system clock is 20 MHz
#endif //!F_CPU

/* Data Register, Port B */
#define    PB7      7
#define    PB6      6
#define    PB5      5
#define    PB4      4
#define    PB3      3
#define    PB2      2
#define    PB1      1
#define    PB0      0

/* Data Register, Port D */
#define    PD7      7
#define    PD6      6
#define    PD5      5
#define    PD4      4
#define    PD3      3
#define    PD2      2
#define    PD1      1
#define    PD0      0
 
#endif //_DEVICE_


接著是主程式
/*
 * 3pi-circularly saunter - demo code for the Pololu 3pi Robot
 * 
 * This code will circularly saunter, using PWM-Based Motor Control Code.
 * It demonstrates circularly movement of 3pi motor control.
 * 
 * Source code is partially reference from Application Note: 
 * Using the Motor Driver on the 3pi Robot and Orangutan Robot Controllers, 
 * http://www.pololu.com/docs/pdf/0J15/motor_driver_application_note.pdf
 *
 * http://seeyababy.blogspot.com/
 *
 */
 

#include <avr/io.h>
#include "device.h" 
#include <util/delay.h> // F_CPU is defined in "device.h" above

// Motor Control Functions -- pwm is an 8-bit value
// (i.e. ranges from 0 to 255)
void M1_forward(unsigned char pwm)
{
 OCR0A = 0;
 OCR0B = pwm;
}
void M1_reverse(unsigned char pwm)
{
 OCR0B = 0;
 OCR0A = pwm;
}
void M2_forward(unsigned char pwm)
{
 OCR2A = 0;
 OCR2B = pwm;
}
void M2_reverse(unsigned char pwm)
{
 OCR2B = 0;
 OCR2A = pwm;
}

// Motor Initialization routine -- this function must be called
// before you use any of the above functions
void motors_init()
{
 // configure for inverted PWM output on motor control pins:
 // set OCxx on compare match, clear on timer overflow
 // Timer0 and Timer2 count up from 0 to 255
 TCCR0A = TCCR2A = 0xF3;
 // use the system clock/8 (=2.5 MHz) as the timer clock
 TCCR0B = TCCR2B = 0x02;
 // initialize all PWMs to 0% duty cycle (braking)
 OCR0A = OCR0B = OCR2A = OCR2B = 0;
 // set PWM pins as digital outputs (the PWM signals will not
 // appear on the lines if they are digital inputs)
 DDRD |= (1 << PD3) | (1 << PD5) | (1 << PD6);
 DDRB |= (1 << PB3);
}

// delay for time_ms milliseconds by looping
// time_ms is a two-byte value that can range from 0 - 65535
// a value of 65535 (0xFF) produces an infinite delay
void delay_ms(unsigned int time_ms)
{
 // _delay_ms() comes from <util/delay.h> and can only
 // delay for a max of around 13 ms when the system
 // clock is 20 MHz, so we define our own longer delay
 // routine based on _delay_ms()
 unsigned int i;
 for (i = 0; i < time_ms; i++)
  _delay_ms(1);
}

int main()
{
    
 motors_init();
 delay_ms(2000);


 while(1)
 {

  M1_forward(25);
  delay_ms(1000);

  //motionless
  M1_forward(0);
  delay_ms(1000);

  //forward
  M1_forward(25);
  M2_forward(25);
  delay_ms(2000);

  //motionless
  M1_forward(0);
  M2_forward(0);
  delay_ms(2000);

   //reverse
  M1_reverse(25);
  M2_reverse(25);
  delay_ms(2000);

  //motionless
  M1_forward(0);
  M2_forward(0);
  delay_ms(1000);

 } 
 
 return 0;
}
  

Conclusion
這篇文章首先簡單介紹了Pololu 3pi Robot它採用AVR Atmel ATmega328P微處理器並且提供In-System Programming。接著,說明了ATmega328PI/O Port及各個Port所代表的意義,而motor連結了具PWM 輸出的Port,透過PWM的輸出加上H-bridge的原理讓我們可以控制motor的速度及方向。最後,透過Demo來呈現motor的控制效果

Coming soon...
3pi在目前這個project是依照我們自己設定的角度(值)來轉彎,然而3pi本身有個Line Following [7]的功能(sensor),因此,在未來我們將會利用Line Following的功能來引導3pi,可以預期的是Line Following將節省我們手動去調那些轉彎角度的時間3pi只需依照規定路線去跑即可完成任務
 
[Reference]
  1. Pololu 3pi Robot User's Guide, http://www.pololu.com/docs/0J21
  2. Toshiba Bi-CD Integrated Circuit Silicon Monolithic
    T B 6 6 1 2 F N G, http://www.pololu.com/file/download/TB6612FNG.pdf?file_id=0J86
  3. Application Note: Using the Motor Driver on the 3pi Robot and Orangutan Robot
    Controllers, http://www.pololu.com/docs/pdf/0J15/motor_driver_application_note.pdf
  4. AVR Solutions - Datasheets, http://www.atmel.com/dyn/resources/prod_documents/8271.pdf
  5. Pulse-width modulation, http://en.wikipedia.org/wiki/Pulse-width_modulation
  6. H-bridge, http://en.wikipedia.org/wiki/H-bridge
  7. Line Following, http://www.pololu.com/docs/0J21/7.a

Sunday, January 31, 2010

Locks for Data Synchronization

「簡介」

在OS課程中提到,Process Communication分為兩大類: 「Shared Memory」、「Message Passing」,接下來即是一大串的討論,所針對的便是Shared Memory所可能產生的問題,以及其解決方案,並提出一些有名的例子,像是「Producer/Consumer Problem」、「Dining Philosophers」等等的例子,我們可以發現重點在於怎麼讓其共享的變數或資料能夠「一致」,為了使其一致,必須規定一些存取上的機制,也就是說,當某個process在存取某個變數時,我們希望其它process可以暫緩存取該變數,這樣才能維持資料的一致性以及使其同步(synchronization),因此,我們必須設計一些鎖(Locks),這些鎖使得process可以鎖定正在使用的區段,而避免被其它process存取,這篇文章的目的就是要描述不同鎖的特性。

「Locks」
不同的系統,提供其各自不同特性的鎖,例如SQL Server,在資料庫的處理上需要透過鎖來幫忙整頓transaction,而這邊要介紹的是在Linux系統中「fcntl」 這個system call所提供的locks。

「Shared Lock」VS「Exclusive Lock」
以下這段描述來自IBM solidDB系統的文件[Ref.1],
    An exclusive lock allows only one user/connection to access (read or update) a particular piece of data. A shared lock allows multiple users to read data, but doesn't allow any of them to update the data.
   
    .........

    Shared and exclusive locks cannot be mixed. If you have an exclusive lock on a record, I cannot get a shared lock (or an exclusive lock) on that same record.
其實Linux系統中的「fcntl」所提供的「Shared Lock」及「Exclusive Lock」除了名稱跟IBM solidDB的Locks取得一樣,其功能亦相同。所以說,Shared Lock是可以由多個process所共享,即共同鎖定某個檔案的某個區塊,然而,當已經有一個process對某檔案的某區塊設定了Shared Lock,則其它任何的process就無法對該區塊設定Exclusive Lock;而關於Exclusive Lock,對於某個檔案的某個區塊只能被唯一一個process對其設定Exclusive Lock。


「fcntl系統呼叫的使用」

  • 標頭檔的引入及變數宣告
#include<fcntl.h>
struct flock region1;

  • 使用Shared Lock來鎖定區塊的第5位元組到第10位元組
region1.l_type = F_RDLCK;
region1.l_whence = SEEK_SET;
region1.l_start = 5;
region1.l_len = 5;

  • 使用Exclusive Lock來鎖定區塊的第5位元組到第10位元組      
region1.l_type = F_WRLCK;
region1.l_whence = SEEK_SET;
region1.l_start = 5;
region1.l_len = 5;


 「Examples」
舉例來說,假設有個Process1開啟了fileA並對這個檔案做一些鎖定如下:
  • Process1利用Shared Lock來鎖定fileA的第5個Byte到第10個Byte

  • Process1利用Exclusive Lock來鎖定fileA的第20個Byte到第30個Byte

  • fileA的第20個Byte到第30個Byte只允許Process1讀寫 

那麼,當另外有個Process2要來對fileA做存取時,我們可以看到:
  • Process2可以存取fileA未被Process1鎖定的部份

  • Process2對於fileA第5個Byte到第10個Byte只能read不能「write

  • Process2對於fileA第20個Byte到第30個Byte不能read,也不能「write

以上就是Shared Lock以及Exclusive Lock的介紹


[Reference]
[1] http://publib.boulder.ibm.com/infocenter/soliddb/v6r3/index.jsp
[2] Beginning Linux Programming 4/e, Richard Stones and Neil Matthew

Saturday, October 10, 2009

POSIX termios interface for canonical/non-canonical mode switching


前言
POSIX termios interface,提供一個介面讓我們可以做Termios Programming然而,Termios Programming是一個很大的主題,並且有許多的應用我們可以透過它來控制terminal在處理字元上的一些設定此外Serial Programming也是需要透過它來做一些設定,例如:設定Baud Rate或Parity Checking。甚至,它可以用來存取RS-232的serial port,並且做client-server終端控制的一些應用。而在這篇我想介紹的是利用termios來切換終端機接收字元的標準/非標準模式(canonical/non-canonical mode

Termios簡介
Termios是一個符合POSIX的介面有定義一些函式或巨集除了控制I/O; 它還可以控制硬體,例如決定使用多少位元來傳送跟接收字元; 並且,它也可以控制終端機的特性,例如決定終端機接收字元的模式(接下來本篇文章將以此來舉例); 最後,它也針對一些字元提供特殊的控制

標準/非標準模式(canonical/non-canonical mode)
不管在MS-DOS或是LINUX的終端機相信大家較常使用的是標準模式,而簡單的說,它就是允許使用者在按下Enter鍵前仍然可以修改輸入的字元(例如使用Backspace鍵來修改),所以按下Enter鍵,程式才會讀取字元。相對來說,非標準模式會讀取使用者輸入的任何字元,當然,我們可以利用termios來控制這些輸入,例如我們可以決定在非標準模式下,當使用者輸入多少個字元時即把這些字元傳送給終端機

如何利用termios來切換標準與非標準模式
底下就開始介紹如何使用termios來切換標準與非標準模式
1) 首先,要使用termios我們當然需要先把它include進來

#include <termios.h>

2) 接下來,宣告兩個termios結構(需要宣告兩個是因為,程式在做完任何只有程式本身所需的設定後,在程式結束前必須負責切換回終端機原本的設定,以免影響其它使用)

struct termios initial_settings, new_settings;

3) 取得目前終端機初始設定,並將設定存到initial_settings

tcgetattr(fileno(stdin),&initial_settings);

4) initial_settings複製給new_settings

new_settings = initial_settings;

5) 利用new_settings來做我們想要的設定,以我們的例子來講,我們需要切換成非標準模式

new_settings.c_lflag &= ~ICANON
6) 復原終端機原本的設定

tcsetattr(fileno(stdin),TCSANOW,&initial_settings);

如此,便可以切換成非標準模式,此外,在非標準模式下termios還有很多功能例如使用VTIMEVMIN來控制輸入舉個例子,每當使用者輸入兩個字元,終端機就自動接收這兩個字元,可以這麼寫

new_settings.c_cc[VMIN] = 2;
new_settings.c_cc[VTIME] = 0;

VMIN代表的是非canonical模式讀的最小字元數目。而VTIME代表讀取字元跟字元中間的延遲時間,設定成0代表不可慮VTIME。然而VMIN跟VTIME有許多不同組合,各代表著不同的意義,這就不是本篇文章討論範圍了

結論
termios允許我們去跟一些硬體做溝通,例如透過serial port跟其他設備溝通,目前又以嵌入式系統比較常需要使用,然而在PC上,由於各種傳輸規格日益增進,因此serial port的方法也慢慢被取代了

[Reference]
Beginning Linux Programming 4/e, Richard Stones and Neil Matthew


Wednesday, October 7, 2009

[AVR] Storing/Retrieving Data in the Program Space (Flash Memory)

AVR Atmel ATmega328P
微處理器 - AVR Atmel ATmega328P提供了32KB的Flash Memory(Program Memory),以及2KB的RAM(Data Memory)。而由於AVR是採用Harvard architecture,因此會利用Flash Memory來儲存program;並利用RAM儲存data,且有各自的address space。 


Harvard architecture V.S. Von Neumann architecture
因為Data Memory容量比較有限,因此我們希望能將一些constant data存放在空間較大的Flash Memory。加上,C語言是for Von Neumann architecture,也就是code跟data皆存在同一個address space,所以,針對AVR(Harvard architecture)所設計的compiler必須符合code跟data存放在不同address space這個設計標準。


AVR GCC __attribute__
要達到Harvard architecture的特性有許多方法
而AVR則利用類似GCC的__attribute__這個tag來實作,簡單講,__attribute__可以附加在函式變數的宣告,就是在編譯時會告訴compiler要特別對該函式變數做一些處理。AVR提供一些特殊用途的attribute,例如它提供__attribute__((__progmem__))這個attribute讓programmer可以將constant data儲存在Flash Mem.(Program Mem.)
In AVR GCC, there is a special attribute called progmem. This attribute is use on data declarations, and tells the compiler to place the data in the Program Memory (Flash). ~ avr-libc user manual 1.6.6
PROGMEM是AVR-Libc自行定義的一個macro,它被定義成類似GCC的attribute syntax,以下是<avr/pgmspace.h>這個標頭檔對於特殊attribute定義的節錄

#ifndef __ATTR_PROGMEM__
#define __ATTR_PROGMEM__ __attribute__((__progmem__))
#endif
/**
   \ingroup avr_pgmspace
   \def PROGMEM

   Attribute to use in order to declare an object being located in
   flash ROM.
 */
#define PROGMEM __ATTR_PROGMEM__


Storing/Retrieving Data in the Program Space 
接下來,我們來看如何利用AVR-Libc所提供的macro來存取program space的data:
首先,假設我們有以下的data
unsigned char mydata[2][10] =
{
{0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09},
{0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13}
};

我們可能會利用以下的方式來存取data
byte = mydata[i][j];

但是我們想要把它存放在program space,因此需要用到PROGMEM這個macro來幫助我們達成目的,因為PROGMEM是在<avr/pgmspace.h>定義,所以我們需將它include進來
#include <avr/pgmspace.h>


然後在變數宣告後面加上PROGMEM
unsigned char mydata[2][10] PROGMEM =
{
{0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09},
{0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13}
};

經過以上幾個步驟,就可以將data存在program space,接下來介紹如何從program space取出data。需要注意的是,我們已經將data搬到了program space了,因此我們沒辦法使用一般的方法來取出data,因為data space的data已被搬到program space。為此,AVR-Libc提供一個pgm_read_* macro讓我們可以取出program space的data,我們只需將data的位址當成pgm_read_* macro的參數即可,用法如下
byte = pgm_read_byte(&(mydata[i][j]));


結論
The macros and functions used to retrieve data from the Program Space have to generate some extra code in order to actually load the data from the Program Space. This incurs some extra overhead in terms of code space (extra opcodes) and execution time. ~ avr-libc user manual 1.6.6
另外,雖然user manual中也提到,從program space中取出data所花費的時間小於把data存入program space的時間,但是,如果程式在呼叫pgm_read_* macro上可以最佳化,當然是至上之選了 : ) 

[Reference]
AVR-Libc user manual (v1.6.6)



Saturday, October 3, 2009

如何將Pololu AVR Library加入自己的Project

前言
Pololu是一家由三個MIT的學生在2000年所創立(http://www.pololu.com/),Pololu主要是生產自家Robot,並且提供一些Robot的週邊零件及相關Library,至於Pololu如何發音?是這樣發的:polo-lu,polo跟俗稱的polo衫發音是一樣的。底下是該公司在Las Vegas的辦公室一景










Pololu AVR Library
這是Pololu公司針對AVR系列的微處理器所提供的Library

如何使用Pololu AVR Library
  1. 首先,根據自己所使用的產品來include所需的標頭檔,例如你購買了Pololu的3pi且想要使用C來coding,則需加入

    #include <pololu/3pi.h>
    

    另外
    ,假如是想使用C++,則加入

    #include <pololu/Pololu3pi.h>
    

  2. 加入所屬微處理器型號的靜態函式庫的連結,例如libpololu_atmega328p.a
  3. 基本上以上兩點已經可以成功編譯,但是為了減少code的大小有個指令可以讓程式在編譯時忽略不必要的function,即-Wl,-gc-sections (for linker option)
下面圖中顯示的是加入Step3前的memory使用狀況


      















而這是加入後的使用狀況
 
可見得把不必要的function拿掉後,程式明顯變小許多

[Reference]
Pololu AVR C/C++ Library User's Guide

Thursday, October 1, 2009

Windows Programming - A Short Introduction

使用環境:Visual C++ 2008


至今在Visual C++中,想要建立一個可以跟使用者互動的應用程式大概有三種方式,
分別是使用Windows API,MFC以及Windows Forms。以下將分別簡介這三種方法:


(1) Windows API:透過Windows API的函式,我們所寫的應用程式就可以跟作業系統做溝通。而利用Windows API來撰寫一個Windows程式至少需要透過兩個函式:WinMain(),它負責程式視窗的初始化;以及WindowProc(),負責處理應用程式的訊息,當有訊息產生,Windows會自動呼叫此函式。當然,這兩個函式都是透過Windows提供的API來跟Windows做溝通。我們來看一下利用這兩個函式所建立的視窗樣貌:


















(2) MFC:MFC是一個封裝Windows API之類別的集合,它簡化了利用Windows API來開發程式,也就是說,我們可以經由一些簡單的類別繼承,加上一些簡單的程式碼,就可以產生我們想要的結果。例如,下圖是繼承CWinAppCFrameWnd,且透過這兩個類別的成員來幫我們產生的視窗圖示:















(3) Windows Forms: Windows Forms應用程式需在CLR環境下執行,它比前面兩種分法更方便建立視窗,programmer完全不需要寫任何code就可以產生如下圖的視窗,並且自動產生code:

















結論:
就以寫GUI為目的來講的話,透過Windows API當然是最吃力的方法,至於剩下來的兩種方法的話,只能說看應用來決定要使用哪種方法,兩種方法各有所長。

[Reference]
Ivor Horton's Beginning Visual C++ 2005, Ivor Horton