news 2026/9/15 1:46:13

LCD1602实用指南:光标定位、数字显示与局部滚屏深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LCD1602实用指南:光标定位、数字显示与局部滚屏深度解析

LCD1602应该算是我在单片机这条路上打交道最多的外设之一。早些年入门的时候,能点亮一个“Hello World”就觉得自己已经征服它了,但真到了做项目才发现,显示静态字符串只是最基本的热身。光标定位、显示动态数字、局部滚屏,这些“骚操作”才是日常项目里真正绕不开的需求。这篇文章我就把这几个操作从原理到代码完整拆开讲,每一步都给出可以直接抄的写法,也把当年踩过的坑一并交代清楚。无论你是刚点亮过LCD1602的新手,还是想在项目里用得更顺手的进阶玩家,这篇应该都能帮上忙。

先说清楚一个概念上的东西:LCD1602本质上是一个只能显示ASCII字符和自定义字符的字符型液晶屏。它内部有一块叫DDRAM(显示数据存储器)的“显存”,屏幕显示什么内容,取决于DDRAM里存了什么数据。你做的所有操作——光标定位、显示数字、局部滚屏,本质上都是在跟这块“显存”打交道。把这一层想明白了,后面所有操作都顺理成章。

1. 别急着写代码,先把LCD1602的显示模型搞明白

1.1 为什么LCD1602只能显示字符?“显存”到底是什么

LCD1602的核心工作原理并不复杂:它的控制芯片(最常见的是HD44780)内部有一块DDRAM,容量是80×2,也就是每行80个字节、共2行,总共160个字节的存储空间。每一个字节对应屏幕上的一个字符位置,写入0x41就显示大写A,写入0x30就显示数字0。

但注意,屏幕实际只显示16×2。这就是问题所在:DDRAM空间大于可见区域。DDRAM每行有80个字节,而物理屏幕只能显示最前面的16列。剩下的64列不是不存在,而是“藏在屏幕外面”。这个设计非常关键,局部滚屏的核心思想就藏在这里。

你可以把DDRAM想象成一卷很长的报纸,物理屏幕只是一个16列宽的取景框。默认情况下,取景框对准的是报纸最左边的16列。只有当内容写入DDRAM的0x00到0x0F这一小段时,才能被看到。这卷“报纸”的另一部分,需要靠后面讲的移屏指令或主动刷新窗口才能显示出来。

1.2 驱动层的三个基本功:写命令、写数据、读忙

网上关于LCD1602的驱动代码一搜一大把,但说实话,我不推荐直接去下载。原因很简单:各家的引脚定义不同、主控不同、延时节奏不同,下载下来要么点不亮,要么显示乱码,最后还是得自己改。不如花十分钟把最底层的三个函数写透,一劳永逸。

首先要搞清楚LCD1602的控制引脚:

  • RS:寄存器选择。RS=0写命令,RS=1写数据。
  • RW:读写选择。RW=0写,RW=1读。
  • E:使能引脚,下降沿锁存数据。

这三根引脚配合8根数据线(DB0~DB7),组成了最基础的通信时序。写命令和写数据的区别,仅仅在于RS引脚电平的不同。核心代码如下:

#include <reg52.h> #define LCD_DATA P0 sbit LCD_RS = P2^0; sbit LCD_RW = P2^1; sbit LCD_E = P2^2; void lcd_delay(unsigned int t) { while(t--); } void lcd_write_cmd(unsigned char cmd) { LCD_RS = 0; // RS=0,选择命令寄存器 LCD_RW = 0; // RW=0,写模式 LCD_DATA = cmd; LCD_E = 1; lcd_delay(10); LCD_E = 0; // 下降沿,数据被锁存 lcd_delay(50); } void lcd_write_data(unsigned char dat) { LCD_RS = 1; // RS=1,选择数据寄存器 LCD_RW = 0; LCD_DATA = dat; LCD_E = 1; lcd_delay(10); LCD_E = 0; lcd_delay(50); }

这段代码有几个细节值得展开。第一,E引脚的高电平持续时间不能太短,否则数据来不及锁存,我用lcd_delay(10)保证至少有几个微秒的稳定时间。第二,每次写完都要留一点间隔,这是给LCD内部的控制器处理数据的时间。网上很多驱动代码点不亮,十有八九是这里延时不够。

