wuzhengmin 发表于 2026-1-10 20:12:32

举个例子:按键INT0

改变LED灯的状态

wuzhengmin 发表于 2026-1-10 21:18:46

外部中断0---INT0

先看汇编:

wuzhengmin 发表于 2026-1-10 21:20:32

再看C语言:

void main(void)
{
        unsigned char LEDdata=0x01;//流水灯移位用
       
        SYS_int();        //系统初始化
       

        IT0=1;//INT0中断打开
        EX0=1;
        EA =1;
       
       
        P40=0;
        P0=0;
       
        while (1);

}


void INTO_ISR(void)interruptINT0_VECTOR
{
   P0 =~P0;
}

我们实验的结果是仿真有点问题,直接烧录就一切顺利

好了,今天就顺利学完了第十四集!

wuzhengmin 发表于 2026-1-12 12:55:55

这几天在尝试修改梁工的DMA例程:

第一步是先搞个实验,证实一下DMA快很多...............

简单点,用串口试一下(布丁橘长基本架子已经搞好了,我们细化一下就可以比较啦)

先看程序:

环境是屠龙刀+“一箭双雕” ,因为要用到串口,我们直接使用“一箭双雕” 的(COM15) CDC1-UART1, 2CDC+HID作为UART1(接P30P31)

偷懒不想翻老旧com转TTL设备啦!

//        @布丁橘长 2023/04/24
//串口1DMA自动收发数据示例:USART1-模式1,主频22.1184MHz,波特率115200,8位数据方式,1位起始位,无校验位,1位停止位,
//        串口1DMA循环发送10000字节数据给PC端显示,同时P2口LED闪烁
//串口1使用默认引脚P3.0(RxD) P3.1(TxD)
//        实验开发板:STC32G12K128屠龙刀三.1 主频@22.1184MHz

#include <STC32G.H>
#include "config.h"
#include "delay.h"

#define BRT (65536 - (MAIN_Fosc / 115200+2) / 4)                // 加 2 操作是为了让 Keil 编译器,自动实现四舍五入运算
                                                                                                                                                                                                                // 波特率115200
#define DMA_AMT_LEN 9999                         // DMA传输总字节(AMT+1) 9999+1=10000字节
                                                                                                                                                                                                                                                                                                                                                                                                       
bit busy;                                                                                        // 串口忙标志
char wptr;                                                                                // 写指针
char rptr;                                                                                // 读指针
char buffer;                                                        // 接收缓存,长度16

u8 xdata DmaBuffer;                // 数据存放在XRAM(XDATA区域),需要使用关键字xdata
bit        DmaTxFlag;                                                                // 发送完成标志
bit        DmaRxFlag;                                                                // 接收完成标志

volatile u16 i;
void sysini(void);                                                // STC32初始化设置
void Blink();                                                                        // P2口LED闪烁
void Uart1Init();                                                        // UART1初始化
void DMA_Config();                                                // DMA初始化
void UartSend(char dat);                //发送字符函数,用这个函数就死机
void UartSendStr(char *p);                //发送字符串函数

void UART_SendByte(unsigned char dat);//发送字符函数,用这个函数就正常
void UART_SendString(char *str);

void main(void)
{
        sysini();                                                                                // STC32初始化设置
        Uart1Init();                                                                // 串口1初始化
        DMA_Config();
        EA = 1;                                                                                        // 使能EA总中断
       
        DmaTxFlag = 0;                                                        // 清零发送完成标志
        DmaRxFlag = 0;                                                        // 清零接收完成标志
        for(i = 0 ;i < 10000;i++)                // 10000个字符
        {
                DmaBuffer = i%128;                        // 10000个字符,为128个ASCII字符
        }
        while (1)
        {
                DmaTxFlag = 1;
                DmaRxFlag = 1;
                if((DmaTxFlag) && (DmaRxFlag))        // 当发送和接收完成标志均为1时,表示空闲
                {
                        DmaTxFlag = 0;                                        // 清零发送完成标志
                        DMA_UR1T_CR = 0xc0;                        // bit7 1:使能 UART1_DMA, bit6 1:开始 UART1_DMA 自动发送
                        DmaRxFlag = 0;                                        // 清零接收完成标志
                        DMA_UR1R_CR = 0xa1;                        // bit7 1:使能 UART1_DMA, bit5 1:开始 UART1_DMA 自动接收, bit0 1:清除 FIFO
                }
               

//                for(i = 0;i< 10000; i++)UART_SendByte(DmaBuffer);//直接发送
                Blink();
    }
}
void sysini()
{
        EAXFR = 1;                                                                         // 使能访问 XFR
        CKCON = 0x00;                                                         // 设置外部数据总线速度为最快
        WTST = 0x00;                                                                // 设置程序代码等待参数,等待时间为0个时钟,CPU执行程序速度最快

        P0M1 = 0x00;P0M0 = 0x00;                // 设置P0口为准双向口模式 //00:准双向口 01:推挽输出 10:高阻输入 11:开漏输出
        P1M1 = 0x00;P1M0 = 0x00;                // 设置P1口为准双向口模式 //00:准双向口 01:推挽输出 10:高阻输入 11:开漏输出
        P2M1 = 0x00;P2M0 = 0x00;                // 设置P2口为准双向口模式 //00:准双向口 01:推挽输出 10:高阻输入 11:开漏输出
        P3M1 = 0x00;P3M0 = 0x00;                // 设置P3口为准双向口模式 //00:准双向口 01:推挽输出 10:高阻输入 11:开漏输出
        P4M1 = 0x00;P4M0 = 0x00;                // 设置P4口为准双向口模式 //00:准双向口 01:推挽输出 10:高阻输入 11:开漏输出
        P5M1 = 0x00;P5M0 = 0x00;                // 设置P5口为准双向口模式 //00:准双向口 01:推挽输出 10:高阻输入 11:开漏输出
        P6M1 = 0x00;P6M0 = 0x00;                // 设置P6口为准双向口模式 //00:准双向口 01:推挽输出 10:高阻输入 11:开漏输出
        P7M1 = 0x00;P7M0 = 0x00;                // 设置P7口为准双向口模式 //00:准双向口 01:推挽输出 10:高阻输入 11:开漏输出
}
void Uart1Init()                                                        // UART1初始化
{
        SCON = 0x50;                                                                // 模式1(8位数据)、接收使能
        T2L = BRT;                                               
        T2H = BRT >> 8;                                                        // 波特率对应的重装载值
        S1BRT = 1;                                                                        // 定时器2做波特率发生器
        T2x12 = 1;                                                                        // 1T模式
        T2R = 1;                                                                                // 启动定时器2
}
void UartSend(char dat)                                // 发送字符函数
{
       
        while (busy);                                                                // 当串口忙时,等待
        busy = 1;                                                                                // 忙标志置1
        SBUF = dat;                                                                // 发送数据dat
}       

