admin 发表于 2025-4-14 15:13:34

汇编语言 实验
2.18.6 深大-点灯实验4(使能Ai8051U内置 USB直接 仿真流水灯)
实验程序汇编语言代码





;以下是控制P6.0~P6.7引脚上一个LED灯循环亮灭跑马灯的汇编语言程序代码。
;特别注意延时子程序汇编语言写法。试通过每一条指令的所花的机器时钟周期计算一下软件延时时间。
;251汇编程序

$INCLUDE(AI8051U.INC)

STACK_POIRTER   EQU   0D0H    ;堆栈开始地址

        ORG   0000H
        LJMP    MAIN
        ORG   0100H

MAIN:
        SETB    EAXFR         ;允许访问扩展的特殊寄存器,XFR
        MOV   WTST,#00H       ;设置取程序代码等待时间,赋值为 0 表示不等待,程序以最快速度运行
        MOV   CKCON,#00H      ;设置访问片内的 xdata 速度,赋值为 0 表示用最快速度访问,不增加额外的等待时间
        MOV   SP, #STACK_POIRTER        ;堆栈栈顶设置
        ;端口初始化
        CLR                  A
        MOV                P0M1, A         ;设置P0为准双向口
        MOV                P0M0, A
        MOV                P1M1, A         ;设置P1为准双向口
        MOV                P1M0, A
        MOV                P2M1, A         ;设置P2为准双向口
        MOV                P2M0, A
        MOV                P3M1, A
        MOV                P3M0, A
        MOV                P4M1, A
        MOV                P4M0, A
        MOV                P5M1, A
        MOV                P5M0, A
        MOV                P6M1, A
        MOV                P6M0, A
        MOV                P7M1, A
        MOV                P7M0, A
        MOV               P0, #0FFH
        ;变量初始化
        CLR                P40                        ;流水灯总开关打开

LOOP:
        MOV                P0,#00H
        ACALL   DELAY500MS        ;调用延时子程序
        MOV                P0,#0FFH
        ACALL   DELAY500MS        ;调用延时子程序

        CLR                P00                ;给P6^0清零,低电平时led亮
        ACALL   DELAY500MS        ;调用延时子程序
        SETB    P00                        ;给P6^0置1,高电平时led灭

        CLR                P01
        ACALL   DELAY500MS        ;调用延时子程序
        SETB    P01
       
        CLR                P02
        ACALL   DELAY500MS        ;调用延时子程序
        SETB    P02
       
        CLR   P03
        ACALL   DELAY500MS        ;500MS;调用延时子程序
        SETB    P03
       
        CLR   P04
        ACALL   DELAY500MS        ;调用延时子程序
        SETB    P04
       
        CLR   P05
        ACALL   DELAY500MS        ;调用延时子程序
        SETB    P05
       
        CLR   P06
        ACALL   DELAY500MS        ;调用延时子程序
        SETB    P06       
       
        CLR                P07
        ACALL   DELAY500MS
        SETB    P07
       
        SJMP    LOOP                ;无条件跳转至LOOP处

DELAY500MS:                                ;延时0.5S子程序@40.000MHz
        PUSH        DR0                        ;保护现场,将双字寄存器DR0(即R0~R3)推入堆栈保存
        MOV                WR0,#76         ;将WR0赋值76(思考R0=?R1=?)
        MOV                WR2,#19262        ;将WR2赋值19262(思考R2=?R3=?)
NEXT:
        DEC                DR0,#1        ;DR0<-(DR0)-1
        JNE                NEXT                ;(DR0)≠0,则跳至NEXT标号处即继续-1,知道等于0则执行下一条语句
        POP                DR0                        ;恢复现场
        RET                     ;子程序返回
        ;-----------------

END                                                ;汇编程序结束伪指令语句

神农鼎 发表于 2025-4-14 15:33:51

C51语言如何嵌入汇编



