news 2026/10/7 17:31:14

uni-app H5 搭配 PWA 后更新不生效?三招根治缓存与 Service Worker 更新链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
uni-app H5 搭配 PWA 后更新不生效?三招根治缓存与 Service Worker 更新链路

先说个现象:uni-app 打出来的 H5 包,配合 Service Worker 做了 PWA,上线之后你推了新版,结果用户那边怎么刷新都是旧页面。有些用户关掉浏览器重进才好,有些干脆一直停在旧版本上。你本地看是好的,隐身窗口看是好的,但只要真实流量上来,问题就冒出来了。

这个系列第一篇讲了 uniapp 怎么把 H5 包改造成 PWA,第二篇我们专门治这个“更新不生效”的毛病。按我做过的项目来说,这个坑不是偶然踩中,而是没有把 PWA 的更新链路想清楚之前,几乎必然踩中。这篇会把机制拆开,再给一套能直接用的工程方案,覆盖从 sw.js 生成到页面提示、再到上线后排查的完整流程。

1. 先把锅分清楚:更新不生效,往往不是 Service Worker 一个人的问题

1.1 三层缓存同时作用,你以为是 SW 的锅,其实是 HTTP 缓存

很多人一听到“PWA 更新不生效”,第一反应就是 Service Worker 缓存捣乱,然后去研究 cache 版本、强刷、清缓存。但在我实际排查的几个项目里,真正卡住用户的往往不是 Service Worker,而是更外层、更基础的那一层:HTTP 缓存。

一个 PWA 页面加载时,资源要经过的缓存链路大致是这样:

  • 浏览器 HTTP 缓存:服务器通过Cache-Control、Expires等响应头决定页面和静态资源能不能被本地缓存、缓存多久。
  • Service Worker 缓存:SW 拦截请求后,通过 Cache Storage API 自己维护的一套缓存。
  • 页面容器缓存:如果外面套了 WebView,或者用户用某些浏览器内置的预加载机制,还会有更隐蔽的缓存。

很多项目只盯着 SW 那一层,把caches.delete写得很溜,却忽略了服务器给index.html设置的长缓存。这样就会出现一个很典型的局面:SW 已经把新版本资源都准备好了,但页面加载时根本没有请求到新index.html,浏览器直接拿着旧的 HTML 跑了,旧 HTML 里引用的还是旧 JS 文件名,于是你推的新版本跟没推一样。

如果你用了 Nginx 或者 CDN,先检查一下index.html的响应头。对于 PWA 来说,index.html应该用no-cache或非常短的缓存时间,让它每次都能回到服务器确认版本。静态资源则应该走带 hash 的文件名加长缓存,这样既能享受缓存红利,又不会影响版本更新。

1.2 uniapp H5 的构建产物,和 SW 缓存是什么关系

把 uniapp 项目编译成 H5 后,产出一般长这样:

  • index.html:整个应用唯一的 HTML 入口。
  • static/js、static/css目录:Vite 或 Webpack 编译出来的 JS、CSS 文件,文件名里通常带内容 hash。
  • manifest.json:PWA 的 Web App Manifest,描述应用名、图标、主题色等。
  • sw.js:如果项目里配置了,会从public目录复制到构建产物根目录。

带 hash 的静态资源是天然适合缓存的:内容变了文件名就变了,老缓存不会命中新请求。但index.html是恒定的 URL,它必须承担“指引浏览器去加载哪个版本”的角色。换句话说,只要index.html是旧的,后面所有资源再新也白搭。

这也是我特别强调“先检查 HTTP 缓存”的原因。SW 的 fetch 拦截虽然能覆盖页面请求,但如果你的 SW 逻辑写的是 cache-first,而且缓存里已经有旧的index.html,那么即使网络上有新版本,用户拿到的仍是旧的。也就是说,SW 这一层同样可能导致 HTML 陈旧。

1.3 先用一张对照表框定问题方向

遇到“更新不生效”,我会先根据现象快速定位,而不是一上来就改代码:

现象最可能的原因优先排查入口
所有用户都停在旧版本服务器index.html缓存时间过长检查响应头Cache-Control
部分用户新、部分用户旧SW 更新存在延迟,或用户长时间不关闭页面检查 SW 生命周期与 waiting 状态
强制刷新后能更新缓存策略 cache-first 太激进调整 fetch 策略为网络优先
手机上有问题,电脑上没问题手机浏览器省电模式或 WebView 容器缓存检查手机端浏览器版本与容器行为
改了 sw.js 但没反应sw.js 内容没变化,字节对比不通过确认每次构建是否真的改了 sw.js 内容

