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微控制器的性能和功能优势。

来源:意法半导体博客

微控制器 (MCU)和微处理器(MPU)有哪些不同之处?简单来说,两者都是嵌入式系统的大脑。几年前,两者之间有非常明显的区别,功能截然不同,对开发者的研发技能要求也大不相同。如今,这两个术语仍然存在,但创新使得两者之间的分界线日趋模糊。以前只用 MCU 的系统集成商现在发现,用MPU更容易,ST也注意到了这一点。微处理器已经成为某些开发者手中的秘密武器,借助其原生的功能或运行嵌入式 Linux 的能力,他们能够开发新的应用或进入新的市场。因此,让我们一起深入研究一下这个新趋势。

故事的开头

MCU的出现是替代 MPU

当业界在上个世纪七十年代推出首个微控制器时,人们希望找到一种替代耗电量太大且设计复杂的 MPU 的产品。MCU虽然计算吞吐量要小很多,但将存储器、处理器、外设和时钟整合在一起,可以运行实时操作系统。工程师只需用一个微控制器即可构建确定性系统,这使得微控制器在汽车和电机控制应用中非常受欢迎。如今,从智能手机到医疗设备或家电,MCU可谓无处不在。相反,MPU芯片空间都用于容纳计算单元,用牺牲功耗或集成度换来了更高的算力。当需要运行多个线程或更复杂的操作系统时,例如,嵌入式 Linux,算力更高的MPU更具吸引力。

1.png

弄清楚MCUMPU之间的区别

根据应用需求选择 MCU MPU

虽然有阐述详尽的MPUMCU 选择攻略,但工程师最终还是不知所措,无法确定选谁。但是,开发者常问一些问题,例如,算力要求。如果一个应用需要性能强大的神经处理单元或者多个计算核心和高性能GPU,又或者执行上下文计算,可能对存储空间有要求,那么, MPU 是一个显而易见的选择。相反,如果应用是一个小软件,偶尔唤醒,检查一次传感器值,或需要几纳秒的确定性响应时间,那么,微控制器是一个正确的选择。因此,在许多情况下,目的决定手段是否正当简而言之,工程师应根据要运行的具体应用来选择一个合适的硬件平台。

影响选型的另一个因素可能是系统的图形需求。过去,具有复杂 3D 动画的人机界面 (HMI)、高分辨率显示屏、与用户界面同时运行的复杂应用,将倾向于选用微处理器的GPU 和存储控制器,而动画和图形更简单的 HMI 越来越依赖于MCUTouchGFX 等框架和 NeoChrom GPU 等硬件 IP 不断优化那些可以在微控制器上运行的应用。同样,嵌入式系统MPU支持更高的分辨率,因为GPU的处理性能更强大。因此,虽然每种产品的功能越来越多,但界定两者的区别仍然非常简单。

根据成本和功耗选择MCU还是MPU

除了计算吞吐量,开发人员还会考虑其他重要指标,例如,功耗、易失性存储器、非易失性存储器需求、所需外设和引脚数量。当工程师设法应对成本限制时,这些选择标准变得至关重要,因为它们会影响整体 PCB 设计和物料成本 (BOM)。例如,许多闪存和附加组件将需要多个 PCB 板层,这会增加交货时间和成本。因此,长期以来,这个选择标准相对简单明了。关注成本或低功耗的系统集成商会选择微控制器。

MCU MPU 之间的界限日趋模糊

21 世纪初以来,MPU 经历了重大变革。系统级模块 (SoM) 和系统级封装 (SiP) 的广泛应用是MPU行业最具颠覆性的技术创新之一。过去,集成商必须围绕微处理器设计整个系统,这意味着要处理更复杂的电源管理系统和繁琐的外部存储器等问题。事实上,使用大容量DDR外存需要反复微调和丰富的专业知识,这可能是阻碍MPU应用的一大障碍,然而,SoM SiP 的出现让所有这些复杂问题都迎刃而解,这两种组装技术安全地将所有必要组件都整合到一个封装或模块内。

此外,ST的一些最新的微处理器已经更接近微控制器的功耗水平。现在,微处理器可以运行实时操作系统,使得 MPU MCU 之间的界限进一步模糊。以前,执行实时应用,例如,电机控制应用,必须使用微控制器。如今,工程师已开始采用 MPU,在不影响执行时间的情况下,获得更强大的计算能力和更大的存储容量,是一举两得的好事。简而言之,一些集成商正在充分利用 MPU的技术创新,当竞争对手还在用 MCU时,他们已经掌握了MPU这个新的秘密武器。

故事的发展

STM32H7还是STM32MP1?

过去几年,高性能 MCU 和入门级 MPU 之间的界限非常模糊,使得 STM32MP13 等产品成为嵌入式系统开发人员的新宠。像STM32H7 一样,STM32MP13 本身也支持 Eclipse ThreadX。因此,给了从未接触过微处理器的开发人员一个熟悉的开发环境,可以去调用 FileXNetDuoX USBX 的应用程序。因此,无需重新培训团队或大幅增加物料成本,就可以享有更高的性能。

此外,STM32 工程师还拥有额外的优势,因为 STM32Cube工具生态系统同时支持MCU MPU,从而进一步降低了进入门槛。例如, STM32CubeMX 上初始化引脚配置和时钟树希望在 STM32 MPU 上实现安全密钥配置的开发人员可以选用 STM32CubeProgrammer,这款工具使安全固件安装 (SFI) 也更容易。因此,ST的生态系统用户有更多的动力去探索 MPU,将其用作支持新应用的秘密武器,因为他们已经熟悉ST的许多开发工具和产品概念。

STM32MP13STM32MP15

对于许多嵌入式系统开发人员来说,问题不再是是否要涉足 MPU 领域,而是深入到何种程度,以及从哪里开始。ST 合作伙伴计划的许多成员都推出了采用 STM32MP13 SiP SoM,因此,对于任何希望将MPU作为秘密武器的团队来说,STM32MP13都是一个绝佳起点。这款微处理器搭载一颗1 GHz Cortex-A7内核,对那些寻求设计简单但性能强大的开发者有很大的吸引力。不是多核,意味着功耗更低(27µW),而且能够将STM32MP13集成到简单的四层 PCB上。

