news 2026/10/22 9:19:13

Delphi中用TPaintBox生成显示二维码,并保存为PNG回读Image的完整实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Delphi中用TPaintBox生成显示二维码,并保存为PNG回读Image的完整实践

1. 从画布到文件:为什么用PaintBox而不是直接放Image

最近在做桌面端工具时,遇到一个挺典型的交互需求:界面某处要根据数据动态生成二维码,当场显示给用户看,同时提供一个“保存图片”的按钮,把二维码落盘。等下次软件启动,还要能从之前保存的文件里把二维码重新读出来,填进界面的图片控件里。

很多刚接触Delphi的人第一反应是:那我用一个TImage,生成完直接给它赋值不就行了?表面看确实简单,但实际做下来会发现TImage在频繁动态生成、反复刷新时并不理想。因为TImage本质上是把图片当作组件的一个属性在管理,每次改Picture都会触发整个控件的重绘和状态变更,高频操作时会有闪烁和内存重分配的开销。而且如果后续要在这个区域叠加绘制状态提示、临时遮罩或者边框,TImage的灵活性也不够。

这时候改用TPaintBox就是更合理的选择。PaintBox是一个纯粹的绘图画板,它不持有图片数据,只负责在OnPaint事件里把内容画出来。换句话说,你可以把二维码的生成逻辑和界面显示解耦:二维码以位图数据的形式存在内存里,PaintBox只是那个把位图呈现出来的“显示器”。需要保存时直接把内存里的位图写文件,操作对象始终是同一份数据,不存在控件和文件之间的歧义。

我在实际项目里就是这么干的。二维码生成后保存到一个TBitmap变量里,PaintBox的OnPaint里从变量取图绘制,保存按钮直接操作这个TBitmap。这样既避免了TImage“既当数据容器又当显示控件”的双重身份带来的混乱,也让后续替换、缩放、加logo这些扩展变得很容易。这篇文章就把整条链路拆开讲清楚,从库选型、绘制逻辑、文件保存,到回读进Image控件,每一步都附上可直接落地的代码和我在实际项目里踩过的坑。

适合谁来参考?如果你正在用Delphi 7到Delphi 10.4之间的某个版本做桌面工具,恰好需要“动态生成二维码、在界面上展示、保存成文件、再加载回控件”这一串功能,这篇文章应该能帮你省下不少试错时间。

2. 二维码生成库的选型:好用的库不止一个,关键是匹配Delphi版本

先说一个容易卡住新手的地方:Delphi的跨版本兼容性并不是无缝的。你在网上搜“Delphi 二维码”会发现好几个不同的开源库,它们适用的编译器版本差别很大。选错了库,光编译报错就够折腾半天。我把常见的几个方案捋了一遍,结合自己的使用经历说下取舍。

我最后固定下来用的是DelphiZXingQRCode这个老牌开源库。这个库不需要额外安装第三方依赖,纯Delphi代码实现,从古老的Delphi 6、7到新版本的RAD Studio基本上都能编译通过。它的核心只有两个单元文件:QRCode.pas和ZXingQRCode.pas,跟着你的工程一起走就行,部署极其省事。生成二维码时只需要调用一个encodeQRCode函数,传入文本、宽高、纠错等级这几个参数,返回的就是一个二维布尔数组,标记着哪些点是黑色。

除了DelphiZXingQRCode,我在开发过程中也研究过另外两个方向:一个是自己生成格式并手动实现RS纠错编码,另一个是直接调用第三方SDK或者系统库。自己生成的方式对二维码编码规范不熟悉的人不建议碰,因为Reed-Solomon纠错编码的计算细节很多,一个字节的疏忽整个码就扫不出来;调用系统库的方式在不同Windows版本上表现也不一致,跨平台发布时容易出幺蛾子。相比之下,DelphiZXingQRCode这种“源码拿进来直接用”的方式最稳妥,编译进exe里就完全不依赖外部文件了。

提示:如果界面用到的二维码要打印或者贴到对外资料上,生成尺寸不要低于256像素,最好设成512。二维码太小了被喷绘出来根本扫不动。这个我后面还会再提。

这个库的使用方式大致是这样:

uses QRCode, ZXingQRCode; var data: T2DIntArray; // 二维点阵数据 begin encodeQRCode('https://example.com', data, 256, 256, qrBoostEcc); end;