这个表不是最终答案,但它能帮你把排查范围从“一头雾水”缩小到具体某一层。接下来我们深入 SW 本身。

2. Service Worker 更新链路里,真正卡住你的其实就两个细节

2.1 字节对比机制:sw.js 内容不变,浏览器就当无事发生

Service Worker 的更新检查有个基础机制,很多教程一笔带过,但它恰恰是“更新不生效”最容易踩的点:浏览器不是通过版本号判断 SW 有没有新版本,而是通过字节对比。

每次浏览器要检查更新时,会重新拉取 sw.js 脚本,然后和当前已安装的 sw.js 做逐字节对比。只要内容有任何不同,哪怕只改了一个空格、一行注释,浏览器就会认为这是一个新 SW,进入安装流程。如果内容完全一致,浏览器就认为“没新版本”,什么都不做。

这就是为什么很多人改了自己的业务代码后,发现 SW 没有更新:你在构建产物里更新的 JS、CSS 都变了,但 sw.js 本身一个字都没变,那浏览器自然认为整个 PWA 没有新版本。它不会替你“聪明地”去检查缓存里的其他资源。

所以工程上必须保证一件事:每次发布,sw.js 的内容一定要有变化。最简单的方式就是把构建时间或版本号写进 sw.js 里,比如生成一个带时间戳的注释,或者把版本号放到缓存名前缀里。这样字节对比必然通过,SW 更新才会被触发。

提示:字节对比的对象是 SW 脚本本身。如果你只是改了manifest.json或者别的静态资源,sw.js 没变,浏览器不会因为这个主动更新 SW。

2.2 waiting 与激活接管:新版 SW 装好了,但不代表它立刻干活

字节对比通过之后,新 SW 进入 installing 状态。如果安装顺利,它会进入 waiting 状态,也就是“等待中”。这个等待状态是个非常经典的卡点。

等待的原因是:浏览器默认不希望新 SW 打断当前正在被旧 SW 控制的页面。如果用户当前还开着你的站点,旧 SW 正在托管这些页面,新 SW 装好了也只能等着,直到所有被旧 SW 控制的页面都关闭后,才会自动 activate。

对用户来说,这意味着就算新版本已经下载到了浏览器里,只要他还有一个标签页没关,新 SW 就一直不接管;下次再打开站点,要先加载一遍旧逻辑,等 SW 切换完成后才可能走到新逻辑。表现上就是“更新不生效”。

解决办法就是那两个 API:self.skipWaiting()和self.clients.claim()。

  • skipWaiting()让处于 waiting 状态的新 SW 跳过等待,直接进入 active。
  • clients.claim()让新 SW activate 之后,立刻接管所有已被控制的客户端页面,不用等页面刷新。

这两个 API 在研发期尤其有用,能让你改了 SW 代码后马上生效。但在生产环境,要用得有策略,不能无脑全开。我后面的方案里会区分“静默更新”和“提示后更新”两种模式。

2.3 fetch 策略决定一切:SW 上岗了,资源也不一定更新

很多项目好不容易解决了 SW 的激活问题,发现资源还是旧的那一份。问题出在 fetch 监听里的缓存策略。

如果你在 SW 的 fetch 事件里写的是:

  1. 先去caches.match找缓存;
  2. 找到就直接返回,完全不碰网络;

这就是 cache-first。离线支持确实好,但线上更新也受它限制。就算新 SW 激活了,只要缓存里还有旧的index.html、旧的 JS,它就能一直满足请求,永远不给网络机会。用户看到的就永远是旧版本。

正确的思路要按资源类型区分策略。以 uniapp H5 为例:

  • HTML 导航请求(页面跳转、刷新):用 network-first,也就是先请求网络,网络成功就返回网络结果并更新缓存,网络失败再回退到缓存。这能保证入口文件总是最新的。
  • 带 hash 的静态资源(JS、CSS、图片):文件名本身带版本信息,可以用 cache-first,即使走缓存也没问题,因为文件名变了一定会触发新请求。
  • 需要新鲜度的接口:别让 SW 参与,或者只做网络优先不写缓存。