那些追求更强性能的人会选择 STM32MP15该产品搭载两颗Cortex-A7内核和一颗Cortex-M4内核,使得在模糊MCU MPU之间的界限的同时推动开发人员深入MPU阵营。例如,可以关闭 Cortex-A7内核,只开启Cortex-M4内核,将其用作传统 MCU,记录传感器数据,同时消耗更少的电能。此外,这款产品的 3D GPU 符合 OpenGL 标准,允许开发人员运行更高级的用户界面。该产品还配备了更多的显示接口和外设。因此,STM32MP15 可以帮助集成商扩展系统。

让我们以一家开发工业用设备(例如,可编程逻辑控制器)的公司为例。开发者可以使用 STM32MP13 设计一个功能强大的无显示屏产品。此后,开发者可以把原始设计迁移到STM32MP15上,增装一块分辨率1080 x 720的显示屏,给PLC控制器增加一个人机界面 (HMI)因为这家公司最初使用的是STM32 MPU,所以,他们可以使用相同的嵌入式 Linux 发行版,并轻松地将应用从一个 MPU 移植到另一个 MPU。该操作系统还运行先进的 UI 框架,例如,以可移植性而闻名的QtCrank

另一个例子是智能恒温器,其中用户界面是产品体验的重要组成部分。厂商一直在寻求产品差异化,使用不同级别的 UI 和屏幕尺寸来吸引更广泛的客户群。从 STM32MP15 迁移到 STM32MP13,开发者可以运行相同的底层应用,还可以选用很多不同的附加功能,创建涵盖更广泛的需求和价位的产品组合。

STM32MP15STM32MP25

开发人员越来越关注如何设计使用寿命更长的产品,并在边缘设备上引入机器学习。MPU 的最新进展可以提供更大的存储灵活性,帮助开发者满足这些需求,这也解释了为什么许多人经常采用 STM32 MPU,以保持竞争优势。例如,新款 STM32MP25 ST第一款除DDR3外还支持 DDR4 LPDDR4 MPU64 位架构还意味着它可以为音视频处理和网络设备等应用提供更多的存储空间,或者同时运行多个软件,以节省资源,提高效率。

大多数工业应用使用相同的存储器接口长达十年或更久,因此,微处理器必须提供灵活性更高的存储控制器(与消费市场相比),这就是为什么 ST MPU 始终支持多个存储器接口,并且ST确保最广泛的兼容性,STM32MP25就是一个这样的产品,它使系统支持变得更加高效,同时也便于进行设计更新和升级

同样,许多人都希望从边缘机器学习中获益。STM32MP25 是第一款支持 64 位架构的 STM32产品,搭载了两颗 Cortex-A35内核,这是目前Arm最高效的内核。因此,这款产品可以运行更强大的应用,同时保持较低的功耗。神经处理单元 (NPU)的处理速度达到1.35 TOPSVulkan 兼容GPU能够在全高清显示屏上轻松运行新颖的用户界面。因此,ST的新 MPU 为一些要求最苛刻的应用带来了机会,例如,能够进行人数统计或物体检测的智能相机,以及空间计算等新系统。

未来将会怎样?

ST决定发布更多的 STM32MP2 MPU,帮助开发人员根据实际需求定制应用。确实,同一系列微控制器通常包含很多不同的产品型号,而微处理器却没有那么多产品型号,因为微处理器制造难度更大。然而,随着制造能力不断优化,ST计划尽快发布更多产品,并使其中多个产品的引脚兼容。ST已经预发布了STM32MP21 STM32MP23STM32MP21搭载Cortex-A35Cortex-M33两个内核、两个以太网控制器和一个摄像头接口,可以满足有成本要求边缘计算机视觉应用。STM32MP23定位在STM32MP25 STM32MP21之间,它的双 Cortex-A35内核可以实现丰富的 UI,同时兼顾成本。

2.png

STM32 MPU产品系列

围观 26

01、引言 

使用 STM32 进行项目开发时,在原理图和 PCB 设计中都需要使用相应的元件封装 库。STM32 元件封装库的准确性是保证设计成功的前提。在最新版本的 STM32CubeMX 中内置了 STM32 元件封装的下载工具,下载的封装库 文件经 EDA 工具转换后可以在原理图和 PCB 设计中调用。

本文将对如何下载和转换做简单的示例说明。

02、在 STM32CubeMX 中下载 STM32 封装文件 

下面我们以 STM32H503CBT 为例简单介绍下如何在 STM32CubeMX 中下载 STM32 MCU 的封装库文件。

打开 STM32CubeMX(本例中使用 Version 6.9.0)后,选中所使用的 STM32 型号 后进入配置页面。

根据电路的需求,分配相应的功能给 STM32 的管脚。(如果不需要在原理图 STM32 封装库中显示管脚所分配的功能,这一步可以忽略。)

这时,我们将见到如下图所示的元件示意图:

1.png

▲ 图1. CubeMX 中的 STM32H503CVTx

点击上图右上角的 Tools 菜单栏。进入 Tools 界面后点击左边的 CAD 菜单,进入 CAD 元件封装库配置和下载页面。 

2.png

▲ 图2. CAD 元件库界面

然后根据硬件设计中使用的 EDA 工具在下图选项框中选择 CAD 文件格式: 

3.png

▲ 图3. CAD 格式选择

可以选择的 STM32 元件库封装格式支持主流的 EDA 设计工具,包括 PADS, Altium Designer, Cadence 等等。这里以 Altium Designer (AD) 和 Pads 为例,可以看到将要 下载的工具格式被列出在下载格内,且支持一次性多格式下载:

4.png

▲ 图4. 选择不同格式工具下载 

从 CubeMX 下载得到的 STM32 CAD 文件是基于 Ultra Librarian 工具在云端生成 的,这里我们需要勾选同意 Ultra Librarian 的条款和条件,不需要再下载安装 Ultra Librarian 工具。

5.png

▲ 图5

在页面的右上角有个选择按钮可以用来选择管脚名的显示方式: 

6.png

▲ 图6 

当选择 Pin name 时,原理图封装库中管脚名按封装名显示:

7.png

▲ 图7. Pin name

如果选择了 Project naming, 管脚名按分配的功能名称显示:

8.png

▲ 图8. Project Name 

选择 Dual 则管脚名和功能名都显示:

9.png

▲ 图9. Dual

更改上述的管脚名显示等选项后,点击 Refresh symbol previews 按钮可以刷新显示。

10.png

▲ 图10. 下载和刷新菜单 

在 PCB 封装库的 Footprint 显示下面有 Basic 和 Detailed 两种显示,Detailed 显示 比 Basic 显示多了一些尺寸标注:

11.png