嗯,我现在需要了解如何在C51语言中嵌入汇编代码。这个问题我之前没有接触过,所以得从头开始学。首先,我得回忆一下C51是什么。C51应该是指用于8051系列单片机的C语言编译器,对吧?那嵌入汇编通常是为了优化性能或者直接操作硬件,这在单片机编程中很常见。
那问题来了,如何在C51中嵌入汇编呢?根据我之前学过的其他编程语言,比如在GCC中使用内联汇编,可能C51也有类似的方法。不过不同的编译器可能有不同的语法,所以我得查一下C51的具体实现方式。
首先,我记得在C语言中嵌入汇编通常有两种方法:内联汇编和单独的汇编模块。在C51中,可能也是这两种方式。不过可能具体的语法会有所不同。比如,在Keil C51中,可能会有特定的关键字或者预处理指令来嵌入汇编代码。
然后,我需要考虑如何在函数内部插入汇编指令。比如,是否有类似于__asm关键字,或者需要用#asm和#endasm这样的预处理指令?比如,在Keil的文档中,可能会提到使用#pragma asm和#pragma endasm来包裹汇编代码,但需要设置编译器选项,比如在项目设置中启用SRC选项生成汇编源文件,然后再编译。
另外,嵌入汇编的时候需要注意寄存器的使用,是否会影响C代码的变量。例如,可能需要保存和恢复某些寄存器,或者使用特定的语法将C变量与汇编操作数绑定。例如,使用_ACC_来表示累加器A,或者直接通过变量名来访问。
另外,单独的汇编文件可能更复杂一些,需要将汇编代码写在独立的.asm文件中,然后在C代码中声明这些函数的原型,通过链接器将它们链接在一起。这可能涉及到调用约定、参数传递等问题,比如参数是通过寄存器还是堆栈传递,返回值如何存放等等。
举个例子,如果我想在C51的函数中插入一段汇编代码来操作端口,可能需要这样写:
void my_function() {#pragma asmMOV A, #0FFhMOV P1, A#pragma endasm}
但可能需要在编译器选项中启用生成SRC文件,或者在项目设置里允许内联汇编。否则编译器可能会报错。
不过,我之前可能混淆了不同的编译器的语法。比如,有些编译器可能使用__asm { ... }这样的结构,而C51可能使用不同的方式。所以可能需要查阅Keil C51的手册或者相关资料,确认正确的语法。
另外,嵌入汇编时还需要考虑中断处理的问题,如果在中断服务例程中嵌入汇编,可能需要特别处理寄存器的保存,或者使用特定的编译器扩展。
还有,变量的访问,比如如何在汇编代码中使用C中定义的变量。可能需要使用特定的符号或者语法,例如用变量名前加下划线,或者通过DPTR来访问数据存储器中的变量。
比如,在汇编中访问C变量var,可能需要这样:
MOV A, _var
或者,如果var位于特定的存储区域,可能需要使用不同的指令。
总的来说,在C51中嵌入汇编的步骤可能包括:
1. 在C函数中使用特定的预处理指令包裹汇编代码,比如#pragma asm和#pragma endasm。2. 配置编译器选项,允许生成汇编源文件,并正确编译。3. 在汇编代码中正确使用寄存器和访问C变量,注意不要破坏C环境。4. 处理函数调用时的参数传递和返回值。
不过具体的细节可能需要查阅C51的编译器手册,或者寻找相关的示例代码。例如,是否需要为嵌入的汇编代码保存和恢复某些寄存器,或者编译器是否自动处理这些。
另外,可能有一些限制,比如不能在所有的函数中嵌入汇编,或者某些优化选项可能与内联汇编冲突。
现在,我应该总结这些可能的步骤,并且给出一个具体的例子,说明如何在C51中嵌入汇编代码,同时提到需要注意的事项,比如编译器设置、寄存器使用、变量访问等。
比如,可能的步骤是:
1. 在C文件中使用#pragma asm和#pragma endasm包裹汇编代码。2. 在Keil中,需要将源文件添加到工程,并右键点击该文件,设置Options,勾选Generate Assembler SRC File和Assemble SRC File选项。3. 在汇编代码中,可以通过_ACC_访问累加器,通过变量名前加下划线访问C变量。4. 注意寄存器的使用,避免影响C代码的执行。
例如,一个具体的例子:
#include <reg51.h>
void delay() {#pragma asmMOV R0, #100LOOP:DJNZ R0, LOOP#pragma endasm}
然后在项目设置中启用生成SRC文件,并确保编译器能够正确编译。
不过,可能还需要在文件开头包含某些头文件,或者声明寄存器的地址,比如reg51.h定义了P1等端口。
总结的话,嵌入汇编需要特定的编译器指令和项目设置,正确使用寄存器和变量,以及注意不要破坏C环境的状态。

在C51语言中嵌入汇编代码可以通过以下步骤实现,主要用于优化关键代码或直接硬件操作:方法一:使用预处理指令内联汇编
[*]编写C函数并嵌入汇编代码
使用#pragma asm和#pragma endasm指令包裹汇编代码:c
复制



#include <reg51.h>// 包含寄存器定义void port_control() {    #pragma asm    MOV A, #0FFh    ; 将立即数0xFF加载到累加器A    MOV P1, A       ; 将A的值输出到P1端口    #pragma endasm}

[*]配置编译器选项(Keil环境)
[*]右键点击C文件,选择“Options for File”。
[*]勾选“Generate Assembler SRC File”和“Assemble SRC File”,以允许生成并编译汇编代码。

[*]注意事项
[*]寄存器使用:避免随意修改可能被C代码使用的寄存器(如R0-R7),必要时保存恢复。
[*]变量访问:C变量在汇编中通过名称加下划线访问,如MOV A, _myVar。
[*]中断处理:在中断服务例程中嵌入汇编时,需手动保存受影响寄存器。

方法二:调用独立汇编模块
[*]编写独立汇编文件
创建.asm文件并定义函数:assembly
复制



; delay.asmPUBLIC DELAY; 声明函数为公共DELAY SEGMENT CODERSEG DELAY    MOV R2, #100LOOP:    DJNZ R2, LOOP    RET

[*]在C中声明并调用函数c
复制



extern void DELAY(void);// 声明外部汇编函数void main() {    DELAY();// 调用汇编函数}

关键注意事项
[*]参数传递:参数通过寄存器(如R7、R5)传递,返回值在ACC或寄存器中。
[*]内存模型:确保汇编代码与C代码的内存模型(SMALL、COMPACT、LARGE)匹配。
[*]优化冲突:高优化级别可能导致内联汇编错误,建议调试阶段关闭优化。
示例:混合C与汇编实现延时c
复制



#include <reg51.h>void asm_delay(unsigned char count) {    #pragma asm    MOV R0, _count; 使用C参数countLOOP:    DJNZ R0, LOOP    #pragma endasm}void main() {    while(1) {      P1 = 0x00;      // C代码控制端口      asm_delay(255); // 调用内联汇编延时      P1 = 0xFF;      asm_delay(255);    }}
常见问题解决
[*]编译错误:未启用SRC生成选项导致#pragma asm不被识别。
[*]变量未定义:汇编中访问C变量时需确保已正确声明且作用域可见。
[*]寄存器冲突:在嵌入代码前后使用PUSH/POP保护关键寄存器(如PSW、ACC)。
通过上述方法,可在C51项目中高效结合C与汇编,兼顾开发效率与性能需求。

















神农鼎 发表于 2025-4-16 10:36:19

AiCube-可视化 图形 I/O配置 体验
https://v.stcai.com/sv/e40e76b-1963e22a9cc/e40e76b-1963e22a9cc.mp4

利用 AiCube@AIapp-ISP V6.95M,
智能配置开发工具启动程序框架自动生成器,实现如下的实验:
32位8051单片机入门的第一个完整的C语言程序,printf_usb("Hello World !\r\n")
https://v.stcai.com/sv/426d0d7d-1963d548feb/426d0d7d-1963d548feb.mp4
AiCube 实验一:直接 printf_usb("Hello World !\r\n")
32位8051单片机入门的第一个完整的C语言程序

AiCube 实验二:查询方式,查询到电脑命令后,
                           printf_usb("Hello World !\r\n")或其他

AiCube 实验三:中断方式,单片机USB中断接收服务函数收到命令后,
                           printf_usb("Hello World !\r\n")或其他

乘风飞扬 发表于 2025-4-21 14:46:35

通过IO口控制74HC595驱动8个共阴极数码管


C语言:
/*---------------------------------------------------------------------*/
/* --- Web: www.STCAI.com ---------------------------------------------*/
/*---------------------------------------------------------------------*/

/*************功能说明    **************

本例程基于AI8051U为主控芯片的实验箱进行编写测试.

使用Keil C251编译器,Memory Model推荐设置XSmall模式,默认定义变量在edata,单时钟存取访问速度快。

edata建议保留1K给堆栈使用,空间不够时可将大数组、不常用变量加xdata关键字定义到xdata空间。

用STC的MCU的IO方式控制74HC595驱动8位数码管。

显示效果为: 数码时钟.

使用Timer0的16位自动重装来产生1ms节拍,程序运行于这个节拍下, 用户修改MCU主时钟频率时,自动定时于1ms.

下载时, 选择时钟 24MHZ (用户可自行修改频率).

******************************************/

#include "..\..\comm\AI8051U.h"
#include "stdio.h"
#include "intrins.h"

typedef         unsigned char        u8;
typedef         unsigned int        u16;
typedef         unsigned long        u32;

#define MAIN_Fosc      24000000UL

//==========================================================================

#define Timer0_Reload   (65536UL -(MAIN_Fosc / 1000))       //Timer 0 中断频率, 1000次/秒

#define DIS_DOT   0x20
#define DIS_BLACK   0x10
#define DIS_      0x11

/*************本地常量声明    **************/
u8 code t_display[]={                     //标准字库
//   0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
    0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black-   H    J    K    L    N    o   P    U   t    G    Q    r   M    y
    0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
    0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46};    //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1

u8 code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};      //位码

/*************IO口定义    **************/
sbit    P_HC595_SER   = P3^4;   //pin 14    SER   data input
sbit    P_HC595_RCLK= P3^5;   //pin 12    RCLk    store (latch) clock
sbit    P_HC595_SRCLK = P3^2;   //pin 11    SRCLK   Shift data clock

/*************本地变量声明    **************/

u8LED8;      //显示缓冲
u8display_index;//显示位索引
bit B_1ms;          //1ms标志

u8hour,minute,second;
u16 msecond;


/*************本地函数声明    **************/


/****************外部函数声明和外部变量声明 *****************/


/********************** 显示时钟函数 ************************/
void DisplayRTC(void)
{
    if(hour >= 10)LED8 = hour / 10;
    else            LED8 = DIS_BLACK;
    LED8 = hour % 10;
    LED8 = DIS_;
    LED8 = minute / 10;
    LED8 = minute % 10;
    LED8 = DIS_;
    LED8 = second / 10;
    LED8 = second % 10;
}

/********************** RTC演示函数 ************************/
void RTC(void)
{
    if(++second >= 60)
    {
      second = 0;
      if(++minute >= 60)
      {
            minute = 0;
            if(++hour >= 24)    hour = 0;
      }
    }
}

/******************** 主函数 **************************/
void main(void)
{
    u8i,k;
   
    WTST = 0;//设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
    EAXFR = 1; //扩展寄存器(XFR)访问使能
    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;   //设置为准双向口

    T0x12 = 1;//Timer0 set as 1T, 16 bits timer auto-reload,
    TH0 = (u8)(Timer0_Reload / 256);
    TL0 = (u8)(Timer0_Reload % 256);
    ET0 = 1;    //Timer0 interrupt enable
    TR0 = 1;    //Tiner0 run
    EA = 1;   //打开总中断
   
    display_index = 0;
    hour   = 11;    //初始化时间值
    minute = 59;
    second = 58;
    RTC();
    DisplayRTC();
   
//for(i=0; i<8; i++)LED8 = DIS_BLACK;    //上电消隐
    for(i=0; i<8; i++)LED8 = i;    //显示01234567
    k = 0;

    while(1)
    {
      if(B_1ms)   //1ms到
      {
            B_1ms = 0;
            if(++msecond >= 1000)   //1秒到
            {
//                P42 = !P42;
                msecond = 0;
                RTC();
                DisplayRTC();
            }
      }
    }
}

/**************** 向HC595发送一个字节函数 ******************/
void Send_595(u8 dat)
{
    u8i;
    for(i=0; i<8; i++)
    {
      dat <<= 1;
      P_HC595_SER   = CY;
      P_HC595_SRCLK = 1;
      P_HC595_SRCLK = 0;
    }
}

/********************** 显示扫描函数 ************************/
void DisplayScan(void)
{
    Send_595(t_display]);   //输出段码
    Send_595(~T_COM);            //输出位码

    P_HC595_RCLK = 1;
    P_HC595_RCLK = 0;
    if(++display_index >= 8)    display_index = 0;//8位结束回0
}

