STM32

STM32是STMicroelectronics(意法半导体)推出的一系列基于ARM Cortex-M内核的32位微控制器(MCU)产品。这些微控制器提供了广泛的产品系列,覆盖了多种不同的性能和功能需求,适用于各种应用领域,包括工业控制、汽车电子、消费类电子、医疗设备等。

STM32系列微控制器以其高性能、低功耗、丰富的外设接口和灵活的开发工具而闻名。它们通常具有丰富的存储器、多种通信接口(如UART、SPI、I2C、CAN等)、模拟数字转换器(ADC)、定时器、PWM输出等功能,以满足不同应用场景下的需求。

STM32微控制器通常使用标准的ARM Cortex-M内核,包括Cortex-M0、M0+、M3、M4和M7等,这些内核具有不同的性能和功耗特性,可根据具体应用的需求进行选择。此外,STM32系列还提供了多种封装和引脚配置,以满足不同尺寸和集成度的要求。

STMicroelectronics为STM32系列提供了丰富的开发工具和支持资源,包括基于ARM开发环境的集成开发环境(IDE)、调试器、评估板和参考设计等。这些工具和资源有助于开发人员快速开发和部署他们的应用,并提供了全面的技术支持和文档资料,帮助用户充分发挥STM32微控制器的性能和功能优势。

学习STM32开发,肯定少不了debug调试这一步骤。那么,本文带你了解一下这个调试相关的知识。

本文以STM32F1、Cortex-M3为例(其它系列芯片/内核的原理类似)讲述一下调试接口相关的知识。

1、概述

在STM32中,有很多调试组件,使用它们可以执行各种调试功能,包括断点、数据观察点、 闪存地址重载以及各种跟踪。

STM32F1使用Cortex™-M3内核,该内核内含硬件调试模块,支持复杂的调试操作。

硬件调试模块允许内核在取指(指令断点)或访问数据(数据断点)时停止。内核停止时,内核的内部状态和系统的外部状态都是可以查询的。完成查询后,内核和外设可以被复原,程序将继续执行。

当STM32F10x微控制器连接到调试器并开始调试时,调试器将使用内核的硬件调试模块进行调试操作。

2、调试框图

STM32F1和Cortex™-M3的调试框图:

“STM32(Cortex-M)内核DEBUG调试接口知识"

提示:Cortex™-M3内核内含的硬件调试模块是ARM CoreSight开发工具集的子集。

ARM Cortex™-M3内核提供集成的片上调试功能。它由以下部分组成:

  • SWJ-DP:串行/JTAG调试端口

  • AHP-AP:AHB访问端口

  • ITM:执行跟踪单元

  • FPB:闪存指令断点

  • DWT:数据触发

  • TPUI:跟踪单元接口(仅较大封装的芯片支持)

  • ETM:嵌入式跟踪微单元(在较大的封装上才有支持此功能的引脚),专用于STM32F1的调试特性

  • 灵活的调试引脚分配

  • MCU调试盒(支持低电源模式,控制外设时钟等)

3、调试接口

STM32支持两种调试接口:

  • 串行接口

  • JTAG调试接口

STM32的5个普通I/O口可用作SWJ-DP(串行/JTAG调试)接口引脚:

“STM32(Cortex-M)内核DEBUG调试接口知识"

4、SWJ调试端口(serial wire and JTAG)

STM32内核集成了串行/JTAG调试接口(SWJ-DP)。这是标准的ARM CoreSight调试接口,包括JTAG-DP接口(5个引脚)和SW-DP接口(2个引脚)。

1)JTAG调试接口(JTAG-DP)为AHP-AP模块提供5针标准JTAG接口。

2)串行调试接口(SW-DP)为AHP-AP模块提供2针(时钟+数据)接口。

在SWJ-DP接口中, SW-DP接口的2个引脚和JTAG接口的5个引脚中的一些是复用的。

SWJ调试端口:

“STM32(Cortex-M)内核DEBUG调试接口知识

上面的图显示异步跟踪输出脚(TRACESWO)和TDO是复用的。因此异步跟踪功能只能在SWDP调试接口上实现,不能在JTAG-DP调试接口上实现。

JTAG-DP和SW-DP切换的机制

JTAG调试接口是默认的调试接口。如果调试器想要切换到SW-DP,必须在TMS/TCK上输出一指定的JTAG序列(分别映射到SWDIO和SWCLK),该序列禁止JTAG-DP,并激活SW-DP。该方法可以只通过SWCLK和SWDIO两个引脚来激活SW-DP接口。

指定的序列是:

1. 输出超过50个TCK周期的TMS(SWDIO)= 1信号

2. 输出16个TMS(SWDIO)信号 0111100111100111 (MSB)

3. 输出超过50个TCK周期的TMS(SWDIO)= 1信号

5、JTAG脚上的内部上拉和下拉

保证JTAG的输入引脚不是悬空的是非常必要的,因为他们直接连接到D触发器控制着调试模式。必须特别注意SWCLK/TCK引脚,因为他们直接连接到一些D触发器的时钟端。

为了避免任何未受控制的I/O电平, STM32在JTAG输入脚上嵌入了内部上拉和下拉。

  • JINTRST:内部上拉
  • JTDI:内部上拉
  • JTMS/SWDIO:内部上拉
  • TCK/SWCLK:内部下拉

一旦JTAG I/O被用户代码释放, GPIO控制器再次取得控制。这些I/O口的状态将恢复到复位时的状态。

  • JNTRST:带上拉的输入
  • JTDI:带上拉的输入
  • JTMS/SWDIO:带上拉的输入
  • JICK/SWCLK:带下拉的输入
  • JTDO:浮动输入

软件可以把这些I/O口作为普通的I/O口使用。

6、利用串行接口并释放不用的调试脚作为普通I/O口

为了利用串行调试接口来释放一些普通I/O口,用户软件必须在复位后设置SWJ_CFG=010,从而释放PA15, PB3和PB4用做普通I/O口。

在调试时,调试器进行以下操作:

  • 在系统复位时,所有SWJ引脚被分配为专用引脚(JTAG-DP + SW-DP)。

  • 在系统复位状态下,调试器发送指定JTAG序列,从JTAG-DP切换到SW-DP。

  • 仍然在系统复位状态下,调试器在复位地址处设置断点

  • 释放复位信号,内核停止在复位地址处。

  • 从这里开始,所有的调试通信将使用SW-DP接口,其他JTAG引脚可以由用户代码改配为普通I/O口。

