code-y 发表于 2025-10-24 12:24:55

【已解决】求助,SPI1复用串口1dma总是不成功

有没有大佬帮看看代码

#define MAIN_Fosc   40000000UL// 明确40MHz系统时钟(与硬件一致)
#include "rgb.h"

// 颜色定义(保持原逻辑,确保GRB顺序匹配WS2812)
uint8 xdata rgb_red = {0xff,0x00,0x00};    // 红色(RGB)→ GRB后为0x00,0xff,0x00
uint8 xdata rgb_green = {0x00,0xff,0x00};// 绿色 → GRB后为0xff,0x00,0x00
uint8 xdata rgb_blue = {0x00,0x00,0xff};   // 蓝色 → GRB后为0x00,0x00,0xff
uint8 xdata rgb_off = {0x00,0x00,0x00};    // 熄灭
uint8 xdata rgb_yellow = {0xff,0xff,0x00}; // 黄色
uint8 xdata rgb_purple = {0xff,0x00,0xff}; // 紫色
uint8 xdata rgb_cyan = {0x00,0xff,0xff};   // 青色
uint8 xdata rgb_white = {0xff,0xff,0xff};// 白色
uint8 xdata rgb_black = {0x00,0x00,0x00};// 黑色

// 呼吸灯颜色变量
uint8 xdata breath_rgb_color;

// WS2812编码缓冲区(3色×4SPI字节=12字节,单个灯,文档推荐缓冲区大小)
uint8 xdata ws2812_buffer;
bit B_SPI_DMA_busy = 0;// SPI-DMA忙标志(文档DMA状态管理规范)

// 状态变量(保持原定义)
static uint8 xdata current_rgb_color;
static RGB_STATE_enum current_state = RGB_STATE_OFF;
static RGB_COLOR_enum current_color = RGB_COLOR_BLACK;
static RGB_STATE_enum target_state = RGB_STATE_OFF;
static RGB_COLOR_enum target_color = RGB_COLOR_BLACK;
static uint32 last_toggle_time = 0;
static uint8 blink_state = 0;
static uint32 last_breath_time = 0;
static uint8 breath_direction = 1;
static uint8 breath_brightness = 10;

// 引脚定义(USART1 SPI通道3:P4.0-SS, P4.1-MOSI, P4.2-MISO, P4.3-SCK,文档2.1.3节管脚图)
#define SPI_MOSI_PIN 0x02// P4.1 (bit1)
#define SPI_SCK_PIN0x08// P4.3 (bit3)
#define SPI_MISO_PIN 0x04// P4.2 (bit2)
#define Pin2 0x04          // P4.2 (bit2)