这个函数会返回一个二维整数数组data,表示生成的二维码黑点矩阵。q rBoostEcc是纠错等级参数,类似的还有qrAuto、qrLow、qrMedium、qrHigh几个取值。需要说明的是,它们返回的是布尔语义的点阵信息,画图的工作其实还没开始。这是理解后续流程的关键:生成二维码和绘制二维码是两件事,中间夹着一层“怎么把点阵变成图像”的转换逻辑。

3. 绘制环节的两条路:控件上画图和位图里画图是两回事

二维码的点阵数据拿到手之后,接下来的核心动作是绘制。很多第一次接触的人会以为直接在PaintBox上画就行,但实际上这里有两层绘制逻辑,必须分开理解。

3.1 为什么必须先把点阵画进TBitmap

PaintBox的OnPaint事件里确实可以画二维码,比如给Canvas赋色、FillRect填充一格一格的黑色方块。但如果只在OnPaint里画,那这份“画出来的东西”只存在于屏幕上,是临时的,窗口一刷新就没了。更麻烦的是,它本身不构成一张可保存的图片数据。

正确的做法是先在内存里创建一个TBitmap,把二维码逐格绘入其中,产生一张实实在在的位图,然后:

  • 屏幕上显示:把TBitmap画到PaintBox的Canvas上
  • 保存文件:把TBitmap直接保存为PNG或BMP

一个数据源,两种用途。这样整个项目里二维码的绘制逻辑只写一遍,所有环节都复用它。别把屏幕绘制和文件生成拆成两套代码,否则改一次尺寸或者换一次纠错等级,就要同步改两个地方,迟早漏掉一个。

3.2 逐格绘制的具体实现

这一步的核心思想是把二维码点阵看成一张稀疏的网格,每个黑色方块对应一个矩形填充。网上的很多示例代码都是三层循环,新手容易看晕,我拆开讲一下:

procedure DrawQRCodeToBitmap(Bitmap: TBitmap; const Data: T2DIntArray); var cellSize, i, j: Integer; x, y: Integer; begin Bitmap.PixelFormat := pf24bit; Bitmap.Width := 256; Bitmap.Height := 256; cellSize := 256 div Length(Data); // 白底 Bitmap.Canvas.Brush.Color := clWhite; Bitmap.Canvas.FillRect(Rect(0, 0, Bitmap.Width, Bitmap.Height)); // 按点阵画黑格 for i := 0 to Length(Data) - 1 do for j := 0 to Length(Data[i]) - 1 do begin if Data[i][j] = 1 then begin x := j * cellSize; y := i * cellSize; Bitmap.Canvas.Brush.Color := clBlack; Bitmap.Canvas.FillRect(Rect(x, y, x + cellSize, y + cellSize)); end; end; end;

注意一个细节:cellSize的计算是把生成尺寸除以点阵边长。二维码点阵通常是37x37、41x41这类不规则数字,不是整数倍也没关系,取整后让最外圈留一点白边,反而有利于扫码识别。如果你做过实物二维码印刷,应该知道留白边(专业说法叫“静区”)是扫码器能够定位的重要前提。

这套绘制逻辑里还有一个容易被忽略的点:BitBlt方式还是FillRect方式,性能差距在静态生成场景里并不明显,但如果你做的工具要批量生成成百上千张二维码,FillRect方式就显得笨重了。那时候可以改用Scanline直接操作像素字节,速度提升明显。但对于界面交互场景,逐格FillRect完全够用,代码简单、不容易出错,别为了炫技过早优化。

4. PaintBox的真实定位:缓存呈现与交互区域的平衡术

DrawQRCodeToBitmap把二维码画进了TBitmap,这一步完成后,下一步是怎么把它呈现在PaintBox上。

PaintBox的OnPaint事件会在窗口需要重绘的任意时机触发——包括窗口移动、遮挡恢复、尺寸变化等等。如果你的OnPaint处理写得不好,就会看到二维码一闪一闪地重画,那就是常见的闪烁问题。我的做法是:

procedure TPaintBoxForm.PaintBox1Paint(Sender: TObject); begin if Assigned(FBitmap) then begin PaintBox1.Canvas.Brush.Color := clWhite; PaintBox1.Canvas.FillRect(PaintBox1.Canvas.ClipRect); PaintBox1.Canvas.Draw(0, 0, FBitmap); end; end;

