硬汉嵌入式论坛

 找回密码
 立即注册
查看: 798|回复: 19
收起左侧

[UART] 任意GPIO使用TIM+DMA+EXTI模拟UART

  [复制链接]

20

主题

94

回帖

154

积分

初级会员

积分
154
发表于 2024-3-5 09:12:19 | 显示全部楼层 |阅读模式
任意GPIO用TIM+DMA+EXTI模拟串口

原理

先从一个最简单的需求开始,用任意一个GPIO来模拟串口的TXD引脚,发送数据,数据格式选择8N1,波特率9600。

串口数据帧
串口的数据帧格式如图所示:

要用GPIO来模拟TXD引脚,就是按照数据帧的格式要求,按照指定的波特率改变引脚电平。
比如以9600波特率,发送数据0x55,就是要在TXD引脚上产生如下波形。

GPIO的BSRR寄存器

改变引脚电平,通过GPIO的BSRR寄存器可以方便的对实现。
比如:

GPIOD->BSRR  = (GPIO_PIN_5) << 16;                // PD5引脚置低
GPIOD->BSRR  = GPIO_PIN5;                                // PD5引脚置高


按照指定的波特率改变引脚电平

定时中断的方式

9600的波特率,就需要以1/9600的频率改变引脚电平。用定时中断来完成的示意代码如下:

typedef enum {
        TX_STATE_IDLE,
        TX_STATE_PREP,
        TX_STATE_START,
        TX_STATE_DATA,
        TX_STATE_STOP,
}enum_tx_state_t;


uint8_t tx_data;                        // 要发送的数据
void putchar(uint8_t x)
{
        tx_data = x;
        enable_timer_isr();
}
void timer_isr(void)
{
        static enum_tx_state_t tx_state = TX_STATE_IDLE;
        static uint8_t tx_count = 0;
        static uint8_t tx_data;                       

        switch(tx_state)
        {
                case TX_STATE_IDLE:
                        tx_count = 0;
                        tx_state = TX_STATE_START;
                        break;
                case TX_STATE_START:
                        TX_PIN_LOW();
                        tx_data = get_send_data();
                        break;
                case TX_STATE_DATA:
                        if(tx_data & (1<<tx_count)){
                                TX_PIN_HIGH();
                        }else{
                                TX_PIN_LOW();
                        }
                        if(++tx_count > 8){
                                tx_state = TX_STATE_STOP;
                        }
                        break;
                case TX_STATE_STOP:
                default:
                        TX_PIN_HIGH();
                        tx_state = TX_STATE_IDLE;
                        disable_timer_isr();
                        break;       
        }

}

采用中断来模拟的缺点在于,如果要以较高的波特率来传输数据时,timer_isr中断频率太高,9600bps就要求0.1ms中断频率了,如果波特率再提高的话,对系统的造成非常高的负载。

采用TIM+DMA的方式
为了降低系统的负载,可以采用的一个方法是,准备一个TxBuffer,使用定时器触发DMA,由DMA将TxBuffer的数据传输到GPIO的BSRR寄存器。
比如,传输0x55这个数据,采用8N1的格式,就可以准备这样一个TxBuffer(假设使用的PD3引脚)

uint32_t TxBuffer[10] = {
        0x00080000,                                        // 起始位, PD5 = 0
        0x00080000,                                        // D0:    PD5 = 0
        0x00000008,                                        // D1:    PD5 = 1
        0x00080000,                                        // D2:    PD5 = 0
        0x00000008,                                        // D3:    PD5 = 1
        0x00080000,                                        // D3:    PD5 = 0
        0x00000008,                                        // D3:    PD5 = 1
        0x00080000,                                        // D3:    PD5 = 0
        0x00000008,                                        // D3:    PD5 = 1
        0x00000008,                                        // 停止位:PD5 = 1
};

传输不同的数据,就是在TxBuffer当中,准备不同的数据。
采用DMA的方式后,只需要在整个TxBuffer发送完成后,再响应一次DMA完成中断,这就可以使得中断的频率降低10倍,大大减轻系统的负载。




评分

