在利用ROHM感測器評估套件實現UCLA AirMouse – 第1部分中,我們完成了專案的硬體。對於發射機部分,我們將AirMouse按鈕以及Uno的GPIO引腳和RF模組之間的介面組裝在一塊麵包板上。由於ROHM感測器擴展板(SensorShield)在原型設計和DIY專案方面的應用非常方便,因此我們選擇將其與加速度計模組接合在一起使用。正如您將在本教材中看到的那樣,ROHM擴展板內建Arduino與其外設之間的 I2C 通信功能,因此,用戶通過一些簡單代碼就可以接收加速度計的資料,無需編寫任何底層 I2C 函數來發送和接收來自裝置位址的資料。對於接收器部分,我們為Teensy微控制器組裝了類似的分線板,以便與RF模組對接。
本教材介紹和描述的代碼將幫您將兩個模組連接在一起,以完成該項目。我們將向您展示兩個模組之間發送資料的基本代碼以及處理加速度計資料以在電腦顯示器上行動游標的基本代碼。同時,我們希望您能創造一些更酷的補充和改進!
我們都用過電腦滑鼠,但是它們只能在桌面之類的平面上工作。我們已經製作了一個“AirMouse”——一款能夠在3D空間中運行的電腦滑鼠。使用者通過傾斜滑鼠就可以讓螢幕上的游標行動,從而可以進行大範圍自訂動作。我們已經在第1部分中介紹了構建AirMouse的基礎知識。AirMouse主要由兩部分組成:戴在手上的滑鼠發射機和連接使用者電腦的接收器。發射機收集滑鼠的方向和按鈕狀態資訊;而接收器則負責轉換這些資訊,從而在電腦螢幕上執行相應操作。AirMouse由Arduino Uno和nRF24L01射頻模組結合ROHM感測器擴展板的加速度計構建而成。
發射機:
接收器:
按照第1部分連接硬體之後,您就可以利用以下程式運行發送器和接收器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
#include <SPI.h> #include "RF24.h" #define byte uint8_t #include <Wire.h> #include <KX022.h> KX022 accelerometer(KX022_DEVICE_ADDRESS_1E); RF24 radio(9,10); uint64_t pipes[2] = {0xF0F0F0F0F0, 0xF0F1F1F1F1}; //reading, writing void initRadio() { radio.setPALevel(RF24_PA_HIGH); //payload size default 32... radio.setChannel(10); //you can change the channel setting radio.setCRCLength(RF24_CRC_16); //2-byte CRC radio.setDataRate(RF24_1MBPS); //1Mbps data rate radio.openReadingPipe(0, pipes[0]); radio.openWritingPipe(pipes[1]); } #define buttonPinR 2 //change these accordingly #define buttonPinL 3 void setup() { // put your setup code here, to run once: Serial.begin(9600); while (!Serial); pinMode(buttonPinR, INPUT); pinMode(buttonPinL, INPUT); radio.begin(); initRadio(); radio.stopListening(); Wire.begin(); accelerometer.init(); } long lastDebounceTimeR = 0; // the last time the output pin was toggled long lastDebounceTimeL = 0; long debounceDelay = 50; int buttonStateR = LOW; // the current reading from the input pin int buttonStateL = LOW; int lastReadingR = LOW; int lastReadingL = LOW; char readButtonR(){ int reading = digitalRead(buttonPinR);//get what state the button is char out = 'a';//the value to return if nothing special happened if (reading != lastReadingR) {//We're reading a new state for button // reset the debouncing timer lastDebounceTimeR = millis(); } if ((millis() - lastDebounceTimeR) > debounceDelay) {//We finally have a stable value if (reading != buttonStateR)//Compared to our previous state, we have a flip { out = 'r';//prepare to toggle the Mini } buttonStateR = reading;//Make the buttonState the same } lastReadingR = reading;//make the last state the "current" state return out; } char readButtonL(){ int reading = digitalRead(buttonPinL); char out = 'a'; if (reading != lastReadingL) { // reset the debouncing timer lastDebounceTimeL = millis(); } if ((millis() - lastDebounceTimeL) > debounceDelay) { if (reading != buttonStateL) { out = 'l'; } buttonStateL = reading; } lastReadingL = reading; return out; } struct data { boolean isPushedR = false; boolean isPushedL = false; int8_t acceleration[3] = {0, 0, 0}; }; data packet; boolean rState = false;//these states are used to represent the current state of the buttons boolean lState = false; void loop() { if(readButtonR() == 'r'){ //toggle button state when button state change is detected rState = !rState; } if(readButtonL() == 'l'){ //toggle button state when button state change is detected lState=!lState; } packet.isPushedR = rState; packet.isPushedL = lState; uint8_t rc; float acc[3]; rc = accelerometer.get_val(acc); if (rc == 0) { //we cast to drop the decimal, we don't need that high precision packet.acceleration[0] = (int8_t)(acc[0]*100); //x //Serial.print(packet.acceleration[0]); Serial.print(" "); packet.acceleration[1] = (int8_t)(acc[1]*100); //y //Serial.print(packet.acceleration[1]); Serial.print(" "); packet.acceleration[2] = (int8_t)(acc[2]*100); //z //Serial.println(packet.acceleration[2]); } radio.write((char*) &packet, sizeof(packet)); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
#include <SPI.h> #include "RF24.h" RF24 radio(9,10); uint64_t pipes[2] = {0xF0F1F1F1F1, 0xF0F0F0F0F0}; //reading, writing void initRadio() { radio.setPALevel(RF24_PA_HIGH); //payload size default 32... radio.setChannel(10); radio.setCRCLength(RF24_CRC_16); //2-byte CRC radio.setDataRate(RF24_1MBPS); //1Mbps data rate radio.openReadingPipe(0, pipes[0]); //reading pipe radio.openWritingPipe(pipes[1]); radio.startListening(); } #define R_PIN 6 //Red LED #define G_PIN 7 //Green LED #define Y_PIN 8 //Yellow LED void setup() { Serial.begin(9600); while(!Serial); //wait until Serial is initialized...(we found that not including this line of code caused errors on the //Teensy because it started executing code without ensuring that Serial communication with the laptop was //properly initialized... radio.begin(); initRadio(); Mouse.screenSize(1920, 1080); // configure screen size randomSeed(analogRead(0)); pinMode(R_PIN, OUTPUT); pinMode(G_PIN, OUTPUT); pinMode(Y_PIN, OUTPUT); } #define CALIX 6 //calibration for X #define CALIY -1 //calibration for Y #define scalingFactor 0.05 #define THRESHOLD 1 double moveVector[2] = {0, 0}; void tiltToVector(const int8_t* acceleration){ moveVector[0] = 0; moveVector[1] = 0; if(abs(acceleration[0] - CALIX) > THRESHOLD){ //calculate move moveVector[1] = (double)(acceleration[0] * scalingFactor); } if(abs(acceleration[1] - CALIY) > THRESHOLD) { moveVector[0] = (double)(acceleration[1] * scalingFactor); } } struct data { boolean isPushedR = false; boolean isPushedL = false; int8_t acceleration[3] = {0, 0, 0}; }; data packet; void loop() { bool stillWaiting = true; //Serial.println("About to read"); while(stillWaiting){ if(radio.available(0)){ //You've got mail!!! radio.read((char*) &packet, sizeof(packet)); stillWaiting = false; } } Mouse.move(moveVector[0], moveVector[1]); Mouse.move(moveVector[0], moveVector[1]); //call it twice within the loop for smoothness :) //prints for debugging purposes Serial.println("Finished writing the pins"); if (packet.isPushedR) { Serial.println("The right button has been clicked!!! (Did you mean to right click?!?!)"); } if (packet.isPushedL) { Serial.println("The left button has been clicked!!! (Did you mean to left click?!?!)"); //Mouse.click(); } Serial.print("X: "); Serial.println(packet.acceleration[0]); Serial.print("Y: "); Serial.println(packet.acceleration[1]); Serial.print("Z: "); Serial.println(packet.acceleration[2]); tiltToVector(packet.acceleration); //re-calculate move vector coordinates // Mouse.move(moveVector[0], moveVector[1]); } |
將代碼上傳到Teensy與將代碼上傳到Arduino Uno略有不同。對於Uno,您只需按照通常的編譯和上傳步驟執行即可:
而對於Teensy,請按照以下步驟上傳接收器代碼:
基於本教材之目的,我們不會詳細涉及所使用的不同通信協議,也不會詳細介紹RF模組的通信軟體。要瞭解有關這些主題的更多資訊,請查看我們的通信協議 和 nRF24L01+ 模組 教材。相反,我們將簡要介紹軟體中主控制電路的工作原理。
在AirMouse中,發射機負責收集資料,但大部分資料處理由接收器模組進行。系統的這種設計方式使得Arduino——比Teensy更弱的處理器——只需要收集資料即可,因此能夠在決策和計算上花費更少的資源,並能夠以更快的週期運行。通過這種實現方法,兩台裝置之間唯一發送的資料就是原始加速計資料和按鈕資料。Teensy接收這些原始資料並進行處理,從而在電腦螢幕上執行相應操作。
為了檢測AirMouse的方位,系統必須能夠解析原始加速計資料。要做到這一點,首先必須確定每個座標的“零值”。零值的定義如下:AirMouse保持平坦(平行於地面)時每個軸的加速度計輸出。確定零值後,軟體就能夠將加速度計資料轉換為方向和數量,通過分析每個軸的加速度(由於重力)並將其與零值進行比較以便在螢幕上行動游標。
現在,我們來看一下接收器模組與電腦之間的交互。Teensy被指定為USB人機介面裝置(這裡是指USB滑鼠)。解析方位資料後,軟體會計算游標行動的速度和方向。此外,該軟體還將點擊左鍵解析為左鍵按一下,將點擊右鍵解析為按右鍵,調用適當的方法在電腦螢幕上顯示左鍵按一下或按右鍵功能。以下才是最酷的部分:您完全可以只通過軟體就能夠修改或添加滑鼠的螢幕功能!目前,滑鼠只具有最基本的功能和特性,但是您只需對軟體進行簡單改動,就可以輕鬆添加諸如滾動、將游標移至螢幕上的某個點等功能!(請點擊此處,瞭解Teensy USB滑鼠的參考指南)以下是您可以實現的一些很酷的硬體和軟體想法:
我們希望您能夠喜歡這個AirMouse項目,並且非常期待您可以對設計和功能進行修改和改進!
作為UCLA IEEE進階專案(Advanced Projects)計畫的一部分,最初的AirMouse由Rahul Iyer、Aaron和Andrew Wilhelm研發。更多資訊請參閱 http://ieeebruins.org
Device Plus正在尋找與活躍的學生工程組織和實驗室展開合作。想瞭解我們硬體贊助計畫的更多資訊,請通過以下電子郵件聯繫我們:info@deviceplus.com。