【裸机驱动LED】使用汇编代码驱动LED(二)—— 汇编代码编写篇

news2024/11/25 16:36:25

上一部分我们已经整理出了所需寄存器的基地址、初始值,因为我们要给寄存器赋值,其实就是向指定地址写入内容。下面所有用到的基地址和初始化值都在上一篇总结好了。

【裸机驱动LED】使用汇编代码驱动LED(一)—— 寄存器解析篇_仲夏夜之梦~的博客-CSDN博客【裸机驱动LED】使用汇编代码驱动LED(一)—— 寄存器解析篇https://blog.csdn.net/challenglistic/article/details/131047800?spm=1001.2014.3001.5501


目录

一、编写汇编代码

1、初始化时钟源

2、设置 IO 复用

3、初始化GPIO(设置电气属性)

4、GPIO 输出

二、编译汇编代码

1、编译生成 .o 文件

2、链接 .o 文件(确定链接地址 / 运行地址)

3、格式转换

4、反汇编

三、烧写到SD卡

四、选择运行地址为 0x87800000 的原因

五、完整汇编代码和Makefile文件

1、汇编代码

2、Makefile文件


一、编写汇编代码

汇编代码对应的是 .s 文件,每个 .s 文件都是以 _start 开头,所以汇编代码的起始模板为:

.global _start

_start:
    /* 开始编写汇编代码 */

1、初始化时钟源

上一篇《初始化时钟源》的末尾已经列举了哪些寄存器是需要被初始化的,以及初始化的值是多少,全都一一列举了。一共有 7 个时钟源,每个时钟源的基地址之间相差 4 个字节,初始化的值都是 0xFFFFFFFF。下面给出两种写法

写法一:循环初始化

.global _start 

/*
 * 描述:  _start函数,程序从此函数开始执行此函数完成时钟使能、
 *		  GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
 */
_start:
    /* 时钟源初始化 */
    ldr r1, =0x020C4068  @ 保存 CCGR0 寄存器基地址到寄存器 r1
    ldr r2, =0xFFFFFFFF  @ 保存初始值到寄存器 r2
    mov r3, #7           @ r3 = 7, 七个时钟源,初始化七次
    bl init              @ 跳转到 init,lr 寄存器会保存下一条指令的地址

init:
    cmp r3, #0             @ 判断 r3 寄存器是否为 0
    moveq pc, lr           @ lr 寄存器保存了回去的地址,如果 r3 == 0,说明已经初始化了7次
    strgt r2, [r1], #4     @ 如果 r3 大于0,将 r2 寄存器的内容保存到 r1,然后 r1 自增4
    sub r3, r3, #1         @ r3 寄存器自减
    b init

写法二:暴力初始化

.global _start 

/*
 * 描述:  _start函数,程序从此函数开始执行此函数完成时钟使能、
 *		  GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
 */
_start:
    /* 1. 使能所有时钟 */
    ldr r1, =0X020C4068 	/* CCGR0 */
	ldr r2, =0XFFFFFFFF  
	str r2, [r1]		
	
	ldr r1, =0X020C406C  	/* CCGR1 */
	str r2, [r1]

	ldr r1, =0X020C4070  	/* CCGR2 */
	str r2, [r1]
	
	ldr r1, =0X020C4074  	/* CCGR3 */
	str r2, [r1]
	
	ldr r1, =0X020C4078  	/* CCGR4 */
	str r2, [r1]
	
	ldr r1, =0X020C407C  	/* CCGR5 */
	str r2, [r1]
	
	ldr r1, =0X020C4080  	/* CCGR6 */
	str r2, [r1]

2、设置 IO 复用

基地址和初始化值:

/* 2. 设置IO复用 */
ldr r1, =0x20E0068
ldr r2, =0x5        @ 指定为 GPIO 功能
str r2, [r1]

3、初始化GPIO(设置电气属性)

基地址和初始化值:

/* 3、配置GPIO1_IO03的IO属性	
 *bit 16:0 HYS关闭
 *bit [15:14]: 00 默认下拉
 *bit [13]: 0 keeper功能
 *bit [12]: 1 pull/keeper使能
 *bit [11]: 0 关闭开路输出
 *bit [7:6]: 10 速度100Mhz
 *bit [5:3]: 110 R0/6驱动能力
 *bit [0]: 0 低转换率
 */
ldr r1, =0x20E02F4
ldr r2, =0x10B0
str r2, [r1]

4、GPIO 输出

基地址和初始化值:

/* 4.GPIO1的第3引脚设为输出 */
ldr r1, =0x209C004
ldr r2, =0x00000008
str r2, [r1]

/* 5.GPIO1的第3引脚输出低电平 */
ldr r1, =0x209C000  
ldr r2, =0
str r2, [r1]