提示:这个地方就是需要大家配置相关的引脚。

参看文献:

1.Cortex™-M3(r1p1版)技术参考手册(TRM)

2.ARM调试接口V5

3.ARM CoreSight 开发工具集(r1p0版)技术参考手册

来源:嵌入式专栏
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 2277

为了防止大家的程序不被剽窃,本文给大家分享单片机加密的方法。

常见加密方法

程序写保护

这种方法是最常见,也是最简单的一种。现在的MUC基本都有写保护功能,但是这种容易被人破解。

烧断数据总线

这个方法听起来不错,但有损坏的风险,同样也能破解。

软件加密

是一些防止别人读懂程序的方法,单一的这种方法不能防止别人全盘复制,须配合其他的加密算法。

添加外部硬件电路的加密方法

这个方法效果看起来比较好,但会增加成本。

芯片打磨改型

这个方法改了型号能误导,但同时也增加成本,解密者一般也能分析出来。

通过通过联网加序列号加密

通过连接网络,在你的MCU中生成一个唯一的随机长序列号,并加入复杂的特种算法,或加入你们重新编码的企业信息在里面,每个芯片内不同,复制者只能复制到一个序列号。

通过MCU唯一的标识加密

以前很多MCU没有唯一标识码,现在的很多MCU都具有唯一标识码了。

这个方法比较好,简单省事,能很好的防止复制。

读保护 + 唯一ID加密

使用读保护 + 唯一ID的加密是最常用的一种方法,也是推荐大家使用的一种方法。

唯一ID

现在正规的芯片,每颗出厂的时候都带了一个唯一标识码,这个号码是唯一不重复的,比如STM32的就使用96位作为唯一ID。

和我们每个人的身份证号码一样,现在刚出生的婴儿,上户的时候就给他一个身份证号,那么每个芯片一生产出来,也就具备了这个身份证号。

加密原理

读保护就不用说了,增加被破解难度。

使用唯一ID加密的方法很多,这里说一种简单的方法:出厂时程序读取唯一ID并保存在一个位置,以后程序执行之前,要读取并匹配这个唯一ID,一致才执行程序。

当然,这种方法是最基础的原理,但也存在被破解的风险。所以,存储的数据,以及读取验证这两个地方需要进一步添加一些算法。

这样操作之后,即使别人读取了你的程序,也是无法正常执行。

来源: 电子工程世界-论坛
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 448

Arduino和STM32性能对比究竟谁更厉害呢?很多电子爱好者面对Arduino和STM32时都会有个两难的决定,不知道如何选择使用。Arduino一直处在火热状态,但是STM32接口多性能强,速度也较快。

Arduino和STM32各自的特点

Arduino:

“STM32和Arduino对比,谁更厉害?"

Arduino更倾向于创意,它弱化了具体的硬件的操作,它的函数和语法都非常简单,而且非常“傻瓜化”。

大部分Arduino的主控是AVR单片机,Arduino的优势还是代码封装性高,所需语句少,降低软件的开发难度。

Arduino上手比较容易,只要懂一点点硬件和C++就能开发。

Arduino大多数功能都有做好了的库,所以使用起来很简单,但是对于稍微复杂点的功能可控性较差。

STM32:

“STM32和Arduino对比,谁更厉害?"

对于有计算或控制要求的,STM32是更好选择。STM32如果买开发板的话就要从硬件底层学起,掌握它的各种细节。

STM32更注重工程实际,事实上工厂中很多简单的仪器什么温控仪,什么普通的电机控制器,什么低档PLC,还有民用中的一些玩具,游戏手柄、有线键盘鼠标等外设,高校食堂里刷卡的pos机等等其实用得很多。

STM32主要是给专业开发人员做产品用的,需要一定的专业知识,但同时编写代码实现功能也相对复杂。比如串口输出一个简单的字符串,对于Arduino可能从新建工程开始,10行代码就能实现,但是如果使用STM32的开发工具比如Keil,可能需要上百行代码甚至更多。

Arduino和STM32对比

Arduino有很多个版本,特性也并不一致,如果拿Arduino UNO R3这个最普遍的来说的话,它的控制器是AVR 138单片机,性能上和STM32不在一个水平上,该款在某种程度上和宏晶的STC12系列差不多(仅在运算上),Arduino MEGA 2560 这款算是MCU版的Arduino中的老大了,接口丰富,应该和STM32F1系列不相上下,但是功耗挺大,芯片发热明显,而同款封装的STM32F1ZET6则仅有微热,环境温度低的时候甚至感觉不到有温度。

根据两者的不同特性我们可以按照不同应用来比较:

在学习容易程度方面:Arduino相对于STM32容易很多。

要求的基本编程思想:Arduino:面向对象的类C++、类Java为主,它的语言基于C但是又有些四不像。而STM32则以正统的C语言为主要开发语言。

学习后获得的知识:Arduino弱于STM32。

因为Arduino封装了很多底层操作。除非自己尝试编写Arduino以及配套电路模块的库,否则还只是停留在基础的编程上。

开源程度上:用STM32做出来的东西想开源就开源,不想开源可以什么都不公布。

外围电路搭建难易程度:Arduino远大于STM32。

可拓展性:Arduino与STM32相当,Arduino基本上把控制器的大部分引脚都印出来了。

成本,假设做出具有相同功能的东西:Arduino成本高于STM32。

总结一下

如果还不知道怎么选择,这里给出选择建议:

  • 如果你是对编程语言了解不深的大学层次以下的普通学生,建议上手Arduino

  • 如果C功底薄弱上来就搞STM32你会很快有放弃的念头

  • 如果只是为了就业而学习,果断STM32类单片机

  • 如果只是为了好玩而学习,本身非电子类专业,没有信心,建议Arduino

  • 如果编程功底好,建议STM32。搞定之后你看看Arduino开源社区做的东西,用STM32轻轻松松搞定

  • 当然如果有能力,两者都接触接触,一般地一周不到Arduino的基本特性你就掌握了,以后有需要的话,可以很自如地移植Arduino代码到STM32等MCU平台上