/**
* @brief RGB模块初始化(适配40MHz时钟,遵循文档SPI/DMA配置规范)
* @note 参考文档:第24章SPI()、第25章HSSPI()、第34章DMA()
*/
void rgb_init(void)
{
    // 1. SPI引脚配置(文档13章I/O口配置)
    // MOSI(P4.1)、SCK(P4.3):推挽输出+高速模式(40MHz下需高速驱动)
    P4M0 |= (SPI_MOSI_PIN | SPI_SCK_PIN);
    P4M1 &= ~(SPI_MOSI_PIN | SPI_SCK_PIN);
    // 使用标准寄存器操作替代SlewRateHigh函数
    P4SR &= ~(SPI_MOSI_PIN | SPI_SCK_PIN);// 高速输出,确保TH/TL时序

    // MISO(P4.2):高阻输入+下拉(文档24.4节SPI从机配置)
    // 使用标准寄存器操作替代函数调用
    P4M1 |= SPI_MISO_PIN;// 高阻输入模式
    P4M0 &= ~SPI_MISO_PIN; // 输入模式
    P4PU &= ~SPI_MISO_PIN; // 禁止上拉
    P4PD |= SPI_MISO_PIN;// 允许下拉,确保空闲低电平

    // 2. USART1-SPI模式配置(文档18章同步串口)
    USARTCR1 = 0x00;                // 复位USART1
    USARTCR1 |= 0x08;               // 使能SPI功能(bit3=1)
    USARTCR1 &= ~0x10;            // 0=SPI模式(bit4=0)
    USARTCR1 |= 0x40;               // 主机模式(bit6=1)
    USARTCR1 |= 0x80;               // 使能USART1(bit7=1)
    USARTCR2 = 0x00;                // CPOL=0, CPHA=0(WS2812要求)
    USARTCR3 = 0x00;                // 8位数据长度(bit0-1=00)

    // 3. SPI波特率计算(40MHz→3.2MHz:Fosc/(2*(USARTBR+1))=3.2MHz → USARTBR=5,文档18.3.5节公式)
    USARTBRH = 0x00;
    USARTBRL = 0x05;

    // 4. 高速SPI配置(40MHz下时序补偿,文档25.1节)
    HSCLKDIV = 1;   // 主时钟分频(高频稳定)
    SPI_CLKDIV = 1;// SPI时钟分频
    HSSPI_CFG = (2 << 4) | 3;       // SS_HOLD=2, SS_SETUP=3(40MHz下补偿传输延迟)
    HSSPI_CFG2 = (1<<5) | (1<<4) | 3;// HSSPIEN=1(使能高速), FIFOEN=1(减少13个时钟延迟), SS_DACT=3

    // 5. DMA配置(USART1发送DMA,文档34.7节)
    DMA_UR1T_CR = 0x00;             // 停止当前DMA
    DMA_UR1T_STA = 0x00;            // 清除状态标志
    // DMA配置:中断使能+发送使能+优先级1+普通优先级
    DMA_UR1T_CFG = (1<<7) | (1<<6) | (1<<2) | 0;
    interrupt_set_priority(DMA_UR1T_VECTOR, 1);// 中断优先级1(文档14章中断)

    // 6. 初始化熄灭灯(确保初始状态可控)
    while(B_SPI_DMA_busy);
    send_color_data(rgb_off);
}

// 根据颜色枚举获取指针(保持原实现)
uint8* get_color_ptr(RGB_COLOR_enum color)
{
    switch(color)
    {
      case RGB_COLOR_RED:    return rgb_red;
      case RGB_COLOR_GREEN:return rgb_green;
      case RGB_COLOR_BLUE:   return rgb_blue;
      case RGB_COLOR_YELLOW: return rgb_yellow;
      case RGB_COLOR_PURPLE: return rgb_purple;
      case RGB_COLOR_CYAN:   return rgb_cyan;
      case RGB_COLOR_WHITE:return rgb_white;
      case RGB_COLOR_BLACK:return rgb_black;
      default:               return rgb_black;
    }
}

// 前向声明(保持原定义)
void RGB_SPI_DMA_TxTRIG(uint8 xdata *TxBuf, uint16 num);

// 发送颜色数据(保持原逻辑,增加SPI空闲检查)
void send_color_data(uint8 *color)
{
    while(B_SPI_DMA_busy);                  // 等待DMA空闲
    while(USARTCR1 & 0x20);               // 等待SPI总线空闲(避免冲突)
   
    // 更新当前颜色
    current_rgb_color = color;
    current_rgb_color = color;
    current_rgb_color = color;
   
    encode_ws2812_data(current_rgb_color);// 编码
    RGB_SPI_DMA_TxTRIG(ws2812_buffer, 12);// DMA发送
    while(B_SPI_DMA_busy);                  // 等待发送完成
   
    system_delay_us(50);// WS2812复位时间(≥50us)
}

/**
* @brief 启动SPI DMA传输(遵循文档34.4.4节配置)
* @param TxBuf:发送缓冲区地址
* @param num:传输字节数(实际长度,内部转换为num-1)
*/
void RGB_SPI_DMA_TxTRIG(uint8 xdata *TxBuf, uint16 num)
{
    uint16 j = (uint16)TxBuf;
    // 扩展寄存器已在主函数使能,无需重复调用EAXSFR

    // 停止当前DMA并等待SPI空闲(文档34.4.4节)
    DMA_UR1T_CR = 0x00;
    while(USARTCR1 & 0x20);// 等待SPI_BUSY标志清除(bit5=0)

    // 配置DMA地址和长度(文档34.7.2节)
    DMA_UR1T_TXAH = (uint8)(j >> 8);         // 发送地址高字节
    DMA_UR1T_TXAL = (uint8)j;               // 发送地址低字节
    DMA_UR1T_AMTH = (uint8)((num-1)/256);   // 总字节数=num-1(文档要求)
    DMA_UR1T_AMT= (uint8)((num-1)%256);
    // 移除不存在的寄存器
    // DMA_UR1T_ITVH = 0;                      // 无间隔时间
    // DMA_UR1T_ITVL = 0;
    DMA_UR1T_STA= 0x00;                   // 清除状态

    // 启动DMA传输(文档34.7.2节)
    DMA_UR1T_CR = (1<<7) | (1<<6) | 1;       // DMA_EN=1, TRIG_M=1, CLRFIFO=1
    B_SPI_DMA_busy = 1;
}