参与人数 1金币 +100 收起 理由
eric2013 + 100 很给力!

查看全部评分

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106660
QQ
发表于 2024-3-5 11:37:41 | 显示全部楼层
谢谢楼主分享,实际批量数据测试,稳定性怎么样。
回复

使用道具 举报

20

主题

94

回帖

154

积分

初级会员

积分
154
 楼主| 发表于 2024-3-5 12:25:40 | 显示全部楼层
初次尝试


ST官方有一个示范,用F4写的,我手上时H7的板子,先改到H7来尝试一下。几个代码如下
bsp_emul_uart_fifio.c (10.54 KB, 下载次数: 13)
bsp_emul_uart_fifio.h (2.02 KB, 下载次数: 9)
stm32h7xx_hal_uart_emul.c (38.31 KB, 下载次数: 12)
stm32h7xx_hal_uart_emul.h (15.77 KB, 下载次数: 9)

这个代码目前还不太完善,先看看效果怎么样,后续再尝试更改。

在主任务中执行以下测试代码:
[C] 纯文本查看 复制代码
uint8_t aTxBuffer[] = {0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa};
ecomSendBuf(ECOM1, aTxBuffer, 5);


测试效果如下:
屏幕截图 2024-03-05 122002.png

可以选择115200的波特率,我的程序中还有较多的其他任务,不知道是否有干扰。定时器时钟是240M,计算115200,波特率有误差,目前不知道是否波特率误差导致误码。
但每次发送的第一个字符都是正确的。

后续再尝试改进,看看是否代码还有问题。

PS:更改ST官方的代码中发现他的代码中TxBuffer是这样定义的
[C] 纯文本查看 复制代码
/* UART Emulation Handle */
static UART_Emul_HandleTypeDef      *huart_emul;

/* First Buffer for format data in reception mode */
static uint32_t *pFirstBuffer_Rx[RX_BUFFER_SIZE];

/* Second Buffer for format data in reception mode */
static uint32_t *pSecondBuffer_Rx[RX_BUFFER_SIZE];

/* First Buffer for format data in transmission mode */
static uint32_t *pFirstBuffer_Tx[TX_BUFFER_SIZE];

/* Second Buffer for format data in transmission mode */
static uint32_t *pSecondBuffer_Tx[TX_BUFFER_SIZE];

这个应该是错的,不应该定义为指针数组,所以我的改过来了,但是官方这个代码没有人测试过吗,测试应该是通不过的才对吧。我改成了:
[C] 纯文本查看 复制代码
static UART_Emul_HandleTypeDef      *huart_emul;

/* First Buffer for format data in reception mode */
static uint32_t pFirstBuffer_Rx[RX_BUFFER_SIZE];

/* Second Buffer for format data in reception mode */
static uint32_t pSecondBuffer_Rx[RX_BUFFER_SIZE];

/* First Buffer for format data in transmission mode */
static uint32_t pFirstBuffer_Tx[TX_BUFFER_SIZE];

/* Second Buffer for format data in transmission mode */
static uint32_t pSecondBuffer_Tx[TX_BUFFER_SIZE];




回复

使用道具 举报

20

主题

94

回帖

154

积分

初级会员

积分
154
 楼主| 发表于 2024-3-5 14:09:29 | 显示全部楼层
误码分析


采用115200波特率发送数据时,每一个bit的时间应该1/115200=8.68us。定时器时钟240000000,240000000/115200=2083.3333,无法得到整数结果,
使用2083时得到的实际波特率为115218。波特率误差18/115200 = 0.015%,这个误差应该在可接受的范围内,不至于导致那么大的误码率。


通过逻辑分析仪抓取误码,得到如下结果:
屏幕截图 2024-03-05 135251.png

屏幕截图 2024-03-05 135359.png
分析其中的错误帧,发现都是一个起始位持续时间有误,怀疑是定时器在触发第一次传输时定时不准确导致的。


