【UE5 C++课程系列笔记】27——多线程基础——ControlFlow插件的基本使用
目录
步骤
一、搭建基本同步框架
二、添加委托
三、添加蓝图互动框架
四、修改为异步框架
完整代码
通过一个游戏初始化流程的示例来介绍“ControlFlows”的基本使用。
步骤
一、搭建基本同步框架
1. 勾选“ControlFlows”插件

2. 新建一个空白C++类,这里命名为“ControlFlowSubSystem”

让“ControlFlowSubSystem” 继承“GameInstanceSubsystem”,然后添加反射所需代码

重写父类“ShouldCreateSubsystem”、“Initialize”和“Deinitialize”方法


3. 在Build.cs中添加“ControlFlows”模块

引入所需库

定义一个蓝图可调用的方法“InitLevel”,表示要执行的初始化流程;定义一个布尔类型变量“bIniting”用于表示当前是否处于初始化流程;再定义5个函数用于表示初始化流程的各个步骤。

“InitLevel”实现如下:

首先,通过检查 bIniting 来判断当前是否已经处于初始化过程中。如果 bIniting 为 true,意味着初始化正在进行或者已经执行过了,此时输出一条日志信息然后直接返回,避免重复执行初始化流程。只有当 bIniting 为 false 时,才会将其设置为 true,表示即将开始初始化流程。
第31行代码通过调用 FControlFlowStatics::Create 静态函数创建一个 FControlFlow 类型的控制流实例 Flow。在创建过程中,传入了 this 指针和一个字符串,这个字符串作为控制流的唯一标识符。
第33~37行代码通过多次调用 Flow.QueueStep 函数,向刚创建的控制流实例中依次添加了多个需要按顺序执行的步骤。
第40行代码调用 Flow.ExecuteFlow() 函数启动控制流的执行。此时,FControlFlow 实例会按照之前添加步骤的顺序,依次调用对应的成员函数,确保整个初始化流程按照预定的顺序有条不紊地进行,直到所有步骤都执行完毕,完成整个初始化过程。
用于表示初始化流程步骤的5个函数实现如下,当执行到最后一个步骤时。将 bIniting 改为 false,表示初始化流程已经结束。

4. 在关卡蓝图中调用“InitLevel”函数

执行结果如下,可以通过日志信息看到完整执行了整个初始化流程。

5. 为了观察每个步骤在哪一帧执行,可以通过添加如下代码实现:


运行结果如下,可以看到所有表示流程步骤的函数都是在同一帧执行的,这可能会造成游戏帧率下降,因此这并不符合我们的需求。

二、添加委托
6. 下面先创建两个委托,通过委托来向外界传递任务进度等信息。
申明两个动态多播委托类型
在第11行代码中,FControlFlows_InitProgress是要声明的委托类型的名称,FGuid 是UE中用于表示全局唯一标识符(GUID)的类型,在这里它作为委托参数的类型,而 InitAsyncID 是给这个参数起的变量名。当委托被调用时,会传递一个 FGuid 类型的全局唯一标识符。该委托在被调用时,还会传递一个表示进度值的浮点型数据,这个值可以用来直观地展示当前异步初始化任务已经完成的比例或者进度情况。
在第12行代码中,FControlFlows_InitResult代表所声明的委托类型名称,FGuid 与InitAsyncID和前面的委托类似,bool 与 bResult用于指示异步初始化任务最终是成功还是失败,直观地告知结果状态。Message 表示委托调用时还会附带更详细的关于初始化结果的说明。

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FControlFlows_InitProgress, FGuid, InitAsyncID, float, ProgressValue); //进度更新
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FControlFlows_InitResult, FGuid, InitAsyncID, bool, bResult, FString, Message); //初始化结果
声明两个委托类型的成员变量,并且通过 UPROPERTY(BlueprintAssignable) 元数据标签对这两个属性进行修饰,使其具备了在蓝图系统中可被绑定具体的回调函数的特性。

7. 为表示初始化步骤的5个成员函数添加进度值输入参数


8. 设置进行“InitLocalAsset”步骤时,初始化进度为10%;进行“InitNetInfo”步骤时,初始化进度为20%;进行“InitUserInfo”步骤时,初始化进度为50%;进行“NotifyMainUI”步骤时,初始化进度为80%;进行“FinishThisInit”步骤时,初始化进度为100%;

