有谁玩过51单片机,把汇编语言是二进制转为二进制码,通过一组IO口,再用高电压烧写程序的?

为用户提供各种低成本,低功耗,低能耗,功能强的微控制器解决方案.恩智浦微控制器,高性能,设计新颖,性能稳定,价格超低,欲购从速,欢迎垂询!

你可能此前一直学习或者从事的笁作都是使用的4 位或者8 位 比如51 类单片机。 因为51是如此的深入人心可以轻易获得大量关于他的学习资料, 在书店51 类的书籍教材甚至用几個架子来摆放某宝上销量最大的开发板一定是51 开发板, 很少有哪个嵌入式工程师或者学生曾经避开过51 而直达别的平台我们从51 学到了 的概念,学到了控制的概念但到了今天,51 的低成本易用,已经不占优势 反观现在的微控领域应用,对MCU 的资源要求越来高51 越来越不适應。

8 位内核的51 类MCU 的资源往往是最大几K-100K 的flash 100-几K 字节的, IO 串口,8 位数据总线, AD 等简单的资源 目标确定,单一结构简单,指令简单 易於理解和操作,这些特点也是51 能深入人心的因素 目前依然是高校的主导实验平台。 也是很多企业的应用平台

在中国的兴起,引起了广夶51 使用者的注意对于我当初进入时的认识,我觉得STM32 速度非常快 flash,ram 好大 能操作SD 卡,这简直相当于微控制器的硬盘了功能这一个51 以前從来没有的东西,终于可以和计算机不需要串口就可以实现通信了定时器那么多路,可以使我做多少的PWM 控制啊16 位的FSMC 总线,实现了高分辨率的LCD 也一样可以高速控制了 再不是51 那个仅仅能使用一些低分辨率且昂贵的LCM 比如12864 这些行将没落的东东。 以前在51 想都不要想的 ucgui 都可以STM32 上尽凊发挥了还有好多好的功能, 控制器轻易实现以前要组合才能实现的can 通信以及的应用等等 这是真正意义的微控领域的SOC 芯片。

初入STM32可能我们最亲切的就是在51 使用过的 , 在51 它叫keil c51 在 它叫RealviewMKD-ARM,简称它MDK 现在版本是MDK4.22, 操作方法基本类似于keil。 我们常用的功能除了编辑工程编译代码,还会用到下载调试。 我们在51 时可能会很少有人用仿真功能,因为51 足够的简单脑子想的往往就是你所看到的。 直接下载到目标板对伱来说更快捷 所以在51 最常见的是下载器。 但在arm阶段资源繁杂,复杂变量众多,没有一个会感到那么的无助。因此 coretex-m3 的使用者基本都會拥有仿真器一般分为ST-LINK ULINK 及JLINK,尤以JLINK 在中国的应用最为普及 我们都懂的原因,JLINK V8 的性价比是这几个最好的 所以,你需要在获得了MDK 后再拥囿一个JLINK。 它不仅仅只支持STM32,它支持绝大多数的ARM 芯片

51 使用者初入STM32,都会存在一个平台转换带来迷惘的一个短暂过程这是器件类型变化较大慥成的认知差异。

但调整一下这个不适会很快过去的。

??1 先看看 51 和STM32 具有的相同类型资源是哪些根据你对51 的熟悉程度, 你会从STM32 的手册仩看到 这些往往是较简单的,也是最容易理解的 比如IO 口线控制,等等

??2 STM32 高级一些的资源,往往也是需要较多精力去理解的这可鉯在入门后再行学习,比如USBO等。

??4 编程方式的不同 比如在 51,用置位或者复位指令就可以很方便的控制IO而在STM32,由于所有资源的功能嘟和该资源对应的32 位寄存器组的操作有关系 因此对于资源的设置和操作都可能需要操作一个或者多个寄存器, 如果用多条指令来控制的話会引起阅读的障碍,以及日后代码维护复杂因此ST 公司引入了库函数的概念。用执行库函数的方式解决复杂的资源操作的问题

??4 STM32 唎程的MDK 工程都有相似的程序结构,结合手册多看例程会使你快速的形成对STM32 例程模板的认识,这个认识一旦形成剩下的代码细节就好比昰你预测到的填空题目。

当你做好了想学习新平台的准备那就义无反顾的投入CORETEX-M3 的怀抱吧。它会使你进步到一个新的境界 带给你愉悦的技术享受。

如何迅速入门STM32单片机

网上有大神说如果会51单片机和C语言一天可入门STM32,仅一天的时间是否有真的这么快。这个要看自己给自巳定的入门的标准了

我眼中的入门:(前提是你学过51 单片机和C 语言)

??1 知道参考官方的什么资料来学习,而不是陷入一大堆资料中无從下手

??2 知道如何参考官方的手册和官方的代码来独立写自己的程序,而不是一味的看到人家写的代码就觉得人家很牛逼

??3 消除對STM32 的恐惧,消除对库开发的恐惧学习是一个快乐而富有成就感的过程。

学习本文时配合《STM32 中文参考手册》GO 章节一起阅读,效果会更佳特别是涉及到寄存器说明的部分。

51 是嵌入式学习中一款入门级的精典MCU因其结构简单,易于教学且可以通过串口编程而不需要额外的汸真器,所以在教学时被大量采用至今很多大学在嵌入式教学中用的还是51。51 诞生于70 年代属于传统的8 位单片机,如今久经岁月的洗礼,既有其辉煌又有其不足现在的市场产品竞争激烈,对成本极其敏感相应地对MCU 的要求也更苛刻:功能更多,功耗更低易用界面和多任务。面对这些要求51 现有的资源就显得得抓襟见肘了。所以无论是高校教学还是市场需求都急需一款新的MCU 来为这个领域注入新的活力。

基于这市场的需求 ARM 公司推出了其全新的基于ARMv7 架构的32 位Cortex-M3微控制器内核。紧随其后ST()公司就推出了基于Cortex-M3 内核的MCU—STM32。STM32 凭借其产品线的多樣化、极高的性价比、简单易用的库开发方式迅速在众多Cortex-M3 MCU 中脱颖而出,成为最闪亮的一颗新星STM32 一上市就迅速占领了中低端MCU 市场,受到叻市场和工程师的无比青睐颇有星火燎原之势。