void UART_SendByte(unsigned char dat)
{
    SBUF = dat;          // 1. 将数据写入发送缓冲寄存器,硬件自动开始发送
    while(!TI);          // 2. 查询等待:直到TI(发送完成标志位)变为1
    TI = 0;            // 3. 软件清零TI,为下一次发送做准备

       
}

void UartSendStr(char *p)                        // 发送字符串函数
{
        while (*p)                                                                        // 字符串结束标志‘\0’前循环
        {
                UartSend(*p++);                                                // 逐个发送字符串的字符
        }
}

void UART_SendString(char *str)
{
    while(*str != '\0')   // 判断当前字符是否为字符串结束符
        {
      UART_SendByte(*str); // 发送当前字符
      str++;               // 指针指向下一个字符
    }
    // 或者简写为: while(*str) { UART_SendByte(*str++); }
}

void Blink()                                                                        // P2口LED闪烁
{
        u8 i;
        for(i = 0;i < 6;i++)                                // 闪烁3次(即亮灭6个过程)
        {
                P2 = ~P2;                                                                        // 取反实现亮灭
                delayms(100);                                                        // 延时200ms
        }
}
void DMA_Config(void)
{
        DMA_UR1T_CFG = 0x80;                                // bit7 1:使能串口1DMA发送中断
        DMA_UR1T_STA = 0x00;                                // 清零串口1DMA发送完成中断标志、清零数据覆盖中断标志
        DMA_UR1T_AMT =DMA_AMT_LEN;                                                                // 设置传输总字节数(低8位):n+1
        DMA_UR1T_AMTH = DMA_AMT_LEN >> 8;                                                // 设置传输总字节数(高8位):n+1
        DMA_UR1T_TXAH = (u8)((u16)&DmaBuffer >> 8);        // 设置传输数据的源地址,高8位
        DMA_UR1T_TXAL = (u8)((u16)&DmaBuffer);                        // 设置传输数据的源地址,低8位
        DMA_UR1T_CR = 0xc0;                                        // bit7 1:使能串口1DMA发送, bit6 1:开始DMA自动发送

        DMA_UR1R_CFG = 0x80;                                // bit7 1:使能串口1DMA接收中断
        DMA_UR1R_STA = 0x00;                                // 清零串口1DMA接收完成中断标志、清零数据丢弃中断标志
        DMA_UR1R_AMT =DMA_AMT_LEN;                                                                // 设置传输总字节数(低8位):n+1
        DMA_UR1R_AMTH = DMA_AMT_LEN >> 8;                                                // 设置传输总字节数(高8位):n+1
        DMA_UR1R_RXAH = (u8)((u16)&DmaBuffer >> 8);        // 设置传输数据的目标地址,高8位
        DMA_UR1R_RXAL = (u8)((u16)&DmaBuffer);                        // 设置传输数据的目标地址,低8位
        DMA_UR1R_CR = 0xa1;                                        //bit7 1:使能串口1DMA接收, bit5 1:开始DMA自动接收, bit0 1:清除 FIFO
}
void UART1_DMA_Interrupt(void) interrupt 13                // 串口DMA中断号大于31,借用13号保留中断中转
{                                                                                                                                                                                        // 详情参照布丁橘长-STC32系列视频第36期,或STC32手册第5.9章节
        if (DMA_UR1T_STA & 0x01)                // 发送完成中断标志为1时
        {
                DMA_UR1T_STA &= ~0x01;                // 清零发送完成中断标志
                DmaTxFlag = 1;                                                // 发送完成标志置1
        }
        if (DMA_UR1T_STA & 0x04)                // 数据覆盖中断标志为1时
        {
                DMA_UR1T_STA &= ~0x04;                // 清零数据覆盖中断标志
        }
        if (DMA_UR1R_STA & 0x01)                // 接收完成中断标志为1时
        {
                DMA_UR1R_STA &= ~0x01;                // 清零接收完成中断标志
                DmaRxFlag = 1;                                                // 接收完成标志置1
        }
        if (DMA_UR1R_STA & 0x02)                // 数据丢弃中断标志为1时
        {
                DMA_UR1R_STA &= ~0x02;                // 清零数据丢弃中断标志
        }
}因为布丁橘长上传的项目文件里,没有void UartSend(char dat) // 发送字符函数


