| 本帖最后由 panxiaoyi 于 2025-4-20 11:24 编辑 
 CH32V103 的 SPI+DMA 的状态寄存器请教:
 我本来是要驱动 ST7789 彩屏的,采用硬件 16位SPI+16位DMA 传送显示的数据,指令数据由 8位SPI 查询死循环等待传送。使用 CH32V103R8T6 和 CH32V203G8R6 都出现了同样的问题。
 
 我现在做了代码精简,只有一个 main.c 文件,不再驱动显示,只看 SPI 输出的 (时钟、数据) 的波形
 现在的代码逻辑是:
 1:使用 SPI/DMA 输出 38400 个 16 位数据。
 2:等待 SPI 空闲,然后,单纯使用 8位SPI 输出几个数据,死循环查询等待结束。
 3:返回循环。
 
 代码如下:
 
 #include "debug.h"
#define SPI1_DMA_RX_CH   DMA1_Channel2
#define SPI1_DMA_TX_CH   DMA1_Channel3
#define SPI1_DMA_TC_FLAG DMA1_FLAG_TC3
#define SPI1_DMA_RC_FLAG DMA1_FLAG_TC2
uint16_t i    = 0;
void SPI1_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};                          // GPIO配置结构体
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;              // 选择SPI的SCK和MOSI引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                     // 复用推挽输出模式(用于SPI主设备输出)
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;                    // 速度
    GPIO_Init(GPIOA, &GPIO_InitStructure);                              // 使用上述参数初始化GPIOx
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;                           // SPI的MISO引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;                       // 上拉输入模式
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//==============================================================================
void SPI1_Init(void)
{
    SPI_InitTypeDef SPI_InitStructure = {0};                            // SPI配置结构体
    SPI_Cmd(SPI1, DISABLE);                                             // 在配置SPI模式前,需要清除SPE位
    SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;           // 单线单向发送模式(仅使用MOSI)
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                       // 配置为主机模式
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                   // 8位数据帧格式
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                         // 时钟极性:空闲时高电平
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;                        // 时钟相位:第二个边沿采样(CPOL=1 + CPHA=1 即SPI模式3)
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                           // 软件控制NSS(片选信号由用户代码控制)
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; // 波特率预分频系数
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                  // 高位先发送(MSB First)
    SPI_InitStructure.SPI_CRCPolynomial = 7;                            // CRC多项式值(此处设置但未启用CRC功能)
    SPI_Init(SPI1, &SPI_InitStructure);                                 // 使用上述参数初始化SPIx寄存器
    SPI_Cmd(SPI1, ENABLE);                                              // 启动SPIx工作(配置完成后激活外设)
}
//==============================================================================
void SPI1_Write(uint8_t data)
{
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);      // 等待SPI发送缓冲区为空
    SPI_I2S_SendData(SPI1, data);                                       // 发送数据
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));               // 等待SPI空闲
}
//==============================================================================
void DISPLAY_WR_REG(uint8_t data)
{
    SPI1_Write(data);
}
//==============================================================================
void DISPLAY_WR_DAT(uint8_t data)
{
    SPI1_Write(data);
}
//==============================================================================
void SPI1_DMA_Init_16B(uint8_t Flag)
{
    SPI_InitTypeDef SPI_InitStructure = {0};
    SPI_Cmd(SPI1, DISABLE);
    SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI1, &SPI_InitStructure);
    SPI_Cmd(SPI1, ENABLE);
        
        // 下面是DMA配置
    DMA_InitTypeDef DMA_InitStructure = {0};                                    // DMA初始化结构体
    DMA_DeInit(SPI1_DMA_TX_CH);                                                 // 复位SPIx发送通道的DMA寄存器到默认值
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DATAR;          // 设置外设地址为SPIx数据寄存器
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)NULL;                      // 暂设内存地址为NULL(实际使用前需配置有效地址)
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;                          // 传输方向:内存到外设(存储器作为源)
    DMA_InitStructure.DMA_BufferSize = 0;                                       // 传输数据量初始为0(实际使用需配置)
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;            // 外设地址固定(SPI数据寄存器不需要递增)
    if(!Flag) DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;           // 内存地址递增(用于传输数组数据)
        else DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;               // 内存地址固定(用于传输相同的数据)
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据宽度:16位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         // 内存数据宽度:16位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                               // 普通模式(传输完成后停止)
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;                         // 高通道优先级
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                                // 禁用内存到内存模式(使用外设模式)
    DMA_Init(SPI1_DMA_TX_CH, &DMA_InitStructure);                               // 使用上述参数初始化DMA通道
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);                            // 使能SPI1的TX DMA请求(当SPI需要发送数据时会自动触发DMA传输)
}
//==============================================================================
void SPI1_DMA_Write16B(uint16_t *data, uint16_t len)
{
    DMA_Cmd(SPI1_DMA_TX_CH, DISABLE);                             // 禁用DMA通道防止配置过程中意外触发
    DMA_ClearFlag(SPI1_DMA_TC_FLAG);                              // 清除该通道的传输完成标志(具体标志需根据通道选择)
    SPI1_DMA_TX_CH->MADDR = (uint32_t)data;                       // 设置内存源地址(待发送数据缓冲区)
    SPI1_DMA_TX_CH->CNTR = len;                                   // 设置传输数据长度(单位:数据项个数)
    DMA_Cmd(SPI1_DMA_TX_CH, ENABLE);                              // 使能DMA通道,开始数据传输
}
//==============================================================================
void DisplaySetWindows(uint16_t xStar, uint16_t yStar, uint16_t xEnd, uint16_t yEnd)
{
    __asm__ volatile("NOP");
    __asm__ volatile("NOP");
//  Delay_Ms(1000);
        while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));               // 等待SPI空闲
        SPI1_Init();
        
    DISPLAY_WR_REG(0x2a);
    DISPLAY_WR_DAT(xStar >> 8);
    DISPLAY_WR_DAT(0x00FF & xStar);
    DISPLAY_WR_DAT(xEnd >> 8);
    DISPLAY_WR_DAT(0x00FF & xEnd);
    DISPLAY_WR_REG(0x2B);
    DISPLAY_WR_DAT(yStar >> 8);
    DISPLAY_WR_DAT(0x00FF & yStar);
    DISPLAY_WR_DAT(yEnd >> 8);
    DISPLAY_WR_DAT(0x00FF & yEnd);
    DISPLAY_WR_REG(0x2C);
}
//==============================================================================
void DisplayFillColor(uint16_t Color, uint16_t len)
{
        while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));               // 等待SPI空闲
        SPI1_DMA_Init_16B(1);
        SPI1_DMA_Write16B(&Color,len);
}
//==============================================================================
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
    printf("This is printf example\r\n");
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);              // 必须开启GPIOB时钟才能操作其引脚
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,  ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,   ENABLE);
        SPI1_GPIO_Init();
    SPI1_Init();
    while(1)
    {
        i+=1000;                                //颜色变化
        DisplayFillColor(i,38400);//38400);     //显示填充(颜色,像数点的数量)
        Delay_Ms(100);                          //延时,以便观察SPI/DMA输出的波形
        DisplaySetWindows(0, 0, 240-1, 160-1);  //设置显示屏的显示窗口
    }
}
/*
现在遇到的问题是:
1:预期正常时:SPI/DMA 输出的时钟波形 = 数据波形(数据波形不应该出现 0 )
2:如果在 188 行设置断点,那面 SPI/DMA 输出的时钟正常,数据正常
3:如果在 188 行的函数的里面的开头设置断点,那么,进入函数后,SPI/DMA 的数据就输出0,时钟还是正常的
4:如果在 188 行里面增加大量延时,故障还是会出现
*/
 
 |