修改代码,在HAL_UART_Emul_Transmit_DMA函数中,使能TIM_C1中断之前,加入一句清零定时器计数值的代码
[C] 纯文本查看 复制代码
    /* Format first Frame to be sent */
    if (huart->TxXferCount == FIRST_BYTE)
    {
      /* Format Frame to be sent */
      UART_Emul_TransmitFormatFrame(huart, *(pData), (uint32_t*)pFirstBuffer_Tx);

	  TimHandle.Instance->CNT = 0;
      /* Enable the Capture compare channel */
      TIM_CCxChannelCmd(TIM1, TIM_CHANNEL_1, TIM_CCx_ENABLE);

      /* Send Frames */
      UART_Emul_TransmitFrame(huart);
    }


现在没有误码了:
屏幕截图 2024-03-05 140854.png
回复

使用道具 举报

20

主题

94

回帖

154

积分

初级会员

积分
154
 楼主| 发表于 2024-3-5 16:14:31 | 显示全部楼层
TX发送执行流程

TX功能成功后,整理一下Tx有关代码,和代码执行流程:

用户层代码调用HAL_UART_Emul_Transmit_DMA,本函数,启动一包数据发送。


  • 调用UART_Emul_TransmitFormatFrame,根据要发送的字符,准备DMA传输需要的TxBuffer
  • 调用UART_Emul_TransmitFrame,设置DMA,启动模拟串口数据收发
  • DMA在定时器的触发下,将TxBuffer的数据搬运到BSRR寄存器,发送一帧数据,传输完成后,产生中断请求
  • 硬件调用DMA中断函数:UART_EMUL_TX_DMA_IRQHandler
  • 中断调用UART_Emul_DMATransmitCplt函数,若数据包没有发送完毕,启动下一帧数据发送,否则调用数据包完成回调函数

数据包发送完成,执行回调 HAL_UART_Emul_TxCpltCallback,这是一个weak定义的函数,用户定义该函数,实现数据包发送完成处理逻辑。

发送有关代码如下:
[C] 纯文本查看 复制代码
/**
  * @brief  This function formats one Frame
  * @param  UART Emulation Handle
  * @param  pdata pinteur in data
  * @retval None
  */
static void UART_Emul_TransmitFormatFrame(UART_Emul_HandleTypeDef *huart , uint8_t Data, uint32_t *pBuffer_Tx)
{
    uint32_t counter = 0;
    uint32_t bitmask = 0;
    uint32_t length = 0;
    uint32_t cntparity = 0;

    length = huart->Init.WordLength;                // 待传输的字符帧长度:5,6,7,8,9

    /* Get the Pin Number */
    bitmask = (uint32_t)huart->Init.TxPinNumber;    // 要传输到BSRR寄存器中设置引脚的数据
    /* with no parity */
    if(huart->Init.Parity == 0x00)
    {		
        for (counter = 0; counter < length; counter++)
        {
            if (((Data >> counter)&BitMask) != 0)
            {
                pBuffer_Tx[counter+1] = bitmask;            // PIN = 1
            }
            else
            {
                pBuffer_Tx[counter+1] = (bitmask << 16);    // PIN = 0
            }
        }
    }
    /* with parity */
    else
    {
        for (counter = 0; counter < length-1; counter++)
        {
            if (((Data >> counter)&BitMask) != 0)
            {
                pBuffer_Tx[counter+1] = bitmask;
                cntparity ++;
            }
            else
            {
                pBuffer_Tx[counter+1] = (bitmask << 16);
            }
        }	
    }	
    // 根据设置的校验类型,计算校验需要的bit值
    switch  (huart->Init.Parity)
    {
        case UART_EMUL_PARITY_ODD:
        {
            /* Initialize Parity Bit */
            if ((cntparity % 2) != SET)
            {
            pBuffer_Tx[length] = bitmask;
            }
            else
            {
            pBuffer_Tx[length] = (bitmask << 16);
            }
        }
        break;
        case UART_EMUL_PARITY_EVEN:
        {
            /* Initialize Parity Bit */
            if ((cntparity % 2) != SET)
            {
            pBuffer_Tx[length] = (bitmask << 16);
            }
            else
            {
            pBuffer_Tx[length] = bitmask;
            }
        }
        break;
        default:
            break;
    }
    /* Initialize Bit Start 设置起始位 */
    pBuffer_Tx[0] = (bitmask << 16);

    /* Initialize Bit Stop 设置停止位,1位或2位 */
    if (huart->Init.StopBits == UART_EMUL_STOPBITS_1)
    {
        pBuffer_Tx[length+1] = bitmask;
    }
    else
    {
        pBuffer_Tx[length+1] = bitmask;
        pBuffer_Tx[length+2] = bitmask;
    }
    /* Reset counter parity */
    cntparity = 0;
}

/**
  * @brief  Sends an amount of Frames
  * @param  huart: UART Emulation handle
  * @param  pData: Frame to be sent
  * @retval None
  */
static void UART_Emul_TransmitFrame(UART_Emul_HandleTypeDef *huart)
{
    uint32_t tmp_sr = 0;
    uint32_t tmp_ds = 0;
    uint32_t tmp_size = 0;

    if ((huart_emul->TxXferCount % 2 ) != 0)
    {
        tmp_sr = (uint32_t)pFirstBuffer_Tx;
    }
    else
    {
        tmp_sr = (uint32_t)pSecondBuffer_Tx;
    }

    tmp_ds = (uint32_t) & ((huart->TxPortName)->BSRR);
    tmp_size = __HAL_UART_EMUL_FRAME_LENGTH(huart);

    /* Configure DMA Stream data length */
    ((DMA_Stream_TypeDef*)hdma_tx.Instance)->NDTR = tmp_size;
    /* Configure DMA Stream destination address */
    ((DMA_Stream_TypeDef*)hdma_tx.Instance)->PAR = tmp_ds;
    /* Configure DMA Stream source address */
    ((DMA_Stream_TypeDef*)hdma_tx.Instance)->M0AR = tmp_sr;

    /* Enable the transfer complete interrupt */
    __HAL_DMA_ENABLE_IT(&hdma_tx, DMA_IT_TC);
    /* Enable the transfer Error interrupt */
    __HAL_DMA_ENABLE_IT(&hdma_tx, DMA_IT_TE);

    /* Enable the Peripheral */
    __HAL_DMA_ENABLE(&hdma_tx);
    /* Enable the TIM Update DMA request */
    __HAL_TIM_ENABLE_DMA(&TimHandle, TIM_DMA_CC1);
    /* Enable the Peripheral */
    __HAL_TIM_ENABLE(&TimHandle);
}

/**
 * @brief  Sends an amount of data
 * @param  huart: UART Emulation handle
 * @param  pData: Pointer to data buffer
 * @param  Size: Amount of data to be sent
 * @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Emul_Transmit_DMA(UART_Emul_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
    uint32_t tmp = 0;

    tmp = huart->State;

    if ((tmp == HAL_UART_EMUL_STATE_READY) || (tmp == HAL_UART_EMUL_STATE_BUSY_RX))
    {
        if ((pData == NULL ) || (Size == 0))
        {
            return HAL_ERROR;
        }

        huart->TxXferSize = Size;
        huart->pTxBuffPtr = pData;
        huart->TxXferCount = 1;
        huart->ErrorCode = HAL_UART_EMUL_ERROR_NONE;

        /* Check if a receive process is ongoing or not */
        if (huart->State == HAL_UART_EMUL_STATE_BUSY_RX)
        {
            huart->State = HAL_UART_EMUL_STATE_BUSY_TX_RX;
        }
        else
        {
            huart->State = HAL_UART_EMUL_STATE_BUSY_TX;
        }
        /* 设置DMA传输完成回调函数 */
        TimHandle.hdma[TIM_DMA_ID_CC1]->XferCpltCallback = UART_Emul_DMATransmitCplt;
        /* Set the DMA error callback */
        TimHandle.hdma[TIM_DMA_ID_CC1]->XferErrorCallback = UART_Emul_DMAError;

        /* Format first Frame to be sent */
        if (huart->TxXferCount == FIRST_BYTE)
        {
            /* Format Frame to be sent */
            UART_Emul_TransmitFormatFrame(huart, *(pData), (uint32_t*)pFirstBuffer_Tx);

            /* 由于定时器一直处于计数过程中,可能导致第一次DMA触发时间不正确,所以使能TIM1_CC1前先清零定时器 */
            /* 这个方法太粗暴了,可能影响正在进行中的Rx */
            TimHandle.Instance->CNT = 0;

            /* Enable the Capture compare channel */
            TIM_CCxChannelCmd(TIM1, TIM_CHANNEL_1, TIM_CCx_ENABLE);
            /* Send Frames */
            UART_Emul_TransmitFrame(huart);
        }

        if ((huart->TxXferCount == FIRST_BYTE) && (huart->TxXferCount < Size))
        { 
            /* Format Second Frame to be sent */
            UART_Emul_TransmitFormatFrame(huart, *(pData + huart->TxXferCount), (uint32_t*)pSecondBuffer_Tx);
        }
        return HAL_OK;          
    }
    else
    {
        return HAL_BUSY;
    }
}