如果你的静态资源文件名没有 hash,那更要注意。没有 hash 的固定文件名,一旦 SW 把旧内容写进缓存,后面所有用户拿到的都是旧文件。此时应该用 stale-while-revalidate:先返回缓存内容让页面快速显示,同时在后台请求网络,用新响应更新缓存,这样下一次加载就是新的了。

3. Uniapp 项目里可直接用的自动更新工程方案

3.1 设计思路:让每次发布都有“新东西”进入 SW 更新链路

光讲原理不够,工程上需要一套能直接塞进 uniapp 项目的方案。我的设计思路是这样:

  1. 每次构建时生成一个新的 sw.js,内容里包含本次构建的时间戳或版本号。
  2. 只要 sw.js 内容变了,浏览器的字节对比就会触发 SW 更新。
  3. SW 更新后执行缓存清理:删除老版本的 Cache Storage,写入新缓存。
  4. 页面端监听 SW 更新状态,选择“自动刷新”或“给用户弹提示”的方式完成切换。

一句话:把构建时间变成版本锚点,让每次发版都在 SW 层面产生一个“可感知的变化”。

3.2 构建脚本:自动生成带版本号的 sw.js

我推荐在 uniapp 项目里用 Vite 的构建流程,同时加一个小脚本。每次执行npm run build:h5时,先跑这个脚本生成 sw.js,再走正常编译流程。

先看脚本本身,我用 Node 写,放在scripts/generate-sw.js:

const fs = require('fs') const path = require('path') const BUILD_VERSION = Date.now().toString(36).toUpperCase() const swCode = ` const BUILD_VERSION = '${BUILD_VERSION}' const CACHE_NAME = 'uniapp-pwa-' + BUILD_VERSION const PRECACHE_LIST = [ './', './index.html', './manifest.json', './static/js/', './static/css/' ] self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then((cache) => cache.addAll(PRECACHE_LIST)) .catch((err) => console.warn('precache failed:', err)) ) self.skipWaiting() }) self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((keys) => { return Promise.all( keys .filter((key) => key !== CACHE_NAME) .map((key) => caches.delete(key)) ) }) ) self.clients.claim() }) self.addEventListener('fetch', (event) => { const request = event.request if (request.method !== 'GET') return if (!request.url.startsWith(self.location.origin)) return // 页面导航走网络优先,保证入口文件最新 if (request.mode === 'navigate') { event.respondWith( fetch(request) .then((response) => { const clone = response.clone() caches.open(CACHE_NAME).then((cache) => cache.put('./index.html', clone)) return response }) .catch(() => caches.match('./index.html')) ) return } // 静态资源走 stale-while-revalidate,兼顾速度与更新 event.respondWith( caches.match(request).then((cached) => { const networkFetch = fetch(request) .then((response) => { if (response && response.ok) { const clone = response.clone() caches.open(CACHE_NAME).then((cache) => cache.put(request, clone)) } return response }) .catch(() => cached) return cached || networkFetch }) ) }) self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting() } }) ` const outputPath = path.join(__dirname, '../public/sw.js') fs.writeFileSync(outputPath, swCode, 'utf8') console.log('sw.js generated with version:', BUILD_VERSION)

注意几个关键点:

  • BUILD_VERSION用的是时间戳的 base36 字符串,即使一天构建十次,内容也不会重复。
  • skipWaiting()我保留在 install 里,同时也在 message 监听里提供手动跳过等待的入口。这样既可以走“自动更新”,也可以走“用户确认后更新”。
  • activate 里删除旧缓存,这一步必须做,否则旧缓存会一直留在 Cache Storage 里,白占空间不说,还可能干扰后面的 fetch 匹配。
  • fetch 事件里用self.location.origin做一次过滤,只处理同源请求。跨域的 CDN 资源是否拦截要看具体场景,我不建议在基础方案里把所有跨域资源都纳入 SW 管理,容易碰见 CORS 响应异常。

然后在package.json里调整构建命令:

{ "scripts": { "build:h5": "node scripts/generate-sw.js && vue-cli-service build --mode production" } }

如果你的 uniapp 是用 HBuilderX 里的“发行”按钮打包,没法直接改 npm scripts,那就改成手动执行脚本,把生成的public/sw.js提交到项目里也能用。核心逻辑没变:每次发布的 sw.js 必须不同。

