3线硬件SPI+DMA驱动 HX8347 TFT屏

3线硬件SPI+DMA驱动 HX8347 TFT屏,实现用DMA清屏。

a81c5161c51b4678bbb7adaeaebc35ee.jpg

参考:基于stm32 标准库spi驱动st7789彩屏TFT(使用DMA)-技术天地-深圳市修德电子有限公司

一、源码

HX8347.h

#ifndef USER_HX8347_H_
#define USER_HX8347_H_

#define SPI_hardware
#define SPI_hardware_dma

#define Buf_Size 480

#define X_MAX_PIXEL 240
#define Y_MAX_PIXEL 320

#define RED     0xf800
#define GREEN   0x07e0
#define BLUE    0x001f
#define WHITE   0xffff
#define BLACK   0x0000
#define YELLOW  0xFFE0
#define GRAY0   0xEF7D      //灰色0 3165 00110 001011 00101
#define GRAY1   0x8410          //灰色1      00000 000000 00000
#define GRAY2   0x4208          //灰色2  1111111111011111

#ifdef SPI_hardware
    #define LCD_CS          GPIO_Pin_0  // CS:PA0
    #define LCD_SDA         GPIO_Pin_7  // SDA:PA7  SPI1 //硬件SPI实现,不用人工处理
    #define LCD_SCL         GPIO_Pin_5  // SCL:PA5  SPI1 //硬件SPI实现,不用人工处理
    #define LCD_RST         GPIO_Pin_4  // RST:PA4
#else
    #define LCD_CS          GPIO_Pin_0  // CS:PA0
    #define LCD_SDA         GPIO_Pin_1  // SDA:PA1
    #define LCD_SCL         GPIO_Pin_3  // SCL:PA3
    #define LCD_RST         GPIO_Pin_4  // RST:PA4
#endif

#define LCD_SCL_SET GPIO_WriteBit(GPIOA, LCD_SCL,Bit_SET)
#define LCD_SDA_SET GPIO_WriteBit(GPIOA, LCD_SDA,Bit_SET)
#define LCD_CS_SET  GPIO_WriteBit(GPIOA, LCD_CS,Bit_SET)
#define LCD_RST_SET GPIO_WriteBit(GPIOA, LCD_RST,Bit_SET)


#define LCD_SCL_CLR GPIO_WriteBit(GPIOA, LCD_SCL,Bit_RESET)
#define LCD_SDA_CLR GPIO_WriteBit(GPIOA, LCD_SDA,Bit_RESET)
#define LCD_CS_CLR  GPIO_WriteBit(GPIOA, LCD_CS,Bit_RESET)
#define LCD_RST_CLR GPIO_WriteBit(GPIOA, LCD_RST,Bit_RESET)


void LCD_GPIO_Init(void);
void Lcd_WriteIndex(unsigned char Index);
void Lcd_WriteData(unsigned char Data);
void LCD_WriteData_16Bit(unsigned int Data);
void Lcd_Write_REG(unsigned char Index,unsigned char Data);
void LCD_Init(void);
void Lcd_Clear(unsigned int Color);
void FillRect(u16 x1, u16 y1, u16 x2, u16 y2, u16 color);
void FillRect_DMA(u16 color);

void Gui_DrawFont_GBK16(unsigned int x, unsigned int y, unsigned int fc, unsigned int bc, unsigned char *s);

#endif /* USER_HX8347_H_ */

HX8347.c

#include "debug.h"
#include "HX8347.h"
#include "font.h"


extern unsigned char TxData[];
extern void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx);

void LCD_GPIO_Init(void)
{
   GPIO_InitTypeDef GPIO_InitStructure = {0};

   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
#ifdef SPI_hardware
   GPIO_InitStructure.GPIO_Pin = LCD_CS|LCD_RST;
#else
   GPIO_InitStructure.GPIO_Pin = LCD_SCL|LCD_SDA|LCD_CS|LCD_RST;
#endif
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_Init(GPIOA, &GPIO_InitStructure);
}


//向SPI总线传输一个8位数据

