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

觉得简单,说明ST成功了

当年STM32是怎么在众多单片机里脱颖而出的?因为有库函数这个东西。

在这之前,单片机主要是51、AVR、PIC、MSP430这些玩意,就是写个UART通信函数,都要先去搜索下别人的代码“借鉴”一下,发现别人写得不完全满足需求又自己修改,然后慢慢地积累出自己的一套库,在换下一款单片机的时候这几年的积蓄发现又用不上。

ST半导体搞这个库函数,不就是为了降低开发门槛么?开发库相关文章:STM32标准库、HAL库特点与应用。

让开发者专注于更抽象的应用而不需要太关注单片机底层的东西才是大趋势,ST抓住这个趋势先发制人才得以迅速占领市场。

什么?直接写寄存器?汇编?这种炫技的东西绝大多数STM32的目标应用都不需要。什么成本敏感?成本敏感谁选STM32啊。大把几毛钱一颗,却只能用汇编编程的单片机,可以去了解一下。

入坑STM32,我们要学习什么

  • 做一个键盘/鼠标,可以学习USB协议

  • 做一个联网设备,需要学习以太网,TCP/IP协议的底层实现

  • 做一个无线设备,可能需要学习蓝牙、WIFI或者zigbee的协议

而最终的应用,可能不一定有库或者包,需要自己去写底层。如果用模块,则可能需要学习SPI、I2C或者SDIO接口去连接模块。

做一个平衡小车,需要学习PID算法,相关项目请移步:STM32平衡自行车项目,你也可以试试!文末附代码。

做一个摄像头能需要学习SCCB协议,如果想深入一些,可能需要学习一些图像处理的知识。

如果这个摄像头要连接PC,做更深度的处理,则需要学习上位机的编写,可能需要设计协议或者写一个服务器,此时可能需要学习开发服务器的知识,以及一门写服务器的语言,包括不限于C++/Java/python/nodeJs。服务器框架可能也要学一个。服务器都有数据库,学习个简单的nosql吧,当然mysql和oracle也可以。

上位机要是需要图形界面,最简单的可能是C#,当然C++调用win32 API更好。

如果要做人脸识别、图形分割,还需要机器学习的知识,可能需要python和tensorflow / pytorch框架,当然其他语言也可以,但可能更复杂,而机器学习,基础是数学,嗯~,很难的那种。

现在都是手机端操作,Android可能学一下,Java走起。如果碰巧手机是苹果的,objective-c学起来。

如果继续深入,做一个更复杂的设备,首先需要自己设计板子。这时候PCB设计需要学,模电数电需要学,电路原理更需要学。

软件层面,可能发现裸机已经不够用了,可能需要学习ucos/freeRTOS之类的操作系统,甚至自己学习写一个操作系统内核。如果这个系统要求健壮、高效,需要深入理解操作系统、数据结构、算法、优化、汇编语言等。

上述种种内容还不少,不知不觉已经学习了计算机科学的一部分了,对计算机科学有了基本的了解了。

如果大家觉得这是强行关联,其实不是。综上所述,只是用STM32做了一个处理单元放在服务器端的小机器人,能保持平衡、识别人脸、能在PC端和手机端控制而已。

语音识别、对话系统等功能还没说,这些可能需要更多自然语言处理、乃至强化学习的知识,你发现,往往效果不尽如人意,这不是你技术不好,而是这个世界的瓶颈,恭喜,可以看论文、写论文,为人类在计算机、人工智能领域的突破做贡献了。

什么?想要把运动单元做波士顿动力那样的?这事情就更复杂了,不然你以为我怎么入坑的?有个波士顿机器人的视频,看这里:波士顿机器人一直被欺负,最后忍无可忍。

当技术往深了说

技术深究起来自己都怕,比如刚学习了STM32的cortex架构,那其寄存器、指令集、内存是如何实现的?刚了解这些,那其锁存器是哪一种,取指与解码过程是怎样的?加法器又怎么实现?刚了解三极管和布尔逻辑运算,那三极管是怎么工作的?刚了解PNP和NPN,那共射特性曲线是怎样的?为什么载流子会运动,正偏反偏是什么?为什么磷、硅、硼作为半导体材料,会形成电子流动?空穴是否能流动?

