简介:九齐NY8系列单片机的例程合集,同时提供汇编(ASM)与C语言两种实现,覆盖该系列绝大多数型号,适合正在选型或开发NY8方案的嵌入式工程师、在校学生快速上手。压缩包内共2000个文件,以nysln工程文件、naprj项目文件、cb源文件、c/h代码和asm汇编为主,配套s启动文件与makefile构建脚本;例程按GPIO、定时器、ADC、PWM、UART、SPI等常见外设模块组织,工程结构完整、便于快速定位相关代码,打开即可对照学习。资源包仅4.34MB,便于下载与本地归档。已有4187人学习下载,常被用作NY8开发的基础参考库。借助这套例程,读者可以快速理解不同外设的寄存器配置、中断处理与汇编/C混合编程思路,同时通过对比两种语言版本,能更高效地完成从验证到量产固件的移植,也适合作为项目原型改写的起点。
1. 九齐NY8例程包:从点灯到量产的起步脚手架
做低成本8位机方案时,最怕的不是芯片手册看不懂,而是手里没有能直接编译、烧录、看到现象的可运行例程。九齐NY8系列这套ASM/C双语例程,正好补齐了这块短板。它把GPIO、定时器、PWM、ADC、中断、低功耗这些常用外设拆成一个个独立工程,每个工程都有汇编和C两版对照,适合两类人:一类是刚接触NY8、想尽快点亮第一颗灯的新手,另一类是从汇编往C语言迁移、需要对照寄存器配置做移植的熟手。下面按我实际拆包的顺序,把资源结构、环境搭建、例程拆解和常见坑一次讲完。
2. 例程包的结构与选型:先看懂文件再动手
2.1 例程目录与工程文件的对应关系
把压缩包解开之后,第一件事不是找IDE,而是先建立目录索引。这套例程按外设模块分文件夹:GPIO、Timer、PWM、ADC、Interrupt、Sleep、EEPROM读写等。每个文件夹下基本都是同一型号的独立工程,工程文件后缀名跟具体IDE版本有关,有的以.prj保存,有的则是XML格式的工程描述文件,但核心文件其实是.asm或.c源文件加一个头文件和工程配置。例程包根目录一般还有原理图或参考接线图,我建议先不看电路图,因为不同厂家做的测试板引脚定义不一定相同,直接照抄会踩坑。
我一般会先打开任意一个例程的工程文件,确认三件事:芯片型号是否与手上的样片一致、编译选项里的振荡器频率和代码选项字是否合理、烧录器型号和电压设置是否正确。例程包里通常会有一个README或Release_Notes文本,把这个版本支持的外设列表和修改记录写清楚。如果没做这一步,直接拿另一个型号的工程改,很容易在代码选项字上翻车。注意这里说的“型号”不仅是NY8A050D和NY8A051F的区别,同一颗料的不同封装或不同等级,存储容量也可能不同,选项字里芯片型号不匹配时,烧录器会直接拒绝写入。
提示:拿到例程包先看Release_Notes,再决定先跑哪个例程,不要上来就改配置。
这个习惯要延伸到后续所有例程:每次复制一个工程修改前,先另存到自己建的目录,不要让IDE直接原地编译。因为IDE生成的一些临时文件和调试缓存会污染原来的工程,等你想回到初始状态对比时,已经没有干净版本了。
2.2 ASM与C两套实现怎么对照看
同一个外设同时提供汇编和C两种实现,是这个例程包最值钱的地方。汇编版把寄存器的每一位怎么操作都摆在明面上,适合用来理解芯片内部结构;C语言版可读性好、改参数快,适合日常做产品迭代。
我推荐的学习顺序是:先编译通过汇编版,找到对应外设的初始化代码段和数据手册里的寄存器描述逐一对照;再打开同功能的C版,只看main函数和控制逻辑,确认同一件事在两种语言下是怎么表达的。比如操作一个IO口输出高电平,汇编版可能是BSF RA, 1,C版则可能写成RA1 = 1或者通过宏SET_PIN(RA, 1),两者最终生成的机器码等价,但写法体现两种思维习惯。汇编需要你手动画出位分布来理解读-改-写过程,C则把这些细节掩盖在宏里面。初学阶段两种都编译一次,用IDE的反汇编窗口看C生成了什么汇编,是最快建立对应关系的方式。
还有一点是寄存器头文件的差异。汇编例程里会用EQU或#define把各寄存器映射到物理地址,C版则统一用sbit和结构体。对照看能发现,不同例程的头文件里可能把同一个寄存器地址定义成不一样的名字,这不是错误,而是型号差异或版本迭代的结果。遇到这种情况,优先信头文件,不要改代码里的名字去“匹配”数据手册。汇编和C的移植边界往往就在头文件这一层,搞清楚之后再换型号,只改头文件和选项字就能跑通大部分外设。
2.3 外设例程的覆盖范围与优先级
从实用角度看,例程覆盖面不必贪多,关键看是否覆盖了产品开发的完整链路。这套例程里,我按优先级排序,重点关注的模块是:
| 外设模块 | 典型应用场景 | 例程形态 |
|---|---|---|
| GPIO | 点灯、按键、输出控制 | ASM + C |
| Timer | 延时、定时唤醒、计数 | ASM + C |
| PWM | 调光、电机调速 | ASM + C |
| ADC | 电池电压检测、传感器采集 | ASM + C |
| 中断 | 外部事件唤醒、定时更新标志 | ASM + C |
第五类中断通常是新手最容易卡住的地方,因为中断涉及寄存器保护和标志位清除,出错时现象又很隐蔽。我建议先把GPIO和Timer例程跑通,再进入中断例程,最后再做低功耗组合场景。这样每一步的变量都被控制在最小范围,出了问题也容易定位。
从一个例程跳到另一个例程时,别只看两个main函数的不同,还要对比它们引用的头文件和选项字配置。PWM例程通常会把定时器配成特定模式,这个模式会改变定时器的中断触发方式;直接把它和中断例程合并,可能两边都跑不起来。例程包的作者往往把每个外设都做成“最小验证工程”,目的就是让你能在干净的背景里看到单模块行为,所以组合前一定要确认配置之间没有冲突。
3. 开发环境与烧录流程:装好环境只算第一步
3.1 IDE、编译器与仿真器的选择
NY8系列官方主推的IDE集成了编辑器、编译器、烧录和仿真功能,入门阶段不需要单独安装编译器,IDE自带的就行。但要确认IDE版本和例程包验证时使用的版本是否一致。不同版本的汇编器和C编译器,对语法的宽容度和优化策略不同,常见情况是例程包在某个版本下编译通过,换到新版IDE后报一个“instruction not recognized”或者“syntax error”,这多半不是代码有问题,而是编译器做了指令集扩展。
我的做法是先用例程包说明里指定的IDE版本把工程原样编译一遍,确认环境变量、路径配置都没问题,再决定要不要升级到新版。升级时重点看编译输出窗口里的警告,警告里往往会提示某个寄存器的地址在新头文件里已经偏移,或者某个中断向量号不存在。编译器选择上与生产成本有关的点在于代码密度:NY8这类8位机的Flash通常只有几K,C编译器生成的代码比汇编大20%到30%是常态。如果例程编译后Flash占用已经到80%以上,建议这部分逻辑用汇编写,或者换一个优化等级。
IDE里一般有O0到O3几档优化,O3的效果明显,但调试信息会变少,中断里变量更新的行为也可能被优化到出乎意料。量产固件建议开O2或O3并做抗干扰测试,开发调试阶段用O0最稳妥。仿真器方面,入门用烧录器自带的仿真功能就够,不一定要单独买仿真头。像例程里这种简单外设,用LED和示波器验证比仿真器更直观。
3.2 工程配置:芯片型号、代码选项字与电压
动笔之前必须确认的工程配置有三处。
第一处是芯片型号。型号选错了,汇编能编过但烧录器不认芯片,或者烧进去了程序跑飞。第二处是代码选项字,也就是芯片的硬件配置位:振荡器源用内部高频还是外部晶振、看门狗开不开、低电压复位阈值选多少、烧录引脚是否复用为普通IO。第三处是烧录电压,通常需要跟着芯片的工作电压走,低压芯片如果烧录用默认5V,可能直接把IO拉坏,也可能让代码选项字里的LVR电压设置失效。
代码选项字这个坑值得单独展开。NY8的选项字不是在main函数里用代码写的,而是在IDE的配置界面里勾选,烧录时一并写入芯片的非易失区。例程包里的工程文件已经把推荐值预设好,但如果你是新建工程只复制代码,选项字就会退回默认值。默认值和例程设计要求不一致时,现象通常是:上电不跑、复位异常、低功耗唤醒不了。我见过一个案例,对方把GPIO代码都抄对了,就是没设振荡器源,芯片一直靠内部低频RC跑,LED每秒才闪一下,他以为是延时函数写错了,查了一个下午。
| 配置项 | 推荐值(视具体型号) | 影响 |
|---|---|---|
| 时钟源 | 内部高频 / 外部晶振 | 主频、定时精度 |
| 看门狗 | 产品建议开启 | 死机恢复 |
| LVR阈值 | 按工作电压 | 低压复位行为 |
| 烧录引脚复用 | 量产可关 | 烧录后IO数量 |
第三处是烧录电压。如果目标板由3.3V供电,烧录器就必须配到3.3V,不要用5V挡。电压不匹配轻则烧录失败,重则让IO口相关寄存器进入不确定状态。
3.3 烧录与验证流程
烧录流程我固定成一条链路:先USB连接烧录器,确认IDE识别到设备;再选择芯片型号,导入目标hex或直接烧整个工程;然后确认电源电压之后点烧录,观察进度条;最后拔线前把烧录器切到离线模式,防止目标板复位异常。目标板的电源如果是由烧录器提供的,还要确认目标板上的大电容不会让电压跌落触发LVR。
烧录完成不等于程序对。我每次烧完后做的第一件事不是看现象,而是测静态电流。拿万用表电流档串到电源正极,如果程序原本设计是Sleep状态,电流却和运行态一样大,多半是代码选项字里的看门狗没有关,导致芯片没法真正Sleep;或者某个IO口悬空,产生了漏电流路径。把静态电流量了,再抓运行态电流,两道数据加起来,例程是否真正生效就一目了然。
调试阶段建议在IDE里打开“烧录后运行”选项,这样每次烧完会自动复位执行。如果没这个选项,就手动断电重启。这里有个小习惯:用外部电源给目标板供电时,烧录器只接数据线,不接电源线,避免两边地电位不一致形成环路电流。
注意:烧录失败时先看电压和接线,再看选项字,最后才怀疑芯片损坏。大部分情况都是前两者。
4. 例程实战:GPIO点灯与定时器中断的完整拆解
4.1 汇编版GPIO点灯:寄存器位操作
例程包里最简单的工程通常是“点亮一颗LED”。汇编版代码量很小,但浓缩了NY8端口操作的核心逻辑。典型结构类似:
; 端口方向寄存器设为输出 MOVIA 0x00 MOVAR TRISA ; RA1输出高电平 BSF RA, 1 loop: ; 延时循环 MOVIA 0xFF MOVAR TMR0 wait: DECRSZ TMR0 GOTO wait GOTO loop这段代码里,TRISA是端口方向控制寄存器,写入0x00表示该端口全部作为输出。BSF指令把RA寄存器的第1位置高,LED就亮了。延时部分用TMR0作循环计数器,DECRSZ在自减后结果为0时跳过下一条指令,实现可变延时。这里有个细节:DECRSZ TMR0指令周期和结果有关,被跳过的GOTO wait会让总延时曲线变成非线性,所以软件延时的时间并不严格等于指令数乘以周期。
对照数据手册会发现,BSF操作一个位的背后有一个读-改-写过程:CPU先把整个RA寄存器的当前值读出来,修改指定位后再写回去。如果端口同时有输入引脚外接电容等负载,读回的值可能和实际电平不一致,所以例程里普遍建议端口加限流电阻,不只是为了LED,更是为了稳定读回电平。
汇编版点灯例程里,通常还会有一个清看门狗指令的调用。很多新手把它忽略,直接删除这个调用,结果就是程序跑一会自动复位。原因是代码选项字里开了看门狗,而主循环里没有喂狗。这个细节在汇编版里特别明显,因为汇编不会帮你隐藏“喂狗”这个动作,C版可能封装成了CLRWDT()这样的函数,反而更容易忽视。
4.2 C语言版点灯:延时与宏定义
功能相同的C语言工程,代码结构更接近产品开发习惯:
#include "ny8.h" #define LED_PIN RA1 // 定义端口位 void delay_ms(unsigned int ms) { unsigned int i, j; for (i = 0; i < ms; i++) for (j = 0; j < 100; j++) ; } void main(void) { TRISA1 = 0; // RA1设为输出 LED_PIN = 1; // 输出高电平 while (1) { LED_PIN = ~LED_PIN; delay_ms(500); } }C版把端口位定义成宏,可读性明显提升。TRISA1 = 0在编译后和汇编的MOVAR TRISA等价,但写起来更接近位操作。延时函数是纯软件循环,没有用定时器,所以延时精度依赖主频和编译器优化等级。如果编译器把内层空循环优化掉,延时会大幅缩短。这是很多人用C版点灯时看到LED狂闪的原因:优化等级选太高,空循环被删了。
更稳的做法是把软件延时里的变量设成volatile,强制编译器每次循环都读写该变量。虽然这会增加几条指令,但保证延时行为可预期。调试阶段我甚至会在延时函数里加一个NOP(),方便用示波器测量实际周期,再反推延时时间是否符合预期。
C语言版的好处还体现在条件编译上。例程里经常能看到#ifdef XXX来控制不同型号的寄存器映射,比如同一段PWM初始化代码,经过宏切换可以支持两种引脚布局。新手第一次看到会觉得乱,但这是例程包故意保留的兼容层,方便你往不同型号上搬。移植时只要改宏定义,不需要动逻辑代码。
4.3 定时器中断:分频系数与重载值计算
点灯例程跑通后,下一步建议拆定时器中断例程。NY8的定时器一般有预分频器和周期寄存器,例程里通常用定时器产生一个周期性中断来翻转LED。C代码骨架如下:
volatile unsigned char flag = 0; void interrupt_isr(void) { if (T0IF) { T0IF = 0; // 清中断标志 flag = !flag; // 翻转标志位 } } void main(void) { TRISA1 = 0; T0IF = 0; T0IE = 1; // 开Timer0中断 GIE = 1; // 开总中断 TMR0 = 0x06; // 预装载值 T0PS = 0b010; // 预分频系数 1:4 while (1) { if (flag) { LED_PIN = 1; flag = 0; } } }中断服务程序里先判断是哪只中断源触发,这里是Timer0的中断标志T0IF。标志位必须手动清零,否则中断会持续触发,主循环根本没机会跑。预分频系数T0PS直接决定中断频率:设成1:2还是1:16,中断周期相差8倍。这一点例程注释里通常写得很清楚,改的时候要连看重装载值和分频系数是不是配套,只改分频不改重载,频率变化不是线性。
设计中断频率时,先算主频。假设内部振荡器是4MHz,分频1:4,再算TMR0从预装载值溢出所需周期数,就可以推出中断周期。例程包里一般会有一张表列出典型频率下的参数组合,我建议自己用Excel重算一遍,因为芯片不同批次的主频会有误差,表中是个近似值,产品里对时间敏感的场景要实测。
参数设计时要注意TMR0是8位还是16位,以及预装载值在中断发生时是否会自动重新加载。如果必须手动重载,应在清标志位后先把值写入TMR0再退出中断,防止写入期间又被中断插队。常见做法是进入中断后立刻关总中断,写完TMR0再开,保证这3条指令不被插队。
5. NY8例程复现中的避坑清单:烧录、唤醒与寄存器读写
5.1 烧录时报“芯片不识别”,实际是代码选项字不对
现象:例程编译通过,烧录器也能找到芯片,但点击烧录后提示芯片ID不匹配或无法握手。
原因:工程里的芯片型号和实际样片不一致,或者代码选项字里选了不存在的振荡器源,导致芯片上电后没有正常工作时钟。还有一种常见情况是芯片已经熔断了烧录引脚,或者烧录引脚被复用成了IO口,烧录器自然握手失败。
解决:先在IDE的工程配置页把芯片型号改成实物型号,然后核对选项字,尤其注意振荡器选择。改完后做一次“擦除芯片”,再重新烧录。如果还是不行,检查烧录器和目标板之间的接线,时钟线和数据线对调同样会出现类似现象。量产板上如果引出了烧录接口,务必量一下这两根线到芯片引脚之间的电阻,串了0欧电阻还好,串了1K电阻就会导致烧录失败。
5.2 低功耗例程唤醒后程序乱跑,原因在唤醒源配置
现象:进Sleep后能用按键唤醒,但唤醒后程序不是从Sleep的下一条指令继续,而是跳到了复位入口,状态全丢了。
原因:唤醒源配置和实际电平不匹配。例程里设置的是下降沿唤醒,但按键电路在按下时产生的是低电平脉冲,如果引脚内部上拉没打开,低电平时间太短,MCU认为是不符合唤醒条件的事件,直接复位。另一个常见原因是外部RC复位电路的时间常数不对,唤醒瞬间立刻触发了一次上电复位。
解决:打开内部上拉电阻,把唤醒条件改成电平触发并配合电容延长低电平时间。唤醒后第一时间读端口电平确认按键状态,再决定是否执行后续动作,不要一唤醒就清状态。进Sleep前也要把端口配置好,特别是唤醒脚以外的IO,该设成输出的设成输出,免得悬空引脚产生额外漏电流。
5.3 中断里改共享变量不关总中断,主循环读到半新半旧的数据
现象:主循环里判断一个标志位去执行动作,但动作时有时无,甚至出现中间状态。
原因:标志位是在中断服务程序里用多条指令更新的,比如先改高字节再改低字节,主循环在两条指令之间读取了变量,拿到一个拼接值。8位机里一个16位变量的读写如果被打断,很容易出现这种情况。
解决:中断服务程序里对外层可见的变量,先关总中断再修改,修改完立即开总中断。主循环读取时也可以临时关中断。这个操作开销很小,但能根治此类偶发问题。如果变量只是单个bit就不需要这么谨慎,因为一次读-改-写周期不会被另一条单bit指令打断出半新半旧的效果。
5.4 例程代码和数据手册寄存器名对不上
现象:打开某个外设例程,里面引用的寄存器名在手上的数据手册里找不到。
原因:例程包编译验证所用的芯片型号和数据手册版本不对。NY8系列不同型号的寄存器布局有差异,数据手册更新后寄存器名也可能调整。还有一种情况是头文件里做了条件编译,不同型号映射到不同物理地址,你在手册里看到的名称和头文件里的宏名不一致。
解决:优先以例程包自带的头文件为准,不要照搬另一型号的头文件。如果手头手册确实强调寄存器改名,就在例程里搜索#define或EQU语句,找到新旧名称的映射关系。最稳妥的办法是直接在新版IDE里新建工程,把源文件替换进去,让IDE按当前头文件重新编译一遍,再根据编译报错反查需要调整的宏。
6. 把例程改造成自己的产品框架:状态机与低功耗验证
例程跑通只是开始,真正实用的是把这些零散外设组合成产品逻辑。我自己的习惯是把主循环改造成一个简易状态机,比如一个典型的手持设备:上电初始化后进入Scan状态,扫描按键改变运行模式;检测到无操作进入Sleep状态;中断唤醒后回到Scan状态。例程里的按键扫描、定时器定时和低功耗三块代码正好对应这三个状态,拼起来只需要改main函数,不需要动外设驱动部分。
复合场景里最实用的技巧是功耗验证。先用万用表电流档串在电源正极,测量运行态和Sleep态的电流差;再用示波器抓唤醒引脚的电平变化,确认按键按下后确实产生了一个满足唤醒条件的边沿。这两步做下来,低功耗是否生效、唤醒是否可靠就能直观看到。需要注意,测量Sleep电流时最好断开烧录器,因为烧录器的供电路径会掩盖芯片的真实功耗。
状态机改造时的边界条件我踩过不少:比如按键进入Sleep前应该先把按键标志位清零,否则唤醒后会执行上一轮残留的动作;比如定时器中断里修改状态变量时,要参照前面说的关中断流程;再比如多个外设合并时,选项字里的看门狗设置要和工作模式对齐。调试阶段我习惯先关看门狗,量产前再开,避免看门狗在长时间调试时频繁复位干扰验证。
从那以后我每次移植例程都强制走一遍:先编译不优化确认现象,再逐个外设替换成自己的驱动,最后做动态功耗测量。这套流程完成之前不会直接进入量产代码编写,基本没再翻过车。希望帮到你。
本文还有配套的精品资源,点击获取