使用寄存器点亮LED—STM32的寄存器映射代码讲解

发布时间:2023-02-15  

本篇文章中,我们以实例讲解如何通过控制寄存器来点亮LED 灯。

建立一个新工程,见图1,可看到一共有三个文件,分别startup_stm32f10x_hd.s 、stm32f10x.h 以及main.c,下面我们对这三个文件进行讲解。

图 1 工程文件结构


硬件连接

在本教程中STM32 芯片与LED 灯的连接见图2,这是一个RGB 灯,里面由红蓝绿三个小灯构成,使用PWM控制时可以混合成256 不同的颜色。

图2 LED 灯电路连接图



图中从3 个LED 灯的阳极引出连接到3.3V 电源,阴极各经过1 个限流电阻引入至STM32 的3 个GPIO 引脚中,所以我们只要控制这三个引脚输出高低电平,即可控制其所连接LED 灯的亮灭。如果您的实验板STM32 连接到LED 灯的引脚或极性不一样,只需要修改程序到对应的GPIO 引脚即可,工作原理都是一样的。

我们的目标是把GPIO 的引脚设置成推挽输出模式并且默认下拉,输出低电平,这样就能让LED 灯亮起来了。


启动文件

名为“startup_stm32f10x_hd.s”的文件,它里边使用汇编语言写好了基本程序,当STM32 芯片上电启动的时候,首先会执行这里的汇编程序,从而建立起C 语言的运行环境,所以我们把这个文件称为启动文件。

startup_stm32f10x_hd.s 文件由官方提供,一般有需要也是在官方的基础上修改,不会自己完全重写。该文件从 ST 固件库里面找到,找到该文件后把启动文件添加到工程里面即可。不同型号的芯片以及不同编译环境下使用的汇编文件是不一样的,但功能相同。

对于启动文件这部分我们主要总结它的功能,不详解讲解里面的代码,其功能如下:

  • 初始化堆栈指针SP;

  • 初始化程序计数器指针PC;

  • 设置堆、栈的大小;

  • 初始化中断向量表;

  • 配置外部SRAM 作为数据存储器(这个由用户配置,一般的开发板可没有外部SRAM);

  • 调用SystemIni() 函数配置STM32 的系统时;

  • 设置C 库的分支入口“__main”(最终用来调用main 函数);


先去除繁枝细节,挑重点的讲,主要理解最后两点,在启动文件中有一段复位后立即执行的程序,代码见代码清单1。在实际工程中阅读时,可使用编辑器的搜索(Ctrl+F)功能查找这段代码在文件中的位置,搜索Reset_Handler 即可找到。

代码清单1 复位后执行的程序


开头的是程序注释,在汇编里面注释用的是“;”,相当于 C 语言的“//”注释符

第二行是定义了一个子程序:Reset_Handler。PROC 是子程序定义伪指令。这里就相当于C 语言里定义了一个函数,函数名为Reset_Handler。

第三行 EXPORT 表示 Reset_Handler 这个子程序可供其他模块调用。相当于C 语言的函数声明。关键字[WEAK] 表示弱定义,如果编译器发现在别处定义了同名的函数,则在链接时用别处的地址进行链接,如果其它地方没有定义,编译器也不报错,以此处地址进行链接。

第四行和第五行 IMPORT 说明 SystemInit 和__main 这两个标号在其他文件,在链接的时候需要到其他文件去寻找。相当于C 语言中,从其它文件引入函数声明。以便下面对外部函数进行调用。

SystemInit 需要由我们自己实现,即我们要编写一个具有该名称的函数,用来初始化STM32 芯片的时钟,一般包括初始化AHB、APB 等各总线的时钟,需要经过一系列的配置STM32 才能达到稳定运行的状态。其实这个函数在固件库里面有提供,官方已经为我们写好。

__main 其实不是我们定义的(不要与C 语言中的main 函数混淆),这是一个C 库函数,当编译器编译时,只要遇到这个标号就会定义这个函数,该函数的主要功能是:负责初始化栈、堆,配置系统环境,并在函数的最后调用用户编写的 main 函数,从此来到 C 的世界。

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

第七行程序跳转到 R0 中的地址执行程序,即执行SystemInit 函数的内容。

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

第九行程序跳转到 R0 中的地址执行程序,即执行__main 函数,执行完毕之后就去到我们熟知的 C 世界,进入main 函数。

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

总之,看完这段代码后,了解到如下内容即可:我们需要在外部定义一个SystemInit函数设置STM32 的时钟;STM32 上电后,会执行SystemInit 函数,最后执行我们C 语言中的main 函数。


stm32f10x.h 文件

看完启动文件,那我们立即写SystemInit 和main 函数吧?别着急,定义好了SystemInit 函数和main 我们又能写什么内容?连接LED 灯的GPIO 引脚,是要通过读写寄存器来控制的,就这样空着手,如何控制寄存器。我们知道寄存器就是给一个已经分配好地址的特殊的内存空间取的一个别名,这个特殊的内存空间可以通过指针来操作。在编程之前我们要先实现寄存器映射,有关寄存器映射的代码都统一写在stm32f10x.h 文件中,见代码清单2。

代码清单2 外设地址定义


使用GPIO 外设必须开启它的时钟。


此时直接编译的话,会出现如下错误:

“Error: L6218E: Undefined symbol SystemInit (referred from startup_stm32f10x.o)”

