再写TIMER+DMA驱动GPIO

本文介绍了一种使用TIMER+DMA+GPIO的组合来优化单线总线时序输出的方法,通过硬件外设而非软件延时来控制电平变化,大大提高了RTOS环境下通信效率,避免了进程抢占导致的时序错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

早两年前写过用DMA直接驱动GPIO的文章,当时写的只是比较原理性的,没有实例。最近在用到单线总线,上了RTOS,为了提高效率,减少内核的浪费,就想到用TIMER+DMA+GPIO去输出单线总线时序。

上图是单线总线的时序,常规的方法就直接控制IO输出,电平的间隔用延时去控制,代码如下:

void SC50X0B_SDA(unsigned char data)
  {
      unsigned char i;
      SC5020_SDA_Reset();
      HAL_Delay(3);
      for(i=0;i < 8;i++)
      {
        SC5020_SDA_Set();
        if(data & 0x01)
        {
          delay_us(1200);
          SC5020_SDA_Reset();
          delay_us(400);
        }
        else
        {
          delay_us(400);
          SC5020_SDA_Reset();
          delay_us(1200);
        }
        data >>= 1;
      }
      SC5020_SDA_Set();
      delay_us(200);
  }

整个时序执行需要2.5+(1.2+0.4)X8 = 15.3ms,这样内核要花15.3ms时间去输出这个时序,如果中断频繁也会导致时序有误差,引用通信失败。尤其是上了RTOS的话,会占用进程太长的时间,导致其它的进程无法及时执行,或者进程被抢占,导致时序误差,通信失败。当然很多人会有说用定时器中断去控制IO输出也可以实现,这样就大大的提高的效率,我这里不过多的讨论。用TIMER+DMA+GPIO的话,全靠外设输出时序,完全不占用内核的时间,这样的效率达到最高。如下是完整的代码实现:

#define TIMEFORDMATOGPIO TIM1
#define TIMEFORDMATOGPIO_CLK __HAL_RCC_TIM1_CLK_ENABLE
#define TIMEFORDMATOGPIODAN DMA1_Channel5
TIM_HandleTypeDef  sc5080htim;
 DMA_HandleTypeDef  hdma_sc5080tim_up;
 uint32_t SC5080Data[4 *8 +1];
void SC5080_TIMx_MspInit(void);
 
void SC5080_TIMx_Init(void)
{
  sc5080htim.Instance = TIMEFORDMATOGPIO;
  sc5080htim.Init.Prescaler = 72 - 1;
  sc5080htim.Init.CounterMode = TIM_COUNTERMODE_UP;
  sc5080htim.Init.Period = 400;
  sc5080htim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  SC5080_TIMx_MspInit();
  __HAL_TIM_ENABLE_DMA(&sc5080htim, TIM_DMA_UPDATE);
  HAL_TIM_Base_Init(&sc5080htim);
}
 
 
void SC5080_TIMx_MspInit(void)
{
    /* Peripheral clock enable */
    TIMEFORDMATOGPIO_CLK();
   
  hdma_sc5080tim_up.Instance = TIMEFORDMATOGPIODAN;
  hdma_sc5080tim_up.Init.Direction = DMA_MEMORY_TO_PERIPH;
  hdma_sc5080tim_up.Init.PeriphInc = DMA_PINC_DISABLE;
  hdma_sc5080tim_up.Init.MemInc = DMA_MINC_ENABLE;
  hdma_sc5080tim_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
  hdma_sc5080tim_up.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
  hdma_sc5080tim_up.Init.Mode = DMA_NORMAL;
  hdma_sc5080tim_up.Init.Priority = DMA_PRIORITY_VERY_HIGH;
  HAL_DMA_Init(&hdma_sc5080tim_up);
}
 
void SC5080B_Set(uint8_t data)
{
      uint8_t i;
      SC5020_SDA_Reset();
#if 0
      HAL_Delay(3);
#else   
      osDelay(3);
#endif
      for(i=0;i < 8;i++)
      {
        if(data & 0x01)
        {
          SC5080Data[i*4 +0] =   SC5020_SDA_Pin;
          SC5080Data[i*4 +1] =   SC5020_SDA_Pin;
          SC5080Data[i*4 +2] =   SC5020_SDA_Pin;
          SC5080Data[i*4 +3] =   SC5020_SDA_Pin<<16;
        }
        else
        {
          SC5080Data[i*4 +0] =   SC5020_SDA_Pin;
          SC5080Data[i*4 +1] =   SC5020_SDA_Pin<<16;
          SC5080Data[i*4 +2] =   SC5020_SDA_Pin<<16;
          SC5080Data[i*4 +3] =   SC5020_SDA_Pin<<16;
        }
        data >>= 1;
      }
            SC5080Data[i*4 +0] = SC5020_SDA_Pin;
     HAL_DMA_Start_IT(&hdma_sc5080tim_up,(uint32_t)(&SC5080Data),(uint32_t)(&(SC5020_SDA_GPIO_Port->BSRR)),4*8+1);
     HAL_TIM_Base_Start(&sc5080htim);
}