我们先按照橘长的思路写了发送函数:

wuzhengmin 发表于 2026-1-12 13:01:37

void UartSend(char dat)                                // 发送字符函数
{
       
        while (busy);                                                                // 当串口忙时,等待
        busy = 1;                                                                                // 忙标志置1
        SBUF = dat;                                                                // 发送数据dat
}       

但是运行后发现会假死在while (busy);        这句

因为发送完10000个字节后busy = 1;没有被清除,我也没能查找到第一次发送完是在哪里把busy清0的

就赶快换个写法:

void UART_SendByte(unsigned char dat)
{
    SBUF = dat;          // 1. 将数据写入发送缓冲寄存器,硬件自动开始发送
    while(!TI);          // 2. 查询等待:直到TI(发送完成标志位)变为1
    TI = 0;            // 3. 软件清零TI,为下一次发送做准备

       
}

这样就解决了假死的问题


wuzhengmin 发表于 2026-1-12 13:13:24

我们先屏蔽DMA语句:

      while (1)
      {
//                DmaTxFlag = 1;
//                DmaRxFlag = 1;
//                if((DmaTxFlag) && (DmaRxFlag))      // 当发送和接收完成标志均为1时,表示空闲
//                {
//                        DmaTxFlag = 0;                                        // 清零发送完成标志
//                        DMA_UR1T_CR = 0xc0;                        // bit7 1:使能 UART1_DMA, bit6 1:开始 UART1_DMA 自动发送
//                        DmaRxFlag = 0;                                        // 清零接收完成标志
//                        DMA_UR1R_CR = 0xa1;                        // bit7 1:使能 UART1_DMA, bit5 1:开始 UART1_DMA 自动接收, bit0 1:清除 FIFO
//                }
               

                for(i = 0;i< 10000; i++)UART_SendByte(DmaBuffer);//直接发送
                Blink();
    }

只循环执行10000次传统USRT发送指令:明显P2口LED闪烁间隔拉长了1005

wuzhengmin 发表于 2026-1-12 13:37:59

换回DMA自动传送10000个字节给UART:



        while (1)
        {
                DmaTxFlag = 1;
                DmaRxFlag = 1;
                if((DmaTxFlag) && (DmaRxFlag))        // 当发送和接收完成标志均为1时,表示空闲
                {
                        DmaTxFlag = 0;                                        // 清零发送完成标志
                        DMA_UR1T_CR = 0xc0;                        // bit7 1:使能 UART1_DMA, bit6 1:开始 UART1_DMA 自动发送
                        DmaRxFlag = 0;                                        // 清零接收完成标志
                        DMA_UR1R_CR = 0xa1;                        // bit7 1:使能 UART1_DMA, bit5 1:开始 UART1_DMA 自动接收, bit0 1:清除 FIFO
                }
               

//                for(i = 0;i< 10000; i++)UART_SendByte(DmaBuffer);//直接发送
                Blink();
    }



u8 xdata DmaBuffer;                // 数据存放在XRAM(XDATA区域),需要使用关键字xdata


如上程序所示,我们把XDATA的大数组DmaBuffer,用DMA传送给UART1:
看视频,快很多(注意主要是通过P2口的LED闪烁间隔来反应)1006



wuzhengmin 发表于 2026-1-12 13:43:49

所以,我们有需要,把STC梁工的例子,详细吃透

然后改写成多文件项目,以便今后拆解加入我们其他程序使用:

我们先把刚才的测试例子整体项目文件上传:



wuzhengmin 发表于 2026-1-12 13:51:01

梁工就是单一文件系统:



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

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

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

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

单色OLED12864显示屏驱动程序,驱动IC为SSD1306,SPI接口,通过SPI DMA将1024字节的图片数据送到彩屏,传送时不占用CPU时间。

显示图形,汉字,英文,数字.

其中图形显示发送命令和图片数据使用SPI DMA操作,传输数据时不占用CPU时间。做GUI最方便了,可以先操作定义于xdata的1024字节缓存,然后触发SPI DMA即可,523us或943us即可自己动刷完。
本例运行于40MHz, SPI速度为主频4分频(10MHz),每次SPI DMA传输总时间943us,SPI速度为主频2分频(20MHz),每次SPI DMA传输总时间523us。

将要显示的内容放在1024字节的显存中,启动DMA传输即可。

下载时, 选择时钟 40MHz (用户可自行修改频率后重新编译即可).

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


        #define        MAIN_Fosc        40000000UL


        #include        "AI8051U.h"
        #include        "ASCII6x8.h"
        #include        "HZK16.h"
        #include        "ASCII-10x24.h"
        #include        "picture1.h"
        #include        "picture2.h"



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

/****************************** IO定义 ***********************************/
/*        定义接口        */
                                                        //GND        AI8051U实验箱 V1.1
                                                        //VCC        3~5V
sbit P_OLED_CLK        =        P3^2;        //D0        SPI or II2 的时钟脚
sbit P_OLED_DIN        =   P3^3;        //D1        SPI or II2 的数据脚
sbit P_OLED_RST        =        P4^7;        //RES        复位脚, 低电平复位
sbit P_OLED_DC        =        P1^1;        //DC        数据或命令脚
sbit P_OLED_CS        =        P3^5;        //CS        片选脚


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

/*************本地常量声明    **************/


