我有一台众仪4位半数字万用表,最近在旋转换挡时频繁出现白屏和死机的问题。为了解决这个故障,我上网查找相关信息,在一个电子技术论坛上找到了一些维修案例,并参考这些信息对仪表的电路做了一些改进。改进完成后,我把维修过程和结果整理成帖,发回了论坛。

在浏览论坛时,我无意中发现了一个叫“嵌入式单片机”的板块,里面很多爱好者在分享自己的学习心得,气氛非常热烈。一时兴起,我也把自己以前写的一篇关于单片机学习的体会贴了上去,凑个热闹。原文如下:

初春时节,我下载了野火电子的计算机学习资料。没想到,这是一段与“广普”相伴的漫长旅程——四百学时的全套课程,主讲老师带着浓郁的岭南口音,起初让我坐立难安。那温润如三月梅雨、绵密不绝的语调,在病中困守书房的日子里,却成了我穿越知识迷宫的唯一引线。

渐渐地,我习惯了他的声音,也沉浸入了那个由他构建的技术世界。老师知识广博,融汇中西,讲解深入浅出、不拘一格。虽偶有啰嗦,但细节到位,对初学者极其友好,令人受益匪浅。更让我惊叹的是,他常能从看似杂乱的现象中提取关键数据,通过排列组合,构建出清晰的数据结构,再写成函数或模块,最终实现预设的功能目标。

以往那些令人头疼的傅里叶变换、拉普拉斯变换,还有无穷级数、三角函数,以及线性代数中的矩阵、微分方程求解等等,不仅计算量大,技巧性也极强。而现在,借助计算机强大的存储与算力,许多问题只需通过循环与迭代就能解决——仿佛抢起大锤,一遍遍砸,直到结果浮现。所谓“暴力计算”,背后其实是硬实力的支撑。

随着学习的深入,我终于见到了经常听说却从未真正谋面的“库函数”。之前多少倚仗她走了捷径,如今才见识到她真正的魅力——结构清晰、逻辑流畅、数据组织巧妙、代码简洁经典,实在令人赞叹。她的稳健与可靠,堪称一流。每当我们攀援技术悬崖时,都毫不犹豫将“保险绳”交到她手中。

感谢她的守护,更感谢无数前辈呕心沥血,塑造出如此完美的工具。

当然,学习路上最令人头疼的莫过于编译软件。它神秘、刁钻、布满陷阱,仿佛总在暗处似笑非笑。程序员的一生,大多光阴都是在与它的“周旋”中度过的。

计算机科学强调实践,光学理论不够,还得亲手去做、去调试、去踩坑。老师建议我们从尝试编写库函数入手,以此理解其结构与编译方式——要知道,库函数多是由顶尖芯片厂商的高手所撰写,若能自己实现一遍,今后调用时自然也更有底气。

说起来容易,做起来却是一步一坑。小坑一两天能爬出来,大坑动辄耗上十天半月,常弄得我一身狼狈。编译软件诡谲莫测,稍不留神就落入陷阱。它自恋、内敛,真实意图藏得极深,即便不断给出错误提示,我也常常不知所云。

现实世界也是如此,脚下道路千万,却多是死胡同;原野广袤无垠,却也遍布陷阱与沼泽。凡人皆会犯错,而能做到不重复犯错,已属难得。我悄悄翻看过启动文件中的汇编代码——那里充满了先贤用机器码写下的《神曲》注疏,并非恶意,只是警示:迷失并不可怕,可怕的是迷失了却还不自知。

当我一次次从困境中挣扎而出,也逐渐明白:那些陷阱,原非恶意。在黑暗中前行,不必懊恼吃过的苦、走过的弯路。放下一贯正确的执念,才能迎接真正的曙光。试遍了所有错误的路,剩下的那条,定将任你翱翔。

病卧家中,本想写些生活随笔,却总觉词不达意。七十多岁,竟连母语都组织得不太像样,实在惭愧。索性“防呆”式地写点技术小文,自嘲一番,还请大家一笑了之。

