这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【纳芯微NS800】3、NS800RT7P65UART2空闲中断不定长接收详解

共1条 1/1 1 跳转至

【纳芯微NS800】3、NS800RT7P65UART2空闲中断不定长接收详解

高工
2026-05-17 11:39:14     打赏

【前言】

最近入手纳芯微NS800开发板一块。这一篇详解串口不定长接收。

【硬件简介】

芯片:NovoSense NS800RT7P65(Cortex-M7 双核) 

开发环境:Keil MDK5 

功能:利用 UART IDLE LINE 中断实现任意长度数据的接收,接收完成后原样回传 

波特率:230400 8N1

【整体架构】

image.png

【环形缓冲区】

#define UART_RXBUF_SIZE   256U    /* 循环接收缓冲区大小 */

static uint8_t
  rxBuf[UART_RXBUF_SIZE];
static volatile uint16_t rxHead = 0U;   /* 写指针:RX ISR 更新 */
static volatile uint16_t rxTail = 0U;   /* 读指针:主循环更新 */
  • rxHead == rxTail 表示缓冲区空

  • 写入时 rxHead = (rxHead + 1) % SIZE

  • 读出时 rxTail = (rxTail + 1) % SIZE

  • 缓冲区满时(新字节到达但队列已满)直接丢弃该字节

【中断配置详解】

1、 NVIC 层:向量号分配

NS800RT7P65 的 UART2 有 三个独立中断源,分配在两个向量号上:

image.png

IDLE 中断和 RX 共用向量 200,这意味着在同一个 ISR 里需要同时处理两种中断。

/* 注册两个向量 */
Interrupt_register(UART2_TX_IRQn, &UART2_TX_handler);   
// 向量 199
Interrupt_register(UART2_RX_IRQn, &UART2_RX_handler);   
// 向量 200(RX + IDLE 共用)

Interrupt_enable(UART2_TX_IRQn);
Interrupt_enable(UART2_RX_IRQn);

2、 UART 外设层:中断使能

初始化时只打开 RX 中断,IDLE 中断由 RX ISR 在收到第1字节后动态使能:

/* 初始状态:只开 RX 中断 */
UART_enableInterrupt(uart, UART_INT_RX_DATA_REG_FULL);   
// 每字节触发
/* IDLE 中断暂不使能,等收到第1字节再开 */

为什么要收到第1字节才开 IDLE?

如果初始化时就打开 IDLE 中断,UART 未开始接收时线路默认处于空闲状态,会立即触发一次 IDLE 中断,导致误判。

3、 接收完成检测:空闲中断原理

/* 设置空闲字符计数 = 1,即检测到 1 个字符时间的空闲后产生 IDLE */
UART_setRxIdleCharacter(uart, UART_IDLE_CHARACTER_CNT1);

IDLE_LINE 检测机制:当 RX 线从高电平(数据位的最后一位)变为低电平并持续 1 个字符时间(包括起始位 + 数据位 + 停止位)时,硬件自动置位 IDLE 标志。

以 230400 波特率、8N1 为例:

• 每字符时间 = 10 bit ÷ 230400 ≈ 43.4μs

• 数据帧之间的间隔超过 43.4μs 即触发 IDLE

4、 RX 中断 handler(向量 200)

向量 200 同时承载 RX_DATA_REG_FULL 和 IDLE_LINE,在同一个 handler 里通过 UART_getIntMode() + UART_getStatusFlag() 区分:

/*******************************************************************************
 * UART RX 中断处理函数
 *
 * UART2_RX_IRQn 同时承载 RX_DATA_REG_FULL 和 IDLE_LINE 两种中断源。
 * 在这里分别检查两种中断的 enable 状态和 flag 状态,模仿原驱动的双重检查风格。
 *
 * 接收流程:
 *   1. 收到第1字节 -> 打开 IDLE 中断
 *   2. 线路空闲 1 字符时间 -> IDLE 触发 -> rxDone=1 -> 关闭 IDLE
 *   3. 主循环取出数据 -> 启动回传
 *   4. 回传完成后重新打开 RX+IDLE 中断
 ******************************************************************************/
