一.问题环境
整活了一套APM32F407的板子,用了APM32F4xx_SDK_V1.4的标准外设库,正在搭建移植底层BSP框架串口部分,BSP底层配置逻辑是从STM32F407移植过来的。DMA发送时才使能通道及配置外设地址及缓存大小。
串口1DMA配置过程如下:
static USART_DMAConfig_t USART_ComDmaTx(DMA_ChannelType_t channel,DMA_StreamType_t stream,uint32_t peripheralBaseAddr,uint8_t IRQChannel,uint32_t dmaFlag)
{
USART_DMAConfig_t dmaConfig = {0};
dmaConfig.channel = channel;
dmaConfig.stream = stream;
dmaConfig.dma.channel = channel;
dmaConfig.dma.peripheralBaseAddr = peripheralBaseAddr; //DMA外设地址:USART数据寄存器地址
dmaConfig.dma.memoryBaseAddr = (uint32_t)0; //内存地址,使用时再配置
dmaConfig.dma.dir = DMA_DIR_MEMORYTOPERIPHERAL; //外设地址为目的地址
dmaConfig.dma.bufferSize = (uint32_t)0; //传输时缓冲区大小,使用时再配置
dmaConfig.dma.peripheralInc = DMA_PERIPHERAL_INC_DISABLE; //外设地址固定,不递增
dmaConfig.dma.memoryInc = DMA_MEMORY_INC_ENABLE; //内存地址递增
dmaConfig.dma.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_BYTE; //外设数据格式为字节
dmaConfig.dma.memoryDataSize = DMA_MEMORY_DATA_SIZE_BYTE; //内存数据格式为字节
dmaConfig.dma.loopMode = DMA_MODE_NORMAL; //工作在正常模式,不循环
dmaConfig.dma.priority = DMA_PRIORITY_HIGH; //DMA传输优先级为高(VeryHigh/High/Medium/Low)
dmaConfig.dma.fifoMode = DMA_FIFOMODE_DISABLE; //禁能DMA的两个Memory中变量相互访问
dmaConfig.dma.peripheralBurst = DMA_PERIPHERALBURST_SINGLE;
dmaConfig.dma.fifoThreshold = DMA_FIFOTHRESHOLD_QUARTER;
dmaConfig.dma.memoryBurst = DMA_MEMORYBURST_SINGLE;
dmaConfig.nvic.NVIC_IRQChannel = IRQChannel;
dmaConfig.nvic.NVIC_IRQChannelPreemptionPriority = 6; //抢占优先级
dmaConfig.nvic.NVIC_IRQChannelSubPriority = 0; //响应优先级
dmaConfig.nvic.NVIC_IRQChannelCmd = DISABLE; //使能中断
dmaConfig.dmaFlag = dmaFlag;
return dmaConfig;
}
static USART_DMAConfig_t USART_Com1DmaTx(void)
{
DMA_ChannelType_t channel;
DMA_StreamType_t stream;
uint32_t peripheralBaseAddr;
uint8_t IRQChannel;
uint32_t dmaFlag;
channel = DMA_CHANNEL_4;
stream = DMA2_Stream7;
peripheralBaseAddr = (uint32_t)(&(USART1->DATA));
IRQChannel = DMA2_STR7_IRQn;
dmaFlag = DMA_INT_TCIFLG7;
return USART_ComDmaTx(channel,stream,peripheralBaseAddr,IRQChannel,dmaFlag);
};
static void USART_DmaTxConfig(USART_t USART)
{
NVIC_InitType_t NVIC_InitStructure;
/* 使能时钟 */
if ((uint32_t)USART->config.dmaTx.stream > (uint32_t)DMA2)
{
GPIO_RcmAHB1PeriphClockCmd(RCM_AHB1_PERIPH_DMA2, ENABLE); //开启DMA时钟,用于USART发射
}
else
{
GPIO_RcmAHB1PeriphClockCmd(RCM_AHB1_PERIPH_DMA1, ENABLE); //开启DMA时钟,用于USART发射
}
NVIC_InitStructure = USART->config.dmaTx.nvic;
DMA_Disable(USART->config.dmaTx.stream); //先禁止DMA通道,若之前有DMA传输,则会终止 ,必须先关闭DMA通道才能配置
DMA_Reset(USART->config.dmaTx.stream); //复位DMA1通道2的初始化
DMA_Config(USART->config.dmaTx.stream,&USART->config.dmaTx.dma); //DMA初始化
if (NVIC_InitStructure.NVIC_IRQChannelCmd)
{
NVIC_EnableIRQRequest((IRQn_Type)NVIC_InitStructure.NVIC_IRQChannel,NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority,NVIC_InitStructure.NVIC_IRQChannelSubPriority); //使能中断
}
DMA_ClearStatusFlag(USART->config.dmaTx.stream,USART->config.dmaTx.dmaFlag); //清除 全部标志
DMA_EnableInterrupt(USART->config.dmaTx.stream,DMA_INT_TCIFLG); //使能DMA通道4传输完成中断
DMA_Disable(USART->config.dmaTx.stream); //更新内存地址和传输大小之后再使能
}
void USART_TypeInit(USART_t USART)
{
USART_Reset(USART->usart);
USART_DisableDMA(USART->usart,USART_DMA_TX);
USART_DisableDMA(USART->usart,USART_DMA_RX);
USART_Construct(USART);
USART_RccConfig(USART);
USART_GpioConfig(USART);
USART_Disable(USART->usart);
USART_InitConfig(USART);
USART_NvicConfig(USART);
if (USART->config.isDmaTxEnable)
{
USART_DmaTxConfig(USART);
USART_EnableDMA(USART->usart, USART_DMA_TX);
}
if (USART->config.isDmaRxEnable)
{
USART_DmaRxConfig(USART);
USART_EnableDMA(USART->usart, USART_DMA_RX);
}
}
static void USART_SendMessage(USART_t USART, uint8_t *sendBuf, uint32_t len)
{
if (USART->config.isRs485Enable)
{
USART_Rs485TxEnable(USART);
}
if (USART->config.isDmaTxEnable)
{
DMA_Disable(USART->config.dmaTx.stream); //先禁止DMA通道,若之前有DMA传输,则会终止 ,必须先关闭DMA通道才能配置
while (DMA_ReadCmdStatus(USART->config.dmaTx.stream) != DISABLE); //确保DMA可以被设置
//=======直接操作寄存器更新内存地址和传输大小-----------------------------------------------------------
USART->config.dmaTx.stream->M0ADDR = (uint32_t)(sendBuf); //更新内存地址
USART->config.dmaTx.stream->NDATA = len; //更新传输时缓冲区大小
DMA_ClearStatusFlag(USART->config.dmaTx.stream,USART->config.dmaTx.dmaFlag);//清除Channel2全部标志,主要是清除传输完成标志
DMA_Enable(USART->config.dmaTx.stream);
//等待DMA发送结束
while(DMA_ReadIntFlag(USART->config.dmaTx.stream,USART->config.dmaTx.dmaFlag) == RESET);
//清除标志
DMA_ClearStatusFlag(USART->config.dmaTx.stream,USART->config.dmaTx.dmaFlag);
if (USART->config.isRs485Enable)
{
USART_Rs485TxDisable(USART);
}
USART->send->flag = true;
}
}
二.问题现象
串口DMA接收和中断接收都没有问题,发送中断也没有问题。但发送配置为DMA就不能发送数据。所有串口端口配置都有这个问题,确定同样的板子刷入STM32F407的的程序也能正常使用,唯独使用APM的外设库就不行。
三.原因分析
当同时使用APM32F4的DMA接收和发送时,虽然同时配置了USART_EnableDMA(USART->usart, USART_DMA_TX)和USART_EnableDMA(USART->usart, USART_DMA_RX),但USART_EnableDMA这个函数你看他是怎么实现的:
/*!
* @brief Enables the USART DMA interface
*
* @param usart: Select the USART or the UART peripheral
*
* @param dmaReq: Specifies the DMA request
* This parameter can be one of the following values:
* @arg USART_DMA_TX : USART DMA receive request
* @arg USART_DMA_RX : USART DMA transmit request
* @arg USART_DMA_TX_RX : USART DMA transmit/receive request
*
* @retval None
*
* @note The usart can be USART1, USART2, USART3, UART4, UART5, USART6, UART7 and UART8
*/
void USART_EnableDMA(USART_T* usart, USART_DMA_T dmaReq)
{
usart->CTRL3_B.DMARXEN = dmaReq & 0x01;
usart->CTRL3_B.DMATXEN = dmaReq >> 1;
}
是不是很明显的看出了问题所在,原来usart->CTRL3_B.DMARXEN = dmaReq & 0x01;这一句用的是直接赋值操作,而不是我们熟悉的按位或,这样当配置了USART_EnableDMA(USART->usart, USART_DMA_RX)后,原来的USART_EnableDMA(USART->usart, USART_DMA_TX);就被重置了,所以只能接收不能发送。
四.问题解决
发现问题后,对代码初始化部分进行改进,经过测试,正常收发。代码如下:
if (USART->config.isDmaTxEnable && USART->config.isDmaRxEnable)
{
USART_DmaTxConfig(USART);
USART_DmaRxConfig(USART);
USART_EnableDMA(USART->usart, USART_DMA_TX_RX);
}
else if (USART->config.isDmaTxEnable)
{
USART_DmaTxConfig(USART);
USART_EnableDMA(USART->usart, USART_DMA_TX);
}
else if (USART->config.isDmaRxEnable)
{
USART_DmaRxConfig(USART);
USART_EnableDMA(USART->usart, USART_DMA_RX);
}