LinuxのHRTを使ってみる

マイコンで制御を行う際に気になるのが、精度のよい周期処理です。ワンチップマイコンであれば直接タイマーを制御して、割り込み処理をかけてやれば精度のよい処理が簡単にできます。しかしながら、LinuxではOSの御作法に従わなければなりません。
Linuxでどうすればよいのか調べてみたところ、HRT(High Resolution Timer)というのを使えば良さそうなことがわかりましたので、試してみました。

まずは、とにかく使ってみます。

1//
2// コンパイル方法: $ gcc hrt_test.c -o hrt_test -lrt
3//
4 
5#include <stdio.h>
6#include <stdlib.h>
7#include <unistd.h>
8#include <time.h>
9#include <inttypes.h>
10 
11static struct timespec hrt_now;
12static struct timespec hrt_next;
13 
14void test(long tick)
15{
16    clock_gettime(CLOCK_MONOTONIC, &hrt_now);
17 
18    while(1){
19        hrt_next.tv_nsec = hrt_now.tv_nsec + tick;
20        if(hrt_next.tv_nsec >= 1000000000){
21            hrt_next.tv_nsec -= 1000000000;
22            hrt_next.tv_sec = hrt_now.tv_sec + 1;
23        } else {
24            hrt_next.tv_sec = hrt_now.tv_sec;
25        }
26        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &hrt_next, NULL);
27        hrt_now = hrt_next;
28        printf("*\n");
29    }
30}
31 
32int main(int argc, char *argv[]) {
33    test(500000000);
34    return EXIT_SUCCESS;
35}

基本的な内容は、clock_gettime(CLOCK_MONOTONIC, …)で現在の精度の高い時刻を取得し、取得した時刻に制御周期を足して、clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ….)でその時刻まで待つ、というものです。
一番参考になったのは、ここここだったりします。
このサンプルプログラムはLinuxMint11(x64)でもRaspbianでも動作し、0.5秒ごとにアスタリスクが表示されます。

次に、 これをRaspberry PiのGPIOの点滅動作に変更します。

1//
2// コンパイル方法: $ gcc hrt_test.c -o hrt_test -lrt
3//
4 
5////////////// GPIOアクセス
6//
7//  How to access GPIO registers from C-code on the Raspberry-Pi
8//  Example program
9//  15-January-2012
10//  Dom and Gert
11//
12 
13// Access from ARM Running Linux
14 
15#define BCM2708_PERI_BASE        0x20000000
16#define GPIO_BASE                (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */
17 
18#include <stdio.h>
19#include <string.h>
20#include <stdlib.h>
21#include <dirent.h>
22#include <fcntl.h>
23#include <assert.h>
24#include <sys/mman.h>
25#include <sys/types.h>
26#include <sys/stat.h>
27 
28#include <unistd.h>
29#include <time.h>
30#include <inttypes.h>
31 
32#define PAGE_SIZE (4*1024)
33#define BLOCK_SIZE (4*1024)
34 
35int  mem_fd;
36char *gpio_mem, *gpio_map;
37char *spi0_mem, *spi0_map;
38 
39// I/O access
40volatile unsigned *gpio;
41 
42// GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y)
43#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
44#define OUT_GPIO(g) *(gpio+((g)/10)) |=  (1<<(((g)%10)*3))
45#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))
46 
47#define GPIO_SET *(gpio+7)  // sets   bits which are 1 ignores bits which are 0
48#define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0
49#define GPIO_GET *(gpio+13) // read   bits
50 
51#define MAX_PORTNUM 25
52 
53char valid_port[] = {
54 1,1,0,0,
55 0,0,0,1,
56 1,1,1,1,
57 0,0,1,1,
58 0,1,1,0,
59 0,1,1,1,
60 1,1
61};
62 
63void setup_io();
64 
65int port_avail(int port)
66{
67    if ((port < 0) || (port > MAX_PORTNUM))
68        return (0);
69    return ((int)valid_port[port]);
70}
71 
72int gpio_read(int port)
73{
74    if (!port_avail(port))
75        return(0);
76    return( (GPIO_GET & (1<<port)) ? 1 : 0);
77}
78 
79void gpio_write(int port, int data)
80{
81    if (!port_avail(port))
82        return;
83    if (data == 0)
84        GPIO_CLR = 1<<port;
85    else    GPIO_SET = 1<<port;
86}
87 
88//
89// Set up a memory regions to access GPIO
90//
91int initcount= 0;
92void setup_io()
93{
94    initcount++;
95   /* open /dev/mem */
96   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
97      printf("can't open /dev/mem \n");
98      exit (-1);
99   }
100 
101   /* mmap GPIO */
102 
103   // Allocate MAP block
104   if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
105      printf("allocation error \n");
106      exit (-1);
107   }
108 
109   // Make sure pointer is on 4K boundary
110   if ((unsigned long)gpio_mem % PAGE_SIZE)
111     gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);
112 
113   // Now map it
114   gpio_map = (char *)mmap(
115      (caddr_t)gpio_mem,
116      BLOCK_SIZE,
117      PROT_READ|PROT_WRITE,
118      MAP_SHARED|MAP_FIXED,
119      mem_fd,
120      GPIO_BASE
121   );
122 
123   if ((long)gpio_map < 0) {
124      printf("mmap error %d\n", (int)gpio_map);
125      exit (-1);
126   }
127 
128   // Always use volatile pointer!
129   gpio = (volatile unsigned *)gpio_map;
130 
131} // setup_io
132 
133///////////////////////////
134// HRTを使ったLED点滅
135//
136 
137static struct timespec hrt_now;
138static struct timespec hrt_next;
139 
140void test(long tick)
141{
142    int v;
143 
144    setup_io();
145    INP_GPIO(7); // must use INP_GPIO before we can use OUT_GPIO
146    OUT_GPIO(7);
147    v = 1;
148 
149    clock_gettime(CLOCK_MONOTONIC, &hrt_now);
150 
151    while(1){
152        hrt_next.tv_nsec = hrt_now.tv_nsec + tick;
153        if(hrt_next.tv_nsec >= 1000000000){
154            hrt_next.tv_nsec -= 1000000000;
155            hrt_next.tv_sec = hrt_now.tv_sec + 1;
156        } else {
157            hrt_next.tv_sec = hrt_now.tv_sec;
158        }
159        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &hrt_next, NULL);
160        hrt_now = hrt_next;
161        gpio_write(7,v);
162        v = 1 - v;
163    }
164}
165 
166int main(int argc, char *argv[]) {
167    test(500000000);
168    return EXIT_SUCCESS;
169}

前半はGPIOを制御するためのコードで、#include の部分に追加はしていますが、Interface誌の記事のものです。
後半の部分は先ほどのアスタリスクを出力するものと一緒ですが、GPIO7を制御するように変更しています。
これで1秒周期で点滅するようになりました。

さらに、周期を1msに変更して、オシロスコープで観測してみました。

時々少し暴れてしまうようですが、この通り1ms周期でポートがH/Lを繰り返しているのが確認できました。

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)