ARM Cortex-M嵌入式C基础编程(上)
ARM Cortex-M嵌入式C基础编程(上)
ARM Cortex-M Embedded C Fundamentals/Tutorial
-Aviral Mittal
此技术是关于从编写简单的嵌入式C代码到执行的过程。
这项技术试图不使用行话,并针对任何人谁有兴趣知道如何开始编写一个嵌入式C程序或ARM Cortex-M系列处理器的汇编语言程序。
世界上充斥着信息,然而这些信息的存在方式使得所有的信息对于一个来自其他背景的人来说都是垃圾。例如,如果您来自硬件背景,则软件信息看起来很神秘。例如,在软件世界中,他们使用“图像”一词来表示从相应的C程序获得的二进制文件。说真的,如果你不知道“图像文件”是什么,那么jpeg或gif就会出现在你的想象中。我不知道为什么它叫“形象”。这项技术是一种试图保持它非常简单,而不使用'什么软件假设每个人都知道出生'之类的行话。
这都是关于ARM架构的,所以它关注的是嵌入式c,也就是为基于ARM的微控制器编写的c程序。
C代码是人类可读的,处理器无法执行它。它必须转换成0和1,因为这是处理器可以执行的。
因此,在执行C代码之前,必须将此C代码转换为二进制格式的处理器指令,然后将此二进制格式的指令放入内存中,然后处理器将开始从该内存中获取并执行指令(请参阅软件术语“图像文件”的使用)
但这听起来很简单。在幕后发生了很多事情,很多事情都要考虑。
The very basic Embedded C program:
本节描述了一个非常简单的嵌入式C程序,并给出了一些解释。
让我们考虑一个非常简单的嵌入式C程序:
typedef unsigned long uint32_t;
int main ()
{
int ii;
for(ii=0;ii<305419896;ii++) {
*((uint32_t *)0x40E00018) = 0x87654321;
asm("NOP");
}
while(1){}
}
如果您之前不知道嵌入式C,那么上面的代码已经很神秘了,但是让我逐行解释一下:
第一行是数据类型的定义,程序将使用。定义了一种新的数据类型,称为“uint32”。这被定义为“长”long'是C语言中预定义的数据类型,表示32位宽的二进制数。
第二行是“main”函数调用。对每一个C程序都是必不可少的。这是用户写他们想做的事情的地方。
“int ii”是一个不言而喻的整数声明,它将在“for循环”中使用。在C语言中,需要显式声明所有变量,然后才能使用它们。
for(ii……)同样是不言而喻的,一个for循环被启动,它将执行305419896次。
然后你就有了这行命令:
*((uint32_t *)0x40E00018) = 0x87654321;
看起来像是第二次世界大战的加密密码,用来指示某人发射鱼雷!。
让我解释一下:
C语言使用所谓的“指针”。指针是指向内存位置的地址。“星号”或“星号”用于定义/声明指针。
“0x”:这意味着“0x”后面的值是十六进制格式。
现在,上面的代码行简单地表示,用户希望将0x8765_4321的十六进制值发送到内存位置0x40E0_0018。很简单。
上面用大括号写的'(unit32_t*)表示用户打算将“0x40E0_0018”的常量值转换为另一种类型的数据,即指针,以便它可以用作内存位置的地址。指针指向简单的内存位置。
这里我们刚刚解释了如何将常量数据(0x40E0_0018)转换为另一种类型的数据,称为指针数据类型。软件人员称之为“类型转换”。也就是,把一种数据转换成另一种数据。或者将一种类型的数据更改为另一种类型的数据。所以这里是“类型选择”行话。
这里我们刚刚解释了如何将常量数据(0x40E0_0018)转换为另一种类型的数据,称为指针数据类型。软件人员称之为“类型转换”。也就是,把一种数据转换成另一种数据。或者将一种类型的数据更改为另一种类型的数据。所以这里是“类型选择”行话。
那么“*((uint32_t*)0x40E00018)=0x87654321”总体上意味着用户现在希望将0x8765_4321的值写入内存位置0x40E0_0018。指针的“星”表示指针指向的位置处的值。因此,在上面的行中,用户将0x8765_4321分配给(uint32_t*)0x40E00018的“star”。记住,'(uint32_t*)0x40E00018'是指向内存位置0x4E0_0018的指针。
asm(“NOP”)是汇编语言中的“no
operation”指令,在这里使用,因为我不知道C语言中有什么替代方法。要使用C语言中的汇编指令,请使用asm(“汇编指令”)。
现在是while循环:
while (1) {}。
这段代码将出现在大多数嵌入式C程序中。在嵌入式世界,只要处理器有电,它就可以运行。它就像一个瓶子里的金妮,它必须一直做些什么。如果处理器的电源没有关闭,或者处理器没有进入睡眠状态,它将继续执行“某些操作”。所以上面的while(1)do nothing循环的作用完全相同。
为某目标微控制器编写了嵌入式C程序。典型的微控制器至少有微处理器、存储器、外围设备和时钟源。上述C程序写入一个内存位置该内存位置可能属于外围设备中的“寄存器”。
The Compile Flow:
技术的这一部分解释编译器/链接器生成的机器代码(汇编代码)的位。
程序集代码的部分已显示/描述。
本文还描述了编译过程中的幕后操作。
好吧,但什么是“编译”?
处理器只能执行二进制指令。编译器将用高级语言(如C)编写的可读程序转换成二进制指令。然后将这些二进制指令放入内存。处理器启动时,从内存中获取这些二进制指令并执行它们。将人类可读代码转换为二进制代码的过程称为“编译”
下面是前面介绍的简单C程序:
typedef unsigned long
uint32_t;
int main ()
{
int ii;
for(ii=0;ii<305419896;ii++) {
*((uint32_t *)0x40E00018) = 0x87654321;
asm("NOP");
}
while(1){}
}
Compile it using KEIL
uVision: Click Here to go to a Very simple Quick Tutorial
编译上述C代码和'statup.s'文件会生成一个名为'axf'的可执行文件。axf是一个人类无法读取的二进制文件,但是可以生成这个“axf”文件的人类可读取版本,它将以“汇编语言”显示指令:这是由Keil提供的名为“fromelf”实用程序完成的。Keil教程演示了如何使用这个实用程序和精确的命令语法来完成这个转换
下面是“axf”文件的可读版本的一节。可以看到C语言中的“main()”是如何转换为汇编指令的。
下面截取的代码还显示了每个指令的地址,即在内存中存储该段代码的位置。例如,“main()”的第一条指令存储在位置0x0000_0134,这是指令MOVS r0,#0。这意味着在寄存器r0中移动值“0”。
还可以注意到,上述“C”程序中的所有“常量”值都存储在从0x0000_014C开始的完全独立的内存位置。有3个这样的常量,如下所示。
注意,上面的main()中的指令不是从地址0x0000_0000开始的,而是从0x0000_0134开始的。
然后,如果对“axf”文件的全文版本进行分析,它将显示出许多情况。上面的“main”代码只有几行。
“axf”文件中的额外内容是什么。
“axf”文件包含许多调试信息。当代码下载到目标设备上,并且设备仍连接到主机PC时,此调试信息有助于调试代码。当目标代码加载到目标本身时,代码和调试信息都加载到开发主机PC的内存中。
当使用某些编译时选项删除调试信息时,axf文件将如下所示:
这又是很多代码,这是多余的'主要'代码。
将二进制axf文件转换成ARM体系结构能够执行的格式需要多余的信息。
在执行用户“main()”之前,将调用以下函数。
__main -> this is not the user main(), but a function called at
the start of the binary executable, which calls other functions.
__scatterload
__rt_entry
__rt_entry in turn will call
__rt_lib_init
User
Code (your code inside main)
exit()
主程序是用户程序的入口点。此主函数是预定义的(尽管用户可以编写自己的主函数)。请注意,这个main与用户的C程序中的main()不同。如果用户打算编写自己的'uuu main',他们可以使用自己的代码和名称。但是,用户必须更新链接器的默认“--startup”选项,例如“--startup my\u main”,因为默认链接器选项是以下“--startup=\u main”。如果用户愿意,也可以使用“--no_startup”。但是,这样做的后果超出了本教程的范围。
__ __main then
calls __scatterload。
对于understand __scatterload,重要的是要进一步了解代码如何存储在内存中以及如何执行。
典型的微控制器系统通常有几种类型的存储器。例如,闪存、ROM、RAM等。
这意味着,同一代码可能在不执行时驻留在一个内存中,然后在执行时移动到另一个内存中。例如,代码及其数据在不执行时可以驻留在ROM中,然后将其移动到RAM中执行。
在另一个例子中,代码可以直接从ROM执行,但是它的变量必须复制到RAM,因为这些变量可能需要由运行的代码更新。Keil教程2展示了如何将变量的初始值存储在只读存储器中,而变量本身存储在读写存储器中,然后在程序执行之前将这些变量的初始值复制到读写存储器中。在Keil教程2中,可以看到C程序有两个整数数组变量,即avar[10]和bvar[10],它们有一些初始值。avar[10]的初始值存储在地址0x0000_015c到0x0000_0180处。这可能是ROM地址。
然而,当程序执行时,变量avar[10]和bvar[10]存储在堆栈存储器的某处堆栈存储器是从0x2000_0000开始的区域中的读/写存储器。在执行用户的main()之前,这些变量的初始值已经在堆栈中可用。这意味着在执行用户的main()之前,这些初始值是如何从区域0x0000_0xxx(只读区域)复制到0x2000_0yyy(读写区域)的。
ARM Cortex-M嵌入式C基础编程(上)的更多相关文章
- ARM Cortex-M嵌入式C基础编程(下)
ARM Cortex-M嵌入式C基础编程(下) ARM Cortex-M Embedded C Fundamentals/Tutorial -Aviral Mittal Load Region Vs ...
- 【转】Shell编程基础篇-上
[转]Shell编程基础篇-上 1.1 前言 1.1.1 为什么学Shell Shell脚本语言是实现Linux/UNIX系统管理及自动化运维所必备的重要工具, Linux/UNIX系统的底层及基础应 ...
- 前端开发工程师 - 03.DOM编程艺术 - 第1章.基础篇(上)
第1章.基础篇(上) Abstract:文档树.节点操作.属性操作.样式操作.事件 DOM (Document Object Model) - 文档对象模型 以对象的方式来表示对应的html,它有一系 ...
- 《Python编程第4版 上》高清PDF|百度网盘免费下载|Python基础编程
<Python编程第4版 上>高清PDF|百度网盘免费下载|Python基础编程 提取码:8qbi 当掌握Python的基础知识后,你要如何使用Python?Python编程(第四版)为 ...
- 嵌入式系统基础知识(一): 系统结构和嵌入式Linux
目录 一. 嵌入式体系结构 二. 开发过程中的分工 三. 嵌入式软件体系结构 四. 嵌入式Linux 一. 嵌入式体系结构 <嵌入式系统设计师教程>这本书的前三章脉络很清晰, 按照嵌入式系 ...
- 嵌入式LINUX基础教程 第2版
嵌入式LINUX基础教程 第2版 目录 第1章 入门 11.1 为什么选择Linux 11.2 嵌入式Linux现状 21.3 开源和GPL 21.4 标准及相关组织 31.4.1 Linux标准基 ...
- 灵动微电子ARM Cortex M0 MM32F0010 GPIO 的配置驱动LED灯
灵动微电子ARM Cortex M0 MM32F0010 GPIO的配置 目录: 1.前言 2.学习方法简要说明 3.要点提示 4.注意事项 5.MM32F0010系统时钟的配置 6.MM32F001 ...
- 【ARM-Linux开发】ARM7 ARM9 ARM Cortex M3 M4 有什么区别
ARM7 ARM9 ARM Cortex M3 M4 区别 arm7 arm9 可以类比386和奔腾, 不同代,arm9相比arm7指令集和性能都有所增强,arm7和arm9都有带mmu和无mmu的版 ...
- 灵动微电子ARM Cortex M0 MM32F0010 UART1和UART2中断接收数据
灵动微电子ARM Cortex M0 MM32F0010 UART1和UART2中断接收数据 目录: 1.MM32F0010UART简介 2.MM32F0010UART特性 3.MM32F0010使用 ...
随机推荐
- Linux日志分析和管理
目录 日志的作用.分类.管理.轮转和级别 rsyslog服务 Journal守护进程 /var/log下相关的日志文件 日志服务器的建立 日志的作用.分类.管理.轮转和级别 日志的作用: 用于记录系统 ...
- Intel汇编程序设计-高级过程(上)
第八章 高级过程 8.1 简介 本章主要讲: 堆栈框架 变量作用域和生存期 对战参数的类型 通过传递值或者传递引用来传递参数 在堆栈上创建和初始化局部变量 递归 编写多模块程序 内存模型和语言关键字 ...
- java线程池实践
线程池大家都很熟悉,无论是平时的业务开发还是框架中间件都会用到,大部分都是基于JDK线程池ThreadPoolExecutor做的封装, 都会牵涉到这几个核心参数的设置:核心线程数,等待(任务)队列, ...
- 在网页添加 Live2D 看板娘
只需要将以下代码粘贴到 标签中即可 <!--看板娘--> <script src="https://cdn.jsdelivr.net/npm/jquery/dist/jqu ...
- 【小技巧】修改eclipse中Java注释中的作者日期等信息
- Tensorflow Probability Distributions 简介
摘要:Tensorflow Distributions提供了两类抽象:distributions和bijectors.distributions提供了一系列具备快速.数值稳定的采样.对数概率计算以及其 ...
- 用 shell 脚本制造连接频繁中断的场景
问题的提出 最近在准备客户端的新版本,在内部灰度过程中,发现一类奇怪的 dump,通过查看日志和堆栈,可以确定是因为每次连上后台就被后台断开了.导致多次重连后随机发生的崩溃.dump 和日志都无法提供 ...
- 三、多线程之Thread与Runnable的区别
Thread与Runnable的区别(用三个窗口同时出售10张车票为例子) 运行代码 运行结果 分析 System.out.println("开始测试多线程");class MyT ...
- spring mvc @Repository 注入不成功 的原因?
这样的代码会影响 @Repository 注入
- [bug] HDFS:DataXceiver error processing WRITE_BLOCK operation
文件格式有误,导致读取错误,我的是把制表符敲成了空格