作为一名合格的嵌入式工程师面对新出现的技术,我们不是充耳不闻而是要尽快吻匼市场的需要,跟上技术的潮流如今STM32 的出现就是一种趋势,一种潮流我们要做的就是搭上这趟快车,让自己的技术更有竞争力

我们先普及一个概念,单片机(即MCU)里面有什么一个人最重要的是大脑,身体的各个部分都在大脑的指挥下工作MCU 跟人体很像,简单来说是甴一个最重要的内核加其他外设组成内核就相当于人的大脑,外设就如人体的各个功能器官

下面我们来简单介绍下51 和STM32 的结构。

图1 51 系统結构框图

我们说的51 一般是指51 系列的单片机型号有很多,常见的有STC89C51、AT89S51其中国内用的最多的是STC89C51/2,下面我们就以STC89C51 来讲解并以51 简称。

51 由一个IP 核和片上外设组成IP 核就是上图中的,片上外设就是上图中的:电路、SFR 和RAM、、定时/计数器、并行I/O 口、串行I/O 口、中断系统IP核跟外设之间由系统总线连接,且是8bit 的速度有限。

51 内核是上个世纪70 年代 公司设计的速度只有12M,外设是IC 厂商(STC)在内核的基础上添加的不同的IC 厂商会茬内核上添加不同的外设,从而设计出各具特色的单片机这里intel 属于IP

我们在学习51 的时候,关于内核部分接触的比较少使用的最多的是片仩外设,我们在编程的时候操作的也就是这些外设

编程的时候操作的寄存器位于SFR 和RAM 这个部分,其中SFR(特殊功能寄存器)占有128 字节(实际仩只用了26 个字节只有26 个寄存器,其他都属于保留区)RAM占有128 字节,我们在程序中定义的变量就是放在RAM 中其中SFR 和RAM 在地址上是重合的,都昰在80~FF 这个地址区间但在物理区间上是分开的,所以51 的RAM 是有256

编写好的程序是烧写到ROM 区剩下的外设都是我们非常熟悉的IO 口,串口、定时器、中断这几个外设

在系统结构上,STM32 和51 都属于单片机都是由内核和片上外设组成。只是STM32 使用的Cortex-M3 内核比51 复杂得多优秀得多,支持的外设吔比51 多得多同时总度也上升到32bit,无论速度、功耗、外设都强与51

从结构框图上看,对比51 内核只有一种总线取指和取数共用。Cortex-M3 内部有若幹个总线接口以使CM3 能同时取址和访内(访问内存),它们是:

指令存储区总线(两条)、系统总线、私有外设总线有两条代码存储区總线负责对代码存储区(即FLASH 外设)的访问,分别是I-Code 总线和D-Code 总线

I-Code 用于取指,D-Code 用于查表等操作它们按最佳执行速度进行优化。

系统总线(System)用于访问内存和外设覆盖的区域包括SRAM,片上外设片外RAM,片外扩展设备以及系统级存储区的部分空间。

私有外设总线负责一部分私囿外设的访问主要就是访问调试组件。它们也在系统级存储区

还有一个MDA 总线,从字面上看 是data memory access 的意思,是一种连接内核和外设的桥梁它可以访问外设、内存,传输不受CPU 的控制并且是双向通信。简而言之这个家伙就是一个速度很快的且不受老大控制的数据搬运工,這个在51 里面是没有的

从结构框图上看,STM32 比51 的外设多得多51 有的串口、定时器、IO 口等外设STM32 都有。STM32 还多了很多特色外设:如FSMC、SDIO、SPI、 等这些外设按照速度的不同,分别挂载到AHB、APB2、APB1 这三条总线上

从内核和外设这两大方面来比较STM32 之于51 就是一个升级版的单片机。它适应市场引流潮流,在中低端的微控制器中流光溢彩

学习51 用寄存器,学习STM32 用库

以前我们在学习51 的时候,用的是寄存器编程的方法想要实现什么效果,直接往寄存器里面赋值优点是直观,简单粗暴知道自己具体干了啥,心里踏实

直接操作寄存器之所以在51 上可行,究其原因我想有两点:

??1 51 主频不高,资源有限必须注重程序执行的效率,只能直接操作寄存器关键的地方还得用汇编,不适合用固件库

要知噵当初我们学习51 单片机的时候用的还是汇编,连现在的C 编程都不是就更别说什么库函数编程。

??2 51 功能简单寄存器不多。以国内普及朂广的STC89C52 为例寄存器全部加起来不到30 个。按照功能区分来记的话可以把每个寄存器背的滚瓜烂熟,并且寄存器每一位的功能都可以记得住在编程的时候做到了然于胸。

现在从51 过度到STM32 的学习很多人还是喜欢沿用51 的学习方法。接受不了库在学习库的时候陷入迷糊之中,來回几个月下来都不知道到底有没学会STM32,因为在这一路的学习中都是在调用库函数压根就没有操作过寄存器,心里面很不踏实其实夶家在调用库函数的时候心中难道就没有疑问,库的底层是怎么实现的难道就没有勇气对库的底层一探究竟。可最后当我们开始跟踪库函数底层的时候看到一堆的宏定义、结构体、指针、各种的文件包含,而且注释全部都是英文的是不是又心生忌惮。

鉴于此我想用兩个原因来总结下很多初学者畏惧库不愿意用库的原因。

??1 C 语言知识点的欠缺

库在实现寄存器映像时使用的宏定义强制类型转换,在萣义寄存器时使用的结构体在外设初始化函数时使用的指针,在组织头文件时使用的条件编译等C 语言知识在大学课程中很少涉及,大哆数老师也基本是不讲在一些简单的51 单片机编程中又很少会用到这些知识。学单片机做嵌入式开发其实80%的工作都跟C 语言编程相关,剩丅的20%的工作就是阅读各种数据手册熟悉各种硬件外设。所以掌握这些基本的C 语言知识是嵌入式学习中一道迈不过去的坎,STM32 的库则给了峩们一次提升C 的机会凡是可以从书本中找到的,相信我们基本都可以学会很多初学者并不是不够聪明或者勤奋,只是缺少方向性的指導罢了对于这欠缺的知识点我们稍微花点时间就可以掌握,剩下的就是不断地实践调试这里我为大家推荐一本C 语言的书籍《C 和指针》。

??2 程序架构设计思想的欠缺

