Skip to content

Instantly share code, notes, and snippets.

@walkure
Last active June 24, 2026 15:37
Show Gist options
  • Select an option

  • Save walkure/0228b23c67425118b757410d8eabbd33 to your computer and use it in GitHub Desktop.

Select an option

Save walkure/0228b23c67425118b757410d8eabbd33 to your computer and use it in GitHub Desktop.
/*
* jjy_sender.c
* $ gcc -O2 -DIS_GPIOD_V2=0 -DGPIO_LINE_NUMBER=18 -DCHIP_PATH='"/dev/gpiochip1"' -DCONSUMER='"my_custom_sender"' jjy_sender.c -o jjy_sender -lgpiod
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <gpiod.h>
/* =========================================================================
* [libgpiod Version Configuration]
* v2.x mode is enabled by default (1).
* To build for v1.x, pass -DIS_GPIOD_V2=0 to your compiler.
* ========================================================================= */
#ifndef IS_GPIOD_V2
#define IS_GPIOD_V2 1 // 1: v2.x mode (default) / 0: v1.x mode
#endif
/* =========================================================================
* [GPIO Default Configurations]
* These can be overridden at compile time using -DOPTION=value.
* Example: gcc -DGPIO_LINE_NUMBER=14 jjy_sender.c -o jjy_sender -lgpiod
* ========================================================================= */
#ifndef CONSUMER
#define CONSUMER "jjy_sender"
#endif
#ifndef CHIP_PATH
#define CHIP_PATH "/dev/gpiochip0"
#endif
#ifndef GPIO_LINE_NUMBER
#define GPIO_LINE_NUMBER 4 // BCM 4
#endif
#define PULSE_0_NS 800000000L
#define PULSE_1_NS 500000000L
#define PULSE_2_NS 200000000L
static volatile sig_atomic_t keep_running = 1;
void signal_handler(int signum) {
keep_running = 0;
}
int calculate_jjy_bit(struct tm *tm_info) {
int second = tm_info->tm_sec;
int minute = tm_info->tm_min;
int hour = tm_info->tm_hour;
int days = tm_info->tm_yday + 1;
int year = (tm_info->tm_year + 1900) % 100;
int weekday = tm_info->tm_wday;
int bcdMinute = (minute % 10) + ((minute / 10) << 5);
int bcdHour = (hour % 10) + ((hour / 10) << 5);
int bcdDays = (days % 10) + (((days / 10) % 10) << 5) + ((days / 100) << 10);
int bcdYear = (year % 10) + ((year / 10) << 4);
if (second == 0 || second % 10 == 9) {
return 2;
} else if (second >= 1 && second <= 8) {
return (bcdMinute >> (8 - second)) & 1;
} else if (second >= 12 && second <= 18) {
return (bcdHour >> (18 - second)) & 1;
} else if (second >= 22 && second <= 33) {
return (bcdDays >> (33 - second)) & 1;
} else if (second == 36) {
return __builtin_popcount(bcdHour) & 1;
} else if (second == 37) {
return __builtin_popcount(bcdMinute) & 1;
} else if (second >= 41 && second <= 48) {
return (bcdYear >> (48 - second)) & 1;
} else if (second >= 50 && second <= 52) {
return (weekday >> (52 - second)) & 1;
}
return 0;
}
int main(int argc, char *argv[]) {
bool verbose = false;
int opt;
// Parse command line options
while ((opt = getopt(argc, argv, "v")) != -1) {
switch (opt) {
case 'v':
verbose = true;
break;
default:
fprintf(stderr, "Usage: %s [-v]\n", argv[0]);
return EXIT_FAILURE;
}
}
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// --- GPIO Initialization Block ---
#if IS_GPIOD_V2
// [v2.x Implementation]
struct gpiod_chip *chip = gpiod_chip_open(CHIP_PATH);
if (!chip) {
perror("Failed to open GPIO chip (v2)");
return EXIT_FAILURE;
}
struct gpiod_line_settings *settings = gpiod_line_settings_new();
gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_OUTPUT);
gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_INACTIVE);
struct gpiod_line_config *line_cfg = gpiod_line_config_new();
unsigned int line_offset = GPIO_LINE_NUMBER;
gpiod_line_config_add_line_settings(line_cfg, &line_offset, 1, settings);
struct gpiod_request_config *req_cfg = gpiod_request_config_new();
gpiod_request_config_set_consumer(req_cfg, CONSUMER);
struct gpiod_line_request *request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
gpiod_request_config_free(req_cfg);
gpiod_line_config_free(line_cfg);
gpiod_line_settings_free(settings);
if (!request) {
if (errno == EBUSY) {
fprintf(stderr, "Error: GPIO line %d is already in use by another process (Busy).\n", GPIO_LINE_NUMBER);
} else {
perror("Failed to request GPIO lines");
}
gpiod_chip_close(chip);
return EXIT_FAILURE;
}
printf("JJY sender started (libgpiod v2.x mode) [Chip: %s, Line: %d, Consumer: %s].\n",
CHIP_PATH, GPIO_LINE_NUMBER, CONSUMER);
#else
// [v1.x Implementation]
const char *chip_name = CHIP_PATH;
if (strncmp(chip_name, "/dev/", 5) == 0) {
chip_name += 5;
}
struct gpiod_chip *chip = gpiod_chip_open_by_name(chip_name);
if (!chip) {
perror("Failed to open GPIO chip (v1)");
return EXIT_FAILURE;
}
struct gpiod_line *line = gpiod_chip_get_line(chip, GPIO_LINE_NUMBER);
if (!line) {
perror("Failed to get GPIO line");
gpiod_chip_close(chip);
return EXIT_FAILURE;
}
if (gpiod_line_request_output(line, CONSUMER, 0) < 0) {
if (errno == EBUSY) {
fprintf(stderr, "Error: GPIO line %d is already in use by another process (Busy).\n", GPIO_LINE_NUMBER);
} else {
perror("Failed to set GPIO line as output");
}
gpiod_chip_close(chip);
return EXIT_FAILURE;
}
printf("JJY sender started (libgpiod v1.x mode) [Chip: %s, Line: %d, Consumer: %s].\n",
CHIP_PATH, GPIO_LINE_NUMBER, CONSUMER);
#endif
// --- Main Loop ---
while (keep_running) {
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
struct timespec target_time;
target_time.tv_sec = now.tv_sec + 1;
target_time.tv_nsec = 0;
time_t t = target_time.tv_sec;
struct tm *tm_info = localtime(&t);
int bit = calculate_jjy_bit(tm_info);
char target_str[32];
strftime(target_str, sizeof(target_str), "%Y/%m/%d %H:%M:%S", tm_info);
struct timespec pulse_sleep = { .tv_sec = 0 };
if (bit == 0) pulse_sleep.tv_nsec = PULSE_0_NS;
else if (bit == 1) pulse_sleep.tv_nsec = PULSE_1_NS;
else if (bit == 2) pulse_sleep.tv_nsec = PULSE_2_NS;
// Two-stage wait: Stage 1 (Coarse Sleep)
struct timespec before_target = target_time;
before_target.tv_nsec -= 5000000; // -5ms
if (before_target.tv_nsec < 0) {
before_target.tv_sec -= 1;
before_target.tv_nsec += 1000000000;
}
if (now.tv_sec < before_target.tv_sec ||
(now.tv_sec == before_target.tv_sec && now.tv_nsec < before_target.tv_nsec)) {
struct timespec sleep_time;
sleep_time.tv_sec = before_target.tv_sec - now.tv_sec;
sleep_time.tv_nsec = before_target.tv_nsec - now.tv_nsec;
if (sleep_time.tv_nsec < 0) {
sleep_time.tv_sec -= 1;
sleep_time.tv_nsec += 1000000000;
}
nanosleep(&sleep_time, NULL);
}
// Two-stage wait: Stage 2 (Busy Wait)
do {
clock_gettime(CLOCK_REALTIME, &now);
} while (now.tv_sec < target_time.tv_sec && keep_running);
if (!keep_running) break;
// --- Assert High at the exact second mark ---
#if IS_GPIOD_V2
gpiod_line_request_set_value(request, GPIO_LINE_NUMBER, GPIOD_LINE_VALUE_ACTIVE);
#else
gpiod_line_set_value(line, 1);
#endif
struct timespec log_time;
if (verbose) {
clock_gettime(CLOCK_REALTIME, &log_time);
}
nanosleep(&pulse_sleep, NULL);
// --- Deassert Low after the pulse duration ---
#if IS_GPIOD_V2
gpiod_line_request_set_value(request, GPIO_LINE_NUMBER, GPIOD_LINE_VALUE_INACTIVE);
#else
gpiod_line_set_value(line, 0);
#endif
if(verbose){
struct tm *log_tm = localtime(&log_time.tv_sec);
char actual_str[32];
strftime(actual_str, sizeof(actual_str), "%Y/%m/%d %H:%M:%S", log_tm);
printf("Data Time: %s | Actual Time: %s.%03ld | Bit: %02d\n",
target_str, actual_str, log_time.tv_nsec / 1000000, bit);
}
}
// --- Cleanup Block ---
#if IS_GPIOD_V2
gpiod_line_request_set_value(request, GPIO_LINE_NUMBER, GPIOD_LINE_VALUE_INACTIVE);
gpiod_line_request_release(request);
#else
gpiod_line_set_value(line, 0);
gpiod_line_release(line);
#endif
gpiod_chip_close(chip);
printf("\nTerminated safely.\n");
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment