Cortex-M0编程入门
目录
- 1.嵌入式系统编程入门
- 微控制器是如何启动的
- 嵌入式程序设计
- 2.输入和输出
- 3.开发流程
- 4.C编程和汇编编程
- 5.什么是程序映像
- 6.C编程:数据类型
- 7.用C语言操作外设
- 8.Cortex微控制器软件接口标准(CMSIS)
- 简介
- 标准化内容
- 组织结构
- 使用方法
- 优势
1.嵌入式系统编程入门
微控制器是如何启动的
- 为了保存编译好的程序代码,大多数的现代微控制器都会包含片上Flash存储器。程序代码在Flash存储器中是以二进制机器码的形式存放的,因此C代码必须经过编译,才能烧写到Flash中。有些微控制器会配备一个独立的启动ROM,里面有一个小的Boot loader程序。微控制器启动后,在执行Flash里的用户程序前,Boot loader会首先运行。
- 程序代码烧写到Flash存储器(或者是其它类型的程序存储器)以后,处理器就可以访问程序了。复位以后,处理器将会运行复位流程。

在复位流程中,处理器会取出MSP的初始值和复位向量,然后开始执行复位处理。这些所需信息都放在一个叫做启动代码的程序文件中。启动代码中的复位处理可能还会履行系统初始化的职责,一般系统初始化是在C程序main()中开始的。 - 执行完C启动代码后,应用程序就开始执行了。其通常包含:硬件初始化(如时钟、PLL和外设)、应用程序的处理部分、中断服务程序。
应用程序可能会用C库函数,此时C编译器/链接器会将所需的库函数纳入编译好的程序映像中。
硬件初始化可能会涉及到一系列的外设、系统控制寄存器和Cortex-M0里的中断控制寄存器。如果在复位处理时没有进行处理,系统时钟控制和PLL此时也需要进行初始化。外设初始化完成后,程序就可以继续执行应用程序处理部分了。
嵌入式程序设计
应用程序处理流程可以用三种方式来实现,下面是一些基本概念:
- 轮询:轮询适用于简单的应用程序,而当应用程序变得复杂或对处理能力的要求更高时,轮询就不合适了。比如当一个进程占用时间过长,其他模块在这段时间内就不会收到处理器的任何服务。
轮询方式示意图如下:

- 中断驱动:对于一些需要低功耗的应用,处理器可以在中断服务程序中执行处理,当没有任务需要处理时进入休眠模式。外部中断源或者芯片外设都可以触发中断,并唤醒处理器。此外,在中断程序的应用程序中,不同设备的中断可以被设置为不同的优先级。
中断驱动应用程序方式如下:

- 轮询和中断驱动的组合使用:许多情况下,应用程序可以将中断驱动和轮询这两种方式组合使用,通过软件变量传递,中断处理程序和主处理流程间可以进行信息交换,如下图:

将一个处理任务分割为中断服务程序和主流程中的处理,可以减少中断服务的持续时间,以便低优先级的中断能有更多的机会获得服务。 - 处理并发任务:当一个处理任务可能会占用大量时间时,上述的处理就不合适了。如果任务A的执行需要太长的时间,任务B和C可能就无法及时响应外设模块的请求。针对这种情况,一般的解决方案如下:
1.将一个长时间的处理任务划分为一系列的状态,每次处理任务时,只执行一种状态。
2.使用实时操作系统(RTOS)处理多任务。
使用第1种方法,一个任务被划分为了几个部分,可以使用软件变量追踪任务的状态。每次执行任务时,状态信息就会得到更新,这样下次再执行这个任务时,就可以继续上次的处理了。如下:

而对于更加复杂的应用程序,可以使用RTOS。RTOS将处理器时间划分为多个时间片,在有多个应用进程运行时,只有一个进程会获得时间片。使用RTOS,需要由定时器产生周期性的中断请求。当一个时间片的时间到时,RTOS的任务调度器会由定时器中断触发,并判断是否需要执行上下文切换。如果需要,任务调度会暂停正在执行的任务,并切换到下一个准备就绪的任务。如下:

RTOS可以提高系统的反应能力,并且确保在一定时间内能够执行到所有任务。
2.输入和输出
在许多嵌入式系统中,可用的输入和输出有简单的电子接口,如数字和模拟输入/输出接口(I/O)、UART、I2C和SPI等,此外,还提供了USB、以太网、CAN图像LCD以及SD卡等接口,这些接口是由微控制器的外设控制的。
Cortex-M0的寄存器映射到了系统空间,并且它们还控制着外设。外设的典型初始化过程一般包括以下步骤:
- 配置时钟控制回路,使能外设的时钟信号,并且有必要的话,初始化相应的引脚。
- 配置I/O,大多数微控制器的引脚都是复用的,需要对I/O引脚的功能进行配置,以确保外设接口正常工作。
- 配置外设,大多数接口外设都有多个可编程的控制寄存器,因此,为了确保外设工作正常,就需要对寄存器进行一系列的编程操作。
- 配置中断,如果外设操作需要中断处理,就需要另外配置中断控制器。
3.开发流程
ARM微控制器可以使用的开发工具链有很多种,它们大多数都支持C和汇编语言。开发嵌入式工程时,可以使用C语言,也可以用汇编,或者两者混合编程。多数情况下,程序代码生成流程可以总结为如下所示的形式:

根据所使用开发工具的不同,链接器可能会使用命令行选项来指定内存分布。
生成可执行映像后,可以将其下载到微控制器的Flash存储器或内存中进行测试。开发工具与在线调试器配合使用时,可以分步进行以下工作:创建工程,编译应用程序,然后下载嵌入式应用程序到微控制器中,如下:

要想对Flash进行编程,可以使用开发组件中的调试器软件,或者从微控制器供应商网站下载的Flash编程工具。在微控制器上运行程序可以测试其正确性,而将调试器连接到微控制器上,则可以控制程序的运行并且观察其各种操作。这些功能都可以通过Cortex-M0处理器的调试接口实现,如下:

4.C编程和汇编编程
C语言和汇编语言的使用比较如下:

- 大多数C编译器都允许在C程序代码中嵌入汇编代码。
- 有些特殊指令没法用C代码生成,所以一些C编译器还提供了内在函数,其一般是与开发工具相关联的。
- 可以在同一个工程里使用C和汇编代码混合编程,程序的大部分代码可以用C编写,无法用C实现的部分则使用汇编,这样做是为了保证输入参数和返回值的正确传递,两部分的函数接口必须保持一致。
5.什么是程序映像
Cortex-M0的程序映像一般包括以下几个部分:
-
向量表:
向量表可以用C语言或汇编语言实现。由于向量表的入口需要编译器和链接器生成的内容,所以向量表代码的实现细节是同开发工具链相关的。
为了将向量表置于系统存储器映射的开头(地址为0x00000000),链接文件或命令行选项需要知道段的名字,以便链接器能够正确识别向量并将其地址映射。
复位向量一般指向C启动代码的开头,不过,也可以自己定义复位处理,在跳转到C启动代码前执行附加的初始化操作。 -
C启动例程:
C启动代码用于设置像全局变量之类的数据,也会清零加载时未被初始化的内存区域。初始化完成后,启动代码跳转到main()程序执行。
C启动代码由编译器/链接器自动嵌入到程序中,并且是和开发工具链相关的,而只使用汇编代码编程则可能不存在C启动代码。对于ARM编译器,C启动代码被标识为"_mian",而是用GUN C编译器生成的代码则通常被标记为"_start"。 -
程序代码(应用程序代码和数据):
用户指定的任务是由应用程序生成的指令完成的,除了指令以外,还有以下各类数据:
1.变量的初始值,函数或子程序中的局部变量需要初始化,这些初始值会在程序执行期间被赋给相应的变量。
2.程序代码中的常量,应用程序中的常量数据有多种用法,如数据值、外设等寄存器的地址和常量字符串等,这些数据在程序映像中一般作为数据块放在一起,并被称作文字库。
3.其他的常量,如查找表和图像数据。 -
C库代码(C库函数的程序代码,链接时插入):
当使用特定的C/C++库函数时,它们的库代码就会由链接器嵌入到程序映像中。另外,由于有些数据处理任务需要浮点数或除法运算,在进行这些运算时,C库代码也会被包含进来。Cortex-M0中没有除法指令,需要借助C函数库中的除法函数来实现除法运算。
除了向量表需要放在存储器映射的开头以外,程序映像中剩余部分的位置就没有限制了。
RAM中的数据:
像程序ROM一样,微控制器的RAM也有多种用法。典型地,RAM的使用一般可以分为数据、栈和堆区域,如下所示:

数据、栈和堆区域存储内容如下:
数据:数据存储在内存的底部,包含全局变量和静态变量,为了节省内存,可以将局部变量分配在栈上,而且函数内未用的局部变量不占用存储器空间。
栈:栈空间用于临时数据存储(常见的PUSH和POP操作)、局部变量的存储空间、函数调用参数传递和异常处理的寄存器备份等。
堆:堆存储用于C函数自动分配存储器区域,以及其他使用这些函数的函数调用。为了确保这些函数能够正确地分配存储器空间,C启动代码需要初始化堆存储及其控制变量。
一般来说,栈位于存储器空间的顶部,而堆区域则位于底部,这样做使得内存使用具有最大的灵活性。在操作系统环境中,可能会有多个内存区域用作数据、栈和堆。
有些操作系统允许用户自定义任务的栈,这样也就需要更大的栈空间。有些操作系统则将内存分为若干个段,每个任务分配一个段,用于各自的数据、栈和堆区域,如下:

6.C编程:数据类型
Cortex-M0处理器支持的数据类型如下:


将应用程序从其他架构的处理器移植到ARM上时,如果两者的数据类型的宽度不同,那么可能就需要修改C代码,以确保程序运行正常。此外,在Cortex-M0上编程时,变量的存放地址为其数据宽度的倍数。
7.用C语言操作外设
- 除了变量以外,微控制器的C应用程序通常需要操作外设。对于ARM Cortex-M0微控制器,外设寄存器被映射到系统存储器空间,它们可以通过指针进行访问。外设访问定义指针时,需要使用volatile关键字。
- 大多数情况下,外设寄存器都被定义为32位宽度,这是因为连接外设的外部总线(APB)是按照32位处理数据传输的,有些外设可能会连接至处理器总线(AHB),这样,寄存器可以按照其他宽度的方式访问。
8.Cortex微控制器软件接口标准(CMSIS)
简介
随着嵌入式软件复杂性的增加,软件代码的兼容性和可重用性变得更加重要。为了使软件产品具有高度的兼容性和可移植性,ARM同一些微控制器供应商一起开发了一个通用的软件框架CMSIS,该框架适用于大多数的Cortex-M处理器以及Cortex-M微控制器产品,如下:

CMSIS一般是作为微控制器厂商提供的设备驱动库的一部分来使用的。为了使用诸如NVIC和系统控制功能等处理器特性,CMSIS提供了一种标准化的软件接口。
标准化内容
CMSIS为嵌入式软件提供了以下标准化的内容:
- 标准化的操作函数,用于访问NVIC、系统控制块(SCB),SysTick的中断控制和初始化。
- NVIC、SCB和SysTick寄存器的标准化定义。
- 使用Cortex-M0微控制器指令的标准化函数。有些指令不能由普通的C代码生成,如果需要这些指令,就可以使用CMSIS提供的这类函数来实现。
- 系统异常处理的标准化命名。
- 系统初始化函数的标准化命名。
- 为时钟频率信息建立标准化的变量。
- 设备驱动库的通用平台。
组织结构
CMSIS可以分为以下 几层:
- 核心外设访问层:命名定义,地址定义,以及访问核心寄存器和NVIC、SCB以及SysTick等核心外设的辅助功能。
- 中间件访问层(正在进行中)
1.典型嵌入式系统访问外设的通用方法。
2.面向通信接口,包括UART、Ethernet和SPI等。
3.嵌入式软件能够在任何支持特定通信接口的Cortex微控制器上使用。 - 设备外设访问层(MCU相关):寄存器名称定义,地址定义,以及访问外设的设备驱动代码。
- 外设的访问函数(MCU相关):可选的外设辅助函数。
这些层的作用在下图中做了总结:

使用方法
CMSIS被集成在微控制器供应商提供的设备驱动包中,如果使用设备驱动库进行软件开发,那么就已经在使用CMSIS了。
对于C程序代码,通常只需要包含微控制器供应商提供的设备驱动库里的头文件。这个头文件又包含了其他所有需要的文件,包括CMSIS特性和外设驱动等。
下图是包含了一个使用CMSIS包建立的简单工程,有些文件的名字由实际的微控制器设备决定(在图中标识为< device >)。