/*************本地变量声明    **************/
u16        SPI_TxCnt;                //SPI DMA触发发送次数, 一次128字节, 一共8次
bit        B_SPI_DMA_busy;        //SPI DMA忙标志, 1标志SPI-DMA忙,SPI DMA中断中清除此标志,使用SPI DMA前要确认此标志为0
u16        SPI_TxAddr;                //SPI DMA要发送数据的首地址
bit        B_TxCmd;                //已发送命令标志
u8 xdata CmdTmp;        //命令缓冲


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




//========================================================================
// 函数: voidSPI_Config(u8 SPI_io, u8 SPI_speed)
// 描述: SPI初始化函数。
// 参数: io: 切换到的IO,            SSMOSI MISO SCLK
//                     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
//       SPI_speed: SPI的速度, 0: fosc/4,1: fosc/8,2: fosc/16,3: fosc/2
// 返回: none.
// 版本: VER1.0
// 日期: 2024-8-13
// 备注:
//========================================================================
voidSPI_Config(u8 SPI_io, u8 SPI_speed)
{
        SPI_io &= 3;

        SPCTL = SPI_speed & 3;        //配置SPI 速度, 这条指令先执行, 顺便Bit7~Bit2清0
        SSIG = 1;        //1: 忽略SS脚,由MSTR位决定主机还是从机                0: SS脚用于决定主机还是从机。
        SPEN = 1;        //1: 允许SPI,                                                                0:禁止SPI,所有SPI管脚均为普通IO
        DORD = 0;        //1:LSB先发,                                                                0:MSB先发
        MSTR = 1;        //1:设为主机                                                                0:设为从机
        CPOL = 1;        //1: 空闲时SCLK为高电平,                                        0:空闲时SCLK为低电平
        CPHA = 1;        //1: 数据在SCLK前沿驱动,后沿采样.                        0: 数据在SCLK前沿采样,后沿驱动.
//        SPR1 = 0;        //SPR1,SPR0   00: fosc/4,   01: fosc/8
//        SPR0 = 0;        //            10: fosc/16,    11: fosc/2
        P_SW1 = (P_SW1 & ~0x0c) | ((SPI_io<<2) & 0x0c);                //切换IO

        HSCLKDIV   = 1;                                        //HSCLKDIV主时钟分频
        SPI_CLKDIV = 1;                                        //SPI_CLKDIV主时钟分频
        SPSTAT = 0x80 + 0x40;                        //清0 SPIF和WCOL标志

        if(SPI_io == 0)
        {
                P1n_standard(0xf0);                        //切换到 P1.4(SS) P1.5(MOSI) P1.6(MISO) P1.7(SCLK), 设置为准双向口
                PullUpEnable(P1PU, 0xf0);        //设置上拉电阻    允许端口内部上拉电阻   PxPU, 要设置的端口对应位为1
                P1n_push_pull(Pin7+Pin5);        //MOSI SCLK设置为推挽输出
                SlewRateHigh(P1SR, Pin7+Pin5);        //MOSI SCLK端口输出设置为高速模式   PxSR, 要设置的端口对应位为1.    高速模式在3.3V供电时速度可以到13.5MHz(27MHz主频,SPI速度2分频)
        }
        else if(SPI_io == 1)
        {
                P2n_standard(0xf0);                        //切换到P2.4(SS) P2.5(MOSI) P2.6(MISO) P2.7(SCLK), 设置为准双向口
                PullUpEnable(P2PU, 0xf0);        //设置上拉电阻    允许端口内部上拉电阻   PxPU, 要设置的端口对应位为1
                P2n_push_pull(Pin7+Pin5);        //MOSI SCLK设置为推挽输出
                SlewRateHigh(P2SR, Pin7+Pin5);        //MOSI SCLK端口输出设置为高速模式   PxSR, 要设置的端口对应位为1.    高速模式在3.3V供电时速度可以到13.5MHz(27MHz主频,SPI速度2分频)
        }
        else if(SPI_io == 2)
        {
                P4n_standard(0x0f);                        //切换到P4.0(SS) P4.1(MOSI) P4.2(MISO) P4.3(SCLK), 设置为准双向口
                PullUpEnable(P4PU, 0x0f);        //设置上拉电阻    允许端口内部上拉电阻   PxPU, 要设置的端口对应位为1
                P4n_push_pull(Pin3+Pin1);        //MOSI SCLK设置为推挽输出
                SlewRateHigh(P4SR, Pin3+Pin1);        //MOSI SCLK端口输出设置为高速模式   PxSR, 要设置的端口对应位为1.    高速模式在3.3V供电时速度可以到13.5MHz(27MHz主频,SPI速度2分频)
        }
        else if(SPI_io == 3)
        {
                P3n_standard(0x3C);                //切换到P3.5(SS) P3.4(MOSI) P3.3(MISO) P3.2(SCLK), 设置为准双向口
                PullUpEnable(P3PU, 0x3c);        //设置上拉电阻    允许端口内部上拉电阻   PxPU, 要设置的端口对应位为1
                P3n_push_pull(Pin4+Pin2);        //MOSI SCLK设置为推挽输出
                SlewRateHigh(P3SR, Pin4+Pin2);        //MOSI SCLK端口输出设置为高速模式   PxSR, 要设置的端口对应位为1.    高速模式在3.3V供电时速度可以到13.5MHz(27MHz主频,SPI速度2分频)
        }
}


voidLCD_delay_ms(u16 ms)        // 1~65535
{
        u16 i;
        do
        {
                i = MAIN_Fosc / 6000;
                while(--i)        ;
        }while(--ms);
}

