Spider-OS

login - registration

Back to the post list

UART and GPIO
2019 Sep 22

What if we communicated with our Raspberry Pi ? We saw in the previous post how to light the ACT led. We can do Morse, but it's still very limited ;-)
No seriously I propose to use the GPIO connector. This has UART inputs / outputs that allow serial communication. With the adapter that goes well, it can be connected to our PC via a USB port.
Motivated to do some DIY ? Here we go.

usb uart gpio

Comments :

Aran (webmaster)
2019 Sep 22 17:48

1. GPIO connector

Let's first see where this GPIO connector is located on the Raspberry Pi. It's titled J8, circled in yellow below :

Raspberry Pi J8 Header
Figure 1 : GPIO header J8

The numbering of the pins is as follows :

Raspberry Pi J8 pins
Figure 2 : header J8 pins

Each pin is configurable. It can have different roles : input, output, or an alternative function from 0 to 5. It is by programming that this choice is defined.
Some pins are not configurable, because it is the power supply (in red) or the ground (in gray).

Here is a table summarizing the functions available to us, associated with the different pins :

GPIO functions
Figure 3 : GPIO functions on Raspberry Pi 4

As you can see, we have a choice. One feature can be split across multiple pins. To find them easier I used colors. For example, all the pins that are used for the UART are in red. The Alt columns indicate which alternate function to choose to enable the feature on the pin.

In our case, we want to use the UART to communicate with our PC. This is good, the UART 0 (PL011) is wired on pins 8 and 10, which correspond to GPIO14 and 15 respectively. Be careful not to confuse the J8 pins, and GPIO functions associated with them.
In serial communication, it is necessary to be able to transmit the data and to receive them. With the UART we speak of TXD for sending the data, and RXD for the reception. In Figure 3, we see that these two features are available in GPIO14 and GPIO15 with the alternative function 0. This is what we will program thereafter.


2. Serial <-> USB connection

To connect our Raspberry Pi to our PC, an adapter is needed. It is thus necessary to convert from an RS232 serial signal to a USB stream. In addition the adapter must also perform a voltage adjustment, which is 5V PC side and 3.3V Raspberry Pi side.

I use this one : USB to TTL Converter, that works well reliably. It is based on the component Silabs CP2102.

Here's how to make the connections :

TTL to USB
Figure 4 : serial connections to usb converter

The pin 8 (TXTD0) on the Raspberry Pi side is connected to the RXD of the converter (in blue in the figure above). The pin 10 (RXD0) on the Raspberry Pi side is connected to TXD of the converter (in green). As a sent data must be received, the signals are crossed: TXD <-> RXD. The ground GND is connected to the pin 14. The other grounds in gray can also be used in figure 4.

danger signDear readers, I want to warn you ! Any such manipulation is risky because a bad connection from you, can damage the Raspberry Pi. You are solely responsible for what you do : I decline all responsibility.



Then you connect the USB adapter to the PC : a driver is necessary for it to be recognized. You can download it here: https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers

When the driver is installed you can see a new COM port in the device manager (on Windows) :

port COM
Figure 5 : COM port CP210x

This COM port will be used later to communicate with the Raspberry Pi. We will use a terminal like putty.


3. GPIO programming

We are now discussing GPIO programming. To understand I invite you to take a look at the official documentation from page 89 : BCM2835 ARM Peripherals.

We want to activate the alternate function 0 on GPIO14 and 15, to have the function UART0 TXD0 and RXD0 (see figure 3).
We use for this the register GPFSEL1 : GPIO Function Select 1. This register is used to program the function (input, output, alternate 0 to 5) GPIOs from 10 to 19. See the table 6-3 in the documentation.

To program the function of the GPIO14, those are the bits 14-12 that are used, entitled FSEL14. In the same way for GPIO15, we use bits 17-15 : FSEL15.

So we see that there are 3 bits to select the function of a GPIO, in the following way:

000input
001output
100alternate function 0
101alternate function 1
110alternate function 2
111alternate function 3
011alternate function 4
010alternate function 5

This is the function alternate 0 that we want. So we put the bits to 100 : FSEL14 = 100b and FSEL15 = 100b.

Here is the code to program the GPIO :

PERIPHERAL_BASE		= 0xfe000000		; Pi 4
GPIO_BASE				= 0x00200000
GPIO_GPFSEL1			= 0x4			; GPIO Function Select 1
GPIO_FSEL4_ALT0		= 0x4000
GPIO_FSEL5_ALT0		= 0x20000

; map UART0 to GPIO 14 and 15

imm32 r4,PERIPHERAL_BASE + GPIO_BASE

ldr r0,[r4,GPIO_GPFSEL1]
bic r0,0x3f000									; clear bits 17-15, 14-12
orr r0,GPIO_FSEL4_ALT0 + GPIO_FSEL5_ALT0		; GPIOs 14 and 15 alternate fonction 0
str r0,[r4,GPIO_GPFSEL1]		
		

