CS107E Lecture 03 C Pointers and Arrays
对应之前写的,重新复习一遍
内存(memory)
汇编语言通过 lw 和 sw 指令将数据在内存和寄存器之前传递。这两个指令每次读取或写入 32 bits 即 4bytes 的内容,与寄存器大小相当。这两个汇编指令很自由,而C语言通过类型系统规范了内存数据存取的行为。
LW rd,imm12(rs)
从内存地址rs + imm12
中读取 4 bytes 的数据存入寄存器rd
中SW rs,imm12(rd)
将寄存器rs
的 32 bits 数据写入地址为rd + imm12
的内存中
C 指针
C 语言强制每个变量都有特定的类型,指针也是。各种类型的操作是否合法,由编译器说了算,
代码对比
GPIO
开始地址是0x02000000
PB_CF0
偏移地址是0x30
PB_CF0
里面相应位设置为0001
代表输出
PB_DAT
偏移地址是0x40
mipsasm
# File: blink.s
lui a0,0x2000 # 寄存器a0保存GPIO地址
addi a1,zero,1 # 寄存器a1保存1,用于对应管脚输出
sw a1,0x30(a0) # PB_CF0 的地址里面写入 1,代表 PB0 管脚做输出
loop:
xori a1,a1,1 # 异或,用于电平翻转
sw a1,0x40(a0) # 输出a1的值,1是高电平,0是低电平
lui a2,0x3f00 # 寄存器a2用来延时计数
delay: # 延时标签
addi a2,a2,-1 # 计数减1
bne a2,zero,delay # 计数变0之前一直循环一直延时
j loop # 延时结束,改变输出电平
c
// File: c_blink.c
void main(void) {
unsigned int *PB_CFG0 = (unsigned int *)0x2000030; // 地址用 uint保存,原因下面解释。
unsigned int *PB_DATA = (unsigned int *)0x2000040;
*PB_CFG0 = 1; // PB0 管脚用于输出
int state = 1; // 用于电平翻转
while (1) {
state = state ^ 1; // 异或
*PB_DATA = state; // PB0 输出值state值
for (int count = 0x3f00000; count != 0; count--)
; // 延时
}
}
地址的算法
C 指针地址的的加减的值,是根据指针类型来的,比如int
指针加一,地址加4,而char
指针加一,地址加1
c
unsigned int *base, *neighbor;
base = (unsigned int *)0x2000030; // PB_CFG0
neighbor = base + 1; // 0x2000034, PB_CFG1
这就解释了为什么 GPIO
的地址用 uint
类型保存:因为GPIO
中寄存器是32位的,使用uint
类型,可以在指针加减中切换到各个寄存器。
进入奇怪的牛角尖
承接上文
之前我有一个错误理解:riscv 是用的64位地址,那么地址的数据类型应该是long类型的啊,所以地址应该是(unsigned long *)0x20000
才对。
现在明白,指针用的类型,指的是地址所保存的数据的类型,而不是指针地址这个数字的类型。我之前想的,是 (unsigned long)0x2000
。
volatile
这个地址内的数据只有本程序在修改,那么就不用;反之,数据受其他因素影响,那么就用。
GPIO 结构 C 语言实现
c
struct gpio {
unsigned int cfg[4]; // 0x0, 0x4, 0x8, 0xC
unsigned int data; // 0x10
unsigned int drv[4]; // 0x14, 0x18, 0x1C, 0x20
unsigned int pull[2]; // 0x24, 0x28
};
volatile struct gpio *pb = (struct gpio *)0x2000030;
pb->cfg[0] = ...