//******************************************
void OLED_WriteData(u8 dat)                        //write display data to LCD
{
        P_OLED_DC = 1;
        P_OLED_CS = 0;
        SPDAT = dat;        //发送一个字节
        while(SPIF == 0)        ;                        //等待发送完成
        SPSTAT = 0x80 + 0x40;                        //清0 SPIF和WCOL标志
        P_OLED_CS = 1;
}

//******************************************
void        OLED_WriteCMD(u8 cmd)
{
        P_OLED_DC = 0;
        P_OLED_CS = 0;
        SPDAT = cmd;        //发送一个字节
        while(SPIF == 0)        ;                        //等待发送完成
        SPSTAT = 0x80 + 0x40;                        //清0 SPIF和WCOL标志
        P_OLED_CS = 1;
        P_OLED_DC = 1;
}

//========================================================================
// 函数: void Set_Dot_Addr_LCD(int x,int y)
// 描述: 设置在LCD的真实坐标系上的X、Y点对应的RAM地址
// 参数: x                 X轴坐标
//               y                 Y轴坐标
// 返回: 无
// 备注: 仅设置当前操作地址,为后面的连续操作作好准备
// 版本:
//      2007/04/10      First version
//========================================================================
void Set_Dot_Addr(u8 x,u8 y)
{
        OLED_WriteCMD((u8)(0xb0 + y));                //设置页0~7
        OLED_WriteCMD((x >> 4)| 0x10);        //设置列0~127 高nibble
        OLED_WriteCMD(x & 0x0f);                        //设置列0~127 低nibble
}


//******************************************
void FillPage(u8 y,u8 color)                        //Clear Page LCD RAM
{
        u8 j;
        Set_Dot_Addr(0,y);
        for(j=0; j<128; j++)        OLED_WriteData(color);
}

//******************************************
void FillAll(u8 color)                        //Clear CSn LCD RAM
{
        u8 i;
        for(i=0; i<8; i++)        FillPage(i,color);
}



//******************************************
void Initialize_OLED(void)        //initialize OLED
{
        SPI_Config(3, 0);        //(SPI_io, SPI_speed), 参数:         SPI_io: 切换IO(SS MOSI MISO SCLK), 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,
                                                //                                                                SPI_speed: SPI的速度, 0: fosc/4,1: fosc/8,2: fosc/16,3: fosc/2
        HSSPI_CFG2 = 0x40;        //交换MOSI MISO, P3.3是MOSI

        P1n_standard(Pin1);                        // SPI引脚设置为准双向口, SPI和控制信号
        PullUpEnable(P1PU, Pin1);        // 允许端口内部上拉电阻   PxPU, 要设置的端口对应位为1
        P3n_standard(0x2c);                        // SPI引脚设置为准双向口, SPI和控制信号
        PullUpEnable(P3PU, 0x2c);        // 允许端口内部上拉电阻   PxPU, 要设置的端口对应位为1
        P4n_standard(Pin7);                        // SPI引脚设置为准双向口, SPI和控制信号
        PullUpEnable(P4PU, Pin7);        // 允许端口内部上拉电阻   PxPU, 要设置的端口对应位为1

        P_OLED_CS= 1;
        P_OLED_RST = 1;
        P_OLED_DC= 1;
        P_OLED_DIN = 1;
        P_OLED_CLK = 1;

        P_OLED_RST = 0;
        LCD_delay_ms(100);
        P_OLED_RST = 1;
        LCD_delay_ms(100);


        OLED_WriteCMD(0xAE);   //Set Display Off

        OLED_WriteCMD(0xd5);   //display divide ratio/osc. freq. mode
        OLED_WriteCMD(0x80);   //

        OLED_WriteCMD(0xA8);   //multiplex ration mode:63
        OLED_WriteCMD(0x3F);

        OLED_WriteCMD(0xD3);   //Set Display Offset
        OLED_WriteCMD(0x00);

        OLED_WriteCMD(0x40);   //Set Display Start Line

        OLED_WriteCMD(0x8D);   //Set Display Offset
        OLED_WriteCMD(0x14);

        OLED_WriteCMD(0xA1);   //Segment Remap

        OLED_WriteCMD(0xC8);   //Sst COM Output Scan Direction

        OLED_WriteCMD(0xDA);   //common pads hardware: alternative
        OLED_WriteCMD(0x12);

        OLED_WriteCMD(0x81);   //contrast control
        OLED_WriteCMD(0xCF);

        OLED_WriteCMD(0xD9);          //set pre-charge period
        OLED_WriteCMD(0xF1);

        OLED_WriteCMD(0xDB);   //VCOM deselect level mode
        OLED_WriteCMD(0x40);          //set Vvcomh=0.83*Vcc

        OLED_WriteCMD(0xA4);   //Set Entire Display On/Off

        OLED_WriteCMD(0xA6);   //Set Normal Display

        OLED_WriteCMD(0xAF);   //Set Display On

        FillAll(0);
}


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

void WriteAscii6x8(u8 x,u8 y, u8 number)
{
        u8 const *p;
        u8 i;

        if(x > (128-5))        return;
        if(y >= 8)        return;
        p = (u16)number * 6 + ASCII6x8;

        Set_Dot_Addr(x,y);
        for(i=0; i<6; i++)
        {
                OLED_WriteData(*p);
                p++;
        }
}

//=====================================================
void WriteHZ16(u8 x, u8 y, u16 hz)        //向指定位置写一个汉字, x为横向的点0~127, y为纵向的页0~7, hz为要写的汉字.
{
        u8 const *p;
        u8 i;

        if(x > (128-16))        return;
        if(y > 6)                        return;
        p = hz * 32 + HZK16;
        Set_Dot_Addr(x, y);
        for(i=0; i<16; i++)                {        OLED_WriteData(*p);        p++;}

        Set_Dot_Addr(x, (u8)(y+1));
        for(i=0; i<16; i++)                {        OLED_WriteData(*p);        p++;}
}



