• [分享交流] 20240922上海海思-基于华为海思星闪前沿技术的单片机与嵌入式课程教学改革探索实践
    为了较好地分享海思最新的技术进展与高校合作规划,共同探讨电子工业高质量应用型人才培养之路,深入推进校企合作和产教融合,切实提高专业建设质量、课程教学质量和人才培养质量,把华为海思MCU技术与星闪前沿技术与课程教学深度结合,真正的实现校企协同育人,扎实做好产教融合工作,为产业输送更多的优秀人才,9月22日,单片机原理与应用虚拟教研室多位教师应邀参加于上海海思创新与生态实验室举办的上海海思-高校人才培养交流会,沈阳理工大学国家一流课程负责人、辽宁省本科教学名师、华为开发者布道师、星闪科技创新团队指导教师张东阳教授作基于海思MCU/星闪前沿技术的单片机与嵌入式课程教学改革主题报告,来自全国各地的二十多位单片机与嵌入式国家一流课程负责人和优秀教师参加了会议。会议首先由海思高校生态合作总监谢晶带领与会教师参观上海海思展厅,海思生态与伙伴发展部部长赵秋静致欢迎词并简要说明了本次会议的目的和意义,随后谢晶总监作海思MCU/星闪前沿技术分享,并发布了新的基于海思MCU/星闪技术的全国大学生嵌入式芯片与系统设计竞赛-海思赛道的竞赛计划。海思致力于使能万物互联的智能终端,成为千行百业数字化、网联化、智能化、低碳化的产业基石,并着眼未来,把高校开发者培养作为自己的重要战略与责任,把高校作为自己实现产业创新最为重要的推动力量,本次会议以高校人才培养为主题,以灵感碰撞为目标,以校企合作惠及高校广大师生,以产教融合惠及更多产业。张东阳教授作为首批华为开发者布道师为大家讲述了华为海思前沿技术融入课程教学实施计划,并作了基于海思MCU/星闪前沿技术的单片机与嵌入式课程教学改革主题报告,报告主要包括三个方面的内容:一是单片机与嵌入式课程教学改革所取得的良好教学成效及其目前在高质量应用型人才方面所面临的主要问题;二是基于星闪前沿技术+海思MCU的单片机和嵌入式课程教学改革、实践教学改革和课外创新团队建设;三是通过深入开展华为海思嵌入式芯片-星闪应用领域“课-训-赛-用”综合人才培养合作,可以探索一套高效的基于前沿技术的课程教学模式和人才培养模式,帮助高校师生拓展行业视野,提升技术知识,丰富实践经验,并应用前沿技术,围绕真实的应用环境,开发真实应用项目,解决真实问题,为自主可控的产业生态快速培养大批高质量嵌入式开发工程师,为师生的未来发展拓展出无限的发展空间。与会教师和华为海思生态专家与技术专家就如何应用星闪前沿技术+海思MCU深入开展单片机与嵌入式课程教学改革、实践教学改革、创新团队建设和高质量应用型人才培养,把海思前沿技术与高校教学深度结合,较好地实现校企合作产教融合协同育人,以校企合作惠及高校广大师生,以产教融合惠及更多产业,为产业输送优秀人才等进行了深入的交流,并达成了广泛的共识。
  • [课程学习] Atlas 200I DK A2不支持算子开发吗
    我看这个文档中有包含这一块的内容,学习向导-Atlas 200I DK A2开发者套件23.0.RC2开发文档-昇腾社区  但是自己实践起来无法编译,显示硬件不支持
  • [活动分享] 2024年上海海思MCU开发者体验官招募,手机/MatePad大奖等你拿!
    活动详情:https://developers.hisilicon.com/postDetail?tid=0296157624908214058
  • [活动分享] 【重磅发布】海思首批星闪开发者体验官招募,星闪手机等你拿!
    参加活动详情:https://developers.hisilicon.com/postDetail?tid=02102154262223765013                                                    指导单位:国际星闪无线短距通信联盟                                                                    主办单位:华为技术有限公司、海思技术有限公司                                                          协办单位:江苏润和软件股份有限公司
  • [技术干货] 华为的tag 有开源协议吗 iBeacon可以自己做硬件设备加入这个互联联盟吗
    在苹果生态中的 air tag 中有公开的入网协议,想问一下,华为的tag 有没有公开的协议。先谢谢各位技术佬了【抱拳】
  • [问题求助] RT-Thread可以通过MQTT连接到华为云吗?
    求助大佬!最近想用RT-Thread星火一号开发板做东西,发现它有连接阿里云,腾讯云的软件包,没有华为云的,这两个是不能连吗?(可以连的话,要怎么连呢?)
  • [其他] 基于华为好望相机路内泊位识别算法程序
    基于华为好望相机路内泊位识别算法程序-附件一、项目背景、基于华为好望相机实现对路侧泊位驶入驶出动态数据的识别和抓拍,摄像机作为前端的采集设备,实现前端智能图像识别,实时视频监测,实时推送识别结果。同时高位相机所含功能支持停车管理规范要求(例如违停检测、跨位检测、压线检测、非机动车检测、车位外停车、逆向停车检测、空位检测、自动对焦、曝光补偿等)。城市路内泊车高位视频识别算法” 分布式部署至前端智能相机中,对车辆泊入泊出动态行为进行识别,后端无需二次识别,自动成单率≥98%,并自动截存车辆泊入泊车的图片和视频,形成完整证据链。  实现车辆进出泊位视频图像的实时采集与智能分析,并将结果通过网络发送给城市高位视频数字泊车综合信息管理云平台。相机型号M2141-EL和M2241-QL。二、项目目标、前端算法要求车辆驶入驶出动作识别达到98%,车牌识别达到99.99%,成单识别率达到98%,响应时间≤2s。同时具备违停检测、跨位检测、压线检测、车位外停车、逆向停车检测、空位检测、自动对焦、曝光补偿。相机程序需要提供:识别异常报警、识别错误报警、断网续传、断点续传。三、需要开发软件的功能、集车牌识别、车辆泊车行为识别、图像采集、视频录像、数据通信功能于一体,无需服务器后端进行二次识别,实现车辆进出泊位视频图像的实时采集与智能深度分析,并将结果通过网络发送给综合信息管理云平台。好望软件程序,嵌入华为相机内和算法相配合,识别后结果回传。程序软件功能: 和服务端长时间通讯,确保设备在线。遇到变焦识别错误问题,进行上报管理平台告警、遇到光照不足/太强,支持曝光补偿、支持车牌倾斜校正、网络速率报警、支持断网续传、断网存储识别图片后等网络链接成功后上传。遇到算法识别错误进行上报告警、支持内存告警、设备问题告警,支持图传功能。识别算法,和相机程序相配置。识别算法功能:对正常车辆驶入驶出做抓拍上传,对非机动车占道、违规停车、树木遮挡报警、号码遮挡车辆、进行报警,同时对特殊车牌识别,并识别车辆车牌信息,绑定证据链上传。识别算法性能: 车牌识别准确率≥99.99%,响应时间≤2秒、空位检测识别准确率≥98%,自动成单率≥98%,车辆驶入驶出动作识别准确率≥98%,响应时间≤1秒、并发处理能力≥5个车牌,识别泊位车牌。图像采集要求:1.补光要求:夜间监控智能开启补光灯,确保车牌区域的光照强度不低于70lux。补光灯应具备自动调节功能,根据环境光线的变化自动调节亮度。2.车牌宽度要求:摄像头应能够在车辆从最远处到最近处行驶的过程中,保持车牌宽度在70px到240px之间。在此范围内,车牌字符应清晰可见,易于识别。3.倾斜度要求:•车牌字符在图像中应保持水平和垂直方向上的正直,无明显倾斜。•系统具备车牌倾斜校正功能,确保车牌字符识别的准确性。4.遮挡要求:•系统应能够应对行人、非机动车、其他车辆等造成的动态或静态遮挡。•在遮挡情况下,系统能够尽可能恢复车牌字符的完整性和清晰度。5.自动对焦与防抖处理:•摄像头应具备自动对焦功能,确保在不同距离和光线条件下车牌字符的清晰度。•摄像头在拍摄过程中应考虑防抖处理,避免因抖动导致的图像模糊或失真。6.图像稳定性:•系统应确保图像的整体稳定性,避免因摄像头抖动或环境干扰导致的图像质量下降。7.对比度要求:•图像中的对比度应适中,避免过曝或欠曝现象,确保车牌字符和背景的细节清晰可见。8.背景干扰要求:•背景应尽可能简洁清晰,减少与车牌字符的混淆和干扰。•系统应具备背景抑制功能,突出车牌字符,提高识别的准确性。9.色彩保真要求:•系统应确保图像色彩的真实性和准确性,避免色彩失真或偏差。•对于不同颜色、材质的车牌(如黄绿蓝车牌、军牌、政府车辆、特种车辆等),系统应能够准确呈现车牌字符的颜色和细节。算法扩展性:系统需要具备良好的模块化设计、灵活的架构和可扩展的接口,以便在未来能够轻松地进行扩展和升级。算法维护性:系统需要定期更新软件版本、修复安全漏洞、调整参数设置等,以确保系统的正常运行和持续优化。需要具备良好的文档支持、易于理解的代码结构、方便的调试工具和自动化的测试机制,以降低维护成本和提高维护效率。传输接口程序,用于配置调试和接口信息:定义传输方式和接口内容,同时包含设置程序、调试程序、配置程序、打包程序等配套程序。四、业务使用场景、城市道路泊位智慧停车,通过高位监控上传车辆驶入驶出证据链,同时支持垂直、平行泊位搭建场景。路侧高位泊位、垂直泊位、车辆出入识别、车辆车牌识别、违规车辆识别。满足我方项目使用华为M2141-EL和M2241-QL相机灌入程序和算法。五、业务流程、由相机算法本地识别车辆动作后,记录抓拍照片后传输至后端服务接口。六、系统软件功能、   定义传输接口、告警接口、上传接口,调试功能、日志记录等,支持手动配置管理。七、可参考的软件或产品、桂林金铱星科技发展有限公司深圳云游四海信息科技有限公司
  • [问题求助] iot studio软件按照步骤安装,编译显示Arm-none-eabi-gcc:致命错误:没有输入文件编译终止。
    iot studio软件按照步骤安装,编译显示Arm-none-eabi-gcc:致命错误:没有输入文件编译终止。
  • [技术干货] 2024年2月嵌入式项目开发专题总汇
    一、前言在嵌入式开发领域,C语言由于其高效性、可移植性和对底层硬件的直接控制能力而被广泛采用。当前通过一系列实践主题和案例,勾勒出C语言在嵌入式系统编程中的关键应用与技术要点。以下所列的文章,介绍知识应用和技术点:(1)C语言语句与位运算:在嵌入式系统中,位运算符和位操作对于高效地处理硬件寄存器配置以及数据压缩等领域至关重要。学习如何利用C语言提供的逻辑与(&)、逻辑或(|)、异或(^)、左移(<<)、右移(>>)等位运算符进行精确的二进制操作是嵌入式开发者的基本技能之一。(2)C语言数组的查找、替换、排序、拼接:数组作为基本的数据结构,在存储传感器数据、设备状态信息等方面不可或缺。掌握数组元素的高效查找、替换算法(如线性搜索、二分查找),实现快速排序、冒泡排序等排序方法,以及数组内容的合并拼接,能够帮助开发者更好地管理内存受限环境下的数据集合。(3)C语言标准时间与秒单位转换:在涉及实时处理、日志记录等功能时,正确处理时间戳至关重要。C语言的标准库提供了时间和日期相关的函数接口,用于将秒单位的时间转换成易读格式或进行精准的时间计算。(4)C语言函数封装与变量作用域:良好的程序设计依赖于模块化和信息隐藏,函数封装是这一原则的重要体现。理解并运用好局部变量、全局变量和静态变量的作用域规则,以及如何定义和调用函数来实现功能模块化,有助于提升代码的可读性、可维护性和复用性。(5)STM32控制max30102读取血氧心率数据实例:该实战项目展示了如何使用C语言配合Keil MDK开发工具,驱动特定传感器(如MAX30102)采集生理信号,并进行数据解析。这种实际案例生动演示了嵌入式软件如何与硬件交互以实现具体应用需求。(6)C语言字符串与指针练习:字符串处理和指针操作是C语言的核心部分,在嵌入式编程中尤为常见,比如处理网络数据包、文件I/O、用户界面显示等。理解和熟练运用字符串函数,以及通过指针动态管理内存,是嵌入式程序员必备的能力。(7)C语言结构体:结构体能够组合不同类型的数据,模拟复杂的实体对象,适用于描述设备配置信息、协议数据包等。掌握结构体声明、初始化、成员访问以及结构体数组和指针的使用,有助于构建复杂的数据模型。(8)C语言单向与双向链表:链表作为一种灵活的数据结构,在资源受限的嵌入式环境中可以有效替代数组,实现动态增删节点的操作,尤其适用于缓存管理、消息队列等场景。(9) C语言标准文件读写接口与文件操作函数案例:文件系统操作是嵌入式系统中常见的任务,如读取配置文件、保存设备日志、上传下载数据等。深入研究C语言标准库中的文件操作函数,如fopen、fclose、fread、fwrite等,以及如何编写安全、高效的文件读写程序,对提高嵌入式系统的数据持久化能力至关重要。(10)基于STM32的儿童智能安全防护书包设计:这个实际项目不仅涉及到C语言的综合运用,还展示了如何结合嵌入式微控制器设计智能产品,整合各类传感器数据、无线通信模块等功能,体现了嵌入式技术在物联网(IoT)和消费电子领域的广泛应用。(11)C语言处理文件目录后缀:针对文件系统的高级操作,例如根据文件扩展名进行过滤或重命名,是嵌入式系统中可能遇到的功能需求。学习如何在C语言中解析路径和文件名,进而处理文件类型和扩展名,有助于增强系统的文件管理功能。(12)Linux与Windows下C语言使用curl库的安装使用及HTTP文件下载:跨平台网络通信在现代嵌入式开发中必不可少,curl库是一个流行的HTTP客户端工具库。了解如何在不同的操作系统环境下安装和使用curl库,编写C语言代码实现HTTP请求、文件下载等网络服务功能,有利于开发具备互联网连接能力的嵌入式产品。二、文章列表【1】嵌入式开发_C语言语句与位运算cid:link_3通过一系列精心设计的C语言编程练习题,巩固和深化对C语言语法的理解和应用。文章中包含了一系列覆盖C语言基础知识点的练习题目,如素数计算、排序算法、求偶数和、可逆素数、水仙花数、大小写字母转换、变量值交换、位运算以及C语言的语法特性等。每个练习题都不仅提供了问题描述,还深入探讨了解题思路和实现方法,帮助读者掌握C语言的编程技巧。通过对这些练习题的逐一解答,读者可以加深对C语言控制结构、数据类型、函数、数组、指针等核心概念的理解。知识点:素数计算:介绍如何通过循环和条件判断来识别素数。排序算法:展示如何使用数组和循环结构实现基本的排序算法。求偶数和:讲解如何利用循环累加数组中的偶数元素。可逆素数:探索如何判断一个数是否为可逆素数,即其平方的末尾数字不变。水仙花数:分析如何查找并验证一个三位数是否为水仙花数。大小写转换:演示如何使用字符操作函数进行大小写字母的转换。变量值交换:阐述不使用临时变量的情况下如何交换两个变量的值。位运算:解释位运算的基本概念及其在C语言中的应用。语法特性:总结C语言的一些重要语法特性,帮助读者避免常见错误。【2】嵌入式开发_C语言数组的查找、替换、排序、拼接cid:link_4在这篇文章中,将深入探讨C语言中位运算的高级应用,并通过一系列实战例子来展示位运算在实际编程中的威力。总结了位运算的基本概念,包括位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)和右移(>>)等操作,然后通过几个具体的编程实例,如快速幂运算、状态压缩等,让读者体会位运算在优化程序性能方面的独到之处。接下来,将从数组的基本定义和用法开始,逐步深入到数组的各种操作技巧。这包括对数组进行排序、插入新元素、拼接两个数组、删除指定元素以及在字符串中查找和替换特定内容等实用技能。每个操作都配以详细的代码示例和解析,帮助读者理解并掌握这些关键的数组处理方法。知识点:位运算实战例子:提供多个实际问题的解决方案,让读者了解如何利用位运算简化计算过程。数组基本定义与用法:介绍数组的声明、初始化及其在内存中的存储方式。数组排序:演示如何使用标准库函数或自定义算法对数组进行排序。数组插入:解释如何在数组中插入新元素并保持原有顺序。数组拼接:展示如何将两个或多个数组合并成一个更大的数组。数组删除:讲述如何从数组中移除特定元素或子数组。字符串查找替换:探索在C语言中处理字符串的技巧,包括查找子串和替换内容。【3】嵌入式开发_C语言标准时间与秒单位的转换cid:link_5在这篇博客文章中,将深入探讨嵌入式单片机中标准时间与秒单位之间的转换方法。介绍了标准时间和秒单位的概念,以及它们在嵌入式单片机中的应用场景。接着,通过两个具体的例子来展示如何将RTC时钟的时间转换为标准时间进行显示。这两个例子都包含了详细的代码注释和思路解析,帮助读者更好地理解和掌握这种转换方法。文章知识点:标准时间与秒单位概念介绍:解释标准时间和秒单位的定义及其在嵌入式单片机中的应用。RTC时钟时间读取:演示如何从RTC时钟中读取秒单位时间,并将其转换为标准时间进行显示。代码注释和思路解析:提供详细注释和思路解析,帮助读者理解代码的实现过程。示例代码演示:给出两个具体的示例代码,展示如何在不同场景下进行标准时间与秒单位的转换。【4】嵌入式开发_C语言函数封装、变量的作用域cid:link_0在这篇文章中,将深入探讨C语言中函数封装的重要性以及变量作用域的规则。从函数的基本概念讲起,阐述如何将常用的代码块封装成可重用的函数,以及如何通过传递参数和返回值来增强函数的灵活性。接着,详细解释了变量作用域——包括局部变量和全局变量的区别,以及如何正确管理变量的生命周期来避免常见的编程错误。后面介绍了一系列关于字符串处理的实战练习,在帮助读者掌握C语言中字符串与数值之间的转换方法。这包括将字符串转换为整数、整数转换为字符串、浮点数与字符串之间的相互转换,以及如何判断给定年份是否为平年或闰年。文章介绍了如何使用标准库中的函数来高效地计算字符串的长度。知识点:函数封装:讲解如何将代码块封装成函数,提高代码的模块性和可维护性。变量作用域:分析局部变量和全局变量的定义、区别及应用场景。字符串转整数:展示如何使用标准库函数如atoi或自定义函数实现字符串到整数的转换。整数转字符串:介绍如何使用sprintf等函数将整数转换为字符串。浮点数与字符串互转:探讨使用sprintf和sscanf等函数处理浮点数与字符串之间的转换。平年闰年判断:编写函数来判断给定年份是平年还是闰年。字符串长度计算:说明如何使用strlen函数来计算字符串的长度。【5】嵌入式开发_C语言函数封装、变量的作用域cid:link_6【6】STM32控制max30102读取血氧心率数据(keil5工程)cid:link_6MAX30102是一款由Maxim Integrated推出的低功耗、高精度的心率和血氧饱和度检测传感器模块,适用于可穿戴设备如智能手环、智能手表等健康管理类电子产品。该传感器主要特性如下:(1)光学测量:MAX30102内置了两个LED光源(红光和红外光),以及一个光电检测器,通过光电容积脉搏波描记法(PPG)来实现心率和血氧饱和度的无创检测。(2)低功耗:在典型的工作模式下,其功耗非常低,有助于延长电池供电设备的使用寿命。(3)集成度高:内部集成了AFE(模拟前端)、LED驱动器、环境光抑制功能以及I²C数字接口,方便与微控制器连接通信。(4)多档位配置:支持多个LED电流输出级别和采样速率选择,可以根据实际应用需求进行灵活配置。(5)高精度:通过先进的信号处理算法,可以有效降低噪声干扰,提高测量数据的准确性。(6)小尺寸封装:采用紧凑型封装设计,便于在空间受限的产品中使用。MAX30102是一款高性能的生物医学传感器,能够帮助开发者在各种便携式和穿戴式设备上实现对人体生理参数的有效监测。【7】嵌入式开发_C语言标准时间与秒单位的转换cid:link_7在这篇博客文章中,将深入探讨嵌入式单片机中标准时间与秒单位之间的转换方法。介绍了标准时间和秒单位的概念,以及它们在嵌入式单片机中的应用场景。接着,通过两个具体的例子来展示如何将RTC时钟的时间转换为标准时间进行显示。这两个例子都包含了详细的代码注释和思路解析,帮助读者更好地理解和掌握这种转换方法。文章知识点:标准时间与秒单位概念介绍:解释标准时间和秒单位的定义及其在嵌入式单片机中的应用。RTC时钟时间读取:演示如何从RTC时钟中读取秒单位时间,并将其转换为标准时间进行显示。代码注释和思路解析:提供详细注释和思路解析,帮助读者理解代码的实现过程。示例代码演示:给出两个具体的示例代码,展示如何在不同场景下进行标准时间与秒单位的转换。【8】 嵌入式开发_C语言字符串与指针的练习cid:link_8在这篇文章中,将深入探讨C语言中字符串和指针相关的一系列重要知识点。文章通过一系列的练习题和实例,详细讲解了浮点数与字符串之间的相互转换、字符串的拷贝和比较、指针用于交换变量的值、指针的优先级规则,以及数据类型的强制转换和内存拷贝函数的使用。文章首先介绍了字符串与指针的基本概念,然后逐步引导读者学习如何操作字符串和指针来完成各种常见的编程任务。每个知识点都配以清晰的代码示例和详细的注释,帮助读者理解并掌握这些关键的C语言技能。【9】嵌入式开发_C语言结构体cid:link_1本篇文章主要介绍C语言中两个核心概念:动态堆空间的内存分配与释放,以及结构体的详细使用方法。介绍了如何在C语言中利用malloc和free函数进行动态内存的分配和回收,这对于处理数量未知或可变的数据集合至关重要。然后,深入探讨了结构体的定义、初始化、赋值操作,以及如何创建和操作结构体数组和结构体指针。在理论知识的基础上,文章通过开发一个简易的学生管理系统来综合运用这些知识点。这个实践案例不仅展示了结构体数组在实际项目中的应用场景,也演示了如何将结构体与动态内存管理结合起来,以高效地处理数据。【10】嵌入式开发_C语言单向与双向链表cid:link_9本篇文章将深入探讨C语言中链表的相关知识点,包括链表的创建、单向链表、循环链表、双向链表和单向循环链表等。总结一些链表常见问题,并提供结构体数组与链表的练习题供读者实践。在下一篇文章里,将提供完整的代码示例,帮助读者更好地理解和应用这些知识点。知识点:链表创建:讲解如何定义链表节点和头节点,以及如何初始化链表。单向链表:介绍单向链表的基本概念和操作,如插入、删除、遍历等。循环链表:解释循环链表的特点和应用场景,并演示如何实现循环链表的操作。双向链表:讲解双向链表的定义和常见操作,如在链表中查找元素、插入和删除节点等。单向循环链表:介绍单向循环链表的概念和特点,以及如何进行相关操作。链表常见问题总结:列举并解答一些常见的链表问题,帮助读者避免常见的错误。结构体数组与链表的练习题:提供一系列练习题,让读者通过实际编程来巩固所学知识。【11】嵌入式开发_C语言标准文件读写接口cid:link_10在这篇文章中探讨C语言中与文件操作相关的一系列重要知识点。文章详细介绍了最常用的文件操作函数,包括fopen、fread、fwrite和fclose等,并通过几个常见的需求场景提供实例,帮助读者更好地理解和掌握这些函数的用法。知识点:文件操作函数介绍:详细解释每个文件操作函数的功能和使用方法。fopen函数:讲解如何打开文件,并处理可能出现的错误情况。fread和fwrite函数:通过实例演示如何读取和写入文件内容。fclose函数:说明如何正确关闭一个已打开的文件。常见需求例子:提供实际应用场景的例子,如创建文件、追加内容、读取特定格式的数据等。错误处理:讨论在进行文件操作时可能遇到的错误,以及如何进行有效的错误处理。【12】嵌入式开发_C语言文件操作函数案例cid:link_11在这篇精彩的博客文章中,为希望提高C语言文件编程技能的读者准备了四个实战练习题。这些练习覆盖了从文件拷贝到学生管理系统开发的关键技能,包括文件加密和利用文件系统保存信息的链表模板。文章不仅提出了挑战,通过实践加深对文件操作函数应用的理解。文章知识点:文件拷贝实现:引导读者了解如何逐字节地读取源文件并写入目标文件,完成文件的复制过程。文件加密练习:介绍简单的文件加密技术,让读者尝试对文件内容进行加密和解密操作。学生管理系统链表模板:提供一个未添加文件操作的学生管理系统链表模板,作为后续练习的基础。学生管理系统模板:要求读者将学生信息通过文件系统进行持久化保存,增强管理系统的实用性。【13】基于STM32的儿童智能安全防护书包设计cid:link_12随着社会的进步和科技的发展,儿童安全问题日益引起广泛关注。在日常生活中,尤其是在上学放学途中、户外活动时,儿童走失事件时有发生,给家庭和社会带来了极大的困扰和担忧。随着学业负担的增加,学生时常会因为忘记携带所需书籍而影响学习。如何利用现代技术手段提高儿童安全保障水平,并辅助他们培养良好的学习习惯,成为了一个待解决的社会需求。基于此背景,当前设计并实现一款基于STM32F103RCT6微控制器为核心的儿童智能安全防护书包,显得尤为必要与实际。这款书包集成了先进的定位技术和无线通信模块,能够实时追踪并发送儿童的位置信息给家长,确保在紧急情况下快速响应 (发送短信的时候,直接通过GPS经纬度拼接百度地图的HTTP请求链接,家长收到短信可以直接打开链接,在网页查看百度地图上显示的具体位置,可以直接通过百度地图导航过去)。同时,具备智能化功能,如课程表录入存储与提醒系统,利用EEPROM(例如AT24C02)进行数据持久化存储,并通过RFID-RC522射频识别模块自动检测所携带书籍是否齐全,避免孩子因疏忽遗漏课本而耽误学习。智能书包还配备了直观易读的1.44寸LCD显示屏,用于显示当前位置信息、当日课表以及未带书籍的提醒。当检测到缺少某本书籍时,蜂鸣器模块会发出声音警报,从而强化提醒效果,帮助学生养成有序整理个人物品的习惯。这款基于STM32的儿童智能安全防护书包是一个集成物联网技术、GPS定位、无线通信和智能感知于一体的创新产品,提升儿童的安全防护等级,加强家校互动,促进学生自我管理能力的培养,充分体现了科技服务于生活、服务于教育的理念。【14】 嵌入式开发_C语言处理文件目录后缀cid:link_2在 C 语言中,对文件名、目录名、文件后缀等数据进行处理通常需要使用字符串处理函数和技巧。常用的字符串处理包括字符串拷贝、字符串连接、字符串比较、分割字符串以及获取文件后缀等操作。(1)字符串拷贝:使用 strcpy 函数可以将一个字符串复制到另一个字符串中。(2)字符串连接:使用 strcat 函数可以将两个字符串连接起来。(3)字符串比较:使用 strcmp 函数可以比较两个字符串是否相等。(4)分割字符串:可以使用 strtok 函数将字符串按照指定分隔符分割成多个子字符串。(5)获取文件后缀:通过查找字符串中最后一个.字符的位置,可以获取文件的后缀名。这些字符串处理函数和技巧能够帮助我们在 C 语言中对文件名、目录名、文件后缀等数据进行灵活处理。【15】Linux与windows下C语言使用curl库的安装使用以及访问HTTP下载文件cid:link_13cURL 是一个命令行工具和库,用于传输数据,支持多种协议,如 HTTP、HTTPS、FTP 等。可以在终端中用来发送和接收数据,执行各种网络操作,如下载文件、上传文件、发送 POST 请求等。
  • [技术干货] 嵌入式开发_C语言处理文件目录后缀
    一. 前言在 C 语言中,对文件名、目录名、文件后缀等数据进行处理通常需要使用字符串处理函数和技巧。常用的字符串处理包括字符串拷贝、字符串连接、字符串比较、分割字符串以及获取文件后缀等操作。(1)字符串拷贝:使用 strcpy 函数可以将一个字符串复制到另一个字符串中。(2)字符串连接:使用 strcat 函数可以将两个字符串连接起来。(3)字符串比较:使用 strcmp 函数可以比较两个字符串是否相等。(4)分割字符串:可以使用 strtok 函数将字符串按照指定分隔符分割成多个子字符串。(5)获取文件后缀:通过查找字符串中最后一个.字符的位置,可以获取文件的后缀名。这些字符串处理函数和技巧能够帮助我们在 C 语言中对文件名、目录名、文件后缀等数据进行灵活处理。下面是C语言的代码:(1)字符串拷贝:使用 strcpy 函数可以将一个字符串复制到另一个字符串中。char str1[100] = "Hello";char str2[100];strcpy(str2, str1); // 将 str1 复制到 str2(2)字符串连接:使用 strcat 函数可以将两个字符串连接起来。char str1[100] = "Hello";char str2[100] = "World";strcat(str1, str2); // 将 str2 连接到 str1 的末尾(3)字符串比较:使用 strcmp 函数可以比较两个字符串是否相等。char str1[100] = "Hello";char str2[100] = "Hello";if (strcmp(str1, str2) == 0){ // 字符串相等}(4)分割字符串:可以使用 strtok 函数将字符串按照指定分隔符分割成多个子字符串。char str[100] = "file.txt";char *token = strtok(str, ".");while (token != NULL){ printf("%s\n", token); token = strtok(NULL, ".");}(5)获取文件后缀:通过查找字符串中最后一个.字符的位置,可以获取文件的后缀名。char filename[100] = "example.txt";char *dot = strrchr(filename, '.');if (dot != NULL){ printf("File extension: %s\n", dot + 1);}二. 实现代码-纯C/C++示例代码:string path = "C:\\Users\\Administ.1.2.3rator\\Desktop\\text\\data.22.txt"; //string path = "http://cqpc:8001/Uploads/1/220512030806054762.mp4"; //1.获取不带路径的文件名 string::size_type iPos; if (strstr(path.c_str(), "\\")) { iPos = path.find_last_of('\\') + 1; } else { iPos = path.find_last_of('/') + 1; } string filename = path.substr(iPos, path.length() - iPos); cout <<"获取不带路径的文件名:"<<filename << endl;​​ //2.获取不带后缀的文件名 string name = filename.substr(0, filename.rfind(".")); cout <<"获取不带后缀的文件名:"<<name << endl;​ //3.获取后缀名 string suffix_str = filename.substr(filename.find_last_of('.') + 1); cout << "获取后缀名:"<<suffix_str << endl;​ //4. 获取基本名称 cout << "基本名称:"<<filename.substr(0, filename.find("."));返回结果:获取不带路径的文件名:data.22.txt获取不带后缀的文件名:data.22获取后缀名:txt基本名称:data三. C语言字符串处理案例1. 计算空格、大小写字母从键盘上输入一个字符串, 计算字符串里有多少个空格、小写字母、大写字母、数字。#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件int main(int argc,char **argv){ int len=0; int i; char str[100]; int cnt[5]={0}; //初始化赋值 //scanf("%s",str); //从键盘上录入字符串,字符串结尾: '\0' //gets(str); //从键盘上录入字符串 fgets(str,100,stdin); //从键盘上录入字符串 (标准输入) //空格、小写字母、大写字母、数字 其他数据 /*1. 计算字符串的长度*/ while(str[len]!='\0')len++; printf("len1=%d\n",len); printf("len2=%d\n",strlen(str)); //计算字符串长度 /*2. 处理字符串*/ for(i=0;i<len;i++) { if(str[i]==' ')cnt[0]++; else if(str[i]>='a'&&str[i]<='z')cnt[1]++; else if(str[i]>='A'&&str[i]<='Z')cnt[2]++; else if(str[i]>='0'&&str[i]<='9')cnt[3]++; else cnt[4]++; } /*3. 打印结果*/ printf("空格:%d\n",cnt[0]); printf("小写:%d\n",cnt[1]); printf("大写:%d\n",cnt[2]); printf("数字:%d\n",cnt[3]); printf("其他:%d\n",cnt[4]); return 0;}2. 字符串排序示例:#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件​int main(int argc,char **argv){ int len=0; int i,j; char tmp; char str[100]; fgets(str,100,stdin); //从键盘上录入字符串 (标准输入) /*1. 计算字符串的长度*/ len=strlen(str); //计算字符串长度 /*2. 字符串排序*/ for(i=0;i<len-1;i++) { for(j=0;j<len-1-i;j++) { if(str[j]<str[j+1]) { tmp=str[j]; str[j]=str[j+1]; str[j+1]=tmp; } } } /*3. 打印结果*/ printf("%s\n",str); //打印字符串(标准输出) puts(str); //打印字符串(标准输出) fputs(str,stdout); //打印字符串(标准输出) return 0;}3. 字符串插入字符串插入: “1234567890” 在第2个位置后面插入”ABC” 最终结果: “12ABC34567890”#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件int main(int argc,char **argv){ int i,j; int src_len; int new_len; /* 123456789 12 3456789 */ char src_str[100]="123456789"; char new_str[]="abcd"; int addr=2; //插入的位置 /*1. 计算字符串的长度*/ src_len=strlen(src_str); //"123" new_len=strlen(new_str); /*2. 字符串移动*/ for(i=src_len-1;i>addr-1;i--) { src_str[i+new_len]=src_str[i]; //向后移动 new_len } /*3. 插入新的数据*/ for(i=0;i<new_len;i++)src_str[addr+i]=new_str[i]; /*4. 打印字符串*/ src_str[src_len+new_len]='\0'; //在字符串结尾添加'\0' printf("src_str=%s\n",src_str); return 0;}4. 字符串查找字符串查找: “123456123abc123hbc” 查找字符串”123”的数量。数量是3#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件​int main(int argc,char **argv){ char src_str[100]; char find_str[10]; int src_len=0,find_len=0; int i,j; int cnt=0; /*1. 录入字符串*/ printf("输入源字符串:"); //123dufvdfv123dfljvb fgets(src_str,100,stdin); //从键盘上录入源字符串 //scanf("%s",src_str); printf("输入查找的字符串:"); //123 fgets(find_str,10,stdin); //从键盘上录入源字符串 //scanf("%s",find_str); /*2. 计算长度*/ src_len=strlen(src_str); src_str[src_len-1]='\0'; src_len-=1; //src_len=src_len-1; find_len=strlen(find_str); //"123\n" =4 find_str[find_len-1]='\0'; find_len-=1; printf("源字符串:%s,%d\n",src_str,src_len); printf("查找的字符串:%s,%d\n",find_str,find_len); /*3. 查找字符串*/ for(i=0;i<src_len-find_len+1;i++) { for(j=0;j<find_len;j++) { //只有一次不相等就退出 //123kdfvfd 123 if(src_str[i+j]!=find_str[j])break; } if(j==find_len) //条件成立表示查找成功 { cnt++; i+=find_len-1;//向后移动 } } /*4. 打印查找结果*/ printf("cnt=%d\n",cnt); return 0;}5. 字符串删除字符串删除: “1234567890” 删除”456” 最终结果: “1237890”示例:#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件​int main(int argc,char **argv){ char src_str[100]; char del_str[10]; int src_len=0,del_len=0; int i,j; int cnt=0; /*1. 录入字符串*/ printf("输入源字符串:"); //123dufvdfv123dfljvb fgets(src_str,100,stdin); //从键盘上录入源字符串 printf("输入查找的字符串:"); //123 fgets(del_str,10,stdin); //从键盘上录入源字符串 /*2. 计算长度*/ src_len=strlen(src_str); src_str[src_len-1]='\0'; src_len-=1; //src_len=src_len-1; del_len=strlen(del_str); //"123\n" =4 del_str[del_len-1]='\0'; del_len-=1; printf("源字符串:%s,%d\n",src_str,src_len); printf("删除字符串:%s,%d\n",del_str,del_len);​ /*3. 查找*/ for(i=0;i<src_len-del_len+1;i++) { for(j=0;j<del_len;j++) { if(src_str[i+j]!=del_str[j])break; } if(j==del_len) { cnt++; /*4.删除*/ for(j=i;j<src_len-del_len;j++) { src_str[j]=src_str[j+del_len]; } src_len-=del_len; i-=1; //继续在当前位置查找 } } src_str[src_len]='\0'; printf("src_str=%s\n",src_str); printf("cnt=%d\n",cnt); return 0;}6. 字符串替换字符串”1234567890”将456替换为”888” 最终: “1238887890”需要考虑3种情况7. 字符串转整数。从键盘上输入一个字符串”12345”, 得到整数: 12345;#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件int string_to_int(char str[]);int main(int argc,char **argv){ int data; char str[]="125abcd"; data=string_to_int(str); printf("data=%d\n",data); return 0;}/*函数功能: 字符串转为整数字符转为整数: -48 或者 -'0'1234*/int string_to_int(char str[]){ int value=0; //存放转换之后的结果 int i=0; while((str[i]!='\0')&&(str[i]>='0'&&str[i]<='9')) { value*=10; value+=str[i]-'0'; i++; } return value;}8. 整数转字符串整数转字符串。输入一个整数1234,得到字符串: "1234"1234%10=4 1234/10=123 123%10=3 123/10=12示例:#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件void int_to_string(char str[],int data);int main(int argc,char **argv){ char str[100]; int_to_string(str,12345); printf("str=%s\n",str); return 0;}/*函数功能: 整数转为字符串函数参数: char str[] //存放转换之后的整数(字符串) int data //待转换的整数*/void int_to_string(char str[],int data){ int i=0,j; char tmp; /*1. 将整数转为字符串*/ while(data) { str[i]=data%10+'0'; data/=10; i++; } str[i]='\0'; /*2. 交换顺序*/ for(j=0;j<i/2;j++) { tmp=str[j]; str[j]=str[i-j-1]; str[i-j-1]=tmp; }}9. 浮点数转字符串浮点数转字符串。输入一个浮点数123.456 得到字符串"123.456"示例:#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件void float_to_string(char str[],float data);int main(int argc,char **argv){ char str[100]; float_to_string(str,12345.123); printf("str=%s\n",str); return 0;}/*函数功能: 浮点数转为字符串函数参数: char str[] //存放转换之后的 浮点数(字符串) int data //待转换的 浮点数*/void float_to_string(char str[],float data){ int i=0,j; char tmp; int addr; int int_data=data; //得到整数 12345 int float_data=(data-int_data)*1000000;// 0.123456 *1000000 =123456 /*1. 将整数部分转为字符串*/ while(int_data) { str[i]=int_data%10+'0'; int_data/=10; i++; } str[i]='.'; //添加小数点 /*2. 交换顺序: 整数*/ for(j=0;j<i/2;j++) { tmp=str[j]; str[j]=str[i-j-1]; str[i-j-1]=tmp; } /*3. 将浮点数部分转为字符串*/ i++; //跨过小数点 addr=i; while(float_data) { str[i]=float_data%10+'0'; float_data/=10; i++; } str[i]='\0'; /*4. 交换顺序: 小数部分*/ for(j=0;j<3;j++) { tmp=str[addr+j]; str[addr+j]=str[i-j-1]; str[i-j-1]=tmp; }}10.字符串转浮点数字符串转浮点数。输入一个字符串: "123.456" 得到浮点数类型: 123.456#include <stdio.h> //标准输入输出#include <string.h> //字符串处理头文件float string_to_float(char str[]);int main(int argc,char **argv){ float data; char str[]="123.456"; data=string_to_float(str); printf("data=%f\n",data); return 0;}/*函数功能: 字符串转为浮点数字符转为整数: -48 或者 -'0'*/float string_to_float(char str[]){ int int_value=0; //存放转换之后的结果 int float_value=0; int i=0; float data; int cnt=0; //记录小数部分的数量 int tmp=1; /*1. 整数部分*/ while((str[i]!='\0')&&(str[i]>='0'&&str[i]<='9')) { int_value*=10; int_value+=str[i]-'0'; i++; } /*2. 浮点数部分*/ i++; //跨过小数点 while((str[i]!='\0')&&(str[i]>='0'&&str[i]<='9')) { float_value*=10; float_value+=str[i]-'0'; i++; cnt++; } for(i=0;i<cnt;i++)tmp*=10; data=int_value; //整数部分 data+=float_value/(tmp*1.0); return data;}
  • [技术干货] 嵌入式开发_C语言字符串与指针的练习
    在这篇文章中,将深入探讨C语言中字符串和指针相关的一系列重要知识点。文章通过一系列的练习题和实例,详细讲解了浮点数与字符串之间的相互转换、字符串的拷贝和比较、指针用于交换变量的值、指针的优先级规则,以及数据类型的强制转换和内存拷贝函数的使用。文章首先介绍了字符串与指针的基本概念,然后逐步引导读者学习如何操作字符串和指针来完成各种常见的编程任务。每个知识点都配以清晰的代码示例和详细的注释,帮助读者理解并掌握这些关键的C语言技能。1. 浮点数转字符串将字符串转整数、整数转字符串、浮点数转字符串、字符串转浮点数 封装为函数。 浮点数转字符串:#include <stdio.h>#include <string.h> //字符串处理void float_to_string(char str[],float data);​​int main(void){ char buff[50]; float data=1234.5; float_to_string(buff,data);​ printf("data=%f,buff=%s\n",data,buff); return 0;}​​/*函数功能: 浮点数转字符串*/void float_to_string(char str[],float data){ int buff[50]; int int_data1=data; //提取整数部分 int int_data2=(data-int_data1)*1000000; //提取小数部分 int i=0,j=0,k=0;​ //转整数部分 while(int_data1) { buff[i]=int_data1%10+'0'; int_data1=int_data1/10; i++; }​ //倒序 for(j=0;j<i;j++) { str[j]=buff[i-j-1]; }​ str[j]='.'; //添加小数点 j++;​ i=0; //转小数部分 while(int_data2) { buff[i]=int_data2%10+'0'; int_data2=int_data2/10; i++; }​ //倒序 for(k=0; k<i; k++) { str[j+k]=buff[i-k-1]; } str[j+k]='\0'; //添加结尾符号​ //优化后面0 i=j+k; //总长度 for(j=i-1; j>=0; j--) { if(str[j]=='0')str[j]='\0'; //添加结束符 else break; }}2. 封装字符串拼接函数封装字符串拼接函数: 函数功能实现将a和b字符串拼接在一起。 比如: char a[100]=”123”; char b[]=”456”; 调用函数之后: a[]=”123456”#include <stdio.h>#include <string.h> //字符串处理void my_strcat(char str1[],char str2[]);​int main(void){ char str1[50]="12345"; char str2[]="67890"; //strcat(str1,str2); //字符串拼接 my_strcat(str1,str2); //字符串拼接 printf("%s\n",str1); return 0;}​/*函数功能:字符串拼接*/void my_strcat(char str1[],char str2[]){ int i=0,len=0; while(str1[len]!='\0') { len++; }​ while(str2[i]!='\0') { str1[len+i]=str2[i]; i++; } str1[len+i]='\0'; //结尾补上'\0'}3. 封装字符串的拷贝函数封装字符串的拷贝函数: 将a字符串拷贝到b字符串。 示例:#include <stdio.h>#include <string.h> //字符串处理void my_strcpy(char str1[],char str2[]);​int main(void){ char str1[50]="12345"; char str2[]="67890"; //strcpy(str1,str2); //字符串拷贝 my_strcpy(str1,str2); printf("%s\n",str1); return 0;}​/*函数功能:字符串拼接*/void my_strcpy(char str1[],char str2[]){ int i=0,len=0; while(str2[i]!='\0') { str1[i]=str2[i]; //依次赋值 i++; } str1[i]='\0'; //补上结尾符号}4. 封装字符串的比较函数封装字符串的比较函数: 比较a字符串和b字符串是否相等。 通过返回值进行区分。 示例:​#include <stdio.h>#include <string.h> //字符串处理int my_strcmp(char str1[],char str2[]);​int main(void){ char str1[50]="中国"; char str2[]="中国"; int a; //a=strcmp(str1,str2); a=my_strcmp(str1,str2); if(a==0)printf("字符串相等!\n"); else printf("字符串不相等!\n"); return 0;}​/*函数功能:字符串比较*/int my_strcmp(char str1[],char str2[]){ int i=0; //只要有一个数组没结束就继续 while(str1[i]!='\0'||str2[i]!='\0') { if(str1[i]!=str2[i])break; i++; } if(str1[i]=='\0' && str2[i]=='\0')return 0; //相等 else return -1; //不相等}​5. 指针特性指针: 是C语言的灵魂。 指针: 可访问计算机的底层----->硬件。 定义指针的语法: <数据类型> *<变量名称>; int *p; 指针的特性:(1)指针本身没有空间。int *p; //定义一个指针变量(2)指针本身就是地址(专门保存地址)。int *p; p=? ?必须是地址类型。​int *p; //定义一个指针变量 int data=123; //p=123; //错误赋值方式 p=&data; //正确的赋值方式​(3)取出指针指向地址的数据 ​ int *p; //定义一个指针变量 int data=123; p=&data; //正确的赋值方式 printf("%d\n",*p); //取出p指针指向空间的值 //*p=888; (4)指针类型的变量都是占4个字节#include <stdio.h>int main(){ int *p1; //定义一个整型指针变量 char *p2; float *p3; double *p4; printf("int=%d\n",sizeof(p1)); printf("char=%d\n",sizeof(p2)); printf("float=%d\n",sizeof(p3)); printf("double=%d\n",sizeof(p4)); return 0;}(5)指针支持自增和自减 ++ --#include <stdio.h>int main(){ char *p; char str[]="1234567890"; p=str; //将数组地址赋值给指针p​ //指针可以直接使用数组下标(指针可以当数组名使用) printf("%c\n",p[0]);//1 printf("%c\n",p[1]);//2​ //通过指针访问数组的成员 printf("%c\n",*p); //1 p++; //指针自增 printf("%c\n",*p); //2 p--; printf("%c\n",*p); //1 return 0;}(6) 不同类型的指针自增和自减的字节数​//指针类型自增和自减的字节数与本身数据类型有关。​#include <stdio.h>int main(){ int data1[10]; char data2[10];​ /*1. 给指针赋值合法的空间*/ int *p1=data1; char *p2=data2;​ printf("0x%X\n",p1); p1++; printf("0x%X\n",p1);​ printf("0x%X\n",p2); p2++; printf("0x%X\n",p2); return 0;}6. 通过指针交换两个变量的值#include <stdio.h>void func(int *a,int *b);​int main(){ int a=100,b=200; func(&a,&b); printf("a=%d,b=%d\n",a,b); //200,100 return 0;}​//通过指针交换两个变量的值void func(int *a,int *b){ int c; c=*a; //取出100 *a=*b; //取出200赋值给a *b=c;}​7. 指针自增优先级​#include <stdio.h>​int main(){ char buff[]="12345"; char *p=buff; printf("%c\n",*p); //1 printf("%c\n",*p++); //1 printf("%c\n",*p++); //2 printf("%c\n",*p); //3 return 0;}8. 计算字符串的长度#include <stdio.h>int my_strlen(const char *str);​int main(){ printf("%d\n",my_strlen("1234567")); return 0;}​//加const为了防止不小心修改了数据int my_strlen(const char *str){ //*str='1'; //错误,不能赋值 char *p=str; //保存地址 while(*p!='\0')p++; return p-str; //得到字符串长度}9. 数据类型的强制转换数据类型的强制转换 (欺骗编译器)char *p1=(char*)src;char *p2=(char*)new;10. 编写一个内存拷贝函数功能: 可以将任何数据的类型进行相互赋值。int a,b=100; float a,b=123.456; ………..#include <stdio.h>void my_memcpy(void *new,void *src,int len);int main(){ //int data1=123,data2=456; //my_memcpy(&data1,&data2,4); //printf("data1=%d\n",data1); int data1[100]; int data2[50]={12,34,5,6,78}; my_memcpy(data1,data2,sizeof(data2)); printf("%d\n",data1[0]); printf("%d\n",data1[1]); return 0;}/*内存拷贝将src的数据拷贝到new,拷贝len*/void my_memcpy(void *new,void *src,int len){ char *p1=(char*)src; char *p2=(char*)new; int i; //for(i=0; i<len; i++)*p2++=*p1++; for(i=0; i<len; i++) { *p2=*p1; p1++; p2++; }}
  • [技术干货] 嵌入式开发_C语言标准时间与秒单位的转换
    前言:在这篇博客文章中,将深入探讨嵌入式单片机中标准时间与秒单位之间的转换方法。介绍了标准时间和秒单位的概念,以及它们在嵌入式单片机中的应用场景。接着,通过两个具体的例子来展示如何将RTC时钟的时间转换为标准时间进行显示。这两个例子都包含了详细的代码注释和思路解析,帮助读者更好地理解和掌握这种转换方法。文章知识点:标准时间与秒单位概念介绍:解释标准时间和秒单位的定义及其在嵌入式单片机中的应用。RTC时钟时间读取:演示如何从RTC时钟中读取秒单位时间,并将其转换为标准时间进行显示。代码注释和思路解析:提供详细注释和思路解析,帮助读者理解代码的实现过程。示例代码演示:给出两个具体的示例代码,展示如何在不同场景下进行标准时间与秒单位的转换。1. 时间转换-秒与标准时间的转换1.时间转换(秒与标准时间的转换) (1)函数1: 将秒单位时间转为标准时间。 --RTC实时时钟--->秒为单位--->每秒钟cnt++; 237562867493 -----xxxx年xx月xx日xx时xx分xx秒 星期x。示例代码: (模拟电子钟)​#include <stdio.h>#include <string.h> //字符串处理#include <Windows.h> ​//时间单位int year,mon,mdeay,hour,min,t_sec;​//闰年的月份int mon_r[12]={31,29,31,30,31,30,31,31,30,31,30,31};​//平年的月份int mon_p[12]={31,28,31,30,31,30,31,31,30,31,30,31};​unsigned int TimeToSec(int year,int mon,int mdeay,int hour,int min,int sec);void SecToTime(unsigned int sec);​​int main(void){ //将标准时间转为秒单位时间 (设置时间) unsigned int time=TimeToSec(2018,6,6,16,40,20); while(1) { time++; Sleep(1000); //睡眠1秒时间。 单位是ms SecToTime(time); printf("%d-%d-%d %d:%d:%d\n",year,mon,mdeay,hour,min,t_sec); } return 0;}​/*函数功能: 判断平年和闰年函数返回值: 1表示闰年 0表示平年*/int GetYearStat(int year){ if((year%4==0&&year%100!=0)||year%400==0)return 1; //闰年 return 0; //平年}​/*将秒单位时间转为标准时间时间基准点: 1970年1月1日0时0分0秒思想: 减法*/void SecToTime(unsigned int sec){ int i; year=1970; //基准年份 /*1. 计算过了多少年*/ while(sec>=365*24*60*60) //秒还够一年 { if(GetYearStat(year)) //闰年 { if(sec>366*24*60*60) { sec-=366*24*60*60; //减去一年 year++; //年份累加 } else { break; } } else { sec-=365*24*60*60; //减去一年 year++; //年份累加 } }​ /*2. 计算过了多少月*/ mon=1; if(GetYearStat(year)) //闰年 { for(i=0; i<12; i++) { if(sec>=mon_r[i]*24*60*60) //够一个月 { sec-=mon_r[i]*24*60*60; //减去一个月 mon++;//增加一个月 } else break; } } else { for(i=0; i<12; i++) { if(sec>=mon_p[i]*24*60*60) //够一个月 { sec-=mon_p[i]*24*60*60; //减去一个月 mon++;//增加一个月 } else break; } } /*3. 计算过了多少天*/ mdeay=1; while(sec>=24*60*60) //判断是否够一天 { sec-=24*60*60; mdeay++; }​ /*4. 过了多少小时*/ hour=0; while(sec>=60*60) { sec-=60*60; hour++; } /*5. 过了多少分钟*/ min=0; while(sec>=60) { sec-=60; min++; } /*6. 过了多少秒*/ t_sec=sec; }​/*将标准时间转为秒单位时间思路: 全程加法时间基准点: 1970年1月1日0时0分0秒返回值: 得到的秒单位时间*/unsigned int TimeToSec(int year,int mon,int mdeay,int hour,int min,int sec){ int i; int sec_cnt=0; //记录秒单位的时间 /*1. 转换年*/ for(i=1970; i<year; i++) { if(GetYearStat(i)) //闰年 { sec_cnt+=366*24*60*60; } else { sec_cnt+=365*24*60*60; } }​ /*2. 转换月*/ for(i=0; i<mon-1; i++) { if(GetYearStat(year)) //闰年 { sec_cnt+=mon_r[i]*24*60*60; } else { sec_cnt+=mon_p[i]*24*60*60; } }​ /*3. 转换天数*/ sec_cnt+=(mdeay-1)*24*60*60; /*4. 转换小时*/ sec_cnt+=hour*60*60;​ /*5. 转换分钟*/ sec_cnt+=min*60;​ /*6. 转换秒*/ sec_cnt+=sec; return sec_cnt; //返回秒单位时间}​​完整的代码(添加星期):#include <stdio.h>#include <string.h> //字符串处理#include <Windows.h> ​//时间单位int year,mon,mdeay,hour,min,t_sec,week;​//闰年的月份int mon_r[12]={31,29,31,30,31,30,31,31,30,31,30,31};​//平年的月份int mon_p[12]={31,28,31,30,31,30,31,31,30,31,30,31};​unsigned int TimeToSec(int year,int mon,int mdeay,int hour,int min,int sec);void SecToTime(unsigned int sec);int GetWeek(unsigned int sec);​int main(void){ //将标准时间转为秒单位时间 (设置时间) unsigned int time=TimeToSec(2018,9,1,16,40,20); while(1) { time++; Sleep(1000); //睡眠1秒时间。 单位是ms SecToTime(time); week=GetWeek(time); //获取星期 printf("%d-%d-%d %d:%d:%d 星期%d\n",year,mon,mdeay,hour,min,t_sec,week); } return 0;}​​/*函数功能: 判断平年和闰年函数返回值: 1表示闰年 0表示平年*/int GetYearStat(int year){ if((year%4==0&&year%100!=0)||year%400==0)return 1; //闰年 return 0; //平年}​/*将秒单位时间转为标准时间时间基准点: 1970年1月1日0时0分0秒思想: 减法*/void SecToTime(unsigned int sec){ int i; year=1970; //基准年份 /*1. 计算过了多少年*/ while(sec>=365*24*60*60) //秒还够一年 { if(GetYearStat(year)) //闰年 { if(sec>366*24*60*60) { sec-=366*24*60*60; //减去一年 year++; //年份累加 } else { break; } } else { sec-=365*24*60*60; //减去一年 year++; //年份累加 } }​ /*2. 计算过了多少月*/ mon=1; if(GetYearStat(year)) //闰年 { for(i=0; i<12; i++) { if(sec>=mon_r[i]*24*60*60) //够一个月 { sec-=mon_r[i]*24*60*60; //减去一个月 mon++;//增加一个月 } else break; } } else { for(i=0; i<12; i++) { if(sec>=mon_p[i]*24*60*60) //够一个月 { sec-=mon_p[i]*24*60*60; //减去一个月 mon++;//增加一个月 } else break; } } /*3. 计算过了多少天*/ mdeay=1; while(sec>=24*60*60) //判断是否够一天 { sec-=24*60*60; mdeay++; }​ /*4. 过了多少小时*/ hour=0; while(sec>=60*60) { sec-=60*60; hour++; } /*5. 过了多少分钟*/ min=0; while(sec>=60) { sec-=60; min++; } /*6. 过了多少秒*/ t_sec=sec; }2. 时间转换-标准时间转秒(2)函数2: 将标准时间转为秒单位的时间。 2018年6月1日19点41分23秒----------xxxxxxx秒 闰年366,平年365。 区分: 每年二月份相差一天. 标准时间基准点: 1970年1月1日0时0分0秒。/*将标准时间转为秒单位时间思路: 全程加法时间基准点: 1970年1月1日0时0分0秒返回值: 得到的秒单位时间*/unsigned int TimeToSec(int year,int mon,int mdeay,int hour,int min,int sec){ int i; int sec_cnt=0; //记录秒单位的时间 /*1. 转换年*/ for(i=1970; i<year; i++) { if(GetYearStat(i)) //闰年 { sec_cnt+=366*24*60*60; } else { sec_cnt+=365*24*60*60; } }​ /*2. 转换月*/ for(i=0; i<mon-1; i++) { if(GetYearStat(year)) //闰年 { sec_cnt+=mon_r[i]*24*60*60; } else { sec_cnt+=mon_p[i]*24*60*60; } }​ /*3. 转换天数*/ sec_cnt+=(mdeay-1)*24*60*60; /*4. 转换小时*/ sec_cnt+=hour*60*60;​ /*5. 转换分钟*/ sec_cnt+=min*60;​ /*6. 转换秒*/ sec_cnt+=sec; return sec_cnt;}​/*函数功能: 根据秒单位时间获取星期函数形参: 秒单位时间返回值 :星期(1~7)*/int GetWeek(unsigned int sec){ int mdeay=sec/60/60/24; //将秒单位时间转为天数 switch(mdeay%7) { case 0: //星期4 return 4; break; case 1://星期5 return 5; break; case 2: //星期6 return 6; break; case 3://星期天 return 7; break; case 4://星期1 return 1; break; case 5://星期2 return 2; break; case 6: //星期3 return 3; break; default: break; }}
  • [技术干货] STM32控制max30102读取血氧心率数据(keil5工程)
    一、前言MAX30102是一款由Maxim Integrated推出的低功耗、高精度的心率和血氧饱和度检测传感器模块,适用于可穿戴设备如智能手环、智能手表等健康管理类电子产品。该传感器主要特性如下:(1)光学测量:MAX30102内置了两个LED光源(红光和红外光),以及一个光电检测器,通过光电容积脉搏波描记法(PPG)来实现心率和血氧饱和度的无创检测。(2)低功耗:在典型的工作模式下,其功耗非常低,有助于延长电池供电设备的使用寿命。(3)集成度高:内部集成了AFE(模拟前端)、LED驱动器、环境光抑制功能以及I²C数字接口,方便与微控制器连接通信。(4)多档位配置:支持多个LED电流输出级别和采样速率选择,可以根据实际应用需求进行灵活配置。(5)高精度:通过先进的信号处理算法,可以有效降低噪声干扰,提高测量数据的准确性。(6)小尺寸封装:采用紧凑型封装设计,便于在空间受限的产品中使用。MAX30102是一款高性能的生物医学传感器,能够帮助开发者在各种便携式和穿戴式设备上实现对人体生理参数的有效监测。二、IIC协议MAX30102 是一款由 Maxim Integrated(现为 Analog Devices 公司的一部分)制造的生物识别传感器,它采用 I2C(Inter-Integrated Circuit)协议进行通信。I2C 协议是一种常见的串行接口标准,特别适用于在嵌入式系统中连接微控制器和其他低速周边设备,如传感器、EEPROM、RTC(实时时钟)等。I2C 协议详解:(1)架构与线路:SDA (Serial Data Line): 串行数据线,用于传输数据。SCL (Serial Clock Line): 串行时钟线,由主设备控制,决定数据传输速率和每个位的时间间隔。多主从架构: 支持一个主设备和多个从设备同时连接到总线上,主设备负责发起通信并控制数据传输方向。(2)信号特性:开始条件(Start Condition): 当 SDA 线在 SCL 高电平时由高电平变为低电平,表示一次传输的开始。停止条件(Stop Condition): 反之,在 SCL 高电平时,SDA 线由低电平变为高电平,标志一次传输结束。地址字节: 每次通信开始时,主设备会通过发送包含7位从设备地址(加上一位读写位)的数据包来寻址目标从设备,例如 MAX30102。(3)数据传输:读/写操作: 地址字节的最低位决定了接下来是读操作(R/W=1)还是写操作(R/W=0)。应答(ACK/NACK): 每个被传送的数据字节后,接收方需拉低 SDA 行线以发出一个确认(ACK)信号。若不响应,则为主动非应答(NACK),可能用于指示传输结束或错误。数据位传输: 数据以高位先出(MSB-first)的方式逐位传输。(4)波特率:I2C 协议允许不同的传输速率,称为标准模式(100kHz)、快速模式(400kHz)、快速模式+(1MHz)以及其他更高性能的模式。对于MAX30102这样的传感器来说,通过I2C接口可以读取其内部寄存器数据,如配置寄存器、状态寄存器以及测量数据缓冲区等,从而实现对传感器的控制和数据采集。开发人员通常使用微控制器提供的硬件I2C模块或者软件模拟的I2C协议来与MAX30102进行通信。模拟I2C协议通常涉及到对硬件时序的精确控制,以下是一个基于软件模拟的、简化版的C语言代码示例,用于演示基本原理。#include <stdio.h>#include <unistd.h>​// 假设sda和scl是连接到GPIO的文件描述符#define SDA 3#define SCL 4​// 设置GPIO为输出模式void gpio_setup_output(int pin) { // 这部分代码依赖于具体的GPIO库或系统调用,这里仅为示意}​// 设置GPIO为输入模式并读取电平int gpio_read_input(int pin) { // 这部分代码依赖于具体的GPIO库或系统调用,这里仅为示意 return value; // 返回0或1}​// 模拟SDA线上的数据传输void sda_write(int data) { gpio_setup_output(SDA); if (data) // 将SDA置高 ; else // 将SDA置低 ;}​// 模拟SCL线上的时钟脉冲void scl_pulse(void) { gpio_setup_output(SCL); // 将SCL拉低 usleep(1); // 延迟以模拟时钟周期的一部分 // 将SCL拉高 usleep(1); // 延迟以完成时钟周期}​// 发送一个字节数据void i2c_send_byte(unsigned char byte) { for (int i = 7; i >= 0; --i) { sda_write(byte & (1 << i)); scl_pulse(); } // 等待ACK gpio_setup_output(SDA); gpio_write(SDA, 1); // 主机释放SDA,从机应答 scl_pulse(); if (gpio_read_input(SDA)) { printf("No ACK received\n"); // 处理无应答的情况... }}​// 接收一个字节数据unsigned char i2c_receive_byte(int ack) { unsigned char byte = 0; gpio_setup_input(SDA); for (int i = 7; i >= 0; --i) { byte <<= 1; scl_pulse(); byte |= gpio_read_input(SDA); } gpio_setup_output(SDA); // 发送ACK/NAK sda_write(!ack); scl_pulse(); return byte;}​// I2C开始条件void i2c_start_condition(void) { sda_write(1); scl_write(1); sda_write(0);}​// I2C停止条件void i2c_stop_condition(void) { sda_write(0); scl_write(1); sda_write(1);}​// 向设备发送地址和数据void i2c_send_address_and_data(unsigned char address, unsigned char data, int is_write) { i2c_start_condition(); i2c_send_byte((address << 1) | (is_write ? 0 : 1)); // 地址 + R/W位 i2c_send_byte(data); // 数据 i2c_stop_condition();}​// 从设备接收数据unsigned char i2c_receive_data(unsigned char address) { i2c_start_condition(); i2c_send_byte((address << 1) | 1); // 地址 + R/W=1(读操作) unsigned char data = i2c_receive_byte(0); // 接收数据并发送ACK i2c_stop_condition(); return data;}三、项目代码下面贴出了STM32工程里完整的max30102的代码,因为是才有寄存器编程。 所有兼容所有的工程,不管你是STM32标准库工程还是STM32HAL库工程,只要把下面的.c文件和.h文件加载到你的STM32工程里。将max30102接好线,按照头文件里说明调用mainx30102函数完成初始化就可以。3.1 max30102.c#include "max30102.h"#include "delay.h"​/*MAX30102心率传感器:SCL<->PB6SDA<->PB7IM<->PB9*/​​//初始化IICvoid IIC_Init(void){ RCC->APB2ENR|=1<<3; GPIOB->CRL&=0x00FFFFFF; GPIOB->CRL|=0x33000000; GPIOB->CRH&=0xFFFFFF0F; GPIOB->CRH|=0x00000080; IIC_SCL=1; IIC_SDA=1;​}​​//产生IIC起始信号void IIC_Start(void){ SDA_OUT(); //sda线输出 IIC_SDA=1; IIC_SCL=1; delay_us(4); IIC_SDA=0;//START:when CLK is high,DATA change form high to low delay_us(4); IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 } //产生IIC停止信号void IIC_Stop(void){ SDA_OUT();//sda线输出 IIC_SCL=0; IIC_SDA=0;//STOP:when CLK is high DATA change form low to high delay_us(4); IIC_SCL=1; IIC_SDA=1;//发送I2C总线结束信号 delay_us(4); }//等待应答信号到来//返回值:1,接收应答失败// 0,接收应答成功u8 IIC_Wait_Ack(void){ u8 ucErrTime=0; SDA_IN(); //SDA设置为输入 IIC_SDA=1;delay_us(1); IIC_SCL=1;delay_us(1); while(READ_SDA) { ucErrTime++; if(ucErrTime>250) { IIC_Stop(); return 1; } } IIC_SCL=0;//时钟输出0 return 0; } //产生ACK应答void IIC_Ack(void){ IIC_SCL=0; SDA_OUT(); IIC_SDA=0; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0;}//不产生ACK应答 void IIC_NAck(void){ IIC_SCL=0; SDA_OUT(); IIC_SDA=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0;} //IIC发送一个字节//返回从机有无应答//1,有应答//0,无应答 void IIC_Send_Byte(u8 txd){ u8 t; SDA_OUT(); IIC_SCL=0;//拉低时钟开始数据传输 for(t=0;t<8;t++) { IIC_SDA=(txd&0x80)>>7; txd<<=1; delay_us(2); //对TEA5767这三个延时都是必须的 IIC_SCL=1; delay_us(2); IIC_SCL=0; delay_us(2); } } //读1个字节,ack=1时,发送ACK,ack=0,发送nACK u8 IIC_Read_Byte(unsigned char ack){ unsigned char i,receive=0; SDA_IN();//SDA设置为输入 for(i=0;i<8;i++ ) { IIC_SCL=0; delay_us(2); IIC_SCL=1; receive<<=1; if(READ_SDA)receive++; delay_us(1); } if (!ack) IIC_NAck();//发送nACK else IIC_Ack(); //发送ACK return receive;}​​void IIC_WriteBytes(u8 WriteAddr,u8* data,u8 dataLength){ u8 i; IIC_Start(); ​ IIC_Send_Byte(WriteAddr); //发送写命令 IIC_Wait_Ack(); for(i=0;i<dataLength;i++) { IIC_Send_Byte(data[i]); IIC_Wait_Ack(); } IIC_Stop();//产生一个停止条件 delay_ms(10); }​void IIC_ReadBytes(u8 deviceAddr, u8 writeAddr,u8* data,u8 dataLength){ u8 i; IIC_Start(); ​ IIC_Send_Byte(deviceAddr); //发送写命令 IIC_Wait_Ack(); IIC_Send_Byte(writeAddr); IIC_Wait_Ack(); IIC_Send_Byte(deviceAddr|0X01);//进入接收模式 IIC_Wait_Ack(); for(i=0;i<dataLength-1;i++) { data[i] = IIC_Read_Byte(1); } data[dataLength-1] = IIC_Read_Byte(0); IIC_Stop();//产生一个停止条件 delay_ms(10); }​void IIC_Read_One_Byte(u8 daddr,u8 addr,u8* data){ IIC_Start(); IIC_Send_Byte(daddr); //发送写命令 IIC_Wait_Ack(); IIC_Send_Byte(addr);//发送地址 IIC_Wait_Ack(); IIC_Start(); IIC_Send_Byte(daddr|0X01);//进入接收模式 IIC_Wait_Ack(); *data = IIC_Read_Byte(0); IIC_Stop();//产生一个停止条件 }​void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data){ IIC_Start(); IIC_Send_Byte(daddr); //发送写命令 IIC_Wait_Ack(); IIC_Send_Byte(addr);//发送地址 IIC_Wait_Ack(); IIC_Send_Byte(data); //发送字节 IIC_Wait_Ack(); IIC_Stop();//产生一个停止条件 delay_ms(10); }​​​​​​​​​​ uint32_t aun_ir_buffer[500]; //IR LED sensor dataint32_t n_ir_buffer_length; //data lengthuint32_t aun_red_buffer[500]; //Red LED sensor dataint32_t n_sp02; //SPO2 valueint8_t ch_spo2_valid; //indicator to show if the SP02 calculation is validint32_t n_heart_rate; //heart rate valueint8_t ch_hr_valid; //indicator to show if the heart rate calculation is validuint8_t uch_dummy;​​//variables to calculate the on-board LED brightness that reflects the heartbeatsuint32_t un_min, un_max, un_prev_data; int i;int32_t n_brightness;float f_temp;u8 temp_num=0;u8 temp[6];u8 str[100];u8 dis_hr=0,dis_spo2=0; #define MAX_BRIGHTNESS 255​​​u8 max30102_Bus_Write(u8 Register_Address, u8 Word_Data){​ /* 采用串行EEPROM随即读取指令序列,连续读取若干字节 */​ /* 第1步:发起I2C总线启动信号 */ IIC_Start();​ /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ IIC_Send_Byte(max30102_WR_address | I2C_WR); /* 此处是写指令 */​ /* 第3步:发送ACK */ if (IIC_Wait_Ack() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ }​ /* 第4步:发送字节地址 */ IIC_Send_Byte(Register_Address); if (IIC_Wait_Ack() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } /* 第5步:开始写入数据 */ IIC_Send_Byte(Word_Data);​ /* 第6步:发送ACK */ if (IIC_Wait_Ack() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ }​ /* 发送I2C总线停止信号 */ IIC_Stop(); return 1; /* 执行成功 */​cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */ /* 发送I2C总线停止信号 */ IIC_Stop(); return 0;}​​​u8 max30102_Bus_Read(u8 Register_Address){ u8 data;​​ /* 第1步:发起I2C总线启动信号 */ IIC_Start();​ /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ IIC_Send_Byte(max30102_WR_address | I2C_WR); /* 此处是写指令 */​ /* 第3步:发送ACK */ if (IIC_Wait_Ack() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ }​ /* 第4步:发送字节地址, */ IIC_Send_Byte((uint8_t)Register_Address); if (IIC_Wait_Ack() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } ​ /* 第6步:重新启动I2C总线。下面开始读取数据 */ IIC_Start();​ /* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ IIC_Send_Byte(max30102_WR_address | I2C_RD); /* 此处是读指令 */​ /* 第8步:发送ACK */ if (IIC_Wait_Ack() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ }​ /* 第9步:读取数据 */ { data = IIC_Read_Byte(0); /* 读1个字节 */​ IIC_NAck(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */ } /* 发送I2C总线停止信号 */ IIC_Stop(); return data; /* 执行成功 返回data值 */​cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */ /* 发送I2C总线停止信号 */ IIC_Stop(); return 0;}​​void max30102_FIFO_ReadWords(u8 Register_Address,u16 Word_Data[][2],u8 count){ u8 i=0; u8 no = count; u8 data1, data2; /* 第1步:发起I2C总线启动信号 */ IIC_Start();​ /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ IIC_Send_Byte(max30102_WR_address | I2C_WR); /* 此处是写指令 */​ /* 第3步:发送ACK */ if (IIC_Wait_Ack() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ }​ /* 第4步:发送字节地址, */ IIC_Send_Byte((uint8_t)Register_Address); if (IIC_Wait_Ack() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } ​ /* 第6步:重新启动I2C总线。下面开始读取数据 */ IIC_Start();​ /* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ IIC_Send_Byte(max30102_WR_address | I2C_RD); /* 此处是读指令 */​ /* 第8步:发送ACK */ if (IIC_Wait_Ack() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ }​ /* 第9步:读取数据 */ while (no) { data1 = IIC_Read_Byte(0); IIC_Ack(); data2 = IIC_Read_Byte(0); IIC_Ack(); Word_Data[i][0] = (((u16)data1 << 8) | data2); //​ data1 = IIC_Read_Byte(0); IIC_Ack(); data2 = IIC_Read_Byte(0); if(1==no) IIC_NAck(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */ else IIC_Ack(); Word_Data[i][1] = (((u16)data1 << 8) | data2); ​ no--; i++; } /* 发送I2C总线停止信号 */ IIC_Stop();​cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */ /* 发送I2C总线停止信号 */ IIC_Stop();}​void max30102_FIFO_ReadBytes(u8 Register_Address,u8* Data){ max30102_Bus_Read(REG_INTR_STATUS_1); max30102_Bus_Read(REG_INTR_STATUS_2); /* 第1步:发起I2C总线启动信号 */ IIC_Start();​ /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ IIC_Send_Byte(max30102_WR_address | I2C_WR); /* 此处是写指令 */​ /* 第3步:发送ACK */ if (IIC_Wait_Ack() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ }​ /* 第4步:发送字节地址, */ IIC_Send_Byte((uint8_t)Register_Address); if (IIC_Wait_Ack() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ } ​ /* 第6步:重新启动I2C总线。下面开始读取数据 */ IIC_Start();​ /* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ IIC_Send_Byte(max30102_WR_address | I2C_RD); /* 此处是读指令 */​ /* 第8步:发送ACK */ if (IIC_Wait_Ack() != 0) { goto cmd_fail; /* EEPROM器件无应答 */ }​ /* 第9步:读取数据 */ Data[0] = IIC_Read_Byte(1); Data[1] = IIC_Read_Byte(1); Data[2] = IIC_Read_Byte(1); Data[3] = IIC_Read_Byte(1); Data[4] = IIC_Read_Byte(1); Data[5] = IIC_Read_Byte(0); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */ /* 发送I2C总线停止信号 */ IIC_Stop();​cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */ /* 发送I2C总线停止信号 */ IIC_Stop();​// u8 i;// u8 fifo_wr_ptr;// u8 firo_rd_ptr;// u8 number_tp_read;// //Get the FIFO_WR_PTR// fifo_wr_ptr = max30102_Bus_Read(REG_FIFO_WR_PTR);// //Get the FIFO_RD_PTR// firo_rd_ptr = max30102_Bus_Read(REG_FIFO_RD_PTR);// // number_tp_read = fifo_wr_ptr - firo_rd_ptr;// // //for(i=0;i<number_tp_read;i++){// if(number_tp_read>0){// IIC_ReadBytes(max30102_WR_address,REG_FIFO_DATA,Data,6);// } //max30102_Bus_Write(REG_FIFO_RD_PTR,fifo_wr_ptr);}​void max30102_init(void){ IIC_Init(); max30102_reset(); // max30102_Bus_Write(REG_MODE_CONFIG, 0x0b); //mode configuration : temp_en[3] MODE[2:0]=010 HR only enabled 011 SP02 enabled// max30102_Bus_Write(REG_INTR_STATUS_2, 0xF0); //open all of interrupt// max30102_Bus_Write(REG_INTR_STATUS_1, 0x00); //all interrupt clear// max30102_Bus_Write(REG_INTR_ENABLE_2, 0x02); //DIE_TEMP_RDY_EN// max30102_Bus_Write(REG_TEMP_CONFIG, 0x01); //SET TEMP_EN​// max30102_Bus_Write(REG_SPO2_CONFIG, 0x47); //SPO2_SR[4:2]=001 100 per second LED_PW[1:0]=11 16BITS​// max30102_Bus_Write(REG_LED1_PA, 0x47); // max30102_Bus_Write(REG_LED2_PA, 0x47); max30102_Bus_Write(REG_INTR_ENABLE_1,0xc0); // INTR setting max30102_Bus_Write(REG_INTR_ENABLE_2,0x00); max30102_Bus_Write(REG_FIFO_WR_PTR,0x00); //FIFO_WR_PTR[4:0] max30102_Bus_Write(REG_OVF_COUNTER,0x00); //OVF_COUNTER[4:0] max30102_Bus_Write(REG_FIFO_RD_PTR,0x00); //FIFO_RD_PTR[4:0] max30102_Bus_Write(REG_FIFO_CONFIG,0x0f); //sample avg = 1, fifo rollover=false, fifo almost full = 17 max30102_Bus_Write(REG_MODE_CONFIG,0x03); //0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED max30102_Bus_Write(REG_SPO2_CONFIG,0x27); // SPO2_ADC range = 4096nA, SPO2 sample rate (100 Hz), LED pulseWidth (400uS) max30102_Bus_Write(REG_LED1_PA,0x24); //Choose value for ~ 7mA for LED1 max30102_Bus_Write(REG_LED2_PA,0x24); // Choose value for ~ 7mA for LED2 max30102_Bus_Write(REG_PILOT_PA,0x7f); // Choose value for ~ 25mA for Pilot LED​​ // // Interrupt Enable 1 Register. Set PPG_RDY_EN (data available in FIFO)// max30102_Bus_Write(0x2, 1<<6);​// // FIFO configuration register// // SMP_AVE: 16 samples averaged per FIFO sample// // FIFO_ROLLOVER_EN=1// //max30102_Bus_Write(0x8, 1<<4);// max30102_Bus_Write(0x8, (0<<5) | 1<<4);​// // Mode Configuration Register// // SPO2 mode// max30102_Bus_Write(0x9, 3);​// // SPO2 Configuration Register// max30102_Bus_Write(0xa,// (3<<5) // SPO2_ADC_RGE 2 = full scale 8192 nA (LSB size 31.25pA); 3 = 16384nA// | (1<<2) // sample rate: 0 = 50sps; 1 = 100sps; 2 = 200sps// | (3<<0) // LED_PW 3 = 411μs, ADC resolution 18 bits// );​// // LED1 (red) power (0 = 0mA; 255 = 50mA)// max30102_Bus_Write(0xc, 0xb0);​// // LED (IR) power// max30102_Bus_Write(0xd, 0xa0); }​void max30102_reset(void){ max30102_Bus_Write(REG_MODE_CONFIG,0x40); max30102_Bus_Write(REG_MODE_CONFIG,0x40);}​​​​​​void maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data){// char ach_i2c_data[2];// ach_i2c_data[0]=uch_addr;// ach_i2c_data[1]=uch_data;// // IIC_WriteBytes(I2C_WRITE_ADDR, ach_i2c_data, 2); IIC_Write_One_Byte(I2C_WRITE_ADDR,uch_addr,uch_data);}​void maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data){// char ch_i2c_data;// ch_i2c_data=uch_addr;// IIC_WriteBytes(I2C_WRITE_ADDR, &ch_i2c_data, 1);// // i2c.read(I2C_READ_ADDR, &ch_i2c_data, 1);// // *puch_data=(uint8_t) ch_i2c_data; IIC_Read_One_Byte(I2C_WRITE_ADDR,uch_addr,puch_data);}​void maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led){ uint32_t un_temp; unsigned char uch_temp; char ach_i2c_data[6]; *pun_red_led=0; *pun_ir_led=0;​ //read and clear status register maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp); maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp); IIC_ReadBytes(I2C_WRITE_ADDR,REG_FIFO_DATA,(u8 *)ach_i2c_data,6); un_temp=(unsigned char) ach_i2c_data[0]; un_temp<<=16; *pun_red_led+=un_temp; un_temp=(unsigned char) ach_i2c_data[1]; un_temp<<=8; *pun_red_led+=un_temp; un_temp=(unsigned char) ach_i2c_data[2]; *pun_red_led+=un_temp; un_temp=(unsigned char) ach_i2c_data[3]; un_temp<<=16; *pun_ir_led+=un_temp; un_temp=(unsigned char) ach_i2c_data[4]; un_temp<<=8; *pun_ir_led+=un_temp; un_temp=(unsigned char) ach_i2c_data[5]; *pun_ir_led+=un_temp; *pun_red_led&=0x03FFFF; //Mask MSB [23:18] *pun_ir_led&=0x03FFFF; //Mask MSB [23:18]}​​void dis_DrawCurve(u32* data,u8 x){ u16 i; u32 max=0,min=262144; u32 temp; u32 compress; for(i=0;i<128*2;i++) { if(data[i]>max) { max = data[i]; } if(data[i]<min) { min = data[i]; } } compress = (max-min)/20; for(i=0;i<128;i++) { temp = data[i*2] + data[i*2+1]; temp/=2; temp -= min; temp/=compress; if(temp>20)temp=20; }}​void MAX30102_data_set(){// printf("\r\n MAX30102 init \r\n"); un_min=0x3FFFF; un_max=0; n_ir_buffer_length=500; //buffer length of 100 stores 5 seconds of samples running at 100sps //read the first 500 samples, and determine the signal range for(i=0;i<n_ir_buffer_length;i++) { while(MAX30102_INT==1); //wait until the interrupt pin asserts// max30102_FIFO_ReadBytes(REG_FIFO_DATA,temp); aun_red_buffer[i] = (long)((long)((long)temp[0]&0x03)<<16) | (long)temp[1]<<8 | (long)temp[2]; // Combine values to get the actual number aun_ir_buffer[i] = (long)((long)((long)temp[3] & 0x03)<<16) |(long)temp[4]<<8 | (long)temp[5]; // Combine values to get the actual number if(un_min>aun_red_buffer[i]) un_min=aun_red_buffer[i]; //update signal min if(un_max<aun_red_buffer[i]) un_max=aun_red_buffer[i]; //update signal max } un_prev_data=aun_red_buffer[i]; //calculate heart rate and SpO2 after first 500 samples (first 5 seconds of samples) maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); }​​​void MAX30102_get(u8 *hr,u8 *spo2){ i=0; un_min=0x3FFFF; un_max=0; //dumping the first 100 sets of samples in the memory and shift the last 400 sets of samples to the top for(i=100;i<500;i++) { aun_red_buffer[i-100]=aun_red_buffer[i]; aun_ir_buffer[i-100]=aun_ir_buffer[i]; //update the signal min and max if(un_min>aun_red_buffer[i]) un_min=aun_red_buffer[i]; if(un_max<aun_red_buffer[i]) un_max=aun_red_buffer[i]; } //take 100 sets of samples before calculating the heart rate. for(i=400;i<500;i++) { un_prev_data=aun_red_buffer[i-1];// while(MAX30102_INT==1); max30102_FIFO_ReadBytes(REG_FIFO_DATA,temp); aun_red_buffer[i] = (long)((long)((long)temp[0]&0x03)<<16) | (long)temp[1]<<8 | (long)temp[2]; // Combine values to get the actual number aun_ir_buffer[i] = (long)((long)((long)temp[3] & 0x03)<<16) |(long)temp[4]<<8 | (long)temp[5]; // Combine values to get the actual number if(aun_red_buffer[i]>un_prev_data) { f_temp=aun_red_buffer[i]-un_prev_data; f_temp/=(un_max-un_min); f_temp*=MAX_BRIGHTNESS; n_brightness-=(int)f_temp; if(n_brightness<0) n_brightness=0; } else { f_temp=un_prev_data-aun_red_buffer[i]; f_temp/=(un_max-un_min); f_temp*=MAX_BRIGHTNESS; n_brightness+=(int)f_temp; if(n_brightness>MAX_BRIGHTNESS) n_brightness=MAX_BRIGHTNESS; } //send samples and calculation result to terminal program through UART if(ch_hr_valid == 1 && n_heart_rate<120 && ch_spo2_valid == 1 && n_sp02<101)//**/ ch_hr_valid == 1 && ch_spo2_valid ==1 && n_heart_rate<120 && n_sp02<101 { dis_hr = n_heart_rate; dis_spo2 = n_sp02; }// else// {// dis_hr = 0;// dis_spo2 = 0;// }// printf("HR=%i, ", dis_hr); // printf("HRvalid=%i, ", ch_hr_valid);// printf("SpO2=%i, ", dis_spo2);// printf("SPO2Valid=%i\r\n", ch_spo2_valid); *hr = dis_hr; *spo2 = dis_spo2; } maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); //红光在上,红外在下 dis_DrawCurve(aun_red_buffer,20); dis_DrawCurve(aun_ir_buffer,0);​}​​/** \file algorithm.c ******************************************************** Project: MAXREFDES117#* Filename: algorithm.cpp* Description: This module calculates the heart rate/SpO2 level*** --------------------------------------------------------------------** This code follows the following naming conventions:** char ch_pmod_value* char (array) s_pmod_s_string[16]* float f_pmod_value* int32_t n_pmod_value* int32_t (array) an_pmod_value[16]* int16_t w_pmod_value* int16_t (array) aw_pmod_value[16]* uint16_t uw_pmod_value* uint16_t (array) auw_pmod_value[16]* uint8_t uch_pmod_value* uint8_t (array) auch_pmod_buffer[16]* uint32_t un_pmod_value* int32_t * pn_pmod_value** ------------------------------------------------------------------------- *//******************************************************************************** Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.** Permission is hereby granted, free of charge, to any person obtaining a* copy of this software and associated documentation files (the "Software"),* to deal in the Software without restriction, including without limitation* the rights to use, copy, modify, merge, publish, distribute, sublicense,* and/or sell copies of the Software, and to permit persons to whom the* Software is furnished to do so, subject to the following conditions:** The above copyright notice and this permission notice shall be included* in all copies or substantial portions of the Software.** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR* OTHER DEALINGS IN THE SOFTWARE.** Except as contained in this notice, the name of Maxim Integrated* Products, Inc. shall not be used except as stated in the Maxim Integrated* Products, Inc. Branding Policy.** The mere transfer of this software does not imply any licenses* of trade secrets, proprietary technology, copyrights, patents,* trademarks, maskwork rights, or any other form of intellectual* property whatsoever. Maxim Integrated Products, Inc. retains all* ownership rights.********************************************************************************/​const uint16_t auw_hamm[31]={ 41, 276, 512, 276, 41 }; //Hamm= long16(512* hamming(5)');//uch_spo2_table is computed as -45.060*ratioAverage* ratioAverage + 30.354 *ratioAverage + 94.845 ;const uint8_t uch_spo2_table[184]={ 95, 95, 95, 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 99, 99, 99, 99, 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 99, 99, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 98, 98, 97, 97, 97, 97, 96, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91, 90, 90, 89, 89, 89, 88, 88, 87, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81, 80, 80, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67, 66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 31, 30, 29, 28, 27, 26, 25, 23, 22, 21, 20, 19, 17, 16, 15, 14, 12, 11, 10, 9, 7, 6, 5, 3, 2, 1 } ;static int32_t an_dx[ BUFFER_SIZE-MA4_SIZE]; // deltastatic int32_t an_x[ BUFFER_SIZE]; //irstatic int32_t an_y[ BUFFER_SIZE]; //red​void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer, int32_t n_ir_buffer_length, uint32_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid, int32_t *pn_heart_rate, int8_t *pch_hr_valid)/*** \brief Calculate the heart rate and SpO2 level* \par Details* By detecting peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the ratio for the SPO2 is computed.* Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow.* Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each ratio.** \param[in] *pun_ir_buffer - IR sensor data buffer* \param[in] n_ir_buffer_length - IR sensor data buffer length* \param[in] *pun_red_buffer - Red sensor data buffer* \param[out] *pn_spo2 - Calculated SpO2 value* \param[out] *pch_spo2_valid - 1 if the calculated SpO2 value is valid* \param[out] *pn_heart_rate - Calculated heart rate value* \param[out] *pch_hr_valid - 1 if the calculated heart rate value is valid** \retval None*/{ uint32_t un_ir_mean ,un_only_once ; int32_t k ,n_i_ratio_count; int32_t i, s, m, n_exact_ir_valley_locs_count ,n_middle_idx; int32_t n_th1, n_npks,n_c_min; int32_t an_ir_valley_locs[15] ; int32_t an_exact_ir_valley_locs[15] ; int32_t an_dx_peak_locs[15] ; int32_t n_peak_interval_sum; int32_t n_y_ac, n_x_ac; int32_t n_spo2_calc; int32_t n_y_dc_max, n_x_dc_max; int32_t n_y_dc_max_idx, n_x_dc_max_idx; int32_t an_ratio[5],n_ratio_average; int32_t n_nume, n_denom ; // remove DC of ir signal un_ir_mean =0; for (k=0 ; k<n_ir_buffer_length ; k++ ) un_ir_mean += pun_ir_buffer[k] ; un_ir_mean =un_ir_mean/n_ir_buffer_length ; for (k=0 ; k<n_ir_buffer_length ; k++ ) an_x[k] = pun_ir_buffer[k] - un_ir_mean ; // 4 pt Moving Average for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){ n_denom= ( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3]); an_x[k]= n_denom/(int32_t)4; }​ // get difference of smoothed IR signal for( k=0; k<BUFFER_SIZE-MA4_SIZE-1; k++) an_dx[k]= (an_x[k+1]- an_x[k]);​ // 2-pt Moving Average to an_dx for(k=0; k< BUFFER_SIZE-MA4_SIZE-2; k++){ an_dx[k] = ( an_dx[k]+an_dx[k+1])/2 ; } // hamming window // flip wave form so that we can detect valley with peak detector for ( i=0 ; i<BUFFER_SIZE-HAMMING_SIZE-MA4_SIZE-2 ;i++){ s= 0; for( k=i; k<i+ HAMMING_SIZE ;k++){ s -= an_dx[k] *auw_hamm[k-i] ; } an_dx[i]= s/ (int32_t)1146; // divide by sum of auw_hamm }​ n_th1=0; // threshold calculation for ( k=0 ; k<BUFFER_SIZE-HAMMING_SIZE ;k++){ n_th1 += ((an_dx[k]>0)? an_dx[k] : ((int32_t)0-an_dx[k])) ; } n_th1= n_th1/ ( BUFFER_SIZE-HAMMING_SIZE); // peak location is acutally index for sharpest location of raw signal since we flipped the signal maxim_find_peaks( an_dx_peak_locs, &n_npks, an_dx, BUFFER_SIZE-HAMMING_SIZE, n_th1, 8, 5 );//peak_height, peak_distance, max_num_peaks ​ n_peak_interval_sum =0; if (n_npks>=2){ for (k=1; k<n_npks; k++) n_peak_interval_sum += (an_dx_peak_locs[k]-an_dx_peak_locs[k -1]); n_peak_interval_sum=n_peak_interval_sum/(n_npks-1); *pn_heart_rate=(int32_t)(6000/n_peak_interval_sum);// beats per minutes *pch_hr_valid = 1; } else { *pn_heart_rate = -999; *pch_hr_valid = 0; } for ( k=0 ; k<n_npks ;k++) an_ir_valley_locs[k]=an_dx_peak_locs[k]+HAMMING_SIZE/2; ​​ // raw value : RED(=y) and IR(=X) // we need to assess DC and AC value of ir and red PPG. for (k=0 ; k<n_ir_buffer_length ; k++ ) { an_x[k] = pun_ir_buffer[k] ; an_y[k] = pun_red_buffer[k] ; }​ // find precise min near an_ir_valley_locs n_exact_ir_valley_locs_count =0; for(k=0 ; k<n_npks ;k++){ un_only_once =1; m=an_ir_valley_locs[k]; n_c_min= 16777216;//2^24; if (m+5 < BUFFER_SIZE-HAMMING_SIZE && m-5 >0){ for(i= m-5;i<m+5; i++) if (an_x[i]<n_c_min){ if (un_only_once >0){ un_only_once =0; } n_c_min= an_x[i] ; an_exact_ir_valley_locs[k]=i; } if (un_only_once ==0) n_exact_ir_valley_locs_count ++ ; } } if (n_exact_ir_valley_locs_count <2 ){ *pn_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range *pch_spo2_valid = 0; return; } // 4 pt MA for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){ an_x[k]=( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3])/(int32_t)4; an_y[k]=( an_y[k]+an_y[k+1]+ an_y[k+2]+ an_y[k+3])/(int32_t)4; }​ //using an_exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration ratio //finding AC/DC maximum of raw ir * red between two valley locations n_ratio_average =0; n_i_ratio_count =0; for(k=0; k< 5; k++) an_ratio[k]=0; for (k=0; k< n_exact_ir_valley_locs_count; k++){ if (an_exact_ir_valley_locs[k] > BUFFER_SIZE ){ *pn_spo2 = -999 ; // do not use SPO2 since valley loc is out of range *pch_spo2_valid = 0; return; } } // find max between two valley locations // and use ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2 ​ for (k=0; k< n_exact_ir_valley_locs_count-1; k++){ n_y_dc_max= -16777216 ; n_x_dc_max= - 16777216; if (an_exact_ir_valley_locs[k+1]-an_exact_ir_valley_locs[k] >10){ for (i=an_exact_ir_valley_locs[k]; i< an_exact_ir_valley_locs[k+1]; i++){ if (an_x[i]> n_x_dc_max) {n_x_dc_max =an_x[i];n_x_dc_max_idx =i; } if (an_y[i]> n_y_dc_max) {n_y_dc_max =an_y[i];n_y_dc_max_idx=i;} } n_y_ac= (an_y[an_exact_ir_valley_locs[k+1]] - an_y[an_exact_ir_valley_locs[k] ] )*(n_y_dc_max_idx -an_exact_ir_valley_locs[k]); //red n_y_ac= an_y[an_exact_ir_valley_locs[k]] + n_y_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k]) ; n_y_ac= an_y[n_y_dc_max_idx] - n_y_ac; // subracting linear DC compoenents from raw n_x_ac= (an_x[an_exact_ir_valley_locs[k+1]] - an_x[an_exact_ir_valley_locs[k] ] )*(n_x_dc_max_idx -an_exact_ir_valley_locs[k]); // ir n_x_ac= an_x[an_exact_ir_valley_locs[k]] + n_x_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k]); n_x_ac= an_x[n_y_dc_max_idx] - n_x_ac; // subracting linear DC compoenents from raw n_nume=( n_y_ac *n_x_dc_max)>>7 ; //prepare X100 to preserve floating value n_denom= ( n_x_ac *n_y_dc_max)>>7; if (n_denom>0 && n_i_ratio_count <5 && n_nume != 0) { an_ratio[n_i_ratio_count]= (n_nume*20)/n_denom ; //formular is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max) ; ///*************************n_nume原来是*100************************// n_i_ratio_count++; } } }​ maxim_sort_ascend(an_ratio, n_i_ratio_count); n_middle_idx= n_i_ratio_count/2;​ if (n_middle_idx >1) n_ratio_average =( an_ratio[n_middle_idx-1] +an_ratio[n_middle_idx])/2; // use median else n_ratio_average = an_ratio[n_middle_idx ];​ if( n_ratio_average>2 && n_ratio_average <184){ n_spo2_calc= uch_spo2_table[n_ratio_average] ; *pn_spo2 = n_spo2_calc ; *pch_spo2_valid = 1;// float_SPO2 = -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ; // for comparison with table } else{ *pn_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range *pch_spo2_valid = 0; }}​​void maxim_find_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num)/*** \brief Find peaks* \par Details* Find at most MAX_NUM peaks above MIN_HEIGHT separated by at least MIN_DISTANCE** \retval None*/{ maxim_peaks_above_min_height( pn_locs, pn_npks, pn_x, n_size, n_min_height ); maxim_remove_close_peaks( pn_locs, pn_npks, pn_x, n_min_distance ); *pn_npks = min( *pn_npks, n_max_num );}​void maxim_peaks_above_min_height(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height)/*** \brief Find peaks above n_min_height* \par Details* Find all peaks above MIN_HEIGHT** \retval None*/{ int32_t i = 1, n_width; *pn_npks = 0; while (i < n_size-1){ if (pn_x[i] > n_min_height && pn_x[i] > pn_x[i-1]){ // find left edge of potential peaks n_width = 1; while (i+n_width < n_size && pn_x[i] == pn_x[i+n_width]) // find flat peaks n_width++; if (pn_x[i] > pn_x[i+n_width] && (*pn_npks) < 15 ){ // find right edge of peaks pn_locs[(*pn_npks)++] = i; // for flat peaks, peak location is left edge i += n_width+1; } else i += n_width; } else i++; }}​​void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance)/*** \brief Remove peaks* \par Details* Remove peaks separated by less than MIN_DISTANCE** \retval None*/{ int32_t i, j, n_old_npks, n_dist; /* Order peaks from large to small */ maxim_sort_indices_descend( pn_x, pn_locs, *pn_npks );​ for ( i = -1; i < *pn_npks; i++ ){ n_old_npks = *pn_npks; *pn_npks = i+1; for ( j = i+1; j < n_old_npks; j++ ){ n_dist = pn_locs[j] - ( i == -1 ? -1 : pn_locs[i] ); // lag-zero peak of autocorr is at index -1 if ( n_dist > n_min_distance || n_dist < -n_min_distance ) pn_locs[(*pn_npks)++] = pn_locs[j]; } }​ // Resort indices longo ascending order maxim_sort_ascend( pn_locs, *pn_npks );}​void maxim_sort_ascend(int32_t *pn_x,int32_t n_size) /*** \brief Sort array* \par Details* Sort array in ascending order (insertion sort algorithm)** \retval None*/{ int32_t i, j, n_temp; for (i = 1; i < n_size; i++) { n_temp = pn_x[i]; for (j = i; j > 0 && n_temp < pn_x[j-1]; j--) pn_x[j] = pn_x[j-1]; pn_x[j] = n_temp; }}​void maxim_sort_indices_descend(int32_t *pn_x, int32_t *pn_indx, int32_t n_size)/*** \brief Sort indices* \par Details* Sort indices according to descending order (insertion sort algorithm)** \retval None*/ { int32_t i, j, n_temp; for (i = 1; i < n_size; i++) { n_temp = pn_indx[i]; for (j = i; j > 0 && pn_x[n_temp] > pn_x[pn_indx[j-1]]; j--) pn_indx[j] = pn_indx[j-1]; pn_indx[j] = n_temp; }}​3.2 max30102.h#ifndef __MYIIC_H#define __MYIIC_H#include "sys.h"#include "usart.h"#include <stdio.h>​/*MAX30102心率传感器:SCL<->PB6SDA<->PB7IM<->PB9*/​#define MAX30102_INT PBin(9)​//IO方向设置#define SDA_IN() {GPIOB->CRL&=0x0FFFFFFF;GPIOB->CRL|=0x40000000;} #define SDA_OUT() {GPIOB->CRL&=0x0FFFFFFF;GPIOB->CRL|=0x70000000;}​​//IO操作函数 #define IIC_SCL PBout(6) //SCL#define IIC_SDA PBout(7) //SDA #define READ_SDA PBin(7) //输入SDA ​//IIC所有操作函数void IIC_Init(void); //初始化IIC的IO口 void IIC_Start(void); //发送IIC开始信号void IIC_Stop(void); //发送IIC停止信号void IIC_Send_Byte(u8 txd); //IIC发送一个字节u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节u8 IIC_Wait_Ack(void); //IIC等待ACK信号void IIC_Ack(void); //IIC发送ACK信号void IIC_NAck(void); //IIC不发送ACK信号​void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);void IIC_Read_One_Byte(u8 daddr,u8 addr,u8* data);​void IIC_WriteBytes(u8 WriteAddr,u8* data,u8 dataLength);void IIC_ReadBytes(u8 deviceAddr, u8 writeAddr,u8* data,u8 dataLength);​​​#define I2C_WR 0 /* 写控制bit */#define I2C_RD 1 /* 读控制bit */​#define max30102_WR_address 0xAE​#define I2C_WRITE_ADDR 0xAE#define I2C_READ_ADDR 0xAF​//register addresses#define REG_INTR_STATUS_1 0x00#define REG_INTR_STATUS_2 0x01#define REG_INTR_ENABLE_1 0x02#define REG_INTR_ENABLE_2 0x03#define REG_FIFO_WR_PTR 0x04#define REG_OVF_COUNTER 0x05#define REG_FIFO_RD_PTR 0x06#define REG_FIFO_DATA 0x07#define REG_FIFO_CONFIG 0x08#define REG_MODE_CONFIG 0x09#define REG_SPO2_CONFIG 0x0A#define REG_LED1_PA 0x0C#define REG_LED2_PA 0x0D#define REG_PILOT_PA 0x10#define REG_MULTI_LED_CTRL1 0x11#define REG_MULTI_LED_CTRL2 0x12#define REG_TEMP_INTR 0x1F#define REG_TEMP_FRAC 0x20#define REG_TEMP_CONFIG 0x21#define REG_PROX_INT_THRESH 0x30#define REG_REV_ID 0xFE#define REG_PART_ID 0xFF​void max30102_init(void); void max30102_reset(void);u8 max30102_Bus_Write(u8 Register_Address, u8 Word_Data);u8 max30102_Bus_Read(u8 Register_Address);void max30102_FIFO_ReadWords(u8 Register_Address,u16 Word_Data[][2],u8 count);void max30102_FIFO_ReadBytes(u8 Register_Address,u8* Data);​void maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data);void maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data);void maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led);​void dis_DrawCurve(u32* data,u8 x);void MAX30102_get(u8 *hr,u8 *spo2);void MAX30102_data_set(void);​​​​​#define true 1#define false 0#define FS 100#define BUFFER_SIZE (FS* 5) #define HR_FIFO_SIZE 7#define MA4_SIZE 4 // DO NOT CHANGE#define HAMMING_SIZE 5// DO NOT CHANGE#define min(x,y) ((x) < (y) ? (x) : (y))​//const uint16_t auw_hamm[31]={ 41, 276, 512, 276, 41 }; //Hamm= long16(512* hamming(5)');////uch_spo2_table is computed as -45.060*ratioAverage* ratioAverage + 30.354 *ratioAverage + 94.845 ;//const uint8_t uch_spo2_table[184]={ 95, 95, 95, 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 99, 99, 99, 99, // 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, // 100, 100, 100, 100, 99, 99, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 98, 98, 97, 97, // 97, 97, 96, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91, // 90, 90, 89, 89, 89, 88, 88, 87, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81, // 80, 80, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67, // 66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, // 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 31, 30, 29, // 28, 27, 26, 25, 23, 22, 21, 20, 19, 17, 16, 15, 14, 12, 11, 10, 9, 7, 6, 5, // 3, 2, 1 } ;//static int32_t an_dx[ BUFFER_SIZE-MA4_SIZE]; // delta//static int32_t an_x[ BUFFER_SIZE]; //ir//static int32_t an_y[ BUFFER_SIZE]; //red​​void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer , int32_t n_ir_buffer_length, uint32_t *pun_red_buffer , int32_t *pn_spo2, int8_t *pch_spo2_valid , int32_t *pn_heart_rate , int8_t *pch_hr_valid);void maxim_find_peaks( int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num );void maxim_peaks_above_min_height( int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height );void maxim_remove_close_peaks( int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance );void maxim_sort_ascend( int32_t *pn_x, int32_t n_size );void maxim_sort_indices_descend( int32_t *pn_x, int32_t *pn_indx, int32_t n_size);​​​​#endif
  • [问题求助] 物联网与嵌入式系统有何关系。
    物联网如何利用嵌入式系统实现智能设备的连接和控制。
  • [技术干货] 2024年1月嵌入式项目开发专题总汇
    一、前言本文将对一系列深入探讨单片机与C语言编程核心技术的文章进行综合概述,这些文章涵盖了从基础知识到实际项目应用的广泛领域,为读者提供一个全面而系统的知识框架。首先从单片机与C语言编程的基础元素出发,详细剖析了C语言编程中的基本语句、运算符使用及其在单片机环境下的具体实践,并通过格式化打印函数的教学以及字符串和数组的全方位练习,帮助读者掌握扎实的数据处理能力。这一阶段的文章包括“单片机与C语言编程基础_格式化打印函数”、“字符串知识点”、“数组全方位练习”、“数组与函数传参”等,进一步深化了对函数封装、字符串操作和数据结构的理解。随着学习的深入,聚焦于单片机系统中的中断管理与通信技术,如以STM32单片机为例,展示了如何采用环形缓冲区实现串口中断数据高效接收与管理,为实时控制和数据传输打下坚实基础。在计算机视觉领域,介绍了如何利用OpenCV4.x版本的C++接口实现人脸检测算法,包括正脸、侧脸乃至眼睛识别,展示了C语言在现代智能硬件系统中的广泛应用潜力。为了拓宽读者的技术视野,还涉及到了Windows平台下的网络编程技术,通过Win32 API结合Visual Studio 2022开发环境,讲解了如何构建基于PC端的网络应用程序。系列文章并没有忽视嵌入式开发领域的另一重要方面——Linux操作系统的基本命令与C语言编程在嵌入式Linux环境下的基础应用。整体而言,本系列文章不仅致力于奠定坚实的单片机与C语言编程基石,而且引领读者逐步走向实际应用场景,诸如智慧农业大棚检测系统的设计与实施,从而全面提升读者在嵌入式系统设计、数据采集处理、通信协议栈以及多平台开发等方面的专业技能与实战经验。二、文章列表(1)基于单片机设计的智慧农业大棚检测系统cid:link_5本项目基于单片机设计一个智慧农业大棚检测系统,以提供实时监测和管理大棚环境的关键参数。系统支持环境温度、湿度检测,光照强度检测,并能根据预设的阀值进行报警提示。为了实现数据的显示和管理,该系统还利用Qt开发了一款对应的Android手机APP,通过蓝牙传输模块将单片机采集到的数据传递到手机APP上进行显示和管理。(2)STM32单片机采用环形缓冲区实现串口中断数据接收管理cid:link_0在嵌入式系统开发中,与上位机进行串口通信是非常常见的场景。上位机可以通过串口发送指令或者数据给嵌入式设备,而嵌入式设备需要可靠地接收并解析这些数据,以执行相应的操作。然而,在串口通信过程中,上位机发送数据的速率往往与嵌入式设备接收和处理数据的速率不一致,这就可能导致数据的丢失或者误解析。为了解决这个问题,决定设计并实现一个环形缓冲区来进行数据接收管理。环形缓冲区是一种高效的数据结构,适用于数据产生速率快于消费速率的场景。它具有固定大小的缓冲区,并且可以循环利用空间,保证数据的连续存储和有效利用。在本项目中,选择使用STM32微控制器来实现串口数据接收功能。STM32具有丰富的外设资源和强大的性能,非常适合用于串口通信和数据处理。(3)OpenCV4.x(C++)人脸检测(眼睛、侧脸、正脸)cid:link_1OpenCV是一款广泛使用的计算机视觉库,提供了许多强大的功能,包括人脸检测和识别。人脸分类器是OpenCV中用于人脸检测的关键工具之一,能够快速准确地检测出图像中的人脸。本文将介绍如何使用OpenCV自带的人脸分类器,并对比不同分类器的精度。在日常生活中,人脸检测的应用非常广泛,例如安防、人机交互、智能交通等领域。而在计算机视觉领域,人脸检测也是一个非常热门的研究方向。OpenCV作为一款免费、开源的计算机视觉库,为我们提供了一种方便快捷的人脸检测方法。使用OpenCV的人脸分类器,可以快速地检测出图像中的正脸、侧脸和眼睛等部位,进而实现更加智能的应用。(4)单片机与C语言编程基础_格式化打印函数、字符串、运算符cid:link_6本文是一篇全面解析C语言核心知识点与实用技巧的文章,重点围绕字符串函数的运用进行了深入探讨,包括对sprintf和sscanf等格式化输入输出函数的功能详解及实际应用案例。同时,文章系统梳理了main函数的标准形参设定规则,以及变量和常量在C语言中的定义方式和使用场景。进一步地总结了C语言中各类基本数据类型(如整型、浮点型、字符型等)所占用的字节数,加深读者对内存分配和数据存储的理解。此外,文中还详细解读了scanf函数如何实现多元化的用户输入数据处理,并通过实例演示其在实际编程中的应用。在进阶内容部分,文章全方位介绍了位运算的概念、操作符及其在程序设计中的重要作用,结合实例帮助读者掌握这一底层编程技术。最后,通过对C语言中所有基本运算符进行全面系统的介绍与解析,使读者能够更加熟练地运用各种运算符进行高效计算和逻辑判断,提升编程效率与代码质量。整篇文章构建扎实的C语言基础知识体系,助力读者在实践中游刃有余地应对各类编程挑战。(5)单片机与C语言编程基础__函数知识cid:link_7C语言是一种结构化、高效且广泛应用的编程语言,其核心特性之一是强大的函数功能。在C语言中,函数是用来封装一段可重用代码的逻辑单元,通过函数定义可以实现特定任务,例如计算、处理数据等。函数定义包括函数名、参数列表以及函数体,其中函数体内部包含了执行特定任务的一系列语句。子函数是在主函数或其它函数内部定义的函数,用于实现更细粒度的功能划分和模块化编程,增强代码的复用性和可读性。子函数可以通过return语句向调用者返回结果,并可通过形参接收外部传入的数据。变量的作用域是程序设计中的重要概念,它决定了变量在其声明位置之后的有效范围。在C语言中,变量有全局作用域和局部作用域之分。全局变量在整个程序文件中都可见,从定义处开始到文件结束;而局部变量仅在定义它的函数或代码块内有效。全局变量可以在任何函数中访问和修改,但过度依赖全局变量可能导致程序逻辑混乱。静态变量则具有局部作用域,但在函数多次调用间保留其值。只读变量(常量)一旦初始化后就不能再改变其值,这有利于提高程序的健壮性和安全性。另外,C语言允许在定义变量时进行初始化,即赋予变量一个初始值,这样在程序运行之初,变量就已经有了确定的值。这种特性使得代码更加清晰明确,减少了潜在的未初始化变量引发的问题。(6)单片机与C语言编程基础___数组全方位练习cid:link_8文章探讨了C语言中函数的使用以及数组操作的各种实践练习。剖析了C语言函数的基本概念与用法,包括函数的定义、封装性原理,如何进行参数传递和调用,以及对局部变量、全局变量、只读变量(const)和静态变量在函数内部的不同应用场景进行了详解。文章聚焦于C语言数组这一重要数据结构的实践运用。通过一系列精心设计的习题,可以亲自动手实现诸如数组插入、删除元素、数组元素的左右移动等基础操作,并进一步探索数组排序算法,如冒泡排序、选择排序,以及对其进行优化,比如采用更高效的快速排序或归并排序方法。文章还涉及到了数组数据拼接的实际问题,指导数组间的数据合并技巧。文章从基础知识出发,详细介绍了数组的基本定义方式,如何对数组元素进行赋值,并延伸至处理字符串这一特殊类型的字符数组,以确保读者对数组的使用有全面且扎实的理解和应用能力。通过这些实际的编程练习,能够提升自身的C语言编程技能,更好地理解和掌握函数与数组在实际项目开发中的综合运用。(7)单片机与C语言编程基础_数组与函数传参cid:link_9该篇文章是一篇关于C语言编程中核心概念和函数库使用的详细介绍。它深入探讨了字符串处理的标准函数,这些函数在C语言的<string.h>库中提供,涵盖了字符串的创建、复制、比较、连接、查找等操作,对于提高程序处理文本数据的能力至关重要。文章详述了指针与数组在作为函数参数时的使用方法和两者之间的内在联系,帮助读者理解和掌握如何在函数间传递和操作动态数据。文中介绍了指针的基本定义方式以及函数返回指针的机制,包括void类型指针的定义及其灵活运用,为高效内存管理打下基础。通过类型强制转换的相关内容,指导开发者在不同数据类型之间安全、准确地转换数据。文章讨论了常量声明的概念,强调了在编程过程中对不变量的合理使用和控制。最后,涉及到了extern关键字的使用,说明如何实现模块间的全局变量引用,以实现程序的不同部分共享和访问同一份数据,这对于编写结构清晰、可复用性强的代码具有重要意义。(8)单片机与C语言编程基础_数组知识cid:link_10该篇文章全面回顾了C语言中数组这一重要数据结构的基础概念与实践操作。文章详尽梳理了数组的定义规则,包括如何声明不同空间类型的数组,如静态数组、动态数组等,并阐述了数组下标的使用规范,即如何通过下标访问数组中的元素。在内容上,文章深入探讨了数组能够存放的各种数据类型,从基本类型到复合类型,并结合实例演示了数组元素的数据替换方法以及数组内容的插入操作。针对数组的输入输出功能,作者详细介绍了标准库函数和格式化输入输出语句在处理数组数据时的应用技巧。特别地,对于字符串这一特殊形式的字符数组,文章着重讲解了字符串的输入输出过程,包括如何利用C语言进行字符串的有效读取和显示,并且讨论了计算字符串长度的相关算法。整体而言,这篇文章系统性地涵盖了C语言数组的关键知识点及其实战应用,为读者理解和掌握数组这一核心数据结构提供了坚实的基础指导。(9)Windows下网络编程(win32API+VS2022)cid:link_11这篇文章系统性地介绍了在Windows操作系统下,如何利用C语言结合Win32 API进行网络编程,并以Visual Studio 2022开发环境为依托,从零起步详细阐述了TCP协议的原理及其在实际编程中的应用。作者不仅细致梳理了在VS2022中从安装配置到新建项目的完整过程,还通过实践案例,深入浅出地展示了如何创建和实现一个功能完备的TCP服务器和客户端程序。文章内容涵盖了TCP/IP基础理论,包括连接建立、数据传输与断开连接等关键环节的原理详解;同时,针对初学者,特别强调了在Visual Studio 2022环境下搭建项目、包含必要的头文件以及调用Win32 API进行套接字编程的具体步骤。通过实战示例代码,读者能够跟随教程逐步掌握在Windows平台上构建稳定、高效的TCP通信程序的方法。(10)单片机与C语言编程基础_语句、运算符cid:link_2这篇文章全面探讨了C语言中的核心概念和关键语法元素,为读者提供一个坚实的基础和深入的理解。文章首先详述了C语言的位运算符,包括按位与(&)、按位或(|)、异或(^)、取反(~)以及左移(<<)和右移(>>)等操作,解释了它们在处理二进制数据时的作用和实际应用案例。接着,文章转向C语言的基本运算符,如算术运算符(加减乘除)、关系运算符(等于、不等于、大于、小于等)、逻辑运算符(与、或、非)以及赋值运算符,并讨论了这些运算符在表达式求值和程序控制流程中的重要作用。在数据类型部分,详细介绍了C语言中各种基本数据类型(如整型、浮点型、字符型等)以及复合数据类型(如数组、结构体),并讨论了如何声明和使用变量,包括其存储方式和生命周期。文中还重点阐述了C语言中的循环结构,包括for语句和while语句的语法格式及其在实现重复执行代码块方面的不同应用场景,同时提及goto语句这一无条件跳转指令的使用及其在现代编程中的争议性。进一步地,文章剖析了switch语句的工作机制,展示了如何通过case标签进行多分支选择,并强调了default子句在异常处理和完整性保证上的价值。文章还涵盖了运算符优先级的概念,指导读者理解不同运算符之间的结合性和计算顺序,从而避免潜在的编程错误。最后,提到了强制类型转换,说明了如何在必要时显式改变变量的数据类型以满足特定的计算需求。(11)单片机与C语言编程基础_字符串函数封装练习cid:link_12这是一篇专注于C/C++编程语言中核心字符串与内存操作实践的文章,通过一系列精心设计的练习题目,深度探索了字符串处理的关键技术,如字符串的比较、连接、查找以及拷贝等操作;同时,文章也详尽探讨了内存管理的基础技能,包括内存的比较、拷贝、初始化等实际应用。此外,文中还延伸至二维数组的定义及基本使用方法,以增强对复杂数据结构的理解与操作能力。最后,通过运用位运算技巧,展示了如何巧妙地借助取模运算将中文字模打印至控制台,使得读者能直观理解并掌握位运算在实际问题解决中的强大功能。整篇文章通过实际动手练习,提升读者对底层数据操作和高级程序设计技术的综合运用能力。(12)单片机与C语言编程基础_字符串知识点cid:link_13这篇文章全面介绍了C语言字符串的各种练习方法,涵盖了字符串解析、大小写判断、字符串插入、字符串删除、字符串排序、字符串转整数、字符串转浮点数、时间转换、GPS数据解析等多个知识点。通过学习这些内容,读者可以提升对C语言字符串处理的理解和实践能力。无论是初学者还是有一定经验的开发者,都能从中获得实用的技巧和解决问题的思路。这篇文章将为读者提供一次全方位的C语言字符串练习机会,帮助他们在字符串操作方面更加熟练和自信。(13)单片机与C语言编程基础__基本语句与位运算cid:link_14这篇文章是一篇关于C语言实践与应用的深度教程,通过一系列精心设计的基础练习题,系统性地涵盖了C语言中的多个核心知识点。文章以实际编程题目为载体,引导读者动手解决计算素数、实现排序算法(如冒泡排序、选择排序等)、求解一定范围内的偶数之和、识别可逆素数(即回文素数)、查找三位数中的水仙花数以及使用位运算进行数据交换和逻辑操作等经典问题。此外,还介绍了C语言的语法特性,包括如何安全高效地交换变量值、掌握字符串操作如大小写转换等实用技术点。通过这些实例和练习,帮助读者巩固C语言基础知识,提升编程技能,并加强对C语言特性和底层机制的理解与运用能力。(14)单片机与C语言编程基础_字符串全方位练习cid:link_3本文是一篇全面而深入的C语言字符串实践指南,通过精心设计的一系列丰富练习题目,系统地涵盖了字符串处理中的多个关键技术和应用场景。文章详细探讨了字符串解析、大小写转换判断、字符串插入与删除操作、字符串排序算法等基础技术,并进一步延伸至复杂应用层面,如将字符串转换为整数和浮点数的功能实现,以及时间格式转换的实践操作。此外,文章还特别引入了GPS数据解析这一实际问题,引导读者运用C语言字符串处理技能解决现代信息技术领域的实际问题。通过这些实例和练习,读者不仅能巩固C语言字符串操作的基础知识,更能提升在实际编程中灵活高效地处理字符串数据的能力,拓宽解决问题的思路和视野。(15)嵌入式开发基础_Linux基本命令与C语言基础cid:link_4本文介绍如何在Linux环境下搭建C语言学习与开发的基本环境。逐步解析了在Linux系统中从零开始安装配置C语言编译器及相关工具链的过程,并深入浅出地讲解了一些必备的基础命令使用方法,如文件操作、目录导航等。探讨了Linux系统下用户权限配置的相关知识,帮助理解并掌握不同用户角色及其对应的操作权限,这对于安全、高效地进行C语言编程至关重要。针对C语言编程的核心要素之一——标准main函数的传参方式,也做了细致解读,能够清晰了解Linux环境下C程序参数的接收和处理机制,从而更好地运用到实际编程实践中。