二、编译汇编代码

这里我们使用 Makefile 工具来进行交叉编译,所以你的虚拟机上需要事先装好交叉编译工具链,交叉编译的步骤:

  • 源文件 .s 编译生成 .o 文件
  • 链接 .o 文件
  • 转换格式(转换成二进制文件)
  • 反汇编

1、编译生成 .o 文件

led.bin: led.s
    arm-linux-gnueabihf-gcc -c -o led.o $^
  • arm-linux-gnueabihf-gcc:表示arm环境下的编译器
  • -c:表示到汇编阶段就停下来,此时会生成 .o 文件
  • -o:表示输出文件的名字
  • $^:makefile内置变量,表示依赖文件,即冒号右边的所有内容(led.s)

2、链接 .o 文件(确定链接地址 / 运行地址)

实际上我们可能生成很多个 .o 文件,我们将这些 .o 文件链接到内存中的指定位置,这里确定的是最终可执行文件的运行地址

  • 存储地址:就是可执 行文件存储在哪里(可随意选择)
  • 运行地址:代码运行的时候所处的地址

比如二进制代码一开始保存在SD卡的0x08000000,加载到内存以后放在0x87800000,这里的0x08000000就是存储地址,0x87800000就是运行地址。(允许存储地址和运行地址相同,即一开始就保存到内存)选择 0x87800000 的原因参考本文的第三部分。

led.bin: led.s
    arm-linux-gnueabihf-gcc -c -o led.o $^
    arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
  •  -Ttext:指定的就是链接地址
  • -o:生成 .elf 文件。保存了足够的系统相关信息使它能支持不同平台上的交叉编译和交叉链接,可移植性很强。既可用于编译链接,也可用于加载执行。

3、格式转换

上面得到的可以看做是一种比较通用的文件,既可以用于编译链接,又可用于程序执行,但是下面我们要把这个文件用于程序执行,生成可以在ARM环境下运行的二进制执行文件。

arm-linux-gnueabihf-objcopy 可以看做是一个格式转换工具,我们将用它将 elf 文件转换成 bin 文件。

led.bin: led.s
    arm-linux-gnueabihf-gcc -c -o led.o $^
    arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
    arm-linux-gnueabihf-objcopy -O binary -S -g led.elf $@
  • -O:指定以什么格式输出,binary 表示以二进制输出
  • -S:表示不要复制源文件中的重定位信息和符号信息
  • -g:表示不要复制调试信息

4、反汇编

其实到上面“格式转换”就可以结束了,执行这一步的目的是,方便后面调试,经过反汇编,我们可以通过汇编代码来调试代码,比如查看函数实际堆栈调用情况、实际初始化顺序是否正常等。

前面也说到,elf 文件包含了足够的源文件信息,当然也可以用于这里的反汇编。

led.bin: led.s
    arm-linux-gnueabihf-gcc -c -o led.o $^
    arm-linux-gnueabihf-ld -Ttext 0x87800000 led.o -o led.elf
    arm-linux-gnueabihf-objcopy -O binary -S -g led.elf $@
    arm-linux-gnueabihf-objdump -D led.elf > led.dis

三、烧写到SD卡

这里使用的是正点原子官方的 imxdownload 工具,注意,就算生成了 bin 文件也不能直接放到SD卡里,还需要添加一些头部信息(包含初始化DDR等操作)

首先,要把imxdownload 添加到当前工作目录下,01、例程源码—01、例程源码—01、裸机例程 —01_leds 就可以找到 imxdownload

其次,将 led.bin 烧写到SD卡

./imxdownload led.bin /dev/sdb   # 将led.bin烧写到 /dev/sdb

最后,将拨码开关拨到指定位置,插入SD卡,给开发板上电。如果你发现,过了一会,开发板亮红灯说明运行成功。(本人的是mini版,LED0和LED1是同一个;alpha版的LED0 和LED1是分开的)

 

四、选择运行地址为 0x87800000 的原因

运行程序时,一般都会先把程序拷贝到内存,然后CPU再从内存中逐指令读取。内存分为内部RAM 和 外部DDR。

  • RAM:CPU内部的一段可用内存,imx6ull 内部RAM的大小为 128K(0X900000~0X91FFFF)
  • DDR:CPU外的存储器,封装在SOC 中,DDR的大小为 256M 或者 512M(虽然看着有2048,但是受到总线约束,CPU可访问的大小是256M)

因此,这里我们选择DDR,0x87800000 就在DDR内存范围内,这样也方便后续的 uboot 移植。

五、完整汇编代码和Makefile文件

1、汇编代码

.global _start 

/*
 * 描述:  _start函数,程序从此函数开始执行此函数完成时钟使能、
 *		  GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
 */
