We use some essential cookies to make our website work.

We use optional cookies, as detailed in our cookie policy, to remember your settings and understand how you use our website.

3 posts • Page 1 of 1
rgz
Posts: 2
Joined: Sun Feb 23, 2025 12:17 am

pi 4B assembly UART

Mon Feb 24, 2025 9:01 am

Hi, I'm trying to send something over the mini UART on a raspberry pi 4B using just assembly.
I tried out doing this in C first, based on the instructions in this rpi4os blog post series, specifically the UART section and it does work just fine.

I started writing the assembly version by comparing the rpi4os C code, it's disassembly and the BCM2711 peripherals manual to see what is actually happening here.
Here is what I've ended up with

Code: Select all

// UART0 - PL011
// UART1 - mini UART
// UART1 - PL011
// UART2 - PL011
// UART3 - PL011
// UART4 - PL011
// UART5 - PL011
.equ peri_base, 0xfe000000
.equ gpio_base, 0xfe200000
.equ aux_base, 0xfe215000
_start:
	mrs x0, mpidr_el1
	and x0, x0, 3
	cbz x0, 2f
1:
	wfe
	b 1b
2:
	ldr x30, =peri_base
	ldr x29, =gpio_base
	ldr x28, =aux_base
	// set AUX_ENABLES to enable UART1
	mov w0, 1
	str w0, [x28, 0x04]
	// set AUX_MU_IER_REG to disable receive and transmit interrupt generation
	mov w0, 0
	str w0, [x28, 0x44]
	// set AUX_MU_CNTL_REG to disable the mini UART receiver and transmitter
	mov w0, 0
	str w0, [x28, 0x60]
	// set AUX_MU_LCR_REG to make UART work in 8 bit mode
	// rpi4os in io.c sets 3 here even though bits 5:1 are reserved
	mov w0, 1
	str w0, [x28, 0x4c]
	// set AUX_MU_MCR_REG to make the UART1_RTS line high
	mov w0, 0
	str w0, [x28, 0x50]
	// set AUX_MU_IER_REG to disable receive and transmit interrupt generation
	// why again?
	mov w0, 0
	str w0, [x28, 0x44]
	// set AUX_MU_IIR_REG to clear the receive and transmit FIFOs
	// rpi4os in io.c sets 0xc6 here even though bits 7:6 are RO
	mov w0, 6
	str w0, [x28, 0x48]
	// set AUX_MU_BAUD_REG to a multiplier (same as in rpi4os io.c obtained from AUX_MU_BAUD(115200))
	// which should produce a baudrate of around 115200 (115313.65313653137) with the formula in 2.2.1.
	// assuming the system_clock_freq (the GPU core frequency, settable via core_freq in config.txt)
	// is 500 MHz.
	mov w0, 541
	str w0, [x28, 0x68]
	// set GPIO_PUP_PDN_CNTRL_REG0 to no resistor for GPIO 14 and GPIO 15.
	// this is called GPPUPPDN0 in rpi4os.
	mov x1, 0
	str w0, [x29, 0xe4]
	// set GPFSEL1 to enable ALT5 (TXD1) on GPIO 14 and ALT5 (RXD1) on GPIO 15
	ldr w0, =0x12000
	str w0, [x29, 0x04]
	// set AUX_MU_CNTL_REG to enable the mini UART receiver and transmitter
	mov w0, 3
	str w0, [x29, 0x60]
	// wait for the "Transmitter empty" bit in AUX_MU_LSR_REG to be set
1:
	ldr w0, [x29, 0x54] // get AUX_MU_LSR_REG
	tbz w0, 5, 1b
	// set AUX_MU_IO_REG to 'A', i.e., send it over UART
	mov w0, 0x41
	str w0, [x29, 0x40]
1:
	ldr w0, [x29, 0x54]
	tbz w0, 5, 1b
	// send 'B'
	mov w0, 0x42
	str w0, [x29, 0x40]
1:
	ldr w0, [x29, 0x54]
	tbz w0, 5, 1b
	// send 'C'
	mov w0, 0x43
	str w0, [x29, 0x40]
1:
	ldr w0, [x29, 0x54]
	tbz w0, 5, 1b
	// send '\r'
	mov w0, 0x0d
	str w0, [x29, 0x40]
