|
1 | 1 | #include "demod/rds.h" |
2 | 2 |
|
| 3 | +#include "hw/rtc.h" |
3 | 4 | #include "utils/dsp.h" |
4 | 5 |
|
5 | 6 | #include <array> |
@@ -44,6 +45,14 @@ constexpr size_t kMaxBasebandSamplesPerSymbol = |
44 | 45 | (kSymbolPhaseModulus + kSymbolPhaseIncrement - 1U) / kSymbolPhaseIncrement; |
45 | 46 | constexpr size_t kGroupQueueCapacity = 16U; |
46 | 47 | constexpr size_t kGroupQueueMask = kGroupQueueCapacity - 1U; |
| 48 | +constexpr uint32_t kUnixEpochMjd = 40587U; |
| 49 | +constexpr uint32_t kMaximumMjd = 99999U; |
| 50 | +constexpr uint32_t kSecondsPerMinute = 60U; |
| 51 | +constexpr uint32_t kMinutesPerHour = 60U; |
| 52 | +constexpr uint32_t kHoursPerDay = 24U; |
| 53 | +constexpr uint32_t kSecondsPerHour = kMinutesPerHour * kSecondsPerMinute; |
| 54 | +constexpr uint32_t kSecondsPerDay = kHoursPerDay * kSecondsPerHour; |
| 55 | +constexpr uint8_t kMaximumLocalOffsetHalfHours = 24U; |
47 | 56 | constexpr size_t kCorrectableBurstCount = |
48 | 57 | 26U + 25U + (24U * 2U) + (23U * 4U) + (22U * 8U); |
49 | 58 | constexpr int64_t kMixerMagnitudeBound = |
@@ -449,21 +458,178 @@ class AcquisitionChannel |
449 | 458 | struct Group |
450 | 459 | { |
451 | 460 | std::array<uint16_t, 4U> words{}; |
452 | | - uint8_t corrected_mask = 0U; |
453 | 461 | }; |
454 | 462 |
|
| 463 | +struct ClockTime |
| 464 | +{ |
| 465 | + uint32_t mjd = 0U; |
| 466 | + uint32_t utc_seconds = 0U; |
| 467 | + int16_t local_offset_minutes = 0; |
| 468 | + uint8_t hour = 0U; |
| 469 | + uint8_t minute = 0U; |
| 470 | + bool valid = false; |
| 471 | +}; |
| 472 | + |
| 473 | +constexpr ClockTime decode_clock_time(uint16_t block_b, |
| 474 | + uint16_t block_c, |
| 475 | + uint16_t block_d) |
| 476 | +{ |
| 477 | + if((block_b & 0xF800U) != 0x4000U) |
| 478 | + { |
| 479 | + return {}; |
| 480 | + } |
| 481 | + |
| 482 | + uint32_t const mjd = |
| 483 | + (static_cast<uint32_t>(block_b & 0x0003U) << 15U) | |
| 484 | + (static_cast<uint32_t>(block_c) >> 1U); |
| 485 | + uint8_t const hour = static_cast<uint8_t>( |
| 486 | + ((block_c & 0x0001U) << 4U) | ((block_d >> 12U) & 0x000FU)); |
| 487 | + uint8_t const minute = static_cast<uint8_t>((block_d >> 6U) & 0x003FU); |
| 488 | + uint8_t const offset_half_hours = static_cast<uint8_t>(block_d & 0x001FU); |
| 489 | + |
| 490 | + if((mjd < kUnixEpochMjd) || (mjd > kMaximumMjd) || |
| 491 | + (hour >= kHoursPerDay) || (minute >= kMinutesPerHour) || |
| 492 | + (offset_half_hours > kMaximumLocalOffsetHalfHours)) |
| 493 | + { |
| 494 | + return {}; |
| 495 | + } |
| 496 | + |
| 497 | + uint64_t const utc_seconds = |
| 498 | + static_cast<uint64_t>(mjd - kUnixEpochMjd) * kSecondsPerDay + |
| 499 | + static_cast<uint64_t>(hour) * kSecondsPerHour + |
| 500 | + static_cast<uint64_t>(minute) * kSecondsPerMinute; |
| 501 | + if(utc_seconds > std::numeric_limits<uint32_t>::max()) |
| 502 | + { |
| 503 | + return {}; |
| 504 | + } |
| 505 | + |
| 506 | + int16_t local_offset_minutes = |
| 507 | + static_cast<int16_t>(offset_half_hours * 30U); |
| 508 | + if((block_d & 0x0020U) != 0U) |
| 509 | + { |
| 510 | + local_offset_minutes = static_cast<int16_t>(-local_offset_minutes); |
| 511 | + } |
| 512 | + |
| 513 | + return { |
| 514 | + mjd, |
| 515 | + static_cast<uint32_t>(utc_seconds), |
| 516 | + local_offset_minutes, |
| 517 | + hour, |
| 518 | + minute, |
| 519 | + true, |
| 520 | + }; |
| 521 | +} |
| 522 | + |
| 523 | +constexpr uint16_t make_clock_time_block_b(uint32_t mjd) |
| 524 | +{ |
| 525 | + return static_cast<uint16_t>(0x4000U | ((mjd >> 15U) & 0x0003U)); |
| 526 | +} |
| 527 | + |
| 528 | +constexpr uint16_t make_clock_time_block_c(uint32_t mjd, uint8_t hour) |
| 529 | +{ |
| 530 | + return static_cast<uint16_t>(((mjd & 0x7FFFU) << 1U) | |
| 531 | + ((hour >> 4U) & 0x0001U)); |
| 532 | +} |
| 533 | + |
| 534 | +constexpr uint16_t make_clock_time_block_d(uint8_t hour, |
| 535 | + uint8_t minute, |
| 536 | + bool negative_offset, |
| 537 | + uint8_t offset_half_hours) |
| 538 | +{ |
| 539 | + return static_cast<uint16_t>( |
| 540 | + ((hour & 0x0FU) << 12U) | |
| 541 | + ((minute & 0x3FU) << 6U) | |
| 542 | + (negative_offset ? 0x0020U : 0U) | |
| 543 | + (offset_half_hours & 0x1FU)); |
| 544 | +} |
| 545 | + |
| 546 | +constexpr ClockTime kUnixEpochClockTime = decode_clock_time( |
| 547 | + make_clock_time_block_b(kUnixEpochMjd), |
| 548 | + make_clock_time_block_c(kUnixEpochMjd, 0U), |
| 549 | + make_clock_time_block_d(0U, 0U, false, 0U)); |
| 550 | +static_assert(kUnixEpochClockTime.valid); |
| 551 | +static_assert(kUnixEpochClockTime.utc_seconds == 0U); |
| 552 | + |
| 553 | +constexpr ClockTime kBoundaryClockTime = decode_clock_time( |
| 554 | + make_clock_time_block_b(65535U), |
| 555 | + make_clock_time_block_c(65535U, 23U), |
| 556 | + make_clock_time_block_d(23U, 59U, true, 1U)); |
| 557 | +static_assert(kBoundaryClockTime.valid); |
| 558 | +static_assert(kBoundaryClockTime.mjd == 65535U); |
| 559 | +static_assert(kBoundaryClockTime.hour == 23U); |
| 560 | +static_assert(kBoundaryClockTime.minute == 59U); |
| 561 | +static_assert(kBoundaryClockTime.local_offset_minutes == -30); |
| 562 | + |
| 563 | +static_assert(!decode_clock_time( |
| 564 | + make_clock_time_block_b(0U), |
| 565 | + make_clock_time_block_c(0U, 0U), |
| 566 | + make_clock_time_block_d(0U, 0U, false, 0U)).valid); |
| 567 | +static_assert(!decode_clock_time( |
| 568 | + make_clock_time_block_b(kUnixEpochMjd), |
| 569 | + make_clock_time_block_c(kUnixEpochMjd, 24U), |
| 570 | + make_clock_time_block_d(24U, 0U, false, 0U)).valid); |
| 571 | +static_assert(!decode_clock_time( |
| 572 | + make_clock_time_block_b(kUnixEpochMjd), |
| 573 | + make_clock_time_block_c(kUnixEpochMjd, 0U), |
| 574 | + make_clock_time_block_d(0U, 60U, false, 0U)).valid); |
| 575 | +static_assert(!decode_clock_time( |
| 576 | + make_clock_time_block_b(kUnixEpochMjd), |
| 577 | + make_clock_time_block_c(kUnixEpochMjd, 0U), |
| 578 | + make_clock_time_block_d(0U, 0U, false, 25U)).valid); |
| 579 | +static_assert(!decode_clock_time( |
| 580 | + make_clock_time_block_b(kMaximumMjd), |
| 581 | + make_clock_time_block_c(kMaximumMjd, 0U), |
| 582 | + make_clock_time_block_d(0U, 0U, false, 0U)).valid); |
| 583 | +static_assert(!decode_clock_time( |
| 584 | + static_cast<uint16_t>( |
| 585 | + make_clock_time_block_b(kUnixEpochMjd) | 0x0800U), |
| 586 | + make_clock_time_block_c(kUnixEpochMjd, 0U), |
| 587 | + make_clock_time_block_d(0U, 0U, false, 0U)).valid); |
| 588 | + |
455 | 589 | static std::array<Group, kGroupQueueCapacity> s_group_queue{}; |
456 | 590 | static std::atomic<uint32_t> s_group_head{0U}; |
457 | 591 | static std::atomic<uint32_t> s_group_tail{0U}; |
458 | | -static std::atomic<uint32_t> s_group_dropped{0U}; |
| 592 | + |
| 593 | +static void process_clock_time(const Group &group) |
| 594 | +{ |
| 595 | + uint16_t const block_b = group.words[1U]; |
| 596 | + if((block_b & 0xF800U) != 0x4000U) |
| 597 | + { |
| 598 | + return; |
| 599 | + } |
| 600 | + |
| 601 | + ClockTime const clock_time = |
| 602 | + decode_clock_time(block_b, group.words[2U], group.words[3U]); |
| 603 | + if(!clock_time.valid) |
| 604 | + { |
| 605 | + std::printf("RDS: CT invalid B=%04X C=%04X D=%04X\r\n", |
| 606 | + static_cast<unsigned int>(block_b), |
| 607 | + static_cast<unsigned int>(group.words[2U]), |
| 608 | + static_cast<unsigned int>(group.words[3U])); |
| 609 | + return; |
| 610 | + } |
| 611 | + |
| 612 | + rtc_set_utc(clock_time.utc_seconds, clock_time.local_offset_minutes); |
| 613 | + |
| 614 | + int const offset = static_cast<int>(clock_time.local_offset_minutes); |
| 615 | + unsigned int const offset_magnitude = |
| 616 | + static_cast<unsigned int>((offset < 0) ? -offset : offset); |
| 617 | + std::printf("RDS: CT MJD=%lu UTC=%02u:%02u LTO=%c%02u:%02u RTC=%lu\r\n", |
| 618 | + static_cast<unsigned long>(clock_time.mjd), |
| 619 | + static_cast<unsigned int>(clock_time.hour), |
| 620 | + static_cast<unsigned int>(clock_time.minute), |
| 621 | + (offset < 0) ? '-' : '+', |
| 622 | + offset_magnitude / 60U, |
| 623 | + offset_magnitude % 60U, |
| 624 | + static_cast<unsigned long>(clock_time.utc_seconds)); |
| 625 | +} |
459 | 626 |
|
460 | 627 | static void enqueue_group(const Group &group) |
461 | 628 | { |
462 | 629 | uint32_t const head = s_group_head.load(std::memory_order_relaxed); |
463 | 630 | uint32_t const tail = s_group_tail.load(std::memory_order_acquire); |
464 | 631 | if((head - tail) >= kGroupQueueCapacity) |
465 | 632 | { |
466 | | - s_group_dropped.fetch_add(1U, std::memory_order_relaxed); |
467 | 633 | return; |
468 | 634 | } |
469 | 635 |
|
@@ -639,11 +805,6 @@ class LinkDecoder |
639 | 805 | if(result.valid) |
640 | 806 | { |
641 | 807 | group.words[position] = result.information; |
642 | | - if(result.corrected) |
643 | | - { |
644 | | - group.corrected_mask = |
645 | | - static_cast<uint8_t>(group.corrected_mask | (1U << position)); |
646 | | - } |
647 | 808 | } |
648 | 809 | } |
649 | 810 |
|
@@ -796,36 +957,16 @@ void rds_reset() |
796 | 957 |
|
797 | 958 | s_group_head.store(0U, std::memory_order_relaxed); |
798 | 959 | s_group_tail.store(0U, std::memory_order_relaxed); |
799 | | - s_group_dropped.store(0U, std::memory_order_relaxed); |
800 | 960 | } |
801 | 961 |
|
802 | 962 | void rds_poll() |
803 | 963 | { |
804 | | - uint32_t const dropped = s_group_dropped.exchange(0U, std::memory_order_relaxed); |
805 | | - if(dropped != 0U) |
806 | | - { |
807 | | - std::printf("RDS: dropped %lu group(s)\r\n", |
808 | | - static_cast<unsigned long>(dropped)); |
809 | | - } |
810 | | - |
811 | 964 | uint32_t tail = s_group_tail.load(std::memory_order_relaxed); |
812 | 965 | uint32_t const head = s_group_head.load(std::memory_order_acquire); |
813 | 966 | while(tail != head) |
814 | 967 | { |
815 | 968 | Group const group = s_group_queue[tail & kGroupQueueMask]; |
816 | | - uint16_t const block_b = group.words[1U]; |
817 | | - unsigned int const type = static_cast<unsigned int>((block_b >> 12U) & 0x0FU); |
818 | | - char const version = ((block_b & 0x0800U) != 0U) ? 'B' : 'A'; |
819 | | - |
820 | | - std::printf("RDS: PI=%04X TYPE=%u%c A=%04X B=%04X C=%04X D=%04X CORR=%X\r\n", |
821 | | - static_cast<unsigned int>(group.words[0U]), |
822 | | - type, |
823 | | - version, |
824 | | - static_cast<unsigned int>(group.words[0U]), |
825 | | - static_cast<unsigned int>(group.words[1U]), |
826 | | - static_cast<unsigned int>(group.words[2U]), |
827 | | - static_cast<unsigned int>(group.words[3U]), |
828 | | - static_cast<unsigned int>(group.corrected_mask)); |
| 969 | + process_clock_time(group); |
829 | 970 | ++tail; |
830 | 971 | s_group_tail.store(tail, std::memory_order_release); |
831 | 972 | } |
|
0 commit comments