void        printf_ASCII_text(u8 x, u8 y, u8 *ptr)        //最快写入10个ASCII码(10*6+9=69个字节)耗时430us@24MHZ
{
        u8c;

        for (;;)
        {
      c = *ptr;
                if(c == 0)                return;        //遇到停止符0结束
                if(c < 0x80)                        //ASCII码
                {
                        WriteAscii6x8(x,y,c);
                        x += 6;
                }
                ptr++;
        }
}

//******************************************
void WriteAscii_10x24(u8 x, u8 y, u8 chr)        //向指定位置写一个ASCII码字符, x为横向的点0~127, y为纵向的页0~7, chr为要写的字符
{
        u8 const *p;
        u8 i;

        if(x > (128-10))        return;
        if(y >= 6)                        return;
        p = (u16)chr * 30 + ASCII10x24;

        Set_Dot_Addr(x, y);
        for(i=0; i<10; i++)                {        OLED_WriteData(*p);        p++;        }

        Set_Dot_Addr(x, (u8)(y+1));
        for(i=0; i<10; i++)                {        OLED_WriteData(*p);        p++;        }

        Set_Dot_Addr(x, (u8)(y+2));
        for(i=0; i<10; i++)                {        OLED_WriteData(*p);        p++;        }
}

//******************************************
void WriteDot_3x3(u8 x, u8 y)        //向指定位置写一个小数点, x为横向的点0~127, y为纵向的页0~7
{
        if(x > (128-3))        return;
        if(y >= 8)                return;

        Set_Dot_Addr(x, y);
        OLED_WriteData(0x38);
        OLED_WriteData(0x38);
        OLED_WriteData(0x38);
}


//************ 打印ASCII 10x24英文字符串 *************************
void        printf_ascii_10x24(u8 x, u8 y, u8 const *ptr)        //x为横向的点0~127, y为纵向的页0~7, *ptr为要打印的字符串指针, 间隔2点.
{
    u8 c;

        for (;;)
        {
                if(x > (128-10))        return;
                if(y > 5)                        return;
                c = *ptr;
                if(c == 0)                return;        //遇到停止符0结束
                if((c >= '0') && (c <= '9'))                        //ASCII码
                {
                        WriteAscii_10x24(x,y,(u8)(c-'0'));
                        x += 12;
                }
                else if(c == '.')
                {
                        WriteDot_3x3(x,(u8)(y+2));
                        x += 6;
                }
                else if(c == ' ')        //显示空格
                {
                        WriteAscii_10x24(x,y,11);
                        x += 12;
                }
                else if(c == '-')        //显示空格
                {
                        WriteAscii_10x24(x,y,10);
                        x += 12;
                }
                        ptr++;
        }
}

//DMA_SPI_CR         SPI_DMA控制寄存器
#define                DMA_ENSPI                (1<<7)        // SPI DMA功能使能控制位,    bit7, 0:禁止SPI DMA功能,1:允许SPI DMA功能。
#define                SPI_TRIG_M                (1<<6)        // SPI DMA主机模式触发控制位,bit6, 0:写0无效,          1:写1开始SPI DMA主机模式操作。
#define                SPI_TRIG_S                (0<<5)        // SPI DMA从机模式触发控制位,bit5, 0:写0无效,          1:写1开始SPI DMA从机模式操作。
#define                SPI_CLRFIFO                1                // 清除SPI DMA接收FIFO控制位,bit0, 0:写0无效,          1:写1复位FIFO指针。


//DMA_SPI_CFG         SPI_DMA配置寄存器
#define                DMA_SPIIE        (1<<7)        // SPI DMA中断使能控制位,bit7, 0:禁止SPI DMA中断,   1:允许中断。
#define                SPI_ACT_TX        (1<<6)        // SPI DMA发送数据控制位,bit6, 0:禁止SPI DMA发送数据,主机只发时钟不发数据,从机也不发. 1:允许发送。
#define                SPI_ACT_RX        (0<<5)        // SPI DMA接收数据控制位,bit5, 0:禁止SPI DMA接收数据,主机只发时钟不收数据,从机也不收. 1:允许接收。
#define                DMA_SPIIP        (0<<2)        // SPI DMA中断优先级控制位,bit3~bit2, (最低)0~3(最高).
#define                DMA_SPIPTY        0                // SPI DMA数据总线访问优先级控制位,bit1~bit0, (最低)0~3(最高).

//DMA_SPI_CFG2         SPI_DMA配置寄存器2
#define                SPI_WRPSS        (0<<2)        // SPI DMA过程中使能SS脚控制位,bit2, 0: SPI DMA传输过程不自动控制SS脚。1:自动拉低SS脚。
#define                SPI_SSS          0                // SPI DMA过程中自动控制SS脚选择位,bit1~bit0, 0: P1.4,1:P2.4,2: P4.0,3:P3.5。

//DMA_SPI_STA         SPI_DMA状态寄存器
#define                SPI_TXOVW        (1<<2)        // SPI DMA数据覆盖标志位,bit2, 软件清0.
#define                SPI_RXLOSS        (1<<1)        // SPI DMA接收数据丢弃标志位,bit1, 软件清0.
#define                DMA_SPIIF        1                // SPI DMA中断请求标志位,bit0, 软件清0.