This should be pretty much identical to the rpi4os C version.
I'm not 100% sure about some things though
  • Why the AUX_MU_IER_REG set to the same value twice. You can see this is uart_init() with mmio_write(AUX_MU_IER_REG, 0).
  • Why the rpi4os code sets some bits in the BCM2711 registers that are marked as reserved or read-only. In my code I just set the bits that weren't reserved or read-only.
  • Why the rpi4os code uses GPFSEL0, which has the FSEL* bits corresponding to GPIO 0-9, instead of GPFSEL1 which has the ones corresponding to GPIO 13-19, since it's setting GPIO 14 and GPIO 15 alternative functions. I used GPFSEL1 in my code.
  • If TXD1 and RXD1 really correspond to the mini UART (UART1) transmit and recieve alternative functions of GPIO 14 and 15. This is probably true since the only other TXD* and RXD* are TXD0 and RXD0 and UART0 is the PL011 UART so it would make sense since the numbers on the end match, I just couldn't find this being said explicitly anywhere in the manual.
I've enabled all the possible boot message logging over UART to get some more info and core_freq is set to 500 since the mini UART is apparently dependent on this for the baudrate calculation.

Here is the output from the rpi4os C code

Code: Select all

RPi: BOOTLOADER release VERSION:e608a69d DATE: 2024年04月15日 TIME: 14:12:14
BOOTMODE: 0x06 partition 0 build-ts BUILD_TIMESTAMP=1713186734 serial 1dd89e9c boardrev c03112 stc 469376
PM_RSTS: 0x00001000
part 00000000 reset_info 00000000
uSD voltage 3.3V
Initialising SDRAM 'Micron' 16Gb x2 total-size: 32 Gbit 3200
DDR 3200 1 0 32 152
XHCI-STOP
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
USBSTS 11
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
xHC ports 5 slots 32 intrs 4
Reset USB port-power 1000 ms
xhci_set_port_power 1 0
xhci_set_port_power 2 0
xhci_set_port_power 3 0
xhci_set_port_power 4 0
xhci_set_port_power 5 0
xhci_set_port_power 1 1
xhci_set_port_power 2 1
xhci_set_port_power 3 1
xhci_set_port_power 4 1
xhci_set_port_power 5 1
XHCI-STOP
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
USBSTS 18
XHCI-STOP
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
USBSTS 19
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
xHC ports 5 slots 32 intrs 4
Boot mode: SD (01) order f724
USB2[1] 400202e1 connected
USB2 root HUB port 1 init
DEV [01:00] 2.16 000000:01 class 9 VID 2109 PID 3431
HUB init [01:00] 2.16 000000:01
SD HOST: 200000000 CTL0: 0x00800000 BUS: 400000 Hz actual: 390625 HZ div: 512 (256) status: 0x1fff0000 delay: 276
SD HOST: 200000000 CTL0: 0x00800f00 BUS: 400000 Hz actual: 390625 HZ div: 512 (256) status: 0x1fff0000 delay: 276
OCR c0ff8000 [2]
CID: 00ad4c535553443030103838fee10188
CSD: 400e0032db790000ec537f800a400000
SD: bus-width: 4 spec: 2 SCR: 0x02358487 0x00000000
SD HOST: 200000000 CTL0: 0x00800f04 BUS: 50000000 Hz actual: 50000000 HZ div: 4 (2) status: 0x1fff0000 delay: 2
MBR: 0x00002000, 1048576 type: 0x0c
MBR: 0x00000000, 0 type: 0x00
MBR: 0x00000000, 0 type: 0x00
MBR: 0x00000000, 0 type: 0x00
Trying partition: 0
type: 32 lba: 8192 oem: 'mkfs.fat' volume: ' NO NAME '
rsc 32 fat-sectors 1024 c-count 130812 c-size 8
root dir cluster 2 sectors 0 entries 0
FAT32 clusters 130812
Trying partition: 0
type: 32 lba: 8192 oem: 'mkfs.fat' volume: ' NO NAME '
rsc 32 fat-sectors 1024 c-count 130812 c-size 8
root dir cluster 2 sectors 0 entries 0
FAT32 clusters 130812
Read config.txt bytes 48 hnd 0x3faa
Read start4.elf bytes 2262304 hnd 0x2ec2
Read fixup4.dat bytes 5459 hnd 0x158
0x00c03112 0x00000000 0x00001fff
MEM GPU: 76 ARM: 948 TOTAL: 1024
Firmware: 03554ca336a03ace164f36755144e0d8c060062d Dec 5 2024 11:45:37
Starting start4.elf @ 0xfec00200 partition 0
XHCI-STOP
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
USBSTS 18
+
MESS:00:00:05.045322:0: arasan: arasan_emmc_open
MESS:00:00:05.046981:0: arasan: arasan_emmc_set_clock C0: 0x00800000 C1: 0x000e0047 emmc: 200000000 actual: 390625 div: 0x00000100 target: 400000 min: 400000 max: 400000 delay: 5
MESS:00:00:05.167228:0: arasan: arasan_emmc_set_clock C0: 0x00800000 C1: 0x000e0047 emmc: 200000000 actual: 390625 div: 0x00000100 target: 400000 min: 400000 max: 400000 delay: 5
MESS:00:00:05.180098:0: arasan: arasan_emmc_set_clock C0: 0x00800f00 C1: 0x000e0047 emmc: 200000000 actual: 390625 div: 0x00000100 target: 400000 min: 390000 max: 400000 delay: 5
MESS:00:00:05.213704:0: arasan: arasan_emmc_set_clock C0: 0x00800f06 C1: 0x000e0207 emmc: 200000000 actual: 50000000 div: 0x00000002 target: 50000000 min: 0 max: 50000000 delay: 1
MESS:00:00:05.299976:0: brfs: File read: /mfs/sd/config.txt
MESS:00:00:05.302667:0: brfs: File read: 48 bytes
MESS:00:00:05.329332:0: HDMI0:EDID error reading EDID block 0 attempt 0
MESS:00:00:05.333843:0: HDMI0:EDID giving up on reading EDID block 0
MESS:00:00:05.350820:0: HDMI1:EDID error reading EDID block 0 attempt 0
MESS:00:00:05.355321:0: HDMI1:EDID giving up on reading EDID block 0
MESS:00:00:05.364247:0: brfs: File read: /mfs/sd/config.txt
MESS:00:00:05.848793:0: gpioman: gpioman_get_pin_num: pin DISPLAY_DSI_PORT not defined
MESS:00:00:05.854344:0: gpioman: gpioman_get_pin_num: pin DISPLAY_DSI_PORT not defined
MESS:00:00:05.863377:0: *** Restart logging
MESS:00:00:05.865152:0: brfs: File read: 48 bytes
MESS:00:00:05.876030:0: hdmi: HDMI0:EDID error reading EDID block 0 attempt 0
MESS:00:00:05.881056:0: hdmi: HDMI0:EDID giving up on reading EDID block 0
MESS:00:00:05.891680:0: hdmi: HDMI0:EDID error reading EDID block 0 attempt 0
MESS:00:00:05.896710:0: hdmi: HDMI0:EDID giving up on reading EDID block 0
MESS:00:00:05.902308:0: hdmi: HDMI:hdmi_get_state is deprecated, use hdmi_get_display_state instead
MESS:00:00:05.916099:0: hdmi: HDMI1:EDID error reading EDID block 0 attempt 0
MESS:00:00:05.921130:0: hdmi: HDMI1:EDID giving up on reading EDID block 0
MESS:00:00:05.931751:0: hdmi: HDMI1:EDID error reading EDID block 0 attempt 0
MESS:00:00:05.936776:0: hdmi: HDMI1:EDID giving up on reading EDID block 0
MESS:00:00:05.942373:0: hdmi: HDMI:hdmi_get_state is deprecated, use hdmi_get_display_state instead
MESS:00:00:05.951138:0: HDMI0: hdmi_pixel_encoding: 300000000
MESS:00:00:05.956606:0: HDMI1: hdmi_pixel_encoding: 300000000
MESS:00:00:05.983580:0: dtb_file 'bcm2711-rpi-4-b.dtb'
MESS:00:00:05.991000:0: brfs: File read: /mfs/sd/bcm2711-rpi-4-b.dtb
MESS:00:00:05.994242:0: Loaded 'bcm2711-rpi-4-b.dtb' to 0x100 size 0xdb2c
MESS:00:00:06.014564:0: brfs: File read: 56108 bytes
MESS:00:00:06.025129:0: brfs: File read: /mfs/sd/overlays/overlay_map.dtb
MESS:00:00:06.052207:0: brfs: File read: 5451 bytes
MESS:00:00:06.056109:0: brfs: File read: /mfs/sd/config.txt
MESS:00:00:06.072684:0: brfs: File read: 48 bytes
MESS:00:00:06.075747:0: brfs: File read: /mfs/sd/cmdline.txt
MESS:00:00:06.079660:0: Read command line from file 'cmdline.txt':
MESS:00:00:06.085552:0: 'console=serial0,115200'
MESS:00:00:06.208767:0: brfs: File read: 23 bytes
MESS:00:00:06.214633:0: brfs: File read: /mfs/sd/kernel8.img
MESS:00:00:06.217177:0: Loaded 'kernel8.img' to 0x200000 size 0x3ee
MESS:00:00:06.223178:0: Kernel relocated to 0x80000
MESS:00:00:06.227764:0: Device tree loaded to 0x2eff2100 (size 0xdefc)
MESS:00:00:06.235645:0: uart: Set PL011 baud rate to 103448.300000 Hz
MESS:00:00:06.243094:0: uart: Baud rate change done...
MESS:00:00:06.245113:0:Hello world!
And here is the output from my code