/**
* @brief  This function handles DMA interrupt request for TC.
* @param  None
* @retval None
*/
void UART_EMUL_TX_DMA_IRQHandler(void)
{ 
    if (__HAL_DMA_GET_FLAG(TimHandle.hdma[TIM_DMA_ID_CC1], __HAL_DMA_GET_TE_FLAG_INDEX(TimHandle.hdma[TIM_DMA_ID_CC1])) != RESET)
    {
        UART_Emul_DMAError(&hdma_tx);
    }

    /* Clear the transfer complete flag */
    __HAL_DMA_CLEAR_FLAG(TimHandle.hdma[TIM_DMA_ID_CC1], __HAL_DMA_GET_TC_FLAG_INDEX(TimHandle.hdma[TIM_DMA_ID_CC1]));

    /* Transfer complete callback */
    TimHandle.hdma[TIM_DMA_ID_CC1]->XferCpltCallback(TimHandle.hdma[TIM_DMA_ID_CC1]);
}

/**
  * @brief  This function is executed in case of Transfer Complete of a Frame.
  *         每传输完一个字符帧,调用本函数。
  *         若没有传输完全部数据,调用UART_Emul_TransmitFrame启动下一个数据帧传输,
  *                            然后调用UART_Emul_TransmitFormatFrame,格式化下一个待传输的帧
  *         若传输完全部数据,关闭TIM_CC1,设置TC标志,更新状态,调用传输完成回调函数
  * @param  None
  * @retval None
  */
static void UART_Emul_DMATransmitCplt(DMA_HandleTypeDef *hdma)
{
    uint32_t tmpbuffer = 0;

    /* Incremente Counter of frame */
    huart_emul->TxXferCount++;

    if (huart_emul->TxXferCount <= huart_emul->TxXferSize)
    { 
        /* Call UART Emulation Transmit frame for next Frame */
        UART_Emul_TransmitFrame(huart_emul);

        if ((huart_emul->TxXferCount % 2 ) != 0)
        {
            tmpbuffer = (uint32_t)pSecondBuffer_Tx;
        }
        else
        {
            tmpbuffer = (uint32_t)pFirstBuffer_Tx;
        }
        /* Format second Data to be sent */
        UART_Emul_TransmitFormatFrame(huart_emul, *(huart_emul->pTxBuffPtr + huart_emul->TxXferCount), (uint32_t*)tmpbuffer);
    }
    else
    {
        /* Disable the transfer complete interrupt */
        __HAL_DMA_DISABLE_IT(TimHandle.hdma[TIM_DMA_ID_CC1], DMA_IT_TC);

        /* Set TC flag in the status register software */
        __HAL_UART_EMUL_SET_FLAG(huart_emul, UART_EMUL_FLAG_TC);

        /* De_Initialize counter frame for Tx */
        huart_emul->TxXferCount = 0;

        /* Initialize the UART Emulation state */
        huart_emul->ErrorCode = HAL_UART_EMUL_ERROR_NONE;

        /* Check if a receive process is ongoing or not */
        if (huart_emul->State == HAL_UART_EMUL_STATE_BUSY_TX_RX)
        {
            huart_emul->State = HAL_UART_EMUL_STATE_BUSY_RX;
        }
        else
        {
            huart_emul->State = HAL_UART_EMUL_STATE_READY;
        }
        /* Handle for UART Emulation Transfer Complete */
        HAL_UART_Emul_TxCpltCallback(huart_emul);				// user callback
    }
}