▲ 图11. Basic 和 Detail 显示 

最后我们点击图 10 所示的下载菜单下载库文件。

如果选择了管脚名的功能定义显示, 可能会先弹出一个警告信息:因为工具不支持一 些特殊的字符,这些字符会被替换:

12.png

▲ 图12. 特殊字符替换警告  

忽略这些信息,点击 Download anyway。等待,此时 Refresh symbol previews 下 的状态条会抖动, 过一会将显示:

13.png

▲ 图13. 选择目标目录下载 

选择将文件保存到电脑目标地址即可。

03、在 EDA 工具中导入封装文件生成封装库 

下载下来的 UL 生成文件并不是 EDA 工具可以直接使用的封装库文件,而是一些中间 文件, 必须在 EDA 工具中导入转换后才可以被 EDA 工具使用。下面我们以最常用的 Altium Designer 和 PADS 为例来说明这一过程。

3.1. 在 Altium Designer 中生成封装库 

我们使用 Altium Designer 22.9 来演示如何从 CubeMX 下载的文件生成库文件,不同版本的 AD 工具导入过程可能略有差异。解压刚才从 CubeMX 下载的压缩文件,在 AltiumDesigner 目录下我们可以看到共有 6 个文件:

14.png

▲ 图14

打开 AD 软件,在菜单栏打开 File --> Open 后找到解压文件中的.prjscr 工程文件打开

15.png

▲ 图15. AD 打开工程文件

工程打开后如下图:

16.png

▲ 图16. 打开后的工程 

 接着运行脚本,File --> Run Script… 

17.png

▲ 图17. 运行脚本  

在弹框中选择 UL_Form.pas 后点击 OK 运行。

18.png

▲ 图18. 选择文件运行脚本

紧接着会生成另一个弹窗,选择导入刚才解压的 UL 生成的 TXT 文件,点击开始导入:

19.png

图19. 选择 TXT 文件后导入  

等待一会可以看到库文件已经生成:

20.png

图20. 生成的 LibPkg 文件 

生成的.pcblib 库文件需要关闭 AD 再重新打开后才能正常检视/使用。

21.jpg

22.jpg

图21. 生成的库文件 

3.2. 在 PADS 中生成封装库 

打开下载的 PADS 封装导入文件,可以看到压缩包内有三个文件:

23.jpg

图22. 

PADS 的库导入相对简单,打开 PADS Layout, 点击菜单栏 File --> Library, 弹出元 件库管理框 Library Manger:

24.jpg

图23. 元件库管理器

在元件库管理器框中依次选择: 

Decals --> Import 导入 pads.d 文件 如图 24 

Parts --> Import 导入 Pads.p 文件,如图 25 

Logic --> Import Pads.c 文件, 如图 26 

导入后点击 Open 即可生成对应的库文件。 

25.jpg

图24. 导入 Decals 封装 

26.jpg

图25. 导入 Parts 库

27.jpg

图26. 导入 Logic 库 

28.jpg

图27. 生成的原理图封装库

29.jpg

图28. 生成的 PCB 电气封装库 

04、小结 

使用 STM32CubeMX 下载得到的元件封装可以让工程师从繁琐的元件封装库设计中解放出来,特别是当管脚数目较多时。

很多用户对原理图封装库的外形,尺寸,管脚排列/命名,功能排列等方面,以及对 PCB 封装的焊盘大小,阻焊设计,字符标注等方面有特别的要求,下载的文件效果可能不 能全部满足要求。这些客户可以在下载的文件上做适当的修改。这样可以避免全新设计封 装库过程中可能发生的各种错误,特别是常发生的 PCB 封装尺寸中的错误。

来源:STM32

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

围观 27

优化的集成化电源管理芯片,内置保护功能,驱动MPU及外设

意法半导体 STM32MP2 微处理器配套电源管理芯片STPMIC25 现已上市。新产品在一个便捷封装内配备 16 个输出通道,可为MPU的所有电源轨以及系统外设供电,完成硬件设计仅需要少量的外部滤波和稳定功能组件。评估板STEVAL-PMIC25V1现已上市,开发者可立即开始开发应用。

1.jpg

新电源管理芯片包含七个 DC/DC 降压转换器和八个低压差 (LDO) 稳压器,还有一个额外的 LDO稳压器为系统 DDR3 和 DDR4 DRAM 提供参考电压 (Vref)。在八个 LDO稳压器中有一个3.3V 通道专用稳压器,用于为 USB 高速和 Type-C PHY IC 供电,还有通用 LDO稳压器,可分配给电源电路,例如,存储卡接口和以太网端口。

降压转换器经过优化设计,可为 MPU 的 CPU、核心电路、GPU、I/O 和模拟电源域供电,并具有给DDR RAM 供电的附加通道和通用辅助输出。转换器采用快速瞬态响应和低纹波设计,适用于各种工作条件,可满足 MPU 电源域的特定需求。所有转换器均采用自适应恒定导通时间控制,以实现高能效,并采用扩频频率调制和相移开关及先进的同步技术来最大限度地降低 EMI干扰。此外,每个转换器都有高低功耗工作模式,可通过应用软件控制模式转换,允许主机系统最大限度地降低转换器静态电流,提高节能效果。所有降压转换器和 LDO 都可以独立启用和禁用。

STPMIC25现已投入生产,采用 6.5mm x 6.5mm 56 引脚 WFQFN 封装,厚度仅为 0.9mm。STEVAL-PMIC25V1 电路板可在 eSTore或通过代理商购买。

详情访问www.st.com/STPMIC25

关于意法半导体

意法半导体拥有5万名半导体技术的创造者和创新者,掌握半导体供应链和先进的制造设备。作为一家半导体垂直整合制造商(IDM),意法半导体与二十多万家客户、成千上万名合作伙伴一起研发产品和解决方案,共同构建生态系统,帮助他们更好地应对各种挑战和新机遇,满足世界对可持续发展的更高需求。意法半导体的技术让人们的出行更智能,让电源和能源管理更高效,让云连接的自主化设备应用更广泛。意法半导体承诺将于2027年实现碳中和(在范围1和2内完全实现碳中和,在范围3内部分实现碳中和)。详情请浏览意法半导体公司网站:www.st.com.cn

围观 19
最近,ST推出首款微处理器嵌入式软件 — 安卓13生态系统OpenSTDROID,该软件基于OpenSTLinux BSP(Linux 内核 6.1 LTS),运行在STM32MP2微处理器系列,是意法半导体开发的、运行在高性能微处理器(STM32MP2系列及后续更高性能MPU)的安卓发行版,提供了在安卓生态框架下运行、开发或者打造自己平台所需的所有组件。

