- /*
- * @brief 初始化
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_uart_init(void)
- {
- GPIO_Config_T gpioConfig;
- USART_Config_T configStruct;
-
- /* GPIO */
- RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA);
- GPIO_ConfigStructInit(&gpioConfig);
- gpioConfig.pin = GPIO_PIN_9 | GPIO_PIN_10;
- gpioConfig.mode = GPIO_MODE_AF;
- gpioConfig.otype = GPIO_OTYPE_PP;
- gpioConfig.speed = GPIO_SPEED_100MHz;
- gpioConfig.pupd = GPIO_PUPD_NOPULL;
- GPIO_Config(GPIOA, &gpioConfig);
-
- /* TX */
- GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_9, GPIO_AF_USART1);
- /* RX */
- GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_10, GPIO_AF_USART1);
-
- /* USART */
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_USART1);
- USART_Reset(UART_INS);
- USART_ConfigStructInit(&configStruct);
- configStruct.baudRate = 115200;
- configStruct.wordLength = USART_WORD_LEN_8B;
- configStruct.stopBits = USART_STOP_BIT_1;
- configStruct.parity = USART_PARITY_NONE ;
- configStruct.mode = USART_MODE_TX_RX;
- configStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;
- USART_Config(UART_INS, &configStruct);
- USART_Enable(UART_INS);
- }
轮询方式的发送数据,基于发送为空标志位,不断的查询,发送为空即将新的数据写入串口数据寄存器即可,待全部数据都传入完成,等待发送完成标志置位,即表示所有数据发送完成。
- /*
- * @brief 发送数据
- *
- * @param buf: 数据缓存
- * buf_len: 缓存大小
- *
- * @retval None
- *
- */
- void bsp_uart_send(uint8_t *buf, uint16_t buf_len)
- {
- uint16_t i = 0;
-
- while (i < buf_len) {
- while (USART_ReadStatusFlag(UART_INS, USART_FLAG_TXBE) == RESET);
- USART_TxData(UART_INS, buf[i++]);
- }
- while (USART_ReadStatusFlag(UART_INS, USART_FLAG_TXC) == RESET);
- }
接收数据通过检查接收非空标志判断,接收非空标志置位,则表示有新数据抵达数据寄存器,从数据寄存器取走数据后,标志位将自动清零。而判断一串完整的报文数据接收是否完整,是通过接收空闲标志确定,接收空闲标志为在一定数量的空闲位后依然无新数据到来,则置位接收空闲标志,同时也意味着连续的数据传输已经结束。
- /*
- * @brief 接收数据
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_uart_recv(void)
- {
- /* 接收非空 */
- if (USART_ReadStatusFlag(UART_INS, USART_FLAG_RXBNE) != RESET) {
- if (rx_len < sizeof(rx_buf)) {
- rx_buf[rx_len++] = USART_RxData(UART_INS);
- }
- }
- /* 接收空闲则表示一帧报文结束 */
- if (USART_ReadStatusFlag(UART_INS, USART_FLAG_IDLE) != RESET) {
- rx_complete = 1;
- USART_ClearStatusFlag(UART_INS, USART_FLAG_IDLE);
- }
- }
中断方式的串口通讯初始化,只比轮询方式多使能接收非空中断和空闲中断,同时需要使能对应的串口中断号。注意:发送的相关中断需在要发送数据时,才能使能,否则立刻使能发送为空中断,将导致程序立刻进入中断。
- /*
- * @brief 初始化
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_uart_init(void)
- {
- GPIO_Config_T gpioConfig;
- USART_Config_T configStruct;
-
- /* GPIO */
- RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA);
- GPIO_ConfigStructInit(&gpioConfig);
- gpioConfig.pin = GPIO_PIN_9 | GPIO_PIN_10;
- gpioConfig.mode = GPIO_MODE_AF;
- gpioConfig.otype = GPIO_OTYPE_PP;
- gpioConfig.speed = GPIO_SPEED_100MHz;
- gpioConfig.pupd = GPIO_PUPD_NOPULL;
- GPIO_Config(GPIOA, &gpioConfig);
-
- /* TX */
- GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_9, GPIO_AF_USART1);
- /* RX */
- GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_10, GPIO_AF_USART1);
-
- /* USART */
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_USART1);
- USART_Reset(UART_INS);
- USART_ConfigStructInit(&configStruct);
- configStruct.baudRate = 115200;
- configStruct.wordLength = USART_WORD_LEN_8B;
- configStruct.stopBits = USART_STOP_BIT_1;
- configStruct.parity = USART_PARITY_NONE ;
- configStruct.mode = USART_MODE_TX_RX;
- configStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;
- USART_Config(UART_INS, &configStruct);
- /* 关闭发送为空中断 */
- USART_DisableInterrupt(UART_INS, USART_INT_TXBE);
- /* 使能接收非空中断 */
- USART_EnableInterrupt(UART_INS, USART_INT_RXBNE);
- /* 使能空闲中断 */
- USART_EnableInterrupt(UART_INS, USART_INT_IDLE);
- /* 使能中断 */
- NVIC_EnableIRQRequest(USART1_IRQn, 0, 0);
- /* 使能串口 */
- USART_Enable(UART_INS);
- }
中断方式的串口发送数据,初始化完发送的基本信息后,使能发送为空中断即可,后续工作将进入中断进行处理。
- /*
- * @brief 发送数据
- *
- * @param buf: 数据缓存
- * buf_len: 缓存大小
- *
- * @retval None
- *
- */
- void bsp_uart_send(uint8_t *buf, uint16_t buf_len)
- {
- if ((buf != NULL) && (buf_len > 0)) {
- tx_buf = buf;
- tx_index = 0;
- tx_len = buf_len;
- /* 开启发送为空中断 */
- USART_EnableInterrupt(UART_INS, USART_INT_TXBE);
- }
- }
串口中断处理函数内部,执行串口的发送和接收工作。发送为空中断,则将待发送数据不断传入数据寄存器,数据传输完毕后,立刻关闭发送为空中断;
串口中断接收数据与轮询方式的接收类似,只是工作改为在中断内部执行,无需主循环代码查询。
- /*
- * @brief 串口中断
- *
- * @param None
- *
- * @retval None
- *
- */
- void USART1_IRQHandler(void)
- {
- /* 发送为空 */
- if (USART_ReadIntFlag(UART_INS, USART_INT_TXBE) != RESET) {
- if (tx_index < tx_len) {
- /* 发数据 */
- USART_TxData(UART_INS, tx_buf[tx_index++]);
- } else {
- /* 关闭发送 */
- USART_DisableInterrupt(UART_INS, USART_INT_TXBE);
- }
- }
- /* 接收非空 */
- if (USART_ReadIntFlag(UART_INS, USART_INT_RXBNE) != RESET) {
- if (rx_len < sizeof(rx_buf)) {
- rx_buf[rx_len++] = USART_RxData(UART_INS);
- }
- }
- /* 空闲 */
- if (USART_ReadIntFlag(UART_INS, USART_INT_IDLE) != RESET) {
- /* 清除空闲状态 */
- (void)USART_RxData(UART_INS);
- rx_complete = 1;
- }
- }
串口的DMA通讯方式,初始化代码相对较为复杂,但此方式却是最为推荐的通讯方式。
初始化工作相对前面的两种方式,需要分别为串口的接收和发送都初始化一个DMA数据流,因为串口接收的数据量没法确定,所以初始化串口接收的DMA传输数据量固定配置为接收的最大值,也因此串口不能单次接收超过此值的数据量;而串口的发送DMA配置无法确定传输的数据量和传输的存储地址,需等到发送数据时才能确认。
- /*
- * @brief 初始化
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_uart_init(void)
- {
- GPIO_Config_T gpioConfig;
- USART_Config_T configStruct;
- DMA_Config_T dmaConfig;
-
- /* GPIO */
- RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA);
- GPIO_ConfigStructInit(&gpioConfig);
- gpioConfig.pin = GPIO_PIN_9 | GPIO_PIN_10;
- gpioConfig.mode = GPIO_MODE_AF;
- gpioConfig.otype = GPIO_OTYPE_PP;
- gpioConfig.speed = GPIO_SPEED_100MHz;
- gpioConfig.pupd = GPIO_PUPD_NOPULL;
- GPIO_Config(GPIOA, &gpioConfig);
-
- /* TX */
- GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_9, GPIO_AF_USART1);
- /* RX */
- GPIO_ConfigPinAF(GPIOA, GPIO_PIN_SOURCE_10, GPIO_AF_USART1);
-
- /* DMA */
- RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_DMA2);
- DMA_ConfigStructInit(&dmaConfig);
- /* DMA -> USART_RX */
- dmaConfig.channel = DMA_CHANNEL_4;
- dmaConfig.peripheralBaseAddr = (uint32_t)&UART_INS->DATA;
- dmaConfig.memoryBaseAddr = (uint32_t)rx_buf;
- dmaConfig.dir = DMA_DIR_PERIPHERALTOMEMORY;
- dmaConfig.bufferSize = sizeof(rx_buf);
- dmaConfig.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
- dmaConfig.memoryInc = DMA_MEMORY_INC_ENABLE;
- dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_BYTE;
- dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_BYTE;
- dmaConfig.loopMode = DMA_MODE_NORMAL;
- dmaConfig.priority = DMA_PRIORITY_LOW;
- dmaConfig.fifoMode = DMA_FIFOMODE_DISABLE;
- dmaConfig.fifoThreshold = DMA_FIFOTHRESHOLD_QUARTER;
- dmaConfig.peripheralBurst = DMA_PERIPHERALBURST_SINGLE;
- dmaConfig.memoryBurst = DMA_MEMORYBURST_SINGLE;
- DMA_Config(RX_DMA, &dmaConfig);
- /* 传输完成中断 */
- DMA_EnableInterrupt(RX_DMA, DMA_INT_TCI**);
- /* 使能中断 */
- NVIC_EnableIRQRequest(DMA2_STR2_IRQn, 0, 0);
-
- /* DMA -> USART_TX */
- dmaConfig.channel = DMA_CHANNEL_4;
- dmaConfig.peripheralBaseAddr = (uint32_t)&UART_INS->DATA;
- dmaConfig.memoryBaseAddr = 0;
- dmaConfig.dir = DMA_DIR_MEMORYTOPERIPHERAL;
- dmaConfig.bufferSize = 0;
- dmaConfig.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
- dmaConfig.memoryInc = DMA_MEMORY_INC_ENABLE;
- dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_BYTE;
- dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_BYTE;
- dmaConfig.loopMode = DMA_MODE_NORMAL;
- dmaConfig.priority = DMA_PRIORITY_LOW;
- dmaConfig.fifoMode = DMA_FIFOMODE_DISABLE;
- dmaConfig.fifoThreshold = DMA_FIFOTHRESHOLD_QUARTER;
- dmaConfig.peripheralBurst = DMA_PERIPHERALBURST_SINGLE;
- dmaConfig.memoryBurst = DMA_MEMORYBURST_SINGLE;
- DMA_Config(TX_DMA, &dmaConfig);
-
- /* USART */
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_USART1);
- USART_Reset(UART_INS);
- USART_ConfigStructInit(&configStruct);
- configStruct.baudRate = 115200;
- configStruct.wordLength = USART_WORD_LEN_8B;
- configStruct.stopBits = USART_STOP_BIT_1;
- configStruct.parity = USART_PARITY_NONE ;
- configStruct.mode = USART_MODE_TX_RX;
- configStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;
- USART_Config(UART_INS, &configStruct);
- USART_EnableDMA(UART_INS, USART_DMA_TX_RX);
- /* 使能空闲中断 */
- USART_EnableInterrupt(UART_INS, USART_INT_IDLE);
- /* 使能发送完成中断 */
- USART_EnableInterrupt(UART_INS, USART_INT_TXC);
- /* 使能中断 */
- NVIC_EnableIRQRequest(USART1_IRQn, 0, 0);
- /* 使能串口 */
- USART_Enable(UART_INS);
- /* 接收数据 */
- bsp_uart_recv();
- }
DMA的通讯方式,判断接收完整一串报文数据,也是通过空闲标志位确定,而确定具体已经接收了多少数据量,则是通过计算当前DMA接收数据流的剩余可传输数据量来反推得出,处理完毕后,需重新开启接受的DMA,并配置为最大的传输数据量,以确保后续新来的数据量能完整接收。
DMA的发送数据,通过发送完成中断确定全部数据发送完毕。
- /*
- * @brief 串口中断
- *
- * @param None
- *
- * @retval None
- *
- */
- void USART1_IRQHandler(void)
- {
- uint16_t num = 0;
-
- /* 空闲 */
- if (USART_ReadIntFlag(UART_INS, USART_INT_IDLE) != RESET) {
- /* 清除空闲状态 */
- (void)USART_RxData(UART_INS);
- /* 计算已经传输的数据量 */
- num = sizeof(rx_buf) - DMA_ReadDataNumber(RX_DMA);
- if (num != 0) {
- rx_len = num;
- rx_complete = 1;
- }
- bsp_uart_recv();
- }
- /* 发送完成中断 */
- if (USART_ReadIntFlag(UART_INS, USART_INT_TXC) != RESET) {
- /* 清除空闲状态 */
- USART_ClearIntFlag(UART_INS, USART_INT_TXC);
- }
- }
另外,DMA接收的数据流完成中断也需要特意处理,处理方式与串口的空闲中断标志一致,即重新开启接收。
- /*
- * @brief DMA中断
- *
- * @param None
- *
- * @retval None
- *
- */
- void DMA2_STR2_IRQHandler(void)
- {
- uint16_t num = 0;
-
- /* USART RX */
- if (DMA_ReadIntFlag(RX_DMA, DMA_INT_TCI**2) != RESET) {
- DMA_ClearIntFlag(RX_DMA, DMA_INT_TCI**2);
- /* 计算已经传输的数据量 */
- num = sizeof(rx_buf) - DMA_ReadDataNumber(RX_DMA);
- if (num != 0) {
- rx_len = num;
- rx_complete = 1;
- }
- bsp_uart_recv();
- }
- }
重新开启接收,需先关闭DMA的使能,重新配置为接收最大传输数据量后,再使能串口接收的DMA数据流。
- /*
- * @brief 接收数据
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_uart_recv(void)
- {
- DMA_Disable(RX_DMA);
- DMA_ConfigDataNumber(RX_DMA, sizeof(rx_buf));
- DMA_Enable(RX_DMA);
- }
串口的DMA发送需每次开启发送前都配置新的数据存储地址和数据数量,因为这些信息在应用层中,不同的处理逻辑传入的值都是不同的。另外,DMA数据流的错误标志需即时清除,否则影响DMA的重新使能。
- /*
- * @brief 发送数据
- *
- * @param buf: 数据缓存
- * buf_len: 缓存大小
- *
- * @retval None
- *
- */
- void bsp_uart_send(uint8_t *buf, uint16_t buf_len)
- {
- if ((buf != NULL) && (buf_len > 0)) {
- uint32_t flag = DMA_INT_FEI**7 | DMA_INT_DMEI**7 | DMA_INT_TEI**7 | DMA_INT_HTI**7 | DMA_INT_TCI**7;
- DMA_ClearIntFlag(TX_DMA, flag);
- DMA_Disable(TX_DMA);
- DMA_ConfigDataNumber(TX_DMA, buf_len);
- TX_DMA->M0ADDR = (uint32_t)buf;
- USART_ClearIntFlag(UART_INS, USART_INT_TXC);
- DMA_Enable(TX_DMA);
- }
- }
优劣势:
轮询方式:代码简单,利于理解,但需要及时查询状态,否则溢出,且发数据期间严重占用内核;
中断方式:代码简单,利于理解,收发数据期间可释放内核,但频繁中断,占用内核,且大量其他中断存在的场景可能影响数据收发的连续性;
DMA方式:收发数据不占用内核,且中断产生较少,收发数据能保证连续性,但初始化代码复杂;
从使用经验上推荐,最推荐的是DMA方式,而中断方式在大量中断的场景下要保证收发数据的绝对连续性,需要把串口的中断优先级提升到非常高的位置,至于轮询方式,只有非常简单的应用场景才能考虑使用,一般不建议使用。
4. 测试
本次测试的目的是将接收到的数据再发出来,如下代码为轮询方式,相当简洁,而中断和DMA方式,则连bsp_uart_recv()函数都不需要调用。
- // 应用初始化
- void app_init(void)
- {
- bsp_uart_init();
- }
- // 应用任务
- void app_task(void)
- {
- /* 接收数据 */
- bsp_uart_recv();
- /* 接收完成再发出来 */
- if (bsp_uart_is_rx_complete() != 0) {
- bsp_uart_send(bsp_uart_get_rx_buf(), bsp_uart_get_rx_len());
- bsp_uart_reset_recv();
- }
- }
如下图的测试结果:串口完整接收到数据后,只间隔100us就立刻发出。
5. 驱动移植说明
驱动的移植只需要修改bsp_uart_init函数即可,改为对应的IO和串口外设号,同时DMA的数据流需对应手册修改。
6. 详细代码
轮询、中断、DMA方式
Interrupt.zip
(6.56 MB, 下载次数: 0)
DMA.zip
(6.57 MB, 下载次数: 0)