S5PV210 | 裸机汇编LED流水灯实验
S5PV210 | 裸机汇编LED流水灯实验
开发板:
1.原理图
上图中,当按下POWER
键后,VDD_5V
和VDD_IO
会产生5V
和3.3V
的电压,其中D26
无须GPIO
控制,为常亮状态,即我们所说的电源指示灯,D[22:25]
对应的GPIO
口如下:
LED指示灯 | GPIO口 | 编号 | 动作 |
---|---|---|---|
D22 | GPJ_3 | LED1 | 1:灭,0:亮 |
D23 | GPJ0_4 | LED2 | 1:灭,0:亮 |
D24 | GPJ0_5 | LED3 | 1:灭,0:亮 |
D25 | GPD0_1 | LED4 | 1:灭,0:亮 |
对应的
GPIO
口输出低电平,点亮LED
;反之,熄灭LED
灯;
2.Datasheet相关
1.S5PV210 RISC微处理器用户手册: S5PV210_UM_REV1.1.pdf 获取方式:可在CSDN搜索下载,也可以@大飞歌获取 2.应用手册(内部ROM启动): S5PV210_iROM_ApplicationNote_Preliminary_20091126.pdf 获取方式:可网路搜索下载,也可以@大飞歌获取 中文文档地址:https://blog.csdn.net/I_feige/article/details/104848609 3.底板电路原理图: x210bv3s.pdf 下载链接:https://download.csdn.net/download/i_feige/11877902
控制LED GPIO的寄存器设置详细参见以下章节(S5PV210_UM_REV1.1.pdf):
V210_ Book cover errata section 01_ overview section 02_ system section 03_ bus section 04_ interupt section 05_ memory section 06_ dma section 07_ timer section 08_ connectivity _ storage section 09_ mutimedia section 10_ audio _ etc section 11_ securty section 12_ etc 2.2.7 PORT GROUP GPD0 CONTROL REGISTER 2.2.7.1 Port Group GPD0 Control Register ( GPD0CON , R / W , Address 0xE020_00A0) 2.2.7.2 Port Group GPD0 Control Register ( GPD0DAT , R / W , Address 0xE020_00A4) 2.2.7.3 Port Group GPD0 Control Register ( GPD0PUD , R / W , Address 0xE020_00A8) 2.2.7.4 Port Group GPD0 Control Register ( GPD0DRV , R / W , Address 0xE020_00AC) 2.2.7.5 Port Group GPD0 Control Register ( GPD0CONPDN , R / W , Address 0xE020_00B0) 2.2.7.6 Port Group GPD0 Control Register ( GPD0PUDPDN , R / W , Address 0xE020_00B4) 2.2.20 PORT GROUP GPJ0 CONTROL REGISTER 2.2.20.1 Port Group GPJ0 Control Register ( GPJ0CON , R / W , Address 0xE020_0240) 2.2.20.2 Port Group GPJ0 Control Register ( GPJ0DAT , R / W , Address 0xE020_0244) 2.2.20.3 Port Group GPJ0 Control Register ( GPJ0PUD , R / W , Address 0xE020_0248) 2.2.20.4 Port Group GPJ0 Control Register ( GPJ0DRV , R / W , Address 0xE020_024C) 2.2.20.5 Port Group GPJ0 Control Register ( GPJ0CONPDN , R / W , Address 0xE020_0250) 2.2.20.6 Port Group GPJ0 Control Register ( GPJ0PUDPDN , R / W , Address 0xE020_0254)
GPD0
控制寄存器组的相关信息(部分摘取如下):
2.2.7 PORT GROUP GPD0 CONTROL REGISTER
有六个控制寄存器,分别是 GPD0CON
、GPD0DAT
、GPD0PUD
、GPD0DRV
、GPD0CONPDN
和
端口组 GPD0
控制寄存器中的 GPD0PUDPDN
。
2.2.7.1 端口组 GPD0
控制寄存器 (GPD0CON
, R/W, Address = 0xE020_00A0
)
GPD0CON | Bit | Description | Initial State |
---|---|---|---|
GPD0CON[3] | [15:12] | 0000 = Input 0001 = Output 0010 = TOUT_3 0011 ~ 1110 = Reserved 1111 = GPD0_INT[3] | 0000 |
GPD0CON[2] | [11:8] | 0000 = Input 0001 = Output 0010 = TOUT_2 0011 ~ 1110 = Reserved 1111 = GPD0_INT[2] | 0000 |
GPD0CON[1] | [7:4] | 0000 = Input 0001 = Output 0010 = TOUT_1 0011 ~ 1110 = Reserved 1111 = GPD0_INT[1] | 0000 |
GPD0CON[0] | [3:0] | 0000 = Input 0001 = Output 0010 = TOUT_0 0011 ~ 1110 = Reserved 1111 = GPD0_INT[0] | 0000 |
2.2.7.2 端口组 GPD0
数据映射寄存器 (GPD0DAT
, R/W, Address = 0xE020_00A4
)
GPD0DAT | Bit | Description | Initial State |
---|---|---|---|
GPD0DAT[3:0] | [3:0] | 当端口被配置为输入端口时,对应的位是引脚状态。 当端口配置为输出端口时,引脚状态与对应位相同。 当端口被配置为功能引脚时,将读取未定义的值。 | 0000 |
2.2.7.3 端口组 GPD0
上、下拉配置寄存器 (GPD0PUD
, R/W, Address = 0xE020_00A8
)
GPD0PUD | Bit | Description | Initial State |
---|---|---|---|
GPD0PUD[n] | [2n+1:2n] n=0~3 | 00 = 上拉/下拉禁用 01 = 下拉启用 10 = 上拉启用 11 = 保留 | 0x0055 |
2.2.7.4 端口组 GPD0
驱动强度配置寄存器 (GPD0DRV
, R/W, Address = 0xE020_00AC
)
GPD0DRV | Bit | Description | Initial State |
---|---|---|---|
GPD0DRV[n] | [2n+1:2n] n=0~3 | 00 = 1x 10 = 2x 01 = 3x 11 = 4x | 0x0000 |
2.2.7.5 端口组 GPD0
低功耗模式配置寄存器 (GPD0CONPDN
, R/W, Address = 0xE020_00B0
)
GPD0CONPDN | Bit | Description | Initial State |
---|---|---|---|
GPD0[n] | [2n+1:2n] n=0~3 | 00 = Output 0 01 = Output 1 10 = Input 11 = Previous state | 0x00 |
2.2.7.6 端口组 GPD0
低功耗模式上拉/下拉寄存器 (GPD0PUDPDN
, R/W, Address = 0xE020_00B4
)
GPD0PUDPDN | Bit | Description | Initial State |
---|---|---|---|
GPD0[n] | [2n+1:2n] n=0~3 | 00 = 上拉/下拉禁用 01 = 下拉启用 10 = 上拉启用 11 = 保留 | 0x00 |
例如设置GPD0_1
IO
口为输出模式,拉高或者拉低(汇编语言实现):
#define GPD0CON 0xE02000A0 #define GPD0DAT 0xE02000A4 /* 初始化GPIO口(配置为输出模式),下面是比较规范的一种写法,也可参考代码实现(流水灯)相关部分 */ ldr r0,=GPD0CON //r0=0xE02000A0 ldr r1,[r0] //将r0地址处的数据读出,保存到r1中(零偏移) orr r1,r1,#0x0010 //设置r1的第4位(置1),其他位保持不变[7:4]->0001=Output str r1,[r0] //将r1中的内容传输到r0中数指定的地址内存中去 /* 点亮LED4,GPIO口输出低电平 */ ldr r0,=GPD0DAT //r0=0xE02000A4 ldr r1,[r0] //将r0地址处的数据读出,保存到r1中(零偏移) bic r1,r1,#0x0002 //清除r1的第1位(置0),其他位保持不变[1] str r1,[r0] //将r1中的内容传输到r0中数指定的地址内存中去 /* 熄灭LED4,GPIO口输出高电平 */ ldr r0,=GPD0DAT //r0=0xE02000A4 ldr r1,[r0] //将r0地址处的数据读出,保存到r1中(零偏移) orr r1,r1,#0x0002 //设置r1的第1位(置1),其他位保持不变[1] str r1,[r0] //将r1中的内容传输到r0中数指定的地址内存中去
3.代码
3-1.代码实现(流水灯,仅作演示)
/******************************************************* * > File Name: start.S * > Author: fly * > Create Time: 2020年07月17日 星期五 07时56分19秒 ******************************************************/ /*===================================================== * 汇编点亮led灯:对应GPIO口输出低电平,点亮LED * D22->GPJ0_3 * D23->GPJ0_4 * D24->GPJ0_5 * D25->PWMOUT1/GPD0_1 *====================================================*/ #define GPD0CON 0xE02000A0 #define GPD0DAT 0xE02000A4 #define GPD0PUD 0xE02000A8 #define GPJ0CON 0xE0200240 #define GPJ0DAT 0xE0200244 #define GPJ0PUD 0xE0200248 #define PS_HOLD_CONTORL 0xE010E81C #define WTCON 0xE2700000 #define SVC_STACK 0xD0037D80 //#define CONFIG_SYS_ICACHE_OFF 1 .global _start _start: //给5v电源置锁 //LDR指令:从内存中将1个32位的字读取到目标寄存器中 //STR指令:将1个32位的字数据写入到指令中指定的内存单元中 //ORR指令:逻辑或操作指令 //BIC指令:位清除指令 //MOV指令:数据传送 ldr r0,=PS_HOLD_CONTORL //r0=0xE010E81C ldr r1,[r0] //将r0地址处的数据读出,保存到r1中(零偏移) orr r1,r1,#0x300 //设置r1的第8、9位,其他位保持不变 orr r1,r1,#0x1 //设置r1的第1位,其他位保持不变 str r1,[r0] //将r1中的内容传输到r0中数指定的地址内存中去 //关看门狗 ldr r0, =WTCON mov r1, #0 //将立即数0传输到r1处 str r1, [r0] //开/关iCache // MRC指令:从协处理器寄存器传数据到ARM寄存器 // MCR指令:从ARM寄存器传数据到协处理器寄存器 mrc p15, 0, r0, c1, c0, 0 #ifdef CONFIG_SYS_ICACHE_OFF bic r0, r0, #0x00001000 @ clear bit 12 (I) I-Cache #else orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache #endif mcr p15, 0, r0, c1, c0, 0 //设置栈,以便调用c函数 ldr sp, =SVC_STACK led_init: /* LED初始化 */ //把GPIO设置输出模式 ldr r0,=0x11111111 ldr r1,=GPJ0CON str r0, [r1] //把GPJ0所有的IO设置为输出模式 ldr r0,=0x00000010 ldr r1,=GPD0CON str r0,[r1] //把GPD0_1设置为输出模式 led_run: /* LED流水灯 */ // 第1步:点亮LED1,其他熄灭 ldr r0, =~(1<<3) //r0=0xFFFFFFF7 ldr r1, =GPJ0DAT //r1=0xE0200244 str r0, [r1] //熄灭LED4 ldr r0, =~(0<<1) //r0=0xFFFFFFFF ldr r1, = GPD0DAT str r0, [r1] bl delay // 第2步:点亮LED2,其他熄灭 ldr r0, =~(1<<4) //r0=0xFFFFFFEF ldr r1, =GPJ0DAT str r0, [r1] bl delay // 第3步:点亮LED3,其他熄灭 ldr r0, =~(1<<5) //r0=0xFFFFFFDF ldr r1, =GPJ0DAT str r0, [r1] bl delay //熄灭LED3/4/5,点亮LED4 ldr r0, = ((1<<3)|(1<<4)|(1<<5)) ldr r1, =GPJ0DAT str r0, [r1] ldr r0, =~(1<<1) //r0=0xFFFFFFFD ldr r1, = GPD0DAT str r0, [r1] bl delay bl led_run half: b half /* 延时函数:delay*/ delay: ldr r2,=9000000 ldr r3,=0x0 delay_loop: //SUB指令:从寄存器Rn中减去shifter_operand表示的数值, //并将结果保存在目标寄存器Rd中,并根据指令的执行结果 //设置CPSR中的相应标志位 //SUB {<cond>} {s} <Rd>,<Rn>,<shifter_operand> sub r2,r2,#1 //r2 = r2 - 1 //CMP指令:使用寄存器Rn的值减去shifter_operand的值, //根据操作的结果更新CPSR中相应的条件标志位,以便后面 //的指令根据相应的条件标志位来判断是否执行 //CMP {<cond>} <Rn>,<shifter_operand> cmp r2, r3 bne delay_loop mov pc,lr
配套编译Makefile文件:
# 将所有的.o文件链接成.elf文件,“-Ttext 0x0” # 表示程序的运行地址是0x0,由于目前编写的是位置 # 无关码,可以在任一地址运行 # 将elf文件抽取为可在开发板上运行的bin文件 # 将elf文件反汇编保存在dis文件中,调试程序会用 # 添加文件头 # 编译器版本:arm-2009q3 .PHONY: all clean tools CROSS ?= arm-linux- NAME := LED LD := $(CROSS)ld OC := $(CROSS)objcopy OD := $(CROSS)objdump CC := $(CROSS)gcc MK := ../../tools/mk_image/mkv210_image all:$(NAME).bin $(NAME).bin : start.o $(LD) -Ttext 0x0 -o $(NAME).elf $^ $(OC) -O binary $(NAME).elf $(NAME).bin $(OD) -D $(NAME).elf > $(NAME)_elf.dis $(MK) $(NAME).bin # 将当前目录下存在的汇编文件及C文件编译成.o文件 %.o : %.S $(CC) -o $@ $< -c %.o : %.c $(CC) -o $@ $< -c clean: $(RM) *.o *.elf *.bin *.dis *.sd tools: make -C ../../tools/mk_image/
arm-linux-ld
:一个链接程序工具,其作用主要是将汇编过的多个二进制文件进行链接,成为一个可执行的二进制文件,这个命令的选项有好多,具体用到的时候大家可以使用--help
选项来查看具体的选项用法。
arm-linux-ld -Ttext 0x0 -o led.elf
$^
:这句话是将所有的依赖文件连接成ELF
格式文件,在连接的过程中,-Ttext 0x0
这个选项告诉连接器我的这段程序需要被加载到RAM
的0x00000000
地址处执行。所以在连接的时候第一条语句的连接地址就是0x00000000
,第二条语句就是跟在其后面。有很多人都议论连接地址和运行地址这个怎么说的都有。运行地址可以等于连接地址,还可以认为运行地址是pc
指针指向的地址,就是正在执行指令的地址。只要理解了这个概念就可以了。
arm-linux-objcopy
:被用来复制一个目标文件的内容到另一个文件中.此选项可以进行格式的转换.在实际编程的,用的最多的就是将
ELF
格式的可执行文件转换为二进制文件
arm-linux-objdump
:常用来显示二进制文件信息,常用来查看反汇编代码
编译:
fly@fly-vm:01-led_s$ make clean rm -f *.o *.elf *.bin *.dis *.sd *.BIN fly@fly-vm:01-led_s$ ls Makefile start.S fly@fly-vm:01-led_s$ make arm-linux-gcc -o start.o start.S -c arm-linux-ld -Ttext 0x0 -o LED.elf start.o arm-linux-objcopy -O binary LED.elf LED.bin arm-linux-objdump -D LED.elf > LED_elf.dis ../../tools/mk_image/mkv210_image LED.bin the checksum 0x000060EB for 228bytes, output: LED.bin.SD.BIN fly@fly-vm:01-led_s$ ls LED.bin LED.bin.SD.BIN LED.elf LED_elf.dis Makefile start.o start.S
3-2.工具mkv210_image
代码
/******************************************************************* * > File Name: mkv210_image.c * > Author: fly * > Create Time: 2021-06-17 4/24 12:03:22 +0800 * > Note: 将USB启动时使用的BIN文件制作得到SD启动的Image * 计算校验和,添加16字节文件头,校验和写入第8字节处 *================================================================*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define ERR_STR strerror(errno) #define SPL_HEADER_SIZE (16) #define SPL_HEADER "@S5PV210$$$$****" #define IMG_SIZE (16*1204) #define FILE_PATH_LEN_MAX (256) char *mk_getCheckSumFile(char *binName) { static char checkSumFileName[FILE_PATH_LEN_MAX] = {0}; //snprintf(checkSumFileName, FILE_PATH_LEN_MAX, "%s%s", "sd.", binName); snprintf(checkSumFileName, FILE_PATH_LEN_MAX, "%s%s", binName, ".SD.BIN"); return (char*)checkSumFileName; } long mk_getFileLen(FILE* fp) { static long fileLen = 0; fseek(fp, 0L, SEEK_END); fileLen = ftell(fp); fseek(fp, 0L, SEEK_SET); return fileLen; } int main(int argc, char* argv[]) { FILE* fps, *fpd; long nbytes, fileLen; unsigned int checksum, count; char *BUF = NULL, *pBUF = NULL; int i; if(argc != 2){ printf("Usage: %s <bin-file>\n", argv[0]);exit(EXIT_FAILURE); } /* 打开源BIN文件 */ fps = fopen(argv[1], "rb"); if (fps == NULL){ printf("fopen %s err: %s\n", argv[1], ERR_STR); exit(EXIT_FAILURE); } /* 创建目标BIN文件 */ fpd = fopen(mk_getCheckSumFile(argv[1]), "w+b"); if (fpd == NULL){ printf("fopen %s err: %s\n", mk_getCheckSumFile(argv[1]), ERR_STR); fclose(fps);exit(EXIT_FAILURE); } /* 获取源文件大小 */ fileLen = mk_getFileLen(fps); if(fileLen < (IMG_SIZE - SPL_HEADER_SIZE)){ count = fileLen; }else{ count = IMG_SIZE - SPL_HEADER_SIZE; } BUF = (char *)malloc(IMG_SIZE);/* malloc 16KB BUF */ if (BUF == NULL){ printf("malloc err: %s\n", ERR_STR); fclose(fps);fclose(fpd); exit(EXIT_FAILURE); } memcpy(&BUF[0], SPL_HEADER, SPL_HEADER_SIZE); nbytes = fread(BUF+SPL_HEADER_SIZE, 1, count, fps); /* 计算文件检验和 */ pBUF = BUF + SPL_HEADER_SIZE; for(i = 0, checksum = 0; i< IMG_SIZE - SPL_HEADER_SIZE; i++) { checksum += (0x000000FF) & *pBUF++; } pBUF = BUF + 8; *((unsigned int *)pBUF) = checksum; /* 将校验和源文件写入目标文件 */ fwrite(BUF, 1, IMG_SIZE, fpd); printf("the checksum 0x%08X for %ldbytes, output: %s\n", \ checksum, fileLen, mk_getCheckSumFile(argv[1])); free(BUF); fclose(fps); fclose(fpd); return 0; }
配套Makefile
.PHONY: all clean CC = gcc SRC = ${wildcard *.c} BIN = ${patsubst %.c, %, $(SRC)} CFLAGS = -g -Wall RM = rm -rf PRJ_PATH= $(shell pwd) all:$(BIN) $(BIN):%:%.c @echo [CC] $@ @$(CC) -o $@ $^ $(CFALGS) -D_PRJ_PATH_='"$(PRJ_PATH)"' clean: $(RM) a.out $(BIN) .*.*.sw? *.sd test: @echo $(PRJ_PATH) .PHONY: clean test
4.运行
SD卡启动
1.OM5
开关打到开发板靠下侧(选择启动方式):
2.将BIN文件下载到SD
卡
2-1.Windos
下使用x210_Fusing_Tool.exe下载(注意使用管理员模式打开)
清理
x210_Fusing_Tool.exe
文件列表,进入目录:C:\Users\fly\AppData\Roaming\SDFusing
,然后删除文件config.ini
;
2-2.Linux下载BIN文件到SD卡脚本命令:
#!/bin/sh #命令行参数检测 if [ -n "$1" ];then echo "Source file: $1" else echo "Usage:$0 <source_file>" exit -1 fi #使用超级用户权限把210.bin读取进来,经过处理再输出到设备sdb上, #跳过该设备的第一个block(每个block的大小为512B) sudo dd iflag=dsync oflag=dsync if=$1 of=/dev/sdb seek=1
另一种更具体的脚本写法:
########################################################### # File Name: s5pv210-irom-sd.sh # Author: fly # Created Time: 2021-06-27 0/25 14:51:59 +0800 ########################################################### #!/bin/bash # s5pv210 irom sd boot fusing tool # display usage message USAGE() { echo Usage: $(basename "$0") '<device> <bootloader>' echo ' device = disk device name of for SD card.' echo ' bootloader = /path/to/*.bin.sd' echo 'e.g. '$(basename "$0")' /dev/sdb boot.bin.sd' } [ `id -u` == 0 ] || { echo "you must be root user"; exit 1;} [ -z "$1" -o -z "$2" ] && { USAGE; exit 1; } dev="$1" xboot="$2" # validate parameters [ -b "${dev}" ] || { echo "${dev} is not a valid block device"; exit 1; } [ X"${dev}" = X"${dev%%[0-9]}" ] || { echo "${dev} is a partition, please use device, perhaps ${dev%%[0-9]}"; exit 1; } [ -f ${xboot} ] || { echo "${xboot} is not a bootloader binary file."; exit 1; } # copy the full bootloader image to block device dd if="${xboot}" of="${dev}" bs=512 seek=1 conv=sync sync; sync; sync; echo "^_^ The image is fused successfully"
3.将SD卡插入SD2通道,上电即可查看程序运行状况
接通电源,长按
POWER
键;可使用串口工具连接UART2
,会有打印调试信息输出;
5.参考
1.书籍:ARM嵌入式体系结构与接口技术(Cortex-A8版)(ARM Embedded Architecture and Interface Technology)
2.书籍:嵌入式LinuxC语言程序设计基础教程(C Language Programming of Embedded Linux)
6.S5PV210_UM_REV1.1.pdf:https://download.csdn.net/download/han1202012/8342745?utm_source=iteye_new
7.S5PV210_iROM_ApplicationNote_Preliminary_20091126.pdf:https://download.csdn.net/download/q171884957/8561553
8.S5PV210_iROM_ApplicationNote_Preliminary_20091126.pdf(中文版本):https://blog.csdn.net/I_feige/article/details/104848609
9.x210_Fusing_Tool.exe下载地址:https://download.csdn.net/download/i_feige/11937635
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
2019-05-11 二路Sensor热拔插问题