#define _POSIX_C_SOURCE 200809L #include "h618_hub75.h" #include #include #include #include #include #include enum { PI_R1 = 1u << 0, PI_G1 = 1u << 1, PI_B1 = 1u << 2, PI_R2 = 1u << 3, PI_G2 = 1u << 4, PI_B2 = 1u << 5, }; static size_t word_index(unsigned row, unsigned plane, unsigned column) { return ((size_t)row * HUB75_PWM_BITS + plane) * HUB75_WIDTH + column; } int main(void) { assert(strcmp(hub75_hardware_mapping(), HUB75_HARDWARE_MAPPING) == 0); assert(HUB75_INTERNAL_PWM_BITS == 11); assert(HUB75_PWM_PLANE_OFFSET == 4); assert(HUB75_SCANS_PER_FRAME == 1); assert(hub75_scans_per_frame_for_brightness(1) == 1); assert(hub75_scans_per_frame_for_brightness(20) == 1); assert(hub75_scans_per_frame_for_brightness(100) == 1); assert(hub75_scans_per_frame_for_brightness(0) == -1); assert(hub75_scans_per_frame_for_brightness(101) == -1); uint16_t previous_mapped = 0; for (int brightness = 1; brightness <= 100; ++brightness) { uint16_t mapped = hub75_map_channel_for_brightness(255, brightness); assert(mapped >= previous_mapped); previous_mapped = mapped; for (unsigned plane = 0; plane < HUB75_PWM_BITS; ++plane) { uint64_t remainder = 123u; assert(hub75_oe_ticks_for_plane( 10250000, brightness, plane, &remainder ) == (1u << (plane + HUB75_PWM_PLANE_OFFSET))); assert(remainder == 0); } } assert(previous_mapped == 2047u); assert(hub75_map_channel_for_brightness(255, 0) == UINT16_MAX); uint64_t invalid_remainder = 0; assert(hub75_oe_ticks_for_plane(5000000, 0, 0, &invalid_remainder) == UINT32_MAX); assert(hub75_oe_ticks_for_plane(5000000, 1, HUB75_PWM_BITS, &invalid_remainder) == UINT32_MAX); uint64_t full_scale_remainder = 0; assert(hub75_oe_ticks_for_plane( 10250000, 100, HUB75_PWM_BITS - 1, &full_scale_remainder ) == 1024u); uint8_t *frame = calloc(1, HUB75_FRAME_BYTES); uint16_t *planes = calloc(HUB75_BITPLANE_WORDS, sizeof(*planes)); assert(frame != NULL); assert(planes != NULL); /* Compare extracted planes with the Raspberry-compatible 11-bit map. */ frame[0] = 0xff; frame[1] = 0x80; frame[2] = 0x40; size_t bottom = (size_t)HUB75_SCAN_ROWS * HUB75_WIDTH * 3u; frame[bottom] = 0x20; frame[bottom + 1] = 0x10; frame[bottom + 2] = 0x08; assert(hub75_build_bitplanes( frame, HUB75_FRAME_BYTES, planes, HUB75_BITPLANE_WORDS ) == 0); const uint8_t values[] = {0xff, 0x80, 0x40, 0x20, 0x10, 0x08}; const uint16_t bits[] = {PI_R1, PI_G1, PI_B1, PI_R2, PI_G2, PI_B2}; for (unsigned plane = 0; plane < HUB75_PWM_BITS; ++plane) { uint16_t expected = 0; for (unsigned component = 0; component < 6; ++component) { uint16_t mapped = hub75_map_channel_for_brightness(values[component], 100); if ((mapped >> (plane + HUB75_PWM_PLANE_OFFSET)) & 1u) { expected |= bits[component]; } } assert(planes[word_index(0, plane, 0)] == expected); } memset(frame, 0xff, HUB75_FRAME_BYTES); assert(hub75_build_bitplanes( frame, HUB75_FRAME_BYTES, planes, HUB75_BITPLANE_WORDS ) == 0); for (size_t index = 0; index < HUB75_BITPLANE_WORDS; ++index) { assert(planes[index] == (PI_R1 | PI_G1 | PI_B1 | PI_R2 | PI_G2 | PI_B2)); } /* Brightness changes bitplane data rather than OE pulse duration. */ memset(frame, 0, HUB75_FRAME_BYTES); frame[0] = 0xff; frame[1] = 0x4f; frame[2] = 0xa3; uint16_t low_planes[HUB75_BITPLANE_WORDS]; assert(hub75_build_bitplanes_for_brightness( frame, HUB75_FRAME_BYTES, 100, planes, HUB75_BITPLANE_WORDS ) == 0); assert(hub75_build_bitplanes_for_brightness( frame, HUB75_FRAME_BYTES, 20, low_planes, HUB75_BITPLANE_WORDS ) == 0); assert(memcmp(low_planes, planes, sizeof(low_planes)) != 0); assert(hub75_build_bitplanes(frame, 1, planes, HUB75_BITPLANE_WORDS) == -1); assert(hub75_build_bitplanes(frame, HUB75_FRAME_BYTES, planes, 1) == -1); hub75_config config = { .brightness = 40, .refresh_rate_hz = 100, .cpu_affinity = 0, .realtime_priority = 1, .use_dev_mem = 0, }; char error[512] = {0}; hub75_context *context = hub75_create(&config, error, sizeof(error)); assert(context != NULL); hub75_fake_registers registers; assert(hub75_get_fake_registers(context, ®isters) == 0); assert(((registers.mux1 >> ((14u - 8u) * 4u)) & 0xfu) == 0x5u); assert(((registers.mux1 >> ((15u - 8u) * 4u)) & 0xfu) == 0x1u); assert((registers.pwm_pccr45 & (1u << 4)) != 0); assert((registers.pwm_pccr45 & ((0xfu) | (1u << 5) | (0x3u << 7))) == 0); assert((registers.pwm_per & (1u << 4)) != 0); assert((registers.pwm_pcr4 & 0x1ffu) == 2u); assert((registers.pwm_pcr4 & (1u << 9)) != 0); assert(registers.pwm_ppr4 == (65535u << 16)); hub75_stats stats; assert(hub75_get_stats(context, &stats) == 0); assert(strcmp(stats.oe_timing_backend, HUB75_OE_TIMING_BACKEND) == 0); assert(stats.oe_pulse_faults == 0); memset(frame, 0xff, HUB75_FRAME_BYTES); assert(hub75_submit_rgb(context, frame, HUB75_FRAME_BYTES) == 0); assert(hub75_set_brightness(context, 100) == 0); assert(hub75_start(context) == 0); struct timespec pulse_test_delay = {.tv_sec = 0, .tv_nsec = 100000000}; nanosleep(&pulse_test_delay, NULL); assert(hub75_get_stats(context, &stats) == 0); assert(stats.max_programmed_oe_ns == 128000u); assert(stats.oe_pulse_faults == 0); assert(hub75_get_fake_registers(context, ®isters) == 0); assert((registers.pwm_pcr4 & (1u << 9)) != 0); assert((registers.pwm_pcr4 & (1u << 10)) == 0); assert((registers.pwm_per & (1u << 4)) != 0); assert((registers.pwm_ppr4 & 0xffffu) != 0); assert((registers.pwm_ppr4 >> 16) + 1u == (registers.pwm_ppr4 & 0xffffu)); hub75_destroy(context); config.fake_pwm4_stuck = 1; context = hub75_create(&config, error, sizeof(error)); assert(context != NULL); assert(hub75_start(context) == 0); struct timespec stuck_test_delay = {.tv_sec = 0, .tv_nsec = 100000000}; nanosleep(&stuck_test_delay, NULL); assert(hub75_get_stats(context, &stats) == 0); assert(stats.oe_pulse_faults == 1); assert(stats.oe_forced_blanks == 1); assert(strstr(stats.oe_timing_error, "start bit did not clear") != NULL); assert(hub75_get_fake_registers(context, ®isters) == 0); assert((registers.pwm_per & (1u << 4)) == 0); assert(((registers.mux1 >> ((14u - 8u) * 4u)) & 0xfu) == 0x1u); hub75_destroy(context); config.fake_pwm4_stuck = 0; config.fake_pwm5_mode = 1; context = hub75_create(&config, error, sizeof(error)); assert(context != NULL); assert(hub75_get_fake_registers(context, ®isters) == 0); assert((registers.pwm_per & (1u << 5)) != 0); assert((registers.pwm_pccr45 & (1u << 6)) != 0); assert(registers.pwm_pcr5 == 0x100u); assert(registers.pwm_ppr5 == 0x000b0006u); hub75_destroy(context); config.fake_pwm5_mode = 2; context = hub75_create(&config, error, sizeof(error)); assert(context == NULL); assert(strstr(error, "incompatible shared PWM4/5 clock") != NULL); assert(hub75_force_safeoff(0, error, sizeof(error)) == 0); free(planes); free(frame); puts("native_bitplane_tests=PASS"); return 0; }