9. 声明InitAsyncID用于唯一标识某次初始化的异步任务

执行“InitLevel”函数进行初始化时为InitAsyncID赋值

当进行各初始化的步骤时,通过InitProgress委托广播当前的初始化进度,传入对应的InitAsyncID和本步骤的当前进度值InProgressValue,这样外部绑定了该委托的对象就能接收到进度更新情况。

在初始化流程的最后一个步骤对应的函数中,添加如下一行代码,通过InitResult委托广播当前的初始化结果。

三、添加蓝图互动框架
10. 添加一个控件蓝图,用于显示当前初始化进度,这里命名为“WBP_ControlFlowsMainUI”

打开“WBP_ControlFlowsMainUI”,添加如下控件,主要是添加一个文本控件用于显示进度值,按钮用于调用“InitLevel”从而开始初始化

在事件构造和结构时分别绑定和解绑“InitProgress”、“InitResult”委托

重命名委托绑定的匹配函数为“UpdateProgress”和“InitResult”

匹配函数“UpdateProgress”和“InitResult”函数逻辑如下,将委托传递的GUID、进度值和初始化结果信息打印出来,并显示进度值。如果初始化成功后就将界面隐藏,如果失败就显示进度值为0.00


当按钮点击后调用“InitLevel”开始初始化

11. 在关卡蓝图中让界面显示出来

此时运行后界面如下

点击初始化按钮后打印信息如下:

此时就完成了初始化流程的同步框架实现,接下来我们希望将同步改为异步实现。
四、修改为异步框架
12. 引入所需头文件

13. 更改“InitLocalAsset”、“InitNetInfo”、“InitUserInfo”函数逻辑如下


主要通过使用AsyncTask函数创建了一个外层的异步任务,并指定其可以在任意线程上执行。在这个异步任务的 lambda 表达式内部首先调用FPlatformProcess::Sleep(0.2)模拟一个耗时操作,接着又创建了一个内层的异步任务,指定在游戏线程上执行后续的耗时操作,以及向外广播初始化的进度信息。
完善“InitUserInfo”逻辑如下

第81行首先获取位于项目保存目录下的文件名为 ControlFlowsUserInfo.txt的目标文件相对路径,然后将相对路径转换为绝对路径。
第82行将初始化任务的唯一标识 InitAsyncID 转换为字符串形式赋值给 UserInitAsyncID。
第83行获取当前的日期时间并转换为 HTTP 日期格式赋值给 UserInitDataTime 。
第84~86行创建一个 TArray<FString> 类型的数组 MyStringInfo,并将前面获取的UserInitAsyncID 和 UserInitDataTime 字符串添加进去,准备将这些信息保存到文件中。
第87行使用 FFileHelper::SaveStringArrayToFile 函数将包含用户初始化相关信息的字符串数组 MyStringInfo 保存到指定路径 UserInfoPath 的 ControlFlowsUserInfo.txt中,并且指定了编码选项为ForceUTF8WithoutBOM,确保文件内容以指定的编码格式存储。
编译后运行,此时当我们点击初始化按钮后,看到输出日志信息如下,可以发现初始化流程的步骤不再是一帧内执行的了。

并且在“Saved”文件夹中多了一个名为 ControlFlowsUserInfo.txt的文件

ControlFlowsUserInfo.txt的内容如下,包括了初始化任务的唯一标识和文件存储时间。

如果初始化流程中的某一步失败了,通过 InitResult 委托向外广播初始化失败的结果信息,第113行调用 SubFlow->CancelFlow()标识取消当前正在执行的控制流,从而及时终止整个初始化流程。将 bIniting 变量设置为 false,表示当前不再处于初始化过程中,同时将 InitAsyncID 重置为默认值,为下一次可能的初始化操作做好准备。

