打印
[RISC-V MCU 应用开发]

CH32V103 的 SPI+DMA 的状态寄存器请教

[复制链接]
1113|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 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 行里面增加大量延时,故障还是会出现
*/


微信图片_20250420112201.png (924.74 KB )

微信图片_20250420112201.png

微信图片_20250420112218.png (770.57 KB )

微信图片_20250420112218.png

微信图片_20250420112227.png (913.97 KB )

微信图片_20250420112227.png

CH32V203G8R6 错误仿真.rar

496.07 KB

使用特权

评论回复

相关帖子

沙发
LIzs6| | 2025-3-31 13:43 | 只看该作者
建议你把不同位置对应的屏幕显示现象放一下。根据程序,位置1和位置2基本没区别,唯一区别可能是while等待与SPI初始化之间的间隔大一些,你可以在片选之后加几个nop指令试一下

使用特权

评论回复
板凳
panxiaoyi|  楼主 | 2025-4-1 00:41 | 只看该作者
好的,过几天试试,这几天比较忙,多谢先

使用特权

评论回复
地板
panxiaoyi|  楼主 | 2025-4-1 23:29 | 只看该作者
本帖最后由 panxiaoyi 于 2025-4-1 23:58 编辑

应该是楼上 LIzs6 说的问题,我插多一条不相干的语句,显示又不同了,估计是CS、DC、与SPI的时序出现了问题

使用特权

评论回复
5
panxiaoyi|  楼主 | 2025-4-20 11:32 | 只看该作者
我已经更新了 1 楼的帖子。
我现在遇到的问题是:
1:预期正常时:SPI/DMA 输出的时钟波形 = 数据波形(数据波形不应该出现 0 )
2:如果在 188 行设置断点,那面 SPI/DMA 输出的时钟正常,数据正常。符合预期
3:如果在 188 行的函数的里面的开头设置断点,那么,进入函数后,SPI/DMA 的数据就输出0,时钟还是正常的
4:如果在 188 行里面增加大量延时,故障还是会出现

现在请教大家,有空帮我看看,是哪里出现了错误?
编译器版本如下图:

微信图片_20250420113221.png (59.75 KB )

微信图片_20250420113221.png

使用特权

评论回复
6
panxiaoyi|  楼主 | 2025-4-22 22:45 | 只看该作者
估计找到问题了,如图,还有注释,就是函数 B 调用函数 A 的问题,当然,我不确定就是这个问题。
我的分析:
1:函数 B 的参数 1 是普通变量,函数 A 的参数 1 是指针变量;
2:由于它们在函数里面,都属于“临时的局部变量”
3:当有其它第三方的函数出现时,其它函数的“参数”也刚刚好用到了上面的“局部变量”
4:由于函数 A 的 DMA 寄存器,要不断的重复的去读取这个临时变量的“地址”的里面的数据
5:这个地址的数据就有可能是错误的。
我现在修正了这个问题,暂时没有发现错误,运行正常。
当然,还请大家帮我分析一下,是不是就是这个问题,还是其它问题,感谢。
下面就是有问题的完整代码:

下面就是main.c文件,就是一个C文件,没有其它用户文件了

#include "debug.h"

#define SPI1_DMA_TX_CH   DMA1_Channel3
#define SPI1_DMA_TC_FLAG DMA1_FLAG_TC3

volatile  uint16_t  i;

volatile  uint32_t  aaa,bbb,ccc,ddd,eee,fff,ggg,hhh;

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_DMA_Init_16B(void)
{
    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数据寄存器不需要递增)
    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)
{
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));     // 等待SPI空闲
    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 SPI_DMA_OUT(uint16_t Color, uint16_t len)
{
        while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));     // 等待SPI空闲
        SPI1_DMA_Write16B(&Color,len);
}

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

void Test_Fun(unsigned char test1, unsigned char test2)
{
    Delay_Ms(200);                 //问题的出现,与这个时间参数的大小没有关联

   Delay_Ms(200);                  //随便添加、删除这两条代码,或者其它代码,问题就会出现

   if(test1 || test2) eee++;       //随便添加、删除这两条代码,或者其它代码,问题就会出现
}

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

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);   //SPI的GPIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,  ENABLE);   //SPI时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,   ENABLE);    //DMA时钟

        SPI1_GPIO_Init();
    SPI1_DMA_Init_16B();

    while(1)
    {
        i=0x0F01;                 //测试的数据

        SPI_DMA_OUT(i,38400);     //实测发送 38400 个数据约需要 275ms

        Delay_Ms(100);            //延时 100ms,用示波器观察(SPI/时钟/数据)波形

        Delay_Ms(1);              //仿真调试,运行到这里暂停,SPI的时钟、数据的输出波形,都是=275ms,说明运行正常
                                  //备注:暂停期间, SPI/DMA 还会正常运行直到结束

        Test_Fun(1,2);            //这个函数会破坏 SPI/DMA 的正常运行
                                  //从进入这个函数后开始,SPI/DMA 的时钟输出还是完整的,但是输出的数据总是=0
                                          
        Delay_Ms(300);            //仿真调试,如果运行到这里才暂停,这时候,SPI输出时钟波形=275ms,属正常
                                  //但是,数据输出波形只有 100ms,相当于后面的输出是 175ms 的 0 波形
                                  //说明这个测试函数破坏了SPI或者DMA的的数据

        Delay_Ms(300);
    }
}


微信图片_20250422222741.png (153.01 KB )

微信图片_20250422222741.png

CH32V203G8R6 SPI DMA 错误.rar

495.43 KB

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

52

主题

413

帖子

2

粉丝