//HSSPI_CFG高速SPI配置寄存器
#define                SS_HOLD                (3<<4)        //高速模式时SS控制信号的HOLD时间, 0~15, 默认3. 在DMA中会增加N个系统时钟,当SPI速度为系统时钟/2时执行DMA,SS_HOLD、SS_SETUP和SS_DACT都必须设置大于2的值.
#define                SS_SETUP                3        //高速模式时SS控制信号的SETUP时间,0~15, 默认3. 在DMA中不影响时间,       当SPI速度为系统时钟/2时执行DMA,SS_HOLD、SS_SETUP和SS_DACT都必须设置大于2的值.

//HSSPI_CFG2高速SPI配置寄存器2
#define                SPI_IOSW        (1<<6)        //bit6:交换MOSI和MISO脚位,0:不交换,1:交换
#define                HSSPIEN                (0<<5)        //bit5:高速SPI使能位,0:关闭高速模式,1:使能高速模式
#define                FIFOEN                (1<<4)        //bit4:高速SPI的FIFO模式使能位,0:关闭FIFO模式,1:使能FIFO模式,使能FIFO模式在DMA中减少13个系统时间。
#define                SS_DACT                        3        //bit3~0:高速模式时SS控制信号的DEACTIVE时间,0~15, 默认3, 不影响DMA时间.当SPI速度为系统时钟/2时执行DMA,SS_HOLD、SS_SETUP和SS_DACT都必须设置大于2的值.


void        SPI_DMA_TRIG(u8 xdata *TxBuf)
{
                                //@40MHz, Fosc/4, 200字节258us,100字节130us,50字节66us,N个字节耗时 N*1.280+2 us, 51T一个字节,其中状态机19T, 传输耗时32T.
                                //@40MHz, Fosc/2, 200字节177us,100字节 89.5us,50字节46us,N个字节耗时 N*0.875+2 us, 35T一个字节,其中状态机19T, 传输耗时16T.
                                //@40MHz, Fosc/2, SPI DMA传输一个字节, FIFO=1, HOLD=0,耗时16+3=19T(0.475us), HOLD=3,耗时16+6=22T(0.55us).
                                //@40MHz, Fosc/4, SPI DMA传输一个字节, FIFO=1, HOLD=0,耗时32+3=35T(0.875us), HOLD=3,耗时32+6=38T(0.95us).
        HSSPI_CFG= SS_HOLD | SS_SETUP;        //SS_HOLD会增加N个系统时钟, SS_SETUP没有增加时钟。驱动OLED 40MHz时SS_HOLD可以设置为0,
        HSSPI_CFG2 = SPI_IOSW | FIFOEN | SS_DACT;        //FIFOEN允许FIFO会减小13个时钟. @40MHz FIFOEN=1, SS_HOLD=0时523us @2T, 943us @4T;    FIFOEN=1, SS_HOLD=3时600us @2T, 1020us @4T.

        SPI_TxAddr   = (u16)TxBuf;                //要发送数据的首地址
        DMA_SPI_ITVH = 0;
        DMA_SPI_ITVL = 0;
        DMA_SPI_STA= 0;
        DMA_SPI_CFG= DMA_SPIIE | SPI_ACT_TX | SPI_ACT_RX | DMA_SPIIP | DMA_SPIPTY;
        DMA_SPI_CFG2 = SPI_WRPSS | SPI_SSS;

        P_OLED_CS = 0;
        B_TxCmd   = 0;                //已发送命令标志
        SPI_TxCnt = 0;                //SPI DMA触发发送次数, 一次128字节, 一共8次
        B_SPI_DMA_busy = 1;        //标志SPI-DMA忙,SPI DMA中断中清除此标志,使用SPI DMA前要确认此标志为0
        DMA_SPI_STA = DMA_SPIIF;        //软件触发SPI DMA中断,启动发送
}



//========================================================================
// 函数: void SPI_DMA_ISR (void) interrupt DMA_SPI_VECTOR
// 描述:SPI_DMA中断函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2024-1-5
//========================================================================
void SPI_DMA_ISR (void) interrupt DMA_SPI_VECTOR
{
        if(SPI_TxCnt >= 8)        //判断发送是否完毕,1.46ms @40MHz SPI-4T, 1.04ms @40MHz SPI-2T.
        {
                DMA_SPI_CR = 0;                        //关闭SPI DMA
                B_SPI_DMA_busy = 0;                //清除SPI-DMA忙标志,SPI DMA中断中清除此标志,使用SPI DMA前要确认此标志为0
                SPSTAT = 0x80 + 0x40;        //清0 SPIF和WCOL标志
                HSSPI_CFG2 = SPI_IOSW | SS_DACT;        //使用SPI查询或中断方式时,要禁止FIFO
                P_OLED_CS = 1;
        }
        else                //仍有数据要发送
        {
                if(!B_TxCmd)        //还没有发设置地址命令,则先发设置地址命令
                {
                        B_TxCmd = 1;        //指示已发地址命令
                        CmdTmp = (u8)(0xb0 + SPI_TxCnt);
                        CmdTmp = (u8)(((0>>4) & 0x0f) + 0x10); //设置列地址的高4 位
                        CmdTmp = 0 & 0x0f;                //设置列地址的低4 位

                        P_OLED_DC = 0;        //写命令
                        DMA_SPI_TXAH = (u8)((u16)CmdTmp >> 8);        //SPI DMA发送命令首地址
                        DMA_SPI_TXAL = (u8)CmdTmp;
                        DMA_SPI_AMTH = 0;                                //设置传输总字节数(高8位),        设置传输总字节数 = N+1
                        DMA_SPI_AMT= 3-1;                                //设置传输总字节数(低8位).
                        DMA_SPI_CR   = DMA_ENSPI | SPI_TRIG_M | SPI_TRIG_S | SPI_CLRFIFO;        //启动SPI DMA发送命令
                }
                else
                {
                        B_TxCmd = 0;        //清除已发地址命令
                        P_OLED_DC = 1;        //写数据
                        DMA_SPI_TXAH = (u8)(SPI_TxAddr >> 8);        //SPI DMA发送数据首地址
                        DMA_SPI_TXAL = (u8)SPI_TxAddr;
                        DMA_SPI_AMTH = 0;                                //设置传输总字节数(高8位),        设置传输总字节数 = N+1
                        DMA_SPI_AMT= 128-1;                        //设置传输总字节数(低8位).
                        DMA_SPI_CR   = DMA_ENSPI | SPI_TRIG_M | SPI_TRIG_S | SPI_CLRFIFO;        //启动SPI DMA发送命令
                        SPI_TxAddr+= 128;        //要发送数据的首地址, 一次DMA传输16字节
                        SPI_TxCnt++;                //发送次数+1
                }
        }

        DMA_SPI_STA = 0;                //清除中断标志
}


