2014年2月10日 星期一

Arduino LCD 功能表(菜單)

Arduino 在軟體上的開發真的很方便,尤其是對於 C/C++ 開發者而言,更是如魚得水。在 Arduino 的世果裡,你可利用 Library方式來將一些功能進行封裝再進行發佈,使得其他開發者方便地使用。最近,我有一個 project 是要用到在 LCD 顯示器中,要有提供多個項目選項給使用者選取。但在範例和網上也找不到一個比較好和完善的Library,廠商給的 Library 祇提供最簡單的標準函式。 於是我便自己寫了這個 Library,希望可以幫到其他遇到同樣問題的開發者。

首先我註明以下的源碼百分之一百原著,如果想轉載,請列明出處。敬請尊重知識產權,多謝。

由於我購買了的 LCD 是用 I2C方式連接的,所以以下我假設大家的 LCD 也是用 I2C,大小為:4行 x 20字。整個 MENU Library是由四個 class 組成:LCD_MENU、MENU_ITEM、CONTENT 和 LINETEXT。我的簡單構想是每個 LCD_MENU功能表類都有一串 linked list的MENU_ITEM選項類物件,並且給 LCD_MENU的Items指著。而每個 MENU_ITEM物件的Main_Menu指著其所屬的功能表物件;Sub_Menu 指著其子功能表物件;Prev_Menu則指著上一層功能表物件(用以返回上一層功能表單);MENU_ITEM物件中的Selected就是使用者指著目前所選的項目;而MENU_ITEM物件中的Content_Page,就是指著該選項被 Enter 選取後,所顯示的內容物件( CONTENT )。內容物件裡有一串 linked list的LINETEXT字行類物件,並給CONTENT 物件中的p_Head指著開始位置。

//------------------------------------------------------------------------------
//檔案: LCD_MENU.h
//
//作者:Anthony Lee (Dephi.Ktop: skcc)
//
//日期:二零一四年二月一日
//
//功能:完整功能表(菜單)類封裝,提供卷頁瀏覽功能表。
//------------------------------------------------------------------------------
#ifndef LCD_MENU_h
#define LCD_MENU_h

#include "LiquidCrystal_I2C.h"

//如果你的LCD 是20字,請將 MAX_SCREEN_CHAR  設成 19 及 MAX_CHAR_BUFFER 設成 20

#define MAX_SCREEN_CHAR  19
#define MAX_CHAR_BUFFER  20

//如果你的LCD 是 4行,請將 MAX_ROW 設成 4
#define MAX_ROW 4

class LINETEXT
{
public:
int Line_Num;
char Line_Text[ MAX_CHAR_BUFFER ];
LINETEXT *p_Previous;
LINETEXT *p_Next;

public:
LINETEXT();
};

class CONTENT
{
public:
LINETEXT *p_Head;
  int LineCount;
 
  int n_select;
  LINETEXT *Selected;
 
public:
  CONTENT();
  void Add_Line( char *str_text );
  int Update_Line( int line_num, char * str_text );
  int Delete_Line( int line_num );
  void Clear_All();
 
  void init();
};

class MENU_ITEM
{
public:
  int Item_ID;
  char Item_Name[ MAX_CHAR_BUFFER ];

  void *Main_Menu;
  void *Prev_Menu;
  void *Sub_Menu;

  MENU_ITEM *Prev_Item;
  MENU_ITEM *Next_Item;
 
  CONTENT *Content_Page;
 
public:
  MENU_ITEM( int a_item_id, char *a_item_name, void* a_main_menu, void* a_prev_menu, void* a_sub_menu, CONTENT* a_contentpage );
 
}; //End of class MENU_ITEM

class LCD_MENU
{
public:
  char MenuTitle[ MAX_CHAR_BUFFER ];
  int ItemCount;
  MENU_ITEM *Items;
 
  MENU_ITEM *Selected;
  int n_select;
 
  CONTENT *Content_Page;

public:
  LCD_MENU( char *a_title  );
 
  void Add_Item( MENU_ITEM *a_item );
  void init();
 

  //----------- User reactions -------
  void Action_Up();
 
  void Action_Down();

  void *Action_Enter();

  void *Action_Back();

}; //End of class LCD_MENU