这个FBitmap是窗体类的成员变量,一旦生成二维码就存一份。每次OnPaint从缓存里取图,仅仅是Draw,不重新计算点阵、不重新逐格填充,性能开销非常小。

这个设计思路本质是“缓存呈现”——用TBitmap保存绘制结果,用PaintBox做呈现层。你可能会问,那屏幕尺寸变化怎么办?PaintBox尺寸拉伸时,Draw是按原始大小贴上去的,不会自动缩放。如果需要自适应,可以在Draw之前对Canvas进行缩放变换:

PaintBox1.Canvas.SetStretchBltMode(PaintBox1.Canvas.Handle, STRETCH_HALFTONE); StretchBlt( PaintBox1.Canvas.Handle, 0, 0, PaintBox1.Width, PaintBox1.Height, FBitmap.Canvas.Handle, 0, 0, FBitmap.Width, FBitmap.Height, SRCCOPY );

这段代码会把FBitmap拉伸到PaintBox的客户区大小,配合STRETCH_HALFTONE插值模式,缩放后的二维码边缘质量比默认模式要光滑不少。但注意这类拉伸只适合屏幕展示,保存文件时还是要基于原始尺寸做,别把拉伸后的效果直接写进文件里,否则会得到一张边缘糊掉的二维码。

还有一点比较复杂:双击交互往往也在PaintBox上做。常见做法是给PaintBox设置一个OnMouseDown或OnDblClick事件,用户点击二维码区域时可以弹出菜单、放大预览、或者触发重新生成。这时候PaintBox作为一个“交互层”的价值就体现出来了——你可以在事件里精确判断鼠标按下位置是否在二维码有效区域内,再决定是否响应用户动作。相比TImage,这种可定制性要灵活得多。

我在实际项目里的界面布局是:左侧放一个导航菜单,右侧中部放PaintBox作为二维码展示区,下方放一个按钮“生成新码”、一个按钮“保存图片”。用户填好文本点“生成新码”,二维码立即画到PaintBox上;点“保存图片”弹出保存对话框,文件落盘。整个交互流程非常干净,用户不需要关心内部的技术细节。

5. 保存本地文件的关键:格式选择和血泪坑——PNG与BMP的取舍

二维码绘制成TBitmap后,保存文件这一环相对机械,但坑一点都不少。我最早用Delphi 7做版本时,TBitmap自带的SaveToFile只能存BMP,那会儿还没意识到这是个问题。直到有一次把生成的BMP发到一个文档里,发现体积竟然有200多KB,而同样的二维码存成PNG只要几KB,这对我这种经常要把二维码嵌入报告、文档、邮件的人来说体验差距太大了。

如果要存PNG,在Delphi较新版本(如XE系列以上)里,可以直接用TPNGImage类;在Delphi 7里就得靠第三方PNG库或者用“BMP转PNG”的工具类。建议直接判断自己手头版本的RTL和第三方库支持情况,优先选PNG,因为二维码这种黑白分明的图形用PNG压缩率非常高,而且支持透明背景。

我常用的保存代码如下:

uses PNGImage; procedure SaveQRCodeAsPNG(ABitmap: TBitmap; const AFileName: string); var png: TPNGImage; begin png := TPNGImage.Create; try png.Assign(ABitmap); png.SaveToFile(AFileName); finally png.Free; end; end;

实际操作中可以做一层封装,让函数直接接收点阵数据,内部完成“创建位图→逐格绘制→保存PNG→释放位图”一整条流程,这样调用方不需要关心TBitmap的生命周期,也有利于在后台线程里批量生成。

procedure GenerateQRCodeFile(const Text: string; const FileName: string; Size, ECCLevel: Integer); var data: T2DIntArray; bmp: TBitmap; begin encodeQRCode(Text, data, Size, Size, ECCLevel); bmp := TBitmap.Create; try DrawQRCodeToBitmap(bmp, data); // 上文实现 SaveQRCodeAsPNG(bmp, FileName); finally bmp.Free; end; end;

注意:保存PNG时如果发现文件打不开,先检查TBitmap的PixelFormat。PNGImage内部对位图格式是有要求的,如果PixelFormat设成了pfDevice,在某些机器上会生成空白图像。显式指定pf24bit或pf32bit可以避开这个坑。

