# 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)