# Xenomai Example on Stepper Motor * [Source code on GitHub](https://github.com/oiz5201618/Xenomai-3-example-demo) ## 實作影片 {%youtube byIJinAMxvE %} --- ## 控制原理 使用 rotary encoder 來取得旋轉的控制角度,其解析度為 400 脈衝/轉,而透過 AB 相正交訊號可以解析出 1600 脈衝/轉,也就是 0.225度/脈衝;而二相步進馬達其步進角為 1.8度/步,在藉由 A4988 驅動晶片將其步進角降為 1/8 也剛好是 0.225度/步,透過這樣的關係,當 rotary encoder 收到輸入訊號時,步進馬達必須馬上做出相對應的控制,因此輸入到輸出間的誤差(稱之為 jitter)愈穩定愈好(愈小不見得愈好,必須是穩定誤差才好控制),在此 Xenomai 便可以發揮其即時回應的功能,來確保系統的即時性。 目前是使用**開迴路控制**,因為沒有第二顆 rotary encoder 當回授來做閉迴路控制,之後添購後,再加入PID控制的實作。 引述**康哲豪**大大的說法: 影片的測試方式,會頻繁地產生process並配置記憶體(./eatmem & ./eatmem & ./eatmem ...)。Latency的原因可能為: * Kernel的記憶體管理機制需要對這些Process配置Address Space。除此之外,在SMP環境裡,這個過程還會flush每個核心的L1 cache,來確保一致性。 * Process(eatmem)數目,在測試的期間是變動的,因此會增加schedule的複雜度。 * 在Non-preemptive Kernel環境裡,Kernel許多地方是non-preemptable,例如spin_lock保護的的區段,因此更會使得上述兩個情況產生大的latency。 --- ## 實驗器材 **1. Raspberry Pi3 b+ with Raspbain** <img src="https://i.imgur.com/BuihOZV.png" style="width:480px;height:480px;display: block;margin:auto;"><br> **2. Linux core v4.1 + Xenomai v3.0.2** * [安裝方法](https://hackmd.io/s/H19m8lWgx) **3. 步進馬達** * [馬達規格](http://goods.ruten.com.tw/item/show?21310205707873) <img src="https://i.imgur.com/ZeoujFq.png" style="width:1000px;height:600px;display: block;margin:auto;"> 步進馬達簡介 ~ 步進馬達有低轉速高扭矩,高轉速低扭矩的特性。所以將脈波週期調低後,也就是轉速昇高,所以扭矩也會跟著下降。當扭矩操作超過pull-out torque時,就會造成失步的現象,而使馬達無法轉動。 所以若要讓步進馬達能高速運轉的話,就必需配合適當的加減速操作方式。馬達必需先由低速啟動,慢慢增加速度,最後才到預計的速度;若要從低速停止下來時,也必需慢慢減速,最典型的就是梯形加減速。(此稱為加減速profile) **4. A4988馬達驅動晶片** * [A4988驅動晶片規格](http://goods.ruten.com.tw/item/show?21534459180136) <img src="https://i.imgur.com/03ilRdK.png" style="width:480px;height:300px;display: block;margin:auto;"> <img src="https://i.imgur.com/MIS86n1.png" style="width:480px;height:200px;display: block;margin:auto;"> **5. Rotary Encoder** * [Rotary Encoder](https://trade.taobao.com/trade/detail/trade_snap.htm?spm=a1z09.6.0.0.iFyMXz&trade_id=2599012491952484&item_num_id=16341987368) :::info 規格 * 性能:400脈衝 / 轉 * 工作電壓:DC5 - 24V * 最大機械轉速:5000rpm * 綜合轉速:2000 rpm * 接線方式: * 綠色 = A相,白色 = B相,紅色 = Vcc,黑= gnd ::: > 由於A、B兩相輸出為正交脈衝,電路輸出為 NPN 集電極開路輸出,此種輸出類型必須使用上拉電阻來讀取訊號。 > * 集電極開路(Open Collector, OC門) > ![](https://i.imgur.com/FqtBNCc.png) > > * 上拉式電阻 > <img src="https://i.imgur.com/7wMK7U9.png" style="width:200px; height:250px"> > > [name=阿Hua][color=blue] --- ## GPIO Control Method ### Sysfs 利用 Linux sysfs interface 來調用 GPIO pin ```clike= FILE *init_gpio(int gpioport) { // 取得 "開啟gpio" 所對應的檔案描述符(file descriptor) FILE *fp = fopen("/sys/class/gpio/export","w"); // 寫入要開啟的腳位 fprintf(fp,"%d",gpioport); fclose(fp); char filename[256]; // 取得 "設定gpio狀態" 所對應的檔案描述符 sprintf(filename,"/sys/class/gpio/gpio%d/direction",gpioport); fp = fopen(filename,"w"); if(!fp){ panic("Could not open gpio file"); } // 將 gpio 設為輸出(out) fprintf(fp,"out"); fclose(fp); // 取得 "設定gpio值" 所對應的檔案描述符 sprintf(filename,"/sys/class/gpio/gpio%d/value",gpioport); fp = fopen(filename,"w"); if(!fp){ panic("Could not open gpio file"); } return fp; // 回傳檔案描述符以供設定使用 } ``` * 設定 GPIO 振盪 <img src="https://i.imgur.com/0ZACBxc.jpg " style="width:420px;height:250px;display: block;margin:auto;"> --- ### C direct register access [BCM2835 Data sheet](https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf) (P89.**BCM2835 ARM Peripherals GPIO**) 使用方法就如標題一樣,直接透過 C 語言去存取記憶體來使用 gpio 在 datasheet 中有說到 bcm2835 的實體週邊( Peripheral )的暫存器放在 0x20000000 到 0x20FFFFFF 的記憶體區段,而我們要使用的 GPIO 的記憶體在 0x20200000 之中。 因此我們只要使用對應的暫存器就可以操作 gpio > 注意!rpi3 的實體週邊記憶體位址從 0x3F000000 開始[name=阿Hua][color=blue] <img src="https://i.imgur.com/rVZMWUl.png " style="width:500px;height:150px;display: block;margin:auto;"> > 以 GPIO 的 set 跟 clear 為例子,這邊必須去存取的記憶體位址便是從 0x3F000000 ( 圖中 0x7E000000 是其對應的 virtual memory ) 位移 28 及 40 因此在 code 中以 macro 來定義 GPIO_SET、GPIO_CLR 的函式,如下:[name=阿Hua][color=blue] > 注意記憶體的基本單位,存取錯誤會覆寫記憶體。 > 28 = 7 * 4 (bytes) > 40 = 10 * 4 (bytes) > [name=阿Hua][color=blue] ```clike= #define BCM2708_PERI_BASE 0x3F000000 #define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */ #define PAGE_SIZE (4*1024) #define BLOCK_SIZE (4*1024) // I/O access volatile unsigned *gpio; // Use volatile to avoid comilper optimize #define GPIO_SET *(gpio+7) // sets bits #define GPIO_CLR *(gpio+10) // clears bits ``` ## Reference video {%youtube uIXkvz1-weQ %} 在影片中,作者 Andreas 使用步進馬達( stepper motor )來呈現非即時作業系統下,系統所可能導致的延遲( latency )。作者在此是透過執行副程式來大量使用記憶體(malloc),使得步進馬達旋轉產生延遲,而作者解決方法是透過 clock_nanosleep() 的系統呼叫,並搭配 TIMER_ABSTIME 的 flag 來確保 real-time task 的執行時間是使用者需要的控制範圍內。 **clock_nanosleep() 補充** ```clike= int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *request, struct timespec *remain); ``` :::info If flags is **TIMER_ABSTIME**, then request is interpreted as an absolute time as measured by the clock, clock_id. If request is less than or equal to the current value of the clock, then clock_nanosleep() returns immediately without suspending the calling thread. ::: 因此我們將使用即時作業系統來重現上述實驗,並紀錄其改善程度。 ## 參考文獻 * [Rotary Encoder](https://en.wikipedia.org/wiki/Rotary_encoder) * [RPi GPIO Code Samples](http://elinux.org/RPi_GPIO_Code_Samples#Direct_register_access)