简介九齐NY8系列单片机例程集合面向嵌入式初学者与电子产品开发者覆盖NY8A051D、NY8A053D、NY8B062D等常见型号提供C和汇编两种编程实例便于对照学习与工程移植。压缩包共2000个文件大小4.48MB以工程文件nysln/naprj/cb、C源码、汇编源文件和头文件为主目录组织清晰适合直接导入开发环境查看。已有4949人学习下载是NY8开发中较常用的参考资料。例程内容涵盖8051内核结构、系统初始化、中断处理、定时器/计数器、串行通信、GPIO控制及ADC/DAC等模块并涉及存储器管理和汇编底层操作不同的功能点都有对应代码示例通过实际编译调试这些例程可以快速掌握NY8系列外设配置与驱动编写思路还能在此基础上扩展出智能小家电、工控板卡等实际项目代码。1. 九齐单片机NY8例程包先把OTP片子的脾气摸清做小家电、电动玩具、氛围灯这些低成本项目时九齐单片机NY8例程包几乎是绕不开的一份资源。它不是给你「能不能跑」的答案而是把资源极度紧张时怎么用C/汇编把IO、定时器、PWM、休眠唤醒做对的完整踩坑记录。适合刚接手NY8系列、被私有IDE和数据手册里语焉不详的配置字折腾过的人也适合想让OTP一次性烧录少翻车的老手。这份例程包真正的价值不是点灯而是把配置字、中断入口、C与汇编混编这些「玄学」环节预先趟平了。2. 搭建NY8开发环境与例程包结构从IDE安装到第一块OTP的完整路径2.1 安装与工程选项三处最容易设置错的位置NY8不使用通用IDE而是厂家提供的NYIDE。这IDE界面不复杂但第一次装完的人大多数会卡在三个地方芯片型号匹配、工程模板选择、以及烧录器配置。型号选错最容易被忽视——比如把A型封装选成了B型代码能编译但引脚映射完全不同点灯怎么点都不亮。常见做法是先打开NYIDE在Project菜单下新建工程指定目标芯片型号然后在其自带模板里选C或ASM的起始工程。三处最容易设置错的位置我按踩坑频率排一下芯片型号务必与实物丝印一致字尾字母不同甚至会影响是否带PWM模块。系统时钟默认模板的启动文件里有一段时钟初始化有些模板默认走ILRC低速模式必须在Option里改为IHRC并指定频率。烧录器协议使用仿真器调试和量产烧录器时连接方式选项不同搞混会报「芯片无响应」。工程选项里有些参数比较隐晦建工程时先确认这张表再往下走配置项常见选择影响工作电压2.0V-5.5V低压供电时高主频可能不稳系统时钟IHRC / ILRC / 外部晶振决定程序运行速度与功耗看门狗使能/关闭使能后必须定时喂狗烧录选项代码保护开/关量产前务必开启到这里环境就算装完了重新检查一遍Option窗口里的三个位置芯片型号、时钟来源、烧录协议。都对了再往后走否则后面每一条例程跑起来都是错的你还以为是代码问题。2.2 例程包的目录结构如何快速找到对应型号的模板工程拿到「九齐单片机NY8例程.zip」第一步不是打开代码而是先看目录结构。常见组织方式是按外设分类一个外设一个子目录命名通常长这样01_GPIO_LED最小编程工程验证芯片和烧录链路。02_Key_Scan带消抖的按键扫描适合学IO方向配置。03_T16_Timer定时器中断示例是所有延时的基础。04_PWM_BuzzerPWM输出驱动蜂鸣器小家电最常用。05_Ext_Int外部中断唤醒示例。06_Sleep_Wakeup休眠唤醒电池产品必看。每个子目录通常是独立工程包含.c或.asm、.h或.inc、工程文件。有个判断封装的小技巧目录命名里的数字通常对应引脚数或型号系列先看实物丝印再选对应工程不要凭板子长得像就乱套。我的习惯是先打开03_T16_Timer而不是点灯工程。因为定时器例程能一次确认时钟、中断、烧录三条链路是否正常这三个环节有任何一处不对后面全白搭。点灯工程只能证明IO能输出证明不了时序。2.3 从LED闪烁开始第一块OTP的完整路径看一个最简C例程体会NY8的IO操作写法// LED闪烁例程示意代码具体宏名以例程包头文件为准 #include ny8_userdefine.h void delay_short(void) { unsigned short i; for (i 0; i 5000; i); } void main(void) { // 1. 将端口配置为输出模式 IO_DIR 0x00; // 2. 主循环翻转IO输出 while (1) { IO_DATA 0x01; // 输出高 delay_short(); IO_DATA 0x00; // 输出低 delay_short(); } }IO_DIR和IO_DATA在不同型号头文件里的宏名略有差异但逻辑一致先设方向寄存器再写数据寄存器。delay_short用的是空循环软件延时效果依赖系统主频——若工程里选的是ILRC低速时钟同样的循环会让闪烁慢到接近静止。编译通过后只是第一步OTP烧录才是真正验证。常见做法是走完整套流程在IDE里点编译生成hex/bin。打开烧录软件选择芯片型号并载入文件。写入配置字选项时钟、LVR、WDT等。烧录前先做「空片校验」确认芯片是空白的。烧录完成后做一次校验。断电再上电测试不要带电扒芯片。提示如果LED不亮把IO_DIR方向反着设一次再试。OTP片子上电后IO默认是输入状态不设方向直接写数据的例程在汇编版本里很容易翻车。3. 六个典型例程逐项拆解GPIO、定时器、PWM、中断与休眠唤醒3.1 GPIO点灯读寄存器表之前先确认端口方向NY8的GPIO寄存器表通常包含方向控制、数据锁存、上拉使能三段每段逐位对应一个IO口。新手最容易犯的错是直接查数据手册的端口地址抄了地址但没看方向位导致写数据寄存器毫无反应。按键输入和LED输出的方向配置不同这点在例程包的02_Key_Scan里体现得很清楚// 按键扫描 LED输出示意代码 void key_scan(void) { // 方向寄存器0输出1输入以例程包内宏定义为准 IO_DIR 0x02; // PA1设为输入其余输出 IO_PULLUP 0x02; // PA1使能内部上拉 if ((IO_DATA 0x02) 0) { // 按键按下低有效 IO_DATA 0x01; // 点亮LED } }这段代码的逻辑是先把PA1设为输入并打开内部上拉再把其余引脚设为输出。然后读IO数据寄存器当PA1读到低电平时说明按键被按下。读IO之前必须确认方向是输入并考虑上拉使能悬空输入读到的值是随机的按键扫描会出现乱跳。IO_PULLUP的上拉使能位是逐位对应端口的不是整组开关。按键接低有效时要在外部加下拉或内部上拉二选一不要两个都上。3.2 定时器T16中断标志要先清再处理T16是16位定时器用预分频和装载值构造任意延时。例程包里最常见的写法是先用定时器产生1ms节拍再用节拍计数做按键消抖、LED呼吸灯这类时间相关的逻辑。计算公式是中断周期 (预分频值 × (装载值加1)) / 时钟频率。举个例子8MHz主频预分频64想要1ms中断装载值 N 8000000 × 0.001 / 64 - 1算出来是124。例程包里的初始化代码大致是这样// 定时器T16初始化与中断示意 void T16_init(void) { T16_PRESCALER 64; // 预分频 T16_LOAD 124; // 周期 (64 * (1241)) / 8MHz 1ms T16_INT_FLAG 0; // 清中断标志 T16_INT_ENABLE 1; // 开中断 } void interrupt_handler(void) { // 先读中断标志再决定做什么 if (T16_INT_FLAG) { T16_INT_FLAG 0; // 必须先清标志 tick_count; } }注意NY8的多个中断源是共用一个入口的C编译器不会自动帮你分辨这次中断来自定时器还是外部引脚必须在中断函数里读标志位判断。清标志要在处理之前否则中断会连续触发看起来像死循环。另外提一点例程包里的tick_count建议声明成volatile否则优化等级开高后主循环里读它可能拿到旧值。这个问题在C写NY8时很常见属于优化器的典型行为。3.3 PWM输出重算频率再来调占空比PWM例程排在04_PWM_Buzzer量产里最常用的场景是驱动无源蜂鸣器和LED调光。PWM输出频率公式是F 时钟源频率 / (预分频 × 周期寄存器值加1)占空比 比较寄存器值 / 周期寄存器值。写PWM初始化前先做一件事确认芯片的PWM模块是几位的。部分型号PWM周期寄存器是8位部分支持16位位数不同可直接设置的周期范围完全不同。例程包里的初始化代码通常长这样// PWM初始化示意代码 void pwm_init(void) { PWM_CLK_SRC 1; // 时钟源选择定时器溢出 PWM_PRESCALER 1; // 1分频 PWM_PERIOD 255; // 周期寄存器决定频率 PWM_DUTY 64; // 比较寄存器决定占空比 PWM_ENABLE 1; // 使能PWM输出 }比如系统时钟8MHz、预分频1、周期寄存器255算出来的频率是8MHz / (1 × 256)约31.25kHz。这个频率驱动蜂鸣器还需要再降预分频或加大周期值。蜂鸣器一般取2.7kHz左右LED调光需要高频率避免频闪所以同一套PWM外设两个场景改的参数并不一样应用目标频率调整方式无源蜂鸣器2kHz~4kHz加大预分频或周期值LED调光20kHz以上减小预分频用DUTY调亮度先算频率再调占空比。例程包里PWM设置的注释要逐行看不同型号PWM位数不同直接抄别的型号的初始化代码输出频率会离预期很远。3.4 外部中断与休眠唤醒唤醒后先查复位原因休眠唤醒是电池产品最关心的部分。NY8进入休眠后电流可以降到很低但唤醒条件、唤醒后程序从哪继续执行这两个问题在不同型号上表现并不一致。例程包的05_Ext_Int和06_Sleep_Wakeup通常会配对使用。休眠前要做三件事把所有IO设为已知状态、关闭无关中断、执行休眠指令。参考写法// 休眠前统一配置 void enter_sleep(void) { IO_DIR 0x00; // 所有IO输出低避免浮空误唤醒 IO_DATA 0x00; DISABLE_ALL_INT 1; // 关闭无关中断 __sleep(); // 进入休眠 // 唤醒后从这里继续 }唤醒后的第一件事不是往下跑业务代码而是查唤醒标志区分这次是外部中断唤醒还是看门狗复位唤醒。这个标志位没查程序可能从错误的状态继续运行表现出来就是「休眠久了数据全不对」。还要注意唤醒后外设时钟的重启状态。部分型号唤醒后需要重新初始化定时器和PWM否则外设不工作。例程包封装好的唤醒函数通常已经处理了这些但你要在工程里找到唤醒后调用的那部分代码确认它不是空函数。4. C与汇编混合编程及配置字为什么很多老手放弃纯C4.1 C编译器的效率边界代码体积和时序精度的权衡NYIDE自带的C编译器在IO翻转、位操作这些场景下生成的代码体积明显偏大。同一个LED翻转操作C可能生成三到四条指令汇编只需要一条。OTP芯片程序空间有限指令多了装不下这是很多量产项目坚持用汇编的根本原因。另一个边界是时序精度。软件延时用C写编译器的循环优化行为不透明延时长短会随优化等级变化汇编写延时每条指令周期可数周期精确可预期。软件串口、单总线协议这类对时序敏感的代码纯C写起来很痛苦。一张对比表能说清各自的定位需求纯CCasm混合IO翻转点灯代码量大单条指令完成软件延时时间随优化漂移周期精确可维护性高中程序空间占用大小所以例程包保留汇编版本不是过时是这类芯片的常规工作方式。C用来写业务逻辑汇编用来写时序关键函数分工明确。4.2 混合编程的三种写法从asm嵌入到独立汇编模块混合编程有几种不同粒度的写法按场景选择。第一种是C里嵌入单条汇编指令__asm(nop); // 空操作常用于精确延时微调或对齐时序这种写法适合在C代码里插入少量汇编做微调。第二种是把关键延时函数独立成汇编模块// C文件里声明外部汇编函数 extern void asm_delay_us(unsigned char n); void main(void) { asm_delay_us(50); // 延时50微秒周期精确 }汇编文件里以全局标号导出函数名C直接调用。第三种是中断入口用汇编跳板先保存现场再跳转到C的中断处理函数。这种方式适合中断里需要做大量工作的场景现场保护逻辑更清晰。写汇编模块时有个容易忽略的点工作寄存器的保护。C调用汇编函数时编译器默认某些寄存器会被破坏但中断上下文里跳C函数所有寄存器的现场都要自己保存。例程包的汇编文件里通常有完整的保护代码抄的时候看清楚它保护了哪些寄存器不要删掉一半。4.3 配置字Option Word详解烧录前必须确认的五个开关配置字是九齐芯片最容易被误解的部分。它不是写在main函数里的代码而是在烧录时写入芯片的选项位。同一个bin文件配置字不同运行表现可以完全不同。量产前我一般要过一遍这五个开关开关可选值选错的后果系统时钟IHRC / ILRC / 外部晶振主频差几十倍延时全乱LVR低压复位高阈值 / 低阈值电压波动时频繁复位WDT看门狗使能 / 关闭未喂狗导致周期性复位代码保护开 / 关关固件可被编程器读回RC校准使用出厂值 / 手动校准主频精度影响串口波特率系统时钟这一步例程包默认会走IHRC但如果模板从别的工程拷过来可能默认在ILRC低速模式那所有延时都会按比例变慢。LVR阈值选得太高电池供电低压时芯片会频繁复位表现为「电量好像还有但设备老是重启」。代码保护这个开关量产时务必开启。关闭状态下编程器可以把Flash内容完整读出来等于固件直接裸露。OTP芯片一旦代码保护开启内部的程序是无法被普通烧录器完整读回的这层保护必须靠Option打开。5. 烧录与调试避坑OTP片子的五个血泪教训5.1 现象1烧录后LED亮度异常或完全不亮一块新板子程序确定烧进去了上电后IO输出方向也对但LED亮度极暗或直接不亮。原因通常是IO的驱动配置没设对。NY8的IO有不同驱动能力档位默认值不一定满足LED直驱需求再加上随手串了个大阻值限流电阻电流不够LED就只能在暗光下勉强亮。解决方法是查例程包IO初始化代码里有没有驱动能力设置把对应IO口调到高驱动档。限流电阻按推荐值取LED直驱一般几百欧到1k不要随手拿10k。用万用表量IO对地电压电压正常但LED暗十有八九是驱动档位问题。5.2 现象2第一次烧录正常第二次同一颗芯片「锁死」样片第一次烧录后运行正常继续调代码再烧录就提示失败报错信息类似「目标区域非空」。原因很直接OTP是One Time Programmable程序区烧录过就不能再擦写。很多新手把OTP当Flash用反复在同一个座子上点烧录芯片第2次就废了。解决方法是调试阶段使用仿真器在线调试或者使用支持重复烧录的样片每次正式烧录前做空片检查。我一般会准备几片专门用来试烧的片确认程序无误后再用正式料避免浪费。把这句话刻在脑子里OTP不是闪存没有后悔药。5.3 现象3休眠时被随意唤醒功耗测下来完全不像休眠程序进了休眠但电流还是很高或者过一会就自动醒用示波器抓唤醒源又抓不到。原因大概率是未使用的GPIO悬空。悬空引脚上的耦合噪声触发了外部中断芯片被反复唤醒另一种可能是休眠前没有彻底关闭所有中断源定时器溢出也能把芯片拽醒。解决方法是休眠前把所有IO置为已知状态不用的引脚设为输出低或上拉输入然后关闭所有无关中断最后执行休眠指令。唤醒后第一件事是读中断标志把残留标志全部清掉再跑业务。这一套顺序不能乱例程包的06_Sleep_Wakeup工程里通常已经排好直接复用。5.4 现象4中断函数里调用延时函数一进中断就死机在中断处理函数里调用了一个软件延时函数程序跑飞或复位有时候是偶发有时候必现。原因在于NY8的中断现场保护很有限。C编译器默认不做完整现场保存中断里调用普通C函数时寄存器冲突导致返回地址被破坏。这种现象在C工程里尤其明显汇编例程反而不容易出现因为汇编工程师习惯性自己保护现场。解决方法是中断函数只做两件事清中断标志、置事件标志。任何延时、复杂计算、IO操作全部放到主循环里按事件标志执行。例程包里的中断代码基本都是这个套路这是经过验证的写法。5.5 现象5同一份固件两片芯片表现不一致同批次的板子一片PWM频率正常另一片偏高串口数据偶发乱码。代码和烧录流程完全相同。原因是每颗芯片内部RC振荡器都有离散性默认频率不完全是标称值。有的芯片8MHz实测7.8MHz有的8.2MHz对外设频率敏感的功能就会表现出来。解决方法是烧录时选「使用出厂校准值」选项芯片出厂时内部有校准数据烧录器会把它写入配置区。要求更严格的场合用PWM输出实测频率再在Option里做一次手动校准。例程包里的校准代码是怎么读、怎么填的值得完整看一遍串口应用靠这个保命。6. 一个提高例程复用率的小习惯先验证时钟再动外设拿到一块新NY8板子不要急着移植例程业务代码。先烧一个「时钟验证固件」让IO口翻转频率直接暴露当前主频。做法很简单写一个空的main函数把某个IO配成输出主循环里翻转它。然后用示波器量这个IO的翻转周期就能反推出系统时钟实际跑了多少// 时钟验证固件示意 void main(void) { IO_DIR 0x01; // PA0输出 while (1) { IO_DATA ^ 0x01; // 翻转PA0 } }示波器量到的频率乘以2就是IO翻转率再对照你配置的时钟源偏差一目了然。这个固件只有十几行跑一次两分钟能省下后面几小时的排查时间。我用的另一个做法是给例程包加一个「板级自检」函数烧录后先跑一遍时钟、LED、按键、PWM的自检流程确认所有外设正常再烧正式应用。这个自检函数不需要太复杂每个外设设置一个状态标志主循环里按顺序检查跑完用LED或串口输出结果。收到板子先自检确认硬件和烧录链路没问题再动手改业务代码这是最省时间的路径。从那以后我每次拿到新NY8板子都强制自己先烧时钟验证固件确认主频准确后再动任何外设。这个习惯帮我筛掉了大量「代码看着对但就是跑不对」的隐形问题。例程包下载后建议也按这个顺序走一遍先开03_T16_Timer验证链路再用时钟验证固件确认主频最后再逐项移植其他外设。希望帮到你。本文还有配套的精品资源点击获取