正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-8.1

2024-04-30 06:04

本文主要是介绍正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-8.1,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 前言:

本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。

引用:

正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com

《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》第 8.1 章

《正点原子资料_A盘/02开发板原理图/IMX6ULL_MINI_V2.2(Mini底板原理图).pdf》

  • 资料盘 开发板资料链接: https://pan.baidu.com/s/1j5Jzbdx9i-g0cWIi3wf2XA 提取码:ag1u


正文:

本文是 “正点原子[第二期]Linux之ARM(MX6U)裸机篇--第8.1讲” 的读书笔记。第8.1讲是如何通过汇编设置 I.MX6U 处理器准备C语言运行环境。位接下来使用C语言来开发 led 点灯驱动程序做准备。

0. 使用汇编准备C语言运行环境

第8章介绍了如何使用汇编语言来编写LED灯实验,但是实际开发过程中汇编用的很少,大部分都是C语言开发,汇编只是用来完成C语言环境的初始化。本章我们就来学习如何使用汇编来完成C语言环境的初始化工作,然后从汇编跳转到C语言代码里面去。

1. 设置处理器运行模式为 SVC 特权模式

在第八章的汇编 LED 灯实验中,我们了解了如何使用汇编来编写LED灯驱动,实际工作中是很少用到汇编去编写嵌入式驱动的,毕竟汇编太难,而且写出来也不好理解,大部分情况下都是使用C语言去编写的。只是在开始部分用汇编来初始化一些C语言环境,也就是C语言代码,一般都是进入 main 函数。

所以我们有两部分文件需要做:

  1. 汇编文件
    汇编文件只是用来完成C语言环境的搭建。
  2. C语言文件
    C语言环境就是万策划给你我们业务层代码的,其实就是实际里程要完成的功能。

在第6章C“ortex-A7 处理器运行模式”一节中,已经说过Cortex-A7有 9 种运行模式。这里我们需要设置处理器运行在  SVC 模式,这是因为只有在 SVC 特权模式下,处理器才有权限访问SoC的全部硬件资源,有些SoC的硬件资源处理器在普通用户模式是访问不到的。

在6.3.2小节已经详细的介绍了 Cortex-A7 的 CPSR (Current Program State Register)寄存器,其中 M[4:0] (CPSR的bit[4:0])就是设置处理器运行模式的,参考表 6.3.2.2,如果要将处理器设置为 SVC 模式,那么 M[4:0] 就要等于 0x13。

在7.2.1 小节已经讲过,设置Cortex-A7 处理器的CPSR寄存器这种特殊寄存器,不能使用 LDR, STR 指令,需要使用 MRS, MSR 指令。MRS读取CPSR到的寄存器,MSR将寄存器里的值传送到CPSR。

  1. 首先我们使用 "mrs r0,cpsr" 将CPSR状态寄存的内容读取到R0寄存器
  2. 参考7.2.6 章节的 “ARM汇编逻辑运算指令”,我们使用 BIC 汇编指令来对R0寄存器的低5位进行清零(也就是CPSR寄存器的 M[4:0]),然后使用 ,ORR  汇编指令来对R0 或上 0x13 ,也就是给CPSR寄存器的M[4;0]位赋值为0x13, 表示SVC模式
  3. 最后使用 "msr cpsr,r0" 将r0的值写回到 CPSR 寄存器,此时处理器就进入了SVC模式。

.global _start_start:
/* 进入 SVC 模式 */
mrs r0, cpsr         @读取cpsr到r0
bic r0, r0, #0x1f    @将r0的低5位清零,也就是cpsr的M0~M4
orr r0, r0, #0x13    @r0或上0x13,表示使用svc模式
msr cpsr, r0         @将r0写回cpsr

2. 准备C语言运行环境栈指针,SP(R13)寄存器