/********************** Timer0 1ms中断函数 ************************/
void timer0 (void) interrupt 1
{
    DisplayScan();//1ms扫描显示一位
    B_1ms = 1;      //1ms标志

}


汇编:
;/*---------------------------------------------------------------------*/
;/* --- Web: www.STCAI.com ---------------------------------------------*/
;/*---------------------------------------------------------------------*/

;*************功能说明    **************

;本例程基于AI8051U为主控芯片的实验箱进行编写测试。

;使用Keil C251编译器,Memory Model推荐设置XSmall模式,默认定义变量在edata,单时钟存取访问速度快。

;data建议保留1K给堆栈使用,空间不够时可将大数组、不常用变量加xdata关键字定义到xdata空间。

;用STC的MCU的IO方式控制74HC595驱动8位数码管。

;显示效果为: 数码时钟.

;使用Timer0的16位自动重装来产生1ms节拍,程序运行于这个节拍下, 用户修改MCU主时钟频率时,自动定时于1ms.

;下载时, 选择时钟 24MHZ (用户可自行修改频率).

;******************************************/

$include (../../comm/AI8051U.INC)

;/****************************** 用户定义宏 ***********************************/
Fosc_KHZ    EQU 24000   ;24000KHZ

STACK_POIRTER   EQU   0D0H    ;堆栈开始地址