其实这两者其实针对的方向略有不同,Arduino一般电子爱好者和DIY的选择,而STM32往往用于实际产品的研发制造。但是现在Arduino系列也能支持STM32的芯片,通过先下载Arduino固件,后就能使用Arduino的开发环境进行编程了。根据自己项目类型和需求选择适合自己的才最重要。

来源:STM32嵌入式开发
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 176

基于STM32分析栈、堆、全局区、常量区、代码区、RAM、ROM

cathy的头像

在一个STM32程序代码中,从内存高地址到内存低地址,依次分布着栈区、堆区、全局区(静态区)、常量区、代码区,其中全局区中高地址分布着.bss段,低地址分布着.data段。

一、片内RAM用完了?

曾有两个不同的STM32用户反馈了相似的问题,他们在对STM32F7Cube库里的工程例程进行编译时,发现了一个令人很不解的事。编译的结果提示芯片内的RAM几乎都耗光了。但以他们对工程基本功能的了解,按理说不应该那样,想知道是怎么回事。

具体情况是这样的:他们先在STM32CubeF7的固件库里打开了一个有关lwip应用的工程示例。比如下面目录处的一个工程,使用ARM MDK进行编译。

\STM32Cube_FW_F7_V1.15.0\Projects\STM32746G-Discovery\Applications\LwIP\LwIP_HTTP_Server_Netconn_RTOS\MDK-ARM。

结果发现编译结果提示片内RAM的空间已经基本用完了。但感觉应该用不了那么多RAM内存。客户还想添加自己的其它用户程序,还需使用RAM呢。

打开相应软件工程,使用ARM MDK进行编译,发现编译结果跟客户反馈的一致。

“基于KEIL

从编译结果来看,感觉RAM真的被用去了320多K,那RAM用到哪里去了呢?打开对应的MAP文件进一步查看,可得到如下数据。

“基于KEIL

先看看芯片内部RAM情况。目前使用的芯片是STM32F746NG,查看其数据手册可知其内部系统RAM容量为320KB【1KB=1024B】,分别由如下三块RAM区域组成,各区域容量及地址范围如下:

“基于KEIL

结合编译结果和上面数据信息来看,貌似RAM真的用完了。既然这样,只好硬着头皮继续查看MAP文件的其它细节,看看RAM到底消耗在哪些地方去了。后来发现在某个地址段有个巨大的PAD补丁填充区。

“基于KEIL

即从0x2000fd08到0x2004bfff这段区域,共246,520Bytes【即上图黄色区域所指】。

一般来讲,代码编译后产生的PAD补丁块往往是因为地址对齐方面的原因导致的一些不便使用的零散内存碎片,正常来讲,这些补丁块不会大面积的集中在一起。

比如使用下面结构体变量的时候会插入padding.

“基于KEIL

但这里提示的补丁块也太大了,高达200多KB而且是连续空间!感觉是哪里误会了。从分析来看,初步判断这个PAD区间应该还是可以被用户使用的。于是尝试在现有代码里随意增加一块16KB的RAM有效使用量,编译一切正常,编译后显示的内存用量结果跟之前几乎一模一样,依然显示RAM用完了。

但通过查看MAP文件,可以发现上面提到的那个大补丁块空间也随之减小了16KB。显然,RAM并非真正用完了,只是编译器把它当作类似对齐原因导致的补丁块了。现在问题是,怎么会被编译器误判成这么大的一个补丁块呢?

进一步查看MAP和部分代码源文件,我们可以发现有一块RAM区域,即芯片内的SRAM2区域被用户使用attribute关键字自行做了内存使用分配了,即这块内存空间不是交给编译器安排的。【下图中绿色方框内的内存分配】

“基于KEIL

结合上面的分析,那个巨大的pad区域正是经编译器分配使用到的RAM空间的末尾地址开始到SRAM2起始地址【0X2004C000】之前的那段空间。

这里让人想到一个地方,那就是MDK IDE选项配置中Target配置的这个地方:

“基于KEIL

这里片内RAM配置是这样的,意味着从0x20000000到0x2004ffff的全部RAM空间交由编译器分配管理。

“基于KEIL

而在实际应用代码中,编译器从0x20000000开始分配内存,而SRAM2区域则由用户自行安排使用的。这样的话,经编译器所分配所用到的RAM内存末尾到0x2004BFFF这段未用区域被视为了pad区域。看来只是误会一场。

那如何消除这个误会呢?我们可以将上面的内存配置项稍微修改下,让SRAM2区域不再让编译器分配管理,这样就避免了编译器分配的内存末尾到SRAM2区域起始地址的这段空间被视为补丁区。像下面一样修改:

“基于KEIL

这样修改后再做编译,结果如下,不再给人RAM都用光的感觉了,只用到70多KB的片内RAM。

“基于KEIL

二、Flash编程算法用不了?

用人使用STM32F7开发产品,发现编译时找不到合适的Flash算法文件,或者MDK自带的现有FLM文件用不了。具体情况是这样的:

有人使用STM32F750V8开发产品,编译完毕后欲进行下载调试,结果没法完成程序下载,提示FLASH下载错误。

“基于KEIL

检查了各个配置后,怀疑FLASH算法文件是否有问题。

打开MDK选项配置里flash_download的编程配置页面,那里已经添加了相关FLASH编程算法文件。

“基于KEIL

不过,如果使用STM32芯片做过开发的人可能比较容易发现有个地方有点刺眼,就是起始地址那个地方。用过STM32F0/F1/F4等系列的人可能比较容易发现,那个FLASH起始地址一般是在0x8000000这个地方,这里却是0x00200000。我们再看看MDK选项配置有关内存分配的那个地方,如下图所示:

“基于KEIL

这里的片内FLASH默认起始地址却是0x8000000。显然这里跟FLASH编程算法文件的起始地址定义不一致,初步判断应该是这个地方导致的问题。

到此,我们有必要看看STM32F7的相关技术手册了关于内部FLASH地址分配的内容。下图是STM32F750片内主从外设及总线的框架图,对于片内FLASH,CPU可以有两条总线通路访问它,一条是通过AXIM接口,经过AXI-AHB桥以64位总线访问,另一条是通过ITCM接口,经过片内ART加速器后访问它。【如下图所示】

“基于KEIL

我们通过进一步查看手册,可以得知CPU访问该FLASH区域时因使用不同总线接口而安排了不同的地址访问区域。

“基于KEIL

