
1. 为什么选这颗8脚芯片PIC12F675的资源边界与适用场景第一次拿到PIC12F675的人第一反应基本都是这芯片也太小了。8个引脚、1K×14位的Flash、64字节RAM放在今天的MCU市场里简直像上个世纪的产物。但我做小家电项目这些年身边工具盒里常备的反而就是这样的小芯片——不是因为情怀而是因为它真的能解决一批用STM32太浪费、用51又没优势的问题。PIC12F675是Microchip PIC12F系列里的经典型号DIP8或者SOP8封装单颗芯片价格很低外围元件少到可以只放一个退耦电容就工作。它的内部资源大概是这样资源参数用于什么Flash程序存储器1K × 14位存程序注意是14位宽指令字不是普通8位字节RAM64字节变量、局部数组空间非常紧张数据EEPROM128字节存校准参数、掉电保存数据I/O引脚6个GP0~GP5其中GP3只能作为输入定时器Timer0 8位、Timer1 16位延时、计数、外部脉冲测量ADC10位多通道其中GP0~GP2可以作为模拟输入模拟比较器有以后你会发现它是个坑后面细讲内部振荡器4MHz出厂校准省掉外部晶振硬件堆栈8层这是决定你C语言写法上限的关键我通常把这类小芯片用在三类场景。第一类是纯逻辑控制比如带按键和LED的小面板、继电器延时控制、门禁状态指示不需要通信协议一根GPIO加一个三极管就能驱动负载。第二类是简单采集用内部ADC读一路电压或者NTC温度结果存到EEPROM里比如电池电压监测。第三类是个别量产产品的辅助板主控忙不过来时用一颗PIC12F675在旁边处理按键扫描和指示灯闪烁成本压得很低。但也有明显的边界。它没有硬件UART、I2C、SPI想跟别的设备通信必须用GPIO模拟波特率不能太高时序也要自己抠。64字节RAM意味着任何超过十六进制级别的缓冲区都要慎重动不动定义一个大数组的人编译能过上电就飞。1K×14位的Flash对C语言来说也不是特别宽裕编译完经常只剩两三百个字的余量优化时要学会跟编译器讲价。如果你是第一次接触PIC单片机拿这颗芯片入门其实比一上来就学PIC18或者PIC32要舒服得多。寄存器就那么几个数据手册单章就能覆盖几乎可以用裸奔的方式搞清楚单片机内部每个模块到底在干什么。这和那些什么外设都集成好的大芯片完全是两种学习曲线。但对应地它也会逼着你用最土的办法做事情而这些土办法恰恰是嵌入式基本功的组成部分。2. MPLAB X IDE XC8 PICkit3先把三件套装明白再写代码网上不少旧教程还在用MPLAB 8.x界面和现在的MPLAB X IDE差别很大照着找菜单会怀疑人生。开发PIC12F675这套流程当前用得最多的组合就是MPLAB X IDE当开发环境、XC8编译器负责把C语言变成Hex、PICkit3通过ICSP接口烧录和调试。这三样东西虽然都被Microchip包办了但它们其实是三个独立部件。MPLAB X IDE本身只是一个壳不装编译器的话你连C语言都编译不了。XC8是8位PIC的官方C编译器PIC10/PIC12/PIC16/PIC18都用它。注意XC8的免费版本对优化等级有限制只能开0级或者1级优化2级以上的优化属于付费专业版功能。实际操作中对PIC12F675这种小容量芯片把优化等级设为1就够了如果代码还是塞不下优先去改代码逻辑而不是指望编译器帮你变魔术。PICkit3则是一个烧录器加调试器通过ICSP这组串行协议跟芯片通信。它已经是停产老产品了现在官方主推PICkit4/5但PICkit3存量很大二手价格便宜网上资料也多很多初学者手里都在用。MPLAB X IDE对PICkit3的驱动支持一直保留着新版本依然能正常烧PIC12F675但如果系统换了新电脑第一次插上PICkit3没反应先去设备管理器里确认驱动有没有装对。安装顺序上我的习惯是先装MPLAB X IDE再装XC8编译器。两者都从Microchip官网下安装路径不要带中文。装完打开IDE第一次新建工程时会提示下载Device Pack——这是对应芯片的设备支持包必须联网下载。如果公司网络或者实验室网络限制比较多可能这块就会卡住建议提前准备好代理或者直接用手机热点。一个容易被忽略的细节MPLAB X IDE基于NetBeans界面默认偏亮色字体也小。用之前先去Tools → Options里把字体调大、主题调成顺眼的因为接下来你要在这个编辑器里反复折腾。虽然也能外接VS Code写代码再拿过来编译但既然走MPLAB X这套全流程没必要给自己增加变量。3. 新建工程三分钟建好十分钟被配置位教做人3.1 New Project向导里的每一步都别跳在MPLAB X IDE中新建工程路径是File → New Project选择Microchip Embedded → Standalone Project。接下来向导会问三件事用哪颗芯片、用什么编程器、用什么编译器。芯片选PIC12F675工具选PICkit3编译器选XC8然后一路Finish。工程建好后左侧Projects窗口会自动生成一堆文件夹比如Source Files、Header Files、Libraries、Important Files。很多第一次用的人会困惑我新建的.c文件该放哪里直接在Source Files文件夹上右键 → New → C Source File名字随便写比如main.c。注意文件名不要用中文MPLAB X对中文路径和中文文件名的支持时好时坏没必要冒这个险。右击工程名 → Properties能看到当前工程的三要素Device、PICkit3、XC8。这里有个坑如果装了多个版本的XC8下拉框里会列出好几个一定要选和你安装匹配的那个。选错的话点击编译直接报Compiler not found或者版本不兼容。3.2 Configuration Bits不是默认值就能用这个环节是PIC单片机和STM32、51最不一样的地方也是新手最容易被劝退的环节。所谓配置位Configuration Bits是在程序烧录前就要写入芯片配置字的一种设置它决定芯片以什么方式运行。51单片机装个晶振、接个复位电路就能跑PIC虽然也能这样做但要不要用外部晶振、要不要启用看门狗、MCLR引脚当复位还是当GPIO这些全部由配置位决定。最早老工程师喜欢在代码开头用__CONFIG()宏写一长串与或表达式现在XC8更推荐用#pragma config一行一个选项直接写。下面是我在自己项目里一直用的开头#pragma config FOSC INTRC_NOCLKOUT #pragma config WDTE OFF #pragma config PWRTE OFF #pragma config MCLRE ON #pragma config CP OFF #pragma config CPD OFF逐项拆开来说。FOSC是振荡器选择开发PIC12F675时大多数情况用内部4MHz振荡器选INTRC_NOCLKOUT意思是用内部振荡器并且不把时钟信号映射到引脚上这样GP5还能省下来当普通I/O用。WDTE是看门狗定时器开发调试阶段务必设为OFF不然程序跑着跑着看门狗没喂狗芯片就自动复位一次你会误以为是程序逻辑有Bug。PWRTE如果设为ON芯片上电后会额外延时一段时间再启动适合电源缓慢爬升的场合普通板子设OFF就行。MCLRE这个选项很关键。PIC12F675的4号引脚要么是复位引脚MCLR要么是普通输入GP3二选一就是靠这个配置位。定成ON就是复位引脚接上拉电阻和复位按键定成OFF它就是GP3输入。看起来自由度挺高但我要提醒你MCLRE设成OFF之后ICSP编程时MCLR引脚就不再承担编程电压输入的角色这种情况下能不能顺利进入编程模式不同批次芯片和不同烧录器表现并不完全一致。所以调试阶段建议老老实实保持MCLRE ON等所有功能都验证完了假如真缺一个输入引脚再把它改成GP3试一把。CP和CPD是代码保护和EEPROM保护量产时才考虑打开开发阶段设OFF否则以后想读回芯片内容根本读不出来。MPLAB X里还提供了一个可视化配置窗口Window → PIC Memory Views → Configuration Bits。打开后能看到完整的寄存器位图每一项都有下拉框鼠标点选后能自动生成对应的#pragma config代码。我建议新手先在这个窗口里点一遍看看每个选项的文字说明再用Generate Source Code to Output把生成结果粘到工程里这样比硬背编译器选项要直观得多。3.3 内部振荡器校准值0x3FF删不得这个坑我踩过而且踩得刻骨铭心。PIC12F675内部4MHz振荡器出厂时是校准过的校准值放在Flash的最后一个地址0x3FF。烧录工具在编程时默认会擦除整个Flash当擦除动作执行后如果这个校准值也跟着没了芯片的时钟频率就会偏轻则串口波特率不准重则内部ADC采样时序直接乱掉。你从MPLAB X里对芯片执行Erase命令时这个地址通常会被一并擦掉而编译器生成的Hex文件根本不会包含这个地址的内容因为你的程序没用到它。所以烧录设置里必须主动保护。具体操作右击工程 → Properties → Categories里选PICkit3 → 在Memory/Program Options里找到Preserve Memory / Preserve Program Memory之类的选项把保留区域设为0x3FF到0x3FF。不同IDE版本菜单名字略有差异关键思路就是告诉烧录器这个地址是老子的禁区擦除和编程时都不要碰。万一真把校准值搞丢了怎么办如果你的项目里用了内部振荡器最直接的挽救办法是用逻辑分析仪或者示波器测MCLR之外的某个翻转引脚对比实际频率偏差然后在软件里加一个频率补偿系数但这就麻烦多了。所以那句0x3FF删不得真不是危言耸听。4. 六个引脚能折腾什么寄存器视角下的点灯与按键4.1 认识TRISIO和GPIO再顺手关掉两个默认恶魔PIC12F675和51单片机的端口操作看起来很像但寄存器名字完全不一样。51叫P0、P1口PIC叫GPIO寄存器51用P1M0/P1M1控制方向PIC用TRISIO寄存器。TRISIO里的每一位对应一个引脚的方向1是输入0是输出上电复位后默认全部是1也就是所有引脚都是高阻输入状态。GPIO寄存器则是你的数据口向某一位写1对应引脚输出高电平写0输出低电平。读按键时读GPIO对应位看它是0还是1。这个对应关系搞清楚了点灯就成功了一半。我在代码里通常直接用GPIObits.GP0这种方式去操作单个位可读性强不容易把整个GPIO寄存器误写。比如GPIObits.GP0 1; // 让GP0输出高电平接下来要提两个默认恶魔新手第一次写PIC12F675几乎都会栽在它们身上。一个是ANSEL寄存器负责选择引脚是数字功能还是模拟功能。芯片上电后有些引脚默认被配置为模拟输入如果你把GP0接了一个LED想让它闪烁但没把ANSEL清掉GP0根本不会正常输出数字电平。解决办法很简单初始化时写一句ANSEL 0x00; // 所有引脚都设为数字功能另一个是CMCON寄存器控制芯片内部模拟比较器。PIC12F675上电后比较器默认是开启的而比较器的输出可能会锁住GP0、GP1、GP2这几个引脚的数字输入输出能力。这就是为什么有人照着教程抄完代码GP2读按键怎么读都是高电平死活读不到按下最后发现是该关的比较器没关。常规操作是在初始化最前面加上CMCON 0x07; // 关闭比较器恢复GP0/GP1/GP2的数字功能对于用按键的板子WPU寄存器还有用它给每个GPIO提供内部弱上拉。比如GP2接了一个按键一端接地按下之后GP2被拉低平时没人按的时候GP2必须保持高电平。如果板子上没焊外部上拉电阻就用内部弱上拉顶上WPUbits.WPU2 1; // GP2内部弱上拉弱上拉电流很小大概几十微安级别正常工作没问题但别指望它能驱动LED。它本质上是给按键、拨码开关这类开漏型输入场景用的。4.2 可复制的Demo上电闪四遍按键控灯废话不多说直接给一个可以烧进PIC12F675完整跑起来的例子。硬件接法GP0接一个LED中间串联一个330Ω电阻到地GP2接一个按键按键另一端接地。板子上电后LED先闪4次代表程序正常运行之后按一下按键LED亮松开LED灭。#define _XTAL_FREQ 4000000UL #include xc.h #pragma config FOSC INTRC_NOCLKOUT #pragma config WDTE OFF #pragma config PWRTE OFF #pragma config MCLRE ON #pragma config CP OFF #pragma config CPD OFF void main(void) { ANSEL 0x00; // 先关模拟功能避免GP0/GP2不能正常数字工作 CMCON 0x07; // 再关比较器恢复引脚数字功能 TRISIO 0b00000100; // GP0输出GP2输入其余引脚保持输入 GPIO 0x00; // 初始所有输出低电平 WPUbits.WPU2 1; // 按键用内部弱上拉 for (unsigned char i 0; i 4; i) { GPIObits.GP0 1; __delay_ms(150); GPIObits.GP0 0; __delay_ms(150); } while (1) { if (GPIObits.GP2 0) { // 按键按下GP2被拉低 GPIObits.GP0 1; } else { GPIObits.GP0 0; } } }这里有个寄存器位操作细节要说明TRISIO 0b00000100这行是把GP2对应的那一位写成1确认它处于输入状态。GP3因为硬件上只能输入无论你把它设成1还是0它都当输入用所以不要试图让GP3去驱动LED或继电器数据手册明确写它没有输出驱动器这是PIC12F675引脚资源里唯一的半个残疾引脚。__delay_ms(150)是XC8编译器提供的延时函数但它依赖_XTAL_FREQ这个宏定义必须提前写在文件最上方。如果忘记写#define _XTAL_FREQ 4000000UL编译会直接报错。这里4000000就是内部4MHz时钟对应的数值XC8会根据这个值帮你算延时的循环次数。5. 8层硬件堆栈PIC的C语言为什么不能乱递归有个问题经常在技术论坛看到单片机的C语言没有堆栈吗问的人多半是学PC编程出身习惯了函数随便递归、局部变量随便开。对PIC12F675这颗芯片来说答案是它真有堆栈但只有8层而且是纯硬件堆栈不占RAM地址空间。硬件堆栈是什么概念每次调用函数、发生中断时当前程序的返回地址会被压入这个8层深的硬件栈里返回时再弹出。普通51单片机也有类似的硬件堆栈设计但嵌套深度同样有限。PIC12F675的问题在于这8层是全芯片唯一的调用轨迹记录场所C语言编译器没法把这个栈扩展成软件栈因为根本没有多余的RAM和时间来做这件事。这意味着什么呢如果你在main函数里调用了func1func1里又调用了func2func2里又调用了func3这个调用链一共3层如果此时突然来一个中断中断服务函数里再调用一个函数这又是2层。加起来就是5层了。假如哪个函数里再嵌套个两层8层很快就满了一旦压栈超过8层最底层的返回地址就会被挤掉程序跳飞到完全未知的地址上。表现形式极其诡异有时候是程序卡死有时候是LED乱闪有时候是看门狗复位后重新跑。所以用PIC12F675写C语言要遵守几条守则。第一尽量不要写递归哪怕你觉得这个递归只有三四层。第二中断服务函数里尽量不要调用普通函数尤其不要调用那些又长又复杂的函数尽量只做置标志位、清中断标志这类轻活。第三编译器对栈溢出的检测基本帮不上忙因为它没法静态分析所有可能的调用路径和中断嵌套顺序别指望它给你报warning。再给一个教科书级的中断使用示例用Timer0实现一个心跳Tick每约20ms进入一次中断全局变量tick加一主循环根据tick的值去做延时而不是用阻塞式__delay_ms()。这在按键消抖、LED呼吸灯、状态机切换里非常实用。#define _XTAL_FREQ 4000000UL #include xc.h #pragma config FOSC INTRC_NOCLKOUT #pragma config WDTE OFF #pragma config PWRTE OFF #pragma config MCLRE ON #pragma config CP OFF #pragma config CPD OFF volatile unsigned int tick 0; void __interrupt() isr(void) { if (INTCONbits.T0IF) { INTCONbits.T0IF 0; // 清零中断标志 tick; } } void main(void) { ANSEL 0x00; CMCON 0x07; TRISIO 0b00000100; GPIO 0x00; WPUbits.WPU2 1; OPTION_REG 0b00000111; // Timer0使用内部指令周期时钟预分频128 TMR0 100; // 初始值100配合预分频得到约20ms溢出 INTCONbits.T0IE 1; // 使能Timer0溢出中断 INTCONbits.GIE 1; // 打开全局中断 while (1) { if (tick 50) { // 大约1秒 tick 0; GPIObits.GP0 ~GPIObits.GP0; // 翻转LED } } }这段代码里OPTION_REG设置为0b00000111bit5的T0CS0代表Timer0的时钟源是内部指令周期4MHz分频后是1MHzbit3的PSA0表示预分频器分配给Timer0bit2~bit0的111表示预分频128倍。于是Timer0每计数一次是1÷1MHz×128128μs从100计数到256溢出需要156次总周期约20ms。中断里什么都不做只清标志和给tick加一主循环检测到50次后再翻转LED。50×20ms正好1秒呼吸节奏很稳定。这里还有一个极其班主任的提醒PIC的中断标志位必须软件清零硬件不会帮你撤销。如果你忘了清T0IF中断会不停地被触发程序跟死循环一样卡在ISR里。这种低级的坑几乎每个人都踩过。6. PICkit3烧录全流程接线、设置与常见烧不进去6.1 ICSP接线PGD和PGC别接反工程编译通过后开始进入烧录环节。PICkit3到目标板的接线方式属于ICSP协议PIC12F675一共8个引脚但烧录时只需要接5根线。我整理了一张对照表你照着接就行PICkit3接口对应PIC12F675引脚说明Pin 1 (MCLR/VPP)Pin 4 (MCLR/VPP)编程电压/复位控制Pin 2 (VDD)Pin 1 (VDD)目标板电源Pin 3 (GND)Pin 8 (VSS)共地Pin 4 (PGD)Pin 6 (GP0/ICSPDAT)数据线Pin 5 (PGC)Pin 7 (GP1/ICSPCLK)时钟线最容易出错的就是PGD和PGC接反。PIC12F675的ICSPDAT在GP0ICSPCLK在GP1如果你把顺序搞反MPLAB X会报Invalid target device id。我见过好几个人拿着杜邦线互插了半天以为数据手册写错了其实只是两个头交换一下的事。接好线后在MPLAB X工具栏上找那个Make and Program Device Main Project按钮图标是个绿色箭头加扳手。点下去输出窗口会滚动显示Reading device...、Programming...、Verify...。看到Programming/Verify complete烧录就算成功了。6.2 供电模式的选择PICkit3本身能向目标板供电通常支持3.3V和5V两档但官方给的电流上限并不高大约只有几十到几百毫安级别。给一个纯LED、按键、单片机的板子供电勉强够用如果板子上接了蜂鸣器、继电器、舵机、LCD屏这类功耗大户PICkit3的电源会直接被拉垮烧录过程中VDD电压掉到芯片正常工作电压以下然后各种稀奇古怪的报错就来了。我的建议是调试板子尽量外接独立电源。PICkit3只负责编程和调试目标板用自己的稳压芯片供电两边只要GND共地就行。如果你确实想让PICkit3供电在工程属性 → PICkit3 → Power标签页里勾选Power target circuit并设置电压值。但如果板子已经有外部电源又开了PICkit3供电两路电源同时作用会有倒灌风险我一般不这么干。6.3 常见烧录失败排查表烧录失败的时候别急着怀疑芯片坏了。按下面这张表排查命中率很高报错/现象最可能的原因处理方式Invalid target device idPGD/PGC接反、杜邦线接触不良、目标板没上电顺一遍接线重新插紧确认VDD/GND电压正常Target device not foundPICkit3的USB驱动异常、USB延长线质量差换USB口、换线设备管理器里删掉设备重新枚举VDD电压异常偏低PICkit3供电能力不足或者板上有大负载断开外设或改用外部电源Programming failed芯片进入了异常状态可能看门狗没关一直复位把WDTE配置位改为OFF重新编译烧录烧录成功但程序不运行配置位振荡器选择错、比较器没关、GPIO配置错检查#pragma config检查ANSEL和CMCON初始化关于0x3FF校准值的保护一定记得在编程设置里勾选Preserve Memory Range把0x3FF这一段单独保住。如果你没设置又对芯片单独执行了Erase操作那校准时值大概率就没了芯片的时钟精度会打折扣。我在这个坑里曾经连续烧废两颗芯片后来养成了一个习惯每次烧录前先看一眼编程设置里保留区间的配置还在不在。7. 没有调试头时怎么调LED、软串口、逻辑分析仪三板斧PICkit3支持在线调试但PIC12F675这颗芯片很特殊——它想用上PICkit3的调试功能需要额外买一个调试头价格比芯片本身贵得多很多淘宝店还不一定有现货。所以绝大多数人手里那套PICkit3 PIC12F675组合实际只能烧录不能像调试STM32那样打断点、看变量的实时值。那程序跑飞了怎么办我总结了三个比较实用的排查手段基本能覆盖日常开发。第一招LED状态码。在启动阶段用GPIO控制LED闪几下代表程序走到了哪一步。比如数组越界前闪2下初始化I2C失败闪3下进入主循环前闪5下。这个做起来成本最低调试完把闪烁代码删掉或者留成正常运行指示都行。第二招软串口。既然芯片没有硬件UART就用GPIO模拟一个发送口按9600波特率往电脑上发日志。9600波特率每位约104微秒PIC12F675内部时钟4MHz用__delay_us()完全能卡出来。下面是一个最简发送骨架#define TX_PIN GPIObits.GP4 #define BIT_DELAY_US 104 // 9600bps每一位的时间 void uart_send_byte(unsigned char c) { TX_PIN 0; // 起始位 __delay_us(BIT_DELAY_US); for (unsigned char i 0; i 8; i) { if (c 0x01) { TX_PIN 1; } else { TX_PIN 0; } __delay_us(BIT_DELAY_US); c 1; } TX_PIN 1; // 停止位 __delay_us(BIT_DELAY_US); }拿到这个字节流之后用USB转TTL模块接GP4电脑串口工具设9600波特率就能看到日志。但注意软串口的时序精度完全依赖内部振荡器准不准如果0x3FF校准值没了9600波特率会偏到串口工具收到一堆乱码——所以前文那个校准值保护真不是小题大做。第三招逻辑分析仪。现在几百块钱的逻辑分析仪不少见一根探针夹在GPIO上就能看到引脚波形。尤其排查GPIO模拟I2C、模拟串口、PWM输出这类时序敏感代码时一把逻辑分析仪比什么调试器都管用。连单片机都不用进入调试模式直接看物理电平就能定位问题。我个人的使用体会是调试8脚小芯片的关键不在于工具多高级而在于你有没有把问题可视化出来的习惯。能点灯就点灯能翻IO就翻IO先把现象暴露出来再一步一步往代码里缩小范围这套方法论在PIC12F675上特别锻炼人。玩熟了以后回头用大芯片写代码你对寄存器、配置位、中断这些底层机制的理解反而比直接上手STM32的人更踏实——至少你不会像当初的我一样以为烧录器点一下程序就跑结果被一颗八脚芯片教了一整天什么叫配置位。