1729244703155505.jpg

1729244933334850.png

为什么推出OpenSTDROID

一方面,作为一个开源操作系统,安卓为嵌入式系统带来了巨大潜力。它的灵活性使得开发者可以根据具体需求进行自定义,从而实现高度的适应性。安卓在性能和用户体验方面进行了深度优化,这使其在嵌入式设备中系统性能表现出色。

此外,安卓丰富的应用程序生态系统也可让开发者轻松地开发和集成各种应用程序,从而增强嵌入式设备的功能和互联性。

ST推出OpenSTDROID可以让用户轻松安装安卓社区的开源应用,让基于安卓开发应用程序更容易。

STM32MP25的高性能处理器以及丰富的存储和外设资源使其可以很好的适配安卓生态。
1729244785870891.png

STM32MP25这款工业级处理器融合了高能效的Cortex-A35与M33异构CPU架构,结合高性能3D GPU、NPU、高清视频编解码等,为各类应用场景提供先进的边缘AI处理能力与强大的多媒体功能。支持多达4GB的32位DDR4/LPDDR4/DDR3(L)存储器。此外它还配备了丰富的外设接口,能够轻松满足多样化设备的连接需求。同时内置的强大安全特性为数据安全与系统防护提供了坚实保障。

TM32MP25工业级品质确保了在高要求环境下的稳健运行与长久使用寿命,真正实现了性能、功耗与外设资源之间的完美平衡。

ST拥有丰富的基于安卓系统进行MPU研发的经验,为STM32MP25提供了最适合的安卓开发环境支持。比如,ST从早期版本就开始参与安卓项目,包括推出安卓手机、进行安卓机顶盒开发,并参加 Bootcamp 活动等。

OpenSTDROID产品规划及维护计划

目前,OpenSTDROID可基于STM32MP25实现安卓13环境下运行。

包括以下软件组合:

■ OpenSTLinux BSP(OP-TEE安全操作系统、引导链和Linux内核)
■ 应用程序框架:基于OpenSTLinux BSP提供的服务来提供特定功能(如代码库、API和工具集)以简化软件应用程序开发

  • OP-TEE应用程序框架:在安全操作系统用户空间中运行(例如TEE内部核心API,用于开发可信应用程序(TA))。

  • Android应用程序框架:在Linux操作系统用户空间中运行(有关更多详细信息,请参阅AOSP[1])。

OpenSTLinux BSP for Android特点:

■ Linux内核源代码基于AOSP[2]中提供的Common kernel。

■ Linux内核配置基于AOSP[3]中提供的参考配置。
Android应用程序框架基于AOSP platform manifes[4]。默认情况下,使用其中一个Google tags[5]作为参考。

当前可在STM32MP257F-EV1评估板运行OpenSTDROID,体验STM32MP25安卓方案为工业 4.0、智能家居等量身定制的精简参考解决方案,具有流畅度高、平滑性优、性能强大等特点:

■ 在图形和形状方面:通过OpenGL ES 3.1支持GPU,实现流畅的形状操作,如缩放、旋转、透视等。

■ 在视频方面:支持视频编码/解码H264 VP8,高质量、高流畅度且性能强大,可以轻松安装安卓社区的开源应用。

预计2025年第三季度,OpenSTDROID将升级到安卓15,支持运行于STM32MP257F-EV1评估板和STM32MP257F-DK板,主要特性包括:

■ 通过 VULKAN 1.1支持GPU优化图形特性

■ 支持视频编码/解码H264 VP8

■ 基于USB Type-C®的USB 3/SD卡

■ 支持密封系统构建/基于AIDL的HAL接口

■ 同时还支持强大的无线连接特性:Wi-Fi® 802.11b/g/n,Bluetooth® 5.2 BR/EDR,低功耗蓝牙。

OpenSTDROID的交付遵循OpenSTLinux交付(基于相同的BSP)。ST将提供长达2年的支持和维护服务。

欢迎通过STM32 MPU WIKI 查看更多信息(OpenSTDroid distribution - stm32mpu (stmicroelectronics.cn)

即刻开始你的嵌入式安卓应用创新之旅!

参考资料

[1]https://source.android.com/
[2]https://android.googlesource.com/kernel/common/
[3]https://android.googlesource.com/kernel/configs/
[4]https://android.googlesource.com/platform/manifest/
[5]https://source.android.com/setup/start/build-numbers#source-code-tags-and-builds

来源:STM32

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

围观 25

简述

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

关于IIC可以参考之前发的一篇文章:《通信协议 IIC 与 SPI 最全对比》来了解。    

使用面向对象的编程思想封装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存储器的封装,最终对外仅提供一个操作对象接口,大大提高了代码的复用性以及封装性。

来源:单片机与嵌入式

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

围观 30

前言    

测试代码的运行时间的两种方法:

  • 使用单片机内部定时器,在待测程序段的开始启动定时器,在待测程序段的结尾关闭定时器。为了测量的准确性,要进行多次测量,并进行平均取值。

  • 借助示波器的方法是:在待测程序段的开始阶段使单片机的一个GPIO输出高电平,在待测程序段的结尾阶段再令这个GPIO输出低电平。用示波器通过检查高电平的时间长度,就知道了这段代码的运行时间。显然,借助于示波器的方法更为简便。

借助示波器方法的实例    

Delay_us函数使用STM32系统滴答定时器实现:

#include "systick.h"

/* SystemFrequency / 1000    1ms中断一次 
 * SystemFrequency / 100000     10us中断一次 
 * SystemFrequency / 1000000 1us中断一次 
*/

#define SYSTICKPERIOD                    0.000001
#define SYSTICKFREQUENCY            (1/SYSTICKPERIOD)

/**  
  * @brief  读取SysTick的状态位COUNTFLAG  
  * @param  无  
  * @retval The new state of USART_FLAG (SET or RESET).  
  */

static FlagStatus SysTick_GetFlagStatus(void) 
{
    if(SysTick->CTRL&SysTick_CTRL_COUNTFLAG_Msk)     
    {
        return SET;    
    }
    else    
    {
        return RESET;    
    }
}

/**  
 * @brief  配置系统滴答定时器 SysTick  
 * @param  无  
 * @retval 1 = failed, 0 = successful  
 */

uint32_t SysTick_Init(void)
{
    /* 设置定时周期为1us  */
    if (SysTick_Config(SystemCoreClock / SYSTICKFREQUENCY))     
    { 
        /* Capture error */
        return (1);    
    }
    
    /* 关闭滴答定时器且禁止中断  */    
        SysTick->CTRL &= ~ (SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);                                                  
        return (0);
}

/**  
    * @brief   us延时程序,10us为一个单位  
    * @param  
    *        @arg nTime: Delay_us( 10 ) 则实现的延时为 10 * 1us = 10us  
    * @retval  无  
    */
    
void Delay_us(__IO uint32_t nTime)
{     
    /* 清零计数器并使能滴答定时器 */    
    SysTick->VAL   = 0;      
    SysTick->CTRL |=  SysTick_CTRL_ENABLE_Msk;     

    for( ; nTime > 0 ; nTime--)    
    {
        /* 等待一个延时单位的结束 */
        while(SysTick_GetFlagStatus() != SET);    
    }
    
    /* 关闭滴答定时器 */    
    SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
}

检验Delay_us执行时间中用到的GPIO(gpio.h、gpio.c)的配置:

#ifndef __GPIO_H
#define    __GPIO_H

#include "stm32f10x.h"

#define     LOW          0
#define     HIGH         1

/* 带参宏,可以像内联函数一样使用 */
#define TX(a)                if (a)    \                                            
                                                                GPIO_SetBits(GPIOB,GPIO_Pin_0);\
else        \                                            
                                                                GPIO_ResetBits(GPIOB,GPIO_Pin_0)

void GPIO_Config(void);

#endif

#include "gpio.h"

/**  
  * @brief  初始化GPIO  
  * @param  无  
  * @retval 无  
  */
  
void GPIO_Config(void)
{        
    /*定义一个GPIO_InitTypeDef类型的结构体*/        
        GPIO_InitTypeDef GPIO_InitStructure;

    /*开启LED的外设时钟*/        
        RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); 
        
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;            
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;             
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;         
    GPIO_Init(GPIOB, &GPIO_InitStructure);    
}

