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使用 ...
随机推荐
- PyCharm调试程序
当我们在运行python程序出错时,我们需要定位到出错的位置.有时候通过程序运行时的报错可以很容易的找到出错的位置,但是有时候必须得通过调试程序才能找出我们的错误. PyCharm中要调试程序的话,在 ...
- CVE-2012-0003:Microsoft Windows Media Player winmm.dll MIDI 文件堆溢出漏洞调试分析
0x01 蜘蛛漏洞攻击包 前言:2012 年 2月,地下黑产中流行着一款国产名为蜘蛛漏洞的攻击包 -- "Zhi-Zhu Exploit Pack",该工具包含 5 个漏洞,都是在 ...
- 如何绕过WAF
目录 HTTP报文包体的解析 Transfer-Encoding Charset 溢量数据 HTTP协议兼容性 HTTP请求行种的空格 HTTP 0.9+Pipelining Websocket.HT ...
- 还在一个模块打天下嘛?你知道引入Jetpack架构后,你的App会发生哪些奇妙的变化吗?
前言 上篇文章我给大家分享了我对Android架构的理解,从思想层面去讲述架构的演进过程.很多小伙伴读完后拍手叫好,表示还想听我讲一下对Jetpack 架构的看法,本着帮人帮到底的精神,今天我将再次动 ...
- Day008 数组的使用
数组的使用 For-Each循环 数组作方法入参 数组作返回值 用普通for循环遍历 int[] arrays={1,2,3,4,5}; //打印全部的数组元素 for (int i = 0; i & ...
- Day002 编译型和解释型语言
编译型和解释型语言 原文链接 编译型(Compile) 用编译型语言写的程序执行之前,需要一个专门的编译过程,针对特定的平台,使用专门的编译器,把高级语言翻译成机器语言,以后直接运行而不需要再编译了, ...
- k3d入门指南:在Docker中运行K3s
在本文中,我们将简单了解k3d,这是一款可让您在安装了Docker的任何地方运行一次性Kubernetes集群的工具,此外在本文中我们还将探讨在使用k3d中可能会出现的一切问题. 什么是k3d? k3 ...
- IO异步,读写压缩文件,监控文件系统
这节结尾IO,讲一下异步操作文件,读写压缩文件,监控文件系统这三个知识点. 异步操作文件: 说到异步,必然要了解的是async和await这两个关键字(异步详情点击基于任务的异步编程(Task ...
- Kafka万亿级消息实战
一.Kafka应用 本文主要总结当Kafka集群流量达到 万亿级记录/天或者十万亿级记录/天 甚至更高后,我们需要具备哪些能力才能保障集群高可用.高可靠.高性能.高吞吐.安全的运行. 这里总结内容主 ...
- Zookeeper详细使用解析!分布式架构中的协调服务框架最佳选型实践
Zookeeper概念 Zookeeper是分布式协调服务,用于管理大型主机,在分布式环境中协调和管理服务是很复杂的过程,Zookeeper通过简单的架构和API解决了这个问题 Zookeeper实现 ...