下图是一个使用CMSIS的简单例子:

优势
-
不同的Cortex-M微控制器之间的程序移植变得容易。
-
提高了兼容性,这是因为由于CMSIS文件的使用,中间件和嵌入式操作系统就会基于相同的内核外设寄存器定义以及内核访问函数。
这同时也减小了代码冲突的概率,因为不同软件组件都分别使用自己的内核访问函数和寄存器定义时,就可能会发生冲突。没有CMSIS,可能会发现不同的第三方软件组件都包含了自己特有的驱动函数,这就可能会带来诸多问题,包括寄存器命名冲突、多个函数拥有类似命名引起的混乱以及重复函数带来的空间浪费,如下:

-
CMSIS内核访问函数占用的存储器空间很小,并且经过测试,这有助于减少软件测试的时间。
相关文章:
Cortex-M0编程入门
目录1.嵌入式系统编程入门微控制器是如何启动的嵌入式程序设计2.输入和输出3.开发流程4.C编程和汇编编程5.什么是程序映像6.C编程:数据类型7.用C语言操作外设8.Cortex微控制器软件接口标准(CMSIS)简介标准化内容组织结构使用方法优势1.嵌入式…...
字符串函数能有什么坏心思?
🚀write in front🚀 📝个人主页:认真写博客的夏目浅石. 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝 📣系列专栏:夏目的C语言宝藏 💬总结:希望你看完之…...
Vue3 组件之间的通信
组件之间的通信 经过前面几章的阅读,相信开发者已经可以搭建一个基础的 Vue 3 项目了! 但实际业务开发过程中,还会遇到一些组件之间的通信问题,父子组件通信、兄弟组件通信、爷孙组件通信,还有一些全局通信的场景。 …...
多路查找树
1.二叉树与 B 树 1.1二叉树的问题分析 二叉树的操作效率较高,但是也存在问题, 请看下面的二叉树 二叉树需要加载到内存的,如果二叉树的节点少,没有什么问题,但是如果二叉树的节点很多(比如 1 亿), 就 存在如下问题:问…...
Mybatis——注入执行sql查询、更新、新增以及建表语句
文章目录前言案例dao和mapper编写XXXmapper.xml编写编写业务层代码,进行注入调用额外扩展--创建表语句前言 在平时的项目开发中,mybatis应用非常广泛,但一般都是直接CRUD类型sql的执行。 本片博客主要说明一个另类的操作,注入sq…...
即时通讯系列-4-如何设计写扩散下的同步协议方案
1. 背景信息 上篇提到了, IM协议层是主要解决会话和消息的同步, 在实现上, 以推模式为主, 拉模式为辅. 本文Agenda: (How)如何同步(How)如何设计同步位点如何设计 Gap过大(SyncGapOverflow) 机制如何设计Ack机制总结 提示: 本系列文章不会单纯的给出结论, 希望能够分享的是&…...
tui-swipe-action组件上的按钮点击后有阴影的解决方法
大家好,我是雄雄,欢迎关注微信公众号:雄雄的小课堂。 目录 前言问题描述问题解决前言 一直未敢涉足电商领域,总觉得这里面的道道很多,又是支付、又是物流的,还涉及到金钱,所以我们所做的项目,一直都是XXXX管理系统,XXX考核系统,移动端的也是,XX健康管理平台…… 但…...
【大数据Hadoop】Hadoop 3.x 新特性总览
Hadoop 3.x 新特性剖析系列11. 概述2. 内容2.1 JDK2.2 EC技术2.3 YARN的时间线V.2服务2.3.1 伸缩性2.3.2 可用性2.3.3 架构体系2.4 优化Hadoop Shell脚本2.5 重构Hadoop Client Jar包2.6 支持等待容器和分布式调度2.7 支持多个NameNode节点2.8 默认的服务端口被修改2.9 支持文件…...
Python-第三天 Python判断语句
Python-第三天 Python判断语句一、 布尔类型和比较运算符1.布尔类型2.比较运算符二、if语句的基本格式1.if 判断语句语法2.案例三、 if else 语句1.语法2.案例四 if elif else语句1.语法五、判断语句的嵌套1.语法六、实战案例一、 布尔类型和比较运算符 1.布尔类型 布尔&…...
失手删表删库,赶紧跑路?!
在数据资源日益宝贵的数字时代公司最怕什么?人还在,库没了是粮库、车库,还是小金库?实际上,这里的“库”是指的数据库Ta是公司各类信息的保险柜小到企业官网和客户信息大到金融机构的资产数据和国家秘密即便没有跟数据…...
技术树基础——16排它平方数(Bigdecimal,int,string,数组的转换)
题目:03879 * 203879 41566646641这有什么神奇呢?仔细观察,203879 是个6位数,并且它的每个数位上的数字都是不同的,并且它平方后的所有数位上都不出现组成它自身的数字。具有这样特点的6位数还有一个,请你…...
04动手实践:手把手带你实现gRPC的Hello World
这篇文章就从实践的角度出发,带大家一起体验一下gRPC的Hello World。文中的代码将全部使用Go语言实现,使用到的示例也是GitHub上提供的grpc-go,下面我们开始: Hello World官方示例 首先我们要clone GitHub上gRPC的源代码到我们本地 git clone https://github.com/grpc/g…...
区块链技术与应用1——BTC-密码学原理
文章目录比特币中的密码学原理1. 哈希函数2. 数字签名3. 比特币中的哈希函数和数字签名简单介绍:比特币与以太坊都是以区块链技术为基础的两种加密货币,因为他们应用最广泛,所以讲区块链技术一般就讲比特币和以太坊。比特币中的密码学原理 1…...
PyTorch学习笔记:data.WeightedRandomSampler——数据权重概率采样
PyTorch学习笔记:data.WeightedRandomSampler——数据权重概率采样 torch.utils.data.WeightedRandomSampler(weights, num_samples, replacementTrue, generatorNone)功能:按给定的权重(概率)[p0,p1,…,pn−1][p_0,p_1,\dots,p_{n-1}][p0,p1,…,pn…...
SpringMVC对请求参数的处理
如何获取SpringMVC中请求中的信息 ? 默认情况下,可以直接在方法的参数中填写跟请求参数一样的名称,此时会默认接受参 数 ,如果有值,直接赋值,如果没有,那么直接给空值 。Controller RequestMapp…...
12年老外贸的经验分享
回想这12年的经历,很庆幸自己的三观一直是正确的,就是买家第一不管什么原因,只要你想退货,我都可以接受退款。不能退给上级供应商,我就自己留着,就是为了避免因为这个拒收而失去买家。不管是什么质量原因&a…...
电子电路中的各种接地(接地保护与GND)
前言多年以前,雷雨天气下,建筑会遭遇雷击,从而破坏建筑以及伤害建筑内的人,为了避免雷击的伤害,人们发明了避雷针,并将避雷针接地线,从而引导雷击产生的电流经过地线流入到地下。地线࿱…...
php实现农历公历日期的相互转换
农历(Lunar calendar)和公历(Gregorian calendar)是两种不同的日历系统。公历是基于太阳和地球的运动来计算时间的,而农历是基于月亮的运动来计算时间的。农历中的月份是根据月亮的相对位置来确定的,而公历…...
基于SpringBoot的房屋租赁管理系统的设计与实现
基于SpringBoot的房屋租赁管理系统的设计与实现 1 绪论 1.1 课题来源 随着社会的不断发展以及大家生活水平的提高,越来越多的年轻人选择在大城市发展。在大城市发展就意味着要在外面有一处安身的地方。在租房的过程中,大家也面临着各种各样的问题&…...
一文带你为PySide6编译MySQL插件驱动
1.概述 最近使用PySide6开发程序,涉及与MySQL的数据交互。但是qt官方自pyqt5.12(记不太清了)以后不再提供MySQL的插件驱动,只能自己根据qt的源码编译。不过网上大部分都是qt5的MySQL驱动的编译教程。后来搜到了一个qt6的编译教程…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
Unity中的transform.up
2025年6月8日,周日下午 在Unity中,transform.up是Transform组件的一个属性,表示游戏对象在世界空间中的“上”方向(Y轴正方向),且会随对象旋转动态变化。以下是关键点解析: 基本定义 transfor…...
C++实现分布式网络通信框架RPC(2)——rpc发布端
有了上篇文章的项目的基本知识的了解,现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...
