Last active
June 24, 2026 15:37
-
-
Save walkure/0228b23c67425118b757410d8eabbd33 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * 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