4. programming the UART

That's it we prepared everything : both GPIO14 and 15 are configured for the UART. We will be able to program our UART component (PL011) for data transmission. You can find a description of these registers in the manual BCM2835 ARM Peripherals chapter 13 page 175.

UART Clock

We start by configuring the clock speed of the UART via a Mailbox message. An explanation of the Mailbox in this post.

struc MboxSetClockRate
{
	dw .end - $					; message size
	dw $00000000				; request code

	dw Set_Clock_Rate			; tag identifier = 0x38002
	dw 0x00000008				; buffer size in bytes
	dw 0x00000000				; process request
	dw CLK_UART_ID				; UART Clock ID = 2
	dw 4000000					; 4 MHz

	dw 0x00000000				; end tag
	.end:
}
		
; set up clock for consistent divisor values
MAIL_TAGS	= 8			; ARM -> VC property channel

align 16
mboxSetClockRate		MboxSetClockRate

mov r0,MAIL_TAGS				; mailbox channel 8
mov r1,mboxSetClockRate			; message address
bl mboxCall
		

Depending on this clock speed, we define the parameters that will determine the serial transmission rate. There are two parameters IBRD : integer baud rate divisor and FBRD : fractional baud rate divisor.

I chose a transmission speed of 115200 bauds. The IBRD is calculated with that formula : clock speed / (16 * serial link speed), that to say : 4 000 000 / (16 * 115200) = 2.170138 = 2.
We recover the fractional part of the previous result, either 0.170138. It is used in the formula to calculate the FBRD : (0.170138 * 64) + 0.5 = 11.38 = 11.

For summary, we have IBRD = 2 and FBRD = 11. That we encode like this :

PL011_REGS_BASE		= 0x201000
UART0_IBRD				= 0x24		; Integer Baud rate divisor
UART0_FBRD			= 0x28		; Fractional Baud rate divisor

imm32 r5,PERIPHERAL_BASE + PL011_REGS_BASE
	
mov r0,2
str r0,[r5,UART0_IBRD]

mov r0,11
str r0,[r5,UART0_FBRD] 
		
frame 8bits

The frame used on the serial link is configured with 8 data bits, no parity, 1 stop bit. And we do not use the FIFO. Here is the code :

UART0_LCRH			= 0x2C		; Line Control register

mov r0,1100000b
str r0,[r5,UART0_LCRH]
		

The reception and transmission of the UART is then activated with the CR register :

UART0_CR				= 0x30		; Control register

mov r0,11b SHL 8			; RXE and TXE enable
orr r0,1					; UART enable
str r0,[r5,UART0_CR]
		

5. sending data : Raspberry Pi to PC

transmit UART

We have completed the configuration of the UART. We can already send data from the UART to the PC. The data is transmitted character by character (8 bits). Two registers are used : FR : Flag register and DR : Data Register.

We check that the FIFO is not full with the register FR, and then we can write the character to be transmitted in the register DR. Here is the code of a subroutine to write a character on the serial link :

UART0_FR		= 0x18		; Flag register
UART0_DR		= 0x00		; Data Register

;--------------------------------------------------------------------------------------------------------
; uartWrite : write char on UART
;
; input r0 = ASCII char to write
;
align 4
uartWrite:

	imm32 r2,PERIPHERAL_BASE + PL011_REGS_BASE

	; check empty FIFO
	.DO:
		ldr r1,[r2,UART0_FR]
		tst r1,1 SHL 5
	bne .DO

	; writing of the ASCII char on uart
	str r0,[r2,UART0_DR]
	
mov pc,lr
		

It's nice all that, but how to see if the PC has received the data ? Well we will use the terminal Putty. It is a text terminal emulator, which works in particular with COM ports.

It must be configured to use the COM port associated with our USB adapter. And also the transmission rate, the number of bits, the parity, the stop bit, as we did in chapter 4 for the UART. Here is an example of setting Putty:

putty serial
Figure 6 : putty serial configuration

Come on I let you imagine a program to write a string with the subroutine uartWrite. The idea is to get that on Putty :

Hello world Putty


6. receiving data : PC to Raspberry Pi

receive UART

How to transmit a character from the PC to our Raspberry Pi ? Well, you just have to type in the Putty console. It is up to the Raspberry Pi to process this received character. Most often we return the character typed on the serial link, it allows to see what you type.

The difficulty here is knowing when a character is received by the UART, in order to process it. There are two possibilities : to make a permanent scan of the registers, or to issue an interruption. The second option is the most efficient, and less greedy in cpu resource.

UART interrupt

IRQ UART
Figure 7 : IRQ UART

We will program the UART to generate an interrupt when it receives data. That is, our main program must be interrupted, and a jump to an IRQ routine is performed to read the received data. After executing the IRQ routine, we return to the main program.
This interruption is of type IRQ. As we saw in the post Raspberry Pi 4 from scratch, it is in the vector table that the address of the IRQ routine is indicated.