void CopyString(char *dest, char *src, int buffersize );
void LCD_Show( LiquidCrystal_I2C *screen, LCD_MENU *p_menu );
void Display_Menu( LiquidCrystal_I2C *screen, LCD_MENU *p_menu );
void Display_Content( LiquidCrystal_I2C *screen, CONTENT *p_content );
#endif


//------------------------------------------------------------------------------
//檔案: LCD_MENU.cpp
//
//作者:Anthony Lee (Dephi.Ktop: skcc)
//
//日期:二零一四年二月一日
//
//
// 如需轉載請列明出處,請尊重知識產權。
//
//------------------------------------------------------------------------------

#include "LCD_MENU.h"
 
//------------------------------------------------------------------------------
//------- String Tools -------
void CopyString(char *dest, char *src, int buffersize )
{
memset( dest, 0, buffersize);
int str_l = strlen( src );
if( str_l > buffersize )
memcpy( dest, src, buffersize-1 );
else
memcpy( dest, src, str_l );
}


//------------------------------------------------------------------------------
//------- Display Entry Function --------
void LCD_Show( LiquidCrystal_I2C *screen, LCD_MENU *p_menu )
{
screen->clear();

  if( p_menu->Content_Page != NULL )
  {
    if( p_menu->Content_Page->LineCount > 0 )
    {
  Display_Content( screen, p_menu->Content_Page );
  }
  else
   Display_Menu( screen, p_menu );
  }
else
 Display_Menu( screen, p_menu );
}

