小提琴自动演奏机器人中的齿轮系统设计与制造

小提琴自动演奏机器人中的齿轮系统设计与制造

项目简介

小提琴作为一种历史悠久、表现力丰富的乐器,其演奏通常需要高度的技巧和长时间的训练。然而,自19世纪末以来,随着机械和电子技术的发展,人们开始尝试开发能够自动演奏小提琴的装置,旨在模拟甚至超越人类的演奏能力。这些自动演奏小提琴的出现,不仅是工程技术上的突破,也反映了人类对音乐自动化和完美演奏的持续追求。

小提琴自动演奏机器人是一个集精密机械、电子、控制和人工智能技术于一体的复杂系统,旨在模拟人类小提琴演奏家的精湛技艺,实现音乐的自动化演奏。该项目的核心在于通过精确控制弓弦摩擦、指板按压和琴身姿态,准确再现音高、音色、节奏和情感表达,将小提琴演奏的复杂动作分解为可编程的机械运动。在整个系统中,齿轮传动系统作为核心传动部件,其设计与制造的精度、稳定性与可靠性直接影响着机器人的演奏性能和音质。

本报告将深入探讨小提琴自动演奏机器人中齿轮系统的设计、材料选择、制造工艺以及相关的仿真与校核,旨在分析小提琴演奏对齿轮系统的特殊要求(如高精度定位、低噪音运行、快速响应),对比不同加工工艺的优缺点,从而得出最适合自动演奏机器人的齿轮制造解决方案。通过对齿轮传动系统的详细分析,旨在为提升小提琴自动演奏机器人的整体性能提供理论依据和实践指导。

不同方案两个版本的样机模型

项目背景及意义

项目进展

我们是如何过渡到这个项目的。

在上个学期,团队深入研究了小提琴教育行业,分析了小提琴产业链各环节的市场状况与主要问题,归纳出三个核心问题:第一,音乐教育市场缺乏创新——与十年前相比,教学模式和方法基本未变,但学习成本却增长了十倍;第二,小提琴制造业虽表面标准化,实则是非标准化生产,从生产到销售缺乏有效的管理和标准化指标,导致消费者需要付出极高成本才能购得理想的琴;第三,也是最核心的问题,音乐消费的价值观正在变化,音乐就像绘画一样,最开始在墙上画,后来在布料上面、在教堂的玻璃上面,材料和载体一直都在变化,而如今,越来越少的人会单纯为音乐消费,但同时,随着流媒体的传播和基础音乐教育的普及,音乐正逐渐成为生活场景中一个必不可少的元素。

小提琴行业整体理解

上个学期在技术层面,我们从第一性原理出发,研究了小提琴的基础发声原理,并开发了基于共振原理的”小提琴音响”样机,通过利用小提琴的琴腔,能够让使用者直观感受到,音乐是由小提琴琴腔发出来的。以及不同材质对音乐风格的独特影响。这种效果与传统扬声器(Speaker)有着本质区别,每种材质的小提琴都能形成独特的”音乐滤镜”。

基于这项技术原理,我们展开产品的设计,基于学琴者家庭日常练琴的“反馈”缺失的痛点,设计了一款能够实施反馈的“智能练琴搭档”。

第一代样机开发

在寒假期间,一次偶然的发现,我们关注庞大的“闲置乐器”市场。2000 年至 2020年,是国内音乐教育的高速增长期,乐器,承载着千千万万家庭的音乐梦想走入千家万户,堪称一场现代”文艺复兴”。作为这场教育浪潮的亲身参与者,我们观察到年轻的乐器学习者随着年龄增长和学习压力加大,往往会逐渐搁置乐器。这个被忽视的闲置现象涉及庞大的市场规模和用户群体,恰恰为我们提供了机遇,本学期的多个项目都以此为出发点。

闲置琴分析:价值&问题

当前的核心方向

当前我们的工作聚焦于以下三个核心方向:

  1. 更好地帮助人们学琴 —— 提高学习效率,降低学习成本;

  2. 打造更优质的小提琴及配件 —— 提升产品体验与性能;

  3. 帮助人们创造并享受音乐 —— 让音乐成为生活中有趣的部分。

项目背景

机器人技术在当今社会已不再局限于工业生产线,而是逐渐渗透到各个领域,并在艺术领域展现出其独特的潜力和广阔的应用前景。小提琴自动演奏机器人正是这一趋势下的产物,它代表了精密机械、控制系统、人工智能等多学科交叉融合的最新成果。小提琴演奏作为一门高度复杂的艺术形式,对演奏者的生理和心理素质提出了极高的要求,需要长期的专业训练才能掌握其精髓。然而,机器人技术的发展为突破人类生理极限、实现音乐的自动化演奏提供了可能。

项目意义

从用户需求出发

“让沉睡的琴声醒来。”在中国家庭中,数百万把价值不菲的小提琴正面临被束之高阁的命运——调查显示,超过60%的家庭小提琴在购买后三年内沦为摆设。我们的自演奏小提琴项目应运而生,我们通过智能演奏再生系统,让每一把被遗忘的小提琴重新焕发生机,成为家庭美育的活跃分子。

我们的自动演奏核心能让闲置的小提琴自主发声:清晨自动演奏《晨曲》唤醒全家,晚餐时即兴伴奏营造温馨氛围,睡前播放舒缓旋律助眠。通过智能琴弦驱动技术,无需人工干预,就能使小提琴持续输出高品质音乐,解决”买来就闲置”的痛点。对于仍有学习需求的家庭,我们考虑提供双模陪伴系统:在练习时,AI可同步示范标准演奏;在休息时,系统会自动播放经典曲目进行熏陶。特别设计的成长记忆功能会记录孩子的练习历程,定期生成”音乐成长报告”,让家长直观看到投资回报。

从技术到体验,我们的项目始终围绕一个核心:让每一把被冷落的小提琴重新获得宠爱。我们不只是提供硬件,更是提供一种全新的乐器使用理念——当您的小提琴能够自主创造价值时,闲置将永远成为过去式。

其他意义

  • 推动技术进步:项目涉及精密机械设计、高性能驱动控制、复杂路径规划、实时传感反馈和人工智能算法等多个前沿领域,其研发过程将极大地推动这些交叉学科的技术进步。

  • 创新音乐教育:作为一种辅助教学工具,小提琴自动演奏机器人可以帮助学生更好地理解和掌握演奏技巧,普及音乐教育,激发更多人对音乐的兴趣。

  • 拓展艺术表现:机器人演奏为音乐创作和表演提供了新的可能性,艺术家可以利用机器人创造出人类难以实现的音效和演奏技巧,从而拓展艺术表现的边界。

  • 文化传承与保护:通过数字化记录和机器人演奏,可以有效地传承和保护濒临失传的演奏技巧和音乐文化遗产。

  • 人才培养实践:该项目为多学科交叉工程实践提供了宝贵的平台,有助于培养具备复合型知识和创新能力的优秀人才。

  • 成本效益与普及性:随着技术的不断成熟和成本的降低,小提琴自动演奏机器人有望走向市场普及,让更多人能够接触和享受高品质的音乐演奏。

材料选择

小提琴自动演奏机器人中关键构件的材料选择至关重要,它直接影响着机器人的性能、精度、寿命和成本。在选择材料时,需要综合考虑其力学性能(如强度、硬度、韧性、耐疲劳性)、物理性能(如密度、热膨胀系数)、化学性能(如耐腐蚀性)、加工性能以及成本等因素。以下将详细阐述齿轮、运弓机构和按弦机构的材料选择及其理由。

齿轮材料选择

齿轮作为核心传动部件,其材料选择需满足高强度、高硬度、优异的耐磨性和抗疲劳性能,以确保在频繁启停和变速条件下保持稳定的性能和精度,并有效降低噪音。根据项目需求和常见齿轮材料特性,做出以下选择:

  • 合金钢(Bow Controller)(齿轮、齿条):20CrMnTi。这种材料经过渗碳淬火回火处理后,表面硬度可达到HRC58-62,心部仍保持高韧性。其优点在于强度高、耐疲劳性能优异,能够承受较大的载荷和冲击,适用于主传动系统,确保齿轮的高精度、高强度和长寿命。同时,其良好的耐磨性有助于延长齿轮的使用寿命。
  • 工程塑料(Fingerboard traversal)(齿轮):POM(聚甲醛)。工程塑料齿轮具有低噪音、自润滑、轻量化和耐腐蚀的优点。在对噪音要求较高或载荷较小的辅助传动系统中,可以考虑使用POM齿轮,以降低整体噪音并减轻重量。然而,其承载能力相对较低,不适用于主传动或高载荷场合。

对于小提琴自动演奏机器人这种对精度和稳定性要求极高的设备,主传动系统中的齿轮必须采用高强度合金钢20CrMnTi,以确保其在长期高频运转条件下具备出色的耐疲劳特性和精度保持能力。而对于一些次级传动或对噪音控制有更高要求的部位,可以辅助性地采用工程塑料齿轮,以平衡性能与成本,并进一步降低噪音。此外,精确的热处理(如淬火和回火)对于高强度合金钢齿轮至关重要,它能使其获得理想的表面硬度和耐疲劳性能。此外,经过查阅资料,对于噪音敏感的部件,还可以考虑在金属齿轮表面施加DLC(类金刚石)涂层,以提高耐磨性和降低摩擦噪音。

运弓(Bow Controller)构件材料选择

运弓机构需要实现轻量化、高刚度、多自由度控制以及平稳性与稳定性,以模拟人类运弓的精细动作。因此,材料选择应侧重于比强度、比刚度、阻尼性能和加工性。

  • 碳纤维复合材料(主体部分):碳纤维复合材料具有极高的比强度和比刚度,同时具备良好的阻尼性能,能够有效抑制振动。将其应用于运弓机构主体,可以显著减轻运弓机构的重量,从而减小运动惯性,提高系统的响应速度和控制精度。其优异的力学性能确保了运弓机构在高速往复运动中的稳定性。

  • 铝合金 **(连接件、标准件)**:主要采用 7075、6061。铝合金具有重量轻、易于CNC加工的特点,成本相对较低。适用于运弓机构的连接件和支撑结构,可以在保证一定强度的前提下,平衡整体重量和制造成本。

运弓作为直接与琴弦接触并进行高速往复运动的关键部件,其轻量化和高刚度是实现高精度控制和快速响应的基础。碳纤维复合材料能够完美满足这些要求,使其成为运弓主体的理想选择。而铝合金则可以作为辅助材料,用于制造对刚度要求稍低但需要精密加工的连接部件,以实现整体性能和成本的优化。

按弦(Fingerboard traversal)构件材料选择

按弦机构需要实现高精度定位、快速响应和可调按压力,并模拟多指操作。因此,其材料选择需具备高强度、耐腐蚀、表面光洁度高和低摩擦等特性。

  • 不锈钢 **(电机传动齿轮)**:例如304、316L。不锈钢具有优异的耐腐蚀性和高强度,表面易于抛光,能够获得较高的光洁度,从而降低摩擦系数。将其应用于按弦杆,可以保证按弦动作的长期稳定性和精度,并能有效抵抗琴板处手部汗液等可能造成的腐蚀。

  • 工程塑料 **(模组滑轨)**:PEEK (聚醚醚酮)、UHMW-PE(超高分子量聚乙烯)。 PEEK 具有自润滑和耐磨特性,UHMW-PE则具有极低的摩擦系数。这些材料适用于按弦机构中的导套、滑块等部件,可以进一步降低摩擦阻力,提高机构的灵敏度和响应速度。

按弦杆需要频繁、精确地按压琴弦,因此材料的强度、耐磨性和表面光洁度至关重要。不锈钢能够提供所需的力学性能和表面特性,确保按弦动作的准确性和稳定性。而高性能工程塑料则可以作为辅助材料,用于制造需要低摩擦和自润滑特性的部件,以优化按弦机构的整体性能。

材料类型对比总结

材料类型 优点 缺点 适用部位
高强度合金钢 强度高、耐疲劳、耐磨 重量大、成本高 主传动系统(齿轮)
不锈钢 耐腐蚀、表面光洁度高 硬度相对较低 按弦杆、外露部件
工程塑料 轻量、低噪音、自润滑、 耐腐蚀 强度低、易磨损、承载 能力有限 次级传动系统(齿轮)、 导套、滑块
铝合金 重量轻、散热好、易于 CNC 加工 耐磨性差 连接件、支撑结构
碳纤维复合材料 极高比强度和比刚度、良 好阻尼性能、轻量化 成本高、加工复杂 运弓主体

综合来看,小提琴自动演奏机器人设计过程中,材料选择是一个多目标优化的过程,需要在性能、成本、加工性等方面进行权衡。通过合理搭配不同材料,可以最大限度地发挥各材料的优势,从而实现机器人的高性能、高精度和长寿命运行。

关键构件设计与建模

通过机械结构代替演奏者的左右手运动,实现小提琴的自动演奏功能,且演奏能够达到学习两年的演奏者水平。

将整琴分为运弓和按压两个模块,根据小提琴的四弦特性,又可将各模块分为单弦上的小模组。利用模块化设计,首先实现单弦的按压和拉弓功能,再将单个模组衍生至整把小提琴的自动演奏。

总体框架

本节将详细阐述小提琴自动演奏机器人中关键构件的设计理念、详细尺寸参数及其设计理由,并展示其三维模型。

齿轮设计与建模

设计理念与理由

  • 高精度传动:为确保运弓和按弦动作的精确性,齿轮传动系统必须达到极高的精度。因此,设计中采用ISO5级或更高精度的齿轮,以最大限度地减小传动误差,保证音准的准确性和一致性。
  • 低噪音运行:齿轮啮合产生的噪音会直接影响演奏音质。为降低噪音,设计中优化了齿轮齿形,采用修形齿轮,并选择合适的模数和齿数,以减少啮合冲击和滑动摩擦。斜齿轮相较于直齿轮,具有渐进啮合的特点,能够显著降低冲击和振动,从而有效降低噪音。
  • 紧凑型设计:考虑到机器人内部空间有限,设计中倾向于采用行星齿轮传动或谐波齿轮传动等紧凑型结构,以在有限空间内实现大传动比,满足机器人小型化和集成化的需求。
  • 长寿命可靠性:频繁的演奏操作要求齿轮具备出色的耐疲劳特性。通过选用优质材料并结合适当的热处理工艺,确保齿轮具有足够的强度和耐磨性,以满足长期稳定运行的需求。

初代齿轮基本参数与计算

根据项目提供的参数,我们以一个模数(m)为 1mm,齿数(Z)为 60 的齿轮为例,进行主要尺寸参数的计算:

  • 模数(m):1mm

  • 齿数(Z):60

  • 齿根半径:0.5mm

  • 厚度:5mm

  • 孔径:8mm

基于这些基本参数,可以计算出齿轮的其他关键尺寸:

  • 分度圆直径(d):d=m*z=1mm*60=60mm

  • 齿顶高(ha):ha=1mm

  • 齿根高(hf):hf=1.25*m=1.25*1=1.25mm

  • 齿顶圆直径(da):da=d+2*ha=60+2*1=62mm

  • 齿根圆直径(df):df=d-2*hf=60-2*1.25=57.5mm

参数选择说明:

  • 齿数 60:选择 60齿的齿轮有助于实现紧凑型设计,并在保证传动比的同时,提供精细的运动控制能力。

  • 齿根半径0.5mm:较小的齿根半径有助于减小齿根处的应力集中,从而提高齿轮的疲劳强度,延长使用寿命。

  • 厚度 5mm:5mm的厚度旨在保证齿轮在承受载荷时的承载能力和整体刚度,防止变形。

  • 孔径 8mm:8mm的孔径设计用于与电机轴进行匹配,并考虑采用键槽连接方式,以确保可靠的动力传输。

斜齿轮齿条设计需求与优化目标

针对小提琴自动演奏机器人对噪音和运动平稳性的高要求,斜齿轮齿条的设计尤为关键。直齿轮在高速运行时容易产生较大的冲击和噪音,而斜齿轮则能有效改善这一问题。

设计需求:

  • 直齿轮冲击振动大,噪音峰值突出,不适用于本机器人系统。

  • 速度需大于 120mm/s。

  • 噪音需控制在 35dB 以下。

  • 阻力需小于 5N。

  • 要求往复运动具有高稳定性。

设计思路与优化目标:

  • 螺旋角:选择15°~25°的螺旋角,以实现渐进啮合,从而降低啮合冲击力,减少噪音和振动。设计值设定为20°,优化目标是在轴向力与噪音之间取得最佳平衡。

  • 齿向修形:通过对齿向进行修形,可以有效补偿偏载,使齿轮在实际运行中受力更加均匀,提高传动平稳性。

  • 齿廓抛物线修型:对齿廓进行抛物线修型,可以消除啮合干涉,进一步优化啮合性能,降低噪音。

参数与设计值及优化目标:

参数 设计值 优化目标 作用
模数(m) 1.5-2mm 轻量化与强度平衡 决定齿轮尺寸和承载能力
螺旋角( β ) 20° 轴向力与噪音平衡 影响啮合平稳性和轴向力
压力角( α ) 20° 传动效率最大化 影响齿轮啮合特性和传动效率
齿宽(b) 25mm 承载能力提升 30% 影响齿轮的强度和刚度
重合度( ε ) 2.1 ≥2.0 确保连续性 保证齿轮啮合的连续性和平稳性

通过上述设计理念和参数选择,旨在构建一个高精度、低噪音、长寿命的齿轮传动系统,为小提琴自动演奏机器人提供稳定可靠的动力输出。

运弓(Bow Controller)构件设计与建模

运弓构件是模拟人类运弓动作的关键部件,其设计需兼顾轻量化、高刚度、多自由度控制和运动平稳性。

Bow Controller v1.0

初代拉弓模组存在“环形弓毛材料选择难”、“整体质量大”、“多弦拓展性不足”的问题,根据上述问题,对拉弓模组进行迭代,决定仍然将琴弓保留。

Bow Controller v2.0

设计需求与理念

  • 轻量化与高刚度:采用碳纤维复合材料和优化结构设计,旨在减小运弓的惯性,从而提高其响应速度和运动精度。轻量化设计有助于降低驱动系统的负担,提高动态性能。

  • 多自由度控制:运弓设计具备三个自由度:往复运动(模拟弓速)、压力控制(模拟弓压)和角度调整(模拟弓毛与琴弦的接触角度)。这使得机器人能够模拟人类运弓的复杂技巧,实现对音色和音量的精细控制。

  • 平稳性与稳定性:为确保运弓运动平稳无颤动,设计中采用高精度导轨和直线电机或丝杠传动。这有助于消除运动过程中的抖动,保证音质的纯净性。

  • 模块化设计:弓毛部分采用可拆卸更换的模块化设计,方便日常维护和根据不同音色需求进行快速更换。

结构设计参数与材料选取

Bow Controller 构件数理模型

详细尺寸参数:

  • 运弓总长:300-400 mm,通过小提琴尺寸和演奏范围确定。

  • 弓压范围:0-5 N,可通过弹簧调节,以模拟不同力度下的弓压。

  • 导轨行程:200-300 mm 可调,确保运弓在琴弦上的有效运动范围。

  • 响应频率:≥100 Hz,保证运弓能够快速响应音乐节奏的变化。

材料配置:

  • 运弓主体:碳纤维复合材料,采用工字型或箱型截面设计,以最大化刚度重量比。

  • 连接件:7075 铝合金,通过 CNC 精密加工,确保连接精度和强度。

  • 导轨系统:高精度直线导轨,重复定位精度可达±0.01mm,保证运弓运动的精确性和稳定性。

设计理由:

  • 轻量化设计:显著提高运弓的动态响应性能,减少运动惯性,使得机器人能够更灵活地进行运弓操作。

  • 多自由度:使机器人能够模拟人类运弓的复杂技巧,实现对音色、音量和节奏的精细控制,提升演奏表现力。

  • 高精度导轨:确保运弓运动的平稳性和重复定位精度,对于音质的稳定性和一致性至关重要。

  • 模块化结构:便于弓毛部分的维护和更换,同时也为未来的功能扩展提供了便利 。

按弦(Fingerboard traversal)构件设计与建模

Fingerboard traversal

按弦机构是模拟人类手指按弦动作的关键部件,其设计要求高精度定位、快速响应、可调按压力和多指模拟。

小提琴侧视图

从技术角度而言,机器人需要能够在连续域内随时间调节音高。上图描绘了小提琴的侧视图。Ls代表弦长,即从琴桥到琴枕的距离。红色箭头描绘了一个示例手指位置,演奏大三度音。L”是指手指位置与琴桥的距离。指板穿越系统应能在琴弦上的任意位置停止琴弦,从而实现L”
<L#。通过改变L”,我们可以改变琴弦长度,进而改变音高。简化起见,假设等间距(实际情况稍有偏差)音阶,音高位置是2 的 12 次方根的函数。对于给定的空弦调音,可以计算出琴桥距离L”在任何品格位置 n 的距离。
$$
L_p=L_s(1-2^{\frac{-n}{12}})
$$

设计需求与理念

设计需求

  • 高精度定位:按弦点在指板上的精确位置直接决定音高。因此,设计中采用高精度步进电机或伺服电机驱动,确保按弦位置的准确性,将定位误差控制在0.1mm 以内,以保证音准的准确性和一致性 。

  • 快速响应:按弦动作需要快速完成,以适应乐曲节奏的快速变化。通过优化机构传动链和减小运动部件质量,实现按弦动作的快速响应。

  • 可调按压力:模拟人类手指按压力度,避免过度按压导致琴弦损伤或按压不足导致音色不佳。通过力传感器反馈和闭环控制,实现按压力的精确调节 。

  • 多指模拟:能够同时或快速连续按压多个琴弦,以演奏和弦或快速音阶。设计中实现四弦独立控制,分别对应G 弦(196Hz)、D 弦(294Hz)、A 弦(440Hz)和 E弦(659Hz)的音高 。

设计理念

  • 采用齿轮齿条传动(耐磨、结构简单)

  • 步进电机驱动(开环控制简单、位置精准、保持转矩)

  • 滚珠式直线导轨(精度高、噪音大)

结构设计参数与材料选取

结构参数:

  • 按弦杆数量:4 根(试验阶段为 1 根),每根按弦杆独立控制,对应小提琴的四根琴弦 。

  • 按弦杆直径:3-5 mm,旨在平衡强度与空间限制,确保按弦杆的刚性 。

  • 按压力范围:0.5-2N,可调节的按压力度,模拟人手力度,以保护琴弦并优化音色 。

  • 按弦杆行程:5-10 mm,覆盖多把位按压琴弦所需的有效行程 。

  • 定位精度:±0.1 mm,确保按弦位置的精确性,保证音高准确 。

  • 响应时间:≤10 ms,保证按弦动作的快速性,适应各种演奏技巧 。

材料配置:

  • 按弦杆:不锈钢 304/316L,以确保低摩擦和长期稳定性 。

  • 导向套:PEEK工程塑料,利用其自润滑特性,减小按弦杆运动时的摩擦阻力 。

  • 驱动系统:伺服电机,构成闭环控制系统,实现按弦动作的精确控制 。

  • 按压末端:硅藻泥,模拟人手指的触感,保护琴弦以及琴面板,更好还原按压效果

参数设计需求:

  • 高精度定位:确保音高准确和节奏流畅,是实现高质量演奏的基础 。

  • 可调按压力:保护琴弦,并能够根据音乐表现需求优化音色 。

  • 独立控制:实现和弦和复杂音阶的演奏,极大地丰富了机器人的演奏能力。

  • 快速响应:使机器人能够适应各种演奏技巧的要求,如快速换把和颤音等。

装配模型

小提琴自动演奏机器人的整体装配模型展示了各个关键构件如何协同工作,以实现复杂的演奏功能。该模型包括指板遍历机构和琴弓控制机构等主要部件,它们共同构成了机器人的核心运动系统。

整体装配效果

工作原理流程简述

  1. 乐谱解析:机器人首先接收 MIDI 文件,并从中解析出音高、音长、节奏等音乐信息 。

  2. 运动规划:根据解析出的音乐信息,系统生成运弓和按弦机构的详细运动轨迹和控制指令 。

  3. 运弓驱动:伺服电机驱动运弓进行往复运动,并精确控制弓压和弓速,模拟人类运弓的力度和速度变化。

  4. 按弦控制:独立的驱动系统控制各按弦杆,精确按压指板上的琴弦,以产生准确的音高 。

  5. 传感器反馈:系统实时监测运弓和按弦机构的运动状态,并通过传感器反馈数据进行闭环控制和校正,确保运动的精确性 。

  6. 音色优化:结合人工智能算法,系统实时优化演奏参数,以提升音色表现力,使其更接近人类演奏 。

控制系统简述

控制系统

功能特点与意义

功能特点:

  • 自动化演奏:实现小提琴音乐的完全自动化演奏。

  • 高精度音高:通过精确的按弦控制,确保音高准确无误。

  • 音色表现力:通过弓压、弓速和角度的精细控制,模拟人类演奏的音色变化。

  • 节奏准确性:精确的运动规划和控制确保演奏节奏的准确性。

  • 可编程性:用户可以通过编程控制机器人演奏不同的乐曲和风格。

  • 实时反馈:传感器反馈系统保证了机器人运动的精确性和稳定性。

技术意义:

  • 技术集成验证:装配模型是验证机器人各部件协调性和系统整体可行性的重要基础。

  • 直观展示:通过爆炸视图等形式,可以直观地展示机器人内部结构和各部件的工作原理,有助于理解其复杂性。

  • 性能评估基础:为后续的仿真分析和性能校核提供了基础模型,有助于在实际制造前发现并解决潜在问题 。

  • 创新应用:该项目是机器人技术在艺术领域创新应用的典型范例,展示了机器人技术在非传统领域的巨大潜力。

工艺链选择

关键构件的工艺链选择对于小提琴自动演奏机器人的性能、精度、成本和生产效率具有决定性影响。本节将详细阐述齿轮、运弓构件和按弦机构的完整工艺链,并说明选择这些工艺的理由,强调多种工艺的结合应用。

齿轮工艺链选择

齿轮作为核心传动部件,其制造工艺链必须确保高精度、高强度、优异的耐磨性和低噪音。以下是所用齿轮完整工艺链及其理由:

毛坯制备与预处理

  • 锻造制坯:对于高强度齿轮,通常采用热模锻工艺制备齿轮毛坯。锻造能够细化晶粒,改善材料的组织结构,从而显著提高齿轮的抗冲击韧性和疲劳强度。这对于承受频繁启停和变速载荷的齿轮至关重要。

    • 理由:相较于铸造,锻造能够消除铸造缺陷,使金属内部组织更致密,力学性能更优异。虽然铸造适用于大型或结构复杂的齿轮毛坯,但对于小提琴机器人所需的高精度、高强度齿轮,锻造是更优选择 。
  • 正火处理:锻造后的齿轮毛坯需要进行正火处理。正火是将钢加热到临界温度以上,保温后在空气中冷却的热处理工艺。其目的是消除锻造过程中产生的内应力,细化晶粒,改善钢的切削性能,为后续的加工和热处理做好组织准备。

    • 理由:正火处理能够均匀化组织,降低硬度,提高塑性,从而便于后续的车削加工,并减少加工硬化现象。
  • 车削加工:利用数控车床对正火后的齿轮毛坯进行粗车削。这一步骤旨在高效去除多余材料,形成齿轮的基本几何形状,并提供精确的基准面,为后续的齿形加工奠定基础。

    • 理由:数控车削具有高效率和高精度的特点,能够确保齿轮毛坯的尺寸精度和表面质量,为后续的精密加工提供良好的起点。

齿形加工与热处理

  • 滚齿加工:滚齿是制造渐开线齿轮最常用且高效的展成法加工方法。它通过滚刀与齿轮毛坯的相对运动,连续切削出齿形。滚齿加工具有高效率、高精度和加工范围广的优点,适用于大批量生产。

    • 理由:滚齿能够保证齿形精度,对于小提琴机器人齿轮所需的ISO5级精度,滚齿是实现这一目标的关键步骤。同时,其高效性有助于控制生产成本。
  • 插齿加工(可选):对于内齿轮或对齿形精度有更高要求的半精加工,可以采用插齿加工。插齿机通过插齿刀的往复运动和旋转运动,切削出齿形,能够获得更高的齿形精度和表面质量。

    • 理由:在某些特定情况下,如内齿轮或需要更高齿形精度的齿轮,插齿可以作为滚齿的补充或替代工艺,以满足更严苛的设计要求。
  • 渗碳淬火回火:这是齿轮制造中至关重要的热处理环节。渗碳是将碳原子渗入齿轮表面,使其表面碳含量增加,然后进行淬火和低温回火。处理后,齿轮表面可获得HRC58-62的高硬度,而心部仍保持高韧性。这显著提高了齿轮的耐磨性和疲劳强度,使其能够承受高载荷和频繁的啮合。

    • 理由:小提琴机器人齿轮需要承受高频次的载荷,渗碳淬火回火能够赋予齿轮“外硬内韧”的特性,确保其在长期运行中的可靠性和寿命。这是提高齿轮性能的关键步骤。

精加工与后处理

  • 磨齿精加工:热处理后,齿轮会产生一定的变形。磨齿精加工是消除热处理变形、进一步提高齿形精度(达到ISO5 级甚至更高)的关键步骤。磨齿能够显著降低传动噪音,提高传动的平稳性 。

    • 理由:磨齿是获得高精度齿轮的必要手段,对于小提琴机器人对噪音和运动平稳性的高要求,磨齿能够有效提升齿轮的性能。
  • 珩磨/抛光 (可选):为了进一步改善齿轮表面粗糙度,获得镜面光洁度,可以进行珩磨或抛光处理。这有助于降低齿面摩擦系数,减少噪音,并提高齿轮的传动效率。

    • 理由:在对噪音和传动效率有极致要求的场合,珩磨/抛光能够提供更优异的表面质量,进一步提升齿轮的性能。
  • 质量检测:在齿轮制造的各个阶段,特别是最终阶段,需要进行严格的质量检测。检测内容包括齿形误差、齿向误差、表面粗糙度、硬度、尺寸精度等,以确保齿轮满足设计要求和精度等级。

    • 理由: 质量检测是保证产品质量的最后一道防线,确保每个齿轮都符合小提琴自动演奏机器人的严苛要求。
  • 表面处理:为了进一步提高齿轮的耐磨性、耐腐蚀性和降低摩擦,可以进行额外的表面处理。例如,对于斜齿轮齿条,可以采用三氯甲烷、氯化烷、冰醋酸的混合溶剂进行微溶解抛光,并施加 PTFE(聚四氟乙烯)润滑喷雾涂层,以降低阻力并减少噪音 。

    • 理由:这些表面处理能够优化齿轮的摩擦学性能,延长使用寿命,并进一步降低运行噪音,对于小提琴机器人这种对噪音敏感的应用至关重要。

齿轮工艺链总结

阶段 工艺名称 目的 关键技术/设备 理由
毛坯制备 锻造制坯 细化晶粒,提高力学性能 热模锻设备 确保高强度和疲劳寿命
正火处理 消除内应力,改善切削性能 热处理炉 为后续加工做准备
齿形加工 车削加工 形成基本形状,提供基准面 数控车床 高效去除余量,保证尺寸精度
滚齿加工 形成精确齿形,高效率 数控滚齿机 实现高精度齿形,适用于大批量生产
插齿加工(可选) 适用于内齿轮或更高精度要求 插齿机 满足特殊齿轮或更高精度需求
热处理 渗碳淬火回火 表面高硬度,心部高韧性 渗碳炉、淬火槽、回火炉 显著提高耐磨性和疲劳强度
精加工 磨齿精加工 消除热处理变形,提高齿形精度,降低噪音 磨齿机 确保最终齿轮精度和降低噪音
后处理 珩磨/抛光 (可选) 进一步改善表面粗糙度,降低摩擦 珩磨机、抛光设备 提升传动效率和进一步降低噪音
质量检测 确保满足设计要求和精度等级 齿轮测量中心、硬度计、粗糙度仪 保证产品质量
表面处理 提高耐磨性、耐腐蚀性,降低摩擦和噪音 喷涂设备、抛光设备 优化齿轮性能,延长使用寿命,降低运行噪音

运弓构件工艺链选择

运弓构件工艺

运弓构件主要采用碳纤维复合材料制造,其工艺链需要确保轻量化、高刚度和优异的力学性能:

  • 预浸料铺层与热压罐固化:这是碳纤维复合材料成型的核心工艺。首先,将碳纤维预浸料(预先浸渍树脂的纤维布)按照设计要求进行精确铺层,形成所需的形状。然后,将铺层后的构件放入热压罐中,在高温高压条件下进行固化。这种工艺能够精确控制纤维含量和树脂分布,消除气泡和孔隙,从而获得高性能的复合材料构件。

    • 理由:热压罐固化能够确保复合材料的致密性和力学性能,是制造高强度、高刚度运弓的关键。
  • CNC铣削加工:固化成型后的碳纤维运弓需要进行精密加工,以达到最终的尺寸和形状精度。采用高精度CNC铣床进行精加工,并使用专用的金刚石刀具和优化切削参数,以避免碳纤维材料在加工过程中出现分层、毛刺等缺陷。

    • 理由:CNC铣削能够实现复杂形状的精密加工,确保运弓的几何精度和表面质量,满足机器人对运动精度的要求。
  • 表面处理:完成机械加工后,运弓可以进行喷漆或涂层处理,以提高其表面耐磨性、耐腐蚀性,并改善外观美观度。

    • 理由:表面处理不仅能保护运弓,还能提升其视觉效果,使其更符合产品设计要求。

运弓构件工艺链总结

阶段 工艺名称 目的 关键技术/设备 理由
成型 预浸料铺层与热压罐固化 形成高性能复合材料构件 铺层台、热压罐 确保轻量 化、高刚度和优异力学性能
精加工 CNC 铣削加工 达到最终尺寸和形状精度 高精度 CNC 铣床、金刚石刀具 确保几何精度和表面质量
后处理 表面处理 提高耐磨性、耐腐蚀性,改善外观 喷漆设备、涂层设备 保护构件,提升产品美观度

按弦机构工艺链选择

按弦机构工艺

按弦机构中的按弦杆主要采用不锈钢制造,其工艺链需要确保高精度、高表面光洁度和优异的耐腐蚀性:

  • 精密棒料下料:选用高品质不锈钢精密棒料作为原材料,通过自动送料和精确下料设备,确保原材料的尺寸精度和一致性。

    • 理由:精确的原材料尺寸是后续精密加工的基础,有助于减少材料浪费和加工时间。
  • 数控车削与铣削:利用数控车床对不锈钢棒料进行车削,形成按弦杆的圆柱外形、台阶和螺纹等特征。对于键槽等非旋转对称特征,则通过数控铣削完成。数控加工能够确保高精度和高效率。

    • 理由:数控车铣复合加工能够一次装夹完成多道工序,提高加工效率和精度,确保按弦杆的几何尺寸符合设计要求。
  • 热处理:根据不锈钢的具体牌号和性能要求,可以进行固溶处理或淬火回火处理。固溶处理旨在消除加工应力,提高材料的塑性和韧性;淬火回火则可以进一步优化材料的力学性能和耐腐蚀性。

    • 理由:热处理能够优化按弦杆的内部组织,提高其强度、硬度和耐腐蚀性,确保其在长期使用中的稳定性和可靠性。
  • 精密研磨**/**抛光:为了获得极高的表面光洁度,按弦杆需要进行精密研磨或抛光处理。可以使用无心磨床或专用抛光设备,使按弦杆表面达到镜面效果。

    • 理由:极高的表面光洁度能够显著降低按弦杆与导向套之间的摩擦阻力,提高按弦动作的灵敏度和响应速度,同时也能提升产品的外观质量。

按弦机构工艺链总结:

阶段 工艺名称 目的 关键技术/设备 理由
原材料 精密棒料 确保原材料尺寸精 自动送料下料机 减少材料浪 费,提高加工
准备 下料 度和一致性 效率
机械加工 数控车削与铣削 形成按弦杆的几何形状和特征 数控车床、数控铣床 确保高精度和高效率
热处理 固溶处理/淬火回火 优化力学性能和耐腐蚀性 热处理炉 提高强度、硬度和耐腐蚀性
精加工 精密研磨/抛光 获得极高表面光洁度,降低摩擦 无心磨床、抛光设备 提升运动灵敏度,改善外观质量

通过上述详细的工艺链选择和组合,可以确保小提琴自动演奏机器人中各个关键构件的制造质量,从而为机器人的高性能、高精度和长寿命运行提供坚实的基础。

模型仿真及校核

有限元分析(FEA)

有限元分析是一种数值计算方法,用于求解复杂结构在载荷作用下的应力、应变、变形和振动等问题。在小提琴自动演奏机器人中,有限元分析主要用于关键构件的强度、刚度和疲劳寿命评估。

  • 应力应变分析:对齿轮、弓臂和按弦杆等关键构件进行应力应变分析,可以识别高应力集中区域,评估构件在最大载荷下的安全裕度,确保其强度满足设计要求,避免发生塑性变形或断裂。

  • 变形分析:计算构件在载荷作用下的变形量,验证其刚度是否满足要求。对于高精度机器人,微小的变形都可能导致定位误差,因此严格控制变形量至关重要。

  • 疲劳寿命预测:小提琴自动演奏机器人需要长时间、高频率地运行,因此关键部件的疲劳寿命是重要的考量因素。有限元分析可以预测齿轮、轴承等易疲劳部件的疲劳寿命,指导材料选择、结构优化和维护计划,从而延长机器人的使用寿命。

  • 仿真软件:常用的有限元分析软件包括 ANSYS、Abaqus 和 SolidWorks Simulation等。这些软件提供了强大的网格划分、材料模型和载荷施加功能,能够进行精确的结构分析,本次仿真使用Fusion 360 内置的 ANSYS插件进行仿真。

仿真结果

以下是齿轮与齿条啮合的仿真分析结果图示例,这些图表直观地展示了齿轮系统在工作状态下的性能表现,是验证齿轮系统性能和安全性的重要依据。

仿真结果

通过上述全面的模型仿真和校核,可以确保小提琴自动演奏机器人的齿轮系统及其他关键构件在设计阶段就达到高标准,为最终产品的成功开发奠定坚实基础。

可持续性分析

小提琴自动演奏机器人项目不仅在技术和艺术领域具有创新意义,其可持续性也是一个值得深入探讨的方面。可持续性分析主要关注项目在环境和经济两个维度上的可行性,以评估其长期发展潜力和社会价值。

环境可行性分析

环境可行性分析旨在评估小提琴自动演奏机器人在其生命周期内对环境可能产生的影响,并探讨如何通过设计和制造优化来降低这些影响。

材料选择与资源消耗

  • 积极影响:项目在材料选择上,如齿轮材料推荐合金钢(20CrMnTi)和工程塑料(POM),弓臂采用碳纤维复合材料,按弦机构采用不锈钢。这些材料的选择在保证性能的同时,也考虑了其可回收性和耐用性。例如,合金钢和不锈钢是可回收材料,有助于减少原生资源消耗。工程塑料如POM 在某些应用中可替代金属,降低能耗和材料消耗。
  • 潜在挑战与优化:碳纤维复合材料的回收目前仍面临挑战,其生产过程也相对能耗较高。未来可以探索更环保的碳纤维生产技术,或开发易于回收的复合材料。此外,在制造过程中应尽量减少废料产生,并对废料进行分类回收。

能源消耗

  • 运行能耗:机器人运行过程中,驱动电机(伺服电机、步进电机、BLDC电机)是主要的能耗来源。通过优化控制算法,提高电机效率,以及采用低功耗的传感器和控制系统,可以有效降低机器人的运行能耗。

  • 制造能耗:齿轮的锻造、热处理、机加工以及碳纤维复合材料的固化等工艺都需要消耗大量能源。选择能效更高的制造设备,优化工艺流程,例如采用精密锻造减少后续机加工量,可以降低整体制造能耗。

噪音与振动

  • 积极影响:项目设计中特别强调了低噪音运行,例如采用斜齿轮、优化齿形、精密磨齿和表面处理(PTFE 涂层)等措施,旨在将噪音控制在 35dB以下。这不仅提升了演奏音质,也降低了机器人运行对周围环境的噪音污染,尤其是在家庭或教育场所使用时,其环境友好性更高。

  • 潜在挑战与优化:尽管采取了降噪措施,但机械运动仍会产生一定噪音。未来可以进一步研究主动降噪技术,或采用更先进的静音材料和结构设计。

废弃物管理

  • 设计优化:在设计阶段就考虑产品的可拆卸性和模块化,便于报废时对不同材料进行分类回收。例如,弓毛部分的模块化设计便于更换和回收。

  • 生命周期评估(LCA):进行全面的生命周期评估,从原材料获取、制造、使用到报废回收的全过程进行环境影响分析,识别环境热点,并制定相应的改进策略。

经济可行性分析

经济可行性分析旨在评估小提琴自动演奏机器人项目的经济效益、市场潜力、成本控制和商业模式,以确保其可持续发展。

市场潜力与需求

  • 艺术与教育市场:小提琴自动演奏机器人面向艺术表演、音乐教育和个人娱乐等多个市场。在音乐教育领域,它可以作为辅助教学工具,降低学习门槛,激发学习兴趣。在艺术表演领域,可以拓展新的表演形式和创作可能性。随着人们对高品质音乐体验和个性化娱乐需求的增长,市场潜力巨大。

  • 技术发展趋势:机器人技术、人工智能和精密制造的不断进步,将进一步降低生产成本,提高产品性能,从而扩大市场接受度。

成本控制与效益

  • 研发成本:项目涉及多学科交叉,研发投入相对较高。然而,通过模块化设计、标准化部件和高效的仿真校核,可以有效缩短研发周期,降低试错成本。

  • 制造成本:齿轮的精密加工、碳纤维复合材料的成型等工艺成本较高。但随着制造技术的成熟和规模化生产,单位成本有望降低。例如,3D打印技术在小批量生产或原型制造中具有成本优势,未来可探索其在某些部件上的应用以降低成本。

  • 运营与维护成本:高耐用性材料和精密设计有助于降低机器人的故障率和维护成本。模块化设计也便于部件更换和维修,进一步降低了长期运营成本。

商业模式与盈利

  • 产品销售:直接销售小提琴自动演奏机器人给个人用户、音乐学院、演出团体等。

  • 服务模式:提供机器人租赁、定制化演奏服务、音乐教育课程等。

  • 技术授权:将核心技术授权给其他制造商,扩大技术影响力并获取收益。

  • 内容生态:开发配套的乐谱库、演奏技巧库和 AI优化算法,形成内容生态,增加用户粘性。

综上所述,小提琴自动演奏机器人项目在环境和经济方面都具备良好的可行性。通过在设计、制造和运营全生命周期中融入可持续发展理念,并积极探索多元化的商业模式,该项目有望实现长期的社会、环境和经济效益。

总结

小提琴自动演奏机器人项目是精密机械、电子、控制和人工智能等多学科深度融合的典范,旨在模拟人类小提琴演奏家的精湛技艺,实现音乐的自动化演奏。本报告围绕该项目,详细阐述了其核心传动部件——齿轮系统的设计与制造,并深入探讨了相关材料选择、关键构件设计、工艺链选择、模型仿真及校核,以及项目的可持续性。

项目背景与意义部分强调了机器人技术在艺术领域的独特潜力,以及小提琴演奏对人类生理和心理素质的极高要求。该项目不仅推动了精密机械、控制系统和人工智能等前沿技术的发展,更在创新音乐教育、拓展艺术表现形式、传承文化遗产以及培养复合型人才方面具有深远意义。其发展趋势预示着更高精度、智能化、多模态融合和模块化定制的未来。

在材料选择方面,报告针对齿轮、弓臂构件和按弦机构等关键部件,推荐了合金钢(如20CrMnTi)、碳纤维复合材料和不锈钢(如304/316L)等高性能材料,并详细阐述了选择理由。这些材料的选择旨在满足机器人对高强度、高刚度、轻量化、低噪音和耐腐蚀等严苛性能要求,并通过材料对比分析,展现了在性能与成本之间进行权衡的综合考量。

关键构件设计与建模部分,以齿轮为例,详细介绍了其基本参数计算、设计理念(高精度传动、低噪音运行、紧凑型设计、长寿命可靠性)和斜齿轮齿条的优化目标。同时,弓臂构件和按弦机构的设计也充分考虑了轻量化、多自由度控制、高精度定位和快速响应等特性。装配模型部分则清晰地展示了机器人的工作原理流程、功能特点和技术意义,凸显了各部件的协同作用。

工艺链选择是确保机器人性能的关键环节。报告详细阐述了齿轮的完整工艺链,包括锻造制坯、正火、车削、滚齿、渗碳淬火回火、磨齿精加工以及珩磨/抛光和表面处理等,并强调了每一步工艺对齿轮性能提升的重要性。弓臂构件的碳纤维复合材料制造工艺(预浸料铺层与热压罐固化、CNC铣削、表面处理)和按弦机构的精密制造工艺(精密棒料下料、数控车铣、热处理、精密研磨/抛光)也得到了详细介绍,展现了多工艺结合以实现高性能部件制造的策略。

模型仿真及校核部分,强调了运动学仿真、动力学仿真和有限元分析在设计验证、安全评估和优化指导中的重要作用。通过这些仿真手段,可以在物理样机制造前发现并解决潜在问题,确保齿轮系统及其他关键构件在设计阶段就达到高标准,从而提高设计的可靠性和效率。

最后,可持续性分析从环境和经济两个维度评估了项目的可行性。在环境方面,通过材料选择、能源消耗优化和噪音控制等措施,努力降低对环境的影响。在经济方面,项目具有广阔的市场潜力,并通过成本控制、多元化商业模式和人才培养,确保其长期可持续发展。

总而言之,小提琴自动演奏机器人项目不仅是工程技术与艺术的完美结合,更是一个系统性、多学科交叉的复杂工程实践。通过对齿轮系统及其他关键构件的精细设计、材料优选、先进制造工艺的应用以及全面的仿真校核,该项目为实现高精度、高性能的自动化音乐演奏奠定了坚实基础,展现了未来机器人技术在非传统领域的巨大应用前景和价值。

参考文献

  1. 黄信行, 黄淑芳, 李文鸿, 邱燕玉, 游凯程, 温扬圣. 小提琴机器人-六轴机器手臂的整合运用. 机械工业网 , 2012(4): 124-135.[https://www.automan.tw/tw/magazine-period/54/982]

  2. 抗疲劳齿轮的研究进展:提升机械可靠性的新方法. 温岭市螺伞齿轮厂.[http://www.lsgears.com/NewsDetail.aspx?ID=78]

  3. 齿 轮 制 造 101 : 齿 轮 生 产 过 程 指 南 . RapidDirect.[https://www.rapiddirect.com/zh-CN/blog/gear-manufacturing/]

  4. 某车型汽车差速器结构设计及其齿轮的性能研究. hanspub.org, 2024.[https://pdf.hanspub.org/mos2024135_102571870.pdf]

  5. 协作机器人结构设计及齿轮传动系统仿真. hanspub.org, 2024.[https://www.hanspub.org/journal/paperinformation?paperid=79757]

  6. 粉末锻造齿轮材料的组织与性能研究. 粉末冶金技术, 2020.[https://pmt.ustb.edu.cn/cn/article/id/fmyjjs202002006]

  7. 齿 轮 加 工 工 艺 流 程 分 析 . ResearchGate, 2025.[https://www.researchgate.net/publication/384875475_chilunjiagonggongyiliuchengfenxi]

  8. 运用力量控制技术于提升小提琴机器人演奏音色之探讨. 华艺线上图书馆.[https://www.airitilibrary.com/Article/Detail/U0017-2211202315312089]

  9. 工业机器人领域谐波齿轮传动应用及运动精度分析. 中国学术期刊网络出版总库.[https://cpfd.cnki.com.cn/Article/CPFDTOTAL-KDIE201710001126.htm]

  10. 齿轮用什么材料,齿轮常用材料有哪几种. 知乎专栏, 2023.[https://zhuanlan.zhihu.com/p/666265403]

SmartRobot扫地机器人

1 项目需求分析

1.1 项目背景介绍

随着科技的快速发展和人们生活水平的不断提高,智能家居设备正逐渐成为现代家庭的重要组成部分。在快节奏的生活中,人们对于家务自动化的需求日益增长,尤其是日常清洁工作,既耗时又费力。传统的清洁方式(如手动扫地、拖地)已无法满足现代家庭对高效、便捷生活的追求。扫地机器人作为智能清洁领域的代表性产品,凭借其自主导航、智能避障、自动回充等先进功能,能够有效减轻用户的清洁负担,提升生活品质。近年来,随着人工智能(AI)、传感器技术、路径规划算法以及物联网(IoT)技术的进步,扫地机器人的性能与智能化水平显著提升,市场需求持续扩大。

本项目基于市场上成熟的海尔扫地机器人硬件方案,通过复刻和优化现有设计,重点研究其核心功能模块,包括运动控制、传感器数据处理、路径规划算法等。在复刻过程中,我们将深入分析现有扫地机器人的优缺点,并结合实际应用场景进行针对性改进,例如优化清洁路径、提升避障精度、降低功耗等。该项目的实施不仅有助于深入理解扫地机器人的关键技术,还能为后续的二次开发和功能拓展奠定基础。通过复刻与优化,我们期望在降低研发成本的同时,提升产品的稳定性和用户体验,为智能家居设备的本地化应用提供参考方案。

图1.1:项目用海尔扫地机器人产品效果图

1.2 设计需求与相关指标确定

基于以上项目背景,结合硬件实机情况与初步调研结果,总结出了以下几条功能设计需求,并本着定量化的思想,在每一条功能需求上都提出了对应的指标:

  1. 良好的避障功能:扫地机器人应集成有效传感器,以实现对障碍物的检测和避让,确保在复杂环境中的自主导航。除此之外,为保证最大程度的清洁,不放过任何一个清洁死角,我们希望机器人在触碰到墙体后,能够尽可能地以低速(不超过0.1m/s)贴近墙体行进。

    具体而言,在碰撞传感器触发时,机器人需要在50ms内作出反应,先后退离开墙体区域,在原地进行小角度旋转后继续向前行进,再次碰到墙体时继续做出这样的反应。

  2. 防坠落能力:设备应具备悬崖检测传感器(本项目中硬件设备为红外传感器),以识别楼梯边缘或高度差,防止从高处坠落。

    具体而言,我们希望在悬崖传感器检测到机器人位于高度垂直落差大于5cm的界面时,能够在50ms内作出反应,先紧急制动,接着低速后退离开悬崖区域,在原地旋转改变运动方向后继续行进,以最大限度地保护机器人地工作安全,避免损坏。

  3. 运动功能:扫地机器人应能根据家庭环境尺寸,实现高精度的运动控制。

    具体而言,我们希望基于编码器利用PID控制器实现高精度的电机调控与运动控制,在直线行驶1m距离的测试中总偏角应不超过5°、距离误差不超过2cm.

  4. 用户通讯控制:设备应配备直观的用户界面,包括按键和指示灯,以便用户轻松启动、暂停和控制扫地机器人。应支持通过蓝牙连接,实现电脑端应用程序的远程控制,包括启动/停止自动清扫、遥控机器人(基于实时遥控信号调整运动状态)、监控电池状态等。

    具体而言,希望在中低速运动条件下(0.05~0.15m/s)实现频率至少10Hz的实时位置坐标与姿态(偏转角度)的数据解算(位置坐标各方向误差<5cm、偏转角度误差<5°),并将数据传至用户界面,根据已有的行进位姿数据进行实时路径可视化,基于已有建图结果实现全覆盖路径规划。

1.3 项目分工

PCB设计及硬件维护:余虹鋈 学号:20222957
嵌入式程序:王冲 学号:20224841
上位机及嵌入式程序:肖范熠 学号:20223984
算法及嵌入式程序:许晶华 学号:20224546

1.4 本章小结

本章对于扫地机器人项目的应用背景进行了介绍,并基于实机测试与调研结果确定了产品的功能需求指标,这为后续的项目开发起到了良好的引领和指导作用,后续的项目功能研发也将紧密围绕这些功能需求点与性能指标进行展开。

2 PCB电路设计

2.1 设计目标

本次PCB设计面向智能扫地机器人控制系统,目标是实现电源管理、电机驱动、传感器输入处理、MCU外围支持、IMU通信等功能模块的稳定集成,并兼顾成本控制、布局合理性与后期调试便利性。

2.2 PCB总体布局与功能划分

PCB采用双面布局,充分利用板面空间,模块化划分如下:

  • 电源管理区:提供从锂电池到各类电压等级(如5V/3.3V)的转换;

  • 单片机核心区:部署STM32及其基本外围电路;

  • 电机驱动区:负责控制左右驱动电机及各个毛刷电机和吸尘电机;

  • 传感器接口区:用于悬崖、碰撞等传感器信号接入与处理;

  • 辅助保护与调试区:如状态指示灯、串口接口等。

图2.1:单片机外围电路

图2.2:传感器、电机电路

图2.3:电源相关电路

2.3 电源管理电路设计

2.3.1 充电电路设计

电源模块采用多级稳压设计,满足不同子系统电压需求。在芯片选型方面,我们选择使用SLM6900降压模块,其特性如下:

  • 输出固定16.8V,无需外围分压,适合三到四节锂电池充电;

  • 高效率(5A负载),封装易焊,适配性好;

  • 可通过ADC分压获得充电状态,便于MCU监控;

  • NCHRG(充电中低电平)和NSTDBY(充满低电平)引脚可直接驱动LED简化状态监控,红绿LED指示灯直观反馈。

相较于另一常见的充电芯片BQ24610RGER而言,拥有更易焊接的封装方式和更便宜的价格。

图2.4:充电保护电路

图2.5:降压模块选型:SLM6900

图2.5:降压模块选型:BQ24610RGER

2.3.2 降压电路设计

在降压电路设计中,我们选择采用TPS54531作为降压芯片,该芯片具有以下优点:

  • 支持从低至锂电池单节电压到24V工业电源的广泛输入,适配多种电源场景(如车载系统、分布式电源);

  • 轻载时自动切换至脉冲跳跃模式,静态电流低至1μA(关断状态),显著提升电池供电设备的待机时长;

  • 逐周期电流限制、频率折返、热关断(165℃触发)三重保护。过压瞬态保护抑制启动电压过冲;

  • 封装焊接简易;

  • 成本低,三元一个,单个可售。

图2.6:16.8v降5v电路

在选型时,我们还考虑了其他型号的降压芯片,其中TPS56637适用于4.5V至28V,价格五元一个,与选用的TPS54531价格相近,但是其封装在焊接过程中出问题的概率更大,综合考虑不予选用;而LM2679S性能强悍,但是价格高昂,五十元一个,不予选用。

图2.7:降压芯片选型:TPS54531

图2.7:降压芯片选型:TPS56637

图2.7:降压芯片选型:LM2679S

线性稳压器方面,由于在这一步仅需5v降到3.3v压差小,因此选用合适、最廉价的芯片即可,不必追求过高性能。这里我们选用AMS1117-3.3线性稳压器,其主要特性如下:

  • 为MCU提供3.3V电压;

  • 精度±1%,外围电路极简,仅需输入/输出电容。

图2.8:15v降3.3v电路

2.4 电机驱动与控制逻辑设计

2.4.1 运动电机驱动芯片选型对比

由于驱动直流电机需要的电流很大,单片机I/O的驱动能力是远远达不到的。因此需要使用专用的电机驱动芯片。芯片驱动能力及使用复杂度简单对比如下:

  • A4950:8~40V,高压大电流,外围适中;

  • DRV8833:2.7~10.8V,体积小,适合低压,使用简单;

  • TB6612FNG:2.5~13.5V,性能稳定,体积小;

  • L298N:3~48V,体积大,驱动强,但外围复杂。

图2.9:DRV8833芯片

DRV8833电机驱动芯片是基于H桥电路的,芯片中共有两个全H桥,因此最多可以同时驱动两个直流电机或一个步进电机。电源供电电压2.7~10.8V,每个H桥输出的均方根(RMS)电流为1.5A,峰值可达2A。内置过热保护和用户可调的限流保护电路。

图2.10:DRV8833芯片功能框图

框图中也包含了DRV8833芯片外部所需要的元件,主要是三个电容以及两个电流检测电阻(电阻可不接)。当温度过高,温度检测保护模块会使nFAULT所接的FET导通拉到低电平,同时H桥转成衰减模式,不再给电机供电。

图2.11:TB6612FNG芯片

TB6612FNC是东芝半导体公司的一款电机驱动芯片,也是集成了两个全H桥。在应用上基本与DRV8833相似,但性能更好,价格也相对较高。电源供电电压2.5~13.5V,H桥输出的平均电流1.2A,最大可到3.2A。(可见驱动能力比DRV8833略强)内置过热保护和低压检测关断电路,PWM控制的频率可达100kHZ。

图2.12:TB6612FNG芯片功能框图

上面框图中画出了使用该芯片需要外接的元件(4个滤波电容)。从中可发现,其与DRV8833最大不同即在输入控制上,除了输入1和输入2,还有一个PWM输入脚。

图2.13:A4950芯片

A4950是美国埃戈罗公司生产的一款单H桥电机驱动芯片。因此网上卖的模块多是使用两块芯片以达到可以控制两个直流电机的能力。电机驱动电压:8~40V,输出最大电流可达3.5A;内置过温保护,短路保护和可选择的过流保护。

图2.14:A4950芯片功能框图

通过引脚说明和功能框图可看出,此芯片不同之处有:只有单H桥,因此引脚较少;限流比较的参考电压由外部给出(VREF脚);因此限流值Isense=Vref/10/Rsense。如上面的模块中,Vref接5V,Rsense为R250精密检测电阻(0.25Ω),因此限流值为2A。当IN1和IN2均保持低电平1ms,芯片进入待机模式。而不是通过引脚直接控制。经过对比发现,此芯片的驱动逻辑与上述的DRV8833PWP芯片完全一致。

图2.15:L298N电机驱动模块

L298N是ST公司的一款电机驱动芯片,也是集成了双H桥,但与上面两个略有不同。该芯片适配的电机驱动电压为3~48V;可持续工作的输出电流为2A,峰值可达3A。如上图,L298N模块明显比前两个芯片模块外接的元件多,这与L298N的内部结构有关。如上图,由于该芯片在H桥上的损耗严重发热较明显(饱和压降大),需要加装散热片,因此在使用上比前两个芯片复杂,体积也相对较大。

图2.16:L298N芯片功能框图

如上图所示:L298N的内部功能很多都类似,比如电流检测,H桥驱动,外接电容等。主要区别在于L298N的H桥采用了BJT而不是MOSFET。这就直接导致没有寄生二极管,无法像前两个芯片一样实现续流。因此需要外接8个续流二极管。因为频率不高,选用普通的整流二极管即可(如1N4007)。如下图所示:

图2.17:L298N芯片连接示意图

此芯片的电流检测脚Sense-X并不像前面的芯片,其没有在内部进行电压比较从而限流,从数据手册上看,需要一个L297芯片配合进行限流。因此一般直接接地,不进行限流。

综上所述,对比四款常用的电机驱动芯片,可以得到如下结论:

  • 三款芯片的内部原理和控制方式大同小异;

  • 可通过两个H桥输出的并联控制一个直流电机,这样最大驱动电流可翻倍,这在芯片的数据手册中均有说明;

  • 以上三种芯片驱动能力排序:DRV8833<BT6612<A4950≈L298N;

  • DRV8833、TB6612和A4950的体积小,外接元件少,使用简单;L298N体积大,外接元件多,使用相对复杂;

在选择这种集成H桥芯片时,需要考虑的参数有:可承受的工作电流要大于电机的堵转电流,防止堵转时驱动芯片烧毁;导通电阻尽可能小,减少芯片的发热损耗。综合这些因素考虑,本项目最终选用A4950作为主驱动芯片,其适配性与功率支持表现均较为优异。

2.4.2 其余电机驱动控制逻辑设计

在刷子电机和风机驱动方面,选择使用74HC14D来对单片机信号进行增强,从而达到控制的目的,因为这些电机不需要特别精细的控制,这样的选择可以最低限度实现功能的同时,减少PCB版面空间的消耗。

这一部分的驱动电路设计主要有以下要点:

  • 74HC14D 施密特反相器用于整形PWM信号,防抖处理;

  • NCE6020AK MOSFET 用作电机功率开关管;

  • 二极管续流保护,防止感性反冲。

图2.18:74HC14D驱动电机电路

在上述电路中,MCU输出的PWM信号经R178/R188限流后输入74HC14D,整形后的反相信号驱动NCE6020AK栅极。同时,74HC14D输出端应通过栅极电阻(典型值10-100Ω)连接MOSFET栅极,连接形式为:VCC15V→ MOSFET漏极 → 源极 → 100mΩ采样电阻 → 电机 →GND。当电机停转时,电感能量经B340A续流二极管释放。该电路还包含过流保护与电压尖峰抑制的功能,将采样电阻压差送ADC6,MCU触发关断,B340A续流二极管吸收反向电动势。

2.5 单片机外围电路

本项目的MCU采用STM32系列芯片,外围电路主要包括以下几个部分:

  • 晶振电路(8MHz或16MHz);

  • 上拉/下拉电阻;

  • 下载接口(SWD);

  • 启动配置引脚;

  • 调试指示LED、UART通讯接口等。

图2.19:单片机外围电路

2.6 PCB布局设计说明

2.6.1 正面布局

以MCU为核心,周边辐射式布局传感器与电源模块,整体走线遵循横平竖直的原则,下半区集中走线,保证PCB美观。

图2.20:PCB正面布局

2.6.2 反面布局

主电源走线、地平面铺铜,布线优化短路径,增强抗干扰能力,布局1206封装的电阻,充分保证正面的布局空间。

图2.21:PCB反面布局

2.6.3 实物图展示

实际焊接效果良好;各模块标识清晰,便于调试。

图2.22:PCB实物图

2.7 本章小结

本次PCB设计实现了功能完备、结构合理的控制系统硬件支持。后续根据产品实际需要,若需要进一步改进,可以从如下方面入手:

1、优化板面尺寸,提升布局紧凑性;

2、考虑EMC设计与测试,提升抗干扰性能;

3、引入模块化可插拔接口,便于维护升级。

3 硬件外设程序设计

3.1 设计目标

本章节内容主要介绍PCB上的硬件外设的程序设计以及功能接口的使用方法,包含LED、按键、IMU、电机驱动、编码器、悬崖传感器、电压读取以及碰撞传感器的相关内容。

3.2 LED

项目中共有四颗可供使用的空闲LED(LED0~1),为方便使用、扩展进行了统一的封装。目前四颗均为上拉连接低电平触发。

图3.1:LED部分电路原理图

3.2.1 硬件描述

为了程序的规范性和可拓展性,我们需要在main文件中使用上述函数对LED进行硬件描述:

1
2
3
4
5
typedef struct {
GPIO_TypeDef \*GPIOx; // GPIO 端口
uint16_t GPIO_Pin; // GPIO 引脚
LED_ActiveMode ActiveMode; // LED 工作模式(主动低或主动高)
} LED_HandleTypeDef;

其中工作模式如下:

1
2
3
4
typedef enum {
LED_ACTIVE_LOW = 0, // LED 在低电平时点亮(即连接到 VCC)
LED_ACTIVE_HIGH // LED 在高电平时点亮(即连接到 GND)
} LED_ActiveMode;

示例:LED0连接在PD端口0号引脚,低电平触发,则如下:

1
2
3
4
5
LED_HandleTypeDef LED0 = {
.GPIOx = GPIOD,
.GPIO_Pin = GPIO_PIN_0,
.ActiveMode = LED_ACTIVE_LOW
};

3.2.2 初始化

效果为根据描述的硬件连接匹配对应的电平输出。对应函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
/* LED 初始化函数 */
void LED_Init(LED_HandleTypeDef *led) {
// 根据LED的工作模式(主动低或主动高)初始化LED
if (led->ActiveMode == LED_ACTIVE_LOW) {
// 如果是主动低模式,初始化时将LED设置为关闭(对应的GPIO引脚输出高电平)
HAL_GPIO_WritePin(led->GPIOx, led->GPIO_Pin, GPIO_PIN_SET);
} else {
// 如果是主动高模式,初始化时将LED设置为关闭(对应的GPIO引脚输出低电平)
HAL_GPIO_WritePin(led->GPIOx, led->GPIO_Pin, GPIO_PIN_RESET);
}
}

示例:初始化LED0,则可直接调用该函数:

1
LED_Init (&LED0);

3.2.3 点亮LED

效果为点亮LED。对应函数代码如下:

1
2
3
4
5
6
7
8
9
10
/* 点亮LED */
void LED_On(LED_HandleTypeDef *led) {
if (led->ActiveMode == LED_ACTIVE_LOW) {
// 如果是主动低模式,点亮LED需要将GPIO引脚输出低电平
HAL_GPIO_WritePin(led->GPIOx, led->GPIO_Pin, GPIO_PIN_RESET);
} else {
// 如果是主动高模式,点亮LED需要将GPIO引脚输出高电平
HAL_GPIO_WritePin(led->GPIOx, led->GPIO_Pin, GPIO_PIN_SET);
}
}

示例:点亮LED0,则可直接调用该函数:

1
LED_On (&LED0);

3.2.4 熄灭LED

效果为熄灭LED。对应函数代码如下:

1
2
3
4
5
6
7
8
9
10
/* 熄灭LED */
void LED_Off(LED_HandleTypeDef *led) {
if (led->ActiveMode == LED_ACTIVE_LOW) {
// 如果是主动低模式,熄灭LED需要将GPIO引脚输出高电平
HAL_GPIO_WritePin(led->GPIOx, led->GPIO_Pin, GPIO_PIN_SET);
} else {
// 如果是主动高模式,熄灭LED需要将GPIO引脚输出低电平
HAL_GPIO_WritePin(led->GPIOx, led->GPIO_Pin, GPIO_PIN_RESET);
}
}

示例:初始化LED0,则可直接调用该函数:

1
LED_Off (&LED0);

3.2.5 翻转LED

效果为转换当前LED的亮灭状态。对应函数代码如下:

1
2
3
4
5
/* 切换LED状态(开/关) */
void LED_Toggle(LED_HandleTypeDef *led) {
// 切换LED状态(如果是开就关,反之亦然)
HAL_GPIO_TogglePin(led->GPIOx, led->GPIO_Pin);
}

示例:初始化LED0,则可直接调用该函数:

1
LED_Toggle (&LED0);

3.3 按键

如下图所示,项目有两个可供使用的微动按键(KEY0、KEY1),均为上拉连接低电平触发,配置为GPIO的Input模式。

图3.2:按键部分电路原理图

3.3.1 硬件描述

硬件描述结构体如下:

1
2
3
4
5
6
7
typedef struct {
GPIO_TypeDef *GPIOx; // GPIO端口(如GPIOD)
uint16_t GPIO_Pin; // GPIO引脚(如GPIO_PIN_11)
Key_ActiveMode ActiveMode; // 有效电平模式
Key_State CurrentState; // 当前状态
uint32_t LastTick; // 最后一次状态变化时的时间戳
} Key_HandleTypeDef;

其中,按键状态又有如下枚举:

1
2
3
4
5
6
typedef enum {
KEY_STATE_RELEASED = 0, // 松开状态
KEY_STATE_PRESSED, // 按下状态
KEY_STATE_JUST_PRESSED, // 刚刚按下(边沿)
KEY_STATE_JUST_RELEASED // 刚刚松开(边沿)
} Key_State;

示例:直接描述两个按钮如下,均为低电平触发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Key_HandleTypeDef key[] = {
{
// KEY0(PD11)
.GPIOx = GPIOD,
.GPIO_Pin = GPIO_PIN_11,
.ActiveMode = KEY_ACTIVE_LOW
},
{
// KEY1(PE15)
.GPIOx = GPIOE,
.GPIO_Pin = GPIO_PIN_15,
.ActiveMode = KEY_ACTIVE_LOW
}
};

3.3.2 按键初始化

效果为重置按键状态,并且更新时间戳。对应函数代码如下:

1
2
3
4
5
/* 按键初始化 */
void Key_Init(Key_HandleTypeDef *key) {
key->CurrentState = KEY_STATE_RELEASED;
key->LastTick = HAL_GetTick();
}

示例:初始化key0:

1
Key_Init (&key[0]);

3.3.3 按键更新

效果为检测并更新按键的按下状态。对应函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* 按键更新(需要定期调用) */
void Key_Update(Key_HandleTypeDef *key) {
uint32_t current_tick = HAL_GetTick();
// 读取当前按键引脚状态
GPIO_PinState pin_state = HAL_GPIO_ReadPin(key->GPIOx, key->GPIO_Pin);
uint8_t is_pressed = (key->ActiveMode == KEY_ACTIVE_LOW) ?
(pin_state == GPIO_PIN_RESET) :
(pin_state == GPIO_PIN_SET);
// 按键状态机
switch (key->CurrentState) {
case KEY_STATE_RELEASED:
if (is_pressed) {
if (current_tick - key->LastTick > DEBOUNCE_DELAY_MS) {
key->CurrentState = KEY_STATE_JUST_PRESSED;
key->LastTick = current_tick;
}
}
break;
case KEY_STATE_JUST_PRESSED:
key->CurrentState = KEY_STATE_PRESSED;
break;
case KEY_STATE_PRESSED:
if (!is_pressed) {
if (current_tick - key->LastTick > DEBOUNCE_DELAY_MS) {
key->CurrentState = KEY_STATE_JUST_RELEASED;
key->LastTick = current_tick;
}
}
break;
case KEY_STATE_JUST_RELEASED:
key->CurrentState = KEY_STATE_RELEASED;
break;
}
}

示例:检测并更新key0:

1
Key_Update (key[0]);

3.3.4 获取按键状态

效果为配合按键状态更新函数,会返回Key_State中的状态,进行逻辑编写。

1
2
3
4
/* 获取当前按键状态 */
Key_State Key_GetState(Key_HandleTypeDef *key) {
return key->CurrentState;
}

示例:读取key1:

1
Key_State state = Key_GetState(&key[1]);

3.4 IMU

本次项目采用JY901S九轴姿态传感器,连接到USART2与单片机通信,使用中断的方式来接收数据,配置如下,根据与算法同学沟通,共需用到加速度、角速度、欧拉角每组三个共九个数据。

图3.3:MCU与IMU间连接示意图

3.4.1 IMU初始化

功能为配置缓冲区并开启首次接收中断。对应函数代码如下:

1
2
3
4
5
void JY901S_Init(UART_HandleTypeDef *huart){
jy_uart = huart;
static uint8_t rx_buf[FRAME_LEN];
HAL_UART_Receive_IT(jy_uart, rx_buf, FRAME_LEN); // 开启首次接收中断
}

示例:初始化IMU:

1
JY901S_Init(&huart2);

3.4.2 IMU数据解析

功能为根据官方文档解算IMU回传的数据。对应函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void JY901S_UART_RxHandler(uint8_t *data){
if (data[0] == 0x55) {
switch (data[1]) {
case 0x51: // 加速度
imu_data.ax = (short)(data[3] << 8 | data[2]) / 32768.0f * 16;
imu_data.ay = (short)(data[5] << 8 | data[4]) / 32768.0f * 16;
imu_data.az = (short)(data[7] << 8 | data[6]) / 32768.0f * 16;
break;
case 0x52: // 角速度
imu_data.gx = (short)(data[3] << 8 | data[2]) / 32768.0f * 2000;
imu_data.gy = (short)(data[5] << 8 | data[4]) / 32768.0f * 2000;
imu_data.gz = (short)(data[7] << 8 | data[6]) / 32768.0f * 2000;
break;
case 0x53: // 欧拉角
imu_data.roll = (short)(data[3] << 8 | data[2]) / 32768.0f * 180;
imu_data.pitch = (short)(data[5] << 8 | data[4]) / 32768.0f * 180;
imu_data.yaw = (short)(data[7] << 8 | data[6]) / 32768.0f * 180;
break;
default:
break;
}
}
HAL_UART_Receive_IT(jy_uart, data, FRAME_LEN); // 重新开启下一次接收中断
}

示例:定义中断回调

1
2
3
4
5
6
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
if (huart->Instance == USART2) {
JY901S_UART_RxHandler(jy_rx_buf);
HAL_UART_Receive_IT(&huart2, jy_rx_buf, 11);
}
}

3.4.3 获取IMU数据

1
2
3
IMU_Data_t* JY901S_GetData(void){
return &imu_data;
}

示例:在主循环中获取数据:

1
imu = JY901S_GetData();

此处imu作全局变量定义(IMU_Data_t *imu;),便于debug监视。

3.5 电机驱动

左右电机分别由一个A4950电机驱动来进行驱动,该芯片需要两路PWM来差值控制,左右驱动分别接至TIM1和TIM9的两路通道,设置如图所示。

图3.4:电机驱动部分电路原理图

3.5.1 电机初始化

图3.5:CubeMX中对两路PWM的配置

基于以上配置的两路PWM通道,编写如下函数进行左右电机驱动的初始化:

1
2
3
4
5
6
7
8
void A4950_Init(void){
// 启动左电机 PWM(TIM1_CH1=PE9, TIM1_CH2=PE11)
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
// 启动右电机 PWM(TIM9_CH1=PE5, TIM9_CH2=PE6)
HAL_TIM_PWM_Start(&htim9, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim9, TIM_CHANNEL_2);
}

3.5.2 左(右)轮驱动

根据CubeMX的设置,PWM调速绝对值上限为99。分别编写左轮与右轮的PWM驱动函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 左轮
void A4950_SetLeft(int16_t speed){
uint16_t duty;
if (speed >= 0) {
duty = (speed > A4950_PWM_MAX ? A4950_PWM_MAX : speed);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, duty);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0);
} else {
duty = ((-speed) > A4950_PWM_MAX ? A4950_PWM_MAX : -speed);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, 0);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, duty);
}
}

// 右轮
void A4950_SetRight(int16_t speed){
uint16_t duty;
if (speed >= 0) {
duty = (speed > A4950_PWM_MAX ? A4950_PWM_MAX : speed);
__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_1, duty);
__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_2, 0);
} else {
duty = ((-speed) > A4950_PWM_MAX ? A4950_PWM_MAX : -speed);
__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_1, 0);
__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_2, duty);
}
}

3.5.3 制动

1
2
3
4
5
6
7
void A4950_Brake(void){
// 两输入同时高 → 主动制动
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, A4950_PWM_MAX);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, A4950_PWM_MAX);
__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_1, A4950_PWM_MAX);
__HAL_TIM_SET_COMPARE(&htim9, TIM_CHANNEL_2, A4950_PWM_MAX);
}

3.6 编码器

3.6.1 结构体关联句柄

1
2
3
4
typedef struct {
TIM_HandleTypeDef *htim; ///< 关联的定时器句柄(如 &htim2 或 &htim5)
int32_t count; ///< 累计脉冲计数(带符号,可正可负)
} Encoder_HandleTypeDef;

示例:在主程序中定义如下结构体,分别代表左编码器与右编码器:

1
Encoder_HandleTypeDef encL, encR;

3.6.2 编码器初始化

效果为关联定时器的编码器接口。对应函数代码如下:

1
2
3
4
5
6
void Encoder_Init(Encoder_HandleTypeDef *enc, TIM_HandleTypeDef *htim){
enc->htim = htim;
enc->count = 0;
// 启动 TIM 的 TI1 和 TI2 通道以进入编码器模式
HAL_TIM_Encoder_Start(enc->htim, TIM_CHANNEL_1 | TIM_CHANNEL_2);
}

示例:绑定左右编码器并启动对应的接口,可按照如下方式调用该函数,需输入对应的编码器结构体与相关联的定时器句柄:

1
2
Encoder_Init(&encL, &htim2);
Encoder_Init(&encR, &htim5);

3.6.3 获取计数

1
2
3
4
5
6
7
8
9
int32_t Encoder_GetCount(Encoder_HandleTypeDef *enc){
int16_t raw = __HAL_TIM_GET_COUNTER(enc->htim);
// 计算与上次读取的差值(考虑 16 位上下溢出)
int32_t delta = (int32_t)raw - (int32_t)(enc->count & 0xFFFF);
if (delta > 32767) delta -= 65536;
if (delta < -32768) delta += 65536;
enc->count += delta;
return enc->count;
}

示例:

1
2
leftCountGlobal = Encoder_GetCount(&encL);
rightCountGlobal = Encoder_GetCount(&encR);

3.6.4 编码器清零

1
2
3
4
void Encoder_Reset(Encoder_HandleTypeDef *enc){
__HAL_TIM_SET_COUNTER(enc->htim, 0);
enc->count = 0;
}

示例:

1
2
Encoder_Reset(&encL);
Encoder_Reset(&encR);

3.7 悬崖传感器

共有四个悬崖传感器(0~3),使用ADC和DMA进行读取并由TIM3定时触发,ADC的配置,TIM3配置如下图所示:

图3.6:CubeMX中对TIM3的配置

图3.7:CubeMX中对ADC的配置

3.7.1 初始化

编写以下函数,以启动定时器3的定时触发以及DMA:

1
2
3
4
void CliffSensor_Init(void){
HAL_TIM_Base_Start(&htim3); // 启动 TIM3 基本定时器
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_values, CLIFF_SENSOR_COUNT); //启动 ADC + DMA,读取 CLIFF_SENSOR_COUNT 个通道
}

3.7.2 读取四个传感器值

1
2
3
4
void CliffSensor_GetValues(uint16_t *out){
for (int i = 0; i < CLIFF_SENSOR_COUNT; i++)
out[i] = adc_values[i];
}

示例:

1
CliffSensor_GetValues(sensor_vals);

3.7.3 判断单个传感器的状态

1
2
3
4
bool CliffSensor_IsCliff(uint8_t idx){
if (idx >= CLIFF_SENSOR_COUNT) return false; // 越界保护
return (adc_values[idx] < cliff_thresholds[idx]);
}

使用时传入对应传感器编号(0~3)即可。

3.7.4 用掩码返回所有传感器状态编码器清零

1
2
3
4
5
6
7
8
uint8_t CliffSensor_GetMask(void){
uint8_t mask = 0;
if (CliffSensor_IsCliff(0)) mask |= CLIFF_1;
if (CliffSensor_IsCliff(1)) mask |= CLIFF_2;
if (CliffSensor_IsCliff(2)) mask |= CLIFF_3;
if (CliffSensor_IsCliff(3)) mask |= CLIFF_4;
return mask;
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
if (mask == 0) {
LED_Toggle(&LED0);
LED_Toggle(&LED1);
LED_Toggle(&LED2);
LED_Toggle(&LED3);
HAL_Delay(200);
} else {
if (mask & CLIFF_1) LED_On(&LED0);
if (mask & CLIFF_2) LED_On(&LED1);
if (mask & CLIFF_3) LED_On(&LED2);
if (mask & CLIFF_4) LED_On(&LED3);
}

3.8 碰撞传感器

机器人左前方右前方各有一个碰撞传感器,为数字器件,采用与按钮类似的逻辑编写即可,此处和悬崖一样使用了掩码方式来进行统一的封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool CrashSensor_Left(void){
// 微动开关闭合时将GPIO拉低,读取GPIO_PIN_RESET表示碰撞
return (HAL_GPIO_ReadPin(CRASH_L_GPIO_Port,CRASH_L_Pin)==GPIO_PIN_RESET);
}

bool CrashSensor_Right(void){
return (HAL_GPIO_ReadPin(CRASH_R_GPIO_Port,CRASH_R_Pin)==GPIO_PIN_RESET);
}

uint8_t CrashSensor_GetStatus(void){
uint8_t status = 0;
if (CrashSensor_Left())
status |= (1U << 0);
if (CrashSensor_Right())
status |= (1U << 1);
return status;
}

3.9 电压读取

如下图所示,R124和R128阻值分别为18kΩ和1kΩ,根据分压原理,电池电压为连接ADC处的19倍,据此我们只需要用ADC读取该处模拟值还原为电压值再乘19就可以得到电池电压。此处ADC也为ADC1,与悬崖传感器一同读取。用于读取电压值的回调函数如下:

1
2
3
4
5
6
7
8
9
10
float BT15V_GetVoltage(void){
// BT15V通道采样值(最后一个)
uint16_t raw = adc_values[CLIFF_SENSOR_COUNT];
float v_adc = ((float)raw) * ADC_REF_VOLTAGE / ADC_RESOLUTION;
float v_battery = v_adc * DIVIDER_RATIO;
float battery = (v_battery - MIN_V) / V_Range * 100;
if (battery > 100)
return 100;
return (v_battery - MIN_V) / V_Range * 100;
}

3.10 本章小结

至此完成了单片机所需调用的所有外设的基础配置工作,并已经过基本的功能验证测试,确认各外设配置与调用均正常。负责嵌入式开发的成员将上述内容整合成使用文档,提供给负责算法的成员进行控制算法等的二次开发工作。此外,为方便版本管理,将嵌入式程序推至远程Github仓库以便于远程协同工作,仓库地址:PyConqueror-16/Sweeping

4 上位机监控系统开发

4.1 系统架构

4.1.1 模块化设计思路

本系统采用”核心-功能-接口”三级模块化架构(如图1所示),各模块通过标准化接口进行通信。

具体设计原则如下:

  1. 功能解耦设计
  • ControlProtocol:模块独立处理指令编码,不依赖具体通信方式;

  • KeyboardController:将输入事件转换为标准速度指令;

  • BluetoothProtocol:输出结构化数据,界面模块负责可视化;

  1. 接口标准化
  • 控制协议接口

    1
    2
    class ContrilProtocol:
    def create_command(mode: int, speed_left: int, speed_right: int) -\> bytes:
  • 键盘控制接口

    1
    2
    class KeyboardController(QObject):
    def eventFilter(self, obj: QObject, event, QEvent) -\> bool:
  • 数据解析接口

    1
    2
    class BluetoothProtocol:	
    def parse_data(self, raw_data: bytes) -\> List[Dict]:
  • 串口通信接口

    1
    2
    class SerialThread(QThread):
    def send_command(self, command: bytes):
  1. 状态管理机制
  • 采用”发布-订阅”模式处理模块间状态同步;

  • 关键状态变更通过PyQt信号通知相关模块;

  • 共享数据通过队列实现线程安全访问。

  1. 异常隔离设计
  • 通信异常限制在SerialThread模块内处理;

  • 界面卡顿不影响后台数据处理;

  • 协议解析错误自动触发数据重传机制。

4.1.2 主要功能组件划分

系统功能组件及其相互关系:

表4.1 系统功能组件及其相互关系

组件类型 核心组件 功能描述 依赖关系
控制核心 ControlCenter 协调各模块运行,维护系统状态机 依赖所有功能组件
通信链 SerialThread 管理串口连接,实现数据收发多线程处理 →BluetoothProtocol
BluetoothProtocol 解析原始数据帧,校验数据完整性 ← SerialThread
人机交互 KeyboardController 将键盘事件转换为标准控制指令 → ControlProtocol
GUIComponents 提供速度调节、模式切换等交互控件 → ControlCenter
数据处理 DataProcessor 提供速度调节、模式切换等交互控件 ←BluetoothProtocol
可视化 TrajectoryVisualizer 实时绘制机器人轨迹和姿态 ← DataProcessor
StatusDashboard 显示速度、电量等实时参数 ← DataProcessor
高级功能 PathPlanner 实现牛耕式覆盖路径规划算法 → ControlProtocol
DataRecorder 支持轨迹记录与回放功能 ← DataProcessor

图4.1 组件交互图

4.1.3 数据流与控制流分析

表4.2 通信数据流一览表

数据类型 方向 频率 数据量 处理方式
控制指令 上位→下位 5-10Hz 6Byte 立即发送
状态数据 下位→上位 10Hz 14Byte 队列缓冲+定时处理
轨迹坐标 内部传输 5Hz 12Byte 环形缓冲区存储
配置参数 双向 事件触发 ≤1KB JSON序列化

实时控制流(周期≤100ms)为:

  1. 键盘输入 → KeyboardController生成标准化速度值(-100~100)

  2. ControlProtocol将速度值编码为机器指令(0xAA 0x01…)

  3. SerialThread通过串口发送指令

  4. 下位机响应指令并返回状态数据

  5. BluetoothProtocol校验解析数据帧

  6. DataProcessor进行数据平滑处理

  7. GUI线程定时获取处理后的数据更新界面

此外,我们还针对多线程任务进行了线程安全设计,采用”生产者-消费者”模式处理串口数据,并使用QTimer实现跨线程定时触发。其中的共享资源通过QMutex进行保护。

4.2 技术选型

4.2.1 开发框架:Python+PyQt5

本系统选用Python 3.8 + PyQt5作为基础技术栈,主要基于以下考量:

  • Python原型能够快速开发,具有很强的开发效率优势;

  • Python动态特性支持实时调试,界面修改可热更新;

  • Python具有丰富的标准库,os/sys/json等模块简化文件操作和数据处理;

  • PyQt5有特有完善的GUI组件,提供超过620个可直接使用的类,对比Tkinter仅约150个;

  • PyQt5的信号槽机制能实现低耦合的模块通信;

  • PyQt5样式定制能力支持CSS语法美化界面。

4.2.2 Matplotlib用于数据可视化

采用FigureCanvasQTAgg后端实现与PyQt5无缝集成。优化后的轨迹绘制可处理10000+数据点无卡顿。

表4.3 Matplotlib功能实现方案与性能需求对应一览表

需求 Matplotlib实现方案 性能指标
机器人位姿显示 Arrow+Circle补丁组合 渲染耗时<15ms
历史轨迹覆盖 fill_between+透明度控制 支持5000点实时绘制
动态坐标轴 **set_xlim()**智能缩放 自适应刷新率

4.2.3 PySerial串口通信库

选用PySerial作为串口通信解决方案,主要基于其简洁高效的API设计以及稳定的通信性能。通过非阻塞读取和双缓冲机制优化,有效避免了数据堆积问题,配合定时发送控制策略(100ms间隔),在Windows平台上保持稳定表现。相较于Qt原生QSerialPort,PySerial虽然与Qt集成度稍逊,但其更简洁的异常处理机制和无需额外依赖的特性,使其成为本项目中平衡性能与开发效率的最佳选择,最终实现了与STM32下位机10Hz稳定通信、500ms内快速校正的优异表现。

4.3 用户界面设计

4.3.1 主界面布局

在主界面布局上,采用经典的”三明治”布局设计,如下图所示:

图4.2:整体界面结构

可以看到,界面中主要包含以下三块主要的功能区域:

  • 顶部控制区(设备连接组、控制设置组)

  • 中部数据显示区(左侧实时数据面板、右侧轨迹图)

  • 底部功能区(辅助控制按钮组、帮助信息栏)

4.3.2 交互设计

本项目用户GUI界面的交互设计主要体现在以下几个板块:

  • 控制面板交互:主要包含速度调节交互(滑块与数值标签联动)与设备连接(流程图如下图所示)

图4.3:设备连接流程

  • 数据可视化:

图4.4:轨迹可视化示意图

  • 状态反馈机制:

图4.5:状态反馈图

在评价交互的响应性能时,我们设定了以下指标,保证用户在界面交互过程中能够得到即时且准确的反馈:

表4.4 交互性能响应指标

操作类型 响应延迟 视觉反馈方式
按钮点击 <50ms 按钮下沉动画
滑块拖动 即时 数值标签实时更新
键盘控制 <30ms 轨迹即时更新
数据加载 <200ms 进度条+旋转等待图标

4.4 核心功能模块

4.4.1 通讯协议模块(ControlProtocol)

实现上下位机通讯时,我们采用帧通讯的方式实现即时通讯。其中,协议帧具有双极性速度编码(正/负方向独立处理),校验和简化算法(求和取低8位),固定6字节帧长保证传输效率等特点。

图4.6:协议帧结构示意图

4.4.2 键盘控制模块(KeyboardController)

在该模块中定义并实现了基本的按键与遥操作功能,并引入了防抖机制。

图4.7:按键控制模块

4.4.3 蓝牙协议解析(BluetoothProtocol)

图4.8:蓝牙协议解析器

4.4.4 串口通信线程(SerialThread)

图4.9:通信线程工作流程图

图4.10 串口通信

4.5 高级功能实现

4.5.1 轨迹记录与回放

图4.11 记录回放系统架构

图4.12 回放JSON数据示例

数据中包含:记录动作创建时间,传送指令总量,总持续时间,控制模式;时间,指令间时间差,指令,模式。

4.5.2 地图数据管理

JSON文件具有加载速度快、压缩率高、可读性强的特点,所以地图存储同样使用JSON文件的不同格式来保存地图信息。

图4.13:加载地图控制面板UI及效果

4.5.3 路径规划功能

此处仅展示路径规划算法可视化效果,具体算法细节在第五章详细阐述。

图4.14:路径规划效果

4.6 本章小结

本章详细介绍了上位机监测与控制系统的设计流程,从整体架构到功能实现效果,有效实现了上下位机间的相互通信,并能成功进行遥控功能与数据可视化。界面交互设计上充分考虑人因学因素,布局合理,简单易用,能够方便用户实现对机器人即时而准确的移动控制与状态监测。

5 控制逻辑与算法设计

5.1 下位机整体底盘控制逻辑

根据需求分析部分提出的设计需求与性能指标,在下位机的底盘控制上主要分为如下几个控制功能逻辑模块:

  • 模式指令读取/基于按键切换

  • 基于编码器获取实时电机转速并利用PID控制器调节电机转速

  • 机器人实时位姿解算与惯导定位

  • 针对碰撞/悬崖等传感器感知数据做出实时响应

这些模块间的交互关系如下面的逻辑框图所示。接下来将依次介绍各模块的实现细节。

图5.1:下位机整体底盘控制逻辑

接下来将依次介绍各模块的实现细节。

5.1.1 开/关机按键与模式指令接受逻辑设计

在开关机按键与模式切换逻辑设计上,我们采用了如下图所示的框图逻辑,充分利用电路板上预留的两个按键,实现机器人的开关机:

图5.2:模式切换逻辑

扫地机器人一共具有三种状态模式,模式代码及其对应功能如下:

  • 0 ROBOT_OFF:关机状态,位姿和里程均重置为0

  • 1 REMOTE_CONTROL:遥控模式,左右轮速度通过上位机指令获取

  • 2 AUTO_MAPPING:自主建图模式,按设定速度(左轮15cm/s,右轮3cm/s)结合碰撞传感器实现贴墙行进

其中,模式1和模式2由接收到的上位机数据包经函数解析后得到的模式码决定,上位机GUI中可自由切换。同时,为便于用户日常使用,我们将PCB电路板上预留的两个按钮KEY0与KEY1使能,通过上述逻辑使得:按下KEY0后为关机状态,此时置为模式0且不会接收上位机发送的模式信息;按下KEY1后为开机状态,此时开始接收上位机发送的模式信息,并按照上位机指令进行后续运动。

该模块实现的主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 更新按键状态
Key_Update(&key[0]); // 检测KEY0:按下关机
Key_Update(&key[1]); // 检测KEY1:按下开机

// 模式切换
if (Key_GetState(&key[0]) == KEY_STATE_JUST_PRESSED){
robot_mode = ROBOT_OFF;
start_get_mode = 0;
current_mode = 0;
}

if (Key_GetState(&key[1]) == KEY_STATE_JUST_PRESSED)
start_get_mode = 1;

if (start_get_mode)
robot_mode = HC05_GetControlMode();

其中调用了用于重置机器人状态的函数ResetRobotState,在关机时会运行该函数,重置当前存储的偏角、里程等数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 重置机器人状态
void ResetRobotState(float* x, float* y, float* distance_all, uint32_t*round, int crash_flag) {
imu = JY901S_GetData();
*x = *y = *distance_all = 0;
*round = 0;
A4950_SetLeft(0);
A4950_SetRight(0);
target_speed_left = 0;
target_speed_right = 0;
crash_flag = 0;
cliff_flag = 0;
memory_deg = imu->yaw;
yaw_deg = imu->yaw - memory_deg;
}

5.1.2 基于编码器的电机转速PID控制算法设计

针对电机转速的精准控制,我们选择采用PID控制器,基于经编码器解析而来的实时转速信息实现反馈调节,使得电机转速能够在较短的时间响应达到设定的速度。

在电机转速的获取上,我们基于编码器的读取数据进行解算,换算关系如下:

查阅电机的产品手册,可以获得该电机与编码器对应的相关参数:

  • 减速比𝑀𝑂𝑇𝑂𝑅_𝑅𝐸𝐷𝑈𝐶𝑇𝐼𝑂𝑁_𝑅𝐴𝑇𝐼𝑂 = 1 : 63

  • 每转过一圈的脉冲数𝑃𝑈𝐿𝑆𝐸_𝑃𝐸𝑅_𝑅𝑂𝑈𝑁𝐷 = 20

  • 单位时间𝑡𝑖𝑚𝑒 = 50ms

  • 轮子周长CIRCLES_OF_TIRE = 34 * 2 * 3.14 mm

图5.3:项目中使用的电机与编码器

基于上述公式,编写以下函数代码,用于计算电机实时转速:

1
2
3
4
5
6
7
float getLeftSpeed(Encoder_HandleTypeDef *enc){
leftCount = leftCountGlobal;
leftCountGlobal = Encoder_GetCount(enc);
leftCount -= leftCountGlobal;
leftSpeed = leftCount / time / PULSE_PRE_ROUND / MOTOR_REDUCTION_RATIO * CIRCLES_OF_TIRE / 10; // cm/s
return leftSpeed;
}
1
2
3
4
5
6
7
float getRightSpeed(Encoder_HandleTypeDef *enc){
rightCount = rightCountGlobal;
rightCountGlobal = - Encoder_GetCount(enc);
rightCount -= rightCountGlobal;
rightSpeed = rightCount/ time / PULSE_PRE_ROUND / MOTOR_REDUCTION_RATIO * CIRCLES_OF_TIRE / 10; // cm/s
return rightSpeed;
}

在电机转速控制器上,我们选择最为经典的PID控制器用于实现电机转速的实时反馈调节,PID控制器主要具有以下几个特点:

  • 为系统指定一个目标值;

  • PID将目标值与被控对象当前的反馈量作差得到误差;

  • PID将误差值分别经过三个环节计算得到输出分量,再将三个分量加起来得到PID的输出;

  • 将PID的输出施加到被控对象上,使反馈量向目标值靠拢。

图5.4:PID控制算法流程框图与对应数学公式

图5.4:PID控制算法流程框图与对应数学公式

基于上述PID算法原理,我们将其封装成了如下的函数,实现对指定转速的快速动态响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void PID_Control_Left(float target_speed_left){
error_left = target_speed_left - leftSpeed;
integral_left += error_left;
if (integral_left > 99.0f)
integral_left = 99.0f;
else if (integral_left < - 99.0f)
integral_left = -99.0f;
derivative_left = error_left - previous_error_left;
pid_output_left = kp_left * error_left + ki_left * integral_left + kd_left * derivative_left;
if (pid_output_left > 99.0f)
pid_output_left = 99.0f;
else if (pid_output_left < - 99.0f)
pid_output_left = -99.0f;
A4950_SetLeft(pid_output_left * 7); //驱动电机
previous_error_left = error_left;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void PID_Control_Right(float target_speed_right){
error_right = target_speed_right - rightSpeed;
integral_right += error_right;
if (integral_right > 99.0f)
integral_right = 99.0f;
else if (integral_right < - 99.0f)
integral_right = -99.0f;
derivative_right = error_right - previous_error_right;
pid_output_right = kp_right * error_right + ki_right * integral_right + kd_right * derivative_right;
if (pid_output_right > 99.0f)
pid_output_right = 99.0f;
else if (pid_output_right < - 99.0f)
pid_output_right = -99.0f;
A4950_SetRight(pid_output_right * 7); //驱动电机
previous_error_right = error_right;
}

在main函数中,我们可以根据接收到的上位机速度指令,直接调用上述函数读取实时转速并利用PID控制器进行速度调节:

1
2
3
4
5
// 运动控制
left_speed = getLeftSpeed(&encL);
right_speed = getRightSpeed(&encR);
PID_Control_Left(target_speed_left);
PID_Control_Right(target_speed_right);

其中,经过实机测试与调整,我们最终采用的PID控制器参数为:KP=0.1,KI=0.1,KD=0.001。经测试,该组参数在目标电机转速为5至15cm/s时具有高灵敏度与高精度的动态响应特性。

5.1.3 机器人实时位姿解算与惯导定位

在机器人的位姿解算上,我们采用了综合电机实时转速与IMU惯导模块的方式,通过电机转速计算单个时间步内的分段里程(根据时间步长可以累加得到总里程),同时结合IMU直接读取到当前机器人的面朝偏角(启动时标志为0,范围为-180°~180°),可以得到单个时间步内机器人的近似位移,进而计算出机器人的实时位置坐标(相对于摁下开机键后的起始点而言,起始点坐标(0,0),面朝方向为y轴正方向)。

在实际计算与调试时,为降低调速过程中的距离计算误差,对达到指定速度前后的距离计算进行不同倍率的调整,以尽可能保证距离计算的精准度。

基于以上思想,编写如下代码实现机器人的实时位姿解算与惯导定位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 位姿计算(融合IMU+编码器)
imu = JY901S_GetData();
if (rounds == 10)
memory_deg = imu->yaw;
yaw_deg = imu->yaw - memory_deg;

if (left_speed < target_speed_left - 1.0f) // 调速过程优化
distance = (left_speed + right_speed) / 2 * ROUND_TIME / 1000.0f;
else // 通过轮速获取位移
distance = ((left_speed + right_speed) / 2 * 1.25) * ROUND_TIME / 1000.0f;
if (target_speed_left == -target_speed_right)
distance = 0;

x += distance * sin(-yaw_deg * M_PI / 180.0f);
y += distance * cos(-yaw_deg * M_PI / 180.0f);
distance_all += fabs(distance);

后续可将实时总里程、位置坐标与偏角等数据发送至上位机,进行进一步的建图可视化处理。

5.1.4 碰撞/悬崖传感器感知数据处理逻辑设计

在碰撞与悬崖传感器反馈的感知数据的处理上,我们设计了如下图所示的框图逻辑(以碰撞为例,逻辑上设置碰撞传感器的处理优先级高于悬崖,事实上两者往往互斥),让机器人实现贴墙/悬崖行进,以尽可能地清扫到所有的清洁死角:

  • 遥控模式下,为避免机器人进入视野盲区(如床底等区域)而产生误操作,设定该模式下机器人遇到障碍物时(即碰撞传感器触发)先后退再原地掉头。

  • 自主运动(建图)模式下,为使得机器人尽可能稳定地贴墙行进,针对两侧碰撞传感器共三种不同的触发状态,分别设置了不同的转角提供给turning_left函数执行左转,并在达到指定转角后重置碰撞状态。

图5.5:碰撞传感器感知数据处理逻辑设计

显然,处于保护机器人自身安全的原则,其对于传感器的感知数据做出的任何反应的优先级均应高于上位机的控制命令。

为对于不同的碰撞情形进行区分,我们对碰撞状态码进行了区分,并设定了机器人在不同碰撞状态下应该做出的反应,以尽可能完成贴墙行走的任务:

  • crash_status = 1 :左侧碰撞 —— 左转60°

  • crash_status = 3 :两侧均碰撞 —— 左转40°

  • crash_status = 2 :右侧碰撞 —— 持续左转直至不再碰撞(直行)

  • crash_status = 0 :无碰撞,按照上位机发送速度或设定速度继续行进

针对悬崖传感器,其感知数据的处理逻辑与碰撞类似,均为先后退再原地掉头(偏转一定角度后继续行进)。不同的是,由于悬崖传感器本质上是红外传感器,因此我们需要事先测定其阈值(由于不同传感器的性能差异,其阈值也有所不同)为{245,145,190,130}。本项目使用的扫地机器人硬件设备上共有四个悬崖传感器,其中任一触发时(大于阈值)其对应的二进制掩码cliff_mask均不为0,此时可认为机器人已到达悬崖边缘,需要做出对应的响应。

针对遥控模式,处理碰撞与悬崖传感器感知数据的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 优先处理碰撞
if (crash_status == 0)
if (crash_flag == 1)
turning_left(175);
else {
imu = JY901S_GetData();
if (crash_flag == 0) {
turn_start_deg = imu-\>yaw;
target_speed_left = -5;
target_speed_right = -5;
crash_flag = 1;
}
}

// 其次处理悬崖
if (cliff_mask == 0)
if (cliff_flag == 1)
turning_left(30);
else {
imu = JY901S_GetData();
if (cliff_flag == 0) {
turn_start_deg = imu-\>yaw;
A4950_SetLeft(0);
A4950_SetRight(0);
target_speed_left = -5;
target_speed_right = -5;
cliff_flag = 1;
}
}

针对自主运动(建图)模式,处理碰撞与悬崖传感器感知数据的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 优先处理碰撞
if (crash_status == 0){
if (crash_flag == 0){
target_speed_left = 15.0f;
target_speed_right = 3.0f;
}
else if (crash_flag == 1)
turning_left(60);
else if (crash_flag == 2) {
target_speed_left = -5.0f;
target_speed_right = 5.0f;
crash_flag = 0;
}
else if (crash_flag == 3)
turning_left(40);
}
else{
imu = JY901S_GetData();
if (crash_flag == 0){
turn_start_deg = imu->yaw;
if (crash_status == 1)
crash_flag = 1;
else if (crash_status == 2) {
target_speed_left = -5.0f;
target_speed_right = 5.0f;
}
else if (crash_status == 3)
crash_flag = 3;
}
else if (crash_flag == 1)
turning_left(60);
else if (crash_flag == 2) {
target_speed_left = -5.0f;
target_speed_right = 5.0f;
crash_flag = 0;
}
else if (crash_flag == 3)
turning_left(40);
}

// 其次处理悬崖
if (cliff_mask != 0) {
target_speed_left = -5;
target_speed_right = -5;
}

5.2 全覆盖路径规划算法设计:牛耕式扫描Boustrophedon

针对扫地这一具体应用场景,希望尽可能不放过区域内的每一个清洁死角,这实际是一个全覆盖路径规划(Complete Coverage Path Planning, CCPP)任务,需要确定一条路径,在避开障碍物的情况下通过该区域范围内的所有点。

我们选择采用离线式的牛耕式扫描法,基于静态环境信息,通过不断地覆盖分割后的子区域实现整个区域的全覆盖。牛耕式扫描(Boustrophedon Coverage)是一种高效的全覆盖路径规划算法,其核心思想是模拟农耕时的犁地模式,通过往复式运动实现无遗漏覆盖。算法首先将工作区域划分为若干条带,条带宽度w由机器人清扫半径(经测量机器人机身直径为32cm)决定,相邻条带间距通常取d≤w以确保覆盖连续性。机器人沿初始方向(y轴)移动,到达边界后垂直偏移d并反向移动,形成如下的路径模式:
$$
\begin{cases}x_{k+1}=x_k+(-1)^k\cdot L\y_{k+1}=y_k+d&\end{cases}
$$
其中L为条带长度,k为往返计数。当遇到障碍物时,算法会将区域分解为若干子多边形,在每个子区域内独立执行牛耕式扫描。该方法的覆盖率理论上可达100%,其路径总长度可表示为:
$$
D\approx\frac{A}{w}\cdot(L+2t)
$$
式中A为区域面积,t为转向损耗距离。

实际应用中,该算法常结合SLAM技术动态调整w,并通过匈牙利算法优化子区域访问顺序以提升效率。这种平衡了覆盖率和能耗的特性,使其成为扫地机器人等清洁设备的首选方案。

图5.6 牛耕式扫描全覆盖路径规划算法效果示意图

在建图过程中,由于没有采用激光雷达(电路板上未预留对应接口),我们通过机器人自主巡航模式下的贴墙碰撞,实现对封闭区域边界的建图。

基于以上算法原理,编写如下Python函数,根据建图过程中采集到的边界点云数据,调用牛耕式扫描算法规划全覆盖路径与关键节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def generate_coverage_path(x_coords, y_coords, robot_width):
# 生成覆盖路径:矩形区域 + Y方向牛耕式扫描,从右下角开始
min_x, max_x = min(x_coords), max(x_coords)
min_y, max_y = min(y_coords), max(y_coords)
path = []
path.append((max_x, min_y)) # 起点
path.append((max_x, max_y)) # 起点后的第一个点,先沿y轴正方向直行到底
# 计算条带位置(沿X方向从右往左移动),排除已经走过的最右侧条带
num_strips = int((max_x - min_x) / robot_width) + 1
x_strips = np.linspace(max_x, min_x, num_strips)
# 第一个条带(x = max_x - robot_width/2)已经部分覆盖,直接跳过第一条带
for i, x in enumerate(x_strips[1:], start=1):
if i % 2 == 1: # 奇数条带:从上到下走
path.append((x, max_y))
path.append((x, min_y))
else: # 偶数条带:从下到上走
path.append((x, min_y))
path.append((x, max_y))
path.append((max_x, min_y)) # 回到起点
return path

基于这些关键节点,可以进一步生成各时间步的左右轮速度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def calculate_wheel_velocities(x_path, y_path, robot_diameter, dt):
# 计算左右轮速度和时间步
v_left, v_right = [], []
time_steps = []
straight_speed = 10 # cm/s
turn_speed = 5 # cm/s
current_time = 0
for i in range(1, len(x_path)):
dx = x_path[i] - x_path[i-1]
dy = y_path[i] - y_path[i-1]
if dx != 0 and dy == 0:
dist = dx
elif dy != 0 and dx == 0:
dist = dy
duration = dist / straight_speed
num_steps = int(duration / dt) + 1 # 计算该段的时间步数
if i == 1 or i == len(x_path)-1: # 开始或结束段 直行
vl = straight_speed
vr = straight_speed
elif i % 2 == 0: # 转弯段
# 左右轮速度相反
vl = -turn_speed
vr = turn_speed
else: # 直行段
direction = 1 if dy >= 0 else -1 # 判断方向
vl = direction * straight_speed
vr = direction * straight_speed
# 为该段添加速度值和时间步
for _ in range(num_steps):
v_left.append(vl)
v_right.append(vr)
time_steps.append(current_time)
current_time += dt
return v_left, v_right, time_steps

以上所有的计算均在上位机完成,将这些速度信息按照时间步顺序依次发送至下位机,从而可以实现规划路径的运动控制。由于建图时仅扫描边界,下位机实际控制时仍然需要结合传感器反馈做出响应。

5.3 本章小结

本章主要介绍了机器人下位机整体底盘控制的基本逻辑框架,并对其中主要的功能模块实现细节进行介绍。该部分为基于已配置好各种外设的单片机工程的二次开发,由负责运动控制算法开发的成员从负责嵌入式的成员建立的Github仓库中Fork出来并在Algorithm分支中进行后续算法部分的开发更新,仓库地址:微电路设计课程项目

除此之外,本章还介绍了我们采用的全覆盖路径规划算法——牛耕式扫描算法的基本原理与部分实现细节,这也与扫地机器人这一实际应用场景相契合。

6 项目实现效果与总结

演示视频链接:https://www.bilibili.com/video/BV1HkKNzDEjV

6.1 项目实现效果

项目最终圆满完成了第一章需求分析中给出的所有功能设计需求,并基本达到了设定的需求指标。下面将给出部分主要功能的测试结果。

6.1.1 直线行驶精度测试

结合高精度的位姿解算与电机转速调节,我们可以对扫地机器人的运动实现高精度的控制。下图为扫地机器人直线行驶固定距离100cm的精度测试情况:

图6.1:机器人行进固定距离100cm精度测试

可以看到,在该测试中无论是距离精度还是行驶偏角均满足设定的性能需求(偏角<5°,距离偏差<2cm),这一方面说明PID控制器对于电机转速的控制具有良好的动态响应特性,另一方面也说明了位姿解算补偿算法的高精度。

6.1.2 指定转角精度测试

图6.2:机器人原地旋转固定角度90°精度测试

为便于应对碰撞/悬崖等突发情况,编写了固定转角旋转函数以根据不同情形做出灵活反应:

void turning_left(float CONTROL_DEG){

1
2
3
4
5
6
7
8
9
10
11
void turning_left(float CONTROL_DEG){
target_speed_left = -5.0f;
target_speed_right = 5.0f;
imu = JY901S_GetData();
if ((imu->yaw + fabs(imu->gy) > (CONTROL_DEG + turn_start_deg)) ||((imu->yaw + fabs(imu->gy) > (turn_start_deg + CONTROL_DEG - 360)) && (imu->yaw + fabs(imu->gy) < (CONTROL_DEG - 180)) && (turn_start_deg > (180 -CONTROL_DEG)))){
crash_flag = 0;
cliff_flag = 0;
target_speed_left = 0;
target_speed_right = 0;
}
}

其中,基于当前角度制,根据几何关系指定如下规则,判定机器人是否转过指定角度(需记录开始旋转时偏角turn_start_deg)【满足以下任一条即可】:

  • 当前偏角 > 起始偏角 + 指定转角

  • 起始偏角 > 180° - 指定转角 且 当前偏角 > 起始偏角 + 指定转角 – 360° 且 当前偏角 < 指定转角 – 180°

为提高旋转角度精度,考虑响应滞后性与运动惯性等问题,还在角度判标中加入转动加速度gy。

调用该函数进行测试,结果如上图所示。可以看到,在设定旋转角度为90°的情况下,机器人转过了91.7°,与实际设定角度相差小于2°,说明在基于IMU的偏角位姿结算上也具有较高的精度。当然,若增大原地旋转时的左右轮速度,该偏差值会由于惯性而有所增大,因此后续优化时可以考虑进一步采用高阶的转动加速度函数对转角控制进行修正。

6.1.3 贴墙行进与建图效果测试

图6.3:机器人贴墙行进与建图效果测试(完整过程见演示视频)

可以看到,在自制的约150cm*100cm的封闭多边形区域内,机器人能够很好地完成贴墙行进的任务,同时在上位机客户端的建图精度也较高,基本还原了整个封闭区域的地形轮廓。

6.1.4 全覆盖路径规划效果测试

图6.4:全覆盖路径规划效果演示

基于上述贴墙建图的测试结果,将路径点云数据以JSON文件形式导出存储。在路径规划测试时,将点云数据再次导入至客户端,并点击“路径规划”,效果如上图所示。可以看到,生成的路径与算法原理相一致。在实机部署实现的过程中,结合路径规划的关键点与实时的碰撞感知数据,机器人能够良好地扫过地图内的所有区域并避开障碍物,这进一步说明了路径规划与运动控制算法的有效性。

6.2 总结与收获

在本次微电路设计课程项目中,我们组的四位成员合理分工、通力合作,成功完成了SmartRobot扫地机器人的设计与开发工作。从项目需求分析、PCB电路设计、硬件外设程序设计,到上位机监控系统开发、控制逻辑与算法设计,再到最终的测试与优化,每一个环节都凝聚了团队成员的心血与智慧。通过复刻和优化现有扫地机器人的硬件方案,我们深入研究了其核心功能模块,包括运动控制、传感器数据处理、路径规划算法等,并结合实际应用场景进行了针对性改进。

在项目过程中,我们不仅掌握了PCB设计、嵌入式编程、传感器集成、电机控制等核心技术,还深刻体会到团队协作的重要性。通过分工合作,每位成员充分发挥了自己的专长,共同攻克了技术难题。例如,PCB设计中的电源管理优化、电机驱动选型,嵌入式程序中的PID控制算法实现,上位机系统的模块化设计与交互优化,以及全覆盖路径规划算法的开发与部署,都是团队智慧的结晶。

此外,项目还锻炼了我们的工程实践能力和问题解决能力。从理论到实践,我们学会了如何将课堂知识应用于实际项目,如何通过调试与测试不断优化系统性能。最终,扫地机器人实现了良好的避障功能、防坠落能力、高精度运动控制以及用户远程监控等设计目标,圆满完成了各项性能指标。

通过这次项目,我们不仅提升了专业技能,还增强了团队协作与沟通能力,为未来的工程实践与科研工作积累了宝贵经验。我们深刻认识到,技术创新需要扎实的理论基础、严谨的工程态度和高效的团队合作。这段经历将成为我们成长道路上难忘的一课,激励我们在未来的学习和工作中继续追求卓越。

6.3 本章小结

本章对于机器人功能与性能指标的测试结果进行了展示与分析,并对于项目成果与收获进行了总结。

常微分方程反演的机器学习方法

选题背景与意义

微分方程是数学中一个重要且广泛应用的领域,涉及到描述变化和相互关系的方程。它是一种包含导数或微分的方程,常用于自然现象建模以及解决科学和工程领域中的问题。微分方程研究是数学中的一个重要分支,涵盖了广泛的领域和应用。通过研究微分方程,我们可以理解和预测自然现象的行为,以及解决科学和工程中的实际问题。同时,微分方程的研究也促进了数学的发展和数学工具在其他学科中的应用。

常微分方程被广泛应用于物理、生物、化学、经济学等各个领域。在许多情况下,观测数据是已知的,而其中隐含的微分方程仍然难以捉摸。因此,数据驱动的常微分方程(或动力系统)发现和反演是一个重要的研究方向。

微分方程反演问题是相对于微分方程的求解而言的:

  • 微分方程的求解通常是一个正向问题,即给定初始条件,求解关于未知函数的方程;
  • 微分方程反演问题是指根据已知的结果,推导出产生这些结果的微分方程,是从结果向方程的逆向推导和反演。

在数学上,微分方程的反演问题具有一系列的数学挑战和困难

  • 反问题的病态性:微小扰动在反问题的输出中会导致较大的误差,因此需要稳定性分析和正则化方法来解决数值计算中的误差;
  • 存在非唯一解情况:需要利用先验信息、约束条件和经验知识等来进行问题的约束和规定,以得到合理的解。

微分方程反演问题研究在数学理论和实际应用中具有重要意义。通过解决微分方程的反演问题,我们可以从有限的观测数据中重建和推断未知的边界条件、初始条件或未知函数,从而深入理解系统的行为和性质,并提供科学、工程和医学等领域的相关应用。

随着计算机技术的发展,数值计算和机器学习在微分方程反演问题研究中的应用也呈现出迅猛发展的趋势。传统的微分方程推断方法依赖于领域专家的知识和经验,而机器学习方法,如神经网络、深度学习和遗传算法等,可以自动从大量数据中学习模式和关系,从而更准确地推断出微分方程模型。如今我们可以获取到大规模的、高维度的数据集,这为从数据中推断微分方程提供了更多的信息和挑战。而传统的模型推断方法在处理大数据集时可能受到维度灾难和计算复杂度的限制。机器学习的强大计算能力和自适应建模能力为解决这些问题提供了新的思路。

利用机器学习从数据中反演微分方程的方法可以分为两个方面:

  • 模型选择方面,机器学习可以通过自动搜索和学习合适的微分方程模型,利用测度函数或结构特征来评估不同模型的性能,并选择最适合数据的微分方程模型;
  • 参数估计方面,机器学习可以利用大数据集中的样本来优化微分方程模型的参数,以最小化模型与实际数据之间的误差。

利用机器学习从数据中反演微分方程是微分方程和机器学习交叉领域的重要研究方向。它利用机器学习的能力和大数据的优势,可以更准确地建模和预测复杂的动力学系统,推进科学研究和实际应用的发展。

针对常微分方程,目前常用的反演方法有如下几种:

  • 高斯过程:将高斯过程置于状态函数之上,然后通过最大化边际对数似然性从数据中推断参数,这种方法适用于解决高维问题,但是对系统的形式有限制,并且仅用于估计系统的参数。
  • 符号回归:创建并更正与观测数据相对应的符号模型,为控制函数提供更具表现力的函数形式,但缺点是对于大型系统来说计算成本高,并可能容易过拟合。
  • 稀疏回归:找到候选基函数的稀疏组合来近似控制函数,其系数由最小二乘法或贝叶斯回归确定。这种方法提供系统的明确形式,不需要太多先验知识,但是依靠适当的候选函数;对于没有简单或稀疏表示的复杂动力系统来说可能是低效的。
  • 统计学习:通过最小化经验误差来学习系统在某个假设空间中的相互作用核,避免维度诅咒,可以发现非常高维度的系统,但是仅适用于具有交互作用的核函数的动力系统。
  • 物理信息神经网络(PINN):一种新的深度学习方法,用于解决非线性偏微分方程的正问题和反问题。通过在前馈神经网络中嵌入由偏微分方程描述的物理信息,在不需要标签数据的情况下,将 PINN 训练为偏微分方程的近似解的代理模型。受此启发,进一步发展出了多步神经网络LMNets,这本质上是使用给定相点上的流映射提供的信息来反演动力系统种的未知控制函数f的一种逆过程。

在数值分析中,发展高阶方法是许多应用中的一个重要课题。传统上,在求解动力系统时,高阶离散化技术,如线性多步法和龙格-库塔方法已经得到了发展。近年来,线性多步法也被用于动力系统的发现。随着使用深度学习的发现取得令人满意的进展,对它在动力系统发现的理论理解也在进一步发展。

基于以上所陈述的微分方程反演问题的研究现状,针对无显式方程的非线性系统这一在真实物理世界中更为广泛且常见的情形,我们希望基于高阶离散化技术线性多步法,结合多步神经网络LMNets这一深度学习方法,拟合和反演出数据背后的动力学物理规律

算法框架与原理

研究对象——常微分方程(组)

首先明确我们的研究对象——常微分方程:
$$
\begin{aligned}&\frac{dx(t)}{dt}=f(x(t),t),\&x(t_{0})=x_{0}\end{aligned}
$$
上述式子是一个最简单的二维常微分方程示例,其中x(t) ∈ R是关于时间t的函数,函数关系由一个微分方程刻画,且给出了一定的初值条件以唯一确定函数x的表达式,从而能够真实地刻画物理过程随时间变化的发展情况。

事实上大多数时候我们需要观测的物理量远远不止x这一个,因此为了后续更容易推广到高维情形(后续无特别说明,均围绕自治常微分方程展开讨论),我们将上述的非自治常微分方程转化为向量化的自治常微分方程
$$
\begin{aligned}&\frac{d\boldsymbol{u}(t)}{dt}=\hat{f}(\boldsymbol{u}(t)),\&\boldsymbol{u}(t_{0})=\boldsymbol{u}{0}\end{aligned}
$$
其中:
$$
u=(x,y)\in\mathbb{R}^{D+1},\hat{f}=(f,1)^{T},u
{0}=(x_{0},t_{0})^{T}
$$
在反演问题中,已知在若干时刻节点t_1、t_2、…、t_n的解x的数据,我们一般不会希望直接解出x(t)的表达式,而是通过确定光滑函数f,从而利用给定的数据反演出微分方程。这样的传统是从简单的反演情形推广并沿用的,因为比起得到一个精确的函数表达式,我们更希望探究这个表达式背后的物理规律,而这往往离不开微分方程,因此保留微分方程的形式具有一定的必要性。

多步神经网络

线性多步法是用于微分方程数值求解的常用方法。传统上,它们用于求解给定常微分方程的解,可以称为微分方程的正问题。但线性多步法也可以用于反演给定解的原微分方程,属于微分方程的反问题,尤其是将线性多步法的经典数值方法与神经网络相结合,例如多步神经网络。

线性多步法

线性多步法是求解常微分方程数值解的一种常见方法。随着计算机技术的发展,线性多步法得到更加广泛的应用。人们逐渐发现,在某些情况下,线性多步法可以提供更高的数值精度和数值稳定性。此外,线性多步法可以与其他数值方法(如龙格-库塔法)结合使用,互补彼此的优点。

对于上述常微分方程(非自治),设其中结点总数为N,时间间隔h为常数:
$$
h = t_{n + 1} - t_n, n = 1, …, N - 1
$$
则第n个结点的步长为M的线性多步法有以下公式:
$$
\sum_{m=0}^{M}[\alpha_{m}x_{n-m}+h\beta_{m}f(x_{n-m},t)]=0,n=M、\ldots、N, \alpha_{_M}\neq0
$$
使用线性多步法求解常微分方程时,必须选择初始的步长M值,以及确定系数α、β的不同方法,然后可以依次计算n≥M的近似值x_n。

事实上,根据确定系数的方法不同,线性多步法也分为多种类型,本项目中主要使用如下的三种常见的线性多步法:

  • Adams-Moulton 法:作为一种隐式方法,需要通过迭代或其他数值求解技术来解决每个时间步长的方程组。它在求解非刚性和刚性常微分方程时都表现良好,并且具有较高的数值稳定性,其精度随步长的减小而提高,但响应地可能会产生更高的计算代价。对于高阶常微分方程,Adams-Moulton 法需要结合相应的差分格式将高阶常微分方程转化为一阶方程组进行求解。

$$
\sum_{m=0}^M\alpha_mx_{n-m}=h\sum_{m=0}^M\beta_mf(x_{n-m})
$$

  • Adams-Bashforth法:作为一种显式方法,在求解非刚性和刚性常微分方程时都表现良好,计算效率较高,并且容易实现,但可能会收到稳定性条件地限制,其精度随步长的减小而提高。对于高阶常微分方程,Adams-Bashforth法需要结合相应的差分格式将高阶常微分方程转化为一阶方程组进行求解。

$$
\sum_{m=0}^M\alpha_mx_{n-m}=h\sum_{m=0}^M\beta_mf(x(t_{n-m}))
$$

  • 后向微分公式法(Backward Differentiation Formula, BDF):同样是一种隐式方法,但与Adams-Moulton法不同,后向微分公式法是通过解一个非线性方程组来得到当前时间步长的数值解。这个方程组可以用牛顿迭代等数值求解技术来解决。该方法具有较好的数值稳定性,其精度依赖于使用的插值多项式的阶数。一般来说,其高阶形式可以提供更高的精度,但也会增加计算的复杂性。它适用于求解非刚性和刚性常微分方程,并且在稳定性和数值精度方面通常有良好的表现。

$$
\sum_{m=0}^{M}\alpha_{m}x_{n-m}=h\beta f(x(t_{n}))
$$

除此之外,即使采用同一种系数确定方式,选择的步长M不同,得到的系数α、β也有所不同。具体而言,本项目中针对以上三种线性多步法,分别选取步长M=2/3/4的三种情况进行实验测试,下面列出各方法对应不同步长时的系数情况:

线性多步法系数

线性多步法的优点在于其高阶的精度和较小的计算代价,可以有效地逼近微分方程的数值解。但线性多步法可能会受到稳定性和初始条件限制,选择适当的步长和求解方法非常重要。

多步神经网络算法

从线性多步法的公式中得到启发,可以用神经网络去反演常微分方程右端的f。通过相等时间间隔h的数值解x的数据训练神经网络,该神经网络的参数可以通过使以下均方误差损失函数MSE最小化来训练得到:
$$
MSE=\frac{1}{N-M+1}\sum_{n=M}^N\parallel y_n\parallel^2
$$
其中:
$$
y_n=\sum_{m=0}^M\left[\alpha_mu_{n-m}+h\beta_mf_{NN}(u_{n-m})\right],n=M,\ldots,N
$$
式中f_NN表示神经网络对函数f的近似,y_n也称为线性多步法(LMM)残差。

在Python中编写函数train()以封装多步神经网络的训练流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 模型训练 
def train(u_tensor, model, loss_func, h, M):
# 参数说明:训练集u_tensor,待训练模型model,损失函数loss_func,时间步长h,步数M,最大训练次数EPOCH
optimizer = torch.optim.Adam(model.parameters(), lr=LR)
loss_history = []

for epoch in range(EPOCHS):
model.train()
batch_u = u_tensor.to(device)
train_loss = loss_func(batch_u, model, h, M)
optimizer.zero_grad()
train_loss.backward()
optimizer.step()
epoch_avg_loss = train_loss.item()
loss_history.append(epoch_avg_loss)

return loss_history

其中loss_func即为上述定义的均方误差损失函数MSE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 损失函数
def loss_func(u, model, h, M):
# 参数说明:模型输入数据u,待训练模型model,时间步长h,步数M

# 根据选定的方法确定系数α、β(示例:Adams-Moulton法,M=4)
alpha = [1, -1, 0, 0, 0]
beta = [251/720, 646/720, -264/720, 106/720, -19/720]

loss = 0.0
for n in range(M, len(u)):
residual = 0.0
for m in range(M+1):
u_nm = u[n - m].unsqueeze(0)
f_nm = model(u_nm)
residual += alpha[m] * u[n - m] + h * beta[m] * f_nm.squeeze()
loss += torch.norm(residual) ** 2
return loss / (len(u) - M + 1)

基于多步神经网络改进的常微分方程反演算法

改进的多步神经网络算法

多步神经网络做出了比较理想的假设,即给出了所有的数值解数据是无噪声的,以及神经网络可以表示对常微分方程产生零残差的反演。这种假设是理想化的,我们可以在实际情况下使用改进的方法尝试发现未知常微分方程。

对于多步神经网络,除线性多步法残差之外,如果神经网络能够满足一些通用近似性质,尤其是在多步神经网络反演法效果不佳的非自治常微分方程的反演问题中,可以扩展精确和完整数据的收敛结果。考虑到神经网络对函数近似的强大能力(见通用近似定理),这意味着至少在合适的光滑的常微分方程中存在良好的泛化误差。

假设数据集为给定在若干时刻t_1、t_2、…、t_N的方程的数值解u的值,把每一时刻t_n对应的解u(t_n)记为u^(n),而t_n对应的导数数据一般无从得知。当时间间隔h较小时,可用二阶中心差分法近似求出导数数据,记对于每一时刻t_n求出的导数数据为d^(n),则:
$$
d^{(n)}=\begin{cases}(-3\boldsymbol{u}^{(n)}+4\boldsymbol{u}^{(n+1)}-\boldsymbol{u}^{(n+2)})/2h,\quad n=1,\(\boldsymbol{u}^{(n+1)}-\boldsymbol{u}^{(n-1)})/2h,\quad1<n<N,\(\boldsymbol{u}^{(n-2)}-4\boldsymbol{u}^{(n-1)}+3\boldsymbol{u}^{(n)})/2h,\quad n=N.&\end{cases}
$$
显然二阶中心差分法求导数d^(n)的误差为o(h^2)。

对于常微分方程的反演,我们改进了多步神经网络的损失函数,其中既包含解数据,又包含导数数据
$$
L=l_1(u,d)+l_2(u,d,f_{NN})
$$
其中:
$$
l_{1}=\frac{1}{N-M+1}\sum_{n=M}^{N}\left|\sum_{m=0}^{M}[\alpha_{m}u_{n-m}+h\beta_{m}f_{NN}(u_{n-m})]\right|^{2}
$$

$$
l_{2}=\frac{1}{N-M+1}\sum_{n=M}^{N}\left|f_{NN}(u_{n})-d_{n}\right|^{2}
$$

$$
n=M,\ldots,N
$$

式中向量范数采用二范数;u为数值解的数据,d为二阶中心差分法得到的导数数据,则有:

  • l_1线性多步法残差,可以表示常微分方程的近似程度
  • l_2均方误差函数,可以表示网络结构的准确度

把u^(n)作为输入,d^(n)作为期望输出进行训练,得到的f_NN即为对函数f的近似。训练流程与改进后的损失函数Python实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 模型训练 
def train(u_tensor, model, loss_func, h, M):
# 参数说明:训练集u_tensor,待训练模型model,损失函数loss_func,时间步长h,步数M,最大训练次数EPOCH
optimizer = torch.optim.Adam(model.parameters(), lr=LR)
loss_history = []

for epoch in range(EPOCHS):
model.train()
batch_u = u_tensor.to(device)
batch_d = d_tensor.to(device)
train_loss = loss_func(batch_u, batch_d, model, h, M)
optimizer.zero_grad()
train_loss.backward()
optimizer.step()
epoch_avg_loss = train_loss.item()
loss_history.append(epoch_avg_loss)

return loss_history

可以看到,与改进前相比,在训练过程中增加了对二阶中心差分法得到的导数的相关计算,进一步利用了训练数据中的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 损失函数
def loss_func(u, d, model, h, M):
# 参数说明:模型输入数据u,输入数据对应的真实微分数据d,待训练模型model,时间步长h,步数M

# 根据选定的方法确定系数α、β(示例:Adams-Moulton法,M=4)
alpha = [1, -1, 0, 0, 0]
beta = [251/720, 646/720, -264/720, 106/720, -19/720]

# 多步法残差 l1
l1 = 0.0
for n in range(M, len(u)):
residual = 0.0
for m in range(M+1):
u_nm = u[n - m].unsqueeze(0)
f_nm = model(u_nm)
residual += alpha[m] * u[n - m] + h * beta[m] * f_nm.squeeze()
l1 += torch.norm(residual) ** 2
l1 = l1 / (len(u) - M + 1)

# 导数拟合误差 l2
pred_d = model(u)
l2 = torch.mean(torch.norm(pred_d - d, dim=1) ** 2)

return l1 + l2 # 总损失

值得注意的是,针对非自治常微分方程,由于在将其变换为自治常微分方程时进行了升维操作,这意味着真实的导数向量在y=t这一分量上的值必然是1,因此后续在检验模型训练效果时可利用这一点对模型性能进行先决的把握。

基于误差界理论论证改进算法的优越性

在上述损失函数中,l_1是对目标函数的良好近似,而l_2是神经网络中常用的损失函数。考虑到神经网络对函数近似的强大能力,这意味着即使对于噪声数据,改进的方法也应有良好的泛化能力和鲁棒性。在这里,我们先从理论上来论证改进方法在泛化能力与鲁棒性上的优越性,后续的数值实验部分将采用三类非自治方程的反演来验证改进的多步神经网络的效果。

对于二分类问题(可推广至函数近似的回归问题),当假设空间是有限个函数的集合F={f_1,f_2,…,f_d}时,对任意一个函数f∈F,至少以概率1-δ,以下不等式成立:
$$
R(f)\leq\hat{R}(f)+\varepsilon(d,N,\delta)
$$
其中:
$$
\begin{gathered}R(f)=E[L(Y,f(X))]\\hat{R}(f)=\frac{1}{N}\sum_{1}^{N}L(y_{i},f(x_{i}))\\varepsilon(d,N,\delta)=\sqrt{\frac{1}{2N}(\log d+\log\frac{1}{\delta})}\end{gathered}
$$
式中,R(f)为泛化误差(测试集上的测试风险),\hat{R}(f)为训练集上的经验风险,\hat{R}(f) + ε(d,N,δ)即为泛化误差界。观察上式可知,泛化误差界与样本数N成正比,与假设空间包含的函数数量d成反比。因此,当样本数N越大,泛化误差界越小,当假设空间F包含的函数越多,泛化误差界越大。

根据上述定理,针对改进后的神经网络,有如下不等式成立:
$$
\left|f_{NN}(u^{(n)})-\hat{f}(u^{(n)})\right|\leq\left|f_{NN}(u^{(n)})-d^{(n)}\right|+\left|\hat{f}(u^{(n)})-d^{(n)}\right|\leq w+o(h^{2})
$$
该不等式表明,改进后的损失函数可以拆解成两部分误差:

  • 第一部分为通过神经网络得到的导数近似值与通过二阶中心差分法求出的导数之间的误差,设该误差限为w
  • 第二部分为导数真实值与通过二阶中心差分法求出的导数之间的误差,根据二阶中心差分法的基本原理,其误差限为**o(h^2)**。

显然改进后的多步神经网络的泛化误差相较改进前有了进一步的约束,尤其显著减小了噪声对模型的影响,具有更强的泛化能力和鲁棒性

数值实验与分析

为充分验证改进方法的泛化能力与鲁棒性,我们选取了六个实际的物理问题(对应常微分方程)作为测试用例,对应自治/非自治以及三种不同的线性多步法,在每一个测试用例内选取不同的线性多步法步数以及训练数据所含的高斯噪声方差,对改进前后的两种基于多步神经网络的常微分方程反演方法进行测试。下面我们以“受迫振动方程”这一用例详细展示测试流程,其他的测试用例仅作结果展示。

数值实验流程

数据构建

基于选定的测试用例对应的常微分方程,可采用如下步骤生成原始的真实数据集:

  1. 在所给区域内均匀选取间隔为h的N个结点t_n,n=1,2,…,N ,使用Python中的Scipy库求出各结点对应的数值解u^(n);
  2. 将数值解u^(n) 代入二阶中心差分法中得到每个x_n 对应的导数值d^(n) ;
  3. 噪声数据构建:在各结点对应的数值解u^(n) 上依次加上期望为0,方差为0、0.01、0.05的高斯噪声

在Python中,用函数get_data()函数实现了数据构建的代码封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def get_data(t0, t_end, h, y0, noise_std=0.0):     
t_eval = np.arange(t0, t_end + h, h)

# 数值解
sol = solve_ivp(forced_oscillator, [t0, t_end], y0, t_eval=t_eval)
x = sol.y.T # shape: [N, 2]
t = sol.t.reshape(-1, 1) # shape: [N, 1]

# 添加高斯噪声
if noise_std > 0:
x += np.random.normal(0, noise_std, size=x.shape)

# 构造 u = [x1, x2, t]
u = np.hstack([x, t])

# 二阶中心差分求导数 d ≈ du/dt
d = np.zeros_like(u)
# 边界点使用前向/后向差分
d[0] = (-3 * u[0] + 4 * u[1] - u[2]) / (2 * h)
d[-1] = (u[-3] - 4 * u[-2] + 3 * u[-1]) / (2 * h)
# 内部点使用中心差分
d[1:-1] = (u[2:] - u[:-2]) / (2 * h)

u_tensor = torch.tensor(u, dtype=torch.float32)
d_tensor = torch.tensor(d, dtype=torch.float32)

return u_tensor, d_tensor, t# 模型训练

以“受迫振动方程”这一测试框架为例,force_oscillator函数中刻画了该场景下的微分方程规律:

1
2
3
4
5
def forced_oscillator(t, y):
x1, x2 = y
dx1dt = x2
dx2dt = -np.cos(t) * x1 - x2 + t / 50
return [dx1dt, dx2dt]

生成的无噪声数据如下图所示:

受迫振动方程数据构建x1

受迫振动方程数据构建x2

神经网络训练

根据上述算法理论部分列出的训练流程框架,适用数据(u,d)对神经网络进行训练,训练后得到的函数f_NN即为对函数f的近似。

关于神经网络的结构与参数,本项目中所使用的神经网络结构均相同(后续不再赘述),均包含4个隐藏层,每层128个神经元,激活函数为tanh,Python中基于pytorch框架对神经网络架构的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import torch.nn as nn

class ODEApproximator(nn.Module):
def __init__(self, input_dim=3, hidden_dim=128, output_dim=3):
super(ODEApproximator, self).__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.Tanh(),
nn.Linear(hidden_dim, hidden_dim),
nn.Tanh(),
nn.Linear(hidden_dim, hidden_dim),
nn.Tanh(),
nn.Linear(hidden_dim, hidden_dim),
nn.Tanh(),
nn.Linear(hidden_dim, output_dim)
)

def forward(self, x):
return self.net(x)

训练过程中实时记录了每一个epoch的损失函数值,下图分别绘制了改进前后的多步神经网络在无噪声数据集上训练过程中损失函数值的变化情况(总Epoch=100),可以看到损失函数均呈收敛态势,且改进后的多步神经网络收敛更快:

改进前多步神经网络训练过程损失函数值变化

改进后多步神经网络训练过程损失函数值变化

效果检验

为定量地比较改进前后的多步神经网络的泛化性能与鲁棒性,采取如下的实验步骤进行训练后模型的效果检验:

  1. 用训练得到的函数f_NN解方程得到数值解u_NN^(n) ,观察近似效果,计算均方误差mse_x 和平均绝对误差mae_x并与多步神经网络作比较;
  2. 用数值解u^(n)代入训练得到的函数f_NN得到的导数的近似值d_NN^(n),将u^(n)代入给定的常微分方程中的 f 得到导数的真实值d^(n),计算均方误差mse_f和平均绝对误差mae_f并与多步神经网络作比较。

其中用于比较性能的指标均方误差mse和平均绝对误差mae的定义式如下所示:
$$
mse_{x}=\frac{1}{ND}\sum_{n=1}^{N}\left|u_{NN}^{(n)}-u^{(n)}\right|_{2}^{2}
$$

$$
mse_{f}=\frac{1}{ND}\sum_{n=1}^{N}\left|d_{NN}^{(n)}-d^{(n)}\right|_{2}^{2}
$$

$$
mae_{x}=\frac{1}{ND}\sum_{n=1}^{N}\left|u_{NN}^{(n)}-u^{(n)}\right|_{1}
$$

$$
mae_{f}=\frac{1}{ND}\sum_{n=1}^{N}\left|d_{NN}^{(n)}-d^{(n)}\right|_{1}
$$

Python中将利用训练好的模型进行预测的过程封装成函数predict_u,指标计算直接调用scikit-learn函数库中的相关函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def rhs(t, u_np):      
"""用于 solve_ivp:输入 u = [x1, x2, t] 输出 du/dt"""
u_tensor = torch.tensor(u_np, dtype=torch.float32).unsqueeze(0).to(device)
with torch.no_grad():
du_dt = model(u_tensor).squeeze().cpu().numpy()
return du_dt

def predict_u(u_tensor):
# 初始条件与时间点一致
u0 = u_tensor[0].cpu().numpy() # [x1, x2, t]
t_span = (0, 100)
t_eval = np.arange(0, 100 + 0.05, 0.05)

sol_nn = solve_ivp(rhs, t_span, u0, t_eval=t_eval)
return sol_nn.y.T # shape: [N, 3]

在“受迫振动方程”这一测试案例中,基于训练轮数Epoch=50的模型,运行以上检验流程,得到如下检验指标数据:

Epoch=50时数值解预测误差

可以看到,随着噪声强度增加,两种方法的误差均增大,但改进方法对数值解的预测误差在各种情况下均小于多步神经网络

Epoch=50时导数值预测误差

可以看到,当数据无噪声时,本文提出的改进方法对于受迫振动方程的近似效果强于多步神经网络;随着噪声强度的增加多步神经网络的误差大幅增加,而改进的方法的误差一直保持较小且始终强于多步神经网络。

除此之外,针对在无噪声数据集上训练轮数分别为Epoch=100和Epoch=500时的模型,将其作为微分方程中的f用于预测数据的生成,效果分别如下图所示:

Epoch=100时预测效果与真实值对比

Epoch=500时预测效果与真实值对比

可以看到,随着训练轮数的增加,模型对于实际微分方程的拟合效果越来越好,生成的预测数据也越来越接近用于训练的真实数据;事实上,从理论上讲,大概在训练轮数Epoch达到10^4数量级时才会有较为精准的拟合效果,本项目由于用于训练的计算资源有限,仅进行了最大训练轮数为1000的测试。在后续的结果分析部分,将直接采用高训练轮数下的精确结果进行展示。

数值实验结果分析

非自治常微分方程

受迫振动方程——Adams-Moulton法

受迫振动方程(Forced Oscillator)是描述一个振动系统在外界力的作用下进行振动的方程。这个方程可以用来研究各种物理系统中的振动现象,例如弹簧振子、摆锤、电路振荡等。受迫振动方程的核心思想是将外界力引入方程中,以描述振动系统在外界激励下的行为。受迫振动方程的研究对于科学、工程和技术领域具有重要意义。它不仅有助于我们深入理解振动现象的本质和规律,还为我们设计和优化振动控制系统、减振装置等提供了理论基础。同时,它在电子设备、结构工程、交通运输等方面也有广泛的应用。

实验使用的受迫振动方程如下:
$$
\begin{aligned}&\frac{dx_1}{dt}=x_2,\&\frac{dx_2}{dt}=-cosy\cdot x_1-x_2+\frac{y}{50},\&\frac{dy}{dt}=1\end{aligned}
$$
使用[0,1,0]T作为初值,生成从t=0到t=100,间隔h=0.05的数据作为训练集,最终得到的多步神经网络对常微分方程的反演效果如下图所示,其中红色曲线是原方程数值解的曲线,蓝色点是用无噪声数据训练的函数f_NN代替f得到的数值解:

受迫振动方程反演效果

线性标量方程——Adams-Bashforth法

线性标量方程(Linear Scalar Equation)是一种描述某个未知函数与其导数之间的关系的方程,其中未知函数的导数的最高次数为1。线性标量方程是微分方程中的一类重要问题,它在数学物理等领域中有广泛应用。线性标量方程的研究对于数学、物理学和工程学等领域具有重要意义。它可以用来描述动力学系统的行为、传热和传质过程、量子力学中的波函数演化等。通过分析和求解线性标量方程,可以深入理解系统的特征、稳定性、渐近行为等,为问题的模拟、控制和优化提供理论基础。在现代科学和工程中,线性标量方程的研究得到了进一步推广和延伸。例如,非线性标量方程、偏微分方程等是线性标量方程的扩展和推广,它们更好地描述了一些复杂的物理现象和现实问题。

实验使用的线性标量方程如下:
$$
\frac{dx_1}{dt}=-x_1(sin4y+1)+cos\frac{y^2}{1000},
$$

$$
\frac{dy}{dt}=1
$$

使用[2,0]T作为初值,生成从t=0到t=20,间隔h=0.05的数据作为训练集,最终得到的多步神经网络对常微分方程的反演效果如下图所示,其中红色曲线是原方程数值解的曲线,蓝色点是用无噪声数据训练的函数f_NN代替f得到的数值解:

线性标量方程反演效果

食饵-捕食者模型方程——后向微分公式法

食饵-捕食者模型(Predator-prey Model)是生态学中研究食物链和生物群落动力学的重要模型之一,描述了食物链中食饵(被捕食者)和捕食者之间的相互作用关系。这种模型的研究对于我们理解生态系统的稳定性、物种相互作用以及生态系统中物种数量的变化具有重要意义。通过食饵-捕食者模型,我们可以研究食饵与捕食者之间的数量关系以及它们之间的相互作用。一般来说,食饵的数量被认为是捕食者的食物来源,并且捕食者的数量受到食饵的供给和捕食行为的影响。该模型通常描述了食饵数量随时间的变化,以及捕食者数量随时间的变化。食饵-捕食者模型的研究对于生态学、环境保护和资源管理等领域具有重要意义。通过该模型,我们可以深入了解生态系统中物种数量的变化规律,从而预测物种灭绝和生态系统崩溃的风险,以及寻找保护和管理生物多样性的方法。

实验使用的食饵-捕食者模型方程如下:
$$
\begin{aligned}&\frac{dx_1}{dt}=x_1-x_1\cdot x_2+sin\frac{y}{2}+cosy+2,\&\frac{dx_2}{dt}=x_1\cdot x_2-x_2,\&\frac{dy}{dt}=1\end{aligned}
$$
使用[3,3,0]T作为初值,生成从t=0到t=50,间隔h=0.05的数据作为训练集,最终得到的多步神经网络对常微分方程的反演效果如下图所示,其中红色曲线是原方程数值解的曲线,蓝色点是用无噪声数据训练的函数f_NN代替f得到的数值解:

食饵-捕食者模型方程反演效果

自治常微分方程

线性常微分方程——Adams-Moulton法

线性常微分方程(Linear ODEs)是微分方程中的一类重要问题,它描述了未知函数及其导数之间的线性关系。线性常微分方程的研究对于数学、物理学和工程学等领域具有重要意义。它们出现在物理学中许多基础问题的数学描述中,例如弹簧振动、电路分析、传热过程等。在工程学中,线性常微分方程也广泛应用于控制系统的建模和分析。随着科学技术的发展,研究者提出了各种各样的数值方法和近似方法,以求解复杂的线性常微分方程。这些方法包括欧拉方法、龙格-库塔方法、有限差分方法、变分法等,它们为实际问题的求解提供了有效的数值工具。随着对非线性动力学系统的研究和认识的深入,研究者们开始将非线性常微分方程引入到线性常微分方程中,以描述更为复杂的现象和现实问题。

实验使用的线性常微分方程如下:
$$
\frac{dx_1}{dt}=x_1-4x_2,
$$

$$
\frac{dx_2}{dt}=4x_1-7x_2
$$

使用[0,-1]T作为初值,生成从t=0到t=10,间隔h=0.01的数据作为训练集,最终得到的多步神经网络对常微分方程的反演效果如下图所示,其中红色曲线是原方程数值解的曲线,蓝色点是用无噪声数据训练的函数f_NN代替f得到的数值解:

线性常微分方程反演效果

阻尼三次振子方程——Adams-Bashforth法

阻尼三次振子(Damped Cubic Oscillator)是一种物理系统,它描述了受到阻尼力和弹力作用的振动系统。阻尼指的是系统中存在能量损耗的因素,如摩擦力,它会导致振动的逐渐减弱;三次振子表示系统的势能函数是一个三次函数,它具有非线性的特性。阻尼三次振子最早出现在振动力学和非线性动力学的研究中。对于振动系统的研究是物理学、工程学和应用数学等领域的重要课题之一。阻尼三次振子的研究对于理解和预测复杂动力学系统的行为具有重要意义。通过分析阻尼三次振子的运动规律和稳定性,我们可以深入了解非线性振动系统的动力学特性,例如振动的周期性、混沌行为等。这些研究也为控制系统、电子电路、力学系统等领域的工程应用提供了参考和启示。

实验使用的阻尼三次振子方程如下:
$$
\frac{dx_1}{dt}=-0.1x_1^3+2x_2^3,
$$

$$
\frac{dx_2}{dt}=-2x_1^3-0.1x_2^3
$$

使用[1,1]T作为初值,生成从t=0到t=25,间隔h=0.01的数据作为训练集,最终得到的多步神经网络对常微分方程的反演效果如下图所示,其中红色曲线是原方程数值解的曲线,蓝色点是用无噪声数据训练的函数f_NN代替f得到的数值解:

阻尼三次振子方程反演效果

阻尼简谐摆方程——后向微分公式法

阻尼简谐摆(Damped Pendulum)是一个经典力学中的物理系统,它由一个具有质量的物体通过一根轻质绳或杆与一个固定支点相连接组成。阻尼简谐摆在受到重力作用下,沿着一条弧线进行周期性振动。阻尼简谐摆的研究对于理解和预测振动系统的行为具有重要意义。通过分析阻尼简谐摆的运动规律和稳定性,我们可以深入了解振动系统在存在阻尼时的响应特性,例如振动的振幅、频率等。这些研究不仅在理论物理学中具有重要意义,也为工程学中的控制系统、机械振动和结构动力学等领域的应用提供了基础。

实验使用的阻尼简谐摆方程如下:
$$
\begin{aligned}&\frac{dx_1}{dt}=x_2,\&\frac{dx_2}{dt}=-\alpha x_2-\beta\sin x_1,\&\alpha=0.2,\beta=8.91\end{aligned}
$$
使用[-1.193,-3.876]T作为初值,生成从t=0到t=20,间隔h=0.01的数据作为训练集,最终得到的多步神经网络对常微分方程的反演效果如下图所示,其中红色曲线是原方程数值解的曲线,蓝色点是用无噪声数据训练的函数f_NN代替f得到的数值解:

阻尼简谐摆方程反演效果

研究结论与创新

研究结论

本项目针对常微分方程的反演问题提出了一种基于多步神经网络改进的常微分方程反演算法,旨在从数据中提取内含的常微分方程。通过将导数数据融入训练数据,我们改进了多步神经网络的数据集,并优化了神经网络的损失函数,以提高模型的准确性和泛化能力。经过训练,改进的神经网络成功拟合原方程中的函数,实现了对非自治常微分方程的反演。我们还推导并讨论了改进算法的误差界。我们进一步将改进的算法应用于自治常微分方程反演,通过训练网络拟合原方程函数,拓展了算法在自治常微分方程反演中的适用性。

本项目通过实验以非自治常微分方程中的受迫振动方程、线性标量方程和食饵-捕食者模型方程,以及自治常微分方程中的线性常微分方程、阻尼三次振子方程和阻尼简谐摆方程为例,展示了算法的有效性。在实验中,我们添加了不同强度的噪声,并利用改进算法对常微分方程进行反演。实验结果表明,改进方法在大多数情况下优于多步神经网络,在处理有噪声数据时表现更为出色。这些结果充分验证了本文提出的方法在反演问题中的可行性和优越性。

研究方法创新性

传统方法通常利用多步神经网络来处理常微分方程的反演问题,主要集中在处理自治常微分方程,对于非自治常微分方程的反演效果不尽理想,特别是在使用带有噪声数据进行训练时表现不佳。本研究首次将研究重点聚焦于非自治常微分方程的反演,通过将非自治方程转化为自治方程的形式,提出了一种新的研究思路。针对带有噪声数据的方程反演问题,采用了改进损失函数的方法以提升算法的鲁棒性。此外,将这种新方法扩展应用于自治常微分方程的反演问题,结果显示其反演效果明显优于多步神经网络。这一新思路对解决常微分方程反演问题带来了一种新的方法。

参考文献

[1] 王开荣,杨大地编著.应用数值分析[M].高等教育出版社,2010.

[2] 付长铠.常微分方程反演的机器学习方法研究[D].长春工业大学,2024.

[3] Raissi M, Perdikaris P, Karniadakis G E. Multistep neural networks for data-driven discovery of nonlinear dynamical systems[J]. arXiv preprint arXiv:1801.01236, 2018.

[4] 李航. 统计学习方法[M]. 第二版. 北京:清华大学出版社, 2019.

[5] 陈新海, 刘杰, 万仟, 等. 一种改进的基于深度神经网络的偏微分方程求解方法[J]. 计算机工程与科学, 2022, 44(11): 1932-1940.

图像插值算法及其优化

研究背景及其意义

图像放大旋转是数字图像处理中最基础的几何变换操作,其核心在于如何通过插值算法重建原始图像中不存在的像素信息。当对图像进行放大操作时,输出图像的像素网格会超出原始图像的采样范围,需要通过插值来填补这些新增像素点的颜色值;而在旋转操作中,即使保持图像尺寸不变,原始像素的整数坐标经过旋转变换后也会落在新图像的非整数位置,同样需要通过插值来重新确定每个输出像素的颜色值。

图像插值是利用原图像中的颜色值通过一定的方法计算出待插入像素点的颜色值的过程。对图像进行插值一般有两个步骤:首先定义一个图像插值公式,然后利用该插值公式计算待插入点的颜色值。常见的图像插值算法有双线性法、最近邻法、非均匀法、双三次卷积插值法、双立方法、Lagrange法、 样条插值法、 克里金(Krijing) 插值法等。这些插值方法通常定义一个插值数据点的隐式函数,再提取该函数的等值面作为图像插值方法,常用的插值核包括线性插值核、样条插值核等。

  • 最近邻插值作为最简单的算法,直接将距离待插值点最近的已知像素值作为结果,虽然计算效率极高(时间复杂度O(1)),但会产生明显的块状伪影(“马赛克”)和锯齿形边缘;
  • 双线性插值通过考虑2×2邻域内四个像素的加权平均,在计算成本(O(n))和视觉效果之间取得平衡,但仍会导致高频信息丢失和边缘模糊;
  • 更高阶的双三次插值(使用4×4邻域)和样条插值虽然能提供更平滑的结果,但计算复杂度显著增加(O(n²)),且可能引入不必要的振铃效应。

现有算法的根本局限在于采用统一的插值核函数处理整幅图像,忽视了图像不同区域的特征差异。例如,在平坦区域使用复杂插值会造成计算资源浪费,而在纹理丰富区域使用简单插值又会导致细节损失。基于此,我们希望通过改良的四平面插值算法对图像的放大与旋转效果进行优化,根据图像局部特征自适应地选择不同的插值策略,以规避用同一个插值公式对所有像素进行插值存在的不足。

常用图像插值算法

课本在6.5节中提到,在插值节点数量较多时,为避免Runge振荡现象的发生,并不提倡用高次多项式进行插值,而宁可用低次多项式作分段插值。在图像处理这一特定的应用场景中,需要处理的图像尺寸规模往往较大,且同一行(列)的所有像素颜色值显然并不具有可以用一个多项式函数显式表达的规律,但相邻的像素点颜色值之间又存在一定的关联性,因此分段插值仅考虑局部特征的特性在这里能够良好地契合所需性能。根据对于待插入像素点周围已有的像素点信息的利用情况,这里列举了几种常见的图像插值算法:

  • 最近邻法:仅利用待插值像素点转换至原图像坐标后距离其最近的一个像素点的颜色值,将其直接作为待插值像素点的颜色值
  • 双线性法:利用待插值像素点转换至原图像坐标后距离其最近的四个像素点的颜色值,加权平均后作为待插值像素点的颜色值
  • 双立方法:利用待插值像素点转换至原图像坐标后距离其最近的十六个像素点的颜色值,加权平均后作为待插值像素点的颜色值

最近邻法

一维最近邻插值示意图

如上图所示,在一维最近邻插值中,坐标轴上各点 xi-1,xi,xi+1 … 两两对半等分间隔 (红色虚线划分),从而非边界的各坐标点都有一个等宽的邻域,并根据每个坐标点的值构成一个类似分段函数的函数约束,从而使各插值坐标点的值等同于所在邻域原坐标点的值。例如,插值点 x 坐落于 坐标点 xi 的邻域,那么其值 f(x) 就等于 f(xi)。

在二维的图像插值场景中,可以对上述一维最近邻插值进行推广,如下图所示:

二维最近邻插值示意图

可以看到,(x0, y0)、(x0, y1)、(x1, y0)、(x1, y1) 都是原图像上的坐标点,颜色值分别对应为 Q11、Q12、Q21、Q22。而颜色值未知的插值点 (x, y)(需转换至原图像坐标),根据最近邻插值方法的约束,其与坐标点 (x0, y0) 位置最接近 (即位于 (x0, y0) 的邻域内),故插值点 (x, y) 的颜色值 P = Q11。

总而言之,最近邻法的基本思想即:将待插入点的坐标进行四舍五入,再以该行列坐标都是整数点的颜色值(灰度值)替代待插入点(x, y)处的颜色值。事实上,这也正是机器学习中KNN(K-Nearest Neighbor)算法在K=1时的情形。

基于以上算法思想,编写python函数代码实现图像放缩与旋转过程中的最近邻法插值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 最近邻法插值实现图像放缩
def nearest_neighbor_interpolation(image, scale_factor):
h, w, channel = image.shape
new_h, new_w = int(h * scale_factor), int(w * scale_factor)
resized_image = np.zeros((new_h, new_w, int(channel)), dtype=image.dtype)

for i in range(new_h):
for j in range(new_w):
src_i = int(round((i + 1) / scale_factor, 0))
src_j = int(round((j + 1) / scale_factor, 0))
resized_image[i, j] = image[src_i - 1, src_j - 1]

return resized_image

# 最近邻法插值实现图像旋转
def nearest_neighbor_rotation(image, angle):
h, w, channel = image.shape
angle_rad = math.radians(angle)

# 计算旋转后的图像尺寸
cos_theta = abs(math.cos(angle_rad))
sin_theta = abs(math.sin(angle_rad))
new_w = int(h * sin_theta + w * cos_theta)
new_h = int(h * cos_theta + w * sin_theta)

# 旋转中心
cx, cy = w / 2, h / 2
new_cx, new_cy = new_w / 2, new_h / 2

rotated_image = np.zeros((new_h, new_w, channel), dtype=image.dtype)

for i in range(new_h):
for j in range(new_w):
# 将新图像坐标转换回原图像坐标
x = (j - new_cx) * math.cos(angle_rad) + (i - new_cy) * math.sin(angle_rad) + cx
y = -(j - new_cx) * math.sin(angle_rad) + (i - new_cy) * math.cos(angle_rad) + cy

# 最近邻插值
if 0 <= x < w and 0 <= y < h:
src_x = int(round(x))
src_y = int(round(y))
rotated_image[i, j] = image[src_y - 1, src_x - 1]

return rotated_image

双线性法

一维线性插值示意图

如上图所示,在一维的线性插值中,坐标轴上各点 xi-1,xi,xi+1 … 的值“两两直接相连”为线段,从而构成了一条连续的约束函数。而插值坐标点例如 x,根据约束函数其值应为 f(x)。因为每两个坐标点之间的约束函数曲线是一次线性的线段,对插值结果而言是“线性” 的,所以该方法称为线性插值。基于线性函数的特性,可以便捷地求取原图像上的两个像素点间任一待插值点的颜色值:

一维线性插值计算示意图

可以看到,图中 x0 和 x1 都是原有的坐标点,颜色值分别对应为 y0 和 y1,此时根据线性插值法约束,在 (x0, y0) 和 (x1, y1) 构成的一次函数上,颜色值未知的插值点 x的颜色值 y 即为:
$$
y=y_0+(x-x_0)\frac{y_1-y_0}{x_1-x_0}=y_0+\frac{(x-x_0)y_1-(x-x_0)y_0}{x_1-x_0}
$$
实际上,即便 x 不在 x0 与 x1 之间,该公式也成立(此时为线性外插),但图像处理中不需涉及此情形。

从一维的线性插值出发,很容易拓展到二维图像的双线性插值,通过三次一阶线性插值(本质为加权求和)获得最终结果,下图便展示了该过程的定性斜视与定量俯视示意图:

二维线性插值定性斜视示意图

二维线性插值定量俯视示意图

其中,(x0, y0)、(x0, y1)、(x1, y0)、(x1, y1) 均为原图像上的像素坐标点,颜色值分别对应为 f(x0, y0)、f(x0, y1)、f(x1, y0)、f(x1, y1)。而颜色值未知的插值点 (x, y),根据双线性插值法的约束,可以先由像素坐标点 (x0, y0) 和 (x0, y1) 在 y 轴向作一维线性插值得到 f(x0, y)、由像素坐标点 (x1, y0) 和 (x1, y1) 在 y 轴向作一维线性插值得到 f(x1, y),然后再由 (x0, y) 和 (x1, y) 在 x 轴向作一维线性插值得到插值点 (x, y) 的灰度值 f(x, y)。

事实上,一维线性插值先作 x 轴向再作 y 轴向,得到的结果完全相同,仅为顺序先后的区别。这里不妨先由像素坐标点 (x0, y0) 和 (x1, y0) 在 x 轴向作一维线性插值得到 f(x, y0)、由像素坐标点 (x0, y1) 和 (x1, y1) 在 x 轴向作一维线性插值得到 f(x, y1):
$$
f(x,y_0)=\frac{x_1-x}{x_1-x_0}f(x_0,y_0)+\frac{x-x_0}{x_1-x_0}f(x_1,y_0)
$$

$$
f(x,y_1)=\frac{x_1-x}{x_1-x_0}f(x_0,y_1)+\frac{x-x_0}{x_1-x_0}f(x_1,y_1)
$$

然后再由 (x, y0) 和 (x, y1) 在 y 轴向作一维线性插值得到插值点 (x, y) 的灰度值 f(x, y):
$$
f(x,y)=\frac{y_1-y}{y_1-y_0}f(x,y_0)+\frac{y-y_0}{y_1-y_0}f(x,y_1)
$$
合并上述式子,得到最终的双线性插值结果:
$$
f(x,y)=\frac{(y_1-y)(x_1-x)}{(y_1-y_0)(x_1-x_0)}f(x_0,y_0)+\frac{(y_1-y)(x-x_0)}{(y_1-y_0)(x_1-x_0)}f(x_1,y_0)+\frac{(y-y_0)(x_1-x)}{(y_1-y_0)(x_1-x_0)}f(x_0,y_1)+\frac{(y-y_0)(x-x_0)}{(y_1-y_0)(x_1-x_0)}
$$
值得注意的是,在实际的图像插值处理过程中,为尽量保证插值效果的准确性,往往仅采用距离待插值点(转换至原图像坐标)最近的四个点,即:([]符号表示待插值点转换至原图像坐标后向下取整)
$$
x_0=[x],y_0=[y]
$$

$$
x_1=x_0+1,y_1=y_0+1
$$

从加权求和的角度理解,可以进一步地将双线性插值结果改写为如下形式:
$$
p=x-[x], q=y-[y]
$$

$$
\begin{array}{rcl}f(x,y)=(1-q){(1-p)f([x][y])+pf([x]+1,[y])}+q{(1-p)f([x],[y]+1)+pf([x]+1,[y]+1)}\end{array}
$$

二维线性插值加权求和角度示意图

基于以上算法思想,编写python函数代码实现图像放缩与旋转过程中的双线性法插值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# 双线性法插值实现图像放缩
def bilinear_interpolation(image, scale_factor):
h, w, channel = image.shape
new_h, new_w = int(h * scale_factor), int(w * scale_factor)
resized_image = np.zeros((new_h, new_w, int(channel)), dtype=image.dtype)

for i in range(new_h):
for j in range(new_w):
x = (j + 1) / scale_factor
y = (i + 1) / scale_factor
x1 = int(x)
y1 = int(y)
x2 = x1 + 1
y2 = y1 + 1
p = x - x1
q = y - y1

# 边界问题处理
if x2 == w + 1:
x2 = x1
if y2 == h + 1:
y2 = y1

resized_image[i ,j] = (1 - q) * ((1 - p) * image[y1 - 1, x1 - 1] + p * image[y1 - 1, x2 - 1]) + q * ((1 - p) * image[y2 - 1, x1 - 1] + p * image[y2 - 1, x2 - 1])

return resized_image

# 双线性法插值实现图像旋转
def bilinear_rotation(image, angle):
h, w, channel = image.shape
angle_rad = math.radians(angle)

# 计算旋转后的图像尺寸
cos_theta = abs(math.cos(angle_rad))
sin_theta = abs(math.sin(angle_rad))
new_w = int(h * sin_theta + w * cos_theta)
new_h = int(h * cos_theta + w * sin_theta)

# 旋转中心
cx, cy = w / 2, h / 2
new_cx, new_cy = new_w / 2, new_h / 2

rotated_image = np.zeros((new_h, new_w, channel), dtype=image.dtype)

for i in range(new_h):
for j in range(new_w):
# 将新图像坐标转换回原图像坐标
x = (j - new_cx) * math.cos(angle_rad) + (i - new_cy) * math.sin(angle_rad) + cx
y = -(j - new_cx) * math.sin(angle_rad) + (i - new_cy) * math.cos(angle_rad) + cy

# 双线性插值
if 0 <= x < w-1 and 0 <= y < h-1:
x1, y1 = int(x), int(y)
x2, y2 = min(x1 + 1, w - 1), min(y1 + 1, h - 1)

# 计算权重
a = x - x1
b = y - y1

# 边界处理
if x2 >= w:
x2 = x1
if y2 >= h:
y2 = y1

# 插值计算
rotated_image[i, j] = (1 - a) * (1 - b) * image[y1, x1] + \
a * (1 - b) * image[y1, x2] + \
(1 - a) * b * image[y2, x1] + \
a * b * image[y2, x2]

return rotated_image

双立方法

双立方法插值又称立方卷积插值/双三次插值,这也是数值分析中最常用的二维插值方法。在这种方法中,插值点 (x, y) 的像素颜色值 f(x, y) 通过矩形网格中最近的十六个采样点的加权平均得到,而各采样点的权重由该点到待求插值点的距离确定,此距离包括水平和竖直两个方向上的距离。相比之下,双线性插值仅由周围的四个采样点加权得到。

双立方法插值示意图

如上图所示,设(转换至原图像中)待求插值点坐标为 (i+u, j+v)【i、j为整数部分,u、v为小数部分】,已知其周围的 16 个像素坐标点 (网格) 的颜色值,还需要计算 16 个点各自的权重。以像素坐标点 (i, j) 为例,因为该点在 y 轴和 x 轴方向上与待求插值点 (i+u, j+v) 的距离分别为 u 和 v,所以其权重为 w(u) × w(v),其中 w(·) 是插值权重核 (可以理解为定义的权重函数)。同理可得其余 15 个像素坐标点各自的权重。那么,待求插值点 (i+u, j+v) 的颜色值 f(i+u, j+v) 将通过如下计算得到:
$$
f(i+u,j+v)=A\times B\times C
$$
其中各项由向量或矩阵表示为:
$$
\mathrm{A}=[w(1+u)w(u)w(1-u)w(2-u)]
$$

$$
\mathrm{B}=\begin{bmatrix}f(i-1,j-1)&f(i-1,j+0)&f(i-1,j+1)&f(i-1,j+2)\f(i+0,j-1)&f(i+0,j+0)&f(i+0,j+1)&f(i+0,j+2)\f(i+1,j-1)&f(i+1,j+0)&f(i+1,j+1)&f(i+1,j+2)\f(i+2,j-1)&f(i+2,j+0)&f(i+2,j+1)&f(i+2,j+2)\end{bmatrix}
$$

$$
\mathbb{C}=[w(1+v)w(v)w(1-v)w(2-v)]^T
$$

插值权重核 w(·) 为:
$$
w(x)=\begin{cases}1-2|x|^2+|x|^3&,|x|<1\4-8|x|+5|x|^2-|x|^3&,1\leq|x|<2\0&,|x|\geq2&\end{cases}
$$
插值权重核 w(·) 的函数图像:

双立方法插值权重核函数图像

为方便后续算法实现,将以上加权求和过程各步骤展开,合并后化简得到待插入点的颜色值计算公式:
$$
f(i+u,j+v)=\sum_{m=0}^{3}\sum_{n=0}^{3}a_{mn}u^{m}v^{n}
$$
其中多项式的系数a_{mn}计算公式如下:(式中p {qr}与上述矩阵B中元素一一对应,如p 00=f(i-1,j-1))
$$
\begin{aligned}
&a
{00}=p
{11}\&a_{01}=-\frac{1}{2}p_{10}+\frac{1}{2}p_{12}\&a_{02}=p_{10}-\frac{5}{2}p_{11}+2p_{12}-\frac{1}{2}p_{13}\&a_{03}=-\frac{1}{2}p_{10}+\frac{3}{2}p_{11}-\frac{3}{2}p_{12}+\frac{1}{2}p_{13}\&a_{10}=-\frac{1}{2}p_{01}+\frac{1}{2}p_{21}\&a_{11}=\frac{1}{4}p_{00}-\frac{1}{4}p_{02}-\frac{1}{4}p_{20}+\frac{1}{4}p_{22}\&a_{12}=-\frac{1}{2}p_{00}+\frac{1}{4}p_{01}-p_{02}+\frac{1}{4}p_{03}+\frac{1}{2}p_{20}-\frac{5}{4}p_{21}+p_{22}-\frac{1}{4}p_{23}\&a_{13}=\frac{1}{4}p_{00}-\frac{3}{4}p_{01}+\frac{3}{4}p_{02}-\frac{1}{4}p_{03}-\frac{1}{4}p_{20}+\frac{3}{4}p_{21}-\frac{3}{4}p_{22}+\frac{1}{4}p_{23}\
&a_{20}=p_{01}-\frac{5}{2}p_{11}+2p_{21}-\frac{1}{2}p_{31}\
&a_{21}=-\frac{1}{2}p_{00}+\frac{1}{2}p_{02}+\frac{5}{4}p_{10}-\frac{5}{4}p_{12}-p_{20}+p_{22}+\frac{1}{4}p_{30}-\frac{1}{4}p_{32}\&a_{22}=p_{00}-\frac{5}{2}p_{01}+2p_{02}-\frac{1}{2}p_{03}-\frac{5}{2}p_{10}+\frac{25}{4}p_{11}-5p_{12}+\frac{5}{4}p_{13}+2p_{20}-5p_{21}+4p_{22}-p_{23}-\frac{1}{2}p_{30}+\frac{5}{4}p_{31}-p_{32}+\frac{1}{4}p_{33}\
&a_{23}=-\frac{1}{2}p_{00}+\frac{3}{2}p_{01}-\frac{3}{2}p_{02}+\frac{1}{2}p_{03}+\frac{5}{4}p_{10}-\frac{15}{4}p_{11}+\frac{15}{4}p_{12}-\frac{5}{4}p_{13}-p_{20}+3p_{21}-3p_{22}+p_{23}+\frac{1}{4}p_{30}-\frac{3}{4}p_{31}+\frac{3}{4}p_{32}-\frac{1}{4}p_{33}\
&a_{30}=-\frac{1}{2}p_{01}+\frac{3}{2}p_{11}-\frac{3}{2}p_{21}+\frac{1}{2}p_{31}\
&a_{31}=\frac{1}{4}p_{00}-\frac{1}{4}p_{02}-\frac{3}{4}p_{10}+\frac{3}{4}p_{12}+\frac{3}{4}p_{20}-\frac{3}{4}p_{22}-\frac{1}{4}p_{30}+\frac{1}{4}p_{32}\&a_{32}=-\frac{1}{2}p_{00}+\frac{5}{4}p_{01}-p_{02}+\frac{1}{4}p_{03}+\frac{3}{2}p_{10}-\frac{15}{4}p_{11}+3p_{12}-\frac{3}{4}p_{13}-\frac{3}{2}p_{20}+\frac{15}{4}p_{21}-3p_{22}+\frac{3}{4}p_{23}+\frac{1}{2}p_{30}-\frac{5}{4}p_{31}+p_{32}-\frac{1}{4}p_{33}\&a_{33}=\frac{1}{4}p_{00}-\frac{3}{4}p_{01}+\frac{3}{4}p_{02}-\frac{1}{4}p_{03}-\frac{3}{4}p_{10}+\frac{9}{4}p_{11}-\frac{9}{4}p_{12}+\frac{3}{4}p_{13}+\frac{3}{4}p_{20}-\frac{9}{4}p_{21}+\frac{9}{4}p_{22}-\frac{3}{4}p_{23}-\frac{1}{4}p_{30}+\frac{3}{4}p_{31}-\frac{3}{4}p_{32}+\frac{1}{4}p_{33}
\end{aligned}
$$
基于以上算法思想,编写python函数代码实现图像放缩与旋转过程中的双立方法插值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# 双立方法插值实现图像放缩
def bicubic_interpolation(image, scale_factor):
h, w, channel = image.shape
new_h, new_w = int(h * scale_factor), int(w * scale_factor)
resized_image = np.zeros((new_h, new_w, channel))

for i in range(new_h):
for j in range(new_w):
x = i / scale_factor
y = j / scale_factor

# 确定16个邻域像素的坐标
x0 = max(0, int(np.floor(x)) - 1)
x1 = x0 + 1
x2 = x0 + 2
x3 = min(w-1, x0 + 3)

y0 = max(0, int(np.floor(y)) - 1)
y1 = y0 + 1
y2 = y0 + 2
y3 = min(h-1, y0 + 3)

# 获取16个邻域像素的值
p = np.zeros((4, 4, 3))
for m in range(4):
for n in range(4):
xi = x0 + n
yi = y0 + m
xi = min(max(xi, 0), w-1) # 边界处理
yi = min(max(yi, 0), h-1)
p[m, n] = image[yi, xi]

# 计算相对位置
dx = x - x1
dy = y - y1

# 系数
a = np.zeros((4, 4, channel))
a[0, 0] = p[1, 1]
a[0, 1] = -0.5*p[1, 0] + 0.5*p[1, 2]
a[0, 2] = p[1, 0] - 2.5*p[1, 1] + 2*p[1, 2] - 0.5*p[1, 3]
a[0, 3] = -0.5*p[1, 0] + 1.5*p[1, 1] - 1.5*p[1, 2] + 0.5*p[1, 3]

a[1, 0] = -0.5*p[0, 1] + 0.5*p[2, 1]
a[1, 1] = 0.25*p[0, 0] - 0.25*p[0, 2] - 0.25*p[2, 0] + 0.25*p[2, 2]
a[1, 2] = -0.5*p[0, 0] + 1.25*p[0, 1] - p[0, 2] + 0.25*p[0, 3] + 0.5*p[2, 0] - 1.25*p[2, 1] + p[2, 2] - 0.25*p[2, 3]
a[1, 3] = 0.25*p[0, 0] - 0.75*p[0, 1] + 0.75*p[0, 2] - 0.25*p[0, 3] - 0.25*p[2, 0] + 0.75*p[2, 1] - 0.75*p[2, 2] + 0.25*p[2, 3]

a[2, 0] = p[0, 1] - 2.5*p[1, 1] + 2*p[2, 1] - 0.5*p[3, 1]
a[2, 1] = -0.5*p[0, 0] + 0.5*p[0, 2] + 1.25*p[1, 0] - 1.25*p[1, 2] - p[2, 0] + p[2, 2] + 0.25*p[3, 0] - 0.25*p[3, 2]
a[2, 2] = p[0, 0] - 2.5*p[0, 1] + 2*p[0, 2] - 0.5*p[0, 3] - 2.5*p[1, 0] + 6.25*p[1, 1] - 5*p[1, 2] + 1.25*p[1, 3] + 2*p[2, 0] - 5*p[2, 1] + 4*p[2, 2] - p[2, 3] - 0.5*p[3, 0] + 1.25*p[3, 1] - p[3, 2] + 0.25*p[3, 3]
a[2, 3] = -0.5*p[0, 0] + 1.5*p[0, 1] - 1.5*p[0, 2] + 0.5*p[0, 3] + 1.25*p[1, 0] - 3.75*p[1, 1] + 3.75*p[1, 2] - 1.25*p[1, 3] - p[2, 0] + 3*p[2, 1] - 3*p[2, 2] + p[2, 3] + 0.25*p[3, 0] - 0.75*p[3, 1] + 0.75*p[3, 2] - 0.25*p[3, 3]

a[3, 0] = -0.5*p[0, 1] + 1.5*p[1, 1] - 1.5*p[2, 1] + 0.5*p[3, 1]
a[3, 1] = 0.25*p[0, 0] - 0.25*p[0, 2] - 0.75*p[1, 0] + 0.75*p[1, 2] + 0.75*p[2, 0] - 0.75*p[2, 2] - 0.25*p[3, 0] + 0.25*p[3, 2]
a[3, 2] = -0.5*p[0, 0] + 1.25*p[0, 1] - p[0, 2] + 0.25*p[0, 3] + 1.5*p[1, 0] - 3.75*p[1, 1] + 3*p[1, 2] - 0.75*p[1, 3] - 1.5*p[2, 0] + 3.75*p[2, 1] - 3*p[2, 2] + 0.75*p[2, 3] + 0.5*p[3, 0] - 1.25*p[3, 1] + p[3, 2] - 0.25*p[3, 3]
a[3, 3] = 0.25*p[0, 0] - 0.75*p[0, 1] + 0.75*p[0, 2] - 0.25*p[0, 3] - 0.75*p[1, 0] + 2.25*p[1, 1] - 2.25*p[1, 2] + 0.75*p[1, 3] + 0.75*p[2, 0] - 2.25*p[2, 1] + 2.25*p[2, 2] - 0.75*p[2, 3] - 0.25*p[3, 0] + 0.75*p[3, 1] - 0.75*p[3, 2] + 0.25*p[3, 3]

# 计算插值结果
value = np.zeros(channel)
for m in range(4):
for n in range(4):
value += a[m, n] * (dx**n) * (dy**m)

resized_image[i, j] = np.clip(value, 0, 255)

return resized_image.astype(np.uint8)

# 双立方法插值实现图像旋转
def bicubic_rotation(image, angle):
h, w, channel = image.shape
angle_rad = math.radians(angle)

# 计算旋转后的图像尺寸
cos_theta = abs(math.cos(angle_rad))
sin_theta = abs(math.sin(angle_rad))
new_w = int(h * sin_theta + w * cos_theta)
new_h = int(h * cos_theta + w * sin_theta)

# 旋转中心
cx, cy = w / 2, h / 2
new_cx, new_cy = new_w / 2, new_h / 2

rotated_image = np.zeros((new_h, new_w, channel))

for i in range(new_h):
for j in range(new_w):
# 将新图像坐标转换回原图像坐标
x = (j - new_cx) * math.cos(angle_rad) + (i - new_cy) * math.sin(angle_rad) + cx
y = -(j - new_cx) * math.sin(angle_rad) + (i - new_cy) * math.cos(angle_rad) + cy

if 0 <= x < w and 0 <= y < h:
# 确定16个邻域像素的坐标
x0 = max(0, int(np.floor(x)) - 1)
x1 = x0 + 1
x2 = x0 + 2
x3 = min(w-1, x0 + 3)

y0 = max(0, int(np.floor(y)) - 1)
y1 = y0 + 1
y2 = y0 + 2
y3 = min(h-1, y0 + 3)

# 获取16个邻域像素的值
p = np.zeros((4, 4, channel))
for m in range(4):
for n in range(4):
xi = x0 + n
yi = y0 + m
xi = min(max(xi, 0), w-1) # 边界处理
yi = min(max(yi, 0), h-1)
p[m, n] = image[yi, xi]

# 计算相对位置
dx = x - x1
dy = y - y1

# 系数
a = np.zeros((4, 4, channel))
a[0, 0] = p[1, 1]
a[0, 1] = -0.5*p[1, 0] + 0.5*p[1, 2]
a[0, 2] = p[1, 0] - 2.5*p[1, 1] + 2*p[1, 2] - 0.5*p[1, 3]
a[0, 3] = -0.5*p[1, 0] + 1.5*p[1, 1] - 1.5*p[1, 2] + 0.5*p[1, 3]

a[1, 0] = -0.5*p[0, 1] + 0.5*p[2, 1]
a[1, 1] = 0.25*p[0, 0] - 0.25*p[0, 2] - 0.25*p[2, 0] + 0.25*p[2, 2]
a[1, 2] = -0.5*p[0, 0] + 1.25*p[0, 1] - p[0, 2] + 0.25*p[0, 3] + 0.5*p[2, 0] - 1.25*p[2, 1] + p[2, 2] - 0.25*p[2, 3]
a[1, 3] = 0.25*p[0, 0] - 0.75*p[0, 1] + 0.75*p[0, 2] - 0.25*p[0, 3] - 0.25*p[2, 0] + 0.75*p[2, 1] - 0.75*p[2, 2] + 0.25*p[2, 3]

a[2, 0] = p[0, 1] - 2.5*p[1, 1] + 2*p[2, 1] - 0.5*p[3, 1]
a[2, 1] = -0.5*p[0, 0] + 0.5*p[0, 2] + 1.25*p[1, 0] - 1.25*p[1, 2] - p[2, 0] + p[2, 2] + 0.25*p[3, 0] - 0.25*p[3, 2]
a[2, 2] = p[0, 0] - 2.5*p[0, 1] + 2*p[0, 2] - 0.5*p[0, 3] - 2.5*p[1, 0] + 6.25*p[1, 1] - 5*p[1, 2] + 1.25*p[1, 3] + 2*p[2, 0] - 5*p[2, 1] + 4*p[2, 2] - p[2, 3] - 0.5*p[3, 0] + 1.25*p[3, 1] - p[3, 2] + 0.25*p[3, 3]
a[2, 3] = -0.5*p[0, 0] + 1.5*p[0, 1] - 1.5*p[0, 2] + 0.5*p[0, 3] + 1.25*p[1, 0] - 3.75*p[1, 1] + 3.75*p[1, 2] - 1.25*p[1, 3] - p[2, 0] + 3*p[2, 1] - 3*p[2, 2] + p[2, 3] + 0.25*p[3, 0] - 0.75*p[3, 1] + 0.75*p[3, 2] - 0.25*p[3, 3]

a[3, 0] = -0.5*p[0, 1] + 1.5*p[1, 1] - 1.5*p[2, 1] + 0.5*p[3, 1]
a[3, 1] = 0.25*p[0, 0] - 0.25*p[0, 2] - 0.75*p[1, 0] + 0.75*p[1, 2] + 0.75*p[2, 0] - 0.75*p[2, 2] - 0.25*p[3, 0] + 0.25*p[3, 2]
a[3, 2] = -0.5*p[0, 0] + 1.25*p[0, 1] - p[0, 2] + 0.25*p[0, 3] + 1.5*p[1, 0] - 3.75*p[1, 1] + 3*p[1, 2] - 0.75*p[1, 3] - 1.5*p[2, 0] + 3.75*p[2, 1] - 3*p[2, 2] + 0.75*p[2, 3] + 0.5*p[3, 0] - 1.25*p[3, 1] + p[3, 2] - 0.25*p[3, 3]
a[3, 3] = 0.25*p[0, 0] - 0.75*p[0, 1] + 0.75*p[0, 2] - 0.25*p[0, 3] - 0.75*p[1, 0] + 2.25*p[1, 1] - 2.25*p[1, 2] + 0.75*p[1, 3] + 0.75*p[2, 0] - 2.25*p[2, 1] + 2.25*p[2, 2] - 0.75*p[2, 3] - 0.25*p[3, 0] + 0.75*p[3, 1] - 0.75*p[3, 2] + 0.25*p[3, 3]

# 计算插值结果
value = np.zeros(channel)
for m in range(4):
for n in range(4):
value += a[m, n] * (dx**n) * (dy**m)

rotated_image[i, j] = np.clip(value, 0, 255)

return rotated_image.astype(np.uint8)

图像插值算法优化:基于四平面

在上述的多种基于分段插值的图像插值算法中,均采用f(i, j)来表示图像的像素点坐标处的颜色值,其中i表示行坐标,j表示列坐标。为进一步地体现图像的局部特征差异并将其用于插值过程,我们引入“平面”的概念,并对图像数据进行升维处理,用三维空间点(i, j, f(i, j))来表示一个像素,并将其对应至空间坐标系中的一个点(x, y, z)。

对一个待插入点而言,可以通过坐标平移将其周围4 个像素点转换为:(注意:此处z0z3为像素坐标点s0s3的颜色值,下同)
$$
s_0(0,0,z_0),s_1(0,1,z_1),s_2(1,0,z_2),s_3(1,1,z_3)
$$
从上述4个点的坐标可以看出它们任意3个点一定不在同一条直线上, 不在同一直线上的3个点可以确定一个平面, 下面讨论具体的插值方法:

  1. 先求出这4个点可能的4个平面方程

    已知空间平面的一般方程为:
    $$
    Ax+By+Cz+D=0
    $$
    将s0、s1、s2分别带入上式可得:
    $$
    \begin{cases}Cz_0+D=0\B+Cz_1+D=0\A+Cz_2+D=0&\end{cases}
    $$
    则有:
    $$
    D=-Cz_0,B=C(z_0-z_1),A=C(z_0-z_2)
    $$
    再将其带回空间平面方程,整理后用f(x, y)代替z得到插值公式:
    $$
    f(x,y)=(z_{2}-z_{0})x+(z_{1}-z_{2})y+z_{0}
    $$
    同理,将s0、s1、s3带入空间平面方程可得插值公式:
    $$
    f(x,y)=(z_{3}-z_{1})x+(z_{1}-z_{0})y+z_{0}
    $$
    将s0、s2、s3带入空间平面方程可得插值公式:
    $$
    f(x,y)=(z_{2}-z_{0})x+(z_{3}-z_{2})y+z_{0}
    $$
    将s1、s2、s3带入空间平面方程可得插值公式:
    $$
    f(x,y)=(z_{3}-z_{1})x+(z_{3}-z_{2})y+(z_{2}+z_{1}-z_{3})
    $$

  2. 如果s0、s1、s2、s3这4 个点在同一平面上, 则使用上述任意一个插值公式进行插值均可。 【平面法】

    判断这4个点是否在同一平面上, 只需要比较z1+z2 与 z0+z3是否相等:

    线段s0s3中点坐标为
    $$
    (\frac12,\frac12,\frac{z_0+z_3}2)
    $$
    线段s1s2中点坐标为
    $$
    (\frac12,\frac12,\frac{z_1+z_2}2)
    $$
    如果它们的中点坐标相同,则说明两条线段相交,相交的两条直线可以决定一个平面,即如果待插人点周围的四个点满足:
    $$
    z_1+z_2=z_0+z_3
    $$
    则这它们就是同一平面上的 4 个点,否则就不是同一平面上的 4 个点。

  3. 从4个可能的平面中选择一个平面进行插值【四平面法】

    如果它们不是同一平面上的4个点, 情况比较复杂, 需认真讨论,s0、s1、s2、s34个点的位置关系如下图所示:

    四点不在同一平面

    在插值的过程中如果一半的区域选择由s0、s1、s2 所确定的平面进行插值, 则另一半必须选择由s1、s2、s3所确定的平面进行插值, 以保证对角线的每一边都是在同一个平面上, 避免出现 “锯齿形” 边缘,为了便于描述, 称s0、s1、s2所确定的平面为 “左下平面”,s1、s2、s3 所确定的平面为 “右上平面”,s0、s1、s3 所确定的平面 “左上平面”,s0、s2、s3所确定的平面 “右下平面”。 为此, 需要参考周围其他点的情况以决定选择哪个平面进行插值。 具体情况如下图所示(黑点是待插入点周围的4个点,白点是参考点):

    待插入点周围的像素点

    1. 对于“左下平面”, 只能参考s0、s1、s2三点左面和下面的点, 即s0、s1、s2三点与s4、s5、s6、s8四个点中的任意一点在同一平面上即可。
    2. 对于“右上平面”,只能参考s1、s2、s3三点右面和上面的点, 即s1、s2、s3三点与s7、s9、s10、s11四个点中的任意一点在同一平面上即可。
    3. 对于 “左上平面”,只能参考s0、s1、s3三点左面和上面的点, 即s0、s1、s3三点与s6、s8、s10、s11四个点中的任意一点在同一平面上即可。
    4. 对于 “右下平面”,只能参考s0、s2、s3三点右面和下面的点, 即s0、s2、s3三点与s4、s5、s7、s9四 个点中的任意一点在同一平面上即可。

    针对1、2两种情况, 当y = 1 + x时,用 “左下平面” 进行插值, 否则用 “右上平面” 进行插值;针对3、4两种情况, 当y = x ^ 3时,用 “左上平面” 进行插值, 否则用 “右下平面” 进行插值。

    判断4个点在同一平面上的方法:(以情况1为例)

    • 对于判断s0、s2、s1、s8 4 点是否在同一平面上, 只需要判断z0 + z1与z2 + z8是否相等即可;

    • 对于s0、s1、s2、s5 4点, 只需要判断z0 + z2与z1 + z5是否相等即可;

    • 对于s0、s1、s2、s6 4点:如果s0、s2、s6 3点在同一直线上, 则直线外一点s1与该直线就可以确定一个平面,而要判断这三点是否在同一直线上,只需判断z2 + z6与2 * z0是否相等即可【线段s2(1, 0, z2) s6(-1, 0, z6) 的中点坐标为(0, 0, z2 + z6),若z2 + z6 = 2 * z0, 则点s0(0, 0, z0)就是它们的中点坐标,当然这3点就在同一条直线上】;

    • 对于s0、s1、s2、s4 4点, 与s0、s1、s2、s6 4 点的情况相同。

  4. 如果2和3两点中的情形均不满足, 说明待插入点周围的情况太复杂(不符合平面插值), 此时采用双线性法进行插值。

基于以上算法思想,编写python函数代码实现图像放缩与旋转过程中的四平面法插值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# 四平面法插值实现图像放缩
def four_plane_interpolation(img, scale):
H, W, C = img.shape
new_H, new_W = int(H * scale), int(W * scale)
output = np.zeros((new_H, new_W, C), dtype=img.dtype)

for y in range(new_H):
for x in range(new_W):
# 计算原图对应坐标(浮点数)
src_x = x / scale
src_y = y / scale

# 获取周围4个整数坐标点
x0, y0 = int(np.floor(src_x)), int(np.floor(src_y))
x1, y1 = min(x0 + 1, W - 1), min(y0 + 1, H - 1)

# 获取4个点的颜色值(z坐标)
s0 = img[y0, x0]
s1 = img[y0, x1]
s2 = img[y1, x0]
s3 = img[y1, x1]

# 计算相对位置(归一化到[0,1])
dx = src_x - x0
dy = src_y - y0

# 判断四点是否共面(z1 + z2 ≈ z0 + z3)
if np.allclose(s1 + s2, s0 + s3, atol=1e-6):
# 共面时,选择任意平面(此处用左下平面)
interpolated = s0 + (s2 - s0) * dx + (s1 - s0) * dy
else:
# 不共面时,动态选择平面
# 获取周围12个参考点(简化实现,仅取最近邻)
# 注:论文中需判断参考点是否共面,此处简化逻辑
if dy > 1 - dx: # 对角线 y = 1 - x 上方
# 选择右上平面
interpolated = (s3 - s1) * dx + (s3 - s2) * dy + (s2 + s1 - s3)
else:
# 选择左下平面
interpolated = s0 + (s2 - s0) * dx + (s1 - s0) * dy

# 边界检查
interpolated = np.clip(interpolated, 0, 255 if img.dtype == np.uint8 else 1.0)
output[y, x] = interpolated

return output

# 四平面法插值实现图像旋转
def four_plane_rotation(image, angle):
h, w, channel = image.shape
angle_rad = math.radians(angle)

# 计算旋转后的图像尺寸
cos_theta = abs(math.cos(angle_rad))
sin_theta = abs(math.sin(angle_rad))
new_w = int(h * sin_theta + w * cos_theta)
new_h = int(h * cos_theta + w * sin_theta)

# 旋转中心
cx, cy = w / 2, h / 2
new_cx, new_cy = new_w / 2, new_h / 2

rotated_image = np.zeros((new_h, new_w, channel), dtype=image.dtype)

for i in range(new_h):
for j in range(new_w):
# 将新图像坐标转换回原图像坐标
x = (j - new_cx) * math.cos(angle_rad) + (i - new_cy) * math.sin(angle_rad) + cx
y = -(j - new_cx) * math.sin(angle_rad) + (i - new_cy) * math.cos(angle_rad) + cy

# 边界检查
if 0 <= x < w and 0 <= y < h:
# 获取周围4个整数坐标点
x0, y0 = int(np.floor(x)), int(np.floor(y))
x1, y1 = min(x0 + 1, w - 1), min(y0 + 1, h - 1)

# 获取4个点的颜色值
s0 = image[y0, x0]
s1 = image[y0, x1]
s2 = image[y1, x0]
s3 = image[y1, x1]

# 计算相对位置
dx = x - x0
dy = y - y0

# 判断四点是否共面
if np.allclose(s1 + s2, s0 + s3, atol=1e-6):
# 共面时,选择任意平面(此处用左下平面)
interpolated = s0 + (s2 - s0) * dy + (s1 - s0) * dx
else:
# 不共面时,动态选择平面
if dy > 1 - dx: # 对角线 y = 1 - x 上方
# 选择右上平面
interpolated = (s3 - s1) * dx + (s3 - s2) * dy + (s2 + s1 - s3)
else:
# 选择左下平面
interpolated = s0 + (s2 - s0) * dy + (s1 - s0) * dx

# 边界检查
interpolated = np.clip(interpolated, 0, 255 if image.dtype == np.uint8 else 1.0)
rotated_image[i, j] = interpolated

return rotated_image

实验测试结果分析

一个理想的插值算法对一幅图像逆时针旋转若干度,再顺时针旋转若干度,应该与原图像相同;同理,对一幅图像放大若干倍,再缩小若干倍,也应该与原图像相同。 基于此,将下面的4幅图像分别用4种算法先逆时针旋转45°,再顺时针旋转45°;先放大4倍,再缩小4倍,然后分别用峰值信噪比(PSNR)验证各算法的优劣。

1琳娜

2辣椒

3狒狒

4房子

从定性实验的效果角度,上述四幅图像通过常用的三种分段插值算法完成上述的放大与旋转任务后得到的结果如下图所示:

1琳娜传统result

2辣椒传统result

3狒狒传统result

4房子传统result

从实验结果上来看,最近邻算法的边缘颜色“最醒目”,且出现了较为严重的“锯齿形”边缘现象;双线性算法的边缘颜色“最暗淡”;双线性算法和双三次算法也有“锯齿形”边缘现象, 但视觉效果相比最近邻算法而言并不明显。

通过改进的四平面插值算法,对上述四幅图像完成上述的放大与旋转任务后得到的结果如下图所示:

1琳娜四平面result

2辣椒四平面result

3狒狒四平面result

4房子四平面result

可以看到,四平面插值算法处理后的图像斜线边缘部分是 “光滑连续” 的, 视觉效果比较好,同时有效避免了“锯齿形”边缘现象和“马赛克”现象。

从定量实验的数据角度,我们对于各图像用不同算法完成上述旋转与放缩任务后得到的图像峰值信噪比与算法运行时间进行了计算与统计,结果如下表所示:

峰值信噪比(PSNR)用于表示信号的最大可能功率与影响其表示的保真度的破坏噪声的功率之间的比率。PSNR在图像处理上主要用于量化受有损压缩影响的图像和视频的重建质量。

PSNR 通过均方误差( MSE ) 定义。

给定一个无噪声的m×n单色图像I及其噪声近似值K,MSE定义为:
$$
MSE=\frac{1}{mn}\sum_{i=0}^{m-1}\sum_{j=0}^{n-1}[I(i,j)-K(i,j)]^2.
$$
故PSNR定义为:
$$
\begin{aligned}\mathrm{PSNR}&=10\cdot\log_{10}\left(\frac{MAX_I^2}{MSE}\right)\&=20\cdot\log_{10}\left(\frac{MAX_I}{\sqrt{MSE}}\right)\&=20\cdot\log_{10}(MAX_I)-10\cdot\log_{10}(MSE).\end{aligned}
$$
一般而言,通过PSNR来判断处理后图像的失真情况有如下通用结论:

  • PSNR > 30 dB:图像质量较好,失真不明显。
  • PSNR 20~30 dB:中等质量,存在可察觉失真。
  • PSNR < 20 dB:质量较差,失真显著。

实际计算时,采用opencv自带的PSNR方法cv2.PSNR(img, output)对原始图像与处理后图像的PSNR进行比较计算。

测试图像 最近邻插值PSNR 双线性插值PSNR 双立方插值PSNR 四平面插值PSNR
琳娜(269*269) 20.74217399 27.11906575 29.36532325 36.70765842
辣椒(268*268) 22.92424674 27.91435345 31.04713312 39.25866529
狒狒(268*268) 21.8194312 28.06968286 29.12614241 38.2193871
房子(256*256) 22.34146151 26.21366716 30.67389681 38.8204405
插值算法 最近邻法 双线性法 双立方法 四平面法
算法运行平均用时 0.678485751 3.293492556 92.66596091 15.02119243

通过对比上述定量实验结果可以发现,在传统的三种分段插值算法中,随着运算阶数(采样待插值点周围的原图像像素点颜色值信息)的增加,图像经过放缩与旋转处理后的失真程度有明显降低,但仍大致处于存在可察觉失真的区间,且算法运行用时也逐渐增加(事实上双立方法的实现可以在编程层面实现优化,这里只是为更直观地展现O(n^2)时间复杂度在图像大小达到一定规模时的显著影响);而引入的四平面算法不仅在失真程度上较传统的插值算法均有显著改善,算法运行用时也明显优于传统算法中效果最好的双立方法。

综合以上的定性与定量实验结果及分析,本文提出的基于四平面的图像插值算法在图像处理效果(失真)与运行效率上均较传统算法有明显提升,这充分证明了该算法的有效性。

将上文提到的全部四种算法及旋转与放缩两种功能集成到基于python的gui可视化系统中,并打包成exe可执行文件,制作了一个基于插值的图像处理系统,基本功能演示如下图所示:

gui演示1

gui演示2

参考文献

[1] 王开荣,杨大地编著.应用数值分析[M].高等教育出版社,2010.

[2] 毛伟伟,于素萍,石念峰.一种基于四平面的图像插值算法[J].洛阳理工学院学报(自然科学版),2024,34(01):76-81.

[3] 刘显德,李笑.任意大小图像的量子描述及双线性插值方法[J].计算机工程与设计,2024,45(08):2423-2432.

[4] 张喜民,詹海生.基于双三次插值的Canny-Devernay亚像素图像边缘检测算法[J].现代制造工程,2025,(03):107-114.

[5] 陈玲玲,周宁,殷永,等.插值方法在光声图像重建中的应用[J].计算机与数字工程,2013,41(10):1676-1677+1694.

求矩阵特征值与特征向量:乘幂法及其改进算法

研究背景意义

矩阵的特征值计算虽然有比较可靠的理论方法,但是,理论方法只适合于矩阵规模很小或者只是在理论证明中起作用,而实际问题的数据规模都比较大,不太可能采用常规的理论解法。计算机擅长处理大量的数值计算,所以通过适当的数值计算理论,写成程序,让计算机处理,是一种处理大规模矩阵的方法,而且是一种好的方法。乘幂法(又称幂法)是求矩阵按模最大特征值的常用方法,其结构简单、便于使用,在实际工程中应用广泛:

  • 在数值分析和优化问题中,乘幂法可通过求解矩阵按模最大特征值来得到其谱半径,从而帮助分析迭代算法的收敛性;
  • 在物理学和工程学中,特征值问题常用于分析系统的稳定性、振动频率和模态分析,乘幂法能够有效求解系统的最大特征值,从而帮助理解系统的动态行为;
  • 在图像处理中,特征值分解用于图像压缩、特征提取和模式识别,乘幂法可用于求解图像矩阵的最大特征值,从而实现高效的图像处理算法;
  • ……

但该方法也存在一定的局限性,其只适用于求矩阵按模最大的特征值,而在实际问题中往往需要求矩阵全部特征值,如在主成分分析中需要求相关矩阵的全部特征值以确定各主成分的贡献率,在求解线性微分方程组中通过求系数矩阵的全部特征值以确定其基础解系;同时该方法在求特定矩阵(如具有一对互为相反数的按模最大特征值的矩阵等)的按模最大特征值时是不收敛的。因此,我们希望以乘幂法基本思想为出发点,在此基础上提出一些改进算法以对其进行泛化,使其能够解决更为广泛且通用的应用场景下的实际问题。

算法基本思想

设n阶矩阵A具有n个线性无关的特征向量
$$
x_1,x_2,…,x_n
$$
相应的特征值
$$
\lambda_1,\lambda_2,…,\lambda_n
$$
满足:
$$
\left|\lambda_1\right|>\left|\lambda_2\right|\geq\left|\lambda_3\right|\geq…\geq\left|\lambda_n\right|
$$
现任取一非零向量u_0,作迭代
$$
u_k=Au_{k-1}, k=0,1,2,…
$$
得到向量序列{u_k}(k=0,1,2,…)。因各特征向量线性无关,故n维向量u_0必可由他们线性表示,即:
$$
u_0=\alpha_{1}x_{1}+\alpha_{n}x_{2}+…+\alpha_{n}x_{n}
$$
显然有:
$$
\begin{aligned}u_{k}&=A^{k}v_{0}=\alpha_{1}A^{k}x_{1}+\alpha_{2}A^{k}x_{2}+\cdots+\alpha_{n}A^{k}x_{n}=\alpha_{1}\lambda_{1}^{k}x_{1}+\alpha_{2}\lambda_{2}^{k}x_{2}+\cdots+\alpha_{n}\lambda_{n}^{k}x_{n}\&=\lambda_{1}^{k}\left[\alpha_{1}x_{1}+\alpha_{2}\left(\frac{\lambda_{2}}{\lambda_{1}}\right)^{k}x_{2}+\cdots+\alpha_{n}\left(\frac{\lambda_{n}}{\lambda_{1}}\right)^{k}x_{n}\right]\end{aligned}
$$

$$
\alpha_{1} \neq 0
$$

$$
\left|\frac{\lambda_{i}}{\lambda_{1}}\right|<1,i=2,3,\cdots,n
$$
可得:
$$
\operatorname*{lim}{k\to\infty}\frac{u{k}}{\lambda_{1}^{k}}=\operatorname*{lim}{k\to\infty}\frac{\lambda{1}^{k}\left[\alpha_{1}x_{1}+\sum_{i=2}^{n}\alpha_{i}\left(\frac{\lambda_{i}}{\lambda_{1}}\right)^{k}x_{i}\right]}{\lambda_{1}^{k}}=\operatorname*{lim}{k\to\infty}\left[\alpha{1}x_{1}+\sum_{i=2}^{n}\alpha_{i}\left(\frac{\lambda_{i}}{\lambda_{1}}\right)^{k}x_{i}\right]=\alpha_{1}x_{1}
$$

$$
\operatorname*{lim}{k\to\infty}\frac{\left(u{k+1}\right){m}}{\left(u{k}\right){m}}=\operatorname*{lim}{k\to\infty}\frac{\left{\lambda_{1}^{k+1}\left[\alpha_{1}x_{1}+\sum_{i=2}^{n}\alpha_{i}\left(\frac{\lambda_{i}}{\lambda_{1}}\right)^{k+1}x_{i}\right]\right}{m}}{\left{\lambda{1}^{k}\left[\alpha_{1}x_{1}+\sum_{i=2}^{n}\alpha_{i}\left(\frac{\lambda_{i}}{\lambda_{i}}\right)^{k}x_{i}\right]\right}{m}}=\lambda{1}\frac{\left(x_{1}\right){m}}{\left(x{1}\right){m}}=\lambda{1}
$$

这表明向量序列u_k具有收敛性,其收敛速度由比值
$$
\left|\frac{\lambda_{2}}{\lambda_{1}}\right|
$$
确定,该比值越小说明收敛速度越快,比值越接近于1收敛速度就越慢。

同时,该结论表明,当k取得足够大时(在实际应用时往往认为某次迭代前后误差小于设定的误差限即满足该条件),有:
$$
u_{k}≈\lambda_{1}^{k}\alpha_{1}x_{1}, \lambda_{1}≈\frac{\left(u_{k+1}\right)}{\left(u_{k}\right)}
$$
即u_ k可近似看作特征值λ_ 1对应的特征向量(较原特征向量x_ 1而言仅乘上了有限的常数系数,其结果仍为该特征值对应的特征向量),而对应的按模最大特征值λ_ 1的估计值可由前后两次迭代的特征向量u_ k相除得到。

当矩阵的按模最大特征值是重根时,上述结论仍然成立。设λ_ 1为r重根,即:
$$
\lambda_{1}=\lambda_{2}=\cdots=\lambda_{r}
$$
且满足条件
$$
\mid\lambda_{r}\mid>\mid\lambda_{r+1}\mid\geq\cdots\geq\mid\lambda_{n}\mid
$$
则有:
$$
u_{k}=A^{k}u_{0}=\lambda_{1}^{k}\left[\sum_{i=1}^{r}\alpha_{i}x_{i}+\sum_{j=r+1}^{n}\alpha_{j}\left(\frac{\lambda_{j}}{\lambda_{1}}\right)^{k}x_{j}\right]
$$
易推得(式中(u_ k)_ m表示向量u_ k的第m个分量):
$$
\lim_{k\to\infty}\frac{u_{k}}{\lambda_{i}^{k}}=\sum_{i=1}^{^{r}}\alpha_{i}x_{i},\lim_{k\to\infty}\frac{\left(u_{k+1}\right){m}}{\left(u{k}\right){m}}=\lambda{1}
$$
与一般情况略有不同的是,此时
$$
u_{k}≈\lambda_{i}^{k}\sum_{i=1}^{^{r}}\alpha_{i}x_{i}
$$
但事实上我们并没有办法在求解之前事先预知一个矩阵是否具有r重按模最大特征值,因此在实际操作的过程中采用与一般情形相同的处理方式(即认为r=1),所得的向量序列u_k仍然收敛,不影响求解结果。

基于以上的算法原理,可以编写如下MATLAB程序,实现一般情况下矩阵按模最大特征值及其对应特征向量的计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function calculatePowerMethod(matrixEdit, iterEdit)
% 获取用户输入的矩阵matrixEdit和最大迭代次数iterEdit
A = str2num(matrixEdit.Value); % 将字符串转换为矩阵
n = length(A);
V = rand(n,1); % 以随机方式初始化迭代向量u0
max_iter = iterEdit.Value; % 获取迭代次数

Eps = 1E-4; % 迭代精度
k = 0; % 初始迭代次数
lambda0 = 0;

% 乘幂法迭代,求得矩阵按模最大特征值lambda及其对应特征向量v
while k <= max_iter - 1
v = A * V;
[vmax, i] = max(abs(v));
lambda = v(i) / V(i);
V = v;
if abs(lambda - lambda0)<Eps
k = k + 1;
break;
end
lambda0 = lambda;
k = k + 1;
end
end

改进的乘幂法

在实际应用层面,上述的算法逻辑只在一小部分的常规矩阵上对于矩阵的按模最大特征值求解有较好效果。事实上,仅依托以上的代码逻辑进行求解往往会出现以下几大问题:

  1. 当矩阵A不具有n个线性无关的特征向量时(事先无法判断),乘幂法不适用。
  2. 当待求取的按模最大特征值abs(λ_ 1)>1时,迭代向量u_ k的各个分量可能会随着abs(λ_ 1)^k变得很大而使计算机“上溢”;而当待求取的按模最大特征值abs(λ_ 1)<1时,迭代向量u_ k的各个分量可能会随着abs(λ_ 1)^k变得很小使u_k成为零向量。
  3. 矩阵的按模最大特征值是一对相反数。
  4. 矩阵的按模最大特征值是一对共轭复数。

(上述算法中还给出了α_1≠0的假设,事实上如果不满足这一点也不影响乘幂法的成功使用。因为舍入误差的影响,在迭代某一步会产生u_k在x_1方向上的分量不为零,以后的迭代仍会收敛)

针对问题1,我们暂时无法提出具有针对性的解决方案,但后续的实际测试证明,当迭代次数k足够大时,我们针对问题3所提出的改进方案在该种情况下相比基本算法能够有更好的收敛性(收敛更快);针对问题2,我们可以通过将向量序列u_k进行规范化处理,限制向量的模在特定小范围内,防止计算机运算的上下溢出;针对问题3,我们在原有的乘幂法算法思想的基础上进行了改进,改进后的算法能够有效对原算法无法求解(不收敛)的按模最大特征值互为相反数的矩阵的按模最大特征值进行有效求解;针对问题4,我们目前还没有找到有效的解决方案。

迭代向量的归一化

设迭代向量u为非零向量,将其归一化得到向量
$$
y=\frac{u}{max(u)}
$$
其中max(u)表示向量u的模最大的分量(即向量的无穷范数)。这样的规范化会保证每一次迭代后得到的新的迭代向量的模最大分量均为1,这有效起到了限制迭代向量分量范围的作用,能够很好地解决上述提到的问题2。值得注意的是,对向量规范化的方式并不只有归一化这一种,实际上可以使用任意一种范数对向量进行规范化处理,如2范数等。可以证明,对于基于不同范数的归一化都有相同的向量序列收敛结论,下面以无穷范数为例给出基本的证明。(显然这样的规范化只是在原有的向量基础上除以了某一个常数,这并不会改变向量序列的收敛性)

取初始向量
$$
u_{0}\neq0
$$
规范化得
$$
y_{0}=\frac{u_{0}}{\max(u_{0})}
$$
构造向量序列:
$$
\begin{aligned}
&u_{1}=Ay_{0}=\frac{Au_{0}}{\max(u_{0})},
&y_{1}=\frac{u_{1}}{\max(u_{1})}=\frac{Au_{0}}{\max(Au_{0})}\
&u_{2}=Ay_{1}=\frac{A^{2}u_{0}}{\max(Au_{0})},
&y_{2}=\frac{u_{2}}{\max(u_{2})}=\frac{A^{2}u_{0}}{\max(A^{2}u_{0})}\
&……\
&u_{k}=Ay{{k-1}}=\frac{A^{k}u{0}}{\max(A^{k-1}u_{0})},
&\quad y_{k}=\frac{u_{k}}{\max(u_{k})}=\frac{A^{k}u_{0}}{\max(A^{k}u_{0})}
\end{aligned}
$$
结合乘幂法基本算法中已经证明的结论
$$
A^{k}u_{0}=u_{k}≈\lambda_{1}^{k}\alpha_{1}x_{1}
$$
则有:
$$
\lim_{k\to\infty}y_{k}=\frac{x_{1}}{\max(x_{1})},
\lim_{k\to\infty}\max(u_{k})=\lambda_{1}
$$
这表明此时y_ k可近似看作特征值λ_ 1对应的特征向量(较原特征向量x_ 1而言仅进行了归一化处理,其结果仍为该特征值对应的特征向量),而对应的按模最大特征值λ_ 1的估计值可近似看作未归一化前的迭代向量最大分量值max(u_k)。

基于以上的算法原理,可以编写如下MATLAB程序,实现归一化处理下矩阵按模最大特征值及其对应特征向量的计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function calculatePowerMethod(matrixEdit, iterEdit)
% 获取用户输入的矩阵matrixEdit和最大迭代次数iterEdit
A = str2num(matrixEdit.Value); % 将字符串转换为矩阵
n = length(A);
V = rand(n,1); % 以随机方式初始化迭代向量u0
max_iter = iterEdit.Value; % 获取迭代次数

Eps = 1E-4; % 迭代精度
k = 0; % 初始迭代次数
lambda0 = 0;

% 乘幂法迭代,求得矩阵按模最大特征值lambda及其对应特征向量v
while k <= max_iter - 1
v = A * V;
[vmax, i] = max(abs(v));
lambda = v(i);
V = v / lambda;
if abs(lambda - lambda0)<Eps
k = k + 1;
break;
end
lambda0 = lambda;
k = k + 1;
end
end

归一化乘幂法的进一步改进

针对问题3,从其基本情形出发(其他条件与先前保持一致),即有:
$$
\mid\lambda_1\mid=\mid\lambda_2\mid>\mid\lambda_3\mid\geqslant\cdotp\cdotp\cdotp\geqslant\mid\lambda_n\mid,\lambda_1=-\lambda_2
$$
在矩阵按模最大特征值为一对相反数的情况下,原迭代向量序列中各相邻向量的比值极限发散:
$$
\lim_{k\to\infty}\frac{\boldsymbol{u}_{k+1}}{\boldsymbol{u}k}=\lambda_1\lim{k\to\infty}\frac{a_1\boldsymbol{x}_1+(-1)^{k+1}a_2\boldsymbol{x}2+\boldsymbol{\varepsilon}{k+2}}{a_1\boldsymbol{x}_1+(-1)^ka_2\boldsymbol{x}_2+\boldsymbol{\varepsilon}_k}
$$
说明在该种情况下,采用原有的乘幂法基本算法,得到的迭代向量序列并不收敛,无法得到稳定的矩阵按模最大特征值数值求解结果。

我们在原有算法的基础上进行了一定的改进,构造了新的迭代向量序列,使其对于矩阵按模最大特征值为一对相反数的情况也能保证收敛,从而可以对该情况下的矩阵按模最大特征值进行有效求解。

先从较为简单的非归一化迭代入手,同样取迭代向量序列u_k,其初始非零向量u_0满足:
$$
u_0=\alpha_{1}x_{1}+\alpha_{n}x_{2}+…+\alpha_{n}x_{n}(\alpha_{1}\neq0)
$$
则有:
$$
\boldsymbol{u}{k}=\boldsymbol{A}^{k}\left(\sum{i=1}^{n}a_{i}\boldsymbol{x}{i}\right)=\lambda{1}^{k}\left(a_{1}\boldsymbol{x}{1}+(-1)^{k}a{2}\boldsymbol{x}{2}+\sum{i=3}^{n}\left(\frac{\lambda_{i}}{\lambda_{1}}\right)^{k}a_{i}\boldsymbol{x}{i}\right)=\lambda{1}^{k}(a_{1}\boldsymbol{x}{1}+(-1)^{k}a{2}\boldsymbol{x}{2}+\boldsymbol{\varepsilon}{k}),\lim_{k\to\infty}\varepsilon_{k}=0
$$
于是可得到:
$$
\lim_{k\to\infty}\frac{u_{k+2}}{u_{k}}=\lambda_{1}^{2}\lim_{k\to\infty}\frac{\alpha_{1}x_{1}+(-1)^{k+2}\alpha_{2}x_{2}+\varepsilon_{k+2}}{\alpha_{1}x_{1}+(-1)^{k}\alpha_{2}x_{2}+\varepsilon_{k}}=\lambda_{1}^{2}
$$
且有:
$$
\lim_{k\to\infty}\frac{u_{k+1}+\lambda_1u_k}{\lambda_1^{k+1}}=2\alpha_1x_1
$$
以上结果说明,在矩阵按模最大特征值为一对相反数(正值λ_ 1,负值λ_ 2)的情形下,若采用原有的迭代向量序列,对序列中的向量一隔一取出,构成的新序列是收敛的,且新序列中的相邻两项向量的比值极限为矩阵按模最大特征值的平方;同时,u_ {k+1}+λ_ {1}u_ {k}可近似看作λ_ {1}的特征向量,同理u_ {k+1}+λ_ {2}u_ {k}也可近似看作λ_ {2}的特征向量。

易证该方法对于规范化后的向量序列也具有相同的收敛性,在此不额外给出证明。

在实际编程实现算法时,为尽可能地节省算力与内存并提高计算效率,我们不会预先求取最大迭代次数内每一次迭代的结果再将其分为两个向量序列判断其是否收敛(相邻向量插值小于设定误差限),而是需要结合上述改进算法与逐步迭代过程,对于每一次迭代的求解过程进行逻辑优化,优化后的逻辑如下所示(u_k代表规范化后的迭代向量序列,采用2范数方式进行规范化,k=1,2,…):


  • $$
    \left|\frac{\left(\boldsymbol{u}_{k+2}^{(1)}\right)_i}{\left(\boldsymbol{u}_k^{(1)}\right)i}-\frac{\left(\boldsymbol{u}{k+1}^{(1)}\right)i}{\left(\boldsymbol{u}{k-1}^{(1)}\right)_i}\right|<\varepsilon:,

    \left|\frac{\left(\boldsymbol{u}_{k+2}^{(1)}\right)i}{\left(\boldsymbol{u}{k+1}^{(1)}\right)i}-\frac{\left(\boldsymbol{u}{k+1}^{(1)}\right)_i}{\left(\boldsymbol{u}_k^{(1)}\right)i}\right|<\varepsilon:
    $$
    则取矩阵按模最大特征值
    $$
    \lambda_1=\frac{(\boldsymbol{u}
    {k+2}^{(1)})i}{(\boldsymbol{u}{k+1}^{(1)})i}
    $$
    对应的特征向量
    $$
    x_1=u
    {k+2}^{(1)}
    $$


  • $$
    \left|\frac{\left(\boldsymbol{u}_{k+2}^{(1)}\right)_i}{\left(\boldsymbol{u}_k^{(1)}\right)i}-\frac{\left(\boldsymbol{u}{k+1}^{(1)}\right)i}{\left(\boldsymbol{u}{k-1}^{(1)}\right)_i}\right|<\varepsilon:,

    \left|\frac{(\boldsymbol{u}{k+2}^{(1)})i}{(\boldsymbol{u}{k+1}^{(1)})i}-\frac{(\boldsymbol{u}{k+1}^{(1)})i}{(\boldsymbol{u}k^{(1)})i}\right|>\varepsilon:
    $$
    则取矩阵按模最大特征值
    $$
    \lambda_1=\pm\sqrt{\frac{(\boldsymbol{u}
    {k+2}^{(1)})i}{(\boldsymbol{u}{k}^{(1)})i}}
    $$
    对应的特征向量
    $$
    x_1=\frac{u
    {k+2}^{(1)}+\lambda_1u
    {k+1}^{(1)}}{\parallel u
    {k+2}^{(1)}+\lambda_1u
    {k+1}^{(1)}\parallel_2}
    $$

基于以上的算法原理,可以编写如下MATLAB程序,实现矩阵按模最大特征值及其对应特征向量求解的改进算法,以解决矩阵按模最大特征值可能为一对相反数的特殊情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function calculatePowerMethod1(matrixEdit, iterEdit, fig)
% 获取用户输入的矩阵matrixEdit和最大迭代次数iterEdit
A = str2num(matrixEdit.Value); % 将字符串转换为矩阵
n = length(A);
V = rand(n,1); % 以随机方式初始化迭代向量u0
max_iter = iterEdit.Value; % 获取迭代次数

Eps = 1E-4; % 迭代精度
k = 0; % 初始迭代次数

% 乘幂法迭代,求得矩阵按模最大特征值lambda及其对应特征向量v
while k <= max_iter - 1
v = A * V;
v1 = A * v;
v_2 =norm(v, 2);
minus1 = A * v1 ./ v - v1 ./ V;
minus2 = A * v1 ./ v1 - v1 ./ v;
if max(abs(minus1(:))) < Eps
if max(abs(minus2(:))) < Eps
lambda0 = A * v1 ./ v1;
lambda = max(lambda0(:));
v2_2 = norm(A * v1, 2);
v = A * v1 / v2_2;
else
lambda0 = A * v1 ./ v;
lambda = sqrt(max(lambda0(:)));
v3 = A * v1 + lambda * v1;
v3_2 = norm(v3, 2);
v = v3 / v3_2;
end
k = k + 1;
break;
end
k = k + 1;
V = v / v_2;
lambda0 = A * v1 ./ v1;
lambda = max(lambda0(:));
end
end

求实对称矩阵的全部特征值

我们知道,实对称矩阵的不同特征值对应的特征向量一定是正交的,因此,可以通过幂法迭代得到矩阵的主特征值λ_ 1和主特征向量x_ 1 ,再重新给出一个新的与v_ 0线性无关的初始迭代向量v_ 1,使v_ 1和x_ 1正交化, 则可以由幂法迭代出矩阵的特征值λ_ 2和特征向量x_ 2。同样使新给出的初始迭代向量v_ 3和x_ 1、x_ 2正交 化,则可以由幂法得到λ_ 3和x_ 3。以此类推,可以计算出矩阵的全部特征值和特征向量。具体证明过程详见参考文献[3],这里仅给出基于上述改进算法的完整算法逻辑:


$$
A_j=\mathbf{A}{j-1}-\lambda{j-1}\boldsymbol{x}{j-1}\boldsymbol{x}{j-1}^{\mathrm{T}}(j=2,3,\cdots n)
$$
采取与先前完全相同的迭代向量序列构造方式,有如下判断逻辑(u_k代表规范化后的迭代向量序列,采用2范数方式进行规范化,k=1,2,…):


  • $$
    \left|\frac{(u_{k+2}^{(j)})i}{(u_k^{(j)})i}-\frac{(u{k+1}^{(j)})i}{(u{k-1}^{(j)})i}\right|<\varepsilon:,\quad\left|\frac{(u{k+2}^{(j)})i}{(u{k+1}^{(j)})i}-\frac{(u{k+1}^{(j)})i}{(u_k^{(j)})i}\right|<\varepsilon:,
    $$
    且满足
    $$
    {|x
    {m}^{\mathrm{T}}v
    {k+2}^{(j)}|<\varepsilon}\left(m=1,2,\cdots,j-1\right)
    $$
    则取矩阵按模最大特征值
    $$
    {\lambda
    {j}}=\frac{\left(\boldsymbol{u}{k+2}^{(j)}\right){i}}{\left(\boldsymbol{u}{k+1}^{(j)}\right){i}}
    $$
    对应的特征向量
    $$
    x_{j}=\boldsymbol{u}_{k+2}^{(j)}
    $$


  • $$
    \left|\frac{(u_{k+2}^{(j)})i}{(u_k^{(j)})i}-\frac{(u{k+1}^{(j)})i}{(u{k-1}^{(j)})i}\right|<\varepsilon:,\quad\left|\frac{(u_{k+2}^{(j)})_i}{(u_{k+1}^{(j)})_i}-\frac{(u_{k+1}^{(j)})_i}{(u_k^{(j)})_i}\right|>\varepsilon:,
    $$
    且满足
    $$
    \left|\boldsymbol{x}
    {m}^{\mathrm{T}}\frac{\boldsymbol{u}
    {k+2}^{(j)}+\lambda_{j}\boldsymbol{u}{k+1}^{(j)}}{\parallel\boldsymbol{u}{k+2}^{(j)}+\lambda_{j}\boldsymbol{u}{k+1}^{(j)}\parallel{2}}\right|<\varepsilon\quad(m=1,2,\cdots,j-1)
    $$
    则取矩阵按模最大特征值
    $$
    \lambda_{j}=\pm\sqrt{\frac{(\boldsymbol{u}{k+2}^{(j)}){i}}{(\boldsymbol{u}{k}^{(j)}){i}}}
    $$
    对应的特征向量
    $$
    \quad x_{j}=\frac{\boldsymbol{u}{k+2}^{(j)}+\lambda{j}\boldsymbol{u}{k+1}^{(j)}}{\parallel\boldsymbol{u}{k+2}^{(j)}+\lambda_{j}\boldsymbol{u}{k+1}^{(j)}\parallel{2}}
    $$

基于以上的算法原理,可以编写如下MATLAB程序,实现矩阵按模最大特征值及其对应特征向量求解的改进算法,并对于实对称矩阵的全部特征值进行依次求解(每次运行该函数进行一个特征值的求解,代码中省去了存储用于求解下一个特征值的矩阵作为全局变量的部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
function calculatePowerMethod2(iterEdit, fig)
% 从存储的全局变量中读取需要求解主特征值的新矩阵data.matrix
A = data.matrix; % 将字符串转换为矩阵
n = length(A);
V = rand(n,1);
max_iter = iterEdit.Value; % 获取迭代次数

Eps = 1E-4; % 迭代精度
k = 0; % 初始迭代次数

% 乘幂法迭代,求得矩阵按模最大特征值lambda及其对应特征向量v
while k <= max_iter - 1
v = A * V;
v1 = A * v;
v_2 =norm(v, 2);
v2_2 = norm(A * v1, 2);
v2 = A * v1 / v2_2;
lambda0 = A * v1 ./ v;
lambda = sqrt(max(lambda0(:)));
v3 = A * v1 + lambda * v1;
v3_2 = norm(v3, 2);
minus1 = A * v1 ./ v - v1 ./ V;
minus2 = A * v1 ./ v1 - v1 ./ v;
if max(abs(minus1(:)))<Eps
num1 = 0;
num2 = 0;
for l = 1 : j
x = full_V(:,l);
if max(abs(minus2(:))) < Eps && abs(x' * v2) < Eps
num1 = num1 + 1;
elseif max(abs(minus2(:))) > Eps && abs(x' * v3 / v3_2) < Eps
num2 = num2 + 1;
end
end
if num1 == j
lambda0 = A * v1 ./ v1;
lambda = max(lambda0(:));
v = A * v1 / v2_2;
k = k + 1;
break;
elseif num2 == j
lambda0 = A * v1 ./ v;
lambda = sqrt(max(lambda0(:)));
v = v3 / v3_2;
k = k + 1;
break;
end
end
k = k + 1;
V = v / v_2;
lambda0 = A * v1 ./ v1;
lambda = max(lambda0(:));
end
end

数据测试实验

针对以上提到的乘幂法及其改进算法,将其MATLAB求解代码整合进GUI中,并对求解过程中矩阵按模最大特征值随迭代次数增加的收敛情况进行可视化,基本界面如下:

1

乘幂法基础算法

将待求解的矩阵按照格式要求输入框体内,并指定最大迭代次数(默认为100次),按下“计算”控件可以利用基础的乘幂法(归一化版本)对矩阵按模最大特征值及其对应特征向量进行求解,同时会调用MATLAB中自带的矩阵特征值求解函数eig()对求解结果进行验证。

例:矩阵(特征值为-1、3、5)

$$
A=\begin{pmatrix}1&-1&2\-2&0&5\6&-3&6\end{pmatrix}
$$

程序求解与可视化结果:

2

可以发现,估计的主特征值与实际的主特征值在限定的精度内基本一致,对应的特征向量相差了相同的倍数,也可视为正确求解。

乘幂法改进算法

将待求解的矩阵按照格式要求输入框体内,并指定最大迭代次数(默认为100次),按下“优化计算”控件可以利用改进的乘幂法(归一化版本)对矩阵按模最大特征值及其对应特征向量进行求解,同时会调用MATLAB中自带的矩阵特征值求解函数eig()对求解结果进行验证。

例1:矩阵(特征值为-1、3、5)

$$
A=\begin{pmatrix}1&-1&2\-2&0&5\6&-3&6\end{pmatrix}
$$

程序求解与可视化结果:

3

可以发现,估计的主特征值与实际的主特征值在限定的精度内保持一致,通过该算法计算得到的对应特征向量也与MATLAB自带函数(基于 Cholesky 分解/QR分解方法的特征值求解)的求解结果在限定精度内完全一致,但计算需要的迭代次数较基础算法而言有明显增加。

例2:矩阵(特征值为3、-3、1,按模最大特征值为一对相反数)

$$
A=\begin{pmatrix}3&-2&4\0&-3&2\0&0&1\end{pmatrix}
$$

程序求解与可视化结果:

调用乘幂法基本算法:

4

调用乘幂法改进算法:

5

可以发现:

  • 调用基础算法时,迭代向量序列在设定的最大迭代次数内并不收敛,最终的求解结果也与实际按模最大特征值有较大误差;
  • 而调用改进算法时,尽管从主特征值收敛过程的可视化结果中无法确定向量序列的收敛性(实际上原序列仍发散),但序列中的向量一隔一选取构成的新序列(图像中对应迭代次数全为奇数/偶数的震荡单边)已由程序判断收敛,且估计的主特征值与实际的主特征值在限定的精度内保持一致,通过该算法计算得到的对应特征向量也与MATLAB自带函数(基于 Cholesky 分解/QR分解方法的特征值求解)的求解结果在限定精度内完全一致。

例3:3阶矩阵(特征值为3(重根),但不具有3个线性无关的特征向量)

$$
A=\begin{pmatrix}3&1&0\0&3&1\0&0&3\end{pmatrix}
$$

程序求解与可视化结果:

调用乘幂法基本算法(最大迭代次数100):

6

调用乘幂法改进算法(最大迭代次数100):

7

调用乘幂法基本算法(最大迭代次数10000):

8

调用乘幂法改进算法(最大迭代次数10000):

9

可以发现,当矩阵不具有n个线性无关的特征向量时,乘幂法从理论上讲不再适用(不满足假设条件),但实际测试可以发现:

  • 当最大迭代次数设置较小(100)时,两种方法均未呈现数值上的收敛(两次迭代间误差不超过设定误差限10^(-4)),所求得的主特征值与对应特征向量与真实值之间均有明显误差;
  • 当最大迭代次数设置足够大(10000)时,两种方法在迭代次数达到10^2数量级时实现了数值上的收敛(两次迭代间误差不超过设定误差限10^(-4)),但在停止迭代后,通过乘幂法基础算法求得的主特征值与对应特征向量与真实值之间仍有明显误差,而通过改进算法求得的主特征值与对应特征向量与真实值已经基本接近,但改进算法所需要的迭代次数也明显更多。

但总的来说,从理论角度出发,在这种情况下乘幂法已不再适用,本例的情况仅为收敛较慢,此时改进算法相较于基础算法有更好的精度;但也会有不收敛的情况,此时两种方法均会有比较大的误差,而这是无法事先判断的,因此在使用乘幂法时还是尽量避免该种情况,或者说在优化算法仍然无法快速收敛的情况下需要注意到矩阵不具有n个线性无关的特征向量的特殊情况,此时应选取其他合适的特征值求解算法。

例4:矩阵(特征值为1+2i、1-2i、0.5,按模最大特征值为一对共轭复数)

$$
A=\begin{pmatrix}1&-2&0\2&1&0\0&0&0.5\end{pmatrix}
$$

程序求解与可视化结果:

调用乘幂法基本算法:

10

调用乘幂法改进算法:

11

此情况下两种乘幂法均呈现了极大的不稳定性,无法正确求解。

求实对称矩阵的全部特征值

例1:矩阵(特征值为-1、3、5)

$$
A=\begin{pmatrix}1&-1&2\-2&0&5\6&-3&6\end{pmatrix}
$$

程序求解与可视化结果:

12

该矩阵不为对称矩阵,无法通过上述算法实现全部特征值的求解,为排错示例。

例2:对称矩阵(特征值为1、2、4)

$$
A=\begin{pmatrix}3&1&1\1&2&0\1&0&2\end{pmatrix}
$$

程序求解与可视化结果:

13

14

15

16

可以看到,该算法可以用于正确求取实对称矩阵的所有特征值及其相应的特征向量,求解结果与MATLAB自带函数(基于 Cholesky 分解/QR分解方法的特征值求解)的求解结果在限定精度内完全一致。

例3:对称矩阵(特征值为2、1、-1)

$$
A=\begin{pmatrix}1&1&0\1&0&1\0&1&1\end{pmatrix}
$$

程序求解与可视化结果:

17

18

19

20

可以看到,即使在矩阵的次主特征值为一对相反数的情况下,该算法也可以用于正确求取实对称矩阵的所有特征值及其相应的特征向量,求解结果与MATLAB自带函数(基于 Cholesky 分解/QR分解方法的特征值求解)的求解结果在限定精度内完全一致。

参考文献

[1] 王开荣,杨大地编著.应用数值分析[M].高等教育出版社,2010.

[2] 曹连英,曲智林,杨瑞智.基于幂法的求实对称矩阵特征值的注记[J].大学数学,2024,40(04):67-72.

[3] 曾莉,肖明.计算实对称矩阵特征值特征向量的幂法[J].南昌大学学报(理科版),2016,40(04):399-402.

[4] 张青,苟国楷,吕崇德.乘幂法的改进算法[J].应用数学与计算数学学报,1997,(01):51-55.

[5] 马志勇,方珑.矩阵特征值求解及其在图像压缩中的应用[J].上海第二工业大学学报,2012,29(04):315-318.

烟雾扩散问题

1 研究背景简介

1.1 研究背景与意义

香烟烟雾中含有数千种化学物质,其中包括一氧化碳、尼古丁、苯等多种对人体有害的成分。这些物质在室内封闭环境中容易积聚,对人体健康构成显著威胁,尤其是二手烟和三手烟的危害已引起广泛关注。报告显示,2018年中国不吸烟者的二手烟暴露率为68.1%,其中家庭和工作场所是二手烟暴露的主要场所,这表明室内烟雾污染已成为影响公众健康的重要问题。因此,我们希望通过研究烟雾在室内环境中的扩散规律,为烟雾的控制、空气污染防治或室内空气质量管理提供参考,并帮助优化通风设计或制定健康防护措施。

烟雾的扩散过程受到室内温湿度、气流模式、房间结构及家具布置等多种因素的影响,具有较高的复杂性和随机性。深入研究烟雾扩散的传输机制,可以帮助揭示其污染范围与浓度分布,为优化室内空气质量管理提供理论支持。但在估计和预测吸入暴露风险时,由于与生物伦理学和动物保护相关的潜在限制,涉及人类志愿者和其他用于毒理学研究的哺乳动物替代模型的体内研究存在局限性。出于这样的考虑,我们希望通过理论分析建模与数值模拟的方式,得到烟雾扩散的一般规律,从而为提出可能的防控手段提供理论依据。

通过实验和仿真研究,能够验证不同通风方案对烟雾扩散的控制效果,为改善居住环境提供技术支持;利用数值模拟,可以精确再现不同环境下烟雾的扩散过程,从而更直观地评估各类干预措施的有效性。这种方法不仅降低了研究成本,还能为制定更加科学、精确的室内空气污染防控策略提供强有力的支持。

图1.1:烟草烟雾中的有毒有害物质

图1.2:长期吸入二手烟对人体健康的危害

1.2 研究目标

本研究旨在探讨室内封闭环境下烟雾的扩散特性,基于扩散方程等物理原理与数学物理方法对烟雾的扩散情况进行解析求解与数值模拟,重点分析烟雾的流动路径、扩散速率和浓度分布,以优化空气管理和烟雾控制策略。

在具体的研究过程中,我们主要采用特定尺寸的密封容器来模拟室内封闭环境以便于实验开展与现象观察;同时,我们的研究主要关注扩散过程中烟雾的扩散效果、扩散速度以及不同时刻的浓度分布这三个要素,并通过定量计算测量与定性观察现象相结合的方式,得到烟雾扩散浓度空间分布随时间变化的的一般数学物理规律,从而为设计室内烟雾浓度控制系统提供理论依据。

图1.3 研究区域示意图

2 理论分析求解

2.1 研究对象

在实际研究过程中,为方便实验的开展,我们选择了粒径分布在0.1~10微米范围内的植物甘油(丙三醇)气雾作为研究对象,这也是电子烟烟雾的主要成分之一。之所以使用植物甘油气雾来代替香烟烟雾进行研究,主要出于以下考量:

  • 安全性:植物甘油为食品级材料,无健康与环境污染风险

  • 可控性:植物甘油气雾生成过程可控,可进行定量化分析

  • 易获取性:价格低廉,易于获取

  • 可重复性:物质纯度高,化学性质稳定

具体而言,植物甘油气雾具有以下几条显著的理化特性:

  • 无色无臭,有甜味,极易溶于水

  • 密度(25℃):1.26 g/cm^3

  • 分子量: 92.09 g/mol

  • 主要粒径属于典型气溶胶粒径范围

  • 挥发性极低,以小颗粒悬浮于空气中

在研究过程中进行这样的材料替换,具有如下的合理性:

  • 植物甘油气雾与香烟烟雾的气溶胶形成机制相同(蒸发-冷凝),二者在扩散和湍流行为上具有共性

  • 二者粒径分布相似,具备相似的扩散动力学特征

  • 二者在扩散过程中的视觉效果接近,便于追踪扩散行为

在本研究的理论分析部分,重点关注植物甘油气雾的浓度(即单位体积中该种物质颗粒的质量)ρ(x, y, z, t)这一物理量在研究区域(长为x_0 = 40cm,宽为y_0 = 50cm,高为z_0 = 60cm的长方体规则区域)空间范围内的分布情况及其随时间的变化情况。

2.2 数学物理方程的导出

针对烟雾扩散这一常见现象,有现成的实验规律——菲克定律(又称扩散定律)可以直接使用,其基本公式如下:

$$
\overrightarrow{q} = - D\nabla\rho
$$
其中:

  • 式中负号代表扩散转移的方向(浓度减小的方向)和浓度梯度(浓度增大的方向)相反;
  • D:扩散系数,与物质的种类以及环境温度有关;
  • ∇ρ:浓度梯度,描述浓度不均匀分布的程度;
  • 扩散流强度,即单位时间通过单位(横截)面积的质量,描述扩散运动的强度:

$$
\overrightarrow{q} = \frac{dm_{g}}{dVdt}
$$
在本研究中,针对常温环境(25℃)下植物甘油气雾在空气中的扩散系数,可进一步通过Chapman-Enskog方程
$$
D_{AB} = \frac{0.00143 \times T^{1.75} \times \sqrt{\frac{1}{M_{A}} + \frac{1}{M_{B}}}}{P \times \sigma_{AB}^{2} \times \Omega_{D}}
$$
进行估算,代入空气的平均分子量28.97 g/mol和估计的分子直径0.5nm可计算得到此时的扩散系数约为:
$$
D = 0.0962{cm}^{2}/s
$$
由于我们的研究区域是一个长方体的三维规则区域,故在研究坐标系的选择上,以长方体区域的底端顶点为原点,长、宽、高延伸方向作为$x、y、z$正方向建立空间直角坐标系,原点在长方体上的对角顶点坐标为$(x_{0},\ y_{0},z_{0})$。针对三个不同方向,还可以进一步地给出菲克定律的分量形式:

$$
q_{x} = - D\frac{\partial\rho}{\partial x},\ \ q_{y} = - D\frac{\partial\rho}{\partial y},\ \ q_{z} = - D\frac{\partial\rho}{\partial z}
$$
接下来将基于菲克定律和质量守恒定律给出三维扩散方程,即本研究涉及的数学物理方程:

对于空间中任一点(x, y, z)附近的无穷小空间而言:该空间内浓度变化取决于穿过其表面的扩散流。

考虑单位时间内x方向的扩散流:

左表面流入流量
$$
\left. \ q_{x} \right|{x}dydz
$$
右表面流出流量
$$
\left. \ q
{x} \right|{x + dx}dydz
$$
故在单位时间内x方向净流入流量为
$$
\left. \ q
{x} \right|{x}dydz - \left. \ q{x} \right|_{x + dx}dydz
$$
图2.1 无穷小空间示意图

由于取无穷小空间,dx足够小,于是有
$$
\frac{\partial q_{x}}{\partial x} = \frac{\left. \ q_{x} \right|{x + dx} - \left. \ q{x} \right|{x}}{dx} 即 \left. \ q{x} \right|{x + dx} - \left. \ q{x} \right|{x} = \frac{\partial q{x}}{\partial x}dx
$$
代入化简后可以得到单位时间内x方向净流入流量为
$$
D\frac{\partial^{2}\rho}{\partial x^{2}}dxdydz
$$
同理可得:单位时间内y方向净流入流量为
$$
D\frac{\partial^{2}\rho}{\partial y^{2}}dxdydz
$$
单位时间内z方向净流入流量为
$$
D\frac{\partial^{2}\rho}{\partial z^{2}}dxdydz
$$
在本研究中,出于解析求解可行性的考虑,在理论分析部分暂时仅考虑常压下有源无汇的理想场景(即仅在某固定坐标点(x_0, y_0, z_0)处有一恒定扩散源,其强度(单位时间内单位体积产生的气体质量)为F(x,y, z,t)),而在后续数值求解与仿真模拟时才会把负压源视作汇的产生原因从而进一步补充对流的相关条件(对流项)。

在此条件下,基于质量守恒定律,单位时间在该无穷小空间内增加的气体质量等于单位时间内净流入的气体质量与扩散源产生的气体质量,其中扩散源产生的气体质量F(x,y, z,t)仅在(x_0, y_0, z_0)处有恒定值F_0而在其余空间各处均为0。于是列出并化简得到如下三维扩散方程(相比通用控制方程暂时不考虑对流项):

$$
\frac{\partial\rho}{\partial t} - D\left( \frac{\partial^{2}\rho}{\partial x^{2}} + \frac{\partial^{2}\rho}{\partial y^{2}} + \frac{\partial^{2}\rho}{\partial z^{2}} \right) = \rho_{t} - D\nabla^{2}\rho = 0, 0 < x < x_{0}, 0 < y < y_{0}, 0 < z < z_{0}
$$
其中:

  • 浓度的时间增长率(关于时间t的偏导数),即单位时间该无穷小空间内增加的气体质量:
    $$
    \frac{\partial\rho}{\partial t}
    $$

  • 扩散系数D仅与物质种类和温度有关,若研究某种特定气体在理想的均匀介质(温度在空间各处保持一致且不随时间变化)条件下的扩散,该系数值为常数;反之则其也应表示为关于空间坐标与时间的函数D(x, y, z, t)(在确定气体类型时仅与温度的分布函数T(x, y, z, t)有关)。

2.3 初始条件与边界条件

该问题的初始条件是显然的:研究区域空间内在初始状态(t = 0)没有该种烟雾气体分布,且在某固定点处有一恒定扩散源,始终以恒定的扩散强度(单位时间内单位体积产生的气体质量)产生气体并向区域空间内扩散。将这样的初始条件写成数学表达式的形式:

$$
\rho(x,y,\ z,0) = F_{0}\delta(x - \frac{x_{0}}{2})\delta(y - \ \frac{y_{0}}{2})\delta(z - 0)
$$
式中F_0表示在固定坐标端点
$$
\left( \frac{x_{0}}{2},\ \frac{y_{0}}{2},\ 0 \right)
$$
处的恒定扩散源的扩散强度。结合实验时采用的实验装置,参考其技术文档可知,其值约为5g/cm^3。

关于边界条件,针对我们所研究的扩散问题,通常采用Dirichlet边界条件:
$$
\frac{\partial\rho}{\partial n} = 0
$$
显然这是一个齐次边界条件,也是一个典型的第二类边界条件。为方便后续求解处理,针对我们所建立的空间直角坐标系,可以将其进一步写成分量形式:

$$
\left{ \begin{array}{r}
\left. \ \frac{\partial\rho}{\partial x} \right|{x = 0} = \left. \ \frac{\partial\rho}{\partial x} \right|{x = x_{0}} = 0 \
\left. \ \frac{\partial\rho}{\partial y} \right|{y = 0} = \left. \ \frac{\partial\rho}{\partial y} \right|{y = y_{0}} = 0 \
\left. \ \frac{\partial\rho}{\partial z} \right|{z = 0} = \left. \ \frac{\partial\rho}{\partial z} \right|{z = z_{0}} = 0
\end{array} \right.
$$

2.4 数学物理方程求解

针对2.2节提出的数学物理方程,可以通过分离变数法,结合2.3节给出的边界条件与初始条件,得到本征值问题的解和本征值的取值,并将多个本征值问题的解依次相乘得到本征解,再对所有可能的本征值求和,即可得到级数解。更进一步的,通过2.3节给出的初始条件,还可以对于级数解中的常数系数进行进一步确定,从而得到该数学物理方程完整的级数解解析表达式。

2.4.1 变量分离

对于扩散的烟雾而言,虽然其浓度会在空间中的不同位置随时间而变化,但时间与空间这两者在浓度瞬态变化的过程中实际上可以看作是相互独立的,即浓度随时间变化的速率ρ_t与浓度在空间分布中的变化速率∇ρ无关。

基于这样的假设,可以对烟雾浓度函数ρ(x, y, z, t)做如下的变量分离:

$$
\rho(x, y, z, t) = v(x,y,z) \bullet T(t)
$$

  • 空间部分v(x,y,z):描述烟雾浓度在三维空间中的分布,可以认为它与时间无关,或者说在某个时刻,密度的空间分布是静态的;

  • 时间部分T(t):描述烟雾密度随时间的变化,通常假设它是一个仅依赖于时间的函数,反映了扩散过程中气体密度随时间的衰减或增长。

在该式中,自变数x, y, z只出现于v之中,自变数t只出现于T之中,烟雾浓度的一般表示式具有分离变数的形式。

更进一步的,由于在空间中x、y、 z方向上的烟雾浓度变化情况也彼此独立,还可以对于空间部分v(x,y,z)做进一步的变量分离,即:

$$
v(x,y,z) = X\left( x) \bullet Y(y) \bullet Z(z \right)
$$
其中,X(x)为烟雾浓度在空间中x方向的分布情况,Y(y)为烟雾浓度在空间中y方向的分布情况,Z(z)为烟雾浓度在空间中z方向的分布情况。

2.4.2 常微分方程与本征值问题

显然三维扩散方程
$$
\rho_{t} - D\nabla^{2}\rho = 0
$$
为齐次方程,对于该齐次方程,可以将分离变量后的烟雾浓度代入三维扩散方程中,化简得到如下式子:

$$
T_{t}(t)v(x,y,z) = DT(t)\nabla^{2}v(x,y,z),即\frac{T_{t}(t)}{DT(t)} = \frac{\nabla^{2}v(x,y,z)}{v(x,y,z)} = \frac{X^{‘’}(x)}{X(x)} = \frac{Y^{‘’}(y)}{Y(y)} = \frac{Z^{‘’}(z)}{Z(z)}
$$
其中,左边均为关于时间t的函数,与坐标位置x, y, z无关;右边则是关于坐标位置x, y, z的函数,与时间t无关。两边相等是显然不可能的,除非两边实际上是同一个常数,不妨将其记为-λ,于是有:

$$
\frac{T_{t}}{DT} = \frac{X^{‘’}}{X} = \frac{Y^{‘’}}{Y} = \frac{Z^{‘’}}{Z} = - \lambda
$$
将该式关于空间部分X(x)、Y(y)、Z(z)与时间部分T(t)分别分离,可以得到关于X、Y、Z以及关于T的一系列常微分方程:

$$
\left{ \begin{array}{r}
T^{‘} + \lambda DT = 0 \
\nabla^{2}v + \lambda v = 0
\end{array} \right.\ 即 \left{ \begin{array}{r}
T^{‘} + \lambda DT = 0 \
X^{‘’} + \lambda X = 0 \
Y^{‘’} + \lambda Y = 0 \
Z^{‘’} + \lambda Z = 0
\end{array} \right.
$$
考虑2.3节给出的齐次边界条件即Dirichlet边界条件:边界处
$$
\frac{\partial\rho}{\partial n} = 0
$$
有:

$$
\left{ \begin{array}{r}
X^{‘}(0) = X^{‘}\left( x_{0} \right) = 0 \
Y^{‘}(0) = Y^{‘}\left( y_{0} \right) = 0 \
Z^{‘}(0) = Z^{‘}\left( z_{0} \right) = 0
\end{array} \right.\
$$
以上常微分方程与齐次边界条件共同构成了本征值问题。

2.4.3 本征值问题求解

首先,对于第一个方程
$$
\mathbf{T}^{‘} + \mathbf{\lambda DT} = \mathbf{0}
$$
而言,该方程为一阶常微分方程,可直接进行求解,结合初始条件
$$
\rho(x,y,\ z,0) = F_{0}\delta(x - \frac{x_{0}}{2})\delta(y - \ \frac{y_{0}}{2})\delta(z - 0)
$$
可得到:

$$
\mathbf{T}\left( \mathbf{t} \right) = \mathbf{F}_{\mathbf{0}}(\mathbf{1} - \mathbb{e}^{- \mathbf{\lambda Dt}})(C为某常数)
$$
其物理意义上的合理性如下:

  • 初始时刻t = 0:
    $$
    T(0) = F_{0}\left( 1 - \mathbb{e}^{- 0} \right) = 0
    $$
    即空间内在初始状态下(t = 0)没有该种烟雾气体分布,与初始条件吻合;

  • 稳定时刻t->∞:
    $$
    T(\infty) = F_{0}\left( 1 - \mathbb{e}^{- \infty} \right) = F_{0}
    $$
    即浓度达到稳定值F_0,与扩散物理过程实际现象一致;

  • 时间项指数因子-λDt决定了扩散速度,其中D为扩散系数,越大扩散越快,λ为空间模态本征值,模态越高衰减越快,这也符合实际物理扩散过程。

而针对后面三个方程,与对应的齐次边界条件形成了三组本征值问题,由于其形式完全一致,下面将以其中一组本征值问题
$$
\left{ \begin{array}{r}
X^{‘’} + \lambda X = 0 \
X^{‘}(0) = X^{‘}\left( x_{0} \right) = 0
\end{array} \right.\
$$
为例,利用幂级数法求得其本征解:

选定点x_1 = 0,显然常微分方程
$$
X^{‘’} + \lambda X = 0
$$
的系数函数在该点领域内是解析的,因此该点为方程
$$
X^{‘’} + \lambda X = 0
$$
的常点。针对常点x_1,可以给出X(x)在该点邻域上的泰勒级数形式:

$$
X(x) = \sum_{k = 0}^{\infty}{a_{k}{(x - x_{1})}^{k}}
$$
将其代入常微分方程可得:
$$
\sum_{k = 0}^{\infty}{\lbrack(k + 2)(k + 1)a_{k + 2} + \lambda}a_{k}\rbrack{(x - x_{1})}^{k} = 0
$$
于是有:
$$
(k + 2)(k + 1)a_{k + 2} + {\lambda a}{k} = 0,\ k = 0,1,2\ldots
$$
可进一步得到系数递推公式:
$$
a
{k + 2} = \frac{- \lambda}{(k + 2)(k + 1)}a_{k}
$$
推得:
$$
\left{ \begin{array}{r}
a_{2k} = \frac{\lambda^{k}{( - 1)}^{k}}{(2k)!}a_{0} \
a_{2k + 1} = \frac{\lambda^{k}{( - 1)}^{k}}{(2k + 1)!}a_{1}
\end{array} \right.\ ,\ k = 1,2,3\ldots
$$
因此可得到常微分方程
$$
X^{‘’} + \lambda X = 0
$$
的解:

$$
X(x) = a_{0}X_{0}(x) + a_{1}X_{1}(x)
$$
其中:

$$
X_{0}(x) = 1 + \frac{- \lambda}{2!}x^{2} + \frac{\lambda^{2}}{4!}x^{4} + \ldots + \frac{\lambda^{k}( - 1)^{k}}{(2k)!}x^{2k} + \ldots = \sum_{k = 0}^{\infty}{\frac{\lambda^{k}( - 1)^{k}}{(2k)!}x^{2k}} = \cos(\sqrt{\lambda}x)
$$

$$
X_{1}(x) = x + \frac{- \lambda}{3!}x^{3} + \frac{\lambda^{2}}{5!}x^{5} + \ldots + \frac{\lambda^{k}( - 1)^{k}}{(2k + 1)!}x^{2k + 1} + \ldots = \sum_{k = 0}^{\infty}{\frac{\lambda^{k}( - 1)^{k}}{(2k + 1)!}x^{2k + 1}} = \frac{\sin(\sqrt{\lambda}x)}{\sqrt{\lambda}}
$$

此时的收敛半径为
$$
R = \lim_{n \rightarrow \infty}\left| \frac{a_{n}}{a_{n + 2}} \right| = \lim_{n \rightarrow \infty}\left| \frac{(n + 2)(n + 1)}{- \lambda} \right| = \infty
$$
即级数解X_0(x)$和X_1(x)在复数域上始终收敛。X_0(x)仅含x的偶次幂,为偶函数;X_1(x)仅含x的奇次幂,为奇函数。同时,式中含有sqrt(λ)项,这要求λ≥0。

于是有:
$$
X^{‘}(x) = - a_{0}\sqrt{\lambda}\sin(\sqrt{\lambda}x) + a_{1}\cos(\sqrt{\lambda}x)
$$
将齐次边界条件
$$
X^{‘}(0) = X^{‘}\left( x_{0} \right) = 0
$$
代入可得:

$$
\left{ \begin{array}{r}
a_{1} = 0 \a_{0}\sqrt{\lambda}\sin(\sqrt{\lambda}x_{0}) + a_{1}\cos(\sqrt{\lambda}x_{0}) = 0
\end{array} \right.\
$$
显然有a_1= 0,但对于a_0而言,如果a_0也为0,这样的解是没有意义的;而要使得a_0不为零,就要求
$$
\lambda = 0或\sin\left( \sqrt{\lambda}x_{0} \right) = 0
$$
恒成立,这意味着
$$
\sqrt{\lambda}x_{0} = k\pi
$$
由于
$$
x_{0} > 0、\sqrt{\lambda} \geq 0
$$
于是k的可取值为
$$
k = 0,1,2,\ldots
$$
在这样的条件下,由于x_0与π均为定值,本征值λ则有常数k唯一确定,此时不妨将常数k视为新的本征值。于是可得到该本征值问题的本征解:

$$
X(x) = a_{0}\cos\left( \sqrt{\lambda}x \right) = a_{0x}\cos\left( \frac{k\pi}{x_{0}}x \right),k = 0,1,2,\ldots
$$
与此类似的,另外两个本征值问题的本征解为:

$$
Y(y) = a_{0y}\cos\left( \frac{m\pi}{y_{0}}y \right),m = 0,1,2,\ldots
$$

$$
Z(z) = a_{0z}\cos\left( \frac{n\pi}{z_{0}}z \right),n = 0,1,2,\ldots
$$

值得注意的是,三个本征值问题中出现了三个新的本征值k、m、n,他们分别与时间部分涉及到的本征值λ有定量关系,但是当他们取不同值时,原本征值λ也会有不同的取值,即本质上有三个不同的本征值k、m、n取代了原先的本征值λ。

基于以上本征值问题的解和本征值的取值,可以将其各部分相乘得到本征解,再对本征解按照所有可能的本征值求和,即可得到烟雾浓度的级数解:

$$
\rho(x,\ y,\ z,\ t) = \sum_{k = 0}^{\infty}{\sum_{m = 0}^{\infty}{\sum_{n = 0}^{\infty}{a_{0x}a_{0y}a_{0z}F_{0}\cos\left( \frac{k\pi}{x_{0}}x \right)\cos\left( \frac{m\pi}{y_{0}}y \right)\cos\left( \frac{n\pi}{z_{0}}z \right)}}}(1 - \mathbb{e}^{- \left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)D\pi^{2}t})
$$
由于式中a_0x、a_0y、a_0z、F_0均为常数(可由初始条件
$$
\rho(x,y,\ z,0) = F_{0}\delta(x - \frac{x_{0}}{2})\delta(y - \ \frac{y_{0}}{2})\delta(z - 0)
$$
进一步确定),不妨将其替换为某一常数
$$
A = a_{0x}a_{0y}a_{0z}F_{0}
$$
以便于后续计算,即此时可得到级数解为:
$$
\rho(x,\ y,\ z,\ t) = \sum_{k = 0}^{\infty}{\sum_{m = 0}^{\infty}{\sum_{n = 0}^{\infty}{A\cos\left( \frac{k\pi}{x_{0}}x \right)\cos\left( \frac{m\pi}{y_{0}}y \right)\cos\left( \frac{n\pi}{z_{0}}z \right)}}}(1 - \mathbb{e}^{- \left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)D\pi^{2}t})
$$

2.4.4 级数解常数系数的确定

需要指出,函数
$$
\varnothing_{kmn} = \cos\left( \frac{k\pi}{x_{0}}x \right)\cos\left( \frac{m\pi}{y_{0}}y \right)\cos\left( \frac{n\pi}{z_{0}}z \right)
$$
是定义在长方体区域
$$
x \in \left\lbrack 0,x_{0} \right\rbrack,\ \ y \in \left\lbrack 0,y_{0} \right\rbrack,z \in \lbrack 0,z_{0}\rbrack
$$
上的一组正交基函数,其满足如下正交性与归一化条件:

$$
\int_{0}^{z_{0}}{\int_{0}^{y_{0}}{\int_{0}^{x_{0}}{\varnothing_{kmn}\varnothing_{k^{‘}m^{‘}n^{‘}}dxdydz}}} = \left{ \begin{array}{r}
x_{0}y_{0}z_{0},\ (k,m,n) = (k^{‘},m^{‘},n^{‘}) \
0,(k,m,n) \neq (k^{‘},m^{‘},n^{‘})
\end{array} \right.\
$$
对于级数解
$$
\rho(x,\ y,\ z,\ t) = \sum_{k = 0}^{\infty}{\sum_{m = 0}^{\infty}{\sum_{n = 0}^{\infty}{A\cos\left( \frac{k\pi}{x_{0}}x \right)\cos\left( \frac{m\pi}{y_{0}}y \right)\cos\left( \frac{n\pi}{z_{0}}z \right)}}}\mathbb{e}^{- \left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)D\pi^{2}t} = \sum_{k = 0}^{\infty}{\sum_{m = 0}^{\infty}{\sum_{n = 0}^{\infty}{A\varnothing_{kmn}}}}{(1 - \mathbb{e}}^{- \left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)D\pi^{2}t})
$$
而言,将两侧同时与Φ_k’m’n’相乘,并在区域内积分:

$$
\int_{0}^{z_{0}}{\int_{0}^{y_{0}}{\int_{0}^{x_{0}}{\rho(x,\ y,\ z,\ t)\varnothing_{k^{‘}m^{‘}n^{‘}}dxdydz}}} = \int_{0}^{z_{0}}{\int_{0}^{y_{0}}{\int_{0}^{x_{0}}{(\sum_{k = 0}^{\infty}{\sum_{m = 0}^{\infty}{\sum_{n = 0}^{\infty}{A\varnothing_{kmn}}}}\mathbb{e}^{- \left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)D\pi^{2}t})\varnothing_{k^{‘}m^{‘}n^{‘}}dxdydz}}}
$$
利用正交性条件,上述式子右边仅保留(k,m,n) = (k’, m’, n’)的项,其余项积分为0:

$$
\int_{0}^{z_{0}}{\int_{0}^{y_{0}}{\int_{0}^{x_{0}}{\rho(x,\ y,\ z,\ t)\varnothing_{kmn}dxdydz}}} = A(1 - \mathbb{e}^{- \left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)D\pi^{2}t})x_{0}y_{0}z_{0}
$$
代入初始条件
$$
\rho(x,y,\ z,0) = F_{0}\delta(x - \frac{x_{0}}{2})\delta(y - \ \frac{y_{0}}{2})\delta(z - 0)
$$
即可在t = 0处解出系数
$$
A = \frac{\int_{0}^{z_{0}}{\int_{0}^{y_{0}}{\int_{0}^{x_{0}}{\rho(x,\ y,\ z,\ t)\varnothing_{kmn}dxdydz}}}}{\left( 1 - \mathbb{e}^{- \left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)D\pi^{2}t} \right)x_{0}y_{0}z_{0}}
$$
由于分子分母在t = 0处均为0,故利用洛必达法则上下对时间$t$求导得:

$$
\rho_{t} = T^{‘} = \mathbb{e}^{- \left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)Dt}(忽略系数)
$$

$$
A = \frac{\left. \rho_{t} \right|{t = 0}\int{0}^{z_{0}}{\int_{0}^{y_{0}}{\int_{0}^{x_{0}}{\rho(x, y, z, 0)\varnothing_{kmn}dxdydz}}}}{\left( {\left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)D\pi^{2}\mathbb{e}}^{- \left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)D\pi^{2}t} \right)x_{0}y_{0}z_{0}}
= \frac{F_{0}}{D\left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)\pi^{2}x_{0}y_{0}z_{0}}\varnothing_{kmn}\left( \frac{x_{0}}{2},\frac{y_{0}}{2},0 \right)
= \frac{F_{0}}{D\left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)\pi^{2}x_{0}y_{0}z_{0}}\cos\left( \frac{k\pi}{x_{0}}\frac{x_{0}}{2} \right)\cos\left( \frac{m\pi}{y_{0}}\frac{y_{0}}{2} \right)\cos\left( \frac{n\pi}{z_{0}}0 \right) = \frac{F_{0}}{D\left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)\pi^{2}x_{0}y_{0}z_{0}}\cos\left( \frac{k\pi}{2} \right)\cos\left( \frac{m\pi}{2} \right),其值随k,m,n的变化而变化
$$

最后,将其代入2.4.3节解得的级数解,可以最终得到植物甘油气雾浓度的解析表达式为:

$$
\rho(x,\ y,\ z,\ t) = \frac{F_{0}}{Dx_{0}y_{0}z_{0}}\sum_{k = 0}^{\infty}{\sum_{m = 0}^{\infty}{\sum_{n = 0}^{\infty}{\frac{1}{\left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)\pi^{2}}\cos\left( \frac{k\pi}{2} \right)\cos\left( \frac{m\pi}{2} \right)\cos\left( \frac{k\pi}{x_{0}}x \right)\cos\left( \frac{m\pi}{y_{0}}y \right)\cos\left( \frac{n\pi}{z_{0}}z \right)}}}(1 - \mathbb{e}^{- \left( \frac{k^{2}}{x_{0}^{2}} + \frac{m^{2}}{y_{0}^{2}} + \frac{n^{2}}{z_{0}^{2}} \right)D\pi^{2}t})
$$

2.5 基于MATLAB的级数解可视化

基于上述求解过程得到的烟雾浓度解析表达式,编写了对应的MATLAB程序,将不同时刻烟雾浓度在空间内各点处的分布情况进行计算与可视化。值得注意的是,由于级数解本质是无限项求和,而在MATLAB程序实际计算中需要指定求和项数,在此综合考虑计算精度与算力需求,选取$k,m,n$三个本征值各10项进行计算与可视化;除此之外,为与实际实验相对应,计算的扩散时间设置为30秒,同时为直观观察扩散过程中烟雾浓度随时间的变化,设置计算步长为1秒。

代入相应的数值并运行程序可以得到如下的可视化结果(代码详见附件1):

图2.2 扩散时间分别为0s、10s、20s、30s时的解析求解可视化结果
图2.2 扩散时间分别为0s、10s、20s、30s时的解析求解可视化结果

图2.2 扩散时间分别为0s、10s、20s、30s时的解析求解可视化结果图2.2 扩散时间分别为0s、10s、20s、30s时的解析求解可视化结果

可以看到,随着时间的推移,烟雾的可见范围越来越大(设定可见浓度为0.3mg/cm^3),且浓度从扩散中心点向四周呈递减趋势,这与我们的生活经验高度一致;在扩散时间为0s(即扩散未开始)时,空间区域内烟雾浓度均为零,这也与我们设定的初始条件相一致。以上事实初步验证了理论推导结果的正确性。

除此之外,为与后续的实验过程相对应,还记录了特定测量点(x_0/2, 0, z_0)处的烟雾浓度随时间的变化情况,并根据换算公式
$$
ppm = \frac{C \times 10^{6}}{\rho}
$$
将原有的气雾质量浓度C(单位:g/cm^3)换算为体积比ppm(百万分之一),得到的结果如下:

图2.3 测量点(x_0/2, 0, z_0)处的烟雾浓度随时间变化情况(单位:ppm)

可以看到,由于该处离扩散中心点较远,此处烟雾浓度大概在扩散时间20s后达到基本稳定,稳定时的烟雾浓度值约为380ppm。

3 数值仿真模拟

3.1 数值分析方法:有限元法

由于严格的解析求解对于方程的复杂度以及初始条件与边界条件等都具有一定的要求,第2章所涉及到的理论分析也仅仅是围绕简化后的烟雾扩散问题展开。相比于真实的场景,这样的理论分析忽略了很多现实环境中的复杂变量(比如对流)。为使得我们的研究更具有实用价值,我们需要对于更加泛化的数学物理模型,采用数值求解的方法去模拟真实的扩散情况。

在这一部分,我们主要考虑到,2.3节提到的初始条件中烟雾源处视为单点并采用δ函数进行数学表示的做法过于理想化。与实验相对应的,我们采用的烟雾发生器的烟雾出口也不仅仅是一个单点,而是一个半径约为0.6cm的圆面。因此,我们希望通过有限元这一数值求解方法,对于初始条件迭代后的问题进行数值模拟分析。

图3.1 烟雾发生器烟雾出口孔径测量

图3.1 烟雾发生器烟雾出口孔径测量

基于有限元法的基本思想,我们主要按照如下的方法步骤进行数值分析:

图3.2 有限元法数值分析步骤

首先进行区域剖分,将长方体研究区域Ω划分为有限个小单元,用于构建离散有限元模型。在划分单元时,采用长方体网格剖分方式,将区域划分为n_x * n_y * n_z个小立方体。每个小立方体单元有8个顶点,因此共有N = (n_x+1) * (n_y+1) * (n_z+1)个节点。对这些节点按列优先顺序进行编号,依次为1、2、…、N,同时对于各单元按体积分块顺序编号,编号为1、2、…、E。

图3.3 区域剖分示意图

接下来需要选择合适的单元基函数以表示单元内的浓度分布。在此我们选取基函数Φ_ i(x,y,z)近似表示浓度:
$$
\rho(x,\ y,\ z,\ t) \approx \sum_{i = 1}^{N}{c_{i}(t)\phi_{i}(x,y,z)}(c_{i}(t)表示节点i处的浓度值)
$$
并采用线性插值基函数(梯形单元),即单元内任意一点的浓度由8个顶点的浓度值线性插值(符号根据顶点位置的坐标取正或负):
$$
\phi_{i}(x,y,z) = \frac{1}{8}(1 \pm x)(1 \pm y)(1 \pm z),\ i = 1,2,\ldots,8
$$
值得注意的是,在单元内,基函数在对应节点取值为1,其余节点取值为0,同时基函数也满足局部支撑性,即在单元外为零。

基于划分好的单元和选取的单元基函数,可以写出对应的单元积分表达式。通过对单元内的质量矩阵、刚度矩阵和源项向量进行积分,单元积分表达式应使得方程余量最小、本质边界条件余量最小、自然边界条件余量最小。将扩散方程乘以任意测试函数Φ_j(x,y,z)后,对区域Ω积分即可得到单元积分表达式:

$$
\int_{\Omega}^{}{\phi_{j}\frac{\partial\rho}{\partial t}\mathbb{d}\Omega} = D\int_{\Omega}^{}{\phi_{j}\nabla^{2}\rho\mathbb{d}\Omega} + \int_{\Omega}^{}{\phi_{j}F\mathbb{d}\Omega}
$$
该表达式中共有三项:

  • 时间导数项(差分离散):
    $$
    \int_{\Omega}^{}{\phi_{j}\frac{\partial\rho}{\partial t}\mathbb{d}\Omega} \approx \int_{\Omega}^{}{\phi_{j}\frac{\rho^{n + 1} - \rho^{n}}{\mathrm{\Delta}t}\mathbb{d}\Omega}
    $$

  • 空间扩散项:通过分部积分,将扩散项转化为:

$$
\int_{\Omega}^{}{\phi_{j}\nabla^{2}\rho\mathbb{d}\Omega} = - \int_{\Omega}^{}{\nabla\phi_{j}\nabla\rho d\Omega} + \int_{\partial\Omega}^{}{\phi_{j}\nabla\rho \bullet \overrightarrow{n}dS}
$$

在齐次边界条件下,边界项为0。

  • 离散形式:代入插值函数近似:

$$
\int_{\Omega}^{}{\phi_{j}\frac{\rho^{n + 1} - \rho^{n}}{\mathrm{\Delta}t}\mathbb{d}\Omega} = D\int_{\Omega}^{}{\nabla\phi_{j}\nabla(\sum_{i = 1}^{N}{C_{i}^{n + 1}\phi_{i}})\mathbb{d}\Omega} + \int_{\Omega}^{}{\phi_{j}F\mathbb{d}\Omega}
$$

基于列出的单元积分表达式,可以对各个单元进行分析,计算其对应的质量矩阵、刚度矩阵与源项向量,从而建立有限元方程。将离散化方程写为矩阵形式:

$$
M\frac{c^{n + 1} - c^{n}}{\Delta t} + Kc^{n + 1} = F
$$
其中:

  • 质量矩阵:
    $$
    M_{\mathbb{i}j} = \int_{\Omega}^{}{\phi_{i}\phi_{j}d\Omega}
    $$

  • 刚度矩阵:
    $$
    K_{\mathbb{i}j} = D\int_{\Omega}^{}{\nabla\phi_{i}\nabla\phi_{j}d\Omega}
    $$

  • 源项向量:
    $$
    F_{\mathbb{i}} = \int_{\Omega}^{}{F\phi_{i}d\Omega}
    $$

进而可以将单元积分表达式更新为有限元方程:

$$
(M + \Delta tk)c^{n + 1} = Mc^{n} + \Delta tF
$$
将每个单元映射到标准立方体单元(边长为2,中心为原点),可以根据基函数梯度
$$
\nabla\phi_{i} = (\frac{\partial\phi_{i}}{\partial x},\ \frac{\partial\phi_{i}}{\partial y},\ \frac{\partial\phi_{i}}{\partial z})
$$
在这些标准单元上使用高斯积分进行质量矩阵和刚度矩阵的计算:

$$
M_{\mathbb{i}j}^{(e)} = \int_{\Omega_{e}}^{}{\phi_{i}\phi_{j}d\Omega}
$$

$$
K_{\mathbb{i}j}^{(e)} = D\int_{\Omega_{e}}^{}{\nabla\phi_{i}\nabla\phi_{j}d\Omega}
$$

将源项向量
$$
F_{\mathbb{i}}^{(e)} = \int_{\Omega_{e}}^{}{F\phi_{i}d\Omega}
$$
离散化为节点的平均值后,也可以通过插值基函数对其进行积分。

完成单元分析后,可以将单元矩阵的局部节点编号1、2、…、8映射到总体矩阵的全局节点编号,并在每个单元中将其局部质量矩阵M^e、刚度矩阵K^e和源项向量F^e加入到总体矩阵M、K和向量F中,进行总体合成。

在对于总体有限元方程进行求解之前,还需要在总体矩阵方程中施加边界条件。在此我们采用消行修正法:对于齐次边界条件,将其带入总体有限元方程,并将边界上的节点浓度固定置零,同时修改总体矩阵对应行列为单位矩阵,右端项置零;对于非齐次边界条件(即初始条件),在边界节点区域(以扩散源中心(x_0/2, y_0/2, 0)为圆心,半径为0.6cm的圆面)中给定固定浓度值F_0,并对应调整右端向量F(已在单元积分中满足):
$$
F_{i} \Leftarrow F_{i} - k_{ij}F_{0}
$$
最后,对于总体有限元线性方程

$$
(M + \Delta tk)c^{n + 1} = Mc^{n} + \Delta tF
$$
可以采用迭代方法进行求解:

  • 初始条件:
    $$
    c^{0} = 0
    $$

  • 时间步进:对于每个时间步,首先计算右端向量
    $$
    Mc^{n} + \Delta tF
    $$
    再采用稀疏矩阵求解器计算下一步的浓度
    $$
    c^{n + 1}
    $$

  • 迭代终止:当时间达到设定终止时间T时,终止求解过程。

3.2 基于Python的有限元数值仿真

基于3.1节提出的有限元分析方法,利用Python中的MeshPy依赖库进行网格生成,并编写对应Python代码进行有限元数值仿真的计算与可视化。出于程序运行的稳定性等方面考虑,数值计算时仅选取原长方体区域的四分之一(高度不变,长宽各取一半,坐标原点即为扩散源中心)作为扩散区域。

运行程序(代码详见附录2),得到以下数值仿真结果:

图3.4 扩散时间分别为0s、10s、20s、30s时,x = x_0/2处垂直截面内的有限元仿真结果
图3.4 扩散时间分别为0s、10s、20s、30s时,x = x_0/2处垂直截面内的有限元仿真结果

图3.4 扩散时间分别为0s、10s、20s、30s时,x = x_0/2处垂直截面内的有限元仿真结果图3.4 扩散时间分别为0s、10s、20s、30s时,x = x_0/2处垂直截面内的有限元仿真结果

图3.5 扩散时间分别为0s、10s、20s、30s时,原长方体四分之一区域内有限元仿真结果图3.5 扩散时间分别为0s、10s、20s、30s时,原长方体四分之一区域内有限元仿真结果

图3.5 扩散时间分别为0s、10s、20s、30s时,原长方体四分之一区域内有限元仿真结果图3.5 扩散时间分别为0s、10s、20s、30s时,原长方体四分之一区域内有限元仿真结果

可以看到,有限元仿真Python程序的运行结果能够较好地反映扩散过程中烟雾浓度分布的变化情况,基本与理论分析可视化中展现的趋势一致,在此不再赘述。相较于理论分析的可视化结果而言,有限元仿真的结果更加精细也更加丰富,特别是在截面的浓度分布上,很好地反映了扩散过程的基本特点,但在空间中的浓度分布可能受到了边界条件的过分影响,导致其明显呈现沿容器边缘扩散的趋势,而在容器内部的扩散范围较为有限。

3.3 基于COMSOL仿真软件的有限元数值仿真

尽管对于初始条件进行了一定的修正,但上述的有限元数值模拟仍然缺乏对于对流项的考虑,这可能是导致其结果受边界条件影响较大的原因之一。除此之外,上述的理论分析与数值计算也仅仅对于烟雾扩散过程中的浓度分布变化进行计算与模拟,而缺乏了烟雾扩散速度方面的计算分析,这也受到了解析求解方法的限制。因此,我们希望借助成熟的商业有限元仿真软件COMSOL进行更进一步的数值仿真,以分别模拟常压扩散与负压吸附两种情况下的植物甘油气雾颗粒扩散情况,并得到常压扩散情况下的扩散速度场与压力分布情况,帮助我们进一步完善烟雾扩散的基本模型。

为更加真实地反映实际扩散过程中的复杂物理环境,我们需要在COMSOL软件中指定相应的物理场模型:

  • 湍流模型:帮助模拟湍流流动对气体扩散过程的影响,增加流体混合、热量传递和物质扩散等过程的复杂性;

  • RANS模型:通过对Navier-Stokes方程进行时间平均,简化了直接求解瞬时湍流方程的复杂度,计算成本相对较低,同时可以较为准确地描述气体扩散过程中湍流对流场的影响,尤其是在稳态流动情况下;

  • 低雷诺数k-ω模型:采用湍动能(k)和湍流频率(ω)作为主要变量,能够较好地捕捉近壁区和低雷诺数流动特性,在复杂的流动区域中能更好地描述气体的湍流行为,提供更准确的边界层模拟,从而提高气体扩散的模拟精度。

图3.6 物理场模型部分参数设置

除此之外,我们还需要使用温度场模型来模拟扩散过程中粒子的运动情况。对于烟雾产生端,采用650~750K的的随机梯度划分以达到不同速度的效果。

图3.7 温度场模型参数设置

为了模拟出烟雾粒子扩散过程中的运动状态,我们还需要模拟出颗粒的流动。由于实际实验中采用的植物甘油气雾本质上是一种固体烟雾颗粒,所以在仿真中,我们选取遵循一阶牛顿规则的粒子,并给予每个颗粒一定的质量(与植物甘油气雾的理化性质相一致,设置其密度为1.26g/cm^3)。

图3.8 扩散粒子参数设置

处于仿真计算的复杂度与运行时间考虑,我们仅在x = x_0/2处的垂直截面区域内(50cm*60cm)进行仿真,并设置仿真时间为6秒、时间步长为0.1秒、网格单元大小为1cm。同时与实验环境保持一致,温度设置为25℃(298.15K)。

图3.9 COMSOL仿真基本设置

考虑到需要对于常压扩散与负压吸附两种情形分别进行仿真,还需要对于仿真边界(壁)进行一定的设置:

  • 常压扩散:将各边设置为壁,壁条件设置为满散射;

  • 负压吸附:将上边界设置为出口,边界条件设置为静压力(-0.02MPa)

完成了以上的所有设置流程,可以点击”计算”运行有限元仿真,仿真结果如下所示:

图3.10 COMSOL有限元仿真结果:常压扩散下的三角网格划分与稳定后的压力空间分布
图3.10 COMSOL有限元仿真结果:常压扩散下的三角网格划分与稳定后的压力空间分布

图3.11 COMSOL有限元仿真结果:常压扩散下稳定后的速度场强度及其空间分布图3.11 COMSOL有限元仿真结果:常压扩散下稳定后的速度场强度及其空间分布

图3.12 COMSOL仿真结果:扩散3s时常压扩散(上)与负压吸附(下)的粒子运动轨迹
图3.12 COMSOL仿真结果:扩散3s时常压扩散(上)与负压吸附(下)的粒子运动轨迹

可以看到,通过COMSOL软件进行有限元数值仿真,可以有效划分三角网格并准确模拟出常压扩散情况下的扩散速度场及其强度分布情况,同时仿真得到的粒子运动轨迹大体趋势也与先前的解析求解与有限元数值计算较为吻合。仿真结果成功验证了先前理论模型推导的有效性,并在扩散速度方面进行了有力补充。

4 实验现象验证

4.1 预实验:常压封闭环境下的水雾扩散

在进行理论模型的推导与演算前,为更准确地把握初始条件、边界条件以及部分环境因素的设置,并对于烟雾扩散的具体现象进行初步了解,我们首先基于手头已有的实验材料进行了定性观察的预实验。在预实验中,我们使用香薰机作为烟雾发生装置,但由于装置限制,仅采用纯净水作为烟雾发生原料以产生水雾,通过观察水雾扩散现象来初步把握扩散过程的基本特征。

图4.1 预实验装置:香薰机(用于产生水雾)图4.1 预实验装置:香薰机(用于产生水雾){width=”1.466816491688539in”
height=”1.9565212160979877in”}

图4.2 水雾发生原料:农夫山泉纯净水100mL

图4.3 40cm\*50cm\*60cm的亚克力长方体容器,底部有半径3cm的开孔供烟雾通入

基于以上预实验装置,搭建如下的实验装置场景:

图4.4 水雾扩散预实验环境场景搭建

启动香薰机,开始产生水雾,并通过长方体容器底部开孔向容器内部区域空间进行烟雾扩散。设置扩散时间为10分钟,拍摄视频记录扩散实验现象。

图4.5 扩散时间为0min、5min、10min时的水雾扩散现象图4.5 扩散时间为0min、5min、10min时的水雾扩散现象

图4.5 扩散时间为0min、5min、10min时的水雾扩散现象

可以看到,随着扩散时间的推移,容器内聚集的水雾越来越多,且越靠近底部、越靠近扩散源中心,水雾的浓度也就越高(从现象来看,可以认为水雾团越密集的地方浓度越高),且当扩散时间达到一定程度时,容器内的水雾逐渐达到饱和,开始向容器底部沉降,并在容器底部呈聚集态势。

预实验的定性观察帮助我们对于烟雾扩散的基本现象有了宏观上的认知,并通过实物实验的方式让我们对于研究对象以及研究区域有了更加具象的认识,这对于我们后续理论模型推导与数值计算仿真的有序推进打下了良好的基础。但定性实验只能帮助我们认识现象,要对于理论模型的研究成果进行更加严谨的实证评估,还需要进行定量化的实验与测量。

4.2 正式实验:常压封闭环境下的植物甘油气雾扩散

在完成了理论模型的推导以及解析求解与数值仿真计算之后,我们还希望通过实际定量实验的测量结果来验证理论模型的合理性。为进行定量化实验,我们将预实验中的部分实验装置进行了替换,以严格定量控制烟雾的产生过程并实时测量特定测量点位处的烟雾浓度,从而与理论计算与仿真结果进行比照。

对于烟雾发生器而言,为达到定量产生植物甘油气雾的效果,选取了Selens手持烟雾发生器作为新的实验器材,该款烟雾发生器可以产生固定强度的植物甘油气雾,查阅其产品技术文档可知,其固定产生烟雾的强度约为F_0 ≈ 5g/cm^3(与先前理论推导采用相同符号);同时为定量测量定点(x_0/2,0,z_0)处(与2.5节一致)的烟雾浓度,选取了MQ-2烟雾浓度传感器进行测量,通过STM32F1开发板对传感器进行供电,同时将数据传输至PC端并实时在屏幕上显示测量到的烟雾浓度值(单位:ppm,与g/cm^3的换算关系在2.5节已提及)。

图4.6 MQ-2烟雾浓度传感器实物图及其灵敏度特性图4.6 MQ-2烟雾浓度传感器实物图及其灵敏度特性

图4.7 Selens手持烟雾发生器实物图(出雾弹直径约40mm)图4.7 Selens手持烟雾发生器实物图(出雾弹直径约40mm)

图4.8 STM32F1开发板实物图

需要注意的是,由于该款传感器读取烟雾浓度的过程是渐变而非突变,因此在使用前需要一定的预热(初始化)时间,且初始化完成后,在还未开始烟雾扩散实验时,读取到的烟雾浓度也并不为零,因此在后续数据处理时还需要移除对应的偏移量,实测平均偏移量在180ppm左右。

图4.9 初始化后的烟雾传感器读取浓度数值

基于以上实验装置,搭建如下的实验装置场景:

图4.10 常压下植物甘油气雾扩散实验环境场景搭建

图4.11 传感器实际装置安装局部图

图4.12 烟雾发生器实际装置安装局部图

对于该款手持烟雾发生器而言,需要手动长按开关才可持续发生烟雾,且该产品具有安全保护机制,最多只能连续产生30s的植物甘油气雾。启动装置后即开始进行烟雾扩散实验,烟雾通过长方体容器底部开孔向容器内部区域空间不断进行扩散。拍摄视频记录扩散实验现象与扩散过程中屏幕上显示的烟雾浓度的实时变化数值及曲线。

图4.13 实验过程中LCD屏幕上显示烟雾传感器测量到的浓度值

图4.14 常压下扩散时间分别为0s、10s、20s、30s时的烟雾扩散现象图4.14 常压下扩散时间分别为0s、10s、20s、30s时的烟雾扩散现象图4.14 常压下扩散时间分别为0s、10s、20s、30s时的烟雾扩散现象
图4.14 常压下扩散时间分别为0s、10s、20s、30s时的烟雾扩散现象

图4.15 常压下停止烟雾发生30s后容器内的烟雾聚集状况

可以发现,随着扩散时间的增加,容器内聚集的植物甘油气雾也越来越多,且扩散的过程与理论解析求解可视化以及有限元数值仿真展示的烟雾扩散趋势基本一致。同时,在常压状态下,相较于水雾扩散而言,容器内聚集的植物甘油气雾在停止发生一段时间后仍然没有明显消散,这是由于植物甘油独特的理化性质所导致的,也比水雾扩散实验的结果更加接近香烟烟雾扩散的实际情况。

将烟雾传感器读取到的烟雾浓度数值实时传输到PC端保存,利用MATLAB进行可视化并与理论解析求解计算的定点测量结果进行比照,结果如下:

图4.16 测量点(x_0/2,0,z_0)处理论计算与实验测量的烟雾浓度随时间变化比照(单位:ppm)蓝线为测量结果,红线为计算结果

可以看到,通过烟雾传感器测量得到的烟雾浓度同样在20s左右达到基本稳定,且两者达到稳定后的浓度数值基本接近(在5%的误差允许范围内,约为380ppm),这说明我们的理论模型能够较好地解释实验测量结果;但比较而言,相比起实际测量的数据,理论求解得到的浓度在达到稳定前均高于实际测量结果,这一方面可能由于理论计算时进行了部分近似假设造成了偏差,另一方面也可能是实际测量时在传感器测量与数据读取传输过程中产生了一定的延迟。

通过理论求解计算结果与实验测量观察结果的比照可以发现,我们建立的理论模型对于烟雾扩散情况的模拟效果无论从现象(可视化效果)上还是从浓度数值上都能对于实际的烟雾扩散现象进行较好的模拟,这充分证明了我们理论解析求解模型研究的有效性。

4.3 正式实验:封闭环境中负压作用下的植物甘油气雾扩散

在正式实验阶段,除了在常压条件下进行烟雾扩散实验以验证烟雾扩散理论模型的准确性之外,为了后续进一步地提出可能的烟雾吸附方案并验证其有效性,我们还在负压环境下进行了对应的植物甘油气雾扩散实验,为后续开发提供参考。

在负压源的选取上,我们采用了涵道风机来营造负压环境。该款涵道风机可以通过旋钮调节产生不同档位强度的负压,通过查阅该产品技术文档可知,我们在实验时次啊用的最低档位负压值为P_0≈-0.02MPa,在通过COMSOL有限元仿真对于负压环境下的烟雾扩散进行数值模拟时也采用了相同的参数。其余的实验装置保持不变。

图4.17 涵道风机实物图(上图为出风口一侧,下图为进风口一侧)图4.17 涵道风机实物图(上图为出风口一侧,下图为进风口一侧)

基于常压实验中搭建好的实验装置,在原烟雾发生处的开口对侧开出一个相同大小(半径3cm)的圆孔,并将涵道风机进风口对准该圆孔,完成负压吸附环境下植物甘油气雾扩散实验环境的搭建:

图4.18 负压环境下植物甘油气雾扩散实验场景搭建

通过STM32F1开发板驱动涵道风机(供电)并通过旋钮将其调至最低档位,并采用与常压环境下实验相同的实验步骤与实验条件,拍摄视频记录扩散实验现象与扩散过程中屏幕上显示的烟雾浓度的实时变化数值及曲线。

图4.19 负压作用下扩散时间分别为0s、10s、20s、30s时的烟雾扩散现象图4.19 负压作用下扩散时间分别为0s、10s、20s、30s时的烟雾扩散现象图4.19 负压作用下扩散时间分别为0s、10s、20s、30s时的烟雾扩散现象图4.19 负压作用下扩散时间分别为0s、10s、20s、30s时的烟雾扩散现象

图4.20 负压作用下停止烟雾发生30s后容器内的烟雾聚集状况

可以发现,随着扩散时间的增加,容器内聚集的植物甘油气雾越来越多,但没有像常压状态下一样出现大量烟雾的堆积与沉降现象,且整体扩散方向受负压源牵引作用明显,这点与COMSOL有限元仿真得到的烟雾粒子运动轨迹基本一致;同时可以注意到,同样是在停止烟雾发生30秒后(期间持续进行负压吸附),在负压吸附的持续作用下容器内聚集的植物甘油气雾迅速地被排出容器外,而不像常压环境下的扩散那样会长时间堆积在容器内难以消散,这也充分验证了负压吸附这一解决方案的可行性与有效性,为后续的开发应用提供了良好的实验基础。

将烟雾传感器读取到的烟雾浓度数值实时传输到PC端保存,利用MATLAB进行可视化并与理论解析求解计算的定点测量结果进行比照,结果如下:

图4.21 负压环境下测量点(x_0/2,0,z_0)处实验测量的烟雾浓度随时间变化结果(单位:ppm)

可以看到,相较于常压环境中的烟雾扩散,在负压作用下测量点处的植物甘油气雾浓度达到基本稳定的速度明显变快(大概在10s左右),且稳定后测得的烟雾浓度数值(100ppm左右)相较于常压下的稳定测量结果(380ppm左右)有明显下降,这样的数值结果在实验现象的基础上更进一步地验证了负压吸附这一解决方案的可行性与有效性,同时为后续的开发应用提供了良好的实验数据基础。

5 研究结论应用

5.1 研究结论

基于以上对于植物甘油气雾扩散问题在解析求解、数值模拟以及实验测量方面的深入研究,针对1.2节提出的研究目标,可以给出如下三点主要的研究结论:

  • 在扩散过程中不同时刻的浓度分布方面,针对常压下密闭环境中的烟雾扩散现象,通过理论计算得到了不同时刻空间内各点处的浓度分布,并结合实验测试结果,印证了理论分析的高精度,在20s后空间内浓度分布基本稳定;

  • 在扩散过程中烟雾的扩散速度方面,通过COMSOL有限元仿真软件对常压下密闭环境中的烟雾扩散进行数值模拟,得到扩散处于基本稳定状态下空间截面内速度场的分布,为分析浓度分布变化趋势提供参考;

  • 在扩散过程中的烟雾扩散效果与除烟效果方面,将常压下和负压吸附下的COMSOL有限元仿真结果与实际实验现象进行对照,可以发现理论分析、数值求解以及实际实验三者的扩散现象结果基本一致,且在引入负压对流后,稳定后的烟雾浓度整体较常压时明显降低,且在烟雾发生停止后能够迅速清除残余的烟雾,具有良好的除烟效果。

除此之外,在这三点研究结论的基础上,我们还通过理论、仿真与实验的有机结合,得到了植物甘油气雾扩散的一般规律与数学物理模型,可以通过该理论模型对于实际的烟雾扩散情况进行定量化的数值模拟分析,从而对于烟雾扩散过程进行更精准的把控与必要的干预管理,并通过多种手段对该理论模型的有效性进行了充分验证,这也为我们后续应用产品的开发打下了良好的理论与实验基础。

5.2 研究成果应用:室内烟雾浓度控制系统

基于数值分析结果与研究结论,我们开发了一套简易的闭环控制系统作为原型样机,通过实时检测监测点处的烟雾浓度,实现对于风机吸附功率的灵活动态调控,以优化用户体验。

在该系统的开发方面,采用STM32F1开发板作为系统主控,通过ADC实时读取MQ-2传感器返回的烟雾浓度数值,并通过PID闭环控制实时调控给涵道风机供电的PWM占空比,从而实现对于风机吸附功率的实施调控。

图5.1 室内烟雾浓度闭环控制系统装置实物图(主要是上面部分)

通过运行该闭环控制系统并测试其烟雾浓度控制效果,可以发现该系统对于烟雾有着较高的灵敏度,并在检测到高浓度烟雾后能够迅速调动涵道电机作出反应,快速吸附容器内的植物甘油气雾并始终维持空间内的烟雾浓度低于150ppm,说明样机能够初步满足用户的基本除烟需求。

6 课程收获感悟

通过本次数学物理方法课程的学习,以及烟雾扩散课程项目的开展,我们通过在实际项目中运用课程中学习到的理论知识,在扩散过程中烟雾浓度分布的定量化求解过程中,对于数学物理方程的导出、边界与初始条件的确定以及基于变量分离与本征值方法求解级数解的过程有了更加深刻的认识与理解,同时通过课程中介绍的数值求解有限元方法对于更加泛化的情况进行了数值模拟与讨论。在解析与数值求解的理论分析基础上,我们还利用成熟的COMSOL有限元仿真软件对于烟雾扩散的粒子运动轨迹以及速度场分布进行数值仿真以作为对我们理论模型的补充,并通过定量化的实验测量进一步验证了理论模型的有效性,为我们后续进行实际的产品开发与应用提供了良好的理论与实验基础。

在本次课程项目报告的最后,再次感谢老师的耐心指导以及提出的宝贵意见,也希望我们能够将数学物理方法教给我们的定量化分析思维带入到未来的项目研究与学习工作中,对我们未来的项目开展与人生成长起到帮助。

参考文献

[1] Gao, Naiping and Jianlei Niu. “Modeling particle dispersion and deposition in indoor environments.” Atmospheric Environment (Oxford, England : 1994) 41 (2007): 3862 - 3876.

[2] Hoegg U. R. (1972). Cigarette smoke in closed spaces. Environmental health perspectives, 2, 117–128.

[3] Kuga K, Ito K, Yoo S-J, et al. First- and second-hand smoke dispersion analysis from e-cigarettes using a computer-simulated person with a respiratory tract model. Indoor and Built Environment. 2018;27(7):898-916.

[4] Holmberg, S., & Li, Y. (1998). Modelling of the Indoor Environment – Particle Dispersion and Deposition. Indoor Air, 8, 113-122.

[5] Al-sarraf, A.A., Yassin, M.F. & Bouhamra, W. Experimental and computational study of particulate matter of secondhand smoke in indoor environment. Int. J. Environ. Sci. Technol. 12, 73–86 (2015).

[6] 陈占秀,陈冠益,王艳,等.丙三醇与1,6-己二醇混合物降温凝固过程的分子动力学模拟[J].化工学报,2013,64(07):2316-2321.

[7] 沈玉峰,孟强龙.基于物理模型的烟雾扩散模拟[J].电子技术,2011,38(03):61-63.

[8] 李强,普小云.用毛细管成像法测量液相扩散系数——等折射率薄层测量方法[J].物理学报,2013,62(09):185-191.

[9] 杜青青.室内烟雾扩散模拟技术研究与实现[D].北京工业大学,2018.

[10] 梁昆淼编.数学物理方法[M].北京:高等教育出版社,1978:593页.

附录

1 MATLAB解析求解可视化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
% 参数定义
x0 = 40; % 长方体的长度
y0 = 50; % 长方体的宽度
z0 = 60; % 长方体的高度
F0 = 0.5; % 源项强度 g/cm3
D = 0.0962; % 扩散系数
x_s = x0 / 2; % 点源的 x 坐标
y_s = y0 / 2; % 点源的 y 坐标
z_s = 0; % 点源的 z 坐标
t_max = 30 * 40; % 最大时间
num_time_steps = 31; % 时间步数
num_terms = 10; % 级数项数
% 创建空间网格
x = linspace(0, x0, 50);
y = linspace(0, y0, 50);
z = linspace(0, z0, 50);
[X, Y, Z] = meshgrid(x, y, z);
time_steps = linspace(0, t_max, num_time_steps); % 时间步长
rho = zeros(length(x), length(y), length(z), num_time_steps); % 计算解的函数
% 测量点坐标 (x0/2, 0, z0)
x_measure = x0 / 2;
y_measure = 0;
z_measure = z0;
rho_measure = zeros(1, num_time_steps); % 初始化存储测量点浓度的数组
for t_idx = 1:num_time_steps
t = time_steps(t_idx); % 当前时间
% 级数求解
sum_term = 0;
for l = 1:num_terms
for m = 1:num_terms
for n = 1:num_terms
% 计算级数各项
lambda = (l^2 * pi^2 / x0^2) + (m^2 * pi^2 / y0^2) + (n^2 * pi^2 / z0^2);
term = cos(l * pi * X / x0) .* cos(m * pi * Y / y0) .* cos(n * pi * Z / z0) .* ...
cos(l * pi * x_s / x0) .* cos(m * pi * y_s / y0) .* cos(n * pi * z_s / z0) .* ...
(1 - exp(-D * lambda * t)) ./ lambda;
sum_term = sum_term + term;
end
end
end
rho(:, :, :, t_idx) = (F0 / (x0 * y0 * z0 * D)) * sum_term; % 乘上源强度项和常数因子
% 计算测量点处的浓度
rho_measure(t_idx) = interp3(X, Y, Z, rho(:, :, :, t_idx), x_measure, y_measure, z_measure);
rho_measure(t_idx) = rho_measure(t_idx) * 10^6 / 1.26; % 单位换算:g/cm^3 -> ppm
disp(t_idx)
end
time_steps = time_steps / 40;
% 创建视频对象
video_file = 'fog_diffusion.avi';
v = VideoWriter(video_file);
v.FrameRate = 5; % 设置帧率
open(v); % 打开视频文件
% 绘制动态可视化
figure;
for t_select = 1:num_time_steps % 遍历时间步
value = rho(:, :, :, t_select);
% 筛选出非零值及其对应的坐标
non_zero_indices = value >= 0.0003; % 设定阈值:可见浓度0.3mg/cm3
X_nonzero = X(non_zero_indices);
Y_nonzero = Y(non_zero_indices);
Z_nonzero = Z(non_zero_indices);
Value_nonzero = value(non_zero_indices);
% 绘制非零值的散点图
scatter3(X_nonzero, Y_nonzero, Z_nonzero, 20, Value_nonzero, 'filled'); % 绘制散点图
colormap jet; % 设置颜色图
colorbar; % 显示颜色条
xlabel('x');
ylabel('y');
zlabel('z');
title(['3D Scatter Plot of Values at t = ' num2str(time_steps(t_select))]);
grid on;
view(3); % 设置三维视角
% 固定坐标轴长度
axis([0, x0, 0, y0, 0, z0]); % 设置 x, y, z 的范围
% 将当前帧保存到视频
frame = getframe(gcf);
writeVideo(v, frame);
end
close(v); % 关闭视频文件
% 绘制测量点浓度随时间的变化
figure;
plot(time_steps, rho_measure, '-o', 'LineWidth', 2);
xlabel('Time (s)');
ylabel('\rho at (x0/2, 0, z0) (ppm)');
title('Concentration at Measurement Point over Time');
grid on;
disp(['视频已保存为: ', video_file]);
% 将测量点浓度数据保存到txt文件
output_file = 'rho_measurement_data.txt';
fileID = fopen(output_file, 'w');
fprintf(fileID, 'Time(s)\tConcentration(ppm)\n'); % 表头
for i = 1:length(time_steps)
fprintf(fileID, '%.2f\t%.6f\n', time_steps(i), rho_measure(i));
end
fclose(fileID);
disp(['测量点数据已保存为: ', output_file]);

2 Python有限元仿真代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import numpy as np
from scipy.sparse import lil_matrix
from scipy.sparse.linalg import spsolve
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize
import matplotlib.animation as animation

# 网格生成函数
def generate_mesh(x0, y0, z0, nx, ny, nz):
from meshpy.tet import MeshInfo, build
points = [(0, 0, 0), (x0, 0, 0), (x0, y0, 0), (0, y0, 0),(0, 0, z0), (x0, 0, z0), (x0, y0, z0), (0, y0, z0)]
facets = [[0, 1, 2, 3], [4, 5, 6, 7], [0, 1, 5, 4], [1, 2, 6, 5], [2, 3, 7, 6], [3, 0, 4, 7]]
mesh_info = MeshInfo()
mesh_info.set_points(points)
mesh_info.set_facets(facets)
mesh = build(mesh_info, max_volume=(x0/nx) * (y0/ny) * (z0/nz))
return mesh
# 全局矩阵组装
def assemble_global_matrices(mesh, D):
num_nodes = len(mesh.points)
M_global = lil_matrix((num_nodes, num_nodes))
K_global = lil_matrix((num_nodes, num_nodes))
for element in mesh.elements:
node_ids = element
vertices = np.array([mesh.points[i] for i in node_ids])
B, volume = compute_B_matrix(vertices)
K_element = D * volume * (B.T @ B)
M_element = volume * np.eye(4)
for i in range(4):
for j in range(4):
M_global[node_ids[i], node_ids[j]] += M_element[i, j]
K_global[node_ids[i], node_ids[j]] += K_element[i, j]
boundary_indices = find_boundary_nodes(mesh)
apply_boundary_conditions(M_global, K_global, boundary_indices)
return M_global, K_global
def compute_B_matrix(vertices):
A = np.ones((4, 4))
A[:, 1:] = vertices
volume = abs(np.linalg.det(A)) / 6.0
B = np.zeros((3, 4))
for i in range(4):
sub_matrix = np.delete(A, i, axis=0)
coeffs = np.linalg.det(sub_matrix[:, 1:])
B[:, i] = coeffs
B /= (6 * volume)
return B, volume
def find_boundary_nodes(mesh):
boundary_indices = set()
for facet in mesh.facets:
boundary_indices.update(facet)
return list(boundary_indices)
def apply_boundary_conditions(M_global, K_global, boundary_indices):
for idx in boundary_indices:
M_global[idx, :] = 0
M_global[:, idx] = 0
K_global[idx, :] = 0
M_global[idx, idx] = 1
K_global[idx, idx] = 1
# 时间步进求解
def solve_diffusion(mesh, M_global, K_global, F0, D, t_max, num_time_steps):
num_nodes = len(mesh.points)
rho = np.zeros((num_nodes, num_time_steps))
dt = t_max / (num_time_steps - 1)
center = np.array([x0/2, y0/2, 0])
radius = 0.6
for t_idx in range(1, num_time_steps):
A = M_global + dt * K_global
F = F0 * np.ones(num_nodes)
for idx, point in enumerate(mesh.points):
if np.linalg.norm(point - center) < radius:
F[idx] += F0
b = M_global @ rho[:, t_idx-1] + dt * F
rho[:, t_idx] = spsolve(A, b)
return rho
# 可视化与动画
def create_animation(mesh, rho, time_steps, output_file):
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
points = np.array(mesh.points)
def update_plot(frame_idx):
ax.clear()
rho_t = rho[:, frame_idx]
norm = Normalize(vmin=0.005, vmax=0.01)
ax.scatter(points[:, 0], points[:, 1], points[:, 2], c=rho_t, cmap='jet', norm=norm, s=10)
ax.set_title(f"Concentration at t = {time_steps[frame_idx]:.2f}")
ax.set_xlim(0, x0)
ax.set_ylim(0, y0)
ax.set_zlim(0, z0)
ani = animation.FuncAnimation(fig, update_plot, frames=len(time_steps), interval=500)
ani.save(output_file, writer='ffmpeg', fps=5)
plt.show()

# 主程序
if __name__ == "__main__":
# 参数
x0, y0, z0 = 20, 25, 60
nx, ny, nz = 10, 10, 10
D = 0.0962
F0 = 0.5
t_max = 30
num_time_steps = 31
mesh = generate_mesh(x0, y0, z0, nx, ny, nz) # 网格生成
M_global, K_global = assemble_global_matrices(mesh, D) # 矩阵组装
# 求解
time_steps = np.linspace(0, t_max, num_time_steps)
rho = solve_diffusion(mesh, M_global, K_global, F0, D, t_max, num_time_steps)
create_animation(mesh, rho, time_steps, "diffusion_animation.mp4") # 生成动画

基于自编码器和卷积网络的肺炎图像识别

1 项目背景与研究意义

1.1 项目背景

肺炎作为一种常见的呼吸系统疾病,对人类健康构成了长期威胁,特别是随着COVID-19新冠肺炎疫情的全球爆发,其公共健康影响更为显著。COVID-19肺炎具有传播速度快、感染范围广、诊断难度大的特点,对全球医疗系统和社会经济产生了深远影响。特别是在疫情高峰期,医疗资源的短缺和诊断效率的瓶颈,进一步突显了快速、准确诊断工具的重要性。

传统肺炎诊断方法主要依赖医生对胸部X光片或CT图像的人工分析,既耗时又容易受到经验和疲劳的影响,尤其在COVID-19疫情期间,大量影像数据的涌现使得人工诊断难以满足需求。与此同时,COVID-19的影像表现与其他类型肺炎的重叠性增加了诊断的复杂性,这进一步加剧了对智能化诊断系统的需求。

随着人工智能技术的快速发展,深度学习为医疗影像分析带来了全新的解决方案。卷积神经网络(Convolutional Neural Network, CNN)凭借其强大的图像特征提取能力,在自动化诊断中展现了巨大潜力。同时,由于不同医疗机构的CT扫描设备性能差异显著,特别是在医疗资源较为匮乏的地区,CT影像常常受到设备老化、分辨率低或操作不规范等因素的影响,图像质量参差不齐,这不仅增加了诊断的复杂性,还对自动化系统的鲁棒性提出了更高要求;而自编码器(Autoencoder)作为一种有效的降噪工具,为医疗影像数据预处理提供了重要支持。通过自编码器的引入,可以有效消除图像中的噪声干扰,减少不同设备间的成像差异,为后续的分类和识别模型提供高质量的输入数据。因此,本项目提出结合自编码器和卷积神经网络的深度学习框架,开发一套针对肺炎(包括COVID-19)CT影像识别的智能诊断系统。

图1.1:新冠肺炎疫情概况(主要症状、防治措施与传播状况)

图1.2:新冠肺炎患者的肺部CT影像

1.2 研究意义

本项目以新型冠状病毒肺炎为切入点,面向未来医学智能化需求,开发的诊断系统不仅能够应对当下疫情挑战,还具有推广至其他医学影像诊断场景的潜力,从而为全球公共健康事业的发展提供有力支持。

面向新冠肺炎疫情期间大规模肺部影像数据的快速诊断需求,本系统依托深度学习技术,有效提升了诊断效率,为疫情防控和患者管理提供重要的技术支持。通过先进的模型算法,系统能够精准识别肺部CT影像中的病变特征,减少人为误差,确保诊断结果的准确性和一致性,大幅降低不同医疗机构和医生之间的诊断差异,避免误诊和漏诊风险,从而更好地保障患者安全。

针对基层医院或偏远地区医疗资源匮乏的现状,本系统可作为一种可靠的辅助诊断工具,为医生提供科学的决策支持,帮助缓解诊断能力不足带来的压力。其高效的处理能力不仅提高了基层医疗服务水平,也为疫情防控的全面推进提供了技术保障,为应对紧急医疗需求的地区解决实际困难。

此外,本系统在高效处理和分析海量肺部CT影像数据的基础上,还为研究新冠肺炎的病理特征及流行规律提供了宝贵的数据支持。这些分析结果可进一步应用于疫情传播趋势预测和公共卫生政策制定,为疫情防控策略的科学性和有效性奠定了坚实基础。

同时,本系统也为患者病情的动态管理提供了重要帮助。通过智能分析新冠肺炎影像特征的变化趋势,系统能够为临床医生提供精确的病情评估建议,有助于及时调整治疗方案。这种基于影像数据的技术支持,不仅提高了患者管理的科学性和有效性,还为医疗资源的合理分配提供了重要依据,进一步推动了疫情防控工作的高效开展。

2 数据集获取与预处理

2.1 数据集介绍

本项目使用的肺部X-光片数据集从Kaggle网站(链接:https://www.kaggle.com/datasets/alsaniipe/chest-x-ray-image)获取,共分为三类标签:新冠肺炎COVID19、正常NORMAL和普通肺炎PNEUMONIA,该数据集已经事先划分好了训练集与测试集。此外,为测试模型对于不同质量CT影像的识别精度,还对测试集中的部分图像使用高斯噪声进行扰动,以模拟实际CT扫描的成像质量差异。

图2.1:肺部CT影像数据集概况
图2.2:肺部CT影像数据集概况

图2.3:数据集中部分肺部CT影像展示(上行为训练集(干净),下行为含噪声测试集)

数据分布如下表所示:

表2.1:数据集数据分布情况

COVID19 NORMAL PNEUMONIA
Train 460 1266 3418
Test 116 317 855
Noisy_Test 26 20 20

2.2 编程环境搭建

本项目中所有的代码编写与运行均是在配备NVIDIA GeForce RTX 3060显卡、16GB运行内存、12th Gen Inter(R) Core(TM) i7-12700@2.10GHz处理器与Microsoft Windows 11操作系统的工作站上使用Python编程语言完成的。

软件环境方面,采用Conda进行环境管理,在控制台中通过命令”conda create -n covid python=3.12”创建虚拟环境,并在激活环境后使用pip install命令依次安装所需的各种依赖库;全部安装并测试完成后,通过命令”pip freeze >
requirements.txt”将虚拟环境中安装的所有依赖库及对应版本写入文件requirements.txt,后续移植时可在新的运行环境中运行命令”pip install -r requirements.txt”完成环境的一键配置。

值得注意的是,在PyTorch(包括torch、torchvision与torchaudio库)安装时需要根据自己电脑使用的CUDA版本(使用CPU则直接在命令行中使用pip install安装即可)在PyTorch官网中找到对应的安装命令进行安装。我使用的CUDA版本为12.6,安装命令的选取如下图所示:

图2.4:PyTorch官网获取对应CUDA版本的安装命令

下面对于项目中使用到的主要依赖库进行简要介绍:

图2.5:项目中使用的主要依赖库

其中,plt用于绘图,nn中包含了用于构建神经网络的隐藏层(全连接层、卷积层等),F中包含了各种激活函数(ReLu、Sigmoid等),DataLoader用于在训练时加载数据,datasets和transforms用于读取和处理数据集,tqdm用于进度条可视化,torchmetrics用于模型精度的测试,torchviz和torchsummary用于以图形与文字的方式描述模型架构概况。

2.3 图像数据读取

torch对于一些常用的数据集做了封装,可以直接调用,例如datasets.MNIST()。但此处我们使用的是本地的图片数据,可以使用ImageFolder将一个文件夹下的图片读取成数据集并完成数据增强工作。在读取完数据集后,还需要定义DataLoader用于加载数据为可分批次(batch)读取的迭代器以供后续使用。为使得代码更加简洁,将上述的数据读取与加载过程为封装在getDataLoader函数中,并在主函数中通过指定不同的目录加载训练集、测试集或是含噪声测试集。

图2.6:数据加载函数getDataLoader代码

可以看到,其中构建了数据增强器transform,在读取数据时进行相应处理:

  • Grayscale: 指以灰度图的形式读取。

  • Resize: 由于图像尺寸各不相同,在训练前需将它们重塑成相同尺寸256*256。

  • ToTensor: 将图片格式转换成张量形式,torch的计算以张量的形式进行。

除此之外,在构建数据加载器时需要指定一个批次(batch)中的图片数据数量batch_size,在模型训练时训练批次大小TRAIN_BATCH_SIZE也是会影响最终模型性能的重要超参数之一。在训练过程中,设定TRAIN_BATCH_SIZE为32,而在测试过程中,为提高测试效率,将TEST_BATCH_SIZE设置为66并对函数进行对应修改。

2.4 叠加噪声函数

不论是构建噪声测试集,还是在利用无噪声的训练集进行训练时,都需要手动添加噪声,故编写add_noise函数,默认的噪声强度为0.5,并在添加噪声后进行归一化以确保图像值位于[0,1]范围内。

图2.7:加噪函数add_noise代码

图2.8:加噪前后效果对比

3 模型构建与网络训练

3.1 整体模型框架

整体模型框架由两个核心部分组成,分别是用于去噪的数据预处理模块和负责分类的卷积神经网络(CNN)。去噪模块采用自编码器(Autoencoder)的架构,专注于从输入数据中去除噪声,以提升后续分类的准确性;分类模块基于卷积神经网络,其强大的特征提取和模式识别能力使其成为分类任务的理想选择。

两个模块相辅相成,通过有效的数据处理和特征提取,确保模型能够在噪声干扰较大的环境中实现高精度分类。噪声数据首先经过自编码器处理,生成质量优化的特征表示,然后被CNN接收并完成分类任务。这一整体框架设计非常适合肺炎图像识别任务,通过结合去噪和分类两大模块的优势,模型不仅能够有效提高数据质量,还能充分挖掘数据中的有用特征,从而能够在复杂的医学影像处理中表现出卓越的鲁棒性和准确性,满足肺炎诊断的实际需求。

图3.1:模型框架图示

3.2 自编码器

自编码器模型用于处理输入数据中的噪声问题,提升后续分类的准确性。其核心思想是通过编码器将输入数据压缩至低维潜在表示(latent representation),再由解码器将其还原至去噪后的重构数据,从而实现降噪效果。

3.2.1 网络结构设计

图3.2:自编码器模型结构

自编码器网络结构由编码器encoder与解码器decoder组成:

  • 编码器由两层卷积(Conv2d)和两次池化(MaxPool2d)操作组成,用于提取特征;

  • 解码器通过两次反卷积(ConvTranspose2d)和两次上采样(UpsamplingNearest2d)逐步恢复图像尺寸到原始大小;

  • 最后使用Sigmoid激活函数将输出值限制在[0,1]区间。

模型定义代码如下:

图3.3:自编码器模型定义代码

模型继承自nn.Module类,在__init__()函数中定义模型的结构,在forward()函数中定义模型的前向传播过程。

通过调用torchviz和torchsummary库,可以输出该模型结构的基本信息:

图3.4:调用torchsummary库输出自编码器网络结构的文字信息

图3.5:调用torchviz库输出自编码器网络结构的架构图示

3.2.2 模型训练

基本的训练流程集成在函数train_autoencoder_process中,如下图所示:

图3.6:自编码器模型训练函数train_autoencoder_process代码

其中指定优化器optimizer为Adam,损失函数为均方误差MSE,并使用超参数:训练轮数Epochs=50、学习率lr=0.001。每轮(Epoch)训练中均需要以多个batch的形式遍历训练集中的所有数据,并在每个batch后对模型进行更新,具体而言每次更新均需执行如下操作:

  • 从加载器中获取输入数据

  • 使用add_noise函数对干净图像加噪

  • 将加噪后图像输入自编码器模型并计算模型输出

  • 根据模型输出和标签计算损失Loss

  • 清空梯度

  • 反向传播

  • 更新模型

值得注意的是,由于用于训练的图像数据没有噪声,因此训练时首先需要对输入的图像进行加噪处理,再输入自编码器模型进行训练。

训练过程中还利用tqdm进度条函数对训练进程进行可视化,并在每轮训练完成后打印出当轮训练过程中模型的平均损失:

图3.7:自编码器模型训练过程进度条(前5个Epoch)

在训练过程中,将每轮训练的平均损失存储在列表中,并在训练结束后将平均损失的变化过程以图像形式呈现:

图3.8:自编码器模型训练损失变化

可以看到,经过多轮训练,模型的损失函数值在不断减小且逐渐趋近于0,这意味着该自编码器的模型训练过程是收敛的,模型具有较稳定的工作性能。

3.3 卷积神经网络

卷积神经网络负责从图像中提取多层次的空间特征,通过逐步减少图像尺寸和增加特征通道来捕捉关键信息,从而实现去噪后肺部CT图像的分类功能。CNN以其强大的特征提取能力,能够有效处理图像的局部依赖性和空间不变性,高效处理结构化数据(如图像、时序数据)。模型简单且高效,具有较强的泛化能力,适合处理小规模数据集的图像分类问题。

3.3.1 网络结构设计

图3.9:卷积神经网络模型结构

卷积神经网络结构(如上图,通过NN-SVG工具绘制)由两层卷积层(Conv2d)和池化层(MaxPool2d)组成,激活函数均选用ReLU,逐步提取特征并将输入图像的尺寸从原始大小减小到64×64。卷积后的特征图展平后通过三个全连接层(Linear),分别将特征维度从32×64×64降至128,再降至32,最后输出3个类别(Covid19、Normal、Pneumonia)的预测结果。

模型定义代码如下:

图3.10:卷积神经网络模型定义代码

模型继承自nn.Module类,在__init__()函数中定义模型的结构,在forward()函数中定义模型的前向传播过程。

通过调用torchviz和torchsummary库,可以输出该模型结构的基本信息:

图3.11:调用torchsummary库输出卷积神经网络结构的文字信息

图3.12:调用torchviz库输出卷积神经网络结构的架构图示

3.3.2 模型训练

基本的训练流程集成在函数train_cnn_process中,如下图所示:

图3.13:卷积神经网络模型训练函数train_cnn_process代码

其中指定优化器optimizer为Adam,损失函数为交叉熵损失CrossEntropy,并使用超参数:训练轮数Epochs=50、学习率lr=0.001。每轮(Epoch)训练中均需要以多个batch的形式遍历训练集中的所有数据,并在每个batch后对模型进行更新,具体而言每次更新均需执行如下操作:

  • 从加载器中获取输入数据

  • 使用add_noise函数对干净图像加噪

  • 将加噪后图像输入训练好的自编码器模型trained_autoencoder_model

  • 将经过自编码器去噪后的图像输入CNN模型并计算模型输出

  • 根据模型输出和标签计算损失Loss

  • 清空梯度

  • 反向传播

  • 更新模型

值得注意的是,由于用于训练的图像数据没有噪声,为与实际的输入情况一致,首先需要对输入的图像进行加噪处理,再利用训练好的自编码器模型进行降噪(为了不在更新CNN的同时更新自编码器,这一步不需要产生梯度),才能输入CNN分类模型进行训练。

训练过程中还利用tqdm进度条函数对训练进程进行可视化,并在每轮训练完成后打印出当轮训练过程中模型的平均损失与在训练集上的测试精度:

图3.14:卷积神经网络模型训练过程进度条(最后5个Epoch)

在训练过程中,将每轮训练的平均损失与模型在训练集上的测试精度存储在列表中,并在训练结束后将两者的变化过程以图像形式呈现:

图3.15:卷积神经网络模型训练损失变化

图3.16:卷积神经网络模型训练过程中在训练集上的精度变化

可以看到,经过多轮训练,模型的损失函数值在不断减小且逐渐趋近于0,这意味着该自编码器的模型训练过程是收敛的,模型具有较稳定的工作性能;同时随着训练轮数增加,模型在训练集上的精度也逐渐增高(波动上升),在模型训练完成时,卷积神经网络在训练集上的分类精度已经可以达到99.59%(一度达到99.90%),接近百分之百,说明模型的分类能力较好。

4 模型测试及应用

4.1 自编码器降噪效果

在自编码器模型的训练过程中,每隔10轮对模型参数进行了一次存档;在测试过程中,分别使用训练轮数为10、20、30、40、50的自编码器模型对于加噪后的模型进行降噪处理,效果如下图所示:

图4.1:训练轮数Epoch=10的自编码器模型降噪效果

图4.2:训练轮数Epoch=20的自编码器模型降噪效果

图4.3:训练轮数Epoch=30的自编码器模型降噪效果

图4.4:训练轮数Epoch=40的自编码器模型降噪效果

图4.5:训练轮数Epoch=50的自编码器模型降噪效果

通过对比不同训练轮数的自编码器模型降噪效果可以发现,随着训练轮数的增加,自编码器模型的降噪效果在逐渐提升,但在Epoch到达30之后,训练带来的降噪效果提升就不如先前显著了。尽管由于较大的噪声强度(0.5)导致降噪后的图像仍然比较模糊,但通过肉眼还是能粗略观察处肺部骨骼的轮廓等特征,后续实验也证明了卷积神经网络确实可以从这样清晰度的图像中提取相应的特征来进行分类,该自编码器模型的设计有效。

4.2 卷积神经网络分类精度

在卷积神经网络的分类精度上,训练过程中已经实时对于每一轮训练后的模型在训练集上进行了精度测试(3.2.2节中已有提及),而在测试集上,可以编写与训练过程类似的代码利用torchmetrics库对模型分类精度进行测试,只是不会更新模型,代码如下:

图4.6:卷积神经网络分类精度在测试集上的测试函数代码

可以看到,由于我们的测试集分为含噪声和不含噪声两类,因此编写了不同的函数对模型分类精度进行测试。两个函数的主要差别就在于,由于含噪声测试集是已经加噪的图片(噪声与手动通过add_noise函数添加的不同),因此在含噪声测试集的测试代码中不必再次手动添加噪声,而是直接将图像输入自编码器降噪后再输入CNN分类模型中进行分类;而对于不含噪声的测试集而言,为模拟与训练集同样的处理流程,会先进行手动加噪再通过自编码器降噪之后才输入CNN分类模型中进行分类。

运行测试代码后,得到模型在含噪测试集上的分类精度为96.97%,在不含噪声的测试集上的分类精度为94.57%,在两个测试集上的分类精度水平均较高,说明该模型具有良好的分类效果。

4.3 模型应用:基于CT影像的肺炎诊断Web服务

通过对比多组超参数的模型降噪与分类效果,最终选定如下的超参数:

  • 训练轮数Epochs=50;

  • 学习率LR=0.001;

  • 训练批次大小Train_Batch_Size=32。

选定参数后,将整体代码抽离为model.py(包含模型定义类代码),run.py(服务端代码)和train.py(训练函数),并将模型部署到实际应用中,使用Flask作为服务端,以Web形式用户提供操作接口以上传图片进行诊断。由于主要功能是提供接口,故网页只做了很简易的一个index.html,给用户提供上传图片的按钮,并在用户上传有噪声的CT影像后返回诊断结果及去噪后的图像。除此之外,还将挂载在本地端口上的Web通过内网穿透映射到公网,以供实时访问。

网页初始界面如下图所示:

图4.7:网页初始界面

接下来分别测试当输入COVID19、NORMAL和PNEUMONIA三个组别的图片,模型能否正确判断:

图4.8:输入类型为COVID19,识别为COVID19(正确)

图4.9:输入类型为COVID19,识别为PNEUMONIA(错误)

图4.10:输入类型为NORMAL,识别为NORMAL(正确)

图4.11:输入类型为PNEUMONIA,识别为PNEUMONIA(正确)

可以发现,模型在大多数情况下可以正确识别图像来源,但也会出现错误识别的情况,这和Test 集上的Accuracy相符合;此外,在测试时还注意到,模型识别结果偶尔会出现不稳定的现象,即输入同一张图像有时识别为某一类别,有时又会识别为另一类别,这是由模型内部部分随机参数导致的,这也反映了模型在一些模棱两可的情况下(两类别概率接近)做出判断时的不稳定性。在实际应用中,为尽可能减少误诊对于患者带来的各方面影响,还需要采取更多优化措施提升模型性能,并对模型在模棱两可的情况下做出的判断进行合理的限制。

5 总结与展望

本项目全部代码(不包含数据集)已上传至Github仓库,仓库URL地址:https://github.com/Asgard-Tim/Pneumonia-Image-Recognition

5.1 项目总结

本项目基于深度学习技术,结合自编码器和卷积神经网络,开发了一套智能诊断系统,用于快速、高效地识别肺部的CT影像并判断该患者是否患有肺炎(包括COVID-19)。自编码器模块有效去除了噪声,提升了图像质量,而卷积神经网络以其强大的特征提取能力,实现了高精度的分类。本项目在数据预处理、模型设计、网络训练及测试等环节中均采用了创新性的技术方案,最终实现了在含噪声测试集上96.97%和在无噪声测试集上94.57%的分类精度,表现出了较高的鲁棒性和实用价值。同时,系统已通过Flask框架部署为Web服务,能够实时接收CT影像并给出诊断结果,为疫情期间大规模影像数据的快速诊断及基层医疗资源匮乏地区的医疗支持提供了重要的技术保障。

5.2 课程收获与反思

本次选修《智能图像处理》这门课程确实让我学到了很多东西,其实自己之前也自己看过一些机器学习方面的内容,有一定的知识基础与环境搭建经验,但由于各方面原因总是没有系统性的去学习计算机视觉的相关知识,也缺乏足够的实战代码与项目经验。通过这门课程的学习,很大程度上锻炼了我Python的代码能力,也在Coding的过程中不断熟悉OpenCV、Pytorch等库的使用,更在实践的过程中不断加深对于各种算法模型(AlexNet、ResNet、YOLO等)的理解。

本次项目让我完整地经历了从数据集获取、论文调研及算法代码实现,再到代码调试与模型训练测试,最终将模型应用到实际系统中的全过程,在项目实现的过程中收获了很多课程教学与实验中涉及不到的东西,包括数据集的收集、模型的选择以及作为一个完整项目的代码实现等等多个方面,这也是我第一次使用GPU资源去进行。虽然由于时间等条件的限制,在模型选择上并没有进行深入的调研与充分的对比试验,只是基于自己已知的一些知识对于架构较为简单的自编码器模型与卷积神经网络进行了复现与设计,最终模型的分类精度还有一定的提升空间,但是这也为我后续的自主学习打下了一个良好的基础,希望未来我能在计算机视觉方面有更加深入的学习与探索,也感谢老师的耐心指导与悉心教学。

参考文献

[1] Nosa-Omoruyi M, Oghenekaro L U. AutoEncoder Convolutional Neural Network for Pneumonia Detection[J]. arXiv preprint arXiv:2409.02142, 2024.

[2] Ratiphaphongthon W, Panup W, Wangkeeree R. An improved technique for pneumonia infected patients image recognition based on combination algorithm of smooth generalized pinball SVM and variational autoencoders[J]. IEEE Access, 2022, 10: 107431-107445.

[3] Gayathri J L, Abraham B, Sujarani M S, et al. A computer-aided diagnosis system for the classification of COVID-19 and non-COVID-19 pneumonia on chest X-ray images by integrating CNN with sparse autoencoder and feed forward neural network[J]. Computers in biology and medicine, 2022, 141: 105134.

[4] García-Ordás M T, Benítez-Andrades J A, García-Rodríguez I, et al. Detecting respiratory pathologies using convolutional neural networks and variational autoencoders for unbalancing data[J]. Sensors, 2020,20(4): 1214.

[5] Xia Y. Enhanced Pneumonia Detection in Chest X-Rays Based on Integrated Denoising Autoencoders and Convolutional Neural Networks[J].

[6] El-Shafai W, El-Nabi S A, El-Rabaie E S M, et al. Efficient Deep-Learning-Based Autoencoder Denoising Approach for Medical Image Diagnosis[J]. Computers, Materials & Continua, 2022, 70(3).

[7] Rana N, Marwaha H. Auto encoder-guided Feature Extraction for Pneumonia Identification from Chest X-ray Images[C]//E3S Web of Conferences. EDP Sciences, 2024, 556: 01011.

[8] Ankayarkanni B, Sangeetha P. An Autoencoder-BiLSTM framework for classifying multiple types of lung diseases from CXR images[J]. Multimedia Tools and Applications, 2024: 1-30.

[9] 孙敬,丁嘉伟,冯光辉.一种基于自编码器降维的神经卷积网络入侵检测模型[J/OL].电信科学,1-7[2025-01-05].

[10] 张淙越,杨晓玲.基于卷积神经网络的新冠肺炎CT图像识别系统[J].电脑与信息技术,2022,30(03):12-14+40.

心电信号采集与处理

1 实验需求分析

1.1 项目背景介绍

心电信号(Electrocardiogram, ECG)是反映心脏活动电生理变化的重要生物电信号,其特征包括心率、节律、波形等参数,能够直观反映心脏健康状况,在临床医学、健康监测和疾病预防中具有不可替代的作用。通过心电信号的测量与分析,可以检测心律失常、心肌缺血、心脏传导阻滞等异常,为心脏疾病的诊断和治疗提供关键支持;借助便携式和可穿戴设备,实时心电监测已成为健康管理的重要手段,为心血管疾病高危人群提供预警,有助于降低发病率和致死率。此外,心电信号还是生物医学研究的重要工具,为心血管药物开发、人工心脏研究等领域提供了基础数据。在当前人口老龄化加剧和心血管疾病高发的背景下,心电信号测量与分析技术显得尤为重要。本项目旨在开发高精度的心电信号采集系统,结合课程中介绍的数字信号处理等专业知识,为心脏健康提供更加便捷和智能的监测方案,推动精准医疗与个性化健康管理的发展。

1.2 心电信号特征与设计需求

心脏内部产生的一系列非常协调的电刺激脉冲,使得心脏肌肉细胞有节奏的舒张和收缩,这些信号传递到人体表面的不同部位形成不同的电位差。通过仪器设备可以从体表检测到这些微弱的电位差信号,称之为心电信号。换言之,心电信号即为人体心脏细胞细胞膜产生的电势差。在医学上,医生往往需要通过心率与幅值等参数来初步判断患者的健康状况,因此实现高精度的心率与幅值测量是本项目中设计的心电信号采集与处理系统的核心功能。

图1.1:心电信号简介

正常的心电信号频率范围为0.05Hz-100Hz,其能量集中在低频段,其中99%的能量集中在0Hz-35Hz。在其采集过程中容易受到各种干扰,主要分为三种:

  • 工频和工频的谐波频率干扰,工频频率在我国为50Hz;

  • 肌颤噪声和采样电路参考电压引入的电源纹波等高频噪声,频率通常在100Hz以上;

  • 呼吸基线漂移和采样引入的直流分量,频率一般分布在0-0.7Hz。

以上的各种干扰会对心电信号采集结果产生较大的影响,使得采集到的心电信号中出现许多杂波与噪声,这是我们所不希望看到的。因此,为提高心电信号的测量精度,需要设计相应的滤波器对传感器采集到的信号进行滤波,从而减小信号中的噪声震荡,提高心率与幅值测量的准确程度。特别的,由于参考电压受环境温度变化会产生一定的温漂,以及人的呼吸活动和电极滑动也导致基线漂移。这些干扰的频率很低,通常在几Hz以内,但和心电信号的有效频谱非常接近,因此需要过渡带较窄的IIR直流陷波器来消除干扰。

基于心电信号的以上特性,对于该心电信号采集与处理系统,提出如下的技术指标需求:

  • 0频处的缓变直流衰减不低于30dB;

  • 降噪滤波器以35Hz为3dB通带截止频率,过渡带不超过10Hz,阻带衰减不低于40dB;

  • 心率估算误差不超过10%。

2 实现方案论证

2.1 系统框架设计

本项目的核心目标是实现心电信号的采集与滤波以及心率测量,同时需要在屏幕上绘制时域波形与频谱图。具体而言,细分的功能如下:

  • 实现ADS1292获取心电信号原始数据,并通过串口传输至PC电脑;

  • 实现PC电脑中通过MATLAB对原始数据进行时域和频域分析;

  • 实现PC电脑中通过MATLAB对原始数据进行降噪和提取心率;

  • 实现STM32单片机中对原始数据进行降噪和提取心率;

  • TFT屏幕中绘制心电信号曲线和显示心率数值。

为实现以上功能,采用如下的系统设计流程:

  1. 调试ADS1292R_PowerOnInit函数中的ADS1292芯片读取,通过读取芯片device_id验证硬件功能正常且连接正确;

  2. 在中断驱动下,读取ADS1292的原始数据,并存储在单片机的存储器中;

  3. 把原始数据传输到PC;

  4. 在PC中分析原始数据的时域和频域;

  5. 在PC中设计滤波器对原始数据进行处理,并提取心率等;

  6. 把PC中的滤波器移植到单片机中;

  7. 在单片机中把心电波形和心率等数据显示到TFT屏幕。

图2.1:系统设计流程

根据如上设计流程,结合目前提供的材料,设计了如下图所示的心电信号采集与分析系统:

图2.2:系统框架

系统的工作流程如下:

  1. 首先,STM32控制器向心电传感器发送采集指令,传感器随后采集来自人体或模拟信号源的心电信号,并将数据反馈至控制器;

  2. 接着,控制器将采集到的数据传输至PC端,供进一步分析处理;

  3. 然后,根据PC端的分析结果,控制器会调整参数并优化心电信号处理;

  4. 最终,处理后的结果将在TFT屏幕上实时显示,供用户查看。

可以看到,该系统主要涉及到STM32主控芯片、ADS1292R传感器、TFT显示屏、心电信号模拟器以及PC端分析软件MATLAB等关键组件。接下来将对于本项目涉及的各硬件组件进行介绍。

2.2 STM32主控芯片

本项目选用的微控制器STM32F407ZG是系统的核心控制单元,负责协调各个模块的工作。其不仅负责信号的采集,还管理信号传输、滤波器应用、以及与TFT屏幕的显示操作。其强大的处理能力和灵活的控制方式使其成为整个系统的”大脑”。该控制器目前搭载在”正点原子”探索者STM32F407开发板V3上,负责完成系统的信号采集、处理与传输任务。

图2.3:STM32F407探索者开发板V3实物图正面

该单片机具备高性能的ARM Cortex-M4内核,主频高达168MHz,同时集成了丰富的外设接口,包括多个ADC通道、DMA(直接存储器访问)、定时器以及USART串口等,为心电信号的实时采集、处理与传输提供了强有力的硬件支持。它通过SPI协议与ADS1292传感器进行数据交换,采集来自人体或模拟信号源的心电信号,并进行初步处理。

2.3 心电信号模拟器

在实验的过程中,无法总是以人体作为心电信号源,因此在缺乏人体数据的情况下,本项目采用SKX-2000心电信号模拟仪作为测试时的模拟信号源。它能够生成不同类型的心电波形,广泛用于测试系统性能和验证心电信号采集、处理的稳定性。此设备对确保系统在实际使用前达到预期的性能标准至关重要。

图2.4:SKX-2000心电信号模拟仪

图2.5:SKX-2000心电信号模拟仪使用说明书图2.5:SKX-2000心电信号模拟仪使用说明书

在本项目中,主要使用到的是正常的心电波形与标准的心率信号,可以通过操作该模拟仪控制其产生心电信号的心率以及幅度。在连接方面,如图2.3所示,采用三导联接法,RA连接右手(红色),LA连接左手(黄色),LL连接左脚(绿色);通过3.5耳机动态导联线,将模拟仪产生的心电信号输入ADS1292S传感器中以供进一步采集。

2.3 信号采集传感器:ADS1292R

ADS1292R作为一款多通道同步采样的24位模数转换器(ADC),被广泛应用于生物电势测量领域,其独特的设计集成了可编程增益放大器、内部基准源和板载振荡器,确保了心电信号的精准采集。在本系统中,ADS1292R作为信号采集模块的核心组件,负责从心电电极捕获原始心电信号。这些信号随后通过SPI接口传输至STM32微控制器,供后续处理和分析。

图2.6:ADS1292R传感器实物图与电路原理图
图2.6:ADS1292R传感器实物图与电路原理图该模块共有12个输出引脚(传感器模块右侧),各引脚接口功能说明如下:

  • GND:接供电电源地;

  • CLK:提供给ADS1292R工作的外部时钟,由于本项目采用内部时钟,故该引脚无需连接;

  • GPIO1、GPIO2:本项目中未使用,无需连接

  • SPI_SCK:接入由STM32单片机提供的SPI时钟信号;

  • SPI_MISO:向STM32单片机发送SPI数据输入信号;

  • SPI_MOSI:接收STM32单片机发出的数据输出信号;

  • SPI_CS0:接入STM32单片机GPIO,为ADS1292R提供片选选中信号;

  • ADS_DRDY:向STM32单片机发送外部中断输入,告知单片机可以通过SPI接收采集信号;

  • ADS_START:接入STM32单片机GPIO,为ADS1292R提供开始采集信号;

  • ADS_PWDN:接入STM32单片机GPIO,为ADS1292R提供复位信号RESET;

  • +5V:接供电电源+5V(由STM32输出)。

除此之外,由于选用的STM32单片机只允许接收0~3.3V的输出电压,故单片机通过SPI通信接收的信号电压必须限制在该范围内,因此需要将ADS1292R传感器模块中间的VDD与3.3V引脚通过跳线帽相连以实现电压转换(与图2.5中左图的连接方式相反)。

2.4 SPI通信读取采集数据

在本系统中,传感器将数字化后的原始心电信号数据通过SPI接口传输到STM32微控制器。SPI通信具有高速度、全双工传输的优势,非常适合用于实时性要求较高的应用场景。SPI接口确保了心电信号数据的快速、稳定传输,保证了信号的实时处理和准确性。通过SPI,ADS1292R和STM32微控制器能够高效地交换数据,从而提升系统的整体性能和响应速度。

SPI通信采用主从结构,其中STM32微控制器作为主设备,ADS1292R作为从设备。具体来说,SPI通信包括四个主要信号线:时钟信号(SCK)、主输出从输入(MOSI)、主输入从输出(MISO)和片选信号(CS)。时钟信号由STM32微控制器提供,用于同步数据传输。数据通过MOSI线从ADS1292R传输到STM32微控制器,而传感器通过MISO线将必要的反馈信息传回STM32。

图2.7:SPI内部结构简图

为了提高系统的响应速度和处理效率,在单片机端选择了基于中断驱动的数据采集机制。当ADS1292R传感器完成一组心电数据的采集后,通过中断信号通知STM32F407进行数据读取和处理。这种机制有效减少了CPU的空闲时间,提高了系统的整体效率。

图2.8:中断驱动SPI通信采集数据的系统框架简图

在单片机程序工程中,通过调用驱动库文件中的ADS1292_Read_Data函数,可以实现单片机与ADS1292R的数据交互以进行实时数据监测,并通过函数ADS1292_Send_CMD将信号数据通过UART串口以115200的波特率传输至PC端并保存。在此过程中,原始信号数据被保存为.dat格式的文件,后续将通过MATLAB读取和显示这些信号数据,并针对这些数据进行滤波器等模块的设计,对信号进行进一步验证与调试。

图2.9:单片机实现读取原始信号数据的函数ADS1292_Read_Data

图2.10:单片机实现串口输出原始信号数据的函数ADS1292_Send_CMD

图2.11:PC端可视化软件中显示采集到的原始心电信号(解码后)

3 理论推导与MATLAB计算

PC端分析软件(本项目中使用MATLAB)用于对采集到的原始心电信号进行深入分析。为验证滤波器对心电信号的作用和效果,可以先在MATLAB上设计算法对心电信号进行一系列处理,包括数据读取、绘制原始数据时域波形、分析原始数据的频谱、设计数字直流陷波器、设计FIR数字低通滤波器、分析并比较滤波前后数据的频谱以及估算心率。通过这些分析,系统能够设计出合适的滤波器以去除噪声并提取出心率等重要参数,为后续移植到单片机上提供理论验证。

3.1 原始心电信号数据的读取与频谱分析

为了研究信号特征和评估处理效果,首先需要导入心电信号数据并进行基本分析。当心电信号传入单片机后,根据ADS1292R的数据手册,还需在单片机中对数据进行解码处理。在2.4节中,直接存入.dat文件的是未经过解码的原始数据,而图2.11展示的则是经过解码转换后的波形数据可视化结果(由单片机内部程序完成解码转换,将在4.2节中详细展开)。

图3.1:MATLAB从串口读取信号数据并写入.dat文件的程序代码

本章中的MATLAB分析均基于未解码的.dat文件,因此要想获得真实的波形数据(电压值),需要先根据以下规则对数据进行解码(4.2中的解码基于同样的原理,后续不再赘述):

由于原始数据格式是24bits的二进制补码(7FFFFF对应的模拟信号是2.4V。800000对应的模拟信号是-2.4V),若考虑保留高16bits,需要注意此时为补码,同时也要考虑到16bits相比于24bits相当于右移了8位。所以此时7FFF的数值对应模拟信号相当于2.4V,8000对应模拟信号-2.4V。

图3.2:原始信号电压与读取信号数据间的关系

基于这样的规则,可以在MATLAB编写对应的接收数据代码,从文件中读取数据(注意把负数的二进制补码转换为原码),其中Off可以为0或1(因为MATLAB接收的起始数据可能是16bits的高8位或者低8位,用off作为偏移调整,这样组合的数据才不会出现错位);同时通过plot函数绘制出原始心电信号的时域波形,并使用快速傅里叶变换(FFT)分析信号频谱,绘制出频谱图。

图3.3:原始心电信号数据解码读取、时域波形绘制与频谱分析绘制MATLAB代码

图3.4:原始心电信号时域波形与频谱图3.4:原始心电信号时域波形与频谱

从频谱图(上右图)中可以明显观察到信号中的低频基线漂移和工频干扰(与1.2节中的分析基本一致),这些成分需要通过滤波器加以去除。

3.2 IIR数字直流陷波器设计与滤波效果

工程中使用高精度传感器采集动态信号,采集的原始数据会因为环境变化、量化字长和参考电压等因素,包含较强的缓变直流分量,如果不予以消除,会导致在降噪等处理中出现运算饱和溢出。由于在采集心电信号时,人的呼吸活动和电极滑动会导致基线漂移,而这些干扰的频率又与心电信号的有效频谱非常接近,因此需要设计窄带IIR直流陷波器来消除该干扰。

直流陷波器的传递函数Η(z)为:
$$
Η(z) = \frac{z - 1}{z - a}
$$
该数字系统的极点为z = a,零点为z=1,其中参数a决定了陷波器的过渡带宽和衰减性能,为接近1的正实数。因为零点对应的幅角ω = 0,所以数字系统在零频处的增益显著衰减(如图3.5所示),其衰减的程度与过渡带的宽度和极点a的数值有关:a越大衰减变小,同时过渡带变窄,反之则衰减增大和过渡带增宽。通过实验调整,选择a=0.992可在基线漂移和信号完整性之间取得平衡。

图3.5:a=0.992时直流陷波器H(z)的增益和相位特性

图3.6:IIR数字直流陷波器设计及滤波后频谱分析绘制MATLAB代码及其运行结果
图3.6:IIR数字直流陷波器设计及滤波后频谱分析绘制MATLAB代码及其运行结果

可以看到,在加入了直流陷波器进行滤波后,0频处的基线漂移基本被消除,说明该滤波器的设计有效并起到了很好的效果。

3.3 FIR数字低通滤波器设计与滤波效果

根据阻带衰减不低于40dB且过渡带不超过10Hz的要求,利用MATLAB中自带的filterDesigner滤波器设计工具对于FIR数字低通滤波器进行设计。在该滤波器中,采用固定窗口,通过对当前点及其前160个点的加权求和来计算输出(指定滤波器阶数为160)。

在固定窗口的选择上,由于不同窗函数的阻带最小衰减各不相同,过渡带应与对应窗函数的”精确过渡带宽”相等。以下是常用窗函数的对比表:

表3.1 不同窗函数的阻带最小衰减

窗函数 第一旁瓣衰减A/dB 近似过渡带宽Bw 精确过渡带宽 旁瓣峰值衰减/(dB * oct^(- 1))
矩形窗 -13 4π/N 1.8π/N 21
汉宁窗 -31 8π/N 6.2π/N 44
海明窗 -41 8π/N 6.6π/N 53
布莱克曼窗 -57 12π/N 11π/N 74
凯泽窗(β = 7.865) -57 10π/N 80

在实际设计中,选择Chebyshev(切比雪夫)窗,其主要特点为:在给定窗口长度的情况下,能够提供最小的主瓣宽度。因此,通过选用Chebyshev窗,在频域上最小化主瓣的振幅波动,以实现滤波器的设计。

除此之外,设计时还指定采样频率为500Hz,阻带频率设计为35Hz,确定好参数后可在filterDesigner工具中导出为MATLAB函数:

图3.7:基于filterDesigner工具的FIR数字低通滤波器设计及其生成的MATLAB代码图3.7:基于filterDesigner工具的FIR数字低通滤波器设计及其生成的MATLAB代码

在主程序中调用生成的滤波器函数构造一个FIR数字低通滤波器实例,并利用filter函数对于直流陷波后的波形数据进行FIR滤波并通过plot函数绘制滤波后的频谱,代码与结果如下所示:

图3.8:信号经FIR数字低通滤波后频谱分析绘制MATLAB代码及其运行结果图3.8:信号经FIR数字低通滤波后频谱分析绘制MATLAB代码及其运行结果

可以看到,在加入了数字低通滤波器进行滤波后,50Hz处的工频干扰完全被消除,说明该滤波器的设计有效并起到了很好的效果。

3.4 滤波结果综合分析

通过IIR和FIR滤波器的级联处理,最终获得了较为清晰的心电信号波形(如图3.9所示)。此过程有效去除了干扰,为后续心率计算和信号分析提供了可靠的基础。

图3.9 滤波后心电图结果图3.9 滤波后心电图结果

图3.10 信号频谱对比图图3.10 信号频谱对比图

为了更直观地展示信号处理过程的效果,对心电信号在各个处理阶段的频谱进行了对比分析。图3.10中显示了原始心电信号以及经过直流陷波器和FIR低通滤波器联合处理后的信号频谱图。

从原始心电信号频谱可以看出,信号中存在显著的工频干扰(约50Hz)和低频基线漂移(频率较低)。此外,信号还包含较多高频噪声,影响了心电信号的质量。经过直流陷波器处理后,低频基线漂移明显被抑制,低频成分显著减少但同时工频干扰和高频噪声依然存在,但其幅值未发生明显变化,表明直流陷波器主要对低频干扰起到作用;在应用FIR低通滤波器后,信号中50Hz附近的工频干扰被有效抑制,同时高频噪声也显著减弱。

最终处理后的频谱显示出清晰的心电信号主频成分(低于30Hz),为后续的R波检测与心率计算提供了可靠的基础。

3.5 心率计算

在对原始心电信号完成滤波处理后,可进一步估算心率这一关键生理参数。心率的计算基于心电信号中的R波峰值检测,通过分析相邻R波之间的R-R间期推算心率值。R波的检测是心率估算的关键环节。在此实验中,采用MATLAB函数findpeaks来检测心电信号中的峰值。为提高检测精度,将滤波后的信号进行缩放,并设置峰值检测的阈值为信号均值加一倍标准差。

检测到R波位置后,可通过计算相邻峰值位置之间的时间间隔(R-R间期)来估算心率。使用diff函数计算相邻R波的时间间隔并计算R-R间期的平均值,并根据如下公式计算出心率:

$$
心率 = \frac{60}{平均R - R间期}
$$
图3.11 MATLAB心率计算代码

最终得出结果为60.29bpm,对比实际信号发生仪60bmp的心率,可以较为准确地估算心率,其结果符合误差小于10%的预期。

4 STM32程序设计与参数选择

项目代码已推送至Github远程仓库:Asgard-Tim/ECG_Final_Project

4.1 单片机程序架构设计

为实现2.1节所提出的基本功能,除了要采集心电数据并对其进行滤波、显示时域波形外,还需要对滤波后的波形进行频谱分析并显示。为了更高效地完成这一过程,系统引入了快速傅里叶变换(FFT)算法,用于将时域信号转化为频域信号,便于分析信号的频谱特性。然而,由于FFT计算量较大,尤其是在较高采样率下,需要处理大量的点,因此为了加快运行速度,程序采用了FreeRTOS实时操作系统,分别对信号滤波和频谱分析进行任务划分和并行处理。

图4.1 单片机程序工程基本工作框架

在系统的实现中,FreeRTOS将任务分为信号采集与滤波任务ECG和FFT计算任务。如上图所示,信号采集任务负责从ADS1292R中读取原始心电信号,并完成IIR和FIR滤波的处理,将滤波后的数据存储到循环缓冲区中,通过中断驱动的方式与传感器交互,确保数据的实时性和可靠性;而FFT计算任务则专注于对缓冲区中的数据进行频谱分析,每当缓冲区填满指定数量的数据点时,FFT计算任务将被启动,这虽然会导致实际运行时FFT频谱更新会产生一定的延时,但这样的设计将使得整个系统的工作更加稳定可控。

通过引入FreeRTOS,系统充分利用了单片机的多任务并行能力,使信号滤波和频谱分析的运行效率大幅提升,同时保证了心电信号处理的实时性。滤波和频谱分析的结合,不仅提高了信号的可用性,还为后续的心率计算和心电异常检测提供了更加丰富和可靠的数据支持。

4.2 采集原始数据解码

通过SPI接口,STM32微控制器不断与ADS1292R传感器进行交互,读取其输出的24位数字信号数据。基于3.1节提到的价码规则,将读取到的数据经过必要的解码和处理后,可将其转换为实际的电压值,并进一步用于实时显示与分析。其核心代码如下所示:

图4.2 ADS1292R原始数据解码核心代码图4.2 ADS1292R原始数据解码核心代码

在具体实现中,通过将ADS1292R的24位原始数据解码为32位有符号整数,消除了符号位可能引起的误差。随后,将解码后的信号根据增益和参考电压转换为实际的电压值。这一过程中,左导联和右导联的连接状态也会被实时检测,以确保数据采集的可靠性。如果导联连接异常(如电极松动),系统会发出提示并暂停后续的采集。

4.3 处理算法移植:滤波器

经过STM32接收心电信号后,系统根据PC端设计好的滤波器方案对原始信号进行双重滤波处理,先通过IIR滤波器消除直流分量,然后利用FIR滤波器进一步去除高频噪声,从而得到更为清晰的心电信号。经过IIR和FIR滤波后的信号显著改善,直流漂移、工频干扰以及高频噪声均被有效抑制,这一改进不仅提高了信号的可分析性,也为后续的特征提取与心率计算打下了坚实基础。

滤波后,处理得到的心电信号被传输至PC端,并通过可视化工具显示其改进的波形。如下图所示,信号的基线漂移已完全消除,高频噪声也显著减弱。滤波后的心电信号为后续的心率估算和其他心电特征分析提供了高质量的数据输入,从而提升了系统的整体性能和实用性。

图4.3:PC端可视化软件中显示采集到的滤波后心电信号(解码后)

4.3.1 IIR数字直流陷波器

在IIR滤波阶段,STM32程序参考了PC端的滤波器设计,实现了高效的实时滤波。IIR滤波器的核心算法通过递归关系计算当前输出值,其中参数a=0.992控制直流分量的衰减程度。算法以较少的存储空间完成了对信号直流漂移的有效抑制,使用上一输入与上一输出值的递归关系更新当前输出值,从而达到实时滤波的目的。

图4.4:IIR数字直流陷波器单片机C语言代码

4.3.2 FIR数字低通滤波器

经过IIR滤波后,信号中的直流分量被有效去除,但仍可能含有工频干扰和其他高频噪声。因此,信号会进一步通过FIR滤波器进行处理。FIR滤波器利用一个固定长度的滑动队列实现,其滤波系数矩阵B由MATLAB中的filterDesigner工具生成并导出到C代码中。该滤波运算采用定点方式,在初始化时需要将滤波器系数乘以66536(如图4.5中右图所示),再取整保存为16bits整数。该部分的核心算法是,通过遍历队列数据与滤波器系数进行逐项乘积累加,实现对信号的精准滤波。代码设计充分考虑了队列操作的效率,通过队列循环的方式减少内存消耗并保证实时性。

图4.5:filterDesigner导出系数矩阵B的部分结果及FIR滤波器参数初始化
图4.5:filterDesigner导出系数矩阵B的部分结果及FIR滤波器参数初始化

图4.6:FIR数字低通滤波器核心算法单片机C语言代码

4.4 处理算法移植:心率与幅值计算

系统的心跳检测功能基于心电信号的峰谷变化,通过heartbeat_check函数实现对R波峰值的实时检测。该函数采用简单高效的阈值法,以波峰和波谷之间的差值判断是否发生心跳事件。函数的核心逻辑包括记录当前波形的上升和下降趋势,以及动态调整波峰(up_value)和波谷(down_value)的值。

具体来说,当检测到波形从下降趋势转为上升趋势,且波峰与波谷的差值超过设定的阈值(经过调试设置为0.55较为合适),即判定为一次心跳事件。此时,波峰和波谷会重新初始化,为下一次心跳检测做好准备。如果当前波形变化幅度未达到阈值,则认为是噪声或非心跳波形,函数返回0。

在实际应用中,该函数与DWT模块配合使用,每次心跳事件发生时记录时间戳,通过计算相邻两次事件的时间间隔(R-R间期)估算心率。高精度的DWT计数器和简洁的心跳检测算法相结合,使得系统能够在保证实时性的同时,准确识别心跳并计算心率。

图4.7:单次心跳检测函数heartbeat_check代码

图4.8:心率计算函数calc_heartbeat_rate代码(基于单次心跳的检测结果)

为衡量心率信号的幅值范围,编写函数 Cal_PeakToPeak 以计算一组浮点数数据的峰峰值,即数据中的最大值与最小值之间的差值,用以反映数据的动态范围或振幅。函数通过接收一个浮点数数组 samples 和数组中的元素个数 sample_count 作为输入,首先将数组的第一个元素初始化为当前的最大值和最小值,然后从第二个元素开始逐一遍历整个数组。在遍历过程中,函数逐步更新最大值 max 和最小值 min,确保能够捕获数据中的真实极值。遍历完成后,函数通过计算 max - min 得到峰峰值,并将结果作为返回值。整个过程采用单次遍历的方法,计算效率较高,适合处理较大规模的采样数据。

图4.9:幅值检测函数Cal_PeakToPeak代码

4.5 TFT屏幕绘制采集波形

TFT屏幕是系统的重要输出模块,负责实时显示处理后的心电波形和心率数据。通过高分辨率和快速刷新率,TFT屏幕能够清晰呈现心电信号的变化,供用户随时监控自己的心脏健康状况。它不仅显示心电波形,还提供当前的心率和可能的异常信号提示,帮助用户及时发现问题。

图4.10 2.8寸TFT LCD电阻触摸屏模块320\*240实物图

对于已经滤波后的心电信号,在移植并调用原有LCD驱动库的基础上,编写了drawCurve函数实现实时的波形显示。该函数根据输入的心电信号数值,将其映射到屏幕的像素坐标,并绘制出连续的曲线。

图4.11:TFT屏幕波形绘制函数drawCurve代码

可以看到,对于滤波后的心电信号值,除了基本的指定区域绘制波形外,为避免波形超出屏幕边界,还对计算得到的y坐标进行上下限约束,使其始终位于屏幕的有效显示区域内:如果y坐标超过屏幕范围,则自动截断至边界位置;当x坐标超出屏幕宽度时,程序清空屏幕并从起始位置重新开始绘制新的波形。通过这种循环显示方式,确保波形在屏幕上以滚动形式连续更新,为实时心电信号的监测提供直观的显示效果。

4.6 频谱分析及其TFT屏幕绘制

在频谱FFT计算的具体实现中,程序使用CMSIS-DSP库提供的高效FFT算法,首先将心电信号数据打包为复数输入,其中实部为滤波后的心电数据,虚部置为零。随后调用arm_cfft_radix4_f32函数完成快速傅里叶变换,并通过arm_cmplx_mag_f32函数计算复数频谱的幅值。计算结果存储在FFT输出缓冲区中,用于后续的频谱绘制。

图4.12:频谱计算(FFT)函数FFT_Calculate代码

对于频域波形,系统通过FFT算法计算心电信号的频率分量,并将其可视化为频谱图。频谱图的x轴代表频率,y轴代表频率分量的幅值,能够直观反映心电信号的频率特性。频域波形的可视化主要依赖Draw_Spectrum函数:

图4.13:频谱绘制函数Draw_Spectrum代码

由于屏幕高度有限,为避免绘图时坐标溢出,函数将FFT输出的幅值限制在合理范围内(0到150,其余位置留给时域波形)。当幅值超过上限或低于下限时,进行截断处理。每个频率分量对应一条垂直线,从屏幕底部开始绘制到计算得到的y坐标。

5 程序测试方法

5.1 ADS1292R驱动移植及引脚配置

首先,在通过STM32CubeMX创建工程时,需要配置单片机的时钟速率,SPI接口,中断以及UART串口通信等。为实现与ADS1292R传感器的数据传输,选用SPI1并对引脚进行如下配置:

图5.1:SPI1引脚与参数配置

其中与ADS1292R传感器通信的SPI接口配置为主模式,时钟极性为CPOL=LOW,时钟相位为CPHA=2EDGE,数据帧格式为8位,通信速率依据传感器要求设置在8MHz以下,此处设置为656.25Kbit/s。

此外,还需要根据ADS1292R库文件中的说明,对UART通信进行设置,其中波特率设置为115200,8位数据位,1位停止位,无校验。

图5.2:UART引脚与参数配置

完成引脚配置后就可以进行初始代码生成。在生成的初始代码工程中,引入ADS1292R的相关库文件(ADS1292R.c与ADS1292R.h),根据对应的配置代码将STM32F407开发板与ADS1292R传感器模块进行连线:

图5.3:ADS1292R.h与spi.c代码文件中关于连线的相关说明图5.3:ADS1292R.h与spi.c代码文件中关于连线的相关说明

图5.4:连线实物图
图5.4:连线实物图

连线完成后,要通过SPI接口实现与ADS1292R的数据交互,还需要在主程序中完成相应的初始化,即调用ADS1292R驱动库中的相关函数,由单片机向ADS1292R发送相应的命令以启动数据采集,并配置工作模式、采样速率、增益等参数。根据ADS1292R数据手册及项目要求,以下是对ADS1292R的主要配置过程:

图5.5:函数ADS1292_PowerOnInit中对于ADS1292R的基本配置代码

首先,将ADS1292R的CONFIG2寄存器配置为0xA3,以启用内部参考电压。内部参考电压的稳定性对于后续心电信号的准确采集至关重要,因此在配置完成后需通过DWT_Delay_ms(10)添加延时,确保内部参考电压稳定。随后,将CONFIG1寄存器设置为0x02,配置心电采集的采样速率为500SPS(Samples Per Second),这一速率能够兼顾信号的时域分辨率和数据量。

接下来,对通道1和通道2进行配置。其中,通道1的CH1SET寄存器设置为0x00,表示通道1工作于正常采集模式,且未启用测试信号输入。通道2的CH2SET寄存器配置为0x05,用于指定通道2采集来自内部测试信号的方波,方便在调试阶段验证系统的采集和传输功能。

此外,为了进一步优化信号的质量,对右腿驱动(RLD)电极进行了配置,通过将RLD_SENS寄存器设置为0x2C,使右腿驱动电极同时连接到通道1和通道2,增强了共模信号的抑制能力。在呼吸阻抗测量相关功能中,将RESP1和RESP2寄存器分别配置为0x02和0x03,根据手册要求开启适当的工作模式。

通过上述配置,ADS1292R可以稳定运行于双通道心电采集模式,通道1用于实时采集患者的心电信号,通道2可用于采集ADS1292R传感器的测试信号以验证该传感器模块是否被正常驱动。

5.2 ADS1292R驱动移植验证:读取传感器设备ID

事实上,在ADS1292_PowerOnInit函数中,图5.5所示初始化代码之前还有一段代码:

图5.6:ADS1292_PowerOnInit初始化函数部分代码

可以看到,要实现5.1节中对于ADS1292R传感器模块的正确驱动,就必须要顺利运行该段代码,其中调用了ADS1292_ReadDeviceID这一函数并设置了判断条件,若读取到的设备ID不为83(ADS1292)或115(ADS1292R)则会一直循环读取ID而不进行后续的初始化,以确保读取的心电信号不会出现无效数据且采集数据符合预期格式。由于本次项目使用的传感器芯片为ADS1292R,故成功读取到并在串口助手中打印的device_id为115,这也证明驱动库文件的移植是正确的且能正常驱动该传感器模块。

5.3 ADS1292R驱动移植验证:测试信号采集及其TFT屏幕绘制

在ADS1292R的芯片数据手册中,给出了其内置测试信号的相关参数:

图5.7:ADS1292R芯片手册测试信号部分

可以看到,当PGA增益倍数设置为1时,芯片将输出测试方波信号,通过MATLAB读取并解码原始数据,得到(含直流)范围在-20到8;而去除直流后,该方波动态范围为-14到14。由于参考电压为2.4V(16bits),此时14/32768*2400mv
约等于1mv,说明此时方波动态范围转换为电压是正负1mv,和数据手册说明一致。方波测试信号的成功读取也进一步验证了ADS1292R驱动库移植的正确性且能正常驱动该传感器模块。

图5.8:MATLAB读取测试方波结果

图5.8:MATLAB读取测试方波结果

6 实验数据记录与分析

演示视频链接:https://www.bilibili.com/video/BV1HWCkYzETe/

6.1 测试信号采集

在测试ADS1292的内部方波信号时,设置PGA增益为1,测试结果显示方波波形良好,基本无误差。系统能够准确读取方波信号的峰峰值和幅度值,其效果如图所示:

图6.1:采集测试信号在LCD屏幕上的显示效果(测量幅度值为155)

此外,系统新增了按键功能,按压KEY_0即可实现在显示测试信号波形与显示心电信号波形间切换。

6.2 模拟器心电信号采集

在实际运行中,drawCurve函数结合系统滤波模块的输出数据和心跳检测结果,将心电信号时域与频域的动态变化以实时曲线的形式绘制在屏幕上。同时,在屏幕上显示心率、波幅等关键参数,进一步增强了系统的直观性和信息量。其效果如下图所示:

图6.2:BPM为60的心电信号(模拟器产生)在LCD屏幕上的显示效果

经过测试,测算出的心率值与心电模拟仪中给出的心率值误差不超过3 BPM(每分钟心跳数),满足系统设计需求;但此时滤波器的滤波效果有点过度,也可能是显示的波形幅度调整的不好,导致最终显示的波形丢失了过多的有用信息。

6.3 模拟器心率信号采集

将模拟器调至2档(心率档),产生的心率信号默认为75BPM,此时时域与频域波形的显示效果如下:

图6.3:BPM为75的心率信号(模拟器产生)在LCD屏幕上的显示效果

可以看到,此时测得的心率基本准确,与心电模拟仪中给出的心率值误差不超过3 BPM,满足系统设计需求,且频谱图也有较明显的尖峰且噪声较少;此时滤波器的滤波效果虽然也有点过度了,但效果明显由于6.2节中对于心电信号的测量,有明显的波峰。

心跳检测功能的设计不仅能够适应动态变化的心电信号,还通过灵活的阈值调整提高了对不同信号幅度的适应性。将模拟器调至8档,以调整产生心率信号的幅值(心率BPM仍为75),采集与显示效果如下:

图6.4:幅值为1mV的心率信号(模拟器产生)在LCD屏幕上的显示效果

图6.5:幅值为2mV的心率信号(模拟器产生)在LCD屏幕上的显示效果

图6.6:幅值为3mV的心率信号(模拟器产生)在LCD屏幕上的显示效果

可以看到,随着心率信号幅值的成倍变化,测量出的波形幅值也在成倍变化,且频谱较为干净,这说明该心电信号采集系统对于不同的信号幅度具有较高的灵敏性,但未对测量的幅值数值进行对应电压的换算导致结果并不直观;同时由于测量心率程序中对于检测跳变幅度的阈值设置较小,导致此时当幅值明显增大时,震荡信号的跳变幅度也随之增加,导致心率测量出现一定程度的偏差。

6.4 人体心电信号采集与测量

未接入人体心电信号时,采集到信号的时域波形与频谱如下:

图6.7:未接入任何心率信号时LCD屏幕上显示的时域与频域波形(白板)

对于人体心电信号检测,由于心电电极对接触状态的敏感性,最佳效果仅在涂抹酒精并佩戴三分钟内获得,因此系统仅能在心电电极贴附于人体的短时间内实现精确检测

在实际测试时,邀请室友将电极片分别贴于手腕和脚部,并分别与数据线连接,此时系统采集到的心电波形如下所示(详细测试过程在演示视频中):

图6.8:人体心电信号测试过程掠影
图6.8:人体心电信号测试过程掠影

图6.9:采集到的人体心电信号时域与频域波形显示结果

从图中可以看出,检测人体心电信号时,波形与心电信号模拟仪的输出相比仅存在轻微差别,整体表现良好,但仍然存在滤波过度的问题。

除了将电极贴于手腕和脚部进行测试外,系统还测试了将电极贴在左右锁骨中线第一肋间和胸骨左缘第四肋间的位置。测试结果显示,这些位置的信号稳定性优于手脚部位,并且能够维持更长时间的良好效果。

7 实验结果总结和心得体会

7.1 实验结果

在本项目中,基于 STM32F407ZG 主控芯片和 ADS1292R 传感器模块,成功实现了人体心电信号的采集与分析功能。针对心电信号易受工频干扰和低频基线漂移影响的问题,设计并实现了 IIR 数字直流陷波器 和 FIR 数字低通滤波器,有效消除了干扰并获得了较为清晰的心电波形。

在开发过程中,利用心电信号模拟仪对单片机程序进行了调试,使系统能够稳定采集和显示心电波形。尽管滤波器在MATLAB仿真中效果良好,但移植到单片机工程中后,出现了滤波过度的问题。初步分析认为,这可能与信号采样频率、显示波形的幅度比例调节以及滤波算法的具体实现有关,仍需进一步优化。然而,系统总体性能令人满意,其采集效果、频域分析结果、心电峰峰值及心率测算结果均较为准确。

此外,系统还实现了对 ADS1292R模块自带测试方波信号的读取、显示以及峰峰值测量功能,基本满足了所有的设计需求。最终也成功采集并显示了实际人体心电信号的时域波形与频谱。

7.2 心得体会

通过本次项目的实践,我对数字信号的采集与处理以及滤波器的设计与实现有了更加深刻的理解。这不仅加深了我对数字信号处理理论知识的掌握,还让我在理论与实践的结合中得以进一步巩固相关技能。在单片机的编程实践与调试过程中,我对STM32尤其是F4系列开发板的硬件和软件开发有了更深入的认识,这大大提升了我的开发效率和调试能力。

本项目也是我首次尝试使用 FreeRTOS操作系统搭建整个工程框架。在此过程中,我学习并掌握了实时操作系统的基本原理和任务调度机制,初步理解了如何优化系统资源管理,成功地将操作系统的使用融入到嵌入式项目开发中。这一尝试不仅丰富了我的开发经验,也让我更好地认识到实时操作系统在复杂工程中的价值。

尽管最终心电信号的显示结果并未完全达到预期,但我从问题中发现了自身在信号采集频率、滤波算法实现以及系统显示优化方面的不足,并明确了未来改进的方向。整个项目开发的过程充满了挑战,同时也伴随着大量的收获。我深刻体会到硬件开发与数字信号处理是一个不断探索和优化的过程,而这次项目实践无疑为我在这些领域的技能提升打下了坚实基础。

此外,本次项目还培养了我独立分析和解决问题的能力。从模块功能的实现到整体工程的构建,我逐步熟悉了完整的开发流程,对系统的设计、调试和优化有了更加系统化的认识。我相信,这些经验和能力将在今后的学习和工作中发挥重要作用,为更高层次的开发任务奠定基础。

参考资料

[1] 朱冰莲,方敏编著.数字信号处理[M].电子工业出版社,2014:276.

[2] 程佩青编著.数字信号处理教程[M].清华大学出版社,2015:524.

[3] 任勇,曾浩编著.单片机原理及应用[M].清华大学出版社,2023.

[4] 德州仪器. ADS1292 数据手册

[5] 任勇. CQU_S12XDEV开发板原理图 微电子与通信工程学院

[6] 任勇. ADS1292-心电信号采集原理图及接口说明-RY

基于直流电源调控的自动调光控制设计

摘要

本项目围绕直流电源调控的自动调光控制系统展开研究与设计,系统性地探讨了Buck变换器的基本原理、建模方法、性能分析及其实验验证过程。在硬件设计方面,基于STM32处理器,选择了高性能的元器件并通过合理的电路拓扑实现高效的能量转换;在软件控制算法方面,采用PID闭环控制,并结合自动控制原理中的经典控制理论,利用PSIM与MWorks等仿真与科学计算工具,对控制系统的时域响应、频域特性和稳定性进行了详尽分析,进而通过参数优化与校正环节设计显著提升了系统的响应速度和稳态性能,同时也验证了闭环控制系统在动态性能、抗干扰能力和输出精度方面的显著优势。此外,通过实验测量与仿真结果对比,探讨了电路寄生参数对系统性能的影响,为后续优化提供了理论依据。在基于光敏电阻的自动调光功能模块中,结合蓝牙通信接口实现了系统的智能化控制,同时对于自动调光系统进行外观设计,赋予产品更多的人文关怀与实用价值;在光伏板最大功率点跟踪(MPPT)功能模块中,根据MPPT的原理与基本思想设计了相应的控制算法,并在实验中成功控制光伏板输出功率,使其约等于负载消耗功率,完成了不同光照强度下最大功率点的跟踪。最后,对于该自动控制系统的设计成果及其在实际应用中的可行性与局限性进行总结,并对未来可能的优化方向和工程实现前景提出了展望。

关键词:Buck变换器;PID闭环控制;自动调光;光伏MPPT

1 课程涉及理论基础和STM32简介

1.1 自动控制原理简介

在科学技术飞速发展的今天,自动控制技术和理论已经成为现代社会不可缺少的组成部分。自动控制技术的应用不仅使生产过程实现自动化,从而提高了劳动生产率和产品质量,降低了生产成本,提高了经济效益,改善了劳动条件,使人们从繁重的体力劳动和单调重复的脑力劳动中解放出来;而且在人类征服大自然、探索新能源、发展空间技术和创造人类社会文明等方面都具有十分重要的意义。

自动控制理论是研究关于自动控制系统组成、分析和综合的一般性理论,是研究自动控制共同规律的技术科学。自动控制是在人不直接参与的情况下,利用外加的自动控制设备或装置(控制装置或控制器),使机器、设备或生产过程(统称为被控对象)的某个工作状态或参数(被控量)自动地按照预定的规律运行,使机器的动作、设备的运转、生产过程的状态能够自动地在一定的精度范围内按照给定的规律变化。学习和研究自动控制理论是为了探索自动控制系统中变量的运动规律和改变这种运动规律的可能性和途径,为建立高性能的自动控制系统提供必要的理论依据。

1.2 本项目所涉及的经典控制理论内容

图1.1:项目涉及的经典控制理论框图

本项目从经典控制理论的基本原理与概念出发,以Buck变换器这一单输入-单输出的线性系统作为研究对象,利用微分方程、Laplace变换与传递函数等数学工具建立系统的数学模型,并基于时域分析、频域分析以及根轨迹法等多种分析方法对于系统的稳定性与响应特性进行详细分析,从而针对特定的性能指标进行对应的校正设计,通过引入PID控制器并调控其参数以改变系统的频率特性从而满足给定的各项性能指标,使得整个闭环控制系统能够兼具稳定性、快速性与准确性。

1.3 STM32处理器介绍

控制核心是控制系统中的重要组成部分,用于计算、解析各种数据,并执行相应的控制算法。芯片选型的设计直接决定了控制板的性能和功能。STM32是由意法半导体公司(ST)推出的基于Arm Cortex-M处理器内核的32位微控制器,专为要求高性能、低成本、低功耗的嵌入式应用设计,集实时功能、数字信号处理、低功耗/低电压操作、连接性等特性于一身,同时还保持了集成度高和易于开发的特点,基于行业标准内核,提供了大量工具和软件选项以支持工程开发,非常适用于小型项目或端到端平台。

本项目选用的处理器STM32F103C8T6作为中等容量高性能系列MCU,集成了工作频率为72MHz的高性能Arm Cortex-M3 32位RISC内核、高速嵌入式存储器(高达128KB的Flash存储器和20KB的SRAM存储器),以及大量连接至2条APB总线的增强型I/O与外设,具有36引脚至100引脚等6种不同的封装类型。所有器件均提供2个12位ADC、3个16位通用定时器、2个PWM定时器以及标准和高级通信接口:多达2个I2C和SPI、3个USART、1个USB和1个CAN。器件的工作电压为2.0V至3.6V。该处理器的工作温度范围为-40℃到+85℃,可扩展至-40℃到+105摄氏度。这些特性使得该处理器成为各种应用的理想之选,也能很好满足本项目对于控制器的性能需求。

图1.2:本项目选用的处理器STM32F103C8T6

1.4 本章小结

本章主要介绍了本课程相关的自动控制理论基础,针对本项目涉及到的经典控制理论框架进行了简要概述,同时对于本项目所选用的控制核心——STM32处理器进行简单介绍,重点分析了我们采用的STM32F103C8T6处理器的性能特性并给出选型原因。这为本课程项目提供了整体框架,并从理论上对后续项目的具体实施给出了方向性的指引。

2 直流Buck变换器设计与调试

2.1 Buck变换器拓扑原理分析

Buck(降压式)变换器是一种输出电压≤输入电压的非隔离直流DC-DC变换器,其中输入电流为脉冲式的,而输出电流为连续的低纹波直流电压。Buck变换器实现的稳态输入输出关系为:
$$
U_{0} = DU_{in}
$$
Buck变换器的主电路由开关管Q,二极管D,输出滤波电感L和输出滤波电容C构成。

图2.1:Buck开关功率变换器基本电路

可以看到,在能量缓冲变换电路中,主要由如下三个部分组成:

  1. 电感L与电容C实质上构成了一个二阶低通滤波器,通过滤除开关频率交流分量而仅保留其直流分量,得到平直的输出电压U0;

  2. 脉冲宽度调制(Pulse Width Modulation,PWM)产生方波电压控制开关管Q的导通;

  3. 二极管D为电感电流提供续流回路。

Buck变换器主电路整体的工作逻辑如下:

  1. 当开关管Q驱动为高电平时,开关管导通,储能电感L被充磁,流经电感的电流线性增加,同时给电容C充电,给负载R提供能量;

图2.2:开关管导通时电流环路

  1. 当开关管Q驱动为低电平时,开关管关断,储能电感L通过续流二极管D放电,电感电流线性减少,输出电压靠输出滤波电容C放电以及减小的电感电流维持。

图2.3:开关管关断时电流环路

事实上,对于该电能变换器,可以通过更改个别元器件的种类、接入方式与顺序,实现搭建具有不同功能的电能变换电路,即Buck变换器的拓扑原理。下面列举几种常见的拓扑电路:

  1. 升压变换器:

图2.4:Buck变换器拓扑:降压--->升压

  1. 降压同步整流变换:采用互补工作模式,可减小损耗

图2.5:Buck变换器拓扑:同步整流

  1. H桥DC-AC逆变器:开关管部分串联构成双极性交流电压源

图2.6:Buck变换器拓扑:DC-DC --->DC-AC

  1. 闭环PWM控制:可以在原有Buck电路基础上增加闭环环路,通过PWM调配开关管Q的导通与否,从而实现对于输出电压的控制,使系统能够更加”稳”、”快”、”准”地得到期望的输出。目前的控制器选择主流为PID控制器,根据不同的指标又可将闭环控制系统分为不同类别:若根据控制对象分类,则可分为电压控制与电流控制;若根据接收调控信息的时间先后分类,又可分为反馈控制与前馈控制;根据其他的分类标准,还可分为线性/非线性控制、平均/纹波控制、模拟/数字控制……在此不一一列举。

2.2 Buck变换器元器件参数选择

在Buck变换主电路中,对电路参数进行如下设定:

  • 输入电压
    $$
    U_{in} = 15V
    $$

  • 输出电压
    $$
    U_{o} = 8V
    $$

  • 占空比
    $$
    d = 50%
    $$

  • 电源转换电压:12V、15V、5V、5V(隔离)

  • 电感
    $$
    L = 100\mu F
    $$

  • 电容
    $$
    C = 660\mu F
    $$

  • 工作频率
    $$
    f = 25kHz
    $$

首先,为满足电源转换与单片机供电的需求,需要在电源直接引入Buck电路前先接入电源模块,涉及到的元器件及相关参数如下:

  1. URB2412YMD-10WR3电源模块:降压模块,将电源提供的输入电压(最大35V,本实验中为15V)转换为12V输出,提供0.83A的电流;

图2.7:URB2412YMD-10WR3电源模块原理图与实物图图2.7:URB2412YMD-10WR3电源模块原理图与实物图

  1. CW7805线性稳压器:将12V输入电压转换为稳定的5V输出电压,并将输出电流转换成1A,分输入、输出与接地三端,主要用于使线性的输出电压稳定;

图2.8:CW7805线性稳压器原理图与实物图图2.8:CW7805线性稳压器原理图与实物图

  1. A1215S-2WR3电源模块:升压模块,将12V输入转换为±15V,适合供给双电源运放电路,本实验中主要用于为采样电路(滤波器)供电;

图2.9:A1215S-2WR3电源模块原理图与实物图图2.9:A1215S-2WR3电源模块原理图与实物图

  1. IB1205S-2W电源模块:降压模块,将12V输入转换为5V,通常用于低功耗电路的供电,本实验中主要用于光耦(与驱动)电路的供电。

图2.10:IB1205S-2W电源模块原理图与实物图图2.10:IB1205S-2W电源模块原理图与实物图其次,在Buck变换主电路中,为满足设定元件参数,选取如下元器件:

  1. 贴片MOS管NCE0130KA:VDS=100V,VGS=10V

图2.11: 贴片MOS管NCE0130KA实物图

  1. 贴片电感:色环直插型,100uH

图2.12:贴片电感实物图

  1. 贴片铝电解电容:330uF,±20%,耐压50V,两个并联达到设定660uF

图2.13:贴片铝电解电容实物图

  1. 电流传感器CC6920SO-5A:初级电流测量范围-5A~5A,供电电压5V

图2.14:电流传感器CC6920SO-5A实物图

  1. 电压传感器LV25-NP:初级电流测量范围10-500V,供电电压±15V

图2.15:电压传感器LV25-NP实物图

其中,电容与电感均采用贴片形式的原因主要有以下几点:

【1】 体积小且便于贴装,适合电路的高集成度需求;

【2】 贴片元件引线较短,寄生电感和电容较小,可提高电路的高频性能;

【3】 贴片元件的散热性能通常较好,有助于提高电路的可靠性;

【4】 贴片元件可以有效减小电磁干扰,提高电路的稳定性。

除此之外,由于本实验采用的主控STM32输出能力有限,无法直接驱动管子开关,因此还需要采用光耦和驱动电路为管子提供驱动信号,涉及到的元器件及相关参数如下:

  1. IR21844S驱动:栅极驱动供电范围10-20V

图2.16:IR21844S驱动实物图

  1. 光耦-逻辑输出6N135:5V供电,光耦隔离

图2.17:光耦-逻辑输出6N135实物图

  1. 运算放大器AD823ARZ:最大输入偏置电流25pA,低失真−108dBc

图2.18:运算放大器AD823ARZ实物图

2.3 Buck变换器实物设计与调试

在Buck变换主电路设计时,选择不对Buck变换器的电路部分进行拓扑,而在闭环PWM控制电路中采用平均电压模式进行控制。本项目采用的实验电路板主要包括Buck电路基本器件、开关管驱动、辅助电源以及采样电路(信号调理电路),实验时将元器件焊接至电路板上并分别调试各模块功能。

图2.19:组员焊接电路板过程图2.19:组员焊接电路板过程

图2.20:焊接后Buck变换器整体电路实物图图2.20:焊接后Buck变换器整体电路实物图

2.3.1 主电路设计与调试

图2.21:Buck变换器主电路原理图

该电路是一个典型的降压型 DC-DC 转换器,其工作原理是通过开关管 Q3 的高速开关动作,将输入电压V_IN转换为期望的输出电压V_OUT。当开关管Q3导通时,输入电压通过 L2、L3和负载形成电流回路,电感存储能量,同时为负载供电;当 Q3关断时,续流二极管 D4 提供电流通路,电感释放能量维持负载电流的连续性。控制器 U6根据反馈电压(通过分压电阻 R8 反馈的V_OUT)与参考电压之间的误差,实时调节 Q3的导通时间(占空比),从而实现输出电压的稳定调节。两级电感 L2 和 L3以及滤波电容 C9、C15进一步平滑输出电流和电压,减少高频纹波,确保输出电压的稳定性和低噪声特性。

经过调试,Buck降压模块主电路可正常实现功能,在参考输入方波幅值为8V、占空比为50%的情况下能够输出占空比为50%、幅值在8V左右(实际约为8.5V)的方波。

图2.22:Buck变换器主电路调试过程与调试结果图2.22:Buck变换器主电路调试过程与调试结果图2.22:Buck变换器主电路调试过程与调试结果图2.22:Buck变换器主电路调试过程与调试结果

2.3.2 控制电路供电设计与调试

图2.23:STM32主控芯片供电电路及预留引脚接口

如图所示为STM32主控芯片供电电路(电源模块)以及单片机内部所使用的接口引脚图,同时将大部分未使用的引脚通过排针引出以供后续拓展功能开发。

在STM32主控芯片供电电路中,包含两个级联的线性稳压器,用于将高电压逐级稳压到所需的5V 和 3.3V。上半部分采用 CJ7805稳压器,将输入电压V_IN转换为稳定的5V输出,通过输入电容C2和C1滤波降低输入纹波,稳压器通过内部反馈电路调节输入电压,使输出稳定在5V,同时通过输出电容C3滤除高频噪声,进一步平滑输出电压。下半部分采用
AMS1117-3.3 稳压器,将上一级提供的 5V 电压进一步稳压为3.3V,通过输入滤波电容 C5和 C6减少输入噪声,并通过输出滤波电容C4提高输出电压的稳定性。整个电路通过分级稳压设计,既提高了稳压效率,又能为负载提供低噪声、高稳定性的5V和3.3V电压,适用于嵌入式系统和低功耗数字电路的电源需求。

调试流程:取下单片机核心板—>接入负载100欧姆—>单片机输出PWM—>观测PA8端口波形—>观测驱动芯片输出端口波形—>上主电24V—>检测辅助电源输出电压—>检测输出电压—>根据占空比计算输出电压是否正常—>完成

经调试,该部分模块可正常工作,为STM32主控芯片提供稳定的5V电压:

图2.24:控制模块供电电路调试输出结果图2.24:控制模块供电电路调试输出结果

2.3.3 驱动电路设计与调试

图2.25:光耦隔离(上)与驱动电路(下)原理图

如图所示,驱动电路为已有的STM32输出提供了合适的电压和电流驱动功率器件,而如果直接使用STM32输出驱动,可能会超出其输出能力或不能实现良好的电气隔离,导致驱动失败或损坏器件,输出信号不稳定。使用光耦和驱动电路则可以更好地实现电气隔离,从而防止高压或大电流对控制电路的干扰或损坏,保护STM32免受高电压或大电流的影响,提高系统工作可靠性。

调试时,先切断仿真器供电,将单片机供电切换为主电路辅助电源供电;单片机烧录输出电压控制程序后,接入后端负载,再上主电,观测输出电压。经调试,驱动电路可正常工作。

图2.26:驱动电路调试后输出结果图2.26:驱动电路调试后输出结果

2.3.4 电压和电流采样调理滤波设计与调试

图2.27:电感电流(上)与输出电压(下)采样调理滤波电路原理图

该电路是一个基于运算放大器的电压比较和分压检测电路,主要功能是将输入电压V_OUT通过电阻分压后与基准电压比较,并输出相应的信号V_S。该电路可用于电压监测或过压保护等场景,通过调整R1 和 R2 的比例,可以设置分压电压,从而灵活设定输入电压的触发阈值。

具体工作原理如下:输入电压$V_{OUT}$经由电阻R1和R2分压后,产生一个分压电压,该电压送入运算放大器U5的反相输入端(引脚2)。运算放大器的非反相输入端(引脚 3)通过稳压二极管 D2提供一个固定的基准电压(3.3V)。当分压后的电压低于基准电压时,运算放大器输出高电平;当分压电压高于基准电压时,运算放大器输出低电平。电容C7和C16用于滤除输入和输出的高频噪声,保证比较过程的稳定性。

调试时,使用仿真器给单片机供电,以调试PWM波形输出是否正常;烧录开环测试程序之后,使用示波器或者上位机观测电路板PA8端口是否正常输出PWM波形。经调试,可对输入信号正确采样并输出对应波形,说明采样模块正常工作:

图2.28:采样模块调试后输出结果图2.28:采样模块调试后输出结果

2.4 本章小结

本章主要介绍了直流Buck变换器的基本工作原理及其拓扑变换,并根据选定的主控芯片STM32F103C8T6以及设定的电路参数进行了基本元器件的选型与电路原理图及PCB电路板的设计,确定电路主要包括Buck降压变换主电路、控制电路供电辅助电源模块、驱动电路以及采样电路(信号调理电路)四个模块;在焊接时对各个模块依次进行焊接与调试,保证各模块均可以正常工作,以便于后续闭环控制实现时STM32主控控制模块与代码的设计与测试。

3 直流Buck变换器建模

3.1 Buck变换器闭环控制原理分析

Buck变换器闭环控制系统主要由以下几个部分组成:

  1. 误差放大器:将参考电压V_ref与实际输出电压V_OUT比较,生成误差信号;

  2. 补偿网络:对误差信号进行处理(例如,PI或PID控制),以提高系统稳定性和动态性能;

  3. PWM调制器:将补偿后的控制信号转换为开关元件的占空比D;

  4. 采样电路:对输出电压 V_OUT进行实时采样。

图3.1:Buck变换器闭环控制电路简图

整个闭环控制系统的工作过程如下:

  1. 输出电压采样:通过分压电路对输出电压V_OUT进行采样,得到反馈电压V_fb;

  2. 误差检测:误差放大器将参考电压V_ref与反馈电压V_fb比较,产生误差信号
    $$
    e(t) = V_{ref} - V_{fb}
    $$

  3. 误差调节:误差信号经过补偿网络调节,生成调节信号V_ctrl,此信号决定PWM占空比;

  4. PWM调制:调制器根据调节信号V_ctrl生成控制信号D,驱动开关元件;

  5. 电感电流调节:开关元件的导通时间决定电感电流的充电时间,从而控制输出电压。

图3.2:Buck变换器闭环控制系统框图

从系统传递函数的角度考虑,根据系统框图与元件特性,可计算其开环传递函数与特征方程:

(1)G_c(s)为PI环节, PI调节器为:
$$
\left{ \begin{aligned}
& \dot{x} = K_{I}v \
& y = K_{p}v + x
\end{aligned} \right.
$$
可得PI环节传递函数G_c(s):

$$
G_{c}(s) = K_{p} + K_{I}\frac{1}{s} = \frac{K_{p}(\tau s + 1)}{\tau s},\tau = \frac{K_{p}}{K_{I}}
$$
(2)PWM脉宽调制环节:由大信号关系
$$
v_{con}(dT) = v_{ramp}(dT) = V_{M}\frac{dT}{T} = dV_{M}
$$
做微分可得小信号的线性关系
$$
\mathrm{\Delta}v_{con} = \mathrm{\Delta}dV_{M}
$$
从而实现传递函数的线性化:

$$
G_{pwm} = \frac{\mathrm{\Delta}d}{\mathrm{\Delta}v_{con}} = 1/V_{M}
$$
(3)PWM脉宽控制开关电路:由大信号关系
$$
v_{D} = dv_{g}
$$
做全微分可得小信号线性关系
$$
\mathrm{\Delta}v_{D} = \mathrm{\Delta}dV_{g} + \mathrm{\Delta}v_{g}D
$$
零初始条件下,对应的工作点为
$$
V_{g} = V_{in},\mathrm{\Delta}v_{g} = 0
$$
于是有传递函数:
$$
G_{vg} = \frac{\mathrm{\Delta}v_{D}}{\mathrm{\Delta}d} = V_{g}
$$
(4)RLC并联二阶输出滤波器:由电路图与元件特性可得各元件间电流电压关系:

$$
\left{ \begin{array}{r}
u_{0} = Ri_{2} \
u_{L} = L\frac{di}{dt} \
i_{1} = C\frac{du_{0}}{dt} \
i = i_{1} + i_{2} \
u_{D} = u_{L} + u_{0}
\end{array} \right.
$$
从而可得该滤波器对应的微分方程:
$$
u_{D} = LC{u_{0}}^{‘’} + \frac{L}{R}{u_{0}}^{‘} + u_{0}
$$
对两边同时进行拉普拉斯变换,化简整理后可得二阶滤波器的传递函数G_vd(s):

$$
G_{vd}(s) = \frac{U_{0}(s)}{U_{D}(s)}V_{in} = \frac{\frac{1}{LC}}{s^{2} + \frac{1}{RC}s + \frac{1}{LC}}V_{in}
$$
又因输出信号v_0直接接入PI控制器,故
$$
H(s) = 1
$$
综上所述:有开环传递函数:

$$
T(s) = G_{c}(s)G_{pwm}G_{\text{vd}}H(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{\frac{1}{LC}}{s^{2} + \frac{1}{RC}s + \frac{1}{LC}}V_{in} = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1}
$$
进而可以得到系统的特征方程
$$
T(s) + 1 = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{g}}{LCs^{2} + \frac{L}{R}s + 1} + 1 = 0
$$
化简后可得:
$$
\Delta(s) = V_{g}K_{p}(\tau s + 1) + \tau sV_{M}\left( LCs^{2} + \frac{L}{R}s + 1 \right) = \tau V_{M}LCs^{3} + \tau V_{M}\frac{L}{R}s^{2} + (V_{g}K_{p} + V_{M})\tau s + V_{g}K_{p} = 0
$$

3.2 Buck变换器PSIM仿真(开环+闭环)

利用PSIM软件进行电路仿真,根据实际电路结构搭建仿真电路图,并将电路各元件实际参数代入(PI控制器参数:K_p = 0.1,tao = 0.004):

图3.3:Buck变换器开环控制PSIM仿真电路图

图3.4:Buck变换器闭环控制PSIM仿真电路图

运行仿真程序,可得到输出电压的仿真结果:

图3.5:Buck变换器开环控制PSIM仿真结果------输出电压

图3.6:Buck变换器闭环控制PSIM仿真结果------输出电压

对比开环与闭环控制系统的输出电压仿真结果可以发现,尽管两个系统都能在短时间内达到稳定的输出电压,但显然闭环控制系统到达稳态的速度更快且震荡更小,稳定后的电压也更接近参考电压8V(约为7.95V)。这说明闭环控制系统具有更快的响应速度与更好的稳定性和准确性。

3.3 输出电压纹波计算(仿真+实验)

图3.7:Buck变换器开环控制实验输出电压纹波波形图3.7:Buck变换器开环控制实验输出电压纹波波形

首先进行Buck变换器的开环控制实验,并通过示波器观察其输出电压的纹波波形。可以看到,输出波形峰值
$$
V_{omax} = 88mV
$$
谷值
$$
V_{omin} = - 76mV
$$
则波形的震荡幅度为
$$
\Delta V_{o} = V_{omax} - V_{omin} = 164mV
$$
随后利用PSIM软件进行电路仿真,根据实际电路结构搭建仿真电路图,并将电路各元件实际参数代入:

图3.8:Buck变换器开环控制PSIM仿真电路图

可得到输出电压纹波的仿真结果:

图3.9:Buck变换器开环控制PSIM仿真结果------输出电压纹波

可以看到,输出电压信号的震荡波形与实际电路示波器显示的纹波形状一致且震荡幅度大致相同。

3.4 电容寄生电阻计算(仿真+实验)

在Buck变换器开环控制实验中,通过万用表测量得到输入电压V_i= 15.304V,输出电压均值V_o = 7.537V:

图3.10:Buck变换器开环控制实验输入、输出电压测量结果图3.10:Buck变换器开环控制实验输入、输出电压测量结果

除了给定的元件参数之外,为对直流Buck变换器进行精确建模,考虑到电容的寄生参数可能对系统有较大影响,故特别计算其寄生电阻阻值ESR:

图3.11:寄生电阻阻值计算理论依据

结合
$$
U_{esr} = \Delta I_{L}*ESR
$$
一式,考虑到电流因流过电容的寄生电阻而产生的压降U_esr应不大于(实际一般处理为等于)波形的震荡幅度,即:
$$
U_{esr} \leq \Delta V_{o}
$$
可以得到在同步Buck电路中(V_d = 0)电容寄生电阻阻值ESR的计算公式:

$$
ESR \leq \frac{\Delta V_{o}fLV_{i}}{V_{o}\left( V_{i} - V_{o} \right)}
$$
结合Buck变换器开环控制实验结果,代入参数
$$
\Delta V_{o} = 164mV,L = 100\mu F,f = 25kHz,V_{i} = 15.304V,V_{o} = 7.537V
$$
可以计算得出该电路中实际的电容寄生电阻阻值
$$
ESR \approx 107.185m\Omega
$$
这里计算的是接入单个电容的寄生电阻阻值,在实际电路中使用了两个330μF的电容等效替代原设计电路中的660μF电容,为使仿真尽可能接近实际,采取了与实际电路相同的结构,因此需要把寄生电阻同样进行等效,根据电阻并联的等效电阻计算可以得到两个330μF电容的寄生电阻值均为
$$
2ESR \approx 214.371m\Omega
$$
利用PSIM软件进行电路仿真,根据实际电路结构搭建仿真电路图,并将计算出的寄生电阻结果代入仿真电路中:

图3.12:引入寄生电阻后的Buck变换器开环控制PSIM仿真电路图

得到的输出电压仿真结果如下图所示:

图3.13:引入寄生电阻后的Buck变换器开环控制PSIM仿真结果------输出电压

图3.14:引入寄生电阻后的Buck变换器开环控制PSIM仿真结果------输出电压纹波

可以看到,仿真结果中输出电压的均值约为7.65V,与实际的测量结果
$$
V_{o} = 7.537V
$$
较为接近;同时输出电压信号的震荡波形也与实际电路示波器显示的纹波形状一致且震荡幅度大致相同。这也印证了寄生电阻的计算以及电路建模与仿真的正确性。

3.5 本章小结

本章主要介绍了直流Buck控制器及其闭环控制系统的建模过程,通过分析Buck电路中的元件特性及闭环控制的各个环节,实现控制系统的数学建模,得到系统的开环传递函数与特征方程;同时结合Buck控制器开环控制实验的实际测量结果,关注到电容寄生电阻对于系统输出的重要影响,并通过输出电压纹波的相关特性对其进行计算,搭建PSIM电路仿真模型观察修正前后的仿真结果,发现引入寄生电阻后的仿真结果与实际实验波形输出基本一致从而说明考虑寄生电阻的必要性。除此之外,还分别搭建了Buck变换器的开环与闭环控制系统PSIM仿真电路并对比输出电压仿真结果,可以发现闭环控制系统具有更好的动态响应性能,其稳定性、快速性与准确性均优于开环控制系统。

4 直流Buck变换器控制性能分析

4.1 直流Buck变换器劳斯稳定判据分析

基于3.1节得到的开环传递函数与系统特征方程,可利用劳斯判据给出系统稳定的PI控制器比例系数K_p临界条件:

根据系统特征方程可给出如下劳斯表:

劳斯表

根据劳斯判据,要使得系统稳定,需同时满足如下条件:

$$
\left{ \begin{array}{r}
\ \tau V_{M}LC > 0 \
\tau V_{M}\frac{L}{R} > 0 \
\left( V_{in}K_{p} + V_{M} \right)\tau - V_{in}K_{p}RC > 0 \
{\ \ V}{in}K{p} > 0
\end{array} \right.\
$$
这是关于PI控制器比例系数K_p的不等式组,可解得其边界条件为:

$$
0 < K_{p} < \frac{V_{M}\tau}{V_{in}(RC - \tau)}
$$
若选定控制环路的时间常数
$$
\tau = 0.004$
$$
三角载波幅值
$$
V_{M} = 0.5V
$$
则可代入数值解得:比例系数K_p的稳定边界为
$$
\frac{V_{M}\tau}{V_{in}(RC - \tau)} \approx 0.0513
$$
而在实际情况下,需要考虑电容的寄生电阻
$$
ESR \approx 107.185m\Omega
$$
这意味着需要对3.1节建立的数学模型进行修正。显然电路结构中除二阶滤波器外的其他环节没有发生改变,针对考虑电容寄生电阻ESR的RLC并联二阶输出滤波器,由电路图与元件特性可得各元件间电流电压关系:

$$
\left{ \begin{array}{r}
u_{0} = Ri_{2} \
u_{L} = L\frac{di}{dt} \
i_{1} = C\frac{du_{C}}{dt} \
i = i_{1} + i_{2} \
u_{0} = u_{C} + ESRi_{1} \
u_{D} = u_{L} + u_{0}
\end{array} \right.\
$$
从而可得该滤波器对应的微分方程,对两边同时进行拉普拉斯变换,化简整理后可得二阶滤波器的传递函数G_vd(s):

$$
G_{vd}(s) = \frac{U_{0}(s)}{U_{D}(s)}V_{in} = \frac{\frac{1}{LC} + \frac{ESR}{L}s}{(1 + \frac{ESR}{R})s^{2} + (\frac{1}{RC} + \frac{ESR}{L})s + \frac{1}{LC}}V_{in}
$$
其他环节保持不变,于是有开环传递函数:
$$
T(s) = G_{c}(s)G_{pwm}G_{\text{vd}}H(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{\frac{1}{LC} + \frac{ESR}{L}s}{\left( 1 + \frac{ESR}{R} \right)s^{2} + \left( \frac{1}{RC} + \frac{ESR}{L} \right)s + \frac{1}{LC}}V_{in} \approx \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{1 + C \bullet ESRs}{LCs^{2} + \frac{L}{R}s + 1}V_{in}
$$
因此近似后系统的特征方程
$$
T(s) + 1 = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{(1 + C \bullet ESRs)V_{in}}{LCs^{2} + \frac{L}{R}s + 1} + 1 = 0
$$
化简后可得:

$$
\Delta(s) = {(1 + C \bullet ESRs)V}{in}K{p}(\tau s + 1) + \tau sV_{M}\left( LCs^{2} + \frac{L}{R}s + 1 \right) = V_{M}LC\tau s^{3} + (V_{M}\frac{L}{R} + C \bullet ESR \bullet V_{in}K_{p})\tau s^{2} + (V_{in}K_{p}\tau + V_{M}\tau + C \bullet ESR \bullet V_{in}K_{p})s + V_{in}K_{p} = 0
$$
接下来利用劳斯判据给出系统稳定的PI控制器比例系数K_p临界条件:

根据系统特征方程可给出如下劳斯表:

劳斯表

根据劳斯判据,要使得系统稳定,需同时满足如下条件:

$$
\left{ \begin{array}{r}
\ \tau V_{M}LC > 0 \
\tau V_{M}\frac{L}{R} > 0 \
V_{in}K_{p}\tau + V_{M}\tau + C \bullet ESR \bullet V_{in}K_{p} - \frac{V_{M}LCV_{in}K_{p}}{V_{M}\frac{L}{R} + C \bullet ESR \bullet V_{in}K_{p}} > 0 \
{\ \ V}{in}K{p} > 0
\end{array} \right.\
$$
这是关于PI控制器比例系数K_p的不等式组,其中第1、2、4个不等式均指向K_p > 0,而第三个不等式是一个关于K_p的一元二次不等式,代入电路元件参数可解得
$$
K_{p} < - 0.0232或K_{p} > - 0.0067
$$
因此可得到K_p的稳定边界为:
$$
K_{p} > 0
$$
即该情况下系统始终稳定。

4.2 直流Buck变换器系统根轨迹分析(手绘+MWorks绘制)

基于3.1节得到的开环传递函数,代入数值可得:

$$
T(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1} = \frac{30K_{p}(s + 250)}{s(6.6*10^{- 8}s^{2} + 10^{- 5}s + 1)}
$$
根据该开环传递函数可知,该系统:

具有1个零点:
$$
z_{1} = 250
$$
具有3个极点:
$$
p_{0} = 0,p_{1} + p_{2} = \frac{5}{33}*10^{3} \approx 151.5,p_{1} = \frac{5}{66}*10^{3} + a_i,p_{1} + p_{2} = \frac{5}{66}*10^{3} - a_i
$$
故有3条根轨迹,1条止于开环零点,2条止于无穷远;

根轨迹的渐近线与实轴的夹角
$$
\varphi_{a} = \pm \frac{2k + 1}{2}\pi(k = 0,1,2\ldots) = \pm 90{^\circ}、180{^\circ}
$$
渐近线与实轴交点的坐标值
$$

  • \delta_{a} = \frac{\sum_{}^{}\left( - p_{i} \right) - \sum_{}^{}\left( - z_{i} \right)}{2} = \frac{- \frac{5}{33}*10^{3} + 250}{2} \approx 49.24
    $$

基于以上结果,可手绘根轨迹草图如下:

图4.1:不考虑寄生电阻------手绘根轨迹草图

编写如下MWorks代码:

1
2
3
4
using TyControlSystems
s=tf('s');
G=(s+250)/(s*(6.6*10^(-8)*s*s+10^(-5)*s+1));
rlocus(G);

运行该段代码,得到MWorks绘制的根轨迹图如下:

图4.2:不考虑寄生电阻------MWorks绘制根轨迹图

根轨迹本质上反映的随着比例系数K_p的变化,特征方程根的变化情况;当两个共轭根恰好位于虚轴上时,此时对应的比例系数K_p(可将此时特征根带回特征方程求出)即为其稳定的边界值(大于该值不稳定,小于该值稳定)。

编写如下MWorks程序,寻找根轨迹与虚轴的交点并带回特征方程,求出比例系数K_p稳定边界值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
% 找寻与虚轴交点
k = linspace(0, 10, 1000); % 增加点数,1000个点
[r, k]=rlocus(G,k);
real_part = real(r);
imag_part = imag(r);
% 查找实部接近零的索引
tolerance = 1e-1; % 设定阈值
idx = find(abs(real_part) < tolerance); % 找到所有交点的索引
intersections = r(idx); % 交点的复数值
% 求解临界kp
s = intersections(2);
% 系统参数
tao = 0.004;
vm = 0.5;
l = 10^(-4);
c = 6.6*10^(-4);
vin = 15;
r = 10;
syms kp; % 定义符号变量
eq = tao*vm*l*c*s*s*s + tao*vm*l*s*s/r + vin*kp*tao*s + vm*tao*s + vin*kp == 0;
solutions = solve(eq, kp); % 求解
real_solutions = real(solutions); % 取实部
decimal_solutions = double(real_solutions); % 转换为小数
disp(decimal_solutions); % 显示结果

运行上述MWorks代码,得到的比例系数K_p稳定边界值结果为:0.0514,这与先前依据劳斯判据得到的结果
$$
\frac{V_{M}\tau}{V_{in}(RC - \tau)} \approx 0.0513
$$
大致一致。

而在实际情况下,需要考虑电容的寄生电阻;基于4.1节修正后的开环传递函数,代入数值可得:

$$
T(s) \approx \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{1 + C \bullet ESRs}{LCs^{2} + \frac{L}{R}s + 1}V_{in} \approx \frac{30K_{p}(s + 250)(1 + 7.0710^{- 5}s)}{s(6.610^{- 8}s^{2} + 10^{- 5}s + 1)}
$$
根据该开环传递函数可知,该系统:

具有2个零点:
$$
z_{1} = 250,z_{2} \approx 1.41*10^{5}
$$
具有3个极点:
$$
p_{0} = 0,p_{1} + p_{2} = \frac{5}{33}*10^{3} \approx 151.5,p_{1} = \frac{5}{66}*10^{3} + bi,p_{1} + p_{2} = \frac{5}{66}*10^{3} - bi
$$
故有3条根轨迹,2条止于开环零点,2条止于无穷远;

根轨迹的渐近线与实轴的夹角
$$
\varphi_{a} = \pm (2k + 1)\pi(k = 0,1,2\ldots) = 180{^\circ}
$$
基于以上结果,可手绘根轨迹草图如下:

图4.3:考虑寄生电阻------手绘根轨迹草图

编写MWorks代码如下:

1
2
3
4
5
6
using TyControlSystems
s=tf('s');
c = 6.6*10^(-4);
esr = 0.107185;
G=(s+250)*(1+c*esr*s)/(s*(6.6*10^(-8)*s*s+10^(-5)*s+1));
rlocus(G);

运行该段代码,得到MWorks绘制的根轨迹图如下:

图4.4:考虑寄生电阻------MWorks绘制根轨迹图

可以看到,在考虑电容寄生电阻的情况下,根轨迹完全位于虚轴左侧,这意味着无论比例系数K_p(>0)如何变化,系统特征方程的根均位于虚轴左侧,即此情况下系统始终稳定,这与先前依据劳斯判据得到的结果也是一致的。这样的结果也充分说明,寄生电阻的加入使得系统的稳定性提高。

4.3 直流Buck变换器奈奎斯特稳定判据分析

在不考虑寄生电阻的情况下,基于3.1节得到的开环传递函数,取负载电阻R = 2欧姆,代入PI控制器参数:
$$
K_{p} = 0.05、\tau = 1\text{/}5000
$$
与三角载波幅值
$$
V_{M} = 3V
$$
可得:

$$
T(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1} = \frac{0.05 \times \left( \frac{1}{5000}s + 1 \right)}{\frac{1}{5000}s} \cdot \frac{1}{3} \cdot \frac{15 \times 10^{8}}{6.6s^{2} + 5000s + 10^{8}} = \frac{2.5 \times 10^{7}(s + 5000)}{s(6.6s^{2} + 5000s + 10^{8})}
$$
根据该开环传递函数,编写MWorks代码如下:

1
2
3
using TyControlSystems
H = tf([2.5*10^7 2.5*10^7*5000],[6.6 5000 10^8 0]);
nyquist(H);

运行该段代码,得到MWorks绘制的奈氏图如下:

图4.5:不考虑寄生电阻------MWorks绘制奈氏图图4.5:不考虑寄生电阻------MWorks绘制奈氏图

观察奈氏图图像可得:正穿越次数N+=1,负穿越次数N- =1

又由系统开环传递函数可知:系统开环右极点数P=0

故由奈奎斯特稳定判据可知:该闭环系统稳定。

图4.6:奈奎斯特稳定判据

4.4 直流Buck变换器系统波特图分析(MWorks绘制)

与4.3节使用相同参数,即开环传递函数可写为:

$$
T(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1} = \frac{2.5 \times 10^{7}(s + 5000)}{s(6.6s^{2} + 5000s + 10^{8})}
$$
根据该开环传递函数,编写如下MWorks代码绘制波特图:

1
2
3
4
using TyControlSystems
s=tf('s');
G=2.5*10^7*(s+5000)/(s*(6.6*s^2+5000*s+10^8));
bode(G);

运行该段代码,得到MWorks绘制的波特图如下:

图4.7:不考虑寄生电阻------MWorks绘制波特图

从图中可读出:剪切频率
$$
\omega_{c}≈1.49*10^{3}rad/s
$$
相位裕度
$$
γ≈102{^\circ}
$$
根据对数频率特性稳定判据可知:当相位裕度γ与增益裕度Lg同时大于零时,闭环系统稳定;而对于最小相位系统,相位裕度γ>0与增益裕度Lg>0是同时发生或同时不发生的,因此只需通过相位裕度γ即可判定闭环系统稳定情况。显然此时相位裕度大于零,表明闭环系统稳定,且稳定性较好(相位裕度较大)。

4.5 直流Buck变换器闭环负载稳定边界计算及仿真验证

基于2.2节中的电路元件参数以及输入电压恒为15V的客观事实,在给定PI控制器参数:
$$
K_{p} = 0.1、\tau = 0.004
$$
与三角载波幅值
$$
V_{M} = 0.5V
$$
的情况下,若不考虑电容寄生电阻,根据4.1节劳斯判据化简后的结果:
$$
RCV_{g}K_{p} \leq (\tau V_{M} + V_{g}K_{p}\tau)
$$
可推得系统处于稳定状态的电阻值范围应为:
$$
R \leq \frac{\tau}{C}\left( \frac{V_{M}}{V_{g}K_{p}} + 1 \right) \approx 8.08\Omega
$$
即直流Buck变换器闭环系统的负载电阻稳定边界约为8.1Ω。

为进一步通过仿真验证计算结果,建立PSIM仿真电路图如下:

图4.8:直流Buck变换器闭环控制负载电阻稳定边界PSIM仿真验证电路图

  1. 取负载电阻值R = 5Ω(小于临界值)时:

图4.9:负载电阻值R=5Ω时输出电压PSIM仿真结果

图4.10:负载电阻值R=5Ω时输出电压纹波PSIM仿真结果

  1. 取负载电阻值R = 8.1欧姆(约等于临界值)时:

图4.11:负载电阻值R=8.1Ω时输出电压PSIM仿真结果

图4.12:负载电阻值R=8.1Ω时输出电压纹波PSIM仿真结果取负载电阻值R=10Ω(大于临界值)时:

图4.13:负载电阻值R=10Ω时输出电压PSIM仿真结果

图4.14:负载电阻值R=10Ω时输出电压纹波PSIM仿真结果

通过对比三组仿真结果可以发现:取不同的负载电阻值并不会影响系统的响应速度与响应瞬时超调量,而是影响输出电压趋于稳定后的纹波波形:

  1. 当负载电阻值小于临界值时,稳定后的输出电压会有较大的震荡(负载越小,震荡幅度越大),但该震荡上没有纹波,系统处于稳定状态;

  2. 当负载电阻接近临界值(实际临界值略小于8.1Ω)时,稳定后输出电压的震荡幅度减小,但开始出现纹波,系统处于临界稳定状态;

  3. 当负载电阻大于临界值时,稳定后输出电压的震荡幅度进一步减小,但纹波幅度有所增大,系统处于不稳定状态。

4.6 考虑寄生参数直流Buck变换器波特图分析(MWorks绘制)

在实际情况下,为实现对直流Buck变换器闭环控制系统更加精准的建模,需要考虑电容的寄生电阻,基于4.1节修正后的开环传递函数(取未近似结果):

$$
T(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{\frac{1}{LC} + \frac{ESR}{L}s}{\left( 1 + \frac{ESR}{R} \right)s^{2} + \left( \frac{1}{RC} + \frac{ESR}{L} \right)s + \frac{1}{LC}}V_{in}
$$
取PI控制器参数:
$$
K_{p} = 1、\tau = 0.01
$$
三角载波幅值
$$
V_{M} = 2V
$$
负载电阻
$$
R = 2\Omega
$$
代入电路各元件参数与3.4节测算得到的寄生电阻
$$
ESR \approx 107.185m\Omega
$$
编写如下MWorks程序绘制波特图:

1
2
3
4
5
6
7
8
9
10
11
12
using TyControlSystems
s=tf('s');
vm=2;
r=2;
esr=0.107185;
c=6.6\*10\^(-4);
l=10\^(-4);
vg=15;
kp=1;
tao=0.01;
G=kp\*(tao\*s+1)\*(esr\*c\*s+1)\*vg/(tao\*s\*vm\*(l\*c\*(1+esr/r)\*s\*s+(l/r+esr\*c)\*s+1));
bode(G);

运行该段代码后绘制出的波特图如下:

图4.15:考虑寄生电阻------MWorks绘制波特图

从图中可读出:剪切频率
$$
\omega_{c}≈1.29*10^{4}rad/s
$$
相位裕度
$$
γ≈50{^\circ}
$$
根据对数频率特性稳定判据可知,此时相位裕度γ>0,表明闭环系统稳定。

4.7 PSIM仿真扫频波特图

取与4.6节相同的参数,利用PSIM搭建扫频电路进行仿真得到波特图:

图4.16:考虑寄生电阻------PSIM扫频仿真电路图

图4.17:考虑寄生电阻------PSIM扫频仿真结果

从图中可读出:剪切频率
$$
\omega_{c} \approx 1997Hz \approx 1.25*10^{4}rad/s
$$
相位裕度
$$
γ≈50{^\circ}
$$
扫频仿真结果与MWorks根据系统开环传递函数绘制的波特图基本一致。

若在仿真电路图中加入锁存器与零阶保持器:

图4.18:考虑寄生电阻------加入锁存器与零阶保持器后PSIM扫频仿真电路图

图4.19:考虑寄生电阻------加入锁存器与零阶保持器后PSIM扫频仿真结果

图4.19:考虑寄生电阻——加入锁存器与零阶保持器后PSIM扫频仿真结果

从图中可读出:剪切频率
$$
\omega_{c}≈1997Hz
$$
相位裕度
$$
γ≈18{^\circ}
$$
可以发现,在仿真中加入锁存器与零阶保持器后,剪切频率基本未发生改变,图线也大致与MWorks通过传递函数得到的波特图相同,但相位裕度有所降低,系统仍然保持稳定。

4.8 本章小结

本章主要对于直流Buck变换器闭环控制系统的控制性能进行了详细的分析,特别是针对系统的稳定性问题,从系统的开环传递函数入手,对于考虑电容寄生电阻与否的两种不同情况,在时域上利用劳斯稳定判据与根轨迹法,讨论在负载电阻R与PI控制器时间常数τ给定的情况下,PI控制器比例系数K_p的稳定边界;在频域上依据奈奎斯特稳定判据(基于奈奎斯特图)与对数频率特性稳定判据(基于波特图,由MWorks绘制与PSIM扫频仿真得到,两者结果高度吻合),通过系统相位裕度γ判断闭环系统稳定性,同时在PI控制器参数给定的情况下讨论负载电阻R的稳定边界,并搭建PSIM仿真电路对不同负载电阻下闭环系统的稳定性进行验证,仿真结果与理论推导结论基本一致。

5 直流Buck变换器闭环控制实验

5.1 KEIL5软件环境安装及创建

Keil 5是一款集成开发环境(IDE),它以其强大的功能和丰富的特性,在嵌入式系统开发领域占据了重要的地位。在本项目中,需要借助该IDE编写主控代码并烧录至STM32芯片中,使其能够在闭环控制系统中正常发挥控制作用。

下载助教在课程群内上传的安装程序压缩包并解压,文件夹内包含如下文件:

图5.1:Keil5 MDK安装程序压缩包解压结果

双击安装程序”MDK524a.EXE”,一路点击”NEXT>>“(注意勾选同意许可证条款与指定软件安装位置)即可完成Keil5软件安装;软件安装完成后会自动运行,关闭后需要重新以管理员身份运行Keil5(右键->以管理员身份运行),随后进入keygen_new2032文件夹,双击运行破解程序”keygen_new2032.exe”,输入Keil5软件内”File->Licence Management…”里的CID并选择ARM,点击”Generate”并将生成的序列号复制到Keil5软件内”File->Licence Management…”下方的LIC一栏,点击”ADD LIC”即可完成破解。

图5.2:Keil5 MDK软件破解流程图5.2:Keil5 MDK软件破解流程

图5.2:Keil5 MDK软件破解流程图5.2:Keil5 MDK软件破解流程

除此之外,针对本项目选用的STM32F103C8T6芯片,还需要安装相关芯片库:双击”支持包”文件夹中的”Keil.STM32F1xx_DFP.2.2.0.pack”文件并点击”NEXT>>“即可完成安装。

完成软件环境的安装后,需要进入Keil5软件,点击上侧菜单栏中”Project->New μVision Project”新建工程,随后会进入到芯片设备的选择环节,选择芯片”STM32F103C8”并点击”OK”,在弹出的Manage Run-Time Environment对话框中再次点击”OK”即可完成工程创建。

图5.3:Keil5中STM32工程创建流程图5.3:Keil5中STM32工程创建流程图5.3:Keil5中STM32工程创建流程图5.3:Keil5中STM32工程创建流程

当然事实上这样建立的工程仍然无法正常使用,还需要引入一系列启动文件与库函数文件等,具体流程在此不详细赘述,可以参考博客:如何在keil中建立一个STM32F10x完整工程。至此Keil5的软件环境安装及工程创建过程已经全部完成,再额外导入一些系统辅助函数文件(如”Delay.c”等),即可在”main.c”文件中进行闭环控制程序的编写了。

5.2 ADC采样及PWM生成原理

ADC(Analog-to-Digital Converter),即模拟到数字转换器,主要用于将连续传输的模拟信号转换为数字信号,便于数字系统(如中央处理器CPU、微控制器MCU等)对传输信息进行快速处理和分析。

图5.4:STM32F103C8T6芯片参考手册中ADC相关模块

采样是指ADC在一定时间间隔内对连续变化的模拟信号进行取样,得到一系列离散的样本点,实现在有限采样率条件下,无失真还原信号波形信息。采样率决定了每秒采集的样本量,通常单位为Hz;其必须满足奈奎斯特采样定理(大于信号最高频率的两倍),否则会产生混叠。

由于数字信号本身不具有实际意义,仅仅表示一个相对大小,故任何一个模数转换器都需要一个参考模拟量作为转换的标准,比较常见的参考标准为最大的可转换信号大小,而输出的数字量则表示输入信号相对于参考信号的大小。在STM32单片机中,ADC为12位,即单片机读取的ADC值应在0~4095范围内,这样的ADC值与0~3.3V的输入电压值之间存在线性对应关系(若输入电压范围超出0~3.3V,则需要在输入ADC引脚前加入电阻分压和放大器等外围电路,在2.3.4小节中有详细介绍该部分采样电路)。

PWM(Pulse Width Modulation),即脉冲宽度调制,是一种通过调节脉冲信号的宽度(即占空比,高电平持续时间占整个周期的比例)来控制输出信号平均值的方法。在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量。简单而言,PWM可以视为一种DA(数字->模拟)转换,通过产生PWM波形这一数字信号等效地实现了模拟信号的输出。

图5.5:PWM信号示意图

PWM实现的原理是:通过锯齿波/三角波(载波)所需要合成的波形(调制波)进行比较,然后确定PWM所需要输出的极性,锯齿波从比较器的反相端端输入,当大于参考电压时输出与锯齿波相反的极性,而当锯齿波从比较器同相端输入,当大于参考电压时输出与锯齿波相同极性。

图5.6:PWM实现流程:黄色线------计算器溢出频率(ARR),即PWM更新频率;红色线------捕获比较寄存器(CRR)的值;蓝色线------计数器(CNT)的值

在Keil工程中,基于以上PWM生成原理编写函数文件”PWM.c”,其中包含了生成PWM波形所需的全部函数。实际运行时,PWM波形是通过定时器的计数和输出比较模块的配合生成的,其具体过程如下:

(1)计数器计数:TIM2定时器按照内部时钟驱动,从0计数到ARR(2999),然后重新清零,重复循环。

(2)比较与输出:定时器每次计数到CCR(1500)时,切换PWM输出的电平状态:从计数开始到计数器值为CCR时,输出高电平;从计数器值为CCR到ARR,输出低电平。

(3)占空比:由CCR与ARR的比值决定。程序中初始设定CCR=1500,ARR=2999,故可计算得出占空比为
$$
\frac{CCR}{ARR + 1} = 50%
$$
同时在主程序的控制过程中还可通过函数PWM_SetCompare1动态设置CCR值,从而实时改变占空比,进而影响输出的等效电压值。

5.3 基本PI控制理论及程序

PI控制器是比例-积分控制器的一种,其核心是通过调节控制变量(输出电压),使系统输出接近目标值,是在控制工程中技术成熟、理论完善、应用最为广泛的一种控制策略。

对于PI控制器而言,其控制量的计算公式为:
$$
u(t) = K_{P} \cdot \mathbb{e}(t) + K_{I}\int\mathbb{e}(t)\mathbb{d}t
$$
其中:

(1)e(t):当前时刻系统输出电压与参考电压之间的误差值;

(2)K_P:比例增益系数,用于快速响应;

(3)K_I:积分增益系数,用于消除稳态误差;

(4)u(t):待控制变量——系统输出电压。

写成微分方程形式则为:
$$
\left{ \begin{aligned}
& \dot{x} = K_{I}v \
& y = K_{p}v + x
\end{aligned} \right.
$$
根据传递函数定义,其传递函数可表示为:

$$
G_{c}(s) = K_{p} + K_{I}\frac{1}{s} = \frac{K_{p}(\tau s + 1)}{\tau s}
$$
其中积分时间常数
$$
\tau = \frac{K_{p}}{K_{I}}
$$
PI控制器具有以下特点:

  1. 从时域上看,只要存在偏差,积分就会不停对偏差积累,因此稳态时误差一定为零;

  2. 比例与积分动作都是对过去控制误差进行操作,不对未来控制误差进行预测,限制了控制性能;

  3. PI调节将比例调节的快速反应与积分调节消除静差的特点结合,主要用于改善控制系统的稳态性能。

图5.7:具有PI控制器的控制系统控制框图

在Keil工程中的主函数文件”main.c”中,其main函数在进行了PWM、ADC与串口等必要的初始化后,在while循环中反复运行update_voltage_reference与control_buck两个函数,其中前者主要是进行参考电压的动态更新,而后者中则包含了PI控制的主要逻辑,部分核心代码如下:

1
2
3
4
error = voltage_ref \* 2500 \* 1000 / 1050 / 20000 - voltage_fb;
integral += error;
control_signal = KP \* error + KI \* integral;
last_error = error;

该段代码主要按照如下流程实现PI控制:

  1. 误差计算:error
    $$
    e(t) = V_{ref} - V_{fb}
    $$

其中V_ref(voltage_ref)为函数update_voltage_reference中设定的目标电压,而V_fb(voltage_fb)为系统输出并反馈至控制器的实际电压。

  1. 积分计算:
    $$
    integral = \int\mathbb{e}(t)\mathbb{d}t
    $$
    为累加误差,用于消除系统的稳态误差(输出电压voltage_fb与参考电压voltage_ref的差值error)。

  2. PI控制量:control_signal
    $$
    u(t) = K_{P} \cdot \mathbb{e}(t) + K_{I}\int\mathbb{e}(t)\mathbb{d}t
    $$
    为最终控制信号,用于调节PWM占空比。

除此之外,为防止占空比超出合理范围,还对控制信号control_signal进行约束:

1
2
3
4
if (control_signal \> 0.8)
control_signal = 0.8;
else if (control_signal \< 0.2)
control_signal = 0.2;

除了PI控制的核心逻辑外,在主控函数control_buck中还实现了其他功能:

  1. 模拟信号采集得到系统输出并反馈至控制器的实际电压V_fb(voltage_fb):
1
2
voltage_fb = (float)AD_GetValue()\*3.3/4096;
trueVoltage = voltage_fb \* 1050 \* 20000 / 2500 / 1000;
  1. 根据PI控制结果,实时更新PWM占空比:
1
2
3
duty_cycle = control_signal \* PWM_PERIOD;
i = (int)duty_cycle;
PWM_SetCompare1(i);
  1. 通过串口发送调试信息,用于监控采样值和控制效果:
1
Serial_Printf(\"%d,%.2f\\r\\n\", sample_index, trueVoltage);

5.4 闭环PI稳压调控输出(8V、10V)

5.3节中对于主控函数control_buck进行了详细的解析,整个闭环PI调控过程都由此函数完成,在此不重复赘述;而对于函数update_voltage_reference而言,该函数实现了电压的切换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void update_voltage_reference(void){
time_counter += 40; // 每次调用增加计时器值
if (time_counter \>= 10000) // 达到10秒{
time_counter = 0; // 重置计时器
if (voltage_state == 0){
voltage_ref = 10.0; // 切换到10V
voltage_state = 1; // 更新状态
}
else{
voltage_ref = 8.0; // 切换到8V
voltage_state = 0; // 更新状态
}
}
}

可以看到,该函数每隔10秒就对于目标电压voltage_ref进行一次切换,由10V切换为8V,再由8V切换回10V,循环往复。该函数主要用于模拟动态负载或参考值变化的情景,以测试控制器再目标电压变化时的响应性能。

事实上,在main函数中,在进行各项初始化(PWM、ADC、串口等)后,while循环中只有参考电压切换函数update_voltage_reference与PI主控函数control_buck反复作用,也正是这两个函数使得该直流Buck变换器闭环控制PI系统能够交替稳压输出8V或10V的电压。

5.5 闭环参考电压8V-10V连续跳变实验与分析(仿真+实验)

在直流Buck变换器闭环PI控制系统参考电压8V-10V连续跳变实验时,重点对于输出电压分别为8V与10V的两种情况下单片机的PWM输出波形进行测试,通过观察其占空比反映其输出电压情况:

  1. 先使用仿真器给单片机供电,以调试PWM波形输出是否正常;

  2. 将上述闭环控制程序放入Keil工程中,成功编译后烧录至单片机内,将单片机(最小系统板)插入电路板上预留的引脚接口处并上电测试,使用示波器或者上位机观测电路板PA8端口输出PWM波形的占空比。

图5.8:参考电压设置为10V时的PWM波形

图5.9:参考电压设置为8V时的PWM波形

可以看到,在参考电压设置为8V时,单片机输出的PWM占空比明显小于参考电压为10V时的结果。

为进一步观察参考电压连续跳变时的动态响应过程,进行了对应的PSIM电路仿真(仿真参数:kp=0.12,τ_i=1/5000,R=2Ω),其中跳变效果通过在直流参考电压后接入一个周期方波信号实现,跳变周期为0.1s:

图5.10:参考电压8V-10V连续跳变PSIM仿真电路图

图5.11:参考电压8V-10V连续跳变PSIM仿真动态响应结果

可以看到,跳变瞬间系统的动态响应较快(约为0.01s)且输出电压稳定后震荡幅度较小,说明该参数下系统具有较好的动态响应性能。

5.6 本章小结

本章主要介绍了对于直流Buck变换器PI闭环控制的整体实验流程,从STM32单片机编程环境的搭建,到根据PWM生成原理编写PWM的初始化函数及占空比实时调控函数,再到根据PI控制的基础理论编写对应的主控代码,在参考电压连续跳变(8V-10V)的情况下调控闭环PI的稳压输出,最后将代码烧录进行实际实验,测试单片机的PWM输出调控情况,并通过仿真观察参考电压连续跳变时的动态响应情况。通过该闭环控制实验,充分证明了PI闭环控制系统对于直流Buck变换器具有良好的控制效果。

6 复合PI控制直流Buck变换器

6.1 PI+超前校正(复合PI)分析设计

为提高PI闭环控制系统暂态响应速度,可以在原有的控制系统中加入一些其参数可以根据需要而改变的控制器,即对于系统进行校正,从而使整个系统的频率特性发生变化。本项目中针对直流Buck变换器的PI闭环控制系统,采用PI+串联超前校正的复合PI控制器,以进一步提升系统性能指标。

图6.1:串联超前校正环节示意图

串联超前校正的核心思想是,将补偿中心ω_ m设计为期望剪切频率ω_ c处,从而提升剪切频率(补偿中心)所对应相位,并利用补偿处增益放大(+20)来改善剪切频率处斜率。具体的设计流程如下:

  1. 根据静态性能指标,确定开环增益K

  2. 为确定校正环节的设计方向,根据所确定的开环增益,画出系统固有部分G_s(s)的波特图,并计算其剪切频率ω_ c1与相位裕度γ_ 0;

  3. 根据要求的相位裕度γ,确定
    $$
    \varphi_{m} = \mathrm{\Delta}\varphi = \gamma - \gamma_{0} + \varepsilon,\varepsilon =5° - 25°
    $$

    $$
    \varphi_{m} < 60{^\circ}
    $$
    说明可采用串联超前校正,即
    $$
    \varepsilon \approx \varphi(\omega_{c1})-
    \varphi(\omega_{c2}),\omega_{c1}为原穿越频率,\omega_{c2}为校正后穿越频率
    $$

  4. 由φ_m确定α:
    $$
    $\alpha = \frac{1 + \sin\varphi_{m}}{1 - \sin\varphi_{m}} > 1
    $$

  5. 令校正后剪切频率
    $$
    \omega_{c2} = \omega_{m} = \frac{1}{T\sqrt{\alpha}}
    $$

  6. 画出校正后系统的波特图,并验算相位裕度是否满足要求:若满足要求,则需要在原有PI控制器前增加环节
    $$
    G_{c}(s) = \frac{\alpha Ts + 1}{Ts + 1}
    $$
    若不满足要求,需增大ε的值,从第(3)步重新开始计算。

图6.2:超前校正环节一般设计流程

对于比例积分PI控制器,一般采用如下的超前校正方式:

图6.3:PI控制器超前校正电路原理图

图6.3:PI控制器超前校正电路原理图

针对直流Buck变换器的PI闭环控制系统,给定PI控制器参数:
$$
K_{p} = 0.8、\tau = 1/5000
$$
负载电阻
$$
R = 2\Omega
$$
与三角载波幅值
$$
V_{M} = 1V
$$
根据3.1节给出的系统开环传递函数(不考虑电容寄生电阻):
$$
T(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1}
$$
在MWorks中编写如下代码绘制校正前的波特图:

1
2
3
4
5
6
7
8
9
10
11
using TyControlSystems
s=tf('s');
vm=1;
r=2;
c=10^(-4);
l=10^(-4);
vg=20;
kp=0.8;
tao=1/5000;
G=kp*(tao*s+1)*vg/(tao*s*vm*(l*c*s*s+(l/r)*s+1));
bode(G);

绘制出校正前的波特图:

图6.4:校正前MWorks绘制波特图

从图中可读出:校正前系统的剪切频率
$$
\omega_{c1}≈4.20*10^{4}rad/s
$$
相位裕度
$$
\gamma_{0}≈0{^\circ}
$$
此时系统处于临界稳定状态。

为使得校正后的相位裕度
$$
\gamma > 30{^\circ}
$$
可确定需增加的相位裕度
$$
\varphi_{m} = \mathrm{\Delta}\varphi = \gamma - \gamma_{0} + \varepsilon = 30{^\circ} - 0{^\circ} + 15.5{^\circ} = 45.5{^\circ} < 60{^\circ}
$$
满足超前校正要求,进而可以确定

$$
\alpha = \frac{1 + \sin\varphi_{m}}{1 - \sin\varphi_{m}} \approx 6
$$
又因为系统周期
$$
T = \frac{1}{70000}s
$$
从而可以确定增加的超前校正环节的开环传递函数为:

$$
H(s) = \frac{\frac{6}{70000}s + 1}{\frac{1}{70000}s + 1}
$$
这意味着校正后整个闭环控制系统的传递函数变为
$$
T^{‘}(s) = T(s)H(s)
$$
可在上述MWorks代码的基础上增加下列代码以实现对于校正后闭环控制系统波特图的绘制:

1
2
3
H=(6*s/70000+1)/(s/70000+1);
G1=G*H;
bode(G1);

绘制出校正后的波特图:

图6.5:校正后MWorks绘制波特图

从图中可读出:校正后系统的剪切频率
$$
\omega_{c2}≈8.71*10^{4}rad/s
$$
相位裕度
$$
\gamma≈31{^\circ} > 30{^\circ}
$$
相位裕度较校正前有明显提升且满足系统对品质指标的要求,校正正确。

6.2 复合PI控制PSIM仿真(校正前后参考电压变化时输出电压分析)

首先通过PSIM扫频仿真对先前波特图的MWorks绘制结果进行验证:

图6.6:超前校正控制PSIM扫频仿真电路图

图6.7:校正前PSIM扫频仿真结果

图6.8:校正后PSIM扫频仿真结果

从图中可读出:校正前系统的剪切频率
$$
\omega_{c1}≈6554Hz
$$
相位裕度
$$
\gamma_{0}≈0{^\circ}
$$
校正后系统的剪切频率
$$
\omega_{c2}≈14126Hz
$$
相位裕度
$$
\gamma≈35{^\circ}
$$
与MWorks绘制的波特图结果基本一致。

为反映超前校正的引入对于系统暂态响应速度的提升效果,搭建PSIM仿真电路,分析校正前后参考电压跳变瞬间的输出电压响应情况:

图6.9:超前校正控制参考跳变PSIM仿真电路图

图6.10:校正前系统暂态响应PSIM仿真结果

图6.11:校正后系统暂态响应PSIM仿真结果

对比校正前后参考电压跳变瞬间的输出电压暂态响应PSIM仿真结果,可以发现:校正前系统暂态响应时间约为0.034s,而加入超前校正环节后系统暂态响应时间为0.0006s,较校正前有显著缩短,说明超前校正的引入有效提升了系统的动态响应特性(快速性)。

6.3 PI+微分环节D(PID)分析设计

除增加串联超前校正环节设计外,还可以通过在原有PI控制器基础上加入微分环节D实现PID闭环控制来提升系统的稳定性与暂态响应速度。事实上,在PID控制器的三个环节中,比例环节P主要负责动态性能的提升,积分环节I主要负责稳态精度的提升,而引入微分环节D则可进一步改善系统的稳定裕度以提升系统稳定性。

图6.12:PID控制器电路原理图

与PI控制器类似,通过对PID控制器的微分方程组进行拉普拉斯变换,可以得到其传递函数为:
$$
G_{c}(s) = \frac{K_{p}(\tau_{i}s + 1)}{\tau_{i}s} + \tau_{d}s
$$
其中τ_ i为积分时间常数,而τ_ d为微分时间常数。

接下来将通过MWorks绘制波特图,配合PSIM的扫频仿真结果,从相位裕度的角度来反映微分环节D的引入对于系统稳定性的提升:

对于PI控制器,给定控制器参数:
$$
K_{p} = 0.12、\tau = 1/5000
$$
负载电阻
$$
R = 2\Omega
$$
与三角载波幅值
$$
V_{M} = 1V
$$
根据3.1节给出的系统开环传递函数(不考虑电容寄生电阻):
$$
T(s) = \frac{K_{p}(\tau s + 1)}{\tau s}\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1}$
$$
在MWorks中编写如下代码绘制波特图:

1
2
3
4
5
6
7
8
9
10
11
12
using TyControlSystems
s=tf('s');
vm=1;
r=2;
esr=0.107185;
c=6.6*10^(-4);
l=10^(-4);
vg=15;
kp=0.12;
tao=1/5000;
G=kp*(tao*s+1)\*(esr*c*s+1)*vg/(tao*s*vm*(l*c*(1+esr/r)*s*s+(l/r+esr*c)*s+1));
bode(G);

图6.13:PI控制器MWorks绘制波特图

同时在PSIM中搭建扫频仿真电路,通过扫频仿真结果验证MWorks绘制波特图的正确性:

图6.14:PI控制器PSIM扫频仿真电路图

图6.15:PI控制器PSIM扫频仿真结果

从图中可读出:PI闭环控制系统剪切频率
$$
\omega_{c}≈6.78*10^{3}rad/s \approx 1105Hz
$$
相位裕度
$$
\gamma$≈$10{^\circ}
$$
且MWorks绘制波特图与PSIM扫频仿真结果基本一致。

对于加入微分环节D后的PID控制器,给定控制器参数:
$$
K_{p} = 0.12、\tau_{i} = 1/5000、\tau_{d} = 0.00001
$$
负载电阻
$$
R = 2\Omega
$$
与三角载波幅值
$$
V_{M} = 1V
$$
根据3.1节给出的系统开环传递函数(不考虑电容寄生电阻)并将其中PI控制器的传递函数替换为PID控制器的传递函数,可得到PID闭环控制系统的开环传递函数:
$$
T(s) = (\frac{K_{p}(\tau_{i}s + 1)}{\tau_{i}s} + \tau_{d}s)\frac{1}{V_{M}}\frac{V_{in}}{LCs^{2} + \frac{L}{R}s + 1}
$$
在MWorks中编写如下代码绘制波特图:

1
2
3
4
5
6
7
8
9
10
11
12
13
using TyControlSystems
s=tf('s');
vm=1;
r=2;
esr=0.107185;
c=6.6*10^(-4);
l=10^(-4);
vg=15;
kp=0.12;
tao=1/5000;
taod=0.00001;
G=(kp*(tao*s+1)+taod*tao*s*s)*(esr*c*s+1)*vg/(tao*s*vm*(l*c*(1+esr/r)*s*s+(l/r+esr*c)*s+1));
bode(G);

图6.16:PID控制器MWorks绘制波特图

同时在PSIM中搭建扫频仿真电路,通过扫频仿真结果验证MWorks绘制波特图的正确性:

图6.17:PID控制器PSIM扫频仿真电路图

图6.18:PID控制器PSIM扫频仿真结果

从图中可读出:PID闭环控制系统剪切频率
$$
\omega_{c}≈6.78*10^{3}rad/s \approx 1026Hz
$$
相位裕度
$$
\gamma≈36{^\circ}
$$
且MWorks绘制波特图与PSIM扫频仿真结果基本一致。

通过对比PI和PID控制下的波特图与扫频结果可以发现,相比于PI控制器,PID控制下的直流Buck变换器闭环系统具有更高的相位裕度,这意味着在加入微分环节D之后,系统具有更好的稳定性。

6.4 PID控制PSIM仿真(校正前后R变化时输出电压分析)

为反映微分环节D的引入对于系统稳定性与响应速度的提升效果,搭建PSIM仿真电路,分析PI与PID控制器在负载电阻R不同时输出电压响应情况:

图6.19:PI控制器动态响应PSIM仿真电路图

图6.20:PI控制器负载电阻R=2Ω时动态响应PSIM仿真结果

图6.21:PI控制器负载电阻R=5Ω时动态响应PSIM仿真结果

图6.22:PID控制器动态响应PSIM仿真电路图

图6.23:PID控制器负载电阻R=2Ω时动态响应PSIM仿真结果

图6.24:PID控制器负载电阻R=5Ω时动态响应PSIM仿真结果

对比PI与PID控制器在负载电阻R不同时的输出电压暂态响应PSIM仿真结果,可以发现:当负载电阻R=2Ω时,在原有的PI控制器闭环控制下,系统的暂态响应时间约为0.008s,而加入微分环节D后,系统在PID控制器的闭环控制下暂态响应时间为0.004s,这意味着在加入微分环节D后,系统的动态响应特性得到一定提升(响应时间缩短50%);除此之外,在相同的控制器作用下,随着负载电阻阻值由2Ω增大到5Ω,系统的输出电压在稳定后的振荡幅度明显减,但此时暂态响应时间也明显增加(增加近两倍)。

6.5 本章小结

本章主要介绍了基于PI控制器的校正设计,对于直流Buck变换器的闭环控制而言,分别采用在原有控制系统上增加超前校正环节与微分环节D的两种校正方式实现复合PI控制,通过MWorks绘制波特图与PSIM扫频仿真,反映校正环节的引入对于相位裕度即系统稳定性的提升效果,并结合PSIM电路仿真的输出电压结果,观察校正环节的引入对于系统暂态响应速度即动态响应特性的提升效果。仿真实验结果表明,对于PI控制器的校正设计(超前校正/引入微分环节形成PID控制)在提升系统稳定性与动态响应快速性方面取得了良好的效果。

7 基于直流电源调控的自动调光控制设计

该系统的主要功能是将灯的电压通过直流Buck变换器完成闭环PID控制,将输出电压控制在0-15V范围内。系统主要包含电量控制与亮度控制两个部分,其中电量控制由STM32数字控制器通过电压控制信号调节Buck变换器输出电压;亮度控制则通过光敏传感器感知外界光强,并将反馈信号传递给STM32,实现对LED灯源亮度的动态调整,从而形成电压与亮度的双闭环调节系统。

图7.1:基于直流电源调控的自动调光系统整体架构

基于该架构,系统设计的基本流程如下:

  1. 搭建Buck电路控制系统,用于调控LED灯源;

  2. 使用开发工具Keil5编写STM32控制程序,实现PWM信号生成和调节;

  3. 将光敏传感器的输出连接到STM32的ADC(模数转换器)端口;

  4. 设计闭环控制系统并编写主控程序,实现根据光照强度自动调节PWM占空比,从而控制LED灯光的亮度;

  5. 调试系统,过程中实时监测电路工作状态,确保电路安全稳定运行且光照强度变化时LED灯亮度能够迅速进行响应调整;

  6. 对系统功能进行测试与优化,验证系统的响应速度和调光精度。

图7.2:基于直流电源调控的自动调光系统设计流程

除此之外,在系统基本功能的设计实现基础上,还进行了基于无线通信的远程控制这一拓展功能设计,将调光系统与蓝牙无线通信模块结合,实现通过手机应用对LED灯进行调光控制,增加系统的便携性和灵活性。

接下来将分模块具体阐述自动调光控制系统各功能设计的详细过程。

7.1 默认电压值设置

在系统上电之后,输出电压默认设置为9V。之所以选择9V作为默认值,是因为这一电压能够在不消耗过多能量的情况下提供足够的亮度,同时避免由于过高或过低电压对系统性能的影响。

具体来说,9V的默认值具有以下几个优点:

(1)能耗平衡:相较于较低电压(5V),9V能够提供更高的亮度输出,同时不会像满电压(15V)那样带来显著的能量消耗,适合作为常规环境下的起始亮度。

(2)避免电压冲击:在系统刚启动时,设定较高的默认电压可能会导致电流冲击,影响电源和负载的寿命。9V作为中间值,有效降低了这种风险。

(3)用户体验优化:默认电压为9V时,灯光亮度适中,避免了开机过亮或过暗对用户造成的不适,同时为后续手动或自动调节提供了便利。

(4)环境适应性:在普通家庭或办公室环境下,9V的亮度通常能满足基本照明需求,而无需立即调整,增强了系统的即用性。

此外,9V的默认值通过主程序固化设置,确保每次系统启动时都能快速恢复到该默认电压值。这一设置不仅提高了系统的稳定性,还为后续用户操作提供了可靠的初始状态。设计中对默认电压的选择经过多次实验验证,综合考虑了实际照明需求和电源性能,最终确定了这一合理数值。

7.2 蓝牙接口通信控制

在本系统中,蓝牙模块被用作与手机或其他移动设备的通信桥梁,用户可通过蓝牙实现对灯光的远程控制,增强系统的操作便利性和用户体验。蓝牙模块选用低功耗蓝牙(Bluetooth Low Energy, BLE)技术,保证了通信的可靠性和功耗优化。具体而言,蓝牙通信具有以下优点:

  • 便捷性:用户可通过手机远程调节灯光,无需物理接触,尤其适合家庭场景。

  • 可扩展性:蓝牙接口支持更多功能指令的扩展,如灯光模式切换等。

  • 低成本实现:蓝牙模块硬件成本低,结合STM32可轻松实现稳定通信。

以下是蓝牙接口通信功能的详细设计:

  1. 硬件接口设计
  • 蓝牙模块选型:选用HC-05蓝牙模块,该模块具有支持串行通信(UART)功能,能与STM32单片机直接连接;模块通过UART接口与STM32的USART1通道连接,波特率设置为9600bps以平衡数据传输速率与稳定性。

  • 引脚连接:蓝牙模块的TX和RX引脚分别连接STM32的RX和TX引脚,模块的VCC和GND引脚分别接单片机供电端和地线,保证模块稳定供电。

  1. 软件功能设计

蓝牙通信通过STM32主程序实现对蓝牙模块接收指令的解析和响应。指令的解析流程大致是这样的:主程序中设定一个蓝牙接收缓冲区,用于存储用户发送的指令;当蓝牙模块接收到数据时,触发中断,将数据写入缓冲区;程序定时轮询缓冲区,并根据指令类型解析执行以下指令以实现功能,包括灯的开关、亮度百分比调整,以及环境光自动调节的触发:

  • “on”/“off”指令:通过电压直接跳转的方式实现即时开关

“on”:将输出电压设定为最大值15V,点亮灯光;

“off”:将输出电压设定为最小值0V,关闭灯光。

  • 亮度调整指令:接收一个0-100的整数,表示灯光亮度为15V的百分比。程序计算目标电压时依据公式:
    $$
    V_{target} = \frac{百分比指令}{100} \times 15
    $$
    调用PWM控制函数调整Buck变换器的输出电压至目标值,实现实时亮度调节。

  • “LL”指令:触发光敏传感器,启动环境光度自动调节模式(详见7.3节)。

  1. 通信协议设计

蓝牙通信基于简单的ASCII协议,用户可通过手机APP或终端工具发送指令。每条指令均以换行符\n结尾,便于解析。以下是通信协议的具体格式:

  • “on\n”:开启灯光。

  • “off\n”:关闭灯光。

  • “50\n”:将亮度设置为50%。

  • “LL\n”:启动环境光度自动调节模式。

通过蓝牙接口通信,系统不仅支持手动调节灯光,还为后续的智能化功能扩展奠定了基础。

7.3 基于光敏传感器的调光功能

本系统采用光敏传感器对环境光强进行实时检测,并基于检测值动态调整灯光的亮度,提供适应不同场景的自动调光功能。通过光敏传感器实现调光,既可以减少用户手动调整灯光的频率,还可以自动优化输出电压,适用多种场景需求。通过闭环控制机制,确保灯光的输出电压与环境光强度的变化相匹配,为用户提供更舒适、高效的照明体验。以下为光敏传感器调光功能的详细设计与实现过程:

  1. 硬件接口设计
  • 光敏传感器选型:选用光敏电阻模块,输出光强的模拟信号值。传感器将环境光强信号转换为电压信号,供STM32单片机的ADC模块读取。

  • 传感器连接方式:光敏传感器的模拟输出端(AO口)连接到STM32单片机的PA5引脚,PA5被配置为ADC输入通道。供电引脚(VCC)连接单片机的3.3V供电,地线(GND)连接系统地,保证传感器的稳定工作。

  1. 软件功能设计
  • 数据采集与转换:STM32通过ADC模块以一定采样频率读取光敏传感器的模拟信号,模拟值范围为0到4095,对应电压范围为0到3.3V;可通过公式
    $$
    V_{target} = \frac{ADC值}{4095} \times 15
    $$
    将光强模拟值映射为输出电压目标值,该线性映射关系保证了灯光亮度的平滑过渡,避免突然变化对用户产生视觉不适。

  • 调光逻辑与策略:当ADC值低于预设阈值(如1024)时,认为环境光较暗,此时系统逐步增加输出电压,以提高灯光亮度补偿环境光;当ADC值高于预设阈值(如3072)时,认为环境光较亮,此时系统逐步降低输出电压,以避免浪费电能或造成视觉不适;当光强处于中等范围时(ADC值1024到3072),输出电压以线性比例动态调节,保持环境与灯光亮度的适应性平衡。

  • PID闭环控制:根据光敏电阻值计算目标电压,并作为参考值输入PID控制器;PID控制器实时计算实际输出电压与目标电压之间的误差,并调整PWM信号占空比控制Buck变换器,确保输出电压快速稳定地收敛到目标值。

  • 状态反馈与异常处理:当光敏传感器信号异常(如ADC值恒定不变或超出有效范围)时,系统进入保护模式,将输出电压设定为安全值9V,并通过蓝牙模块通知用户;传感器数据每次读取后均存储在缓冲区中,并定期更新,避免因单次采样噪声造成调光不稳定。

为进一步验证调光功能的灵敏度,需要测试光敏传感器在不同光强条件下的响应时间和精度,确保其采集的光强信号与实际环境光强相符。经过调试,光敏电阻灵敏度较高,但光敏电阻的朝向会对空间中同一点的光敏传感有不同的值。解决方式为固定光敏电阻朝向位置,使其主要接收来自上方的环境光。除此之外,还需要验证在自动调光模式下,灯光亮度调整是否与环境光变化相匹配。经过我们的实验,该灯泡在8V电压以下不会发光,且为了保护电路和用户,我们将最高输出电压15V通过程序限制在12V,于是我们将光亮百分比线性映射公式修改为:
$$
V_{target} = \frac{百分比指令}{100} \times 4 + 8
$$
从而使得亮度百分比更符合实际;由于百分比电压现在被限制在8-12V,光敏需要更多的外边界,从原先0-15V电压的计算,改为5-15V电压的线性映射,光敏电阻映射公式也需要对应修改为:
$$
V_{target} = \frac{ADC值}{4095} \times 10 + 5
$$
此时由于光敏电阻在一般的环境光下位于1024-3072而并不会趋于极端值,且程序有保护设计,故一定可以限制输出电压在8-12V范围内。

为进一步优化用户的使用体验,还开展了用户测试实验为产品优化提供参考。实验记录了光敏传感器自动调光时用户的视觉体验,并通过调整PID参数和映射公式提高舒适性。通过光敏传感器的引入,本系统实现了基于环境光强的自动调光功能,显著提升了灯光控制的智能化水平与用户体验。

7.4 闭环PID控制原理

闭环PID控制是本系统的核心功能之一,负责根据目标输出电压值和实际输出电压值的误差,动态调节PWM信号,从而控制Buck变换器的输出电压,实现稳定、精准的调光效果。通过PID控制算法,可以使Buck变换器的输出电压始终接近目标值,无论输入电压波动、负载变化,还是环境光强条件改变,都能够保持系统的高稳定性和快速响应性。

PID控制算法主要由三部分组成:比例(P)、积分(I)、微分(D),可通过公式
$$
u(t) = K_{p}e(t) + K_{i}\int_{0}^{t}{e(\tau)d\tau} + K_{d}\frac{de(t)}{dt}
$$
计算其控制输出,其中u(t)为控制信号(用于调节PWM占空比);e(t)为当前误差值,定义为目标值与实际值之差,即
$$
e(t) = V_{target} - V_{actual}
$$
K_p,K_i,K_d分别为比例、积分、微分的调节系数,分别控制系统的响应速度、稳态误差消除能力和动态性能。

通过调节K_p,K_i,K_d三个参数,可以调整控制器的性能,从而实现系统响应特性的优化,提升系统控制效果。具体而言,三个参数对于响应输出的影响如下:

  • 比例(P)参数K_p:比例项主要控制误差对输出的直接影响,增大K_p会使系统响应更迅速,但过大可能引起震荡。本系统初始设置
    $$
    K_{p} = 0.1
    $$
    通过实验验证实现了较平滑的响应。

  • 积分(I)参数K_i:积分项通过累积误差消除稳态误差,确保输出精度。由于积分过大会导致超调或积分饱和,本系统设置
    $$
    K_{i} = 0.01
    $$

  • 微分(D)参数K_d:微分项对误差变化率进行调节,用于改善动态性能并抑制震荡。为避免过分灵敏的微分效应引入噪声,D参数设置为较小值
    $$
    K_{d} = 0.01
    $$

在本项目的自动调光系统中,PID控制器的控制流程主要分为如下几个步骤:

  1. 误差计算:STM32单片机实时采集目标电压值 V_target 和实际输出电压值 V_actual,计算误差:
    $$
    e(t) = V_{target} - V_{actual}
    $$

  2. 控制信号计算:根据误差值,通过PID公式计算控制信号u(t),调整PWM信号的占空比:
    $$
    u(t) = K_{p}e(t) + K_{i}\sum_{k = 0}^{t}{e(k)\mathrm{\Delta}t} + K_{d}\frac{e(t) - e(t - 1)}{\mathrm{\Delta}t}
    $$
    其中Δt为控制周期;∑e(k)Δt为误差累积值;(e(t)-e(t-1))/Δt为误差变化率。

  3. PWM调节:将计算得到的u(t)映射为PWM信号的占空比,直接控制Buck变换器的输出电压。占空比范围为10%至80%,对应输出电压范围为0V至15V。

  4. 反馈调整:系统持续监测实际输出电压,更新误差值并重复上述步骤,形成闭环控制。

在基于STM32单片机编写程序具体实现PID控制器算法时,还需要特别注意以下两点:

  • 采样频率:ADC采样频率需要设置为与main函数主循环频率同步,以保证控制系统对输入误差的快速响应。同时PID计算周期也应同步于ADC采样,以避免数据滞后影响调节效果。

  • 保护机制:若误差值持续过大(如超出安全范围),系统触发保护模式,因此实际实现时将PWM占空比最小设为10%,最大设为80%。

在实际调试验证时,主要对于控制系统的动态性能与抗干扰能力进行了测试与优化,以进一步提升自动调光功能的灵敏性与稳定性,从而优化用户体验:

  • 动态性能测试:在快速切换目标电压值时,观察系统的响应时间和稳定性,确保输出电压能够迅速收敛到目标值。在负载变化的情况下,验证系统能否保持输出电压的稳定。

  • 抗干扰能力测试:模拟输入电压波动和环境光强突变,测试系统的鲁棒性和PID调节效果。

最终经过多次调试与不断优化,该闭环PID控制系统实现了以下目标:

  • 高精度输出:在误差范围内快速稳定输出目标电压。

  • 快速响应:对目标值或负载变化的响应时间快,满足实际调光需求。

  • 稳定性强:在输入波动、负载变化和环境干扰下,系统保持良好的稳定性。

7.5 外观设计制作

为了使本系统不仅具有实用性,还能满足美观性和设计感的需求,我们对外观部分进行了重新设计和制作。本节详细介绍了外观设计的灵感来源、制作过程以及技术实现中的注意事项。

本系统的外观设计灵感来源于游戏《无畏契约》(Valorant)中的角色”尚博勒”(Chamber)的道具”摄像头”。这一设计概念契合科技感与现代感的视觉效果,适合科技爱好者的审美需求,同时其独特的造型也为灯具的装饰性增色。我们力求将其视觉元素融入本项目,打造一款既具功能性又有极高辨识度的灯具设备。同时在外壳设计时也结合了灯泡的散热需求,保证了灯具长时间工作时的安全性和稳定性。设计中注重可拆卸性,用户可以方便地调整灯泡朝向和光照亮度。

图7.3:产品外观设计原型

详细的制作过程如下:

  1. 原型拆解

我们首先将旧灯具的灯罩部分锯下来,仅保留其内部核心组件,包括灯泡、散热装置、电路板和连接线。为确保灯具正常运行,对保留的灯泡和电路板进行功能检测,确认其性能稳定,并清理了原型中多余的固定结构,为后续的外壳重新设计预留空间。

图7.4:灯具原型拆解部分过程图7.4:灯具原型拆解部分过程

  1. 3D建模与设计

使用Fusion 360软件,根据”尚博勒摄像头”的设计特点,建模了一个以球体和多面体造型的外壳结构。模拟摄像头的结构,在灯具上设计了圆形凹陷部分,提升灯具的科技感。外壳设计了散热格栅,与灯具散热需求相结合;外壳底部设计了支撑脚架,便于放置和移动。摄像头部分设计轨道凹槽,可旋转控制摄像头方向。

图7.5:产品外形3D建模效果图

  1. 材料选择与加工

外壳部分采用轻质耐用的 PLA 材料,利用3D打印技术制作。打印分为两个部分:上半部分为灯具主壳,下半部分为安装支架,组装后固定。3D打印完成后,外壳表面进行手工打磨并喷涂金色亚光涂料,灯罩边缘则涂上金属漆,营造高科技感。

  1. 组件安装与调试

将灯泡、电路板、散热器嵌入3D打印外壳中,通过卡扣、限位固定,确保结构稳固。为避免外壳对散热产生影响,在装配后对设备进行长时间运行测试,确保温度稳定在安全范围内。重新调整灯光的投射方向和亮度,使其与设计的外壳结构匹配,确保在不同环境下具备良好的照明效果。

图7.6:最终产品样机实物图

为进一步提升用户体验,后续将持续对产品进行深度优化,包括添加更多灯光模式(如动态光效或多色渐变)以进一步增强装饰性和互动性,或引入语音控制功能并将其与蓝牙通信相结合以提升智能化体验。

7.6 本章小结

本章介绍了基于直流电源调控的自动调光控制设计,通过蓝牙模块完成手动输出电压控制,通过光敏传感器进行环境光度自动调节,最终通过Buck变换器闭环PID控制完成灯的电压和光亮度稳定。

8 基于直流电源调控的光伏MPPT控制设计

8.1 设计背景与系统架构

光伏发电是当前清洁能源的主流之一,最大功率点跟踪(Maximum Power Point Tracking, MPPT)是光伏系统中提高效率的关键技术。由于光伏板的输出功率受到光照强度影响,不同的光照强度下最大功率点对应的输出电压不同,需要不断通过改变输出电压来寻找最大功率点(MPP)。因此,希望通过搭建一个基于直流电源调控的光伏MPPT控制系统,结合STM32单片机和Buck变换器实现光伏阵列的最大功率点跟踪,从而使得系统维持在最大功率工作,减少不必要的能量损失,有效储存其他能量转化成的电能。

图8.1:光伏发电板

在该系统中,外界光照被太阳能电池板转化为电能,经功率变换电路处理后向负载供电。STM32微控制器实时采集电池板电压、电流等参数,运行MPPT算法,生成控制信号优化功率变换电路的工作状态,确保太阳能电池板始终在最大功率点运行,提高能源利用效率。

图8.2:基于直流电源调控的光伏MPPT控制系统整体架构

8.2 实验原理

光伏板的输出功率会受到环境条件(如光照、温度)影响,这一点无法改变,但是输出功率同时也受到工作电流、电压的因素影响,因此可以通过调节负载电压,使光伏系统的功率维持在最大值,即工作在最大功率点(实现输入侧阻抗匹配)。在本课程使用的Buck变换器中,可以根据STM32输出PWM波的占空比来控制输出的电压,同时由于电路中在不断采集输出电压,从而可以通过设计相应的控制算法算法实现对于输出电压的精确闭环控制。

为了找到最大功率点,可采用扰动观察法(Perturb and Observe, P&O)算法。通过降低与增加占空比来调节输出电压,由于电阻一定,可以通过计算算出变化后的功率,同时将其与变化前的功率进行比较,若功率增大,则沿当前方向继续调整占空比;否则反向调整。在每次调整占空比后,计算功率并记录趋势,从而找到最大功率点。在本项目中,采用改进的扰动观察法,通过记录功率变化并自动调整占空比,从而快速收敛到最大功率点。

图8.3:不同光照强度对光伏板的P -V曲线

8.3 代码逻辑

在Keil5中编写的STM32主控代码主要分为如下4个主要的功能模块:

  1. 系统初始化:初始化系统时钟与ADC、PWM模块,为数据采集及控制做好准备。
1
2
3
4
SystemInit(); // 初始化系统时钟
PWM_Init(); // 初始化PWM模块
AD_Init(); // 初始化ADC模块
_enable_irq(); // 开启全局中断
  1. PWM控制模块:通过实时调控PWM输出占空比实现Buck变换器输出电压的改变,从而影响光伏阵列的工作点。值得注意的是,需要调整PWM输出频率与Buck变换器相适应,同时确保输出占空比范围在0~1范围内。
1
2
3
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 配置为PWM模式1
TIM_OCInitStructure.TIM_Pulse = 0; // 设置初始占空比为0
TIM_SetCompare1(TIM2, (uint16_t)(dutyCycle * PWM_PERIOD)); // 调整PWM占空比
  1. ADC采样模块:将光伏阵列输出的电压信号通过ADC采样转换为数字信号并读取,再通过与电阻的计算得出电流值,以用于功率计算。值得注意的是,ADC通道需与光伏阵列信号对应,同时为确保采样精度还需事先对ADC模块进行校准。

图8.4:ADC采样模块相关函数代码

  1. MPPT算法实现:根据扰动观察法原理,通过电流与电压相乘得到当前输出功率,与上个点的功率相比较,若大于上个点的功率,则继续增大占空比,增加输出电压,从而增加输出功率,若小于,则减小占空比,并在每次调整占空比后再次计算输出功率。循环往复进行上述流程,同时记录功率变化趋势及对应占空比,以寻找最大功率点。值得注意的是,程序中设置的步长STEP_SIZE值应适中,过大会导致振荡,而过小则会影响跟踪的速度,同时还应避免控制过程中PWM波形的占空比超出0~1范围。

图8.5:MPPT算法实现相关函数代码

在主循环main函数中编写总控代码对上述代码逻辑进行整合,每次循环都会测量电压与电流从而得到功率,再通过进行扰动观察法寻找最大功率点。

图8.6:main函数总控代码

8.4 实验现象

实验时,将Buck变换器、STM32开发板与光伏阵列进行连接,并确保采样与控制电路正常工作。将代码烧录到STM32开发板,并通过调整光照强度,观察功率点变化及最大功率点跟踪效果。接下来需要进行逐步调试:

  1. 使用示波器观察PWM信号及Buck变换器输出电压,验证占空比调节是否正常,并使用万用表验证电压、电流采样值是否准确;

图8.7:PWM输出占空比调试结果

图8.8:Buck变换器输出电压调试结果

可以看到,此时PWM输出占空比为50%,占空比调节正常;光伏板输出电压为5.1V,电流为0.025A,输入功率约为0.1275W。

  1. 通过调整光照强度,观察功率点变化及最大功率点跟踪效果。

图8.9:负载电压图

图8.10:负载消耗功率图

可以看到,此时所接负载为200Ω,输出电压为4.5V,输出功率约为0.12W。考虑功率损耗,光伏板输出功率约等于负载消耗功率,完成了对该光照强度下最大功率点(MPP)的准确寻找。

随后又对于该控制系统的动态跟踪性能进行进一步的测试,可以观察到,当外界环境的光照强度改变时,控制系统能快速做出响应并使光伏板输出功率达到最大(约为负载消耗功率),说明控制系统对于最大功率点的实时跟踪性能良好。

8.5 本章小结

本章主要介绍了基于直流电源调控的光伏板最大功率点追踪(MPPT)控制系统的设计,设计时主要采用递归算法,每次通过扰动当前功将率与变换前功率作比较,若大,则继续增加占空比,若小,则减少占空比,以此类推,不断循环,从而找到最大功率点。每次变化占空比的步长与延时时间影响找到最大功率点的效率,需要调试找到最佳的相应效率。同时,在步长固定的情况下,通过自适应步长控制算法也可以更有效率的寻找最大功率。通过动态追踪,显著提高了光伏发电效率。

9 总结与展望

9.1 课程小结

《自动控制原理》课程作为一门项目制课程,重点围绕经典控制相关理论知识及应用实践,有效地将理论教学与设计实践结合起来。通过课程的理论学习与项目实践,我们在实践操作中对于经典控制理论的知识有了更加深刻的理解,建立了”控制”工程观与系统性分析思维,也锻炼了自己将理论应用于工程实践的能力,以及对于控制工程问题的分析与解决能力。

该项目制课程主要分为两个项目板块:

控制基础实践项目为”直流电源控制分析与系统设计”,以直流电源这一经典工程案例串联起经典控制理论知识各版块,涵盖控制系统建模(微分方程与传递函数)、时域分析、根轨迹分析、频域分析,频域校正与PID控制等,从理论分析、仿真、实验三维度强化同一理论知识点的学习,完成直流电源控制系统的分析设计以及调试。这一部分项目在理论课程学习的同时穿插完成,主要涉及到经典控制理论的学习,并将其应用到实际的Buck变换器PI闭环控制系统中、结合实际电路参数完成的PSIM电路仿真与相应的MWorks分析计算。

综合应用实践项目为”基于电源的综合应用系统”,以直流电源驱动系统应用为综合实践项目,运用传感、闭环控制、先进控制等硬件与算法,完成基于直流电源控制的LED自动调光与光伏最大功率跟踪(MPPT)等项目实践。这一部分项目主要以实验方式开展,最终本小组顺利完成实验与设计内容并进行课程项目汇报。

图9.1:小组答辩风采展示

回顾这门课程一路走来,从课程引入与理论教学,到实际实验调试与项目设计,再到最终的项目测试与汇报,我们在理论与实践的结合中扎实掌握了经典控制理论的相关知识,并锻炼了自己的电路设计分析与实践能力。我们能取得如此的进步与成就离不开两位老师与四位助教的辛勤付出,在此再次向各位老师与助教们送上最真诚的感谢,也衷心祝愿这门课程在未来能建设得越来越好。

图9.2:课程汇报班级合影留念

9.2 课程收获及建议

通过《自动控制原理》这门课程的学习,我了解到了许多经典控制理论的相关知识:从系统的数学建模入手,微分方程与传递函数是描述一个控制系统性能的基本工具;为调整系统参数以提高控制系统的稳定性,可从时域与频域两个角度分别进行分析,时域上可以使用劳斯判据进行系统稳定性的判定并根据根轨迹找到系统的临界稳定状态,频域上可以使用奈奎斯特稳定判据或依据Bode图对系统稳定性进行分析;基于频率特性,还可以从频域校正环节的设计层面调节系统的动态响应特性。理论学习之余,课程紧密穿插了相应的仿真与电路调试实验,理论与实践的结合使得我对于这些枯燥的理论知识有了更加生动而深刻的理解。

在课程项目中,我主要负责对于闭环控制系统的理论分析、仿真验证与参数调试,以及课程项目报告绝大部分的撰写。令我印象最深刻的是,在理论部分对系统进行频率特性分析时,使用MWorks根据系统开环传递函数绘制Bode图,得到了穿越频率与相位裕度;而在根据实际电路结构在PSIM中搭建了相应的扫频电路之后,当看到运行扫频仿真得到的结果与MWorks绘制的Bode图几乎完全一致时,我深刻体会到了理论的有效性,这代表理论的分析确实可以真实地指导实际控制系统的设计,在后续项目设计与调试时也充分利用了这一点,有效提高了设计与调试的效率。最终我们也顺利完成了基于直流电源调控的自动调光与MPPT控制系统的设计与实现,并将自动调光系统实例化为一个具有实际应用意义的产品。相信在这门课程中学到的知识与技能能够为我们未来的硬件产品开发中起到重要的作用。

最后感谢两位老师耐心的教学与指导以及四位助教的辛苦付出,特别是几位助教,耐心地回答我们的问题、批改我们的作业并协助我们进行硬件实物的调试,为我们的项目实践提供了丰富的参考资料,帮助我们快速上手项目。在此提出一点小小的建议,这次项目中在电路焊接方面浪费了许多时间,希望未来的课程项目设计能够在各个实现细节上更加完善,在设计与测试时考虑的更加全面;同时希望在理论课程中穿插的各种仿真作业能够与实际的控制系统结合的更加紧密,完善一些逻辑不严谨的地方(如提供的报告框架中,是否考虑电容寄生电阻这点体现的略显混乱)。但总的来说,这门课程确实是已有项目制课程中的精品,也希望能够在未来建设得越来越好。

参考文献

[1] 胡寿松主编.自动控制原理[M].科学出版社,2019:a670.

[2] 卢京潮主编.自动控制原理习题解答[M].清华大学出版社,2013:195.

[3] 王天威编著.控制之美[M].清华大学出版社,2022.

[4] 李昆.智能技术在室内LED照明系统中的应用研究[J].光源与照明,2024,(10):51-53.

[5] 郑盛梅.恒照度自动调光台灯的设计[J].光源与照明,2023,(01):69-71.

[6] 侯耀华,陈萍,於崇干.直流电源系统高级功能在无人值班变电站的应用[J].供用电,2015,(02):19-24.

[7] 许桂敏,解皓月,张子泉,等.基于Simulink的光伏电池特性及MPPT算法仿真研究[J].智能建筑电气技术,2024,18(06):36-39+60.

[8] 王青苗.PLC模糊PID控制系统在隧道照明节能控制中的应用[J].微型电脑应用,2024,40(06):114-118+122.

[9] 陈礼俊,兰志勇.单片机控制的双调控高压直流电源[J].现代电子技术,2017,40(12):165-168.

[10] 樊战亭,万欣,王超.太阳能电池板自动追光控制系统设计[J].咸阳师范学院学报,2024,39(06):12-16.

[11] Gene F. Franklin, J. David Powell, Abbas Emami-Naeini, et al. Feedback control of dynamic systems[M].Publishing House of Electronics Industry,2013:14,590.

[12] Karl Johan Astrom, Richard M. Murray.自动控制多学科视角[M].人民邮电出版社,2010:310.

附录

1 STM32闭环PI控制程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "AD.h"
#include "Serial.h"

// 定义全局变量
#define PWM_FREQUENCY 10000 // PWM频率
#define MAX_PWM_VALUE 1000 // PWM最大值
#define PWM_PERIOD 3000 // PWM周期为1000个计数单位
#define KP 0.1 // 比例增益
#define KI 0.01 // 积分增益
#define KD 0.01
float voltage_ref = 10; // 设定的输出电压
float voltage_fb = 0.0; // 反馈的实际输出电压
float duty_cycle = 0.0; // 占空比
float error = 0.0; // 误差
float integral = 0.0; // 积分项
float control_signal = 0.0; // 控制信号
float last_error = 0.0; // 上一次的误差
float derivative = 0.0; // 微分项
int sample_index = 0; // 样本计数器
float trueVoltage=0.0; // 实际输出电压值
float VREF;
uint32_t time_counter = 0; // 用于记录时间的计数器
uint8_t voltage_state = 0; // 0 表示电压为8V,1 表示电压为10V
uint16_t ADValue;
float Voltage;
float i;

void control_buck(void){
voltage_fb = (float)AD_GetValue()*3.3/4096; //读取ADC的值
trueVoltage = voltage_fb*1050*20000/2500/1000; // 计算实际电压 (根据外部电路参数)
error = voltage_ref*2500*1000/1050/20000 - voltage_fb; // 计算误差
integral += error; // 积分计算
derivative = error - last_error; // 微分计算
control_signal = KP * error + KI * integral + KD * derivative; // PID控制输出
if (control_signal > 0.8)
control_signal = 0.8;
else if (control_signal < 0.2)
control_signal = 0.2;
duty_cycle = control_signal * PWM_PERIOD; // 更新占空比
i = (int)duty_cycle;
PWM_SetCompare1(i);
}
int main(void){
OLED_Init();
PWM_Init();
AD_Init();
Delay_ms(10);
NVIC_Configuration();
Serial_Init(); // 初始化串口
while (1){
control_buck();
}
}

2 自动调光程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "AD.h"
#include "Serial.h"
#include "HC05.h"
#include <string.h>
#include <stdlib.h>

// 定义全局变量
#define PWM_FREQUENCY 10000 // PWM频率
#define MAX_PWM_VALUE 1000 // PWM最大值
#define PWM_PERIOD 3000 // PWM周期为1000个计数单位
#define KP 0.1 // 比例增益
#define KI 0.01 // 积分增益
#define KD 0.01
float Light_Intensity = 0.0; // 光敏实际模拟量
float voltage_ref = 5 * 1.38; // 设定的输出电压
float voltage_fb = 0.0; // 反馈的实际输出电压
float duty_cycle = 0.0; // 占空比
float error = 0.0; // 误差
float integral = 0.0; // 积分项
float control_signal = 0.0; // 控制信号
float last_error = 0.0; // 上一次的误差
float derivative = 0.0; // 微分项
//int sample_index = 0; // 样本计数器
float trueVoltage=0.0; // 实际输出电压值
float VREF;
uint32_t time_counter = 0; // 用于记录时间的计数器
uint8_t voltage_state = 0; // 0 表示电压为8V,1 表示电压为10V
uint16_t ADValue;
float Voltage;
float i;
uint8_t RxSTA = 1;
char RxData[100] = "N";

void control_buck(void){
voltage_fb = (float)AD_GetValue(ADC_Channel_4)*3.3/4096; //读取ADC的值
trueVoltage = voltage_fb*1050*20000/2500/1000; // 计算实际电压 (根据外部电路参数)
error = voltage_ref*2500*1000/1050/20000 - voltage_fb; // 计算误差
integral += error; // 积分计算
derivative = error - last_error; // 微分计算
control_signal = KP * error + KI * integral + KD * derivative; // PID控制输出
if (control_signal > 0.8)
control_signal = 0.8;
else if (control_signal < 0)
control_signal = 0;
duty_cycle = control_signal * PWM_PERIOD; // 更新占空比
i = (int)duty_cycle;
PWM_SetCompare1(i);
}

int main(void){
OLED_Init();
PWM_Init();
AD_Init();
Delay_ms(10);
NVIC_Configuration(); // NVIC初始化
Serial_Init(); // 初始化串口
HC05_Init();

while (1){
// 蓝牙读取开关
HC05_GetData(RxData);
if (RxSTA == 0){
if (strcmp(RxData, "LL") == 0){
RxSTA = 1;
while(1){
// 读取光敏模拟输入量
Light_Intensity = (float)AD_GetValue(ADC_Channel_5);
voltage_ref = (Light_Intensity * 2 / 3500 + 8) * 1.38;
control_buck();
HC05_GetData(RxData);
if (RxSTA == 0)
break;
}
}
else if (strcmp(RxData, "on") == 0)
voltage_ref = 10 * 1.38;
else if (strcmp(RxData, "off") == 0)
voltage_ref = 5 * 1.38;
else{
int num = atoi(RxData);
if (num >= 0 && num <= 100)
voltage_ref = ((float)num / 100 * 2 + 8) * 1.38;
else if (num > 100)
voltage_ref = 10 * 1.38;
else
voltage_ref = 5 * 1.38;
}
memset(RxData, 0, sizeof(RxData));
strcpy(RxData, "N");
RxSTA = 1;
}
control_buck(); // 控制输出电压
}
}