Raspberry Pi Screen01

实验目标

了解图形图像的基本知识

背景补充

有关黑白、灰度、8色、低彩、高彩、真彩和RGB32的详细说明与区别见原文

Pi在开机时,先运行的是图像处理器,后运行主处理器,当外接显示器时可以看到在启动Pi时,是先在屏幕上显示一张全彩的影像,然后才进入到系统启动。

Pi在进行与图像处理器通信的时候采用的是信箱通信方式,以发送信件以及接收回答信件为进程间通信的基本方式。当一个进程希望与另一进程通信时,就创建一个链接两个进程的信箱,发送进程把信件投入信箱,而接收进程可以在任何时刻取走信件。 采用信箱通信的最大好处是,发送方和接收方不必直接建联系,没有处理时间上的限制。发送方可以在任何时间发信,接收方也可以在任何时间收信。

信箱的地址表如下

发送方:

  1. 发送方等待直到状态字段的最高位为0。
  2. 最低的4位代表了所要发送的信箱序号,高28位代表了所要发送的内容。

接收方:

  1. 接收方等待直到状态字段的30标志位为0。
  2. 读取。
  3. 确认信箱的正确,否则重复上述步骤直到正确。

代码分析

GetMailboxBase

.globl GetMailboxBase
GetMailboxBase:
ldr r0,=0x2000B880
mov pc,lr

根据上表我们知道0x2000B880是信箱的地址。

MailboxWrite

  1. 校验输入(r0)输出(r1)的低四位都是0,确保信箱正确性。
  2. 利用GetMailboxBase函数得到信箱地址。
  3. 读取状态字段。
  4. 校验状态字段的最高位是否为0,否则返回第3步。(等待)
  5. 连接发送内容(高28位)与信箱通道(低4位)
.globl MailboxWrite
MailboxWrite:
tst r0,#0b1111
movne pc,lr
cmp r1,#15
movhi pc,lr

tst 数据处理指令,用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的与运算,并根据运算结果更新CPSR中条件标志位的值,这几行代码就是检查r0和r1寄存器的最低4为是否为0,若不为0则把lr寄存器装载到pc中,返回并退出。

channel .req r1
value .req r2
mov value,r0
push {lr}
bl GetMailboxBase
mailbox .req r0

保护现场,避免覆盖r0的值。

wait1$:
status .req r3
ldr status,[mailbox,#0x18]

根据表格可以知道0x2000B898是Status的地址。

tst status,#0x80000000
.unreq status
bne wait1$

检查最高位是否为0。

add value,channel
.unreq channel

高28位数值于低4位通道号连接。

str value,[mailbox,#0x20]
.unreq value
.unreq mailbox
pop {pc}

根据表格可以得知地址2000B8A0是WRITE,将value的值保存到该地址中即可,同时取消所有定义,pc出栈,完成操作。

MailboxRead

从信箱中读取信件的代码与之前实现基本相同。在读取的时候要从0x2000B880地址中读取。

FrameBufferInfo

.section .data
.align 4
.globl FrameBufferInfo
FrameBufferInfo:
.int 1024 /* #0 Physical Width */
.int 768 /* #4 Physical Height */
.int 1024 /* #8 Virtual Width */
.int 768 /* #12 Virtual Height */
.int 0 /* #16 GPU - Pitch */
.int 16 /* #20 Bit Depth */
.int 0 /* #24 X */
.int 0 /* #28 Y */
.int 0 /* #32 GPU - Pointer */
.int 0 /* #36 GPU - Size */

以上代码是定义传到GPU的数据格式,各项意义均已说明,注意字对齐。

接下来的步骤如下:

  1. 向mailbox1 写入地址FrameBufferInfo + 0x40000000。
  2. 从mailbox1 中读出数据,如果不是0,则停止申请frame buffer。
  3. 将指针指向我们所要展示的图片,就能在显示器上看到图片了。
FrameBufferInfo + 0x40000000

加上0x40000000是一种特定的指令,为了能够告诉GPU禁止自身的cache,以便能让我们观察到数据变换,这是规定好的指令。

  1. 输入
  2. 将输入写到帧缓冲区中
  3. 将frame buffer + 0x40000000 的地址送到mailbox中
  4. 从mailbox中得到返回值
  5. 判断返回是否为0,如果是0则表示成功
  6. 返回一个帧缓冲区的指针

InitialiseFrameBuffer

.section .text
.globl InitialiseFrameBuffer
InitialiseFrameBuffer:
width .req r0
height .req r1
bitDepth .req r2
cmp width,#4096
cmpls height,#4096
cmpls bitDepth,#32
result .req r0
movhi result,#0
movhi pc,lr

检查width和height是否都小于等于4096,bitDepth是否小于等于32,如果满足条件则将0保存到result中,表示符合条件。

fbInfoAddr .req r3
push {lr}
ldr fbInfoAddr,=FrameBufferInfo
str width,[fbInfoAddr,#0]
str height,[fbInfoAddr,#4]
str width,[fbInfoAddr,#8]
str height,[fbInfoAddr,#12]
str bitDepth,[fbInfoAddr,#20]
.unreq width
.unreq height
.unreq bitDepth

按照之前给出的数据格式保存到相应的地址中

mov r0,fbInfoAddr
add r0,#0x40000000
mov r1,#1
bl MailboxWrite

这里就是之前说的固定格式的地址指令,r1中则是选择了通道。

mov r0,#1
bl MailboxRead

r0中选择了通道,与上面写信箱的通道保持一致。

teq result,#0
movne result,#0
popne {pc}

测试返回值是否为0,如果结果不为0则返回0表示有错误发生。

mov result,fbInfoAddr
pop {pc}
.unreq result
.unreq fbInfoAddr

将帧缓冲地址返回


现在我们要使用GPU处理图像。

mov r0,#1024
mov r1,#768
mov r2,#16
bl InitialiseFrameBuffer

定义一个1024*768,16位色的帧

teq r0,#0
bne noError$

mov r0,#16
mov r1,#1
bl SetGpioFunction
mov r0,#16
mov r1,#0
bl SetGpio

error$:
b error$

noError$:
fbInfoAddr .req r4
mov fbInfoAddr,r0

检查r0寄存器中是否为0,若不为0,进行图像处理程序,否则通过GPIO口点亮ACT灯。

在大多数的位图的存储方式中,总是从左向右,从上到下,所以我们使用两个循环完成每个像素点。

render$:
fbAddr .req r3
ldr fbAddr,[fbInfoAddr,#32]

colour .req r0
y .req r1
mov y,#768
drawRow$:
x .req r2
mov x,#1024
drawPixel$:
strh colour,[fbAddr]
add fbAddr,#2
sub x,#1
teq x,#0
bne drawPixel$

sub y,#1
add colour,#1
teq y,#0
bne drawRow$

b render$

.unreq fbAddr
.unreq fbInfoAddr

strh 指令是半字存储,在通过x和y分别控制循环变量,实现嵌套的循环,完成整个屏幕像素点的加载


结果展示