从上面表格可以清楚地看到,CPU从不同接口访问FLASH时其对应地址是不一样的。通过AXIM接口访问内部flash的起始地址为0x08000000,通过ITCM接口访问内部flash的起始地址是0x00200000.

结合到上面案例,MDK选项配置中Target页面的片内FLASH地址安排与Flash算法定义的地址不一致导致程序下载失败。那么,我们可以将二者的地址及大小调整得一致,具体根据实际开发需求来调整,即根据你希望CPU通过哪个接口来访问FLASH来做相应调整。

1、如果希望走ITCM接口来访问,我们就将Target页面的FLASH存储起始地址与FLASH算法文件定义的起始地址都设置为0x00200000,如下图所示:

“基于KEIL

2、如果希望走AXIM接口来访问,我们就将FLASH算法文件定义的起始地址与Target页面定义得一致,这里就是0x08000000.

基于MDK提供的FLASH算法文件我们可以直接修改其起始地址及大小。

比如,如果刚加载进来的算法文件是基于ITCM接口的地址定义,如下图所示。

“基于KEIL

我们可以直接基于上面算法文件参数进行起始地址修改,即修改椭圆里的起始地址数据,修改完毕后点击下方的OK按钮即可。操作如下图所示:

“基于KEIL

【注:我所用ARM MDK版本为5.28。】

基于修改后的配置,再进行编译后即可进行下载调试。

“基于KEIL

来源:茶话MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 258

简述

IIC(Inter-Integrated Circuit)其实是IICBus简称,它是一种串行通信总线,使用多主从架构,在STM32开发中经常见到。

使用面向对象的编程思想封装IIC驱动,将IIC的属性和操作封装成一个库,在需要创建一个IIC设备时只需要实例化一个IIC对象即可,本文是基于STM32和HAL库做进一步封装的。

底层驱动方法不重要,封装的思想很重要。在完成对IIC驱动的封装之后借助继承特性实现AT24C64存储器的驱动开发,仍使用面向对象的思想封装AT24C64驱动。

IIC驱动面向对象封装

iic.h头文件主要是类模板的定义,具体如下:

//定义IIC类
typedef struct IIC_Type
{
   //属性
   GPIO_TypeDef  *GPIOx_SCL;  //GPIO_SCL所属的GPIO组(如:GPIOA)
   GPIO_TypeDef  *GPIOx_SDA;  //GPIO_SDA所属的GPIO组(如:GPIOA)
   uint32_t GPIO_SCL;     //GPIO_SCL的IO引脚(如:GPIO_PIN_0)
   uint32_t GPIO_SDA;     //GPIO_SDA的IO引脚(如:GPIO_PIN_0)
   //操作
   void (*IIC_Init)(const struct IIC_Type*);        //IIC_Init
   void (*IIC_Start)(const struct IIC_Type*);       //IIC_Start
   void (*IIC_Stop)(const struct IIC_Type*);        //IIC_Stop
   uint8_t (*IIC_Wait_Ack)(const struct IIC_Type*);    //IIC_Wait_ack,返回wait失败或是成功
   void (*IIC_Ack)(const struct IIC_Type*);       //IIC_Ack,IIC发送ACK信号
   void (*IIC_NAck)(const struct IIC_Type*);       //IIC_NAck,IIC发送NACK信号
   void (*IIC_Send_Byte)(const struct IIC_Type*,uint8_t);       //IIC_Send_Byte,入口参数为要发送的字节
   uint8_t (*IIC_Read_Byte)(const struct IIC_Type*,uint8_t);     //IIC_Send_Byte,入口参数为是否要发送ACK信号
   void (*delay_us)(uint32_t);              //us延时
}IIC_TypeDef;

iic.c源文件主要是类模板具体操作函数的实现,具体如下:

//设置SDA为输入模式
static void SDA_IN(const struct IIC_Type* IIC_Type_t)
{
  uint8_t io_num = 0;  //定义io Num号
  switch(IIC_Type_t->GPIO_SDA)
  {
   case GPIO_PIN_0:
    io_num = 0;
   break;
   case GPIO_PIN_1:
    io_num = 1;
   break; 
   case GPIO_PIN_2:
    io_num = 2;
   break; 
   case GPIO_PIN_3:
    io_num = 3;
   break;
   case GPIO_PIN_4:
    io_num = 4;
   break; 
    case GPIO_PIN_5:
    io_num = 5;
   break; 
   case GPIO_PIN_6:
    io_num = 6;
   break; 
   case GPIO_PIN_7:
    io_num = 7;
   break;
   case GPIO_PIN_8:
    io_num = 8;
   break; 
   case GPIO_PIN_9:
    io_num = 9;
   break;
   case GPIO_PIN_10:
    io_num = 10;
   break;
   case GPIO_PIN_11:
    io_num = 11;
   break; 
   case GPIO_PIN_12:
    io_num = 12;
   break;
   case GPIO_PIN_13:
    io_num = 13;
   break;
   case GPIO_PIN_14:
    io_num = 14;
   break; 
   case GPIO_PIN_15:
    io_num = 15;
   break;
  }
  IIC_Type_t->GPIOx_SDA->MODER&=~(3<<(io_num*2)); //将GPIOx_SDA->GPIO_SDA清零
  IIC_Type_t->GPIOx_SDA->MODER|=0<<(io_num*2);   //将GPIOx_SDA->GPIO_SDA设置为输入模式
}