3.3 页面端:监听 SW 更新,按需刷新或提示用户

SW 那边准备就绪后,页面端要做的,是感知 SW 的状态变化,然后决定“直接刷新”还是“告诉用户”。

我一般在 uniapp 的 App.vue 里(H5 端)挂一段逻辑:

export default { onLaunch() { // #ifdef H5 if ('serviceWorker' in navigator) { this.setupSWUpdate() } // #endif }, methods: { setupSWUpdate() { let refreshing = false navigator.serviceWorker.addEventListener('controllerchange', () => { if (refreshing) return refreshing = true // 新 SW 接管后强制刷新,拿到新页面 window.location.reload() }) navigator.serviceWorker.register('/sw.js').then((registration) => { // 如果当前已经存在 waiting 的新 SW,直接提示或刷新 if (registration.waiting) { this.noticeOrReload(registration.waiting) } registration.addEventListener('updatefound', () => { const newWorker = registration.installing if (!newWorker) return newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { this.noticeOrReload(newWorker) } }) }) }) }, noticeOrReload(worker) { // 简单方案:直接跳过等待,controllerchange 会自动刷新 worker.postMessage({ type: 'SKIP_WAITING' }) // 如果想给用户提示,这里可以改成弹窗,等用户点击后再 postMessage } } }

上面这个写法是“自动刷新”路线:发现新 SW 后,直接给它发SKIP_WAITING,新 SW 激活后触发controllerchange,页面自动刷新。好处是无感,用户最多感觉闪了一下;坏处是如果用户正在填表单、正在看视频,强制刷新会打断体验。

如果是内容型站点,我建议用自动刷新。如果是工具型、带操作状态的站点,我更建议改成提示模式:

noticeOrReload(worker) { // 用 uni.showModal 提示用户 uni.showModal({ title: '发现新版本', content: '是否立即更新到最新版?', success(res) { if (res.confirm) { worker.postMessage({ type: 'SKIP_WAITING' }) } } }) }

用户点“确定”之后,SW 跳过等待,激活,页面刷新,新版本生效。这里的体验控制比“强制刷新”温和得多,我觉得生产环境更应该用这种。

3.4 发布流程:从改代码到线上生效的完整检查单

配合上面的方案,我每次发版走这样的流程:

  1. 确认scripts/generate-sw.js能正常执行,生成的 sw.js 内容里包含新的 BUILD_VERSION。
  2. 执行npm run build:h5,产物生成到dist/build/h5。
  3. 把产物部署到服务器,注意替换index.html时别覆盖保留旧缓存目录,让 Nginx 给index.html配Cache-Control: no-cache。
  4. 静态资源目录正常发布即可,带 hash 的文件不会和旧的冲突。
  5. 自己用无痕窗口打开页面,看 Network 面板里 sw.js 是不是新版本,Cache Storage 里是不是新缓存名。
  6. 第二次打开页面,确认没有报错,确认旧缓存被清理掉。

这套流程跑熟了之后,你会发现“更新不生效”的概率大幅下降。剩下偶尔冒出来的问题,基本都能靠下一节的排查手段快速定位。

4. 实战排查:我遇到过的几个“更新不生效”现场

4.1 现场一:服务器 Cache-Control 直接把 SW 甩开了

有个项目部署在 CDN 后面,CDN 给index.html默认加了Cache-Control: max-age=3600。第一次访问时 SW 正常安装,用户也看到了页面。之后我们发布新版本,SW 的 fetch 逻辑是 network-first,按说应该能拿到新 HTML。但问题是,CDN 节点直接返回了缓存里旧 HTML,SW 拿到的就是旧 HTML 并把它写进了缓存。结果用户永远是旧版本,而且看起来 SW “正常工作”。

排查过程:先看 Network 面板,发现请求/返回的是from disk cache,根本没走到 SW。再查响应头,确认Cache-Control是长缓存。这时候就算把 sw.js 改上天也没用,因为入口请求在到达 SW 之前就被 HTTP 缓存吃了。

解决:把 CDN 针对 HTML 类型的缓存改成no-store或者极短的s-maxage,让请求每次都能回源或至少回到应用服务器。静态资源保持长缓存,因为文件名有 hash。

这也提醒我一个原则:PWA 的 SW 不是万能网关,它只是浏览器进程里的一个拦截器。HTTP 层一旦命中缓存,可能根本轮不到 SW 执行。

4.2 现场二:sw.js 内容没变化,浏览器根本不更新

另一个项目,我们发版后等了半小时,线上用户还是旧版。自己用 Chrome 打开,Application 面板里看到 SW 状态永远停在“activated but no new version available”。

查到最后发现:前端同事改了页面代码,但 sw.js 是手工维护的,这次发版压根没动它。浏览器每次对比 sw.js 的字节,都和已安装的一模一样,自然认为“没有新 SW”,也就不会去执行skipWaiting、activate这些流程。缓存里的旧资源被反复命中,新版本永远上不去。

解决:引入了构建时自动生成 sw.js 的脚本,也就是上面那段代码。从制度上保证“每次发版 sw.js 必然变”。这是最治本的办法,比靠人记着“这版记得改版本号”靠谱得多。

4.3 现场三:register 作用域不对,SW 管不到页面

还有一个项目部署在子路径下,比如https://example.com/h5/。当时图省事,把 sw.js 放在了站点根目录的静态服务里,页面里注册时写的是/sw.js。理论上 SW 作用域是整个/,能管到/h5/下的页面。但实际上因为部署配置问题,根目录的 sw.js 没有正确发布,实际访问路径返回的是 404,注册自然失败,SW 一直没装上。

后来把 sw.js 直接放到/h5/sw.js,注册路径改成./sw.js,作用域默认就是/h5/,问题才解决。这里的关键在于:SW 的作用域取决于 sw.js 文件的 URL 路径,而不是你注册时想管多大就管多大。跨作用域需要Service-Worker-Allowed响应头配合,否则会被浏览器拒绝。

如果你用了子路径部署,务必确认 sw.js 所在目录和页面 URL 的覆盖关系。否则你排查半天,最后发现是 SW 压根没接管。

4.4 一份我实战中用的排查顺序清单

遇到更新不生效,我的排查顺序是固定的,按从外到内:

  1. 无痕窗口打开站点,看 Network 面板,确认index.html有没有走网络,响应头对不对。
  2. 看sw.js是否被正常加载,Network 里有没有 404 或 200。
  3. Application 面板 -> Service Workers,看当前状态是 running 还是 stopped,有没有新的 installing/waiting worker。
  4. Application 面板 -> Cache Storage,看缓存名是不是新版本,旧缓存有没有被清理。
  5. 手动点一次 “Update on reload”,在 DevTools 里勾选后刷新,看 SW 是否能正确更新。
  6. 如果手动更新成功、用户环境更新失败,再检查是不是浏览器兼容或服务器缓存问题。

这套顺序基本能覆盖 90% 的情况。剩下的就是浏览器版本差异、企业私网缓存这种环境问题,通常做线上兜底提示比硬修更高效。

5. 别让“自动更新”绑架用户体验:一点工程上的取舍建议

5.1 静默更新和用户提示模式,怎么选才合理

自动更新是一把双刃剑。用户永远停留在旧版本,是体验问题;但每次发版都强制刷新,也是体验问题。我的取舍是这样:

  • 内容展示型站点(新闻、博客、活动页):静默更新,用户无感知刷新。
  • 工具型页面(表单、编辑器、复杂状态):提示更新,让用户自己选择时机。
  • 业务核心页面(支付前后、长流程填单):不要自动刷新,只给“有新版本”的提示,让用户处理完当前流程再决定。

如果把这两种模式分开看,核心区别只在noticeOrReload这一步放的是worker.postMessage({type:'SKIP_WAITING'})还是uni.showModal。SW 本身的逻辑不用动,这点上设计好了解耦,后续改起来成本很低。

5.2 顺带说下小程序端和 App 端的更新逻辑,别在同一套代码里踩坑

很多 uniapp 项目是同时做 H5、小程序、App 三端的。这里的“更新不生效”问题,在三端有完全不同的表现,不能把 PWA 的经验直接搬过去。

小程序端:微信有自己的包更新机制。用户打开小程序时,微信不一定马上拉取最新代码包,新版可能需要用户退出后重新进入才生效。很多开发者在代码里用uni.getUpdateManager()监听更新事件,在收到“新版本下载完成”后提示用户重启小程序。这个和 PWA 的 SW 接管本质上是两套东西,靠的是微信的更新管理接口,不是 Service Worker。小程序还有主包 2MB 的体积限制,超过就要拆分包,这是另一个工程话题。

App 端:uniapp 的 App 有整包更新和 wgt 资源热更新两种。整包更新要过应用商店审核,更新周期长;wgt 热更新走的是 uni 原生提供的资源包机制,可以绕过商店,但 iOS 的审核政策对热更新有限制,不能用来绕开审核上架。这个更要小心,因为 App 端一旦出现“旧版不兼容新版接口”的情况,用户没升级的话问题比 PWA 更新失败严重得多。

我的建议是:多端项目按“一版配置文件 + 多端更新代码”的思路拆开,H5 走 PWA 的 SW 逻辑,小程序走微信的 UpdateManager,App 走 uni 的资源更新接口。别在某一个端把另一端的逻辑硬塞进去。

5.3 上线前回归清单,把“更新不生效”挡在发布之前

最后分享一份我每次上线前会过的回归清单,算是这些年踩坑换来的习惯:

  • 检查index.html响应头:no-cache或短缓存,不能被 CDN 长缓存。
  • 检查静态资源文件名:必须带 hash,或保证每次发布内容能覆盖旧文件。
  • 检查 sw.js:本次构建生成的版本号和上次不同。
  • 检查 SW 的 activate 逻辑:旧缓存能正常删除。
  • 检查 fetch 策略:HTML 走网络优先,静态资源走缓存优先或 stale-while-revalidate。
  • 检查页面端监听:controllerchange 和 updatefound 都接上了。
  • 真机测试:至少用 Chrome 手机模拟、iOS Safari 实测一次,因为移动浏览器的行为差异更大。
  • 确认用户提示按钮:如果用了提示模式,按钮事件能触发 SKIP_WAITING。

这套流程跑下来,你在发布窗口几乎不会再收到“更新不生效”的反馈。PWA 的自动更新难的不是某个 API,而是整条链路里多个环节的配合。只要把版本号、字节对比、SW 生命周期、缓存策略这四件事理顺,这个痛点就能基本根除。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 17:30:03

红杉AI峰会启示录:当工具消失,TaoToken如何重新编码智能体经济

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 17:29:46

Xilinx FPGA BRAM从选型到读写验证:PL数据缓存实战指南

第一次用Xilinx做数据采集时,我遇到一个特别尴尬的情况:采样率不高,但每次采回来的一帧数据有1024个点,每个点16bit。起初我用寄存器数组缓存,综合完一看资源,LUT被吃掉一大片;换成FIFO&#xf…

作者头像 李华
网站建设 2026/10/7 17:28:53

Altium Designer 17.0.6离线授权与中文汉化完整指南

简介:本资源是一份面向电子设计工程师、PCB初学者及Altium Designer软件使用者的AD17.0.6(Altium Designer 17.0.6)完整安装与激活指南,聚焦解决正版软件部署难、破解流程不清晰、汉化步骤易出错等实际痛点。压缩包仅含1个PDF文件…

作者头像 李华
网站建设 2026/10/7 17:28:51

接口写死、串口禁用与脉宽漂移:嵌入式与IoT适配实战

1. 三个问题的共同底层逻辑做嵌入式或IoT集成的朋友,大概率都有过这种经历:明明是按照文档写的代码,接上去就是不通;明明硬件型号一模一样,换一批固件就行为异常;明明接口文档写得清清楚楚,联调…

作者头像 李华
网站建设 2026/10/7 17:28:17

自动加料机S7-200 PLC与MCGS触摸屏控制方案及梯形图解析

上个月帮一家小化工厂恢复一台搁置了两年的自动加料机,打开控制柜一看,里面是一块S7-200 CPU224加上一台MCGS触摸屏,旁边散落着半卷被老鼠咬断的线。设备本身没坏,坏的是图纸和程序——原厂工程师离职时把工程文件、注释和密码一起…

作者头像 李华
网站建设 2026/10/7 17:27:45

工业数据采集采样频率怎么定?从奈奎斯特到Modbus与MQTT上云实战

工业现场最容易被低估的一个参数,不是量程,不是精度,而是采样频率。我见过太多项目,传感器选得挺好,PLC 也不差,Modbus 链路跑得也稳,结果数据一上云就发现波形不对、峰值丢了、报警滞后&#x…

作者头像 李华