Timer0_Reload   EQU   (65536 - Fosc_KHZ); Timer 0 中断频率, 1000次/秒

DIS_DOT         EQU   020H
DIS_BLACK       EQU   010H
DIS_            EQU   011H

;*******************************************************************
;*******************************************************************

;*************IO口定义    **************/
P_HC595_SER   BIT   P3.4;   //pin 14    SER   data input
P_HC595_RCLK    BIT   P3.5;   //pin 12    RCLk    store (latch) clock
P_HC595_SRCLK   BIT   P3.2;   //pin 11    SRCLK   Shift data clock

;*************本地变量声明    **************/
Flag0         DATA    20H
B_1ms         BIT   Flag0.0 ; 1ms标志

LED8            DATA    30H   ; 显示缓冲 30H ~ 37H
display_index   DATA    38H   ; 显示位索引

usrhour         DATA    39H   ;
usrminute       DATA    3AH
usrsecond       DATA    3BH   ;
msecond         DATA    3CH   ; 2 byte

;*******************************************************************
;*******************************************************************
      ORG   0000H               ;程序复位入口,编译器自动定义到 0FF0000H 地址
      LJMP    F_Main

      ORG   000BH               ;1Timer0 interrupt
      LJMP    F_Timer0_Interrupt

;*******************************************************************
;*******************************************************************


;******************** 主程序 **************************/
    ORG   0100H          ;编译器自动定义到 0FF0100H 地址
F_Main:
    MOV   WTST, #00H   ;设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
    MOV   CKCON,#00H   ;提高访问XRAM速度
    ORL   P_SW2,#080H    ;使能访问XFR

    MOV   P0M1, #00H   ;设置为准双向口
    MOV   P0M0, #00H
    MOV   P1M1, #00H   ;设置为准双向口
    MOV   P1M0, #00H
    MOV   P2M1, #00H   ;设置为准双向口
    MOV   P2M0, #00H
    MOV   P3M1, #00H   ;设置为准双向口
    MOV   P3M0, #00H
    MOV   P4M1, #00H   ;设置为准双向口
    MOV   P4M0, #00H
    MOV   P5M1, #00H   ;设置为准双向口
    MOV   P5M0, #00H
    MOV   P6M1, #00H   ;设置为准双向口
    MOV   P6M0, #00H
    MOV   P7M1, #00H   ;设置为准双向口
    MOV   P7M0, #00H

    MOV   SP, #STACK_POIRTER
    MOV   PSW, #0
    USING   0       ;选择第0组R0~R7

;================= 用户初始化程序 ====================================

    MOV   display_index, #0
    MOV   R0, #LED8
    MOV   R2, #8
L_ClearLoop:
    MOV   @R0, #DIS_BLACK   ;上电消隐
    INC   R0
    DJNZ    R2, L_ClearLoop
   
    CLR   TR0
    ORL   AUXR, #(1 SHL 7)    ; Timer0_1T();
    ANL   TMOD, #NOT 04H      ; Timer0_AsTimer();
    ANL   TMOD, #NOT 03H      ; Timer0_16bitAutoReload();
    MOV   TH0, #Timer0_Reload / 256   ;Timer0_Load(Timer0_Reload);
    MOV   TL0, #Timer0_Reload MOD 256
    SETB    ET0         ; Timer0_InterruptEnable();
    SETB    TR0         ; Timer0_Run();
    SETB    EA          ; 打开总中断
   
    MOV   usrhour,   #12 ; 初始化时间值
    MOV   usrminute, #0
    MOV   usrsecond, #0
    LCALL   F_DisplayRTC
   
;=================== 主循环 ==================================
L_Main_Loop:
    JNB   B_1ms,L_Main_Loop   ;1ms未到
    CLR   B_1ms

    MOV   WR6, msecond
    INC   WR6, #1         ;msecond + 1
    MOV   msecond, WR6
    CMP   WR6, #1000
    JC      L_Main_Loop   ;if(msecond < 1000), jmp

    MOV   WR6, #0
    MOV   msecond, WR6    ;msecond = 0

    LCALL   F_RTC
    LCALL   F_DisplayRTC

    LJMP    L_Main_Loop

;========================================================================
; 函数: F_DisplayRTC
; 描述: 显示时钟子程序。
; 参数: none.
; 返回: none.
; 版本: VER1.0
; 日期: 2013-4-1
; 备注: 除了ACCC和PSW外, 所用到的通用寄存器都入栈
;========================================================================
F_DisplayRTC:
    PUSH    B       ;B入栈
   
    MOV   A, usrhour
    MOV   B, #10
    DIV   AB
    MOV   LED8, A
    MOV   LED8+1, B
    MOV   LED8+2, #DIS_;
   
    MOV   A, usrminute
    MOV   B, #10
    DIV   AB
    MOV   LED8+3, A;
    MOV   LED8+4, B;
    MOV   LED8+5, #DIS_;

    MOV   A, usrsecond
    MOV   B, #10
    DIV   AB
    MOV   LED8+6, A;
    MOV   LED8+7, B;
   
    POP   B       ;B出栈
    RET