_start:
    /* 1.时钟源初始化 */
    ldr r1, =0x020C4068  @ 保存 CCGR0 寄存器基地址到寄存器 r1
    ldr r2, =0xFFFFFFFF  @ 保存初始值到寄存器 r2
    mov r3, #7
    bl init              @ 跳转到 init,LR 寄存器会被设置

    /* 2.设置IO复用 */
    ldr r1, =0x20E0068
    ldr r2, =0x5
    str r2, [r1]

    /* 3.初始化GPIO */
    ldr r1, =0x20E02F4
    ldr r2, =0x10B0
    str r2, [r1]

    /* 4.GPIO1的第3引脚设为输出 */
    ldr r1, =0x209C004
    ldr r2, =0x00000008
    str r2, [r1]

    /* 5.GPIO1的第3引脚输出低电平 */
    ldr r1, =0x209C000  
    ldr r2, =0
    str r2, [r1]
/*
 * 描述:	loop死循环
 */
loop:
	b loop 	

init:
    cmp r3, #0             @ 判断 r3 寄存器是否为 0
    moveq pc, lr           @ lr 寄存器保存了回去的地址
    strgt r2, [r1], #4     @ 将 r2 寄存器的内容保存到 r1,然后 r1 自增4
    sub r3, r3, #1         @ r3 寄存器自减
    b init

2、Makefile文件

如果将交叉编译工具链的根路径添加到了环境变量,那么这里就无需指定交叉编译工具链的根路径。

TOOLCHAIN_PATH		:= /home/pigeon/workspace/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
CC					:= $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-gcc
LD					:= $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-ld
OBJCOPY				:= $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-objcopy
OBJDUMP				:= $(TOOLCHAIN_PATH)/arm-linux-gnueabihf-objdump

led.bin: led.s
	$(CC) -c -o led.o $^
	$(LD) -Ttext 0X87800000 led.o -o led.elf
	$(OBJCOPY) -O binary -S -g led.elf $@
	$(OBJDUMP) -D led.elf > led.dis
	
.PHONY:clean
clean:
	rm -rf *.o *.elf *.dis *.bin

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/616400.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nginx+Tomcat负载均衡,动静分离

文章目录 一.Nginx应用1.1Nginx负载均衡实现原理1.2Nginx动静分离实现原理 二.NginxTomcat负载均衡、动静分离(七层实例) 一.Nginx应用 Nginx是一款非常优秀的HTTP服务软件 支持高达50000个并发连接数的响应拥有强大的静态资源处理能力运行稳定内存、C…

spring.factories

Spring Boot 如何管理第三方Bean 首先抛出一个问题:如果想要被Spring容器管理的Bean的路径不再Spring Boot 的包扫描路径下,怎么办呢?也就是如何去加载第三方的Bean 呢?换句话说:在 Spring Boot 项目中,如果你想要被 …

使用 PicX 创建免费的图床神器

写博客文章时,图片的上传和存放是一个问题,我们也许会在不同的平台发布同一篇文章,这样一来每个平台都要上传图片。为了解决这些问题,做法是把图片统一上传到一个在线的第三方静态资源库中,我们把这个资源库称为图床。…

【博学谷学习记录】超强总结,用心分享丨人工智能 AI项目 ROUGE评估算法简记

目录 ROUGE核心思想评价标准ROUGE-NROUGE-L ROUGE ROUGE的全称是Recall-Oriented Understudy for Gisting Evaluation, 是一种基于召回率指标的评价算法. 核心思想 由多个专家分别生成人工摘要, 构成标准摘要集. 将模型生成的自动摘要和人工摘要做对比, 通过统计两者之间重叠…

C++ 结构体声明(定义)以及不同写法的差异

定义方式总结 在C中,结构体(struct)的定义主要有多种形式,,你还可以在定义结构体时直接初始化它的成员,或者在定义后创建结构体的实例。以下是一些例子: 定义并初始化结构体: str…

gitlab基本操作

1.gitlab 基本操作 git branch // 查看分支 git branch dev //新建dev 分支 git checkout dev //切换到dev 分支修改 **** git status // 查看哪些文件被修改 git add . //修改了文件需要提交添加上去(注意 . 点号) git commit -m “update”…

重温数据结构与算法之A star 算法

文章目录 前言一、原理1.1 网格距离1.2 宽度优先搜索1.3 Dijkstra 算法1.4 最佳优先搜索1.5 A*算法 二、代码实现2.1 伪码2.2 python 实现2.3 可视化 三、优缺点分析3.1 优点3.2 缺点 参考 前言 A*(A-Star)算法是一种静态路网中求解最短路径有效的直接搜索方法,也是…

uniapp(三) 之 表单提交