在main函数中检验Delay_us的执行时间:

1.png

示波器的观察结果:

2.png

3.png

可见Delay_us(100),执行了大概102us,而Delay_us(1)执行了2.2us。

更改一下main函数的延时参数:

4.png

示波器的观察结果:

5.png

6.png

可见Delay_us(100),执行了大概101us,而Delay_us(10)执行了11.4us。    

结论:此延时函数基本上还是可靠的。

使用定时器方法的实例    

Delay_us函数使用STM32定时器2实现:

#include "timer.h"

/* SystemFrequency / 1000            1ms中断一次 
 * SystemFrequency / 100000     10us中断一次 
 * SystemFrequency / 1000000         1us中断一次 
 */
 
#define SYSTICKPERIOD                    0.000001
#define SYSTICKFREQUENCY            (1/SYSTICKPERIOD)

/**  
  * @brief  定时器2的初始化,,定时周期1uS  
  * @param  无  
  * @retval 无  
  */
  
void TIM2_Init(void)
{    
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    
    /*AHB = 72MHz,RCC_CFGR的PPRE1 = 2,所以APB1 = 36MHz,TIM2CLK = APB1*2 = 72MHz */    
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    /* Time base configuration */    
        TIM_TimeBaseStructure.TIM_Period = SystemCoreClock/SYSTICKFREQUENCY -1;    
        TIM_TimeBaseStructure.TIM_Prescaler = 0;    
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;    
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
        
    TIM_ARRPreloadConfig(TIM2, ENABLE);

/* 设置更新请求源只在计数器上溢或下溢时产生中断 */    
    TIM_UpdateRequestConfig(TIM2,TIM_UpdateSource_Global);     
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
}

/**  
  * @brief   us延时程序,10us为一个单位  
  * @param    
  *        @arg nTime: Delay_us( 10 ) 则实现的延时为 10 * 1us = 10us  
  * @retval  无  
  */
  
void Delay_us(__IO uint32_t nTime)
{     
    /* 清零计数器并使能滴答定时器 */    
        TIM2->CNT   = 0;      
        TIM_Cmd(TIM2, ENABLE);     
    for( ; nTime > 0 ; nTime--)    
    {
        /* 等待一个延时单位的结束 */
        while(TIM_GetFlagStatus(TIM2, TIM_FLAG_Update) != SET);     
        TIM_ClearFlag(TIM2, TIM_FLAG_Update);    
    }
    
  TIM_Cmd(TIM2, DISABLE);
}

在main函数中检验Delay_us的执行时间:

#include "stm32f10x.h"
#include "Timer_Drive.h"
#include "gpio.h"
#include "systick.h"

TimingVarTypeDef Time;

int main(void)
{        
    TIM2_Init();        
    SysTick_Init();    
    SysTick_Time_Init(&Time);
    
    for(;;)    
    {        
        SysTick_Time_Start();         
        Delay_us(1000);        
        SysTick_Time_Stop();    
    }     
}

怎么去看检测结果呢?用调试的办法,打开调试界面后,将Time变量添加到Watch一栏中。然后全速运行程序,既可以看到Time中保存变量的变化情况,其中TimeWidthAvrage就是最终的结果。    

7.png

可以看到TimeWidthAvrage的值等于0x119B8,十进制数对应72120,滴答定时器的一个滴答为1/72M(s),所以Delay_us(1000)的执行时间就是72120*1/72M (s) = 0.001001s,也就是1ms。验证成功。    

备注:定时器方法输出检测结果有待改善,你可以把得到的TimeWidthAvrage转换成时间(以us、ms、s)为单位,然后通过串口打印出来,不过这部分工作对于经常使用调试的人员来说也可有可无。相关推荐:学习STM32单片机,绕不开的串口。

两种方法对比

软件测试方法    

操作起来复杂,由于在原代码基础上增加了测试代码,可能会影响到原代码的工作,测试可靠性相对较低。由于使用32位的变量保存systick的计数次数,计时的最大长度可以达到2^32/72M = 59.65 s。

示波器方法    

操作简单,在原代码基础上几乎没有增加代码,测试可靠性很高。由于示波器的显示能力有限,超过1s以上的程序段,计时效果不是很理想。但是,通常的单片机程序实时性要求很高,一般不会出现程序段时间超过秒级的情况。