还有一个很容易踩的坑是中文文件名路径。如果你用TFileStream或者SaveToFile时FileName参数里带中文,某些老版本的Delphi会出现编码混乱,导致保存失败或者文件名乱码。最省心的方案是统一用TEncoding.UTF8转换一下路径字符串,或者直接让用户在保存对话框里选路径,借助对话框内部的编码处理来规避。

6. 回灌Image控件:从文件读取到赋值的完整链路与高清失真问题

保存好了PNG,另一个需求接踵而至:软件重启后要从之前保存的本地文件里把二维码重新读出来,显示到界面某个TImage控件上。这一条链路看起来就两行代码,但我在测试中发现了一个很值得说的细节:Image控件默认缩放模式会导致二维码变糊的视觉问题。

6.1 标准加载方式

先把加载代码列出来:

uses PNGImage; procedure LoadPNGIntoImage(const AFileName: string; AImage: TImage); var png: TPNGImage; begin if not FileExists(AFileName) then Exit; png := TPNGImage.Create; try png.LoadFromFile(AFileName); AImage.Picture.Graphic := png; finally png.Free; end; end;

TImage的Picture.Graphic在赋值时会自动持有这个Graphic对象,所以这里png.Free是安全的。有人可能会担心赋值后释放会出问题,其实TPNGImage被赋给AImage.Picture.Graphic之后,Picture会维护自己的引用计数,释放原对象不会导致显示异常。这在Delphi里是正常行为。

如果你更喜欢让Image自己管理图像数据,也可以用AImage.Picture.LoadFromFile(AFileName),这样省一步手动创建。但两种写法在遇到文件损坏时处理不一致,我在项目中更喜欢显式创建PNG对象,因为能在加载失败时捕获更精确的异常信息。

6.2 高清失真的真凶与解法

Image控件读取图片后,如果它比图片大,Delphi默认会进行拉伸显示,而拉伸的默认插值模式比较粗糙,会造成明显的锯齿,二维码的边缘看起来“毛刺”感很重。特别是当原始PNG是256x256、Image控件显示区域是300x300甚至更大的时候,这个问题几乎必现。

最简单的解法是给Image设置合适的Stretch属性和保持比例:

Image1.Stretch := True; Image1.Proportional := True;

Stretch = True让图片可以跟随控件尺寸变化,Proportional = True让缩放时保持纵横比,避免二维码被拉成矩形。但这样只能解决比例问题,并不能彻底解决模糊。要让二维码在控件的不同尺寸下都保持清晰,更靠谱的思路是让加载时保持原始尺寸,或者让Image控件的大小与图片像素尺寸尽量一致。

如果你对清晰度有更高要求,可以绕开TImage的自动缩放能力,用自定义绘制的方式显示二维码。比如放一个TPaintBox,加载完PNG后把位图设为FBitmap,在OnPaint里用StretchBlt拉伸绘制,并设置STRETCH_HALFTONE模式。这样的显示效果比TImage的默认拉伸要细腻。在二维码这类边缘锐利的图形上,差的其实不是尺寸而是插值算法,这个细节很多人没注意。

// OnPaint 里采用高质量拉伸 PaintBox1.Canvas.SetStretchBltMode(PaintBox1.Canvas.Handle, STRETCH_HALFTONE); StretchBlt( PaintBox1.Canvas.Handle, 0, 0, PaintBox1.Width, PaintBox1.Height, FBitmap.Canvas.Handle, 0, 0, FBitmap.Width, FBitmap.Height, SRCCOPY );

这段代码可以和本文前面第4小节里PaintBox显示二维码的方案统一起来:只要FBitmap是从本地PNG加载进来的,PaintBox既能显示动态生成的二维码,也能显示本地文件里读回来的二维码。一个控件、一套事件逻辑,同时覆盖两种来源,工程上非常省事。

我还试过一个更简单的替代方案:干脆让Image控件在启动时用代码置为原始图片像素尺寸,或者把窗体的界面布局做成固定尺寸。许多桌面工具的二维码展示区本来就是固定大小,这种情况下并不需要复杂的缩放逻辑,直接把Image按图片原始尺寸摆放即可。但放到通用的工程框架里,仍然建议用PaintBox + StretchBlt的方案,毕竟控件的尺寸在你的软件里大概率不是永远不变的。

