マイコンで制御を行う際に気になるのが、精度のよい周期処理です。ワンチップマイコンであれば直接タイマーを制御して、割り込み処理をかけてやれば精度のよい処理が簡単にできます。しかしながら、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 |
11 | static struct timespec hrt_now; |
12 | static struct timespec hrt_next; |
13 |
14 | void 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 |
32 | int 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 |
35 | int mem_fd; |
36 | char *gpio_mem, *gpio_map; |
37 | char *spi0_mem, *spi0_map; |
38 |
39 | // I/O access |
40 | volatile 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 |
53 | char 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 |
63 | void setup_io(); |
64 |
65 | int port_avail( int port) |
66 | { |
67 | if ((port < 0) || (port > MAX_PORTNUM)) |
68 | return (0); |
69 | return (( int )valid_port[port]); |
70 | } |
71 |
72 | int gpio_read( int port) |
73 | { |
74 | if (!port_avail(port)) |
75 | return (0); |
76 | return ( (GPIO_GET & (1<<port)) ? 1 : 0); |
77 | } |
78 |
79 | void 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 | // |
91 | int initcount= 0; |
92 | void 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 |
137 | static struct timespec hrt_now; |
138 | static struct timespec hrt_next; |
139 |
140 | void 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 |
166 | int main( int argc, char *argv[]) { |
167 | test(500000000); |
168 | return EXIT_SUCCESS; |
169 | } |
前半はGPIOを制御するためのコードで、#include の部分に追加はしていますが、Interface誌の記事のものです。
後半の部分は先ほどのアスタリスクを出力するものと一緒ですが、GPIO7を制御するように変更しています。
これで1秒周期で点滅するようになりました。
さらに、周期を1msに変更して、オシロスコープで観測してみました。
時々少し暴れてしまうようですが、この通り1ms周期でポートがH/Lを繰り返しているのが確認できました。