PHP 项目要接 SQL Server这件事在 Windows 环境下看着简单真动手的时候坑一个接一个。我自己第一次给一个老系统做数据对接本地环境是 PHP 5.6对方数据库是 SQL Server 2012光是让sqlsrv_connect()返回一个不是false的资源就折腾了大半天。后来项目升级到 PHP 7.x同一套代码又得换驱动版本重来一遍。所以这篇东西我打算把 PHP 5.6 和 7.x 两个阶段连接 SQL Server 的完整路径都捋清楚包括驱动怎么选、扩展怎么装、连接参数怎么写、出错怎么查。适合手里有遗留系统要维护的开发者也适合刚接手一个 PHP 对接 SQL Server 项目、还没搞清楚sqlsrv和pdo_sqlsrv区别的朋友。下面全部是我实际踩过或者帮别人排查过的内容能直接照着做。1. 先搞清楚驱动选型这件事1.1 为什么 PHP 连 SQL Server 不能像连 MySQL 那样开箱即用很多人第一次接触这个需求会有一个疑问PHP 不是自带一堆数据库扩展吗为什么连 SQL Server 还要单独装东西原因在于 PHP 官方内核里从来没有内置过 SQL Server 的原生驱动。MySQL 有mysqli和pdo_mysql跟着 PHP 源码一起发布而 SQL Server 的通信协议是微软自己的 TDSTabular Data StreamPHP 核心团队不维护这部分实现所以连接能力一直是以外部扩展的形式存在的。历史上出现过好几条技术路线。最早是mssql扩展基于 FreeTDS 库PHP 5.3 之后就被标记为废弃PHP 7.0 直接移除。然后是odbc路线通过 ODBC 驱动中转配置繁琐但通用性强。再往后就是微软官方出手维护的sqlsrv和pdo_sqlsrv两个扩展这也是目前唯一还在持续更新、并且同时覆盖 PHP 5.6 和 7.x 的方案。你在网上搜到的绝大多数教程只要提到sqlsrv_connect说的就是这条路。这里有个关键认知sqlsrv是面向过程的函数式 APIpdo_sqlsrv是 PDO 驱动。两者底层共用同一套通信库但上层接口完全不同。选哪个不是技术优劣问题而是看你项目现有代码风格。老项目大量用mssql_query那种写法的迁移到sqlsrv_*函数改动最小新项目或者已经在用 PDO 抽象层的直接上pdo_sqlsrv更统一。1.2 sqlsrv 与 pdo_sqlsrv 的取舍对照我把两者的差异整理成一张表选型的时候对着看就行对比维度sqlsrvpdo_sqlsrv接口风格面向过程函数面向对象 PDO学习成本低函数名直观需要了解 PDO 基础参数化查询支持sqlsrv_query传数组支持标准 PDO 预处理多数据库切换不支持绑定 SQL Server支持同一套代码换 DSN事务控制sqlsrv_begin_transaction等标准 PDO 事务方法结果集遍历sqlsrv_fetch_arrayfetch/fetchAll适用场景遗留系统改造、快速对接新项目、已有 PDO 体系我个人的判断标准很简单如果这个 PHP 项目里已经有 PDO 连接其他数据库的代码那毫不犹豫用pdo_sqlsrv保持抽象层一致后面换库或者加库都省事。如果是一个纯粹的老系统满屏mysql_query风格的代码那用sqlsrv函数迁移起来心理负担小改动也更局部。注意不要在同一台机器上同时加载sqlsrv和pdo_sqlsrv的多个版本。这两个扩展依赖同一套底层 DLL版本错配会导致 Apache 或 PHP-FPM 启动时直接崩溃而且报错信息往往指向别处非常难查。1.3 版本对应关系是最容易翻车的地方PHP 5.6 和 PHP 7.x 用的驱动版本完全不是一回事这一点必须先钉死。微软的驱动发布是按 PHP 大版本分支的PHP 5.6 对应的是 3.x 系列的驱动PHP 7.0 到 7.4 对应的是 5.x 系列。你如果拿 PHP 7 的驱动往 PHP 5.6 上装扩展加载阶段就会报“不是有效的 Win32 应用程序”或者干脆静默不加载。更细的一点是线程安全TS和非线程安全NTS的区分。这取决于你 PHP 的编译方式用phpinfo()看“Thread Safety”那一行enabled 就是 TSdisabled 就是 NTS。IIS 下跑 FastCGI 通常是 NTSApache 的 mod_php 模式通常是 TS。驱动包下载页面上会明确标注 ts 和 nts选错了同样加载失败。还有一个位数问题PHP 是 32 位还是 64 位驱动必须对应。现在新环境基本都是 64 位但一些老服务器上还跑着 32 位 PHP这个要提前确认用php -v或者phpinfo()都能看到架构信息。2. PHP 5.6 环境下的完整落地步骤2.1 驱动下载与文件放置PHP 5.6 这个版本比较特殊它处在驱动支持周期的尾端能用的最新驱动是 3.2 版本。下载下来是一个自解压包或者压缩包解开后里面按 PHP 版本和线程安全类型分了目录你要找的是类似php_sqlsrv_56_ts.dll和php_pdo_sqlsrv_56_ts.dll这样的文件。放置位置是 PHP 的扩展目录也就是php.ini里extension_dir指向的那个路径。Windows 下常见的是C:\php\ext或者C:\php56\ext。把两个 DLL 复制进去就行不需要放系统目录。这里有个细节值得说驱动包解压后往往还带一个msodbcsql.msi或者类似的 ODBC 组件安装程序。PHP 5.6 时代的sqlsrv扩展对系统 ODBC 驱动的依赖没有后来那么强但如果你要用到一些高级特性或者遇到连接层面的怪问题把这个 ODBC 组件也装上会更稳。装的时候注意版本别装了个太新的把老环境搞乱。2.2 php.ini 配置的正确写法打开php.ini找到扩展加载区域加上两行extensionphp_sqlsrv_56_ts.dll extensionphp_pdo_sqlsrv_56_ts.dll注意文件名要和你实际放进去的文件名完全一致包括前缀php_和后缀.dll。有些教程写的是extensionsqlsrv这种简写在某些 PHP 版本上能识别但 5.6 上我建议写全名避免歧义。改完保存重启 Web 服务。然后访问一个phpinfo()页面搜索sqlsrv应该能看到两个独立的配置区块分别对应sqlsrv和pdo_sqlsrv里面会列出支持的版本号和编译信息。如果搜不到说明扩展没加载成功往下看排查部分。提示修改php.ini之前先备份一份。老环境上经常出现改错一个字符导致整个 PHP 起不来的情况有备份能快速回滚。2.3 第一个连接测试脚本扩展加载成功后写一个最小化的测试脚本验证连通性?php $serverName 192.168.1.100,1433; $connectionOptions array( Database TestDB, Uid sa, PWD your_password, CharacterSet UTF-8 ); $conn sqlsrv_connect($serverName, $connectionOptions); if ($conn false) { die(print_r(sqlsrv_errors(), true)); } echo 连接成功; sqlsrv_close($conn); ?几个参数说明一下。$serverName里逗号后面是端口SQL Server 默认 1433如果对方改过端口就写实际端口。CharacterSet设成 UTF-8 是为了避免中文乱码这个参数在 PHP 5.6 的驱动里是支持的但要注意它影响的是客户端和服务器之间的字符转换数据库本身的排序规则是另一回事。如果这个脚本返回“连接成功”说明整条链路通了。如果返回falsesqlsrv_errors()会给出具体的错误码和消息这是排查的起点。2.4 用 PDO 方式连接的写法如果决定走pdo_sqlsrv连接代码是这样的?php $dsn sqlsrv:Server192.168.1.100,1433;DatabaseTestDB; $username sa; $password your_password; try { $pdo new PDO($dsn, $username, $password, array( PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION, PDO::SQLSRV_ATTR_ENCODING PDO::SQLSRV_ENCODING_UTF8 )); echo 连接成功; } catch (PDOException $e) { echo 连接失败: . $e-getMessage(); } ?PDO::SQLSRV_ATTR_ENCODING这个常量是pdo_sqlsrv特有的设成 UTF8 的效果和上面CharacterSet参数类似。把ERRMODE设成异常模式后面所有数据库操作出错都会抛异常比手动检查返回值清爽很多。3. PHP 7.x 的迁移与差异处理3.1 驱动版本切换的注意事项从 PHP 5.6 升到 7.x驱动必须换成 5.x 系列。文件名规则变成php_sqlsrv_7x_ts.dll这种形式其中7x对应具体的 PHP 小版本比如 7.2 就是php_sqlsrv_72_ts.dll。这里要特别注意驱动包对 PHP 小版本是有区分的7.0、7.1、7.2、7.3、7.4 各有各的文件不能混用。升级操作上先把旧的 5.6 驱动 DLL 从ext目录删掉或者移走再放新的进去然后改php.ini里的文件名。如果旧文件不清理有时候 PHP 会加载到错误的那个表现出的症状是扩展明明在phpinfo里显示了但函数调用报“未定义”。PHP 7.x 时代微软还推出了一个统一驱动包安装方式变成了跑一个 exe 安装程序它会自动检测系统里的 PHP 版本并放置对应文件。这种方式省事但有个副作用它可能同时给多个 PHP 版本装驱动如果你机器上有多个 PHP 环境要确认它装到了你实际在用的那个上面。3.2 PHP 7 下 API 层面的变化好消息是sqlsrv_*函数和 PDO 的用法在 7.x 下基本没变之前写的连接代码可以直接跑。但有几个行为差异需要留意。第一是错误处理更严格了。PHP 7 对类型检查更严sqlsrv_query传参时如果类型不对5.6 下可能默默转换7.x 下会直接报错。比如把字符串传给期望整数的参数或者传了个null给必填参数行为会不一样。第二是sqlsrv_fetch_array的默认返回模式。5.6 下默认是SQLSRV_FETCH_BOTH7.x 下这个默认值没变但如果你代码里依赖了某些边界行为比如字段名大小写建议显式指定SQLSRV_FETCH_ASSOC避免踩到差异。第三是资源类型的变化。PHP 7 里很多资源变成了对象sqlsrv的连接资源在 7.x 下仍然是资源类型但如果你用is_resource()去判断某些场景下结果可能和 5.6 不同。稳妥的做法是用$conn false来判断失败而不是用is_resource判断成功。3.3 一个兼容两个版本的封装思路如果项目需要同时兼容 5.6 和 7.x最省心的做法是写一层薄封装把连接和查询的差异挡在外面。我的做法是定义一个连接工厂根据 PHP 版本或者配置决定用哪个驱动但对外暴露统一的接口。?php class DbFactory { public static function connect($config) { if (extension_loaded(pdo_sqlsrv)) { $dsn sprintf( sqlsrv:Server%s,%s;Database%s, $config[host], $config[port], $config[db] ); return new PDO($dsn, $config[user], $config[pass], array( PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION )); } if (function_exists(sqlsrv_connect)) { $conn sqlsrv_connect( $config[host] . , . $config[port], array( Database $config[db], Uid $config[user], PWD $config[pass], CharacterSet UTF-8 ) ); if ($conn false) { throw new RuntimeException(print_r(sqlsrv_errors(), true)); } return $conn; } throw new RuntimeException(没有可用的 SQL Server 驱动); } } ?这个封装的好处是业务代码不用关心底层是哪个扩展迁移的时候只改这一处。当然如果两个扩展都装了优先用 PDO 那套因为接口更现代后续维护成本低。4. 连接参数与字符集处理的深水区4.1 连接字符串里每个参数的实际作用很多人连接串是抄来的参数含义并不清楚出问题就抓瞎。我把常用参数逐个拆开讲。Server后面跟主机和端口格式是host,port。如果实例不是默认实例还要加实例名写法是host\instance,port。这里有个坑实例名和端口同时写的时候某些驱动版本会优先用端口实例名被忽略导致连到错误的实例上。稳妥做法是只写端口或者只写实例名别混着来。Database指定初始数据库。不写的话连到登录账号的默认库如果那个库不存在或者没权限连接会失败但报错信息可能很含糊。Uid和PWD是账号密码。如果要用 Windows 集成认证这两个不写改成TrustServerCertificate和认证模式相关的参数但集成认证在跨机器场景下配置复杂一般项目里还是用 SQL 账号。CharacterSet前面提过设 UTF-8。这里要补充一点这个参数只影响驱动层的字符转换如果数据库排序规则是Chinese_PRC_CI_AS这类非 UTF-8 的写入中文时仍然可能出问题。根治办法是把数据库或者字段的排序规则改成支持 Unicode 的类型比如NVARCHAR。LoginTimeout控制连接超时秒数默认 15 秒左右。内网环境可以调小到 5 秒快速失败比干等强。Encrypt参数控制是否加密传输老版本 SQL Server 可能不支持强制加密设成 true 会连不上这个要看服务器配置。4.2 中文乱码的三层排查法中文乱码是这类项目最高频的问题我总结了一个从外到内的排查顺序。第一层看连接层。CharacterSet有没有设 UTF-8PDO 那边有没有设PDO::SQLSRV_ATTR_ENCODING。这一层没设对取出来的中文直接是问号或者乱码方块。第二层看字段类型。如果字段是VARCHAR而不是NVARCHAR那它存的是按排序规则编码的字节不是 Unicode。这种情况下即使连接层设了 UTF-8驱动做转换时也可能丢信息。解决办法是把字段改成NVARCHAR或者在查询时用CONVERT函数转换。第三层看 PHP 文件本身的编码和输出。PHP 源文件要存成 UTF-8 无 BOM页面输出要设header(Content-Type: text/html; charsetutf-8)。这三层任何一层出问题都会表现为乱码但根源完全不同所以要按顺序排查别一上来就改代码。提示排查乱码时先写一个只查单个中文字段、直接var_dump输出的脚本绕开所有框架和模板看到原始字节再判断是哪一层的问题。这样能省掉大量猜测时间。4.3 连接池与长连接的取舍SQL Server 驱动支持连接池在连接字符串里加ConnectionPooling1可以开启。这个特性在 Web 场景下能减少反复建连的开销但也有副作用连接被复用后会话状态比如临时表、SET选项可能残留导致一些诡异的行为。我的经验是如果应用是短请求、无状态的那种开连接池收益明显如果应用里有大量依赖会话状态的操作比如临时表、游标、事务跨请求那还是关掉连接池每次新建连接更可控。PHP 本身是每请求结束就释放资源的模型所谓长连接在 PHP 里意义有限别被其他语言的经验带偏。5. 常见报错与排查实录5.1 扩展加载失败的典型症状最常见的症状是phpinfo()里根本看不到sqlsrv区块。这时候按这个顺序查先确认 DLL 文件确实在extension_dir目录下文件名拼写和php.ini里写的一致再确认线程安全类型匹配TS 的 PHP 配 TS 的 DLL然后确认位数匹配64 位 PHP 配 64 位 DLL最后看 PHP 启动日志或者 Apache 错误日志里面通常会有“无法加载动态库”的具体原因。还有一种情况是phpinfo里能看到扩展但函数调用报未定义。这通常是加载了错误的版本或者php.ini里同一个扩展被加载了两次一次用全名一次用简写导致冲突。检查php.ini里有没有重复行。5.2 连接阶段的错误码对照连接失败时sqlsrv_errors()返回的数组里错误码是最有价值的线索。我整理了几个高频的错误码含义常见原因处理方向08001无法建立连接主机端口不通、实例名错检查网络和端口28000登录失败账号密码错、账号被禁用核对凭据和账号状态4060无法打开数据库数据库不存在或无权限确认库名和账号权限18456登录失败服务器侧认证模式不对检查服务器是否混合认证233连接被强制关闭协议不匹配、TLS 版本检查驱动和服务器版本兼容性18456这个特别常见服务器如果只开了 Windows 认证没开混合认证SQL 账号根本登不进去报错就是这个。这个要在服务器端改认证模式客户端怎么调都没用。233在 PHP 7.x 配老版本 SQL Server 时偶尔出现根源是 TLS 版本协商失败。新版驱动默认要求 TLS 1.2老服务器可能只支持 TLS 1.0。解决办法要么升级服务器要么在驱动配置里调整安全协议设置具体看驱动文档里的Encrypt和协议相关参数。5.3 查询执行阶段的坑连接通了不代表查询就顺。sqlsrv_query返回false时同样用sqlsrv_errors()看原因。常见的有参数数量不匹配、参数类型不对、SQL 语法在目标版本上不支持。参数化查询这里有个容易忽略的点sqlsrv_query的第三个参数是参数数组数组元素的顺序要和 SQL 里问号的顺序严格对应。如果 SQL 里用了命名参数:name形式那要配合sqlsrv_prepare使用sqlsrv_query对命名参数的支持有限。还有一个坑是sqlsrv_fetch_array遍历大结果集时的内存问题。默认情况下驱动会把结果缓冲在客户端结果集特别大的时候内存会爆。可以在连接选项里设Scrollable SQLSRV_CURSOR_FORWARD或者用sqlsrv_fetch逐行取减少内存占用。5.4 事务处理的注意事项用sqlsrv_begin_transaction开事务后所有操作必须用同一个连接资源。如果中间不小心用了另一个连接执行了语句那个语句不在事务里回滚的时候不会撤销会造成数据不一致。另外PHP 脚本执行超时或者被中断时未提交的事务会被服务器回滚但连接可能还挂在池子里。如果开了连接池下一个请求拿到这个连接时事务状态可能不干净。所以事务代码一定要用try-catch包住确保异常路径上也执行了rollback或者commit。6. 性能与安全上的实操建议6.1 参数化查询不是可选项拼接 SQL 字符串在这个场景下风险尤其高因为 SQL Server 的注入手法和 MySQL 有差异一些在 MySQL 上被过滤的写法在 SQL Server 上照样能生效。sqlsrv_query和 PDO 预处理都支持参数绑定没有理由不用。参数绑定时注意类型。sqlsrv_query的第四个参数可以指定每个参数的类型和方向比如array($value, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING, SQLSRV_SQLTYPE_NVARCHAR(50))。不指定的话驱动会做推断大多数情况没问题但涉及日期、二进制、大文本时最好显式指定避免隐式转换带来的性能损失或者精度问题。6.2 查询超时与重试策略sqlsrv_query的第五个参数是选项数组可以设QueryTimeout。默认没有超时或者超时很长一个慢查询能把 PHP 进程挂住很久。生产环境建议设一个合理的值比如 30 秒配合 PHP 的max_execution_time一起控制。重试要谨慎。对于读操作超时后重试一次通常安全对于写操作重试可能导致重复插入除非有幂等设计。我的做法是只对明确的连接类错误做重试查询超时不重试直接报错让上层处理。6.3 权限最小化原则连接数据库的账号不要用sa。给应用单独建一个账号只授予它需要的库和表的权限需要执行存储过程就只给执行权限不需要写就不给写权限。这个原则在哪都一样但在 SQL Server 场景下尤其要注意因为默认的public角色权限有时候比想象中大建账号后要实际验证一下它能做什么不能做什么。6.4 日志与可观测性驱动本身不提供详细的日志但你可以把sqlsrv_errors()的返回内容记到应用日志里包括错误码、消息、SQLSTATE。排查线上问题时这些信息比一句“数据库连接失败”有用得多。另外记录慢查询把执行时间超过阈值的 SQL 和参数记下来对优化很有帮助。我在实际项目里吃过一个亏早期没记错误详情线上偶发连接失败日志里只有一句笼统的提示查了两天才定位到是网络设备对长连接的空闲回收导致的。后来把错误码和发生时间都记上同类问题几分钟就能判断方向。最后分享一个小技巧如果你在本地开发环境反复遇到驱动加载问题可以写一个check_env.php脚本把phpversion()、php_sapi_name()、extension_loaded(sqlsrv)、extension_loaded(pdo_sqlsrv)、PHP_INT_SIZE这些信息一次性打出来换机器或者换环境时先跑一遍比逐个去phpinfo里翻快得多。这个脚本我到现在还留在几个老项目的根目录里每次环境变动都靠它快速确认状态。
企业数字化 ERP 产品动态
相关推荐
TensorFlow 2024 实战指南:从安装到部署的完整避坑手册 1. 为什么 2024 年还在聊 TensorFlow先把结论放前面:如果你现在准备入坑深度学习,或者团队要选一个长期维护的训练框架,TensorFlow 依然是绕不开的选项,但它和 PyTorch 的定位已经发生了明显分化。2024 年这个时间点,T… · 2026/11/3 13:00:43
Rust重写Nostr中继buzz:高并发事件验证与agent接入实战 1. 从“buzz”这个词说起:一个用Rust重写的Nostr中继到底想解决什么问题 第一次看到“buzz”这个项目名,加上Nostr、relay、Rust、agent这几个关键词,我脑子里第一反应是:又有人拿Rust来啃Nostr的中继层了。Nostr这个协议本身不复… · 2026/11/3 13:00:43
Android二手交易平台开发:架构设计与安全实践 1. 项目背景与核心需求在移动互联网高速发展的今天,二手物品交易已成为年轻人生活中不可或缺的一部分。基于Android的旧物交易平台正是瞄准了这一市场需求,旨在为用户提供一个便捷、安全的二手生活用品交易环境。这个毕设项目不仅需要实现基础的交易功能… · 2026/11/3 13:00:43
旋转圆叠加方波动画全解析:从傅里叶级数到Python实现 你肯定刷到过那个画面:一个接一个的圆套着圆,圆心之间互相牵着走,最外圈的小笔尖拖出一条不断延伸的波形,最后硬生生拼出一个棱角分明的方波。这几乎是“傅里叶级数”的官方代言图,我第一次看到时第一反应是“这玩意儿… · 2026/11/3 17:24:34
SMP平台函数功能实现与优化实践 1. 函数功能在SMP平台中的实现历程 作为一名长期从事企业信息化系统开发的工程师,我在使用SMP(软件制作平台)进行应用开发时遇到了一个关键瓶颈——平台原生缺乏函数功能。这导致我们在处理复杂业务逻辑时,不得不重复编写大量相似… · 2026/11/3 17:24:34
Redis Lua实现大模型API配额防透支的原子扣减方案 1. 项目概述:为什么“API配额防透支”不是锦上添花,而是生死线你刚上线一个面向中小企业的AI助手SaaS平台,后端调用的是OpenRouter或智谱千问这类大模型服务商的API。初期用户增长不错,某天凌晨三点,运维告警疯狂弹窗&… · 2026/11/3 17:24:34
Bookinfo微服务故障排查与性能优化实战 1. 项目背景与核心价值 Bookinfo作为微服务架构的经典示例应用,几乎成为每个云原生工程师的"必修课"。但在实际生产环境中,从简单的服务调用失败到复杂的性能瓶颈,各种问题往往让开发者头疼不已。这次我们以6.8版本Bookinfo为例&a… · 2026/11/3 17:24:34
Whisper语音转写实战:从环境搭建到批量出稿的完整指南 1. 手动整理一小时的痛,和 Whisper 给我的转机先说说我为什么会专门写这篇 Whisper 语音转写实战。我手里头有大量音频要处理:每周至少两场客户访谈、一期播客、还有零零散散的会议录音。以前靠的是最原始的办法——听一句暂停一下,把重点敲进… · 2026/11/3 17:23:32
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46