大概的原理就是利用定时器的update(或者PWM)去触发对应的DMA通道,DMA从内存搬到数据到GPIO的BSRR寄存器,GPIO对的Pin就会改变电平。这里用到了TIM1,查手册知道TIM1的update对应的DMA通道是DMA1的通道5,所以初始了TIM1跟DMA1_Channel5。

发送一个Bit需要1600us,也就是4个400us,也就是说如果发1就是,定时器触发4次DMA,DMA搬运内存到GPIO的BSRR,只要搬到的数据到BSRR对应IO的输出1110,GPIO就是输出1200us的高电平,400us的低电,那IO就正好输出时序中的1。8个Bit,就是让DMA搬到4X8次,就可以整个时序。实际使用中只要调用 void SC5080B_Set(uint8_t data),计算当时发送时序需要搬到的数据,再启动DMA跟定时器,时序就会自动输出,不在需要内核的干预。当然这很容易拓展到同时最多输出16路的时序,效率上也一样的,大大的节省的内核的开销,让程序更加的高效。

/*************************************************************************** * @file hw_timer.h * @brief This file contains all the macros for bsp_timer.c & bsp_timer.h. * **************************************************************************** * @attention * * Created on: 2025-07-18 * Author: YL Monitor Software group * **************************************************************************** * @description * * 定时器的硬件资源映射 * 定时器中断服务,用于高频执行事件任务的服务入口 * ****************************************************************************/ #ifndef __HW_TIMER_H_ #define __HW_TIMER_H_ #ifdef __cplusplus extern "C" { #endif /************************ Includes *************************/ /************************ Exportd types ********************/ /************************ Exportd constants ****************/ /************************ Exportd macros *******************/ /* HMI CAN timer 硬件资源映射*/ #define HW_TIMER TIM4 /* APB1 Timer Clock 72MHz */ #define HW_TIMER_RCC_CLK RCC_APB1Periph_TIM4 #define HW_TIMER_ClockCmd RCC_APB1PeriphClockCmd #define HW_TIMER_PRESCALER 7199 //((uint16_t) ((SystemCoreClock / 1000) - 1))//1ms #define HW_TIMER_PERIOD 9 #define HW_TIMER_IRQn TIM4_IRQn #define HW_TIMER_PreemptionPriority 0 #define HW_TIMER_IRQHandler TIM4_IRQHandler /************************ Exportd variables ****************/ /************************ Exportd Functions ****************/ /************************************************************ * @funName : BSP_HwInitHwTimer * @Input : NULL * @Description : 定时器硬件资源初始化 * @Athor : YL Software Group * @Version : V0.0.0 * @Data : 2025-07-18 *************************************************************/ extern void BSP_HwInitHwTimer(void); extern void Main_Loop_Process(void); #ifdef __cplusplus } #endif #endif /* __HW_TIMER_H_ */ /************************ End of file *************************/ /*************************************************************************** * @file bsp_timer.c * @brief **************************************************************************** * @attention * Created on: 2025-07-18 * Author: YL Monitor Software group **************************************************************************** * @description ****************************************************************************/ /************************ Includes *************************/ #include "main.h" /************************ Private macros *************************/ /************************ Private types **************************/ /*********************** Private constants ***********************/ /************************ Private variables **********************/ /************************ Functions ******************************/ // 发送数据缓冲区(50字节,用于50ms发送阶段) uint8_t g_send_buffer[50] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31}; // 状态变量 typedef enum { SEND_PHASE, // 发送阶段(50ms) DELAY_PHASE // 延时阶段(200ms) } WorkPhase; static WorkPhase current_phase = SEND_PHASE; // 当前阶段,初始为发送阶段 static uint8_t send_index = 0; // 发送字节索引(0-49) static uint8_t delay_counter = 0; // 延时计数器(0-199,对应200ms) static uint8_t timer1ms_flag = 0; // 1ms定时标志 /************************************************************ * @funName : BSP_HwInitHwTimer * @Input : NULL * @Description : 定时器硬件资源初始化 * @Athor : YL Software Group * @Version : V0.0.0 * @Data : 2025-07-18 *************************************************************/ void BSP_HwInitHwTimer(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; /* HMI CAN TIM clock enable */ HW_TIMER_ClockCmd(HW_TIMER_RCC_CLK, ENABLE); TIM_InternalClockConfig(HW_TIMER); /* Time base configuration */ TIM_TimeBaseStructure.TIM_Period = HW_TIMER_PERIOD; TIM_TimeBaseStructure.TIM_Prescaler = HW_TIMER_PRESCALER; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(HW_TIMER, &TIM_TimeBaseStructure); /* TIM IT enable */ TIM_ITConfig(HW_TIMER, TIM_IT_Update, ENABLE); /* Enable the HMI CAN TIM global Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = HW_TIMER_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4;//HW_TIMER_PreemptionPriority; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); /* HMI CAN TIM enable counter */ TIM_Cmd(HW_TIMER, ENABLE); } /************************************************************ * @funName : HW_TIMER_IRQHandler * @Input : NULL * @Description : TIM7 中断函数 * @Athor : YL Software Group * @Version : V0.0.0 * @Data : 2025/3/22 *************************************************************/ void HW_TIMER_IRQHandler(void) { if(TIM_GetITStatus(HW_TIMER, TIM_IT_Update)==SET) { /* 清除标志位 */ TIM_ClearITPendingBit(HW_TIMER, TIM_IT_Update); timer1ms_flag = 1; } } /** * @brief 主循环处理函数,根据当前阶段执行对应逻辑 */ void Main_Loop_Process(void) { if (timer1ms_flag) { // 检测到1ms定时 timer1ms_flag = 0; // 清零标志 switch (current_phase) { case SEND_PHASE: // 发送当前索引的字节 BSP_Send2Serial1(&g_send_buffer[send_index], 1); send_index++; // 发送完50字节后,切换到延时阶段 if (send_index >= 50) { send_index = 0; // 重置发送索引 current_phase = DELAY_PHASE; // 切换到延时阶段 delay_counter = 0; // 重置延时计数器 } break; case DELAY_PHASE: delay_counter++; // 延时200ms后,切换回发送阶段 if (delay_counter >= 200) { current_phase = SEND_PHASE; // 切换到发送阶段 delay_counter = 0; // 重置延时计数器 } break; default: current_phase = SEND_PHASE; // 异常状态重置 delay_counter=0; send_index=0; break; } } } /************************ End of file *************************/ /*************************************************************************** * @file hw_serial1.h * @brief This file contains all the macros for bsp_serial1.c & bsp_serial1.h. * **************************************************************************** * @attention * * Created on: 2025-03-18 * Author: YL Monitor Software group * **************************************************************************** * @description * * 串行通信1(与功率部件1内部通信)的硬件资源映射 * ****************************************************************************/ #ifndef __HW_SERIAL1_H_ #define __HW_SERIAL1_H_ #ifdef __cplusplus extern "C" { #endif /************************ Includes *************************/ #include "main.h" /************************ Exportd types ********************/ /************************ Exportd constants ****************/ /************************ Exportd macros *******************/ /* 串行通信1 波特率 */ #define SERIAL1_BAUD_RATE 115200 /* 串口1硬件资源映射 */ #define SERIAL1 USART3 #define SERIAL1_IRQn USART3_IRQn #define SERIAL1_PreemptionPriority 6 #define SERIAL1_IRQHandler USART3_IRQHandler /****************Tx****************/ #define SERIAL1_TX_PORT GPIOB #define SERIAL1_TX_PIN GPIO_Pin_10 /****************Rx****************/ #define SERIAL1_RX_PORT GPIOB #define SERIAL1_RX_PIN GPIO_Pin_11 #define SERIAL1_CLK RCC_APB1Periph_USART3 //USART1 #define SERIAL1_ClockCmd RCC_APB1PeriphClockCmd //USART1时钟 #define SERIAL1_DMA_CLK RCC_AHBPeriph_DMA1 //DMA1 #define SERIAL1_DMA_ClockCmd RCC_AHBPeriphClockCmd //DMA1时钟 /* RM0008 Table 78. Summary of DMA1 requests for each channel */ #define SERIAL1_DMATx_Channel DMA1_Channel2 #define SERIAL1_DMATx_IT_TC DMA1_IT_TC2 #define SERIAL1_DMATx_FLAG DMA1_FLAG_GL2|DMA1_FLAG_TC2|DMA1_FLAG_HT2|DMA1_FLAG_TE2 #define SERIAL1_DMATx_PreemptionPriority 7 #define SERIAL1_DMATx_IRQn DMA1_Channel2_IRQn #define SERIAL1_DMATx_IRQHandler DMA1_Channel2_IRQHandler /* RM0008 Table 78. Summary of DMA1 requests for each channel -- RM0008 * 表78每个通道的DMA1请求摘要*/ #define SERIAL1_DMARx_Channel DMA1_Channel3 #define SERIAL1_DMARx_IT_TC DMA1_IT_TC3 #define SERIAL1_DMARx_FLAG DMA1_FLAG_GL3|DMA1_FLAG_TC3|DMA1_FLAG_HT3|DMA1_FLAG_TE3 #define SERIAL1_DMARx_PreemptionPriority 8 #define SERIAL1_DMARx_IRQn DMA1_Channel3_IRQn #endif /************************ Exportd variables ****************/ /************************ Exportd Functions ****************/ #ifdef __cplusplus } #endif /* __HW_SERIAL1_H_ */ /********************************************************* * @file bsp_serial1.c * Created on: 2025-07-22 * Author: YL Monitor Software group *********************************************************/ /************************ Includes *************************/ #include "main.h" /************************ Private macros *************************/ /************************ Private types **************************/ /*********************** Private constants ***********************/ /************************ Functions ******************************/ /********************* DMA收发缓冲区大小定义*********************/ /* 串行通信 DMA发送缓冲区大小 */ #define SERIAL_TX_BUFF_SIZE 64 /* 串行通信 DMA接收缓冲区大小 */ #define SERIAL_RX_BUFF_SIZE 64 /************************ Private variables **********************/ /* 串行通信 DMA发送缓存 */ static uint8_t DMA_TxBuff[SERIAL_TX_BUFF_SIZE] = {0}; /* 串行通信 DMA接收缓存 */ static uint8_t DMA_RxBuff[SERIAL_RX_BUFF_SIZE] = {0}; /* 串行通信 接收数据队列 */ static QueueHandle_t queueRecvData = NULL; static SemaphoreHandle_t mutexSerialSend = NULL; /* 串行通信1 数据接收离线状态时间计数器 * 在硬件定时器中进行累加 * DMA接收中断中清零 * 当离线超过5ms,接收结束,或者设备离线,可以进行数据转发至上位机*/ /************************ Functions ******************************/ /**************************************** * @funName : BSP_HwInitSerial1 * @Description : 串行通信口1 硬件资源初始化 * @Athor : YL Software Group * @Version : V0.0.0 * @Data : 2025-07-22 ********************************************/ void BSP_HwInitSerial1(void) { GPIO_InitTypeDef GPIO_InitStructure ={0}; USART_InitTypeDef USART_InitStructure ={0}; NVIC_InitTypeDef NVIC_InitStructure ={0}; DMA_InitTypeDef DMA_InitStructure ={0}; /* 开启所有GPIO 时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //USART1开时钟 SERIAL1_ClockCmd(SERIAL1_CLK, ENABLE); // 使能DMA1时钟 SERIAL1_DMA_ClockCmd(SERIAL1_DMA_CLK,ENABLE); /****************Tx****************/ GPIO_InitStructure.GPIO_Pin = SERIAL1_TX_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; GPIO_Init(SERIAL1_TX_PORT, &GPIO_InitStructure); /****************Rx****************/ GPIO_InitStructure.GPIO_Pin = SERIAL1_RX_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入 GPIO_Init(SERIAL1_RX_PORT, &GPIO_InitStructure); /* 配置USART1 */ USART_DeInit(SERIAL1); USART_InitStructure.USART_BaudRate = SERIAL1_BAUD_RATE; //波特率 USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(SERIAL1, &USART_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = SERIAL1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = SERIAL1_PreemptionPriority; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // USART_ITConfig(SERIAL1, USART_IT_IDLE, ENABLE); //启用的USART1中断 USART_ITConfig(SERIAL1, USART_IT_RXNE, ENABLE); /* DMA发送初始化 */ DMA_DeInit(SERIAL1_DMATx_Channel); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&SERIAL1->DR); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DMA_TxBuff; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = SERIAL_TX_BUFF_SIZE; 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_Mode_Circular DMA_Mode_Normal DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(SERIAL1_DMATx_Channel, &DMA_InitStructure); /* DMA发送中断优先级设置 */ NVIC_InitStructure.NVIC_IRQChannel = SERIAL1_DMATx_IRQn ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = SERIAL1_DMATx_PreemptionPriority; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); DMA_ITConfig(SERIAL1_DMATx_Channel, DMA_IT_TC, ENABLE); USART_DMACmd(SERIAL1, USART_DMAReq_Tx, ENABLE); DMA_Cmd(SERIAL1_DMATx_Channel, DISABLE); /* DMA接收初始化 */ DMA_DeInit(SERIAL1_DMARx_Channel); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&SERIAL1->DR); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DMA_RxBuff; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = SERIAL_RX_BUFF_SIZE; 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_Mode_Circular DMA_Mode_Normal DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(SERIAL1_DMARx_Channel, &DMA_InitStructure); /* DMA接收中断优先级设置 */ NVIC_InitStructure.NVIC_IRQChannel = SERIAL1_DMARx_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = SERIAL1_DMARx_PreemptionPriority; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); DMA_ITConfig(SERIAL1_DMARx_Channel, DMA_IT_TC, DISABLE); DMA_Cmd(SERIAL1_DMARx_Channel, ENABLE); USART_DMACmd(SERIAL1, USART_DMAReq_Rx, ENABLE); //启用USAR1T1外设 USART_Cmd(SERIAL1, ENABLE); } /********************************************* * @funName : BSP_SwInitSerial1 * @Description : 串行通信口1 软件资源初始化 * @Athor : YL Software Group * @Version : V0.0.0 * @Data : 2025-07-22 ************************************************/ void BSP_SwInitSerial1(void) { // 创建接收队列(存储uint8_t类型数据) if(NULL == queueRecvData) { queueRecvData = xQueueCreate(SERIAL_DATA_QUEUE_SIZE, sizeof(uint8_t)); /* 数据接收队列创建失败 */ if(NULL == queueRecvData) { } } // 创建发送互斥锁 if(NULL == mutexSerialSend) { mutexSerialSend = xSemaphoreCreateBinary(); // mutexSerialSend = xSemaphoreCreateMutex(); /* 数据发送互斥量创建失败 */ if(NULL == mutexSerialSend) { } else { /* 释放数据发送互斥量 */ xSemaphoreGive(mutexSerialSend); } } } /****************************************** * @funName : SERIAL1_IRQHandler * @Description : 串行通信口1 中断,用于处理空闲接收中断, * 获取DMA接收的数据,并缓存至数据队列 * @Athor : YL Software Group * @Version : V0.0.0 * @Data : 2025-07-22 **************************************************/ void SERIAL1_IRQHandler(void) { if(USART_GetITStatus(SERIAL1, USART_IT_RXNE) != RESET)//接收数据寄存器不空中断 { uint8_t byte = 0; uint16_t i = 0, rlen = 0; portBASE_TYPE xRecvWoken = pdFALSE; byte = USART_ReceiveData(SERIAL1); rlen = SERIAL_RX_BUFF_SIZE - DMA_GetCurrDataCounter(SERIAL1_DMARx_Channel); DMA_Cmd(SERIAL1_DMARx_Channel, DISABLE); DMA_ClearFlag(SERIAL1_DMATx_FLAG); if (rlen > 0 && queueRecvData != NULL) { // 将DMA接收缓冲区数据存入队列 for (i = 0; i < rlen; i++) { // xQueueSendFromISR(queueRecvData, &DMA_RxBuff[i], NULL); if(errQUEUE_FULL == xQueueSendFromISR(queueRecvData, &DMA_RxBuff[i], &xRecvWoken)) { break; } } } USART_ClearITPendingBit(SERIAL1, USART_IT_IDLE); DMA_SetCurrDataCounter(SERIAL1_DMARx_Channel, SERIAL_RX_BUFF_SIZE); DMA_Cmd(SERIAL1_DMARx_Channel, ENABLE); } /* 全部数据发送完成,产生该标记 */ if (USART_GetITStatus(SERIAL1, USART_IT_TC) != RESET) //检查USART3中断是否发生 { USART_ClearITPendingBit(SERIAL1, USART_IT_TC); //清除USART3的中断挂起位-传输完全中断 } } /***************************************** * @funName : SERIAL1_DMATx_IRQHandler * @Description : 串行通信口1 DMA 发送中断 * @Athor : YL Software Group * @Version : V0.0.0 * @Data : 2025-07-22 *******************************************/ void SERIAL1_DMATx_IRQHandler(void) { portBASE_TYPE xSendWoken = pdFALSE; if(RESET != DMA_GetITStatus(SERIAL1_DMATx_IT_TC)) { /* 关闭 DMA */ DMA_Cmd(SERIAL1_DMATx_Channel, DISABLE); /* 清除完成标记 */ DMA_ClearITPendingBit(SERIAL1_DMATx_IT_TC); /* 清除数据长度 */ DMA_SetCurrDataCounter(SERIAL1_DMATx_Channel, 0); USART_ITConfig(SERIAL1, USART_IT_TC, DISABLE); if(NULL != mutexSerialSend) { /* 释放 OS 数据发送权限 */ xSemaphoreGiveFromISR(mutexSerialSend, &xSendWoken); portYIELD_FROM_ISR(xSendWoken); } } } /********************************************** * @funName : BSP_Send2Serial1 * @Description : 串行通信口1 DMA发送启动 * @Athor : YL Software Group * @Version : V0.0.0 * @Data : 2025-07-22 **********************************************/ void BSP_Send2Serial1(uint8_t* sbuf, const uint16_t slen) { uint16_t len = slen; if(len == 0 ||NULL == sbuf) { return; } /******************************************** *获取OS 数据发送权限 * <DMA发送完成后,在串行通信中断服务中释放> *********************************************/ if(NULL != mutexSerialSend) { xSemaphoreTake(mutexSerialSend, portMAX_DELAY); } else { //检查是否设置了DMA1通道标志 DMA1_IT_TC2 while(!DMA_GetFlagStatus(SERIAL1_DMATx_IT_TC)); } /*发送DMA流的地址不自增-不使用*/ //SERIAL1_DMATx_Channel->CCR |= (1 << 10); /*设置接收和发送的内存地址*/ SERIAL1_DMATx_Channel->CMAR = (uint32_t)sbuf; //设置当前DMA1通道传输中的数据单元数 DMA_SetCurrDataCounter(SERIAL1_DMATx_Channel, len);//DMA1_Channel4 //启用DMA1通道4 DMA_Cmd(SERIAL1_DMATx_Channel, ENABLE); //启用USART1的DMA接口 USART DMA传输请求 USART_DMACmd(SERIAL1, USART_DMAReq_Tx, ENABLE); } /******************************************** * @funName : BSP_Recv4Serial1 * @Description : 从串口1接收数据队列获取数据 * @Athor : YL Software Group * @Version : V0.0.0 * @Data : 2025-07-22 **********************************************/ void BSP_Recv4Serial1(uint8_t * rbuf, uint16_t* rlen) { uint16_t iCnt = 0; *rlen = 0; if(NULL == queueRecvData || NULL == rbuf) { return; } for(iCnt = 0; iCnt < SERIAL_DATA_QUEUE_SIZE; ++iCnt){ if(pdFALSE == xQueueReceive(queueRecvData, &rbuf[iCnt], 0)){ break; } } *rlen = iCnt; } /************************ End of file *************************/ /*************************************************************************** * @file main.c * @attention * Created on: 2025-03-18 * Author: YL Monitor Software group **************************************************************************** * @description ****************************************************************************/ /************************ Includes ********************************/ #include "main.h" /************************ Private types ***************************/ /************************ Private constants ***********************/ /************************ Private macros **************************/ /* 任务优先级定义 */ #define TASK_SCI1_RECV_PRIORITY (tskIDLE_PRIORITY + 7) //任务sci1接收优先级 #define TASK_SCI1_RECV_STACK_SIZE 128 //任务sci1接收堆栈大小 /************************ Private variables ***********************/ /************************ Functions *******************************/ /****************************** * @funName : main * @Description : 系统主函数,完成软硬件初始化 * 产品业务相关任务创建,并启动任务调度 * @Athor : YL Software Group * @Version : V0.0.0 * @Data : 2025/3/27 * ***********************************/ int main( void ) { /* 功率部件1的串行通信驱动-DMA1 */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); /* Flash1的spi驱动 */ BSP_HwInitFlash1SPI(); //Flash1 存储器 SPI通信接口初始化 /* 功率部件1的串行通信驱动-DMA1 */ BSP_HwInitSerial1(); //串行通信口1 硬件资源初始化-DMA1 BSP_HwInitHwTimer(); //定时器硬件资源初始化 //进入临界区时,首先关闭中断,当退出所有嵌套的临界区后再使能中断 taskENTER_CRITICAL(); /*********** 初始化各个模块使用到的OS相关资源 *********************/ BSP_SwInitSerial1(); /********************* 创建产品业务相关任务 ***********************/ //功率部件1(DSP SCI)通信信息流接收处理任务-USART3-DMA1 /* 功率部件1信息流监听任务 */ //任务创建(任务功能) xTaskCreate((TaskFunction_t ) vTask_SCI1RecvPro, (char* ) "vTask_SCI1RecvPro", (uint16_t ) TASK_SCI1_RECV_STACK_SIZE, (void * ) NULL, (UBaseType_t ) TASK_SCI1_RECV_PRIORITY, //任务控制块-空 (TaskHandle_t* ) NULL); /* 退出任务 */ taskEXIT_CRITICAL(); /* Start the scheduler. */ vTaskStartScheduler(); return 0; } /************************ End of file *************************/ 用keil530版本软件编写STM32F105VCT6芯片单片机中,APB2=72MHZ,APB1=36HZ,针对以上程序,当S-TLINK未连接时,BSP_Send2Serial1发送出来的数据乱码,深度分析程序,从keil5软件和代码中给出有效的解决办法,用标准库写出需要修改的代码和注释
最新发布
08-01
<think>我们面对的问题是:STM32F105VCT6芯片,使用标准外设库(可能是StdPeriph库),在Keil5.30环境下,通过DMA发送串口数据出现乱码,且特别指出在ST-LINK未连接时出现异常。 首先,我们需要分析可能的原因: 1. 时钟配置问题:串口和DMA的时钟使能是否正确,系统时钟配置是否正确(特别是使用外部晶振时)。 2. 波特率设置问题:串口波特率计算错误,与实际通信的波特率不一致。 3. 引脚配置问题:串口引脚(TX/RX)是否配置正确,复用功能是否正确。 4. DMA配置问题:DMA传输的源地址、目标地址、数据长度、传输模式等配置是否正确。 5. 中断问题:是否有相关中断冲突,或者DMA传输完成中断处理不当。 6. 电源和复位问题:ST-LINK未连接时,可能通过ST-LINK给目标板供电,断开后供电不足导致工作异常。 7. 程序启动配置问题:启动文件是否正确,堆栈大小是否足够。 8. 调试器影响:有时调试器连接时会改变某些行为(比如时钟源),断开后可能不同。 根据用户描述,问题在ST-LINK未连接时出现,因此我们特别关注第6点(电源)和第8点(调试器影响)。另外,时钟配置也很关键,因为ST-LINK连接时可能会提供外部时钟(如果使用调试器的时钟源),断开后使用板载时钟,如果配置不当则会出现问题。 具体步骤: 1. 检查电源:确保在断开ST-LINK后,目标板有独立的电源供电,且电压稳定。因为STM32在电压不足时可能工作不稳定。 2. 检查时钟源:STM32F105VCT6的时钟源可以来自内部HSI(8MHz)或外部HSE(通常8MHz)。在标准库中,SystemInit()函数会在启动时调用,它会根据配置选择时钟源。如果使用HSE,而板子上没有外部晶振或者晶振未起振,则系统会切换到HSI,导致时钟频率变化,从而波特率也会变化,出现乱码。 3. 检查波特率设置:在串口初始化时,波特率的计算依赖于系统时钟。如果系统时钟因时钟源改变而变化,波特率就会不准确。因此,需要确认系统时钟配置是否正确,以及是否使用了外部晶振。 4. DMA配置:检查DMA的配置,包括: - 源地址:是否为要发送的数据的地址(通常是数组的地址) - 目标地址:是否为串口数据寄存器的地址(如USART1->DR) - 传输方向:内存到外设 - 数据宽度:8位(串口数据寄存器是8位) - 传输模式:是否使能了循环模式?在发送时通常使用正常模式(一次传输) - 是否使能了DMA传输完成中断?如果使用,中断服务函数是否正确? 5. 串口配置:确保在DMA传输前,串口已经初始化,并且使能了串口的DMA发送请求(USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);)。 6. 在启动DMA传输前,需要确保DMA通道已经配置并使能。同时,在发送数据时,需要检查前一次DMA传输是否完成,避免重叠发送。 7. 乱码问题还可能是由于硬件问题,比如串口线接触不良、电平不匹配(如RS232需要电平转换)等,但这里用户提到ST-LINK连接时正常,断开时异常,所以硬件问题可能性小。 8. 考虑堆栈设置:如果使用DMA中断,中断服务函数中如果有较大的局部变量,可能导致栈溢出,从而破坏内存。可以尝试增大堆栈大小(在启动文件中修改Stack_Size)。 9. 检查初始化顺序:确保时钟初始化(RCC配置)->GPIO初始化->串口初始化->DMA初始化,并且DMA配置在串口初始化之后。 针对ST-LINK未连接时的异常,我们重点检查时钟和电源: - 时钟:在程序启动时,默认情况下是使用内部HSI(8MHz)作为时钟源。如果程序配置了使用外部晶振(HSE),但外部晶振没有工作,则系统会继续使用HSI。而ST-LINK连接时,可能会通过某种方式(如提供时钟)使外部晶振工作,断开后就不工作。因此,我们需要检查外部晶振是否正常,以及程序是否正确地切换到了HSE。 如何检查? 在程序初始化后,可以读取RCC->CFGR寄存器的值,查看系统时钟源。或者,在调试时(连接ST-LINK)查看时钟配置情况,然后断开ST-LINK,重新上电,通过串口打印系统时钟状态(如果串口还能工作的话)。但问题中串口输出乱码,可能无法打印。因此,我们可以用以下方法: 方法1:使用示波器测量晶振引脚,看是否有振荡信号。 方法2:在程序启动时,通过设置一个GPIO引脚,用软件模拟一个时钟信号(比如在初始化时钟前拉高,初始化后拉低),然后用示波器测量这个GPIO的脉冲宽度,可以大致判断系统时钟频率(但不够准确)。 方法3:在程序中,通过SysTick定时器来测量系统时钟频率。例如,配置SysTick为1ms中断一次,然后在中断中翻转一个GPIO,用示波器测量该GPIO的周期(应该是2ms方波)。 但用户可能没有示波器,所以我们可以通过软件方法检查:在程序中读取RCC->CFGR的SWS位(系统时钟切换状态),判断当前系统时钟源。 参考代码: ```c // 在main函数初始化后,读取当前系统时钟源 uint32_t clockSource = RCC->CFGR & RCC_CFGR_SWS; if (clockSource == RCC_CFGR_SWS_HSI) { // 当前系统时钟为HSI } else if (clockSource == RCC_CFGR_SWS_HSE) { // 当前系统时钟为HSE } else if (clockSource == RCC_CFGR_SWS_PLL) { // 当前系统时钟为PLL } ``` 如果发现断开ST-LINK后,系统时钟源为HSI,而连接时是PLL(由HSE倍频得到),那么问题就出在外部晶振没有起振。 解决外部晶振不起振的方法: - 检查硬件:晶振是否损坏,负载电容是否正确,布线是否过长。 - 在程序中调整启动时间:在RCC配置中,增加HSE启动超时时间,或者尝试使用CSS(时钟安全系统)检测HSE状态。 另外,如果用户没有使用外部晶振,而是使用内部HSI,那么需要确保在初始化串口时,波特率是按照HSI的频率计算的。但是,HSI的精度较低(1%),在较高波特率时可能误差较大,导致乱码。因此,建议使用外部晶振。 9. 电源:断开ST-LINK后,目标板必须由其他电源供电(比如USB或外部电源)。检查供电电压是否在2.0V~3.6V之间,并且稳定。 10. 复位电路:ST-LINK连接时,复位引脚可能被调试器控制,断开后复位电路是否正常?检查复位引脚是否有上拉电阻,复位信号是否稳定。 11. 还有一种可能是:在断开ST-LINK后,程序没有完全复位,导致状态异常。可以尝试在断开ST-LINK后,手动复位目标板。 12. 关于DMA配置,这里给出一个标准配置示例(以USART1_TX,DMA1通道4为例): ```c DMA_InitTypeDef DMA_InitStructure; // 使能DMA1时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 配置DMA DMA_DeInit(DMA1_Channel4); // 通道4对应USART1_TX DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)txBuffer; // 发送缓冲区 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 内存到外设 DMA_InitStructure.DMA_BufferSize = txBufferSize; 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(DMA1_Channel4, &DMA_InitStructure); // 使能DMA通道 DMA_Cmd(DMA1_Channel4, ENABLE); // 使能串口的DMA发送请求 USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); ``` 在发送数据时,需要先设置DMA的源地址和长度,然后启动DMA传输: ```c // 设置源地址和长度 DMA_SetCurrDataCounter(DMA1_Channel4, len); // 设置传输长度 DMA_SetMemoryAddress(DMA1_Channel4, (uint32_t)buffer); // 设置内存地址 // 启动传输(如果之前已经使能了通道,这里不需要再使能,但是需要清除传输完成标志) DMA_ClearFlag(DMA1_FLAG_TC4); // 清除传输完成标志 DMA_Cmd(DMA1_Channel4, ENABLE); // 如果之前被禁用,则使能 // 等待传输完成(或者使用中断方式) while (!DMA_GetFlagStatus(DMA1_FLAG_TC4)); ``` 注意:在启动一次新的传输前,要确保DMA通道已经停止(或者等待上一次传输完成),然后重新设置传输长度和地址,再启动。 13. 如果以上都检查无误,那么可能是软件配置的某个细节问题。我们可以尝试使用一个简单的串口发送函数(不使用DMA,而是轮询方式)来测试串口,如果轮询方式在断开ST-LINK时也正常,则问题在DMA配置或使用上。 总结解决步骤: 1. 确保断开ST-LINK后,目标板有独立稳定的电源。 2. 检查时钟配置,确保系统时钟源正确,并且波特率设置正确。 3. 检查外部晶振是否正常工作。 4. 检查DMA和串口配置代码。 5. 检查堆栈大小,适当增大。 6. 尝试手动复位目标板。 如果用户能够提供具体的代码片段(如时钟初始化、串口初始化、DMA初始化),我们可以进一步分析。 由于用户提到在ST-LINK未连接时异常,我们优先怀疑时钟和电源问题。 相关问题: 1. 如何检查STM32的外部晶振是否起振? 2. STM32使用DMA发送串口数据时,如何避免数据覆盖? 3. STM32F105VCT6的时钟树配置有哪些注意事项? 4. 如何通过软件检测STM32的系统时钟频率? 5. 在使用标准外设库时,如何正确配置DMA进行串口发送?</think>### STM32F105VCT6 DMA串口发送乱码问题分析与解决 #### 问题现象分析 当ST-LINK未连接时,STM32F105VCT6使用DMA发送串口数据出现乱码,可能涉及以下原因: 1. **时钟配置问题**:ST-LINK连接时会提供时钟参考,断开后时钟源不稳定 2. **DMA配置错误**:内存/外设地址对齐或传输模式不当 3. **波特率偏差**:系统时钟变化导致波特率计算错误 4. **电源干扰**:ST-LINK断开造成供电波动 5. **初始化顺序**:外设初始化时序不当 #### 关键解决步骤 ##### 1. 时钟配置检查 ```c // 确保使用外部晶振并正确配置PLL RCC_DeInit(); RCC_HSEConfig(RCC_HSE_ON); if (RCC_WaitForHSEStartUp() == SUCCESS) { RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); // 8MHz*9=72MHz RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); SystemCoreClockUpdate(); // 更新系统时钟变量 } ``` **验证**:在`main()`中添加时钟状态检测: ```c if (RCC_GetSYSCLKSource() != 0x08) { // 0x08表示PLL作为系统时钟源 Error_Handler(); } ``` ##### 2. DMA配置修正 ```c DMA_InitTypeDef DMA_InitStructure; // 确保DMA配置匹配外设 DMA_DeInit(USART1_TX_DMA_CHANNEL); // 通道4 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)txBuffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 内存->外设 DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE; 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_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(USART1_TX_DMA_CHANNEL, &DMA_InitStructure); ``` ##### 3. 波特率精确计算 使用宏确保波特率计算准确: ```c #define USART_BAUDRATE 115200 USART_InitStructure.USART_BaudRate = SystemCoreClock / 0x10000 * (SystemCoreClock / USART_BAUDRATE + 0x8000) >> 16; ``` ##### 4. 电源稳定性增强 在VDDA和VSSA引脚添加滤波电容: ```markdown 硬件修改建议: - 增加10μF钽电容并联0.1μF陶瓷电容到VDD - VDD与VDDA间串接磁珠(22Ω/100MHz) - 复位引脚加10kΩ上拉电阻 ``` ##### 5. 初始化顺序优化 ```c // 正确初始化顺序: 1. RCC时钟配置 2. GPIO初始化(USART_TX推挽输出,50MHz) 3. DMA初始化 4. USART初始化(使能DMA发送请求) 5. NVIC配置(如需中断) USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); // 关键步骤! ``` #### 验证方法 1. 断开ST-LINK独立供电 2. 使用示波器测量: - 晶振引脚波形(应稳定8MHz) - 串口TX引脚波特率(115200±2%) 3. 发送固定模式数据(如0x55, 0xAA)检验波形完整性 #### 常见问题排查表 | 现象 | 可能原因 | 解决方案 | |------|----------|----------| | 随机单字节错误 | DMA内存地址未对齐 | 使用`__align(4)`定义缓冲区 | | 连续乱码 | 波特率偏差>3% | 检查PLL配置,更换晶振 | | 仅首字节正确 | DMA未重新使能 | 传输前调用`DMA_Cmd(DISABLE)`+`DMA_SetCurrDataCounter()`+`DMA_Cmd(ENABLE)` | | ST-LINK连接时正常 | 调试器提供时钟 | 启用CSS时钟安全系统:`RCC_ClockSecuritySystemCmd(ENABLE)` | > 最终解决方案往往需要结合逻辑分析仪抓取DMA传输时序和USART波形进行对比分析[^1][^2]。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值