单片机与嵌入式单片机,嵌入式,C语言,电路PCB,半导体5篇原创内容公众号单片机302定时器6stm3212代码64单片机 · 目录上一篇单片机开发若干年,一些见闻与感悟下一篇为什么单片机学了很久还是不会做项目?

来源:STM32嵌入式开发

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

围观 22

作为现代制造业的核心驱动力,工业自动化对功能安全的需求日益紧迫。在追求高效、智能的生产过程中,功能安全不仅是技术进步的基石,更是对每一位工作人员生命安全的庄严承诺。国内外市场针对产品的功能安全认证要求也变得越来越严格。

功能安全要求工业自动化系统在任何潜在风险面前,都能够主动侦测风险,或在必要时以安全的方式进入故障状态。这意味着,设计者不仅要关注系统的稳定性和可靠性,更要深入研究和应用各种安全技术,确保在突发情况下,系统能够迅速、准确地做出反应,最大限度地避免人员伤害、设备损坏和生产中断等风险。

基于内置强大功能安全特性的 STM32 MCU和 MPU产品,STM32提供免费的功能安全设计包,包括:经认证的自检库和全面的安全设计文档,旨在极大减少客户实现功能安全认证或满足功能安全标准所需的开发工作量,节省时间及成本。

1.png

STM32芯片为工业而生,内置丰富的功能安全属性,全系列提供功能安全设计包,使终端客户在为功能安全产品选型时不必局限于特定型号,能游刃有余地选择合适的STM32产品,使最终产品更具竞争力。

2.png

每个STM32 MCU系列都提供一个独立的STM32 SIL功能安全自检库X-Cube-STL。自检库包含MCU内核的关键安全部件的自检,以及对应的安全手册和用户指南,且认证证书可以直接在官网下载,大大节省了终端用户的项目开发时间,并降低开发复杂度。

3.png

STM32 SIL功能安全设计包提供全面的安全文档,分为两大类:

一类是在官网公开的文档,如安全手册,详细列出了在硬件层面、软件层面和应用层面可以采取的安全机制,以指导STM32用户设计出符合IEC61508标准的产品。每个STM32系列对应一份独立的安全手册。标注为AN的应用文档都可以在st.com.cn官网下载,标注为LAT的文档则可在中文官网下载。

另一类是签署保密协议(NDA)后就可以免费释放的安全文档,如FMEA和FMEDA文档。FMEA里列出了MCU的失效模式和可采取的检测方法,FMEDA里列出整个MCU和各个基本功能模块的失效率数据等,在计算产品失效率时需要用到。


4.png

工业自动化中的功能安全应用场景

PLC、I/O、执行器、变频器、传感器

在工业自动化应用中,从核心PLC到现场末端的I/O、执行器、传感器等都有功能安全需求。

目前,安全PLC、伺服、变频器及I/O模块等这些工业自动化核心组成部件的功能安全认证需求正处于上升阶段。

STM32G4,STM32H5,STM32H7,STM32MP1及STM32F4等高性能MCU早已在行业内被普遍采用,这些产品系列都提供对应的功能安全设计包。在考虑添加功能安全认证的需求下,与认证机构充分沟通,根据IEC61508 SIL2/SIL3的要求,整合X-Cube-STL自检库,添加相关功能安全检测等方法实现产品的认证。

ST工业自动化中心也提供多个Demo可供评估。比如硬件上取得了TÜV Italia评估、可满足IEC61508 SIL2 要求的safe PLC demo STEVAL-SILPLC01, 采用了单颗STM32H723VGT6, 冗余DI/DO设计;目标为满足IEC61508、ISO13849等标准 SIL3/PLe要求的safe PLC demo STEVAL-SILKT01,采用STM32H743ZGT6和STM32G431RBT6冗余设计,并冗余设计DI/DO等。

5.jpg


6.jpg

STM32的合作伙伴则可提供Profisafe、FSoE等工业通信安全解决方案。

工业机器人

机器人在工业自动化中的应用越来越普遍,无论是协作机器人还是AGV机器人等都有功能安全认证需求。STM32F4、STM32G4、STM32H5、STM32H7等高性能MCU常被选做安全控制器的主控MCU。AN5698文档介绍了使用X-CUBE-STL库支持其他的标准,包含了对ISO13849的支持。

安全模块,安全开关,安全继电器

安全模块,安全开关,安全继电器等安全组件在自动化安全系统中也扮演着重要角色,STM32C0、STM32G0、STM32F4系列等高性价比MCU常被选用。

激光雷达/扫描仪,安全光栅/光幕,安全门闩

在现代化工厂中,为了减少自动化机械设备给工人带来的伤害,必须在存在危险或潜在危险的机床等设备周边提供安全的工作环境。激光雷达/扫描仪,安全光栅/光幕,安全门闩等被用作安全距离预警和危险区域识别,这些设备对功能安全认证也有严格要求。高性能STM32H5、STM32H7系列是激光雷达/扫描仪的理想选择。高性价比的STM32C0、STM32G0、STM32F4、STM32H5系列则适合安全光栅、光幕等设备。

传感及仪表

在传感及仪表侧的一些关键的场合也需要经功能安全认证的产品,如火焰的检测,燃烧的控制,液位的监测,涡轮的监测等场合。STM32U0、STM32U5等超低功耗系列在满足性能的同时又提供了超低功耗的设计。免费的自检库及安全文档则可加速终端客户的功能安全设计及认证过程。

STM32中文官网也为功能安全应用开设了专门的页面 (点击此处进入),方便用户信息查看和资源下载。

为工业而生的STM32,以其强大的性能,辅以功能安全自检库及丰富的功能安全文档,已帮助众多客户成功实施了丰富的功能安全应用,未来还将继续助力用户的功能安全设计,为工业自动化系统的安全保驾护航。

来源:STM32

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

围观 44

嵌入式系统因移动性和物联网节点端的资源限制,采用电池供电的场景越来越多。这样的应用场景下,对系统的低功耗性能提出了颇高要求。

那么,低功耗MCU该如何选型?我们有幸请到意法半导体中国区市场经理张明老师为大家解答。

Q1:STM32是一个超级MCU平台,超低功耗产品线是五大系列之一。请问为什么要开辟出超低功耗产品线?

嵌入式产品为了实现便捷的移动性,或者物联网应用场景中节点端的资源受限,采用电池供电的应用越来越普遍。那么,在电池供电的场景下,对产品的低功耗设计要求非常高。此外全世界都在寻找可持续发展的战略,有上亿颗的传感器和执行器布局在世界的各个角落,如果把这些设备功耗做一定的优化,就可以大大节省能源损耗。低功耗的产品可以延长整个电池的使用寿命,减少电池替换频率,从而达到节约能源,减少碳排放的目的,为全球节约能源做出贡献。

