Hello World Link to heading
So far we calculated the IBRD
and FBRD
now we just have to inform the CPU about those new values.
We can find that the UART Address Map is located at 0x7E20100
which is actually located at 0x3F20100
in our case, because the 0x7E..
addresses are for peripherals like the GPU.
The IBRD
and FBRD
are located at the memory offset of 0x24
and 0x28
.
Awesome can we just set it now? No! In the BCM2835 Peripherals
we can find under the LCRH Register
(p. 184) a note:
NOTE: The UART_LCRH
, UART_IBRD
, and UART_FBRD
registers must not
be changed:
-
when the UART is enabled
-
when completing a transmission or a reception when it has been programmed to become disabled.
Configuration Overview Link to heading
There are a few things that have to be configured and all the information is located in different locations of the documentation so I’ll cut it short and give you a list of the order we have to do things:
- Disable UART
- Clear UART FIFO by disabling it
- Write
IBRD
andFBRD
- Set word length to 8 bits and enable FIFO again
- Enable UART and enable transmission
1. Disable UART Link to heading
Okay, let’s start by disabling UART so we can write IBRD
, and FBRD
.
1const UART0_CR: u32 = 0x3F20_1030;
2const UART0_CR_UARTEN: u32 = 1 << 0;
3
4//...
5
6fn uart_enable(enable: bool) {
7 unsafe {
8 let mut cr = core::ptr::read_volatile(UART0_CR as *mut u32);
9
10 if enable {
11 cr |= UART0_CR_UARTEN;
12 } else {
13 cr &= !UART0_CR_UARTEN;
14 }
15
16 core::ptr::write_volatile(UART0_CR as *mut u32, cr);
17 }
18}
Here, we read the current Control Register value, flip the first bit depending on wether we want to enable or disable UART, and write it back.
1pub fn configure_uart() {
2 let baud_div_times_64 = (UART_CLK * 4) / BAUD;
3
4 let ibrd = baud_div_times_64 / 64;
5 let fbrd = baud_div_times_64 % 64;
6
7 unsafe {
8 uart_enable(false);
9 //...
2. Disable UART FIFO Link to heading
The FIFO is used as a temporary storage before sending data over the UART connection. It’s the same logic as the uart_enable
just with a different Register and offset.
1const UART0_LCRH: u32 = 0x3F20_102c;
2const UART0_LCRH_FEN: u32 = 1 << 4;
3
4//...
5
6fn uart_fifo_enable(enable: bool) {
7 unsafe {
8 let mut lcrh = core::ptr::read_volatile(UART0_LCRH as *mut u32);
9
10 if enable {
11 lcrh |= UART0_LCRH_FEN;
12 } else {
13 lcrh &= !UART0_LCRH_FEN;
14 }
15
16 core::ptr::write_volatile(UART0_LCRH as *mut u32, lcrh);
17 }
18}
And now we just have to call it in our configuration function:
1pub fn configure_uart() {
2 let baud_div_times_64 = (UART_CLK * 4) / BAUD;
3
4 let ibrd = baud_div_times_64 / 64;
5 let fbrd = baud_div_times_64 % 64;
6
7 unsafe {
8 uart_enable(false);
9 uart_fifo_enable(false);
10 //...
3. Updating IBRD and FBRD Link to heading
We’ll just write it inline into the corresponding registers.
1const UART0_IBRD: u32 = 0x3F20_1024;
2const UART0_FBRD: u32 = 0x3F20_1028;
3
4pub fn configure_uart() {
5 //...
6 uart_fifo_enable(false);
7
8 core::ptr::write_volatile(UART0_IBRD as *mut u32, ibrd);
9 core::ptr::write_volatile(UART0_FBRD as *mut u32, fbrd);
10 //...
4. Set word length and enable FIFO Link to heading
The word length decides on how many bits get sent in one UART frame.
Here we have multiple different configurations but we’ll use 8 bits aka. 0b11
.
register value | Word bit count |
---|---|
0b11 |
8 bits |
0b10 |
7 bits |
0b01 |
6 bits |
0b00 |
5 bits |
In this case we will overwrite the entire LCRH register.
1fn uart_set_lcrh(wlen: u32, enable_fifo: bool) {
2 unsafe {
3 let mut value = (wlen & 0b11) << 5;
4 if enable_fifo {
5 value |= UART0_LCRH_FEN;
6 }
7 core::ptr::write_volatile(UART0_LCRH as *mut u32, value);
8 }
9}
And again add it to our configuration function:
1pub fn configure_uart() {
2 //...
3 core::ptr::write_volatile(UART0_FBRD as *mut u32, fbrd);
4
5 uart_set_lcrh(0b11, true);
6 //...
5. Enable transmission and UART Link to heading
1pub fn configure_uart() {
2 let baud_div_times_64 = (UART_CLK * 4) / BAUD;
3
4 let ibrd = baud_div_times_64 / 64;
5 let fbrd = baud_div_times_64 % 64;
6
7 unsafe {
8 uart_enable(false);
9 uart_fifo_enable(false);
10
11 core::ptr::write_volatile(UART0_IBRD as *mut u32, ibrd);
12 core::ptr::write_volatile(UART0_FBRD as *mut u32, fbrd);
13
14 uart_set_lcrh(0b11, true);
15
16 let mut cr = core::ptr::read_volatile(UART0_CR as *mut u32);
17 cr |= UART0_CR_UARTEN | UART0_CR_TXE;
18
19 core::ptr::write_volatile(UART0_CR as *mut u32, cr);
20 }
21}
Let’s print Link to heading
Now at last we just need one more function to write to UART
1const UART0_DR: u32 = 0x3F20_1000;
2
3const UART0_FR: u32 = 0x3F20_1018;
4const UART0_FR_TXFF: u32 = 1 << 5;
5
6//...
7
8pub fn print(s: &str) {
9 for byte in s.bytes() {
10 unsafe {
11 // Wait until FIFU is not full anymore
12 while core::ptr::read_volatile(UART0_FR as *const u32) & UART0_FR_TXFF != 0 {}
13
14 // Write our byte to the FIFO
15 core::ptr::write_volatile(UART0_DR as *mut u32, byte as u32);
16 }
17 }
18 // Wait till uart is not busy anymore
19 unsafe { while (core::ptr::read_volatile(UART0_FR as *const u32) >> 3) & 0b1 != 0 {} }
20}
Now in our main we can call uart_configure
and print
.
pub unsafe extern "C" fn _start() {
//...
main();
}
fn main() {
uart::configure_uart();
loop{
print("Hello World!\r\n");
}
}
Okay lets start the simulator and see what happens:
> qemu-system-aarch64 \
-M raspi3b \
-cpu cortex-a53 \
-serial stdio \
-sd sd.img \
-display none \
-kernel ../target/aarch64-unknown-none/release/kernel8.img
Hello World!
Hello World!
Hello World!
Hello World!
...
via GIPHY
BUT WAIT
it still doesn’t work on the real Raspberry PI???
Yeah, we have to configure the GPIO pins first before we can use UART on real hardware.