// SPI DMA中断服务函数(保持原逻辑)
void SPI_DMA_ISR (void) interrupt DMA_UR1T_VECTOR
{
    EAXSFR();// 使用宏替代直接寄存器访问
    DMA_UR1T_STA = 0;    // 清除中断标志
    DMA_UR1T_CR = 0x00;// 停止DMA
    B_SPI_DMA_busy = 0;// 释放忙标志
}

// WS2812编码(保持原逻辑,确认GRB顺序)
void encode_ws2812_data(uint8 *rgb_color)
{
    static const uint8 code_table = {0x88, 0x8E, 0xE8, 0xEE};// 00=0x88, 01=0x8E, 10=0xE8, 11=0xEE
    uint8 dat;
    uint8 color_order;

    // 清空缓冲区(32位操作优化)
    *((uint32*)&ws2812_buffer) = 0;
    *((uint32*)&ws2812_buffer) = 0;
    *((uint32*)&ws2812_buffer) = 0;

    // WS2812要求GRB顺序(原代码正确)
    color_order = rgb_color;// Green
    color_order = rgb_color;// Red
    color_order = rgb_color;// Blue

    // 编码Green通道
    dat = color_order;
    ws2812_buffer = code_table[(dat >> 6) & 0x03];
    ws2812_buffer = code_table[(dat >> 4) & 0x03];
    ws2812_buffer = code_table[(dat >> 2) & 0x03];
    ws2812_buffer = code_table;

    // 编码Red通道
    dat = color_order;
    ws2812_buffer = code_table[(dat >> 6) & 0x03];
    ws2812_buffer = code_table[(dat >> 4) & 0x03];
    ws2812_buffer = code_table[(dat >> 2) & 0x03];
    ws2812_buffer = code_table;

    // 编码Blue通道
    dat = color_order;
    ws2812_buffer = code_table[(dat >> 6) & 0x03];
    ws2812_buffer = code_table[(dat >> 4) & 0x03];
    ws2812_buffer = code_table[(dat >> 2) & 0x03];
    ws2812_buffer = code_table;
}

// RGB任务处理(保持原逻辑)
void rgb_task(void)
{
    uint8 *color_ptr;
    uint32 current_time = sys_tick_get();

    if (target_state != current_state || target_color != current_color) {
      current_state = target_state;
      current_color = target_color;
      color_ptr = get_color_ptr(current_color);
      blink_state = 0;
      breath_brightness = 10;
      breath_direction = 1;
      last_toggle_time = current_time;
      last_breath_time = current_time;
    }

    switch(current_state)
    {
      case RGB_STATE_OFF:
            send_color_data(rgb_off);
            break;
      case RGB_STATE_ON:
            color_ptr = get_color_ptr(current_color);
            send_color_data(color_ptr);
            break;
      case RGB_STATE_BLINK:
            if(current_time - last_toggle_time >= 500)
            {
                last_toggle_time = current_time;
                blink_state = !blink_state;
                send_color_data(blink_state ? get_color_ptr(current_color) : rgb_off);
            }
            break;
      case RGB_STATE_FAST_BLINK:
            if(current_time - last_toggle_time >= 200)
            {
                last_toggle_time = current_time;
                blink_state = !blink_state;
                send_color_data(blink_state ? get_color_ptr(current_color) : rgb_off);
            }
            break;
      case RGB_STATE_BREATH:
            if(current_time - last_breath_time >= 30)
            {
                last_breath_time = current_time;
                // 更新亮度
                if(breath_direction)
                  breath_brightness = (breath_brightness >= 245) ? 255 : (breath_brightness + 10);
                else
                  breath_brightness = (breath_brightness <= 10) ? 10 : (breath_brightness - 10);
                if(breath_brightness >= 255 || breath_brightness <= 10)
                  breath_direction = !breath_direction;

                // 计算呼吸颜色
                color_ptr = get_color_ptr(current_color);
                breath_rgb_color = (uint16)color_ptr * breath_brightness / 255;
                breath_rgb_color = (uint16)color_ptr * breath_brightness / 255;
                breath_rgb_color = (uint16)color_ptr * breath_brightness / 255;

                // 避免完全熄灭
                if(breath_rgb_color == 0 && breath_rgb_color == 0 && breath_rgb_color == 0)
                {
                  if(color_ptr > 0) breath_rgb_color = 1;
                  else if(color_ptr > 0) breath_rgb_color = 1;
                  else if(color_ptr > 0) breath_rgb_color = 1;
                }
                send_color_data(breath_rgb_color);
            }
            break;
      default:
            send_color_data(rgb_off);
            break;
    }
}