;========================================================================
; 函数: F_RTC
; 描述: RTC演示子程序。
; 参数: none.
; 返回: none.
; 版本: VER1.0
; 日期: 2013-4-1
; 备注: 除了ACCC和PSW外, 所用到的通用寄存器都入栈
;========================================================================
F_RTC:
    INC   usrsecond      ; usrsecond + 1
    MOV   A, usrsecond
    CLR   C
    SUBB    A,#60
    JC      L_QuitRTC   ; usrsecond >= 60?
    MOV   usrsecond, #0;

    INC   usrminute      ; usrminute + 1
    MOV   A, usrminute
    CLR   C
    SUBB    A,#60
    JC      L_QuitRTC   ; usrminute >= 60?
    MOV   usrminute, #0

    INC   usrhour      ; usrhour + 1
    MOV   A, usrhour
    CLR   C
    SUBB    A,#24
    JC      L_QuitRTC   ; usrhour >= 24?
    MOV   usrhour, #0
L_QuitRTC:
    RET


; *********************** 显示相关程序 ****************************************
T_Display:                      ;标准字库
;    0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
DB03FH,006H,05BH,04FH,066H,06DH,07DH,007H,07FH,06FH,077H,07CH,039H,05EH,079H,071H
;black-    H    J    K    L    N    o    P    U    t    G    Q    r    M    y
DB000H,040H,076H,01EH,070H,038H,037H,05CH,073H,03EH,078H,03dH,067H,050H,037H,06EH
;    0.   1.   2.   3.   4.   5.   6.   7.   8.   9.   -1
DB0BFH,086H,0DBH,0CFH,0E6H,0EDH,0FDH,087H,0FFH,0EFH,046H

T_COM:
DB001H,002H,004H,008H,010H,020H,040H,080H   ;   位码

;========================================================================
; 函数: F_Send_595
; 描述: 向HC595发送一个字节子程序。
; 参数: ACC: 要发送的字节数据.
; 返回: none.
; 版本: VER1.0
; 日期: 2024-4-1
; 备注: 除了ACCC和PSW外, 所用到的通用寄存器都入栈
;========================================================================
F_Send_595:
    PUSH    02H   ;R2入栈
    MOV   R2, #8
L_Send_595_Loop:
    RLC   A
    MOV   P_HC595_SER,C
    SETB    P_HC595_SRCLK
    CLR   P_HC595_SRCLK
    DJNZ    R2, L_Send_595_Loop
    POP   02H   ;R2出栈
    RET

;========================================================================
; 函数: F_DisplayScan
; 描述: 显示扫描子程序。
; 参数: none.
; 返回: none.
; 版本: VER1.0
; 日期: 2013-4-1
; 备注: 除了ACCC和PSW外, 所用到的通用寄存器都入栈
;========================================================================
F_DisplayScan:
    PUSH    DPH   ;DPH入栈
    PUSH    DPL   ;DPL入栈
    PUSH    00H   ;R0 入栈
   
    MOV   DPTR, #T_Display
    MOV   A, display_index
    ADD   A, #LED8
    MOV   R0, A
    MOV   A, @R0
    MOVC    A, @A+DPTR
    LCALL   F_Send_595      ;输出段码

    MOV   DPTR, #T_COM
    MOV   A, display_index
    MOVC    A, @A+DPTR
    CPL   A
    LCALL   F_Send_595      ;输出位码
   
    SETB    P_HC595_RCLK
    CLR   P_HC595_RCLK    ;   锁存输出数据
    INC   display_index
    MOV   A, display_index
    ANL   A, #0F8H            ; if(display_index >= 8)
    JZ      L_QuitDisplayScan
    MOV   display_index, #0;;8位结束回0
L_QuitDisplayScan:
    POP   00H   ;R0 出栈
    POP   DPL   ;DPL出栈
    POP   DPH   ;DPH出栈
    RET


;*******************************************************************
;**************** 中断函数 ***************************************************

F_Timer0_Interrupt: ;Timer0 1ms中断函数
    PUSH    PSW   ;PSW入栈
    PUSH    ACC   ;ACC入栈

    LCALL   F_DisplayScan   ; 1ms扫描显示一位
    SETB    B_1ms         ; 1ms标志

    POP   ACC   ;ACC出栈
    POP   PSW   ;PSW出栈
    RETI
   
END




乘风飞扬 发表于 2025-4-21 14:48:33

通过硬件SPI控制74HC595驱动8个共阴极数码管


C语言:
/*---------------------------------------------------------------------*/
/* --- Web: www.STCAI.com ---------------------------------------------*/
/*---------------------------------------------------------------------*/

/*************功能说明    **************

本例程基于AI8051U为主控芯片的实验箱进行编写测试.

使用Keil C251编译器,Memory Model推荐设置XSmall模式,默认定义变量在edata,单时钟存取访问速度快。

edata建议保留1K给堆栈使用,空间不够时可将大数组、不常用变量加xdata关键字定义到xdata空间。

用STC的MCU的SPI接口控制74HC595驱动8位数码管。

显示效果为: 数码时钟.

使用Timer0的16位自动重装来产生1ms节拍,程序运行于这个节拍下, 用户修改MCU主时钟频率时,自动定时于1ms.

下载时, 选择时钟 24MHZ (用户可自行修改频率).

******************************************/

#include "..\..\comm\AI8051U.h"
#include "stdio.h"
#include "intrins.h"

typedef         unsigned char      u8;
typedef         unsigned int      u16;
typedef         unsigned long      u32;

#define MAIN_Fosc      24000000UL

//==========================================================================

#define Timer0_Reload   (65536UL -(MAIN_Fosc / 1000))       //Timer 0 中断频率, 1000次/秒

#define DIS_DOT   0x20
#define DIS_BLACK   0x10
#define DIS_      0x11

/*************本地常量声明    **************/
u8 code t_display[]={                     //标准字库
//   0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
    0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black-   H    J    K    L    N    o   P    U   t    G    Q    r   M    y
    0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
    0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46};    //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1

u8 code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};      //位码