//------------------------------------------------------------------------------
//------- Display Menu Items --------
void Display_Menu( LiquidCrystal_I2C *screen, LCD_MENU *p_menu )
{
  char buffer[ MAX_CHAR_BUFFER ];

  screen->setCursor(0,0);
  memset( buffer, 0, MAX_CHAR_BUFFER);
  sprintf( buffer,"[ %s ]", p_menu->MenuTitle );
  screen->print( buffer );

  int n_end;
  int n_start;
  int n_page;  
  int row = 0;
 
  //Found the start item ans end item...
  MENU_ITEM *p_start_item;
  MENU_ITEM *p_end_item;
  p_start_item = p_menu->Items;
  p_end_item = p_menu->Items;
 
  if( p_menu->n_select < (MAX_ROW - 1) )
  {
    n_start = 0;
  }
  else
  {
    n_page =  p_menu->n_select / (MAX_ROW - 1);
    n_start = n_page * (MAX_ROW - 1);
  }

  if( (n_start + MAX_ROW - 1) > p_menu->ItemCount )
    n_end = p_menu->ItemCount;
  else
    n_end = n_start + MAX_ROW - 1;

  for( int i=0; i    p_start_item = p_start_item->Next_Item;
 
  for( int i=0; i    p_end_item = p_end_item->Next_Item;
 
  MENU_ITEM *p_item = p_start_item;
  while( p_item != p_end_item )
  {
    row = row + 1;
    if( p_menu->Selected == p_item )
    {
      screen->setCursor( 0, row );
      screen->print( ">" );          
    }
    screen->setCursor( 1, row );
    screen->print( p_item->Item_Name );
    p_item = p_item->Next_Item;
  }
 
}

//------------------------------------------------------------------------------
//------- Display Conent --------

void Display_Content( LiquidCrystal_I2C *screen, CONTENT *p_content )
{
  char buffer[ MAX_CHAR_BUFFER ];
  int n_end;
  int n_start;
  int n_page;
  int row = -1;
 
  if( p_content->n_select < (MAX_ROW) )
  {
    n_start = 0;
  }
  else
  {
    n_page =  p_content->n_select / (MAX_ROW);
    n_start = n_page * (MAX_ROW);
  }

  if( (n_start + MAX_ROW) > p_content->LineCount )
    n_end = p_content->LineCount;
  else
    n_end = n_start + MAX_ROW;
   
  LINETEXT *p_start_line;
  LINETEXT *p_end_line;
  p_start_line = p_content->p_Head;
  p_end_line = p_content->p_Head;
     
  for( int i=0; i    p_start_line = p_start_line->p_Next;
 
  for( int i=0; i    p_end_line = p_end_line->p_Next;  
   
  LINETEXT *p_line = p_start_line;
  while( p_line != p_end_line )
  {
    row = row + 1;
    if( p_content->Selected == p_line )
    {
      screen->setCursor( 0, row );
      screen->print( ">" );          
    }
    screen->setCursor( 1, row );
    screen->print( p_line->Line_Text );
    p_line = p_line->p_Next;
  }  
   
}

//------------------------------------------------------------------------------
LINETEXT::LINETEXT()
{
  memset( Line_Text, 0, MAX_CHAR_BUFFER);
  Line_Num = 0;
  p_Previous = NULL;
  p_Next = NULL;
}

//------------------------------------------------------------------------------
CONTENT::CONTENT()
{
  LineCount = 0;
  p_Head = NULL;
}

//------------------------------------------------------------------------------
void CONTENT::init()
{
  n_select = 0;
  Selected = p_Head;
}

//------------------------------------------------------------------------------
void CONTENT::Add_Line( char *str_text  )
{
  //Prepare the LINETEXT...
  LINETEXT *a_line = new LINETEXT();
  CopyString( a_line->Line_Text, str_text, MAX_CHAR_BUFFER - 1 );
 
  LINETEXT *p_Line = p_Head;
 
  if( p_Line != NULL )
  {
    while( p_Line->p_Next != NULL )
    {
      p_Line = p_Line->p_Next;
    }
    p_Line->p_Next = a_line;
    a_line->p_Previous = p_Line;
    a_line->p_Next = NULL;
  }
  else
  {
    p_Head = a_line;
    p_Head->p_Previous = NULL;
    p_Head->p_Next = NULL;
  }
   
  a_line->Line_Num = LineCount;
  LineCount++;
}

//------------------------------------------------------------------------------
int CONTENT::Update_Line( int line_num, char * a_text )
{
  //Locate the line....
  LINETEXT *p_Line = p_Head;
  while( p_Line != NULL )
  {
    if( p_Line->Line_Num == line_num )
    {
    CopyString( p_Line->Line_Text, a_text, MAX_CHAR_BUFFER );
    return( 1 );
    }
    p_Line = p_Line->p_Next;
  }
  return( -1 );
}
//------------------------------------------------------------------------------
int CONTENT::Delete_Line( int line_num )
{
  //Locate the line....
  LINETEXT *p_Line = p_Head;
  LINETEXT *p_PrevNode, *p_NextNode;
  int ln = 0;
 
  if( p_Line == NULL )
  {
 LineCount = 0;
 p_Head = NULL;
 init();    
  return(-1);
  }
   
  while( p_Line != NULL )
  {
    if( p_Line->Line_Num == line_num )
    {
      p_PrevNode = p_Line->p_Previous;
      p_NextNode = p_Line->p_Next;
    delete p_Line;
    p_PrevNode->p_Next = p_NextNode;
    p_NextNode->p_Previous = p_PrevNode;
   
    //Re-arrange the line num...
    p_Line = p_Head;
    while( p_Line != NULL )
    {
    p_Line->Line_Num = ln;
    ln++;
p_Line = p_Line->p_Next;
    }
    LineCount = ln;
    init();
    return( 1 );
   
    }
    p_Line = p_Line->p_Next;
  }
  return( -1 );
}

//------------------------------------------------------------------------------
void CONTENT::Clear_All()
{
  LINETEXT *p_PrevNode;
 
  //Get the rear line node...
  LINETEXT *p_RearLine = p_Head;
 
  if( p_RearLine == NULL )
  {
 LineCount = 0;
 p_Head = NULL;
 init();    
  return;
  }
 
  while( p_RearLine->p_Next != NULL )
  {
    p_RearLine = p_RearLine->p_Next;
  }
 
  while( p_RearLine->p_Previous != NULL )
  {
    p_PrevNode = p_RearLine->p_Previous;
    delete p_RearLine;
    p_PrevNode->p_Next = NULL;
    p_RearLine = p_PrevNode;
  }
  LineCount = 0;
  p_Head = NULL;
  init();  
}

//------------------------------------------------------------------------------
MENU_ITEM::MENU_ITEM( int a_item_id, char *a_item_name, void* a_main_menu, void* a_prev_menu, void* a_sub_menu, CONTENT* a_contentpage )
{
  Item_ID = a_item_id;
  CopyString( Item_Name, a_item_name, MAX_CHAR_BUFFER );
  Main_Menu = a_main_menu;
  Prev_Menu = a_prev_menu;
  Sub_Menu = a_sub_menu;

  Content_Page = a_contentpage;
 
  Prev_Item = NULL;
  Next_Item = NULL;
}

//------------------------------------------------------------------------------
LCD_MENU::LCD_MENU( char *a_title  )
{
  CopyString( MenuTitle, a_title, MAX_CHAR_BUFFER );
  Items = NULL;
  ItemCount = 0;
}

//------------------------------------------------------------------------------
void LCD_MENU::Add_Item( MENU_ITEM *a_item )
{
  MENU_ITEM *p_Item = Items;
 
  if( p_Item != NULL )
  {
    while( p_Item->Next_Item != NULL )
    {
      p_Item = p_Item->Next_Item;
    }
    p_Item->Next_Item = a_item;
    a_item->Prev_Item = p_Item;
  }
  else
  {
    Items = a_item;
    Items->Prev_Item = NULL;
  }
   
  a_item->Next_Item = NULL;
  ItemCount++;
}

//------------------------------------------------------------------------------
void LCD_MENU::init()
{
  n_select = 0;
  Selected = Items;
  Content_Page = NULL;
}

//------------------------------------------------------------------------------
  void LCD_MENU::Action_Up()
{
  MENU_ITEM *p_item = Items;
  if( Content_Page == NULL )
  {
 if( n_select > 0 )
 {
   n_select = n_select - 1;
   Selected = Selected->Prev_Item;
 }
  }
  else
  {
 if( Content_Page->n_select > 0 )
 {
   Content_Page->n_select = Content_Page->n_select - 1;
   Content_Page->Selected = Content_Page->Selected->p_Previous;
 }
  }
}

//------------------------------------------------------------------------------
void LCD_MENU::Action_Down()
{
  MENU_ITEM *p_item = Items;
  if( Content_Page == NULL )
  {
 if( n_select < (ItemCount - 1) )
 {
   n_select = n_select + 1;
   Selected = Selected->Next_Item;
 }
  }
  else
  {
 if( Content_Page->n_select < (Content_Page->LineCount - 1))
 {
   Content_Page->n_select = Content_Page->n_select + 1;
   Content_Page->Selected = Content_Page->Selected->p_Next;
 }
  }
}

//------------------------------------------------------------------------------
void *LCD_MENU::Action_Enter()
{
if( Selected->Content_Page != NULL )
{
 Content_Page = Selected->Content_Page;
 return( this );
}
  return( Selected->Sub_Menu );
}

//------------------------------------------------------------------------------
void *LCD_MENU::Action_Back()
{
void* ret_menu;
if( Content_Page != NULL )
{
 Content_Page->n_select = 0;
 Content_Page->Selected = Content_Page->p_Head;
Content_Page = NULL;
return( this );
}
else
{
    ret_menu = Selected->Prev_Menu;
   
    if( ret_menu == NULL )
    return( this );
    else
    {
   Selected = Items; //Reset the pointer at the first item;
   n_select = 0;
   return( ret_menu);
    }
  }
}
//------------------------------------------------------------------------------


以上就是我的這個 LCD_MENU 物件的源始碼…下回將講述如何使用…密切留意。

2014年2月8日 星期六

製作自己的 Arduino

製作自己的 Arduino

要說 Arduino的好玩之處,就是不可不說它可讓你重無到有。亦即是說,你可自己做一塊Arduino塊子。在網上其實已有很多文章講述如何製作 Arduino,我也是從網上收集了一些資訊後,融為己用。因此以下內容的大部份圖和照片均是本人親拍和繪劃,並無抄襲。祇有兩幅示意圖是從網上引用,並且註明出處,敬請注意。

要制作一塊最簡單的Arduino 板,材料如下:
Atmega Microprocessor x 1pc
16MHz 晶振 x 1pc
22pf 陶瓷電容 x 2pcs
10k電阻 x 1pc
104pf 陶瓷電容 x 1pc
微開關 x 1pc
圈圈板 / 麵包板 x 1pc
接線仔或杜邦線多條
排針和排座多條
Programmer x 1pc
ICSP cable x 1pc
USB Module (Optional) x 1pc

這裡所說的Programmer是指燒錄器,Atmega Microprocessor這顆處理器是需要將Arduino Boot Loader燒進去才能使它成為真正的 Arduino,那麼如果沒有燒Boot Loader的 Microprocessor又何分別呢?其分別就是有 Boot Loader的,可以利用 USB Module,直接用Arduino的 IDE將 program編譯後 upload到 Microprocessor裡。沒有 Boot Loader的,每次要把 program編譯後,必需利用 programmer才可 upload到Microprocessor裡。

關於燒錄器,你可以用一塊Arduino UNO又或者 AVR Programmer 又或者自制 Parallel Programmer…由於我的電腦沒有 Parallel Port而我又有塊 UNO 板,所以我假設是用 Arduino UNO 做為 Programmer,亦因為要用 Arduino UNO 做為 Programmer 所以要另加 電解電容100uf 16v用來防止不小心寫到這塊 UNO板,如果想清楚看到 Arduino UNO做 Programmer的燒錄過程中的狀態你可能需要 黃色、紅色、綠色 LED 各一,另加一隻 220歐的電阻。

事前工作


在開始製作自己的 Arduino 板之前,我們是有一些事前工作要預備好的,包括製作 Arduino ISP 即:仿AVR Programmer,與 Arduino ICSP Cable。這條 Cable是用來將 Arduino ISP 連接到我們製作後的 Arduino板子的。

製作 Arduino ISP

好了,如果你所有材料齊全的話,可以開始先製作自己的 Arduino UNO Programmer (即Arduino ISP)。

步驟:
1. 連好 UNO板到電腦。
2. 打開 Arduino IDE。
3. 檢查 Serial Port及板子。
3. File -> Examples -> ArduinoISP。
4. 按下Upload
5. 完成。

怎樣?簡單了吧!這樣就成了 AVR Programmer了。好的,先將 UNO從電腦拔出,將 100uf電解電容的負極插到 GND,正極插到 Reset接口。再將 UNO接上電腦,試試是否無法從 IDE將program上傳到這塊 UNO板!?若是,這代表當我們將此 UNO板用作 programmer時不會把任何東西燒進去,即安全也。然後,為了使我們能看見燒錄過程中的狀態,所以我弄了一塊狀態燈的圈圈板子,其電路如下:



實物 ArduinoISP 狀態燈板子:


由於我那顆 100uf電容腳斷掉,所以焊接在圈圈板:



製作 Arduino ICSP cable

這樣整個 ArduinoISP即 programmer大致上已經完成,現在我們要弄一條 ICSP cable,製作材料很簡單:6芯杜邦線(F-F) 和 膠紙。製作方法很簡單,像 3x2排法即可,要留意的是該6芯杜邦線中,最好祇有一條紅色和一條白色,並且把紅色安排於插頭的左上角,白色安排於插頭的右上角。排好線後,用膠紙把它們綑起來。另外,於 cable 的另一端除了紅色安排於插頭的左上角外,白色要獨立一條分出來就像下圖:



有關 ICSP 的pin assignment 請參閱下圖 (此圖出自effluviaofascatteredmind.blogspot.com ):



我的 ICSP Cable:


開始製作自己的 Arduino

我將製作Arduino板子的過程分為三個部份,包括:Microprocessor基本連接、ICSP接腳引出、Serial接腳引出(包括 Auto Reset)。

Microprocessor基本連接
有關Microprocessor的腳位功能,請自行在網上參閱相關文章。在這裡,我假設是用 Atmega8L-8PU、Atmega328P-PU及是直插式Dip 28封裝的。因為這兩款的腳位是一樣和都是我親自做過的。好了,Microprocessor基本連接請參閱下圖連接:


有關 Atmega8的 pinout可參閱下圖(此圖出自 www.arduino.cc):



ICSP接腳引出
ICSP接腳是用於ArduinoISP把 Boot Loader或programe code上載至 Microprocessor的。ICSP接頭與 Microprocessor的連接可參閱下圖:




Serial接腳引出
Serial接腳是用於連接 USB Module的。USB Module 是一個將 USB 轉成串列埠的一個模組,用以 Arduino與電腦之間的通訊。由於我們是自製Arduino板子,所以要留下接腳,用以將來加個PL2303、CH340、CP2102、FTDI…等的USB Module…再網上流存,FTDI的最穩,但亦最貴。根據本人實測,PL2303與CP2102也不錯,可選有 DTR腳引出的那種(如要作成 Auto Reset)。將 VCC接上板的VCC,GND接上板的 GND,TX接上板的 RX,RX接上板的 TX。

下圖為我平常用的 USB Module PL2303HX,因為它原本是沒有 DTR 接線,所以我要自行將 DTR引出。尤幸打開Module後,發現該板有引出位置可供接線…


另外,我選擇了Support Auto Reset的方法,其方法是DTR接上 104pf陶瓷電容,電容另一隻腳Reset腳。如果是自製板子請於 Reset腳同時另接 10k電阻,電阻另一端接到 VCC。這樣可避免每次upload program 都要按 reset 按鈕。詳細連接,請參閱下圖:




下圖為我的製成品,我特別在 Digital 13 腳再引出到 LED 的正極,其負極接 220Ω電阻,電阻的另一端再接到GND,這樣可方便測試。




燒錄 Boot Loader
燒錄前的連接
在這個環節裡,我們首先要連好 UNO 板子,將 100uf電解電容的負極插到 GND,正極插到 Reset接口。再將狀態燈板子裝好在 UNO板子上。還記得我們那條 ICSP Cable嗎?將白色獨立出來的一邊,插進 UNO板上的ICSP接口,記著紅色要對正 ICSP 接口的 Vcc,然後白色那條插進狀態燈板子預留的 pin10腳位。將ICSP Cable的另一端播進我們的Arduino板子的 ICSP接口,同樣地紅色必須接在 Vcc上。

在Arduino UNO裝上狀態燈板子和100uf電解電容後的實物圖:



用 ICSP Cable將 UNO 和我自製的板子連接後的模樣:



開始燒錄 Boot Loader
1. 將 UNO的 USB cable連上電腦,沒問題的情況下你可見到UNO板上的狀態燈板的綠燈在呼吸(一閃一閃),然後打開 Arduino IDE。
2. 檢查Serial Port。在 Tools->Serial Port中,應為你的 UNO端口。
3. 檢查板子。在 Tools->Board中,應為我們自製的 Arduino板子所用的Microprocessor的類型。例如我們是用 Atmega328,那麼選 Arduino UNO好了。如果是 Atmega8,那麼選 Arduino NG or older w/ ATmega8就對了。
4. 檢查Programmer。在 Tools->Programmer中,請選 Arduino as ISP。(因為我們是用 UNO來做ArduinoISP)
5. 所有設定沒問題的話,按 Tools->Burn Bootloader,開始燒錄。等5秒後,這時你在 UNO板上的狀態燈板的綠燈會長亮,而黃色的就好快的閃爍,這代表正在燒錄。如果出現紅色燈,則你要檢查一下你的電路和ICSP Cable 各部份的連接是否正確。
6. 燒錄完成後,你可見到UNO板上的狀態燈板的綠燈又在呼吸(一閃一閃) 。

上載程式到自製的 Arduino板子
燒錄完成後,把之前所有的連線全部拔掉,而那塊 UNO板亦可功成身退了。然後,接上 USB Module到我們的 Arduino板子。將 USB Module連上電腦,如果你的 USB Module需要安裝 Driver,請在此時安裝。USB Module成功連接後,如果是 Windows,在"Device Manager"可找到該COM port的。如果你是用 Linux,請檢查你的 tty是否有 ttyACM或 ttyUSB0…等等。好了,一切正常的話,你可打開 Arduino的 IDE。
1. 在 Tools->Serial Port中檢查一下連接埠。
2. 檢查板子。在 Tools->Board中,應為我們自製的 Arduino板子所用的Microprocessor的類型。(關於類型請參閱上一個環節: 開始燒錄 Boot Loader的第 3步驟)
3. 按下Upload,上載程式。

下圖為自製的 Arduno裝上 USB Module後的樣子:


如無問題你可發現我們這塊Arduino與一塊 UNO板是無兩樣的,一樣可以自由上載arduino的程式,接口完全一樣。亦因此我用了另一顆Atmega8做了另一塊 Arduino 板子,用來作為 Arduino ISP,把 UNO板留來做其它的事情,雖知道我的板子很便宜呢~~

自製的 ArduinoISP板子:


還可再變型?

細心的朋友可能留意到,我的 Arduino板子其實還預留了擴展空間。就是利用排座將Microprocessor 腳位全部引出,所以我另外弄了個擴展底座。

我的擴展底座:


擴展底座與我的 Arduino板子合體:


我的擴展底座除了將Digital I/O和 Analog I/O引出,使之更適合用於感應器外,當中還加上了外接電源部份。簡單而言,其實加了數顆電容和一顆 L7805作 5v 穩壓之用而已。大家可發揮一下創意,就可以設計出不同功的擴展底座。期待著有更創意的作品…




玩樂無窮的 Arduino

於 2013年尾才開始鑽研 Arduino 。誰知不玩由可,一玩就不能自拔,實在太吸引我了。最初我是看了一些關於 Arduino 的新聞,心想這麼細小的 IC可以做那麼多的事情,實在太牛了吧…對於我不是電子系出身的,這些新聞看了便算,不會當作一回事。後來,因工作方面需要弄一個溫度數據收集器,來即時監測整個 Server Room的不同區域的溫度,這時我就想起 Arduino 了。於是在網上查了一下有關 Arduino的玩法,看過後給我的感覺是—就是這麼簡單!?於是在淘寶買了一塊 UNO R3 和一些感應器,玩了一個星期後便著迷了。以一個人機界面的工程而言,要我編寫個界面用於電腦顯示是一件很容易的事,但如何去用程式驅動硬件就很困難了(也許之前也沒接觸過 8051,也不熟電子零件) 。但當我接觸了 Arduino後,這個再也不是問題了。

下圖為是在面包板上複製Arduino:


Arduino給我的感覺是:

很容易入門 
— 在 Arduino 板子上分清楚:Power接口、Digital I/O接口和 Analog I/O接口。透過程式控制 Digital I/O 高低電位、讀取 I/O 接口的數值,來使不同特性的感應器和電子零件產生作用即成。

硬件連接方面也很簡單
— 我在這裡嘗試簡單地說說我在這幾個月來所收集的資料和我的理解。

電源連接。首先看清楚正負極,若要給電驅動的電子零件,就在Power接口接上。如果Arduino本身不夠電流或電壓,去驅動電流較大的電子零件或電器時,可以加外接電源如:外接變壓器或較高容量的電池 (記著要 Common Ground) 。

感應器連接。如果你的工程需要接收Arduino板子和電腦以外的訊息,例如:溫度、濕度、光度、磁場、濃煙、紅外線、Orientation…等等,就要接上感應器。可按不同需要,零敏度等選擇感應器。再根據該感應器的接腳接上VCC、GND、I/O腳即可。

控制連接。要產生高或低電位來控制的,就接上 Digital I/O。要讀入電壓降,就接 Analog I/O。要果讀入較大電流電壓就要先加個分流電阻。如果要驅動電流較大的電子零件或電器產品,就要選擇先接上 NPN Transistor、光耦、繼電器等等…再利用Digital I/O接口加以開關。如果Digital I/O接腳不夠,可以接個 74HC595(Output Only) 、74HC4067、PCF8574…等等來擴展。如果要輸出PWM值也可直接透過Arduino 本身已有的 PWM pin輸出,如果不夠 PWM pin,可以接TLC5940來擴展。

下圖為我自製的兩組PCF8574擴展板與 Arduino UNO 合體照:


摩打(馬達) 連接。不要直接接上 Arduino板子,因為會燒掉Arduino板子的。你可以透過NPN Transistor、光耦、繼電器等等…如果你要控制摩打又要向前又要向後又或者要控制步進Servo Motor時,請利用 L298 / L295 IC來驅動比較容易及合適。

USB連接。如果你是自製Arduino板子,又或者是 Arduino Pro Mini、 Microduino…等,沒有USB接口的板子。可以加個 PL2303、CH340、CP2102、FTDI…等的USB Module…再網上流存,FTDI的最穩,但亦最貴。根據本人實測,PL2303與CP2102也不錯,可選有 DTR腳引出的那種(如要作成 Auto Reset)。將 VCC接上板的 VCC,GND接上板的 GND,TX接上板的 RX,RX接上板的 TX,DTR接上 104pf陶瓷電容,電容另一隻腳Reset腳。如果是自製板子請於 Reset腳同時另接 10k電阻,電阻另一端接到 VCC。

Arduino 的程式 Framework 很簡單很容易令人理解 
— 一般的開發者,如果已經懂 C / C++則即可上馬,完全無痛。Arduino 的程序很簡單,主要有兩個函式:void setup() 和 void loop()。setup()就是把要預設的東西,通通放到這裡就是了。例如:pinMode(led, OUTPUT); 簡單而言就是 Initialization... 初始化所有設定。loop()簡單不過,就是不停重覆又重覆運行…除非你個 program有 bug又或者 memory overflow,如果不是的話,它是會不停地運行直至電源給拔掉才會停止。

我下次會嘗試以我消化之後的角度去談談如何複製 Arduino…