当前位置:首页 > STM32
  • 意法半导体推出支持STM32的计算机视觉快速开发工具助力经济实惠的边缘AI应用开发

    意法半导体推出支持STM32的计算机视觉快速开发工具助力经济实惠的边缘AI应用开发

    中国,2021年3月4日——意法半导体推出新的AI固件功能包和摄像头模块硬件套件,让嵌入式开发人员开发出可在基于STM32 *微控制器(MCU)的边缘设备上运行的经济实惠且功能强大的计算机视觉应用。 STM32Cube功能包FP-AI-VISION1包含几个完整的计算机视觉应用代码示例,这些例程在STM32H747上运行卷积神经网络(CNN),并且可以在STM32全系列产品上轻松移植。该固件提出了几个应用示例,开发人员可以用所选数据集重新训练神经网络,为解决各种用例问题提供更大的自由空间和灵活度。 新功能包括支持USB VC摄像头(网络摄像头模式),简化图像采集任务,还包括食品分类和用户存在检测代码示例,其中用户存在检测示例可创建方便的视觉“ 唤醒语”,将系统从省电模式唤醒。在STM32 Wiki中有一篇如何将使用Teachable Machine在线工具配合STM32Cube.AI和FP-AI-VISION1功能包创建图像分类应用的文章。 B-CAMS-OMV摄像头套件与FP-AI-VISION1固件配合使用效果最好,并提供培训部署神经网络模型所需的硬件。摄像头套件包含一个内置意法半导体MB1379 500万像素OV5640彩色摄像头模块的转接卡。转接卡兼容所有的配有ZIF接口的STM32 Discovery探索板和评估板,还可以与意法半导体的VG5661车规灰度全局快门摄像头配合使用。此外,Waveshare接口和OpenMV接口让用户可以连接各种第三方红外和可见光摄像头,以解决更广泛的计算机视觉应用问题。在STM32 Wiki上有一篇如何将STM32Cube.AI生成的代码集成到OpenMV生态系统的文章。 FP-AI-VISION1包含各种帧缓冲处理功能、摄像头驱动程序,以及图像捕获软件、预处理软件和神经网络推断软件,还有几种神经网络模型可供使用,包括基于浮点的模型和X-CUBE-AI生成的量化模型,X-CUBE-AI是意法半导体优化的人工神经网络C代码生成器,因为支持灵活的内存配置,可以让开发者为预期的应用微调神经模型。

    时间:2021-03-04 关键词: STM32 计算机视觉 边缘AI

  • 基于STM32、FreeRTOS实现硬件看门狗+软件看门狗监测多任务的思路

    作者 | strongerHuang 微信公众号 | 嵌入式专栏 这是一篇旧文,技术交流群有人在讨论这个问题,今天就来分享一下。 我们都知道硬件看门狗的目的: 是用来监测系统,防止系统死机,并在死机的情况下使其系统复位重启。 在RTOS操作系统中,如果任务(线程)较多,出现高优先级任务长时间占用CPU资源,低优先级任务长时间得不到执行这种想象,那么我们的系统就是具有“Bug”的系统。 如上描述,假如我们的线程没有死机,只是长时间得不到执行。在这种异常情况下,我们又不希望系统复位,只希望执行特定代码,那我们该如何来避免这种问题呢? 嵌入式专栏 1 关于看门狗 硬件看门狗:利用一个定时器计数电路,其定时输出连接到电路的复位端,程序在一定时间范围内对定时“喂狗”。 因此程序正常工作时,定时器总不能溢出,也就不能产生复位信号。如果程序出现故障,不在定时周期内喂狗,就使得看门狗定时器溢出产生复位信号并重启系统。 在STM32中,有两个看门狗:独立看门狗和窗口看门狗。原理和功能都类似,只是应用场景不一样。 软件看门狗:软件看门狗和硬件看门狗原理类似,都是定期(在时间溢出之内),对其喂狗。只是软件喂狗的方式是通过自身设计的计数来实现。 嵌入式专栏 2 硬件+软件看门狗监测多任务的原理 1.利用一个监测线程(自身),来监测其它多个线程; 2.利用硬件看门狗来监测自身。 如图: 假如我系统中有多个应用线程(如上图),我就利用一个监测线程(自身),来监测其它多个应用线程。 同时,为了防止自身线程异常,利用一个硬件看门狗来监测自身。这样就可以做到双重监测的作用。 嵌入式专栏 3 结合软件来讲原理 上一节上述的原理可能对于有些人来说,是比较抽象的。那么这一节来看看代码: 监测线程(自身): 简单来说:在监测线程(自身)之中,需要对硬件看门狗进行喂狗。软件看门狗的角色:在这里就是对齐计数,浏览是否溢出,我把它封装成一个浏览函数。具体的喂狗就在其他各个被监测的线程中。 那么,再看软件看门狗对其中一个应用线程喂狗的代码: 这里只是简单的举例,一个主线程里面的喂狗。相当于:我线程启动之后,就需要定时喂狗。如果这里长时间不喂狗,那么监测线程(自身)就会发现你没有喂狗。 嵌入式专栏 4 简答的实现方法 看到这里,相信大家都知道其原理了。具体实现的方式方法很多种,可根据自己实际项目需要,添加相应的接口。这里举例几点吧。 定义一个数据结构: 这里举例,是实现最基础的东西,比如计数器,最大超时值等。 注册接口函数: 监测浏览函数接口: 以上只是教大家方法,具体的实现,可自己根据自己习惯,项目需求来定制化开发。

    时间:2021-03-03 关键词: 看门狗 FreeRTOS STM32

  • STM32单片机和51单片机有何区别?

    单片微型计算机简称单片机,简单来说就是集CPU(运算、控制)、RAM(数据存储-内存)、ROM(程序存储)、输入输出设备(串口、并口等)和中断系统处于同一芯片的器件。 在我们自己的个人电脑中,CPU、RAM、ROM、I/O这些都是单独的芯片,然后这些芯片被安装在一个主板上,这样就构成了我们的PC主板,进而组装成电脑。而单片机将这些都集中在了一个芯片上。 51单片机 应用最广泛的8位单片机,当然也是初学者们最容易上手学习的单片机。最早由Intel推出,由于其典型的结构和完善的总线专用寄存器的集中管理,众多的逻辑位操作功能及面向控制的丰富的指令系统,堪称为一代“经典”,为以后的其它单片机的发展奠定了基础。 ▼ 51单片机特性 51单片机之所以成为经典,成为易上手的单片机主要有以下特点: 从内部的硬件到软件有一套完整的按位操作系统,称作位处理器,处理对象不是字或字节而是位。不但能对片内某些特殊功能寄存器的某位进行处理,如传送、置位、清零、测试等,还能进行位的逻辑运算,其功能十分完备,使用起来得心应手。 同时在片内RAM区间还特别开辟了一个双重功能的地址区间,使用极为灵活,这一功能无疑给使用者提供了极大的方便。 乘法和除法指令,这给编程也带来了便利。很多的八位单片机都不具备乘法功能,做乘法时还得编上一段子程序调用,十分不便。 ▼ 51单片机缺点 (1)AD、EEPROM等功能需要靠扩展,增加了硬件和软件负担。 (2)虽然I/O脚使用简单,但高电平时无输出能力,这也是51系列单片机的最大软肋。 (3)运行速度过慢,特别是双数据指针,如能改进能给编程带来很大的便利。 (4)51保护能力很差,很容易烧坏芯片。 ▼ 51单片机应用范围 目前在教学场合和对性能要求不高的场合大量被采用。 使用最多的器件:8051、80C51。 STM32单片机 由ST厂商推出的STM32系列单片机,行业的朋友都知道,这是一款性价比超高的系列单片机,应该没有之一,功能及其强大。其基于专为要求高性能、低成本、低功耗的嵌入式应用专门设计的ARM Cortex-M内核;同时具有一流的外设,1μs的双12位ADC,4兆位/秒的UART,18兆位/秒的SPI等。 在功耗和集成度方面也有不俗的表现,当然和MSP430的功耗比起来是稍微逊色的一些,但这并不影响工程师们对它的热捧程度。由于其简单的结构和易用的工具,再配合其强大的功能,在行业中赫赫有名。 ▼ STM32单片机特性 (1)内核:ARM 32位Cortex-M3 CPU,最高工作频率72MHz,1.25DMIPS/MHz,单周期乘法和硬件除法。 (2)存储器:片上集成32-512KB的Flash存储器,6-64KB的SRAM存储器。 (3)时钟、复位和电源管理:2.0-3.6V的电源供电和I/O接口的驱动电压,POR、PDR和可编程的电压探测器(PVD),4-16MHz的晶振,内嵌出厂前调校的8MHz RC振荡电路,内部40 kHz的RC振荡电路,用于CPU时钟的PLL,带校准用于RTC的32kHz的晶振。 (4)调试模式:串行调试(SWD)和JTAG接口,最多高达112个的快速I/O端口、最多多达11个定时器、最多多达13个通信接口。 ▼ STM32常用的器件 使用最多的器件:STM32F103系列、STM32 L1系列、STM32W系列。 51和STM32的区别 51单片机是对所有兼容Intel8031指令系统的单片机的统称,这一系列的单片机的始祖是Intel的8031单片机,后来随着flash ROM技术的发展,8031单片机取得了长足的进展成为了应用最广泛的8bit单片机之一,它的代表型号就是ATMEL公司的AT89系列。 STM32单片机则是ST(意法半导体)公司使用ARM公司的cortex-M3为核心生产的32bit系列的单片机,它的内部资源(寄存器和外设功能)较8051、AVR和PIC都要多的多,基本上接近于计算机的CPU了,适用于手机、路由器等。 版权归原作者所有,如有侵权,请联系删除。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2021-03-03 关键词: 51单片机 STM32

  • STM32学习笔记 | I2C通信容易出错的情况

    I²C:全称为Inter-Integrated Circuit(内部集成电路),是一种串行通讯总线,常用于嵌入式电子产品中。 I²C是飞利浦公司在1980年为了让各种低速设备(飞利浦芯片)连接起来而研发的一种通信总线。目前,I²C依然是最常见的通信总线之一,现在绝大部分MCU都内部集成了I²C控制器,STM32也不例外,至少有一个I²C控制器,有的型号甚至多达6个。 嵌入式专栏 1 STM32 I2C基础内容 I²C总线协议有多个版本,有的STM32遵循的是第2版本,有的是第3版本。所以,不同型号的 STM32 中I²C 可能存在一些差异,但基本功能相似。 1. 主从模式特性 主模式特性: 时钟生成 起始位和停止位生成 从模式特性: 可编程 I²C地址检测 双寻址模式,可对 2 个从地址应答 停止位检测 2. 通信速度 标准速度:高达 100 kHz 快速速度:高达 400 kHz 超快速度:高达 1 MHz(第3版) 3.寻址模式 7 位寻址模式 10 位双寻址模式 广播呼叫地址 4.收发模式 从发送器 从接收器 主发送器 主接收器 这些都是STM32 I²C 的基础功能,更多内容请查阅芯片对应的参考手册。 嵌入式专栏 2 I2C 总线协议 I²C总线就两根线:SCL时钟信号,SDA数据信号。其中SCL由主机产生,SDA由主机或者从机产生。 I²C是同步串行通信,同一时间SDA只能由一个设备发送信号,也就是说它属于半双工通信。 I²C 总线协议可参考总线(SDA和SCL)的时序进行理解: 通常包含:起始位、数据/地址、ACK、结束位。 1. 开始和停止 在时钟线保持高的情况下, SDA数据线由高 -> 低:为总线开始条件; 在时钟线保持高的情况下 ,SDA数据线由低 -> 高:为总线结束条件; 2. 地址 I2C地址分7位和10位。 7位地址: 10位地址: 3. 应答(ACK) 应答(ACK)和非应答(NACK)发生在每个字节之后,是由接收方向发送方发出确认信号,表明数据已成功接收,并且可以继续发送下一字节数据。 I2C 总线协议更多内容可参看: https://zh.wikipedia.org/wiki/I²C https://www.nxp.com/docs/en/user-guide/UM10204.pdf 嵌入式专栏 3 STM32 I2C常见问题 I²C 总线通信,通常不会像CAN、USB这类总线添加一些复杂的(软件)通信协议。I²C 虽然硬件和协议简单,但在实际应用中还是经常出现各种问题。下面就来分析一下常见的问题。 问题一:IO模式不对 有些工程师对用于I²C 总线的GPIO不了解,写驱动代码时把总线(SDA、SCL)配置成推挽输出模式,导致应用上的异常。 I²C 总线是一种特殊的总线,因为多器件需共用总线,加上数据线需支持双向通信。SDA要求开漏输出模式。由于开漏无法直接输出“高”时,需外加上拉电阻配合。 解决办法:STM32的IO有8种应用模式,如果你通过软件模拟I²C,并将SDA配置为开漏输出模式,配合上拉电阻。这往往适用于主模式器件。如果使用硬件I²C,则需要配置成开漏复用功能。建议使用STM32CubeMX工具配置底层初始化代码。 问题二:总线电压不匹配 I²C 总线电压通常为3.3V或5V。有的I²C C总线上挂的设备比较多,有可能存在特殊电压,比如2.5V,或者3.3V不兼容5V,就容易引起信号辨识错误导致总线通信失败的情况。 解决办法:如果存在电压不匹配的情况,需要从硬件方面来解决,比如:通过专业转换模块。 问题三:软件检测死机 I²C 总线一般通过ACK信号来判断总线的情况,STM32实现I²C 收发、检测等操作是由内部控制器自动完成。 然而由于一些外部因素,比如干扰信号、电压不匹配等,容易引起总线上的信号不正常,从而导致检测失败,通信失败。 解决办法:解决这种因异常引起的死机,除了从硬件方面做调整外,也可以从软件入手,常见的做法是添加超时处理机制,不要让程序一直死等检测应答信号。 比方当发送超时情形时,可以尝试复位STM32 I2C外设或相关设备。 复盘一下 ▼I²C 基础内容:主从模式特性、通信速度、寻址模式、收发模式; ▼I²C  总线协议:起始位、数据/地址、ACK、结束位; ▼I²C 常见问题:IO模式不对、总线电压不匹配、软件检测死机 ------------ END ------------ 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2021-03-03 关键词: 通讯总线 I2C STM32

  • 51单片机学习单片机之路总结

    51单片机学习单片机之路总结

    学习单片机有一学期了,现在也由51转到STM32了。一直想对51的学习做一个总结。也希望对别人有一些启发。也给后学者提供一些建议。当然本文是我对自己学习过程的总结,若有不对的地方,还请高手指出。 我想,再看本文之前,最好对单片机有了最基础的了解,最好能用单片机驱动起一个LED灯,否则,可能会不知所云。 首先我想问一个问题,你认为单片机有哪些内容呢?也许你现在手里有一块开发板,你已经开始从流水灯开始,一个一个外设在跑了。也许你已经看过一些单片机入门的书了。如果是这样,我估计你会回答,单片机包括了流水灯,键盘,数码管,定时器,中断,串口,AD,DA,液晶,DS18B20……其实这样的回答其实符合了大多数初学者的心态。因为天祥老师的视频也是这么教的。但是当你会操作流水灯,键盘,数码管,液晶,你有没有发现他们有一个共同点,就是都是通过I/O口输出或检测高低电平来驱动这些外设,那我们可不可以把这些模块归为一类,就叫I/O口操作呢,那么这样,要学的内容就又被浓缩了一下,可分为四个模块了,I/O口,定时器,中断,串口。对于AD,有的单片机,例如**公司的加强型单片机就自带了AD,若使用AD芯片,则也属于I/O部分,AD部分我放到后面再提。 这样,学起来条理就清楚了,其实当你驱动了流水灯和按键模块。可以说,你就完成第一部分,I/O口的学习了。I/O的输入和输出你都学会了。至于数码管,液晶,那是为了加强你I/O口编程的能力,说来说去,就是在什么时间输出高电平,什么时间输出低电平以符合他们的通信协议而已。如果你能理解他们都是I/O操作,学起来这些是很简单的。 接下来,继续定时器,中断的学习,无所质疑,定时器与中断是分不开的,没有中断,定时器也很难实现功能。在这里,建议先学习外部中断,如果你认真的学完外部中断,我想你应该能深刻了解中断的含义(这里插一句,一定要理解中断,为后来更高级处理器的中断系统打基础),在学定时器与定时器中断。如果你能深入学习定时器与定时器中断,我想这是,你应该能用数码管做一个电子钟了,具体实现就看你的编程水平了,可以试一试哦。 然后,花一把功夫学完串口通信,我可以对你说,你把单片机的内容学完了。现在,你可以试着把这些分立的模块组合做成一个实际的东西巩固一下。这时,但是,你要知道,还有更多的事等着你。现在只是基础,你前面学的是单片机自身的内容,这时,你需要去了解单片机的外围设备了,例如AD,DA,I2C,SPI等等内容了。但是,有些单片机自带了AD的功能,但我仍然不把他列入单片机自身的内容,包括PWM,也不属于单片机的内容,尽管有些单片机带这个功能。这些外设还是需要花大气力研究的,学到这里,你应该很容易读懂芯片的时序图了,也就是协议。这时对于DS18B20,红外,315M无线通信等等,应该都不在话下了。 你原先的程序都是在开发板上跑的,你现在可以学习如何自制电路板了,也就是学会画板,推荐使用Altium Designer软件。现在可以自己画一块系统板,做出来,看看能不能工作。如果不能,找找原因,可以跟你说,这个是必须要会的。至此,单片机学的差不多了。 但是,更高的目标还在等着你,因为,一开始就是用C语言在编程,对底层的认识比较浅薄的,你可以看一看单片机的汇编语言,不要求你会写汇编,但至少你要能看懂别人的汇编代码,你可以接触一下底层寄存器到底是怎么工作的,怎么寻址的。在这里插一句,以前你写代码时第一句总是#include,我想如果把这一句去掉,你还能让编译通过吗?你理解这个文件里有哪些内容吗,把这个文件里的定义弄明白了,51单片机的寄存器也就差不多了。这时,你应该熟悉了51单片机了。这时学习其他单片机也应该很简单了,只是换一种编译器,寄存器改了而已,原理是不变的,反正都是用C语言编程,只要稍微改一下就完全可以适应另一种芯片。这就是我的整个学习过程,仅供参考。 这时,你需要的就是培养单片机的开发经验。这时,不能说你精通了单片机,只能说会用单片机了。我想问一句,这时,你可以开发一个仓库多点温度测控系统,数据传回电脑并处理吗?不能,我也不能,但是如果你花大学四年时间就搞51的话,我确定是可以的。在这里就涉及到一个方向选择问题,关于方向选择,我的学长张永翔给了我比较好建议,这时你有两个方向,一个往低层做,就是继续学习51单片机开发,你的目标就是用最简单的芯片,最低的成本实现最复杂的系统,比的是成本,也就是说,实现同样功能的系统,你可以用比别人更低的成本,更简单的硬件,更高效的算法去完成,这样你才有市场。另一个当然是往高层做,去学习更高级的单片机,学习ARM嵌入式,操作系统,不过这条路比前一条难很多,当然就业的报酬也很多。只是,你要花更多的时间以及更高的投资,ARM开发板等等,这可不是一笔小数目,你要花的起,当然,从文章的第一句就知道了,我选的是后者。其实我觉得,作为本科生,还是选择后者比较好,回旋余地大,可以继续考研的。我现在还记得我的电路分析老师说的一句话,“现在学电子的本科生,如果你毕业时只会一个单片机,你就废了”,我觉得还是挺有道理的,作为本科生,尤其是电子专业的本科生,往高层做是必须的,而且随着技术的发展,高级单片机例如STM32的成本也一直在下降,高级单片机的普及已成为一种趋势了。 当然,这只是个人的理解,仁者见仁,智者见智。若有不同见解,欢迎讨论。再次重申,本文系个人的总结,若有不对的地方,请指正。

    时间:2021-03-02 关键词: 51单片机 串口 STM32

  • U盘容量大小造假技术手段实现之8M变4G(以STM32 SPI_FLASH为例)

    以前经常听别人说上某多或者某宝买便宜U盘的时候发现被坑,比如一个U盘大小是4GB,买回来到了手上插上PC端电脑显示也是4GB,但是真正用的时候发现并没有那么多,可能就只有那么几百MB的大小,甚至是几MB的大小,这些商家为了利益便会使用这样投机的方法,其目的是榨取用户的金钱;因此这样的商家真的很无良。当然不止是U盘可以这么来造假,其实市面上很多产品存储部分为了满足招标参数可能也会这么来搞, 那么这种手段是怎么来实现的呢?我们简单的用SPI_FLASH来模拟一下,揭露无良商家的丑陋的一面: 以下例程基于野火霸道秉火STM32开发板 关于开发板的详细资料请使用野火大学堂进行下载: 1、使用STM32CubeMX建立一个基本工程 1.1 RCC时钟配置 1.2 SYS配置 1.3 SPI配置(用于驱动W25Q64的SPI FLASH) PA4在这里是片选引脚 1.4 调试串口配置 1.5 USB配置 1.6、Fatfs文件系统配置 1.7、按键配置 用于手动删除扇区。 1.8、堆栈设置 2、移植SPI_FLASH驱动 开发板例程里有,我们直接复制过来简单修改添加即可,详细请下载文末例程。 3、让FLASH适配fatfs以及USB MSC 3.1、Fatfs适配 先适配fatfs,首先打开user_diskio.c,然后添加spi_flash的头文件,接下来填写接口: USER_initialize USER_status USER_read USER_write USER_ioctl (1)USER_initialize接口 DSTATUS USER_initialize (  BYTE pdrv           /* Physical drive nmuber to identify the drive */ ) {   /* USER CODE BEGIN INIT */     SPI_FLASH_Init(); return RES_OK ;   /* USER CODE END INIT */ } 这个很简单,直接写个SPI初始化函数然后返回RES_OK就行了。 (2)USER_status接口 DSTATUS USER_status (  BYTE pdrv       /* Physical drive number to identify the drive */ ) {   /* USER CODE BEGIN STATUS */ return RES_OK;   /* USER CODE END STATUS */ } 不捕捉对应的状态,直接返回RES_OK。 (3)DRESULT USER_read接口 DRESULT USER_read (  BYTE pdrv,      /* Physical drive nmuber to identify the drive */  BYTE *buff,     /* Data buffer to store read data */  DWORD sector,   /* Sector address in LBA */  UINT count      /* Number of sectors to read */ ) {   /* USER CODE BEGIN READ */     SPI_FLASH_BufferRead(buff, sector * 4096, count * 4096); return RES_OK;   /* USER CODE END READ */ } 实现对SPI FLASH的读,由于野火的例程里读FLASH这个接口不是说直接传0,1,2,3...的编号就表示第0、1、2、3...个扇区,而是读一个扇区,再读下一个的时候需要偏移4096个字节(一个扇区的大小)才是下一个扇区,所以记得这里要乘上4096(一个扇区的大小),就刚好是一个扇区,这个取决于驱动接口怎么写,有些接口如果内部乘了4096,那么在这里就不需要乘以4096了。 (4)DRESULT USER_write接口 DRESULT USER_write (  BYTE pdrv,          /* Physical drive nmuber to identify the drive */  const BYTE *buff,   /* Data to be written */  DWORD sector,       /* Sector address in LBA */  UINT count          /* Number of sectors to write */ ) {   /* USER CODE BEGIN WRITE */     /* USER CODE HERE */     SPI_FLASH_SectorErase(sector * 4096);     SPI_FLASH_BufferWrite((BYTE *)buff, sector * 4096, count * 4096); return RES_OK;   /* USER CODE END WRITE */ } 写的话需要先调用扇区擦除,再写,这是SPI FLASH的特性,和读接口一样,这里也需要乘上4096。 (5)DRESULT USER_ioctl接口 DRESULT USER_ioctl (  BYTE pdrv,      /* Physical drive nmuber (0..) */  BYTE cmd,       /* Control code */  void *buff      /* Buffer to send/receive control data */ ) {   /* USER CODE BEGIN IOCTL */     DRESULT res = RES_ERROR; if(pdrv != 0) return RES_PARERR;     switch(cmd)     { case CTRL_SYNC:         res = RES_OK; break; case GET_SECTOR_COUNT:        // *(DWORD*)buff = 2048;     *(DWORD*)buff = 1048576 ; //4GB = 4 * 1024 * 1024KB / 4KB = 1048576个扇区         res = RES_OK; break; case GET_SECTOR_SIZE:         *(WORD*)buff = 4096;         res = RES_OK; break; case GET_BLOCK_SIZE:         *(DWORD*)buff = 1;         res = RES_OK; break;     default:         res = RES_PARERR; break;     } return res;   /* USER CODE END IOCTL */ } GET_SECTOR_COUNT指的是获取扇区的个数,这里我们需要把SPI FLASH的大小从8MB扩容到4GB,所以我们要计算一下4GB一共有多少个扇区,计算公式如下: 4GB = 4 * 1024MB = 4 * 1024 * 1024KB 总扇区数 = 4 * 1024 * 1024 KB / 4KB = 1048576 所以,直接把这个参数写成1048576即可。 GET_SECTOR_SIZE指的是一个扇区的大小。 GET_BLOCK_SIZE指的是一个块的大小,这里不需要,直接返回1。 参考官网文档关于参数描述来实现即可: 2.2、USB MSC适配 打开usbd_storage_if.c,包含SPI_FLASH驱动的头文件,然后实现如下接口即可: STORAGE_Init_FS STORAGE_GetCapacity_FS STORAGE_Read_FS STORAGE_Write_FS STORAGE_Write_FS (1)STORAGE_Init_FS接口 int8_t STORAGE_Init_FS(uint8_t lun) {   /* USER CODE BEGIN 2 */ return (USBD_OK);   /* USER CODE END 2 */ } 直接返回OK即可,因为驱动已经在fatfs里初始化过了。 (2)STORAGE_GetCapacity_FS接口 #define W25Q64FV_FLASH_SIZE                  0x800000 /* 64 MBits => 8MBytes */ #define W25Q128FV_SUBSECTOR_SIZE             0x1000    /* 4096 subsectors of 4kBytes */ int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size) {   /* USER CODE BEGIN 3 */   //*block_num  = W25Q64FV_FLASH_SIZE/W25Q128FV_SUBSECTOR_SIZE;  *block_num = 1048576 ;  *block_size = W25Q128FV_SUBSECTOR_SIZE; return (USBD_OK);   /* USER CODE END 3 */ } 这里的block_num指的是扇区的个数,直接把4GB的计算出来参数个数填写在这里即可,block_size指的是一个扇区的大小,这里是4096。 (3)STORAGE_Read_FS接口 int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) {   /* USER CODE BEGIN 6 */  SPI_FLASH_BufferRead(buf,blk_addr*4096,blk_len*4096); return (USBD_OK);   /* USER CODE END 6 */ } 读函数很简单,直接实现即可。 (4)STORAGE_Write_FS接口 int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) {   /* USER CODE BEGIN 7 */  SPI_FLASH_SectorErase(blk_addr * 4096);   SPI_FLASH_BufferWrite(buf, blk_addr * 4096, blk_len * 4096); return (USBD_OK);   /* USER CODE END 7 */ } 写函数,也是一样,先擦除扇区再写,但是注意了,如果复制进来的数据超过本身FLASH的大小,是会破坏分区表的。 (5)STORAGE_GetMaxLun_FS接口 int8_t STORAGE_GetMaxLun_FS(void) {   /* USER CODE BEGIN 8 */  // return (STORAGE_LUN_NBR - 1); return 0 ;   /* USER CODE END 8 */ } 指的是操作一个设备,NBR此时为1。 4、实现业务逻辑 为了方便调试,实现printf的重定向: //定义printf的重定向函数fputc,满足串口调试打印 int fputc(int ch, FILE* file) {     HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 1000); return ch; } main函数里主要完成以下功能: 1、挂载SPI_FLASH 2、循环读取按键状态,手动删除分区表后重启 SPI_FLASH在第一次上电的时候里面是没有任何东西的,我们可以选择直接格式化或者在PC端格式化,但这里我采用的是直接在PC端进行格式化,所以直接挂载即可,失败也没事。 uint8_t Mount_Fatfs(void) {     retUSER = f_mount(&USERFatFS, USERPath, 1); if(retUSER != FR_OK)     { printf("spi-flash文件系统挂载失败\r\n"); return 1 ;     } printf("spi-flash文件系统挂载成功\r\n"); return 0 ; } 按键逻辑很简单,当按下按键时,擦除SPI FLASH的第一个扇区,因为Fatfs的分区表就放在第一个扇区: while (1)     {         /* USER CODE END WHILE */         /* USER CODE BEGIN 3 */ if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET)         {             HAL_Delay(100); if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET)             { printf("有按键按下...擦除第一个扇区,其实就是把FAT分区表删掉了!\n");                 SPI_FLASH_SectorErase(0); printf("即将重启!\n");                 HAL_NVIC_SystemReset();             }         }     } main函数整体实现如下: int main(void) {     /* USER CODE BEGIN 1 */     /* USER CODE END 1 */     /* MCU Configuration--------------------------------------------------------*/     /* Reset of all peripherals, Initializes the Flash interface and the Systick. */     HAL_Init();     /* USER CODE BEGIN Init */     /* USER CODE END Init */     /* Configure the system clock */     SystemClock_Config();     /* USER CODE BEGIN SysInit */     /* USER CODE END SysInit */     /* Initialize all configured peripherals */     MX_GPIO_Init();     MX_SPI1_Init();     MX_USART1_UART_Init();     MX_FATFS_Init();     MX_USB_DEVICE_Init();     /* USER CODE BEGIN 2 */     HAL_Delay(2);     SPI_FLASH_Init();     /*挂载SPI FLASH*/     Mount_Fatfs();     /* USER CODE END 2 */     /* Infinite loop */     /* USER CODE BEGIN WHILE */ while (1)     {         /* USER CODE END WHILE */         /* USER CODE BEGIN 3 */ if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET)         {             HAL_Delay(100); if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET)             { printf("有按键按下...擦除第一个扇区,其实就是把FAT分区表删掉了!\n");                 SPI_FLASH_SectorErase(0); printf("即将重启!\n");                 HAL_NVIC_SystemReset();             }         }     }     /* USER CODE END 3 */ } 5、运行结果 将程序编译完下载到开发板上: 接下来我们需要手动格式化,点击格式化磁盘 格式化的过程可能会比较久,耐心等一下,格式化成功后显示如下: 接下来打开这个磁盘,放一个小于8MB的文件进去: 接下来将开发板断电重启: 由于我们在PC端进行了格式化,所以断电重启后提示的就是挂载成功了!接下来我们打开这个U盘,看到如下文件就已经被存储在了SPI FLASH的Fatfs文件系统里了,并且可以正常打开浏览: 那如果我们复制一个超出FLASH大小的文件到盘里会怎么样呢??一样可以复制进去,然后也一样可以在PC端打开: 但是,断电重启之后就嘿嘿嘿了: 然后我们就会发现之前存进去的文件打开都是失败的了,很显然分区表已经被破坏了。 这个例程里我做了一个按键,用来恢复分区表,按下对应的按键重启然后重新格式化即可;至此,我们成功的把8MB的FLASH扩容成了4GB(注意,感官上的4GB哈,不是真正的4GB)。 4、例程开源地址 码云仓库: https://gitee.com/morixinguan/personal-open-source-project/tree/master/7.usb_fatfs_msc_expansion 获取项目方法: git clone https://gitee.com/morixinguan/personal-open-source-project.git 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2021-03-02 关键词: 容量 U盘 STM32

  • 如何设置单片机STM32的引脚

    如何设置单片机STM32的引脚

    时间:2021-02-27 关键词: 单片机 引脚 STM32

  • STM32 HAL 库 uS 延时的 3 种实现方式

    CU BEMX 可视化初始化配置,结合 HAL 库,给我们开发带来了很多便利,但 HAL 库封装的延时函数目前仅支持 ms 级别的延时,日常很多情况下会用到 us 延时,特别是一些传感器的数据读取过程,对时序要求比较严格,us 延时必不可少,基于此项需求,此次给大家介绍 3 种 uS 延时的实现方式,方法同样适用标准库,不足之处,还请大佬指出。 实验目标 使用普通定时器实现 us 延时 使用 Systick 功能实现 us 延时 使用 for 循环实现 us 延时 1、普通定时器实现 us 延时 使用定时器 TIM2 来实现 us 延时,采用 cubemx 对工程进行配置,时钟是 MCU 的心脏,先对时钟进行配置。1.1、外部时钟选择 也可以使用内部 RC 高速时钟,本次主要介绍使用外部高速时钟,上图: 我板子上焊接的是 8M 的晶体,如果小伙伴们的板子上不是 8M,根据自己的晶振频率配置即可,左侧圈 1 中,可以根据自己的晶体频率,输入相应的频率,经过分频、倍频后,系统时钟频率设置为最大,168MHZ,APB1 的时钟频率为 84MHZ,也是后面用到的 TIM2 挂载的时钟源的频率。 1.2、TIM2 基础配置 这个就比较简单了,分频系数 83,计数单位为 84MHZ/84 = 1uS,向上计数方式,周期 65535,由于没有使用到中断,不需要开启中断。 时钟及定时器的配置就完成了,下面是 cubemx 生成工程时的几项设置,建议大家勾选。首先是 HAL 库是否需要包含所有的文件,我们选择只需要用到的文件,这样可以缩短工程编译时间,只编译我们用到的库文件,接着是勾选为每个外设生成单独的.c .h 文件,这个建议一定要勾选,会使代码结构非常清晰,第三点就非常的重要了,用过 cubemx 的小伙伴是否遇到过每次重新生成工程后,之前添加的文件都不见了,这一项勾选之后,会保留用户文件。 然后是编译器选择,可以根据自己喜欢的 IDE 选择,我选择的是 KEIL5。 至此,配置工作就完成了,生成工程就可以了。 1.3、代码实现 /*  普通定时器实现us延时 */ void user_delaynus_tim(uint32_t nus) {  uint16_t  differ = 0xffff-nus-5; //设置定时器2的技术初始值 __HAL_TIM_SetCounter(&htim2,differ); //开启定时器 HAL_TIM_Base_Start(&htim2); while( differ<0xffff-5)  {   differ = __HAL_TIM_GetCounter(&htim2);  }; //关闭定时器 HAL_TIM_Base_Stop(&htim2); } /*  普通定时器实现ms延时,可直接使用HAL库函数HAL_delay() */ void delay_ms_tim(uint16_t nms) {  uint32_t i; for(i=0;i

    时间:2021-02-25 关键词: 初始化配置 HAL STM32

  • STM32学习笔记 | 电源管理及低功耗设计要点

    一款好的电子产品,都需要认真考虑电源管理的问题,电池供电的产品更应该注意低功耗的实现。 嵌入式专栏 1 STM32电源介绍 每一块STM32芯片中都有一个电源控制器(PWR),不同系列的STM32有相似,也有差异。 1.电压 绝大部分STM32的电压要求介于 1.8 V 到 3.6 V 之间,嵌入式线性调压器用于提供内部 1.2 V 数字电源。 2.类型 STM32的电源通常分为三类:数字电源、模拟电源、备份电源。 数字电源:VDD也是其主电源,主要用于数字部分; 模拟电源:VDDA用于模拟部分的电源,比如ADC,这样可以单独滤波并屏蔽 PCB 上的噪声。 备份电源:VBAT用于备份区域的电源,比如RTC、备份SRAM等,一旦主电源断开,VBAT可以为这些区域提供电源。 ▲ STM32F4备份域 3. 调压器 多数STM32都有电源调节器(有些型号没有),为备份域和待机电路以外的所有数字电路供电,调压器输出电压约为 1.2 V。 运行模式: 调压器为 1.2 V 域(内核、存储器和数字外设)提供全功率。 停止模式: 调压器为 1.2 V 域提供低功率,保留寄存器和内部 SRAM 中的内容。 待机模式: 调压器掉电。除待机电路和备份域外,寄存器和 SRAM 的内容都将丢失。 嵌入式专栏 2 STM32的低功耗模式 STM32 的工作模式通常可分为 4类 :运行模式、睡眠模式、停止模式、待机模式。根据 STM32 类型不同,可将工作模式进一步划分。比如 STM32L 低功耗系列,睡眠 模式可进一步划分为:普通睡眠模式和低功耗睡眠模式。 以ST M32F4为例: 运行模式:默认进入该模式; 睡眠模式:内核停止,外设保持运行 停止模式:所有时钟都停止 待机模式:1.2 V 域断电 ▲ STM32睡眠/停止/待机模式图解 嵌入式专栏 3 STM32低功耗设计要点 STM32低功耗通常会结合项目实际情况,以及应用场景来进行针对性设计。以下面几个案例来进行说明。 案例一:有工程师提到:STM32F103 进入STOP 模式后无法通过串口唤醒 ? 分析原因:这位工程师对低功耗唤醒机制理解有误:STM32进入 STOP 模式后不能直接通过 UART 等中断外设唤醒, 只能通过 EXTI 外部中断方式唤醒。 解决办法: 我们可以在 MCU 进入 STOP 前将 RX 脚设为 EXTI 模式,并使能对应的中断来实现。 案例二: 有工程师提到: STM32F051进入低功耗之后,实际功耗远大于理想功耗。 分析原因:造成问题的原因可能是工程师通过直接调用“停止模式”,进入低功耗,但部分IO外部有上拉电阻,进入低功耗之前未做调整,导致功耗偏大。 解决办法: 进入低功耗之前,对使用和未使用 IO 状态进行调整。比如有外部上拉,可配置成模拟输入等。 案例三: 有工程师提到:通过按键唤醒 STM32F103 ,串口不能正常工作? 分析原因: 经分析发现,开发者进入的低功耗模式为待机模式,唤醒之后,未初始化串口外设,导致串口不能正常工作。在待机模式下,所有外设都关闭,意味着所有外设配置都为默认值。 解决办法: 唤醒 STM32 之后,重新初始化串口(以及所有使用的)外设。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2021-02-24 关键词: 电源管理 低功耗设计 STM32

  • 硬核DIY:基于STM32的智能小车

    这次我要分享的内容是DIY一辆STM32智能小车。该小车需要自己根据原理图焊接组装,有利于提高新手对于硬件的人生,然后有智能循迹、红外避障、红外遥控、超声波避障功能,对于锻炼动手能力,培养逻辑思维能力非常好。 这些天一直在焊接和调试,忘记发帖了。收到货之后,就是全是零散件,需要自己焊接,东西挺多的,对于焊接和机械结构有一点的要求。哈哈,立刻买了一个洛铁,然后开始焊接,组装。 一、驱动板; 二、电池、车轮、车子轮廓板;机械结构的零件 三、这块就是最重要的核心板了,stm32平台的,是不是非常期待给力,今天写几个程序跑一下。 经过日夜加班加点,控制板和底板焊接完成,控制板主要是驱动芯片、指示灯和电源开关。底板包括电机的安装、红外板子焊接、超声波主板焊接,及其轮胎安装,电池盒安装。以下为成品。焊接的架势还挺大的,见笑了,上图一张(最后) 最后的成品上线,可寻线,遥控,超声波避障。 总结: 经过整整一个星期的辛苦努力,智能车算是过一段落了,现在来分享一下。 1、这次的智能设计,首先是焊接,必须识别基本的元器件,特别是这次的电阻是磁环电阻,需要去一一做识别; 2、必须懂硬件,能够看懂基本的原理图,这样才能正确的焊接,保证最后智能车能够正常运行;3、熟悉stm32这款芯片,keil软件,这样才能编程和控制智能车,焊接用了3天,后面是自己编程用了几天。总体来说收货还是挺多,又重温了一下智能车的快来。 潜力变实力原创 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2021-02-08 关键词: 智能小车 DIY STM32

  • 盘点STM32的国产替代者(5)

    应读者要求,嵌入式ARM将继续介绍能够替代STM32的国产产品。今日带来能够完美替代STM32的产品是雅特力科技的AT32F407系列和AT32F403。 雅特力科技AT32F407系列高效能微控制器,搭载32位ARM® Cortex®-M4内核,配合先进制程可达超高效能240MHz的运算速度。内建的单精度浮点运算单元(FPU)及数字信号处理器(DSP),搭配丰富的外设及灵活的时钟控制机制,能满足多种领域应用。完善的内存设计,最高可支持1MB闪存存储器(Flash)及224KB随机存取存储器(SRAM), 其闪存存储器执行零等待的优异表现,超越业界同级芯片水平。 AT32F407系列除集成高效能的运算效能外,也导入sLib安全库(SecurityLibrary)可支持密码保护指定范围程序区,方案商烧录核心算法到此区域,提供给下游客户做二次开发。 另外特别支持8组UART串口,2组CAN总线,还集成兼容IEEE-802.310/100Mbps以太网口控制器特别适用于物联网应用,以及USB设备应用不需外挂晶振,可同时提升终端产品的可靠度与降低成本的多重用途。 AT32F407可运行于工业级温度范围-40~105°C,并因应多样的内存使用需求,提供一系列芯片供选用,其丰富的片上资源分配、高集成及高性价比的一流市场竞争力,特别适用于工业自动化 (industrial automation),电机控制 (motorcontrol),物联网 (IoT) 及消费性电子 (consumerelectronics) 等各种成本敏感及高运算需求的设计。 END

    时间:2021-02-05 关键词: 微控制器 嵌入式 STM32

  • STM32配置SPI通讯功能

    一、SPI协议 串行外围设备接口,是一种高速全双工的通信总线。在ADC/LCD等与MCU间通信。 1、SPI信号线 SPI 包含 4 条总线,SPI 总线包含 4 条总线,分别为SS 、SCK、MOSI、MISO。 (1)SS(SlaveSelect):片选信号线,当有多个 SPI 设备与 MCU 相连时,每个设备的这个片选信号线是与 MCU 单独的引脚相连的,而其他的 SCK、MOSI、MISO 线则为多个设备并联到相同的 SPI 总线上,低电平有效。 (2)SCK (Serial Clock):时钟信号线,由主通信设备产生,不同的设备支持的时钟频率不一样,如 STM32 的 SPI 时钟频率最大为 f PCLK /2。 (3)MOSI (Master Output, Slave Input):主设备输出 / 从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入数据,即这条线上数据的方向为主机到从机。 (4)MISO(Master Input, Slave Output):主设备输入 / 从设备输出引脚。主机从这条信号线读入数据,从机的数据则由这条信号线输出,即在这条线上数据的方向为从机到主机。 2、SPI模式 根据 SPI 时钟极性(CPOL)和时钟相位(CPHA) 配置的不同,分为 4 种 SPI 模式。时钟极性是指 SPI 通信设备处于空闲状态时(也可以认为这是 SPI 通信开始时,即SS 为低电平时),SCK 信号线的电平信号。CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时则相反。时钟相位是指数据采样的时刻,当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的奇数边沿被采样。当 CPHA=1 时,数据线在 SCK 的偶数边沿采样。 首先,由主机把片选信号线SS 拉低,意为主机输出,在SS 被拉低的时刻,SCK 分为两种情况,若我们设置为 CPOL=0,则 SCK 时序在这个时刻为低电平,若设置为 CPOL=1,则 SCK 在这个时刻为高电平。采样时刻都是在 SCK 的奇数边沿(注意奇数边沿有时为下降沿,有时为上升沿)。 CPHA=1时,数据信号的采样时刻为偶数边沿。 二、SPI特性及架构 (1)单次传输可选择为 8 或 16 位。 (2)波特率预分频系数(最大为 fPCLK/2) 。 (3)时钟极性(CPOL)和相位(CPHA)可编程设置 。 (4)数据顺序的传输顺序可进行编程选择,MSB 在前或 LSB 在前。 (5)可触发中断的专用发送和接收标志。 (6)可以使用 DMA 进行数据传输操作。 MISO 数据线接收到的信号经移位寄存器处理后把数据转移到接收缓冲区,然后这个数据就可以由我们的软件从接收缓冲区读出了。 当要发送数据时,我们把数据写入发送缓冲区,硬件将会把它用移位寄存器处理后输出到 MOSI 数据线。 SCK 的时钟信号则由波特率发生器产生,我们可以通过波特率控制位(BR)来控制它输出的波特率。 控制寄存器 CR1 掌管着主控制电路,STM32 的 SPI 模块的协议设置(时钟极性、相位等)就是由它来制定的。 而控制寄存器 CR2 则用于设置各种中断使能。 最后为 NSS 引脚,这个引脚扮演着 SPI 协议中的SS 片选信号线的角色,如果我们把 NSS 引脚配置为硬件自动控制,SPI 模块能够自动判别它能否成为 SPI 的主机,或自动进入 SPI 从机模式。 但实际上我们用得更多的是由软件控制某些 GPIO 引脚单独作为SS信号,这个 GPIO 引脚可以随便选择。 三、SPI接口读取Flash 各信号线相应连接到 Flash(型号 :W25X16/W25Q16)的 CS、CLK、DO 和 DIO 线,实现SPI 通信,对 Flash进行读写,其中 W25X16 和 W25Q16 在程序上不同的地方是 FLASH 的ID 不一样。 读取 Flash 的 ID 信息,写入数据,并读取出来进行校验,通过串口打印写入与读取出来的数据,输出测试结果。 不同的设备都会相应的有不同的指令,如 EEPROM 中会把第一个数据解释为存储矩阵的地址(实质就是指令)。而 Flash 则定义了更多的指令,有写指令、读指令、读ID 指令等。 SPI-FLASH通信: (1)配置 I/O端口,使能 GPIO。 (2)根据将要进行通信器件的 SPI模式,配置 STM32的 SPI,使能 SPI时钟。 (3)配置好 SPI后,根据各种 Flash定义的命令控制对它的读写。 注意,在操作Flash前要进行解锁操作。 main.c int main(void) { /* 配置串口 1 为:115200 8-N-1 */ USART1_Config(); printf("\r\n 这是一个 2M 串行 flash(W25X16)实验 \r\n"); /* 2M 串行 flash W25Q16 初始化 */ SPI_FLASH_Init(); /* Get SPI Flash Device ID */ DeviceID = SPI_FLASH_ReadDeviceID();   Delay( 200 ); /* Get SPI Flash ID */ FlashID = SPI_FLASH_ReadID(); printf("\r\n FlashID is 0x%X, Manufacturer Device ID is 0x%X\r\n",  FlashID, DeviceID); /* Check the SPI Flash ID */ if (FlashID == sFLASH_ID) /* #define sFLASH_ID 0xEF3015 */ { printf("\r\n 检测到串行 flash W25X16 !\r\n");     SPI_FLASH_SectorErase(FLASH_SectorToErase);     SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress,  BufferSize); printf("\r\n 写入的数据为:%s \r\t", Tx_Buffer);     SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize); printf("\r\n 读出的数据为:%s \r\n", Tx_Buffer); /* 检查写入的数据与读出的数据是否相等 */ TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize); if ( PASSED == TransferStatus1 )     { printf("\r\n 2M 串行 flash(W25X16)测试成功!\n\r");     } else { printf("\r\n 2M 串行 flash(W25X16)测试失败!\n\r");     }   }// if (FlashID == sFLASH_ID) else { printf("\r\n 获取不到 W25X16 ID!\n\r");   }   SPI_Flash_PowerDown(); while (1); } (1)调用 USART1Confi g() 初始化串口。 (2)调用 SPI_FLASH_Init() 初始化 SPI 模块。 (3)调用 SPI_FLASH_ReadDeviceID() 读取 Flash 器件生产厂商的 ID 信息。 (4)调用 SPI_FLASH_ReadID() 读取 Flash 器件的设备 ID 信息 (5)若读取得的ID正确, 则调用 SPI_FLASH_SectorErase()把 Flash 的内 容擦除,擦除后调用SPI_FLASH_BufferWrite() 向Flash 写入数据,然后再调用SPI_FLASH_BufferRead()从刚刚写入的地址中读出数据。最后调用 Buffercmp() 函数对写入的数据与读取的数据进行比较,若写入的数据与读出的数据相同,则把标志变量TransferStatus1 赋值为 PASSED(自定义的枚举变量)。 (6)最后调用 SPI_Flash_PowerDown()函数关闭 Flash 设备的电源,因为数据写入到Flash 后并不会因断电而丢失,我们在使用它时才重新开启 Flash 的电源。 SPI初始化 #define SPI_FLASH_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4) #define SPI_FLASH_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4) void SPI_FLASH_Init(void) {   SPI_InitTypeDef  SPI_InitStructure;   GPIO_InitTypeDef GPIO_InitStructure;   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD, ENABLE);   RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); /* SCK */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   GPIO_Init(GPIOA, &GPIO_InitStructure); /* MISO */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;   GPIO_Init(GPIOA, &GPIO_InitStructure); /* MOS */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;   GPIO_Init(GPIOA, &GPIO_InitStructure); /* CS  */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   GPIO_Init(GPIOA, &GPIO_InitStructure);   SPI_FLASH_CS_HIGH();   SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;   SPI_InitStructure.SPI_Mode = SPI_Mode_Master;   SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;   SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;   SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;   SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;   SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;   SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;   SPI_InitStructure.SPI_CRCPolynomial = 7;   SPI_Init(SPI1, &SPI_InitStructure);   SPI_Cmd(SPI1, ENABLE); } (1)SPI_Mode :主机模式(SPI_Mode_Master)或从机模式(SPI_Mode_Slave),这两个模式的最大区别为 SPI 的 SCK 信号线的时序,SCK 的时序是由通信中的主机产生的。若被配置为从机模式,STM32 的 SPI 模块将接受外来的 SCK 信号。 (2)SPI_DataSize :SPI 每次通信的数据大小(称为数据帧)为 8 位还是 16 位。 (3)SPI_CPOL 和 SPI_CPHA :配置SPI的时钟极性(CPOL)和时钟相位CPHA),这两个配置影响到 SPI 的通信模式,该设置要符合将要互相通信的设备的要求。CPOL 分别可以取 SPI_CPOL_High(SPI 通信空闲时 SCK 为高电平)和SPI_CPOL_Low(SPI 通信空闲时 SCK 为低电平)。CPHA 则可以取 SPI_CPHA_1Edge(在 SCK 的奇数边沿采集数据) 和 SPI_CPHA_2Edge(在 SCK偶数边沿采集数据)。 (4)SPI_NSS :配置NSS引脚的使用模式,硬件模式(SPI_NSS_Hard)与软件模式(SPI_NSS_Soft),在硬件模式中的 SPI 片选信号由硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。如果外界条件允许,硬件模式还会自动将 STM32 的 SPI 设置为主机。我们使用软件模式,向这个成员赋值为 SPI_NSS_Soft。 (5)SPI_BaudRatePrescaler:本成员设置波特率分频值,分频后的时钟即为 SPI 的 SCK信号线的时钟频率。这个成员参数可设置为 f PCLK 的 2、4、6、8、16、32、64、128、256 分频。赋值为 SPI_BaudRatePrescaler_4,即 f PCLK 的 4 分频。 (6)SPI_FirstBit:所有串行的通信协议都会有 MSB 先行(高位数据在前)还是 LSB先行(低位数据在前)的问题,而 STM32 的 SPI 模块可以通过这个结构体成员,对这个特性编程控制。据 Flash 的通信时序,我们向这个成员赋值为MSB先行(SPI_FirstBit_MSB)。 (7)SPI_CRCPolynomial:这是 SPI 的 CRC 校验中的多项式,若我们使用 CRC 校验时,就使用这个成员的参数(多项式)来计算 CRC 的值。由于本实验的 Flash 不支持 CRC校验,所以我们向这个结构体成员赋值为 7 实际上是没有意义的。 配置完这些结构体成员后,我们要调用 SPI_Init() 函数把这些参数写入寄存器中,实现SPI 的初始化,然后调用 SPI_Cmd() 来使能 SPI1。 读FLASH ID #define Dummy_Byte   0xFF u8 SPI_FLASH_SendByte(u8 byte) { // 等待发送数据寄存器清空 while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);   SPI_I2S_SendData(SPI1, byte); // 向从机发送数据 while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET); // 等待接收数据寄存器非空 return SPI_I2S_ReceiveData(SPI1); // 获取接收寄存器中的数据 } u32 SPI_FLASH_ReadDeviceID(void) {   u32 Temp = 0;   SPI_FLASH_CS_LOW();   SPI_FLASH_SendByte(W25X_DeviceID);   SPI_FLASH_SendByte(Dummy_Byte);   SPI_FLASH_SendByte(Dummy_Byte);   SPI_FLASH_SendByte(Dummy_Byte);   Temp = SPI_FLASH_SendByte(Dummy_Byte);   SPI_FLASH_CS_HIGH(); return Temp; } 读厂商ID u32 SPI_FLASH_ReadID(void) {   u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;   SPI_FLASH_CS_LOW();   SPI_FLASH_SendByte(W25X_JedecDeviceID); // 0x9F Temp0 = SPI_FLASH_SendByte(Dummy_Byte);   Temp1 = SPI_FLASH_SendByte(Dummy_Byte);   Temp2 = SPI_FLASH_SendByte(Dummy_Byte);   SPI_FLASH_CS_HIGH();   Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2; return Temp; } 擦除FLASH内容 void SPI_FLASH_WriteEnable(void) {   SPI_FLASH_CS_LOW();   SPI_FLASH_SendByte(W25X_WriteEnable); // 06H SPI_FLASH_CS_HIGH(); } void SPI_FLASH_WaitForWriteEnd(void) {   u8 FLASH_Status = 0;   SPI_FLASH_CS_LOW();   SPI_FLASH_SendByte(W25X_ReadStatusReg); // 05H do {     FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);   } while ((FLASH_Status & WIP_Flag) == SET);   SPI_FLASH_CS_HIGH(); } void SPI_FLASH_SectorErase(u32 SectorAddr) {   SPI_FLASH_WriteEnable();   SPI_FLASH_WaitForWriteEnd();   SPI_FLASH_CS_LOW();   SPI_FLASH_SendByte(W25X_SectorErase); // 20H SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);   SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);   SPI_FLASH_SendByte(SectorAddr & 0xFF);   SPI_FLASH_CS_HIGH();   SPI_FLASH_WaitForWriteEnd(); } 向Flash写数据——分页 void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite) {   SPI_FLASH_WriteEnable();   SPI_FLASH_CS_LOW();   SPI_FLASH_SendByte(W25X_PageProgram); // 02H SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);   SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);   SPI_FLASH_SendByte(WriteAddr & 0xFF); if(NumByteToWrite > SPI_FLASH_PerWritePageSize)   {     NumByteToWrite = SPI_FLASH_PerWritePageSize;   } while (NumByteToWrite--)   {     SPI_FLASH_SendByte(*pBuffer);     pBuffer++;   }   SPI_FLASH_CS_HIGH();   SPI_FLASH_WaitForWriteEnd(); } void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite) {   u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;   Addr = WriteAddr % SPI_FLASH_PageSize;   count = SPI_FLASH_PageSize - Addr;   NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;   NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize; if (Addr == 0)   { if (NumOfPage == 0)     {       SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);     } else { while (NumOfPage--)       {         SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);         WriteAddr +=  SPI_FLASH_PageSize;         pBuffer += SPI_FLASH_PageSize;       }       SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);     }   } else { if (NumOfPage == 0)     { if (NumOfSingle > count)       {         temp = NumOfSingle - count;         SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);         WriteAddr +=  count;         pBuffer += count;         SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);       } else {         SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);       }     } else {       NumByteToWrite -= count;       NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;       NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;       SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);       WriteAddr +=  count;       pBuffer += count; while (NumOfPage--)       {         SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);         WriteAddr +=  SPI_FLASH_PageSize;         pBuffer += SPI_FLASH_PageSize;       } if (NumOfSingle != 0)       {         SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);       }     }   } } FLASH读 void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead) {   SPI_FLASH_CS_LOW();   SPI_FLASH_SendByte(W25X_ReadData); // 03H SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);   SPI_FLASH_SendByte((ReadAddr & 0xFF00) >> 8);   SPI_FLASH_SendByte(ReadAddr & 0xFF); while (NumByteToRead--)    {     *pBuffer = SPI_FLASH_SendByte(Dummy_Byte);     pBuffer++;   }   SPI_FLASH_CS_HIGH(); } 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2021-02-02 关键词: 通讯功能 SPI STM32

  • 被STM32G0快速编程难倒的,看这里

    难点 某STM32用户在其产品设计中,采用了 STM32G070RBT6,开发工程师希望在进行代码升级的时候使用快速编程来提高编程速度,但是写代码时遇到很多问题。而在目前的 STM32G0 的 Cube 库中并没有 FLASH_FastProgram 例程,所以客户希望得到一个参考例程来快速实现设计。 1 了解问题 检查最新版本的STM32Cube_FW_G0_V1.3.0/Projects/STM32CubeProjectsList.html 文件,确实可以看到现有的    STM32G0Cube 库中并没有 FLASH_FastProgram 例程,根据参考手册,参考\STM32Cube_FW_L4_V1.16.0\Projects\NUCLEO-L452RE\Examples\FLASH\FLASH_FastProgram 例程,对    \STM32Cube_FW_G0_V1.2.0\Projects\NUCLEO-G070RB\Examples\FLASH\FLASH_EraseProgram 进行修改以移植代码。以下就撰写例程代码时,需要注意的问题简单地介绍一下。 3 问题解决 上面几个要点,如果软件工程师使用的是 STM32Cube 库,那么在撰写代码上最主要是检查一下前面三个要点的情况。后面几个要点稍微了解就可以了。 建议 软件工程师在撰写Flash快速编程时,仔细阅读下参考手册,并参考本文中的各个要点,然后根据自己的实际应用情况,理清逻辑,来撰写完整的 Flash 编程代码。

    时间:2021-02-02 关键词: 快速编程 STM32G0 STM32

  • STM32低功耗定时器(LPTIM)有哪些独特功能?

    作者 | strongerHuang 微信公众号 | 嵌入式专栏 开发低功耗产品,我们会比较关注整个系统的功耗问题。那么,LPTIM低功耗定时器你有关注吗? 1写在前面 在早些年,可能较少听见LPTIM这个名词。随着低功耗产品需求越来越严格,MCU厂商就推出了针对低功耗应用的LPTIM定时器。 定时器是我们常见的一种外设,之所以这么常见,原因在于定时器的用途非常广泛。 在STM32所有MCU中都配有定时器,那么你有关注、对比过各系列,各型号MCU中定时器的差异吗? 2哪些STM32配有LPTIM定时器 在STM32中,相对较新的MCU部分型号配有LPTIM定时器。 比如:STM32F7、H7高性能MCU,STM32L0、 L4低功耗MCU,以及新推出的G0、G4系列中都配有这种LPTIM定时器。 具体哪些MCU配有LPTIM,大家可以下载对应的数据手册查看。 本文围绕STM32G0讲述其中的LPTIM定时器。 3LPTIM功能 LPTIM:Low-power timer,即低功耗定时器。 LPTIM 是一个 16 位定时器,得益于其定时器的低功耗。 由于 LPTIM 的时钟源具有多样性,因此 LPTIM 能够在所有电源模式(待机模式除外)下保持运行状态。 即使没有内部时钟源, LPTIM 也能运行,鉴于这一点,可将其用作“脉冲计数器”,这种脉冲计数器在某些应用中十分有用。 此外, LPTIM 还能将系统从低功耗模式唤醒,因此非常适合实现“超时功能”,而且功耗极低。 LPTIM 引入了一个灵活的时钟方案,该方案能够提供所需的功能和性能,同时还能最大程度地降低功耗。 我仔细对比了一下STM32各系列的LPTIM低功耗定时器,发现很多功能基本一样。 1.框图 STM32G0低功耗定时器框图: STM32L0低功耗定时器框图: 对比框图,可以发现这个LPTIM片上外设有相似之处。 当然,有些细节是不一样的,像在STM32H7中有多个LPTIM,这几个LPTIM之间是有一定差异的。 2.LPTIM 主要特性 16 位递增计数器 3 位预分频器,可采用 8 种分频系数(1、 2、 4、 8、 16、 32、 64 和 128) 可选时钟 – 内部时钟源:LSE、 LSI、 HSI 或 APB 时钟 – LPTIM 输入的外部时钟源(在没有 LP 振荡器运行的情况下工作,可在使用脉冲计数器应用场景中使用) 16 位 ARR 自动重载寄存器 16 位比较寄存器 连续/单触发模式 可选软件/硬件输入触发 可编程数字防抖动干扰滤波器 可配置输出:脉冲和 PWM 可配置 I/O 极性 编码器模式 拿这些特性和其它基本定时器相对较,你会发现,这些特性中很多都是LPTIM独有的。 3.LPTIM  RCC LPTIM的RCC和其他定时器相比较,其RCC功能更加丰富。 通过上面框图可以发现,LPTIM 可通过多个时钟源提供时钟。 它可以由内部时钟信号提供时钟,内部时钟信号可通过复位和时钟控制器 (RCC) 在 APB、 LSI、 LSE 或 HSI 时钟源中进行选择。 4.干扰滤波器 这个功能也是LPTIM所特有的一个功能。 LPTIM 输入由数字滤波器保护,避免任何毛刺和噪声干扰在 LPTIM 内部传播,从而防止产生意外计数或触发。 滤波示意图: 这个原理比较简单,如果不能理解请查看参考手册详解。 LPTIM定时器的功能比较多,可能初学者一看到那么多内容就吓到了。其实,把内容拆开来看并不难。 本文旨在让更多朋友知道这些功能,想要深入掌握其中知识,需结合手册和实践编程。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2021-01-25 关键词: 低功耗 定时器 LPTIM STM32

  • STM32 时钟分析

    01 前言 在嵌入式系统中时钟是其脉搏,处理器内核在时钟驱动下完成指令执行,状态变换等动作。外设部件在时钟的驱动下完成各种工作,比如串口数据的发送、A/D转换、定时器计数等等。 02 STM32时钟源 HSI是高速内部时钟,RC振荡器,频率为8MHz。 HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,比较常用的8MHz 12MHz  25MHz。 LSI是低速内部时钟,RC振荡器,频率为40kHz。 LSE是低速外部时钟,接频率为32.768kHz的石英晶体。 在STM32中每个外设都有其单独的时钟,在使用某个外设之前必须打开该外设的时钟 ,为什么要这么麻烦来设置每一个外设的时钟而不是将所有外设的时钟统一打开?因为STM32的外设繁多,外设的运作所需要的最佳时钟各不相同,如果所有时钟同时运行会给MCU带来极大的负载,所以STM32为了实现低功耗,而设计的功能完善构成复杂的时钟系统,称之时钟树。使外设功能的时钟可自配置。 03 STM32 时钟树 ▲ 图源网络 上图下红框中LSE和LSI是提供给系统看门狗和RTC(实时时钟)使用的。如果需要精度较高的RTC时钟,需要使用LSE,频率为32.768K提供一个精确的时钟源。 HSI为8M,和HSE相比精度较差,对于性能无要求场景又要节省成本使用HSI。 在时钟树系统中,主时钟选择由PLL生成,PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。 css时钟监视系统但HSE失效时自动切换至HSI5、外设有独立的时钟分频配置,主要有USB、SDIO、FSMC、APB1、APB2、ADC等。APB1和APB2是俩个总线桥:APB1和APB2,其中APB1是低速总线,APB2是全速总线。 具体哪些外设挂在那条总线上可参考下图: ** 使用STM32CubeMX配置时钟** 以STM32F105为例打开工程选择HSE,选择外部晶振作为输入。 点击Clock Configuration如下图: 04 结尾 本篇主要分析STM32的时钟,并以stm32f105为例使用STM32CubeMX配置时钟演示。如有疑问,欢迎留言讨论。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2021-01-23 关键词: 嵌入式 时钟 STM32

  • STM32的FLASH和SRAM的使用情况分析

    01 前言 STM32片上自带FLASH和SRAM,简单讲FLASH用来存储程序的,SRAM是用来存储运行程序中的中间变量。本文详细分析下如何查看程序中FLASH和SRAM的使用情况。 本文开发工具: keil5 芯片: STM32F105VCT6 02 FLASH和SRAM介绍 FLASH存储器又成为闪存,它与EEPROM都是掉电后数据不丢失的存储器,但是FLASH的存储容量都普遍的大于EEPROM,在存储控制上,最主要的区别是FLASH芯片只能一大片一大片地擦除,而EEPROM可以单个字节擦除。 SRAM是静态随机存取存储器。它是一种具有静止存取功能的内存,不需要刷新电路即能保存它内部存储的数据。STM32F1系列可以通过FSMC外设来拓展SRAM。 注意:SRAM和SDRAM是不相同的,SDRAM是同步动态随机存储器,同步是指内存工作需要同步时钟,内部的命令的发送与数据的传输都以它为基准;动态是指存储阵列需要不断的刷新来保证数据不丢失;随机是指数据不是线性依次存储,而是自由指定地址进行数据读写。STM32的F1系列是不支持SDRAM的。 stm32不同型号的SRAM和FLASH大小是不相同的,可在datasheet中查看如下图: 03 编译结果分析 在keil中编译结果如下图: 打开生成的map文件拉到最后可看到如下: 编译结果里面几个的含义 Code:代码空间,本质是ARM指令( FLASH)。 RO-data:即 Read Only-data, 表示程序定义的常量,如 const 类型( FLASH)。 RW-data:即 Read Write-data, 非0初始化的全局和静态变量占用的RAM大小,同时还要占用等量的ROM大小用于存放这些非0变量的初值(FLASH+RAM)。 ZI-data:即 Zero Init-data, 0初始化的内存区的大小(该区域3个用途:0初始化的全局和静态变量+堆区+栈区)(RAM)。 由上可知: 程序占用FLASH=Code + RO-data + RW-data 即map文件中ROM size 程序占用RAM  = RW-data + ZI-data     即map文件中RW size 常见的俩个疑问: 1、RW-data为什么会即占用Flash又占用RAM空间? 由前文知道RAM掉电数据会丢失,RW-data是非0初始化的数据,已初始化的数据需要被存储在掉电不会丢失的FLASH中,上电后会从FLASH搬移到RAM中。 2、为什么烧录的镜像文件不包含ZI-data呢? 我们都知道在烧写程序的时候,需要烧写bin文件或者hex文件到STM32的flash中,被烧写的文件称为镜像像文件image。image的内容包含这三个Code 、 RO-data 和 RW-data。 通过第一个问题大家应该有所理解,因为ZI数据是0,没必要包含,只要在程序运行前把ZI数据区域一律清零即可,包含进去反而浪费Flash存储空间。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2021-01-23 关键词: Flash SRAM STM32

  • ​STM32延时函数的四种方法

    述工程源码仓库: 不支 https://github.com/strongercjd/STM32F207VCT6/tree/master/02-Template https://github.com/strongercjd/STM32F207VCT6/tree/master/04-Delay 不支https://github.com/strongercjd/STM32F207VCT6/tree/master/03-ASM 不支

    时间:2021-01-23 关键词: 单片机 延时函数 STM32

  • 盘点STM32的国产替代者(4)

    应读者要求,嵌入式ARM将继续介绍能够替代STM32的国产产品。 MM32是一个全球化的MCU产品,灵动微在上海设立芯片设计及运营中心,借助上海晶圆代工、封装测试完整产业链,确保灵动MCU从研发到生产一条龙进程;在南京设立软件及方案中心,一个50人规模的团队充分保障MCU方案的研发;深圳则建立销售及技术支持中心,可第一时间给予客户服务支持;此外还有香港建有海外运营及客服务中心,在台湾新竹的前不久刚成立东亚营销及方案中心。这使得MM32在中国形成多据点、本地化布局,以及时、快速的响应服务广大客户。 最新发布的MM32 MCU产品家族的五大产品系列,包括MM32 F系列通用高性能微控制器产品、MM32 L系列低功耗宽电压微控制器产品、MM32 W系列无线微控制器产品、MM32 P系列超小封装微控制器产品以及MM32 S系列安全加密微控制器产品。 根据21ic坛友火星国务卿的总结,MM32拥有以下亮点: 亮点一:MM32F的强悍之处 MM32F1主频高达168MHz,Flash/SRAM高达512KB/128KB,并有丰富的接口,据悉将在第四季度供货。 另一款MM32F0,标准主频全面升级到72MHz,保留超频潜力,相比通常只有48MHz主频的MCU提升不少。另外新增MM32F031C8T6系列对客户已经有百K级的交付。娄方超表示,针对近期MCU市场供不应求甚至炒货的情况,灵动微电子承诺只要有货绝不存货,准时发货,同时价格保持不变。 亮点二:L系列低功耗宽电压 灵动低功耗宽电压MCU系列具有全球主流低功耗MCU水准,超宽的工作电压, 同时MindSafe强大的安全功能,坚固的代码保护和数据流加密等。 亮点三:无线MCU 支持无线连接方式BLE,支持OTA(空中升级),sub 1GHz(将于2018年Q1支持)。 MM32无线系列的W0/W3产品,与F/L系列全部管脚兼容,并首次提出无线MCU原位替换通用MCU,同时还提倡让无线变成标准接口理念。 亮点四:P系列超小封装 亮点五:MindSafe技术 除此之外,灵动MM32已经建立了丰富成熟的生态系统,用娄总的话说,七年时间做MCU,其中五年优化生态,可见完善的生态系统对MCU产品的重要性。这个生态系统包括了应用文档、库函数与样例,开发评估板、解决方案、仿真工具以及在线支持等等。 灵动首席科学家刘强表示,基于灵动MM32开发平台,传统的库函数、例程、外设得以自然融合,给开发者以极大的便利,十倍百倍的提升开发效率,有效降低开发风险,并且使得开发成果易于复用、重用和维护。灵动还将推出在全球业界领先的SMART敏捷开发平台,将本土MCU开发水平提升至世界水准。 用户:BinWin 首先感谢厂家和社区提供这样一个直接体验产品的平台和机会。希望如此大力的推广可以收到较好的效果,加深工程师对灵动的印象,未来更多的产品内蕴藏着灵动微电的中国芯。 下面要看收到的物件了,整个板卡沉稳黑色,且期间布局比较美观整齐,接口靠近板边,看得出设计者考虑的还是比较细致的。 另外板载MM-LINK调试器,含虚拟串口,对调试来说很是方便,一根USB线就解决了烧录和串口打印。 加上厂商有编程的上位机软件,配套调试器堪称全家桶。EEPROM存储器,CAN控制器, FLASH存储器也都板载,可以进行SPI和I2C协议的调试,三个电位器接在ADC端口上。这些组成让板卡可以开箱即用,实现项目的初期调试。 说了这么多,看下实物照片。 同样给了黑色的背景 展示完了硬件,来烧录个程序看看吧。利用定时器设计时间片任务轮询,添加按键检测,LED提示,蜂鸣器响应,停机模式触发,串口打印信息几个任务,通过这些代码的调试体验改MCU的开发难度和外设易用性,也可评估低功耗特性和稳定性。下面看主要代码。 上电任务开始运行后,进入停机模式之前,LED1以0.5hz闪烁,LED2以1hz闪烁,ADC任务采集核心温度,串口打印如下图,内心凉凉。 工程结构如下所示 整个工程的建立和调试相对于其他厂商来说还是很便利的,而且库函数的很多寄存器与常用的MCU比较近似,所以寻求替换的朋友们可以尝试了。应该说成本敏感和地方,确切外设应用的地方,使用MM32是比较有优势的。从demo板的设计与资源来看,厂家的支持应该也不错的。整体体验暂如此,接下来的小项目中继续挖掘详细的内容。 【MM33 eMiniBoard测评报告】+我的评测总结 用户: hu9jj 有幸获得了MM32 eMiniBoard的评测机会,自从上月20日收到这块小巧精致的评测板之后,我立即开始了评测过程,在短短的半个月时间里,我测试过厂家提供的ADC、I2C、UART、INT、TIM、PWM等例程。对于ADC转换还同时对比测试了固件库和寄存器两个版本,测试了开通DAM进行二阶滤波ADC转换以及多通道ADC转换。同时还用轮询法和中断法两种方式测试板载的四个按键,尽管K1按键没有测试成功,但其它按键的测试效果良好。还测试了定时器及PWM输出等例程。 通过一系列的测试,MM32 eMiniBoard均能很好地与外设进行数据通讯,体现了良好的适应性能。 然而在测试过程中也发现厂家提供的例程资料的不足部分,例如代码中LED的编号与电路图和板上丝印正好相反的问题、按键例程的引脚定义与实际不符的问题、TIM1例程中定时时间过长影响测试效果的问题等,这些现象表明厂家在提供资料时还需要严格把关,所有的例程都应该在指定板子上运行测试通过后再提供给大家,这样会给大家更好的感受。 具体测评来说: 翘首盼望了多天,一直没有快递的信息,今天上午忍不住,发邮件询问了快递号,下午就收到了。下面就是评测板的全貌——黝黑端庄: 这是评测板的背面——干净整洁: 板上有一个下载/调试接口和一个USB接口,另外还有一个三线串口(含GND)和CAN通讯接口,我迫不急待地连接好下载调试接口,随着一声短促的“嘀”声,评测板上四个不同颜色的LED便按照不同的频率闪烁起来了——绚丽多彩: 收到评测板并上电测试后,第二件必不可少的事情就是建立开发平台和自己的测试程序。相关的资料早已下载好了,只等评测板到了就可动手,万事俱备,只等东风。 一、开发平台的建立 我用的是Keil 5.28,先运行MindMotin.MM32L0xx_DFP.1.0.9.pack升级包,将MM32L0xx系统的相关参数添加到Keil中,此时Keil中就可以识别到mm32L0xx系列的MCU了。 再运行mm32_devkit.setup.exe程序,将mm32-LINK添加进去,此时keil中就可以选择到mm32-LINK,就可以下载烧录代码了。 END 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2021-01-23 关键词: 国产 嵌入式 STM32

  • 基于STM32的韦根协议接收实现

    01 前言 今年疫情期间,为了便于管理,智能门禁系统被广泛应用,市面上大量的门禁终端都是使用韦根协议进行通讯。什么是韦根协议?如何实现?本文详细解读。 02 韦根协议介绍 Wiegand(韦根)协议是由摩托罗拉公司制定的一种通讯协议,它适用于涉及门禁控制系统的读卡器和卡片的许多特性;其协议并没有定义通讯的波特率、也没有定义数据长度。它有很多格式,标准的韦根26-bit是最常用的格式。 此外,还有34-bit、36-bit、44-bit等格式。而标准的26-bit格式是一个开放式的格式,这就意味着任何支持韦根26-bit输入\输出的设备都可以互相连接进行通信。 韦根数据输出由两条数据线DATA0和DATA1,和公共的信号地GND组成。在没有数据输出时,DATA0和DATA1都保持高电平(典型为+5V电平),若输出'0'时,DATA0输出低脉冲而DATA1保持为高电平,输出'1'时,DATA1输出低脉冲而DATA0保持为高。典型的低脉冲宽度为50us,输出每一bit之前的间隔为1ms(实际的信号电平和时序由实际的韦根读卡器决定)。下图为韦根时序图 韦根26:一个“韦根包”有26位数据,第1位为第1到第13位的偶校验,最后1位为第14到第26位的奇校验,中间24位为数据位。 韦根34:即一个“韦根包”有34位数据,格式为第1位为第1到第17位的偶校验,最后1位为第18到第34位的奇校验,中间32位为数据位。 03 软件实现 本文实现韦根协议26bit/34bit的接收和数据解析,在数据接收时采用一个循环缓冲数组进行接收,通过中断接收,以信号的下降沿触发,不用判断脉冲宽度,只要做个计时器,做个超时判断数据接收完成进行处理。 管脚初始化和外部中断初始化: //头文件中对管脚进行定义PC9 DATA0 PC8 DATA1#define DATA0_GPIO_PORT GPIOC#define DATA0_GPIO_PIN GPIO_Pin_9#define DATA0_GPIO_MODE GPIO_Mode_IN_FLOATING#define DATA0_GPIO_EXTI_PORT_SOURCE GPIO_PortSourceGPIOC#define DATA0_GPIO_EXTI_PIN_SOURCE GPIO_PinSource9#define DATA0_EXTI_LINE EXTI_Line9#define DATA0_EXTI_TRIGGER EXTI_Trigger_Falling#define DATA0_EXTI_IRQ EXTI9_5_IRQn #define DATA1_GPIO_PORT GPIOC#define DATA1_GPIO_PIN GPIO_Pin_8#define DATA1_GPIO_MODE GPIO_Mode_IN_FLOATING#define DATA1_GPIO_EXTI_PORT_SOURCE GPIO_PortSourceGPIOC#define DATA1_GPIO_EXTI_PIN_SOURCE GPIO_PinSource8#define DATA1_EXTI_LINE EXTI_Line8#define DATA1_EXTI_TRIGGER EXTI_Trigger_Falling#define DATA1_EXTI_IRQ EXTI9_5_IRQn //C文件中进行初始化/**----------------------------------------------------------------- * @函数名 wiegand_init * @功能   初始化wiegand接口,和外部中断配置 * * @参数 无 * @返回值 u8 0表示初始化完成***----------------------------------------------------------------*/u8 wiegand_init(void){ NVIC_InitTypeDef NVIC_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = DATA0_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = DATA0_GPIO_MODE; GPIO_Init(DATA0_GPIO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = DATA1_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = DATA1_GPIO_MODE; GPIO_Init(DATA1_GPIO_PORT, &GPIO_InitStructure); GPIO_EXTILineConfig(DATA0_GPIO_EXTI_PORT_SOURCE, DATA0_GPIO_EXTI_PIN_SOURCE); EXTI_InitStructure.EXTI_Line = DATA0_EXTI_LINE; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = DATA0_EXTI_TRIGGER; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = DATA0_EXTI_IRQ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); GPIO_EXTILineConfig(DATA1_GPIO_EXTI_PORT_SOURCE, DATA1_GPIO_EXTI_PIN_SOURCE); EXTI_InitStructure.EXTI_Line = DATA1_EXTI_LINE; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = DATA1_EXTI_TRIGGER; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = DATA1_EXTI_IRQ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); return 0;} 外部中断接收处理: void EXTI9_5_IRQHandler(void){ if(EXTI_GetFlagStatus(DATA0_EXTI_LINE)) //DATA0数据触发 { EXTI_ClearFlag(DATA0_EXTI_LINE); if(g_wiegandType.Flag.StartFlag == 0) //数据接收开始标志 { g_wiegandType.Flag.StartFlag = 1; g_wiegandType.DataIn.DataLen = 0; g_wiegandType.DataIn.Data[g_wiegandType.DataIn.DataLen++] = 0; //数据0存入数组 g_wiegandType.bitIntervalTime = MAX_BIT_INTERVAL_TIME; //数据位超时处理 } else { g_wiegandType.DataIn.Data[g_wiegandType.DataIn.DataLen++] = 0; g_wiegandType.bitIntervalTime = MAX_BIT_INTERVAL_TIME; } } if(EXTI_GetFlagStatus(DATA1_EXTI_LINE)) //DATA1数据触发 { EXTI_ClearFlag(DATA1_EXTI_LINE); if(g_wiegandType.Flag.StartFlag == 0) { g_wiegandType.Flag.StartFlag = 1; g_wiegandType.DataIn.DataLen = 0; g_wiegandType.DataIn.Data[g_wiegandType.DataIn.DataLen++] = 1; //数据1存入数组 g_wiegandType.bitIntervalTime = MAX_BIT_INTERVAL_TIME; } else { g_wiegandType.DataIn.Data[g_wiegandType.DataIn.DataLen++] = 1; g_wiegandType.bitIntervalTime = MAX_BIT_INTERVAL_TIME; } if(g_colStatus == 1) { COL1_LOW(); } else { COL1_HIGH(); } } } 收到一组数据后进行解析: /**----------------------------------------------------------------- * @函数名 wiegand26 * @功能 wiegand26数据解析 * * @参数 无 * @返回值 u8 >0表示数据异常***----------------------------------------------------------------*/u8 wiegand26(void){ u8 i; u32 result; u8 odd = 0; u8 even = 0; if (g_wiegandType.DataIn.DataLen != 26) { return 1; } result = 0; for (i = 0; i < 24; ++i) { if(g_wiegandType.DataIn.Data[i+1] == 1) { result |= 1<<(23-i); if(i<12) { even++; } else { odd++; } } } g_wiegandType.result = result; if(even%2) //偶校验判断 { if(g_wiegandType.DataIn.Data[0]!=1) return 3; } else { if(g_wiegandType.DataIn.Data[0]==1) return 3; } if(odd%2)//奇校验判断 { if(g_wiegandType.DataIn.Data[25]==1) return 3; } else { if(g_wiegandType.DataIn.Data[25]!=1) return 3; } return 0;}/**----------------------------------------------------------------- * @函数名 wiegand34 * @功能 wiegand34数据解析 * * @参数 无 * @返回值 u8 》0表示数据异常***----------------------------------------------------------------*/u8 wiegand34(void){ u8 i; u32 result; u8 odd = 0; u8 even = 0; if (g_wiegandType.DataIn.DataLen != 34) { return 1; } result = 0; for (i = 0; i < 32; ++i) { if(g_wiegandType.DataIn.Data[i+1] == 1) { result |= 1<<(31-i); if(i<16) { even++; } else { odd++; } } } g_wiegandType.result = result; if(even%2) { if(g_wiegandType.DataIn.Data[0]!=1) return 3; } else { if(g_wiegandType.DataIn.Data[0]==1) return 3; } if(odd%2) { if(g_wiegandType.DataIn.Data[33]==1) return 3; } else { if(g_wiegandType.DataIn.Data[33]!=1) return 3; } return 0;} 处理好的数据通过调试串口输出,查看和自己的发送的卡号是否一致,如果异常请检查DATA0和DATA1是否接反。 04 结语 韦根协议是一个比较简单的单工通信,很容易理解,如果有问题欢迎讨论。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2021-01-23 关键词: 实现 韦根协议 STM32

  • 基于STM32的DIY遥控小船制作

    01 前言 周末带小朋友去公园玩耍,别的小孩在池塘里玩饮料瓶做的漂流船,看着他欢乐的跟着跑跳,无比羡慕的眼神,却又不能上手的小失落,又回想起儿时用泡沫板和小电机以及电池做的小船,和那时对于电驱动产生的无比兴趣,我决定升级一下儿时的装备,基于STM32给小朋友DIY一个遥控小船,使他成为公园里焦点,同时也期待他对电气控制产生一点点好奇。 基本构想如下:stm32驱动两个小电机,小电机上安装两个螺旋桨,可以实现双桨前进、后退,单桨转弯等。供电使用18650电池,通过升压放电板管理电池的充放电。遥控使用最廉价的红外遥控,来控制小船的各种动作。同时可以增加一组数码管作为输出设备。 在万能的某宝上可以淘到所有需要的材料,并且价格都十分实惠。 材料 单价 备注 船体 0 废旧收纳盒 驱动电机、螺旋桨 10.5 红外遥控器、红外接收器 3.5 18650电池 0 废旧小电扇拆件 充电升压放电板 5.8 STM32最小系统板 16.2 4位数码管显示器 4.3 电池盒 1.8 线材 0 废线材 电源开关 1.8 合计 43.9 02 硬件模块 红外遥控模块 一般遥控玩具使用的是2.4G高频无线遥控模块,但是本着能省则省的原则,选用遥控器带接收头3.5元还包邮的HX1838红外遥控模块。这种遥控器类似电视遥控器,优点是便宜,开发简单,缺点是控制距离短,并且要像遥控电视一样指着玩具操作,这些缺陷留着下一代产品迭代时升级。 电机驱动模块 电机使用直流小电机,3-6V可驱动,使用一片L298N驱动板驱动。STM32输出PWM可调速,可正反转。 数码管显示 基于TM1637的四位数码管,用于显示一些简单的信息。TM1637是天微公司出品的LED驱动专用芯片,集成简单,开发方便。除了TM1637,天微公司还有一系列TM16XX的芯片,主要区别就是位输出和段输出个数多少。 电池和充放电模块 18650电池具有容量大,寿命长,安全性高等优点,普遍使用在充电宝,笔记本电池、仪器仪表中,甚至特斯拉的电池组也是由7000多节这样的电池组合成的。充放电线路板,主要的作用是将电池3.7V的输出电压升压到5V,供给STM32及外围电路使用,同时还具备给电池充电的功能。实际上等同于一个充电宝,充电宝的原理就是若干个18650电池并联,再用一块充放电板管理而已。 03 嵌入式软件 红外遥控器驱动 HX1838采用的是 NEC 编码格式。载波频率为38khz。 逻辑1是2.25ms,脉冲时间560us;逻辑0为1.12ms,脉冲时间560us。根据脉冲时间长短来解码。 协议示意图: 一组数据组成: 起始是9ms的高电平脉冲 4.5ms的低电平 8位地址码,低位在前 8位地址码的反码,用于校验 8位命令码,低位在前 8位命令码的反码。 需要注意的是1838红外一体接收头为了提高接受灵敏度。输入高电平,其输出的是相反的低电平。实际测量接收到的按键波形如下图,可以看到电平是相反的。 代码实现: 使用下降沿外部中断作为一次按键的检测触发,然后每个20us读取一次数据管脚,按照上述的协议逻辑,读出一个u32的数据。 U8 Infrared_Receiver_Process(void){ U16 nTime_Num = 0; U8 nData = 0; U8 nByte_Num = 0; U8 nBit_Num = 0; nTime_Num = Infrared_Receiver_GetLowLevelTime();//t0=nTime_Num;if((nTime_Num >= 500) || (nTime_Num <= 400))//9ms 数据头高电平 hx1838输入的反的{ return INFRARED_RECEIVER_ERROR;}nTime_Num = Infrared_Receiver_GetHighLevelTime();//4.5ms 低电平 hx1838输入的反的if((nTime_Num >= 250) || (nTime_Num <= 200)){ return INFRARED_RECEIVER_ERROR;} for(nByte_Num = 0; nByte_Num < 4; nByte_Num++)//4个8位码{ for(nBit_Num = 0; nBit_Num < 8; nBit_Num++) { nTime_Num = Infrared_Receiver_GetLowLevelTime();//560us 高电平 hx1838输入的反的 nTime_Num=28 if((nTime_Num >= 60) || (nTime_Num <= 20)) { return INFRARED_RECEIVER_ERROR; } nTime_Num = Infrared_Receiver_GetHighLevelTime();//1690us(nTime_Num=84.5) 是逻辑1 560us是逻辑0 nTime_Num=28 if((nTime_Num >=60) && (nTime_Num < 100)) { nData = 1; } else if((nTime_Num >=10) && (nTime_Num < 50)) { nData = 0; } else { return INFRARED_RECEIVER_ERROR; } gInfraredReceiver_Data <<= 1; gInfraredReceiver_Data |= nData; }} return INFRARED_RECEIVER_OK;} 经过调试,得到遥控器的按键键值如下: KEY_OK = 0x38,KEY_UP = 0x18,KEY_DOWN = 0x4a,KEY_LEFT = 0x10,KEY_RIGHT = 0x5a,KEY_1 = 0xa2,KEY_2 = 0x62,KEY_3 = 0xe2,KEY_4 = 0x22,KEY_5 = 0x02,KEY_6 = 0xc2,KEY_7 = 0xe0,KEY_8 = 0xa8,KEY_9 = 0x90,KEY_STAR = 0x68,KEY_0 = 0x98,KEY_SHARP = 0x80, 另外由于NEC编码格式的红外遥控广泛的用于电视遥控,我们也可以找一个电视遥控器,把键值读出来,写入到stm的程序中,这样就可以用电视遥控器来操作小船了。 数码管显示驱动 TM1637与其他的一些TM芯片有一些区别,没有同步通信的STB脚,控制时序也有相应改变。 在输入数据时当CLK是高电平时,DIO上的信号必须保持不变;只有CLK上的时钟信号为低电平时,DIO上的信号才能改变。数据输入的开始条件是CLK为高电平时,DIO由高变低;结束条件是CLK为高时,DIO由低电平变为高电平。TM1637的数据传输带有应答信号ACK,当传输数据正确时,会在第八个时钟的下降沿,芯片内部会产生一个应答信号ACK将DIO管脚拉低,在第九个时钟结束之后释放DIO口线。 代码实现: void start(void){dio_output();GPIO_SetBits(CLK_GPIO_PORT,CLK_GPIO_PIN);GPIO_SetBits(DIO_GPIO_PORT,DIO_GPIO_PIN);Delay();GPIO_ResetBits(DIO_GPIO_PORT,DIO_GPIO_PIN);Delay();} void stop(void){dio_output();/*GPIO_ResetBits(CLK_GPIO_PORT,CLK_GPIO_PIN);Delay();*/GPIO_ResetBits(DIO_GPIO_PORT,DIO_GPIO_PIN);Delay();GPIO_SetBits(CLK_GPIO_PORT,CLK_GPIO_PIN);Delay();GPIO_SetBits(DIO_GPIO_PORT,DIO_GPIO_PIN);Delay();} void ack(void){u8 i;dio_input();GPIO_ResetBits(CLK_GPIO_PORT,CLK_GPIO_PIN);Delay();while(readDio()==1&&(i<250))i++;GPIO_SetBits(CLK_GPIO_PORT,CLK_GPIO_PIN);Delay();GPIO_ResetBits(CLK_GPIO_PORT,CLK_GPIO_PIN);dio_output();}void write_data(u8 wr_data){u8 i;for(i=0;i<8;i++)//开始传送8位数据,每循环一次传送一位数据{ GPIO_ResetBits(CLK_GPIO_PORT,CLK_GPIO_PIN); if(wr_data & 0x01){ GPIO_SetBits(DIO_GPIO_PORT,DIO_GPIO_PIN); }else{ GPIO_ResetBits(DIO_GPIO_PORT,DIO_GPIO_PIN); } Delay(); wr_data >>= 1; GPIO_SetBits(CLK_GPIO_PORT,CLK_GPIO_PIN); Delay();}ack();}void tm1637_DispStr(u8 *str){u8 i;u8 ledCode[5]; for(i = 0;i < 4;i++){ledCode[i] = GetLedCode(str[i]);}start();write_data(0x44); //固定地址模式stop();start();write_data(LED_GRID1);write_data(ledCode[0]);stop();start();write_data(LED_GRID2);write_data(ledCode[1]);stop();start();write_data(LED_GRID3);write_data(ledCode[2]);stop();start();write_data(LED_GRID4);write_data(ledCode[3]);stop();start();write_data(0x89);stop();} 小船控制逻辑 目前的逻辑比较简单, 就是收到按键后控制电机的转停。 void dealIrKeyDown(){u8 len;len = irKeyfifo_count;if(len > 0){ irKeyDataOut = irKeyfifo_DataOut(); //按键显示 U8ToHexstr(irKeyDataOut.index,dispStr); U8ToHexstr(irKeyDataOut.cmd,dispStr+2); tm1637_DispStr(dispStr); switch(irKeyDataOut.cmd){ case KEY_OK: case KEY_OK_CHANGHONG: motor1_Stop(); motor2_Stop(); break; case KEY_UP: case KEY_UP_CHANGHONG: motor1_ForwardRun(); motor2_ForwardRun(); break; case KEY_DOWN: case KEY_DOWN_CHANGHONG: motor1_BackwardRun(); motor2_BackwardRun(); break; case KEY_LEFT: case KEY_LEFT_CHANGHONG: motor1_ForwardRun(); motor2_Stop(); state = SHIP_LEFT; break; case KEY_RIGHT: case KEY_RIGHT_CHANGHONG: motor2_ForwardRun(); motor1_Stop(); state = SHIP_RIGHT; break; default: motor1_Stop(); motor2_Stop(); state = SHIP_STOP; break; } }} 04 DIY成品展示和演示视频 05 总结存在的问题 遥控不灵敏 毕竟红外遥控的使用场合并不是移动的物体上,再加上距离近,经常会出现遥控要按多次的情况。后期考虑改进stm的协议处理方法,增加容错性,如果还是不行就考虑升级为2.4G专用的玩具遥控。 防水性问题 正常使用不会有水进入,但是在没有大人的情况下交给小孩操作,就没那么保险了。 后期考虑升级防水性能。 没有声光电,不够酷炫。 考虑升级,增加led灯条。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2021-01-23 关键词: 遥控小船 STM32

首页  上一页  1 2 3 4 5 6 7 8 9 10 下一页 尾页
发布文章

技术子站

更多

项目外包