2013/08/27

讓Arduino Uno變成USB鍵盤

最早期的Arduino板子使用序列埠與電腦溝通,但現今一般電腦上已經找不到序列埠了(9針或25針),後來Arduino的板子採用FTDI公司的USB轉序列埠的晶片,讓電腦的USB埠化身變成虛擬序列埠;而Uno板改用Atmega8U2或Atmega16U2(R3),其實它就是一顆具有USB功能的微控制器,只不過在出廠時它預先燒錄具有「USB轉序列埠」功能的韌體,於是可作為電腦USB埠與Arduino板微控制器序列埠之間的中介橋樑。但我們可以讓8U2/16U2進入DFU(Device Firmware Update)模式,便可更新韌體,燒錄別的功能,譬如變成USB鍵盤,從Arduino板子傳送「按鍵」給電腦。

所謂DFU是USB規格的其中一項標準,讓USB裝置韌體更新的功能予以標準化,在Atmega8U2/Atmega16U2裡已含有DFU bootloader,我們從電腦將韌體傳給DFU bootloader,進行燒錄動作。

我的環境:電腦是Windows XP、開發板是Arduino Uno R3、Arduino軟體開發環境是1.0.5版。底下提到「韌體」時,若無特別註明,皆指8U2/16U2的韌體。

下圖紅圈處便是Uno R3的Atmega16U2,旁邊2x3針腳是它的ICSP接頭。


我們要做的事情有:
  • 下載並安裝電腦端的DFU燒錄軟體,Atmel Flip。
  • 讓Arduino Uno進入DFU模式,安裝它的DFU模式驅動程式。
  • 燒錄「不斷送出hello world按鍵」的草稿碼到板子的微控制器晶片裡。
  • 燒錄8U2/16U2的新韌體,變成USB鍵盤。
  • 在電腦上看到從Arduino Uno不斷傳來的「hello world」。
電腦端的DFU燒錄軟體,我們將使用Atmel提供的Flip這套軟體(需要Java),可到Atmel網站下載,有Windows版,安裝過程很簡單,一直按下一步即可,在此不贅述。Mac與Linux可使用dfu-programmer,此處不介紹。

然後請連接Arduino板電腦,可在裝置管理員裡找到代表板子的連接埠。下圖顯示出我的板子Arduino Uno R3。


要進入DFU模式,需重置8U2/16U2,作法是讓下圖的兩個腳位接觸一下(只需一下即可),其中一個是GND接地,另一個是RESET腳位,接觸一下即可重置。


若是R2以前的Uno板,必須在下圖這個位置焊接10K歐姆的電阻,我手上沒有這種板子,所以沒實際試過。


重置後,Windows XP電腦端就會發現硬體。


請選「從清單或特定位置安裝(進階)」。


然後選擇剛剛的Flip安裝目錄裡的usb子目錄,裡頭含有驅動程式。


接下來就稍等一下。


安裝成功囉。


然後在裝置管理員裡便可看到ATmega16U2。您的板子若不是Arduino Uno R3,將會看到別的晶片型號。


接下來,請到這裡下載,裡頭含有能讓Arduino板不斷送出「hello world」的草稿碼(helloworld/helloworld.pde),還有8U2/16U2的新韌體(Arduino-keyboard-0.3.hex),能讓Arduino Uno板變成USB鍵盤。

請拔除板子的電源與USB線進行重置,便可離開DFU模式回到一般的模式,然後燒錄(上傳)剛剛下載的helloworld/helloworld.pde。

接下來要燒錄新韌體,如前所述,讓8U2/16U2的RESET腳位接觸GND接地,一下即可,便可進入DFU模式,然後開啟Flip,出現如下畫面。


點按左上角看起來像是晶片的圖示,或是選「Device-Select...」,以我的例子而言,應選ATmega16U2。您應該選在裝置管理員裡所看到的晶片型號。


然後點按看起來像USB插頭的圖示,然後點按「USB」;或是選「Settings-Communication-USB」。


然後畫面便會從灰色變成彩色。


然後請選「File-Load HEX File...」,選擇剛剛下載的Arduino-keyboard-0.3.hex韌體,點按左下「Run」進行燒錄,然後點按右下的「Start Application」重置。

此時Arduino板便會是USB鍵盤,不斷地送出「hello world」,請隨便開啟一個文字編輯器,便會看到類似下圖的樣子。


若沒看到,可能板子並未重置成功,請手動拔掉板子的電源與USB線,再插入。