/**
  * @brief  Initializes the UART Emulation Transfer Complete.
  * @param  huart: UART Emulation Handle
  * @retval None
  */
__weak void HAL_UART_Emul_TxCpltCallback(UART_Emul_HandleTypeDef *huart)
{
  /* NOTE : This function Should not be modified, when the callback is needed,
            the HAL_UART_Emul_TransferComplete could be implemented in the user file
   */
}

回复

使用道具 举报

3

主题

96

回帖

105

积分

初级会员

积分
105
发表于 2024-3-5 16:17:49 | 显示全部楼层
这个通过TIM中断让DMA来输出高低电平来做模拟串口?中断的频率还是那么快啊,DMA减轻了不少CPU的时间。
回复

使用道具 举报

20

主题

94

回帖

154

积分

初级会员

积分
154
 楼主| 发表于 2024-3-5 17:12:24 | 显示全部楼层
2859932063 发表于 2024-3-5 16:17
这个通过TIM中断让DMA来输出高低电平来做模拟串口?中断的频率还是那么快啊,DMA减轻了不少CPU的时间。

TIM只是触发DMA传输,在DMA传输完成后触发DMA中断,中断频率比用TIM中断降低了10倍,TIM每一个bit中断1次,采用DMA后,1+8+1总共10个bit中断1次,还是大大减轻负载了的。
回复

使用道具 举报

20

主题

94

回帖

154

积分

初级会员

积分
154
 楼主| 发表于 2024-3-5 17:15:55 | 显示全部楼层
如果想进一步降低中断频率,可以做到一个数据包DMA中断1次。比如数据包长度为N个字节要发送,可以分配N*10的TxBuffer,由DMA完成后,才产生一个中断,当然代价是需要更大的内存。

用这个方法,应该可以模拟任何时序信号。
回复

使用道具 举报

75

主题

683

回帖

908

积分

金牌会员

积分
908
发表于 2024-3-5 19:40:11 | 显示全部楼层
ifree 发表于 2024-3-5 17:15
如果想进一步降低中断频率,可以做到一个数据包DMA中断1次。比如数据包长度为N个字节要发送,可以分配N*10 ...

很不错的思路,就是数据优点占内存
回复

使用道具 举报

3

主题

96

回帖

105

积分

初级会员

积分
105
发表于 2024-3-6 09:18:18 | 显示全部楼层
ifree 发表于 2024-3-5 17:12
TIM只是触发DMA传输,在DMA传输完成后触发DMA中断,中断频率比用TIM中断降低了10倍,TIM每一个bit中断1次 ...

明白了TIM触发DMA传输,但是TIM不产生中断,DMA传完一次才产生一次中断
回复

使用道具 举报

5

主题

201

回帖

216

积分

高级会员

积分
216
发表于 2024-3-6 09:44:50 | 显示全部楼层
挺不错的思路,不过接收就不好搞了吧
回复

使用道具 举报

3

主题

96

回帖

105

积分

初级会员

积分
105
发表于 2024-3-6 10:38:50 | 显示全部楼层
honami520 发表于 2024-3-6 09:44
挺不错的思路,不过接收就不好搞了吧

接收也简单,外部中断之后开始接收起始位,把TIM的时间改成原来的时间的一半就可以了,接收到起始位之后,把TIM调回来就可以了。不过确实会比发送麻烦一些
回复

使用道具 举报

20

主题

94

回帖

154

积分

初级会员

积分
154
 楼主| 发表于 2024-3-6 10:38:54 | 显示全部楼层
这个方法从理论上讲和用中断驱动的硬件串口效率差不多,都是一个字符中断一次。不过st官方给的代码好像问题蛮多,调试起来还有不少问题。
回复