错误提示SystemInit 没有定义。从分析启动文件时我们知道,Reset_Handler 调用了该函数用来初始化SMT32 系统时钟,为了简单起见,我们在 main 文件里面定义一个SystemInit 空函数,什么也不做,为的是骗过编译器,把这个错误去掉。关于配置系统时钟我们在后面再写。当我们不配置系统时钟时,STM32 会把HSI 当作系统时钟,HSI=8M,由芯片内部的振荡器提供。我们在main 中添加如下函数:


这时再编译就没有错了, 完美解决。还有一个方法就是在启动文件中把有关SystemInit 的代码注释掉也可以,见代码清单3。

代码清单3 注释掉启动文件中调用SystemInit的代码


接下来在main 函数中添加代码,实现我们的点灯之旅

GPIO 模式

首先我们把连接到LED 灯的GPIO 引脚PB0 配置成输出模式,即配置GPIO 的端口配置低寄存器CRL,见图 8-9。CRL 中包含0-7 号引脚,每个引脚占用4 个寄存器位。MODE 位用来配置输出的速度,CNF 位用来配置各种输入输出模式。在这里我们把PB0 配置为通用推挽输出,输出的速度为10M,具体见代码清单4。

代码清单4 配置输出模式

在代码中,我们先把控制PB0 的端口位清0,然后再向它赋值“0001 b”,从而使GPIOB0 引脚设置成输出模式,速度为10M。

代码中使用了“&=~”、“|=”这种操作方法是为了避免影响到寄存器中的其它位,因为寄存器不能按位读写,假如我们直接给CRL 寄存器赋值:

1 GPIOB_CRL = 0x0000001;

这时CRL 的的低4 位被设置成“0001”输出模式,但其它GPIO 引脚就有意见了,因为其它引脚的MODER 位都已被设置成输入模式。


控制引脚输出电平

在输出模式时,对端口位设置/清除寄存器BSRR 寄存器、端口位清除寄存器BRR 和ODR 寄存器写入参数即可控制引脚的电平状态,其中操作BSRR 和BRR 最终影响的都是ODR 寄存器,然后再通过ODR 寄存器的输出来控制GPIO。为了一步到位,我们在这里直接操作ODR 寄存器来控制GPIO 的电平。具体见代码清单5。

代码清单5 控制引脚输出电平


图3 GPIO 端口控制低寄存器CRL


图4 GPIO 数据输出寄存器ODR


开启外设时钟

设置完GPIO的引脚,控制电平输出,以为现在总算可以点亮 LED 了吧,其实还差最后一步。由于STM32 的 外设很多,为了降低功耗,每个外设都对应着一个时钟,在芯片刚上电的时候这些时钟都是被关闭的,如果想要外设工作,必须把相应的时钟打开。

STM32 的所有外设的时钟由一个专门的外设来管理,叫 RCC(reset and clockcontrol),

所有的 GPIO都挂载到 APB2 总线上,具体的时钟由APB2 外设时钟使能寄存器(RCC_APB2ENR)来控制,具体见代码清单6。

代码清单6 开启端口时钟

图5 APB2 外设时钟使能寄存器



水到渠成

开启时钟,配置引脚模式,控制电平,经过这三步,我们总算可以控制一个 LED 了。现在我们完整组织下用 STM32 控制一个 LED 的代码,见代码清单7。


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    另存为(A)...”保存文件类型中不要选择“文本文件”,应该选择“所有文件”,这样就能获得*.hex文件了。如何精确计算延时子程序的执行时间?    汇编语言的一大优势就是能够精确控制程序的执行时间,这在......
    S3C2440 用C语言点亮LED;一、从汇编到C函数 1、设置栈 为什么从汇编调用C函数需要设置栈?1、因为arm汇编调用C函数参数要遵循APCS规则。即参数小于等于4个可以用R0-R3来传......
    : @******************************************************************************      @ 功能:LED灯程序点亮LED1-4......
    51单片机实现最小系统的原理和电路与编程设计说明;本课以AT89S51单片机最小系统来教你如何实现单片机编程,该程序驱动单片机P1.7端口上的发光二极管不停闪烁,系统程序用keil 汇编语言......
    是否溢出等。 无论是汇编语言还是C51语言编写的程序都不是单片机直接运行的程序,只有机器码程序单片机才能直接运行,对于汇编语言程序需要通过汇编程序汇编成机器码程序,对于 C51程序需要通过编译器程序编译成机器码程序......
    告诉你怎么编写一个最简单的单片机程序;在汇编语言中,让某个端口输出高电平或低电平都有专用的语句,以P1.0端口为例: 让该端口输出高电平的语句是: SETB P1.0 让该......
    S3C2440③ | GPIO实验;实验1 —— 点亮一个LED 1.看原理图确定硬件如何连接 原理图中表示了芯片控制LED的硬件电路以及芯片的引脚与LED如何连接。 2. 看主......
    而言,具有更小代码量和更快的运行速度。(可以参看文章:用 汇编 和 C语言灯程序有什么不一样?) 因为单片机的RAM和Flash资源相比较小,运行速度也相对较低,所以,你会发现:很少......
    = 0xfe;”就足矣点亮一个LED灯,再看标准库的一大堆.h .c文件,倒腾了一上午一编译好几十个error(s)  warning (s),看着就头皮发麻,忙活了半天LED灯也没点亮。。。 用......
    只要将LED1~LED8依次点亮、熄灭,依始类推,8只LED变会一亮一暗的做流水灯了。   实现8个LED流水灯程序用中文表示为:P1.0低、延时、P1.0高、P1.1低、延时、P1.1高、P1.2低......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>