void UART2_RX_handler(void)
{
    /* ---- IDLE LINE 检测(与 RX 共用同一向量) ---- */
    if (UART_getIntMode(uart, UART_INT_IDLE_LINE))
    {
        if (UART_getStatusFlag(uart, UART_IDLE_LINE_DETECT))
        {
            /* 读空 FIFO(可能还有残留字节)*/
            while (UART_getRxFifoStatus(uart) > UART_FIFO_RX0)
            {
                uint16_t next = (rxHead + 1U) % UART_RXBUF_SIZE;
                if (next != rxTail)
                {
                    rxBuf[rxHead] = UART_readChar(uart);
                    rxHead = next;
                }
                else
                {
                    (void)UART_readChar(uart);
                }
            }

            rxDone   = 1U;
            rxActive = 0U;
            UART_disableInterrupt(uart, UART_INT_IDLE_LINE);
        }
    }

    /* ---- RX DATA REG FULL(每字节到达时触发) ---- */
    if (UART_getIntMode(uart, UART_INT_RX_DATA_REG_FULL))
    {
        if (UART_getStatusFlag(uart, UART_RX_DATA_REG_FULL))
        {
            while (UART_getRxFifoStatus(uart) > UART_FIFO_RX0)
            {
                uint16_t next = (rxHead + 1U) % UART_RXBUF_SIZE;
                if (next != rxTail)
                {
                    rxBuf[rxHead] = UART_readChar(uart);
                    rxHead = next;
                }
                else
                {
                    (void)UART_readChar(uart);
                }
            }

            if (!rxActive)
            {
                rxActive = 1U;
                UART_enableInterrupt(uart, UART_INT_IDLE_LINE);
            }
        }
    }
}

双重检查的原因:UART_getIntMode() 检查中断是否在 NVIC 层面被使能,UART_getStatusFlag() 检查硬件状态标志位。两层检查确保只有真正使能的中断源才会被处理,防止误触发。

5 、TX 中断 handler(向量 199)

/*******************************************************************************
 * UART TX 中断处理函数
 ******************************************************************************/
void UART2_TX_handler(void)
{
    /* TX DATA REG EMPTY:TX FIFO 至少空 1 字节,填充它 */
    if (UART_getIntMode(uart, UART_INT_TX_DATA_REG_EMPTY))
    {
        if (UART_getStatusFlag(uart, UART_TX_DATA_REG_EMPTY))
        {
            while (UART_isSpaceAvailable(uart) && txIdx < txCount)
            {
                UART_writeChar(uart, txBuf[txIdx++]);
            }
            /* 全部发送完毕?切换到 TX_COMPLETE 等待发送器排空 */
            if (txIdx >= txCount)
            {
                UART_disableInterrupt(uart, UART_INT_TX_DATA_REG_EMPTY);
                UART_enableInterrupt(uart, UART_INT_TX_COMPLETE);
            }
        }
    }

    /* TX COMPLETE:TX FIFO + 移位器均空,发送彻底完成 */
    if (UART_getIntMode(uart, UART_INT_TX_COMPLETE))
    {
        if (UART_getStatusFlag(uart, UART_TX_COMPLETE))
        {
            UART_disableInterrupt(uart, UART_INT_TX_COMPLETE);

            /* 释放总线:重新打开 RX 和 IDLE 中断,准备下一次接收
             * 注意:rxTail 和 rxHead 必须同时清零,防止残留数据与新数据混叠 */
            txActive = 0U;
            rxActive = 0U;
            rxTail   = 0U;
            rxHead   = 0U;

            UART_enableInterrupt(uart, UART_INT_RX_DATA_REG_FULL);
            UART_enableInterrupt(uart, UART_INT_IDLE_LINE);
        }
    }
}

【状态机总结】

image.png

【主循环与中断的协作】

/**
 * @brief   主函数:初始化后等待接收,接收完毕后回传数据
 */
int main(void)
{
    Device_init();
    Device_unlockPeriphReg();
    Board_init();
    uart_init(uart);

    printf("UART IDLE-line variable-length receive demo!\r\n");
    printf("Baud:230400 8N1. Send data, I'll echo it back.\r\n");

    while (1)
    {
        /* 等待一帧接收完毕(IDLE 检测到 rxDone=1)*/
        if (rxDone)
        {
            uint16_t len = 0U;
            rxDone = 0U;

            /* 从循环缓冲区取出数据到发送缓冲区 */
            while (rxHead != rxTail)
            {
                txBuf[len++] = rxBuf[rxTail];
                rxTail = (rxTail + 1U) % UART_RXBUF_SIZE;
            }

            if (len > 0U)
            {
                /* 打印接收到的数据(ASCII,逐字符打印避免 \0 覆盖问题)*/
                printf("[RX %u]: ", len);
                for (uint16_t i = 0U; i < len; i++)
                {
                    printf("%c", txBuf[i]);
                }
                printf("\r\n");

                /* 启动回传 */
                txIdx   = 0U;
                txCount = len;
                txActive = 1U;

                /* 发送期间禁止接收(防止 TX 回传数据触发新的 IDLE)*/
                UART_disableInterrupt(uart, UART_INT_RX_DATA_REG_FULL);
                UART_disableInterrupt(uart, UART_INT_IDLE_LINE);

                UART_enableInterrupt(uart, UART_INT_TX_DATA_REG_EMPTY);
            }
        }
    }
}

【FIFO 配置】

image.png

【硬件连接】

image.png

【验证】

使用串口助手连接到开发板,打开串口助手,发送任意长度字符串均可接收无误:

image.png

【总结】

在串口使用中,不定长接收是非常常见,因此搞通不定长接收的意义非常有必要。




关键词: NS800RT7P65     UART2     中断     空闲接收    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]