//====================================================================================
u8        xdata DisTmp;        //显示缓冲,将要显示的内容放在显存里,启动DMA即可. 由于LCM DMA有4字节对齐问题,所以这里定位对地址为4的倍数


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


void main(void)
{
        u16        i;

        EAXFR = 1;        //允许访问扩展寄存器
        WTST= 0;
        CKCON = 0;


        P0M1 = 0;        P0M0 = 0;        //设置为准双向口
        P1M1 = 0;        P1M0 = 0;        //设置为准双向口
        P2M1 = 0;        P2M0 = 0;        //设置为准双向口
        P3M1 = 0;        P3M0 = 0;        //设置为准双向口
        P4M1 = 0;        P4M0 = 0;        //设置为准双向口
        P5M1 = 0;        P5M0 = 0;        //设置为准双向口
        P6M1 = 0;        P6M0 = 0;        //设置为准双向口
        P7M1 = 0;        P7M0 = 0;        //设置为准双向口

        Initialize_OLED();
        EA = 1;

        while(1)
        {
                for(i=0; i<1024; i++)        DisTmp = 0;        //清除显存
                SPI_DMA_TRIG(DisTmp);
                while(B_SPI_DMA_busy);        //等待SPI DMA完成

                printf_ASCII_text(0, 0, "OLED12864 SSD1306");
                for(i=0; i<8; i++)        WriteHZ16((u8)(i*16),2,i);
                printf_ascii_10x24(0,5,"-12.345 678");
                LCD_delay_ms(3000);

                for(i=0; i<1024; i++)        DisTmp = gImage_picture1;        //将图片装载到显存
                SPI_DMA_TRIG(DisTmp);        // @40MHz FIFOEN=1, SS_HOLD=0时523us @2T, 943us @4T;
                LCD_delay_ms(3000);

                for(i=0; i<1024; i++)        DisTmp = gImage_picture2;        //将图片装载到显存
                SPI_DMA_TRIG(DisTmp);        // @40MHz FIFOEN=1, SS_HOLD=0时523us @2T, 943us @4T;
                LCD_delay_ms(3000);
        }
}







wuzhengmin 发表于 2026-1-12 13:53:07

我们把梁工的文件分成3块:

首先是主程序:

图形显示发送命令和图片数据使用SPI DMA操作,传输数据时不占用CPU时间。做GUI最方便了,可以先操作定义于xdata的1024字节缓存,然后触发SPI DMA即可,523us或943us即可自己动刷完。
本例运行于40MHz, SPI速度为主频4分频(10MHz),每次SPI DMA传输总时间943us,SPI速度为主频2分频(20MHz),每次SPI DMA传输总时间523us。

将要显示的内容放在1024字节的显存中,启动DMA传输即可。

下载时, 选择时钟 40MHz (用户可自行修改频率后重新编译即可).

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


       


        #include        "AI8051U.h"
        #include    "config.h"
        #include    "spi_dma.h"
        #include         "oled.h"
        #include        "picture1.h"
        #include        "picture2.h"


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



void main(void)
{
        u16        i;

        config();
       
        Initialize_OLED();
        EA = 1;

        while(1)
        {
                for(i=0; i<1024; i++)        DisTmp = 0;        //清除显存
                SPI_DMA_TRIG(DisTmp);
                while(B_SPI_DMA_busy);        //等待SPI DMA完成

                printf_ASCII_text(0, 0, "OLED12864 SSD1306");
                for(i=0; i<8; i++)        WriteHZ16((u8)(i*16),2,i);
                printf_ascii_10x24(0,5,"-12.345 678");
                LCD_delay_ms(3000);

                for(i=0; i<1024; i++)        DisTmp = gImage_picture1;        //将图片装载到显存
                SPI_DMA_TRIG(DisTmp);        // @40MHz FIFOEN=1, SS_HOLD=0时523us @2T, 943us @4T;
                LCD_delay_ms(3000);

                for(i=0; i<1024; i++)        DisTmp = gImage_picture2;        //将图片装载到显存
                SPI_DMA_TRIG(DisTmp);        // @40MHz FIFOEN=1, SS_HOLD=0时523us @2T, 943us @4T;
                LCD_delay_ms(3000);
        }
}


页: 53 54 55 56 57 58 59 60 61 62 [63] 64 65 66 67 68 69 70 71 72
查看完整版本: 有关DMA,山东大学陈桂友教授