マイコンで制御を行う際に気になるのが、精度のよい周期処理です。ワンチップマイコンであれば直接タイマーを制御して、割り込み処理をかけてやれば精度のよい処理が簡単にできます。しかしながら、LinuxではOSの御作法に従わなければなりません。
Linuxでどうすればよいのか調べてみたところ、HRT(High Resolution Timer)というのを使えば良さそうなことがわかりましたので、試してみました。
まずは、とにかく使ってみます。
// // コンパイル方法: $ gcc hrt_test.c -o hrt_test -lrt // #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <time.h> #include <inttypes.h> static struct timespec hrt_now; static struct timespec hrt_next; void test(long tick) { clock_gettime(CLOCK_MONOTONIC, &hrt_now); while(1){ hrt_next.tv_nsec = hrt_now.tv_nsec + tick; if(hrt_next.tv_nsec >= 1000000000){ hrt_next.tv_nsec -= 1000000000; hrt_next.tv_sec = hrt_now.tv_sec + 1; } else { hrt_next.tv_sec = hrt_now.tv_sec; } clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &hrt_next, NULL); hrt_now = hrt_next; printf("*\n"); } } int main(int argc, char *argv[]) { test(500000000); return EXIT_SUCCESS; }
基本的な内容は、clock_gettime(CLOCK_MONOTONIC, …)で現在の精度の高い時刻を取得し、取得した時刻に制御周期を足して、clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ….)でその時刻まで待つ、というものです。
一番参考になったのは、こことここだったりします。
このサンプルプログラムはLinuxMint11(x64)でもRaspbianでも動作し、0.5秒ごとにアスタリスクが表示されます。
次に、 これをRaspberry PiのGPIOの点滅動作に変更します。
// // コンパイル方法: $ gcc hrt_test.c -o hrt_test -lrt // ////////////// GPIOアクセス // // How to access GPIO registers from C-code on the Raspberry-Pi // Example program // 15-January-2012 // Dom and Gert // // Access from ARM Running Linux #define BCM2708_PERI_BASE 0x20000000 #define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <dirent.h> #include <fcntl.h> #include <assert.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <time.h> #include <inttypes.h> #define PAGE_SIZE (4*1024) #define BLOCK_SIZE (4*1024) int mem_fd; char *gpio_mem, *gpio_map; char *spi0_mem, *spi0_map; // I/O access volatile unsigned *gpio; // GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y) #define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3)) #define OUT_GPIO(g) *(gpio+((g)/10)) |= (1<<(((g)%10)*3)) #define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3)) #define GPIO_SET *(gpio+7) // sets bits which are 1 ignores bits which are 0 #define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0 #define GPIO_GET *(gpio+13) // read bits #define MAX_PORTNUM 25 char valid_port[] = { 1,1,0,0, 0,0,0,1, 1,1,1,1, 0,0,1,1, 0,1,1,0, 0,1,1,1, 1,1 }; void setup_io(); int port_avail(int port) { if ((port < 0) || (port > MAX_PORTNUM)) return (0); return ((int)valid_port[port]); } int gpio_read(int port) { if (!port_avail(port)) return(0); return( (GPIO_GET & (1<<port)) ? 1 : 0); } void gpio_write(int port, int data) { if (!port_avail(port)) return; if (data == 0) GPIO_CLR = 1<<port; else GPIO_SET = 1<<port; } // // Set up a memory regions to access GPIO // int initcount= 0; void setup_io() { initcount++; /* open /dev/mem */ if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) { printf("can't open /dev/mem \n"); exit (-1); } /* mmap GPIO */ // Allocate MAP block if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) { printf("allocation error \n"); exit (-1); } // Make sure pointer is on 4K boundary if ((unsigned long)gpio_mem % PAGE_SIZE) gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE); // Now map it gpio_map = (char *)mmap( (caddr_t)gpio_mem, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, mem_fd, GPIO_BASE ); if ((long)gpio_map < 0) { printf("mmap error %d\n", (int)gpio_map); exit (-1); } // Always use volatile pointer! gpio = (volatile unsigned *)gpio_map; } // setup_io /////////////////////////// // HRTを使ったLED点滅 // static struct timespec hrt_now; static struct timespec hrt_next; void test(long tick) { int v; setup_io(); INP_GPIO(7); // must use INP_GPIO before we can use OUT_GPIO OUT_GPIO(7); v = 1; clock_gettime(CLOCK_MONOTONIC, &hrt_now); while(1){ hrt_next.tv_nsec = hrt_now.tv_nsec + tick; if(hrt_next.tv_nsec >= 1000000000){ hrt_next.tv_nsec -= 1000000000; hrt_next.tv_sec = hrt_now.tv_sec + 1; } else { hrt_next.tv_sec = hrt_now.tv_sec; } clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &hrt_next, NULL); hrt_now = hrt_next; gpio_write(7,v); v = 1 - v; } } int main(int argc, char *argv[]) { test(500000000); return EXIT_SUCCESS; }
前半はGPIOを制御するためのコードで、#include の部分に追加はしていますが、Interface誌の記事のものです。
後半の部分は先ほどのアスタリスクを出力するものと一緒ですが、GPIO7を制御するように変更しています。
これで1秒周期で点滅するようになりました。
さらに、周期を1msに変更して、オシロスコープで観測してみました。
時々少し暴れてしまうようですが、この通り1ms周期でポートがH/Lを繰り返しているのが確認できました。