如题,本文演示STM32+FREERTOS实现串口双缓冲接收。双缓冲接收指的是,为串口设置两个接收缓存区,可以以字节串为单位,交替保存串口收到的信息。它的好处是,在T时间内收到两条字节串时,不会错过其中一条(T时间指的是单片机在接收信息完成,产生IDLE中断后,到处理完信息的时间),另一个好处是,避免了在没关闭DMA情况下,T时间内被另一条字节串部分覆盖而产生的错误。当然,单缓冲足够应付多数场合,只需要注意在接收完毕后,马上关闭串口DMA接收,等处理完信息再开启串口DMA接收!
关于串口接收的IDLE中断模式,是STM32单片机的一个亮点,简单讲,就是每当有一串字节发过来的时候,接收字节的空隙不会产生中断,在接收完这串字节后(单片机进行判断),会产生一个IDLE中断。这个功能加上DMA,可以大大地减少单片机CPU在串口接收过程中的负担。具体在CSDN有诸多大神的详细演示。
根据DMA的特性,串口发送当然也采用DMA最好,而且采用串口DMA发送中断来确认一条字节串发送完毕,才允许下一次的发送(单缓冲模式)。具体的流程是:
1.使能串口DMA发送完成中断
2.待发送的字节串写入缓冲区,禁止下一次发送
3.启动串口DMA发送
4.发送完毕产生串口DMA发送中断,允许下一次发送。
但是经过反复的测试,发现存在一个问题:利用该功能,连续发送字节串,会导致字节串后部分被覆盖,也就是说,串口DMA发送完成中断产生的时候,DMA发送并没有完全结束!!!
对于这个问题,目前束手无策,只能采用阻塞式发送(浪费CPU资源)
请有心人讨论,指点!感谢!
开发环境:
IDE:STM32CubeIDE 1.8
固件库:STM32Cube_FW_F1_V1.8.4
硬件:Waveshare Open107V,STM32F107VC, 晶振25MHz,工作频率72MHz
关键代码:
//初始化时,启用接收IDLE中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
//启用串口中断,以下是stm32f1xx_it.c中对IDLE中断的响应
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))//确认是IDLE中断
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志
IsrProcRxIdle();//在freertos中处理(双缓冲)
}
/* USER CODE END USART1_IRQn 1 */
}
//main.c和main.h中的宏和全局变量定义
typedef enum {false = 0, true = 1} bool;
#define BUF_TOP 254
#define BUF_SIZE (BUF_TOP + 1)
#define BUF_FLAG (BUF_TOP + 1)
#define BUF_STATUS_FREE 0
#define BUF_STATUS_RX (BUF_TOP + 1)
#define BUF_ARRAY_SIZE 2
uint8_t rxBuf10[BUF_SIZE + 1];
uint8_t rxBuf11[BUF_SIZE + 1];
//rxBufxx[BUF_FLAG]用来作为状态变量BUF_STATUS_RX或BUF_STATUS_FREE,其他值则为接收到的字符长度。
//在freertos.c中实现DMA双缓冲接收
//Called in USART1_IRQHandler
void IsrProcRxIdle(void)
{
BaseType_t pxHigherPriorityTaskWoken;
uint16_t len;
HAL_UART_DMAStop(&huart1);//停止uart dma
//获取接收的字符数
len = BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
//双缓冲处理
if(rxBuf10[BUF_FLAG] == BUF_STATUS_RX)
{
rxBuf10[BUF_FLAG] = len;
if(rxBuf11[BUF_FLAG] == BUF_STATUS_FREE)
{
rxBuf11[BUF_FLAG] = BUF_STATUS_RX;
HAL_UART_Receive_DMA(&huart1, rxBuf11, BUF_SIZE);
}
}
else if(rxBuf11[BUF_FLAG] == BUF_STATUS_RX)
{
rxBuf11[BUF_FLAG] = len;
if(rxBuf10[BUF_FLAG] == BUF_STATUS_FREE)
{
rxBuf10[BUF_FLAG] = BUF_STATUS_RX;
HAL_UART_Receive_DMA(&huart1, rxBuf10, BUF_SIZE);
}
}
xSemaphoreGiveFromISR(myBsRx1Handle, &pxHigherPriorityTaskWoken);
portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
}
//在freertos.c中串口接收的处理任务:
/* USER CODE END Header_taskCallBackRx1 */
void taskCallBackRx1(void const * argument)
{
/* USER CODE BEGIN taskCallBackRx1 */
uint8_t * pBufRx;
char pStrBuf[50];
char * strx;
uint8_t i, bn = 9;
uint16_t len;
BaseType_t xReturn;
/* Infinite loop */
for(;;)
{
xReturn = xSemaphoreTake(myBsRx1Handle, 1);//看看有没有接收到字节串
if(pdPASS == xReturn)
{
//HAL_UART_Transmit_DMA(&huart1, (uint8_t *)rxBuf10, strlen(rxBuf10));//test OK!
//BSP_LED2_Toggle(); //Test OK!
pBufRx = rxBuf10;
while(pBufRx != NULL)
{
if(pBufRx == rxBuf10)
{
bn = 0;
}
else if(pBufRx == rxBuf11) bn = 1; //for test, check the current buffer
if(pBufRx[BUF_FLAG] != BUF_STATUS_FREE && pBufRx[BUF_FLAG] != BUF_STATUS_RX)
{
//处理接收到的指令
strx = strstr((char*)pBufRx, (char*)"SET");
if(strx)
{
switch(pBufRx[3])
{
case 'D': //收到SETD指令,用串口DMA连续发送5条字节串
printf("\n\n");
for(i = 0; i < 5; i++)
{
memset(pStrBuf, 0, 50);
sprintf(pStrBuf, "Receive cache:%d, response with DMA...%d.\n", bn, i);
len = strlen(pStrBuf);
Usart1SendString_DMA((uint8_t *)pStrBuf, len);
}
break;
case 'N': //收到SETD指令,用串口DMA连续发送5条字节串
printf("\n\n");
for(i = 0; i < 5; i++)
{
memset(pStrBuf, 0, 50);
sprintf(pStrBuf, "Receive cache:%d, response in blocking mode...%d.\n", bn, i);
len = strlen(pStrBuf);
Usart1SendString((uint8_t *)pStrBuf, len);
}
break;
default:
;
}
}
//双缓冲接收处理。
//两个缓冲区都有数据需要处理,处理完信息后,将第一个设为接收缓冲区使能串口DMA接收
if(rxBuf10[BUF_FLAG] != BUF_STATUS_RX && rxBuf10[BUF_FLAG] != BUF_STATUS_FREE &&
rxBuf11[BUF_FLAG] != BUF_STATUS_RX && rxBuf11[BUF_FLAG] != BUF_STATUS_FREE)
{
rxBuf10[BUF_FLAG] = BUF_STATUS_FREE;
HAL_UART_Receive_DMA(&huart1, rxBuf10, BUF_SIZE);
//BSP_LED4_Toggle();
}
else
{
//BSP_LED3_Toggle(); //Test OK!
pBufRx[BUF_FLAG] = BUF_STATUS_FREE;
}
}//end of if(pBuf[BUF_FLAG] != ...
if(rxBuf10 == pBufRx) pBufRx = rxBuf11;
else pBufRx = NULL;
}//end of while
/**/
}//end of if(pdPASS = xReturn)
osDelay(1);
}
/* USER CODE END taskCallBackRx1 */
}
串口发送代码:
//阻塞式发送
void Usart1SendString(uint8_t * buf, uint16_t len)
{
xSemaphoreTake(myMutexTx1Handle, portMAX_DELAY);
while(*buf && len)
{
HAL_UART_Transmit(&huart1, (uint8_t *)buf, 1, HAL_MAX_DELAY);//Send in blocking mode
buf++;
len--;
}
xSemaphoreGive(myMutexTx1Handle);
}
//DMA发送
bool Usart1SendString_DMA(uint8_t * buf, uint16_t len)
{
bool res = false;
xSemaphoreTake(myMutexTx1Handle, portMAX_DELAY);
res = Usart1Send(buf, len);
xSemaphoreGive(myMutexTx1Handle);
return res;
}
bool Usart1Send(uint8_t * buf, uint16_t len)
{
int16_t i, n;
bool bResult = false;
if(buf == NULL || len == 0 || len > BUF_SIZE) return false;
//确保串口DMA发送空闲
n = 1000;
do
{
osDelay(2);
}
while(HAL_DMA_STATE_READY != HAL_DMA_GetState(&hdma_usart1_tx) && n--) ;
if(n <= 0)
{
HAL_UART_DMAStop(&huart1);
}
//确保串口DMA发送空闲 发送状态是空闲的!
n = 1000;
do
{
osDelay(1);
}
while(txBuf10[BUF_FLAG] != BUF_STATUS_FREE && n--);
if(n <= 0) return false;
//满足条件后发送,就这样,连续发送两条,第二条都会覆盖第一条的一部分!!!
if(txBuf10[BUF_FLAG] == BUF_STATUS_FREE)
{
for( i = 0; i < len; i++)
{
txBuf10[i] = *(buf + i);
}
txBuf10[BUF_FLAG] = len;
bResult = true;
//BSP_LED3_Toggle(); //test OK!
HAL_UART_Transmit_DMA(&huart1, (uint8_t*)txBuf10, len);
}
return bResult;
}
//串口DMA发送中断的处理 stm32f1xx_it.c中
void DMA1_Channel4_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Channel4_IRQn 0 */
/* USER CODE END DMA1_Channel4_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_usart1_tx);
/* USER CODE BEGIN DMA1_Channel4_IRQn 1 */
//确认是DMA完全发送结束中断,注意有一个发送一半中断
if(__HAL_DMA_GET_IT_SOURCE(&hdma_usart1_tx, DMA_IT_TC))
{
huart1.gState = HAL_UART_STATE_READY;
hdma_usart1_tx.State = HAL_DMA_STATE_READY;
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_TC4);
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_HT4);
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_TE4);
__HAL_UNLOCK(&hdma_usart1_tx);
txBuf10[BUF_FLAG] = BUF_STATUS_FREE;//设置为可发送状态
}
/* USER CODE END DMA1_Channel4_IRQn 1 */
}
测试结果:
21:23:55 You Send : SETN
Receive cache:0, response in blocking mode...0.
Receive cache:0, response in blocking mode...1.
Receive cache:0, response in blocking mode...2.
Receive cache:0, response in blocking mode...3.
Receive cache:0, response in blocking mode...4.
21:23:56 You Send : SETN
Receive cache:1, response in blocking mode...0.
Receive cache:1, response in blocking mode...1.
Receive cache:1, response in blocking mode...2.
Receive cache:1, response in blocking mode...3.
Receive cache:1, response in blocking mode...4.
21:24:03 You Send : SETD
Receive cache:0, response with DMA..Receive cache:0, response with DMA.Receive cache:0, response with DMAReceive cache:0, response with DMA.Receive cache:0, response with DMA...4.
21:24:04 You Send : SETD
Receive cache:1, response with DMA..Receive cache:1, response with DMA.Receive cache:1, response with DMAReceive cache:1, response with DMA.Receive cache:1, response with DMA...4.
从结果看,接收双缓冲用上了,串口DMA发送拉胯。。。,阻塞式发送优秀!!
实在找不出原因了。。。囧
完整代码:
FREERTOS+USART+IDLE+DMA双缓冲接收,DMA发送和阻塞式发送-硬件开发文档类资源-CSDN下载
版权声明:本文为CSDN博主「st01lsp」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/st01lsp/article/details/122708662
如题,本文演示STM32+FREERTOS实现串口双缓冲接收。双缓冲接收指的是,为串口设置两个接收缓存区,可以以字节串为单位,交替保存串口收到的信息。它的好处是,在T时间内收到两条字节串时,不会错过其中一条(T时间指的是单片机在接收信息完成,产生IDLE中断后,到处理完信息的时间),另一个好处是,避免了在没关闭DMA情况下,T时间内被另一条字节串部分覆盖而产生的错误。当然,单缓冲足够应付多数场合,只需要注意在接收完毕后,马上关闭串口DMA接收,等处理完信息再开启串口DMA接收!
关于串口接收的IDLE中断模式,是STM32单片机的一个亮点,简单讲,就是每当有一串字节发过来的时候,接收字节的空隙不会产生中断,在接收完这串字节后(单片机进行判断),会产生一个IDLE中断。这个功能加上DMA,可以大大地减少单片机CPU在串口接收过程中的负担。具体在CSDN有诸多大神的详细演示。
根据DMA的特性,串口发送当然也采用DMA最好,而且采用串口DMA发送中断来确认一条字节串发送完毕,才允许下一次的发送(单缓冲模式)。具体的流程是:
1.使能串口DMA发送完成中断
2.待发送的字节串写入缓冲区,禁止下一次发送
3.启动串口DMA发送
4.发送完毕产生串口DMA发送中断,允许下一次发送。
但是经过反复的测试,发现存在一个问题:利用该功能,连续发送字节串,会导致字节串后部分被覆盖,也就是说,串口DMA发送完成中断产生的时候,DMA发送并没有完全结束!!!
对于这个问题,目前束手无策,只能采用阻塞式发送(浪费CPU资源)
请有心人讨论,指点!感谢!
开发环境:
IDE:STM32CubeIDE 1.8
固件库:STM32Cube_FW_F1_V1.8.4
硬件:Waveshare Open107V,STM32F107VC, 晶振25MHz,工作频率72MHz
关键代码:
//初始化时,启用接收IDLE中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
//启用串口中断,以下是stm32f1xx_it.c中对IDLE中断的响应
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))//确认是IDLE中断
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志
IsrProcRxIdle();//在freertos中处理(双缓冲)
}
/* USER CODE END USART1_IRQn 1 */
}
//main.c和main.h中的宏和全局变量定义
typedef enum {false = 0, true = 1} bool;
#define BUF_TOP 254
#define BUF_SIZE (BUF_TOP + 1)
#define BUF_FLAG (BUF_TOP + 1)
#define BUF_STATUS_FREE 0
#define BUF_STATUS_RX (BUF_TOP + 1)
#define BUF_ARRAY_SIZE 2
uint8_t rxBuf10[BUF_SIZE + 1];
uint8_t rxBuf11[BUF_SIZE + 1];
//rxBufxx[BUF_FLAG]用来作为状态变量BUF_STATUS_RX或BUF_STATUS_FREE,其他值则为接收到的字符长度。
//在freertos.c中实现DMA双缓冲接收
//Called in USART1_IRQHandler
void IsrProcRxIdle(void)
{
BaseType_t pxHigherPriorityTaskWoken;
uint16_t len;
HAL_UART_DMAStop(&huart1);//停止uart dma
//获取接收的字符数
len = BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
//双缓冲处理
if(rxBuf10[BUF_FLAG] == BUF_STATUS_RX)
{
rxBuf10[BUF_FLAG] = len;
if(rxBuf11[BUF_FLAG] == BUF_STATUS_FREE)
{
rxBuf11[BUF_FLAG] = BUF_STATUS_RX;
HAL_UART_Receive_DMA(&huart1, rxBuf11, BUF_SIZE);
}
}
else if(rxBuf11[BUF_FLAG] == BUF_STATUS_RX)
{
rxBuf11[BUF_FLAG] = len;
if(rxBuf10[BUF_FLAG] == BUF_STATUS_FREE)
{
rxBuf10[BUF_FLAG] = BUF_STATUS_RX;
HAL_UART_Receive_DMA(&huart1, rxBuf10, BUF_SIZE);
}
}
xSemaphoreGiveFromISR(myBsRx1Handle, &pxHigherPriorityTaskWoken);
portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
}
//在freertos.c中串口接收的处理任务:
/* USER CODE END Header_taskCallBackRx1 */
void taskCallBackRx1(void const * argument)
{
/* USER CODE BEGIN taskCallBackRx1 */
uint8_t * pBufRx;
char pStrBuf[50];
char * strx;
uint8_t i, bn = 9;
uint16_t len;
BaseType_t xReturn;
/* Infinite loop */
for(;;)
{
xReturn = xSemaphoreTake(myBsRx1Handle, 1);//看看有没有接收到字节串
if(pdPASS == xReturn)
{
//HAL_UART_Transmit_DMA(&huart1, (uint8_t *)rxBuf10, strlen(rxBuf10));//test OK!
//BSP_LED2_Toggle(); //Test OK!
pBufRx = rxBuf10;
while(pBufRx != NULL)
{
if(pBufRx == rxBuf10)
{
bn = 0;
}
else if(pBufRx == rxBuf11) bn = 1; //for test, check the current buffer
if(pBufRx[BUF_FLAG] != BUF_STATUS_FREE && pBufRx[BUF_FLAG] != BUF_STATUS_RX)
{
//处理接收到的指令
strx = strstr((char*)pBufRx, (char*)"SET");
if(strx)
{
switch(pBufRx[3])
{
case 'D': //收到SETD指令,用串口DMA连续发送5条字节串
printf("\n\n");
for(i = 0; i < 5; i++)
{
memset(pStrBuf, 0, 50);
sprintf(pStrBuf, "Receive cache:%d, response with DMA...%d.\n", bn, i);
len = strlen(pStrBuf);
Usart1SendString_DMA((uint8_t *)pStrBuf, len);
}
break;
case 'N': //收到SETD指令,用串口DMA连续发送5条字节串
printf("\n\n");
for(i = 0; i < 5; i++)
{
memset(pStrBuf, 0, 50);
sprintf(pStrBuf, "Receive cache:%d, response in blocking mode...%d.\n", bn, i);
len = strlen(pStrBuf);
Usart1SendString((uint8_t *)pStrBuf, len);
}
break;
default:
;
}
}
//双缓冲接收处理。
//两个缓冲区都有数据需要处理,处理完信息后,将第一个设为接收缓冲区使能串口DMA接收
if(rxBuf10[BUF_FLAG] != BUF_STATUS_RX && rxBuf10[BUF_FLAG] != BUF_STATUS_FREE &&
rxBuf11[BUF_FLAG] != BUF_STATUS_RX && rxBuf11[BUF_FLAG] != BUF_STATUS_FREE)
{
rxBuf10[BUF_FLAG] = BUF_STATUS_FREE;
HAL_UART_Receive_DMA(&huart1, rxBuf10, BUF_SIZE);
//BSP_LED4_Toggle();
}
else
{
//BSP_LED3_Toggle(); //Test OK!
pBufRx[BUF_FLAG] = BUF_STATUS_FREE;
}
}//end of if(pBuf[BUF_FLAG] != ...
if(rxBuf10 == pBufRx) pBufRx = rxBuf11;
else pBufRx = NULL;
}//end of while
/**/
}//end of if(pdPASS = xReturn)
osDelay(1);
}
/* USER CODE END taskCallBackRx1 */
}
串口发送代码:
//阻塞式发送
void Usart1SendString(uint8_t * buf, uint16_t len)
{
xSemaphoreTake(myMutexTx1Handle, portMAX_DELAY);
while(*buf && len)
{
HAL_UART_Transmit(&huart1, (uint8_t *)buf, 1, HAL_MAX_DELAY);//Send in blocking mode
buf++;
len--;
}
xSemaphoreGive(myMutexTx1Handle);
}
//DMA发送
bool Usart1SendString_DMA(uint8_t * buf, uint16_t len)
{
bool res = false;
xSemaphoreTake(myMutexTx1Handle, portMAX_DELAY);
res = Usart1Send(buf, len);
xSemaphoreGive(myMutexTx1Handle);
return res;
}
bool Usart1Send(uint8_t * buf, uint16_t len)
{
int16_t i, n;
bool bResult = false;
if(buf == NULL || len == 0 || len > BUF_SIZE) return false;
//确保串口DMA发送空闲
n = 1000;
do
{
osDelay(2);
}
while(HAL_DMA_STATE_READY != HAL_DMA_GetState(&hdma_usart1_tx) && n--) ;
if(n <= 0)
{
HAL_UART_DMAStop(&huart1);
}
//确保串口DMA发送空闲 发送状态是空闲的!
n = 1000;
do
{
osDelay(1);
}
while(txBuf10[BUF_FLAG] != BUF_STATUS_FREE && n--);
if(n <= 0) return false;
//满足条件后发送,就这样,连续发送两条,第二条都会覆盖第一条的一部分!!!
if(txBuf10[BUF_FLAG] == BUF_STATUS_FREE)
{
for( i = 0; i < len; i++)
{
txBuf10[i] = *(buf + i);
}
txBuf10[BUF_FLAG] = len;
bResult = true;
//BSP_LED3_Toggle(); //test OK!
HAL_UART_Transmit_DMA(&huart1, (uint8_t*)txBuf10, len);
}
return bResult;
}
//串口DMA发送中断的处理 stm32f1xx_it.c中
void DMA1_Channel4_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Channel4_IRQn 0 */
/* USER CODE END DMA1_Channel4_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_usart1_tx);
/* USER CODE BEGIN DMA1_Channel4_IRQn 1 */
//确认是DMA完全发送结束中断,注意有一个发送一半中断
if(__HAL_DMA_GET_IT_SOURCE(&hdma_usart1_tx, DMA_IT_TC))
{
huart1.gState = HAL_UART_STATE_READY;
hdma_usart1_tx.State = HAL_DMA_STATE_READY;
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_TC4);
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_HT4);
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_tx, DMA_FLAG_TE4);
__HAL_UNLOCK(&hdma_usart1_tx);
txBuf10[BUF_FLAG] = BUF_STATUS_FREE;//设置为可发送状态
}
/* USER CODE END DMA1_Channel4_IRQn 1 */
}
测试结果:
21:23:55 You Send : SETN
Receive cache:0, response in blocking mode...0.
Receive cache:0, response in blocking mode...1.
Receive cache:0, response in blocking mode...2.
Receive cache:0, response in blocking mode...3.
Receive cache:0, response in blocking mode...4.
21:23:56 You Send : SETN
Receive cache:1, response in blocking mode...0.
Receive cache:1, response in blocking mode...1.
Receive cache:1, response in blocking mode...2.
Receive cache:1, response in blocking mode...3.
Receive cache:1, response in blocking mode...4.
21:24:03 You Send : SETD
Receive cache:0, response with DMA..Receive cache:0, response with DMA.Receive cache:0, response with DMAReceive cache:0, response with DMA.Receive cache:0, response with DMA...4.
21:24:04 You Send : SETD
Receive cache:1, response with DMA..Receive cache:1, response with DMA.Receive cache:1, response with DMAReceive cache:1, response with DMA.Receive cache:1, response with DMA...4.
从结果看,接收双缓冲用上了,串口DMA发送拉胯。。。,阻塞式发送优秀!!
实在找不出原因了。。。囧
完整代码:
FREERTOS+USART+IDLE+DMA双缓冲接收,DMA发送和阻塞式发送-硬件开发文档类资源-CSDN下载
版权声明:本文为CSDN博主「st01lsp」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/st01lsp/article/details/122708662
暂无评论