|
前言 在嵌入式系统学习的漫漫长路上,点亮第一颗LED与向世界说出“Hello, World!”,是每一位开发者最具仪式感的第一步。然而,在8051单片机的世界里,这一小步往往伴随着复杂的寄存器配置、神秘的汇编指令和海量的芯片手册。许多初学者满怀热情打开一个工程,却被那些陌生的头文件、特殊功能寄存器和内嵌汇编吓得望而却步。 今天,我们要为你展示的不仅仅是一个“点亮LED”的例程,而是一套完整、规范、值得反复研习的单片机项目框架。这套框架基于金水32051编译器,运行在AI8051U这颗性能强劲的8位单片机之上,采用了经典的8BIT模式(兼容8051指令集)。它包含了精确的软件延时、标准串口初始化、CPU性能调优以及清晰的后台主循环结构。可以说,吃透了这个程序,你对8051的理解将不再是“照葫芦画瓢”,而是真正踏入工程师的门槛。 本文将以这个“官方范例”为主线,用超过八千字的篇幅,手把手带你理解其中每一个细节。我们将从单片机本身讲起,逐步深入到时钟配置、汇编延时、串口通信、I/O端口控制,最后在闪亮的跑马灯和课后练习中巩固你的知识。请准备好你的开发板、编译器和一颗好奇心,让我们开始这段奇妙的8051之旅。
第一部分 初识AI8051U与金水32051编译器1.1 什么是AI8051U?AI8051U是 STC 推出的一款高性能1T 8051单片机。与传统的12T 8051(例如经典的AT89C51)相比,它的指令执行速度有了质的飞跃——在同等工作频率下,大部分指令仅需1个机器周期,而非12个。这也就意味着,即使运行在相同的晶振频率下,AI8051U的处理能力也相当于传统8051的12倍。 然而,AI8051U远不止“提速”这么简单。它内置了丰富的外设:多个定时器/计数器、串行口、高速ADC、PWM、SPI、I2C以及大容量的片内Flash和XRAM。更强大的是,它支持两种指令集模式: - 8BIT模式(本文所用):完全兼容传统8051指令集,使用经典的C51编译工具链即可开发。所有原有的8051代码几乎可以无缝迁移,但又能享受芯片本身的高速和丰富资源。
- 32BIT模式:部分型号支持切换到32位总线模式,能更高效地访问片内大容量存储器,通常需要特定编译器的支持。
在本项目中,我们选择8BIT模式,并使用金水32051编译器,这非常适合初学者快速上手,同时又能深度理解底层硬件。 1.2 金水32051编译器简介金水32051编译器是一款专门为新一代8051单片机(特别是STC的1T系列)优化的C语言编译器。它完整支持C51标准,并做了大量改进,比如优化代码生成、支持更灵活的指针运算、内嵌汇编接口更加自然。在我们的项目框架中,你会看到这样的代码: #pragma asm
// 汇编代码
#pragma endasm这就是金水编译器(以及某些扩展C51编译器)支持的内嵌汇编语法。它允许我们在C函数内部直接书写8051汇编指令,享用C的结构化优势同时又保留了对硬件的精准控制。后面我们将详细剖析那个精妙的软件延时函数,它正是得益于这种C与汇编的完美融合。 1.3 项目框架的意义很多初学者一上来就只盯着main()函数里的几行点灯代码,而忽略了大段大段的初始化设置。实际上,一个严谨的嵌入式项目框架包含: - 硬件相关的头文件(寄存器地址定义)
- 标准输入输出库(方便调试)
- 系统时钟与波特率定义
- 底层硬件初始化(CPU、端口、串口)
- 用户应用程序(LOGO、主循环任务)
这套框架的意义在于“规范”。当你把AI8051U所有I/O口都配置成准双向口,当系统频率明确定义为33.1776MHz,当波特率计算公示清晰呈现时,整个程序的行为就是可预期的、可维护的。无论是小到一个LED闪烁,还是将来扩展成一个复杂的智能家居控制器,这个框架都能成为你的基石。
第二部分 头文件:通往硬件世界的钥匙让我们翻开源程序最前面几行: #include "..\C351_XSFR_AI8051U_8BIT.h"
#include "..\C351_STDIO.h"#include "..\C351_PRTSCN.h"三个头文件,各司其职。 2.1 单片机XFR定义头文件C351_XSFR_AI8051U_8BIT.h是整座大厦的地基。它定义了AI8051U所有特殊功能寄存器(SFR)和扩展寄存器(XFR)的地址。 在8051中,诸如P0、P1、SCON、TH1等寄存器都映射在固定的地址上,例如P0地址为0x80,SCON为0x98。这些定义通常类似于: sfr P0 = 0x80;
sfr SCON = 0x98;而AI8051U拥有大量位于扩展地址空间的寄存器,如P_SW2、AUXR、P0M1、WTST等,它们通常需要通过特定的使能位才能访问。因此头文件中不仅有这些寄存器的地址声明,还有一些位操作的宏定义,让我们能用易读的符号去操作寄存器。没有这个头文件,我们面对的就是一堆冷冰冰的十六进制数字,极易出错。务必确保工程中正确包含了与你单片机型号匹配的这个文件。 2.2 标准输入输出库与串口打印C351_STDIO.h和C351_PRTSCN.h提供了标准C库函数的支持,尤其是我们马上要用到的printf函数。 对于没有操作系统的单片机而言,printf的输出重定向到哪里呢?答案是串口。金水32051编译器的STDIO库已经做了底层适配:当你调用printf,字符就会通过串口1发送出去。这意味着我们只要在PC端打开串口调试助手,就能实时接收到单片机打印的信息,这是软件开发中最为基础的调试手段。 需要注意的是,在使用printf之前,必须初始化串口并设置好波特率,否则单片机并不知道该以何种速率发送数据。后面我们将看到串口初始化函数JSx51_Uart_Init()是如何完成的。 2.3 宏定义的魅力:#define构建系统常量在头文件之后,程序定义了几个宏: #define Fosc_KHZ_A51 33177 // 主频 33.1776MHz
#define MAIN_Fosc 33177600UL
#define Baudrate 115200L
#define TM (65536 -(MAIN_Fosc/Baudrate/4))这些宏是整个程序运行参数的集中体现,体现了“一处修改,处处生效”的良好风格。我们需要深刻理解它们。 - Fosc_KHZ_A51:系统主频以kHz为单位。为什么有个这么奇怪的值?因为AI8051U内部可以产生多种频率,33.1776MHz是一个常用频率(它恰好是11.0592MHz的3倍,而11.0592是传统8051串口通信的“神奇频率”)。把它保存为整数,用于内嵌汇编延时中的立即数赋值。
- 之所以专门设置一个以kHz为单位的主频是为了兼容Keil的A51,A51只支持16位的整数。
- MAIN_Fosc:主频以Hz为单位,带UL后缀表示无符号长整型,确保在计算时不发生溢出。
- Baudrate:目标波特率115200bps,这是现代串口通信最常用的高速率。
- TM:这是一个精密的计算宏,用于产生波特率所需定时器重载值。后面在串口初始化部分我们将仔细推导这个公式。
宏定义的幂等性、无类型特性可能带来隐患,但在这里清晰、简洁地完成了系统参数设定,是值得初学者模仿的优秀实践。
第三部分 精确软件延时:C与汇编的完美协奏如果只能挑选一段代码作为本框架的精华,那毫无疑问是JSx51_Delay_MS函数。它用短短二十多行内嵌汇编,实现了一个精确的毫秒级软件延时,不占用任何定时器硬件资源。 3.1 为什么需要软件延时?在点亮LED或跑马灯这类应用中,我们需要让亮、灭的状态保持一段时间,人眼才能分辨。延时有两种方案: - 硬件定时器延时:精确,但会占用宝贵的定时器资源。
- 软件延时:通过执行一堆“无用”指令消耗时间,简单,不占外设。
对于入门学习和非实时任务,软件延时足够了。而精确度则是区分“随便写的循环”和“工程级别代码”的分水岭。许多初学者会这样写: void delay(unsigned int x) { while(x--) ;}这种C语言延时与系统主频、编译器优化等级高度相关,换一个环境时间就变了,简直是一场灾难。而我们的框架则给出了教科书般的解决方案。 3.2 函数原型与参数传递
void JSx51_Delay_MS(unsigned int MS)在8051的C编译器调用约定中,当传入一个unsigned int参数时,通常通过寄存器组传递。以金水32051编译器为例,第一个参数放在R6、R7中(R7低字节,R6高字节)。因此,在汇编体内可以认为MS的计数值已经安静地躺在R6:R7里了。 3.3 逐行解析汇编艺术我们将汇编代码一步步拆开,请保持耐心,当你完全看懂它时,你对8051汇编指令的掌握将上一个大台阶。 MOV A, R7
JZ JSx51_Delay_MS_01
INC R6
JSx51_Delay_MS_01:这一段处理的是毫秒数边界情况。如果R7为0,意味着低8位为0,那么高8位需要减1吗?实际上这是为后面的DJNZ双重循环构建准确的16位计数值。DJNZ指令是先减1再判零,如果R7原本为0,递减后变为0xFF并不为0,就会循环。这么一转换,确保了(R6,R7)这个16位组合能正确地控制循环次数。 MOV R2, #HIGH (Fosc_KHZ_A51 / 10) ; 取高8位
MOV R3, #LOW (Fosc_KHZ_A51 / 10) ; 取低8位这里出现了核心常数:Fosc_KHZ_A51/10 = 33177 / 10 = 3317。这是什么意思呢?我们想产生1毫秒的延时,可以做一个空循环,这个空循环的次数就等于Fosc_KHZ_A51除以循环一次需要的指令周期数,对于1T单片机这个周期数等于10,这样就先构建了一个1毫秒的基本延时。R2:R3保存的数值3317,就是这个1ms延时的循环基数。 JSx51_Delay_MS_02:
MOV A, R2
MOV R4, A
MOV A, R3
MOV R5, A; ----------- 内层16位循环 -----------
JSx51_Delay_MS_03:
MOV A, R5
DEC R5
JNZ JSx51_Delay_MS_04
DEC R4
JSx51_Delay_MS_04:
DEC A
ORL A, R4
JNZ JSx51_Delay_MS_03; ------------------------------------
DJNZ R7, JSx51_Delay_MS_02
DJNZ R6, JSx51_Delay_MS_02内层循环的本质是一个16位减法计数器,从(R4,R5)递减到0。每递减一步需要执行若干条指令。当(R4,R5)减到0时,内层循环结束,然后外层DJNZ使(R6,R7)减1并判断是否继续。这样总循环次数 = R6:R7次 × 内层循环所需的时间,而内层循环所需时间正是10T。 在AI8051U的8BIT模式下,大部分指令为单周期,MOV A,R5、DEC R5为1周期,JNZ跳转时3周期,不跳转2周期。我们只要精确计算出一轮内层循环消耗的机器周期数(10T),乘以3317,就能得到1ms的精确时间。再将这个基数交给外层进行毫秒级计数。 3.4 为什么不用C写延时?C语言中for循环延时完全依赖编译器的代码生成策略。开启优化后可能被直接删除,不开优化又会插入很多冗余代码。一个精确的软件延时,必须牢牢控制每一个机器周期,汇编是唯一的选择。金水32051编译器支持#pragma asm,让我们无需单独创建汇编文件,在C函数内部即可“全速”手动操控,是嵌入式开发者的利器。
第四部分 串口通信:搭起与PC对话的桥梁JSx51_Uart_Init函数虽然不到十行,却蕴含了通用异步收发传输器(UART)配置的全部关键要素。 4.1 串口模式选择SCON = (SCON & 0x3f) | 0x40;SCON是串行口控制寄存器。这一行将SM0和SM1位设置为01,即模式1:8位UART,波特率可变(由定时器1提供)。同时保留SM2、REN、TB8、RB8等其他位的原有状态(通过& 0x3f清除高2位后,再置位0x40)。这种“读-修改-写”操作是单片机编程的典范,避免误改无关位。 4.2 定时器1作为波特率发生器8051的串口模式1波特率来源于定时器1的溢出率。AI8051U支持将定时器1配置为1T模式(每个时钟周期计数一次,而非传统的12分频),以提高波特率精度。 AUXR |= (1<<6); // T1x12=1,定时器1时钟1T模式
AUXR &= ~S1BRT; // 清除S1BRT位,选择定时器1作为串口1波特率发生器AUXR是辅助寄存器。位6为T1x12,写1后定时器1的时钟就等于系统主频,不再进行12分频。位3为S1BRT,写0表示用定时器1,写1则使用独立波特率发生器(AI8051U新增)。这里清0,选择了经典的定时器1方案。 4.3 波特率重载值计算
TL1 = TM;TH1 = TM>>8;TR1 = 1;TM我们前面已经定义过: #define TM (65536 - (MAIN_Fosc / Baudrate / 4))为什么这个公式成立?在定时器1的1T模式、串口模式1、SMOD=0(默认不倍频)的情况下,波特率与定时器溢出率的关系为: Baudrate=SYSCLK/(4x(65536−TH1:TL1)) 将Baudrate=115200,SYSCLK=33177600代入: 65536−RL=33177600/(4×115200)=72
RL=65536−72=6546465464=0xFFB8 宏TM计算的就是这个值。这正是主频33.1776MHz能够精准产生115200波特率的原因——72是个整数,没有误差!倘若使用12MHz晶振,无论如何也得不到115200的零误差,帧错误率会非常高。这个例子完美诠释了为什么通信系统中要选用“神奇频率”。 最后TR1 = 1启动定时器1,串口立刻开始工作。此后在main中的printf就能够顺利将“金水32051编译器”发送到PC端了。
第五部分 系统初始化:为奔跑做好所有准备在进入应用逻辑前,main()函数开头的一大段寄存器操作,称为“系统初始化”。它们的作用就像交响乐开始前的调音,缺一不可。 5.1 中断总开关
EA = 0;EA是中断总允许位,先将其清零,确保在初始化过程中不被任何中断打断。配置完全部硬件、进入主循环前,如果有需要可以重新打开EA并开启所需的中断源。 用RTOS操作系统的话来说,单片机开机的硬件初始化过程是一个不容许被打断的过程,是一个临界区,关闭总中断是一个最高级别的临界区保护措施。 虽然单片机冷启动时EA是处于关闭状态,但是由于程序BUG导致的系统重启时(比如中断堆栈溢出)从地址0x0000执行,中断却有可能是开着的,所以单片机程序的man()函数开头关闭总中断是笔者以沉痛代价换来的经验。 5.2 CPU状态字和扩展寄存器访问
PSW = 0;
P_SW2 |= 0x80; // EAXFR=1PSW是程序状态字,保存了进位、半进位、寄存器组选择等标志。初始化为0,意味着使用第0组寄存器组,无任何算术标志置位。P_SW2的位7是EAXFR,当它为1时,允许访问XFR(扩展特殊功能寄存器),也就是地址超过0x97的寄存器空间。AI8051U的很多高级寄存器(包括WTST、CKCON、端口模式控制等)都在扩展RAM区域,必须先使能EAXFR才能配置。 5.3 速度优化寄存器
WTST = 0; // 程序指令延时等待
CKCON = 0; // 提高访问XRAM速度WTST是程序存储器等待时间控制寄存器。在较高频率下,CPU可能需要插入等待周期以保证从Flash中稳定取指。赋值为0表示不插入等待,这是AI8051U在33.1776MHz下支持的极速模式。CKCON的某些位控制着片内XRAM的访问时序,赋0可使访问XRAM的速度最快。这两项设置决定了单片机能否火力全开,发挥1T内核的真正实力。 5.4 I/O端口模式的统一配置
P0M1 = 0x00; P0M0 = 0x00;
P1M1 = 0x00; P1M0 = 0x00;
// ... 一直到 P7M1, P7M0AI8051U的每一个I/O口都可以独立配置为四种模式:准双向口(传统8051模式)、推挽输出、仅输入(高阻)、开漏输出。这些模式由两个寄存器PxM1和PxM0控制: - 00:准双向口(最强驱动力适中,可承受外部强行拉低,适合直接驱动LED)
- 01:推挽输出(强上拉和强下拉)
- 10:高阻输入
- 11:开漏输出
将所有端口均设为00(准双向口),意味着完全兼容传统8051的I/O特性,可以直接点亮LED、读取按键而无需额外处理。这也是一种“最安全”的默认状态,大大降低初学者因端口模式配置错误造成的硬件损伤风险。
第六部分 程序LOGO与延时测试——第一次说“Hello”系统初始化完成后,通常我们需要一个人机交互的信号,证明单片机已经活过来了。这就是“LOGO”的由来。 for(i=0; i<3; i++) {
P0=0x00;
P1=0x00;
P2=0x00;
JSx51_Delay_MS(50);
P0=0xFF;
P1=0xFF;
P2=0xFF;
JSx51_Delay_MS(500);
}
printf("\r\n\r\n金水32051编译器\r\n");开始时,端口P0、P1、P2全部输出低电平(0x00),如果这些端口接了LED(低电平驱动发光),则会全亮。延时50ms后,全部输出高电平(0xFF),灯灭,并延时500ms。如此反复3次后,产生三下快速的“闪烁”效果,类似于开机自检灯。 随后,printf输出字符串,通过串口调试助手可以看到“金水32051编译器”几个字,自带两个换行保证清晰。这便是嵌入式的“Hello, World!”,同时测试了串口发送功能。 注释中保留了一段被注释掉的for(;;)循环: // for(;;) { // 软件延时测试
// ...//
}
这是用户调试时可能用到的:通过不断全亮、全灭来观察软件延时的计时是否稳定,用示波器或肉眼大致验证软件延时的准确性。一旦确认无误,便可注释掉,进入正式任务。 第七部分 后台任务主循环与跑马灯艺术LOGO展示完毕后,程序进入一个无限循环Main_Loop。这也是嵌入式软件的经典形态——“初始化 + 超级循环”。 Main_Loop:
printf("\r\n跑马灯开始");
// 跑马灯实现 ...
goto Main_Loop;
7.1 跑马灯:最经典的入门算法跑马灯(也叫流水灯)是指一排LED依次点亮,形成光点流动的效果。在我们的代码中,P0、P1、P2三个端口同时驱动三组LED: m = 0x1;
for(i=0; i<8; i++) {
P0 = ~m;
P1 = ~m;
P2 = ~m;
JSx51_Delay_MS(100);
m = m * 2;
}逻辑非常清晰。变量m的初始值为0x01(0000 0001),然后循环8次,每次: - 将m按位取反后送给端口。因为LED低电平点亮,所以~m为0xFE(1111 1110)时,最低位LED亮;
- 延时100毫秒,让人眼观察到状态;
- m左移一位(乘以2),依次变为0x02、0x04 … 0x80,点亮的LED位置依次从低位移动到高位。
这样,每个端口上就出现了依次点亮的效果。三个端口同步动作,可以保证不论初学者使用什么样的开发板,只要这3个端口上连有LED,就可以看到效果,如果硬件上接了LED阵列,会非常壮观。 7.2 goto的使用:争议与合理很多教材会告诉你“避免使用goto”,但在极简单的超级循环中,goto Main_Loop清晰声明了跳转意向,且不会造成深层的嵌套和混乱。也可以用while(1)代替,完全等同。选择goto在这里更多是风格偏好,本质是形成一个永不退出的循环,持续执行跑马灯。初学者可以试着把它换成while(1){...},编译后看看汇编语言是否一致,答案是在汇编语言中两者是一模一样。 从本质上来说,所有的CPU指令集只有与C语言“goto”对应的“JMP”跳转指令,并且裸机程序的中断过程和RTOS的任务切换的本质也是“goto”对应的“JMP”跳转过程,因此学好单片机编程,必须学好“goto”语句。 7.3 主循环中的打印与实时性你可能注意到循环中还有一句printf。这意味着每进行一次跑马灯循环,都会在串口中打印一次“跑马灯开始”。这种操作在开发调试期非常常用,但要注意printf在115200波特率下发送一串汉字需要较长时间(大约几毫秒),会轻微影响流水灯的延时精度。在实际产品中我们通常会将调试输出关闭或减少,以确保控制时序稳定。这里展示的正好是调试与功能并存的典型形态。
总结从最初的茫然看着数个#include,到现在能够头头是道地解析每个寄存器的配置,你已经完成了一次深刻的8051单片机学习旅程。我们回顾一下本文的核心收获: - AI8051U是一款高速1T 8051单片机,在8BIT模式下完全兼容传统指令集,同时拥有可调的多级速度优化寄存器;
- 金水32051编译器提供了优秀的内嵌汇编接口、完善的STDIO库,让开发效率大幅提升;
- 头文件是硬件的抽象,它把枯燥的地址变成有意义的符号,是工程可维护性的基石;
- 精确软件延时函数演示了内嵌汇编的强大,通过精心设计的指令序列实现了与硬件定时器媲美的精度,这是算法与底层功底的结合;
- 串口初始化过程清晰地示范了如何通过定时器1和AUXR寄存器产生零误差的115200波特率,频率选择背后的数学逻辑令人着迷;
- 系统初始化顺序(关中断→配置CPU→端口模式→外设启动)体现了嵌入式开发的严谨性;
- LOGO与跑马灯则让冰冷的硬件发出了光芒,同时也在主循环结构中留下了灵活的扩展接口。
或许你会觉得33.1776MHz、EAXFR、1T模式这些名词还有些生涩,但只要亲手编译、下载,看着LED规律地闪烁,听着串口助手“叮咚”弹出调试信息,理论便能瞬间转化为会心一笑。这正是嵌入式开发最大的魅力。 课后练习理论需要实践的浇灌,请在开发板上完成以下任务,巩固所学知识。 练习一:修改延时时间,观察流水灯速度变化找到JSx51_Delay_MS(100),将100改为50、200、500,编译下载。观察跑马灯流动速度的变化,并能简单解释为什么延时常数直接影响视觉效果。 练习二:改变流水灯花样修改for循环内的逻辑,实现从高位向低位流水(即反向跑马灯)。提示:可以将m初始值设为0x80,然后每次m = m / 2,或使用移位运算符>>。尝试实现一个来回往复的“霹雳灯”效果。 练习三:理解汇编延时,计算一个内层循环的精确时间查阅AI8051U数据手册或编译器文档,确认在WTST=0,CKCON=0时各指令的执行周期数(1T)。手工计算JSx51_Delay_MS_03循环每执行一次消耗的机器周期数,并推导3317这个常数是如何对应到1ms的。写出计算公式和步骤。 练习四:切换到独立波特率发生器AI8051U还有一个独立的波特率发生器BRT,它在某些场合比定时器1更灵活。修改JSx51_Uart_Init,使用BRT产生115200波特率(可查阅芯片手册的公式)。要求串口打印功能依然正常,并验证效果。 练习五:新增按键控制跑马灯方向电路上增加一个按键(如P3.2),在Main_Loop中检测按键状态。当按键按下时,跑马灯反向流动;未按下时,正向流动。需要考虑按键消抖(可再次调用延时函数)。这考察你对端口输入、条件判断与主循环结合的能力。 练习六:代码模块化将JSx51_Delay_MS、JSx51_Uart_Init以及端口初始化等分别移入独立的.c文件和.h文件,形成一个最小化的驱动库。在主程序中通过extern声明或include头文件来调用。完成后,体会模块化工程与单文件巨大差异。
完成以上练习后,你不仅掌握了如何在AI8051U上写出规范的C程序,还具备了独立查阅芯片手册、推导时序公式和构建自己代码库的工程素养。记住,从点亮第一个LED开始,你的每一个debug、每一次寄存器配置,都在为更大的嵌入式梦想铺路。愿你在此刻的跑马灯光芒中,照亮通往复杂系统的坦途。
附件:C语言程序全文/* --------------------------------------------- 金水32051编译器 项目框架 主程序
单片机 AI8051U-8BIT 系统运行主频 33.1776MHz 作者: 杨为民 2026-05 QQ: 86547995
--------------------------------------------- */ // ==== 单片机 类型模式 定义 ============= // ---- 单片机XSFR定义 ---------------- #include "..\C351_XSFR_AI8051U_8BIT.h" // ---- 标准IO库 ------------------------ #include "..\C351_STDIO.h" #include "..\C351_PRTSCN.h" // **** 系统运行设置 ******************* // ==== 系统运行参数 ======================= #define Fosc_KHZ_A51 33177 // 主频 33.1776MHz #define MAIN_Fosc 33177600UL #define Baudrate 115200L #define TM (65536 -(MAIN_Fosc/Baudrate/4)) // ==== 软件延时 毫秒 ======================= void JSx51_Delay_MS(unsigned int MS) {
#pragma asm
// ---- R6R7 参数 ---------------- MOV A, R7; JZ JSx51_Delay_MS_01; INC R6; JSx51_Delay_MS_01: MOV R2, # HIGH (Fosc_KHZ_A51 / 10); // STC8051U_8Bit MOV R3, # LOW (Fosc_KHZ_A51 / 10); // STC8051U_8Bit JSx51_Delay_MS_02: MOV A, R2; MOV R4, A ; MOV A, R3; MOV R5, A ;
JSx51_Delay_MS_03: MOV A, R5 ; DEC R5 ; JNZ JSx51_Delay_MS_04 ; DEC R4 ; JSx51_Delay_MS_04: DEC A ; ORL A, R4 ; JNZ JSx51_Delay_MS_03 ; DJNZ R7, JSx51_Delay_MS_02 ; DJNZ R6, JSx51_Delay_MS_02 ; #pragma endasm
} // ==== 串口1 设置 ======================= void JSx51_Uart_Init(void) { SCON = (SCON & 0x3f) | 0x40; AUXR |= (1<<6); //定时器时钟1T模式 AUXR &= ~S1BRT; //串口1选择定时器1为波特率发生器 TL1 = TM; TH1 = TM>>8; TR1 = 1; //定时器1开始计时 } // **** C351 主函数 单片机入口 **************** void main(void){ int i, m;
// ==== 单片机 CPU 初始化 =========== // ---- 明显关闭中断 开始设置 ----------- EA=0;
// ---- CPU 设置 ----------- PSW=0; P_SW2 |= 0x80; // EAXFR=1 扩展寄存器(XFR)访问使能
WTST = 0; //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快 CKCON = 0; //提高访问XRAM速度 // ==== 单片机系统 硬件设置 ============ P0M1 = 0x00; P0M0 = 0x00; //设置为准双向口 P1M1 = 0x00; P1M0 = 0x00; //设置为准双向口 P2M1 = 0x00; P2M0 = 0x00; //设置为准双向口 P3M1 = 0x00; P3M0 = 0x00; //设置为准双向口 P4M1 = 0x00; P4M0 = 0x00; //设置为准双向口 P5M1 = 0x00; P5M0 = 0x00; //设置为准双向口 P6M1 = 0x00; P6M0 = 0x00; //设置为准双向口 P7M1 = 0x00; P7M0 = 0x00; //设置为准双向口 JSx51_Uart_Init();
// ==== LOGO ============ // for(;;) { // 软件延时测试 for(i=0;i<3;i++) { // LOGO P0=0x00; P1=0x00; P2=0x00; JSx51_Delay_MS(50); P0=0xFF; P1=0xFF; P2=0xFF; JSx51_Delay_MS(500); } printf("\r\n\r\n金水32051编译器\r\n"); // }
// ==== 任务主循环 =========================== Main_Loop: printf("\r\n跑马灯开始"); // ---- 跑马灯 ----------------------- m=0x1; for(i=0;i<8;i++) { P0=~m; P1=~m; P2=~m; JSx51_Delay_MS(100); m=m*2; } // ---- 主循环 结束 ----------------- goto Main_Loop;
}
|