/*************IO口定义    **************/
sbit P_HC595_SER   = P3^4;   //pin 14    SER   data input
sbit P_HC595_RCLK= P3^5;   //pin 12    RCLk    store (latch) clock
sbit P_HC595_SRCLK = P3^2;   //pin 11    SRCLK   Shift data clock

/*************本地变量声明    **************/

u8LED8;      //显示缓冲
u8display_index;//显示位索引
bit B_1ms;          //1ms标志

u8hour,minute,second;
u16 msecond;


/*************本地函数声明    **************/

void SPI_init(void);

/****************外部函数声明和外部变量声明 *****************/


/********************** 显示时钟函数 ************************/
void DisplayRTC(void)
{
    if(hour >= 10)LED8 = hour / 10;
    else            LED8 = DIS_BLACK;
    LED8 = hour % 10;
    LED8 = DIS_;
    LED8 = minute / 10;
    LED8 = minute % 10;
    LED8 = DIS_;
    LED8 = second / 10;
    LED8 = second % 10;
}

/********************** RTC演示函数 ************************/
void RTC(void)
{
    if(++second >= 60)
    {
      second = 0;
      if(++minute >= 60)
      {
            minute = 0;
            if(++hour >= 24)    hour = 0;
      }
    }
}

/******************** 主函数 **************************/
void main(void)
{
    u8i;
   
    WTST = 0;//设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
    EAXFR = 1; //扩展寄存器(XFR)访问使能
    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;   //设置为准双向口

    SPI_init();
   
    T0x12 = 1;//Timer0 set as 1T, 16 bits timer auto-reload,
    TH0 = (u8)(Timer0_Reload / 256);
    TL0 = (u8)(Timer0_Reload % 256);
    ET0 = 1;    //Timer0 interrupt enable
    TR0 = 1;    //Tiner0 run
    EA = 1;   //打开总中断
   
    display_index = 0;
    hour   = 11;    //初始化时间值
    minute = 59;
    second = 58;
    RTC();
    DisplayRTC();
   
//for(i=0; i<8; i++)LED8 = DIS_BLACK;    //上电消隐
    for(i=0; i<8; i++)LED8 = i;    //显示01234567

    while(1)
    {
      if(B_1ms)   //1ms到
      {
            B_1ms = 0;
            if(++msecond >= 1000)   //1秒到
            {
//                P42 = !P42;
                msecond = 0;
                RTC();
                DisplayRTC();
            }
      }
    }
}

/**************** SPI初始化程序 ******************/
void SPI_init(void)
{
    SSIG = 1; //忽略 SS 引脚功能,使用 MSTR 确定器件是主机还是从机
    SPEN = 1; //使能 SPI 功能
    DORD = 0; //先发送/接收数据的高位( MSB)
    MSTR = 1; //设置主机模式
    CPOL = 1; //SCLK 空闲时为高电平,SCLK 的前时钟沿为下降沿,后时钟沿为上升沿
    CPHA = 1; //数据在 SCLK 前时钟沿驱动,后时钟沿采样
    //采用漏极开路+上拉电阻方式驱动,需要调低SPI频率才能正常与Flash通信
    SPCTL = (SPCTL & ~3) | 2;   //SPI 时钟频率选择, 0: 4T, 1: 8T,2: 16T,3: 2T
    SPI_S1 = 1;   //00: P1.4 P1.5 P1.6 P1.7, 01: P2.4 P2.5 P2.6 P2.7, 10: P4.0 P4.1 P4.2 P4.3, 11: P3.5 P3.4 P3.3 P3.2
    SPI_S0 = 1;

    HSCLKDIV = 0x01;    //高速时钟1分频,默认2分频
   
    P_HC595_SRCLK = 0;// set clock to low initial state
    P_HC595_SER = 1;
    SPIF = 1;   //清SPIF标志
    WCOL = 1;   //清WCOL标志
}

/**************** 向HC595发送一个字节函数 ******************/
void Send_595(u8 dat)
{
    P_HC595_RCLK = 0;
    SPDAT = dat;
    while(SPIF == 0) ;
    P_HC595_RCLK = 1;
    SPIF = 1;   //清SPIF标志
    WCOL = 1;   //清WCOL标志
}

/********************** 显示扫描函数 ************************/
void DisplayScan(void)
{
    Send_595(t_display]);   //输出段码
    Send_595(~T_COM);            //输出位码

    if(++display_index >= 8)    display_index = 0;//8位结束回0
}

/********************** Timer0 1ms中断函数 ************************/
void timer0 (void) interrupt 1
{
    DisplayScan();//1ms扫描显示一位
    B_1ms = 1;      //1ms标志

}


汇编:
;/*---------------------------------------------------------------------*/
;/* --- Web: www.STCAI.com ---------------------------------------------*/
;/*---------------------------------------------------------------------*/

;*************功能说明    **************

;本例程基于AI8051U为主控芯片的实验箱进行编写测试。

;使用Keil C251编译器,Memory Model推荐设置XSmall模式,默认定义变量在edata,单时钟存取访问速度快。

;data建议保留1K给堆栈使用,空间不够时可将大数组、不常用变量加xdata关键字定义到xdata空间。

;用STC的MCU的SPI接口控制74HC595驱动8位数码管。

;显示效果为: 数码时钟.

;使用Timer0的16位自动重装来产生1ms节拍,程序运行于这个节拍下, 用户修改MCU主时钟频率时,自动定时于1ms.

;下载时, 选择时钟 24MHZ (用户可自行修改频率).

;******************************************/

$include (../../comm/AI8051U.INC)

;/****************************** 用户定义宏 ***********************************/
Fosc_KHZ    EQU 24000   ;24000KHZ

STACK_POIRTER   EQU   0D0H    ;堆栈开始地址

Timer0_Reload   EQU   (65536 - Fosc_KHZ); Timer 0 中断频率, 1000次/秒

DIS_DOT         EQU   020H
DIS_BLACK       EQU   010H
DIS_            EQU   011H

;*******************************************************************
;*******************************************************************