这个比较难搞很多C 语言学习得挺好好的人,也比较难掌握还好我们遇到了STM32 的库,这给了我们一个学习囷提升C 语言绝佳的机会库的整个架构是如何搭建起来的,代码上是如何如何一步一步写出来的:从寄存器映像开始到寄存器的封装,嘫后到函数的编写到每个外设函数对应的驱动文件,这里面涉及到了大量的条件编译文件包含的思想,对应刚写过几行51 单片机的初学鍺来说简直就是噩梦但是,如果你把这一系列的关系弄明白了那么对库的整个架构也了解的差不多了,以后你就不用嚷嚷着说要操作寄存器了

如果你一开始不喜欢用库,对库开发很忌惮那么请自问:是不是我的C 语学得不够好。库是一种全新的学习方法是一种潮流,我更把它看做是与C 语言的又一次历练和提升是否用库,只差你一个闪亮的回眸

3、用寄存器点亮LED

为了顺利过渡到库开发,在STM32 编程的开始我们对照51 点亮一个LED 的方法,给大家演示一下STM32 如何用操作寄存器的方法点亮一个LED然后再慢慢讲解到底什么是库,让大家知道库跟寄存器的关系

在用STM32 点亮一个LED 之前,我们先来复习下用51 如何点亮一个LED

硬件上我们假设51 单片机的P0 口的第0 位接了一个LED,负逻辑亮如果我们要点煷这个LED,代码上我们会这么写:

这里面我们用的是总线操作的方法即是对P0 口的8 个IO 同时操作,但起作用的只是P0^0

除了这种总线操作的方法,我们还学习过位操作利用51 编译器的关键字sbit,我们可以定义一个位变量:

那么LED = 0;就点亮了LEDLED = 1;就关闭了LED。为了让程序看起来见名知义我們定义两个宏:

点亮和关闭LED 的代码就变成了:

上面总线和位操作的的方法,学过51 的朋友是非常熟悉的也很容易理解。

那么我们再说一下夶家容易忽略的几个知识点

??什么是寄存器

在点亮LED 的时候,我们都是用操作寄存器的方法来实现的那大家是否想过,这个寄存器到底是什么为什么我们可以直接操作P0 口?

解答上面的问题之前我们先简单介绍下51 单片机的主要组成部分,这对我们学习其他单片机也有恏处

我们以国内的STC89C51 为例,该单片机主要由51 内核、外设IP、和总线这三大部分组成内核是由Intel 公司生产的,外设IP 就是STC 公司在内核的基础上添加的诸如定时器、串口、IO 口等这些东西总线就是用来连接内核和外设的接口单元。Intel 在这里属于IP 核设计公司STC 属于IC 设计公司。世界上能设計IP 核的公司屈指可数我们非常熟悉的ARM 公司就属于IP 核设计公司,ARM 给其他公司授权其他IC 公司就在ARM 内核上设计出各具特色的MCU,我们后面要学習的STM32 就是属于一中基于ARM 内核的MCU

寄存器则是内置于各个IP 外设中,是一种用于配置外设功能的存储器就是一种内存,并且有想对应的地址学过C 语言我们就知道,要操作这些内存就可以使用C 语言中的指针通过寻址的方式来操作这些具有特殊功能的内存—寄存器。比如P0 口对應的地址是0X80那么我们要修改0X80 这个地址对应的内存的内容的话,按照常理可以这样操作:

可当我们编译的时候编译器会报错,在51 里面只能通过SFR 和SBIT 这两个关键字来实现寄存器映像不能直接操作寄存器对应的地址,这是51 相较于STM32 不同的地方

51 单片机的这些寄存器位于地址80H~FFH 中,對应着128 个地址但不是每个地址都是有效的,51 系列的单片机有21 个52 系列的则有26 个,其他的都是保留区

图3 51 寄存器映射

实际上我们在编程的時候并不是通过指针来操作寄存器的,而是直接给P0、P1 这些端口寄存器赋值那么这些外设资源是如何与地址建立一一对应的关系(寄存器映射定义),这得益与51 特有的两个关键字:SFR 和sbit其他单片机没有,只能用其他的方式来实现寄存器映射这两个关键字帮我们实现了所有寄存器的定义,所以我们才可以像操作普通变量一个来操作寄存器其实我们一开始提到的点亮LED 的代码,全貌应该是这样的:

为了方便起見我们可以把寄存器映射全部写好封装在一个头文件里面,不用每用一个寄存器就定义一次其实这方面的工作不用我们做,我们在编程的时候都会在开始的地方添加一个头文件:

这个头文件已经实现了全部寄存器的定义该文件是keil 自带,在安装目录:Keil\C51\INC 下可以找到这个攵件实现了字节寄存器和位寄存器的定义。

还有一个就是启动代码这个也是很多初学者容易忽略的地方,对于这部分我们主要总结下它嘚功能不详解讲解里面的代码。

单片机在上电复位后首先执行的是启动文件—STARTUP.A51,而不是我们通常看到的mn 函数我们新建51 工程的时候会囿一个提示:是否拷贝启动代码到当前的工程,我们一般选择是

图4 是否添加启动代码

启动代码用汇编语言是二进制编写,主要实现了以丅功能:清除内部数据存储器、清除外部数据存储器、清除外部页储存器、初始化small 模式下的可重入栈和指针、初始化large 模式下可重入栈和指針、初始化compact 模式下的可重入栈和指针、初始化8051 硬件栈指针、传递初始化全局变量的控制命令或者在没有初始化全局变量时给main 函数传递命令然后程序就跳转到main 函数,来到我们熟知的C 世界

在讲解用51 点亮LED 的时候,我们补充了什么是寄存器、寄存器映射、启动代码这三部分的内嫆这三部分内容本来是放到STM32 里面讲解的,但考虑到大家已经有51 的基础并且对51 比较熟悉,那我再添加点内容大家自然没有那么抗拒,並且可以根据上面讲的内容亲自实践学习得也会更深入。那当我再在STM32 讲解这几个内容的时候大家就会对比着学习,对STM32

对比着51 点亮LED 的方法我们先用操作寄存器的方法用STM32 点亮一个LED,然后再一步步完善代码构建最简单的库函数,让我们知道库是怎么建立起来的

在写代码の前,我们先建一个工程大家要注意的是,虽然51 跟STM32 用的都是keil但是针对的MCU 是不一样,软件在安装的时候要安装在不同的目录且不能安装茬英文目录不然会起冲突。我们这里用的是keil5MDK5.15 版本。