void  SPI_WriteData(unsigned char Data)
{
#ifdef SPI_hardware

     SPI_I2S_SendData( SPI1, Data);
     while( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) == RESET );
     //Delay_Us(1);

     uint32_t i;

     SysTick->SR &= ~(1 << 0);
     i = 8;

     SysTick->CMP = i;
     SysTick->CTLR |= (1 << 4);
     SysTick->CTLR |= (1 << 5) | (1 << 0);

     while((SysTick->SR & (1 << 0)) != (1 << 0))
         ;
     SysTick->CTLR &= ~(1 << 0);

#else
    unsigned char i=0;
    for(i=8;i>0;i--)
    {
      if(Data&0x80)
          LCD_SDA_SET; //输出数据
      else LCD_SDA_CLR;

      LCD_SCL_CLR;
      LCD_SCL_SET;
      Data<<=1;
    }
#endif
}

//向液晶屏写一个8位指令

void Lcd_WriteIndex(unsigned char Index)
{
   //SPI 写命令时序开始
   LCD_CS_CLR;
   SPI_WriteData(0x70);
   SPI_WriteData(Index);

   LCD_CS_SET;
}



//向液晶屏写一个8位数据

void Lcd_WriteData(unsigned char Data)
{
   LCD_CS_CLR;

   SPI_WriteData(0x72);
   SPI_WriteData(Data);

   LCD_CS_SET;
}


//向液晶屏写一个16位数据
void LCD_WriteData_16Bit(unsigned int Data)
{
   LCD_CS_CLR;
   SPI_WriteData(0x72);
   SPI_WriteData(Data>>8);
   SPI_WriteData(Data);
   LCD_CS_SET;
}

void Lcd_Write_REG(unsigned char Index,unsigned char Data)
{
    Lcd_WriteIndex(Index);
    Lcd_WriteData(Data);
}

void Lcd_Reset(void)
{
    LCD_RST_CLR;
    Delay_Ms(50);
    LCD_RST_SET;
    Delay_Ms(50);
}





//  set region to paint
void LCD_SetWindow(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)
{

    //SC
    Lcd_Write_REG(0x02,x1>>8);       // Column address start2
    Lcd_Write_REG(0x03,(u8)x1);          // Column address start1
    //EC
    Lcd_Write_REG(0x04,x2>>8);       // Column address end2
    Lcd_Write_REG(0x05,(u8)x2);          // Column address end1
    //SP
    Lcd_Write_REG(0x06,y1>>8);       // Row address start2
    Lcd_Write_REG(0x07,(u8)y1);          // Row address start1
    //EP
    Lcd_Write_REG(0x08,y2>>8);       // Row address end2
    Lcd_Write_REG(0x09,(u8)y2);          // Row address end1
    //写0x22到index register,那么下次send data就会直接被写到graphic ram
    Lcd_WriteIndex(0x22);
}



void FillRect(u16 x1, u16 y1, u16 x2, u16 y2, u16 color)
{
    LCD_SetWindow(x1, y1,x2,y2);
    x2 = x2 - x1 + 1;
    y2 = y2 - y1 + 1;

    LCD_CS_CLR;
    SPI_WriteData(0x72);

    for(x1 = x2; x1 != 0 ; x1--)
    {
        for (y1 = y2;y1 != 0 ;y1--)
        {
            SPI_WriteData(color>>8);
            SPI_WriteData(color);
        }
    }

    LCD_CS_SET;
}


void FillRect_DMA(u16 color)
{
    int16_t j;
    LCD_SetWindow(0, 0,239,319);

    LCD_CS_CLR;
    SPI_WriteData(0x72);

    for(j=0 ;j<480;){
        TxData[j] = color>>8;
        TxData[j+1] = color;
     j += 2;
    }

    for(j = 0 ; j<320 ; j++){
        SPI_I2S_DMACmd(SPI1,SPI_I2S_DMAReq_Tx,ENABLE);
        MYDMA_Enable(DMA1_Channel3);

        while(1){
            if(DMA_GetFlagStatus(DMA1_FLAG_TC3)!=RESET)
            {
                DMA_ClearFlag(DMA1_FLAG_TC3);
                break;
            }
        }

    }
    LCD_CS_SET;
}