因而,STM32通过专门的低功耗产品线帮助开发者实现低功耗设计。STM32 超低功耗系列自2011年问世,到现在,累计出货量已达20亿颗,全球市场份额超过 30%。

Q2:STM32超低功耗系列有哪些子系列?

低功耗系列有STM32L0、STM32L1、STM32L4、STM32L4+ 以及STM32L5,新一代超低功耗系列有STM32U0和STM32U5,一共7个子系列。其中STM32L1系列,我们仅维持存量项目,不向新项目供货。各个子系列的内核、主频和外设资源各有不同,CoreMark跑分和ULPMark分数有所差异。

1.png

目前,主推的是STM32U5和全新系列STM32U0。STM32U5采用了40nm工艺制程,而STM32U0则基于90nm工艺制程;两个子系列都具备优异的功耗特性,还集成了ULP定时器。STM32U5是Cortex-M33内核,资源更多,性能更强,面向复杂的低功耗应用;STM32U0则基于Cortex-M0+内核,面向入门级低功耗应用。

Q3:STM32L0也是基于Cortex-M0+内核,与其相比, STM32U0的关键特性和优势有哪些?

先向大家晒一下STM32U0的ULPMark成绩单。

2.png

STM32U0功耗远远低于前几代产品。与STM32L0相比,STM32U0具有:

  • 更低的功耗特性,延长设备使用时间;

  • 增强的固件保护和信息安全特性;

  • 灵活的ART Accelerator 加速器,提高执行效率;

  • 更高的集成度,降低产品BOM 成本。

Q4:使用STM32U0开发时,我们可以期待哪些节能效果?

这实际上取决于最终应用的使用场景(即工作模式与低功耗模式之间的时间比)。但与STM32L0相比,在典型的水表应用中获得38%的节能效果,在工业传感器应用中获得50%的节能效果。

3.png


Q5:STM32U0是否可以看作是STM32L0的替代升级版?

STM32U0可以看做是STM32L0的升级,因为无论从性能,功耗,安全级别,还是从性价比角度考虑,STM32U0都有了很大的提高。但在需要较小Flash资源的入门级低功耗应用中,我们仍然推荐STM32L0。

STM32L0 未来不会再推出新的产品,我们会着力发展STM32U0。这两个系列完全Pin2Pin兼容。

Q6: 简要介绍一下STM32U0与STM32L0的性能差别?

4.png


  • STM32U0 最小flash 为16KB;STM32L0 最小Flash 为8KB;

  • STM32U0 最大Flash 为256KB;而STM32L0 最大Flash 为192KB;

  • STM32U0 RAM 最大40KB;STM32L0 RAM 最大20KB;

  • STM32U0 新增加了LQFP80 pin 封装,取消了LQFP100 pin 封装;STM32U0 的所有封装都已经全部量产;

  • STM32U0 和STM32L0都有PLL 锁相环时钟,时钟源非常丰富,包括HSI,MSI,HSE,LSE,LSI 等;

  • STM32U0全系列内置了VBAT 功能;STM32L0 只有大资源、大管脚封装才有VBAT功能;

  • STM32U0 内置ART Accelerator 加速器, 可以提高执行效率,使系统尽快进入休眠模式;STM32L0没有内置ART Accelerator 加速器;

  • STM32U0 和STM32L0都有三个级别的固件保护功能,但STM32U0 可以基于密码回退到低版本级别;

  • STM32U0 通过SESIP 3级、PSA 认证1级,以及NIST 认证的Cotex ®-M0+ MCU。

来源:STM32

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

围观 40

随着以ChatGPT为代表的AI大模型的衍生应用不断推出,许多终端开始智能化体验升级,从而产生了海量的终端数据分析处理需求。企业的业务部署场景和数据产生正在向端侧、边缘侧“迁移”。边缘人工智能,又称为“边缘计算”,是指在边缘侧使用人工智能算法和模型处理和分析数据,而不是将数据传输到中央服务器进行处理。在边缘侧设备中运行AI有很多优势:设备响应速度快、超低延时;降低数据传输量;更有效地保护隐私、增强信息安全;降低边缘侧设备的运行功耗;还可以降低推理成本以实现其他新的功能操作。

目前,逐步成熟并规模化落地的边缘AI应用场景主要集中在工业和物联网领域。

预测性维护

预测性维护(PdM)是工业互联网的重要应用,在上世纪90年代就已经被用于飞机发动机领域。最近几年,随着工业人工智能技术和边缘计算技术的逐步推进和成熟,过去仅仅集中应用于高端装备的预测性维护,可以“飞入寻常百姓家”,具备了大范围应用的经济性前提。

在工业维护 — 状态监测/预测性维护应用中,边缘AI系统可用于实时监测工业设备和系统的健康状况和性能,主动且精确地识别潜在的故障,从而将供电中断的影响降至最低。  

1.png

ST基于STM32硬件平台,通过NanoEdge Studio模型创建工具,为工业预测性维护提供完整的解决方案,如为驱动层的变频器、伺服等设备提供的方案自带机器学习功能,只需通过本体电流、电压进行故障检测和预测性维护,用户不需要增加额外的传感器,可以有效节约成本。为确保开发者能够轻松、快速地构建准确、高效的人工智能算法,以经济的方式将算法部署在资源受限的边缘计算设备上,NanoEdge AI Studio现已完全免费,允许在所有STM32 MCU上免费无限量部署。

2.jpg

另一个预测性维护应用场景是振动检测。用户只需在设备外置振动检测小盒子,借助NanoEdge AI库在终端设备上的学习能力进行渐进式学习,通过传感器(振动传感器、超声、温湿度、气压和加速度计)以高精准度实时检测设备的任何偏移或者异常,然后通过不同的通信协议发出警报,以便在发生重大故障之前作出调整。这些功能的实现得益于在超低功耗STM32微控制器上运行的机器学习算法。

3.jpg

机器视觉

随着工业数字化、智能化转型逐渐深入,市场对于工业机器视觉的需求逐渐增多。机器视觉用计算机模拟人的视觉功能,用于实际检测、测量和控制,具有高度自动化、高效率、高精度和适应较差环境等优点。

在工业领域,机器视觉对计算高效性和实时性有严格要求,将计算资源部署在工业现场附近才能满足需求,因此边缘计算成为关键节点。