用KEIL5 新建一个工程把工程放在一个事先建好的文件夹内,工程命名为REG 后保存然后茬工程目录下添加启动文件:startup_stm32f10x_hd.s,该文件可以从KEIL5 安装目录找到也可以从ST 库里面找到,然后把启动文件添加到工程里面

启动文件由汇编语訁是二进制编写,具体功能跟51 里面的启动文件:STARTUP.A51 差不多

STM32 的启动文件主要实现了:

3、设置向量表入口地址,并初始化向量表

5、跳转到标號_mian,最终来到C 的世界这里我们先去除繁枝细节,挑重点的讲主要理解第四和第五点,在启动文件的147~155 行是复位处理函数,代码如下:

這里我们简单介绍下这10 行代码

第一行是程序注释,在汇编里面注释用的是“;”跟C 语言不一样。

第二行是定义了一个子程序:Reset_HandlerPROC 是子程序定义伪指令。一般用法为:

其中NEAR 和FAR 是属性词NEAR 属性(段内近调用): 调用程序和子程序在同一代码段中,只能被相同代码段的其他程序调用。FAR 属性(段间远调用): 调用程序和子程序不在同一代码段中,可以被相同或不同代码段的程序调用

关键字[WEAK] 表示弱定义,如果编译器发现在别处定义叻同名的函数则在链接时用别处的地址进行链接,如果其它地方没有定义编译器也不报错,以此处地址进行链接

第四行和第五行IMPORT 说奣SystemInit 和__main 这两个标号在其他文件,在链接的时候需要到其他文件去寻找

SystemInit 在库文件system_stm32f10x.c 实现,用来初始化STM32 的一系列时钟把系统时钟设置为72MHZ。STM32 的时鍾比51 单片机复杂需要经过一系列的配置才能达到稳定运行的状态。

__main 其实不是我们定义的当编译器编译时,只要遇到这个标号就会定义這个函数该函数的主要功能是:负责初始化栈、堆,配置系统环境并在最后跳转到用户自定义的main 函数,从此来到C 的世界

第六行把SystemInit 的哋址加载到寄存器R0。

第七行程序跳转到R0 中的地址执行程序之后系统的时钟就被设置成72MHZ。

第八行把_main 的地址加载到寄存器R0

第九行程序跳转箌R0 中的地址执行程序,执行完毕之后就去到我们熟知的C 世界

第十行表示子程序的结束。

总结下就是Reset_Handler 这个函数执行了两个函数调用,一個是SystemInit把系统时钟设置成72M,令一个是__main初始化好系统环境,最终调用C 的main从此去到C 的世界。

等下我们点亮LED 的时候采用最简单的方法直接使用内部的LSI 时钟(8MHZ)作为主时钟即可,不使用外部时钟LSE

__main 函数由编译器生成,负责初始化栈、堆等并在最后跳转到用户自定义的main()函数,來到C 的世界

用记事本新建一个main.c 文件放到工程目录下,然后把main.c 添加到工程中

现在我们就可以开始编写程序了,我们先编写一个main 函数里媔啥都没有,暂时为空这时跟编写51 程序时是不是很像。

现在我们可以编译看看看看有啥现象。

错误提示说SystemInit 没有定义从分析启动文件時我们知道,Reset_Handler 调用了该函数用来初始化系统时钟而该函数是在库文件system_stm32f10x.c 中实现的。我们重新写一个这样的函数也可以把功能完整实现一遍,但是为了简单起见我们在main 文件里面定义一个SystemInit 空函数,为的是骗过编译器把这个错误去掉。关于配置系统时钟我们在后面再写简单嘚代码

这时我们再编译就没有错了,完美解决还有一个方法就是在启动文件中把有关SystemInit 的代码注释掉也可以,代码如下所示:

下面我们從三个方面来讲解STM32 的IO 在控制LED 时跟51 的区别有关STM32 的IO 的寄存器介绍,我们可以看《STM32 中文参考手册》的第八章即可下面涉及到的IO寄存器均来自這一章的第二小节:8.2 GPIO 寄存器描述

51 单片机的IO 口如果要输出1 和0,可以直接赋值不用控制其他寄存器。

而STM32 的IO 口比较复杂如果要输出1 和0,则要通过控制:端口输出数据寄存器ODR 来实现ODR 是:Output data register 的简写,在STM32 里面其寄存器的命名名称都是英文的简写,很容易记住从手册上我们知道ODR 是┅个32 位的寄存器,低16位有效高16 位保留。低16 位对应着IO0~IO16只要往相应的位置写入0 或者1 就可以输出低或者高电平。

PB0 输出低电平代码如下:

这時候编译,我们会发现有个错误说GPIOB_ODR 没有定义,不过我们确实没有定义在51 单片机中,我们可以直接往P0 口赋值那是因为在reg51.h 这个头文件中實现了P0 口这个寄存器的映像,用的是51 特有的关键字SFR 来定义的

STM32 跟51 不一样,没有SFR只能用其他的方式来实现寄存器映像。因为寄存器实际上僦是具有特殊功能的内存那么我们可以通过宏定义来实现寄存器映像,其实ST的库函数中用的也是这种方法

从手册中我们看到ODR 寄存器的哋址偏移是:0CH,这个偏移地址是基于端口的起始地址而言的在STM32 中,每个外设都有一个起始地址叫做外设基地址,外设的寄存器就以这個基地址为标准按照顺序排列跟结构体里面的成员差不多。

在手册中的第二章:存储器和总线构架的2.3:存储器映像小节中可以查看到所囿外设的基地址如下:

图5 STM32 寄存器组起始地址

其中GPIOB 的起始地址是:0X,这样就可以算出GPIOB_ODR 寄存器的地址是:0X + 0X0C = 0XC现在我们就可以定义GPIOB_ODR 这个寄存器叻,代码如下:

有了这个寄存器定义我们就可以直接操作GPIOB_ODR 了。

虽然配置了ODR 寄存器但是这个时候还不能点亮LED,因为STM32 的IO 口还要配置方向這个由端口配置寄存器来控制。端口配置寄存器分为高低两个每4bit 控制一个IO 口,所以端口配置低寄存器:CRL 控制这IO 口的低8 位端口配置高寄存器:CRH控制这IO 口的高8bit。在4 位一组的控制位中CNFy[1:0] 用来控制端口的输入输出,MODEy[1:0]用来控制输出模式的速率即输出时,IO 电平翻转的速度

输入有彡种模式,输出有4 中模式我们在控制LED 的时候选择通用推挽输出。

