🛠 Simple UART Automation with Sapphire RV64
Objectives
This tutorial aims to understand the interaction between embedded software and hardware on the Efinix Sapphire RV64 SoC. We will configure a UART peripheral to transmit textual messages. Although the topic mentions automation, for a beginner level, we focus on serialized communication, the essential foundation of any automation or supervision system. You will learn to initialize device registers and send character strings.
Required Equipment
- Compatible Efinix FPGA board (Titanium, Topaz, or Trion T20 or higher).
- USB cable for programming and serial communication.
- Efinity 2026.1 or newer software.
- Serial terminal (such as PuTTY, Minicom, or the integrated Efinity tool).
- Sapphire RV64 design generated with at least 1 UART master enabled.
Steps
- Open your Efinity project containing the Sapphire RV64 configuration. Ensure the UART IP block is included in the design and connected to the AXI4/APB3 bus.
- Generate the bitstream and program the FPGA. Verify that the VexiiRiscv core is active.
- Open your RISC-V development environment (IDE). Create a new C/C++ project.
- Define the memory addresses of the UART registers. These addresses are provided by the hardware description file generated by Efinity. For example:
#define UART_BASE 0x40000000
#define UART_DATA (UART_BASE + 0x00)
#define UART_STATUS (UART_BASE + 0x08)
#define UART_CTRL (UART_BASE + 0x10)
- Write a wait function to check that the transmit buffer is empty before sending a byte:
void uart_wait_tx_ready() {
while ((*(volatile unsigned int*)UART_STATUS & 0x1) == 0) {
// Busy wait
}
}
- Implement the character send function:
void uart_putc(char c) {
uart_wait_tx_ready();
*(volatile unsigned int*)UART_DATA = (unsigned int)c;
}
- Create a function to send a complete string:
void uart_puts(const char *str) {
while (*str) {
uart_putc(*str++);
}
}
- In
main(), initialize the UART controller (set the baud rate divider according to your clock frequency, e.g., 115200 bauds) and send a message:
int main() {
// Simplified initialization (adapt according to your registers)
*(volatile unsigned int*)UART_CTRL = 0x1;
uart_puts("Sapphire RV64: System Ready\n");
while(1);
}
- Compile the code for the RV64IMACFD architecture. Use compilation flags adapted to your core configuration (with or without FPU).
- Load the binary onto the FPGA via the JTAG debugger or bootloader.
Verification
Open a serial terminal connected to the board's UART. Configure the speed to 115200 bauds (8N1). If the configuration is correct, you should see the message "Sapphire RV64: System Ready" displayed. The absence of a message indicates a memory address error, baud rate configuration error, or UART pin mapping issue.
Going Further
Explore the use of interrupts to improve CPU efficiency. Next, integrate a sensor via I2C (available on the board) to read data and transmit it via UART, thus creating a first automated data acquisition system.
Other tutorials for this board
Code examples for this board
gpio_blink_riscv.inoC (RISC-V)uart_echo_riscv.inoC (RISC-V)spi_sensor_read.inoC (RISC-V)i2c_master_write.inoC (RISC-V)timer_interrupt_handler.inoC (RISC-V)i2c-bmp280-temperature-6.inoC