通过 'ldr rp, =0x8020_0000' 指令设置 SVC 模式下的 SP 栈指针 = 0x8020_0000,因为正点原子 I.MX6U ALPHA/Mini 开发板上的 DDR3 的地址范围是 0x8000_0000 ~ 0xA000_0000 (512MB)或者 0x8000_0000 ~ 0x9000_0000(256MB),不管是512MB还是256MB版本的,其DDR3起始地址都是 0x8000_0000 。由于 Cortex-A7 的堆栈是向下增长的,所以将 SP 指针这是为 0x8020_0000 ,因此SVC模式下的栈大小是 ,0x8020_0000 - 0x8000_0000 = 0x0020_0000 = 2MB, 2MB 的栈空间已经很大了,如果做裸机开发的话绰绰有余。 

ldr sp, =0x8020_0000    @设置栈指针
b main                  @跳转到main函数

然后使用 'b main' 指令跳转到main函数,main 函数就是C语言代码了。

至此汇编程序部分执行完成,就几行代码,用来设置处理器运行到 SVC 模式,然后初始化 SP 指针,最终跳转到 mian 函数中。

如果有玩过三星的 S3C2440 或者 S5PV210 的话会知道我们在使用 SDRAM 或者 DDR之前必须初始化 SDRAM后者DDR。所以三星的 S3C2440 或者 S5PV210的汇编文件里是一定会有SDRAM或DDR初始化代码的。我们上面编写的 start.s 文件中却没有初始化 DDR3 的代码,但是却将 SVC 模式下的SP指针设置到了 DDR3 的地址范围中,这不会出问题么?肯定不会的,DDR3肯定是要初始化的,但是不需要再 start.s 文件中完成。在9.4.2 小节里面分析 DCD (Device Config Data)数据的时候就已经讲过了,DCD数据包含了 DDR 配置参数,I.MX6U 内部的 boot ROM 会读取 DCD  数据中的 DDR 配置参数然后完成DDR初始化的。

3. 编写Makefile

现在已经写好了 main.c 和 start.s 两个文件,我们编写一个 Makeile 来实现对两个文件的编译,链接,转换为 .bin 格式,并且反编译文件用来检查生成的代码是否符合预期。

在Makefile里使用的命令 'arm-linx-gnueabihf-xx, 在上一节编译汇编 led.s 程序的时候都已经使用过,并且解释了这行命令中的选型的含义。这里我们使用了 Makefile 语法总的 makefile 自动变量 ‘$@’, '$<', '$^' ,这些makefile 自动变量的含义可以查考手册中的“3.4 Makefile 语法”章节。

       arm-linux-gnueabihf-ld -Ttext 0x87800000 $^ -o ledc.elf
        arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
        arm-linux-gnueabihf-objdump -D ledc.elf -m arm > ledc.dis

#objs = main.o start.o
objs = start.o main.o ledc.bin : $(objs)arm-linux-gnueabihf-ld -Ttext 0x87800000 $^ -o ledc.elfarm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@arm-linux-gnueabihf-objdump -D ledc.elf -m arm > ledc.dis%.o : %.carm-linux-gnueabihf-gcc -c -Wall -nostdlib -o $@ $<%.o : %.s arm-linux-gnueabihf-gcc -c -Wall -nostdlib -o $@ $<clean:rm -rf *.o ledc.elf ledc.bin ledc.dis

这里执行 'make' 命令编译之后,需要查看反编译出来的 'ledc.dis' 文件,确认下链接出来的 ledc.elf 文件中 '_start' 符号是不是在文件的最开始,因为 '_start' 是设置准备C语言的运行环境的代码,必须在最开始的时候执行,所以 _start 应该被链接到地址 0x80700000,这个地址也就是我们在上一节已经讨论过了选择的 .bin 文件的加载地址和处理器执行起始地址。

如下图,可以看到反汇编出来的 ledc.dis 文件中,_start 符号没有被链接到 0x80700000 地址位置,这样I.MX6U处理器上电boot ROM上电启动跳转到 0x87800000地址起始地址处开始执行时,执行的就不是准备C语言运行环境的 _start 符号处的指令。这样就会出现错误,因为此时C语言执行环境 SP指针还没有初始化,不能执行C语言函数(C语言函数里有PUSH压栈指令,此时SP指针还没有初始化)。