7. 集成代码全貌与后续扩展:从单张二维码到批量生成流程

把整条链路串起来的完整示例代码我整理了一份,这个版本做了适度精简,但核心结构完整。代码里我删减了菜单和事件绑定部分,只留下关键的业务逻辑,方便你在自己的工程里移植:

unit QRCodeDemo; interface uses Windows, Graphics, ExtCtrls, Classes, Controls, Dialogs, QRCode, ZXingQRCode, PNGImage; type TQRCodeForm = class(TForm) PaintBox1: TPaintBox; Image1: TImage; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure PaintBox1Paint(Sender: TObject); private FBitmap: TBitmap; procedure GenerateQRProc(const AText: string; ASize: Integer); procedure SaveCurrentQR(const AFileName: string); procedure LoadQRToImage(const AFileName: string); public end; implementation procedure TQRCodeForm.FormCreate(Sender: TObject); begin FBitmap := TBitmap.Create; FBitmap.PixelFormat := pf24bit; GenerateQRProc('示例文本', 256); end; procedure TQRCodeForm.FormDestroy(Sender: TObject); begin FBitmap.Free; end; procedure TQRCodeForm.GenerateQRProc(const AText: string; ASize: Integer); var data: T2DIntArray; cellSize, i, j: Integer; begin encodeQRCode(AText, data, ASize, ASize, qrBoostEcc); FBitmap.Width := ASize; FBitmap.Height := ASize; cellSize := ASize div Length(data); FBitmap.Canvas.Brush.Color := clWhite; FBitmap.Canvas.FillRect(Rect(0, 0, ASize, ASize)); for i := 0 to Length(data) - 1 do for j := 0 to Length(data[i]) - 1 do if data[i][j] = 1 then begin FBitmap.Canvas.Brush.Color := clBlack; FBitmap.Canvas.FillRect(Rect( j * cellSize, i * cellSize, (j + 1) * cellSize, (i + 1) * cellSize )); end; PaintBox1.Invalidate; end; procedure TQRCodeForm.SaveCurrentQR(const AFileName: string); var png: TPNGImage; begin png := TPNGImage.Create; try png.Assign(FBitmap); png.SaveToFile(AFileName); finally png.Free; end; end; procedure TQRCodeForm.LoadQRToImage(const AFileName: string); begin if FileExists(AFileName) then Image1.Picture.LoadFromFile(AFileName); end; procedure TQRCodeForm.PaintBox1Paint(Sender: TObject); begin if Assigned(FBitmap) then begin PaintBox1.Canvas.Brush.Color := clWhite; PaintBox1.Canvas.FillRect(PaintBox1.Canvas.ClipRect); PaintBox1.Canvas.Draw(0, 0, FBitmap); end; end; end.

代码里我保留了最原始的实现,没有加StretchBlt缩放,这是为了阅读方便。实际项目中请按第3、6小节的方案,在需要自适应时替换为StretchBlt方式。

这套方案的扩展空间也很大,我简单说两个方向:

第一个是批量生成。应付“一批数据各生成一张二维码”的场景时,只要把GenerateQRProc里的绘制核心抽成函数,在后台线程里循环处理,再按数据编号命名文件即可。注意Delphi中的VCL组件访问不是线程安全的,后台线程里不要直接操作TBitmap和PaintBox,建议线程只负责生成点阵数据,把结果通过Synchronize或者Queue转回主线程再绘制。

第二个是添加Logo。二维码中央可以贴一个小图,整体辨识度会高很多。实现上就是在DrawQRCodeToBitmap全部填充完之后,在Bitmap的中央区域再画一张小图:

var LogoBitmap: TBitmap; Cx, Cy, LogoSize: Integer; begin Cx := (FBitmap.Width - LogoSize) div 2; Cy := (FBitmap.Height - LogoSize) div 2; FBitmap.Canvas.Draw(Cx, Cy, LogoBitmap); end;

但这一步要谨慎,Logo过大会遮挡过多编码区域导致扫码失败。一般建议Logo边长不超过二维码总边长的四分之一,也就是占比控制在25%以内。而且生成时用的纠错等级要优先选qrHigh,因为高纠错等级能在中间被遮挡一块的情况下依然正确解码。

