Raspberry PiでGPIOを制御する複数のライブラリが公開されているが好みのシンプルで使いやすそうなライブラリがないので作ってみた。
数年前にsysfsを使ったものなら作ったことがあるがsysfsは非推奨となっていたのでその後継となるGPIO Character Deviceを使ってみた。
汎用的に使えるはずなのでRaspberry Piはもちろんのこと全てのLinuxで使えるはず。試してないけど。
いつものようにArduino風に作ってあるが仕様拡張しているため完全互換ではない。
なお、アプリやライブラリ終了時にdigitalWrite()で設定したピンの状態が変わることがあるらしいがGPIO Character Deviceの仕様らしいのであきらめること。(-_-;)
参考までにdigitalWrite()の速度を試してみたら約120KHz弱しか出なかった。ioctl()しか呼び出してないんだけどなぁ...HWダイレクトアクセスなら12MHzぐらい出ましたけど。って、早けりゃいいってもんでもないか。
【Lチカのサンプル】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include "pinctrl.h" #define PIN_LED 18 #define PIN_BTN 19 int main(int argc, char **argv) { GPIO.begin(); GPIO.pinMode(PIN_LED, OUTPUT); GPIO.pinMode(PIN_BTN, INPUT, PULL_UP); while (GPIO.digitalRead(PIN_BTN)) { GPIO.digitalWrite(PIN_LED, GPIO.digitalRead(PIN_LED) ? LOW : HIGH); GPIO.sleep(1000); } GPIO.end(); return 0; } |
【ピン変化割り込みのサンプル】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include "pinctrl.h" #define PIN_INT 18 #define PIN_BTN 19 void callback(void *arg, const struct gpio_v2_line_event& event) { const char *msg = "?"; switch (event.id) { case GPIO_V2_LINE_EVENT_RISING_EDGE: msg = "RISING_EDGE"; break; case GPIO_V2_LINE_EVENT_FALLING_EDGE: msg = "FALLING_EDGE"; break; } printf("line %u: %s at %llu\n", event.offset, msg, event.timestamp_ns); } int main(int argc, char **argv) { GPIO.begin(); GPIO.attachInterruptParam(PIN_INT, callback, NULL, CHANGE, PULL_UP, 10000); GPIO.pinMode(PIN_BTN, INPUT, PULL_UP); while (GPIO.digitalRead(PIN_BTN)) GPIO.sleep(1000); GPIO.end(); return 0; } |
【概要】
1 |
PinCtrl GPIO; |
このライブラリにアクセスするためのグローバル変数。
1 |
size_t begin(void); |
ライブラリを初期化。
1 |
void end(void); |
ライブラリを終了し使用中の全てのピンを解放する。
1 |
void pinMode(pin_size_t pin, PinMode mode, PinPull pull = PULL_DISABLE, uint32_t debounce_period_us = 0); |
ピンの入出力モードやプルアップ・モード、デバウンス時間を設定する。
1 |
void digitalWrite(pin_size_t pin, PinStatus value); |
ピン状態を設定する。
1 |
PinStatus digitalRead(pin_size_t pin); |
ピン状態を取得する。
1 |
void attachInterruptParam(pin_size_t pin, event_callback_t func, void *arg, PinEdge edge, PinPull pull = PULL_DISABLE, uint32_t debounce_period_us = 0); |
ピン変化割り込み、エッジタイプ、デバウンス時間を設定する。割り込み対応はポーリングスレッドにて行っているのでリアルではないことに注意。
1 |
void detachInterrupt(pin_size_t pin); |
release()と同じ。
1 |
void release(pin_size_t pin); |
ピンを解放し他のアプリで使えるようにする。
1 |
const struct gpiochip_info *chip_info(size_t num); |
GPIOチップ情報を取得する。numが取得したチップ数を超えるとNULLを返す。
1 |
void sleepMicroseconds(size_t usec); |
指定マイクロ秒の間スリープする。(おまけ)
1 |
void sleep(size_t msec); |
指定ミリ秒の間スリープする。(おまけ)
1 |
void delayMicroseconds(size_t usec); |
指定マイクロ秒の間ビジーループする。(おまけ)
1 |
void delay(size_t msec); |
指定ミリ秒の間ビジーループする。(おまけ)
1 |
size_t micros(void); |
システムタイマーをマイクロ秒で返す。(おまけ)
1 |
size_t millis(void); |
システムタイマーをミリ秒で返す。(おまけ)
【ピン割り当て】
gpiochip0 … 0 – 63
gpiochip1 … 64 – 127
gpiochip2 … 128 – 191
gpiochip3 … 192 – 256
【修正】
2022-07-31
OPEN_DRAIN/OPEN_SOURCEの処理が間違っていたので修正。
2022-07-28
pinmode.hを追加。
2022-07-26
detachInterrupt()の仕様変更。
2022-07-24
デバウンスの設定にミスがあったので修正し動作も確認できた。スイッチ入力等を想定し1秒毎に10ms幅のパルスを入力した場合、デバウンスに10ms以上を指定したときは無反応となり、10ms以下を指定すると反応するようになる。ただ、デバウンス時間に5ms未満を指定したときは問題ないのだが、5-10msを指定すると2回に1回取りこぼしが発生してしまう。つまり、デバウンス時間とチャタリング時間との兼ね合いによりとりこぼしが発生するのが避けられないということになるので現状では使うのは避けた方が良さそうだ。GPIO Character Deviceのバグだと思うが、デバウンス機能はスイッチ入力のチャタリング処理をお任せすることができて凄く便利なのでぜひ改善をお願いしたい。
【ダウンロード】
pinctrl.zip
【ライブラリ】
pthreadを使っているのでリンカーオプション-lpthreadの指定を忘れずに。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
/* pinmode.h - GPIO Pin Mode Copyright (c) 2022 Sasapea's Lab. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include <stdint.h> #include <stdbool.h> typedef enum { INPUT = 0, OUTPUT = 1, OPEN_DRAIN = 2, OPEN_SOURCE = 3, } PinMode; typedef enum { PULL_DISABLE = 0, PULL_UP = 1, PULL_DOWN = 2, } PinPull; typedef enum { CHANGE = 0, RISING = 1, FALLING = 2, } PinEdge; typedef enum { LOW = 0, HIGH = 1, } PinStatus; typedef uint32_t pin_size_t; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
/* pinctrl.h - GPIO Control Library for Linux GPIO Character Device Copyright (c) 2022 Sasapea's Lab. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #pragma once #include <stdint.h> #include <stdbool.h> #include <linux/gpio.h> #include "pinmode.h" #ifndef PINCTRL_MAX_CHIPS #define PINCTRL_MAX_CHIPS 4 #endif #ifndef PINCTRL_MAX_EVENTS #define PINCTRL_MAX_EVENTS 16 #endif #define PINCTRL_MAX_LINES (PINCTRL_MAX_CHIPS * GPIO_V2_LINES_MAX) typedef void (*pinctrl_callback_t)(void *param, const struct gpio_v2_line_event& event); class PinCtrl { public: PinCtrl(void); virtual ~PinCtrl(void); size_t begin(void); const struct gpiochip_info *chip_info(size_t num); void end(void); void release(pin_size_t pin); void stop(pin_size_t pin); void pinMode(pin_size_t pin, PinMode mode, PinPull pull = PULL_DISABLE, uint32_t debounce_period_us = 0); void digitalWrite(pin_size_t pin, PinStatus value); PinStatus digitalRead(pin_size_t pin); void attachInterruptParam(pin_size_t pin, pinctrl_callback_t func, void *arg, PinEdge edge, PinPull pull = PULL_DISABLE, uint32_t debounce_period_us = 0); void detachInterrupt(pin_size_t pin); void sleepMicroseconds(size_t usec); void sleep(size_t msec); void delayMicroseconds(size_t usec); void delay(size_t msec); size_t micros(void); size_t millis(void); protected: int config(pin_size_t pin, gpio_v2_line_config& cfg); private: size_t _chip_count; struct gpiochip_info _chip_info[PINCTRL_MAX_CHIPS]; int _chip_fd[PINCTRL_MAX_CHIPS]; int _line_fd[PINCTRL_MAX_LINES]; int _pipe_fd[PINCTRL_MAX_LINES]; }; extern PinCtrl GPIO; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
/* pinctrl.cpp - GPIO Control Library for Linux GPIO Character Device Copyright (c) 2022 Sasapea's Lab. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <stdio.h> #include <string.h> #include <malloc.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <poll.h> #include <pthread.h> #include <sys/ioctl.h> #include <sys/epoll.h> #include "pinctrl.h" #define GPIO_DEVICE "/dev/gpiochip%d" PinCtrl GPIO; PinCtrl::PinCtrl(void) : _chip_count(0) { memset(_chip_fd, -1, sizeof(_chip_fd)); memset(_line_fd, -1, sizeof(_line_fd)); memset(_pipe_fd, -1, sizeof(_pipe_fd)); } PinCtrl::~PinCtrl(void) { end(); } size_t PinCtrl::begin(void) { end(); for (_chip_count = 0; _chip_count < PINCTRL_MAX_CHIPS; ++_chip_count) { char name[64]; int& chip = _chip_fd[_chip_count]; struct gpiochip_info& info = _chip_info[_chip_count]; snprintf(name, sizeof(name), GPIO_DEVICE, _chip_count); if ((chip = open(name, O_RDWR | O_CLOEXEC)) < 0) { if (_chip_count == 0) fprintf(stderr, "[%s:%u] open(\"%s\", O_RDWR | O_CLOEXEC): %s\n", __FILE__, __LINE__, name, strerror(errno)); break; } if (ioctl(chip, GPIO_GET_CHIPINFO_IOCTL, &info) < 0) { fprintf(stderr, "[%s:%u] ioctl(%d, GPIO_GET_CHIPINFO_IOCTL, 0x%08X): %s\n", __FILE__, __LINE__, chip, &info, strerror(errno)); close(chip); chip = -1; break; } } return _chip_count; } const struct gpiochip_info *PinCtrl::chip_info(size_t num) { return num < _chip_count ? &_chip_info[num] : nullptr; } void PinCtrl::end(void) { for (pin_size_t i = 0; i < PINCTRL_MAX_LINES; ++i) release(i); for (int i = 0; i < PINCTRL_MAX_CHIPS; ++i) { int& chip = _chip_fd[i]; if (chip >= 0) { close(chip); chip = -1; } } _chip_count = 0; } void PinCtrl::release(pin_size_t pin) { stop(pin); if (pin < PINCTRL_MAX_LINES) { int& line = _line_fd[pin]; if (line >= 0) { close(line); line = -1; } } } void PinCtrl::stop(pin_size_t pin) { int& pipe = _pipe_fd[pin]; if (pipe >= 0) { write(pipe, "Q", 1); // terminate polling thread close(pipe); pipe = -1; } } int PinCtrl::config(pin_size_t pin, gpio_v2_line_config& cfg) { if (pin < PINCTRL_MAX_LINES) { int chip = _chip_fd[pin / GPIO_V2_LINES_MAX]; if (chip >= 0) { int& line = _line_fd[pin]; if (line >= 0) { stop(pin); if (ioctl(line, GPIO_V2_LINE_SET_CONFIG_IOCTL, &cfg) < 0) fprintf(stderr, "[%s:%u] ioctl(%d, GPIO_V2_LINE_SET_CONFIG_IOCTL, 0x%08X): %s\n", __FILE__, __LINE__, chip, &cfg, strerror(errno)); else return line; } else { struct gpio_v2_line_request req = {.offsets = {(uint32_t)(pin % GPIO_V2_LINES_MAX)}, .config = cfg, .num_lines = 1}; if (ioctl(chip, GPIO_V2_GET_LINE_IOCTL, &req) < 0) fprintf(stderr, "[%s:%u] ioctl(%d, GPIO_V2_GET_LINE_IOCTL, 0x%08X): %s\n", __FILE__, __LINE__, chip, &req, strerror(errno)); else return line = req.fd; } } } return -1; } void PinCtrl::pinMode(pin_size_t pin, PinMode mode, PinPull pull, uint32_t debounce_period_us) { struct gpio_v2_line_config cfg = {.num_attrs = (mode == INPUT), .attrs = {{.attr = {.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE, .debounce_period_us = debounce_period_us}, .mask = 1}}}; switch (mode) { default: case INPUT: cfg.flags = GPIO_V2_LINE_FLAG_INPUT; break; case OUTPUT: cfg.flags = GPIO_V2_LINE_FLAG_OUTPUT; break; case OPEN_DRAIN: cfg.flags = GPIO_V2_LINE_FLAG_OUTPUT | GPIO_V2_LINE_FLAG_OPEN_DRAIN; break; case OPEN_SOURCE: cfg.flags = GPIO_V2_LINE_FLAG_OUTPUT | GPIO_V2_LINE_FLAG_OPEN_SOURCE; break; } switch (pull) { default: case PULL_DISABLE: cfg.flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED; break; case PULL_UP: cfg.flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP; break; case PULL_DOWN: cfg.flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN; break; } config(pin, cfg); } void PinCtrl::digitalWrite(pin_size_t pin, PinStatus value) { if (pin < PINCTRL_MAX_LINES) { int line = _line_fd[pin]; if (line >= 0) { struct gpio_v2_line_values val = {.bits = (value != 0), .mask = 1}; if (ioctl(line, GPIO_V2_LINE_SET_VALUES_IOCTL, &val) < 0) fprintf(stderr, "[%s:%u] ioctl(%d, GPIO_V2_LINE_SET_VALUES_IOCTL, 0x%08X): %s\n", __FILE__, __LINE__, line, &val, strerror(errno)); } } } PinStatus PinCtrl::digitalRead(pin_size_t pin) { if (pin < PINCTRL_MAX_LINES) { int line = _line_fd[pin]; if (line >= 0) { struct gpio_v2_line_values val = {.bits = 0, .mask = 1}; if (ioctl(line, GPIO_V2_LINE_GET_VALUES_IOCTL, &val) < 0) fprintf(stderr, "[%s:%u] ioctl(%d, GPIO_V2_LINE_GET_VALUES_IOCTL, 0x%08X): %s\n", __FILE__, __LINE__, line, &val, strerror(errno)); else return val.bits & 1 ? HIGH : LOW; } } return LOW; } typedef struct { pinctrl_callback_t func; void *arg; int fd; int pipe; } polling_params_t; static void *polling(void *arg) { polling_params_t params = *(polling_params_t *)arg; free(arg); int epoll; if ((epoll = epoll_create1(EPOLL_CLOEXEC)) < 0) fprintf(stderr, "[%s:%u] epoll_create1(EPOLL_CLOEXEC): %s\n", __FILE__, __LINE__, strerror(errno)); else { struct epoll_event ev[2]; ev[0].events = EPOLLIN; ev[0].data.fd = params.fd; ev[1].events = EPOLLIN; ev[1].data.fd = params.pipe; if (epoll_ctl(epoll, EPOLL_CTL_ADD, ev[0].data.fd, &ev[0]) < 0) fprintf(stderr, "[%s:%u] epoll_ctl(%d, EPOLL_CTL_ADD, %d, 0x%08X): %s\n", __FILE__, __LINE__, epoll, ev[0].data.fd, &ev[0], strerror(errno)); else if (epoll_ctl(epoll, EPOLL_CTL_ADD, ev[1].data.fd, &ev[1]) < 0) fprintf(stderr, "[%s:%u] epoll_ctl(%d, EPOLL_CTL_ADD, %d, 0x%08X): %s\n", __FILE__, __LINE__, epoll, ev[1].data.fd, &ev[1], strerror(errno)); else { struct epoll_event events[PINCTRL_MAX_EVENTS]; for (int nfds = 0; nfds >= 0; ) { nfds = epoll_wait(epoll, events, PINCTRL_MAX_EVENTS, -1); if (nfds < 0) fprintf(stderr, "[%s:%u] epoll_wait(%d, 0x%08X, %d, -1): %s\n", __FILE__, __LINE__, epoll, events, PINCTRL_MAX_EVENTS, strerror(errno)); else { for (int i = 0; i < nfds; ++i) { struct gpio_v2_line_event evt; int fd = events[i].data.fd; if (fd == params.pipe) { // fprintf(stderr, "[%s:%u] terminate polling thread\n", __FILE__, __LINE__); nfds = -1; break; } ssize_t n = read(fd, &evt, sizeof(evt)); if (n != sizeof(evt)) { fprintf(stderr, "[%s:%u] read(%d, 0x%08X, %d) != %d: %s\n", __FILE__, __LINE__, fd, &evt, sizeof(evt), n, "error reading line event"); nfds = -1; break; } params.func(params.arg, evt); } } } } close(epoll); } close(params.pipe); return NULL; } #define GPIO_V2_LINE_FLAG_EDGE_BOTH (GPIO_V2_LINE_FLAG_EDGE_RISING | GPIO_V2_LINE_FLAG_EDGE_FALLING) void PinCtrl::attachInterruptParam(pin_size_t pin, pinctrl_callback_t func, void *arg, PinEdge edge, PinPull pull, uint32_t debounce_period_us) { struct gpio_v2_line_config cfg = {.num_attrs = 1, .attrs = {{.attr = {.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE, .debounce_period_us = debounce_period_us}, .mask = 1}}}; switch (edge) { default: case CHANGE: cfg.flags = GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_EDGE_BOTH; break; case RISING: cfg.flags = GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_EDGE_RISING; break; case FALLING: cfg.flags = GPIO_V2_LINE_FLAG_INPUT | GPIO_V2_LINE_FLAG_EDGE_FALLING; break; } switch (pull) { default: case PULL_DISABLE: cfg.flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED; break; case PULL_UP: cfg.flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP; break; case PULL_DOWN: cfg.flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN; break; } int fd; if ((fd = config(pin, cfg)) >= 0) { int filedes[2]; if (pipe(filedes) < 0) fprintf(stderr, "[%s:%u] pipe(0x%08X): %s\n", __FILE__, __LINE__, filedes, strerror(errno)); else { polling_params_t *params = (polling_params_t *)malloc(sizeof(*params)); if (!params) fprintf(stderr, "[%s:%u] malloc(%u): %s\n", __FILE__, __LINE__, (uint32_t)sizeof(*params), "insufficient memory"); else { pthread_t thid; params->func = func; params->arg = arg; params->fd = fd; params->pipe = filedes[0]; if (pthread_create(&thid, NULL, polling, params) < 0) fprintf(stderr, "[%s:%u] pthread_create(0x%08X, NULL, 0x%08X, 0x%08X): %s\n", __FILE__, __LINE__, &thid, polling, params, strerror(errno)); else { _pipe_fd[pin] = filedes[1]; return; // success } free(params); } close(filedes[0]); close(filedes[1]); } } } void PinCtrl::detachInterrupt(pin_size_t pin) { release(pin); } void PinCtrl::sleepMicroseconds(size_t usec) { struct timespec rem, req = { tv_sec: (time_t)(usec / 1000000), tv_nsec: (long)((usec % 1000000) * 1000) }; while (nanosleep(&req, &rem) == EINTR) req = rem; } void PinCtrl::sleep(size_t msec) { struct timespec rem, req = { tv_sec: (time_t)(msec / 1000), tv_nsec: (long)((msec % 1000) * 1000000) }; while (nanosleep(&req, &rem) == EINTR) req = rem; } void PinCtrl::delayMicroseconds(size_t usec) { size_t t = micros(); while (micros() - t < usec) continue; } void PinCtrl::delay(size_t msec) { size_t t = millis(); while (millis() - t < msec) continue; } size_t PinCtrl::micros(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC_RAW, &ts); return (size_t)ts.tv_sec * 1000000 + (ts.tv_nsec / 1000); } size_t PinCtrl::millis(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC_RAW, &ts); return (size_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); } |