出现 'clk_enable'符号被链接到文件的起始为止,而 '_start' 没有被链接到文件起始为止的原因是在 Makefile 里链接时 ".o" 文件的列表顺序里先写了 'main.o' 然后是 "start.o",这样main.o 就放在了文件的起始为止。

修改Makefile 文件里 "start.o"和 “main.o”的文件顺序,运行命令'make clean', ‘make ’ 再次编译,重新查看反编译的 ledc.dis 文件,这次可以到到 _start 符号已经被链接到文件的起始位置 0x87800000 地址处,I.MX6U 上电启动跳转到 DDR 地址 0x87800000 处开始执行的时候,第一步执行的命令就是 '_start' 符号处的汇编执行,这行执行配置处理器进入 SVC模式,并且配置 SP 堆栈指针准备好C语言运行环境,然后跳转到main函数,从main函数开始就是C语言了。

4. 实现short_dealy短时延

通过让处理器死循环执行指令的方法(忙等)来实现短时延,正点原子的视频教程里讲到经过正点的原子的测试,在I.MX6U处理器运行在 396MHz 的主频下,处理器循环执行 0x7ff 次循环的时间大概耗时 1ms。所以在正点原子视频教程和文档里通过如下两个函数 'dely_short()' 来让处理器执行指定的循环次数,而'dely_short(0x7ff)' 循环 0x7ff 次处理器的耗时大约是1ms,通过 'delay()' 让处理器等待指定时延的 ms 数,我们就可以实现最简单的处理器忙等的时延函数。

/* short delay */
void delay_short(volatile unsigned int delay){while(delay--){;}
}/* delay 指定的 ms 数 */
void delay(volatile unsigned int n){while(n--){delay_short(0x7ff);}
}

5. 在main函数中调用时延函数并且turun on/off LED灯,实现LED灯的闪烁

有了如上的时延函数,然后通过写 GPIO1_DR 寄存器 bit3 位的值的方式来控制输出高电平和低电平来实现LED灯的亮灭。我们把控制LED灯亮灭,写寄存器的语句封装成函数, ‘led_on()’, 'led_off()' 。

/* turn on led */ 
void led_on(void)
{GPIO1_DR &= ~(1<<3);    //clean bit3 to 0     
}/* turn off led */
void led_off(void)
{GPIO1_DR |= (1<<3);     //set bit3 to 1
}

在 main 函数的最后 while(1)循环中,我们通过调用 delay 时延函数和 led_on, led_off 函数就可以实现LED的亮灭控制。

int main(void)
{clk_enable();led_init();while(1){led_on();delay(50);led_off();delay(50);}return 0;
}

6. 编译代码并烧录到SD卡中,开发上电验证LED灯是否闪烁

按照上一节中的步骤,编译代码,并且使用正点原子提供的 ‘imxdownload’ 来将编译出来的 led.bin 文件加上 "IVT+BootData+DCD" I.MX6U 要求的 image-heander 头,并烧写到SD卡,把SD卡查到正点原子 I.MX6U ALPHA/Mini 开发板上,开发板上电,验证下LED灯是否闪烁。

./imxdownload ledc.bin /dev/sdb

我开发版的验证结果是LED灯正常的按照预期闪烁。

7. 总结

总结,通过汇编程序准备C语言执行环境,只需要简单的几条汇编执行就可以准备好C语言的执行环境。对于C语言执行环境来说,我个人从这个实验的 start.s 理解,最基本的也是最简单的就是准备好C语言执行需要的栈内存空间,然后把栈的起始地址写到 SP (R13)即寄存器里就可以了。

start.s 通过'MSR', 'MRS' 命令写ARM处理器的CPSP当前状态寄存器其,通过写CPSR M[4:0] bit位,就可以让处理器进入指定的ARM运行模式,例如,本例子中的让I.MX6U 运行在 SVC 模式,然后设置 SVC 模式的 SP指针。因为如之前的"Cortex-A7 寄存器”章节所接收的,在不同的运行模式下,ARM处理器可能有自己模式下的独有的物理SP寄存器。

这篇关于正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-8.1的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/948059

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss