Escolar Documentos
Profissional Documentos
Cultura Documentos
Raspberry Pi
Em 2009 foi fundada no Reino Unido a Raspberry Pi Foundation, uma
organização sem fins lucrativos que, à semelhança da iniciativa do “BBC-Micro”
na década de 80, tem por objetivo a promoção do estudo de Ciência básica da
Computação nas escolas. Uma das metas é colocar os estudantes novamente em
contato com o “hardware” – manuseando placas e componentes eletrônicos,
como acontecia nas décadas de 80 e 90, com os computadores que também
tinham nomes de frutas.
Os “raspberry pi” são computadores de placa única (single board computer
ou SBC) de baixo custo baseados em unidades de processamento altamente
integradas (SoC), produzidas pela Broadcom com exclusividade para a Raspberry
Pi Foundation.
Modelo CPU Arquitetura Clock GPU Memória modelos
RPi-0
BCM2835 ARM1176JZF-S (1) 700 MHz VideoCore IV 512 MiB
RPi-1
RPi-2 BCM2836 Cortex-A7 (4) 900 MHz VideoCore IV 1 GiB
RPi-3 BCM2837 Cortex-A53 (4) 1.4 GHz VideoCore IV 1 GiB
RPi-4 BCM2711 Cortex-A72 (4) 1.8 GHz VideoCore VI 4 GiB
84
Laboratório de Processadores
Bruno Basseto / 2023
15 É muito provável que as versões mais recentes do bootloader já saibam interpretar o formato
ELF diretamente. Mas vamos manter a tradição e, como bônus, aprender a usar o objcpy.
85
Laboratório de Processadores
Bruno Basseto / 2023
Interfaces de depuração
Vamos utilizar as interfaces J-TAG e UART para interagir com as
aplicações ARM que criarmos. Alguns dos pinos do conector de expansão são
usados para interligar a placa Raspberry Pi ao computador hospedeiro através
dessas interfaces, conforme a figura a seguir.
conexões:
JTAG e UART
1 2
6 gnd
8 rx UART (3.3V)
10 tx
TMS 13
TRST 15 16 RTCK
18 TDO
20 GND J-TAG
22 TCK
TDI 37
39 40
• Interface J-TAG
TMS (marrom) Pino 13 GPIO 27
TCK (laranja) Pino 22 GPIO 25
TDI (amarelo
amarelo) Pino 37 GPIO 26
TDO (verde) Pino 18 GPIO 24
TRST (Cinza) Pino 15 GPIO 22
RTCK (roxo) Pino 16 GPIO 23*
GND (preto) Pino 20
• Interface UART
TX (verde) Pino 10 GPIO 15
RX (branco
branco) Pino 8 GPIO 14
GND (preto) Pino 6
Observe que todos os sinais presentes no conector de expansão são de
nível elétrico 3.3 V e a placa pode ser danificada caso tensões mais altas
apareçam nos pinos.
Para acessar a interface J-TAG vamos empregar o software OpenOCD, com
um arquivo de configuração específico para a versão correta do Raspberry Pi e da
interface (ou “transporte”) a utilizar (placa Segger J-Link). Para acessar a
interface UART usaremos um conversor USB/Serial e qualquer software de
emulação de terminal (por exemplo, screen).
86
Laboratório de Processadores
Bruno Basseto / 2023
Makefile exemplo
FONTES = arquivo.c arquivo.s
# Arquivos de saída
EXEC = kernel.elf
MAP = kernel.map
IMAGE = kernel.img
LIST = kernel.list
PREFIXO = arm-none-eabi-
LDSCRIPT = kernel.ld
AS = ${PREFIXO}as
LD = ${PREFIXO}ld
GCC = ${PREFIXO}gcc
OBJCPY = ${PREFIXO}objcopy
OBJDMP = ${PREFIXO}objdump
OPTS = -march=armv7-a -mtune=cortex-a7 -g
OBJ = $(FONTES:.s=.o)
OBJETOS = $(OBJ:.c=.o)
# Gerar executável
${EXEC}: ${OBJETOS}
${LD} -T ${LDSCRIPT} -M=${MAP} -o $@ ${OBJETOS}
# Gerar imagem
${IMAGE}: ${EXEC}
${OBJCPY} ${EXEC} -O binary ${IMAGE}
# Gerar listagem
${LIST}: ${EXEC}
${OBJDMP} -d ${EXEC} > ${LIST}
# Compilar arquivos em C
.c.o:
${GCC} ${OPTS} -c -o $@ $<
# Iniciar openocd
ocd:
@if pgrep openocd >/dev/null ; then\
echo "openocd já está executando"; \
else openocd -f rpi2.cfg & \
fi
# Iniciar gdb
gdb: ${EXEC}
gdb-multiarch -ex "set architecture arm" \
-ex "target extended-remote :3333" \
-ex "load" \
${EXEC}
stop:
-pkill openocd
-pkill gdb-multiarch
87
Laboratório de Processadores
Bruno Basseto / 2023
Conector Conector
Câmera HDMI
88
Laboratório de Processadores
Bruno Basseto / 2023
reset:
mrc p15, 0, r0, c0, c0, 5 // registrador MPIDR
ands r0, r0, #0x03 // 2 bits menos significativos
beq core0
cmp r0, #1
beq core1
cmp r0, #2
beq core2
b core3
89
Laboratório de Processadores
Bruno Basseto / 2023
Periféricos
Os periféricos do SoC são configurados e controlados por “registradores” registradores dos
periféricos
que são mapeados na memória do sistema. O endereço inicial na memória física a
partir do qual os registradores de configuração dos periféricos são mapeados é
denominado PERIPH_BASE e seu valor depende do modelo:
◦ PERIPH_BASE é 0x3f000000 no Raspberry Pi modelos 2 e 3.
GPIOs
O SoC utilizado no Raspberry Pi possui um módulo de controle de GPIO
com as seguintes características:
• 54 pinos configuráveis como entrada ou como saída e eventualmente
compartilhados com sinais de controle de outros periféricos (UART, SPI,
etc.);
• Qualquer dos pinos pode ser configurado individualmente com relação à
presença ou ausência de resistores de pull-up ou pull-down internos;
• O módulo GPIO permite identificar eventos em qualquer das entradas
(bordas de subida ou descida e níveis elétricos) e produzir interrupções
no processador.
Os registradores de controle dos GPIOs começam no endereço
PERIPH_BASE + 0x200000 e são descritos a seguir.
#include <stdint.h>
#define PERIPH_BASE 0x3f000000 // no RPi 2
#define GPIO_ADDR (PERIPH_BASE + 0x200000)
typedef struct {
uint32_t gpfsel[6]; // Function select (3 bits/gpio)
unsigned : 32;
uint32_t gpset[2]; // Output set (1 bit/gpio)
unsigned : 32;
uint32_t gpclr[2]; // Output clear (1 bit/gpio)
unsigned : 32;
uint32_t gplev[2]; // Input read (1 bit/gpio)
unsigned : 32;
uint32_t gpeds[2]; // Event detect status
unsigned : 32;
uint32_t gpren[2]; // Rising-edge detect enable
unsigned : 32;
uint32_t gpfen[2]; // Falling-edge detect enable
unsigned : 32;
uint32_t gphen[2]; // High level detect enable
unsigned : 32;
uint32_t gplen[2]; // Low level detect enable
unsigned : 32;
uint32_t gparen[2]; // Async rising-edge detect
unsigned : 32;
uint32_t gpafen[2]; // Async falling-edge detect
unsigned : 32;
uint32_t gppud; // Pull-up/down enable
uint32_t gppudclk[2]; // Pull-up/down clock enable
} gpio_reg_t;
90
Laboratório de Processadores
Bruno Basseto / 2023
Registrador GPIOs
gpfsel[0] 0a9
gpfsel[1] 10 a 19
gpfsel[2] 20 a 29
gpfsel[3] 30 a 39
gpfsel[4] 40 a 49
gpfsel[5] 50 a 53
91
Laboratório de Processadores
Bruno Basseto / 2023
GPIO Pino ALT0 ALT1 ALT2 ALT3 ALT4 ALT5 funções
*
0 27+ SDA0 SA5 PCLK alternativas
*
1 28+ SCL0 SA4 DE
*
2 3 SDA1 SA3 VSYNC
*
3 5 SCL1 SA2 HSYNC
*
4 7 GPCLK0 SA1 D0 TDI
*
5 29 GPCLK1 SA0 D1 TDO
*
6 31 GPCLK2 SOE/SE D2 RTCK
7 *
26 CS0(1) SWE/SRW D3
*
8 24 CS0(0) SD0 D4
†
9 21 MISO0 SD1 D5
†
10 19 MOSI0 SD2 D6
†
11 23 SCK0 SD3 D7
†
12 32 PWM0 SD4 D8 TMS
†
13 33 PWM1 SD5 D9 TCK
†
14 8 TXD0 SD6 D10 TXD1
†
15 10 RXD0 SD7 D11 RXD1
†
16 36 SD8 D12 CTS0 CS1(2) CTS1
†
17 11 SD9 D13 RTS0 CS1(1) RTS1
†
18 12 PCMCK SD10 D14 BSDA CS1(0) PWM0
†
19 35 PCMFS SD11 D15 BSCL MISO1 PWM1
†
20 38 PCMIN SD12 D16 BMISO MOSI1 GPCLK0
†
21 40 PCMOUT SD13 D17 BCE SCK1 GPCLK1
†
22 15 SD14 D18 SDCLK TRST
†
23 16 SD15 D19 SDCMD RTCK
†
24 18 SD16 D20 SDDAT0 TDO
†
25 22 SD17 D21 SDDAT1 TCK
†
26 37 D22 SDDAT2 TDI
†
27 13 D23 SDDAT3 TMS
28 SDA0 SA5 PCMCK
29 SCL0 SA4 PCMFS
†
30 SA3 PCMIN CTS0 CTS1
†
31 LAN SA2 PCMOUT RTS0 RTS1
†
32 (cam-12) GPCLK0 SA1 TXD0 TXD1
†
33 SA0 RXD0 RXD1
*
34 GPCLK0 SOE/SE SDCLK
35 *
(power)
CS0(1) SWE/SRW SDCMD
*
36 CS0(0) SD0 TXD0 SDDAT0
†
37 MISO0 SD1 RXD0 SDDAT1
†
38 USB MOSI0 SD2 RTS0 SDDAT2
†
39 SCK0 SD3 CTS0 SDDAT3
†
40 (pwm) PWM0 SD4 MISO2 TXD1
†
41 (cam-11) PWM1 SD5 MOSI2 RXD1
†
42 SMPS GPCLK1 SD6 SCK2 RTS1
†
43 SMPS GPCLK2 SD7 CE2(0) CTS1
44 ETH GPCLK1 SDA0 SDA1 CE2(1)
45 (pwm) PWM1 SCL0 SCL1 CE2(2)
*
46 (hdmi-19)
*
47 (led)
*
48 (sd) SDCLK
*
49 (sd) SDCMD
*
50 (sd) SDDAT0
*
51 (sd) SDDAT1
*
52 (sd) SDDAT2
*
53 (sd) SDDAT3
* = pull-up após reset, † = pull-down após reset
92
Laboratório de Processadores
Bruno Basseto / 2023
Para controlar o valor dos GPIOs configurados como saída utilizam-se os gpset
registradores gpset (para ligar uma ou mais saídas) e gpclr (para desligar uma gpclr
ou mais saídas); esses registradores associam cada bit a uma saída digital (em
gpset[0] os GPIOs de 0 a 31 e em gpset[1] os GPIOs de 32 a 53). Usar dois
grupos diferentes de registradores para ligar e para desligar é útil para controlar
um grupo de GPIOs sem interferir com os demais: ao escrever algum bit “1” em
gpset ou gpclr, somente as saídas correspondentes serão afetadas, nada
ocorrendo para os GPIOs cujos bits foram escritos com “0”.
// lê o estado do GPIO 17
int gpio17 = GPIO_REG(gplev[0]) & (0x01 << 17);
void __attribute__((interrupt("IRQ")))
trata_irq(void) {
// Interrupções comuns ("basic")
int basic = IRQ_REG(pending_basic);
if(bit_is_set(basic, 9)) {
// Interrupções do grupo 2
int pend = IRQ_REG(pending_2);
if(bit_is_set(pend, 20)) {
// IRQ 52 = interrupção GPIO
uint32_t ev = GPIO_REG(gpeds[0]);
if(bit_is_set(ev, 12)) trata_evento_gpio_12();
GPIO_REG(gpeds) = ev; // reconhece
}
}
}
void configura_gpio_12(void) {
// GPIO12 = entrada
GPIO_REG(gpfsel[1]) = GPIO_REG(gpfsel[1]) & (~(0x7 << 6));
GPIO_REG(gpren[0]) |= (1 << 12); // detectar borda de subida
IRQ_REG(enable_2) |= (1 << 20); // habilita int. GPIO (52)
}
94
Laboratório de Processadores
Bruno Basseto / 2023
Interrupções
O processador do Raspberry Pi é um ARM de arquitetura v.7 (ou v.8, no
caso dos modelos 3 e 4), cujo funcionamento com relação às interrupções é o
mesmo estudado até agora.
O vetor de interrupções sempre é situado a partir do endereço físico
zero. Em cada posição do vetor deve existir uma única instrução de salto
( b <rótulo> ou ldr pc, ... ). Como os serviços de interrupção podem
estar em qualquer posição da memória, normalmente ao vetor de interrupção se
segue uma tabela que contém os endereços absolutos dos serviços, para serem
lidos pelas instruções do vetor de interrupções com endereçamento indireto (por
exemplo, ldr pc, [pc, #24] ).
.section .init
.global start
start:
// Vetor de interrupções
// (no endereço 0x8000: deve ser copiado)
ldr pc, reset_addr
ldr pc, undef_addr
ldr pc, swi_addr
ldr pc, inst_abort_addr
ldr pc, data_abort_addr
nop
ldr pc, irq_addr
ldr pc, fiq_addr
95
Laboratório de Processadores
Bruno Basseto / 2023
interrupção do grupo “1” (bit “8”) ou do grupo “2” (bit “9”), dispensando assim a
verificação dos registradores pending_1 e pending_2, caso esses bits sejam
iguais a “zero”.
As tabelas a seguir descrevem as todas as interrupções existentes no
Raspberry Pi e os seus respectivos índices:
• Relação das interrupções do grupo “1” (índices “0” até “31”) nos
registradores com sufixo “_1”:
Índice Bit IRQ Índice Bit IRQ primeiro grupo
de interrupções
0 0 system timer (0) 16 16 DMA (0)
1 1 system timer (1) 17 17 DMA (1)
2 2 system timer (2) 18 18 DMA (2)
3 3 system timer (3) 19 19 DMA (3)
4 4 Codec (0) 20 20 DMA (4)
5 5 Codec (1) 21 21 DMA (5)
6 6 Codec (2) 22 22 DMA (6)
7 7 JPEG 23 23 DMA (7)
8 8 ISP 24 24 DMA (8)
9 9 USB 25 25 DMA (9)
10 10 3D 26 26 DMA (10)
11 11 Transposer 27 27 DMA (11-14)
12 12 MC sync (0) 28 28 DMA (comum)
13 13 MC sync (1) 29 29 Aux (mini-uart, spis)
14 14 MC sync (2) 30 30 ARM ?
15 15 MC sync (3) 31 31 VPUDMA
97
Laboratório de Processadores
Bruno Basseto / 2023
// Serviço de interrupção
void __attribute__((interrupt("IRQ")))
irq_service(void) {
if(IRQ_REG(pending_basic) & (1 << 0)) {
// trata interrupção do timer ARM
}
if(IRQ_REG(pending_basic) & (1 << 9)) {
// alguma interrupção do grupo 2 está pendente
if(IRQ_REG(pending_2) & ...) {
// etc...
}
}
}
// ...
IRQ_REG(enable_basic) = (1 << 0); // habilita int. do timer
IRQ_REG(disable_basic) = (1 << 0); // desabilita int. do timer
typedef struct {
uint32_t io;
uint32_t ier;
uint32_t iir;
uint32_t lcr;
uint32_t mcr;
uint32_t lsr;
uint32_t msr;
uint32_t scratch;
uint32_t cntl;
uint32_t stat;
uint32_t baud;
} mu_reg_t;
void mini_uart_init(void) {
// configura GPIO14 e GPIO15 como função ALT5 (mini UART)
uint32_t sel = GPIO_REG(gpfsel[1]);
sel = (sel & (~(7<<12))) | (2<<12);
sel = (sel & (~(7<<15))) | (2<<15);
GPIO_REG(gpfsel[1]) = sel;
AUX_REG(enables) = 1;
MU_REG(cntl) = 0;
MU_REG(ier) = 0;
MU_REG(lcr) = 3; // 8 bits
MU_REG(mcr) = 0;
MU_REG(baud) = 270; // para 115200 bps em 250 MHz
MU_REG(cntl) = 3; // habilita TX e RX
}
void mini_uart_putc(uint8_t c) {
while((MU_REG(stat) & 0x02) == 0) ; // não há espaço
MU_REG(io) = c;
}
uint8_t mini_uart_getc(void) {
while((MU_REG(stat) & 0x01) == 0) ; // não há dados a ler
return MU_REG(io);
}
// no serviço de interrupção...
int pend = IRQ_REG(pending_1);
if(bit_is_set(pend, 29)) {
// Interrupções do periférico AUX.
int irq_aux = AUX_REG(irq);
if(bit_is_set(irq_aux, 0)) uart_irq(); // mini uart
// outras interrupções (SPI?)...
}
100
Laboratório de Processadores
Bruno Basseto / 2023
Core timer
• O valor atual do timer pode ser lido ou escrito no registrador counter; o counter
valor de recarga pode ser configurado nos registradores load ou reload. load
reload
Uma alteração no registrador reload somente tem efeito após a próxima
interrupção do timer;
• O registrador pre deve conter uma constante para o divisor de pre
frequências, de modo a manter a frequência de clock para o core timer em 1
MHz. Como a frequência padrão do sistema no Raspberry Pi é de 250 MHz
e a frequência do barramento APB é 125 MHz, devemos gravar o valor
“126” nesse registrador; control
• control é o registrador empregado para ativar o timer e sua interrupção; ack
101
Laboratório de Processadores
Bruno Basseto / 2023
void timer_init(void) {
TIMER_REG(load) = 1000; // 1MHz / 1000 = 1kHz
TIMER_REG(pre) = 126;
TIMER_REG(control) = (1 << 9) // free-running counter
| (1 << 7) // habilita timer
| (1 << 5) // habilita interrupção
| (1 << 1); // timer de 23 bits
void __attribute__((interrupt("IRQ")))
interrupt_vector(void) {
if(IRQ_REG(pending_basic) & (1 << 0)) {
// Interrupção do timer ARM a cada 1 ms
TIMER_REG(ack) = 1; // reconhece interrupção
ticks++;
}
// outras interrupções...
}
102