void LCD_Init(void)
{
    //LCD_GPIO_Init();
    Lcd_Reset();

    Lcd_Write_REG(0x18,0x88);        //UADJ 75Hz
    Lcd_Write_REG(0x19,0x01);        //OSC_EN='1', start Osc
    //Power Voltage Setting
    Lcd_Write_REG(0x1B,0x1e); //VRH=4.60V   0x1e
    Lcd_Write_REG(0x1C,0x04); //AP Crosstalk    04
    Lcd_Write_REG(0x1A,0x01); //BT (VGH~15V,VGL~-10V,DDVDH~5V)   0x01
    Lcd_Write_REG(0x24,0x21); //VMH 27   0x38
    Lcd_Write_REG(0x25,0x5F); //VML




    //VCOM offset
    Lcd_Write_REG(0x23,0x8C); //for Flicker adjust
    Lcd_Write_REG(0x1F,0x88);// GAS=1, VOMG=00, PON=0, DK=1, XDK=0, DVDH_TRI=0, STB=0
    Delay_Ms(50);
    Lcd_Write_REG(0x1F,0x80);// GAS=1, VOMG=00, PON=0, DK=0, XDK=0, DVDH_TRI=0, STB=0
    Delay_Ms(50);
    Lcd_Write_REG(0x1F,0x90);// GAS=1, VOMG=00, PON=1, DK=0, XDK=0, DVDH_TRI=0, STB=0
    Delay_Ms(50);
    Lcd_Write_REG(0x1F,0xD0);// GAS=1, VOMG=10, PON=1, DK=0, XDK=0, DDVDH_TRI=0, STB=0
    Delay_Ms(50);
    //Display ON Setting
    Lcd_Write_REG(0x28,0x38);   //GON=1, DTE=1, D=1000
    Delay_Ms(50);
    Lcd_Write_REG(0x28,0x3C);   //GON=1, DTE=1, D=1100
    Lcd_Write_REG(0x36,0x09);   //REV, BGR
    Lcd_Write_REG(0x17,0x05);  //16BIT/PIXEL


    //Gamma 2.2 Setting
    Lcd_Write_REG(0x40,0x00); //
    Lcd_Write_REG(0x41,0x00); //
    Lcd_Write_REG(0x42,0x00); //
    Lcd_Write_REG(0x43,0x11); //
    Lcd_Write_REG(0x44,0x0e); //
    Lcd_Write_REG(0x45,0x23); //
    Lcd_Write_REG(0x46,0x08); //
    Lcd_Write_REG(0x47,0x53); //
    Lcd_Write_REG(0x48,0x03); //
    Lcd_Write_REG(0x49,0x11); //
    Lcd_Write_REG(0x4A,0x18); //
    Lcd_Write_REG(0x4B,0x1a); //
    Lcd_Write_REG(0x4C,0x16); //
    Lcd_Write_REG(0x50,0x1c); //
    Lcd_Write_REG(0x51,0x31); //
    Lcd_Write_REG(0x52,0x2e); //
    Lcd_Write_REG(0x53,0x3f); //
    Lcd_Write_REG(0x54,0x3f); //
    Lcd_Write_REG(0x55,0x3f); //
    Lcd_Write_REG(0x56,0x2c); //
    Lcd_Write_REG(0x57,0x77); //
    Lcd_Write_REG(0x58,0x09); //
    Lcd_Write_REG(0x59,0x05); //
    Lcd_Write_REG(0x5A,0x07); //
    Lcd_Write_REG(0x5B,0x0e); //
    Lcd_Write_REG(0x5C,0x1c); //
    Lcd_Write_REG(0x5D,0x88); //


     Delay_Ms(100);
     FillRect(0, 0, 239, 319, RED );
}


/*************************************************
函数名:LCD_DrawPoint
功能:画一个点
入口参数:无
返回值:无
*************************************************/
void Gui_DrawPoint(unsigned int x,unsigned int y,unsigned int Data)
{
    LCD_SetWindow(x,y,x+1,y+1);
    LCD_WriteData_16Bit(Data);
}


void Gui_DrawFont_GBK16(unsigned int x, unsigned int y, unsigned int fc, unsigned int bc,unsigned char *s)
{
    unsigned char i,j;
    unsigned short k,x0;
    x0=x;
    while(*s)
    {

        if((*s) < 128)
        {
            k=*s;
            if (k==13)
            {
                x=x0;
                y+=16;
            }
            else
            {
                if (k>32) k-=32; else k=0;

                for(i=0;i<16;i++)
                for(j=0;j<8;j++)
                    {
                        if(asc16[k*16+i]&(0x80>>j)) Gui_DrawPoint(x+j,y+i,fc);
                        else
                        {
                            if (fc!=bc) Gui_DrawPoint(x+j,y+i,bc);
                        }
                    }
                x+=8;
            }
            s++;
        }

        else
        {


            for (k=0;k>j))  Gui_DrawPoint(x+j,y+i,fc);
                                else {
                                    if (fc!=bc) Gui_DrawPoint(x+j,y+i,bc);
                                }
                            }
                        for(j=0;j<8;j++)
                            {
                                if(hz16[k].Msk[i*2+1]&(0x80>>j))    Gui_DrawPoint(x+j+8,y+i,fc);
                                else
                                {
                                    if (fc!=bc) Gui_DrawPoint(x+j+8,y+i,bc);
                                }
                            }
                    }
                }
              }
            s+=2;x+=16;
        }

    }
}

main.c

/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2021/06/06
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for 
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
 *@Note
 GPIO routine:
 PA0 push-pull output.

*/

#include "debug.h"
#include "HX8347.h"

/* Global define */


/* Global Variable */

unsigned char TxData[Buf_Size]={0};
u16 DMA1_MEM_LEN;

/*********************************************************************
 * @fn      GPIO_Toggle_INIT
 *
 * @brief   Initializes GPIOA.0
 *
 * @return  none
 */
void GPIO_Toggle_INIT(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}


void SPI_1Lines_HalfDuplex_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure={0};
    SPI_InitTypeDef SPI_InitStructure={0};


    RCC_APB2PeriphClockCmd( RCC_APB2Periph_SPI1|RCC_APB2Periph_GPIOA, ENABLE );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOA, &GPIO_InitStructure );

    SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;         //在空闲状态下,时钟线保持低电平
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;        //数据在时钟的上升沿被传输。
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init( SPI1, &SPI_InitStructure );

    SPI_Cmd( SPI1, ENABLE );
}


//使能dma1的通道3,因为spi输出对应的是此通道
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{
  DMA_Cmd(DMA_CHx, DISABLE );
  DMA_SetCurrDataCounter(DMA1_Channel3,DMA1_MEM_LEN);
  DMA_Cmd(DMA_CHx, ENABLE);
}

/*********************************************************************
 * @fn      DMA_Tx_Init
 *
 * @brief   Initializes the DMAy Channelx configuration.
 *
 * @param   DMA_CHx - x can be 1 to 7.
 *          ppadr - Peripheral base address.
 *          memadr - Memory base address.
 *          bufsize - DMA channel buffer size.
 *
 * @return  none
 */
void DMA_Tx_Init( DMA_Channel_TypeDef* DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize)
{
    DMA_InitTypeDef DMA_InitStructure={0};

    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );

    DMA_DeInit(DMA_CHx);
    DMA1_MEM_LEN=bufsize;

    DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;
    DMA_InitStructure.DMA_MemoryBaseAddr = memadr;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = bufsize;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init( DMA_CHx, &DMA_InitStructure );
}

/*********************************************************************
 * @fn      main
 *
 * @brief   Main program.
 *
 * @return  none
 */
int main(void)
{
    u8 i = 0;

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);	
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    printf("GPIO Toggle TEST\r\n");
    GPIO_Toggle_INIT();

    LCD_GPIO_Init();
#ifdef SPI_hardware
    printf("SPI_HARDWARE");
    SPI_1Lines_HalfDuplex_Init();
#ifdef SPI_hardware_dma
    printf("SPI_HARDWARE_DMA");
    DMA_Tx_Init( DMA1_Channel3, (u32)&SPI1->DATAR, (u32)TxData, Buf_Size );
    DMA_Cmd( DMA1_Channel3, ENABLE );
#endif
#else
    printf("SPI_SOFTWARE");
#endif
    LCD_Init();


    GPIO_WriteBit(GPIOB, GPIO_Pin_13,Bit_RESET);



    while(1)
    {
        //下面用的是SPI+DMA

        Delay_Ms(1000);
        FillRect_DMA(GREEN);
        Delay_Ms(1000);
        FillRect_DMA(GREEN);
        Delay_Ms(1000);
        FillRect_DMA(BLUE);
        Delay_Ms(1000);
        FillRect_DMA(YELLOW);
        Delay_Ms(1000);
        FillRect(0, 0, 239, 63, RED );

        //下面用的硬件SPI

        Gui_DrawFont_GBK16(15,5,BLACK,GRAY0,"HELLO world");
        Gui_DrawFont_GBK16(15,25,RED,GRAY0,"LCD OK DISPLAY");
        Gui_DrawFont_GBK16(15,45,RED,GRAY0,"智能物联红外测温系统通过人数");
        Gui_DrawFont_GBK16(15,65,RED,GRAY0,"报警人数人体温度环境表面距离");
        Gui_DrawFont_GBK16(15,85,RED,GRAY0,"网络连接中.温度气象站雨量风速");
        Gui_DrawFont_GBK16(15,105,RED,GRAY0,"创新实验平台℃");

        Delay_Ms(1000);
        GPIO_WriteBit(GPIOB, GPIO_Pin_13, (i == 0) ? (i = Bit_SET) : (i = Bit_RESET));
        printf( "GPIOB:%d\r\n", i);


        FillRect(0, 0, 239, 319, RED );
        Delay_Ms(1000);
        FillRect(0, 0, 239, 319, GREEN );
        Delay_Ms(1000);
        FillRect(0, 0, 239, 319, BLUE );
        FillRect(50, 50, 100, 100, RED );




    }
}

二、说明

1、DMA初始化:

void DMA_Tx_Init( DMA_Channel_TypeDef* DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize)
{
    DMA_InitTypeDef DMA_InitStructure={0};

    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );

    DMA_DeInit(DMA_CHx);
    DMA1_MEM_LEN=bufsize;

    DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;
    DMA_InitStructure.DMA_MemoryBaseAddr = memadr;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = bufsize;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init( DMA_CHx, &DMA_InitStructure );
}

2、用DMA主要是清屏:

void FillRect_DMA(u16 color)
{
    int16_t j;
    LCD_SetWindow(0, 0,239,319);

    LCD_CS_CLR;
    SPI_WriteData(0x72);

    for(j=0 ;j<480;){
        TxData[j] = color>>8;
        TxData[j+1] = color;
     j += 2;
    }

    for(j = 0 ; j<320 ; j++){
        SPI_I2S_DMACmd(SPI1,SPI_I2S_DMAReq_Tx,ENABLE);
        MYDMA_Enable(DMA1_Channel3);                //传输

        while(1){
            if(DMA_GetFlagStatus(DMA1_FLAG_TC3)!=RESET)
            {
                DMA_ClearFlag(DMA1_FLAG_TC3);
                break;
            }
        }

    }
    LCD_CS_SET;
}
 

为了使用DMA申请了一个480字节的空间,就是屏幕上一行所需空间(240*2):

unsigned char TxData[Buf_Size]={0}; 

清屏过程就是每次DMA发送480个字节清一行,然后循环320次清掉整个屏幕。

如果改变 TxData的值,可以向屏幕发送一些字符、图画什么的。需要写一个改变TxData内容的函数。

3、可以考虑为DMA申请更大的内存空间,那样就可以一次传更多的数据。但是240*320*2=153600=150KB空间,一般单片机没有这么大的SRAM,怎么都需要分屏传输。

SPI+DMA

你可能感兴趣的:(c语言,单片机,开发语言)