
简介本资源是一套基于AS3933芯片的125kHz低频发射驱动代码面向嵌入式开发工程师及RFID/门禁系统开发者解决MCU通过SPI协议精准控制低频发射模块的核心问题。资源包共45个文件包含6个C源文件含main.c、piezo.c等核心驱动逻辑、9个头文件如platform.h、switch.h等硬件抽象层定义、20个HTML文档提供寄存器说明与配置指南以及Makefile.mk等构建脚本整体压缩后仅96KB轻量易集成。已有704人学习下载适用于动物追踪、无源RFID唤醒、近场身份识别等典型125kHz应用场景。读者可直接获取完整SPI初始化流程、AS3933寄存器映射表、发射启停控制函数及针对PIC24FJ64GB002平台的硬件适配代码尤其适合需要快速验证低频发射功能或调试寄存器配置的中级嵌入式开发者。 搞过主动式RFID或者低频唤醒方案的朋友对AS3933这颗芯片应该不陌生。这几天我重新整理了一个工程内容是AS3933低频唤醒方案的配套125kHz发射代码MCU负责产生125kHz载波按曼彻斯特编码拼接唤醒帧驱动LC谐振天线往外发信号配合另一侧的AS3933接收端完成低频唤醒。这类代码从原理上看不复杂但真跑起来以后载波频率校准、天线谐振点、编码时序、SPI寄存器配置随便哪一项都能让人耗上一整天。我把自己调通的完整过程、硬件参数和踩过的坑整理出来给正在做125kHz低频唤醒项目的朋友一条可以直接参考的路径。1. 别被名字绕晕AS3933是接收器那发射代码到底在干嘛1.1 AS3933在系统里的真实角色不少第一次接触这个工程的人会问AS3933明明是一颗低频唤醒接收器为什么项目名里写着发射代码答案是在这个压缩包里发射端的控制代码和接收端的驱动代码是配套发布的工程名只是笼统地把它称为AS3933低频发射代码。真正负责发射125kHz信号的不是AS3933本身而是MCU加发射天线驱动电路。AS3933的定位是低频唤醒接收器工作频率范围在15kHz到150kHz之间最常用的就是125kHz频段。它的工作方式是这样的天线端接收到125kHz载波信号后芯片内部完成放大、解调、曼彻斯特解码然后把解出来的数据和自己预置的唤醒ID做相关匹配一旦匹配成功就通过IRQ引脚或者SPI寄存器告诉MCU“我被人叫醒了”。整个过程里AS3933始终处在一个听的状态真正开口喊话的是MCU控制的发射链路。所以这套工程的完整逻辑链是MCU输出带编码信息的125kHz调制信号 → 驱动电路放大 → 发射天线发出低频磁场 → 另一侧AS3933接收端的天线感应到信号 → 解调匹配ID → 触发唤醒中断。发射端和接收端必须遵守同一套载波频率和编码格式否则AS3933那边的相关器会直接判定不是我的菜唤醒永远不触发。1.2 一套125kHz低频唤醒系统的四大部分如果把这套系统拆开看大概可以分成四个部分发射控制单元MCU负责产生125kHz载波、按照协议拼接唤醒帧、控制发射时序。发射驱动与天线把MCU输出的3.3V/5V PWM信号转换成足够强的天线电流通过LC谐振天线形成磁场。接收天线与调谐网络AS3933前端的三路天线输入并联谐振电容把空间中的125kHz磁场转化为电压信号。AS3933接收处理单元载波频率校验、增益控制、曼彻斯特解码、唤醒ID匹配、IRQ输出等。这四个部分里接收端的AS3933配置相对固定只要寄存器设置正确链路接通它就能稳定工作。真正决定项目成败的反而是发射端的天线谐振和载波质量。很多项目调不通问题都出在MCU的PWM明明是125kHz但天线端没谐振起来信号辐射不出去。所以我会花较多篇幅讲发射硬件设计这部分比代码本身更容易踩坑。2. 125kHz发射硬件设计天线谐振、驱动方案与波形判断2.1 LC天线参数计算先让天线响起来低频发射天线的本质是一个LC并联谐振回路。在125kHz这个频段线圈电感量通常比较大常见数值在几百微亨到几毫亨之间再并联一个电容让谐振频率落在125kHz上。谐振频率公式是f 1 / (2π√(LC))如果要求 f 125kHz那么 L × C 的乘积就需要落在约 1.62×10⁻⁶ 附近。常见组合我列在下面线圈电感 L谐振电容 C计算谐振频率560μH2.9nF≈125kHz1.0mH1.62nF≈125kHz1.2mH1.35nF≈125kHz2.4mH680pF≈124.7kHz需要提醒的是这个计算是理想电感电容的谐振点。实际线圈还有分布电容和等效串联电阻所以焊好板子后一定要用示波器或者阻抗分析仪实测电容留一对微调位比如标称电容并联一个5~30pF的可调电容会方便很多。2.2 常见驱动电路单管、半桥与专用驱动芯片怎么选MCU引脚直接驱动低频天线是不现实的125kHz谐振天线工作时的电流需要几十到几百毫安MCU GPIO根本扛不住。实际项目里最常见的做法有这么几类方案一单NMOS管驱动LC并联谐振这是最省钱的做法。PWM信号经过栅极电阻接到NMOS的栅极NMOS漏极接LC并联网络上端接VCC源极接地。PWM为高时MOS导通给LC回路储能PWM为低时MOS关断LC自由振荡产生高压脉冲。这种单管拓扑效率偏低漏极上会打出较高的峰峰电压但胜在器件少、调试简单适合低成本的近距离唤醒。方案二半桥驱动LC谐振两个MOSFET组成半桥高边管和低边管互补导通中点通过电容和天线线圈串联谐振。这种方案的发射效率高、谐波含量低天线两端波形更接近标准正弦波。代价是需要一组互补PWM而且必须处理死区时间。125kHz的周期只有8微秒死区时间控制在几百纳秒即可。方案三专用低频驱动芯片比如TC4427这类MOSFET栅极驱动器或者直接选用集成的LF读写器前端芯片。优点是波形干净、保护齐全缺点是成本和物料数量上去了。驱动方案器件数量效率谐波水平调试难度适用场景单NMOSLC并联1个MOS中等较高低低成本、近距离NMOS半桥LC谐振2个MOS高较低中等距离要求较高专用栅极驱动/前端芯片集成方案高低低量产产品2.3 判断天线波形是否合格的三个标准联调时示波器探头直接夹在天线两端我通常按下面三个标准判断发射端硬件是否正常频率准在示波器上数周期一个周期应该是8微秒左右125kHz。如果跑偏就得重新检查PWM参数和谐振电容。波形干净理想情况是正弦波或者接近正弦的波形。如果看到大量毛刺或高频分量说明MOS管的驱动电阻和LC匹配还有调整空间。幅值稳定天线两端电压幅值在每一帧发射期间应该保持稳定不能出现明显的衰减振荡。如果看到一发射就萎掉的波形基本可以判断电源带载能力不足或者LC参数不对。我把这个环节放在最前面是因为后面所有代码调试都要建立在磁场确实发射出来了这个前提上。天线没谐振代码写得再对也没用。3. MCU端发射代码定时器载波、曼彻斯特编码与帧结构3.1 用定时器PWM精确产生125kHz载波125kHz载波可以靠GPIO翻转加延时生成但那样精度差、还占死CPU。正规做法是用定时器的PWM输出。以STM32F103为例系统时钟72MHz要得到125kHz分频后的计数周期数就是72000000 / 125000 576也就是说定时器从0数到575一共576个时钟周期输出一个完整的125kHz周期。代码可以这样写/* 系统时钟72MHzTIM3_CH1输出125kHz PWM */ #define LF_CARRIER_FREQ 125000UL #define LF_ARR_VALUE 575 /* 0~575共576个计数点 */ #define LF_DUTY_VALUE 287 /* 50%占空比 */ void LF_Carrier_Init(void) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tim; TIM_OCInitTypeDef oc; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); gpio.GPIO_Pin GPIO_Pin_6; gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); tim.TIM_Prescaler 0; tim.TIM_CounterMode TIM_CounterMode_Up; tim.TIM_Period LF_ARR_VALUE; tim.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, tim); oc.TIM_OCMode TIM_OCMode_PWM1; oc.TIM_Pulse LF_DUTY_VALUE; oc.TIM_OutputState TIM_OutputState_Enable; TIM_OC1Init(TIM3, oc); TIM_CtrlPWMOutputs(TIM3, ENABLE); TIM_Cmd(TIM3, DISABLE); /* 先不输出载波 */ } void LF_Carrier_On(void) { TIM_Cmd(TIM3, ENABLE); } void LF_Carrier_Off(void) { TIM_Cmd(TIM3, DISABLE); GPIO_ResetBits(GPIOA, GPIO_Pin_6); /* 关断时输出低防止MOS误导通 */ }占空比选50%是比较合理的起点太低会让天线端能量不足太高会增加MOS导通损耗。实际项目里如果用了单管方案可能要小幅调整占空比来兼顾储能效率和波形质量。3.2 曼彻斯特编码和唤醒帧结构AS3933内部相关器支持曼彻斯特编码。曼彻斯特编码的核心特点是每个bit的中间必然有一次电平跳变。这样接收端能直接从跳变位置提取时钟不怕收发两端的时钟漂移。我习惯的约定是bit 1 表示先高后低bit 0 表示先低后高。半位时间需要和AS3933配置的数据速率一致。比如数据速率2kbps一个bit持续500微秒半位就是250微秒。代码实现很简单#define HALF_BIT_US 250 /* 2kbps速率下的半位时间 */ void LF_DelayUs(uint32_t us) { /* 基于定时器的微秒延时或者用SysTick实现 */ } void LF_SendBit(uint8_t bit) { if (bit) { LF_Carrier_On(); LF_DelayUs(HALF_BIT_US); LF_Carrier_Off(); LF_DelayUs(HALF_BIT_US); } else { LF_Carrier_Off(); LF_DelayUs(HALF_BIT_US); LF_Carrier_On(); LF_DelayUs(HALF_BIT_US); } } void LF_SendByte(uint8_t data) { int i; for (i 7; i 0; i--) { LF_SendBit((data i) 0x01); } } void LF_SendFrame(uint8_t *buf, uint8_t len) { uint8_t i; /* 前导载波让AS3933的AGC和频率校准完成锁定经验值大于1ms */ LF_Carrier_On(); delay_ms(2); LF_Carrier_Off(); delay_ms(1); for (i 0; i len; i) { LF_SendByte(buf[i]); } LF_Carrier_Off(); }前导载波那段很重要。AS3933在检测到连续载波后会用载波周期校准内部RC振荡器。如果前导时间太短频率还没锁定就进入数据段后面的解码会出错。实测下来载波前导至少留1毫秒我习惯留2毫秒稳妥。3.3 AS3933接收端的SPI初始化和唤醒配置发射端代码写好了接收端AS3933也需要正确初始化。AS3933的寄存器配置在工程包里通常有一个独立的驱动文件核心流程包括软复位、设置载波频率为125kHz、使能曼彻斯特编码、配置数据速率、设置接收灵敏度、写入唤醒ID pattern、选择输入通道。uint8_t AS3933_WriteReg(uint8_t reg, uint8_t data) { uint8_t status; AS3933_CSN_L(); status SPI_SendByte(reg | 0x80); /* 写命令位 */ SPI_SendByte(data); AS3933_CSN_H(); delay_us(10); return status; } uint8_t AS3933_ReadReg(uint8_t reg) { uint8_t data; AS3933_CSN_L(); SPI_SendByte(reg 0x7F); /* 读命令位 */ data SPI_SendByte(0x00); AS3933_CSN_H(); return data; } void AS3933_Init(void) { /* 软复位 */ AS3933_WriteReg(0x00, 0x80); delay_ms(2); /* 125kHz载波频率设置 */ /* 寄存器值根据数据手册FREQ[5:0]换算不同版本兼容芯片略有差异 */ AS3933_WriteReg(0x01, 0x3E); /* 使能曼彻斯特编码2kbps数据速率 */ AS3933_WriteReg(0x02, 0x12); /* 灵敏度设置先按手册推荐值后续联调再调整 */ AS3933_WriteReg(0x04, 0x48); /* 三通道输入全开 */ AS3933_WriteReg(0x05, 0x07); /* 写入唤醒ID pattern这里以0x5A为例 */ AS3933_WriteReg(0x06, 0x5A); AS3933_WriteReg(0x07, 0x00); }需要注意的是AS3933不同批次的寄存器位定义可能略有差异尤其是国产兼容芯片工程里常见的丝印带CEM型号。烧代码前务必拿到对应芯片的数据手册逐个寄存器确认。有一个快速验证SPI是否通的方法读器件ID寄存器如果返回0xFF或0x00多半是SPI极性问题。AS3933的SPI对CPOL/CPHA有要求遇到死活读不对时把CPHA翻转再试十次里有九次能解决。4. 联调顺序按三步走省掉一晚上瞎折腾4.1 第一步不编码先测125kHz载波拿到新板子我从来不会直接烧完整通信代码而是先烧一个最简单的载波常开程序让MCU持续输出125kHz PWM然后用示波器看天线两端波形。这一步能同时验证三件事PWM频率是否正确、MOS驱动是否正常、LC是否谐振在125kHz。示波器时基建议拉到2微秒每格能看到一个周期约8微秒的连续波形。把探头换成10倍衰减因为谐振电容两端电压可能到几十伏。如果波形频率不是125kHz先查定时器分频和重装值如果频率对了但幅值很小问题基本在LC谐振上微调电容到幅值最大即是谐振点。4.2 第二步用接收端Data引脚验证曼彻斯特时序载波正常后再把完整的发射帧代码烧进去并在接收端逻辑分析仪上抓AS3933的Data引脚。AS3933在解调成功后会从Data引脚输出恢复后的曼彻斯特数据流。把逻辑分析仪和解码器打开对照发射端Sleep的帧结构逐位确认前导长度、bit极性、数据内容是否一致。如果发现bit极性完全相反不是改发射端就是改接收端寄存器两边保持统一即可。这一步也能发现一个隐藏问题如果半位时间设置和接收端数据速率不一致解码会错位波形上看到的是乱码或者半截脉冲。解决办法是让发射端的HALF_BIT_US和AS3933的DATABW速率严格对起来。4.3 第三步整机距离测试与灵敏度补偿联调最后一步才是真实距离测试。最初跑测试时把AS3933灵敏度寄存器调到最大值距离优先做出来再逐步降低灵敏度压制误唤醒。实测时拿着接收板贴紧天线确认IRQ能触发后再往外拉距离记录一下稳定触发的最大距离。影响距离的主要因素按优先级排序是天线电流、LC谐振准确度、载波频率偏差、数据速率、接收灵敏度。距离不够时不要盲目调发射功率先重新检查LC谐振再核对载波频率这两点对距离的影响往往比后端灵敏度大得多。反过来如果出现不发射也误唤醒的情况多半是接收灵敏度太高或者供电噪声太大此时需要降低灵敏度、增加匹配帧的长度。5. 实际项目里最折磨人的四个坑5.1 载波频率偏半格接收灵敏度全线崩塌有次我图省事直接把一个9600bps波特率的工程改成了125kHz载波定时器PSC没改对实际输出是124.7kHz。示波器上看完全没问题但接收端距离始终上不去从两米直接掉到二十厘米。后来一测频率才发现差了一点点。AS3933虽然内部有自动频率校准但它校准的基准是前导载波如果载波频率偏离预期太多校准后内部时钟跟着偏解码窗口会对不准灵敏度明显下降。这种问题最容易出现在定时器分频值算错或者晶振偏差大的板子上。排查方法很简单示波器直接测量PWM引脚频率别只看代码注释里的理论值。5.2 天线Q值过高OOK包络拖尾会毁掉解码这个坑比较隐蔽。LC谐振Q值高说明选频特性好但同时也意味着天线两端能量衰减慢。125kHz系统用的是OOK调制载波有/无当bit 1切换成bit 0时载波停止可谐振回路还在响拖尾的幅度会让接收端误判为还在发射。接收端解码时对包络边沿有要求Q值过高会让包络下降沿变得太缓半位时间窗口里电平判断出错。解决办法是给天线回路串一个几十欧的阻尼电阻把Q值压到30到60之间。虽然幅值会损失一点但解码稳定性大幅提升。用一个简单的方法估计Q值在示波器上看载波关断后波形衰减到1/e所需的时间如果发现衰减时间超过半个bit时间就能确定Q过高了。5.3 发射大电流回灌SPI读取数据乱跳发射天线工作电流可能到几百毫安如果发射驱动电路的电源和AS3933、MCU的电源共用一个LDO发射瞬间电压跌落会非常明显SPI通信因此出错AS3933寄存器配置有时候莫名其妙就变成了0xFF。这类问题的根子是地回路串扰。我后来把发射级单独用一组电源或至少在发射MOS管的源级到地之间加一个电流检测电阻让强电流走独立路径模拟地和功率地单点相连。分地之后SPI读数稳定很多IRQ误触发也少了。5.4 低功耗需求下发射帧太长反而更耗电有些项目对功耗比较敏感会想通过加大前导载波时间来保证接收端唤醒。但前导载波本身就是发射电流持续时间天线电流几百毫安2ms前导本文还有配套的精品资源点击获取