重点不是STM32能做什么,而是我们能用STM32做什么?能做什么程度的产品,市场竞争力如何,甚至考虑是否满足了用户未被满足的需求,也就是填补了市场空白。技术并不是独立的,而是拓扑的。

来源:STM32嵌入式开发

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

围观 11

WiFi6/Bluetooth®低功耗蓝牙5.3/Thread ST67W611M1模块加快开发进度,提高设计灵活性,提供先进的消费和工业物联网解决方案

服务多重电子应用领域、全球排名前列的半导体公司意法半导体(STMicroelectronics,简称ST;纽约证券交易所代码:STM) 推出了与高通技术公司(Qualcomm)战略合作的首款产品,新产品可以简化下一代工业和消费物联网无线解决方案开发过程。此项合作的初期目标是依托意法半导体的强大的STM32生态系统,借助高通技术公司领先的无线连接解决方案,为消费和工业市场推出无线物联网模块。

1734938311727271.jpeg

第一款模块ST67W611M1包含一个 Qualcomm® QCC743 多协议连接系统芯片 (SoC),预装了 Wi-Fi6、Bluetooth 5.3 qualified和Thread combo协议,可以与任何 一款STM32微控制器 (MCU) 或微处理器 (MPU)轻松集成。该模块将支持Wi-Fi上的Matter协议,可以实现面向未来的无线连接,让STM32产品组合能够顺利进入Matter生态系统。为了方便系统集成,该模块还包含4MB的代码和数据闪存,以及一个40MHz晶振。此外,模块还配有一个集成的PCB天线或微型 RF (uFL) 外部天线连接器。

意法半导体微控制器、数字 IC 和射频产品部 (MDRF) 总裁 Remi El-Ouazzane 表示:“我们的合作为使用STM32 系列设计嵌入式系统的广大开发者带来了多重优势。现在,产品开发者可以轻松获得高通的极具影响力和使用广泛的无线连接技术和STM32开发生态系统强大的软件、工具和功能,以及加快项目进度的优势。”

高通技术公司连接、宽带和网络业务部总经理Rahul Patel表示:“我们的使命才刚刚开始,我们预计这一合作将会产生更多的成果,为新的先进的边缘处理应用赋能,我们期待与意法半导体继续合作,通过 Wi-Fi、蓝牙、AI、5G等技术为用户带来更多无与伦比的连接体验。

模块内置高级硬件安全功能,包括硬件加密加速器以及安全启动和安全调试等服务,达到 PSA 认证的 1 级保护。该模块是一个独立的产品,根据强制性规范进行了预认证,没有要求开发者必须具备射频设计专业知识。该模块在32 引脚LGA 封装内集成了许多功能,可直接安装在电路板上,可以使用简单的低成本的两层 PCB电路板。

ST67W611M1依托STM32生态系统。该生态系统包含4,000多款产品、强大的 STM32Cube工具和软件,以及促进边缘人工智能开发的软硬件,其中包括最近推出的 STM32N6 MCU和ST Edge AI Suite软件。STM32N6 MCU集成了意法半导体自研的神经网络处理器Neural-ART Accelerator;ST Edge AI Suite提供AI Model Zoo模块库以及STM32Cube.AI和NanoEdge AI优化工具。

这些模块的设计意图是与任何STM32微控制器或STM32微处理器快速集成,为客户提供灵活、广泛的性能、价格和功耗选择。现有微控制器产品系列齐全,低中高端应用市场全覆盖,有搭载Arm® Cortex®-M0+内核的成本和功耗敏感的产品,还有基于高性能内核的微控制器,例如,搭载Cortex-M4和Cortex-A7的STM32MP1/2 MPU。

ST67W611M1样片现已上市,2025年第一季度开始为OEM供货,大众市场供货时间为2025年第二季度。若要申请样品和询价,请联系当地的意法半导体销售办事处。

产品详情访问 www.st.com/st67w

还可以查阅10月的新闻稿: STMicroelectronics and Qualcomm enter strategic collaboration in wireless IoT

关于意法半导体

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

围观 19
01、简介 

STM32 Model Zoo是指ST在github上开源的AI相关模型仓库,其包含的应用包括:手势识别,人体活动姿态识别,图片分类,目标检测等相关应用代码,用户可以使用其中的代码进行简单的配置并进行训练,然后使用STM32Cube.AI工具或STM32CubeAI Developer Cloud 得到模型的Flash,Ram等情况,并进行模型的测试验证,然后部署到开发板中。