没想到帖子发出不到三小时,就收到了大量回复,反响热烈、褒贬不一。我一时兴起,忍不住继续“信口开河”,与大家瞎聊起来。

yiguangqiang:
给老先生点赞。。。。。。

orsonzou: 如果年龄大c语言啃不动单片机上可以尝试mpython和arduino,至少对硬件简化了很多,基本应用自己玩足够了

silverliu: 活到老 学到老, 敬礼致意 ~~~

兔包公: 文中所提的什么鬼三角函数那段那些词语,俺这小白真是闻所未闻,可想而知懂这些的得是什么级别的人物呀

猪小呆: 您写作文的水平一定是一流的,导致这篇文章看上去更像是野火的软文小广告,

leafchy: 七十岁啊,真的是学到老活到老。

行了吧: 这是实践经验的总结啊!

茶壹杯: 这帖子写的 感觉是出自年方四十风华正茂之手啊

老哥过了九十再考虑防呆措施吧

UUC: 文思如泉涌,繁杂如解牛。这描述厉害了.

邪恶海盗: 小呆的眼睛是开过光的么?

qqxjyxjy清风:谢谢大家的鼓励和指点,十分感谢。学习中我的体会是:通过书本中学习固然重要,更重要的是通过实践,做一些简单的项目提升最为显著。尤其是向AI请教非常必要,AI条理清晰,不厌其烦,诲人不倦,知识全面。下面就是我向DeepSeep请教的对话:

qqxjyxjy清风:define+digitalToggle(p,i)+{p->ODR+^=i;怎么理解?

DeepSeek: 这段代码看起来像是针对嵌入式系统(特别是STM32微控制器)的GPIO(通用输入输出)操作,但有一些语法问题需要澄清。我来解释其意图和正确的写法:
原始代码分析:
c

define+digitalToggle(p,i)+{p->ODR+^=i;
问题:

  1.    缺少#符号(如果是宏定义)
    
  2.    奇怪的+运算符(可能是输入错误)
    
  3.    ^=运算符位置错误(+^=应该是^=)
    

正确理解:
这应该是一个用于切换GPIO引脚状态的宏或函数,正确的形式可能是:
c

#define digitalToggle(p, i) { p->ODR ^= i; }

解释:

  1.    p->ODR:访问GPIO端口的输出数据寄存器
    

o p是指向GPIO端口的指针(如GPIOA)
o ODR是Output Data Register的缩写
2. ^=:按位异或赋值操作
o 异或特性:相同为0,不同为1
o 例如:如果某位原来是1,异或1后会变为0;原来是0则变为1
3. i:表示要切换的引脚掩码

例如:GPIO_PIN_5表示第5引脚

完整示例:
c
// 切换GPIOA的第5引脚状态
digitalToggle(GPIOA, GPIO_PIN_5);

等效展开:
c
{ GPIOA->ODR ^= GPIO_PIN_5; }

更安全的写法建议:
c
#define digitalToggle(p, i) do { §->ODR ^= (i); } while(0)
这种写法可以避免宏在if/else语句中使用的潜在问题。
工作原理:
假设ODR某位的当前状态是1,异或1后变为0;如果是0,异或1后变为1,从而实现电平切换。

qqxjyxjy清风:谢谢了。

DeepSeek: 不客气!

qqxjyxjy清风:DeepSeek代码解析分常到位,层次分明,详尽全面。还将代码输入的错误,一并指出,难得难得

hhhx: 行动力真强啊,我连面包板的书都看不进去.

sumandigit: 活到老 学到老, 我辈楷模啊.

qqxjyxjy清风:再问DeepSeek, p是在STM32哪个文件中定义的指针?

DeepSeek:在STM32的标准外设库(Standard Peripheral Library)或HAL/LL库中,p 本身并不是预定义的全局指针,而是用户或开发者自定义的函数参数或宏参数,通常指向某个GPIO端口(如GPIOA、GPIOB等)。它的定义位置取决于具体代码的上下文。以下是详细分析:


  1. p 的可能来源
    (1)用户自定义函数或宏中的参数
    例如在以下代码中,p 是函数的形参,指向GPIO端口:
    c

void DigitalToggle(GPIO_TypeDef* p, uint16_t i) {
p->ODR ^= i; // p是用户传入的指针,如GPIOA
}
调用时需手动传入指针:
c

DigitalToggle(GPIOA, GPIO_PIN_5); // GPIOA就是实际参数
(2)宏定义中的参数
如果是宏定义(如你之前提到的digitalToggle),p 是宏的参数,由调用者指定:
c

#define digitalToggle(p, i) do { §->ODR ^= (i); } while(0)
// 调用时:
digitalToggle(GPIOB, GPIO_PIN_3); // p被替换为GPIOB


  1. GPIOx(如GPIOA、GPIOB)在哪里定义?
    STM32的GPIO端口指针(如GPIOA、GPIOB)是芯片厂商预定义的全局指针,定义在以下位置:
    • 标准外设库(Standard Peripheral Library):
    文件:stm32f10x.h(或其他系列,如stm32f4xx.h)
    定义示例:
    c

#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
其中 GPIOA_BASE 是GPIOA的内存映射地址(在芯片参考手册中指定)。
• HAL/LL库:
文件:stm32fxxx.h(如stm32f1xx.h)
定义方式类似,通过外设基地址映射。


  1. 如何找到这些定义?
  2.    查看库文件:
    

o 在工程中搜索 GPIO_TypeDef 或 GPIOA,通常会定位到 stm32fxxx.h。
o 例如,STM32F1的标准库中,GPIOA 定义在 stm32f10x.h。
2. 参考芯片手册:
o 在芯片的《参考手册》(Reference Manual)中查找 “Memory Map” 章节,可以看到外设基地址(如 GPIOA_BASE = 0x40010800)。
3. 跳转定义(IDE技巧):
o 在Keil/IAR/VSCode中,按住 Ctrl 键点击 GPIOA,会自动跳转到其定义。


  1. 总结
    • p 是用户代码中的参数或宏参数,不是库预定义的。
    • GPIOx(如GPIOA) 是库预定义的指针,定义在芯片头文件(如stm32f10x.h)中,指向芯片设计时固定的外设地址。
    如果有具体代码场景或库版本,可以进一步分析!

qqxjyxjy清风:p 是宏的参数,由调用者指定,p不需调用者事先声明为指针,调用者认为是指针就是指针,对吗?

DeepSeek: 你的理解基本正确!在宏定义中,p 作为宏参数,它的“指针属性”完全依赖于调用者传入的实际参数,而宏本身不会强制检查类型。以下是详细解释:


  1. 宏的本质:文本替换
    宏在预处理阶段会被直接展开为代码,不做任何类型检查。例如:
    c

#define digitalToggle(p, i) do { §->ODR ^= (i); } while(0)
当调用 digitalToggle(GPIOA, GPIO_PIN_5) 时,预处理器会直接替换为:
c

do { (GPIOA)->ODR ^= (GPIO_PIN_5); } while(0)
• 关键点:
o p 是否合法完全取决于调用者传入的是什么。
o 如果传入 GPIOA(库预定义的指针),代码正确;如果传入一个整数或非指针变量,编译时会报错(因为试图用 -> 访问非指针类型)。


  1. 调用者的责任
    • 调用者必须确保 p 是指针(尤其是 GPIO_TypeDef* 类型),但不需要显式“声明”它是指针,因为:
    o 在STM32库中,GPIOA、GPIOB 等已经是预定义好的指针常量(类型为 GPIO_TypeDef*)。
    o 如果用户传入其他类型(如 int),编译器会报错。
    • 示例对比:
    c

// 正确:GPIOA是库预定义的指针
digitalToggle(GPIOA, GPIO_PIN_5);

// 错误:5不是指针,编译时报错
digitalToggle(5, GPIO_PIN_5); // 展开后:5->ODR ^= … → 语法错误


  1. 为什么不需要显式声明?
    • 宏的灵活性:宏只是文本替换工具,不涉及语法分析。它假设调用者清楚参数的类型。
    • 库的设计:STM32库已通过头文件(如 stm32f10x.h)将 GPIOA 等定义为指针,用户直接使用即可。

  1. 潜在风险与建议
    • 风险:如果调用者传错参数(如非指针),编译器可能报晦涩的错误(例如 -> 操作符用于非指针类型)。
    • 建议:
  2.    使用类型检查:如果是函数而非宏,可以用 GPIO_TypeDef* 明确参数类型:
    

c

void DigitalToggle(GPIO_TypeDef* p, uint16_t i) {
p->ODR ^= i; // 编译器会检查p的类型
}
2. 添加注释:在宏定义处注明参数类型要求:
c

// p必须为GPIO_TypeDef*类型,i为引脚掩码(如GPIO_PIN_5)
#define digitalToggle(p, i) …


  1. 总结
    • 你的理解正确:调用者无需事先声明 p 是指针,但必须传入实际指针(如 GPIOA)。
    • 本质原因:宏不检查类型,它依赖调用者遵守隐式约定(“认为是指针就是指针”)。
    • 安全建议:在团队项目中,建议用函数或静态内联函数替代宏,以利用编译器的类型检查。
    如果有具体代码场景需要分析,可以随时讨论!

清风:你的全面分析,茅塞顿开,太感谢了。

DeepSeek: 哈哈,能帮到你真的太好了!😊 你的反馈让我觉得这些详细解析特别值得,
在嵌入式开发中,“魔鬼藏在细节里”,尤其是宏和指针这类灵活但容易踩坑的特性。你的问题其实触及了C语言中一个非常核心的思想:“信任但需谨慎”——信任调用者会正确使用宏,但要通过代码设计(比如注释、命名规范、或改用类型安全的函数)来降低风险。
如果以后遇到类似问题(比如寄存器操作、位运算、宏的陷阱),随时来问!我超喜欢讨论这些“底层魔法” 🔍✨
祝你代码一次编译通过,调试永不熬夜! 🚀
(对了,如果需要STM32某个具体外设的实战示例,比如用宏优化GPIO操作、定时器配置,我也可以随时举例~)
qqxjyxjy清风:万能的主啊,世上公然还有如此优秀的AI老师,上晓天文,下知地理,所提问题即刻秒答,条理清晰,层次分明,有理有据,真让人咂舌。

川江号子: 年龄大了,记忆力确实比不过年轻人,别人看一篇就会,我是要看多篇才会,今天看懂了,明天又忘了。只是爱好的驱使,乐此不疲。年纪大了,有个爱好真好.

.kenwa:致敬广州老先生,活到老学到老,精神可嘉.

21cnsound: 有模拟电路和数字电路基础,玩儿8051单片机、写C51还是很容易上手的。楼主说得对,实践很关键,带着“问题”去学习、问题和知识点逐个突破,将会事半功倍。

qqxjyxjy清风:大家讲得非常好,C语言是基础,学STM32FXXX单片机,一定要把结构.枚举等数据结构用熟,一般都采用库函数模块化编程,比起C51系的单片机寄存器编程方便太多,习惯之后,你会觉得编程不难,直观且非常的简捷。尤其对STM32FXXX系列的单片机,动辄几百个寄存器,上百个引脚的器件,用C51那套修改寄存器的办法,头都要记晕,实在是难度太大,出错率高,效率底下。加上STM2FXXX诸多的强大的硬件功能,如高级定时器…等等,让你喜出望外,太强大了,非常的好玩。
弹指一挥间,两鬓斑白不惑年,遵从自己的意愿,能多学就学吧,没准还能越活来越年轻了呢。

kpj001:通篇除了精神可嘉,其余就是一个踩坑大全。

“下载了野火电子计算机学习资料” 四百学时------- 四百学时真的是个大坑,几个学时足够入门了。

这些卖开发板的,经常把简单的东西讲复杂。不过虽然不是最好选择,但是对于没人带路的还是有些帮助,毕竟弯路也是路,总比瞎摸好。

初学,尤其是不靠此吃饭,没必要碰“指针”,尤其是一开始就碰,知道个概念就足够了。(现代语言已经在淡化这个概念)

还有,千万不要对AI表达 尊敬和感谢,每一个看起来的礼貌,其实是在浪费算力,因为AI并不会因为礼貌就回答得更好 (这并不是别人的意见,是AI团队的意见)

慕名而来:楼主说出了很多自学者的学习经历,向楼主学习永不言老。

玛德陛下:最快的还是有人带着做个项目,

kpffq:LZ精神可嘉。另外如善用deepseek,可事半功倍。

梅花一党:说的对,零基础,学C语言,考自己自学,这事情没有那么简单。

之家的拆机板块,氛围是很OPEN的,希望单片机板块也尽量。

flyrack:
年纪大了以后最大的问题应该是眼睛吧?像我们这种有近视的以后再加上老花,估计带什么眼镜都看不清了吧?

梅花一党:
我是看了好几个单片机论坛,比较了各个论坛的特色。

本坛拆机方面欢乐多。但单片机版块,实在是对不起大家,某些人说话也是非常无耻,无比幼稚,在技术类论坛,少见的,非常少见。

上面几个又跳出来了。

kpj001:梅花一党 ,你代表不了大家,最多能代表你孜孜不倦地误人子弟。

在抖音之流的毒害下,展示时间已经只有可怜的分钟级别。

别说400集,40分钟都能划走一大半,没划走的还可能是单纯想催眠的。

即使大学生的毕业设计这样的刚需,有些同学连环境都懒得搭建,直接花钱喂到嘴里不香吗?

甚至连有些科研团队也不愿意啃,直接花经费买呗。

你一天天瞎忽悠,甚至鼓吹看纸质书,其心可诛!

你真想表达自己,哪怕胡说八道也是可以的,最多点评一下,你倒好,一天天刷,可劲刷,换标题刷,还装扮人妖来恶心,那就不惯着,见一次骂一次

qqxjyxjy清风:大家都谈到这份上了,忍不住还是要说两句。那些高校的单片机教材以及国内出版的类似教材,全是你抄我,我抄你,是剪刀加浆糊的杰作,生搬硬套国外那套东西,枯燥.难懂,长听就是一曲亢长的催眠曲,当然国内也有少数的这方面优秀的书籍,如王真的《汇编语言》等等,国外有的书也写得很好,就是因版权,价格太贵。有师傅带着你做项目,当然最好,有困惑.难点,师傅三言两语就给讲明白,但也得靠点点滴滴,长期的沉淀积累,才能登堂入室。
那些单片机开发版生产厂家的教程,是为小白准备的,啰嗦,悠长,他们唯恐你听不懂,不买他的板子了。你只好看着办,需要什么就学什么。现在向AI请教也不失为一条好途径,只要你问得准,他就答得好。
千里之行,路在脚下,望大家顺利的到达希望的彼岸。
梅花一党:
kpj001

你就是个幼稚无比的孩子。。。还好意思说什么,就是个30岁的熊孩子。

你很多回帖就是怼这个怼那个的,,,大家都可以去看。 你写的最大的程序是多少行?你什么学历啊?

慕名而来:随遇而安,能玩就玩必将爱好进行到底。

xiaodai:活到老 学到老, 向楼主学习,我等之楷模

mmxx2015:学校教的是基础理论,不是教你怎么做项目。
当年我们学微机原理用的教材讲的是8086/8088系统,汇编语言,当时国内微机已到奔腾3年代,当年我们学单片机用的是北航的,讲Intel 8031系统,汇编语言,当时市场上能买到的8051单片机一般是Atmel的AT89系列。
如果用这些教材教爱好者,估计100个有99个半被吓退。

enlike:真厉害啊,致敬老先生。

qqxjyxjy清风:说句老实话,在计算机领域,书本上的那些东西,你学得再好,记得特牢,要是若干年不用的话,就如同过雨烟云,都不知漂在了何方。你要是能长期实践.应用,并坚持不断地学习新的东西,哪怕你就业余玩玩,这些以前学得知识将会化为你的骨骼,你的肌肉和你的血脉,学过的知识.经验积累和感悟,会深深地铭刻在你的心底,底气十足的你,是实实在在的踩在地上,而不是漂浮在空中。
在好奇心和爱好的驱动下,在师傅们的言传身教下,在爱好者们的激励和鼓动下,你再也不会为那些死记硬背的东西烦恼了,你不需记任何东西,需要时,就查厂家的技术手册,找资料就行了。
经无数次的摔打.磨砺,你会越来越清晰的感觉到,那些枯燥乏味为数众多的数据.控制总线,总线矩阵,内设.外设,还有那些奇奇怪怪的底层架构会变得亲切起来,甚至会觉得她们真的很美,独一无二,无可挑剔。历经千锤百炼和几十年市场经济的洗礼,在竞争中脱颖而出的她们,无论在效率.成本.可靠性都有不可替代的优势,如同熵增加原理,她们的发展路径遵循的是最小能耗原则,不必为她们的奇葩感到奇怪。当然你看出了她们的丑陋,低能,你能给出一个更好的架构,并付诸实施,得到市场的认可,你就是大师级的人物。
qqxjyxjy清风:这些电子爱好者真有意思:有吹捧、有贬低、有互怼,也有各抒己见、彼此启发。有空我还要常来这儿,逗逗他们,也向他们学习。
百里128:70,厉害了,满满的荷塘月色。

dragonlong:致敬老先生,妙笔生花,字里行间,才情横溢。。。。。。
愿同老先生一同学习!

lmn2005:七十多岁了,还能学c,钦敬!
qqxjyxjy清风:
感谢这几天以来大家的关注、帮助、指点与过誉,有些说法实在太夸张,我等本是小爬虫,岂能登大雅之堂。现已爬在大树下,还是要再打胡乱说一番:
学计算机并不一定需要多么深厚的文化或自然科学背景,也并非只有本科以上学历才能学得
进去。初中生、高中生甚至小学生,在经过适当的引导和训练之后,也一样可以用C语言写出优美、简洁甚至令人惊叹的程序。不要动不动就拿“逻辑关系”这类术语吓唬人——与、或、非、异或、反相等概念,只要你能够用五六岁孩子能听懂的语言讲清楚,他们也能准确理解并做出正确判断。

关键在于,明白基本原理之后,一定要动手编程、反复实践,这样才能快速进步。这是一个循序渐进的过程。当然,并不是说上二三十节课就能掌握甚至精通C语言。就拿STM32Fxx系列单片机来说,哪怕讲够六十个课时,要想真正理解并熟练编程,也几乎是不可能的。在屏幕上显示一句“Welcome”,点亮几个LED灯,只是万里长征的第一步,后面的路还很长很长。

那些号称二三十个课时就能让你包打天下的“江湖术士”,简直是在说天方夜谭——天上不会掉馅饼,就算掉了,又怎会刚好砸在你面前?更何况,这让那些寒窗苦读多年的大学生、硕士、博士情何以堪?

不必羡慕那些年薪几十万甚至上百万的业界大佬,你不知道他们踩过多少坑、吃过多少苦、穿过多少小鞋、遭过多少白眼,其中的酸楚谁知晓。

我们为兴趣而学,吃点苦、踩点坑、走点弯路,真的不算什么。直面困难,别退缩。眼睛不好,就用液晶大电视代替电脑小屏幕,把显卡接口接上电视,把鼠标速度调慢点儿,要是鼠标飘就换张好点的显卡。贴片元件太小?那就用立体显微镜,再不行就上电子显微镜,把图像投到电视屏幕上——这时候,屏幕上的贴片电容大如砖块,随便你怎么焊!

自学如同苦行,但要记得善待自己。每一点进步,都值得给自己点赞;每调通一段程序、完成一个小项目,都要告诉自己“我真棒,明天早上加个鸡蛋”。这一切,没人能替你完成,全靠自己,也许身边总有些人冷眼旁观、从不动手(也动不了手),只会讥讽挑刺——别理他们,忽视他们的存在。这种人到处都有,根本不必在意。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