摘要:接收缓冲区请求和发送缓冲区请求是独立的。 此时,三个字节实际上还没有发送到串口。 数据寄存器和移位寄存器的3个字节仍然必须传输,并且不能关闭串口传输。
文章目录 串行通信 串口 USART 中断 串口模式设置 使用 DMA 进行连续通信 使用 DMA 进行发送 使用 DMA 进行接收编程 接收处理 主要函数: 中断处理函数: 初始化(标准库) ) ) 传输过程打开串口 传输完成中断打开DMA 传输完成中断打开DMA传输功能分析程序1 程序2`fifo_buff`代码`fifo_buff.c`fifo_buff.h`完整代码及用例参考
串行通信串行端口串行接口称为串行端口,也称为串行通信接口或串行通信接口(通常称为COM接口)。 这是使用串行通信的扩展接口。 串行接口是指按顺序逐位传输数据。 其特点是通信线路简单。 如果有一对传输线,就可以进行双向通信(可以用电话线作为传输线),可以显着降低成本,特别适合长距离通信,但传输速度慢。
USART (Universal synchronous Asynchronous Receiver and Transmitte):通用同步异步接收器和发送器
USART是一种串行通信设备,可以灵活地与外部设备进行全双工数据交换。
UART(Universal Asynchronous Receiver and Transmitter):通用异步接收器和发送器
异步串行通信端口(UART)通常被称为嵌入式系统中的串行端口。 那也是一种通用的数据通信协议。
区别:
USART是指单片机端口模块,可以根据需要配置为同步模式(SPI、I2C)或异步模式。 后者是一个UART。 因此,虽然UART可以被称为与SPI或I2C同等的“协议”,但USART应该被理解为一个实体而不是协议。
与同步通信相比,UART不需要统一的时钟线,布线更容易。 然而,为了成功解码信号,使用 UART 通信的双方必须事先就波特率或每个单元事件发送的符号数达成一致。
附录:
在电子通信领域,波特率是调制速率,是有效数据信号调制载波的速率,即传输速率。载波的调制状态每单位时间改变。 频率。 这是符号传输速率的度量。 1 波特是指每秒传输一个符号。 可以使用各种调制方案将多比特信号加载到单个符号中。 [1]“Baud”本身就是一个速率,所以不需要写成Baud Rate(速率是多余的)。 单位“波特”本身已经代表每秒的调制次数。 使用“波特/秒”作为单位是一个常见的错误。 但在普通汉语口语中,仍常用“波特率/秒”。
USART 中断
[外部链接图像传输失败。 源站点可能具有适当的反浸出机制。 我们建议保存图片并直接上传(img -VkffEvL6-1632828582303)(wp-content/uploads/qf0f2j0lor0.jpg)]
USART 中断事件已连接。 相同中断向量:
发送:发送完成,发送或发送数据寄存器为空时清除中断。 接收期间:检测到空闲线、溢出错误、接收数据寄存器不为空、奇偶校验错误。错误、LIN 开路检测、噪声标志(仅限多缓冲区通信)和帧错误(仅限多缓冲区通信)
[外部链接图像传输失败。 源站点可能具有适当的反浸出机制。 受到推崇的。 保存图片请到下面直接上传(img-IbZhqtpL-1632828582305)(wp-content/uploads/o3xins1q4v4.jpg) ]
配置串口模式
使用DMA进行连续通信
USART可以使用DMA进行连续通信。 接收和发送缓冲区的 DMA 请求是独立的。
使用 DMA 发送
使用 DMA 发送 将 USART_CR3 寄存器中的 DMAT 位设置为 1,启用 DMA 模式进行发送。 当 TXE 位置 1 时,数据可以从 SRAM 区域(通过 DMA 配置,请参阅 DMA 部分)加载到 USART_DR 寄存器中。 要映射用于 USART 传输的 DMA 通道,请按照以下步骤操作(x 代表通道号):
将USART_DR寄存器地址写入DMA控制寄存器,并将其设置为传输的目的地址。 每次 TXE 事件后,数据都会从内存移至该地址。
将内存地址写入DMA控制寄存器,并将其设置为源地址。 在每个 TXE 事件之后,数据将从该存储区域加载到 USART_DR 寄存器中。
设置要传输到 DMA 控制寄存器的字节总数。
在DMA寄存器中设置通道优先级。
DMA中断在一半或全部传输完成后产生,具体取决于应用程序的需要。
向 SR 寄存器中的 TC 位写入 0 将其清除。
激活DMA寄存器通道。 当到达 DMA 时当设置了控制器内要传输的数据量时,DMA 控制器会在 DMA 通道的中断向量中生成中断。 在发送模式下,DMA 写入所有要发送的数据后(DMA_ISR 寄存器中的 TCIF 标志被置位),可以监控 TC 标志以确保 USART 通信完成。 为了避免破坏最后一次传输,必须在禁用 USART 或进入停止模式之前执行此步骤。 软件必须等到 TC=1。 TC 标志在所有数据传输期间必须保持清零,并在最后一帧传输后由硬件设置。
使用 DMA 进行接收
将 USART_CR3 寄存器中的 DMAR 位设置为 1,以启用 DMA 模式进行接收。 当接收到数据字节时,数据从 USART_DR 寄存器加载到 SRAM 区域(通过 DMA 配置,请参阅 DMA 规范)。 要映射 DMA 通道以进行 USART 接收,请按照下列步骤操作:
将USART_DR寄存器地址写入DMA控制寄存器,并将其设置为传输的源地址。 每当 RXNE 事件发生时,数据就会从该地址移入内存。
将内存地址写入DMA控制寄存器,并将其设置为传输的目标地址。 每个 RXNE 事件后,数据都会从 USART_DR 寄存器加载到该存储区域。
设置要传输到 DMA 控制寄存器的字节总数。
在DMA控制寄存器中设置通道优先级。
根据应用程序的需要,在一半或全部传输后生成中断。
激活DMA控制寄存器通道。 当达到 DMA 控制器中配置的数据传输量时,DMA 控制器在 DMA 通道的中断向量中生成中断。 在中断子程序中,USART_CR3 寄存器中的 DMAR 位必须由软件清零。
注意:如果使用 DMA 接收,请勿启用 RX。NEIE位
【外部链接图片传输失败。 源站点可能具有适当的反浸出机制。 我们建议保存图片并直接上传(img-NpkQgBln-1632828582308)(wp-content/uploads/5yacuselxub.jpg)]
多缓冲区通信中的错误标志和中断生成
如果在多缓冲区通信期间事务中发生错误,则在当前字节后设置错误标志。 如果中断使能位被置位,则会产生中断。 在单字节接收过程中,RXNE 上设置的帧错误、溢出错误和噪声标志有一个多频带错误标志中断使能位(USART_CR3 寄存器中的 EIE 位)。 如果设置该位,则任何这些错误都将在当前字节之后导致中断。
编程
接收方式:
DMA 接收中断接收 DMA+串口+DMA 空闲中断接收 DMA 双缓冲区+串口+DMA 空闲中断接收 DMA+串口+DMA 空闲中断+环形队列Accept传输方式: DMA+串口传输 单串口传输 DMA+串口传输+环形队列(双缓冲) 动态内存分配 FIFIO
下面主要是环形队列+DMA+非动态内存分配+IDLE中断
建议先阅读底部参考文章
接收处理
USART1 + DMA + IDLE中断+无锁队列
主要功能: int main(void){ uint8_t buff_read[32]; usart1_init(); while (1) { length = fifo_read_buff(pfifo_x, buff_read, 32);{ printf("lengtt = %d", length); // 实际接收到的数据长度 // 处理接收到的数据 } else { printf("no data rx"); // 无数据 } if (pfifo_x- >error ) { printf("fifo error %d", pfifo_x->error); // 接收错误 pfifo_x->error = 0; } }} 中断处理函数: void USART1_IRQHandler(void) // 数据接收中断 { __IO uint8_t Len = 0; //发送完成中断/* * DMA中断简单来说就是所有需要传输的数据字节已经传输到串口的发送数据寄存器中。 *此时串口上还有2个字节尚未发送。 数据和移位寄存器2字节仍然必须传输,并且串口传输不能关闭。 * 同样,如果 485 切换方向,则必须等到传输完成,即直到设置移位寄存器传输完成 - TC 标志。 * * TXE 指发送缓冲器 DR 空,TC 指 SHIFT 移位寄存器空。 * DMA 完成仅意味着最后一个字节已发送到 DR 寄存器。 此时,一个字节已开始发送到 SHIFT 移位寄存器。 *DR寄存器有1个字节等待发送,2个字节尚未发送。 发送完成。 */ if (USART_GetITStatus(USART1, USART_IT_TC) == SET) { USART_ClearITPendingBit(USART1, USART_IT_TC);USART_ITConfig(USART1, USART_IT_TC, disabled); DMA2_Stream7_working = 0; } //总线空闲中断 if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) //触发中断标志 { Len = USART1->SR; Clear Len = USART1->DR; //清除USART_IT_IDLE标志 Len = DMA_GetCurrDataCounter(DMA2_Stream5); // 获取当前剩余数据大小的函数 if (pfifo_1 != 0) { // Len 是当前接收索引 pfifo_1 [ k4]>in += ((pfifo_1->last_cnt - Len) & (pfifo_1 ->Size- 1)); //更新 pfifo_1->last_cnt = Len Masu. if (( pfifo_1->in - pfifo_1->out) > pfifo_1->size) { pfifo_1->out = pfifo_1->in; // 清除缓存并分配 请注意订单。 pfifo[ k4]>in = pfifo->out 错误 pfifo_1->error |= FIFO_DMA_ERROR_RX_FULL; } } else { pfifo_1->Error |= FIFO_DMA_ERROR_RX_POINT_NULL; } }} 初始化(标准库)#define USART1_RX_LEN 32#define USART1_TX_LEN 32uint8_t Usart1_Rx[USART1_RX_LEN] = {0};uint8_t Usart 1_ T x[USART1_TX_LEN] = { 0} ;uint8_t Usart1_Tx_Buffer[USART1_TX_LEN] = { 0};fifo_rx_def fifo_usart_rx_1;fifo_rx_def *pfifo_1 = &fifo_usart_rx_1;fifo_rx_def fifo_usart_tx_1;uint8_t DMA2_Stream7_working = 0;void usart1_init(vo id ){ /* ---[k4 ] [k4 ]----[k4 ]---- 使能模块时钟源 ----[ k4] - ----- ---------- [k4 ]------ ]*/ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能 GPIOA 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //使能USART1时钟 GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); //GPIOA9复用为USART1 GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); //GPIOA10复用为USART1 / * --[ k4 ]----------[k4 ] GPIO 配置 -[ k4]--[ k4 ]----------- ]-[k4 ]--- ----------------[k4 ]-*/ { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART1_InitStruct; //USART1 端口设置 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; //GPIOA9 和 GPIOA10 GP IO_InitStru ct.GPIO_Mode = GPIO_Mode_AF; //复用函数GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽复用输出 GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOA, &GPIO_InitStruct); //初始化 PA9 和 PA10 //初始化 USART1 USART_DeInit(USART1); ART_波特率 = 115200; //波特率设置 USART1_InitStruct.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式 USART1_InitStruct.USART_StopBits = USART_StopBits_1; //1个停止位 USART1_InitStruct.USART_Parity = USART_Parity _No; //无奇偶校验位 USART1_InitStruct.美国ART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制 USART1_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式 USART_Init(USART1, &USART1_InitStruct); //初始化串口1 USART_Cmd(USART1, ENABLE); //使能串口1 USART_ClearFlag(USART1, USART_FLAG_TC); //Usart1 NVIC配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;串口1中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //启用IRQ通道 NVIC_Init(&NVIC _InitStructure); IC根据参数注册USART_ set ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能相关中断 USART_ITConfig(USART1, USART_IT_TC, DISABLE); USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); k4]-[ k4]------- Co配置 DMA ---------------[k4 ] ----------------[ k4 ] ]-------*/ /* 发送*/ { DMA_InitTypeDef DMA_InitStruct; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);传输 DMA_Cmd(DMA2_Stream7, DISABLE); //关闭 DMA DMA_DeInit(DMA2_Stream7); //重置为默认值 DMA_InitStruct.DMA_Channel = DMA_Channel_4; DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t) & (USART1->DR); address DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)(Usart1_Tx); //目标地址 DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral; //数据传输方向为内存外设 DMA_InitStruct.DMA_BufferSize = USART1_TX_LEN; //设置数据缓冲区大小 DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址不变 DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存缓冲区地址增加 DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte ; //8位字节传输DMA_Init结构.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位 DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; //工作在正常模式 DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh; //最高优先级 DMA_InitStruct.DMA_FIFOMode = DMA_FIFO Mode_Disable; DMA_FIFOThreshold_1QuarterFull; DMA_InitStruct.DMA_MemoryBurst; = DMA_MemoryBurst_Single; DMA_InitStruct.DMA_PeripheralBurst =DMA_Peripheral Burst_Single; DMA_Init(DMA2_Stream7, &DMA_InitStruct); NVIC_IRQChannel = DMA2_Stream7_IRQn; NVIC_IRQChannelPreemptionPriority = 2; NVIC_IRQChannelCmd = 启用; NVIC_Init(&NVIC_InitStructure);使能传输完成中断 DMA_Cmd(DMA2_Stream7, DISABLE); USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); //使能串口 DMA 传输 if (fifo_init(&fifo_usart_tx_1, Usart1_Tx_Buffer, USART1_TX_LEN) == - 1) { // 必须平方} } // 接收数据 { DMA_InitTypeDef DMA_InitStruct; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);// 关闭 DMA while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE) ; //重置为默认值 DMA_InitStruct.DMA_Channel = DMA_Channel_4; DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t) & (USART1[ k4] >DR ); //源地址 DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)(Usart1_Rx); //目标地址 DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory; //数据传输方向为内存外设 DMA_InitStruct.DMA_BufferSize = USART1_RX_LEN; ct.DMA_外设公司= DMA_PeripheralInc_Disable; //外设地址不变 DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //增加内存缓冲区地址 DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //itStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位 DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; //工作在循环缓存模式 DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh; //最高优先级 DMA_InitStruct.DMA_FIFOMode = DMA_InitStru; ct .DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull; DMA_InitStruct 。 DMA_MemoryBurst = DMA_Mode_Normal; //DMA_MemoryBurst_Single;// DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA2_Stream5, &DMA_InitStruct); x,启用);如果(fifo_init(pfifo_1,Usart1_Rx,USART1_RX_LEN)== -1) { // 必须是2的幂} }} 设计中的传输过程
以下是串行端口驱动程序过程中应遵循的准则:
尽可能减少程序执行时间。 尽可能减少程序占用的内存。 使能串口发送完成中断 void USART1_IRQHandler(void) // 接收数据中断 { __IO uint8_t Len = 0 ; //发送完成中断/* * 当 DMA 中断时,表示所有需要传输的数据字节都已发送仅仅意味着它已经被传输到串口的发送数据寄存器中。 *此时串口上还有2个字节尚未发送。 数据和移位寄存器2字节仍然必须传输,并且串口传输不能关闭。 * 同样,如果 485 切换方向,则必须等到传输完成,即直到设置移位寄存器传输完成 - TC 标志。 * * TXE 指发送缓冲器 DR 空,TC 指 SHIFT 移位寄存器空。 * DMA 完成仅意味着最后一个字节已发送到 DR 寄存器。 此时,一个字节已开始发送到 SHIFT 移位寄存器。 *DR寄存器有1个字节等待发送,2个字节尚未发送。 发送完成。 */ if (USART_GetITStatus(USART1, USART_IT_TC) == SET) { USART_ClearITPendingBit(USART1, USART_IT_TC); USART_ITConfig(USART1, USART_IT_TC, 禁用); DMA2_Stream7_working = 0; }} 启用 DMA 传输完成中断 //uint8_t DMA2_Stream7_working = 0;/ ** * @brief 传输完成中断 * @param[in] void * @retval void *//* * ST 人员可使用 APPNOTE 指南(对于带 UART 的微控制器型号):无 RS485 功能):*1。在启动DMA之前,首先关闭UART发送完成中断并清除发送完成中断标志。 *2. DMA 传输完成中断功能使能 UART 传输完成中断。 *3. UART发送完成中断功能将RS485切换至接收状态。 */void DMA2_Stream7_IRQHandler(void){ if (DMA_GetITStatus(DMA2_Stream7, DMA_IT_TCIF7) != RESET) { DMA_ClearFlag(DMA2_Stream7, DMA_IT_TCIF7); DMA_Cmd(DMA2_Stream7, disabled); //禁用传输 //while (DMA_GetCmdStatus(DMA2_Stream7 ) != DISABLE ); USART_ITConfig(USART1, USART_IT_TC, ENABLE); //DMA2_Stream7_working = 0; }} DMA 发送函数 //uint8_t DMA2_Stream7_working = 0;/** * @brief 串口 1 + DMA 发送* @param[in] *data:要发送的数据* @param[in] len: 数据大小* @retval void */uint32_t usart1_dma_send(uint8_t *data, uint16_t len){ uint32_t result = fifo_write_buff(&fifo_usart_tx_1, data, len); //数据放入循环中缓冲。 //result != 0 表示数据放置成功。 DMA2_Stream6_working== 0 表示缓冲区中没有数据 if (DMA2_Stream7_working == 0 && result != 0) { len = fifo_read_buff(&fifo_usart_tx_1, Usart1_Tx, USART1_TX_LEN); //从循环缓冲区获取数据 DMA_SetCurrDataCounter(DMA2_Stream7, len); / / 设置传输长度 DMA_Cmd(DMA2_Stream7, ENABLE); //启用传输 DMA2_Stream7_working = 1; } if (result == len) { return len; } else { 返回结果; }}分析
https://www.amobbs.com/thread-4516795-1-1.html
stm32使用DMA发送串口数据时DMA中断传输程序1 / *指针指向ptr,所以必须发送count数据*/void USART1WriteDataToBuffer(*ptr, u8 count){ /*发送数据*/ while(count-- ) { /* 发送数据*/ USART1SendByte(*ptr++); /*等待此数据发送完毕,然后进入数据发送过程*/ while(USART_GetFlagStatus(USART1, USART_FLAG_TC); } /*发送数据时返回*/ }
这一段在实际应用中,此步骤可能会产生灾难性的结果。 首先,CPU通过向寄存器发送数据启动后,等待数据发送。 这样,在发送完所有数据之前,CPU 无法执行任何其他操作。 与CPU核心的执行速度相比,串口外设的运行速度非常快。 如果一个非常快的设备等待一个相对较慢的设备,您的程序将非常低效。
所以让我们尝试使用中断发送数据。
程序2 /*写入数据到发送缓冲区*/void USART1WriteDataToBuffer(*ptr, u8 count){ while (count != "/ 0" ) { USART1SendTCB[Index++] = *ptr++; } / ***溢出等判断省略***/}/***发送中断ISR***/void USART1SendUpdate(void){ /***判断发送缓冲区中的数据是否已发送* ** / /***发送发送缓冲区中的数据***/ USART1SendByte (*ptr++); /***将发送指针加一并等待。 发送的字节数会减1,并显示一些其他代码***/}
这样,当调用函数USART1WriteDataToBuffer时,我们就会将要发送的数据写入到数据中。发送缓冲区,CPU可以执行其他任务,直到数据发送完毕。 完成后,触发中断ISR,将下一个数据写入中断服务程序的发送寄存器,并开始下一次发送,直至完整发送完成。
但是,在实际的工程应用中,也经常会遇到类似的情况。 串口显示器必须显示1000个点,这1000个点的颜色RGB年度值通过串口发送。 将这1000个数据写入发送寄存器后,开始发送。 对于115200波特率、1个起始位、1个停止位、无奇偶校验位,至少需要(10*1000*2)/115200=0。1736秒,在此时间内时钟更新并且必须发送另一组时间更新数据。 该数据大约有100条,因此我们需要将这个数据串写入发送缓冲区中的发送字节之后。
同样,如果此时显示任务有更新,其他数据也会写入缓冲区。
【外部链接图片传输失败。 源站点可能具有适当的反浸出机制。 我们建议保存图片并直接上传(img-JxeVlfbL-1632828582309)(wp-content/uploads/5povf3vlbt1.jpg)]
如您所见,程序2满足时间要求,但不满足空间要求。 该数据缓冲区是单向的,因此当发送缓冲区已满时,可以清除发送缓冲区中的数据,以便下次可以缓冲数据。 这在内存较小的嵌入式系统中是无法容忍的。
所以你也可以在发送缓冲区内创建一个环形缓冲区。 在该缓冲区中,空白区和数据区由起始指针(HostIndex)和结束指针(HostIndex)定位。
起始指针:指向数据区的开头。 每次写入数据时都会更新起始指针。 当到达缓冲区末尾时,它自动返回到缓冲区的开头(StartIndex),直到写入结束指针,并且缓冲区已满,无法加载更多数据。 尾指针:指向数据区的末尾。 当数据发送时,尾指针的位置被更新。 当到达缓冲区的末尾(EndIndex)时,它会自动返回到缓冲区的开头(StartIndex),直到到达头指针。 至此证明所有数据已经发送完毕。
这样发送缓冲区的空白区和数据区是动态调整的。 提交的数据立即打开并保存下一个数据,尽可能保存有价值的数据。 发送缓冲区空间提高了效率。
fifo_buff 代码 fifo_buff.c /******************************************************** * ************************** * @file fifo_buff.c/h * @brief 无锁队列 * @note * @history 2021.07。 08 * @verbatim ================================================= =============================== 串口+DMA+IDLE中断+串口接收采用无锁队列 提高效率(参见Osprey谈单片机公众号) 1、执行串口初始化函数时,串口
评论前必须登录!
注册