以上便上讓Arduino Uno變成USB鍵盤的過程,若你想讓板子回復到原來的樣子,只需重新燒錄原本的「USB轉序列埠」功能的韌體,位於Arduino目錄裡的hardware\arduino\firmwares\atmegaxxu2\arduino-usbserial\Arduino-usbserial-atmega16u2-Uno-Rev3.hex,這支是我Arduino Uno R3板的韌體,您應該找找適合你的板子的韌體,燒錄後即可還原,再以一般方式燒錄其他草稿碼即可。


參考資料:

52 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. 不好意思請問一下
    我的Arduino似乎進不去DFU mode
    我發現好像是16u2的韌體太舊,我剛好也有兩塊arduino,所以想要利用一塊當isp燒錄器來燒錄另一塊Arduino的16u2韌體
    但是網路上的資料對於這方面有點複雜,不知道有沒有建議的方向或是資料願意提供呢?
    感謝

    ReplyDelete
    Replies
    1. 我沒有用過一塊Arduino當ISP燒錄器來燒錄另一塊Arduino的16u2韌體,也沒用過單獨的ISP燒錄器來燒錄。

      沒有這方面的經驗。orz。

      Delete
  3. 沒關係
    最近有找到相關資料了
    http://blog.xuite.net/bv2ci/twblog/125382518-Arduino+UNO+R3+%E7%84%A1%E6%B3%95%E9%80%B2%E5%85%A5+DFU+%E6%A8%A1%E5%BC%8F
    還是感謝你的幫忙!!

    ReplyDelete
    Replies
    1. Good.
      有空我也會看看,謝謝。

      Delete
  4. 你好,我是arduino新手,請問一下如果今天想要讓板子同時使用mouse click and USB keyboard,是要燒兩個HEX給板子(keyboard 和mouse)嗎?
    但是燒錄是不是一次只能燒一個?
    我現在有一塊UNO板子,我想讓他同時可以控制滑鼠與鍵盤,那可以有同時做到這兩個方式嗎?
    謝謝你的幫忙

    ReplyDelete
  5. 這篇是換掉UNO的Atmega16U2的韌體,模擬USB鍵盤或USB滑鼠。
    根據我的了解,無法同時模擬。

    如果是Leonardo,應該可以使用程式庫Keyboard與Mouse,同時模擬。

    ReplyDelete
    Replies
    1. 謝謝你即時的回覆!!
      那另外想請教一下,利用模擬USB滑鼠,如果我想要使用mouse.ckick();這個方法,再模擬滑鼠中要怎麼使用呢?
      因為我找到的幾乎都是模擬滑鼠移動,不清楚如果我想要模擬double click或是click要如何使用?

      真的太謝謝你的幫忙了

      Delete
  6. 我找到似乎是用下面這個方式?
    mouseReport.buttons = 0;
    mouseReport.x = 0;
    mouseReport.y = 0;
    mouseReport.wheel = 0;
    但是細節部份還要釐清

    ReplyDelete
    Replies
    1. 呼叫click(button),傳入參數,
      參數見http://arduino.cc/en/Reference/MouseClick

      Delete
    2. 我呼叫click他說只適用於arduino leonardo,為什麼會這樣呢

      Delete
  7. 程式庫Keyboard與Mouse只有Leonardo, Micro, or Due能用。

    ReplyDelete
    Replies
    1. 恩是的,那我是用uno R3,然後目前是換掉UNO的Atmega16U2的韌體,模擬USB滑鼠。

      想請問如果在這種方式下,我想要做到跟mouse click這個方法一樣的話,在模擬滑鼠中要怎麼使用?

      因為我找到的幾乎都是模擬滑鼠移動,不清楚如果我想要模擬double click或是click要如何使用?
      謝謝你的幫忙。

      Delete
    2. 參考http://coopermaa2nd.blogspot.tw/2011/11/arduino-uno-mouse.html
      mouseReport.buttons填入底下想要的值:
      Bit 0 – Left Button
      Bit 1 – Middle Button
      Bit 2 – Right Button

      Delete
  8. 恩謝謝你的幫忙,但是目前我不知道要怎麼表示在我想要的座標點去double click,
    例如我想要在(x,y)=(565,573)的地方按下double click,
    以上面這個方法我不太清楚應該怎麼表示。
    抱歉,我想順便問一下,
    Serial.write((uint8_t *)&mouseReport, 4);
    Serial.write((uint8_t *)&nullReport, 4);
    這兩行是代表甚麼意思呢?
    真的感謝你的回答,謝謝!!

    ReplyDelete
    Replies
    1. Serial.write((uint8_t *)&mouseReport, 4);
      Serial.write((uint8_t *)&nullReport, 4);
      是把mouseReport的內容送往Serial,就此情況而言,會是送往電腦端,也就是把滑鼠的狀態,送給電腦。

      因為double click就是送出兩次click,
      所以應該要執行兩次上述的程式碼。

      Delete
    2. 所以如果我想要在座標(56,57)地方滑鼠double click是這樣寫嗎?

      mouseReport.buttons = LEFT_BUTTON;
      mouseReport.x = 56;
      mouseReport.y = 57;
      mouseReport.wheel = 0;
      // Send
      Serial.write((uint8_t *)&mouseReport, 4);
      Serial.write((uint8_t *)&nullReport, 4);
      Serial.write((uint8_t *)&mouseReport, 4);
      Serial.write((uint8_t *)&nullReport, 4);
      delay(50);
      抱歉,因為我不清楚要在哪裡指定座標,謝謝你

      Delete
    3. 試試看就知道了。

      Delete
  9. 葉難好師您好, 再參閱您的書後,順利寫出keypad之完成鍵。
    目前想以一個keypad藉由切換,來控制兩個加熱棒
    以下是以一個keypad控制一家熱棒的程式
    if (key != NO_KEY){
    int b = key - 48;//因為是ASCII數值,所以才要-48
    lcd.print(b);
    if(key == '#'){
    Serial.println(a);
    lcd.clear();
    lcd.print(a);
    int ob = a;
    analogWrite(heaterPin, ob); // heater輸出功率
    }
    else if(key == '*'){
    a =0;//輸入錯誤從新開始打一次
    int ob =0;
    }
    else{
    a = a*10+b;
    }
    if(key == '*'){
    lcd.clear();
    }

    }

    想請教老師,該如何修改做出切換的功能呢?
    (ex.同時按下*與#顯是初選擇的畫面,然後再輸入1or2 來選擇所要控制的加熱棒)
    以下是我自己亂寫的程式,但無法順利達成目標
    if (key != NO_KEY){
    if(key == '*' && '#'){
    lcd.print("select 1 or 2");
    Serial.println("select 1 or 2");

    if(key == '1'){
    lcd.clear();
    int b = key - 48;//因為是ASCII數值,所以才要-48
    lcd.print(b);......恕刪

    還望老師能指點一下 感激不盡 3Q !!

    ReplyDelete
  10. > 同時按下*與#
    這功能做不到吧。

    試試看底下的程式碼。因為沒電路,所以沒測試過。
    在heater功率數值最後加上'1'代表要給加熱棒1,加上'2'代表要給加熱棒2。
    譬如輸入5431#的話,代表要把543設定給加熱棒1。
    if (key != NO_KEY){
    int b = key - 48;//因為是ASCII數值,所以才要-48
    lcd.print(b);

    if(key == '#'){
    Serial.println(a);
    lcd.clear();
    if(a % 2 == 0){ // 代表偶數,代表加熱棒2
    a /= 10;
    analogWrite(heaterPin_2, a); // heater輸出功率
    }
    else{ // 代表奇數,代表加熱棒1
    a / = 10;
    analogWrite(heaterPin_1, a); // heater輸出功率
    }
    lcd.print(a);
    }
    else if(key == '*'){
    lcd.clear();
    a = 0;//輸入錯誤從新開始打一次
    }
    else{
    a = a*10+b;
    }
    }

    ReplyDelete
    Replies
    1. 感謝 葉難老師的幫忙 以經順利完成了 !! 3Q~~~

      Delete
  11. 您好,

    小弟使用Arduino UNO R3 (from Arduino.Org)於Windows 7時,以老師教授的方法,將Atmega16u2之RESET與GND接觸一下後,
    Win 7會自動安裝driver,並回報無法順利安裝,於裝置管理員發現Atmega16u2出現驚嘆號,手動更新驅動程式(指向"Flip 3.4.7\usb")後仍為驚嘆號,
    執行Flip於USB時會出現"cannot open USB device",請問老師是否知道發生何事...

    感謝幫忙!!!

    ReplyDelete
    Replies
    1. 問題就是「驅動程式沒裝好」。

      至於原因與如何解決,嗯,再試試看吧...

      Delete
    2. 瞭解'感謝幫忙!!!
      改至Win XP已可進入DFU mode與安裝driver。

      Delete
  12. 您好,

    請問燒錄Atmega16u2之hex file除了您文中提到之"Arduino-usbserial-atmega16u2-Uno-Rev3.hex",另外
    在Arduino org之網頁 "http://labs.arduino.org/Guide+to+reflash+Atmega+16u2"
    提到需用"UNO-dfu_and_usbserial_combined.hex",老師是否知道兩份hex file有何不同...

    小弟將hex file燒錄至Atmega16u2、並將bootloader燒錄至Atmega328P-PU後,目前之Arduino UNO upload
    Blink會出現以下message,不知老師是否知道原因...

    感謝幫忙!!!

    Arduino:1.7.7 (Windows 7), 板子:"Arduino Uno"

    草稿碼使用了 1,030 bytes (3%) 的程式存儲空間。最大值為 32,256 bytes。

    全域變數使用了 9 bytes (0%) 動態記憶體,剩餘 2,039 bytes 的局部變數。最大值為 2,048 bytes 。

    avrdude: stk500_recv(): programmer is not responding

    avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0xfa

    avrdude: stk500_recv(): programmer is not responding

    avrdude: stk500_getsync() attempt 2 of 10: not in sync: resp=0xfa

    avrdude: stk500_recv(): programmer is not responding

    avrdude: stk500_getsync() attempt 3 of 10: not in sync: resp=0xfa

    avrdude: stk500_recv(): programmer is not responding

    avrdude: stk500_getsync() attempt 4 of 10: not in sync: resp=0xfa

    avrdude: stk500_recv(): programmer is not responding

    avrdude: stk500_getsync() attempt 5 of 10: not in sync: resp=0xfa

    avrdude: stk500_recv(): programmer is not responding

    avrdude: stk500_getsync() attempt 6 of 10: not in sync: resp=0xfa

    avrdude: stk500_recv(): programmer is not responding

    avrdude: stk500_getsync() attempt 7 of 10: not in sync: resp=0xfa

    avrdude: stk500_recv(): programmer is not responding

    avrdude: stk500_getsync() attempt 8 of 10: not in sync: resp=0xfa

    上傳到板子時發生問題。可行建議請見http://www.arduino.cc/en/Guide/Troubleshooting#upload。

    avrdude: stk500_recv(): programmer is not responding

    avrdude: stk500_getsync() attempt 9 of 10: not in sync: resp=0xfa

    avrdude: stk500_recv(): programmer is not responding

    avrdude: stk500_getsync() attempt 10 of 10: not in sync: resp=0xfa

    這份報告的詳情將會在
    "編譯時顯示詳細輸出資訊"
    在檔案 > 偏好設定裡啟用。

    ReplyDelete
  13. 葉老師您好
    如果我要由rfid讀卡號再將卡號輸出應該如何做?
    1.我會讀rfid顯示再lcd
    2.轉變成usb鍵盤輸出字串
    但是我兩個兜不起來成一個

    ReplyDelete
    Replies
    1. 既然已經能夠讀出rfid卡號,
      接著就是以usb鍵盤的身分,把卡號轉成一個個按鍵,送出去。
      這一篇主要著重於韌體更換,程式部分,請參考
      https://docs.google.com/file/d/0B4GOwiN2Qm96M2FiNmFmMjMtMjU5MS00N2E4LTk5MDUtNDVjYWI5MmExZjlm/edit
      裡面的
      helloworld.pde

      看它如何送出helloworld等字母。

      Delete
    2. 好....我試試...謝謝!

      Delete
    3. 葉老師您好
      那如果我要把RFID 讀取的卡號(serNum[0],serNum[1],serNum[2],serNum[3],serNum[4]) 多個 int 轉成一個字串(String)該怎麼做呢?

      Delete
    4. serNum是個別分開的嗎?還是應該看做一體?

      不就是直接丟入String嗎?
      String myString = String(n);

      或者,可使用stdlib.h的itoa()。

      其實若有個int n;,可以直接送往序列埠,Serial.print(n),那就會轉成ASCII編碼的字串。

      Delete
  14. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  15. 板子是UNO R3 ATmega16U2
    進入DFU mode後回不去了...
    開啟安裝路徑中的Arduino-usbserial-atmega16u2-Uno-Rev3.hex
    到按run都正常
    可是一按Start Application
    視窗右下角的狀態欄
    就從USB ON 變成 Communication OFF
    然後會聽到硬體卸載的音效
    在裝置管理員也看不到板子
    需要短路RESET和GND才能再抓到板子
    請問這是為什麼呢?

    ReplyDelete
    Replies
    1. 你想燒錄板子原本的韌體Arduino-usbserial-atmega16u2-Uno-Rev3.hex,對吧,

      需要做的動作,應該是從【請拔除板子的電源與USB線進行重置,便可離開DFU模式回到一般的模式...】此處往下。

      至於你說的問題,我猜是步驟順序不對吧。不確定。

      Delete
    2. 感謝葉老師的回覆!
      覺得奇怪的是我有成功燒錄keyboard-0.3.hex
      也有成功的讓打出hello world
      是在想燒回板子韌體時才出現Communication OFF的問題

      當時也有試過老師所說的移除電源、USB重接
      不過一接上電腦還是在DFU模式裡面回不來

      覺得沒轍的把板子收起來兩天
      沒想到再次拿出來的時候電腦抓到的是一般模式!!
      也可以正常的進出DFU

      難道是某些暫存器出了問題
      因為斷電太久而釋放才變正常了嗎...

      Delete
    3. 嘛,細節不明,無法回答。

      總之能動了,恭喜你。

      Delete
  16. 如果我將蜂鳴器反用當作震動感應元件,當arduino收到震動時會讓電腦輸入一個英文字,請問程式碼該如何修改呢?
    摸索了一陣子都沒有頭緒,如果可以的話希望能為我解答,感恩

    ReplyDelete
    Replies
    1. 收到震動時,arduino傳出按鍵(文字)給電腦,不是嗎?

      > 如何修改呢?
      問題太模糊了。

      程式碼?

      Delete
    2. 就是將原本會一直發送hello world修改成只要收到震動arduino就會傳出按鍵文字給電腦。
      由於最近才開始接觸arduino所以很多地方還不太明白,請見諒。

      Delete
    3. 如你所說,
      在程式裡,不斷地偵測有無震動,
      有震動時,就傳出代表按鍵的資料給電腦。

      Best regards.

      Delete
    4. 沒錯,就是這個意思,只是我一直搞不太清楚要怎麼做才能夠達到那種效果,請問該怎麼處理呢?
      我是想讀取A0的數值然後由數值大小判斷是否讓arduino輸入按鍵,目前嘗試的方法都沒辦法成功。

      Delete
    5. 嗯,基本樣子應該如下吧
      void loop(){
      int x = analogRead(A);
      if(x > 800){
      // do something...
      }
      else if(x > 400){
      // do something...
      }
      else{
      // do something...
      }
      }

      Delete
    6. This comment has been removed by the author.

      Delete
    7. 我目前已經做到這樣了,可是產生了按鍵變成壓著不放的狀況,請問需要加入什麼來讓按鍵放開呢?
      #define KEY_D 7
      uint16_t val;
      uint8_t buf[8] = { 0 };
      void setup()
      {
      Serial.begin(9600);
      pinMode(A0, INPUT);
      //delay(100);
      }

      void loop()
      {
      val = analogRead(A0);
      if(val>30){
      buf[2] = KEY_D;
      Serial.write(buf, 8);
      }
      delay(100);

      }

      Delete
    8. 要放掉按鍵
      uint8_t keyNone[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };

      Serial.write(keyNone, 8);

      Delete
    9. 成功了!!感謝!!

      Delete
  17. This comment has been removed by the author.

    ReplyDelete
  18. 請問葉難大師你給程式碼能更改輸出的HELLO WORLD嗎,能否改成其他文字?還是只能固定輸出HELLO WORLD?

    ReplyDelete

  19. 請問葉老師如果要將紅外線、超音波感測器從序列埠上讀取到的多筆數值,輸出成TXT檔可行嗎(週邊界面是Arduino接USB->PC)? 還有主程式寫法是用存檔指令嗎(類似以下範例)?

    #include "stdafx.h"
    #include "iostream"
    using namespace std;
    int _tmain(int argc, _TCHAR* argv[])
    {
    int a[10];
    for (size_t i=0; i<10; i++)
    {
    a[i] = 255;
    }
    while (true)
    {
    cin >> a[0];

    if ((a[0] - a[9]) <-100)
    {
    cout << "black" << endl;
    }
    else
    {
    cout << "white" << endl;
    }
    for (size_t i=0; i<10; i++)
    {
    cout << "a[" << i << "]=" << a[i] << " ; ";
    }
    cout << endl;
    for (size_t i=9; i>0; i--)
    {
    a[i] = a[i-1];
    }
    }
    system("pause");
    return 0;
    }

    ReplyDelete
    Replies
    1. 用fstream,開檔,然後就可以用 << 與 >> 來存取檔案。

      Delete
  20. 我买了一个arduino uno r3 Arduino Uno R3 我想学习和使用它。请帮我看看它的基本用法和教程网站 Arduino Uno R3

    ReplyDelete