// 设置RGB颜色和状态(保持原逻辑)
void rgb_set_color_state(RGB_COLOR_enum color, RGB_STATE_enum state)
{
    target_color = color;
    target_state = state;
}

国芯人工智能 发表于 2025-10-24 12:28:00

您好,关于您提到的“SPI1复用串口1 DMA总是不成功”的问题,我将从以下几个方面进行分析与建议,帮助您排查问题根源:

一、问题定位与理解

您提到使用的是 SPI1,但尝试将其复用为 串口1(UART1)DMA 功能。这在某些MCU(如STM32系列)中是可能的,因为DMA通道通常是多路复用的,但需要注意以下几点:
DMA通道与外设映射关系:不同外设(SPI、UART等)使用的DMA通道和请求源不同,必须正确配置DMA请求映射。
引脚复用配置:若SPI1与UART1共享部分引脚或DMA通道,需确认GPIO配置是否冲突。
DMA优先级与中断冲突:多个DMA请求同时触发可能导致优先级冲突或未正确处理中断。

二、常见问题排查方向

1. 外设时钟是否已正确使能

确保以下外设时钟均已开启:

c
RCCAPB2PeriphClockCmd(RCCAPB2PeriphSPI1, ENABLE);
RCCAPB2PeriphClockCmd(RCCAPB2PeriphUSART1, ENABLE);
RCCAHBPeriphClockCmd(RCCAHBPeriphDMA1, ENABLE); // 或DMA2,视具体型号而定

2. DMA通道选择与映射是否正确

STM32中每个外设的DMA请求映射是固定的。例如:

| 外设       | DMA请求源 | DMA通道 | 方向(Mem/Periph) |
|------------|------------|----------|---------------------|
| SPI1 TX    | DMA1Channel3 | Channel3 | Mem → Periph |
| UART1 TX   | DMA1Channel4 | Channel4 | Mem → Periph |

请检查是否将SPI1的数据流错误地绑定到了UART1的DMA通道上,或者反之。

3. DMA配置参数是否正确

DMA配置包括:
数据源地址(Memory Address)
目标地址(Peripheral Address)
数据长度
数据宽度(字节/半字/字)
传输方向(Mem → Periph / Periph → Mem)
循环模式(Circular Mode)
中断使能(Transfer Complete / Error)

示例配置代码片段:

c
DMAInitTypeDef DMAInitStruct;

DMAInitStruct.DMAPeripheralBaseAddr = (uint32t)&SPI1->DR; // 或USART1->DR
DMAInitStruct.DMAMemoryBaseAddr = (uint32t)txBuffer;
DMAInitStruct.DMADIR = DMADIRPeripheralDST;
DMAInitStruct.DMABufferSize = bufferSize;
DMAInitStruct.DMAPeripheralInc = DMAPeripheralIncDisable;
DMAInitStruct.DMAMemoryInc = DMAMemoryIncEnable;
DMAInitStruct.DMAPeripheralDataSize = DMAPeripheralDataSizeByte;
DMAInitStruct.DMAMemoryDataSize = DMAMemoryDataSizeByte;
DMAInitStruct.DMAMode = DMAModeNormal;
DMAInitStruct.DMAPriority = DMAPriorityHigh;
DMAInitStruct.DMAM2M = DMAM2MDisable;