Code: Select all

RPi: BOOTLOADER release VERSION:e608a69d DATE: 2024年04月15日 TIME: 14:12:14
BOOTMODE: 0x06 partition 0 build-ts BUILD_TIMESTAMP=1713186734 serial 1dd89e9c boardrev c03112 stc 470645
PM_RSTS: 0x00001000
part 00000000 reset_info 00000000
uSD voltage 3.3V
Initialising SDRAM 'Micron' 16Gb x2 total-size: 32 Gbit 3200
DDR 3200 1 0 32 152
XHCI-STOP
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
USBSTS 11
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
xHC ports 5 slots 32 intrs 4
Reset USB port-power 1000 ms
xhci_set_port_power 1 0
xhci_set_port_power 2 0
xhci_set_port_power 3 0
xhci_set_port_power 4 0
xhci_set_port_power 5 0
xhci_set_port_power 1 1
xhci_set_port_power 2 1
xhci_set_port_power 3 1
xhci_set_port_power 4 1
xhci_set_port_power 5 1
XHCI-STOP
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
USBSTS 18
XHCI-STOP
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
USBSTS 19
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
xHC ports 5 slots 32 intrs 4
Boot mode: SD (01) order f724
USB2[1] 400202e1 connected
USB2 root HUB port 1 init
DEV [01:00] 2.16 000000:01 class 9 VID 2109 PID 3431
HUB init [01:00] 2.16 000000:01
SD HOST: 200000000 CTL0: 0x00800000 BUS: 400000 Hz actual: 390625 HZ div: 512 (256) status: 0x1fff0000 delay: 276
SD HOST: 200000000 CTL0: 0x00800f00 BUS: 400000 Hz actual: 390625 HZ div: 512 (256) status: 0x1fff0000 delay: 276
OCR c0ff8000 [2]
CID: 00ad4c535553443030103838fee10188
CSD: 400e0032db790000ec537f800a400000
SD: bus-width: 4 spec: 2 SCR: 0x02358487 0x00000000
SD HOST: 200000000 CTL0: 0x00800f04 BUS: 50000000 Hz actual: 50000000 HZ div: 4 (2) status: 0x1fff0000 delay: 2
MBR: 0x00002000, 1048576 type: 0x0c
MBR: 0x00000000, 0 type: 0x00
MBR: 0x00000000, 0 type: 0x00
MBR: 0x00000000, 0 type: 0x00
Trying partition: 0
type: 32 lba: 8192 oem: 'mkfs.fat' volume: ' NO NAME '
rsc 32 fat-sectors 1024 c-count 130812 c-size 8
root dir cluster 2 sectors 0 entries 0
FAT32 clusters 130812
Trying partition: 0
type: 32 lba: 8192 oem: 'mkfs.fat' volume: ' NO NAME '
rsc 32 fat-sectors 1024 c-count 130812 c-size 8
root dir cluster 2 sectors 0 entries 0
FAT32 clusters 130812
Read config.txt bytes 48 hnd 0x3faa
Read start4.elf bytes 2262304 hnd 0x2ec2
Read fixup4.dat bytes 5459 hnd 0x158
0x00c03112 0x00000000 0x00001fff
MEM GPU: 76 ARM: 948 TOTAL: 1024
Firmware: 03554ca336a03ace164f36755144e0d8c060062d Dec 5 2024 11:45:37
Starting start4.elf @ 0xfec00200 partition 0
XHCI-STOP
xHC ver: 256 HCS: 05000420 fc000031 00e70004 HCC: 002841eb
USBSTS 18
+
MESS:00:00:05.046774:0: arasan: arasan_emmc_open
MESS:00:00:05.048433:0: arasan: arasan_emmc_set_clock C0: 0x00800000 C1: 0x000e0047 emmc: 200000000 actual: 390625 div: 0x00000100 target: 400000 min: 400000 max: 400000 delay: 5
MESS:00:00:05.168675:0: arasan: arasan_emmc_set_clock C0: 0x00800000 C1: 0x000e0047 emmc: 200000000 actual: 390625 div: 0x00000100 target: 400000 min: 400000 max: 400000 delay: 5
MESS:00:00:05.181547:0: arasan: arasan_emmc_set_clock C0: 0x00800f00 C1: 0x000e0047 emmc: 200000000 actual: 390625 div: 0x00000100 target: 400000 min: 390000 max: 400000 delay: 5
MESS:00:00:05.215151:0: arasan: arasan_emmc_set_clock C0: 0x00800f06 C1: 0x000e0207 emmc: 200000000 actual: 50000000 div: 0x00000002 target: 50000000 min: 0 max: 50000000 delay: 1
MESS:00:00:05.301472:0: brfs: File read: /mfs/sd/config.txt
MESS:00:00:05.304161:0: brfs: File read: 48 bytes
MESS:00:00:05.330826:0: HDMI0:EDID error reading EDID block 0 attempt 0
MESS:00:00:05.335337:0: HDMI0:EDID giving up on reading EDID block 0
MESS:00:00:05.352313:0: HDMI1:EDID error reading EDID block 0 attempt 0
MESS:00:00:05.356814:0: HDMI1:EDID giving up on reading EDID block 0
MESS:00:00:05.365744:0: brfs: File read: /mfs/sd/config.txt
MESS:00:00:05.817272:0: gpioman: gpioman_get_pin_num: pin DISPLAY_DSI_PORT not defined
MESS:00:00:05.822823:0: gpioman: gpioman_get_pin_num: pin DISPLAY_DSI_PORT not defined
MESS:00:00:05.831858:0: *** Restart logging
MESS:00:00:05.833631:0: brfs: File read: 48 bytes
MESS:00:00:05.844511:0: hdmi: HDMI0:EDID error reading EDID block 0 attempt 0
MESS:00:00:05.849537:0: hdmi: HDMI0:EDID giving up on reading EDID block 0
MESS:00:00:05.860161:0: hdmi: HDMI0:EDID error reading EDID block 0 attempt 0
MESS:00:00:05.865190:0: hdmi: HDMI0:EDID giving up on reading EDID block 0
MESS:00:00:05.870788:0: hdmi: HDMI:hdmi_get_state is deprecated, use hdmi_get_display_state instead
MESS:00:00:05.884579:0: hdmi: HDMI1:EDID error reading EDID block 0 attempt 0
MESS:00:00:05.889610:0: hdmi: HDMI1:EDID giving up on reading EDID block 0
MESS:00:00:05.900231:0: hdmi: HDMI1:EDID error reading EDID block 0 attempt 0
MESS:00:00:05.905255:0: hdmi: HDMI1:EDID giving up on reading EDID block 0
MESS:00:00:05.910853:0: hdmi: HDMI:hdmi_get_state is deprecated, use hdmi_get_display_state instead
MESS:00:00:05.919618:0: HDMI0: hdmi_pixel_encoding: 300000000
MESS:00:00:05.925086:0: HDMI1: hdmi_pixel_encoding: 300000000
MESS:00:00:05.952047:0: dtb_file 'bcm2711-rpi-4-b.dtb'
MESS:00:00:05.959461:0: brfs: File read: /mfs/sd/bcm2711-rpi-4-b.dtb
MESS:00:00:05.962703:0: Loaded 'bcm2711-rpi-4-b.dtb' to 0x100 size 0xdb2c
MESS:00:00:05.983064:0: brfs: File read: 56108 bytes
MESS:00:00:05.993548:0: brfs: File read: /mfs/sd/overlays/overlay_map.dtb
MESS:00:00:06.020573:0: brfs: File read: 5451 bytes
MESS:00:00:06.024475:0: brfs: File read: /mfs/sd/config.txt
MESS:00:00:06.041065:0: brfs: File read: 48 bytes
MESS:00:00:06.043781:0: brfs: File read: /mfs/sd/cmdline.txt
MESS:00:00:06.048041:0: Read command line from file 'cmdline.txt':
MESS:00:00:06.053933:0: 'console=serial0,115200'
MESS:00:00:06.177013:0: brfs: File read: 23 bytes
MESS:00:00:06.182725:0: brfs: File read: /mfs/sd/kernel8.img
MESS:00:00:06.185268:0: Loaded 'kernel8.img' to 0x200000 size 0xd8
MESS:00:00:06.191182:0: Kernel relocated to 0x80000
MESS:00:00:06.195769:0: Device tree loaded to 0x2eff2100 (size 0xdefc)
MESS:00:00:06.203651:0: uart: Set PL011 baud rate to 103448.300000 Hz
MESS:00:00:06.211092:0: uart: Baud rate change done...
MESS:00:00:06.213115:0:
As you can see mine just ends with no output while it should output "ABC" with a newline and the rpi4os one does output "Hello world!".