机器视觉在工业领域的应用场景主要是通过工业相机进行拍照,并利用AI算法进行快速判断,实现丰富的视觉识别应用,如各种条形码、二维码识别、瑕疵识别/检测、PCB检测、半导体产线破损检测系统等。 

STM32针对机器视觉应用提供了强大的硬件平台,包括集成硬件NPU和丰富视频接口的STM32MCU和MPU。

STM32N6是ST首款集成自研硬件NPU神经网络硬件处理单元的通用微控制器,其算力可达0.6TOPS(每秒0.6万亿次运算)。STM32N6还集成了新IP和视频外设,如MIPI CSI摄像机、机器视觉图像信号处理器(ISP)、H.264视频编码器和支持时间敏感网络(TSN)端点的千兆以太网控制器。此外,STM32N6是一款通用STM32产品,符合工业客户的所有要求,包括在高温环境中工作。

4.png

STM32MP2是ST去年推出的第二代MPU产品,是一款带有神经处理单元(NPU)的64位微处理器,采用大小核异构架构:主频为1.5GHz的双核Cortex-A35和主频为400M的Cortex-M33内核,大小核均可单独启动。Cortex-A35大核通常运行基于Linux 或Android 的应用,Cortex-M33核运行基于裸机或RTOS的硬实时应用。异构双核可基于片上共享内存实现高效的双核通信,并可灵活分配片上资源。

STM32MP25具有先进的边缘AI能力以及丰富的多媒体功能。它内置有1.35 TOPS算力(每秒1.35万亿次运算)的NPU加速器,并且还支持带ISP的 MIPI CSI接口,可以实现如机器视觉在内的多种AI应用;STM32MP25还内置有900MHz的3D GPU,全高清视频编解码,并配有ISP处理器的MIPI CSI-2 摄像头接口,以及丰富的显示接口,支持RGB、LVDS 和DSI输出的全高清视频。

基于STM32MP2卓越的处理性能及先进的边缘AI和多媒体功能,开发者可灵活选择在CPU、GPU、NPU上运行AI应用。

5.png

典型应用场景:电梯

边缘AI解决方案为很多传统行业赋予了新价值和新生命,其中一个典型案例就是电梯应用。该应用将预测性维护与机器视觉场景汇于一身,可实现语音识别、视觉识别、手势识别、群控箱智能算法、电梯故障点检测和预测性维护。

智能化电梯预测性维保解决方案融合了云计算、大数据和机器学习的优势,可以对所有连接的设备进行实时监控,并通过数据分析识别潜在问题并提供建议,以便维保人员及时修复故障以及进行预防性保养,大大提升电梯的安全性和可用性。

电梯的机器视觉识别应用将高清影像、精准数据与智能分析融为一体,赋予电梯前所未有的“视觉”、“感知”与“思维”,确保安全、高效运行。具体场景包含:

  • 负责安全、身份识别的人脸识别;

  • 负责电梯安全的危险物品识别(如电瓶车识别,这是最近热议话题);

  • 判断人员流动、计数功能,可配合群控箱算法做群控,实现节能减排的目标。

ST 边缘AI助工业客户快速落地

ST为边缘AI提供丰富的硬件产品、软件工具和强大的生态系统,保证开发者能够在MCU和MPU上优化和运行AI模型,帮助工业用户快速落地。

6.png

在硬件方面,ST不断推出更多通用和带硬件加速的MCU和MPU,如STM32 N6和STM32MP2。

在软件工具方面,ST提供丰富的软件工具,满足用户的各种需求,包括:

  • NanoEdge AI Studio:面向STM32 MCU的自动化机器学习工具;

  • STM32Cube.AI:适用于STM32 MCU的AI模型优化器;

  • STM32Cube.AI开发者云平台,可创建、优化和生成适用于STM32微控制器的人工智能,以及进行基准测试;

  • X-LINUX-AI,STM32 MPU上面向OpenSTLinux的完整AI框架,可简化基于OpenSTLinux的项目中经训练的AI模型的集成。

ST的目标是通过嵌入式AI,赋能工业自动化,为传统行业开启全新应用可能性,解锁AI应用的普惠之道。下篇文章,我们将探讨工业应用的功能安全和信息安全,敬请关注!

来源:STM32

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

围观 89

在用Keil对STM32的程序进行仿真时程序有时会跑飞,停止仿真程序会停在HardFault_Handler 函数里的死循环while(1)中。

这说明 STM32 出现了硬件错误。

STM32出现硬件错误可能有以下原因:
  1. 数组越界操作;
  2. 内存溢出,访问越界;
  3. 堆栈溢出,程序跑飞;
  4. 中断处理错误;

遇到这种情况,可以通过以下2种方式来定位到出错代码段。

方法1:

在硬件中断函数HardFault_Handler里的while(1)处打调试断点,程序执行到断点处时点击STOP停止仿真。

2.png

在Keil菜单栏点击View——Registers Window,在寄存器查看窗口查找R14(LR)的值。

如果R14(LR) = 0xFFFFFFE9,继续查看MSP(主堆栈指针)的值;如果R14(LR) = 0xFFFFFFFD,继续查看PSP(进程栈指针)的值;

我的程序R14(LR) = 0xFFFFFFF9,接下来以此为例。

3.png

在Keil菜单栏点击“View”——“Memory Windows”——“Memory1”;

在“Address”地址栏中输入MSP的值:0x20001288,然后在对应的行里找到地址。

地址一般以 0x08 开头的32位数。本例中,地址为0x08003CB9。

4.png

在Keil菜单栏点击View——Disassembly Window,在Disassembly窗口中右击,在下拉菜单中选择Show Disassemblyat Address...。

在弹出框Show Code atAdress的地址框中输入地址0x08003CB9进行搜索,然后就会找到相对应的代码。这里的代码就是进入循环中断之前的情况。

仔细查看附近区域的相关代码来排查错误具体原因。

5.jpg

方法2:

在硬件中断函数HardFault_Handler里的while(1)处打调试断点,程序执行到断点处时点击“STOP”停止仿真。

6.jpg

在Keil菜单栏点击View——Call Stack Window弹出Call Stack + Locals对话框。

然后在对话框中右键选择Show Caller Code,就会跳转到出错之前的函数处,仔细查看这部分函数被调用或者数组内存使用情况。

来源:嵌入式从入门到放弃

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

围观 866

页面

订阅 RSS - STM32