8. 关于显示比例和DPI缩放,我踩过的最后一个大坑

最后提一个屏幕显示相关的问题:同一个二维码,在100%缩放的电脑上显示正常,在缩放到125%或150%的电脑上,PaintBox里的二维码边缘会明显发虚,或者像素格出现大小不均匀。

这背后的原因是不同DPI设置下,Delphi窗体和控件的坐标、Canvas的缩放规则会变。特别是启用PerMonitorV2 DPI感知之后,某些旧版本VCL的绘制代码没有适配这种缩放。表现就是你在绘制时用的尺寸是逻辑像素,但实际显示在物理像素上,两者不一致导致拉伸错位。

我目前的应对策略是:

  • 界面高DPI适配优先用TForm的ScaleForCurrentDpi或TForm.ScaleBy,让整个窗体统一缩放
  • PaintBox的尺寸定义成逻辑值,绘制时以PaintBox的实际Width、Height为准动态计算二维码绘制区域
  • 如果项目还在使用不支持高DPI的Delphi老版本,建议关闭PerMonitorV2,或者手动在工程文件里写入SetProcessDPIAware以保持兼容

如果二维码只是展示和保存,其实这样已经足够。真正追求像素级完美的场景,就得在绘制的坐标系里乘上Screen.PixelsPerInch / 96这个比例系数,但这是另一个深度话题了,普通业务需求用不到。

我把这套流程完整跑过好几轮,从生成、显示、保存到回读,整个链路里最容易翻车的是PNG格式处理和DPI缩放,其余部分Delphi基础控件事先都帮你兜住了。希望这篇文章能让你少走一段弯路,直接跑通自己项目里的二维码功能。

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

NopCommerce插件架构全解析:运行时扫描与元数据驱动机制

我第一次读 NopCommerce 4.9.3 的源码时,第一件事就是翻 Program.cs,想把插件加载的注册逻辑找出来。找了大半天,愣是没看见一行 AddPlugin。后来才反应过来,NopCommerce 插件架构设计的核心思路并不是“启动时逐个注册”&#xf…

作者头像 李华
网站建设 2026/10/19 12:47:40

Flink CEP实战:从流数据中识别复杂事件模式

1. 从一个真实的数据处理困境说起三年前我接手过一个物联网设备监控的活儿,设备端每隔几秒就往消息队列里扔一条状态数据,包括设备编号、温度、电压、信号强度这些字段。业务方的需求听起来特别朴素:设备温度连续三次超过阈值就报警&#xff…

作者头像 李华
网站建设 2026/10/20 0:04:32

模型层设计实战:从字段到行为,构建可维护的工程枢纽

1. 从“models”这个标题说起:一个被低估的工程枢纽“models”这个词,单独拎出来看,信息量几乎为零。它可以是机器学习模型,可以是数据模型,可以是前端表单模型,也可以是3D建模文件。但恰恰是这种模糊性&am…

作者头像 李华
网站建设 2026/10/20 9:23:48

AI Agent工程化落地核心指标:智能面积(ICA)详解

1. 项目概述:一本手册为何引发行业关注?“阿里云发布 Alibaba Cloud AI Agent Handbook,阐述‘智能面积’”——这个标题乍看像是一则常规的产品公告,但背后藏着当前AI落地阶段最棘手的真问题:我们建了无数个大模型API…

作者头像 李华
网站建设 2026/10/21 17:25:32

CAD图纸乱码怎么办?SHX字体合集安装与配置指南

简介:面向建筑、机械、测绘、电力等行业制图人员的AutoCAD SHX字体合集,集中整理中文简体、繁体及工程符号类字体,可解决图纸打开时提示缺少字体、文字显示为问号或乱码的常见问题。压缩包共360个文件,以351个shx字体文件为主体&a…

作者头像 李华
网站建设 2026/10/19 23:14:00

NB-IoT+GPS低功耗定位终端设计:STM32与WH-NB73、NEO-7N协同优化

1. 项目概述:为什么这个组合在低功耗广域定位场景里“稳得一批”“基于NB-IoT的GPS定位系统(STM32、WH-NB73、U-BLOX NEO-7N)”——光看标题,你就能嗅到一股典型的工业级物联网味道:它不追求炫酷UI,也不拼算…

作者头像 李华