The IMSC : Interrupt Mask Set Clear register is used to enable the interrupt on the UART. And more precisely the RXIM bit to have an interrupt only when receiving a data :

UART0_IMSC				= 0x38		; Interupt Mask Set Clear Register
UART_IMSC_RXIM		= 1 SHL 4	; Receive interrupt mask

mov r0,UART_IMSC_RXIM		; UART_IMSC_RTIM 
str r0,[r5,UART0_IMSC]
		

UART interrupt on ARM

It's not enough ! It is also necessary to activate the UART interrupt on the ARM processor side. And yes there is electronic wiring behind all this ;-)
The Interrupt enable register 2 registry is used as indicated in the documentation BCM2835 ARM Peripherals page 117. We also see that the UART IRQ has the number 57. We activate the corresponding bit in the register :

INTERRUPT_REGS_BASE		= 0xb000
ARM_ENABLE_IRQS_2			= 0x214
IRQ_PL011_UART				= 1 SHL 25	; 57

mov r4,PERIPHERAL_BASE
orr r4,INTERRUPT_REGS_BASE

ldr r0,[r4,ARM_ENABLE_IRQS_2]		; interrupt enable register 2 (irq 32-63)
orr r0,IRQ_PL011_UART
str r0,[r4,ARM_ENABLE_IRQS_2]  
		

IRQ interrupt on ARM

Last step: it is necessary to generally activate IRQ interrupts on the ARM processor.

mrs r0,cpsr				; read status cpu
bic r0,1 SHL 7				; IRQ bit
msr cpsr,r0				; write status cpu 
		

IRQ routine

IRQ interrupts are now active on the UART. If you type a character in Putty, the IRQ routine indicated in the vector table is called. As a reminder, this is the program irqHandler.
Normally this program must handle all interrupts of type IRQ, but in our example we will just manage the IRQ interrupt caused by the UART.

Our IRQ routine will first verify that the interrupt was indeed caused by the UART, and if so we call a subroutine to read the character : irqUart. This subroutine returns the character typed on the serial link in order to display it in the Putty console.
It's OK ? You understood ? So let's go :

;--------------------------------------------------------------------------------------------------------
; irqHandler : routine called if IRQ 0x18
; calling subroutines depending on the origin of the IRQ
;
align 4
irqHandler:

	; Return to the interrupted instruction
	sub lr,lr,4
	
	; saves registers on the IRQ stack
	stmfd sp!,{r0-r12,lr} 
	
	mov r4,PERIPHERAL_BASE      
	orr r4,INTERRUPT_REGS_BASE	
	
	; check if the IRQ comes from the UART 
	ldr r0,[r4,ARM_IRQ_PENDING_2]
	tst r0,IRQ_PL011_UART
	bne irqUart
	
ldmfd sp!,{r0-r12,pc}^		; restore registers and CPSR (return to supervisor mode)
		
;--------------------------------------------------------------------------------------------------------
; irqUart : interrupt routine uart
;
align 4
irqUart:

	bl uartRead			
	bl uartWrite			; echo of the character received

ldmfd sp!,{r0-r12,pc}^		; restore registers and CPSR (return to supervisor mode)
		

reading the character received

As you can see in the code, we still have the reading program of the data we need to do : uartRead :

;--------------------------------------------------------------------------------------------------------
; uartRead : read char on UART
;
; output r0 = ASCII char received
;
align 4
uartRead:

	imm32 r1,PERIPHERAL_BASE + PL011_REGS_BASE

	.DO:
		ldr r0,[r1,UART0_FR]
		tst r0,0x10					; check bit 4 RXFE (Receive register Empty)
	bne .DO

	; char received
	ldr r0,[r1,UART0_DR]				; read data register
	and r0,0xff						; char on 8 bits lsb

	mov pc,lr
		

7. echo

Voila, it's over. You can have fun typing in the Putty console, it should be displayed. It's the Raspberry Pi that makes it !

putty echo
Figure 8 : echo Putty

You can download the compiled program to test directly on your Raspberry Pi here. Just copy the files to a blank SD card.

I hope you liked this post. Do not hesitate to leave me a comment.

w1ll12520114
2019 Oct 11 14:33

Really cool post! Im glad you found an easy way to directly start messing around with the Raspberry Pi 4.

Coding for the new GPU is long and repetitive lol

Next step is more complicated answers from the pi!

ps you forgot to update your logo in the login panel

Keep us tuned
Aran (webmaster)
2019 Oct 20 10:10

A little oversight at the end of chapter 4.
You have to activate the UART by setting bit 0 of the CR register.
I corrected the code:
UART0_CR				= 0x30		; Control register

mov r0,11b SHL 8			; RXE and TXE enable
orr r0,1					; UART enable
str r0,[r5,UART0_CR]
		

Add a comment

Page : 1