DMAInit(DMA1Channel3, &DMAInitStruct);
DMACmd(DMA1Channel3, ENABLE);

4. 外设DMA请求是否使能

对于SPI或UART,需单独开启其DMA请求:

c
SPII2SDMACmd(SPI1, SPII2SDMAReqTx, ENABLE); // SPI1 DMA使能
USARTDMACmd(USART1, USARTDMAReqTx, ENABLE);   // UART1 DMA使能

5. 是否存在DMA通道冲突

若同时使用多个DMA通道(如SPI1与UART1),请检查它们是否属于同一DMA控制器(DMA1/DMA2),并确保优先级设置合理,避免冲突。

三、调试建议

1. 使用调试器查看DMA状态寄存器

通过调试器查看DMAISR寄存器的状态,判断是否出现以下情况:
传输完成标志(TCIFx)
传输错误标志(TEIFx)
缓存溢出(DMEIFx)

2. 添加DMA中断处理函数

启用DMA中断并实现中断处理函数,可以更精确地定位失败原因:

c
void DMA1Channel3IRQHandler(void) {
    if (DMAGetITStatus(DMA1ITTC3)) {
      // 传输完成处理
      DMAClearITPendingBit(DMA1ITTC3);
    }
    if (DMAGetITStatus(DMA1ITTE3)) {
      // 传输错误处理
      DMAClearITPendingBit(DMA1ITTE3);
    }
}

3. 使用逻辑分析仪或示波器观察SPI/I2C波形

验证SPI是否发出数据,有助于判断是DMA未启动,还是外设本身未正确工作。

四、代码优化建议

从您提供的代码片段来看,似乎是在为WS2812灯带发送GRB数据。这类应用通常要求精确的时序控制,使用DMA可以有效减轻CPU负担。但需要注意以下几点:
DMA缓冲区应为全局或静态变量,避免局部变量被优化或释放。
SPI时钟频率应与WS2812要求匹配(通常为2.4MHz~3.2MHz)。
使用SPI的DMA发送模式,而非UART,因为UART波特率精度和DMA控制不如SPI灵活。

五、总结与建议

综上所述,您的问题可能源于以下几点之一:

1. DMA通道与外设映射错误
2. 外设DMA请求未正确使能
3. DMA配置参数不匹配
4. DMA中断未处理或冲突
5. SPI/UART引脚复用配置错误

建议您按照以下步骤逐一排查:

1. 检查DMA通道是否与外设匹配;
2. 检查外设DMA请求是否开启;
3. 配置DMA参数并启用中断;
4. 使用调试工具查看DMA状态;
5. 确保外设时钟和GPIO配置正确。

如能提供完整DMA初始化代码和外设配置代码,我可以进一步帮助您分析具体问题。

祝调试顺利!

如有其他问题,欢迎继续交流。

—— 国芯人工智能

[本文内容由国芯人工智能辅助生成,仅供参考]

ercircle 发表于 2025-10-24 13:34:18

有例程可参考:
https://www.stcaimcu.com/data/download/DemoCode/STC32G-DEMO-CODE-V9.6.zip




code-y 发表于 2025-10-24 16:19:21

ercircle 发表于 2025-10-24 13:34
有例程可参考:
https://www.stcaimcu.com/data/download/DemoCode/STC32G-DEMO-CODE-V9.6.zip



对就是看的这个,{:baoquan:}

code-y 发表于 2025-10-24 16:22:21

ercircle 发表于 2025-10-24 13:34
有例程可参考:
https://www.stcaimcu.com/data/download/DemoCode/STC32G-DEMO-CODE-V9.6.zip



佬,多少频率要上高速spi,还有高速spi是不是只支持独立spi模块,usart复用的不支持

ercircle 发表于 2025-10-24 16:29:42

code-y 发表于 2025-10-24 16:22
佬,多少频率要上高速spi,还有高速spi是不是只支持独立spi模块,usart复用的不支持 ...


spi二分频就开hsspi,usart和hsspi是否兼容得试下

页: [1]
查看完整版本: 【已解决】求助,SPI1复用串口1dma总是不成功