输出速率有三种模式:2M、10M、50M这里我们选择2M。

同GPIOB_ODR 一样我们也可以算出GPIO_CRL 嘚地址为:0x40010C00。那么设置PB0 为通用推挽输出输出速率为2M 的代码则如下所示:

当我们设置了IO 口的方向,并在相应的输出寄存器里面输入了值的時候以为现在总算可以点亮LED 了吧,其实还差最后一步

STM32 外设很多,为了降低功耗每个外设都对应着一个时钟,在系统复位的时候这些時钟都是被关闭的如果想要外设工作,必须把相应的时钟打开

STM32 的外设因为速率的不同,分别挂载到三条总系上:AHB、APB2、APB1APB为高速总线,APB2 佽之APB1 再次之。所以的IO 口都挂载到APB2 总线上属于高速外设。时钟由APB2 外设时钟使能寄存器(RCC_APB2ENR)来控制其中PB 端口的时钟由该寄存器的位3 写1 使能。

哃ODR 和CRL我们可以算出RCC_APB2ENR 的地址为:0x。那么使能PB 口的时钟代码则如下所示:

如果你足够细心你会发现我们虽然开了端口时钟,那这个时钟到底是多大时钟到底是从哪里来的?

如果我们用的是库那么有个库函数SystemInit,会帮我们把系统时钟设置成72M现在我们没有使用库,那现在时鍾是多少答案是8M,当外部HSE 没有开启或者出现故障的时候系统时钟由内部低速时钟LSI 提供,现在我们是没有开启HSE所以系统默认的时钟是LSI=8M。至于更深入的细节我们在后面的RCC 时钟树中再详细分析如果你想自己先尝鲜,那么看RCC 外设中的:时钟控制寄存器(RCC_CR)和时钟配置寄存器(RCC_CFGR)这两個寄存器即可

控制了电平,配置了方向开启了时钟,经过这三步我们总算可以控制一个LED了。比起51 直接输出电平控制STM32 的IO 多了两步:即配置方向可开启时钟。比起A 和PIC 这两种单片机则多了开启时钟这一步

现在我们完整组织下用STM32 控制一个LED 的代码:

很多人说学习STM32 很难,一堆嘚寄存器不知道怎么操作,特别是那些刚学习完51 的朋友不知道怎么过度。这里我们对比了51 的编程方法写了个简单的用STM32 寄存器点亮LED 的方法,希望可以起到抛砖引玉的作用

4、再接再厉—构建库的雏形

学习STM32 存在着一个用寄存器好还是用库好的争议点,就好比编程是用汇编恏还是用C 好一样其实孰优孰劣,市场自有定论用户群说明一切。

虽然我们上面用寄存器点亮了LED乍看一下好像代码也很简单,但是我們别侥幸以后就可以一直用寄存器开发在用寄存器点亮LED 的时候,我们是否发现STM32 的寄存器都是32 位的在配置的时候非常容易出错,而且代碼还很不好理解所以学习TM32 最好的方法是用库,然后在库的基础上了解底层看遍所有寄存器。

但是很多人对库还是很忌惮因为一开始鼡库的时候有很多代码,很多文件不知道如何入手。不知道你是否认同这么一句话:一切的恐惧都来源于认知的空缺我们对库忌惮那昰因为我们不知道什么是库,不知道库是怎么实现的

接下来,我们在寄存器点亮LED 的代码上继续完善把代码一层层封装,实现库的最初嘚雏形相信经过这一步的学习后,你会对库的运用做到游刃有余这里我们只讲关于GPIO 库,其他外设的我们直接参考库学习即可不必自巳写。

1定义外设寄存器结构体

上面我们在操作寄存器的时候操作的是寄存器的绝对地址,如果每个寄存器都这样操作那将非常麻烦。

峩们考虑到外设寄存器的地址都是基于外设基地址的偏移地址都是在外设基地址上逐个连续递增的,每个寄存器占32 个或者16 个字节这种方式跟结构体里面的成员类似。所以我们可以定义一种外设结构体结构体的地址等于外设的基地址,结构体的成员等于寄存器成员的排列顺序跟寄存器的顺序一样。这样我们操作寄存器的时候就不用每次都找到绝对地址只要知道外设的基地址就可以操作外设的全部寄存器,即操作结构体的成员即可

下面我们先定义一个GPIO 寄存器结构体,结构体里面的成员是GPIO 的寄存器成员的顺序按照寄存器的偏移地址從低到高排列,成员类型跟寄存器类型一样

在《STM32 中文参考手册》8.2 寄存器描述章节,我们可以找到结构体里面的7 个寄存器描述在点亮LED 的時候我们只用了CRL 和ODR 这两个寄存器,至于其他寄存器的功能大家可以自行看手册了解

在GPIO 结构体里面我们用了两个数据类型,一个是uint32_t表示無符号的32 位整型,因为GPIO 的寄存器都是32 位的这个类型声明在标准头文件stdint.h 里面,我们在程序上只要包含这个头文件即可

另外一个是__IO,这个昰我们自己定义的原型是volale,作用就是告诉编译器不要因优化而省略此指令必须每次都直接读写其值,这样就能确保每次读或者写寄存器都真正执行到位

关于这两个数据类型,我们添加如下代码:

现在GPIO 寄存器结构体已经定义好了STM32F1 系列的GPIO 端口分A~G,即GPIOA、GPIOB。。。GPIOG每個端口都含有GPIO_TypeDef 结构体里面的寄存器,我们可以根据各个端口的基地址把GPIO 的各个端口定义成一个GPIO_TypeDef 类型的指针然后我们就可以根据端口名(實际上现在是结构体指针了)来操作各个端口的寄存器,码实现如下:

对于其他外设我们也可以这样把外设的名字定义成一个外设寄存器結构体类型的指针这里我们只讲GPIO。

对于每个GPIO 的基地址我们可以从《STM32 中文参考手册》2.3 小节:存储器映像中找到如下所示:

图6 APB2 总线外设寄存器起始地址

讲到基地址的时候我们再引人一个知识点:Cortex-M3 存储器系统,这个知识点在《Cortex-M3 权威指南》第5 章里面讲到CM3 的地址空间是4GB,如下图所示:

我们这里要讲的是片上外设就是我们所说的寄存器的根据地,其大小总共有512MB512MB 是其极限空间,并不是每个单片机都用得完实际仩各个MCU 厂商都只是用了一部分而已。STM32F1 系列用到了:0x ~0x5003 FFFF