更换UI库 经过我昨天仔细看了下ThorUI,里面有个会员组件,好像有用的组件都是会员组件一样,作为一个白嫖怪,我决定今天再换一个UI库 引入后,根据组件位置自行调整位置 好了现在使用UNI-UI了 但是我的语法是vue3,官网范…

Hadoop数据仓库的主要特征有哪些?

数据仓库(英语:Data Warehouse,简称数仓、DW),是一个用于存储、分析、报告的数据系统。数据仓库的目的是构建面向分析的集成化数据环境,分析结果为企业提供决策支持(Decision Support)。 数据仓库本身并不“生产”任何数据,其数据…

Nebula分布式集群

2022年9月15日18:47:38文章目录 Nebula1.安装:2.数据模型3.NebulaGraph 架构总览4.基本命令文档4.0 数据类型4.1 spaces图空间CREATE SPACEDROP SPACECLEAR SPACESHOW SPACESDESC SPACE 4.2 Tag4.3 edge4.4 点语句INSERT VERTEXDELETE VERTEXUPDATE VERTEXUPSERT VERTEX 4.5 边…

day11 -- 存储过程+触发器+事物处理

学习内容 什么是存储过程 如何使用存储过程 学习记录 存储过程 经常会有一个完整的操作需要多条语句才能完成。 此外,需要执行的具体语句及其次序也不是固定的,它们可能会根据条件而选择性的执行。 那应该怎么办呢?可以创建存储过程。 存储…

工业镜头分类、相关参数含义

一、工业镜头参数 1、焦距/后焦距 焦距是像方主面到像方焦点的距离。后焦距指光线离开镜头最后一片镜片表面到sensor感光面的距离,如8mm,16mm,25mm等; 焦距的大小决定着视角大小,焦距数值小,视角大&#…

4个月完成职位申请并CSC改派出国|新加坡南洋理工大学访学申请记

由于原访学国家签证被拒,O老师期望能申请手续便捷且容易通过签证的国家,最终我们成功申请到世界名校新加坡南洋理工大学的国家教育研究学院。从获得邀请函、办理CSC改派及派出、顺利签证直至出国等全套手续,仅仅4个月。 O老师背景&#xff1a…

腾讯应用宝 - 微下载

首次接触微下载这个概念,故简单记录一下 产品:微下载配置好了吗? Me: 嗯?什么微下载? 基础认知微下载是什么?微下载在哪里使用?微下载链接获取方式?个性化功能&#xff…

搜索引擎召回策略总结

一、搜索引擎召回策略的方法和注意事项(自己能想到的&待补充) 二、相关资料 同义变换在百度搜索广告中的应用 https://mp.weixin.qq.com/s/ybkbU8p_3jgKuCGdNWeG8w 2020年kdd Facebook搜索向量召回读后感【小红书MXie】 https://zhuanlan.zhihu.com/p/184920498 美团搜…

一键安装 HomebrewCN

一键安装 HomebrewCN Brew介绍Homebrew 能干什么?Homebrew自身如何使用安装Homebrew国内源安装 Homebrew(github源) Brew介绍 macOS 和 Linux 缺失软件包的管理器 Homebrew 能干什么? 使用 Homebrew 安装 Mac(或Linux)没有预装但你需要的东西。 Ho…

python 第三章 基础语句

系列文章目录 第一章 初识python 第二章 变量 文章目录 3.1 输出格式化输出格式化符号格式化字符串扩展f-格式化字符串转义字符结束符 3.2 输入3.3 数据类型转换转换数据类型的函数 3.4 PyCharm交互式开发3.5 运算符运算符的分类算数运算符赋值运算符复合赋值运算符比较运算符逻…

java设计模式之:工厂模式详解(简单工厂+工厂方法+抽象工厂)

文章目录 简单工厂实现应用场景 工厂方法实现适用场景缺点 抽象工厂实现缺点 在面向对象编程中,创建对象实例最常用的方式就是通过 new 操作符构造一个对象实例,但在某些情况下,new 操作符直接生成对象会存在一些问题。举例来说,对…

软件测试2023年行情怎么样?仔细讲解!

目录 前言: 普通功能测试人员不建议跳槽 还有一个要求就是要对业务的极致理解 那么产业互联网趋势会导致什么呢? 现在跳槽涨薪需要掌握到什么样的技术呢? 给大家一些跳槽建议 前言: 软件测试是为了发现程序中的错误而执行程序的…

Keil5新建工程

STM32新建工程 1、基于寄存器、基于库函数、基于HAL2、基于标准库的工程3、工程架构4、基于库函数点灯实验 1、基于寄存器、基于库函数、基于HAL 1、基于寄存器:与51单片机开发案方式一样,是用程序直接配置寄存器,来达到我们想要的功能&…