初始化流程也有固定套路,按照HD44780手册推荐的顺序来,稳得很:

void lcd_init(void) { lcd_delay(15000); // 上电等待,让LCD内部完成复位 lcd_write_cmd(0x38); // 8位数据、2行显示、5x8点阵 lcd_write_cmd(0x08); // 显示关闭 lcd_write_cmd(0x01); // 清屏,DDRAM清零,地址归零 lcd_write_cmd(0x06); // 写入数据后,地址指针自动+1 lcd_write_cmd(0x0C); // 显示打开,光标关闭 }

这里有一个常见的初学者误区:只发0x38就以为完成了初始化。实际上0x38只是设置了接口模式和显示行数,如果不关显示、不清屏、不设置地址指针移动方向,后续操作很容易出现莫名其妙的偏移。

理论上还要在读操作前检查忙标志位(BF位,即DDRAM地址或数据是否在忙状态),但很多实际项目图省事,直接用延时替代。延时方案在8位模式下、主频不高时完全够用。但如果你的代码跑得很快、操作间隙很短,建议还是老老实实加一个lcd_busy(),从DB7引脚读忙标志,不然数据写快了会丢失。

2. 光标定位:本质就是写一个地址进去

2.1 DDRAM地址和光标位置的映射关系

很多人在LCD1602上遇到过这样一个问题:想让光标跑到第二行第5列,就直接写lcd_write_cmd(0x80 + 5),结果光标跑到了第一行,或者干脆不显示。原因在于,他忽略了第二行的DDRAM起始地址不是0x00,而是0x40。

看一下DDRAM地址和屏幕位置的对应关系:

屏幕位置DDRAM地址范围
第1行第1~16列0x00 ~ 0x0F
第2行第1~16列0x40 ~ 0x4F

LCD1602的写地址命令格式是:最高位固定为1,低7位是DDRAM地址。也就是说,你发送0x80,实际是把“地址指针”设置到0x00,即第一行的行首。要让光标定位到第一行第n列,命令是0x80 + n;要定位到第二行第n列,命令是0xC0 + n,因为0x80 + 0x40 + n = 0xC0 + n

完整的定位函数是这个样子:

void lcd_set_cursor(unsigned char row, unsigned char col) { unsigned char addr; if (row == 0) { addr = 0x00 + col; } else { addr = 0x40 + col; } lcd_write_cmd(0x80 | addr); }

注意col的范围是0~15,row是0或1。很多人的代码在这里埋了隐患:col传入了16甚至更大的值,地址就越界了。虽然表面上看起来还是“写进去了”,但显示位置会跑到不该出现的地方。

2.2 三种光标玩法:不显示、下划线、闪烁块

光标定位之后,接下来要考虑光标本身的显示状态。LCD1602的光标控制命令是0x08加上几个标志位组合出来的。核心就三组:

命令效果
0x0C显示开,光标关,闪烁关
0x0E显示开,下划线光标开
0x0F显示开,下划线光标开,光标闪烁
0x0D显示开,光标关,但光标位置的字符闪烁

这四种模式我在实际项目里全都用过。0x0C是默认状态,适合普通显示场景;0x0E适合做菜单选项的高亮提示,下划线光标会出现在当前字符位置下方;0x0F适合做输入框,闪烁块提示用户“这里可以输入”;0x0D这个模式比较冷门,它会让光标位置的字符整体闪烁,适合做倒计时的数字提醒,效果比下划线醒目。

还有一组移动光标的指令不能漏:0x10(光标左移一位)、0x14(光标右移一位)、0x18(整屏左移)、0x1C(整屏右移)。这几个指令和写地址定位不太一样,它们不会改变DDRAM里的数据,只是改变“取景框”的位置,属于硬件层面的移动。用好了可以实现很多特殊效果,比如跑马灯式的菜单项轮流展示。

2.3 代码示例:做一个密码输入框的光标闪烁提示

把上面这些内容串起来,可以做一个非常实用的组合:密码输入框。用户每输入一位数字,光标自动右移一位,而且光标保持闪烁,提示“该输入下一位了”。

void display_password_input(void) { unsigned char i; lcd_write_cmd(0x01); // 清屏 lcd_write_cmd(0x0F); // 显示开,光标开,闪烁开 lcd_set_cursor(0, 0); for (i = 0; i < 6; i++) { lcd_write_data('*'); // 用*占位,模拟密码输入后的显示 } lcd_set_cursor(0, 0); // 光标回到起始位置,开始等待输入 // 之后每收到一次按键,就把光标右移一位 }

这里有一个我踩过的坑:清屏之后直接发0x0F,光标默认回到第一行行首,但因为是“闪烁块”模式,整个屏幕看起来像有6个格子都在闪。这时候一定要先画好占位符、再定位光标到起始位置,否则光标闪烁会让用户以为出bug了。

光标定位这块掌握了,接下来可以处理一个更实际的问题:怎么让LCD1602显示出来自变量的数字。切到数字显示这个主题,顺便把ADC采样电压显示、温度传感器数值显示这类场景一次性解决干净。

3. 显示数字:给MCU当“翻译官”的中间层

3.1 为什么直接send_char(123)显示不出来

有读者可能尝试过直接这样写:

lcd_write_data(123);

然后屏幕上一个字符都没显示,或者显示成了一个乱码。原因很简单:LCD1602只能显示ASCII字符,你要它显示数字3,写入DDRAM的值必须是字符'3'的ASCII码,也就是0x33。直接写入123,对应的是ASCII码表里第123个字符,那是一个你没见过的控制字符或特殊符号,屏幕当然显示不对。

这里的本质是:MCU里存的123是二进制数值,而LCD1602认的是ASCII字符编码。一个数值要显示出来,中间必须有一个“翻译”过程,把数值逐位拆成对应的ASCII码。字符'0'到'9'的ASCII码是0x30到0x39,正好是连续排列的,所以“翻译”的公式极其简单:数字 + 0x30 = 对应字符的ASCII码。

3.2 多位数拆分算法:从后往前拆和从前往后拆

有了单个数字到字符的转换,剩下的就是把一个多位整数拆成一位一位的数字。最经典的方法是循环取模:

void lcd_print_int(unsigned int num) { unsigned char buf[6]; unsigned char i = 0; if (num == 0) { lcd_write_data('0'); // 0的单独处理,很多坑都出在这 return; } while (num > 0) { buf[i++] = (num % 10) + 0x30; // 取出最低位,转成字符 num /= 10; } while (i > 0) { lcd_write_data(buf[--i]); // 逆序输出,因为刚才拆出来的是反的 } }

这个算法里有两个细节必须有名字。第一,num == 0的情况必须单独处理,否则while循环一次都不进,函数什么都不打印,屏幕上留个空位,还没人能看懂为什么。第二,因为取模是先从个位开始拆,所以要先存到临时数组里,再倒序逐个发送,顺序才是十进制的正常顺序。

如果你希望数字固定占满几位,比如显示时间时希望显示“09:05”而不是“9:5”,可以在前面补一个‘0’字符。这种需求补空格还是补0,要看场景,菜单项显示一般补空格右对齐,时间显示必须补0。

3.3 进阶方案:用sprintf做格式化,一次搞定负数和小数

如果项目里需要显示负数或者浮点数,手写拆分就有点力不从心了。负数要先判断符号,再对绝对值做拆分;浮点数就更麻烦,精度控制、四舍五入、小数位对齐全是细节。这时候直接用C标准库的sprintf,写起来又快又稳:

#include <stdio.h> void lcd_print_num(long num) { char buf[12]; sprintf(buf, "%ld", num); lcd_print_string(buf); } void lcd_print_string(char *str) { while (*str) { lcd_write_data(*str++); } }

注意,51单片机用sprintf会有不小的资源开销,尤其涉及到浮点时,链接出来的代码体积会明显增大。我的建议是:整数场景直接用sprintf没问题,但浮点场景要慎重。比如要显示一个温度值25.6℃,可以先把浮点放大10倍变成256,再手动插入小数点,这样既不依赖浮点格式化,又省代码空间。这招在实际项目里很常用,是资源紧张的MCU上的标准做法。

其实,想显示带小数的数值,还有一种更优雅的写法,同时兼容整数的通用思路:把小数部分和整数部分拆开计算,然后拼接字符串。比如:

// temp = 25.6℃,要显示成 "25.6" int int_part = (int)temp; int frac_part = (int)((temp - int_part) * 10 + 0.5); // +0.5是为了四舍五入 sprintf(buf, "%d.%d", int_part, frac_part);

这里的+0.5是个老生常谈的细节,很多新手没注意到,直接截断会导致显示值比实际值小0.1,做温度控制时误差很容易被放大。

3.4 代码示例:一个通用的lcd_print_num函数

把整数、负数、定长补零这些需求揉到一起,可以写一个稍微好用点的通用函数:

void lcd_print_int_fixed(long num, unsigned char width) { char buf[12]; unsigned char len, i; sprintf(buf, "%ld", num); len = strlen(buf); if (len < width) { for (i = 0; i < width - len; i++) { lcd_write_data(' '); // 位数不够就用空格补,右对齐 } } lcd_print_string(buf); }

width参数可以在显示菜单序号、多组数据对齐时派上大用场,比每次都手动拼空格省心太多。比如显示榜单前三名:

名次显示结果
第1名空格空格1
第12名空格12
第123名123

右对齐的效果在LCD上看起来整齐得多,做UI的时候体验提升明显。

4. 局部滚屏:把LCD当成可移动的取景框

4.1 局部滚屏的两种实现路线

滚屏的需求一般来自两类场景:一是要显示的文本长度超过16个字符,比如一段说明文字、一条长传感器数据;二是一行里要滚动显示多条信息,像跑马灯一样轮流展示。但LCD1602没有真正意义上的“局部滚屏”指令,要做成这个效果,有两条路线选。

第一是硬件移屏指令,也就是前面提到的0x18和0x1C。这两个指令能让整屏内容左移或右移一格。问题在于它是“整屏移动”,不是“局部滚屏”——你只想让第二行滚起来,第一行也会跟着跑,完全没法接受。硬件移屏的最好归宿是制作整屏动画,比如开机时屏幕向左滑入,做欢迎动画还是很好用的。

第二是“缓冲区刷新”方案,也是实现局部滚屏最正统的方案。思路是:在MCU的RAM里维护一个比屏幕更宽的字符缓冲区,比如2行×20列。然后定义屏幕的“可见窗口”,每次刷新时,把窗口中对应位置的内容重新写入DDRAM。要滚动时,只要改变窗口的起始偏移量,再整体刷新一遍即可。

这个方案的精髓在于,LCD1602的DDRAM不是只能写0x00~0x0F这几个地址,你可以往0x10~0x4F这些“不可见区域”写数据,然后通过移屏指令让它们进到可见窗口。但缓冲区刷新方案连这部分都不用管,直接每次刷新可见窗口的16列就行,稳、简单、局部可控。

4.2 显示缓冲区的设计:20列窗口怎么塞进16列屏幕

下面以一行20个字符、从左向右滚动为例,具体算一算怎么做。这是“局部滚屏”最常见的场景,适合放在第二行,第一行保持固定显示标题。

定义一个缓冲区:

unsigned char code line2[20] = "0123456789ABCDEFGHIJ"; // 要滚动显示的20个字符 unsigned char offset = 0; // 可见窗口的起始偏移量,范围0~4

为什么offset上限是4?因为20个字符减去16列可见窗口,能滑动的最大偏移量是20 - 16 = 4。超过4,右边就会露出空白,看起来就露馅了。

每次滚动的刷新逻辑:

void refresh_scroll_line(void) { unsigned char i; lcd_set_cursor(1, 0); // 定位到第二行行首 for (i = 0; i < 16; i++) { lcd_write_data(line2[offset + i]); } }

每次刷新前只要把offset加1,再调用这个刷新函数,第二行就像被向左推了一格。如果到末尾,让offset重置为0,就形成了循环滚动的效果。整个过程看起来是屏幕“吃掉”了最左侧的一个字符,右侧新露出一列,行云流水。

4.3 代码示例:两行独立局滚

把固定行和滚动行结合起来,就能实现一种非常实用的UI模式:第一行显示菜单或状态,第二行滚动显示详细内容。下面是整合后的代码框架:

void scroll_display(void) { unsigned char i; static unsigned char offset = 0; // 刷新第一行:固定内容 lcd_set_cursor(0, 0); lcd_print_string("Sensor Status "); // 固定显示,占满16列 // 刷新第二行:滚动内容 lcd_set_cursor(1, 0); for (i = 0; i < 16; i++) { lcd_write_data(line2[offset + i]); } offset++; if (offset > 4) { offset = 0; // 回到头部,循环滚动 } }

这个函数每次被调用,就完成一帧刷新。我建议在主循环里用一个定时标志来控制调用频率,比如每200ms调用一次,滚动速度刚好适合人眼阅读。如果放到while里空转,刷新太快,字符会闪成一片糊的,观感极差。

还有一点必须注意:刷新第二行时,只写了16个字符。如果原来的内容比16个长,而滚动到末尾时后面没有内容了,屏幕最右侧可能会残留上一次显示的旧字符残影。稳妥的做法是每次刷新前,先在要刷新的行首写16个空格“清屏”,然后再写新的滚动内容。虽然多花一点时间,但能避免很多视觉脏数据问题。

4.4 滚屏节奏控制:定时器 vs 延时函数的取舍

说到调用频率,这里必须多写几句。新手最喜欢在while(1)里直接套delay来控制滚屏速度,比如:

while (1) { scroll_display(); delay_ms(200); }

这个写法在单任务的裸机程序里没问题,但你一旦同时要扫描按键、刷新ADC数据、驱动蜂鸣器,整个程序就会卡在delay_ms里面,其他任务全部停摆。用定时器加标志位的方式明显更靠谱:

unsigned char flag_scroll = 0; // 定时器0中断,每200ms置一次标志 void timer0_isr(void) interrupt 1 { TH0 = 0xFC; TL0 = 0x18; // 定时器重装值,假设12MHz晶振 flag_scroll = 1; } void main(void) { // 主循环 while (1) { if (flag_scroll) { flag_scroll = 0; scroll_display(); } // 这里还能干别的:读按键、刷新LED、处理传感器…… } }

定时器方案的另外一个好处是滚动节奏稳定,不会因为主循环里其他任务的时长变化而忽快忽慢。滚屏这种东西,最怕的就是速度不稳定,看着像抽风。

5. 调试实录:踩过的坑和排查思路速查表

5.1 常见问题:白屏、乱码、错位、闪烁

LCD1602调试过程中会遇到的坑,说来说去就那么几类,但每一类都能卡住人好几天。我整理了一个速查表,基本覆盖了常见情况:

现象可能原因排查建议
屏幕亮但无字符对比度电位器没调好调节Vo引脚的电位器,直到出现黑色小方块
上电完全白屏初始化时序不对或电源问题检查5V供电、E引脚延时、是否先等15ms
字符乱码数据线接错或时序中的建立时间不足用逻辑分析仪对比数据线上电平与手册要求
显示位置对不上第二行地址算错确认0xC0才是第二行行首
数字显示成乱码直接把数值写进了DDRAM检查是否做了“数值+0x30”的ASCII转换
局部刷新有残影刷新区域没先清空刷新前先补16个空格
滚屏速度忽快忽慢用delay阻塞控制节奏改用定时器+标志位

白屏不显示这个问题,我多说一句。我遇到过很多次,最后发现不是代码问题,而是对比度电位器拧到了最暗。LCD1602的Vo引脚电压决定屏幕对比度,电压太高字迹会变淡甚至消失。第一次上电时,先调电位器看有没有小方块出现,是最快的判断方法。你可以手动把电位器旋到中间位置再上电,大概率能看到那两排小方块。

5.2 排查技巧:逻辑分析仪和“最小复现法”

如果你的程序写了好几屏,发现定位和数字显示都不对,别着急一行一行盯代码。最快的办法是“最小复现法”:写一个只有初始化和字符显示的空程序,让屏幕显示一行固定的“ABCDEFGHIJKLMNOP”,如果这都显示不对,问题一定出在底层驱动;如果这行显示正常,再逐层往上加功能,加到哪一步出问题,问题就在哪一步。

有条件的话,逻辑分析仪是排查LCD1602时序最好的帮手。把RS、RW、E这3根线和DB0~DB7接上去,抓一段写命令和写数据的波形,和HD44780数据手册里的时序图对比一下。绝大多数“偶发乱码”、“莫名错位”的问题,一眼就能看出是建立时间不够,或者数据线上有毛刺。

5.3 关于“哪里可以下载LCD1602的驱动代码”这件事

经常有朋友问哪里能下载LCD1602的驱动代码。我的回答是,如果你已经读到这里,真的就不需要再去找现成的下载了,直接手写一套不香吗?LCD1602的驱动就那么几个函数:初始化、写命令、写数据、定光标、清屏。加起来不超过100行,而且是自己写的,引脚、延时、时钟频率全都心里有数,出问题也能快速定位。

退一步说,就算去下载了网上现成的代码,也很可能因为引脚定义和你的板子不一样,或者初始化顺序和你的晶振频率不匹配,点不亮、显示乱码。到时候你还是得读懂别人的代码、自己改,费的功夫比从零写一套多得多。驱动代码这种底层的东西,越早自己掌握越好。

还有一个经验:把底层驱动和应用层严格分开。底层只提供几个函数接口,比如lcd_init()lcd_set_cursor()lcd_put_char()lcd_print_string()。应用层爱怎么玩就怎么玩,显示菜单也好、滚屏也好、显示传感器数据也好,一律只调用这些接口,不要直接在应用层操作RS、RW这些引脚,更不要裸写0x80、0xC0这样的DDRAM地址。这样无论后面怎么改版、换主控,应用层代码几乎不用动。

5.4 关于光标闪烁和动态刷新的一个小技巧

最后分享一个我一直在用的小技巧。当LCD1602同一行里既有静态的标题文字,又有动态变化的数字时,如果每次都把整行重刷一遍,就能看到明显的闪烁。正确的做法是:只刷新数字所在的那几个字符位置,标题部分不动。具体来说,用lcd_set_cursor(1, 10)把光标定位到数字起始列,然后只往这个位置写入新的数字字符,刷新后立刻用空格把数字后面多出来的字符覆盖掉,避免旧数字残留。这样刷新区域最小,屏幕观感比整行刷新稳得多。

还有一种更稳的做法,是利用光标定位实现“精准更新”。比如电压显示固定在第2行第0到第3列,更新的时候就只重写这4列的内容。实时性要求高的场合,这种“局部更新”策略几乎是必须的,否则动态画面闪得人眼睛疼。

在实际项目里,LCD1602虽然老,但胜在便宜、稳定、驱动简单。把光标定位、数字显示、局部滚屏这三套操作吃透,已经能覆盖大部分数据展示型项目的需求。后面不管你是要写一个多级菜单、做一个温度监控站,还是给仪器加一个滚动状态栏,底层逻辑都是今天说的这些。

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

Tamagui 配置完全指南:从 createTamagui 到生产级设计系统

Tamagui 配置完全指南&#xff1a;从 createTamagui 到生产级设计系统 【免费下载链接】tamagui Style React fast with 100% parity on React Native, an optional UI kit, and optimizing compiler. 项目地址: https://gitcode.com/GitHub_Trending/ta/tamagui 本文围…

作者头像 李华
网站建设 2026/9/15 1:45:12

私人wordpress实战案例:搞定备案后的5个UI设计细节

私人wordpress实战案例:搞定备案后的5个UI设计细节 做网站最怕什么?不是代码报错,是备案那几天盯着邮箱等审核,心里直打鼓。很多老板拿到《ICP备案成功通知》短信,手一抖,以为万事大吉,结果网站打开全是乱码或者布局错乱。我见过太多这种 实战案例 ,明明花了大价钱做的 私人wordpress…

作者头像 李华
网站建设 2026/9/15 1:44:57

Rust Trait 深度解析:从泛型约束到动态分发

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

作者头像 李华
网站建设 2026/9/15 1:44:47

AI Agent自主漏洞利用与自我复制实验警示:安全防御如何破局

2024年底&#xff0c;安全圈被一项来自伊利诺伊大学厄巴纳-香槟分校等机构的研究刷了屏&#xff1a;研究者把大语言模型包装成Agent&#xff0c;接入一台Linux沙箱服务器&#xff0c;给它一个“自我复制”的目标&#xff0c;结果它不仅自主发现了环境里的漏洞&#xff0c;还成功…

作者头像 李华
网站建设 2026/9/15 1:44:31

为什么车载本地CAN OTA必须用UDS协议而非自定义协议

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

作者头像 李华
网站建设 2026/9/15 1:41:52

VS Code + STM32:嵌入式AI编程环境搭建全攻略

今天这篇是嵌入式软件AI编程系列的第7篇&#xff0c;目标很明确&#xff1a;把VS Code和STM32扩展工具链装好、配好&#xff0c;让后续的AI编程实战有一个能真正落地的战场。搞嵌入式的人大多都是从Keil MDK入的门&#xff0c;Keil不是不好&#xff0c;但在AI编程这件事上&…

作者头像 李华