[{"content":"PX4-Simulink联合环境配置与跨系统编译排雷记录 最近升级了控制器，从老的Pixhawk 2.4.8换到了Pixhawk6c控制器，所以重新配置了Simulink测试实验环境，用了新的MATLAB2025A版本，PX4版本用的1.14.3。但是遇到了以下很多的问题，大都是这套环境自身的坑（毕竟开发维护人员少之又少），特此记录如下，希望能帮到别人：\n在日常的控制算法研究与台架验证中，利用 MATLAB/Simulink 结合 PX4 硬件支持包（UAV Toolbox Support Package for PX4 Autopilots）进行代码自动生成与硬件在环（Connected I/O）测试，是极大地提升开发效率的手段。\n然而，在 Windows 11 + WSL2 (Ubuntu 22.04) + MATLAB 2025A 的架构下配置 Pixhawk 6C 的联合开发环境时，跨文件系统与工具链缓存往往会引发一些极为顽固的编译错误。本文详细记录了在此次环境搭建中遇到的核心难题及最终的“降维打击”解决方案。\n踩坑一：PX4 Firmware build not done 错误与跨系统句柄警告 在尝试使用 Simulink 编译处于 WSL 网络路径下的 PX4 源码时，通常会遇到第一个警告，紧接着编译彻底失败：\n警告: 无法获得 \\\\wsl.localhost\\ubuntu-22.04\\home\\...\\PX4-Autopilot\\build\\px4_fmu-v6c_default 的更改通知句柄。 错误：PX4 Firmware build not done for the selected cmake config, perform setup screens\n现象描述： 在 MATLAB 的 Hardware Setup 中可以正常走完验证，在 WSL 终端中敲 make 也可以正常编译出固件，但一旦回到 Simulink 程序中点击编译或部署，就会直接报上述错误，导致全部功能受阻。\n问题根源与排雷： 很多时候我们会以为是 \\\\wsl.localhost 的跨文件系统监控权限问题，试图通过**映射网络驱动器（如映射为 Z: 盘）**来解决。但实际上改映射驱动器根本没用！ 这个错误的最根本原因在于：WSL 中的 PX4 交叉编译工具链（Toolchain）版本过旧或缺失。 导致 Simulink 底层的脚本在第一步检查固件环境时，无法与当前的 CMake 配置正确匹配。\n解决方案： 必须在 WSL 环境中彻底更新 PX4 的 Toolchain（例如安装较新的 gcc-arm-none-eabi）。只要系统底层的编译器版本满足当前 PX4 固件（如 v6c）的 CMake 要求，Simulink 才能顺利识别固件。\n踩坑二：CMD不支持UNC路径的致命拦截 在部署编译时，Windows 底层调用命令行往往会直接报错并中断：\n用作为当前目录的以上路径启动了 CMD.EXE。 UNC 路径不受支持。默认值设为 Windows 目录。\n问题根源： MATLAB 底层使用 Windows 的 cmd.exe 执行编译脚本，但 CMD 原生不支持在网络共享路径（即 \\\\wsl.localhost\\... 这样的 UNC 路径）下执行命令。一旦遇到，会自动掉线回退到 C:\\Windows 目录，导致后续的所有 CMake 和 Make 命令彻底迷失方向。\n解决方案：修改注册表 为 CMD 强行开启 UNC 路径支持：\n打开 Windows 注册表编辑器，定位到 HKEY_CURRENT_USER\\Software\\Microsoft\\Command Processor。 （如果没有 Command Processor 项，则右键新建该项）。 在其中新建一个 DWORD (32位) 值，命名为 DisableUNCCheck。 将其数值数据设置为 1（十六进制），保存后即可解决。 (参考来源：CSDN博主 lisayh 的文章 / CC 4.0 BY-SA版权)\n踩坑三：极其顽固的 GCC Toolchain 路径缓存 Bug（核心难点） 在清理完上述网络路径问题，并成功更新了 WSL 内部的交叉编译工具链（换用了新版的 arm-none-eabi-gcc 10.3.1，真实路径位于 /usr/bin）后，Simulink 依然会报出极其离谱的版本路径错误：\nCompilation failure for command /opt/gcc-arm-none-eabi-9-2020-q2-update/bin/arm-none-eabi-g++ ... bash: line 1: /opt/gcc-arm-none-eabi-9-2020-q2-update/bin/arm-none-eabi-g++: No such file or directory 后面跟一大堆。。。核心错误点就是上述内容。\n问题根源： 我在 WSL 终端里自己敲 make 都能成功调用新编译器，但 MATLAB 只要一介入，就非要去抓 /opt/ 下的旧版编译器。 这是因为 MATLAB/Simulink 的 PX4 支持包底层配置缓存极其顽固。哪怕通过 Toolbox 的设置重新走向导、删除 slbuild 缓存、甚至是 make clean 清理源码目录，MATLAB 生成的底层 CMake 配置文件依然会“死记硬背”着第一次安装时的旧工具链路径。\n终极解决方案：软链接“降维打击”\n既然 MATLAB 内部的代码生成器是个“老顽固”，死活揪着旧路径不放，最高效的解决逻辑是：不与 MATLAB 的缓存较劲，直接在 Linux 系统层面给它造一个“替身”。\n利用 Linux 的软链接（Symbolic Link）功能，我们在 /opt/ 下创建一个与旧路径完全一致的空壳目录，并将内部指令重定向到系统真实的新版编译器上。\n打开 WSL 终端，直接执行以下脚本代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #!/bin/bash # fix_px4_compiler.sh # 1. 创建 MATLAB 死活要找的那个旧目录 sudo mkdir -p /opt/gcc-arm-none-eabi-9-2020-q2-update/bin # 2. 把真实的新编译器(即你当前生效的 /usr/bin 下的编译器)链接过去 sudo ln -sf /usr/bin/arm-none-eabi-g++ /opt/gcc-arm-none-eabi-9-2020-q2-update/bin/ sudo ln -sf /usr/bin/arm-none-eabi-gcc /opt/gcc-arm-none-eabi-9-2020-q2-update/bin/ # 3. 顺便把其他工具链核心工具（链接器、汇编器等）也一并链接过去 for tool in ar as ld objcopy objdump strip nm ranlib readelf size strings; do if [ -f \u0026#34;/usr/bin/arm-none-eabi-$tool\u0026#34; ]; then sudo ln -sf \u0026#34;/usr/bin/arm-none-eabi-$tool\u0026#34; /opt/gcc-arm-none-eabi-9-2020-q2-update/bin/ fi done # 4. 验证符号链接是否生效 echo \u0026#34;验证编译器版本：\u0026#34; /opt/gcc-arm-none-eabi-9-2020-q2-update/bin/arm-none-eabi-g++ --version (参考来源：CSDN博主 hahaha12139 的文章 / CC 4.0 BY-SA版权)\n","date":"2026-04-15T00:00:00Z","image":"https://a233a2.github.io/p/pixhawk6c_simulink%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE-%E7%94%A8%E4%BA%8E%E7%AE%97%E6%B3%95%E9%AA%8C%E8%AF%81%E7%AD%89%E8%B8%A9%E5%9D%91%E8%AE%B0%E5%BD%95/fengmian.png","permalink":"https://a233a2.github.io/p/pixhawk6c_simulink%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE-%E7%94%A8%E4%BA%8E%E7%AE%97%E6%B3%95%E9%AA%8C%E8%AF%81%E7%AD%89%E8%B8%A9%E5%9D%91%E8%AE%B0%E5%BD%95/","title":"Pixhawk6C_Simulink开发环境配置-用于算法验证等踩坑记录"},{"content":"最近使用XMC4500进行引脚功能初始化配置的时候，由于不清楚XMC4500内部寄存器与实际引脚的关系，摸索了很久，在此将两者的关系搞明白一下\n首先确定PORTx-\u0026gt;IOCRx在控制哪些引脚 每个IOCRx寄存器控制着4个引脚。每个引脚占用一个字节（8bit）。 比如IOCR4控制 Px.4 Px.5 Px.6 Px.7 更具体，PORT1-\u0026gt;IOCR4 控制着P1.4 P1.5 P1.6 P1.7\n每个引脚占用着8bit。比如PORT1-\u0026gt;IOCR4=0x00009000H 从右边往左边数，每8bit，控制一个引脚。比如0x90代表控制P1.5引脚。\n引脚是如何通过8bit被定义的? 比如PORT1-\u0026gt;IOCR4=0x00009000H,其中0x90代表控制P1.5引脚。 则0x90H=10010000B。此处，高五位控制引脚状态，低三位一般为000。\n则高五位剔出=10010，前面补0得到高5位为0001 0010，即0x12.然后由端口功能表决定输出功能。\n","date":"2025-05-17T00:00:00Z","permalink":"https://a233a2.github.io/p/xmc4500%E5%BC%95%E8%84%9A%E5%9C%B0%E5%9D%80-%E5%AF%84%E5%AD%98%E5%99%A8%E5%9C%B0%E5%9D%80%E9%97%AE%E9%A2%98/","title":"XMC4500引脚地址-寄存器地址问题"},{"content":"ADRC课程笔记 本人对ADRC控制有着深厚的研究兴趣。在讨论研究韩京清先生的ADRC知识之前，我想先给不太专业的自己补习一些经典与现代控制理论方向的相关知识。\n生理前馈系统 在生理学上，前馈控制的例子是在实际体力活动之前，自主神经系统对心跳的正常预期调节。前馈控制可以比作对已知线索的预期反应（预测编码）。心跳的反馈调节为机体提供了进一步对运动的适应性。 \u0026ndash;wiki百科\n在前馈系统中，控制变量的调整不是以目标和回授之间的误差为基础，它是以过程数学模型的知识和过程扰动的知识或测量为基础。\n所以对于数学模型的整体掌握是对系统扰动进行前馈消除的前提。\n正所谓要想系统稳得住，必须得有前馈引入。前馈控制在某些控制场合下是必须项。\n前馈控制 摘自《高志强: 自抗扰控制思想探究》 前馈控制是一种基于对扰动的预测进行补偿的控制方法。它通过测量可能影响系统输出的扰动变量，并根据扰动变量与系统输出之间的关系，计算出所需的控制量，在扰动对系统产生影响之前就进行调节，以抵消扰动的影响。\n在自动控制系统理论中学习过，由传递函数理论数学推导，前馈的引入可以完全的消除系统的扰动误差。现在这显然并不是这么的简单，这需要对系统数学模型有着十分精确的掌握。\n前馈控制在工业界用途广泛, 但是学术界对它很少关心, 认为是开环控制, 没有什么理论可言. 前馈的使用方式主要有两种: 一种是根据参考输入和对象模型的逆而得到的控制量, 不依赖对象的实时信息,暂且称之为A类前馈; 另外一种前馈, 是根据被控量之外的对象信息所产生的控制量, 即B类前馈。\n前馈控制提出的本意是要区别于上面提到的狭义的反馈控制, 表示控制量的一部分甚至全部都可以完全独立于被控量的量测. Black定义的反馈(feedback)本来就是针对前馈(feedforward)而言的, 表示信号流向反转, 由输出到输入. 这里信号的走向一正一反, 概念很清楚。\n可是当反馈这个通讯工程的概念被借用到控制工程时, 它的反义词前馈的含义便有了两种解释. 比如上面说的A类前馈明显是开环控制, 控制量完全独立于对象的实时信息. 可是B类前馈则不然, 它依赖的还是对象信息, 实际上是前面提到的广义反馈, 只不过反馈的不是输出量而已. B类前馈在文献中也被称为扰动前馈, 它是基于对象扰动信息的实时提取, 就像指南车. 可是同样是基于对象信息的提取, 指南车为什么会被称为开环控制呢？其实B类前馈属于广义反馈, 不应称其为前馈或开环控制. 鉴于前馈的概念和用法前后重叠, 为了概念的清晰, 建议今后可把B类前馈归入抗扰原理讨论; 把前馈狭义地定义为A类前馈.那么这样的前馈有什么意义呢？为什么它的用途这么广呢？主要原因是它降低了快速跟踪的成本。\n一个控制系统要使输出迅速跟踪给定值有两个途径: 1) 高带宽; 2) 前馈. 但是在工业上, 带宽就是成本.高带宽虽然能使跟踪速度提高, 但也带来很多问题:1) 对执行机构的品质要求提高; 2) 激励了对象的高频动态使控制问题复杂化; 3) 闭环系统的稳定裕度下降,对相位滞后和时间延迟更敏感; 4) 对传感器噪声更敏感, 等等.高带宽的成本在20世 50年代就有专门、详细的讨论, 比如文献[46], 但是至今没能在理论界引起重视, 乃至高增益控制器、观测器的文章比比皆是, 而能用上的却寥寥无几. 这反映了不考虑成本的研究, 在工程上是没有多少意义的。\n工程师们在实践中基于对系统物理特性的知识发现了前馈这个办法. 这种独立于反馈回路拟合出的控制量通常是结合参考输入, 以数据或图表形式表示的,常常在控制信号中占主要部分. 同时也使用PID反馈控制器, 发挥微调、纠错的作用. 因此, 工业上的PID控制器常常是与前馈控制结合使用的。\n以上总结的是控制论的基本原理, 是从事自动控制的人们在长期的工程实践中发现、挖掘出来的, 是控制论继续发展的基石. 要系统地、科学地建立和发展一套完整的理论体系, 就需要对基本原理进行提炼、抽象和升华, 使得工程控制的实践能够更加系统,并不断进入更高的层次, 从而“下学而上达”。\n从PID到ADRC 积分控制器 三种矫正网络 超前校正网络 滞后矫正网络 超前-滞后矫正网络 这些都要配合BODE图去确定频率指标 设计较为复古，较为原始。\nwc剪切频率处的中频段宽度对闭环系统特性有至关重要的影响。\n更进一步\u0026mdash;PID控制器 1 极点配置方法调节PID参数 确定极点位置来对闭环系统的Kp Ki进行确定。 这里极点配置 根据一阶系统 二阶系统 的阶跃响应曲线来根据期望的TS调节时间根据经验选取。 2 ZN方法调节PID参数\n3 经验方法\n积分串联型控制 简单理想发动机转速控制模型： 从扭矩 或者电压调整转速 这属于一阶系统。采用比例控制即可很好的进行控制 如下图所示设计控制过程。 从力矩到转速这属于积分环节：如下 $$ J\\dot{\\omega}=M $$ J是转动惯量，转速由w的导数表示。M为力矩。 则可以得到w与M的关系如下 $$ \\frac{\\omega}{M}=\\frac{1}{Js} $$ 设计如下： 简单理想发动机转角控制模型： $$ \\frac{\\theta}{M}=\\frac{1}{Js^2} $$ 设计比例微分PD控制器。\n现实世界中，几乎没有上述两种理想系统。 一阶系统有摩擦存在时，二阶系统有摩擦存在时，传统控制方法适用性不再好。 积分串联型真的好很容易控制，但是实际系统没有。我们ADRC需要构造这种系统。\n误差积分被动补偿-创造理想系统 传统引入积分控制，便是提高了系统的型别。但是不一定会好用。\n传统发动机控制电脑中，会有几千个PID参数表，用于对应不同的发动机工作环境。\n总扰动的概念 扩张状态观测器ESO-估计总扰动 个人理解：扩张状态观测器，即在原本观测器的基础上，加入积分环节（一个积分环节对应一个状态，也就是直观的扩张出来了一个状态）。使得总扰动的估计能够消除稳态误差。 系统类似下图，使用Simulink模块简单画一下，表示一下 果然反步法的设计过程是重要的，这样看来也有一定相似之处。\n观测器，观察每一个状态的演变过程，所以用导数。也就是状态方程。 总扰动f的导不知道，先写一个h。 观测器使用测量的偏差与估计的偏差。分别矫正了所有状态的微分方程。只要参数β123调节好，就可以准确估计总扰动。便可以改造出积分串联型理想系统。进而可以很好的控制系统。 这样原本Y的二阶导只等于U0 此时U0采用比例微分控制即可实现完全控制。\n扩张状态观测器ESO-如何选择β123？ 对于 $$ \\dot{y}=ay+bu $$ 给定一个阶跃信号，决定系统调节速度的是a。 证明：写出传递函数如下： $$ \\frac{Y}{U}=\\frac{b}{s+a} $$ a决定极点的位置。所以很明显决定系统稳定以及收敛速度的是a。 对状态空间表达式x导=Ax+bu也是如此。A决定系统稳定性以及各种性能。 特征值决定了状态空间中，每一个状态演变的快与慢。特征值也就是传递函数的极点。这是一个普遍且重要的理论。 如何调整特征值？ A矩阵写出行列式$A-\\lambda$。 令行列式等于0得到特征方程。 令特征方程等于 $$ \\left( \\lambda +\\omega _0 \\right) ^3=0 $$ 对应相等即可求出β123。\nADRC中的fst函数 fst函数是ADRC跟踪微分器中的非线性控制器，负责计算加速度控制信号。\nfst函数是非线性最速控制律（Fastest Control），它是ADRC中跟踪微分器（Tracking Differentiator, TD）里用于计算控制输入（类似“加速度”或“控制力”）的核心函数。\n具体来说，fst函数根据当前的跟踪误差和速度，输出一个加速度控制信号，使得跟踪误差能够以最快速度收敛，同时保证系统的平滑性和鲁棒性。\n一个经典ADRC控制中的FST函数\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function out = fst(x1, x2, omega, h) %fst Summary of this function goes here % 最速控制综合函数 d = omega * h; d0 = x1 + h * d; y = x1 + h * x2; a0 = sqrt(d * d + 8 * omega * abs(y)); if abs(y) \u0026gt; d0 a = x2 + (a0 - d) / 2 * sign(y); else a = x2 + y / h; end if abs(a) \u0026gt; d out = - omega * sign(a); else out = - omega * a / d; end end x1：跟踪误差（这里是位置误差，即v1 - v0）\nx2：当前速度（状态变量速度）\nomega：设计参数，控制收敛速度的权重，类似“频率”\nh：采样周期或滤波因子，影响控制平滑程度\n输出 out 是加速度控制量，用于驱动跟踪误差快速收敛。\n关键步骤说明：\n参数计算\nd = omega * h：一个阈值，用来区分控制策略\nd0 = x1 + h * d 和 y = x1 + h * x2 是辅助计算量，用于判断当前误差状态\n判断条件if abs(y) \u0026gt; d0 这是非线性分段条件，用于区分误差较大和较小时采用不同的控制策略。\n当误差较大时（abs(y) \u0026gt; d0），采用一种非线性控制策略加快收敛。\n当误差较小时，采用线性控制策略平滑收敛。\n计算控制量a\n计算一个加速度控制量a，根据误差和速度调整。\na越大，控制作用越强，推动误差快速减小。\n输出控制信号out\n根据a大小做饱和处理，保证输出在[-omega, omega]范围内，防止控制信号过大导致振荡。\n输出out就是TD控制器对速度的“加速度控制”，用来驱动状态变量快速收敛。\nADRC中的fal函数 fal函数是ADRC控制器中常用的非线性函数（nonlinear function），它的全称是“fractional power function”或“非线性误差变换函数”。\n它的主要作用是对误差e做一种平滑且带“非线性幂次”的变换，用于非线性反馈控制中，可以增强控制器对误差的响应速度和稳定性。\n1 2 3 4 5 6 7 8 9 10 function out = fal( e, a, delta ) %fal Summary of this function goes here % Detailed explanation goes here if abs(e) \u0026gt; delta out = abs(e) ^ a * sign(e); else out = e / (delta ^ (1 - a)); end end e：输入误差值，可以是位置误差、速度误差等。\na：幂次参数，通常0 \u0026lt; a \u0026lt; 1，控制非线性程度。\ndelta：临界阈值，用于定义分段非线性函数的范围。\n分段说明： 当误差较大时：|e| \u0026gt; delta\n使用非线性幂次函数 |e|^a * sign(e)。\n这会使误差响应不是线性增长，而是一个小于1次幂的曲线，\n当a\u0026lt;1时，误差较大时输出增长较慢，防止控制量过大。\n当误差较小时：|e| \u0026lt;= delta\n使用线性函数 e / (delta^(1 - a))，保证函数在e=0附近光滑连续，\n防止非线性函数在小误差时过于“死板”，保证控制器灵敏响应。\n为什么要这样设计？ 平滑性：函数在e = ±delta处连续且可导，避免了控制器输出跳变。\n非线性控制效果：非线性幂次a让控制器在大误差时减弱输出，避免过冲；在小误差时线性提高灵敏度，保证快速收敛。\n鲁棒性提升：这种设计提高了控制器在面对非线性和扰动时的性能。\nADRC中的ESO 状态空间方程推导而来的ESO，这个没什么好说的？\nESO 是ADRC的核心模块之一，主要用来：\n在线估计系统的状态变量和总扰动（包含内部未知动态和外部扰动）。\n实时补偿系统扰动，提高控制系统的鲁棒性和抗扰动能力。\nESO的设计目标是让观测误差快速收敛，从而准确估计系统的状态和扰动。\n四旋翼中的ESO，二阶。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function [dz1,dz2,dz3] = fcn(u,e,z2, z3) %#连续形式的扩张状态观测器(ESO) %% 参数赋值 beta01_y = 30; % 可调参数 beta02_y = 300; % 可调参数 beta03_y = 1000; % 可调参数 a1 = 0.75; % 可调参数 a2 = 0.5; % 可调参数 a3 = 0.25; % 可调参数 delta = 0.006; % 线性段的区间长度 b0_y = 0.06; % 决定补偿强弱的“补偿因子” dz1 = z2 - beta01_y * fal(e, a1, delta); dz2 = z3 - beta02_y * fal(e, a2, delta) + b0_y * u; dz3 = - beta03_y * fal(e, a3, delta); 输入\nu：控制输入信号（控制器输出的控制量）\ne：观测误差，通常是测量输出与估计输出的误差，e = z1 - y（这里y是系统输出，z1是估计的状态）\nz2, z3：ESO内部状态变量，对应系统速度状态和扰动估计\n输出\ndz1, dz2, dz3：状态变量的导数，供积分器计算下一时刻状态估计值\nADRC中参数调节 ADRC的高志强老师的ACC2006论文中的ADRC模型如下\n","date":"2025-05-16T00:00:00Z","permalink":"https://a233a2.github.io/p/adrc%E5%B0%8F%E8%AE%B0/","title":"ADRC小记"},{"content":"项目概述 主控芯片：WCH-CH32V307VCT6\n预驱芯片：DRV8301\nMOS：NEC6050\n下载芯片：CH549G\n电流检测：DRV8301内置\n项目图片 MCU核心设计原理图\nMCU核心其他设计原理图 驱动核心设计原理图\n驱动核心其他设计原理图 一些叙述 这一版设计仍然算不上顺利，作为一个初学者，踩了很多的坑。\n问题1：设计出来的电感出现啸叫问题，应该是焊接出现了问题，焊接出现了电感空腔，导致电感啸叫。 问题2：DRV8301的BUCK电源输出异常，应该是电源设计出现了问题，我计划先暂停驱动板的设计，使用手头上的驱动板先完成FOC的实现再去绘制驱动板。 问题3：驱动板与控制板合体之后，仅USB供电下出现了MOS快速发热，我估计是出现了局部短路情况，在多次修补之后问题解决，但是我没用万用表测出是哪里短路的。。我把这个原因归结于我自己的焊接问题。。。。\n问题4：驱动板，设计的时候使用了统一的GND 没有区分GND AGND PGND这几个，我原先以为使用统一GND并没有什么大问题，但是这个项目因为采样电阻很小，导致驱动一路直接近似导通到GND了。不知道这与问题3有没有关联。。\n之后的措施：控制板的设计得益于多次的CH32V307芯片板设计经验，是没什么问题的。计划先使用这个控制板搭配之前买的DENGFOC驱动板先移植一下FOC的程序。再后面移植到FreeRTOS系统中。实现FOC电流闭环之后再去设计新的驱动板。总之，这次的设计也是增长了许多经验，但是之后需要更脚踏实地去做项目，没有头绪的乱摸索可能是最浪费时间的行为。\n闲暇之余，还复刻了B站的一个FOC开源项目。屏幕还没有买，等后续买一个装上去调试一下。但是自己的焊接水平也很一般。还不知道有没有问题。 AS5600设计 这一版使用了上一版V1.0设计的AS5600编码器电机底座，验证完全没有问题。在此开源一个自己写的CH32V307系列芯片的AS5600驱动。代码太长，我提前放到了CSDN上，-\u0026gt;[(https://blog.csdn.net/apple_50191511/article/details/146131385)]\u0026lt;-(https://blog.csdn.net/apple_50191511/article/details/146131385)网址在这里，希望能帮到别人^ ^。\nFOC程序设计 程序移植参考了DENGFOC-FOR-STM32的项目。另外也参考了刚复刻的开源项目。\n","date":"2025-03-19T00:00:00Z","image":"https://a233a2.github.io/p/cm_foc%E9%A9%B1%E5%8A%A8%E5%99%A8v2.0/1.4.jpg","permalink":"https://a233a2.github.io/p/cm_foc%E9%A9%B1%E5%8A%A8%E5%99%A8v2.0/","title":"CM_FOC驱动器V2.0"},{"content":"超简单自用模板 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 \\documentclass[11pt]{report} %设置正文的字体大小 以及文章类型 常用report article IEEEtran等 %ASMATH公式 graphicx支持图片IEEEtran \\usepackage{amsmath, amsthm, amssymb, graphicx} \\usepackage[bookmarks=true, colorlinks, citecolor=blue, linkcolor=black]{hyperref} \\usepackage{cite}% \\cite{bibtex1，bibtex2，bibtex3} 饮用后可以变为--\u0026gt; [1]-[3] \\usepackage{multicol} % 用于实现在同一页中实现不同的分栏,导包 \\setlength\\columnsep{0.6cm} % 设置两栏之间的间距为0.6cm \\columnseprule=0.0pt % 实现插入分隔线，用于分栏 \\begin{multicols}{2} % 分两栏 若花括号中为3则是分三栏 \\end{multicols} \\usepackage{geometry}% 页面布局设置 \\geometry{left=1.00in,right=1.00in,top=0.75in,bottom=0.75in} %页面布局设置 % 导言区 \\begin{document} %栏外标题！！！ \\markboth{Random process assignment,~Vol .~1, No.~1,~12~2024}{Shell \\MakeLowercase{\\textit{et al.}}: Paper Title} % 题目 无人机运动控制中随机控制与优化的发展现状综述 \\title{\\textbf{A review of the research status of Stochastic and interference in UAV motion control}}%作者 标题加粗 \\author{}%作者 \\date{2024.12.11}%时间 \\maketitle %摘要部分 \\begin{abstract} \\end{abstract} \\section{INTRODUCTION} %正文部分 \\begin{thebibliography}{00}%引用 \\end{thebibliography} \\end{document} 其他功能 插入图片 1 2 3 4 5 6 7 \\usepackage{graphicx} %必须要导入这个 尽量使用EPS格式的矢量图片 保证清晰 \\begin{figure}[htbp] \\centering \\includegraphics[width=8cm]{images/1.1.png} \\caption{UAV} \\label {1} \\end{figure} 写论文的话 还是使用Overleaf比较好 ","date":"2024-12-15T00:00:00Z","permalink":"https://a233a2.github.io/p/latex%E5%B0%8F%E8%AE%B0/","title":"Latex小记"},{"content":"YOLO（You Only Look Once）是一种流行的实时目标检测算法，它将目标检测任务作为一个回归问题来处理，直接预测图像中所有目标的位置和类别。与传统的目标检测方法（例如 R-CNN 系列）不同，YOLO 通过一个单一的神经网络在一次前向传播中同时进行目标识别和定位，因此非常高效，能够在实时应用中使用。\nYOLO 运行环境配置 参考教程： YOLO环境配置\nAnaconda的安装与环境设置 CUDA、Pytorch、Pycharm的安装与配置\n我的操作环境 环境：Win11专业版24H2版本 CPU GPU：i7 14650HX + RTX4060 Laptop Driver Version: 566.14 CUDA Version:12.7\nConda创建虚拟环境 采用Anaconda管理，使用Conda创建虚拟环境。类似Docker，感觉相比Docker更容易上手。 首先下载了Anaconda 2024-6-1版本，内置Conda版本为25. Conda创建了虚拟环境。名为yolo_env 首先创建并激活虚拟环境。在Anaconda Navagator中操作即可。\n虚拟环境中Pytorch的安装 Pytorch的安装：https://pytorch.org/get-started/locally/ 选择如上的版本号。在Pycharm终端中输入即可在对于环境中安装。\nCUDA安装-不是安装在虚拟环境 首先安装CUDA toolkit 这里我选了12.4 与Pytorch对应起来。\nPS:cuda安装 安装cuda时，第一次会让设置临时解压目录，第二次会让设置安装目录； 临时解压路径，建议默认即可，也可以自定义。安装结束后，临时解压文件夹会自动删除； 安装目录，建议默认即可； 注意：临时解压目录千万不要和cuda的安装路径设置成一样的，否则安装结束，会找不到安装目录的！！！ 选择自定义安装 安装完成后，配置cuda的环境变量； 命令行中，测试是否安装成功； 双击“exe文件”，选择下载路径（推荐默认路径）\n之后选择自定义安装，精简版本是下载好所有组件，并且会覆盖原有驱动，所以在这里推荐自定义下载\n网上都建议将Visual Studio Integration选项取消（在CUDA选项下），其没什么用而且会影响下载 确定安装路径（可以修改，最好记住）\n注意事项：取消勾选Visual Studio Integration（这里解释一下，这个模块是对VS编译的支持，没有安装VS无法征常工作，而需要VS辅助则是需要编译cuda程序，这种编译不建议在Windows下进行，一般Windows下能跑深度学习原生框架的代码就行），其余全部勾选。\n安装完成后查看一下是否有环境变量，没有自己手动添加（9.0之后的版本环境变量是自动配置的，无需添加） 一共需要4个环境变量，网上说只自动生成了前两个，再手动添加一下上图中下面那两个。\n卸载CUDA 参考：https://www.jianshu.com/p/c184e270b8d4 保留如下的红色框选 删除其他的 删除顺序任意 把Nsight CUDA toolkit删除了 先不删除了 其他的和这个古老教程对应不起来\n还是使用Docker配置简单一些，但是不知道Docker能否配置CUDA？或者Nvidia有专门的Docker仓库。？\n再次安装CUDA时发现CUDA安装失败。 在网上查资料发现是驱动错误导致的，之前我是GameReady驱动，现在换成Studio系列。在这个驱动管理软件下能直接更换。 CUDNN安装 cuDNN 其实就是 CUDA 的一个补丁而已，专为深度学习运算进行优化的。然后再参加环境变量 下载后发现其实cudnn不是一个exe文件，而是一个压缩包，解压后，有三个文件夹，把三个文件夹拷贝到cuda的安装目录下。 拷贝时看到，CUDA 的安装目录中，有和 cuDNN 解压缩后的同名文件夹，这里注意，不需要担心，直接复制即可。cuDNN 解压缩后的同名文件夹中的配置文件会添加到 CUDA安装目录中的同名文件夹中。【此处还是建议还是分别把文件夹的内容复制到对应文件夹中去】 在系统变量-Paht中添加环境变量 如何测试环境是否安装成功？ 打开cmd，输入nvcc -V查看cuda版本 使用Torch验证CUDA是否可用：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # yolo detect train data=data/data.yaml model=yolo11n.pt epochs=10 batch=4 imgsz=640 device=0 import torch # 检查 CUDA 是否可用 print(f\u0026#34;CUDA available: {torch.cuda.is_available()}\u0026#34;) # 如果 CUDA 可用，打印当前设备名称 if torch.cuda.is_available(): print(f\u0026#34;Current device: {torch.cuda.get_device_name(0)}\u0026#34;) # 获取 CUDA 版本 cuda_version = torch.version.cuda print(f\u0026#34;CUDA Version: {cuda_version}\u0026#34;) # 获取 cuDNN 版本 cudnn_version = torch.backends.cudnn.version() print(f\u0026#34;cuDNN Version: {cudnn_version}\u0026#34;) 不可用的运行结果\n1 2 3 CUDA available: False CUDA Version: None cuDNN Version: None 可用的运行结果(示例)\n1 2 3 4 CUDA available: True Current device: NVIDIA GeForce RTX 4060 Laptop GPU CUDA Version: 12.4 cuDNN Version: 90100 torch安装 pip install torch即可 Torch 和 PyTorch 是两个不同的深度学习框架。 1、Torch 是一个用 Lua 编程语言编写的深度学习框架，而 PyTorch 是一个用 Python 编程语言编写的深度学习框架。 2、Torch 是由 Facebook 的研究团队开发的，而 PyTorch 是由 Facebook AI Research（FAIR）团队开发的。 3、PyTorch 的设计更加灵活和易于使用，提供了更多高级接口和功能，使得用户可以更方便地构建、训练和部署深度学习模型。 4、Torch 在一些方面比 PyTorch 更早成为流行的深度学习框架，但 PyTorch 在近年来逐渐取代了 Torch，成为了研究和工业界广泛使用的深度学习框架之一。\nPycharm配置虚拟环境 下一步准备使用Pycharm进行开发。感觉相比Vscode好配置很多。 在Pycharm中新建项目，选择之前创建的虚拟环境。 后在这个虚拟环境中安装Pytorch。\nYOLO 架构与原理 ultralytics发布了最新的作品YOLOv11，这一次YOLOv11的变化相对于ultralytics公司的上一代作品YOLOv8变化不是很大的（YOLOv9、YOLOv10均不是ultralytics公司作品），其中改变的位置涉及到C2f变为C3K2，在SPPF后面加了一层类似于注意力机制的C2PSA，还有一个变化大家从yaml文件是看不出来的就是它的检测头内部替换了两个DWConv，以及模型的深度和宽度参数进行了大幅度调整，但是在损失函数方面就没有变化还是采用的CIoU作为边界框回归损失。\nYOLO v11 Demo 安装ultralytics。运行时提示缺少package的错误就是没安装这个。\n按照官方教程使用Conda安装即可 如下图 1 conda install conda-forge::ultralytics YOLO Example：\n1 2 3 4 from ultralytics import YOLO model=YOLO(\u0026#34;yolo11n.pt\u0026#34;) results=model(\u0026#34;H:\\Computer Vision\\yolov11\\pra\\pra1\\ice_skating.mp4\u0026#34;,save=True,show=True) 参考YOLO官方仓库，安装ultralytics：https://github.com/ultralytics/ultralytics Python中引用地址可能存在转义错误，在地址前加r即可。即保持字符原始值的意思。 如r\u0026quot;H:\\Computer Vision\\yolov11\\pra\\pra1\\ice_skating.mp4\u0026quot; PS 也可以替换为双反斜杠，替换为正斜杠。\n自己训练数据并完成任务-打僵尸为例子 参考教程：yolo 锁头 教程\n数据集获取 首先下载一个植物大战僵尸经典版本。这个属实是难倒我了。。。最后在植物大战僵尸吧找到了资源,并下载了完美存档。 下一步就是先自己玩一把，然后录屏，截图出300张左右的数据集。 数据标注 先使用教学视频中UP给出的数据集压缩包进行自己的训练，后续有时间再试试自己标注数据集。\n训练数据 设置训练了500次 实际训练了200+次数 运行程序 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 from ultralytics import YOLO #导入 import pyautogui as pt # 控制鼠标点击的一个Python库 import pygetwindow # 导入pygetwindow库，用于获取窗口信息 import numpy as np # 导入numpy库，用于数组和矩阵操作 import cv2 as cv # 导入OpenCV库，用于图像处理 import torch # 导入PyTorch库，用于深度学习操作 from PIL import ImageGrab # 从Pillow库中导入ImageGrab，用于屏幕截图 model = YOLO(\u0026#34;best.pt\u0026#34;) # 加载YOLO模型，使用训练好的\u0026#34;best.pt\u0026#34;权重文件 window_title = \u0026#34;植物大战僵尸中文版\u0026#34; # 设置目标窗口标题为“植物大战僵尸中文版” window = pygetwindow.getWindowsWithTitle(window_title)[0] # 获取该窗口的窗口对象 device = torch.device(\u0026#34;cuda:0\u0026#34;) # 设置设备为GPU（CUDA），确保YOLO模型在GPU上运行 model.to(device) # 将模型加载到GPU上 while True: if window: # 检查目标窗口是否存在 x, y, w, h = window.left, window.top, window.width, window.height # 获取窗口的位置信息和大小 screenshot = ImageGrab.grab(bbox=[x, y, x + w, y + h]) # 对窗口进行截图 image_src = cv.cvtColor(np.array(screenshot), cv.COLOR_RGB2BGR) # 将截图转换为OpenCV支持的BGR格式 size_x, size_y = image_src.shape[1], image_src.shape[0] # 获取截图的宽度和高度 image_det = cv.resize(image_src, (640, 640)) # 将截图调整为YOLO模型需要的输入大小（640x640） result = model.predict(source=image_det, imgsz=640, conf=0.7, save=False) # 使用YOLO模型进行目标检测 boxes = result[0].boxes.xywhn # 获取检测框的中心点和宽高（归一化坐标） boxes = sorted(boxes, key=lambda x:x[0]) # 按检测框的x坐标从左到右排序 count = 0 # 初始化计数器，用于限制点击次数 for box in boxes: # 遍历每个检测框 # 在截图中绘制检测框 cv.rectangle(image_src, (int((box[0] - box[2]/2) * size_x), int((box[1] - box[3]/2) * size_y)), (int((box[0] + box[2]/2) * size_x), int((box[1] + box[3]/2) * size_y)), color=(255, 255, 0), thickness=2) pt.click(x=x + box[0] * size_x, y=y + box[1] * size_y) # 模拟鼠标点击检测框中心 count += 1 if count \u0026gt; 4: # 如果点击次数超过4次，退出循环 break cv.imshow(\u0026#34;frame\u0026#34;, image_src) # 显示实时处理后的图像，窗口名称为“frame” if cv.waitKey(1) == ord(\u0026#39;q\u0026#39;): # 检测键盘按键，如果按下‘q’，退出程序 break pass 僵尸水族馆 实现自动收集阳光，自动购买僵尸，自动喂食僵尸三个功能。\n数据集制作 OBS录屏，使用Python中的CV库进行处理，截图120张左右。人工筛选出带有阳光的100张左右图片。 以下是，使用Python对视频进行截图的程序：\n1 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 import cv2 import os # 设置视频文件路径 video_path = \u0026#39;textttt.mp4\u0026#39; output_folder = \u0026#39;raw_picture\u0026#39; # 创建输出文件夹，如果不存在的话 if not os.path.exists(output_folder): os.makedirs(output_folder) # 打开视频文件 cap = cv2.VideoCapture(video_path) # 获取视频的帧率（FPS） fps = cap.get(cv2.CAP_PROP_FPS) # 获取视频的总帧数 frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 循环读取视频帧 for frame_num in range(0, frame_count, int(fps)): # 每秒截图一次 cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num) # 设置当前帧的位置 ret, frame = cap.read() # 读取当前帧 if ret: # 保存帧为图片 frame_filename = os.path.join(output_folder, f\u0026#39;frame_{frame_num}.jpg\u0026#39;) cv2.imwrite(frame_filename, frame) else: break # 释放视频文件 cap.release() print(f\u0026#34;已完成截图，截图保存在 \u0026#39;{output_folder}\u0026#39; 文件夹中。\u0026#34;) 之后使用RoboFlow平台进行在线标注。\nRoboFlow平台数据集标注 上传图片到RoboFlow平台，选择人工标注。这里可以选自动标注与平台帮你。这里我们选自己标注。 图片总数100.这里可以团队分工。 在线标注平台 标注完成后，选择分别用于训练，验证，以及测试的比例。 训练即训练模型用的图片，验证则为在训练中验证的图片。以反馈训练效果。测试即训练完成后的测试。 这里选择常用比例7：2：1 最终如下，有4张图片没有标注，也就是图片中没有目标。 本地训练 训练同上 运行程序 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 from ultralytics import YOLO # 导入YOLO模型 import pyautogui as pt # 控制鼠标点击的Python库 import pygetwindow # 导入pygetwindow库，用于获取窗口信息 import numpy as np # 导入numpy库，用于数组和矩阵操作 import cv2 as cv # 导入OpenCV库，用于图像处理 import torch # 导入PyTorch库，用于深度学习操作 from PIL import ImageGrab # 从Pillow库中导入ImageGrab，用于屏幕截图 import time # 导入time库，用于控制点击间隔 # 加载YOLO模型 model = YOLO(\u0026#34;best.pt\u0026#34;) # 加载训练好的模型权重文件 # 设置目标窗口标题 window_title = \u0026#34;植物大战僵尸中文版\u0026#34; window = pygetwindow.getWindowsWithTitle(window_title)[0] # 获取窗口对象 # 设置设备为GPU device = torch.device(\u0026#34;cuda:0\u0026#34;) model.to(device) # 将模型加载到GPU上 # 设置点击目标的坐标 buy_zombie_position = (window.left + 110, window.top + 70) # 右上角位置 feed_zombie_position1 = (window.left + window.width // 2, window.top + window.height // 2 -100) # 中间空白区域 feed_zombie_position2 = (window.left + window.width // 2 - 100, window.top + window.height // 2-100) # 中间空白区域 feed_zombie_position3 = (window.left + window.width // 2 + 100, window.top + window.height // 2-100) # 中间空白区域 feed_zombie_position4 = (window.left + window.width // 2 - 100, window.top + window.height // 2) # 中间空白区域 feed_zombie_position5 = (window.left + window.width // 2 + 100, window.top + window.height // 2) # 中间空白区域 # 控制点击间隔时间 click_interval = 1 # 每秒点击一次 while True: if window: # 检查目标窗口是否存在 x, y, w, h = window.left, window.top, window.width, window.height # 获取窗口的位置信息和大小 screenshot = ImageGrab.grab(bbox=[x, y, x + w, y + h]) # 对窗口进行截图 image_src = cv.cvtColor(np.array(screenshot), cv.COLOR_RGB2BGR) # 转换为BGR格式 size_x, size_y = image_src.shape[1], image_src.shape[0] # 获取截图的宽度和高度 image_det = cv.resize(image_src, (640, 640)) # 调整为YOLO模型需要的输入大小 result = model.predict(source=image_det, imgsz=640, conf=0.7, save=False) # 使用YOLO模型进行目标检测 boxes = result[0].boxes.xywhn # 获取检测框 boxes = sorted(boxes, key=lambda x: x[0]) # 按x坐标排序 # 在右上角购买僵尸 # pt.click(buy_zombie_position) # 模拟点击右上角购买僵尸 # pt.click(feed_zombie_position1) # 模拟点击中间位置喂食僵尸 # pt.click(feed_zombie_position2) # 模拟点击中间位置喂食僵尸 # pt.click(feed_zombie_position3) # 模拟点击中间位置喂食僵尸 # pt.click(feed_zombie_position4) # 模拟点击中间位置喂食僵尸 # pt.click(feed_zombie_position5) # 模拟点击中间位置喂食僵尸 # 遍历每个检测框 count = 0 for box in boxes: # 在截图中绘制检测框 cv.rectangle(image_src, (int((box[0] - box[2]/2) * size_x), int((box[1] - box[3]/2) * size_y)), (int((box[0] + box[2]/2) * size_x), int((box[1] + box[3]/2) * size_y)), color=(255, 255, 0), thickness=2) # 在检测框中心点击 pt.click(x=x + box[0] * size_x, y=y + box[1] * size_y) count += 1 if count \u0026gt; 1: # 如果点击次数超过4次，退出循环 break # 显示实时图像 cv.imshow(\u0026#34;frame\u0026#34;, image_src) # 如果按下‘q’键，退出程序 不知道为什么 不起作用 if cv.waitKey(1) == ord(\u0026#39;q\u0026#39;): break cv.destroyAllWindows() # 关闭OpenCV显示窗口 ","date":"2024-11-30T00:00:00Z","permalink":"https://a233a2.github.io/p/yolo%E8%AF%95%E7%8E%A9-%E5%AE%9E%E4%BE%8B%E8%AE%B0%E5%BD%95/","title":"YOLO试玩+实例记录"},{"content":"PX4-Simulink联合环境配置 参考如下教学： PX4与Simulink联合仿真-入门篇 PX4与Simulink联合仿真-进阶篇 使用Pixhawk2.4.8基于Simulink进行入门级飞控算法自主开发的操作 基于Simulink的PX4飞控算法的开发实践（1.硬件支持包配置） 基于Simulink的ROS2下PX4无人机控制框架-入门篇\nPX4版本：适配matlab 2022b的v1.12.3 Matlab版本：2022b windows：Windows11家庭版 遇到的错误：在编译时一直提示找不到.px4文件的错误如下图： 直接放弃2022的了，改用2024的Matlab进行开发的尝试。\n开发环境的配置1 更换各部件版本如下 电脑：LEGION Y7000P IRX9 LAPTOP 硬件：Intel(R) Core(TM) i7-14650HX 2.20 GHz + RTX4060 + 16GB PX4版本：适配matlab 2024b的v1.14 Matlab版本：2024b windows：Windows11专业版-24H2版本（现改的） 首先安装如下的几个必须的依赖，再安装这个工具箱。 安装的附加功能如下 WSL2的安装 此时在WSL中没有Ubuntu系统，需要安装一下22.04的ubuntu 不知道为啥这win11用wsl命令一直下载不下来，卡在0%半天不动。网上说是系统更新的原因。。。网上建议去手动下载，或者是Store里面下载，但是我Store也是打不开，所以手动下载地址如下： 下载地址：Manual installation steps for older versions of WSL 安装完后显示这个错误，是因为我的设置导致新应用安装到了D盘，需要更改以下Ubuntu的位置到C盘。 卸载重装一般是没用的，这个貌似是默认安装在你选择的默认安装位置，所以需要更改默认安装位置再安装或者是直接移动App。 卸载重装是不行的，需要选择移动到C盘才可以。。。 之后成功安装了Ubuntu系统 但是仍然打开工具箱后无法跳转到下一步，按照社区给的指示操作如下。 更改以下系统的类型到专业版，方便后续更改电脑的语言。 弄到这我也是服了，这b win11语言一直下载不下来，网上尝试了各种办法都特么不行。 然后重启了几次，显示更新xxx，结果重新打开matlab之后便可以进行到下一步了。。。。。我特么！ 在这里怀疑一个很大的原因是没有设置默认WSL启动为ubuntu，可以按照matlab文档里面设置一下，就CMD一个命令。然后重启打开Matlab。 PS:折腾了半天，win11的语言包终于能下载了，不过也没用了。。。 开发环境的配置2 ok，过了第一步剩下的其实好说。 配置参考：https://blog.csdn.net/weixin_29062909/article/details/138366879\nPX4源码下载 这里选择Python地址来验证。没有尝试过他的自动下载，也不建议自动。 这里去下载PX4的源码，2024B系列Matlab支持的是PX4 v1.14版本 这里与之前的2022版本的Matlab的UAV工具箱不同，固件要下载到WSL目录之下。\nCloning the firmware in WSL2 home directory is crucial. If you clone it outside of the WSL file system, then you will encounter slow execution issues and access right / permission errors. 在 WSL2 主目录中克隆固件至关重要。如果将其克隆到 WSL 文件系统之外，那么您将遇到执行缓慢的问题和访问权限/权限错误。\n执行如下的命令以在WSL中下载固件。可能就是安装到C盘去了，作为我这个上古时代的电脑分盘使用者来说很不友好，弄得我很不爽。\n1 2 3 4 5 6 mkdir mypx4 cd mypx4 git clone https://github.com/PX4/PX4-Autopilot.git --recursive cd PX4-Autopilot git checkout v1.14.0 -f git submodule update --init --recursive 这里出现了个没有分支的错误，我们创建一个。不创建也问题不大，这里官方文档中并没有这一步。 git switch -c \u0026lt;v1.14\u0026gt; 接下来就是漫长的下载submodule时间。。。。 下载完成后，对上面的硬件设置窗口，点击下一步。 在文件资源管理器中，可以找到Linux的地址，一般是home下，找到地址复制过去。验证。ok！ PX4 ToolChain下载以及编译程序 https://www.mathworks.com/help/releases/R2024b/uav/px4/ug/setup-px4-toolchain-wsl.html 跟着官网教程进入目录 运行脚本下载即可。这些都是前人为我们铺好的路。 此处时间较长可以给自己弄杯咖啡等着。。。。 下面检验下工具链的安装是否正确。 在PX4-Autopilot目录下运行了make px4_fmu-v3_default 出现了在ubuntu配置时以前遇到过的缺少STM32在Linux下开发工具的错误。 之前的解决方法：编译时报错：The CMAKE_CXX_COMPILER arm-none-eabi is not a full path and was not found in the PATH.的问题解决方法 重启之后，之前的错误居然没了。成功编译。 这里与我之前ubuntu下配置一样。我不禁思考我在配置ubuntu时，是不是重启一下也能解决呢？ 配置引导页面 重启之后，这个导引窗口也没了安装工具链那一步了。 让我们确认 翻译如下： 应该是问你是否要自己设计控制器，自己要设计控制器的话，勾选，后续可能需要自己搭建。 我们此处选项勾选。不勾选的以后再试试。 下一步，选择我们的F427老爷飞控。 应该是选自启动脚本。第一个是选择在PX4自身中的默认启动脚本。第二个是选择在SD卡中的用户编辑的脚本。 CSDN博主说选择第二个较为繁琐，那我们选第一个。 确定QGC地面站的位置 这一步，应该是让我们在QGC中选择鸡架,先下一步吧。Mavlink还没连接上应该也选择不了。 编译工程。我们勾选删除所有目标xxxx。 我们之前人工make构建了一次，这里再点一次，直接秒成功了。可能是检测到了之前构建产生的px4文件。 再次在matlab中构建，依然成功。 这里叫我们测试硬件连接。这里我拿之前焊的PX4进行连接。 连接上之后烧录固件。如果开着QGC，需要关掉。否则串口占用没法下载 Matlab页面显示下载烧录完成。 可以验证以下陀螺仪加速度数据。可以看到我这里能正常读取数据。 获取陀螺仪数据的Simulink实例程序 官方教程地址：Getting Started with Connected I/O for PX4 Host Target 这可以作为我们的第一个开发程序。 在simulink建模中，模型设置，按照官方给出的指导设置如下。 Simulink建立一个这样的简单读取数据的模型。 可以看到可以成功读取到数据，仿真10秒。之前我没动，后面动了两下。实在是太开心了。 再来一张吧 配合可视化平台进行仿真 根据教程PX4与Simulink联合仿真-入门篇 我们新建一个控制电机PWM的模型如下图所示： 首先创建一个开关，连接PWM模块的解锁端口。并将开关的模块同Const模块连接起来，使得打开开关，值为1，否则为0。 设置数据类型。前两个设置为布尔类型，后面的CH通道的PWM输入设置为uint16类型。 设置硬件参数，如下图所示。配合jMAVSim进行可视化仿真。另外，仿真运行时间设置为无穷，即设置为inf。 进入Ubuntu终端，运行jmavsim可视化仿真程序。 jmavsim程序已经启动。Simulink中点击Run In IO 即可开始。此处JmavSim自动关闭的话需要再执行命令将其打开，自动关闭是因为缺少自动启动的脚本导致的。 ","date":"2024-11-29T00:00:00Z","image":"https://a233a2.github.io/p/simulink-px4-wsl2%E4%BB%BF%E7%9C%9F%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/fengmian.png","permalink":"https://a233a2.github.io/p/simulink-px4-wsl2%E4%BB%BF%E7%9C%9F%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/","title":"Simulink-PX4-WSL2仿真环境搭建"},{"content":"基础操作 PADS简易教程 EDAHelper\u0026ndash;EDA增强工具 教程 | 24小时教会使用PADS进行PCB设计 PADS 入门教程（一） PADS VX系列 全套零基础入门PCB Layout设计实战视频教程PCB设计培训凡亿教育 PADS Layout：封装 PADS Logic：原理图 PADS Router：布线\n绘制板框 在Layout页面右下角可以切换mm或者mil单位。\n全选画的线，右键选择特性，类型选择板框。另外i有DXF文件也可以导入DXF文件来确定板框。\n原理图转PCB 原理图与PCB通过Logic连接。点击发送网表即可。也可以在PCB页面手动导入网表（.asc文件） 然后在原理图中勾选部分元件，在PCB上右键勾选的元件，点击“分散”即可将元件分散开，方便之后布局。\nPS：在原理图选择后，在PCB上高亮。快捷键Ctrl+E可以快速选择移动。\nLayout 布局位置确定后，可以右键器件选择特性，勾选胶粘G。可以防止误移。\n得到PCB和SCH工程后如何建库关联 原理就是新建一个库，把所有现存的封装都加进去，然后就可以了，十分简单。 首先新建一个库，将其放到最开始。这样索引的时候可以最先索引。 选择原理图中的元件，只选原件。 选中后，右键选择保存到库中。 全选-确定。提示有重复的话直接覆盖就可以。 PCB也是如此，在开始时，可能由于有胶粘元件的设置无法选中元件，在空白处右键选择筛选条件，把胶粘元件勾选上。 一样的操作，全选，覆盖。 之后PCB和原理图中的封装就对应起来了。可以进行二次开发了。\n","date":"2024-11-21T00:00:00Z","image":"https://a233a2.github.io/p/pads%E5%B0%8F%E8%AE%B0/pads.jpg","permalink":"https://a233a2.github.io/p/pads%E5%B0%8F%E8%AE%B0/","title":"PADS小记"},{"content":"参考： Docker-从入门到实践\u0026mdash;一本书 求求你了，用Docker吧\u0026mdash;某博客 Docker安装教程\u0026mdash;官方安装教程 GeekHour-30分钟Docker入门教程\u0026mdash;特别棒\nDocker基础的几个概念 镜像 Image 理解为一个虚拟机的快照，内部包含要部署的应用程序以及他所关联的所有库。 是一个包含有文件系统的面向Docker引擎的只读模板。镜像是一个模板,装了一些系统的配置文件, 我们可以通过镜像建立更多的容器, 容器从镜像启动时,Docker在镜像的上层创建一个可写层, 镜像本身不变。\n容器 Container 通过镜像Image，可以创建许多不同的容器Container。容器可以比喻为一台台运行起来的虚拟机，容器中运行着要部署的程序。每个容器相互独立运行，互不影响。 容器是基于镜像创建, 相互隔离的, 可以理解为小型虚拟机，真正的执行单元。\n相当于每一个实例。\n一台计算机上可以运行几个虚拟机，但是可以运行几百个容器。\n容器和镜像的关系 镜像和容器的关系就像java中类和实例的关系一样（我也没学过java..抄的） 镜像就类似一个食谱，容器就是根据食谱做出来的一道菜。\nDockerfile 自动化脚本，用以创建镜像。\nDocker 仓库-Docker Repository 存放镜像的仓库 最流行的是DockerHub，是一个公共仓库，集中存储和管理Docker镜像。另外还有Harbor\nDocker容器化 将应用程序打包成容器，然后在容器中运行程序的过程。 1# 创建Dockerfile 告诉Docker构建应用程序镜像所需的步骤以及配置。 2# 使用Dockerfile构建镜像。 3# 使用镜像创建和运行容器。\nDocker简单HelloDocker实践 创建HelloDocker文件，创建node.js文件\n1 console.log(\u0026#34;欢迎来到Docker！\u0026#34;) Dockerfile中写入如下的运行流程，交给Docker自动运行。 项目根目录创建名为Dockerfile的文件\n1 2 3 FROM node:14-alpine COPY index.js /index.js CMD [\u0026#34;node\u0026#34;,\u0026#34;/index.js\u0026#34;] 输出结果： 有个小Warning,暂时不清楚原因。\nplay with docker网站 在线运行docker镜像 https://labs.play-with-docker.com/\nDocker命令小计 1 2 3 4 5 docker images #查看安装的镜像 docker image ls #查看安装的镜像 docker run 镜像名称 #运行程序 docker pull xxx #获取镜像 DockerDesktop的使用 DockerDesktop集成了容器日常使用与管理的各种常用功能\nDocker Compose 统筹各个容器，形成一个项目。 使用一个yml文件定义，使用一条命令来自动安装各种依赖以及配置环境。然后在本地运行项目。\n1 $ docker compose up Docker部署深度学习项目实例 先鸽了\nDocker部署PX4开发环境实例 Docker的PX4容器项目地址：https://github.com/PX4/PX4-containers\n1 克隆项目到本地 2 执行 1 2 cd docker docker build -t px4io/px4-dev-ros-melodic -f Dockerfile_ros-melodic . 构建完成镜像大小为6.5GB左右\n3 构建Firmware时使用Docker环境？ 3这个还没试过\n","date":"2024-11-20T00:00:00Z","image":"https://a233a2.github.io/p/docker%E4%BD%BF%E7%94%A8%E5%B0%8F%E8%AE%B0/11.jpg","permalink":"https://a233a2.github.io/p/docker%E4%BD%BF%E7%94%A8%E5%B0%8F%E8%AE%B0/","title":"Docker使用小记"},{"content":"基础 两层神经网络分析为例 摘自zhihu：神经网络15分钟入门！足够通俗易懂了吧 任务描述：在坐标系中，给出一个坐标系，使用神经网络进行分类象限。\n输入层 在我们的例子中，输入层是坐标值，例如（1,1），这是一个包含两个元素的数组， 也可以看作是一个12的矩阵。输入层的元素维度与输入量的特征息息相关，如果输 入的是一张3232像素的灰度图像，那么输入层的维度就是32*32。\n输入层到隐藏层 连接输入层和隐藏层的是W1和b1。由X计算得到H十分简单，就是矩阵运算： $$ H=XW1+b1 $$ 如上图中所示，在设定隐藏层为50维（也可以理解成50个神经元）之后，矩阵H的大小为（150）的矩阵。\n隐藏层到输出层 连接隐藏层和输出层的是W2和b2。同样是通过矩阵运算进行的： $$ Y=H*W2+b2 $$ 通过上述两个线性方程的计算，我们就能得到最终的输出Y了，但是如果你还对线性代数的计算有印象的话，应该会知道：一系列线性方程的运算最终都可以用一个线性方程表示。也就是说，上述两个式子联立后可以用一个线性方程表达。对于两次神经网络是这样，就算网络深度加到100层，也依然是这样。这样的话神经网络就失去了意义。\n激活层 神经网络中的激活层（Activation Layer）主要负责为网络中的每一层神经元引入非线性因素。没有激活函数，神经网络就只能执行线性变换，而线性变换无法表达复杂的模式和特征。因此，激活函数是神经网络能够处理非线性问题、进行更复杂计算的关键。\n简而言之，激活层是为矩阵运算的结果添加非线性的。常用的激活函数有三种，分别是阶跃函数、Sigmoid和ReLU。 其中，阶跃函数输出值是跳变的，且只有二值，较少使用；Sigmoid函数在当x的绝对值较大时，曲线的斜率变化很小（梯度消失），并且计算较复杂；ReLU是当前较为常用的激活函数。 激活函数具体是怎么计算的呢？ 假如经过公式H=X*W1+b1计算得到的H值为：(1,-2,3,-4,7\u0026hellip;)，那么经过阶跃函数激活层后就会变为(1,0,1,0,1\u0026hellip;)，经过ReLU激活层之后会变为(1,0,3,0,7\u0026hellip;)。 需要注意的是，每个隐藏层计算（矩阵线性运算）之后，都需要加一层激活层，要不然该层线性计算是没有意义的。 神经网络之所以能够处理复杂的任务，正是因为非线性激活函数的存在。激活函数将线性变换的输出“扭曲”成非线性，从而让网络能够捕捉数据中的非线性关系，例如在图像、语音、文本等复杂场景中。\n输出的正规化 现在我们的输出Y的值可能会是(3,1,0.1,0.5)这样的矩阵，诚然我们可以找到里边的最大值“3”，从而找到对应的分类为I，但是这并不直观。我们想让最终的输出为概率，也就是说可以生成像(90%,5%,2%,3%)这样的结果，这样做不仅可以找到最大概率的分类，而且可以知道各个分类计算的概率值。\nSoftmax正规化 $$ S_i=\\frac{e^i}{\\sum{_je^j}} $$ 简单来说分三步进行：（1）以e为底对所有元素求指数幂；（2）将所有指数幂求和；（3）分别将这些指数幂与该和做商。这样求出的结果中，所有元素的和一定为1，而每个元素可以代表概率值。 我们将使用这个计算公式做输出结果正规化处理的层叫做“Softmax”层。此时的神经网络将变成如上图所示：\n衡量输出的好坏 通过Softmax层之后，我们得到了I，II，III和IV这四个类别分别对应的概率，但是要注意，这是神经网络计算得到的概率值结果，而非真实的情况。\n比如，Softmax输出的结果是(90%,5%,3%,2%)，真实的结果是(100%,0,0,0)。虽然输出的结果可以正确分类，但是与真实结果之间是有差距的，一个优秀的网络对结果的预测要无限接近于100%，为此，我们需要将Softmax输出结果的好坏程度做一个“量化”。 一种直观的解决方法，是用1减去Softmax输出的概率，比如1-90%=0.1。不过更为常用且巧妙的方法是，求对数的负数。 还是用90%举例，对数的负数就是：-log0.9=0.046 可以想见，概率越接近100%，该计算结果值越接近于0，说明结果越准确，该输出叫做“交叉熵损失（Cross Entropy Error）”。 我们训练神经网络的目的，就是尽可能地减少这个“交叉熵损失”。 反向传播与参数优化 上边的1~4节，讲述了神经网络的正向传播过程。一句话复习一下：神经网络的传播都是形如Y=WX+b的矩阵运算；为了给矩阵运算加入非线性，需要在隐藏层中加入激活层；输出层结果需要经过Softmax层处理为概率值，并通过交叉熵损失来量化当前网络的优劣。 算出交叉熵损失后，就要开始反向传播了。其实反向传播就是一个参数优化的过程，优化对象就是网络中的所有W和b（因为其他所有参数都是确定的）。 神经网络的神奇之处，就在于它可以自动做W和b的优化，在深度学习中，参数的数量有时会上亿，不过其优化的原理和我们这个两层神经网络是一样的。\n迭代 神经网络需要反复迭代。 如上述例子中，第一次计算得到的概率是90%，交叉熵损失值是0.046；将该损失值反向传播，使W1,b1,W2,b2做相应微调；再做第二次运算，此时的概率可能就会提高到92%，相应地，损失值也会下降，然后再反向传播损失值，微调参数W1,b1,W2,b2。依次类推，损失值越来越小，直到我们满意为止。 此时我们就得到了理想的W1,b1,W2,b2。 此时如果将任意一组坐标作为输入，利用图4或图5的流程，就能得到分类结果。\n各类型神经网络 CNN卷积神经网络30分钟入门 摘自：【深度学习-第2篇】CNN卷积神经网络30分钟入门！足够通俗易懂了吧（图解）\n从前馈神经网络到CNN 前馈神经网络（Feedforward Neural Networks）是最基础的神经网络模型，也被称为多层感知机（MLP）。\n它由多个神经元组成，每个神经元与前一层的所有神经元相连，形成一个“全连接”的结构。每个神经元会对其输入数据进行线性变换（通过权重矩阵），然后通过一个非线性函数（如ReLU或Sigmoid）进行激活。这就是前馈神经网络的基本操作。 卷积神经网络（Convolutional Neural Network, 简称CNN）开始。很大程度上，是由于CNN的基本组成部分与前馈神经网络有很紧密的关联，甚至可以说，CNN就是一种特殊的前馈神经网络。 这两者的主要区别在于，CNN在前馈神经网络的基础上加入了卷积层和池化层（下边会讲到），以便更好地处理图像等具有空间结构的数据。\n现在画图说明一下。对于前馈神经网络，我们可以将简化后的网络结构如下图表示： 当然，【全连接层-ReLU】可以有多个，此时网络结构可以表示为： 简单地说，CNN就是在此基础上，将全连接层换成卷积层，并在ReLU层之后加入池化层（非必须），那么一个基本的CNN结构就可以表示成这样： 卷积层 使用卷积是为了更好的处理图像等信息。若使用全连接前馈神经网络来处理图像，会使得参数太多、不利于表达空间上的结构。另外难以反应平移不变性。CNN由于权重共享，可以无论特征在何处出现都能被检测到，从而提供了一种平移不变性。另外难以表征抽象层级。CNN通过多个卷积层和池化层的叠加，可以从低级的边缘和纹理特征逐渐抽取出高级的语义特征。这个特性使得CNN非常适合于处理图像等需要多层抽象表示的数据。 卷积的过程，其实是一种滤波的过程，所以卷积核（Convolution Kernel）还有一个别名叫做Filter，也就是滤波器。 当一组数像滑窗一样滑过另外一组数时，将对应的数据相乘并求和得到一组新的数，这个过程必然和卷积有着莫大的关系。 其中权重系数都为1/3，也就是均值滤波的过程。变换不同的权重系数，滤波器将展现出不同的滤波特性。所以我们又可以得到一个结论：当权重系数（卷积核）的参数改变时，它可以提取的特征类型也会改变。所以训练卷积神经网络时，实质上训练的是卷积核的参数。 1 2 3 4 5 1.定义一个卷积核：卷积核是一个小的矩阵（例如3x3或5x5），包含一些数字。这个卷积核的作用是在图像中识别特定类型的特征，例如边缘、线条等，也可能是难以描述的抽象特征。 2.卷积核滑过图像：卷积操作开始时，卷积核会被放置在图像的左上角。然后，它会按照一定的步长（stride）在图像上滑动，可以是从左到右，也可以是从上到下。步长定义了卷积核每次移动的距离。 3.计算点积：在卷积核每个位置，都会计算卷积核和图像对应部分的点积。这就是将卷积核中的每个元素与图像中对应位置的像素值相乘，然后将所有乘积相加。 4.生成新的特征图：每次计算的点积结果被用来构建一个新的图像，也称为特征图或卷积图。 5.重复以上过程：通常在一个 CNN 中，我们会有多个不同的卷积核同时进行卷积操作。这意味着我们会得到多个特征图，每个特征图捕捉了原始图像中的不同特征。 ReLU在CNN中的位置 卷积层和全连接一样，也是一种线性变换，无论进行多少次这样的操作，都只能获得输入数据的线性组合。如果没有非线性的激活函数，那么即使是多层的神经网络，在理论上也可以被一个单层的神经网络所表达，这极大地限制了网络的表达能力。\nReLU函数是一个非线性函数，只保留正数元素，将负数元素设置为0。这种简单的修正线性单元具有许多优点，例如，它能够缓解梯度消失问题，计算速度快，同时ReLU的输出是稀疏的，这有助于模型的正则化。ReLU的响应函数图像如下： 化繁为简的池化层 ReLU激活层之后就是池化层。 池化层的主要作用是对非线性激活后的结果进行降采样，以减少参数的数量，避免过拟合，并提高模型的处理速度。 池化层主要采用最大池化（Max Pooling）、平均池化（Average Pooling）等方式，对特征图进行操作。以最常见的最大池化为例，我们选择一个窗口（比如 2x2）在特征图上滑动，每次选取窗口中的最大值作为输出，这就是最大池化的工作方式： 大致可以看出，经过池化计算后的图像，基本就是左侧特征图的“低像素版”结果。也就是说池化运算能够保留最强烈的特征，并大大降低数据体量。\n到现在，“卷积层→ReLU→池化层”这样一个CNN网络中的基本组成单元的基础概念就讲完了。但是需要注意，卷积层、ReLU和池化层的组合是一种常见模式，但不是唯一的方式。比如池化层作为降低网络复杂程度的计算环节，在算力硬件条件越来越好的当下，有些时候是可以减少采用次数的，也就是池化层可以在部分层设置、部分层不设置。 关于输出层 在卷积神经网络中，最后一层（或者说最后一部分）通常被称为输出层。这个层的作用是将之前所有层的信息集合起来，产生最终的预测结果。\n对于CNN进行分类任务时，输出部分的网络结构通常是一个或多个全连接层，然后连接Softmax。\n当然，如果想要从卷积层过渡到全连接层，你需要对卷积层的输出进行“展平”处理，简而言之就是将二维数据逐行串起来，变成一维数据。\n由于此时数据经过多层卷积和池化操作，数据量已大大减少，所以全连接层设计的参数就不会有那么多了。\n由基础模块搭建摩天大楼 在实际应用中，CNN网络往往是由多个卷积层构成，后续再缀接卷积层，则就是将上一层的输出作为后续的输入，然后重复“输入层→卷积层→ReLU→池化层”这个过程，当然池化层是非必须的。\n实例分析 CNN基础实验，手写数字识别！ 在bilibili上观看了手写数字识别的教程，跟着配置下来非常简单，是基于Python代码的。调用了torch来进行模型训练与识别。\nmodel.py 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 import torch from torch import nn #定义神经网络Network class Network(nn.Module): def __init__(self): super().__init__() # 线性层1，输入层和隐藏层之间的线性层 self.layer1 = nn.Linear(784, 256) # 线性层2，隐藏层和输出层之间的线性层 self.layer2 = nn.Linear(256, 10) # 在前向传播，forward函数中，输入为图像x def forward(self, x): x = x.view(-1, 28 * 28) # 使用view函数，将x展平 x = self.layer1(x) # 将x输入至layer1 x = torch.relu(x) # 使用relu激活 return self.layer2(x) # 输入至layer2计算结果 #手动的遍历模型中的各个结构，并计算可以训练的参数 def print_parameters(model): cnt = 0 for name, layer in model.named_children(): #遍历每一层 # 打印层的名称和该层中包含的可训练参数 print(f\u0026#34;layer({name}) parameters:\u0026#34;) for p in layer.parameters(): print(f\u0026#39;\\t {p.shape} has {p.numel()} parameters\u0026#39;) cnt += p.numel() #将参数数量累加至cnt #最后打印模型总参数数量 print(\u0026#39;The model has %d trainable parameters\\n\u0026#39; % (cnt)) #打印输入张量x经过每一层时的维度变化情况 def print_forward(model, x): print(f\u0026#34;x: {x.shape}\u0026#34;) # x从一个5*28*28的输入张量 x = x.view(-1, 28 * 28) # 经过view函数，变成了一个5*784的张量 print(f\u0026#34;after view: {x.shape}\u0026#34;) x = model.layer1(x) #经过第1个线性层，得到5*256的张量 print(f\u0026#34;after layer1: {x.shape}\u0026#34;) x = torch.relu(x) #经过relu函数，没有变化 print(f\u0026#34;after relu: {x.shape}\u0026#34;) x = model.layer2(x) #经过第2个线性层，得到一个5*10的结果 print(f\u0026#34;after layer2: {x.shape}\u0026#34;) if __name__ == \u0026#39;__main__\u0026#39;: model = Network() #定义一个Network模型 print(model) #将其打印，观察打印结果可以了解模型的结构 print(\u0026#34;\u0026#34;) print_parameters(model) #将模型的参数打印出来 #打印输入张量x经过每一层维度的变化情况 x = torch.zeros([5, 28, 28]) print_forward(model, x) test.py 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 from model import Network from torchvision import transforms from torchvision import datasets import torch if __name__ == \u0026#39;__main__\u0026#39;: transform = transforms.Compose([ transforms.Grayscale(num_output_channels=1), transforms.ToTensor() ]) # 读取测试数据集 test_dataset = datasets.ImageFolder(root=\u0026#39;./mnist_images/test\u0026#39;, transform=transform) print(\u0026#34;test_dataset length: \u0026#34;, len(test_dataset)) model = Network() # 定义神经网络模型 model.load_state_dict(torch.load(\u0026#39;mnist.pth\u0026#39;)) # 加载刚刚训练好的模型文件 right = 0 # 保存正确识别的数量 for i, (x, y) in enumerate(test_dataset): output = model(x) # 将其中的数据x输入到模型 predict = output.argmax(1).item() # 选择概率最大标签的作为预测结果 # 对比预测值predict和真实标签y if predict == y: right += 1 else: # 将识别错误的样例打印了出来 img_path = test_dataset.samples[i][0] print(f\u0026#34;wrong case: predict = {predict} y = {y} img_path = {img_path}\u0026#34;) # 计算出测试效果 sample_num = len(test_dataset) acc = right * 1.0 / sample_num print(\u0026#34;test accuracy = %d / %d = %.3lf\u0026#34; % (right, sample_num, acc)) train.py 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 import torch from torch import nn from torch import optim from model import Network from torchvision import transforms from torchvision import datasets from torch.utils.data import DataLoader if __name__ == \u0026#39;__main__\u0026#39;: # 图像的预处理 transform = transforms.Compose([ transforms.Grayscale(num_output_channels=1), # 转换为单通道灰度图 transforms.ToTensor() # 转换为张量 ]) # 读入并构造数据集 train_dataset = datasets.ImageFolder(root=\u0026#39;./mnist_images/train\u0026#39;, transform=transform) print(\u0026#34;train_dataset length: \u0026#34;, len(train_dataset)) # 小批量的数据读入 train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) print(\u0026#34;train_loader length: \u0026#34;, len(train_loader)) model = Network() # 模型本身，它就是我们设计的神经网络 optimizer = optim.Adam(model.parameters()) # 优化模型中的参数 criterion = nn.CrossEntropyLoss() # 分类问题，使用交叉熵损失误差 # 进入模型的迭代循环 for epoch in range(10): # 外层循环，代表了整个训练数据集的遍历次数 # 整个训练集要循环多少轮，是10次、20次或者100次都是可能的， # 内存循环使用train_loader，进行小批量的数据读取 for batch_idx, (data, label) in enumerate(train_loader): # 内层每循环一次，就会进行一次梯度下降算法 # 包括了5个步骤: output = model(data) # 1.计算神经网络的前向传播结果 loss = criterion(output, label) # 2.计算output和标签label之间的损失loss loss.backward() # 3.使用backward计算梯度 optimizer.step() # 4.使用optimizer.step更新参数 optimizer.zero_grad() # 5.将梯度清零 # 这5个步骤，是使用pytorch框架训练模型的定式，初学的时候，先记住就可以了 # 每迭代100个小批量，就打印一次模型的损失，观察训练的过程 if batch_idx % 100 == 0: print(f\u0026#34;Epoch {epoch + 1}/10 \u0026#34; f\u0026#34;| Batch {batch_idx}/{len(train_loader)} \u0026#34; f\u0026#34;| Loss: {loss.item():.4f}\u0026#34;) torch.save(model.state_dict(), \u0026#39;mnist.pth\u0026#39;) # 保存模型 数据的下载 数据采用mnist国际通用的手写数字识别库，下载方式采用python自动下载，参考了网上大佬的开源，脚本如下：\n1 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 \u0026#39;\u0026#39;\u0026#39; 1. 通过 torchvision.datasets.MNIST 下载、解压和读取 MNIST 数据集； 2. 使用 PIL.Image.save 将 MNIST 数据集中的灰度图片以 PNG 格式保存。 \u0026#39;\u0026#39;\u0026#39; import sys, os from torchvision.datasets import MNIST from tqdm import tqdm sys.path.insert(0, os.getcwd()) # 将当前工作目录添加到模块搜索路径的开头 if __name__ == \u0026#34;__main__\u0026#34;: # 图片保存路径 root = \u0026#39;mnist_images\u0026#39; # 定义保存图片的根目录 if not os.path.exists(root): # 如果根目录不存在 os.makedirs(root) # 创建根目录 # 训练集60K、测试集10K # torchvision.datasets.MNIST接口下载数据 training_dataset = MNIST( # 实例化torchvision.datasets.MNIST 类，加载MNIST数据集 root=\u0026#39;mnist\u0026#39;, # 数据集将被下载到当前工作目录下的 mnist 文件夹中 train=True, # 指定要下载的是训练集 download=True, # 如果本地路径中没有找到数据集，则联网下载；如果数据集已经存在于指定的 root 目录中，则不会重新下载 ) test_dataset = MNIST( # 实例化torchvision.datasets.MNIST 类，加载MNIST数据集 root=\u0026#39;mnist\u0026#39;, # 数据集将被下载到当前工作目录下的 mnist 文件夹中 train=False, # 指定要下载的是测试集 download=True, # 如果本地路径中没有找到数据集，则联网下载；如果数据集已经存在于指定的 root 目录中，则不会重新下载 ) # 保存训练集图片 with tqdm(total=len(training_dataset), ncols=150) as pro_bar: # 创建进度条，宽度为150个字符 for idx, (X, y) in enumerate(training_dataset): # 遍历训练集，enumerate函数为training_dataset的每个元素生成一个包含索引（idx）和元素本身（X,y）的元组，X代表图像数据，y则为对应标签 # 创建目标文件夹 train_dir = os.path.join(root, \u0026#34;train\u0026#34;, str(y)) # 定义保存训练集图片的目录 if not os.path.exists(train_dir): # 如果目录不存在 os.makedirs(train_dir) # 创建目录 f = os.path.join(train_dir, f\u0026#34;training_{y}_{idx}.png\u0026#34;) # 保存的文件名 X.save(f) # 保存图片，torchvision.datasets.MNIST默认将图像加载为PIL图像格式，.save() 是PIL库中图像对象的一个方法，用于将图像保存到文件 pro_bar.update(n=1) # 更新进度条 # 保存测试集图片 with tqdm(total=len(test_dataset), ncols=150) as pro_bar: # 创建进度条，宽度为150个字符 for idx, (X, y) in enumerate(test_dataset): # 遍历测试集，enumerate函数为training_dataset的每个元素生成一个包含索引（idx）和元素本身（X,y）的元组，X代表图像数据，y则为对应标签 # 创建目标文件夹 test_dir = os.path.join(root, \u0026#34;test\u0026#34;, str(y)) # 定义保存测试集图片的目录 if not os.path.exists(test_dir): # 如果目录不存在 os.makedirs(test_dir) # 创建目录 f = os.path.join(test_dir, f\u0026#34;test_{y}_{idx}.png\u0026#34;) # 保存的文件名 X.save(f) # 保存图片，torchvision.datasets.MNIST默认将图像加载为PIL图像格式，.save() 是PIL库中图像对象的一个方法，用于将图像保存到文件 pro_bar.update(n=1) # 更新进度条 ","date":"2024-11-13T00:00:00Z","permalink":"https://a233a2.github.io/p/cnn%E7%A5%9E%E7%BB%8F%E7%BD%91-%E8%87%AA%E7%94%A8/","title":"CNN神经网-自用"},{"content":"PMSM的数学建模 自然坐标系下PMSM的三相电压方程以及磁链方程。\n磁链（Flux Linkage） 是描述电磁现象的一个物理量，它表示穿过某个回路的磁场的总效应。 在电机中，磁链通常用来描述电流与磁场之间的关系。磁链的概念在电机控制和电磁学中非常重要。 在永磁同步电机（PMSM）和其他电机中，磁链是分析电压、电流和转矩的关键因素。 根据电磁感应定律，磁链的变化会在导体中产生感应电动势（电压）。 在控制系统中，通过控制磁链的大小和方向，可以间接控制电机的转矩和转速。 磁链的定义为：穿过导体线圈的磁通量与线圈匝数的乘积。 数学表达式为：\n$$𝜆=𝑁⋅Φ$$\n$𝜆$表示磁链（Flux Linkage），单位为韦伯（Wb）。\n$N$为线圈的匝数。\n$Φ$为磁通量（Flux），表示穿过线圈的磁场强度的总量，单位也是韦伯（Wb）。\n电流产生的磁链：当电流通过线圈时，会产生一个环绕线圈的磁场，这个磁场的磁通量在线圈中累积，形成磁链。永磁体产生的磁链：在永磁同步电机中，转子上的永磁体产生的磁场也会在定子绕组中产生磁链，这个磁链通常称为“转子磁链”或“永磁体磁链”。 根据法拉第电磁感应定律，磁链的变化率决定了感应电动势的大小，数学表达为： $$e=−dt/dλ$$ 因此，磁链的变化直接影响电机的电压方程和控制策略。\n自然坐标系下PMSM的三相电压方程以及磁链方程 电磁转矩 电磁转矩另外可以理解为电机中磁场储能对机械角度的偏导数。（来源于教材） 这种理解来源于电机中的能量转换原理，即电磁能量的变化会推动转子旋转，形成电磁转矩。\n磁场储能公式 教材中有电流转置，是因为电流需要加一个转置符号通常是因为在电机控制和矩阵运算中，电流和磁链通常表示为向量，而磁场储能计算涉及向量之间的点积或矩阵乘法。为了进行这些运算，需要确保维度匹配，这就是为什么需要将电流向量进行转置。\n电机的机械运动方程 三相坐标变换 对数学模型进行降价与解耦变换。\nClark变换（静止坐标变换） Clark变换（Clarke Transform）是一种将三相交流信号转换为两相直交坐标系的数学变换，广泛应用于电机控制和电力电子领域。其主要目的是简化三相电流或电压的分析和控制，尤其是在进行控制算法设计时（如在空间矢量调制和PID控制中）。 Park变换（同步旋转坐标变换） Park变换（Park Transform）是一种用于将三相交流信号（通常是电流或电压）转换为旋转坐标系的数学变换。它是电机控制中非常重要的工具，特别是在控制电机的矢量控制（Field-Oriented Control, FOC）中，Park变换帮助将三相信号转换为直流信号，从而简化控制策略。 反Park变换 反Park变换（Inverse Park Transform）是Park变换的逆过程，用于将旋转坐标系中的直流信号（$d$ 和 $q$ 分量）转换回三相交流信号（通常是电流或电压）。反Park变换在电机控制中非常重要 ，因为它可以将控制算法输出的信号转换为实际电机所需的三相信号，以便控制电机的运行。\n","date":"2024-10-26T00:00:00Z","permalink":"https://a233a2.github.io/p/pmsm%E5%8E%9F%E7%90%86%E5%8A%A0%E6%8E%A7%E5%88%B6%E6%96%B9%E6%B3%95%E4%BB%A5%E5%8F%8A%E4%BB%BF%E7%9C%9F/","title":"PMSM原理加控制方法以及仿真"},{"content":"此Blog记录了本人按照DRCAN视频推荐的学习顺序来系统学习DRCAN发表的控制相关视频。博客作为笔记使用。\n1-状态空间表达 以一个质量块-弹簧-阻尼器系统来进行分析：\n状态空间表达是即为：系统输入输出状态变量的一个集合，用一阶微分方程的形式表达出来。 状态空间表达与传递函数之间的关系：包括转换方程，以及重要结论：|SI-A|矩阵的特征值就是传递函数的极点！\n2-状态空间方程的解 有点难以推导且认为推导过程不太重要\n3-相图-相轨迹 相轨迹是系统的状态随时间变化的轨迹。当时间变化时，系统的状态点在状态空间中移动，这条移动的轨迹就是相轨迹。\n对于一个阻尼摆，系统的能量会逐渐衰减，最终趋于静止。其相轨迹不会形成闭合的曲线，而是一个逐渐收缩的螺旋，最终收敛到平衡点（摆静止的位置）。这说明系统是稳定的，且有能量损失。\n可以通过分析导数正负来判别该点的稳定性，后面结论重要一些。\n特殊的一种鞍点。在此令了上述x与y的导来确定x的输出？这个有些复杂。应该是用到了线性代数的坐标变换 看不太懂。。看特征值直观点。 对于复数的，而实部为0的特征值。是一个椭圆，Fixed Point为Center。 特征值复数但是实部不为0的时候。 总结上述情况。\n3.5-连续系统的离散化 采样频率至少要为原系统频率的两倍，这样才能重建出原信号。否则可能出现混叠现象，不能复现原系统变化趋势。（2倍只是下限理论值，实际工程中选取5-10倍） Zero Order Hold （ZOH零阶保持器），使得控制量在一个控制周期内保持不变。\n采样周期要与数据处理控制时间相匹配。若数据读入处理需要50ms，则小于50ms的采样周期将变得没有意义。 Error ：G(T) = ∫(0,T) exp(Aτ) dτ·B 状态空间的解输出，在离散系统下的表达形式。离散系统下不关心t，而着重与每个周期。系统输入u在一个周期内可以看作一个常数（ZOH的作用）。所以可以提取出来。\n使用软件将连续系统转化为离散系统 指令c2d(sys,f)。\n4-相图-相轨迹动态系统分析Phase Portrait爱情故事 相轨迹\n5-系统的可控性 讲解了对于状态空间表达的秩判据的相关内容。秩判据的相关数学证明: 另外讲解了定义的可控性是何种意义上的可控性。 6-李雅普诺夫稳定性 讲解了李雅普诺夫稳定性的严禁数学定义。以及对于状态转移矩阵A矩阵特征值的几种形式来定义李雅普诺夫的集中稳定性。 针对非线性系统，区别于传统解微分方程，采用李雅普诺夫第二法进行解决，在此处进行了简单的介绍。 7-线性控制器设计 这部分便是之前现代控制理论方面的根据期望的特征值来确定系统的不同k增益的输入。 令u=kx，对期望的$\\lambda$列出闭环矩阵Acl，对Acl求特征值?（应该是 太久远了 有点遗忘） 列出以$\\lambda$为未知量的方程，结合期望$\\lambda$的特征值方程对应相等。求得k矩阵。 8-LQR控制（Liner Quadratic Regulator） 线性二次型调节器。是一种对控制系统的目标$\\lambda$进行确定的最优控制算法。 其设置了一种惩罚函数J，通过求得MinJ来确定系统最优的$\\lambda$。进而进行线性控制器的设计。 9-状态观测器设计（Luenberger为例） LQR控制等线性控制u=-kx的前提是状态x全部可测。而对于状态不可测的系统需要观测器Observer 也是之前现代控制理论的必做题类型。观测器建立了一个新的反馈系统目标是使得观测值与实际值相差的err为0。 观测器也就是 根据系统现有的输入和输出 来估计系统的状态。 另外根据之前的阻尼器-弹簧-质量块经典系统进行了观测器设计，设定两个特征值期望为-1 -1。 Simulink仿真 观测器状态空间是目标$\\lambda$=-1 -1求解出来的。 Z2状态不可测，直接运行时，估计值与真实值完全重合。 在Z2hat估计值的地方对z2估计值进行赋初值为1的操作时，可以认为z2开始时估计值与真实值有了偏差。 此时，系统的输出图像有了偏差，z1与z2估计值有关，所以两个图像全部出现了偏差。且能够在后续过程中完成跟随观测。 10-可观测性与分离原理 12-非线性理论基础 \u0026hellip;.介绍了正定 半正定 负定 半负定的各种函数。 此处在设计Lyapunov函数的时候使用到了物理上的能量概念，动能与重力势能相结合搞出来了一个真正的能量函数V(x)\n13-不变性原理 对Lyapunov的稳定性判定分了很多的类，抽时间再整理一下。\n14-非线性稳定性设计 设计如下的非线性系统。 将三种u的处理方式整合成三个子系统。 1 直接线性化处理。 2 李雅普诺夫直接法设计 3 李雅普诺夫直接法直接消除非ND项。 设定仿真时间10s，x初始值为10； 输出状态变量为右图所示，输入为左图所示。 1黄色线条 直接线性化处理。 2橙色线条 李雅普诺夫直接法设计 3蓝色线条 李雅普诺夫直接法直接消除非ND项。 可以看到直接线性化处理的方式简单粗暴，因为输入存在x的三次方所以导致开始时输入值非常大。这是难以实现的。 对于李雅普诺夫直接法设计的输入以及输出较为合理。 李雅普诺夫分析后直接消除非ND项的做法较直接线性化处理有一定优势，但是稳态效果不好。\n15-非线性反步法设计-Important 反步法设计可以说是非线性链式系统的通用设计方法。 如下图，输入可以直接控制X2的状态，但是输入无法直接影响状态X1。 对两个引入的状态进行稳定性分析。 Dr.Can设计的Simulink仿真模型。 对例题进行分析 16-非线性自适应控制器 自认为的自适应控制器简化设计步骤： 自适应控制器是处理a参数未知的情况。通过设计一个估计值，再引入估计误差。 估计误差的导数因为a参数缓慢变化，所以a的导数为0。但是不禁让人思考a变化迅速的时候呢？ 对估计误差与原本控制误差进行联合Lyapunov稳定行为分析。设计u使得李函数的导数为ND。 在之前认为a参数已知的反馈线性化的设计过程中，把a换为a的估计值，带入到u中。\n处理联合的李函数，通过设计a的估计使得难以负定的项为0。得到a估计的导的Hope值。 此时把a积分，再带回反馈系统线性化设计的u。设计完成。 （貌似这些设计的步骤都是通用的，都类似反步法的设计步骤）\n此时联合李函数是NSD的。不能说a估计和e趋近于0，只能说他们是稳定的。 需要引入Lyapunov-like lemma。证明一下。\n参考DrCan以及其他学员分享的Simulink设计，设计如下的系统。 Xd改为变化量，a改为变化量。在k等于20的情况下跟踪性能也还行。\nk太小不行。k小的时候x根本无法完成跟踪。k必须很大才能很好的跟踪。 但是k太大的话会导致u变得十分的抽象，显然是难以实现u的输出 不符合实际工程的运用。 下图为k为20的跟踪情况，感觉已经非常不错了！ 下图为k为20的输入u的情况。可以看到已经有点抽象了，幅度跨度很大。也可能是我这个系统的a变化幅度太大，以及目标值太过苛刻。不知道实际工程中的使用情况是如何的？ 课后题：对经典弹簧系统进行非线性自适应控制器的设计 自己做的有点错误 标准答案如下图片 17-非线性鲁棒控制器 鲁棒控制相关可以看山东科技大学的周克敏教授视频：https://space.bilibili.com/615075414\n17-1 滑膜控制器 这里滑模控制的形式就是使得 $$ u=ke+\\dot{x}_d+\\rho \\frac{\\left| e \\right|}{e} $$ 而e的项是一个类似符号函数sgn(e)的东西。\n而$\\rho$是一个大于fx的绝对值的一个函数。 将u的形式代回到e导中，画出e和e导的状态图相轨迹。可以看到一个面-滑模面，系统状态就在这个面上趋近于0。 部分摘自：滑膜控制的简单理解-知乎 上图右侧是证明过程，在此用到了一个数学手法，对微分方程的不等式的证明，引入了一个松弛变量来变为等式。最终通过微分方程的通解的方式解出李函数在经过放缩，得出李函数小于某值。 接着将李函数反带回这个不等式，直接解出e的状态是小于xx值的，最后得出这个状态e是指数渐进稳定的。这个证明手法确实巧妙。\n17-2 其他两种鲁棒控制 高增益/高频 在Drcan的视频中解释鲁棒控制的u通式都是 $$ u=ke+\\dot{x}d+u{aux} $$ 其中$u_{aux}$是辅助用的。针对不同的鲁棒控制，仅仅是$u_{aux}$而已不同。 下图给出了其他两种鲁棒控制的$u_{aux}$形式。另外给出了证明过程。都是与滑膜的证明过程相似的过程。可以好好学习一下。 可以简单的理解 高增益控制方式就是使用足够大的输入去抵消不确定性。 而高频的控制方式就是滑膜的一种变式，通过设置参数使得其相较于滑模控制更为平和。 以下是对三种控制方式进行建模分析。对滑膜控制分一个子系统，对其他两种分别不同的参数大小分别设置4个子系统如下图： 运行结果：\n首先是四个系统的误差消除情况如下图：\n接着是四个系统的输入情况： 分析： 滑膜：输入极为抽象，控制效果中规中矩。 高增益0.1：输入在开始时很大，很抽象，但后续很平滑，收敛速度也最快。 高增益1：都差不多。 高频0.1：都差不多。 高频1：都差不多，收敛效果最差。\n","date":"2024-10-23T00:00:00Z","image":"https://a233a2.github.io/p/drcan-learn-blog-advance/1-1.jpg","permalink":"https://a233a2.github.io/p/drcan-learn-blog-advance/","title":"DRCAN-Learn-Blog-Advance"},{"content":"Hugo博客的个人git部署工作流 自动部署BAT文件 (Auto Push) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @echo off ::获取当前脚本路径 cd /d %~dp0 ::自动提交 git init git add . git commit -m \u0026#34;Auto Push:%date:~10%,time:~0,8%\u0026#34; :: git commit -m git push origin main -f @echo 已完成 SET daoTime=60 :dao set /a daoTime=daoTime-1 ping -n 2 -w 500 127.1\u0026gt; cls echo 上传git完成，倒计时退出：%daoTime%秒 if%daoTime%==0 (exit0) else (goto dao) Git合并分支(多端协作使用) 每次进行写博客时，都需要进行pull操作。\n1 2 git reset --hard //在需要合并的本地有修改导致冲突时 使用此语句。 git pull origin main 解决Push以及Pull超时错误的问题 方法：将代理端口与Git端口设置为一致即可\n本机代理设置如下所示：\n之后将Git配置为一致的端口号以及地址：\n1 2 git config --global http.proxy http://127.0.0.1:7890 git config --global https.proxy http://127.0.0.1:7890 2024-11-14错误记录 引用gif文件时，出现编译错误：配置项被弃用以及主题模板中可能存在的无限递归问题 使用到的引用格式：! [ASD] (ASD.GIF) 更换HTML标签渲染后问题解决。\n1 \u0026lt;img src=\u0026#34;路径/文件名.gif\u0026#34; alt=\u0026#34;描述文字\u0026#34; width=\u0026#34;500\u0026#34; height=\u0026#34;auto\u0026#34;\u0026gt; 分析：可能是这个主题不支持GIF？？？\n解决网站域名转发时自动部署删除CNAME文件的问题 在网站域名转发时，在github.io这个工程仓库下创建了CNAME文件，但是每次部署网站都会自动的删除掉。这使得我的网站无法打开。\n最后将CNAME文件放到了网站文件夹目录下的static文件下。重新上传后CNAME文件便一直都在了。\n基于Hugo-GithubPages的新站建站记录 首先下载Hugo hugo目录下，使用Hugo new site name命令，创建一个Hugo站点文件夹。\n构建完成后，hugo提示了接下来的任务。首先就是更换目录。 创建完后，应该是如下的内容结构。这个目录下缺少hugo.exe，我们复制一个进来。 使用如下命令，来构建网站。这里没有主题，构建完后应该是什么都没有。 选择一个主题，这里我选择下图这个主题。\n主题下载到本地文件，hugo中这个themes目录下。 这里是网站文章的主要存放文件，我们需要把这两个复制出来。复制到dev目录下。 toml文件为主题的主要设置文件，有的主题中为yaml文件。 content为文章的内容。 需要注意下，toml设置文件中，主题的名字需要和themes目录下这个文件一样，所以我们改一下让这两个地方名字一致即可。 现在创建一个仓库，用来存放dev主目录下的文件。这个最好设置为Private私人仓库。 再创建一个仓库，用来存放dev\\public下的构建完后的网站文件。这个可以创建为Public公开。 我创建的如下： 这里我两个网站合并到一个主目录下了。 hugo主目录下 设置一下别都上传。 这里手动PUSH一下。把两个仓库先推上去试试。推之前最好本地hugo server -D 构建一下试试，先能构建出来。 自用-Push本地项目文件到一个新建的空仓库 起因是自己用win的git push一个工程时遇到太多问题，故写一个自己用的错误解决路径在这里给自己用。\n1 2 3 4 git init git add . git commit -m \u0026#34;first commit\u0026#34; git remote add origin 你的链接github.com/// git push -u origin main\n此时 错误 可能是本地master对不上远程仓库的main\n运行git branch -m master main 本地的master改名为main\n继续git push -u origin main此时仍然错误，可能是本地有文件，远程仓库也有点文件，历史对不上去。\n我们先Pull一下,再Push。\n1 2 git pull origin main --allow-unrelated-histories git push -u origin main 我近期手动更新了git 到了2.48.1 之前是2.47.1\n","date":"2024-10-14T00:00:00Z","permalink":"https://a233a2.github.io/p/hugo%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%AA%E4%BA%BAgit%E9%83%A8%E7%BD%B2%E5%B7%A5%E4%BD%9C%E6%B5%81/","title":"Hugo博客的个人git部署工作流"},{"content":"FreeRTOS FreeRTOS在嵌入式的开发中十分的重要，本人之前对于同一时间进行多种任务的做法普遍都是设置定时器中断程序。这样做或许能解决燃眉之急，但是在大型项目中便有一些捉襟见肘，尤其是使用这种方式对于MCU的任务调度十分的不明确，显得十分的乱。故对于FreeRTOS这种MCU操作系统的应用便显得尤为重要。 官方文档：https://freertos.org/zh-cn-cmn-s/Documentation/00-Overview\nFreeRTOS核心机制 优先级调度 高优先级的准备就绪后，直接抢占CPU。\nFCFS(先来先服务)\n同优先级，按照准备就绪的先后顺序执行。\n时间片轮询\n有相同优先级的两个任务，其中一个一直没被抢占，则运行时间达到一个时间片则主动让出CPU。\nps：低优先级的任务对时间的控制较为不精确。高优先级的任务可以做到时间精确控制。\nFreeRTOS延时函数 vTaskDelay(ms);\n当前任务暂时暂停xx ms，程序进入阻塞态，其中暂时时间内可以执行其他任务。\nHAL_Delay(ms);\nCPU暂时挂起xx ms，啥也不执行。类似裸机运行时的delay。\nFreeRTOS的部署 RT-Thread ","date":"2024-09-18T00:00:00Z","permalink":"https://a233a2.github.io/p/freertos%E5%B0%8F%E8%AE%B0/","title":"FreeRTOS小记"},{"content":"KiCad学习日记 导出Gerber文件-JLC打样 1 导出Gerber的设置： 从b站搬运：https://www.bilibili.com/read/cv22773523/?spm_id_from=333.976.0.0 （KiCAD设计使用立创免费打样的方法及KiCAD插件） 生成gerber文件\n生成钻孔文件\n生成的gerber文件最终结构如下：\n2 修改Gerber文件使得可在JLC打样 创建两个文件 1 2 from .give_me_free_PCB import GiveMeFreePCB # Note the relative import! GiveMeFreePCB().register() # Instantiate and register to Pcbnew 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 import pcbnew import os import shutil import zipfile import datetime # 工程目录下Gerber和钻孔文件的存放位置 path_out = \u0026#34;out\u0026#34; # 下单用的文件的位置 path_final = \u0026#34;out/final\u0026#34; # 用于检查文件是否为Gerber文件以判断是否进行替换操作 file_filter = (\u0026#39;.gbl\u0026#39;,\u0026#39;.gbs\u0026#39;,\u0026#39;.gbp\u0026#39;,\u0026#39;.gbo\u0026#39;,\u0026#39;.gm1\u0026#39;,\u0026#39;gm13\u0026#39;, \u0026#39;.gtl\u0026#39;,\u0026#39;.gts\u0026#39;,\u0026#39;.gtp\u0026#39;,\u0026#39;.gto\u0026#39;,\u0026#39;.drl\u0026#39;,\u0026#39;.G1\u0026#39;, \u0026#39;.G2\u0026#39;,\u0026#39;.gko\u0026#39;) jlc_header=\u0026#34;\u0026#34;\u0026#34;G04 Layer: BottomSilkscreenLayer* G04 EasyEDA v6.5.25, 2023-03-20 21:11:36* **********************************至少这一行要换成自己的************************************ G04 Gerber Generator version 0.2* G04 Scale: 100 percent, Rotated: No, Reflected: No * G04 Dimensions in millimeters * G04 leading zeros omitted , absolute positions ,3 integer and 6 decimal *\\n\u0026#34;\u0026#34;\u0026#34; # 两张对应表，分别根据结尾和文件名来判断该给什么生成的文件什么名称 replace_list_end = [(\u0026#39;.gbl\u0026#39;,\u0026#34;Gerber_BottomLayer.GBL\u0026#34;), (\u0026#39;.gko\u0026#39;,\u0026#34;Gerber_BoardOutlineLayer.GKO\u0026#34;), (\u0026#39;.gbp\u0026#39;,\u0026#34;Gerber_BottomPasteMaskLayer.GBP\u0026#34;), (\u0026#39;.gbo\u0026#39;,\u0026#34;Gerber_BottomSilkscreenLayer.GBO\u0026#34;), (\u0026#39;.gbs\u0026#39;,\u0026#34;Gerber_BottomSolderMaskLayer.GBS\u0026#34;), (\u0026#39;.gtl\u0026#39;,\u0026#34;Gerber_TopLayer.GTL\u0026#34;), (\u0026#39;.gtp\u0026#39;,\u0026#34;Gerber_TopPasteMaskLayer.GTP\u0026#34;), (\u0026#39;.gto\u0026#39;,\u0026#34;Gerber_TopSilkscreenLayer.GTO\u0026#34;), (\u0026#39;.gts\u0026#39;,\u0026#34;Gerber_TopSolderMaskLayer.GTS\u0026#34;), (\u0026#39;.gd1\u0026#39;,\u0026#34;Drill_Through.GD1\u0026#34;), (\u0026#39;.gm1\u0026#39;,\u0026#34;Gerber_MechanicalLayer1.GM1\u0026#34;), (\u0026#39;.gm13\u0026#39;,\u0026#34;Gerber_MechanicalLayer13.GM13\u0026#34;)] replace_list_contain = [(\u0026#39;_PCB-PTH\u0026#39;, \u0026#34;Drill_PTH_Through.DRL\u0026#34;), (\u0026#39;_PCB-NPTH\u0026#39;, \u0026#34;Drill_NPTH_Through.DRL\u0026#34;), (\u0026#39;-PTH\u0026#39;, \u0026#34;Drill_PTH_Through.DRL\u0026#34;), (\u0026#39;-NPTH\u0026#39;, \u0026#34;Drill_NPTH_Through.DRL\u0026#34;), (\u0026#39;_PCB-In1_Cu\u0026#39;, \u0026#34;Gerber_InnerLayer1.G1\u0026#34;), (\u0026#39;_PCB-In2_Cu\u0026#39;, \u0026#34;Gerber_InnerLayer2.G2\u0026#34;), (\u0026#39;_PCB-Edge_Cuts\u0026#39;, \u0026#34;Gerber_BoardOutlineLayer.GKO\u0026#34;)] def zipFolder(folder_path, output_path): \u0026#34;\u0026#34;\u0026#34; 压缩指定路径下的文件夹 :param folder_path: 要压缩的文件夹路径 :param output_path: 压缩文件的输出路径 \u0026#34;\u0026#34;\u0026#34; with zipfile.ZipFile(output_path, \u0026#34;w\u0026#34;, zipfile.ZIP_DEFLATED) as zip: for root, dirs, files in os.walk(folder_path): for file in files: file_path = os.path.join(root, file) zip.write(file_path, os.path.relpath(file_path, folder_path)) # 读取Gerber文件和钻孔文件，修改名称并给Gerber文件内容添加识别头后写入到输出文件夹 def fileTransform(filename, path_out): # 按行读取文件内容 lines = open(filename).readlines() # 检查文件类型并给新文件取好相应的名称，写入识别头和原来的文件内容 hit_flag = 0 for replace_couple in replace_list_end: if filename.endswith(replace_couple[0]): file_new = open(path_out + \u0026#39;/\u0026#39; + replace_couple[1], \u0026#39;w\u0026#39;) hit_flag = 1 break if hit_flag == 0: for replace_couple in replace_list_contain: if filename.find(replace_couple[0]) != -1: file_new = open(path_out + \u0026#39;/\u0026#39; + replace_couple[1], \u0026#39;w\u0026#39;) hit_flag = 1 break if hit_flag == 1: hit_flag = 0 file_new.write(jlc_header) for line in lines: file_new.write(line) file_new.close() def pathInit(path_out): # 检查下目录是否存在，没有就创建 folder_out = os.path.exists(path_out) if not folder_out: os.makedirs(path_out) print(\u0026#34;Folder %s created!\u0026#34; % path_out) else: print(\u0026#34;Folder \\\u0026#34;%s\\\u0026#34; already exists!\u0026#34; % path_out) # 清空目录 for files in os.listdir(path_out): path = os.path.join(path_out, files) try: shutil.rmtree(path) except OSError: os.remove(path) print(\u0026#34;Folder \\\u0026#34;%s\\\u0026#34; clean!\u0026#34; % path_out) class GiveMeFreePCB(pcbnew.ActionPlugin): def defaults(self): self.name = \u0026#34;Give me free PCB!\u0026#34; self.category = \u0026#34;A descriptive category name\u0026#34; self.description = \u0026#34;A description of the plugin\u0026#34; self.show_toolbar_button = True # Optional, defaults to False self.icon_file_name = os.path.join(os.path.dirname(__file__), \u0026#39;icon.png\u0026#39;) # Optional # 关于路径，写的是处理工程目录下out目录里的文件， def Run(self): # 获取当前工程路径 path_workdir = os.environ.get(\u0026#39;KIPRJMOD\u0026#39;) # 把工程根目录设为工作目录 os.chdir(path_workdir) path_out_abs = os.path.join(os.getcwd(), path_out) pathInit(os.path.join(path_workdir, path_final)) file_count = 0 path_files = os.listdir(os.path.join(os.getcwd(), path_out)) # 遍历out目录下的文件，识别类型并进行相应的处理 for p in path_files: if(os.path.isfile(os.path.join(path_out_abs, p))): if(p.endswith(file_filter)): print(\u0026#34;Gerber file %s found.\u0026#34; % p) fileTransform(os.path.join(path_out_abs, p), os.path.join(os.getcwd(), path_final)) file_count += 1 timestamp = datetime.datetime.now().strftime(\u0026#39;%Y%m%d%H%M%S\u0026#39;) board = pcbnew.GetBoard() project_name = os.path.splitext(os.path.basename(board.GetFileName()))[0] zipFolder(path_out_abs + \u0026#39;/\u0026#39; + \u0026#34;final\u0026#34;, path_out_abs + \u0026#39;/\u0026#39; + \u0026#34;out_\u0026#34; + project_name + \u0026#39;-\u0026#39; + timestamp + \u0026#34;.zip\u0026#34;) # 打开资源管理器 os.system(\u0026#34;explorer.exe %s\u0026#34; % path_out_abs) 对源文件进行替换，把标*的行之上换为自己的工程的。\n在Kicad的脚本控制台中输入如下：来引入脚本 任意选择其中一个地址放入两个.py文件。之后点击工具-外部插件-刷新插件。出现新的插件即表示成功，注意文件编码为UTF-8，不然插件刷新不出来 输出时候需要输出Gerber的时候在路径那里敲个./out，不然找不到文件。\n然后点击插件即可输出JLC支持的Gerber文件！\n","date":"2024-09-16T00:00:00Z","permalink":"https://a233a2.github.io/p/kicad%E5%B0%8F%E8%AE%B0/","title":"KiCad小记"},{"content":"学习记录： 学习记录。 内容：The Missing Semester of Your CS Education 中文版\nLinuxBasic GUI和CLI是两种用户与计算机进行交互的方式。 GUI（Graphical User Interface，图形用户界面）：GUI是一种基于图形化界面的操作系统用户界面。 CLI（Command Line Interface，命令行界面）：CLI是一种通过在命令行或终端界面输入命令来与计算机进行交互的用户界面。 安装PA所需工具\n1 2 3 4 5 6 7 apt-get install build-essential # build-essential packages, include binary utilities, gcc, make, and so on apt-get install man # on-line reference manual apt-get install gcc-doc # on-line reference manual for gcc apt-get install gdb # GNU debugger apt-get install git # revision control system apt-get install libreadline-dev # a library used later apt-get install libsdl2-dev # a library used later Ubuntu 中安装中文输入法\n1 2 3 4 5 6 7 8 9 #安装框架 sudo apt install ibus #切换框架 im-config -s ibus #由于Ubuntu Desktop 20.04使用的是GNOME桌面，所以需要安装相应的平台支持包 sudo apt install ibus-gtk ibus-gtk3 #选择简体拼音输入法，完成安装 sudo apt install ibus-pinyin #完成安装后，将中文输入法添加到输入源选项中 以上摘录自：https://blog.csdn.net/Aimonii/article/details/141260938\nLinux基础课程概览与shell 1 missing:~$ $ 符号表示您现在的身份不是 root 用户,位置是 ~ (表示 “home”)。\necho命令 ‌echo命令是Linux中用于在终端输出字符串的命令，常用于脚本编程、调试和显示变量值。\nwhich命令 用于定位执行文件的路径。当输入一个命令时，which 会在环境变量 PATH 所指定的路径中搜索每个目录，以查找指定的可执行文件。 可以使用 which 程序。通过直接指定需要执行的程序的路径来执行该程序。\npwd命令 获取当前工作目录。\ncd命令 切换目录 . 表示的是当前目录，而 .. 表示上级目录\nls命令 查看指定目录下包含哪些文件 利用第一个参数指定目录，否则 ls 会打印当前目录下的文件。 通常，在执行程序时使用 -h 或 \u0026ndash;help 标记可以打印帮助信息，以便了解有哪些可用的标记或选项。\nls: List directory contents dir: Briefly list directory contents dir: Verbosely list directory contents dircolors: Color setup for ls\nls -l是Linux和unix命令，意思指以长格式的形式查看当前目录下所有可见文件的详细属性。\nman命令 它会接受一个程序名作为参数，然后将它的文档（用户手册）展现给您。注意，使用 q 可以退出该程序。\ncat命令 cat命令是Linux系统中用于查看、创建和合并文件内容的工具。‌它源自英文单词“concatenate”，意为“连接”。cat命令的基本功能是将多个文件的内容串联起来显示在标准输出（通常是终端）上。\n查看单个文件的内容‌：使用命令 cat filename。 创建新文件并输入内容‌：使用命令 cat \u0026gt; newfile，用户可以在终端中输入内容，然后使用Ctrl+D保存文件。 ‌合并多个文件的内容‌：使用命令 cat file1 file2，这将合并file1和file2的内容并显示在终端上。 ‌重定向输出‌：可以将命令的输出重定向到文件中，使用 \u0026gt; 符号创建新文件，使用 \u0026raquo; 符号将内容附加到现有文件。‌\ntouch命令 创建一个文件。\nmkdir命令 创建新文件夹\nrm命令 删除指定的文件。谨慎使用\nrm \u0026lt;文件名\u0026gt;：删除指定的文件。 rm -r \u0026lt;目录名\u0026gt;：递归删除指定目录下的所有文件和子目录。 rm -f \u0026lt;文件名\u0026gt;：强制删除，不进行确认提示。 rm -i \u0026lt;文件名\u0026gt;：在删除前进行确认提示。\n重定向输入输出流 \u0026lt; file和 \u0026gt; file\n1 echo hello \u0026gt; hello.txt 给hello.txt里面写入hello。\nsudo root提升权限。\nchmod命令 chmod（change mode）命令是用于控制用户对文件的权限的命令。 Linux/Unix 的文件调用权限分为三级 : 文件所有者（Owner）、用户组（Group）、其它用户（Other Users）如下： 只有文件所有者和超级用户可以修改文件或目录的权限。可以使用绝对模式（八进制数字模式），符号模式指定文件的权限，而使用权限则为所有使用者。 chmod 777 file 给全部权限打开。读 + 写 + 执行\n1 2 chmod 777 file//表示User、Group、及Other的权限都设为rwx chmod 764 file//表示User、Group、及Other的权限分别为rwx、rw-、r-- 以上摘录自：https://blog.csdn.net/qq_52836452/article/details/129642664\nsh命令 sh是linux中运行shell的命令，是shell的解释器，shell脚本是linux中壳层与命令行界面，用户可以在shell脚本输入命令来执行各种各样的任务。 shell程序必须以“#!/bin/sh”开始。shell中#一般表示注释的意思，所以很多时候认为\u0026quot;#!\u0026ldquo;也是注释，但实际上并不是。\nPS \u0026ldquo;#!/bin/sh\u0026quot;是对shell的声明，说明你所用的是哪种类型的shell及其路径所在。 #!/bin/是指此脚本使用.bin/sh来执行。 #!是特殊的表示符，其后面跟的是解释此脚本的shell的路径，如果没有声明，则脚本将在默认的shell中执行，默认shell是由用户所在的系统定义为执行shell脚本，如果脚本被编写为在Kornshell ksh中运行，而默认运行shell脚本的为C shell csh,则脚本在执行过程中很可能失败。所以建议大家就把\u0026rdquo;#!/bin/sh\u0026quot;当成C 语言的main函数一样，写shell必须有，以使shell程序更严密。\n以上摘录自：https://blog.csdn.net/mrsgflmx/article/details/143429498 WSL2下查看笔记本电量。虚拟机无此数据，无法查看。 Shell工具和脚本 \u0026lsquo;xx\u0026rsquo;与\u0026quot;xx\u0026quot;符号的区别\n1 2 3 4 5 foo=bar echo \u0026#34;$foo\u0026#34; # 打印 bar echo \u0026#39;$foo\u0026#39; # 打印 $foo bash支持函数操作\n1 2 3 4 mcd () { mkdir -p \u0026#34;$1\u0026#34; cd \u0026#34;$1\u0026#34; } 这里 $1 是脚本的第一个参数。与其他脚本语言不同的是，bash 使用了很多特殊的变量来表示参数、错误代码和相关变量。\n$0 - 脚本名 $1 到 $9 - 脚本的参数。 $1 是第一个参数，依此类推。 $@ - 所有参数 $# - 参数个数 $? - 前一个命令的返回值 $$ - 当前脚本的进程识别码 !! - 完整的上一条命令，包括参数。常见应用：当你因为权限不足执行命令失败时，可以使用 sudo !! 再尝试一次。 $_ - 上一条命令的最后一个参数。如果你正在使用的是交互式 shell，你可以通过按下 Esc 之后键入 . 来获取这个值 命令通常使用 STDOUT 来返回输出值，使用 STDERR 来返回错误及错误码，便于脚本以更加友好的方式报告错误。 返回码或退出状态是脚本/命令之间交流执行状态的方式。返回值 0 表示正常执行，其他所有非 0 的返回值都表示有错误发生。 退出码可以搭配 \u0026amp;\u0026amp;（与操作符）和 ||（或操作符）使用，用来进行条件判断，决定是否执行其他程序。它们都属于短路 运算符（short-circuiting） 同一行的多个命令可以用 ; 分隔。程序 true 的返回码永远是 0，false 的返回码永远是 1。\n编辑器 (Vim) 数据整理 命令行环境 版本控制(Git) ","date":"2024-09-16T00:00:00Z","permalink":"https://a233a2.github.io/p/linux/","title":"Linux"},{"content":"臭猫FOC驱动器设计日志 主控芯片：WCH-CH32V307VCT6\n预驱芯片：EG2133\nMOS：NEC6050\n下载芯片：CH549G\n电流检测：INA240A2 2024-9-15 期待已久的FOC驱动器终于开工设计！设计完成PCB以及原理图。第一次设计双路的FOC驱动器，也是第一次是设计功率地与普通的GND隔离。\n另外就是电流检测芯片选择的是TI的INA240A2，有点难以购买qaq0.0每个芯片的价格在3元左右，属实有点成本爆炸。下次准备学习更换更为便宜的INA199或者其他国产方案。\n2024-9-17 计划物料到货开始焊接\n这板子有一些bug，就是CH549G下载电路的供电有些问题。在使用DC-12v供电测试的时候，CH549G下载电路的供电不稳定。 另外在开环测试的时候供电也不稳定。不知道是不是程序问题。 接下来抽时间对驱动器进行软件开发\n方波驱动测试 在每个换相周期内，定子绕组中的电流被切换为高或低两个状态，形成矩形波。通常采用六步换向法，即每60度电角度换相一次，从而产生转矩。\n开环FOC驱动测试 开环运行即根据系统时间为索引，根据时间固定生成三相的正弦波。需要涉及Clark变换以及Park变换。 simpleFOC的开环速度代码如下 参考了：https://github.com/haotianh9/DengFOC_on_STM32 电压控制\n1 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 static inline uint32_t LL_SYSTICK_IsActiveCounterFlag(void) { return ((SysTick-\u0026gt;CTRL \u0026amp; SysTick_CTRL_COUNTFLAG_Msk) == (SysTick_CTRL_COUNTFLAG_Msk)); } uint32_t getCurrentMicros(void) { /* Ensure COUNTFLAG is reset by reading SysTick control and status register */ LL_SYSTICK_IsActiveCounterFlag(); uint32_t m = HAL_GetTick(); const uint32_t tms = SysTick-\u0026gt;LOAD + 1; __IO uint32_t u = tms - SysTick-\u0026gt;VAL; if (LL_SYSTICK_IsActiveCounterFlag()) { m = HAL_GetTick(); u = tms - SysTick-\u0026gt;VAL; } return (m * 1000 + (u * 1000) / tms); } //【应该是获取运行时间的代码段。】 float _electricalAngle(float shaft_angle, int pole_pairs) { return (shaft_angle * pole_pairs); } float _normalizeAngle(float angle){ float a = fmod(angle, 2*M_PI); //取余运算可以用于归一化，列出特殊值例子算便知 return a \u0026gt;= 0 ? a : (a + 2*M_PI); //三目运算符。格式：condition ? expr1 : expr2 //其中，condition 是要求值的条件表达式，如果条件成立，则返回 expr1 的值，否则返回 expr2 的值。 //可以将三目运算符视为 if-else 语句的简化形式。 //fmod 函数的余数的符号与除数相同。因此，当 angle 的值为负数时，余数的符号将与 _2M_PI 的符号相反。 //也就是说，如果 angle 的值小于 0 且 _2M_PI 的值为正数，则 fmod(angle, _2M_PI) 的余数将为负数。 //例如，当 angle 的值为 -M_PI/2，_2M_PI 的值为 2M_PI 时，fmod(angle, _2M_PI) 将返回一个负数。 //在这种情况下，可以通过将负数的余数加上 _2M_PI 来将角度归一化到 [0, 2M_PI] 的范围内，以确保角度的值始终为正数。 } void setPwm(float Ua, float Ub, float Uc) { //\t// 限制上限 Ua = _constrain(Ua, 0.0f, voltage_limit); Ub = _constrain(Ub, 0.0f, voltage_limit); Uc = _constrain(Uc, 0.0f, voltage_limit); // 计算占空比 // 限制占空比从0到1 dc_a = _constrain(Ua / voltage_power_supply, 0.0f , 1.0f ); dc_b = _constrain(Ub / voltage_power_supply, 0.0f , 1.0f ); dc_c = _constrain(Uc / voltage_power_supply, 0.0f , 1.0f ); //写入PWM到PWM 0 1 2 通道 TIM1-\u0026gt;CCR1 = (uint32_t) roundf(dc_a*period); TIM1-\u0026gt;CCR2 = (uint32_t) roundf(dc_b*period); TIM1-\u0026gt;CCR3 = (uint32_t) roundf(dc_c*period); } void setPhaseVoltage(float Uq,float Ud, float angle_el) { angle_el = _normalizeAngle(angle_el + zero_electric_angle); // 帕克逆变换 Ualpha = -Uq*sin(angle_el); Ubeta = Uq*cos(angle_el); // 克拉克逆变换 Ua = Ualpha + voltage_power_supply/2; Ub = (sqrt(3)*Ubeta-Ualpha)/2 + voltage_power_supply/2; Uc = (-Ualpha-sqrt(3)*Ubeta)/2 + voltage_power_supply/2; setPwm(Ua,Ub,Uc); } //开环速度函数 float velocityOpenloop(float target_velocity){ //\tuint32_t now_us = getCurrentMicros(); //\tuint32_t now_us = HAL_GetTick(); // Provides a tick value in microseconds. //计算当前每个Loop的运行时间间隔 // float Ts = (now_us - open_loop_timestamp) * 1e-3f; float Ts=5E-3f; // 通过乘以时间间隔和目标速度来计算需要转动的机械角度，存储在 shaft_angle 变量中。 //在此之前，还需要对轴角度进行归一化，以确保其值在 0 到 2π 之间。 shaft_angle = _normalizeAngle(shaft_angle + target_velocity*Ts); //以目标速度为 10 rad/s 为例，如果时间间隔是 1 秒，则在每个循环中需要增加 10 * 1 = 10 弧度的角度变化量，才能使电机转动到目标速度。 //如果时间间隔是 0.1 秒，那么在每个循环中需要增加的角度变化量就是 10 * 0.1 = 1 弧度，才能实现相同的目标速度。 //因此，电机轴的转动角度取决于目标速度和时间间隔的乘积。 // Uq is not related to voltage limit float Uq = 5.5; setPhaseVoltage(Uq, 0, _electricalAngle(shaft_angle, pole_pairs)); // open_loop_timestamp = now_us; //用于计算下一个时间间隔 return Uq; } 波形图根据上述代码来生成的话有些错误。这导致电机不能正确转动。而发出固定的啸叫。\n时间改为us后，波形图更加混乱。\n将时间的获取改为定时器中断+全局变量后。首先设置1us生成波形图如下：\n调整为200us时波形图如下：\n按理说这样运行开环已经可以完美运行，但是接入电脑5v-usb供电的时候电机依然跳动+啸叫。\n接入正常12v电源供电时，板子直接没了反应。。可能是设计的电源出现了问题。将电压降低到5v后板子可以正常上电启动，但是无法正常运行，整体是一种上电运行后，电机转动力巨大，然后系统死机重启陷入了死循环。\n电源设计还是不太过关。。。而且设计的有些混乱。。计划下一板改进，但是这种工程耗费时间太大了，下次进行工程实现之前应该提前做好功课。\nAS5600磁编码器电机底板 2024-9-18 PCB刚刚到货，准备抽时间进行焊接。再一次把CM_FOC的串口通信部分焊接完成，方便查看波形等。 SH1.25的接口画错了，属实是大意了。飞线暂时解决了问题。 AS5600磁编码器数据读取 AS5600是12位的霍尔磁编码器，它的地址是0x36，只需要读取0x0C、0x0D这两个寄存器就可以读出角度的原始数据，再将其乘以360，再除以4096，就可以获得角度值。 AS5600已经全部测试完毕，详情可以见下一个FOC博客-CHOUMAO_FOC_2.0！\n","date":"2024-09-15T00:00:00Z","image":"https://a233a2.github.io/p/cm_foc%E9%A9%B1%E5%8A%A8%E5%99%A8/cm_foc_real2.png","permalink":"https://a233a2.github.io/p/cm_foc%E9%A9%B1%E5%8A%A8%E5%99%A8/","title":"CM_FOC驱动器"},{"content":"CH32平衡车开发板设计 主控芯片：CH32V307VCT6 电机驱动：TB6612方案 下载芯片：CH549G 开源链接：https://github.com/a233a2/CH32_DC_BalanceCar\nPID平衡部分计划使用两种方案进行对比 1 pid_1.c文件 即不通过控制角度来确定速度，通过将直立环PID的目标设为重力0点，来维持平衡，之后通过速度环调速。 2 pid.c文件 通过控制角度来确定速度，速度环输出目标速度下的应得角度，直立环与速度环串级，直立环负责达到对应角度。\n某些地方还并不完善，比如PID参数方面还不尽完美。在之后我将从算法方面对其进行优化。从数学角度找找这两种方案的不同之处。\n测试 新设备推送设置\n","date":"2024-07-15T00:00:00Z","image":"https://a233a2.github.io/p/ch32%E5%B9%B3%E8%A1%A1%E8%BD%A6%E5%BC%80%E5%8F%91%E6%9D%BF/mainboard.JPG","permalink":"https://a233a2.github.io/p/ch32%E5%B9%B3%E8%A1%A1%E8%BD%A6%E5%BC%80%E5%8F%91%E6%9D%BF/","title":"CH32平衡车开发板"}]