//设置SDA为输出模式
static void SDA_OUT(const struct IIC_Type* IIC_Type_t)
{
  uint8_t io_num = 0;  //定义io Num号
  switch(IIC_Type_t->GPIO_SDA)
  {
   case GPIO_PIN_0:
    io_num = 0;
   break;
   case GPIO_PIN_1:
    io_num = 1;
   break; 
   case GPIO_PIN_2:
    io_num = 2;
   break; 
   case GPIO_PIN_3:
    io_num = 3;
   break;
   case GPIO_PIN_4:
    io_num = 4;
   break; 
    case GPIO_PIN_5:
    io_num = 5;
   break; 
   case GPIO_PIN_6:
    io_num = 6;
   break; 
   case GPIO_PIN_7:
    io_num = 7;
   break;
   case GPIO_PIN_8:
    io_num = 8;
   break; 
   case GPIO_PIN_9:
    io_num = 9;
   break;
   case GPIO_PIN_10:
    io_num = 10;
   break;
   case GPIO_PIN_11:
    io_num = 11;
   break; 
   case GPIO_PIN_12:
    io_num = 12;
   break;
   case GPIO_PIN_13:
    io_num = 13;
   break;
   case GPIO_PIN_14:
    io_num = 14;
   break; 
   case GPIO_PIN_15:
    io_num = 15;
   break;
  }
  IIC_Type_t->GPIOx_SDA->MODER&=~(3<<(io_num*2)); //将GPIOx_SDA->GPIO_SDA清零
  IIC_Type_t->GPIOx_SDA->MODER|=1<<(io_num*2);   //将GPIOx_SDA->GPIO_SDA设置为输出模式
}
//设置SCL电平
static void IIC_SCL(const struct IIC_Type* IIC_Type_t,int n)
{
  if(n == 1)
  {
    HAL_GPIO_WritePin(IIC_Type_t->GPIOx_SCL,IIC_Type_t->GPIO_SCL,GPIO_PIN_SET);     //设置SCL为高电平
  }
  else{
    HAL_GPIO_WritePin(IIC_Type_t->GPIOx_SCL,IIC_Type_t->GPIO_SCL,GPIO_PIN_RESET);     //设置SCL为低电平
  }
}
//设置SDA电平
static void IIC_SDA(const struct IIC_Type* IIC_Type_t,int n)
{
  if(n == 1)
  {
    HAL_GPIO_WritePin(IIC_Type_t->GPIOx_SDA,IIC_Type_t->GPIO_SDA,GPIO_PIN_SET);     //设置SDA为高电平
  }
  else{
    HAL_GPIO_WritePin(IIC_Type_t->GPIOx_SDA,IIC_Type_t->GPIO_SDA,GPIO_PIN_RESET);     //设置SDA为低电平
  }
}
//读取SDA电平
static uint8_t READ_SDA(const struct IIC_Type* IIC_Type_t)
{
  return HAL_GPIO_ReadPin(IIC_Type_t->GPIOx_SDA,IIC_Type_t->GPIO_SDA);  //读取SDA电平
}
//IIC初始化
static void IIC_Init_t(const struct IIC_Type* IIC_Type_t)
{
      GPIO_InitTypeDef GPIO_Initure;
    
   //根据GPIO组初始化GPIO时钟
   if(IIC_Type_t->GPIOx_SCL == GPIOA || IIC_Type_t->GPIOx_SDA == GPIOA)
   {
     __HAL_RCC_GPIOA_CLK_ENABLE();   //使能GPIOA时钟
   }
   if(IIC_Type_t->GPIOx_SCL == GPIOB || IIC_Type_t->GPIOx_SDA == GPIOB)
   {
     __HAL_RCC_GPIOB_CLK_ENABLE();   //使能GPIOB时钟
   }
   if(IIC_Type_t->GPIOx_SCL == GPIOC || IIC_Type_t->GPIOx_SDA == GPIOC)
   {
     __HAL_RCC_GPIOC_CLK_ENABLE();   //使能GPIOC时钟
   }
   if(IIC_Type_t->GPIOx_SCL == GPIOD || IIC_Type_t->GPIOx_SDA == GPIOD)
   {
     __HAL_RCC_GPIOD_CLK_ENABLE();   //使能GPIOD时钟
   }
   if(IIC_Type_t->GPIOx_SCL == GPIOE || IIC_Type_t->GPIOx_SDA == GPIOE)
   {
     __HAL_RCC_GPIOE_CLK_ENABLE();   //使能GPIOE时钟
   } 
   if(IIC_Type_t->GPIOx_SCL == GPIOH || IIC_Type_t->GPIOx_SDA == GPIOH)
   {
     __HAL_RCC_GPIOH_CLK_ENABLE();   //使能GPIOH时钟
   }     
   
     //GPIO_SCL初始化设置
     GPIO_Initure.Pin=IIC_Type_t->GPIO_SCL;
     GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
     GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
     GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH;    //快速
     HAL_GPIO_Init(IIC_Type_t->GPIOx_SCL,&GPIO_Initure);
     //GPIO_SDA初始化设置
     GPIO_Initure.Pin=IIC_Type_t->GPIO_SDA;
     GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
     GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
     GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH;    //快速
     HAL_GPIO_Init(IIC_Type_t->GPIOx_SDA,&GPIO_Initure);
     
            //SCL与SDA的初始化均为高电平
      IIC_SCL(IIC_Type_t,1);
       IIC_SDA(IIC_Type_t,1);
}
//IIC Start
static void IIC_Start_t(const struct IIC_Type* IIC_Type_t)
{
  SDA_OUT(IIC_Type_t);      //sda线输出
  IIC_SDA(IIC_Type_t,1);      
  IIC_SCL(IIC_Type_t,1);
  IIC_Type_t->delay_us(4);
   IIC_SDA(IIC_Type_t,0);  //START:when CLK is high,DATA change form high to low 
  IIC_Type_t->delay_us(4);
  IIC_SCL(IIC_Type_t,0);  //钳住I2C总线,准备发送或接收数据 
}
//IIC Stop
static void IIC_Stop_t(const struct IIC_Type* IIC_Type_t)
{
  SDA_OUT(IIC_Type_t); //sda线输出
  IIC_SCL(IIC_Type_t,0);
  IIC_SDA(IIC_Type_t,0); //STOP:when CLK is high DATA change form low to high
   IIC_Type_t->delay_us(4);
  IIC_SCL(IIC_Type_t,1); 
  IIC_SDA(IIC_Type_t,1); //发送I2C总线结束信号
  IIC_Type_t->delay_us(4); 
}
//IIC_Wait_ack 返回HAL_OK表示wait成功,返回HAL_ERROR表示wait失败
static uint8_t IIC_Wait_Ack_t(const struct IIC_Type* IIC_Type_t)   //IIC_Wait_ack,返回wait失败或是成功
{
  uint8_t ucErrTime = 0;
  SDA_IN(IIC_Type_t);      //SDA设置为输入  
  IIC_SDA(IIC_Type_t,1);IIC_Type_t->delay_us(1);   
  IIC_SCL(IIC_Type_t,1);IIC_Type_t->delay_us(1);
  while(READ_SDA(IIC_Type_t))
  {
    ucErrTime++;
    if(ucErrTime>250)
    {
      IIC_Type_t->IIC_Stop(IIC_Type_t);
      return HAL_ERROR;
    }
  }
  IIC_SCL(IIC_Type_t,0);//时钟输出0     
  return HAL_OK;  
}
//产生ACK应答
static void IIC_Ack_t(const struct IIC_Type* IIC_Type_t)      
{
  IIC_SCL(IIC_Type_t,0);
  SDA_OUT(IIC_Type_t);
  IIC_SDA(IIC_Type_t,0);
  IIC_Type_t->delay_us(2);  
  IIC_SCL(IIC_Type_t,1);
  IIC_Type_t->delay_us(2);  
  IIC_SCL(IIC_Type_t,0);
}
//产生NACK应答
static void IIC_NAck_t(const struct IIC_Type* IIC_Type_t)      
{
  IIC_SCL(IIC_Type_t,0);
  SDA_OUT(IIC_Type_t);
  IIC_SDA(IIC_Type_t,1);
  IIC_Type_t->delay_us(2);  
  IIC_SCL(IIC_Type_t,1);
  IIC_Type_t->delay_us(2);  
  IIC_SCL(IIC_Type_t,0);
}
//IIC_Send_Byte,入口参数为要发送的字节
static void IIC_Send_Byte_t(const struct IIC_Type* IIC_Type_t,uint8_t txd)     
{
     uint8_t t = 0;   
     SDA_OUT(IIC_Type_t);      
     IIC_SCL(IIC_Type_t,0);//拉低时钟开始数据传输
     for(t=0;t<8;t++)
     {              
          IIC_SDA(IIC_Type_t,(txd&0x80)>>7);
          txd <<= 1;    
       IIC_Type_t->delay_us(2);     //对TEA5767这三个延时都是必须的
       IIC_SCL(IIC_Type_t,1);
       IIC_Type_t->delay_us(2);  
       IIC_SCL(IIC_Type_t,0); 
       IIC_Type_t->delay_us(2);  
     }  
}
//IIC_Send_Byte,入口参数为是否要发送ACK信号
static uint8_t IIC_Read_Byte_t(const struct IIC_Type* IIC_Type_t,uint8_t ack)     
{
   uint8_t i,receive = 0;
   SDA_IN(IIC_Type_t);//SDA设置为输入
   for(i=0;i<8;i++ )
   {
      IIC_SCL(IIC_Type_t,0); 
      IIC_Type_t->delay_us(2);
      IIC_SCL(IIC_Type_t,1);
      receive<<=1;
      if(READ_SDA(IIC_Type_t))receive++;   
      IIC_Type_t->delay_us(1);
   }      
  if (!ack)
         IIC_Type_t->IIC_NAck(IIC_Type_t);//发送nACK
  else
         IIC_Type_t->IIC_Ack(IIC_Type_t); //发送ACK   
  return receive;
}
//实例化一个IIC1外设,相当于一个结构体变量,可以直接在其他文件中使用
IIC_TypeDef IIC1 = {
  .GPIOx_SCL = GPIOA,   //GPIO组为GPIOA
  .GPIOx_SDA = GPIOA,   //GPIO组为GPIOA
  .GPIO_SCL = GPIO_PIN_5,   //GPIO为PIN5
  .GPIO_SDA = GPIO_PIN_6,  //GPIO为PIN6
  .IIC_Init = IIC_Init_t,
  .IIC_Start = IIC_Start_t,
  .IIC_Stop = IIC_Stop_t,
  .IIC_Wait_Ack = IIC_Wait_Ack_t,
  .IIC_Ack = IIC_Ack_t,
  .IIC_NAck = IIC_NAck_t,
  .IIC_Send_Byte = IIC_Send_Byte_t,
  .IIC_Read_Byte = IIC_Read_Byte_t,
  .delay_us = delay_us     //需自己外部实现delay_us函数
};