本文将以手势识别为例,介绍Model Zoo的使用。

02、环境安装 

详细的安装步骤可以参考ST的wiki页面

2.1. 设置代理等环境变量 

为了后续使用pip进行python包的安装过程顺利进行,需要设置好代理。需要设置的环境变量包括:

1733707647769729.png

注意特殊字符需要编码成utf-8格式,比如@要替换为%40,其他的特殊字符包括%、@、&、!等。stmai_username 和stmai_password 可以不设置,如果不设置,每次访问 stm32CubeAI Devleloper cloud 都需要手动重新输入用户名和密码。

2.2. 安装 STM32Cube.AI 

可以使用云端的STM32Cube.AI Developer Cloud, 也可以在本地安装STM32Cube.AI。

  • 如果使用云端,需要保证网络连接,并且注册 myST 账号,确保自己可以正常访问 STM32Cube.AI Developer Cloud 网站。

  • 如果使用本地,可以通过CubeMX进行安装。

2.3. 安装STM32 model zoo 

可以使用git或使用zip包获取STM32 model zoo相关的代码。github仓库位于:https://github.com/STMicroelectronics/stm32ai-modelzoo

1使用Git:git clone https://github.com/STMicroelectronics/stm32ai-modelzoo.git

2. 使用zip:从git仓库中下载zip压缩包,解压即可。

2.4. 安装Python环境 

如果没有本地Python,需要安装好Python,推荐Python v3.10.x。可以使用 Python 或miniconda来管理python环境,推荐使用miniconda。具体安装步骤请参考上文提到的ST WIKI页面。

2.5. 安装STM32CubeIDE

需要安装STM32CubeIDE 来重新编译生成的固件,安装包可以通过ST官方网站下载。注意:STM32CubeIDE 和 STM32Cube.AI 的安装路径不要有空格,否则可能会出错。

2.6. 更新yaml文件 

Model zoo 中的所有yaml中的path_to_stm32ai 和path_to_cubeIDE,需要和本地的安装目录保持一致。

注意:Model zoo使用mlflow管理生成的模型,Windows的最大路径长度限制为 256,可能会导致问题,建议通过注册表修改LogPathsEnabled为1避免该问题。该键位 Computer/HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/FileSystem/

03、手势识别应用

手势识别应用位于stm32ai-modelzoo的hand_posture目录下,使用ST的TOF传感器VL53L5CX或VL53L8CX采集数据。用于识别静态的手势,比如like, dislike, love 等手势。其demo应用部署在NUCLEO-F401RE和X-NUCLEO-53LxA1扩展板组合的开发板上。

1733707757754804.png

▲ 图1. 部署环境和开发板 

3.1. 数据集 

手势识别应用的数据集可以直接使用hand_posture/datasets/目录下的数据集文件:ST_VL53L8CX_handposture_dataset.zip,解压后的数据集如下图所示:

1733707784510161.png

▲ 图2. 手势数据集 

也可以使用ST提供的数据采集和标记工具STSW-IMG035_EVK,进行数据采集和标记。点击进入工具的下载地址

1733707808288780.png

▲ 图3. 手势数据采集和标记工具 

3.2. 配置 

在stm32ai-modelzoo\hand_posture\src目录下,有user_config.yaml配置文件,里面可以对各种参数进行配置。

3.2.1. 训练 

操作模式operation_mode的配置 

操作模式选择training可以进行模型训练,其他可选择的操作模式包括evaluation, deployment, benchmarking。

5.png

▲ 图4. 选择模式 

数据集的配置:

1733707848933633.png

▲ 图5. 数据集配置 

数据集可以来源于仓库的数据集,也可以来源自自己采集的数据,其内容需要和文 件stm32ai-modelzoo\hand_posture\src\utils\handposture_dictionnary.py的数据一致。验证集可以不指定,默认将使用数据集内的20%作为验证。

预处理和数据增强的配置:

1733707875217443.png

▲ 图6. 预处理和数据增强

preprocessing: 

  • Max_distance:单位为mm,代表传感器的最远距离,如果超过这个距离,就 从数据集中丢弃该帧。

  • Min_distance:单位为mm,代表传感器的最远距离,如果小于该距离,则从 数据集中丢弃该帧。

  • Background_distance:单位为mm,手势背景的最大间隔,超过这个间隔的 区域将会被忽略掉。