现在我们说的STM32 的寄存器就是位于这个区域,这里面ST 设计了三条总线:AHB、APB2 和APB1其中AHB 和APB2 是高速总线,APB1 是低速总线不同的外设根据速度不同分别挂载到这三条总线上。从下往上依次是:APB1、APB2、AHB每个总线对应的地址分别是:APB1:0x,APB2:0xAHB:0x4001

这三条总线的基地址我们是从《STM32 中文参考手册》2.3 小节—存储器映像得到的:APB1 的基地址是TIM2 定时器的起始地址,APB2 的基地址是AFIO 的起始地址AHB 的基地址是SDIO 的起始地址。

其中APB1 地址又叫做外设基地址是所有外设的基地址,叫做PERIPH_BASE

现在我们把这三条总线地址用宏定义出来,以后我們在定义其他外设基地址的时候只需要在这三条总线的基址上加上偏移地址即可,代码如下:

因为GPIO 挂载到APB2 总线上那么现在我们就可以根据APB2 的基址算出各个GPIO 端口的基地址,用宏定义实现代码如下:

现在我们把上面的代码稍微整理下如下:

在点亮LED 的时候,我们还开了GPIO 的时鍾用到了RCC 这个外设,现在我们也定义一个RCC 寄存器结构体加上那些地址定义,总体代码如下:

跟GPIO 不同的是RCC 这个外设是挂载到AHB 总线上。

現在我们点亮LED 的函数就变成了

一个用的是结构体一个用的是宏,仅仅从这三行代码看不出有啥区别但是如果要操作其他寄存器的时候,用结构体就可以直接操作用宏就还要一个个找到寄存器的绝对地址重新定义。

比如我们要操作GPIOB 的BSRR(bit reset register)的时候用结构体时我们就可以這样操作:

这时候PB0 就输出低电平,LED 被点亮注意:BRR 低16 位有效,只能以字的形式操作功能是复位相应的IO 口,写1 清0写0 没有影响。

图8 GPIO 端口位清除寄存器

现在我们再整理下代码如下所示:

现在我们来总结下上面代码实现的过程,这个过程也是我们从零开始点亮LED 的过程代码全蔀由我们自己编写(除了启动代码),每一行都有根有据都可以从《STM32中文参考手册》查到。

①、定义一个外设(GPIO)寄存器结构体结构體的成员包含该外设的所有寄存器,成员的排列顺序跟寄存器偏移地址一样成员的数据类型跟寄存器的一样。

②外设内存映射即把地址跟外设建立起一一对应的关系。51 单片机中用SFR 实现STM32 中用宏定义实现。

③外设声明即把外设的名字定义成一个外设寄存器结构体类型的指针。

④操作寄存器实现点亮LED。

为了使代码看起来不那么臃肿我们这里引入文件的概念,让不同功能的代码放在不同的文件里面在main.c 裏面我们只保留main 函数和一些头文件,把其他的宏定义放到一个单独的文件

新建一个stm32f10x.h,跟寄存器相关的代码都放在这里主要是寄存器映潒,跟51单片机里面的reg51.h 这个头文件差不多然后我们在main.c 里面包含这个头文件即可,现在我们的主函数就变成这样:

上面我们在控制GPIO 输出内容嘚时候控制的是ODR(Output data register)寄存器ODR 是一个16 位的寄存器,必须以字的形式控制相当于51 里面的总线操作。

其实我们还可以控制BSRR 和BRR 这两个寄存器来控制IO 的电平下面我们简单介绍下BRR 寄存器的功能,BSRR 自行看手册研究

图9 GPIO 端口位清除寄存器

位清除寄存器BRR 只能实现位清0 操作,是一个32 位寄存器低16 位有效,写0 没影响写1 清0。

现在我们要使PB0 输出低电平点亮LED,则只要往BRR 的BR0 位写1 即可其他位为0,代码如下:

这时PB0 就输出了低电平LED 僦被点亮了。

如果要PB2 输出低电平则是:

如果要PB3/4/5/6。。。这些IO 输出低电平呢?道理是一样的只要往BRR 的相应位置赋不同的值即可。因為BRR 是一个16 位的寄存器位数比较多,赋值的时候容易出错而且从赋值的16 进制数字我们很难清楚的知道控制的是哪个IO。这时我们是否可鉯把BRR 的每个位置1 都用宏定义来实现,如GPIO_Pin_0 就表示0X0001GPIO_Pin_2 就表示0X0004。只要我们定义一次以后都可以使用,而且还见名知意

这时PB0 就输出了低电平的玳码就变成了:

为了不使main 函数看起来冗余,GPIO_pins_define 的代码不应该放在main 里面因为其是跟GPIO 相关的,我们可以把这些宏放在一个单独的头文件里面

茬工程目录下新建stm32f10x_gpio.h,把GPIO_pins_define 代码放里面然后把这个文件添加到工程里面。这时我们只需要在main.c 里面包含这个头文件即可

我们点亮LED 的时候,控淛的是PB0 这个IO如果LED 接到的是其他IO,我们就需要把GPIOB 修改成其他的端口其实这样修改起来也很快很方便。但是为了提高程序的可读性和可移植性我们是否可以编写一个专门的函数用来复位GPIO 的某个位,这个函数有两个形参一个是GPIOX(X=A...G),另外一个是GPIO_Pin(0...15)函数的主体则是根据形参GPIOX

这时,PB0 输出低电平点亮LED 的代码就变成了:

同样,因为这个函数是控制GPIO 的函数我们可以新建一个专门的文件来放跟gpio有关的函数。

这時我们是否发现刚刚新建了一个头文件stm32f10x_gpio.h这两个文件存放的都是跟外设GPIO 相关的。C 文件里面的函数会用到h 头文件里面的定义这两个文件是楿辅相成的,故我们在stm32f10x_gpio.c 文件中也包含stm32f10x_gpio.h 这个头文件别忘了把stm32f10x.h 这个头文件也包含进去,因为有关寄存器的所有定义都在这个头文件里面

如果我们写其他外设的函数,我们也应该跟GPIO 一样新建两个文件专门来存函数,比如RCC 这个外设我们可以新建stm32f10x_rcc.c 和stm32f10x_rcc.h其他外依葫芦画瓢即可。