;*************IO口定义    **************/
P_HC595_SER   BIT   P3.4;   //pin 14    SER   data input
P_HC595_RCLK    BIT   P3.5;   //pin 12    RCLk    store (latch) clock
P_HC595_SRCLK   BIT   P3.2;   //pin 11    SRCLK   Shift data clock

;*************本地变量声明    **************/
Flag0         DATA    20H
B_1ms         BIT   Flag0.0 ; 1ms标志

LED8            DATA    30H   ; 显示缓冲 30H ~ 37H
display_index   DATA    38H   ; 显示位索引

usrhour         DATA    39H   ;
usrminute       DATA    3AH
usrsecond       DATA    3BH   ;
msecond         DATA    3CH   ; 2 byte

;*******************************************************************
;*******************************************************************
      ORG   0000H               ;程序复位入口,编译器自动定义到 0FF0000H 地址
      LJMP    F_Main

      ORG   000BH               ;1Timer0 interrupt
      LJMP    F_Timer0_Interrupt

;*******************************************************************
;*******************************************************************


;******************** 主程序 **************************/
    ORG   0100H          ;编译器自动定义到 0FF0100H 地址
F_Main:
    MOV   WTST, #00H   ;设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
    MOV   CKCON,#00H   ;提高访问XRAM速度
    ORL   P_SW2,#080H    ;使能访问XFR

    MOV   P0M1, #00H   ;设置为准双向口
    MOV   P0M0, #00H
    MOV   P1M1, #00H   ;设置为准双向口
    MOV   P1M0, #00H
    MOV   P2M1, #00H   ;设置为准双向口
    MOV   P2M0, #00H
    MOV   P3M1, #00H   ;设置为准双向口
    MOV   P3M0, #00H
    MOV   P4M1, #00H   ;设置为准双向口
    MOV   P4M0, #00H
    MOV   P5M1, #00H   ;设置为准双向口
    MOV   P5M0, #00H
    MOV   P6M1, #00H   ;设置为准双向口
    MOV   P6M0, #00H
    MOV   P7M1, #00H   ;设置为准双向口
    MOV   P7M0, #00H

    MOV   SP, #STACK_POIRTER
    MOV   PSW, #0
    USING   0       ;选择第0组R0~R7

;================= 用户初始化程序 ====================================

    MOV   display_index, #0
    MOV   R0, #LED8
    MOV   R2, #8
L_ClearLoop:
    MOV   @R0, #DIS_BLACK   ;上电消隐
    INC   R0
    DJNZ    R2, L_ClearLoop
   
    MOV   SPCTL,#0DEH
    ORL   P_SW1, #(3 SHL 2)   ; 0: P1.4 P1.5 P1.6 P1.7, 1: P2.4 P2.5 P2.6 P2.7, 2: P4.0 P4.1 P4.2 P4.3, 3: P3.5 P3.4 P3.3 P3.2
    MOV   SPSTAT,#0C0H      ; Clear SPIF,WCOL
   
    CLR   TR0
    ORL   AUXR, #(1 SHL 7)    ; Timer0_1T();
    ANL   TMOD, #NOT 04H      ; Timer0_AsTimer();
    ANL   TMOD, #NOT 03H      ; Timer0_16bitAutoReload();
    MOV   TH0, #Timer0_Reload / 256   ;Timer0_Load(Timer0_Reload);
    MOV   TL0, #Timer0_Reload MOD 256
    SETB    ET0         ; Timer0_InterruptEnable();
    SETB    TR0         ; Timer0_Run();
    SETB    EA          ; 打开总中断
   
    MOV   usrhour,   #12 ; 初始化时间值
    MOV   usrminute, #0
    MOV   usrsecond, #0
    LCALL   F_DisplayRTC
   
;=================== 主循环 ==================================
L_Main_Loop:
    JNB   B_1ms,L_Main_Loop   ;1ms未到
    CLR   B_1ms

    MOV   WR6, msecond
    INC   WR6, #1         ;msecond + 1
    MOV   msecond, WR6
    CMP   WR6, #1000
    JC      L_Main_Loop   ;if(msecond < 1000), jmp

    MOV   WR6, #0
    MOV   msecond, WR6    ;msecond = 0

    LCALL   F_RTC
    LCALL   F_DisplayRTC

    LJMP    L_Main_Loop

;========================================================================
; 函数: F_DisplayRTC
; 描述: 显示时钟子程序。
; 参数: none.
; 返回: none.
; 版本: VER1.0
; 日期: 2013-4-1
; 备注: 除了ACCC和PSW外, 所用到的通用寄存器都入栈
;========================================================================
F_DisplayRTC:
    PUSH    B       ;B入栈
   
    MOV   A, usrhour
    MOV   B, #10
    DIV   AB
    MOV   LED8, A
    MOV   LED8+1, B
    MOV   LED8+2, #DIS_;
   
    MOV   A, usrminute
    MOV   B, #10
    DIV   AB
    MOV   LED8+3, A;
    MOV   LED8+4, B;
    MOV   LED8+5, #DIS_;

    MOV   A, usrsecond
    MOV   B, #10
    DIV   AB
    MOV   LED8+6, A;
    MOV   LED8+7, B;
   
    POP   B       ;B出栈
    RET

;========================================================================
; 函数: F_RTC
; 描述: RTC演示子程序。
; 参数: none.
; 返回: none.
; 版本: VER1.0
; 日期: 2013-4-1
; 备注: 除了ACCC和PSW外, 所用到的通用寄存器都入栈
;========================================================================
F_RTC:
    INC   usrsecond      ; usrsecond + 1
    MOV   A, usrsecond
    CLR   C
    SUBB    A,#60
    JC      L_QuitRTC   ; usrsecond >= 60?
    MOV   usrsecond, #0;

    INC   usrminute      ; usrminute + 1
    MOV   A, usrminute
    CLR   C
    SUBB    A,#60
    JC      L_QuitRTC   ; usrminute >= 60?
    MOV   usrminute, #0

    INC   usrhour      ; usrhour + 1
    MOV   A, usrhour
    CLR   C
    SUBB    A,#24
    JC      L_QuitRTC   ; usrhour >= 24?
    MOV   usrhour, #0