Any suggestions on what I might be doing wrong?

pmoore
Posts: 61
Joined: Thu Jun 28, 2018 9:53 am

Re: pi 4B assembly UART

Wed Mar 12, 2025 11:00 pm

Hi rgz,

I didn't dive too deep into your issue, but instead put something together to achieve what you were trying to do. Perhaps by comparing mine vs yours, you'll work out what was wrong.

I created the following uart.s file:

Code: Select all

.global _start
.align 2
_start:
 mrs x0, mpidr_el1 // x0 = Multiprocessor Affinity Register value.
 and x0, x0, #0x3 // x0 = core number.
 cbz x0, 2f // Put all cores except core 0 to sleep.
1:
 wfi
 b 1b
2:
# ------------------------------------------------------------------------------
# Initialise the Mini UART interface for logging over serial port.
# Note, this is Broadcomm's own UART, not the ARM licenced UART interface.
# ------------------------------------------------------------------------------
 ldr x1, =0xfe215000 // x1 = aux_base
 ldr w2, [x1, #0x4] // w2 = [AUX_ENABLES] (Auxiliary enables)
 orr w2, w2, #1
 str w2, [x1, #0x4] // [AUX_ENABLES] |= 0x00000001 => Enable Mini UART.
 str wzr, [x1, #0x44] // [AUX_MU_IER] = 0x00000000 => Disable Mini UART interrupts.
 str wzr, [x1, #0x60] // [AUX_MU_CNTL] = 0x00000000 => Disable Mini UART Tx/Rx
 mov w2, #0x6 // w2 = 6
 str w2, [x1, #0x48] // [AUX_MU_IIR] = 0x00000006 => Mini UART clear Tx, Rx FIFOs
 mov w3, #0x3 // w3 = 3
 str w3, [x1, #0x4c] // [AUX_MU_LCR] = 0x00000003 => Mini UART in 8-bit mode.
 str wzr, [x1, #0x50] // [AUX_MU_MCR] = 0x00000000 => Set UART1_RTS line high.
 mov w2, 0x0000021d
 str w2, [x1, #0x68] // [AUX_MU_BAUD] = 0x0000021d
 // => baudrate = system_clock_freq/(8*([AUX_MU_BAUD]+1))
 // (as close to 115200 as possible)
 ldr x4, =0xfe200000 // x4 = [gpio_base] = 0xfe200000
 ldr w2, [x4, #0x4] // w2 = [GPFSEL1]
 and w2, w2, #0xfffc0fff // Unset bits 12, 13, 14 (FSEL14 => GPIO Pin 14 is an input).
 // Unset bits 15, 16, 17 (FSEL15 => GPIO Pin 15 is an input).
 orr w2, w2, #0x00002000 // Set bit 13 (FSEL14 => GPIO Pin 14 takes alternative function 5).
 orr w2, w2, #0x00010000 // Set bit 16 (FSEL15 => GPIO Pin 15 takes alternative function 5).
 str w2, [x4, #0x4] // [GPFSEL1] = updated value => Enable UART 1.
 str wzr, [x4, #0x94] // [GPPUD] = 0x00000000 => GPIO Pull up/down = OFF
 mov x5, #0x96 // Wait 150 instruction cycles (as stipulated by datasheet).
 3:
 subs x5, x5, #0x1 // x0 -= 1
 b.ne 3b // Repeat until x0 == 0.
 mov w2, #0xc000 // w2 = 2^14 + 2^15
 str w2, [x4, #0x98] // [GPPUDCLK0] = 0x0000c000 => Control signal to lines 14, 15.
 mov x0, #0x96 // Wait 150 instruction cycles (as stipulated by datasheet).
 4:
 subs x0, x0, #0x1 // x0 -= 1
 b.ne 4b // Repeat until x0 == 0.
 str wzr, [x4, #0x98] // [GPPUDCLK0] = 0x00000000 => Remove control signal to lines 14, 15.
 str w3, [x1, #0x60] // [AUX_MU_CNTL] = 0x00000003 => Enable Mini UART Tx/Rx
# ------------------------------------------------------------------------------
# Send 'ABC' over Mini UART
# ------------------------------------------------------------------------------
 mov w0, #0x41 // start char 'A'
 mov w3, #0x43 // end char 'C'
 5:
 6:
 ldr w2, [x1, #0x54] // w2 = [AUX_MU_LSR]
 tbz x2, #5, 6b // Repeat last statement until bit 5 is set.
 strb w0, [x1, #0x40] // [AUX_MU_IO] = w0
 add w0, w0, #1
 cmp w0, w3
 b.le 5b
 b 1b
Then I built it as follows:

Code: Select all

$ aarch64-none-elf-as -o uart.o uart.s
$ aarch64-none-elf-ld --no-warn-rwx-segments -N -Ttext=0x80000 -o uart.elf uart.o
$ aarch64-none-elf-objcopy --set-start=0x80000 uart.elf -O binary uart.img
Note, the kernel image I created is called uart.img. Then I created the following config.txt:

Code: Select all

# Stop start.elf from filling in ATAGS (memory from 0x100) before launching the
# kernel.
disable_commandline_tags=1
# Disable initial turbo since it affects Mini UART timing.
# See https://github.com/raspberrypi/firmware/issues/1945
initial_turbo=0
# Dynamic frequency clocking minimum value needed for Mini UART to function correctly.
core_freq_min=500
# Ensure the bootloader loads a 64-bit kernel (not entirely necessary but a
# good safety check).
arm_64bit=1
# Kernel image to load
kernel=uart.img
I deployed this to my rasbperry pi 400, and it worked. Hopefully it should for you too!

One note: in your code, I see you are setting x29 and x30 to peripheral addresses. This may work, but is somewhat unconventional. Normally x29 is used as the frame pointer, and x30 is the link register. When you call a function with a bl instruction, x30 will be set to the return address, so that ret instruction functions correctly. So as soon as you execute a bl instruction, the x30 register will be overwritten. For this reason, you might prefer to use different registers for storing those peripheral addresses.

rgz
Posts: 2
Joined: Sun Feb 23, 2025 12:17 am

Re: pi 4B assembly UART

Fri Mar 28, 2025 12:03 pm

pmoore wrote: Hi rgz,

I didn't dive too deep into your issue, but instead put something together to achieve what you were trying to do. Perhaps by comparing mine vs yours, you'll work out what was wrong.

I created the following uart.s file:

Code: Select all

.global _start
.align 2
_start:
 mrs x0, mpidr_el1 // x0 = Multiprocessor Affinity Register value.
 and x0, x0, #0x3 // x0 = core number.
 cbz x0, 2f // Put all cores except core 0 to sleep.
1:
 wfi
 b 1b
2:
# ------------------------------------------------------------------------------
# Initialise the Mini UART interface for logging over serial port.
# Note, this is Broadcomm's own UART, not the ARM licenced UART interface.
# ------------------------------------------------------------------------------
 ldr x1, =0xfe215000 // x1 = aux_base
 ldr w2, [x1, #0x4] // w2 = [AUX_ENABLES] (Auxiliary enables)
 orr w2, w2, #1
 str w2, [x1, #0x4] // [AUX_ENABLES] |= 0x00000001 => Enable Mini UART.
 str wzr, [x1, #0x44] // [AUX_MU_IER] = 0x00000000 => Disable Mini UART interrupts.
 str wzr, [x1, #0x60] // [AUX_MU_CNTL] = 0x00000000 => Disable Mini UART Tx/Rx
 mov w2, #0x6 // w2 = 6
 str w2, [x1, #0x48] // [AUX_MU_IIR] = 0x00000006 => Mini UART clear Tx, Rx FIFOs
 mov w3, #0x3 // w3 = 3
 str w3, [x1, #0x4c] // [AUX_MU_LCR] = 0x00000003 => Mini UART in 8-bit mode.
 str wzr, [x1, #0x50] // [AUX_MU_MCR] = 0x00000000 => Set UART1_RTS line high.
 mov w2, 0x0000021d
 str w2, [x1, #0x68] // [AUX_MU_BAUD] = 0x0000021d
 // => baudrate = system_clock_freq/(8*([AUX_MU_BAUD]+1))
 // (as close to 115200 as possible)
 ldr x4, =0xfe200000 // x4 = [gpio_base] = 0xfe200000
 ldr w2, [x4, #0x4] // w2 = [GPFSEL1]
 and w2, w2, #0xfffc0fff // Unset bits 12, 13, 14 (FSEL14 => GPIO Pin 14 is an input).
 // Unset bits 15, 16, 17 (FSEL15 => GPIO Pin 15 is an input).
 orr w2, w2, #0x00002000 // Set bit 13 (FSEL14 => GPIO Pin 14 takes alternative function 5).
 orr w2, w2, #0x00010000 // Set bit 16 (FSEL15 => GPIO Pin 15 takes alternative function 5).
 str w2, [x4, #0x4] // [GPFSEL1] = updated value => Enable UART 1.
 str wzr, [x4, #0x94] // [GPPUD] = 0x00000000 => GPIO Pull up/down = OFF
 mov x5, #0x96 // Wait 150 instruction cycles (as stipulated by datasheet).
 3:
 subs x5, x5, #0x1 // x0 -= 1
 b.ne 3b // Repeat until x0 == 0.
 mov w2, #0xc000 // w2 = 2^14 + 2^15
 str w2, [x4, #0x98] // [GPPUDCLK0] = 0x0000c000 => Control signal to lines 14, 15.
 mov x0, #0x96 // Wait 150 instruction cycles (as stipulated by datasheet).
 4:
 subs x0, x0, #0x1 // x0 -= 1
 b.ne 4b // Repeat until x0 == 0.
 str wzr, [x4, #0x98] // [GPPUDCLK0] = 0x00000000 => Remove control signal to lines 14, 15.
 str w3, [x1, #0x60] // [AUX_MU_CNTL] = 0x00000003 => Enable Mini UART Tx/Rx
# ------------------------------------------------------------------------------
# Send 'ABC' over Mini UART
# ------------------------------------------------------------------------------
 mov w0, #0x41 // start char 'A'
 mov w3, #0x43 // end char 'C'
 5:
 6:
 ldr w2, [x1, #0x54] // w2 = [AUX_MU_LSR]
 tbz x2, #5, 6b // Repeat last statement until bit 5 is set.
 strb w0, [x1, #0x40] // [AUX_MU_IO] = w0
 add w0, w0, #1
 cmp w0, w3
 b.le 5b
 b 1b
Then I built it as follows:

Code: Select all

$ aarch64-none-elf-as -o uart.o uart.s
$ aarch64-none-elf-ld --no-warn-rwx-segments -N -Ttext=0x80000 -o uart.elf uart.o
$ aarch64-none-elf-objcopy --set-start=0x80000 uart.elf -O binary uart.img
Note, the kernel image I created is called uart.img. Then I created the following config.txt:

Code: Select all

# Stop start.elf from filling in ATAGS (memory from 0x100) before launching the
# kernel.
disable_commandline_tags=1
# Disable initial turbo since it affects Mini UART timing.
# See https://github.com/raspberrypi/firmware/issues/1945
initial_turbo=0
# Dynamic frequency clocking minimum value needed for Mini UART to function correctly.
core_freq_min=500
# Ensure the bootloader loads a 64-bit kernel (not entirely necessary but a
# good safety check).
arm_64bit=1
# Kernel image to load
kernel=uart.img
I deployed this to my rasbperry pi 400, and it worked. Hopefully it should for you too!

One note: in your code, I see you are setting x29 and x30 to peripheral addresses. This may work, but is somewhat unconventional. Normally x29 is used as the frame pointer, and x30 is the link register. When you call a function with a bl instruction, x30 will be set to the return address, so that ret instruction functions correctly. So as soon as you execute a bl instruction, the x30 register will be overwritten. For this reason, you might prefer to use different registers for storing those peripheral addresses.
Thanks a lot, I just tried out your code and it does work.
I'll try to understand where I went wrong and post here when I figure it out.
Also thanks about the description on the x29 and x30 registers, when I wrote that code I didn't really dig into AAPCS64 yet but now I've gotten familiar with how to setup and use frame records on the stack.

3 posts • Page 1 of 1

Return to "Bare metal, Assembly language"

AltStyle によって変換されたページ (->オリジナル) /