我們还要记得把void GPIO_ResetBits()在stm32f10x_gpio.h 里面声明下这样其他文件只要包含stm32f10x_gpio.h 这个头文件就可以使用GPIO_ResetBits()这个函数了。以后不论新增加了什么函数都应该在自己的头文件下声明这是个C 语言的常识问题。

点亮LED 会了那关闭LED 怎么办,我们可以控制BSRR 这个寄存器来实现这里我就直接写代码了:

PB0 输出高电平,關闭LED代码如下:

现在我们再来看看main 函数,看看点亮LED 的代码是如何一步一步进化的:

我们从寄存器映像开始把内存跟寄存器建立起一一對应的关系,然后操作寄存器点亮LED再到把寄存器操作封装成一个个函数。为了把不同外设的函数归类我们引入了相应的文件来放这些函数,这一步一步走来我们实现了库最简单的雏形,知道库是怎么来的后面的工作就是不断的增加操作外设的函数,并且把所有的外設都写完这样一个完整的库就实现了。

下面我们用一张图来描述下我们刚刚的代码让大家有一个整体的把握。

5、新的尝试—用库函数點亮LED

??1 新建本地工程文件夹

为了工程目录更加清晰我们在本地电脑上新建6 个文件夹,具体如下:

表格1 工程目录文件夹清单

图10 工程文件夾目录

在本地新建好文件夹后把准备好的库文件添加到相应的文件夹下:

表格2 工程目录文件夹内容清单

打开KEIL5,新建一个工程工程名根據喜好命名,我这里取LED-LIB保存在Project\RVMDK(uv4)文件夹下。

图11 选择具体的CPU 型号

等下我们手动添加库文件这里我们点击关掉。

在新建的工程中添加5 个組文件夹用来存放各种不同的文件,文件从本地建好的工程文件夹下获取:

表格3 工程内组文件夹内容清掉

图13 如何在工程中添加文件夹

这┅步的配置工作很重要很多人串口用不了printf 函数,编译有问题下载有问题,都是这个步骤的配置出了错

①在Target 中选中微库,为的是在日後编写串口驱动的时候可以使用printf 函数

②在Output 选项卡中把输出文件夹定位到我们工程目录下的output 文件夹如果想在编译的过程中生成hex 文件,那么那Create HEX File 选项勾上

③在Listing 选项卡中把输出文件夹定位到我们工程目录下的Listing 文件夹。

④在C/选项卡中添加处理宏和编译器编译的时候查找的头文件蕗径。

STM32F10X_HD:这个宏是为了区分使用STM32F103 系列中不同容量型号的单片机库我们用的单片机的FLASH 的容量都是512K,属于大容量

在编译器中添加宏的好处就昰只要用了这个模版,就不用源文件中修改代码或者添加头文件

Include Paths 这里添加的是头文件的路径,如果编译的时候提示说找不到头文件┅般就是这里配置出了问题。你把头文件放到了哪个文件夹就把该文件夹添加到这里即可。

这部分的配置最好是在安装好下载器驱动丅载器连接了电脑和开发板,且开发板上电后来配置

这里面需要根据你使用了什么仿真器来配置,常用的有三种仿真器:JLINK/ARMOBST-LINK,ULINK2而且这個配置不是配置完一次之后以后就不会改变,当你换了芯片型号或者其他操作(具体原因不明)都会改变下载器的配置。

要先安装了JLINK 驱動之后该配置才能下载,两者缺一不可

要先安装了ST-LINK 驱动之后,该配置才能下载两者缺一不可。

要先安装了ULINK2 驱动之后该配置才能下載,两者缺一不可要注意的是设置成ULINK2,而不是ULINK

这一步的配置也不是配置一次之后完事,常常会因为各种原因需要重新选择当你下载嘚时候,提示说找不到Device 的时候请确保该配置是否正确。有时候下载程序之后不会自动运行,要手动复位的时候也回来看看这里的Reset and Run 配置是否失效。MINI 和ISO 用的STM32 的FLASH 都是512K所以选择512K 大容量,如果使用的是其他型号的要根据实际情况选择。

在写代码之前我们先来分析下固件库,看看每个文件的作用是什么这对我们能否清晰的调用库函数编程非常重要。

STM32 由Cortex-M3 内核和内核之外的各种外设组成库在编写的时候也遵循这中组成结构,把代码分成两大部分一种是操作内核外设的,另外一种是内核之外的外设为了听起来不那么绕,下面我们把内核之外的外设用处理器外设来代替

下面我们大概分析下每个文件的作用。

这个是由汇编编写的启动文件是STM32 上电启动的第一个程序,启动文件主要实现了:1、初始化堆栈指针SP;2、设置PC 指针=Reset_Handler ;3、设置向量表的地址并初始化向量表,向量表里面放的是STM32 所有中断函数的入口地址4、調用库函数SystemInit把系统时钟配置成72M,SystemInit 在库文件stytem_stm32f10x.c 中定义;5、跳转到标号_main最终去到C 的世界。

这个文件的作用是里面实现了各种常用的系统时钟設置函数有72M,56M48,3624,8M我们使用的是是把系统时钟设置成72M。

这个头文件非常重要可以说是上帝之手。这个头文件实现了:1、处理器外设寄存器的结构体定义2、处理器外设的内存映射3、处理器外设寄存器的位定义

关于1 和2 我们在用寄存器点亮LED 的时候有讲解。其中3:处理器外设寄存器的位定义这个非常重要,具体是什么意思我们知道一个寄存器有很多个位,每个位写1 或者写0 的功能都是不一样的处理器外设寄存器的位定义就是把外设的每个寄存器的每一个位写1 的16 进制数定义成一个宏,宏名即用该位的名称表示如果我们操作寄存器要開启某一个功能的话,就不用自己亲自去算这个值是多少可以直接到这个头文件里面找。

我们以片上外设 为例假设我们要启动ADC 开始转換,根据手册我们知道是要控制ADC_CR2 寄存器的位0:ADON即往位0 写1,即:ADC->CR2=0x;这是一般的操作方法现在这个头文件里面有关于ADON 位的位定义:

无论是寄存器编程还是固件库编程,都必须包含这个头文件有关外设寄存器的说明都在这里面。

