DEV Community

Hedy
Hedy

Posted on

How to sent RTC to FPGA board using mainc automatic?

Here’s a clean, practical way to send RTC time from an MCU (e.g., STM32) to an FPGA automatically from main.c. I’ll use UART because it’s the simplest to implement on both sides; I’ll also note SPI/I²C options at the end.

The plan (TL;DR)

  1. Wire MCU UART → FPGA UART (TX→RX, GND↔GND, same voltage, e.g., 3.3 V).
  2. Frame a tiny time packet: sync bytes + YY/MM/DD/HH/MM/SS + checksum.
  3. STM32: read RTC each second (RTC wakeup or timer) and auto-send packet from main.c (UART DMA or interrupt).
  4. FPGA: simple UART RX + small state machine to parse the packet and update time registers.

1) Hardware hookup

  • MCU TX → FPGA RX
  • MCU GND ↔ FPGA GND
  • Voltage levels: keep both at 3.3 V (avoid 5 V unless level-shifted).
  • Pick a common baud (e.g., 115200 8N1).

2) Minimal packet format (easy to parse)

Use a fixed 10-byte frame:

[0] 0xA5
[1] 0x5A
[2] sec    (0–59)
[3] min    (0–59)
[4] hour   (0–23)
[5] day    (1–31)
[6] month  (1–12)
[7] year   (0–99) // e.g., 25 for 2025
[8] csum   // XOR of bytes [2..7]
[9] 0x0D   // end marker
Enter fullscreen mode Exit fullscreen mode

You can switch to BCD later if you prefer, but raw binary is fine.

3) STM32 (HAL) — “automatic” send from main.c

Below is a compact pattern that works on CubeMX-generated projects. It:

  • Inits RTC + UART,
  • Uses RTC Wakeup (1 Hz) or a hardware timer to trigger,
  • Reads time and transmits via UART (DMA or blocking).
// main.c (STM32 HAL-style)
#include "main.h"
#include "usart.h"
#include "rtc.h"

static uint8_t frame[10];

static uint8_t make_checksum(uint8_t *p) {
    uint8_t x = 0;
    for (int i = 2; i <= 7; ++i) x ^= p[i];
    return x;
}

static void build_time_frame(RTC_TimeTypeDef *t, RTC_DateTypeDef *d) {
    frame[0] = 0xA5; frame[1] = 0x5A;
    frame[2] = t->Seconds;
    frame[3] = t->Minutes;
    frame[4] = t->Hours;
    frame[5] = d->Date;
    frame[6] = d->Month;
    frame[7] = d->Year;      // 0..99 (RTC BCD config off -> values are binary)
    frame[8] = make_checksum(frame);
    frame[9] = 0x0D;
}

static void send_time_now(void) {
    RTC_TimeTypeDef t; RTC_DateTypeDef d;
    // Read TIME first, then DATE (RTC shadow regs)
    HAL_RTC_GetTime(&hrtc, &t, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &d, RTC_FORMAT_BIN);
    build_time_frame(&t, &d);
    // non-blocking is nicer; blocking works too for 10 bytes
    HAL_UART_Transmit(&huart1, frame, sizeof(frame), 10);
    // or: HAL_UART_Transmit_DMA(&huart1, frame, sizeof(frame));
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_RTC_Init();
    MX_USART1_UART_Init();

    // Option A) RTC Wakeup @1 Hz
    HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0x0FFF, RTC_WAKEUPCLOCK_RTCCLK_DIV16); // tune for ~1 s on your part

    while (1) {
        // Option B) If not using Wakeup interrupt, poll every second:
        // static uint32_t ms0; if (HAL_GetTick() - ms0 >= 1000) { ms0 += 1000; send_time_now(); }

        // low-power friendly: __WFI();
    }
}

// Called each second if using RTC wakeup IT
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc) {
    send_time_now();
}
Enter fullscreen mode Exit fullscreen mode

Notes

  • In CubeMX, enable RTC (LSE/LSEBYP, etc.) and USART.
  • If you prefer not to use the RTC wakeup interrupt, use a TIMx interrupt (1 Hz) to call send_time_now().
  • For robust delivery, switch HAL_UART_Transmit to DMA and check HAL_UART_TxCpltCallback.