data_augmetation: 

使用数据增强可以在一定程度上提升模型的泛化能力,并且当数据比较少的时候可以补充数据集,目前支持的增强模式包括:水平翻转,垂直翻转,水平+垂直翻转。

训练参数的配置:训练参数的配置如下图7所示,具体内容不再赘述。

1733707901844888.png

▲ 图7. 训练参数的配置

3.2.2. 评估 

操作模式operation_mode选择评估(evaluation)可以进行模型评估,评估的内容主要包括模型flash,ram的占用量,模型的执行时间等。tools的配置如下:

1733707928757240.png

▲ 图8. Tools的配置 

on_cloud:是否需要使用STM32Cube.AI开发者云对模型进行评估,如果不使用云,选择False,则可以使用本地的Cube.AI工具。

path_to_stm32ai和path_to_cubeIDE需要配置成自己本地STM32Cube.AI和 STM32CubeIDE的路径。

3.2.3. 部署 

操作模式operation_mode选择部署(deployment)可以进行模型部署。当需要部署到ST开发板进行测试时,需要确保以下配置都正确:

1733707946467794.png

 ▲ 图9. 部署相关的配置

3.3. 运行脚本 

在安装好python环境,并且配置好配置文件之后,就可以运行脚本进行训练,部署或评估流程了。

3.3.1. 模型训练 

直接运行脚本:

➢ python stm32ai_main.py 

或者指定模式运行脚本:

➢ python stm32ai_main.py operation_mode=”training” 

训练过程中的输出结果:

1733707982896778.png

▲ 图10. 训练中的输出结果

1733708017686368.png

▲ 图11. 混淆矩阵 

训练完成的结果保存在:experiments_outputs/<时间戳>目录下,包含了训练的log,训练曲线,混淆矩阵,模型等信息。

3.3.2. 可视化训练结果 

进入logs所在目录(位于experiments_outputs/<时间戳>目录下),然后运行 tensorboard: 

➢ tensorboard --logdir logs 

打开网页后,可以看到整个训练过程中的曲线,比如loss曲线,学习率曲线,epoch 曲线等等。  

1733708051425773.png

▲ 图12. 准确率曲线 

也可以通过mlflow查看实验过程中保存的参数信息,模型实验对比信息等等。进入experiments_outputs目录,然后执行以下命令:

➢ mlflow ui

1733708104531600.png

▲ 图13. Mlflow界面

3.3.3. 模型评估 

将上一步生成的模型路径,填写到user_config.yaml的model_path目录中,然后运行以下命令,可以查看运行模型评估脚本,并查看结果:

➢ python stm32ai_main.py operation_mode=”evaluation”

1733708124778970.png

▲ 图14. 配置文件和模型评估结果 

3.3.4. 基准测试 

在上一步完成后,可以运行以下命令,进行模型的基准测试,可以获取模型的flash, ram,macc等信息:

➢ python stm32ai_main.py operation_mode=”benchmarking” 

如果不使用开发者云,使用本地的STM32CubeAI,需要将on_cloud改为False,如下图:

1733708153735912.png

▲ 图15. 配置文件和基准测试

3.4. 部署 

连接好开发板,设置好STM32CubeIDE的路径后,运行以下命令可以编译工程并部署到开发板:

➢ python stm32ai_main.py operation_mode=”deployment”

1733708171554870.png

▲ 图16. 部署流程 

3.5. 总结 

本文介绍了STM32 model-zoo的基本内容,并以手势识别为例,介绍了具体的使用方法,包括模型训练,如何查看模型训练后的结果以及如何部署模型。对于其他model zoo 内的应用案例,感兴趣的话可以去github上下载整个仓库,然后进行阅读并使用。

来源:STM32

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

围观 43

来源:意法半导体博客

微控制器 (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产品系列

围观 37

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)。

围观 65

优化的集成化电源管理芯片,内置保护功能,驱动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

围观 22
最近,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)。

围观 32

简述

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)。

围观 37

前言    

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

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

  • 借助示波器的方法是:在待测程序段的开始阶段使单片机的一个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)。

围观 27

页面

订阅 RSS - STM32