完整代码
“ControlFlowSubSystem.h”
// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "ControlFlow.h"
#include "ControlFlowManager.h"
#include "ControlFlowNode.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Async/Async.h"
#include "ControlFlowSubSystem.generated.h"DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FControlFlows_InitProgress, FGuid, InitAsyncID, float, ProgressValue); //进度更新
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FControlFlows_InitResult, FGuid, InitAsyncID, bool, bResult, FString, Message); //初始化结果#define INITRESULT falseUCLASS()
class STUDY_API UControlFlowSubSystem : public UGameInstanceSubsystem
{GENERATED_BODY()public:virtual bool ShouldCreateSubsystem(UObject* Outer) const override;virtual void Initialize(FSubsystemCollectionBase& Collection) override;virtual void Deinitialize() override;UFUNCTION(BlueprintCallable)void InitLevel();public:UPROPERTY(BlueprintAssignable)FControlFlows_InitProgress InitProgress;UPROPERTY(BlueprintAssignable)FControlFlows_InitResult InitResult;protected:bool bIniting = false;FGuid InitAsyncID = FGuid();void InitLocalAsset(FControlFlowNodeRef SubFlow, double InProgressValue);void InitNetInfo(FControlFlowNodeRef SubFlow, double InProgressValue);void InitUserInfo(FControlFlowNodeRef SubFlow, double InProgressValue);void NotifyMainUI(FControlFlowNodeRef SubFlow, double InProgressValue);void FinishThisInit(FControlFlowNodeRef SubFlow, double InProgressValue);
};
“ControlFlowSubSystem.cpp”
// Fill out your copyright notice in the Description page of Project Settings.#include "ControlFlowSubSystem.h"bool UControlFlowSubSystem::ShouldCreateSubsystem(UObject* Outer) const
{return true;
}void UControlFlowSubSystem::Initialize(FSubsystemCollectionBase& Collection)
{Super::Initialize(Collection);
}void UControlFlowSubSystem::Deinitialize()
{Super::Deinitialize();
}void UControlFlowSubSystem::InitLevel()
{if (bIniting){UE_LOG(LogTemp, Warning, TEXT("Initing..."));return;}InitAsyncID = FGuid::NewGuid();bIniting = true;uint64 FrameIndex = GFrameCounter;UE_LOG(LogTemp, Warning, TEXT("InitFlow -- FrameIndex: %d"), FrameIndex);FControlFlow& Flow = FControlFlowStatics::Create(this, TEXT("ControlFlow_InitLevel"));Flow.QueueStep(TEXT("InitLocalAsset"), this, &UControlFlowSubSystem::InitLocalAsset, 0.1);Flow.QueueStep(TEXT("InitNetInfo"), this, &UControlFlowSubSystem::InitNetInfo, 0.2);Flow.QueueStep(TEXT("InitUserInfo"), this, &UControlFlowSubSystem::InitUserInfo, 0.5);Flow.QueueStep(TEXT("NotifyMainUI"), this, &UControlFlowSubSystem::NotifyMainUI, 0.8);Flow.QueueStep(TEXT("FinishThisInit"), this, &UControlFlowSubSystem::FinishThisInit, 1.0);UE_LOG(LogTemp, Warning, TEXT("ExecuteFlow -- FrameIndex: %d"), FrameIndex);Flow.ExecuteFlow();
}void UControlFlowSubSystem::InitLocalAsset(FControlFlowNodeRef SubFlow, double InProgressValue)
{uint64 FrameIndex = GFrameCounter;UE_LOG(LogTemp, Warning, TEXT("InitLocalAsset -- FrameIndex: %d"), FrameIndex);AsyncTask(ENamedThreads::AnyThread, [this, SubFlow, InProgressValue]() {FPlatformProcess::Sleep(0.2);AsyncTask(ENamedThreads::GameThread, [this, SubFlow, InProgressValue]() {FPlatformProcess::Sleep(0.2);InitProgress.Broadcast(InitAsyncID, InProgressValue);SubFlow->ContinueFlow();});});
}void UControlFlowSubSystem::InitNetInfo(FControlFlowNodeRef SubFlow, double InProgressValue)
{uint64 FrameIndex = GFrameCounter;UE_LOG(LogTemp, Warning, TEXT("InitNetInfo -- FrameIndex: %d"), FrameIndex);AsyncTask(ENamedThreads::AnyThread, [this, SubFlow, InProgressValue]() {FPlatformProcess::Sleep(0.2);AsyncTask(ENamedThreads::GameThread, [this, SubFlow, InProgressValue]() {FPlatformProcess::Sleep(0.2);InitProgress.Broadcast(InitAsyncID, InProgressValue);SubFlow->ContinueFlow();});});
}void UControlFlowSubSystem::InitUserInfo(FControlFlowNodeRef SubFlow, double InProgressValue)
{uint64 FrameIndex = GFrameCounter;UE_LOG(LogTemp, Warning, TEXT("InitUserInfo -- FrameIndex: %d"), FrameIndex);AsyncTask(ENamedThreads::AnyThread, [this, SubFlow, InProgressValue]() {FPlatformProcess::Sleep(0.2);FString UserInfoPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir()/TEXT("ControlFlowsUserInfo.txt"));FString UserInitAsyncID = InitAsyncID.ToString();FString UserInitDataTime = FDateTime::Now().ToHttpDate();TArray<FString> MyStringInfo;MyStringInfo.Add(UserInitAsyncID);MyStringInfo.Add(UserInitDataTime);FFileHelper::SaveStringArrayToFile(MyStringInfo, *UserInfoPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM);AsyncTask(ENamedThreads::GameThread, [this, SubFlow, InProgressValue]() {FPlatformProcess::Sleep(0.2);InitProgress.Broadcast(InitAsyncID, InProgressValue);SubFlow->ContinueFlow();});});
}void UControlFlowSubSystem::NotifyMainUI(FControlFlowNodeRef SubFlow, double InProgressValue)
{uint64 FrameIndex = GFrameCounter;UE_LOG(LogTemp, Warning, TEXT("NotifyMainUI -- FrameIndex: %d"), FrameIndex);if (INITRESULT){InitProgress.Broadcast(InitAsyncID, InProgressValue);SubFlow->ContinueFlow();}else{AsyncTask(ENamedThreads::GameThread, [this]() {InitResult.Broadcast(InitAsyncID, false, TEXT("Init Failed"));});SubFlow->CancelFlow();bIniting = false;InitAsyncID = {};}
}void UControlFlowSubSystem::FinishThisInit(FControlFlowNodeRef SubFlow, double InProgressValue)
{uint64 FrameIndex = GFrameCounter;UE_LOG(LogTemp, Warning, TEXT("FinishThisInit -- FrameIndex: %d"), FrameIndex);InitProgress.Broadcast(InitAsyncID, InProgressValue);InitResult.Broadcast(InitAsyncID, true, TEXT("Init Success"));SubFlow->ContinueFlow();bIniting = false;InitAsyncID = {};
}
相关文章:
【UE5 C++课程系列笔记】27——多线程基础——ControlFlow插件的基本使用
目录 步骤 一、搭建基本同步框架 二、添加委托 三、添加蓝图互动框架 四、修改为异步框架 完整代码 通过一个游戏初始化流程的示例来介绍“ControlFlows”的基本使用。 步骤 一、搭建基本同步框架 1. 勾选“ControlFlows”插件 2. 新建一个空白C类,这里…...
有收到腾讯委托律师事务所向AppStore投诉带有【水印相机】主标题名称App的开发者吗
近期,有多名开发者反馈,收到来自腾讯科技 (深圳) 有限公司委托北京的一家**诚律师事务所卞,写给AppStore的投诉邮件。 邮件内容主要说的是,腾讯注册了【水印相机】这四个字的商标,所以你们这些在AppStore上的app&…...
标定 3
标定场景与对应的方式 标定板标定主要应用场景: (1)无法获取到执行机构物理坐标值,比如相机固定,执行机构为传送带等 (2)相机存在畸变等非线性标定情况,需要进行畸变校正 (3)标定单像素精度 (4)获取两个相机之间的坐标系关系 标定板操作步骤: (1)确定好拍…...
用 C# 绘制谢尔宾斯基垫片
谢尔宾斯基垫片是一个三角形,分解成多个小三角形,如右图所示。有几种方法可以生成这种垫片。这里展示的方法是其中一种比较令人惊讶的方法。 程序从三个点开始(图中圆圈所示)。“当前位置”从其中一个点开始。为了生成后续点&…...
java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
今天在朋友机子上运行代码,在生成token的时候,遇到了这样一个问题: Caused by: java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter at io.jsonwebtoken.impl.Base64Codec.decode(Base64Codec.java:26) ~[jjwt-0.9.1.jar:0.…...
双因素身份验证技术在NPI区域邮件安全管控上的解决思路
在制造业中,NPI(New Product Introduction,新产品导入)区域是指专门负责新产品从概念到市场推出全过程的部门或团队。NPI 的目标是确保新产品能够高效、高质量地投入生产,并顺利满足市场需求。在支撑企业持续创新和竞争…...
java后端对接飞书登陆
java后端对接飞书登陆 项目要求对接第三方登陆,飞书登陆,次笔记仅针对java后端,在看本笔记前,默认已在飞书开发方已建立了应用,并获取到了appid和appsecret。后端要做的其实很简单,基本都是前端做的&…...
记录一次Android Studio的下载、安装、配置
目录 一、下载和安装 Android Studio 1、搜索下载Android studio 2、下载成功后点击安装包进行安装: 3、这里不用打勾,直接点击安装 : 4、完成安装: 5、这里点击Cancel就可以了 6、接下来 7、点击自定义安装:…...
直流无刷电机控制(FOC):电流模式
目录 概述 1 系统框架结构 1.1 硬件模块介绍 1.2 硬件实物图 1.3 引脚接口定义 2 代码实现 2.1 软件架构 2.2 电流检测函数 3 电流环功能实现 3.1 代码实现 3.2 测试代码实现 4 测试 概述 本文主要介绍基于DengFOC的库函数,实现直流无刷电机控制&#x…...
73.矩阵置零 python
矩阵置零 题目题目描述示例 1:示例 2:提示: 题解思路分析Python 实现代码代码解释提交结果 题目 题目描述 给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例…...
垃圾收集算法
分代收集理论 分代收集理论,建立在两个分代假说之上。 弱分代假说:绝大多数对象都是朝圣夕灭的。 强分代假说:熬过越多次垃圾收集的过程的对象就越难以消亡。 这两个分代假说奠定了垃圾收集器的一致设计原则:收集器应该将Java…...
SQL-leetcode-262. 行程和用户
262. 行程和用户 表:Trips --------------------- | Column Name | Type | --------------------- | id | int | | client_id | int | | driver_id | int | | city_id | int | | status | enum | | request_at | varchar | --------------------- id 是这张表的主键…...
太原理工大学软件设计与体系结构 --javaEE
这个是简答题的内容 选择题的一些老师会给你们题库,一些注意的点我会做出文档在这个网址 项目目录预览 - TYUT复习资料:复习资料 - GitCode 希望大家可以给我一些打赏 什么是Spring的IOC和DI IOC 是一种设计思想,它将对象的创建和对象之间的依赖关系…...
Leetcode 139. 单词拆分 动态规划
原题链接:Leetcode 139. 单词拆分 递归,超时 class Solution { public:bool isfind(string s,map<string,int>& mp){for(auto x:mp){string wordx.first;if(sword) return true;int nword.size();if(n>s.size()) continue;string s1s.subs…...
python异常机制
异常是什么? 软件程序在运行过程中,非常可能遇到刚刚提到的这些问题,我们称之为异常,英文是Exception,意思是例外。遇到这些例外情况,或者交异常,我们怎么让写的程序做出合理的处理,…...
运行爬虫时可能遇到哪些常见问题?
在运行Python爬虫时,可能会遇到以下一些常见问题及相应的解决方法: 1. 请求频繁被封 IP 问题描述:爬虫请求频繁时,网站可能会识别到异常行为并封禁 IP,从而导致后续请求失败。解决方法: 使用代理…...
BGP与CN2的区别 详解两者在网络传输中的应用与优势
在现代互联网环境中,选择合适的网络传输协议和解决方案对于企业的业务运行至关重要。BGP(Border Gateway Protocol)和CN2(China Telecom Next Carrier Network)是两种广泛应用的网络技术,但它们的设计理念、…...
Spring 项目 基于 Tomcat容器进行部署
文章目录 一、前置知识二、项目部署1. 将写好的 Spring 项目先打包成 war 包2. 查看项目工件(Artifact)是否存在3. 配置 Tomcat3.1 添加一个本地 Tomcat 容器3.2 将项目部署到 Tomcat 4. 运行项目 尽管市场上许多新项目都已经转向 Spring Boot࿰…...
“负载均衡”出站的功能、原理与场景案例
在企业日常网络中,外网访问速度不稳定是一个常见问题。特别是多条外网线路并行时,不合理的流量分配会导致资源浪费甚至网络拥堵。而出站负载均衡,正是解决这一问题的关键技术。 作为一种先进的网络流量管理技术,其核心是优化企业内…...
02-51单片机数码管与矩阵键盘
一、数码管模块 1.数码管介绍 如图所示为一个数码管的结构图: 说明: 数码管上下各有五个引脚,其中上下中间的两个引脚是联通的,一般为数码管的公共端,分为共阴极或共阳极;其它八个引脚分别对应八个二极管…...
Python自动化办公:用PyPDF2批量给PDF加密、调整页面顺序,解放你的双手
Python自动化办公实战:用PyPDF2实现PDF批量加密与智能排序 在数字化办公环境中,PDF文件处理已成为行政、财务和法律从业者的日常必修课。当面对数百份合同需要加密保护,或是季度报告需要重新编排页码时,手动操作不仅效率低下&…...
CANN/asc-devkit:int64转half精度函数
__ll2half_ru 【免费下载链接】asc-devkit 本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。 项目地址: https://gitcode.c…...
BetterDiscord Installer完全指南:如何一键安装和优化Discord插件
BetterDiscord Installer完全指南:如何一键安装和优化Discord插件 【免费下载链接】Installer A simple standalone program which automates the installation, removal and maintenance of BetterDiscord. 项目地址: https://gitcode.com/gh_mirrors/ins/Instal…...
Midjourney × CLO 3D无缝协同方案(工业级打版前必读):实现AI草图→虚拟缝合→力学模拟零损转换
更多请点击: https://kaifayun.com 第一章:Midjourney CLO 3D无缝协同方案(工业级打版前必读):实现AI草图→虚拟缝合→力学模拟零损转换 在高精度服装数字样衣开发流程中,Midjourney生成的创意草图常因缺…...
在i.MX6UL开发板上移植ncnn:嵌入式AI部署实战与性能优化
1. 项目概述:为什么要在边缘设备上跑神经网络?最近几年,AI应用从云端下沉到边缘的趋势越来越明显。无论是智能摄像头里的人脸识别、工业质检设备上的缺陷检测,还是智能音箱里的语音唤醒,都要求模型能在本地、低功耗的设…...
别再纠结软件IIC了!用STM32硬件IIC驱动0.96寸OLED,实测代码稳定不掉线
从软件IIC到硬件IIC:STM32驱动OLED的终极稳定方案 在嵌入式开发中,OLED显示屏因其高对比度、低功耗和快速响应等优势,成为许多项目的首选显示设备。然而,许多开发者在使用STM32驱动OLED时,往往会遇到通信不稳定、显示闪…...
智能安卓主板选型指南:从需求分析到量产落地的全流程解析
1. 项目概述:智能安卓主板选型的核心价值在嵌入式开发和智能硬件项目里,选对一块主板,往往意味着项目成功了一半。我见过太多团队,前期功能设计得天花乱坠,结果卡在了硬件选型上,要么性能过剩成本失控&…...
QR码扫描模块全解析:从原理到工程实践
1. 项目概述:不只是“扫一扫”那么简单如果你以为QR码扫描就是个“打开摄像头、对准、识别”的简单功能,那可能错过了它背后一整套精密的技术栈和丰富的应用场景。作为一个在移动应用和嵌入式设备领域折腾了十多年的老码农,我见过太多项目在集…...
LM331芯片实测翻车记:从面包板到PCB,为什么我的V/F转换电路输出总在抖?
LM331电压频率转换电路实战:从抖动问题到稳定性优化全解析 作为一名电子工程师,我最近在项目中遇到了一个看似简单却令人头疼的问题——使用LM331芯片搭建的电压频率转换电路输出信号始终存在明显抖动。这原本应该是一个教科书级别的经典电路,…...
ATxmega时钟与GPIO配置详解:从原理到实战调试
1. 项目概述:从零认识ATxmage的时钟与GPIO最近在捣鼓一块ATxmage的开发板,很多刚入门的朋友拿到手,面对密密麻麻的引脚和一堆陌生的寄存器,往往不知道从哪里下手。其实,玩转任何一款微控制器,最核心、最基础…...