L_QuitRTC:
    RET

; *********************** 显示相关程序 ****************************************
T_Display:                      ;标准字库
;    0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
DB03FH,006H,05BH,04FH,066H,06DH,07DH,007H,07FH,06FH,077H,07CH,039H,05EH,079H,071H
;black-    H    J    K    L    N    o    P    U    t    G    Q    r    M    y
DB000H,040H,076H,01EH,070H,038H,037H,05CH,073H,03EH,078H,03dH,067H,050H,037H,06EH
;    0.   1.   2.   3.   4.   5.   6.   7.   8.   9.   -1
DB0BFH,086H,0DBH,0CFH,0E6H,0EDH,0FDH,087H,0FFH,0EFH,046H

T_COM:
DB001H,002H,004H,008H,010H,020H,040H,080H   ;   位码

;========================================================================
; 函数: F_Send_595
; 描述: 向HC595发送一个字节子程序。
; 参数: ACC: 要发送的字节数据.
; 返回: none.
; 版本: VER1.0
; 日期: 2024-4-1
; 备注: 除了ACCC和PSW外, 所用到的通用寄存器都入栈
;========================================================================
F_Send_595:
    CLR   P_HC595_RCLK
    MOV   SPDAT,A
L_Wait:
    JNB   SPIF, L_Wait
    CLR   SPIF
    CLR   WCOL
    SETB    P_HC595_RCLK
    RET

;========================================================================
; 函数: F_DisplayScan
; 描述: 显示扫描子程序。
; 参数: none.
; 返回: none.
; 版本: VER1.0
; 日期: 2013-4-1
; 备注: 除了ACCC和PSW外, 所用到的通用寄存器都入栈
;========================================================================
F_DisplayScan:
    PUSH    DPH   ;DPH入栈
    PUSH    DPL   ;DPL入栈
    PUSH    00H   ;R0 入栈
   
    MOV   DPTR, #T_Display
    MOV   A, display_index
    ADD   A, #LED8
    MOV   R0, A
    MOV   A, @R0
    MOVC    A, @A+DPTR
    LCALL   F_Send_595      ;输出段码

    MOV   DPTR, #T_COM
    MOV   A, display_index
    MOVC    A, @A+DPTR
    CPL   A
    LCALL   F_Send_595      ;输出位码
   
    INC   display_index
    MOV   A, display_index
    ANL   A, #0F8H            ; if(display_index >= 8)
    JZ      L_QuitDisplayScan
    MOV   display_index, #0;;8位结束回0
L_QuitDisplayScan:
    POP   00H   ;R0 出栈
    POP   DPL   ;DPL出栈
    POP   DPH   ;DPH出栈
    RET

;**************** 中断函数 ***************************************************

F_Timer0_Interrupt: ;Timer0 1ms中断函数
    PUSH    PSW   ;PSW入栈
    PUSH    ACC   ;ACC入栈

    LCALL   F_DisplayScan   ; 1ms扫描显示一位
    SETB    B_1ms         ; 1ms标志

    POP   ACC   ;ACC出栈
    POP   PSW   ;PSW出栈
    RETI
   
;*******************************************************************

    END




zhp 发表于 2025-4-23 12:01:41

深圳大学 Ai8051U《单片机原理及应用》实验课,大型综合实战:
定时器做RTC,按键(去抖,长按,短按),发声
实验目的:
1、本项目主要是帮助同学们熟悉如何管理 多文件项目
2、熟悉矩阵式按键扫描方法,含去抖动,短按,长按
3、了解使用74HC595控制 LED数码管
4、普通定时器软件模拟RTC,实现RTC的 时、分、秒控制
5、I/O控制无源蜂鸣器发声,
   按键 Key0 ~ Key7 按下时,发出对应的 Do/Re/Mi/Fa/So/La/Si/Do音符
6、扩展部分:如何实现长按键

实验现象:
将本项目的HEX文件下载到Ai8051U的实验箱后
1、在LED数码管上面低6位会动态显示当前的RTC时间
2、按下两行4列的8个矩阵按键后
   在LED数码管的最高位会显示当前的按键键码,并会发出对应的声音
   同时会根据不同的按键发出不同按键音
   (特别注意:需要将实验箱上“跳线3:蜂鸣器”的跳线连上,蜂鸣器才会发声)
3、矩阵按键中的按键0,是RTC模式设置键
   上电后RTC为正常工作模式,此时数码管动态显示RTC时间
   按一次按键0,RTC会切换为小时设置模式,此时RTC的小时值会0.5秒闪烁一次
   再按一次按键0,RTC会切换为分钟设置模式,此时RTC的分钟值会0.5秒闪烁一次
   再按一次按键0,RTC会切换为秒钟设置模式,此时RTC的秒钟值会0.5秒闪烁一次
   在按一次按键0,恢复为RTC正常工作模式
4、矩阵按键中的按键4和按键5分别是上调键和下调键
   只有在RTC的时/分/秒设置模式式,上调键和下调键才有效
5、时钟设置模式时,长按按键4和按键5,可实现快速设置功能












神农鼎 发表于 2025-4-23 13:22:45






sbk100 发表于 2025-4-23 14:03:23

深圳大学的单片机水平这么强吗

神农鼎 发表于 2025-4-23 19:37:08

sbk100 发表于 2025-4-23 14:03
深圳大学的单片机水平这么强吗
深圳大学,代表我们深圳的水平,当然可以
深圳,全球 科技 中心

神农鼎 发表于 2025-4-24 10:55:00

74HC595资料,使用说明


74HC595_百度百科
页: 1 2 3 4 [5] 6 7
查看完整版本: 深圳大学《单片机原理及接口技术》上机实验演示视频@Ai8051U