上述就是IIC驱动的封装,由于没有应用场景暂不测试其实用性,待下面ATC64的驱动缝缝扎黄写完之后一起测试使用。

ATC64XX驱动封装实现

at24cxx.h头文件主要是类模板的定义,具体如下:

// 以下是共定义个具体容量存储器的容量
#define AT24C01  127
#define AT24C02  255
#define AT24C04  511
#define AT24C08  1023
#define AT24C16  2047
#define AT24C32  4095
#define AT24C64   8191         //8KBytes
#define AT24C128 16383
#define AT24C256 32767  

//定义AT24CXX类
typedef struct AT24CXX_Type
{
  //属性
  u32 EEP_TYPE;           //存储器类型(存储器容量)
  //操作
  IIC_TypeDef IIC;       //IIC驱动
  uint8_t (*AT24CXX_ReadOneByte)(const struct AT24CXX_Type*,uint16_t);  //指定地址读取一个字节
  void (*AT24CXX_WriteOneByte)(const struct AT24CXX_Type*,uint16_t,uint8_t); //指定地址写入一个字节
  void (*AT24CXX_WriteLenByte)(uint16_t,uint32_t,uint8_t); //指定地址开始写入指定长度的数据
  uint32_t (*AT24CXX_ReadLenByte)(uint16_t,uint8_t);   //指定地址开始读取指定长度数据
  void (*AT24CXX_Write)(uint16_t,uint8_t *,uint16_t);  //指定地址开始写入指定长度的数据
  void (*AT24CXX_Read)(uint16_t,uint8_t *,uint16_t);   //指定地址开始写入指定长度的数据
  void (*AT24CXX_Init)(const struct AT24CXX_Type*); //初始化IIC
  uint8_t (*AT24CXX_Check)(const struct AT24CXX_Type*);   //检查器件
}AT24CXX_TypeDef;

extern AT24CXX_TypeDef AT24C_64;     //外部声明实例化AT24CXX对象

at24cxx.c源文件主要是类模板具体操作函数的实现,具体如下:

//在AT24CXX指定地址读出一个数据
//ReadAddr:开始读数的地址  
//返回值  :读到的数据
static uint8_t AT24CXX_ReadOneByte_t(const struct AT24CXX_Type* AT24CXX_Type_t,uint16_t ReadAddr)
{      
  uint8_t temp=0;                          
  AT24CXX_Type_t->IIC.IIC_Start(&AT24CXX_Type_t->IIC);  
  //根据AT的型号发送不同的地址
  if(AT24CXX_Type_t->EEP_TYPE > AT24C16)
  {
    AT24CXX_Type_t->IIC.IIC_Send_Byte(&AT24CXX_Type_t->IIC,0XA0);    //发送写命令
    AT24CXX_Type_t->IIC.IIC_Wait_Ack(&AT24CXX_Type_t->IIC);
    AT24CXX_Type_t->IIC.IIC_Send_Byte(&AT24CXX_Type_t->IIC,ReadAddr>>8);//发送高地址     
  }else AT24CXX_Type_t->IIC.IIC_Send_Byte(&AT24CXX_Type_t->IIC,0XA0+((ReadAddr/256)<<1));   //发送器件地址0XA0,写数据     
  AT24CXX_Type_t->IIC.IIC_Wait_Ack(&AT24CXX_Type_t->IIC); 
  AT24CXX_Type_t->IIC.IIC_Send_Byte(&AT24CXX_Type_t->IIC,ReadAddr%256);   //发送低地址
  AT24CXX_Type_t->IIC.IIC_Wait_Ack(&AT24CXX_Type_t->IIC);     
  AT24CXX_Type_t->IIC.IIC_Start(&AT24CXX_Type_t->IIC);        
  AT24CXX_Type_t->IIC.IIC_Send_Byte(&AT24CXX_Type_t->IIC,0XA1);           //进入接收模式      
  AT24CXX_Type_t->IIC.IIC_Wait_Ack(&AT24CXX_Type_t->IIC);  
  temp=AT24CXX_Type_t->IIC.IIC_Read_Byte(&AT24CXX_Type_t->IIC,0);     
  AT24CXX_Type_t->IIC.IIC_Stop(&AT24CXX_Type_t->IIC);//产生一个停止条件     
  return temp;
}
//在AT24CXX指定地址写入一个数据
//WriteAddr  :写入数据的目的地址    
//DataToWrite:要写入的数据
static void AT24CXX_WriteOneByte_t(const struct AT24CXX_Type* AT24CXX_Type_t,uint16_t WriteAddr,uint8_t DataToWrite)
{                                
   AT24CXX_Type_t->IIC.IIC_Start(&AT24CXX_Type_t->IIC);   
  if(AT24CXX_Type_t->EEP_TYPE > AT24C16)
  {
    AT24CXX_Type_t->IIC.IIC_Send_Byte(&AT24CXX_Type_t->IIC,0XA0);    //发送写命令
    AT24CXX_Type_t->IIC.IIC_Wait_Ack(&AT24CXX_Type_t->IIC);
    AT24CXX_Type_t->IIC.IIC_Send_Byte(&AT24CXX_Type_t->IIC,WriteAddr>>8);//发送高地址     
  }else AT24CXX_Type_t->IIC.IIC_Send_Byte(&AT24CXX_Type_t->IIC,0XA0+((WriteAddr/256)<<1));   //发送器件地址0XA0,写数据     
  AT24CXX_Type_t->IIC.IIC_Wait_Ack(&AT24CXX_Type_t->IIC); 
   AT24CXX_Type_t->IIC.IIC_Send_Byte(&AT24CXX_Type_t->IIC,WriteAddr%256);   //发送低地址
  AT24CXX_Type_t->IIC.IIC_Wait_Ack(&AT24CXX_Type_t->IIC);               
  AT24CXX_Type_t->IIC.IIC_Send_Byte(&AT24CXX_Type_t->IIC,DataToWrite);     //发送字节          
  AT24CXX_Type_t->IIC.IIC_Wait_Ack(&AT24CXX_Type_t->IIC);            
   AT24CXX_Type_t->IIC.IIC_Stop(&AT24CXX_Type_t->IIC);//产生一个停止条件 
 AT24CXX_Type_t->IIC.delay_us(10000);  
}
//在AT24CXX里面的指定地址开始写入长度为Len的数据
//该函数用于写入16bit或者32bit的数据.
//WriteAddr  :开始写入的地址  
//DataToWrite:数据数组首地址
//Len        :要写入数据的长度2,4
static void AT24CXX_WriteLenByte_t(uint16_t WriteAddr,uint32_t DataToWrite,uint8_t Len)
{   
  uint8_t t;
  for(t=0;t<Len;t++)
  {
    AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
  }                
}
//在AT24CXX里面的指定地址开始读出长度为Len的数据
//该函数用于读出16bit或者32bit的数据.
//ReadAddr   :开始读出的地址 
//返回值     :数据
//Len        :要读出数据的长度2,4
static uint32_t AT24CXX_ReadLenByte_t(uint16_t ReadAddr,uint8_t Len)
{   
  uint8_t t;
  uint32_t temp=0;
  for(t=0;t<Len;t++)
  {
    temp<<=8;
     temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);          
  }
  return temp;                
}
//在AT24CXX里面的指定地址开始写入指定个数的数据
//WriteAddr :开始写入的地址 对24c64为0~8191
//pBuffer   :数据数组首地址
//NumToWrite:要写入数据的个数
static void AT24CXX_Write_t(uint16_t WriteAddr,uint8_t *pBuffer,uint16_t NumToWrite)
{
  while(NumToWrite--)
  {
   AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
    WriteAddr++;
    pBuffer++;
  }
}
//在AT24CXX里面的指定地址开始读出指定个数的数据
//ReadAddr :开始读出的地址 对24c64为0~8191
//pBuffer  :数据数组首地址
//NumToRead:要读出数据的个数
static void AT24CXX_Read_t(uint16_t ReadAddr,uint8_t *pBuffer,uint16_t NumToRead)
{
  while(NumToRead)
  {
    *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++); 
    NumToRead--;
  }
} 
//初始化IIC接口
static void AT24CXX_Init_t(const struct AT24CXX_Type* AT24CXX_Type_t)
{
  AT24CXX_Type_t->IIC.IIC_Init(&AT24CXX_Type_t->IIC);//IIC初始化
}
//检查器件,返回0表示检测成功,返回1表示检测失败
static uint8_t AT24CXX_Check_t(const struct AT24CXX_Type* AT24CXX_Type_t)   
{
 uint8_t temp;
  temp = AT24CXX_Type_t->AT24CXX_ReadOneByte(AT24CXX_Type_t,AT24CXX_Type_t->EEP_TYPE);//避免每次开机都写AT24CXX      
  if(temp == 0X33)return 0;     
  else//排除第一次初始化的情况
  {
      AT24CXX_Type_t->AT24CXX_WriteOneByte(AT24CXX_Type_t,AT24CXX_Type_t->EEP_TYPE,0X33);
       temp = AT24CXX_Type_t->AT24CXX_ReadOneByte(AT24CXX_Type_t,AT24CXX_Type_t->EEP_TYPE);
      if(temp==0X33)return 0;
  }
  return 1;  
}
//实例化AT24CXX对象
AT24CXX_TypeDef AT24C_64={
 .EEP_TYPE = AT24C64,           //存储器类型(存储器容量)
 //操作
 .IIC={
  .GPIOx_SCL = GPIOA,
  .GPIOx_SDA = GPIOA,
  .GPIO_SCL = GPIO_PIN_5,
  .GPIO_SDA = GPIO_PIN_6,
  .IIC_Init = IIC_Init_t,
  .IIC_Start = IIC_Start_t,
  .IIC_Stop = IIC_Stop_t,
  .IIC_Wait_Ack = IIC_Wait_Ack_t,
  .IIC_Ack = IIC_Ack_t,
  .IIC_NAck = IIC_NAck_t,
  .IIC_Send_Byte = IIC_Send_Byte_t,
  .IIC_Read_Byte = IIC_Read_Byte_t,
  .delay_us = delay_us
 },                   //IIC驱动
 .AT24CXX_ReadOneByte = AT24CXX_ReadOneByte_t,  //指定地址读取一个字节
 .AT24CXX_WriteOneByte = AT24CXX_WriteOneByte_t,//指定地址写入一个字节
 .AT24CXX_WriteLenByte = AT24CXX_WriteLenByte_t, //指定地址开始写入指定长度的数据
 .AT24CXX_ReadLenByte = AT24CXX_ReadLenByte_t,   //指定地址开始读取指定长度数据
 .AT24CXX_Write = AT24CXX_Write_t,  //指定地址开始写入指定长度的数据
 .AT24CXX_Read = AT24CXX_Read_t,   //指定地址开始读取指定长度的数据
 .AT24CXX_Init = AT24CXX_Init_t, //初始化IIC
 .AT24CXX_Check = AT24CXX_Check_t   //检查器件
};

