正点原子[第二期]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

相关文章

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

详解Linux中常见环境变量的特点与设置

《详解Linux中常见环境变量的特点与设置》环境变量是操作系统和用户设置的一些动态键值对,为运行的程序提供配置信息,理解环境变量对于系统管理、软件开发都很重要,下面小编就为大家详细介绍一下吧... 目录前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变

Linux系统中的firewall-offline-cmd详解(收藏版)

《Linux系统中的firewall-offline-cmd详解(收藏版)》firewall-offline-cmd是firewalld的一个命令行工具,专门设计用于在没有运行firewalld服务的... 目录主要用途基本语法选项1. 状态管理2. 区域管理3. 服务管理4. 端口管理5. ICMP 阻断

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Linux中修改Apache HTTP Server(httpd)默认端口的完整指南

《Linux中修改ApacheHTTPServer(httpd)默认端口的完整指南》ApacheHTTPServer(简称httpd)是Linux系统中最常用的Web服务器之一,本文将详细介绍如何... 目录一、修改 httpd 默认端口的步骤1. 查找 httpd 配置文件路径2. 编辑配置文件3. 保存

Linux使用scp进行远程目录文件复制的详细步骤和示例

《Linux使用scp进行远程目录文件复制的详细步骤和示例》在Linux系统中,scp(安全复制协议)是一个使用SSH(安全外壳协议)进行文件和目录安全传输的命令,它允许在远程主机之间复制文件和目录,... 目录1. 什么是scp?2. 语法3. 示例示例 1: 复制本地目录到远程主机示例 2: 复制远程主

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

Linux基础命令@grep、wc、管道符的使用详解

《Linux基础命令@grep、wc、管道符的使用详解》:本文主要介绍Linux基础命令@grep、wc、管道符的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录grep概念语法作用演示一演示二演示三,带选项 -nwc概念语法作用wc,不带选项-c,统计字节数-