4) FPGA side — simple UART RX + parser (Verilog sketch)

UART RX (parameterize by FPGA clock and baud):

  • If your FPGA clock is 50 MHz and baud is 115200, set CLKS_PER_BIT = 50_000_000 / 115200 ≈ 434.
  • Most open-source “uart_rx.v” modules work fine; or use the following outline.
// uart_rx.sv (very compact outline)
module uart_rx #(parameter CLK_HZ=50_000_000, BAUD=115200) (
    input  logic clk, rstn,
    input  logic rx,
    output logic [7:0] data,
    output logic       data_valid
);
    localparam int CLKS_PER_BIT = CLK_HZ / BAUD; // 434 @50MHz
    // ... implement standard 8N1 oversampling or mid-bit sampling FSM ...
    // On each received byte, assert data_valid for one clk and present 'data'.
endmodule
Enter fullscreen mode Exit fullscreen mode

Packet parser: wait for 0xA5, 0x5A, collect 8 payload bytes, verify checksum, then update registers.

module time_parser (
    input  logic        clk, rstn,
    input  logic [7:0]  rx_data,
    input  logic        rx_strobe,
    output logic [6:0]  year, month, day, hour, minute, second,
    output logic        time_valid_pulse
);
    typedef enum logic [2:0] {IDLE, S1, PAYLOAD, CSUM, END} state_t;
    state_t st; logic [3:0] idx; logic [7:0] buf[0:7]; logic [7:0] xsum;

    always_ff @(posedge clk or negedge rstn) begin
        if (!rstn) begin st<=IDLE; idx<=0; xsum<=0; time_valid_pulse<=0; end
        else begin
            time_valid_pulse <= 1'b0;
            if (rx_strobe) begin
                unique case (st)
                    IDLE:   st <= (rx_data==8'hA5) ? S1 : IDLE;
                    S1:     st <= (rx_data==8'h5A) ? PAYLOAD : IDLE;
                    PAYLOAD: begin
                        buf[idx] <= rx_data; // [0]=sec, [1]=min, ... [5]=month, [6]=year
                        xsum <= (idx==0) ? rx_data : (xsum ^ rx_data);
                        if (idx==6) begin idx<=0; st<=CSUM; end
                        else idx <= idx + 1;
                    end
                    CSUM:   st <= (rx_data==xsum) ? END : IDLE;
                    END: begin
                        if (rx_data==8'h0D) begin
                            second <= buf[0][6:0];
                            minute <= buf[1][6:0];
                            hour   <= buf[2][6:0];
                            day    <= buf[3][6:0];
                            month  <= buf[4][6:0];
                            year   <= buf[5][6:0];
                            time_valid_pulse <= 1'b1; // one-clock strobe
                        end
                        st <= IDLE;
                    end
                endcase
            end
        end
    end
endmodule
Enter fullscreen mode Exit fullscreen mode

Wire uart_rx.data_valid → rx_strobe, and uart_rx.data → rx_data. On each time_valid_pulse, your design now has fresh YY/MM/DD/HH/MM/SS registers.

5) Alternatives (if UART isn’t your thing)

  • SPI (MCU master → FPGA slave): fastest to implement in HDL. Define a fixed 8-byte register burst; use a chip-select and a “frame counter” in FPGA.
  • I²C: MCU as master; FPGA as slave (more logic to implement). Use a small set of time registers at fixed addresses.
  • Parallel GPIO: 8- or 16-bit bus + strobe if you already have spare pins.

6) Reliability tips

  • Clock accuracy: UART tolerates small error. With 50 MHz and 115200, CLKS_PER_BIT=434 is fine (~0.006% error).
  • Loss handling: Keep the sync bytes (0xA5, 0x5A) so the FPGA can re-lock if it ever desyncs.
  • Triggering: Use RTC Wakeup/Alarm on the MCU for true 1 Hz sends.
  • Validation: Blink an LED on time_valid_pulse and print the received time on an FPGA UART TX (loopback to a PC) during bring-up.

Top comments (0)