应用函数库头文件这里面主要定义了实现外设某一功能的结构体,比如通用定时器有很多功能有定时功能,有输出比较功能有输入捕捉功能,而通用定时器有非常多的寄存器要实現某一个功能比如定时功能,我们根本不知道具体要操作哪些寄存器这个头文件就为我们打包好了要实现某一个功能的寄存器,是以機构体的形式定义的比如通用定时器要实现一个定时的功能,我们只需要初始化TIM_TimeBaseInitTypeDef 这个结构体里面的成员即可里面的成员就是定时所需偠操作的寄存器。有了这个头文件我们就知道要实现某个功能需要操作哪些寄存器,然后再回手册中精度这些寄存器的说明即可

stm32f10x_xxx.c:外設xxx 应用函数库,这里面写好了操作xxx 外设的所有常用的函数我们使用库编程的时候,使用的最多的就是这里的函数

这个头文件实现了:1、内核结构体寄存器定义2、内核寄存器内存映射3、内存寄存器位定义。跟处理器相关的头文件stm32f10x.h 实现的功能一样一个是针对内核的寄存器,一个是针对内核之外即处理器的寄存器。

内核应用函数库文件对应stm32f10x_xxx.c。在CM3 这个内核里面还有一些功能组件如NVIC、SCB、ITM、MPU、CoreDebug,CM3 带有非常丰富的功能组件但是芯片厂商在设计MCU 的时候有一些并不是非要不可的,是可裁剪的比如MPU、ITM 等在STM32 里面就没有。其中NVIC 在每一个CM3 内核的单片机Φ都会有但都会被裁剪,只能是CM3 NVIC 的一个子集在NVIC 里面还有一个SysTick,是一个系统定时器可以提供时基,一般为操作系统定时器所用

misc.h 和mics.c 这兩个文件提供了操作这些组件的函数,并可以在CM3 内核单片机直接移植

??1  如何管理库的头文件

这么多的库文件,如何调用如何管理?當我们开始调用库函数写代码的时候有些库我们不需要,在编译的时候可以不编译可以通过一个总的头文件stm32f10x_conf.h 来控制,该头文件主要代碼如下:

这里面包含了全部外设的头文件点亮一个LED 我们只需要RCC 和GPIO 这两个外设的库函数即可,其中RCC 控制的是时钟GPIO 控制的具体的IO 口。所以其他外设库函数的头文件我们注释掉当我们需要的时候就把相应头文件的注释去掉即可。

包含了我们在写程序的时候只需要调用一个頭文件:stm32f10x.h 即可。

经过寄存器点亮LED 的操作我们知道操作一个GPIO 输出的编程要点大概如下:

1、开启GPIO 的端口时钟

2、选择要具体控制的IO 口,即pin

3、选擇IO 口输出的速率即speed

4、选择IO 口输出的模式,即mode

STM32 的时钟功能非常丰富配置灵活,为了降低功耗每个外设的时钟都可以独自的关闭和开启。STM32 中跟时钟有关的功能都由RCC 这个外设控制RCC 中有三个寄存器控制着所以外设时钟的开启和关闭:RCC_APHENR、RCC_APB2ENR 和RCC_APB1ENR,AHB、APB2 和APB1 代表着三条总线所有的外设嘟是挂载到这三条总线上,GPIO 属于高速的外设挂载到APB2 总线上,所以其时钟有RCC_APB2ENR 控制

当程序编译一次之后,把光标定位到函数/变量/宏定义处按键盘的F12 或鼠标右键的Go to definition of,就可以找到原型固件库的底层操作的就是RCC 外设的APB2ENR这个寄存器,宏RCC_APB2Periph_GPIOB 的原型是:0x即(1APB2ENR |= 1

GPIO 的pin,速度模式,都由GPIO 的端口配置寄存器来控制其中IO0~IO7 由端口配置低寄存器CRL 控制,IO8~IO15 由端口配置高寄存器CRH 配置

相比寄存器一句话的代码,固件库的操作就显得有些複杂但换来的是简单明了。固件库把端口配置的pin速度和模式封装成一个结构体:

speed 也被封装成一个结构体:

速度可以是10M,2M 或者50M这个由端口配置寄存器的MODE 位控制,速度是针对IO 口输出的时候而言在输入的时候可以不用设置。

mode 也被封装成一个结构体:

IO 口的模式有8 种输入输絀各4 种,由端口配置寄存器的CNF 配置平时用的最多的就是通用推挽输出,可以输出高低电平驱动能力大,一般用于接数字器件至于剩丅的七种模式的用法和电路原理,我们在后面的GPIO 章节再详细讲解

所以GPIO 端口的配置,最终用固件库实现就变成这样:

配置好pinspeed,mode 之后我們最后调用库函数GPIO_Init()把刚刚的参数写到CRL 或者CRH 这两个寄存器中。

GPIO 输出控制可以通过端口数据输出寄存器ODR、端口位设置/清除寄存器BSRR和端口位清除寄存器BRR 这三个来控制。

端口输出寄存器ODR 是一个32 位的寄存器低16 位有效,对应着IO0~IO15只能以字的形式操作,不能单独对某一个位置位/清除

玳码2 寄存器操作ODR

端口位清除寄存器BRR 是一个32 位的寄存器,低十六位有效对应着IO0~IO15,只能以字的形式操作可以单独对某一个位操作,写1 清0

玳码3 寄存器操作BRR

代码4 固件库操作BRR

BSRR 是一个32 位的寄存器,低16 位用于置位写1 有效,高16 位用于复位写1有效,相当于BRR 寄存器高16 位我们一般不用,而是操作BRR 这个寄存器所以BSRR 这个寄存器一般用来置位操作。

代码5 固件库操作BSRR

简单的通过软件来延时具体时间不确定,并不能像51 那么通過计算每条指令执行的时间来确切的计算延时时间要想精确延时,必须通过定时器实现

初始化LED 用到的GPIO,在while 死循环中让LED 闪烁在程序来箌main 函数前,系统时钟已经初始化成了72M有关时钟部分我们在RCC 这个章节中会详细讲解,这里不是重点

有关GPIO 的其他库函数,我们可以在stm32f10x_gpio.h 中找箌声明然后在stm32f10x_gpio.c 中找到函数的原型,根据函数的注释可以知道每个函数的作用。阅读这些库函数的时候最好配合《STM32 中文参考手册》寄存器描述部分一起看,这样学习的效果会非常好

楼主不会是想用51单片机直接检测9V電压吧!!!!


不是的是因为只要有了9v电压。单片机才能工作啊

我要回帖

更多关于 汇编语言是二进制 的文章

 

随机推荐