使用道具 举报

3

主题

96

回帖

105

积分

初级会员

积分
105
发表于 2024-3-6 14:28:35 | 显示全部楼层
ifree 发表于 2024-3-6 10:38
这个方法从理论上讲和用中断驱动的硬件串口效率差不多,都是一个字符中断一次。不过st官方给的代码好像问题 ...

我看了一下,你上面好像是对整组GPIO操作,这样不会对其他GPIO造成影响吗?
回复

使用道具 举报

7

主题

26

回帖

47

积分

新手上路

积分
47
发表于 2024-3-22 11:23:50 | 显示全部楼层
ifree 发表于 2024-3-5 17:12
TIM只是触发DMA传输,在DMA传输完成后触发DMA中断,中断频率比用TIM中断降低了10倍,TIM每一个bit中断1次 ...

楼主你好,看了文章,可不可以这样理解思路,Tx_buffer准备好要传输的10个数据位,然后启动发送,DMA开始把各个位的数据,依次存到BSRR寄存器,但是,我不明白,DMA的传输速度是不可控的,你是怎么通过DMA控制波特率的,假如起始位刚发送到BSRR,然后第一个数据位立刻来了,岂不是,等不到足够的时间,起始位的保持电平时间不够长,数据就被覆盖掉?
回复

使用道具 举报

3

主题

96

回帖

105

积分

初级会员

积分
105
发表于 2024-3-22 11:43:40 | 显示全部楼层
麦克斯韦Maxwell 发表于 2024-3-22 11:23
楼主你好,看了文章,可不可以这样理解思路,Tx_buffer准备好要传输的10个数据位,然后启动发送,DMA开始 ...

定时器触发的DMA,时间当然是固定的
回复

使用道具 举报

7

主题

26

回帖

47

积分

新手上路

积分
47
发表于 2024-3-22 14:23:20 | 显示全部楼层
2859932063 发表于 2024-3-22 11:43
定时器触发的DMA,时间当然是固定的

定时器可以控制DMA的开始和终止时间,但是并不能控制DMA的数据传输速度啊,我看代码,定时器触发DMA后,一次发送一帧数据,一帧有10BIT,但是我们怎么保证每一个BIT的电平保持时间符合UART要求呢?比如说,我们设置定时器10S,UPDATE一次,一帧传10BIT,那一个BIT的电平可以保持1S的时间,但是如果DMA仅需要5S就完成的一帧的传输呢,那岂不是一个BIT仅有0.5S的保持时间,接收方不就解码错误,而剩下的5S则是直接进入空闲状态?
回复

使用道具 举报

20

主题

94

回帖

154

积分

初级会员

积分
154
 楼主| 发表于 2024-3-23 07:43:58 | 显示全部楼层
麦克斯韦Maxwell 发表于 2024-3-22 14:23
定时器可以控制DMA的开始和终止时间,但是并不能控制DMA的数据传输速度啊,我看代码,定时器触发DMA后, ...

定时器触发DMA传输,意思是每次产生定时器事件,触发一次DMA传输,时间就是固定的。
不是你理解的定时器触发DMA传输了,DMA一下子把配置的数据全部传完。
回复

使用道具 举报

20

主题

94

回帖

154

积分

初级会员

积分
154
 楼主| 发表于 2024-3-23 07:45:23 | 显示全部楼层
2859932063 发表于 2024-3-6 14:28
我看了一下,你上面好像是对整组GPIO操作,这样不会对其他GPIO造成影响吗?

操作BSRR寄存器,是可以只对GPIO的某一个引脚进行操作,而不影响同一组下的其他引脚的。
回复

使用道具 举报

7

主题

26

回帖

47

积分

新手上路

积分
47
发表于 2024-3-27 10:27:20 | 显示全部楼层
ifree 发表于 2024-3-23 07:43
定时器触发DMA传输,意思是每次产生定时器事件,触发一次DMA传输,时间就是固定的。
不是你理解的定时器 ...

好的,谢谢你,我明白了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-4-28 07:45 , Processed in 0.396281 second(s), 33 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表