简单分析:可以看出AT24CXX类中包含了IIC类的成员对象,这是一种包含关系,因为没有属性上的一致性因此谈不上继承。

之所以将IIC的类对象作为AT24CXX类的成员是因为AT24CXX的实现需要调用IIC的成员方法,IIC相当于AT24CXX更下层的驱动,因此采用包含关系更合适。

因此我们在使用AT24CXX的时候只需要实例化AT24CXX类对象就行了,因为IIC包含在AT24CXX类中间,因此不需要实例化IIC类对象,对外提供了较好的封装接口。下面我们看具体的调用方法。

主函数main调用测试

在main函数中直接使用AT24C_64来完成所有操作,下面结合代码来看:

#include "at24cxx.h"    //为了确定AT24C_64的成员方法和引用操作对象AT24C_64
int main(void)
{
  /************省略其他初始化工作****************/
  //第一步:调用对象初始化方法来初始化AT24C64
  AT24C_64.AT24CXX_Init(&AT24C_64);
  //第二步:调用对象检测方法来检测AT24C64           
  if(AT24C_64.AT24CXX_Check(&AT24C_64) == 0)
  {
    printf("AT24C64检测成功\r\n");
  }
  else{
    printf("AT24C64检测失败\r\n");
  }
  return 0;
}

可以看出所有的操作都是通过AT24C_64对象调用完成的,在我们初始化好AT24C_64对象之后就可以放心大胆的调用其成员方法,这样封装的好处就是一个设备对外只提供一个对象接口,简洁明了。

总结

本文详细介绍了面向对象方法实现IIC驱动封装以及AT24CXX存储器的封装,最终对外仅提供一个操作对象接口,大大提高了代码的复用性以及封装性。

来源:STM32嵌入式开发
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 221

一个产品的功耗不光是硬件功耗的事,其实软件也是影响整个产品功耗的一个关键因素。进行STM32低功耗产品开发时,可以通过下文GPIO配置方式来减低功耗。

把闲置GPIO输入配置为模拟输入

GPIO始终有一个输入通道,可以是数字或模拟通道。

如果不需要读取GPIO数据,则优先配置为模拟输入。这节省了输入施密特触发器的消耗。

在STM32CubeMX配置中都有这么一个选项:将不用引脚配置为模拟状态。

“”

调节GPIO速度

上升时间,下降时间和最大频率可使用GPIOx_OSPEEDR配置寄存器进行配置。

这种调整对EMI(电磁干扰)和SSO(同时开关输出)有影响,因为开关电流峰值较高。因此必须平衡GPIO性能与噪声。

每个GPIO信号的上升时间和下降时间必须适应与相关信号频率和电路板容性负载兼容的最小值。

不使用时禁用GPIO寄存器时钟

如果某个GPIO组不需要长时间使用,禁用其时钟。

比如:

标准外设库,禁用GPIOA时钟:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, DISABLE);
br

HAL库,禁止GPIOA时钟:

__HAL_RCC_GPIOA_CLK_DISABLE();

br

进入低功耗模式时配置GPIO

进入低功耗模式时,所有引脚信号必须连接到VDD或接地。

如果GPIO连接到外部接收器(外部元件输入),则必须使用PP或PU/PD强制GPIO信号值。

当GPIO连接到驱动器(外部元件输出或总线)时,驱动器必须提供有效电平( VDD或接地)。如果未定义驱动器电平,则必须使用PU/PD强制GPIO上的信号。

出于实际原因,当GPIO是运行模式下的输入(模拟或数字)时,在低功耗模式下使用输入PU/PD可能更容易;当GPIO是运行模式下的输出时,则使用输出PP。这可以避免在进入或退出停止模式时管理更改。

退出关机模式

退出关机(shut down)模式时, GPIO会在上电复位时重新配置为默认值。

在将它们重新编程为正确值之前,这会需要额外的系统消耗。

如果这是应用程序的问题,则必须使用待机(standby)模式替代关机模式。

来源:网络
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 397

页面

订阅 RSS - STM32