纳芯微NS800RT7P65D开发板

NS800RT7P65D(S) 系列是纳芯微基于 Arm Cortex-M7 内核推出的实时微控制器

纳芯微NS800RT7P65D开发板封面
RT-Thread2026-07-09 18:34:38BSD License
11
Star

PCBA

设计文件

原理图.zip2.16MB(2次下载)

源代码

其他附件

详细介绍

纳芯微NS800RT7P65D开发实践指南

目录

作者

零、实践指南说明

RT-Thread & 纳芯微

一、NS800RT7P65D上的UART实践

张海涛

二、NS800RT7P65D上的GPIO实践

熊治坤

三、NS800RT7P65D上的ADC实践

朱玉施

四、NS800RT7P65D上的HWTimer实践

刘协泉

五、NS800RT7P65D上的WDT实践

袁胜富

六、NS800RT7P65D上的SPI实践

刘建华

七、NS800RT7P65D上的SPI实践

张工

八、NS800RT7P65D上的eCAP实践

程廷桢

九、NS800RT7P65D上的PWM实践

程廷桢

十、NS800RT7P65D上的CAN实践

htang

十一、NS800RT7P65D上的IIC实践

孙晓辉

十二、NS800RT7P65D上的RTC实践

朱文治


零、纳芯微NS800RT7P65D实践指南说明

1. 简介

NS800RT7P65D(S) 系列是纳芯微基于 Arm Cortex-M7 内核推出的实时微控制器

一、纳芯微NS800RT7P65D上的UART实践(张海涛)

1. NS800RT7P65 概述

飞书链接(优先更新):https://my.feishu.cn/wiki/DhMnwIciFiYMQuk3HtBcI1nZn5c

1.1 总览

NS800RTP65D(S)系列是纳芯微(NSSine™)基于 ARM Cortex®-M7 内核推出的一款实时微控制器。该系 列融合了数字信号处理与微控制器两大优势,为纳芯微的最新研发成果。 本产品搭载两个 ARM Cortex®-M7 内核。该内核基于 ARMv7E-M 架构,集成双精度浮点单元(FPU),符 合 IEEE 754 标准;内置 DSP 扩展指令集,支持单周期 16/32 位 MAC 及 SIMD 运算;并配备内存保护单 元(MPU),用于内存访问权限管理,提升系统安全性。

1.2 资源概述

核心架构与处理性能

  • 双核架构: 搭载两个 ARM Cortex®-M7 内核,基于 ARMv7E-M 架构。内核采用六级双发射超标量流水线,支持分支预测。

  • 浮点单元(FPU): 每个内核均配备双精度浮点单元,符合 IEEE 754 标准,支持单精度与双精度数据处理指令和数据类型。

  • DSP 指令集:支持完整的 DSP 扩展指令集,包括单周期 16/32 位乘法累加(MAC)及 8/16 位单指令 多数据流(SIMD)运算。

  • 保护单元(MPU): 内置内存保护单元,支持多区域内存访问权限控制,实现任务级代码、数据与 堆栈隔离,增强系统安全性。

  • 代码执行: 支持从片上 FLASH、TCM(紧耦合内存)或片上 SRAM 中执行浮点或定点代码,可高效处 理 DSP 算术与系统控制任务。TCM 提供与内核同频的高速低延迟访问。

数学加速单元

  • MMATH 单元: 搭载与内核 Cortex®-M7 紧耦合的三角函数运算单元,可与内核并行运算,显著增强三 角函数计算性能。

  • EMATH 单元: 集成系统级数学运算加速器,支持单精度浮点运算(FPU),可与内核并行运算,提升 通用数学函数处理速度。

存储资源

  • 片上存储: 集成高达 1MB 支持 ECC 的片上 FLASH,以及 256KB 支持 ECC 的片上 SRAM。

  • 紧耦合存储(TCM): 每个内核独立配备 128KB ITCM 和 128KB DTCM。在 ITCM 或 DTCM 中运行代 码可实现零等待访问,且所有 TCM 均支持 ECC 校验。

1.3 模块框图及系统总线框图

模块框图

系统总线框图

2. 芯片层面 Uart 资源 - 解析

UART 支持全双工、异步、NRZ 串行通信,由波特率发生器、发送器和接收器组成。发送器和接收器独立工作,尽管它们使 用相同的波特率发生器。其资源分布主要如下:

  • 1.3.1 模块框图 可以看出,该芯片总共支持4路Uart;

  • 根据1.3.2 系统总线框图 可以观察到,芯片的Uart1 / 2分布在APB 2,Uart3 / 4分布在APB4,总线最大时钟频率200Mhz;

根据参考手册,Uart模块具有以下特性,此处不再赘述:

3. 芯片层面 Uart 配置 - 说明

3.1 引脚复用配置

  • 数据手册 Page 63页 开始,查看GPIO引脚复用信息,根据自身板卡情况,决定板级引脚复用配置,下图仅列出部分引脚复用情况;

当前板卡驱动直接在drv_uart的config数组中 HardCode 为具体配置;

3.2 波特率配置

  • 根据下图芯片手册中的介绍,可以看到波特率主要由: 芯片时钟配置、SBR寄存器、OSR寄存器三方决定:

  • 如是不想计算,则可以参考数据手册建议的 波特率配置参考值

注意: 上述表格对应的 UART 时钟频率为 200M !!!

当前板卡使用的是默认配置;

4. 代码层面 - Uart 功能测试

4.1 基础功能测评

Msh 功能

可以看到shell功能运行正常,基于shell功能编写测试用例;

收发数据测试

虽然第一步shell的成功使用,基本说明串口功能的正常,但是还是编写了Uart测试代码;在shell中执行uart_test;


/* 与 USB 串口助手相连的控制台口,默认 uart0 (TX=PB15 RX=PA8) */
#ifndef UART_TEST_DEV_NAME
#define UART_TEST_DEV_NAME      RT_CONSOLE_DEVICE_NAME
#endif

#define UART_TEST_BAUD          115200
#define UART_RX_TIMEOUT_MS      5000

static rt_device_t uart_dev = RT_NULL;
static struct rt_semaphore uart_rx_sem;
static rt_thread_t uart_echo_tid = RT_NULL;
static rt_bool_t uart_inited = RT_FALSE;

static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size)
{
    RT_UNUSED(dev);
    RT_UNUSED(size);
    rt_sem_release(&uart_rx_sem);
    return RT_EOK;
}

static rt_err_t uart_ensure_ready(void)
{
    if (uart_inited)
        return RT_EOK;

    uart_dev = rt_console_get_device();
    if (uart_dev == RT_NULL)
    {
        rt_kprintf("[uart] console device unavailable\n");
        return -RT_ERROR;
    }

    rt_device_set_rx_indicate(uart_dev, uart_rx_ind);
    uart_inited = RT_TRUE;

    rt_kprintf("[uart] ready: %s, %d 8N1, TX=PB15 RX=PA8\n",
               UART_TEST_DEV_NAME, UART_TEST_BAUD);
    return RT_EOK;
}

static rt_err_t uart_do_tx(rt_size_t len)
{
    rt_size_t i, written;
    char *buf = RT_NULL;

    if (uart_ensure_ready() != RT_EOK)
        return -RT_ERROR;

    if (len == 0)
        len = 64;
    if (len > 256)
        len = 256;

    buf = rt_malloc(len + 1);
    if (buf == RT_NULL)
        return -RT_ENOMEM;

    rt_snprintf(buf, len + 1, "[UART TX test, %u bytes]\r\n", (unsigned)len);
    for (i = rt_strlen(buf); i < len; i++)
        buf[i] = (char)('A' + (i % 26));

    written = rt_device_write(uart_dev, 0, buf, len);
    rt_kprintf("[uart] TX: sent %u / %u bytes %s\n",
               (unsigned)written, (unsigned)len,
               (written == len) ? "OK" : "FAIL");

    rt_free(buf);
    return (written == len) ? RT_EOK : -RT_ERROR;
}

static rt_err_t uart_do_rx(rt_uint32_t timeout_ms)
{
    rt_size_t rx_len, total = 0;
    rt_uint8_t rx_buf[128];
    rt_tick_t deadline;

    if (timeout_ms == 0)
        timeout_ms = UART_RX_TIMEOUT_MS;

    if (uart_ensure_ready() != RT_EOK)
        return -RT_ERROR;

    rt_sem_control(&uart_rx_sem, RT_IPC_CMD_RESET, RT_NULL);
    rt_kprintf("[uart] RX: please send data from PC within %u ms...\n", timeout_ms);

    deadline = rt_tick_get() + rt_tick_from_millisecond(timeout_ms);
    while (rt_tick_get() < deadline)
    {
        rt_tick_t wait = deadline - rt_tick_get();
        if (wait > rt_tick_from_millisecond(100))
            wait = rt_tick_from_millisecond(100);

        if (rt_sem_take(&uart_rx_sem, wait) != RT_EOK)
            continue;

        do
        {
            rx_len = rt_device_read(uart_dev, 0, rx_buf, sizeof(rx_buf) - 1);
            if (rx_len > 0)
            {
                total += rx_len;
                rx_buf[rx_len] = '\0';
                rt_kprintf("[uart] RX(%u): %s\n", (unsigned)rx_len, rx_buf);
            }
        } while (rx_len > 0);
    }

    rt_kprintf("[uart] RX done, total %u bytes %s\n",
               (unsigned)total, (total > 0) ? "OK" : "FAIL");

    return (total > 0) ? RT_EOK : -RT_ERROR;
}

测试结果:

4.2 Uart Menuconfig 功能配置测评

  1. 在Menuconfig中将Uart2 ~ 4均打开,测试Uart功能;

  • 可以看到配置完成后rtconfig.h中新增下列宏定义;

  • 此时再次刷写测试,发现所有Uart Device均被register;

  • 由于当前题主仅有一个usb转串口工具,因此依次将console映射到不同Uart进行测试;测试结果均无问题;

5. Uart 代码优化

针对板级驱动中的部分 HardCode 考虑可以进行优化,增加BSP包的 鲁棒性 ;将目前 HardCode 部分映射到 Menuconfig 配置中,用户可以 直接通过 Menuconfig 配置 Uart 的停止位和波特率等;

PR: https://github.com/RT-Thread/rt-thread/pull/11423

5.1 串口基础配置项 - 优化

drv_uart.c

/*
 * Copyright (c) 2006-2026, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-10-30     SummerGift   first version
 * 2020-03-16     SummerGift   add device close feature
 * 2020-03-20     SummerGift   fix bug caused by ORE
 * 2026-05-10     Codex        Reworked driver around BSP UART config tables
 */

#include "board.h"
#include "drv_uart.h"
#include "drv_config.h"

#ifdef RT_USING_SERIAL

#define DRV_DEBUG
#define LOG_TAG             "drv.uart"
#include <drv_log.h>

#if !defined(BSP_USING_UART1) && !defined(BSP_USING_UART2) && !defined(BSP_USING_UART3) && \
    !defined(BSP_USING_UART4)
#error "Please define at least one BSP_USING_UARTx"
#endif

#ifndef BSP_NS800_UART_TX_TIMEOUT
#define BSP_NS800_UART_TX_TIMEOUT 6000
#endif

#ifdef RT_USING_SERIAL_V2
#define NS800_UART_BUF_CONFIG(_rxbuf, _txbuf)              \
    .rx_bufsz = (_rxbuf),                                  \
    .tx_bufsz = (_txbuf),
#define NS800_UART_DEFAULT_TX_TIMEOUT 0U
#else
#ifndef RT_SERIAL_RB_BUFSZ
#define RT_SERIAL_RB_BUFSZ 64
#endif

#define NS800_UART_BUF_CONFIG(_rxbuf, _txbuf)              \
    .rx_bufsz = RT_SERIAL_RB_BUFSZ,                        \
    .tx_bufsz = 0U,
#define NS800_UART_DEFAULT_TX_TIMEOUT BSP_NS800_UART_TX_TIMEOUT
#endif

/* Default UART configuration from Kconfig */
#ifndef BSP_UART_DEFAULT_BAUDRATE
#define BSP_UART_DEFAULT_BAUDRATE 115200
#endif

/* Data bits mapping */
#if defined(BSP_UART_DATABITS_5)
#define NS800_UART_DEFAULT_DATA_BITS DATA_BITS_5
#elif defined(BSP_UART_DATABITS_6)
#define NS800_UART_DEFAULT_DATA_BITS DATA_BITS_6
#elif defined(BSP_UART_DATABITS_7)
#define NS800_UART_DEFAULT_DATA_BITS DATA_BITS_7
#elif defined(BSP_UART_DATABITS_9)
#define NS800_UART_DEFAULT_DATA_BITS DATA_BITS_9
#else
#define NS800_UART_DEFAULT_DATA_BITS DATA_BITS_8
#endif

/* Stop bits mapping */
#ifdef BSP_UART_STOPBITS_2
#define NS800_UART_DEFAULT_STOP_BITS STOP_BITS_2
#else
#define NS800_UART_DEFAULT_STOP_BITS STOP_BITS_1
#endif

enum
{
#ifdef BSP_USING_UART1
    UART1_INDEX,
#endif
#ifdef BSP_USING_UART2
    UART2_INDEX,
#endif
#ifdef BSP_USING_UART3
    UART3_INDEX,
#endif
#ifdef BSP_USING_UART4
    UART4_INDEX,
#endif
};

#ifdef BSP_USING_UART1
void UART1_IRQHandler(void);
#endif
#ifdef BSP_USING_UART2
void UART2_IRQHandler(void);
#endif
#ifdef BSP_USING_UART3
void UART3_IRQHandler(void);
#endif
#ifdef BSP_USING_UART4
void UART4_IRQHandler(void);
#endif

static struct ns800_uart_config uart_config[] =
{
#ifdef BSP_USING_UART1
    {
        .name = "uart1",
        .Instance = UART1,
        .rx_irq_type = UART1_RX_IRQn,
        .tx_irq_type = UART1_TX_IRQn,
        .irq_handler = UART1_IRQHandler,
        .rx_port = GPIOA,
        .rx_pin = GPIO_PIN_13,
        .rx_mux = ALT6_FUNCTION,
        .rx_pad = GPIO_PIN_TYPE_PULLUP,
        .rx_direction = GPIO_DIR_MODE_IN,
        .rx_drive_max = RT_FALSE,
        .tx_port = GPIOA,
        .tx_pin = GPIO_PIN_12,
        .tx_mux = ALT6_FUNCTION,
        .tx_pad = GPIO_PIN_TYPE_STD,
        .tx_direction = GPIO_DIR_MODE_OUT,
        .tx_drive_max = RT_FALSE,
        .tx_block_timeout = NS800_UART_DEFAULT_TX_TIMEOUT,
        NS800_UART_BUF_CONFIG(BSP_UART1_RX_BUFSIZE, BSP_UART1_TX_BUFSIZE)
    },
#endif
#ifdef BSP_USING_UART2
    {
        .name = "uart2",
        .Instance = UART2,
        .rx_irq_type = UART2_RX_IRQn,
        .tx_irq_type = UART2_TX_IRQn,
        .irq_handler = UART2_IRQHandler,
        .rx_port = GPIOB,
        .rx_pin = GPIO_PIN_7,
        .rx_mux = ALT1_FUNCTION,
        .rx_pad = GPIO_PIN_TYPE_PULLUP,
        .rx_direction = GPIO_DIR_MODE_IN,
        .rx_drive_max = RT_FALSE,
        .tx_port = GPIOB,
        .tx_pin = GPIO_PIN_6,
        .tx_mux = ALT1_FUNCTION,
        .tx_pad = GPIO_PIN_TYPE_STD,
        .tx_direction = GPIO_DIR_MODE_OUT,
        .tx_drive_max = RT_TRUE,
        .tx_block_timeout = NS800_UART_DEFAULT_TX_TIMEOUT,
        NS800_UART_BUF_CONFIG(BSP_UART2_RX_BUFSIZE, BSP_UART2_TX_BUFSIZE)
    },
#endif
#ifdef BSP_USING_UART3
    {
        .name = "uart3",
        .Instance = UART3,
        .rx_irq_type = UART3_RX_IRQn,
        .tx_irq_type = UART3_TX_IRQn,
        .irq_handler = UART3_IRQHandler,
        .rx_port = GPIOB,
        .rx_pin = GPIO_PIN_25,
        .rx_mux = ALT11_FUNCTION,
        .rx_pad = GPIO_PIN_TYPE_PULLUP,
        .rx_direction = GPIO_DIR_MODE_IN,
        .rx_drive_max = RT_FALSE,
        .tx_port = GPIOB,
        .tx_pin = GPIO_PIN_24,
        .tx_mux = ALT11_FUNCTION,
        .tx_pad = GPIO_PIN_TYPE_STD,
        .tx_direction = GPIO_DIR_MODE_OUT,
        .tx_drive_max = RT_TRUE,
        .tx_block_timeout = NS800_UART_DEFAULT_TX_TIMEOUT,
        NS800_UART_BUF_CONFIG(BSP_UART3_RX_BUFSIZE, BSP_UART3_TX_BUFSIZE)
    },
#endif
#ifdef BSP_USING_UART4
    {
        .name = "uart4",
        .Instance = UART4,
        .rx_irq_type = UART4_RX_IRQn,
        .tx_irq_type = UART4_TX_IRQn,
        .irq_handler = UART4_IRQHandler,
        .rx_port = GPIOB,
        .rx_pin = GPIO_PIN_13,
        .rx_mux = ALT7_FUNCTION,
        .rx_pad = GPIO_PIN_TYPE_PULLUP,
        .rx_direction = GPIO_DIR_MODE_IN,
        .rx_drive_max = RT_FALSE,
        .tx_port = GPIOB,
        .tx_pin = GPIO_PIN_12,
        .tx_mux = ALT7_FUNCTION,
        .tx_pad = GPIO_PIN_TYPE_STD,
        .tx_direction = GPIO_DIR_MODE_OUT,
        .tx_drive_max = RT_TRUE,
        .tx_block_timeout = NS800_UART_DEFAULT_TX_TIMEOUT,
        NS800_UART_BUF_CONFIG(BSP_UART4_RX_BUFSIZE, BSP_UART4_TX_BUFSIZE)
    },
#endif
};

static struct ns800_uart uart_obj[sizeof(uart_config) / sizeof(uart_config[0])] = {0};

static void ns800_uart_gpio_init(const struct ns800_uart_config *config)
{
    RT_ASSERT(config != RT_NULL);

    GPIO_setPinConfig(config->rx_port, config->rx_pin, config->rx_mux);
    GPIO_setAnalogMode(config->rx_port, config->rx_pin, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(config->rx_port, config->rx_pin, config->rx_pad);
    GPIO_setQualificationMode(config->rx_port, config->rx_pin, GPIO_QUAL_SYNC);
    GPIO_setDirectionMode(config->rx_port, config->rx_pin, config->rx_direction);
    if (config->rx_drive_max)
    {
        GPIO_setDriveLevel(config->rx_port, config->rx_pin, GPIO_DRV_MAX);
    }

    GPIO_setPinConfig(config->tx_port, config->tx_pin, config->tx_mux);
    GPIO_setAnalogMode(config->tx_port, config->tx_pin, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(config->tx_port, config->tx_pin, config->tx_pad);
    GPIO_setQualificationMode(config->tx_port, config->tx_pin, GPIO_QUAL_SYNC);
    GPIO_setDirectionMode(config->tx_port, config->tx_pin, config->tx_direction);
    if (config->tx_drive_max)
    {
        GPIO_setDriveLevel(config->tx_port, config->tx_pin, GPIO_DRV_MAX);
    }
}

static UART_BitCountPerChar ns800_uart_data_bits(rt_uint32_t data_bits)
{
    switch (data_bits)
    {
    case DATA_BITS_7:
        return UART_7_BITS_PER_CHAR;
    case DATA_BITS_9:
        return UART_9_BITS_PER_CHAR;
    case DATA_BITS_8:
    default:
        return UART_8_BITS_PER_CHAR;
    }
}

static void ns800_uart_apply_runtime_cfg(struct ns800_uart *uart, struct serial_configure *cfg)
{
    RT_ASSERT(uart != RT_NULL);
    RT_ASSERT(cfg != RT_NULL);

    uart->handle.Instance = uart->config->Instance;
    uart->handle.baud_rate = cfg->baud_rate;
    uart->handle.data_bits = cfg->data_bits;
    uart->handle.stop_bits = cfg->stop_bits;
    uart->handle.parity = cfg->parity;
}

static void ns800_uart_clear_errors(UART_TypeDef *instance)
{
    UART_clearErrorFlags(instance,
                         UART_STAT_OR_M |
                         UART_STAT_NF_M |
                         UART_STAT_FE_M |
                         UART_STAT_PF_M);
}

static rt_err_t ns800_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
{
    struct ns800_uart *uart;
    UART_BitCountPerChar bit_count;

    RT_ASSERT(serial != RT_NULL);
    RT_ASSERT(cfg != RT_NULL);

    uart = rt_container_of(serial, struct ns800_uart, serial);

    ns800_uart_apply_runtime_cfg(uart, cfg);
    ns800_uart_gpio_init(uart->config);

    bit_count = ns800_uart_data_bits(cfg->data_bits);

    UART_resetModule(uart->handle.Instance);
    UART_setBaud(uart->handle.Instance, cfg->baud_rate);

    if (cfg->parity == PARITY_ODD)
    {
        UART_setBitCountPerChar(uart->handle.Instance, bit_count, true);
        UART_setParityMode(uart->handle.Instance, UART_PAR_ODD);
    }
    else if (cfg->parity == PARITY_EVEN)
    {
        UART_setBitCountPerChar(uart->handle.Instance, bit_count, true);
        UART_setParityMode(uart->handle.Instance, UART_PAR_EVEN);
    }
    else
    {
        UART_setBitCountPerChar(uart->handle.Instance, bit_count, false);
    }

    switch (cfg->stop_bits)
    {
    case STOP_BITS_2:
        UART_setStopBitCount(uart->handle.Instance, UART_TWO_STOP_BIT);
        break;
    case STOP_BITS_1:
    default:
        UART_setStopBitCount(uart->handle.Instance, UART_ONE_STOP_BIT);
        break;
    }

    ns800_uart_clear_errors(uart->handle.Instance);

#ifdef RT_USING_SERIAL_V2
    UART_enableTxFifo(uart->handle.Instance);
    UART_resetTxFifo(uart->handle.Instance);
    UART_setTxFifoWatermark(uart->handle.Instance, UART_FIFO_TX6);

    UART_enableRxFifo(uart->handle.Instance);
    UART_resetRxFifo(uart->handle.Instance);
    UART_setRxFifoWatermark(uart->handle.Instance, UART_FIFO_RX1);
    UART_setRxIdleCharacter(uart->handle.Instance, UART_IDLE_CHARACTER_CNT0);
#else
    /*
     * RT-Thread serial v1 consumes RX data byte-by-byte from getc().
     * Keep the hardware in the simplest non-FIFO mode to avoid RDRF
     * reasserting on idle-partial FIFO conditions.
     */
    UART_disableTxFifo(uart->handle.Instance);
    UART_disableRxFifo(uart->handle.Instance);
    UART_setRxIdleCharacter(uart->handle.Instance, UART_IDLE_CHARACTER_CNT0);
#endif

    UART_enableTxModule(uart->handle.Instance);
    UART_enableRxModule(uart->handle.Instance);

    uart->tx_block_timeout = uart->config->tx_block_timeout;

    return RT_EOK;
}

static rt_err_t ns800_control(struct rt_serial_device *serial, int cmd, void *arg)
{
    struct ns800_uart *uart;

    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct ns800_uart, serial);

    switch (cmd)
    {
    case RT_DEVICE_CTRL_CLR_INT:
    {
        rt_uint32_t direction = (rt_uint32_t)arg;

        Interrupt_disable(uart->config->rx_irq_type);
        if (direction == RT_DEVICE_FLAG_INT_RX)
        {
            UART_disableInterrupt(uart->handle.Instance, UART_INT_RX_DATA_REG_FULL);
        }
        else if (direction == RT_DEVICE_FLAG_INT_TX)
        {
            UART_disableInterrupt(uart->handle.Instance, UART_INT_TX_COMPLETE);
            Interrupt_disable(uart->config->tx_irq_type);
        }
        break;
    }

    case RT_DEVICE_CTRL_SET_INT:
    {
        rt_uint32_t direction = (rt_uint32_t)arg;

        if (direction == RT_DEVICE_FLAG_INT_RX)
        {
            UART_enableInterrupt(uart->handle.Instance, UART_INT_RX_DATA_REG_FULL);
            Interrupt_register(uart->config->rx_irq_type, uart->config->irq_handler);
            Interrupt_enable(uart->config->rx_irq_type);
        }
        else if (direction == RT_DEVICE_FLAG_INT_TX)
        {
            UART_enableInterrupt(uart->handle.Instance, UART_INT_TX_COMPLETE);
            Interrupt_register(uart->config->tx_irq_type, uart->config->irq_handler);
            Interrupt_enable(uart->config->tx_irq_type);
        }
        break;
    }

    case RT_DEVICE_CTRL_CLOSE:
        UART_disableTxModule(uart->handle.Instance);
        UART_disableRxModule(uart->handle.Instance);
        break;

    case UART_CTRL_SET_BLOCK_TIMEOUT:
    {
        rt_uint32_t block_timeout = (rt_uint32_t)arg;

        if (block_timeout == 0U)
        {
            return -RT_ERROR;
        }

        uart->tx_block_timeout = block_timeout;
        break;
    }

    default:
        break;
    }

    return RT_EOK;
}

static int ns800_putc(struct rt_serial_device *serial, char c)
{
    struct ns800_uart *uart;
    rt_uint32_t block_timeout;

    RT_ASSERT(serial != RT_NULL);

    uart = rt_container_of(serial, struct ns800_uart, serial);
    block_timeout = uart->tx_block_timeout;

    while (!UART_isSpaceAvailable(uart->handle.Instance))
    {
        if (block_timeout-- == 0U)
        {
            return -1;
        }
    }

    UART_writeChar(uart->handle.Instance, (rt_uint8_t)c);

    while ((uart->handle.Instance->STAT.BIT.TC == false) && (--block_timeout != 0U))
    {
    }

    return (block_timeout != 0U) ? 1 : -1;
}

static int ns800_getc(struct rt_serial_device *serial)
{
    struct ns800_uart *uart;

    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct ns800_uart, serial);

    if (UART_isDataAvailable(uart->handle.Instance))
    {
        return (int)UART_readChar(uart->handle.Instance);
    }

    return -1;
}

static void uart_isr(struct rt_serial_device *serial)
{
    struct ns800_uart *uart;

    RT_ASSERT(serial != RT_NULL);
    uart = rt_container_of(serial, struct ns800_uart, serial);

    if (UART_getStatusFlag(uart->handle.Instance, UART_RX_OVERRUN) ||
        UART_getStatusFlag(uart->handle.Instance, UART_NOISE_DETECT) ||
        UART_getStatusFlag(uart->handle.Instance, UART_FRAME_ERR) ||
        UART_getStatusFlag(uart->handle.Instance, UART_PARITY_ERR))
    {
        ns800_uart_clear_errors(uart->handle.Instance);
    }

    if (UART_getStatusFlag(uart->handle.Instance, UART_RX_DATA_REG_FULL))
    {
        rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_IND);
    }
    else if (UART_getStatusFlag(uart->handle.Instance, UART_TX_COMPLETE))
    {
        UART_disableInterrupt(uart->handle.Instance, UART_INT_TX_COMPLETE);
        rt_hw_serial_isr(serial, RT_SERIAL_EVENT_TX_DONE);
    }
}

#ifdef BSP_USING_UART1
void UART1_IRQHandler(void)
{
    rt_interrupt_enter();
    uart_isr(&uart_obj[UART1_INDEX].serial);
    rt_interrupt_leave();
}
#endif

#ifdef BSP_USING_UART2
void UART2_IRQHandler(void)
{
    rt_interrupt_enter();
    uart_isr(&uart_obj[UART2_INDEX].serial);
    rt_interrupt_leave();
}
#endif

#ifdef BSP_USING_UART3
void UART3_IRQHandler(void)
{
    rt_interrupt_enter();
    uart_isr(&uart_obj[UART3_INDEX].serial);
    rt_interrupt_leave();
}
#endif

#ifdef BSP_USING_UART4
void UART4_IRQHandler(void)
{
    rt_interrupt_enter();
    uart_isr(&uart_obj[UART4_INDEX].serial);
    rt_interrupt_leave();
}
#endif

static void ns800_uart_fill_default_config(struct serial_configure *config,
                                           const struct ns800_uart_config *hw)
{
    RT_ASSERT(config != RT_NULL);
    RT_ASSERT(hw != RT_NULL);

    *config = (struct serial_configure)RT_SERIAL_CONFIG_DEFAULT;

    /* Override with Kconfig settings */
    config->baud_rate = BSP_UART_DEFAULT_BAUDRATE;
    config->data_bits = NS800_UART_DEFAULT_DATA_BITS;
    config->stop_bits = NS800_UART_DEFAULT_STOP_BITS;

#ifdef RT_USING_SERIAL_V2
    config->rx_bufsz = hw->rx_bufsz;
    config->tx_bufsz = hw->tx_bufsz;
#else
    config->bufsz = hw->rx_bufsz;
#endif
}

static const struct rt_uart_ops ns800_uart_ops =
{
    .configure = ns800_configure,
    .control = ns800_control,
    .putc = ns800_putc,
    .getc = ns800_getc,
};

int rt_hw_uart_init(void)
{
    rt_err_t result = RT_EOK;
    rt_size_t i;

    for (i = 0; i < sizeof(uart_obj) / sizeof(uart_obj[0]); i++)
    {
        uart_obj[i].config = &uart_config[i];
        uart_obj[i].serial.ops = &ns800_uart_ops;
        ns800_uart_fill_default_config(&uart_obj[i].serial.config, uart_obj[i].config);
        uart_obj[i].tx_block_timeout = uart_obj[i].config->tx_block_timeout;

        result = rt_hw_serial_register(&uart_obj[i].serial,
                                       uart_obj[i].config->name,
                                       RT_DEVICE_FLAG_RDWR |
                                       RT_DEVICE_FLAG_INT_RX |
                                       RT_DEVICE_FLAG_INT_TX,
                                       RT_NULL);
        RT_ASSERT(result == RT_EOK);
    }

    return result;
}

#endif /* RT_USING_SERIAL */


Kconfig

menu "On-chip Peripheral Drivers"

    menuconfig BSP_USING_GPIO
        bool "Enable GPIO"
        select RT_USING_PIN
        default y
        if BSP_USING_GPIO
            config BSP_GPIO_PIN_IRQ
                bool "Enable GPIO pin IRQ hooks"
                default n
        endif

    menuconfig BSP_USING_UART
        bool "Enable UART"
        default y
        select RT_USING_SERIAL
        if BSP_USING_UART
            config BSP_NS800_UART_TX_TIMEOUT
                int "UART TX timeout"
                default 6000
                depends on !RT_USING_SERIAL_V2

            config BSP_UART_DEFAULT_BAUDRATE
                int "UART default baudrate"
                default 115200
                help
                    Default baudrate for all UART ports.

            choice BSP_UART_DEFAULT_DATABITS
                prompt "UART default data bits"
                default BSP_UART_DATABITS_8
                config BSP_UART_DATABITS_5
                    bool "5 bits"
                config BSP_UART_DATABITS_6
                    bool "6 bits"
                config BSP_UART_DATABITS_7
                    bool "7 bits"
                config BSP_UART_DATABITS_8
                    bool "8 bits"
                config BSP_UART_DATABITS_9
                    bool "9 bits"
            endchoice

            choice BSP_UART_DEFAULT_STOPBITS
                prompt "UART default stop bits"
                default BSP_UART_STOPBITS_1
                config BSP_UART_STOPBITS_1
                    bool "1 bit"
                config BSP_UART_STOPBITS_2
                    bool "2 bits"
            endchoice

            menuconfig BSP_USING_UART1
                bool "Enable UART1"
                default y
                if BSP_USING_UART1
                    config BSP_UART1_RX_BUFSIZE
                        int "UART1 RX buffer size"
                        range 64 65535
                        depends on RT_USING_SERIAL_V2
                        default 256

                    config BSP_UART1_TX_BUFSIZE
                        int "UART1 TX buffer size"
                        range 0 65535
                        depends on RT_USING_SERIAL_V2
                        default 0
                endif

            menuconfig BSP_USING_UART2
                bool "Enable UART2"
                default n
                if BSP_USING_UART2
                    config BSP_UART2_RX_BUFSIZE
                        int "UART2 RX buffer size"
                        range 64 65535
                        depends on RT_USING_SERIAL_V2
                        default 256

                    config BSP_UART2_TX_BUFSIZE
                        int "UART2 TX buffer size"
                        range 0 65535
                        depends on RT_USING_SERIAL_V2
                        default 0
                endif

            menuconfig BSP_USING_UART3
                bool "Enable UART3"
                default n
                if BSP_USING_UART3
                    config BSP_UART3_RX_BUFSIZE
                        int "UART3 RX buffer size"
                        range 64 65535
                        depends on RT_USING_SERIAL_V2
                        default 256

                    config BSP_UART3_TX_BUFSIZE
                        int "UART3 TX buffer size"
                        range 0 65535
                        depends on RT_USING_SERIAL_V2
                        default 0
                endif

            menuconfig BSP_USING_UART4
                bool "Enable UART4"
                default n
                if BSP_USING_UART4
                    config BSP_UART4_RX_BUFSIZE
                        int "UART4 RX buffer size"
                        range 64 65535
                        depends on RT_USING_SERIAL_V2
                        default 256

                    config BSP_UART4_TX_BUFSIZE
                        int "UART4 TX buffer size"
                        range 0 65535
                        depends on RT_USING_SERIAL_V2
                        default 0
                endif
        endif

    menuconfig BSP_USING_ECAP
        bool "Enable ECAP"
        default n

    menuconfig BSP_USING_CAN
        bool "Enable CAN"
        select RT_USING_CAN
        default n
        if BSP_USING_CAN
            config BSP_USING_CANFD1
                bool "Enable CANFD1"
                select RT_CAN_USING_CANFD
                default n

        endif

endmenu

5.2 串口低波特率时 - 丢失串口数据

  • 新增 ns800_wait_tx_space()

    • rt_tick_from_millisecond() + rt_tick_get() 做真实时间超时等待 TX 可写

  • 修改 ns800_putc()

    • 先调用 ns800_wait_tx_space(),可写后直接 UART_writeChar()

    • 去掉“每个字节都等待 TC 置位”的逻辑(这在低波特率下最容易导致截断)

解决方案:

  • 之前是“循环次数”超时,CPU快/波特率低时很容易提前超时,且每字节强行等待 TC 会把发送路径变得非常脆弱,低波特率下更明显。

  • 现在按系统 tick 等待 TX 空位,低波特率也能稳定逐字节输出。

6. 后续可优化点

  1. 将波特率和停止位等 基础配置 信息,分别映射到 每一路Uart 上,而不是所有Uart共用;

  2. 后续可以考虑 是否可以引脚复用引脚配置 也映射到 Menuconfig 中;

二、纳芯微NS800RT7P65D上的GPIO实践(熊治坤)

作者:熊治坤

原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的GPIO实践

源文章:https://club.rt-thread.org/ask/article/d7a4e5d88394553f.html

硬件

我的硬件版本是1.3,如下图所示:

不同的版本,硬件会有所不同,需要特别注意。

源代码下载

由于我之前已经下载过,需要进行更新即可。

git pull命令就行。

会提示是否最新。

MDK工程生成

ENV进行GPIO配置

这里为了验证中断功能。

ENV串口使能

ENV配置保存,选择Y

ENV生成MDK工程

配置完成后,会自动生成工程文件

这里成功后会有如上提示。

这里可以从文件时间可以查看是否是最新生成的

代码编写与调试

打开工程后进行编译

成功后会有如下提示

下面需要对如下三个文件进行修改

GPIO部分代码较长,这里不展示。后面根据GPIO验证,对main文件进行说明。

GPIO验证

这里根据板子上的LED1、LED2、KEY 1进行测试

对应的GPIO如下原理图所示

实现功能如下:

LED1,1秒钟闪烁1次。

LED2,根据KEY1的按下的状态,进行翻转。

KEY1通过中断,进行串口输出和LED2转态翻转

主代码实现

/*
 * Copyright (c) 2006-2026, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author           Notes
 * 2026-05-06     Jiawei.Deng      first version
 */

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

/* defined the LED1 pin: GPIO_68 = PC4 */
#define LED1_PIN    PIN_NUM(GPIO_68)
#define LED2_PIN    PIN_NUM(GPIO_69)
#define KEY1_PIN    PIN_NUM(GPIO_41)

void keydown(void *args)
{
    rt_kprintf("KEY1 Press Down!\n");
    rt_pin_write(LED2_PIN, !rt_pin_read(LED2_PIN));
}

int main(void)
{
        rt_base_t level;
        level = rt_hw_interrupt_disable();
    rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);
        rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);

      /* 按键1引脚为输入模式 */
    rt_pin_mode(KEY1_PIN, PIN_MODE_INPUT_PULLUP);
    /* 绑定中断,下降沿模式,回调函数名为keydown */
    if(rt_pin_attach_irq(KEY1_PIN, PIN_IRQ_MODE_FALLING, keydown, RT_NULL)    ==    RT_EOK)
        {
            rt_kprintf("bangding chenggong!\n");
        }
    /* 使能中断 */
    if(rt_pin_irq_enable(KEY1_PIN, PIN_IRQ_ENABLE)==    RT_EOK)
        {
            rt_kprintf("shineng chenggong!\n");
        }

    rt_hw_interrupt_enable(level);
    while (1)
    {
/*        rt_kprintf("\r\n led1_thread_entry running! \r\n"); */
        rt_pin_write(LED1_PIN, PIN_HIGH);
        rt_thread_mdelay(1000);
        rt_pin_write(LED1_PIN, PIN_LOW);
        rt_thread_mdelay(1000);

    }
}


串口输出情况,说明中断有效

LED1、2运行如下

总结

基于板上的GPIO进行了读写测试,完成了GPIO的验证。对这个芯片有了进一步的了解和认识。

三、纳芯微NS800RT7P65D上的ADC实践(朱玉施)

作者:朱玉施

原文标题:【纳芯微NS800RT7P65D】基于 RT-Thread 的 ADC 外设及其拓展的移植与应用

源文章:https://club.rt-thread.org/ask/article/233cd28f73853683.html

1. 实践目标

在本工程/博客中,解决了以下几个问题:

  1. 基于NS800的SDK,实现RT-Thread的ADC外设移植与测试

  2. 实现了ADC拓展外设:irq中断,PPB后处理模块的移植与测试

  3. 梳理实现思路,即“如何从基础SDK到RT-Thread ADC框架的移植”

    2. 背景与目标

    本次实践的目标是在 RT-Thread 主线 BSP bsp/novosns/ns800 中补充 NS800RT7P65D 的 ADC 驱动支持,并在本地 demo 工程中完成基础功能验证。最终 PR 侧代码以主线仓库中的如下文件为准:

    • bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_adc.c

    • bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_adc.h

    • bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig

    3. 资料收集与代码参考

    3.1 RT-Thread ADC 设备模型

    RT-Thread ADC 设备模型的核心接口是 rt_adc_ops

    static const struct rt_adc_ops ns800_adc_ops =
    {
        ns800_adc_enabled,
        ns800_adc_convert,
        ns800_adc_get_resolution,
        ns800_adc_get_vref,
    };


    这四个函数分别对应:

    • enabled:使能或关闭某个 ADC 通道。

    • convert:执行一次通道转换并返回结果。

    • get_resolution:返回 ADC 分辨率。

    • get_vref:返回参考电压。

    来自官方ADC设备接口

    驱动通过 rt_hw_adc_register() 注册为 RT-Thread ADC 设备:

    result = rt_hw_adc_register(&adc_obj.device,
                                adc_obj.config->name,
                                &ns800_adc_ops,
                                &adc_obj);


    注册后,应用层可通过设备名 adc0 找到该设备,并使用 RT-Thread 标准 ADC API 读取通道。

    3.2 NS800 HAL DriverLib 资料

    ADC 底层操作来自 bsp/novosns/ns800/libraries 下的 HAL/DriverLib。驱动主要使用这些底层能力:

    ADC_setVREF():设置 ADC 参考电压。

    ADC_setPrescaler():设置 ADC 时钟分频。

    ADC_setInterruptPulsePosMode():设置 EOC 中断脉冲位置。

    ADC_enableConverter():使能 ADC 转换器。

    ADC_setupSOC():配置 SOC 采样序列。

    ADC_forceMultipleSOC():软件触发指定 SOC。

    ADC_readResult():读取普通转换结果。

    ADC_setupPPB():绑定 PPB 与 SOC。

    ADC_configureRepeater():配置 repeater oversampling。

    ADC_forceRepeaterTrigger():触发 repeater 采样。

    ADC_readPPBSum()ADC_readPPBMin()ADC_readPPBMax():读取 PPB 统计结果。

    Interrupt_register()Interrupt_enable()Interrupt_disable():注册和控制 ADC 中断。

4. 驱动代码设计思路

4.1 文件边界

PR 中新增两个驱动文件:

  • drv_adc.h:公开 RT-Thread 侧需要使用的 ADC 控制命令、PPB/IRQ 常量、IRQ 回调结构,以及 rt_hw_adc_init()

  • drv_adc.c:保存所有 NS800 ADC 内部状态、硬件配置、SOC 配置、PPB oversampling、中断绑定和 RT-Thread ops。

头文件保持很薄:

#define NS800_ADC_CHANNEL_MAX       32U
#define NS800_ADC_SOC_MAX           32U
#define NS800_ADC_INT_MAX           4U
#define NS800_ADC_PPB_MAX           4U

#define NS800_ADC_CMD_DISABLE_EXT   (RT_DEVICE_CTRL_BASE(ADC) + 0x10)
#define NS800_ADC_CMD_ENABLE_PPB    (RT_DEVICE_CTRL_BASE(ADC) + 0x11)
#define NS800_ADC_CMD_ENABLE_IRQ    (RT_DEVICE_CTRL_BASE(ADC) + 0x12)
#define NS800_ADC_CMD_SET_CALLBACK  (RT_DEVICE_CTRL_BASE(ADC) + 0x13)

typedef void (*ns800_adc_irq_callback_t)(void *user_data);

struct ns800_adc_callback
{
    ns800_adc_irq_callback_t callback;
    void *user_data;
};

int rt_hw_adc_init(void);


这样不需要知道内部配置结构与具体实现也能快速调用。

4.2 静态配置表

ADC 驱动使用 struct ns800_adc_config 描述硬件实例:

struct ns800_adc_config
{
    const char *name;
    ADC_TypeDef *instance;
    ADCRESULT_TypeDef *result;
    IRQn_Type conv_irq;
    ADC_ClkPrescale clock_prescaler;
    rt_uint8_t resolution_bits;
    ADC_PulsePosMode eoc_pulse_pos;
    ADC_Trigger trigger;
    rt_bool_t continuous;
};


当前 PR 只打开一个 RT-Thread ADC 设备 adc0,映射到 NS800 的 ADCA

static const struct ns800_adc_config adc_config[] =
{
#ifdef BSP_USING_ADC
    {
        .name = "adc0",
        .instance = ADCA,
        .result = ADCARESULT,
        .conv_irq = ADCA_CONV_IRQn,
        .clock_prescaler = ADC_CLK_DIV_4,
        .resolution_bits = NS800_ADC_RESOLUTION_BITS,
        .eoc_pulse_pos = ADC_PULSE_END_OF_CONV,
        .trigger = ADC_TRIGGER_SW_ONLY,
        .continuous = RT_FALSE,
    },
#endif
};


adc0 内部状态由 struct ns800_adc 保存:

struct ns800_adc
{
    struct rt_adc_device device;
    const struct ns800_adc_config *config;
    const struct ns800_adc_pin_config *pins;
    rt_uint32_t pin_count;
    rt_uint32_t enabled_mask;
    enum ns800_adc_mode mode;
    rt_uint32_t active_channel;
};


其中:

  • device 是 RT-Thread ADC 设备对象。

  • config 指向静态硬件配置。

  • pinspin_count 描述 ADC 输入 GPIO。

  • enabled_mask 记录哪些 ADC 通道已使能。

  • mode 记录当前读取模式:普通、PPB、IRQ。

  • active_channel 记录当前活跃通道,IRQ 模式依赖该字段绑定 EOC SOC。

4.3 GPIO 与 ADC 输入声明

ADC 输入引脚采用静态数组声明:

static const struct ns800_adc_pin_config adc_pins[] =
{
    {GPIOH, GPIO_PIN_0, ALT0_FUNCTION},
    {GPIOH, GPIO_PIN_1, ALT0_FUNCTION},
    {GPIOH, GPIO_PIN_2, ALT0_FUNCTION},
    {GPIOH, GPIO_PIN_3, ALT0_FUNCTION},
    {GPIOH, GPIO_PIN_4, ALT0_FUNCTION},
    {GPIOH, GPIO_PIN_5, ALT0_FUNCTION},
    {GPIOH, GPIO_PIN_6, ALT0_FUNCTION},
    {GPIOH, GPIO_PIN_7, ALT0_FUNCTION},
};


初始化时统一配置为模拟输入:

GPIO_setPinConfig(adc->pins[index].port,
                  adc->pins[index].pin,
                  adc->pins[index].mux);
GPIO_setAnalogMode(adc->pins[index].port,
                   adc->pins[index].pin,
                   GPIO_ANALOG_ENABLED);
GPIO_setPadConfig(adc->pins[index].port,
                  adc->pins[index].pin,
                  GPIO_PIN_TYPE_STD);
GPIO_setQualificationMode(adc->pins[index].port,
                          adc->pins[index].pin,
                          GPIO_QUAL_ASYNC);
GPIO_setDirectionMode(adc->pins[index].port,
                      adc->pins[index].pin,
                      GPIO_DIR_MODE_IN);


这里需要注意:GPIO 声明必须显式存在,通道号描述 ADC 内部 channel,GPIO 配置描述芯片封装引脚复用,两者不是同一层概念。

4.4 硬件初始化

硬件初始化集中在 ns800_adc_hw_init()

ADC_setVREF(adc->config->instance, ADC_REFERENCE_INTERNAL, ADC_REFERENCE_3_3V);
ADC_setPrescaler(adc->config->instance, adc->config->clock_prescaler);
ADC_setInterruptPulsePosMode(adc->config->instance, adc->config->eoc_pulse_pos);
ADC_enableConverter(adc->config->instance);

ADC_disableBurstMode(adc->config->instance);
ADC_setSOCPriority(adc->config->instance, ADC_PRI_ALL_ROUND_ROBIN);
ADC_disableContinuousMode(adc->config->instance, ADC_INT_NUMBER1);
ADC_disableInterrupt(adc->config->instance, ADC_INT_NUMBER1);
ADC_clearInterruptStatus(adc->config->instance, ADC_INT_NUMBER1);
ADC_clearInterruptOverflowStatus(adc->config->instance, ADC_INT_NUMBER1);


当前默认配置是:

  • 内部 3.3 V 参考电压。

  • ADC 时钟 4 分频。

  • 12 bit 分辨率。

  • EOC 位于转换结束。

  • 默认软件触发。

  • 默认非连续转换。

adc_inited 用于避免重复初始化。

4.5 SOC 配置与普通读取

普通读取的核心流程是:

  1. 检查通道号是否合法。

  2. 将 channel 映射到同编号 SOC。

  3. 配置 SOC 为软件触发。

  4. ADC_INT_NUMBER1 的中断源设置为该 SOC。

  5. 清 pending。

  6. ADC_forceMultipleSOC() 触发转换。

  7. 轮询等待 EOC。

  8. 读取结果寄存器。

  9. 清除 pending 和 overflow。

实现对应:

static rt_err_t ns800_adc_read_normal(struct ns800_adc *adc,
                                      rt_uint32_t channel,
                                      rt_uint32_t *value)
{
    if (ns800_adc_config_sw_soc(adc, channel) != RT_EOK)
    {
        return -RT_EINVAL;
    }

    ns800_adc_clear(adc, ADC_INT_NUMBER1);
    ADC_forceMultipleSOC(adc->config->instance, 1UL << channel);
    if (ns800_adc_wait_done(adc) != RT_EOK)
    {
        return -RT_ETIMEOUT;
    }

    *value = ADC_readResult(adc->config->result, (ADC_SOCNumber)channel);
    ns800_adc_clear(adc, ADC_INT_NUMBER1);

    return RT_EOK;
}


4.6 读取模式切换

驱动内部定义三种模式:

enum ns800_adc_mode
{
    NS800_ADC_MODE_NORMAL = 0,
    NS800_ADC_MODE_PPB,
    NS800_ADC_MODE_IRQ,
};


ns800_adc_convert() 根据当前模式分派:

switch (adc->mode)
{
case NS800_ADC_MODE_NORMAL:
    return ns800_adc_read_normal(adc, adc_channel, value);

case NS800_ADC_MODE_IRQ:
    return ns800_adc_read_irq(adc, adc_channel, value);

case NS800_ADC_MODE_PPB:
    return ns800_adc_read_ppb(adc, adc_channel, value);

default:
    break;
}


这部分是驱动结构的关键点:

  1. 模式配置由 control 完成,读取由 read/convert 完成。

  2. 应用层切换模式后继续调用 rt_adc_read(),不需要直接接触 SOC、PPB 或中断寄存器。

4.7 IRQ 模式

IRQ 模式使用 NS800_ADC_CMD_ENABLE_IRQ 进入。应用层可传入:

struct ns800_adc_callback
{
    ns800_adc_irq_callback_t callback;
    void *user_data;
};


驱动内部会:

  1. 保存 callback 和 user data。

  2. 根据 active_channel 配置软件触发 SOC。

  3. ADC_INT_NUMBER1 绑定到该 SOC 的 EOC。

  4. 注册 ADCA_CONV_IRQn 的 ISR。

  5. 使能 ADC 中断与 NVIC 中断。

  6. 模式切换为 NS800_ADC_MODE_IRQ

关键代码:

ret = ns800_adc_irq_attach(NS800_ADC_INT1,
                           adc->active_channel,
                           adc_irq_entries[NS800_ADC_INT1].callback,
                           adc_irq_entries[NS800_ADC_INT1].user_data);
if (ret == RT_EOK)
{
    adc->mode = NS800_ADC_MODE_IRQ;
}


ISR 中无论是否存在 callback,都先清 ADC pending:

if (ADC_getInterruptStatus(adc_obj.config->instance, (ADC_IntNumber)index) != 0U)
{
    ns800_adc_clear(&adc_obj, (ADC_IntNumber)index);
    if (adc_irq_entries[index].callback != RT_NULL)
    {
        adc_irq_entries[index].callback(adc_irq_entries[index].user_data);
    }
}


调试过程中曾出现 adc_pr_irq 后再执行普通 adc read 卡死的问题,根因就是 IRQ 状态与 pending 处理被漏掉了。

现在 detach 时会关闭 ADC 中断、清 ADC pending、清 callback,并关闭 NVIC pending:

ADC_disableInterrupt(adc_obj.config->instance, (ADC_IntNumber)int_no);
ns800_adc_clear(&adc_obj, (ADC_IntNumber)int_no);
adc_irq_entries[int_no].callback = RT_NULL;
adc_irq_entries[int_no].user_data = RT_NULL;
Interrupt_disable(adc_obj.config->conv_irq);
NVIC_ClearPendingIRQ(adc_obj.config->conv_irq);


注意:

使用 ADC IRQ 前,务必完成 全局中断向量表初始化 (也是使用任意中断外设的前提条件),否则进入 IRQ 模式时会卡死。

4.8 PPB oversampling 模式

简介

这是一个新的ADC外设拓展,PPB(Post-Processing Block)后处理块

可以实现一系列常用的拓展功能,由硬件进行操作,降低算力负担

如:

  1. 过限检测(上下限)

  2. 过零检测

  3. 偏置

  4. 误差计算

  5. oversamping(重复采样取均值)

使用

PPB 模式通过 NS800_ADC_CMD_ENABLE_PPB 打开。

默认行为是:

  • 使用 PPB1

  • 使用 SOC0

  • 对当前读取通道执行 oversampling。

  • 默认采样 8 次。

  • 默认 sample window 为 8。

  • 默认丢弃最大值和最小值后进行均值计算,得到计算结果。

  • rt_adc_read() 返回 PPB 处理后的值。

读取入口:

static rt_err_t ns800_adc_read_ppb(struct ns800_adc *adc,
                                   rt_uint32_t channel,
                                   rt_uint32_t *value)
{
    struct ns800_adc_ppb_oversampling_config ppb_config;
    struct ns800_adc_ppb_oversampling_result ppb_result;

    rt_memset(&ppb_config, 0, sizeof(ppb_config));
    ppb_config.bind.ppb = NS800_ADC_PPB1;
    ppb_config.bind.soc = 0U;
    ppb_config.adc_channel = channel;
    ppb_config.sample_count = NS800_ADC_DEFAULT_PPB_SAMPLES;
    ppb_config.sample_window = NS800_ADC_SAMPLE_WINDOW;
    ppb_config.drop_min_max = RT_TRUE;

    if (ns800_adc_ppb_oversampling_start(&ppb_config, &ppb_result) != RT_EOK)
    {
        return -RT_ERROR;
    }

    *value = (rt_uint32_t)ppb_result.value;

    return RT_EOK;
}


PPB 内部流程:

  1. Reset repeater。

  2. 将 SOC 配置为 repeater trigger。

  3. ADC_setupPPB() 绑定 PPB 与 SOC。

  4. 配置 PPB trip limit、event 和 offset。

  5. 配置 repeater 为 oversampling mode。

  6. 设置 PPB count limit、compare source、OSINT source 和 shift。

  7. 触发 repeater。

  8. 读取 count、sum、min、max、min_index、max_index。

  9. 计算 average 或 trimmed average。

其中 shift 处理是一个实现细节:当采样次数是 2 的幂且用户没有显式给出 shift 时,驱动会自动取 log2(sample_count)。默认 8 次采样对应 shift 3。

static rt_uint32_t ns800_adc_ppb_effective_shift(const struct ns800_adc_ppb_oversampling_config *config)
{
    if ((config->shift == 0U) &&
            (ns800_adc_ppb_is_power_of_two(config->sample_count) == RT_TRUE))
    {
        return ns800_adc_ppb_log2(config->sample_count);
    }

    return config->shift;
}


5. 应用代码编写

5.1 普通读取测试

核心代码:

device = adc_pr_find_device();
if (device == RT_NULL)
{
    return -RT_ERROR;
}

//enable ADC设备与指定通道
rt_adc_enable((rt_adc_device_t)device, channel);

//调用read函数进行读取该设备与对应通道
value = rt_adc_read((rt_adc_device_t)device, channel);

rt_kprintf("adc_pr_read: channel=%u value=%u\r\n",
           (unsigned int)channel,
           (unsigned int)value);


普通读取用于验证最基础链路:

rt_adc_enable()
    -> ns800_adc_enabled()
        -> ns800_adc_hw_init()
        -> 记录 enabled_mask / active_channel

rt_adc_read()
    -> ns800_adc_convert()
        -> ns800_adc_read_normal()
            -> ADC_setupSOC()
            -> ADC_forceMultipleSOC()
            -> 等待 EOC
            -> ADC_readResult()


5.2 IRQ 测试

测试代码中的回调:

//我们手动实现的回调函数
static void adc_pr_irq_callback(void *user_data)
{
    RT_UNUSED(user_data);
    rt_kprintf("adc irq callback test.\r\n");
    adc_pr_irq_count++;

    if (adc_pr_irq_sem_inited == RT_TRUE)
    {
        rt_sem_release(&adc_pr_irq_sem);
    }
}


开启 IRQ 模式:

//配置回调函数接口
struct ns800_adc_callback callback =
{
    .callback = adc_pr_irq_callback,
    .user_data = (void *)(rt_ubase_t)channel,
};

//使能外设与通道
rt_adc_enable((rt_adc_device_t)device, channel);

//发布control信号,将adc外设配置为irq模式,并传入参数来绑定我们的回调函数与其参数
result = rt_device_control(device, NS800_ADC_CMD_ENABLE_IRQ, &callback);

if (result != RT_EOK)
{
    rt_kprintf("adc_pr_irq: enable irq failed %d\r\n", result);
    return result;
}

//发起read请求
value = rt_adc_read((rt_adc_device_t)device, channel);

//测试用:阻塞式等待中断执行完毕。落实到具体应用的话没必要等中断,adc的采样特别快,等中断会拖慢速度
result = rt_sem_take(&adc_pr_irq_sem, rt_tick_from_millisecond(ADC_PR_TIMEOUT_MS));

//发布control信号,退出中断模式
rt_device_control(device, NS800_ADC_CMD_DISABLE_EXT, RT_NULL);


IRQ 测试的调用路径:

rt_device_control(NS800_ADC_CMD_ENABLE_IRQ)
    -> ns800_adc_set_mode(NS800_ADC_MODE_IRQ)
        -> ns800_adc_config_sw_soc()
        -> ns800_adc_irq_attach()
            -> ADC_setInterruptSource()
            -> ADC_enableInterrupt()
            -> Interrupt_register()
            -> Interrupt_enable()

rt_adc_read()
    -> ns800_adc_read_irq()
        -> ADC_forceMultipleSOC()
        -> 等待 ADC not busy
        -> ADC_readResult()

ISR
    -> ns800_adc_conv_irq_handler()
        -> ns800_adc_clear()
        -> callback(user_data)
        -> rt_sem_release()


测试结束后必须调用:

rt_device_control(device, NS800_ADC_CMD_DISABLE_EXT, RT_NULL);


否则设备会保持 IRQ 模式,后续普通 adc read 可能受中断状态影响。

当前驱动在 DISABLE_EXT 中恢复 normal 模式并 detach IRQ。

5.3 PPB 测试

测试代码:

//enable ADC设备与指定通道
rt_adc_enable((rt_adc_device_t)device, channel);

//发布control信号,将adc外设配置为PPB模式
result = rt_device_control(device, NS800_ADC_CMD_ENABLE_PPB, RT_NULL);
if (result != RT_EOK)
{
    rt_kprintf("adc_pr_ppb: enable ppb failed %d\r\n", result);
    return result;
}

//发起read,这里read到的已经是PPB模式下的返回值了
value = rt_adc_read((rt_adc_device_t)device, channel);

//退出PPB模式
rt_device_control(device, NS800_ADC_CMD_DISABLE_EXT, RT_NULL);
rt_kprintf("adc_pr_ppb: channel=%u value=%u\r\n",
           (unsigned int)channel,
           (unsigned int)value);


PPB 模式下,应用层仍然调用 rt_adc_read(),但驱动内部返回的是 PPB oversampling 后的结果,而不是单次 ADC_readResult() 的结果。

6. Kconfig 实现思路与应用方法

6.1 主线 BSP Kconfig

主线 BSP 中 ADC 配置项位于:

bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig


ADC 配置为:

menuconfig BSP_USING_ADC
    bool "Enable ADC"
    select RT_USING_ADC
    default n


这里的逻辑是:

  • 用户在 menuconfig 中打开 BSP_USING_ADC

  • Kconfig 自动选择 RT_USING_ADC

  • RT_USING_ADC 使 RT-Thread ADC 设备框架可用。

  • BSP_USING_ADC 让 BSP 的 SConscript 编译 drv_adc.c

6.2 SConscript 接入

主线驱动编译接入位于:

bsp/novosns/ns800/libraries/HAL_Drivers/drivers/SConscript


ADC 条件编译:

if GetDepend('BSP_USING_ADC'):
    src += ['drv_adc.c']


这样可以保证未开启 ADC 时不会引入 ADC 驱动和对应 HAL 依赖,减少对其他 BSP 配置的影响。

6.3 CI attachconfig

为了让主线 CI 覆盖 ADC 配置,在:

bsp/novosns/ns800/ns800rt7p65-nssinepad/.ci/attachconfig/ci.attachconfig.yml


加入:

devices.adc:
    <<: *scons
    kconfig:
      - CONFIG_BSP_USING_ADC=y


这样 CI 能以“打开 ADC 外设”的配置构建一次 BSP,避免 ADC 文件只在本地可编译、主线 CI 未覆盖的问题。

6.4 推荐配置流程

主线 BSP:

cd /path/to/rt-thread/bsp/novosns/ns800/ns800rt7p65-nssinepad
source ~/.env/env.sh
scons --menuconfig


在 menuconfig 中打开:

On-chip Peripheral Drivers
    Enable ADC


保存后生成 .configrtconfig.h,然后构建:

scons -j4


7. 运行时调用说明

7.1 基础读取

rt_device_t dev;
rt_uint32_t value;

dev = rt_device_find("adc0");
if (dev == RT_NULL)
{
    return -RT_ERROR;
}

rt_adc_enable((rt_adc_device_t)dev, 0);
value = rt_adc_read((rt_adc_device_t)dev, 0);
rt_kprintf("adc0 ch0=%u\r\n", value);


7.2 查询分辨率和参考电压

rt_uint8_t resolution;
rt_int16_t vref;

rt_device_control(dev, RT_ADC_CMD_GET_RESOLUTION, &resolution);
rt_device_control(dev, RT_ADC_CMD_GET_VREF, &vref);


当前默认返回:

  • resolution:12 bit。

  • vref:3300 mV。

7.3 切换到 PPB 模式

rt_adc_enable((rt_adc_device_t)dev, channel);
rt_device_control(dev, NS800_ADC_CMD_ENABLE_PPB, RT_NULL);
value = rt_adc_read((rt_adc_device_t)dev, channel);
rt_device_control(dev, NS800_ADC_CMD_DISABLE_EXT, RT_NULL);


PPB 模式默认使用 8 次 oversampling,并返回 PPB 处理后的值。

7.4 切换到 IRQ 模式

static void adc_irq_cb(void *user_data)
{
    rt_kprintf("adc irq\r\n");
}

struct ns800_adc_callback cb =
{
    .callback = adc_irq_cb,
    .user_data = RT_NULL,
};

rt_adc_enable((rt_adc_device_t)dev, channel);
rt_device_control(dev, NS800_ADC_CMD_ENABLE_IRQ, &cb);
value = rt_adc_read((rt_adc_device_t)dev, channel);
rt_device_control(dev, NS800_ADC_CMD_DISABLE_EXT, RT_NULL);


IRQ 模式使用前应确保全局向量表已初始化。驱动只注册 ADC IRQ handler,不负责系统级向量表初始化。

7.5 control 命令总结

命令

参数

作用

RT_ADC_CMD_ENABLE

channel

使能指定 ADC 通道

RT_ADC_CMD_DISABLE

channel

关闭指定 ADC 通道

RT_ADC_CMD_GET_RESOLUTION

rt_uint8_t *

获取 ADC 分辨率

RT_ADC_CMD_GET_VREF

rt_int16_t *

获取参考电压,单位 mV

NS800_ADC_CMD_ENABLE_PPB

RT_NULL

切换到默认 PPB oversampling 模式

NS800_ADC_CMD_ENABLE_IRQ

struct ns800_adc_callback *

切换到 IRQ 模式并注册回调

NS800_ADC_CMD_SET_CALLBACK

struct ns800_adc_callback *

仅设置 IRQ callback

NS800_ADC_CMD_DISABLE_EXT

RT_NULL

退出 PPB/IRQ 扩展模式,恢复 normal

四、纳芯微NS800RT7P65D上的HWTimer实践(刘协泉)

作者:刘协泉

原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的硬件定时器实践

源文章:https://club.rt-thread.org/ask/article/9eb602c9d20cf27b.html

前提知识

  RTT的硬件定时器框架,本质上是通过计算出误差最小的分频系数和计数值来实现定时功能,使用硬件定时加软件计数(如果计时超过了定时器能支持的最大单次时间)的方式实现计时功能。
  另外框架层其实更倾向于硬件定时器跑在1M的速率,若做不到,则设置为最低主频(这个可以在clock time框架层代码的init函数中见到),个人猜测是功耗方面的考量,毕竟现在的硬件定时器,主频都动不动几十上百兆的,其实可以做的更加精确。
  最后,由于最终的主频和预分频系数是框架层设置的,因此驱动层最好是能做到可以设置任意的预分频系数,否则硬件定时器的计时功能就大概率误差大。
  基于这些信息,如果要很好的适配上RTT的硬件定时器框架,硬件定时器就得有如下功能:

  1. 定时器的主频最好是大于或等于1M

  2. 定时器支持中断事件

  3. 最好定时器的分频系数可以任意设置(有效范围内的整数分频系数任意设置)

    资料收集

      通过查询NS800RT7P65D的规格书以及SDK代码,发现这颗芯片支持14个高级定时器,分别是:

  经过查看这些资料,基本上确认完全做定时器的模块只有有 TIM、BTIM、LPTIM和STIM,其他模块都属于特定应用场景下使用的。
  而在对接RTT定时器框架的需求上看,STIM因为不支持预分频,只能排除掉。BTIM和LPTIM由于只支持特定系数的分频,若强行适配上,会出现大部分时间计时有偏差的问题,因此也只能放弃,以免留雷。

package拉取

  在bsp目录下启动env,运行 pkgs —upgrade后运行pkgs —update拉取代码。

驱动代码编写

  首先,RTT在5.2.2之后的版本,对hwtimer模块进行了重构,新的模块叫clock time。
  虽然名字变更了,但是实际上接口变化不算大,对于驱动来说,基本上就三个变更项:

  1. 把HWTIMER宏变更为CLOCK_TIMER

  2. hwtimer变更为clock_timer

  3. 驱动文件名由drv_hwtimer.c变更为drv_timer.c

    KConfig

      这个文件的位置在bsp目录下的bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig,修改主要是增加硬件定时器相关宏开关

    menuconfig BSP_USING_TIM
        bool "Enable TIM (Hardware Timer)"
        select RT_USING_CLOCK_TIME
        default n
        help
            Enable hardware timer (TIM/BTIM/STIM/LPTIM) driver

    if BSP_USING_TIM
        menu "TIM Timer Configuration"
            config BSP_USING_TIM1
                bool "Enable TIM1"
                default n
            config BSP_USING_TIM2
                bool "Enable TIM2"
                default n
        endmenu

    endif


drv_timer.c

  这个文件加在bsp/novosns/ns800/libraries/HAL_Drivers/drivers/目录下

/*
 * Copyright (c) 2006-2025, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 * Author: oxlm
 */

#include <board.h>
#include <rtthread.h>
#include <rtdevice.h>
#include "drv_config.h"

#ifdef BSP_USING_TIM

struct ns800_clock_timer
{
    rt_clock_timer_t        timer;
    void                   *instance;
    IRQn_Type               irqno;
    void (*irq_handler)(void);
    char                   *name;
    rt_clock_timer_mode_t   mode;
};

enum
{
#ifdef BSP_USING_TIM1
    TIM1_INDEX,
#endif
#ifdef BSP_USING_TIM2
    TIM2_INDEX,
#endif
};

#ifdef BSP_USING_TIM1
void TIM1_IRQHandler(void);
#endif
#ifdef BSP_USING_TIM2
void TIM2_IRQHandler(void);
#endif

static struct ns800_clock_timer ns800_timers[] =
{
#ifdef BSP_USING_TIM1
    {.instance = TIM1,    .irqno = TIM1_IRQn,   .irq_handler = TIM1_IRQHandler, .name = "timer1"},
#endif
#ifdef BSP_USING_TIM2
    {.instance = TIM2,    .irqno = TIM2_IRQn,   .irq_handler = TIM2_IRQHandler, .name = "timer2"},
#endif
};

#define NS800_TIMER_NUM    (sizeof(ns800_timers) / sizeof(ns800_timers[0]))

static void ns800_clock_timer_isr(void *param)
{
    rt_interrupt_enter();

    struct ns800_clock_timer *tim = (struct ns800_clock_timer *)param;

    TIM_TypeDef *htim = (TIM_TypeDef *)tim->instance;
    if (TIM_getFlags(htim, TIM_FLAG_UPDATE))
    {
        TIM_clearFlags(htim, TIM_FLAG_UPDATE);
        rt_clock_timer_isr(&tim->timer);

        if (tim->mode == CLOCK_TIMER_MODE_ONESHOT)
            TIM_disableCounter(htim);
    }

    rt_interrupt_leave();
    __DSB();
}

#ifdef BSP_USING_TIM1
void TIM1_IRQHandler(void) { ns800_clock_timer_isr(&ns800_timers[TIM1_INDEX]); }
#endif
#ifdef BSP_USING_TIM2
void TIM2_IRQHandler(void) { ns800_clock_timer_isr(&ns800_timers[TIM2_INDEX]); }
#endif

static void ns800_clock_timer_init(rt_clock_timer_t *timer, rt_uint32_t state)
{
    struct ns800_clock_timer *tim = (struct ns800_clock_timer *)timer->parent.user_data;
    TIM_TypeDef *htim = (TIM_TypeDef *)tim->instance;

    if(state)
    {
        __IO uint32_t cfg =
            TIM_PWMMODE_ONEPOINT |
            TIM_CLOCKDIVISION_DIV1 |
            TIM_AUTORELOADPRELOAD_ENABLE |
            TIM_COUNTERMODE_UP |
            TIM_ONEPULSEMODE_REPETITIVE;

        TIM_configTimeBase(htim, 200-1, 100-1, cfg);
        TIM_clearFlags(htim, TIM_FLAG_UPDATE);
        TIM_enableInterruptSource(htim, TIM_IT_UPDATE);
    }
    else
    {
        TIM_disableInterruptSource(htim, TIM_IT_UPDATE);
        TIM_disableCounter(htim, TIM_FLAG_UPDATE);
    }
}

static rt_err_t ns800_clock_timer_start(rt_clock_timer_t *timer, rt_uint32_t cnt, rt_clock_timer_mode_t mode)
{
    struct ns800_clock_timer *tim = (struct ns800_clock_timer *)timer->parent.user_data;
    tim->mode = mode;

    TIM_TypeDef *htim = (TIM_TypeDef *)tim->instance;

    if (cnt > 0xFFFF) cnt = 0xFFFF;
    TIM_setAutoReload(htim, cnt-1);
    TIM_setCounter(htim, 0);
    TIM_enableCounter(htim);
    return RT_EOK;
}

static void ns800_clock_timer_stop(rt_clock_timer_t *timer)
{
    struct ns800_clock_timer *tim = (struct ns800_clock_timer *)timer->parent.user_data;

    TIM_disableCounter((TIM_TypeDef *)tim->instance);
}

static rt_uint32_t ns800_clock_timer_count_get(rt_clock_timer_t *timer)
{
    struct ns800_clock_timer *tim = (struct ns800_clock_timer *)timer->parent.user_data;

    return TIM_getCounter((TIM_TypeDef *)tim->instance);
}

static rt_err_t __set_timerx_freq(rt_clock_timer_t *timer, uint32_t freq)
{
    #define TIM_SRC_CLK    200000000UL
    struct ns800_clock_timer *tim = timer->parent.user_data;

    rt_uint32_t psc = (TIM_SRC_CLK / freq ) - 1;
    TIM_setPrescaler(tim->instance, psc);

    return RT_EOK;
}

static rt_err_t ns800_clock_timer_control(rt_clock_timer_t *timer, rt_uint32_t cmd, void *args)
{
    rt_err_t err = RT_EOK;
    rt_int32_t freq;

    switch (cmd)
    {
        case CLOCK_TIMER_CTRL_FREQ_SET:
            freq = *(rt_uint32_t *)args;
            __set_timerx_freq(timer, freq);
            break;
        case CLOCK_TIMER_CTRL_INFO_GET:
            *(struct rt_clock_timer_info*)args = *timer->info;
            err = RT_EOK;
            break;

        case CLOCK_TIMER_CTRL_MODE_SET:
            timer->mode = *(rt_uint32_t *)args;
            break;

        case CLOCK_TIMER_CTRL_STOP:
            ns800_clock_timer_stop(timer);
            break;
    }
    return err;
}

static const struct rt_clock_timer_info ns800_clock_timer_info[] =
{
#ifdef BSP_USING_TIM1
    {
        .maxfreq = 200000000UL,
        .minfreq = 1UL,
        .maxcnt  = 0xFFFF,
        .cntmode = CLOCK_TIMER_CNTMODE_UP,
    },
#endif
#ifdef BSP_USING_TIM2
    {
        .maxfreq = 200000000UL,
        .minfreq = 1UL,
        .maxcnt  = 0xFFFF,
        .cntmode = CLOCK_TIMER_CNTMODE_UP,
    },
#endif
};

static const struct rt_clock_timer_ops ns800_clock_timer_ops =
{
    .init       = ns800_clock_timer_init,
    .start      = ns800_clock_timer_start,
    .stop       = ns800_clock_timer_stop,
    .count_get  = ns800_clock_timer_count_get,
    .control    = ns800_clock_timer_control,
};

int rt_hw_clock_timer_init(void)
{
    if (NS800_TIMER_NUM == 0)
        return RT_EOK;

    uint8_t i;
    IRQn_Type last_irq = (IRQn_Type)0;

    for (i = 0; i < NS800_TIMER_NUM; i++)
    {
        ns800_timers[i].timer.info = &ns800_clock_timer_info[i];
        ns800_timers[i].timer.ops  = &ns800_clock_timer_ops;

        rt_clock_timer_register(&ns800_timers[i].timer,
                               ns800_timers[i].name,
                               &ns800_timers[i]);

        if (ns800_timers[i].irqno != last_irq)
        {
            Interrupt_register(ns800_timers[i].irqno, ns800_timers[i].irq_handler);
            Interrupt_enable(ns800_timers[i].irqno);
            last_irq = ns800_timers[i].irqno;
        }

        rt_kprintf("[%s] register ok\n", ns800_timers[i].name);
    }

    return RT_EOK;
}

INIT_DEVICE_EXPORT(rt_hw_clock_timer_init);

#endif /* BSP_USING_TIM */


SConscript
  这个文件的位置是bsp中的驱动目录下存储的,具体路径为bsp/novosns/ns800/libraries/HAL_Drivers/drivers/SConscript,主要选择编译时选择哪些文件编译

if GetDepend(['BSP_USING_TIM']):
    src += ['drv_timer.c']


测试代码编写

#include <rtthread.h>
#include <rtdevice.h>

static rt_err_t test_timer_callback(rt_device_t dev, rt_size_t size)
{
    rt_kprintf("[clock_timer] %d!\n",rt_tick_get());
    return RT_EOK;
}

int clock_timer_test(int argc, char** argv)
{
    rt_device_t dev;
    rt_err_t err;
    rt_clock_timerval_t t = {
        .sec = 1,
        .usec = 0,
    };
    rt_uint32_t mode;

    if (argc < 2)
    {
        rt_kprintf("usage: clock_timer_test <device>\n");
        rt_kprintf("eg: clock_timer_test timer1\n");
        return -1;
    }

    dev = rt_device_find(argv[1]);
    if (!dev)
    {
        rt_kprintf("device not found\n");
        return -1;
    }

    err = rt_device_open(dev, RT_DEVICE_OFLAG_RDWR);
    if (err != RT_EOK)
    {
        rt_kprintf("open failed\n");
        return err;
    }

    rt_device_set_rx_indicate(dev, test_timer_callback);

    rt_kprintf("\n[TEST] ONESHOT mode\n");
    mode = CLOCK_TIMER_MODE_ONESHOT;
    rt_device_control(dev, CLOCK_TIMER_CTRL_MODE_SET, &mode);
    rt_device_write(dev, 0, &t, sizeof(t));

    rt_thread_mdelay(2000);
    rt_device_control(dev, CLOCK_TIMER_CTRL_STOP, RT_NULL);

    rt_kprintf("\n[TEST] PERIOD mode\n");
    mode = CLOCK_TIMER_MODE_PERIOD;
    rt_device_control(dev, CLOCK_TIMER_CTRL_MODE_SET, &mode);
    rt_device_write(dev, 0, &t, sizeof(t));

    rt_thread_mdelay(3000);

    rt_kprintf("\n[TEST] read count\n");
    rt_device_read(dev, 0, &t, sizeof(t));
    rt_kprintf("Read: Sec = %d, Usec = %d\n", t.sec, t.usec);

    rt_device_control(dev, CLOCK_TIMER_CTRL_STOP, RT_NULL);

    rt_device_close(dev);
    rt_kprintf("\n[OK] all test done\n");
    return 0;
}

MSH_CMD_EXPORT(clock_timer_test, test clock timer);


配置工程

拉取依赖的驱动

pkgs --upgrade
pkgs --update


选择驱动

  menuconfig后选择如下内容后保存

更新工程

scons --target=mdk5


编译后验证

 \ | /
- RT -     Thread Operating System
 / | \     5.3.0 build May 16 2026 10:47:47
 2006 - 2024 Copyright by RT-Thread team
[timer1] register ok
[timer2] register ok
[btim1] register ok
[btim2] register ok
[lptim] register ok
msh >clo
clock_timer_test
msh >clock_timer_test timer1

[TEST] ONESHOT mode
[clock_timer] 4784!

[TEST] PERIOD mode
[clock_timer] 6849!
[clock_timer] 7848!
[clock_timer] 8847!

[TEST] read count
Read: Sec = 3, Usec = 4897

[OK] all test done
msh >clo
clock_timer_test
msh >clock_timer_test timer2

[TEST] ONESHOT mode
[clock_timer] 14707!

[TEST] PERIOD mode
[clock_timer] 16772!
[clock_timer] 17771!
[clock_timer] 18770!

[TEST] read count
Read: Sec = 3, Usec = 4897

[OK] all test done
msh >


从测试结果看,定时器的误差很小

总结

  总体来说,这颗芯片的资料还是挺全的,开放度也高,基本上看SDK驱动部分就知道能力大致在哪。最主要的是,整个资料全中文编写,降低了开发者的理解门槛。

问题点处理

选择clock time框架后,直接编译报错

  原因是bsp下的rtconfig.py在生成keil工程时,默认选择的工具链是armcc。由于我实际的工具链是armclang,因此会编译报错。

解决办法:

  直接修改py脚本,改成选择keil时的PLATFORM为armclang就行

模板工程转化后无法编译通过

  原因是生成的工程没有选择芯片,暂时采用在SConscript里预定义宏的方式规避

解决办法:

五、纳芯微NS800RT7P65D上的WDT实践(袁胜富)

作者:袁胜富

原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的IWDG1实践

源文章:https://club.rt-thread.org/ask/article/4e8e5f60f9162bd2.html

1. 概述

  1. 本文档记录将 RT-Thread WDG 总线驱动移植到 NS800RT7P65X BSP 的完整过程。NS800 芯片有 1 个 iwdg1 外设,本移植以iwdg1的中断和复位测试进行验证。

  2. 内部集成两个高安全性的独立看门狗定时器(IWDG1),每个 CPU 控制一个,其计时时钟源都为 MIRC1。 该独立看门狗可检测并解决由软件错误导致的故障,并在计数器从给定的超时值计数到 0 时触发系统复位;该独立看门狗还支持窗口刷新功能,用户只能在设定的计时窗口内刷计数器。用户可配置 IWDG1 在低功耗模式下继续运行或暂停运行。

  3. IWDG1 的溢出周期由 PSC 及 TOPS 共同控制,其溢出周期的范围为 102.4us ~ 13.42s。

  1. IWDG1 运行时,其计数器计数值范围为 0 ~ ARR,其溢出周期可通过如下公式进行计算:

  • 𝑡𝑂𝑉 = (𝐴𝑅𝑅 + 1) × 𝑡𝑊𝐷𝐶𝐿𝐾𝐷

  • 当 ARR 为 0x3FF、 tWDCLKD 为 0.1us 时, IWDG1 具有最小溢出时间 102.4us。

  • 当 ARR 为 0x3FFF、 tWDCLKD为 819.2us 时, IWDG1 具有最大溢出时间 13.42s。

  • 注意:用户应合理配置 PSC 及 TOPS 寄存器,以使 IWDG1 的溢出时间符合应用预期。

  • 由于硬件寄存器 PSC 和 TOPS 的组合有限,无法精确匹配任意超时时间,只能选择最接近的配置。不过驱动中的 iwdg1_calc_config 函数已经帮您自动完成了计算,您只需要传入期望的超时毫秒数,它会返回最接近的硬件配置和实际超时。

PSC编码

分频比

tWDCLKD (μs)

TOPS编码

ARR

计数值

实际超时 (ms)

0

1

0.1

0

1023

1024

0.1024

0

1

0.1

1

4095

4096

0.4096

0

1

0.1

2

8191

8192

0.8192

0

1

0.1

3

16383

16384

1.6384

1

2

0.2

0

1023

1024

0.2048

1

2

0.2

1

4095

4096

0.8192

1

2

0.2

2

8191

8192

1.6384

1

2

0.2

3

16383

16384

3.2768

2

4

0.4

0

1023

1024

0.4096

2

4

0.4

1

4095

4096

1.6384

2

4

0.4

2

8191

8192

3.2768

2

4

0.4

3

16383

16384

6.5536

3

8

0.8

0

1023

1024

0.8192

3

8

0.8

1

4095

4096

3.2768

3

8

0.8

2

8191

8192

6.5536

3

8

0.8

3

16383

16384

13.1072

4

16

1.6

0

1023

1024

1.6384

4

16

1.6

1

4095

4096

6.5536

4

16

1.6

2

8191

8192

13.1072

4

16

1.6

3

16383

16384

26.2144

5

32

3.2

0

1023

1024

3.2768

5

32

3.2

1

4095

4096

13.1072

5

32

3.2

2

8191

8192

26.2144

5

32

3.2

3

16383

16384

52.4288

8

128

12.8

0

1023

1024

13.1072

8

128

12.8

1

4095

4096

52.4288

8

128

12.8

2

8191

8192

104.8576

8

128

12.8

3

16383

16384

209.7152

9

256

25.6

0

1023

1024

26.2144

9

256

25.6

1

4095

4096

104.8576

9

256

25.6

2

8191

8192

209.7152

9

256

25.6

3

16383

16384

419.4304

10

512

51.2

0

1023

1024

52.4288

10

512

51.2

1

4095

4096

209.7152

10

512

51.2

2

8191

8192

419.4304

10

512

51.2

3

16383

16384

838.8608

11

1024

102.4

0

1023

1024

104.8576

11

1024

102.4

1

4095

4096

419.4304

11

1024

102.4

2

8191

8192

838.8608

11

1024

102.4

3

16383

16384

1677.7216

12

2048

204.8

0

1023

1024

209.7152

12

2048

204.8

1

4095

4096

838.8608

12

2048

204.8

2

8191

8192

1677.7216

12

2048

204.8

3

16383

16384

3355.4432

13

4096

409.6

0

1023

1024

419.4304

13

4096

409.6

1

4095

4096

1677.7216

13

4096

409.6

2

8191

8192

3355.4432

13

4096

409.6

3

16383

16384

6710.8864

14

8192

819.2

0

1023

1024

838.8608

14

8192

819.2

1

4095

4096

3355.4432

14

8192

819.2

2

8191

8192

6710.8864

14

8192

819.2

3

16383

16384

13421.7728

15

64

6.4

0

1023

1024

6.5536

15

64

6.4

1

4095

4096

26.2144

15

64

6.4

2

8191

8192

52.4288

15

64

6.4

3

16383

16384

104.8576

注意:PSC 编码 6、7 无效,已跳过。
从表中可以看出,实际超时是离散的,您只需要选择最接近您需求的那个值即可。例如:
希望 150ms → 最接近的是 104.86ms (PSC=8, TOPS=2) 或 209.72ms (PSC=8, TOPS=3)
希望 500ms → 最接近的是 419.43ms (PSC=9, TOPS=3) 或 838.86ms (PSC=10, TOPS=3)
驱动已经采用了“最接近”算法,所以您无需手动查表。

2. 移植思路

  1. RT-Thread WDG 框架概述 RT-Thread 提供了一套标准 WDG 框架,核心结构包括: struct rt_watchdog_device:WDG设备对象,用于挂载总线 struct rt_watchdog_ops:WDG操作函数集,包括init和control

int rt_hw_iwdg1_init(void)
{
    iwdg1_dev.base = IWDG1;
    /* Default factory configuration: maximum timeout 13421.7728 ms */
    iwdg1_dev.psc_code = 14;           // PSC = 14 (DIV8192), tWDCLKD = 819.2us
    iwdg1_dev.tops_code = 3;           // TOPS = 3 (ARR=16383)
    iwdg1_dev.timeout_ms = (tops_arr_table[3] + 1) * tWDCLKD_us_table[14] / 1000.0f; // 16384*819.2/1000 = 13421.7728 ms
    iwdg1_dev.started = RT_FALSE;
      iwdg1_dev.interrupt_mode = RT_TRUE;

    watchdog.ops = &iwdg1_ops;
    if (rt_hw_watchdog_register(&watchdog, IWDG1_DEVICE_NAME, RT_DEVICE_FLAG_DEACTIVATE, RT_NULL) != RT_EOK)
    {
        rt_kprintf("iwdg1: register failed\n");
        return -RT_ERROR;
    }
    rt_kprintf("iwdg1: driver initialized (clock=%d Hz, max timeout=%.4f ms)\n", IWDG1_CLK_FREQ_HZ, iwdg1_dev.timeout_ms);
    return RT_EOK;
}

INIT_BOARD_EXPORT(rt_hw_iwdg1_init);


iwdg1_dev.interrupt_mode = RT_TRUE;这行代码RT_FALSE代表看门狗超时复位,RT_TRUE代表看门狗超时进入中断。

2.1 驱动实现流程

1.打开env,第一次的话,需要输入命令pkgs —upgrade然后输入pkgs —update;
2.输入命令scons —menuconfig,进入RT-Thread Componets,进入Device Drivers,选择使用Using Watch Dog device drivers,按s键保存,然后按ESC;
3.在路径rt-thread\bsp\novosns\ns800\libraries\HAL_Drivers\drivers下创建drv_iwdg1.c和drv_iwdg1.h.并在SConscript文件中添加

if GetDepend(['BSP_USING_IWDG1']):
    src += ['drv_iwdg1.c']


4.在路径rt-thread\bsp\novosns\ns800\ns800rt7p65-nssinepad\board下的Kconfig文件里添加

    menuconfig BSP_USING_WDG
        bool "Enable WDG"
        select RT_USING_WDT
        default n
        if BSP_USING_WDG
            menuconfig BSP_USING_IWDG1
                bool "Enable IWDG1"
                default n
        endif


5.进入ENV,输入scons —menuconfig,选择On-Chip Peripheral Drivers,进入选择Enable WDG,进入选择Enable IWDG1,按s键保存,然后按ESC;
6.输入scons —target=mdk5身材Keil5 MDK工程

2.2 测试

int rt_hw_wwdg_init(void)
{
    wwdg_dev.base = WWDG;

    /* 默认配置:最大分频 128,计数器初值 0x7F,窗口 0x7F,约 167.772 ms */
    wwdg_dev.wdgtb = 7;
    wwdg_dev.reload = 0x7F;
    wwdg_dev.window = 0x7F;
    wwdg_dev.timeout_s = 0.16777216f;
    wwdg_dev.started = RT_FALSE;
    wwdg_dev.interrupt_mode = RT_FALSE;  /* 默认复位模式 */

    watchdog.ops = &wwdg_ops;
    if (rt_hw_watchdog_register(&watchdog, WWDG_DEVICE_NAME, RT_DEVICE_FLAG_DEACTIVATE, RT_NULL) != RT_EOK)
    {
        rt_kprintf("wwdg: register failed\n");
        return -RT_ERROR;
    }

    rt_kprintf("wwdg: driver initialized (HCLK=%d Hz)\n", HCLK_FREQ_HZ);
    rt_kprintf("wwdg: timeout range: 20.48 us ~ 167.772 ms\n");
    rt_kprintf("wwdg: default mode: reset, timeout: 167.772 ms\n");

    return RT_EOK;
}

INIT_BOARD_EXPORT(rt_hw_wwdg_init);


wwdg_dev.interrupt_mode = RT_FALSE; / 默认复位模式 /

/*
 * Copyright (c) 2006-2026, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author           Notes
 * 2026-05-06     Jiawei.Deng      first version
 */

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

#define LED1_PIN    PIN_NUM(GPIO_68)
#define LED2_PIN    PIN_NUM(GPIO_69)

int main(void)
{
    rt_device_t wdg = rt_device_find("iwdg1");
    if (wdg)
    {
        rt_device_init(wdg);
        rt_uint32_t timeout = 100;   // /* NOTE: The maximum possible timeout is about 13.42 seconds.
        rt_device_control(wdg, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
        rt_uint32_t actual;
        rt_device_control(wdg, RT_DEVICE_CTRL_WDT_GET_TIMEOUT, &actual);
        rt_kprintf("actual timeout = %d ms\n", actual);
        rt_device_control(wdg, RT_DEVICE_CTRL_WDT_START, RT_NULL);
                uint32_t cr = IWDG1->CR.WORDVAL;
                rt_kprintf("CR = 0x%08X, CKS=%d, TOPS=%d\n", cr, (cr >> IWDG_CR_CKS_S) & IWDG_CR_CKS_M, (cr >> IWDG_CR_TOPS_S) & IWDG_CR_TOPS_M);
    }
    else
    {
        rt_kprintf("can't find iwdg1 device\n");
    }
    rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);
    rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);

    while (1)
    {
        rt_pin_write(LED1_PIN, PIN_HIGH);
        rt_pin_write(LED2_PIN, PIN_HIGH);
        rt_thread_mdelay(49);
        rt_pin_write(LED1_PIN, PIN_LOW);
        rt_pin_write(LED2_PIN, PIN_LOW);
        rt_thread_mdelay(49);
        if (wdg)
        {
            rt_device_control(wdg, RT_DEVICE_CTRL_WDT_KEEPALIVE, RT_NULL);
            rt_kprintf("Feed\n");  // 可选择性打印
        }

    }
}


测试
编译,烧录,使用Xshell8
结果,定时100ms,实际有误差为104.8576ms

 \ | /
- RT -     Thread Operating System
 / | \     5.3.0 build May 14 2026 00:59:12
 2006 - 2024 Copyright by RT-Thread team
[I/drv.ecap] ecap1 register done
psc_code=8, tops_code=2, actual=104.8576 ms
iwdg1: set timeout req=100 ms, actual=104.8576 ms (PSC=8, TOPS=2)
actual timeout = 104 ms
iwdg1: started, timeout=104.8576 ms
CR = 0x00003386, CKS=48, TOPS=2
msh >Feed


wwdg_dev.interrupt_mode = RT_TRUE; / 中断模式 /

/*
 * Copyright (c) 2006-2026, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author           Notes
 * 2026-05-06     Jiawei.Deng      first version
 */
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

#define LED1_PIN    PIN_NUM(GPIO_68)
#define LED2_PIN    PIN_NUM(GPIO_69)

int main(void)
{
    rt_device_t wdg = rt_device_find("iwdg1");
    if (wdg)
    {
        rt_device_init(wdg);
        rt_uint32_t timeout = 100;   // /* NOTE: The maximum possible timeout is about 13.42 seconds.
        rt_device_control(wdg, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
        rt_uint32_t actual;
        rt_device_control(wdg, RT_DEVICE_CTRL_WDT_GET_TIMEOUT, &actual);
        rt_kprintf("actual timeout = %d ms\n", actual);
        rt_device_control(wdg, RT_DEVICE_CTRL_WDT_START, RT_NULL);
                uint32_t cr = IWDG1->CR.WORDVAL;
                rt_kprintf("CR = 0x%08X, CKS=%d, TOPS=%d\n", cr, (cr >> IWDG_CR_CKS_S) & IWDG_CR_CKS_M, (cr >> IWDG_CR_TOPS_S) & IWDG_CR_TOPS_M);
    }
    else
    {
        rt_kprintf("can't find iwdg1 device\n");
    }
    rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);
    rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);

    while (1)
    {
        rt_pin_write(LED1_PIN, PIN_HIGH);
        rt_pin_write(LED2_PIN, PIN_HIGH);
        rt_thread_mdelay(49);
        rt_pin_write(LED1_PIN, PIN_LOW);
        rt_pin_write(LED2_PIN, PIN_LOW);
        rt_thread_mdelay(49);
        //if (wdg)
        //{
            //rt_device_control(wdg, RT_DEVICE_CTRL_WDT_KEEPALIVE, RT_NULL);
            //rt_kprintf("Feed\n");  // 可选择性打印
        //}

    }
}
编译,烧录,使用Xshell8
结果,定时100ms,实际有误差为104.8576ms


\ | /

  • RT - Thread Operating System
    / | \ 5.3.0 build May 14 2026 00:59:12
    2006 - 2024 Copyright by RT-Thread team
    [I/drv.ecap] ecap1 register done
    psc_code=8, tops_code=2, actual=104.8576 ms
    iwdg1: set timeout req=100 ms, actual=104.8576 ms (PSC=8, TOPS=2)
    actual timeout = 104 ms
    iwdg1: started, timeout=104.8576 ms
    CR = 0x00003382, CKS=48, TOPS=2
    msh >Enter iwdg1 IRQ!

3.文件源码

drv_iwdg1.c

/*
 * Copyright (c) 2006-2026, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2026-05-13     Jeffery.Yuan  Floating-point timeout support
 */

#include <rtthread.h>
#include <rtdevice.h>
#include "board.h"
#include "iwdg1.h"
#include "drv_iwdg1.h"

#ifdef RT_USING_WDT
#ifdef BSP_USING_IWDG1

/* IWDG1 clock source frequency (Hz) = 10 MHz */
#define IWDG1_CLK_FREQ_HZ      (10000000U)

/* PSC encoding to division ratio and tWDCLKD (microseconds) */
static const uint32_t psc_div_table[] = {
    1,      /* 0: DIV1   */
    2,      /* 1: DIV2   */
    4,      /* 2: DIV4   */
    8,      /* 3: DIV8   */
    16,     /* 4: DIV16  */
    32,     /* 5: DIV32  */
    0,      /* 6: Invalid */
    0,      /* 7: Invalid */
    128,    /* 8: DIV128 */
    256,    /* 9: DIV256 */
    512,    /* 10: DIV512*/
    1024,   /* 11: DIV1024*/
    2048,   /* 12: DIV2048*/
    4096,   /* 13: DIV4096*/
    8192,   /* 14: DIV8192*/
    64      /* 15: DIV64  */
};
static const float tWDCLKD_us_table[] = {
    0.1f,  0.2f,  0.4f,  0.8f,  1.6f,  3.2f,
    0.0f,  0.0f,
    12.8f, 25.6f, 51.2f, 102.4f, 204.8f, 409.6f, 819.2f, 6.4f
};

/* TOPS encoding to ARR value */
static const uint32_t tops_arr_table[] = {
    1023,   /* 00: 0x3FF */
    4095,   /* 01: 0xFFF */
    8191,   /* 10: 0x1FFF */
    16383   /* 11: 0x3FFF */
};

/* IWDG1 device object */
struct iwdg1_device
{
    IWDG1_TypeDef    *base;
    uint8_t           psc_code;      /* PSC encoding (0~15) */
    uint8_t           tops_code;     /* TOPS encoding (0~3) */
    float             timeout_ms;    /* Actual timeout in milliseconds (floating point) */
    rt_bool_t         started;
      rt_bool_t         interrupt_mode; /* intterrupt flag */
};

static struct iwdg1_device iwdg1_dev;
static rt_watchdog_t watchdog;

/**
 * @brief Calculate the closest hardware configuration for a desired timeout
 * @param timeout_ms  Desired timeout in milliseconds (float)
 * @param psc_code    Output PSC encoding
 * @param tops_code   Output TOPS encoding
 * @param actual_ms   Output actual timeout in milliseconds (float)
 * @return RT_EOK always successful
 */
static rt_err_t iwdg1_calc_config_float(float timeout_ms, uint8_t *psc_code, uint8_t *tops_code, float *actual_ms)
{
    float target_us = timeout_ms * 1000.0f;
    float best_diff = 1e9f;
    uint8_t best_psc = 0, best_tops = 0;
    float best_actual_us = 0;

    for (uint8_t psc = 0; psc < 16; psc++)
    {
        float tWDCLKD_us = tWDCLKD_us_table[psc];
        if (tWDCLKD_us == 0.0f) continue;
        for (uint8_t tops = 0; tops < 4; tops++)
        {
            uint32_t arr = tops_arr_table[tops];
            float ov_us = (arr + 1) * tWDCLKD_us;
            float diff = (ov_us > target_us) ? (ov_us - target_us) : (target_us - ov_us);
            if (diff < best_diff)
            {
                best_diff = diff;
                best_psc = psc;
                best_tops = tops;
                best_actual_us = ov_us;
                if (diff == 0) goto found;
            }
        }
    }

found:
    *psc_code = best_psc;
    *tops_code = best_tops;
    *actual_ms = best_actual_us / 1000.0f;
    if (*actual_ms < 0.0001f) *actual_ms = 0.1024f;   // 最小 0.1024ms
    rt_kprintf("psc_code=%d, tops_code=%d, actual=%.4f ms\n", best_psc, best_tops, *actual_ms);
    return RT_EOK;
}

/* IWDG1 Interrupt Handler
   NOTE: Due the IWDG1 operates in a low-speed clock domain and the operation
         to clear the flag requires a certain number of clock cycles, so wait
         for the flag status update to complete before exiting!
 */
void IWDG1_Handler(void)
{
      rt_interrupt_enter();
      rt_kprintf("Enter iwdg1 IRQ!\n");
    IWDG1_clearErrorStatus(iwdg1_dev.base);
    IWDG1_clearIntStatus(iwdg1_dev.base);
    while(IWDG1_getIntStatus(iwdg1_dev.base) == 1)
    {
        ;
    }

    IWDG1_enableModule(iwdg1_dev.base);
        rt_interrupt_leave();
}

/* Apply hardware configuration (without enabling) */
static void iwdg1_apply_config(void)
{
    uint32_t temp;
    uint32_t cr = iwdg1_dev.base->CR.WORDVAL;
    if (cr != 0x000033FF) {
        rt_kprintf("iwdg1: CR already configured (0x%08X), skip write\n", cr);
        // Reverse calculate actual timeout from existing CR
        uint8_t psc = (cr >> 4) & 0xF;
        uint8_t tops = cr & 0x3;
        iwdg1_dev.psc_code = psc;
        iwdg1_dev.tops_code = tops;
        iwdg1_dev.timeout_ms = (tops_arr_table[tops] + 1) * tWDCLKD_us_table[psc] / 1000.0f;
        rt_kprintf("iwdg1: using existing config, timeout=%.4f ms\n", iwdg1_dev.timeout_ms);
        return;
    }
        if (iwdg1_dev.interrupt_mode)
    {
            /* Interrupt handler function registration. */
            Interrupt_register(IWDG1_IRQn, &IWDG1_Handler);
            /* Enable the IWDG1 interrupt signals. Enable global interrupts.*/
            Interrupt_enable(IWDG1_IRQn);
            temp = iwdg1_dev.tops_code | (IRQ_EN << IWDG_CR_RSTIRQS_S) \
                   | (LPRUN << IWDG_CR_SLCSTP_S) \
                   | (iwdg1_dev.psc_code << IWDG_CR_CKS_S) \
                   | (END_PERCENT0 << IWDG_CR_RPES_S) \
                   | (HEAD_PERCENT100 << IWDG_CR_RPSS_S);
        }
        else
        {
            temp = iwdg1_dev.tops_code | (RESET_EN << IWDG_CR_RSTIRQS_S) \
                   | (LPRUN << IWDG_CR_SLCSTP_S) \
                   | (iwdg1_dev.psc_code << IWDG_CR_CKS_S) \
                   | (END_PERCENT0 << IWDG_CR_RPES_S) \
                   | (HEAD_PERCENT100 << IWDG_CR_RPSS_S);
        }
    WRITE_REG(iwdg1_dev.base->CR.WORDVAL, temp);

    IWDG1_clearErrorStatus(iwdg1_dev.base);
    IWDG1_clearIntStatus(iwdg1_dev.base);
}

/* Start watchdog (enable) */
static void iwdg1_start_hw(void)
{
    IWDG1_enableModule(iwdg1_dev.base);
    iwdg1_dev.started = RT_TRUE;
    rt_kprintf("iwdg1: started, timeout=%.4f ms\n", iwdg1_dev.timeout_ms);
}

static rt_err_t iwdg1_init(rt_watchdog_t *wdt)
{
    return RT_EOK;
}

static rt_err_t iwdg1_control(rt_watchdog_t *wdt, int cmd, void *arg)
{
    switch (cmd)
    {
    case RT_DEVICE_CTRL_WDT_KEEPALIVE:
        IWDG1_refreshModule(iwdg1_dev.base);
        break;

    case RT_DEVICE_CTRL_WDT_SET_TIMEOUT:
        {
            // Standard integer millisecond interface (backward compatible)
            uint32_t req_ms_int = *(uint32_t *)arg;
            float req_ms = (float)req_ms_int;
            uint8_t psc, tops;
            float actual_ms;
            iwdg1_calc_config_float(req_ms, &psc, &tops, &actual_ms);
            iwdg1_dev.psc_code = psc;
            iwdg1_dev.tops_code = tops;
            iwdg1_dev.timeout_ms = actual_ms;
            rt_kprintf("iwdg1: set timeout req=%u ms, actual=%.4f ms (PSC=%d, TOPS=%d)\n",
                       req_ms_int, actual_ms, psc, tops);
            if (iwdg1_dev.started)
            {
                iwdg1_apply_config();
                IWDG1_enableModule(iwdg1_dev.base);
            }
        }
        break;

    case RT_DEVICE_CTRL_WDT_GET_TIMEOUT:
        // Standard integer millisecond interface returns floor of actual timeout
        *(uint32_t *)arg = (uint32_t)iwdg1_dev.timeout_ms;
        break;

    case RT_DEVICE_CTRL_WDT_START:
        if (!iwdg1_dev.started)
        {
            iwdg1_apply_config();
            iwdg1_start_hw();
        }
        else
        {
            rt_kprintf("iwdg1: already started\n");
        }
        break;

    case RT_DEVICE_CTRL_WDT_STOP:
        rt_kprintf("iwdg1: stop not supported\n");
        return -RT_ENOSYS;

    default:
        return -RT_ERROR;
    }
    return RT_EOK;
}

/* Operation function table - constant */
static const struct rt_watchdog_ops iwdg1_ops = {
    .init    = iwdg1_init,
    .control = iwdg1_control,
};

int rt_hw_iwdg1_init(void)
{
    iwdg1_dev.base = IWDG1;
    /* Default factory configuration: maximum timeout 13421.7728 ms */
    iwdg1_dev.psc_code = 14;           // PSC = 14 (DIV8192), tWDCLKD = 819.2us
    iwdg1_dev.tops_code = 3;           // TOPS = 3 (ARR=16383)
    iwdg1_dev.timeout_ms = (tops_arr_table[3] + 1) * tWDCLKD_us_table[14] / 1000.0f; // 16384*819.2/1000 = 13421.7728 ms
    iwdg1_dev.started = RT_FALSE;
      iwdg1_dev.interrupt_mode = RT_TRUE;

    watchdog.ops = &iwdg1_ops;
    if (rt_hw_watchdog_register(&watchdog, IWDG1_DEVICE_NAME, RT_DEVICE_FLAG_DEACTIVATE, RT_NULL) != RT_EOK)
    {
        rt_kprintf("iwdg1: register failed\n");
        return -RT_ERROR;
    }
    rt_kprintf("iwdg1: driver initialized (clock=%d Hz, max timeout=%.4f ms)\n", IWDG1_CLK_FREQ_HZ, iwdg1_dev.timeout_ms);
    return RT_EOK;
}

INIT_BOARD_EXPORT(rt_hw_iwdg1_init);

#endif /* BSP_USING_IWDG1 */
#endif /* RT_USING_WDT */


drv_iwdg1.h

/*
 * Copyright (c) 2006-2026, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author            Notes
 * 2026-05-12     Jeffery Yuan      first version
 */

#ifndef DRV_IWDG1_H__
#define DRV_IWDG1_H__

#include <rtdevice.h>
#include <board.h>

#ifdef __cplusplus
extern "C" {
#endif

#define IWDG1_DEVICE_NAME    "iwdg1"

/**
 * @brief Initialize IWDG1 hardware and register it as a watchdog device.
 *
 * @return RT_EOK on success, negative error code otherwise.
 */
int rt_hw_iwdg1_init(void);

#ifdef __cplusplus
}
#endif

#endif /* DRV_IWDG1_H__ */


六、纳芯微NS800RT7P65D上的SPI实践(刘建华)

作者:刘建华

原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的SPI实践

源文章:https://club.rt-thread.org/ask/article/119a830b1b1d5b0e.html

NS800 RT-Thread SPI 驱动移植指南

1. 概述

本文档记录将 RT-Thread SPI 总线驱动移植到 NS800RT7P65X BSP 的完整过程。NS800 芯片有 4 个 SPI 外设 (SPI1~SPI4),本移植以 SPI1 为例,配置为内部回环模式进行测试验证。

2. 移植思路

2.1 RT-Thread SPI 框架概述

RT-Thread 提供了一套标准 SPI 框架,核心结构包括:

结构体

说明

struct rt_spi_bus

SPI 总线对象,封装底层硬件

struct rt_spi_device

SPI 设备对象,挂载到总线上

struct rt_spi_ops

SPI 操作函数集,包含 configurexfer

struct rt_spi_configuration

SPI 配置参数(模式、位宽、频率)

驱动实现流程:

INIT_BOARD_EXPORT(rt_hw_spi_init)
     遍历 spi_config[] 数组
     初始化时钟和 GPIO
     rt_spi_bus_register() 注册 SPI 总线


2.2 移植步骤概览

  1. 创建驱动文件 - drv_spi.hdrv_spi.c

  2. 实现 GPIO 初始化 - 配置 SCK/MOSI/MISO 引脚及复用功能

  3. 实现 SPI 配置 - 设置 CPOL/CPHA、位宽、时钟频率

  4. 实现数据传输 - 基于轮询的 SPI_transmitReceive() 实现

  5. 更新构建系统 - 修改 SConscript

  6. 配置 Kconfig - 添加 SPI 配置选项

  7. 更新 rtconfig.h - 启用 SPI 宏定义

  8. 编写测试代码 - SPI 回环测试

3. 文件清单

3.1 新建文件

文件路径

说明

bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_spi.h

SPI 驱动头文件

bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_spi.c

SPI 驱动源文件

3.2 修改文件

文件路径

修改内容

bsp/novosns/ns800/libraries/HAL_Drivers/drivers/SConscript

添加 drv_spi.c 编译条件

bsp/novosns/ns800/libraries/HAL_Drivers/drivers/Kconfig

添加 SPI1~4 配置选项

bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig

添加 SPI menuconfig

bsp/novosns/ns800/ns800rt7p65-nssinepad/rtconfig.h

添加 BSP_USING_SPIBSP_USING_SPI1

bsp/novosns/ns800/ns800rt7p65-nssinepad/applications/main.c

添加 SPI 回环测试代码

4. 驱动实现详解

4.1 头文件 drv_spi.h

定义 SPI 配置结构和 SPI 对象结构体:

struct ns800_spi_config
{
    const char *name;          // SPI 总线名称,如 "spi1"
    SPI_TypeDef *Instance;     // SPI 外设基地址,如 SPI1
    IRQn_Type rx_irq_type;     // RX 中断号
    IRQn_Type tx_irq_type;     // TX 中断号
    // GPIO 配置
    GPIO_TypeDef *sck_port;    // SCK 端口
    GPIO_PinNum sck_pin;       // SCK 引脚号
    GPIO_AltFunc sck_mux;      // SCK 复用功能
    GPIO_TypeDef *mosi_port;
    GPIO_PinNum mosi_pin;
    GPIO_AltFunc mosi_mux;
    GPIO_TypeDef *miso_port;
    GPIO_PinNum miso_pin;
    GPIO_AltFunc miso_mux;
};

struct ns800_spi
{
    SPI_TypeDef *Instance;
    struct rt_spi_bus spi_bus;     // 继承 RT-Thread SPI 总线
    struct ns800_spi_config *config;
};


4.2 GPIO 初始化 ns800_spi_gpio_init()

配置 SPI 四个引脚的复用模式、模拟模式、驱动强度、滤波和方向:

static void ns800_spi_gpio_init(const struct ns800_spi_config *config)
{
    // SCK - 输出
    GPIO_setPinConfig(config->sck_port, config->sck_pin, config->sck_mux);
    GPIO_setAnalogMode(config->sck_port, config->sck_pin, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(config->sck_port, config->sck_pin, GPIO_PIN_TYPE_STD);
    GPIO_setQualificationMode(config->sck_port, config->sck_pin, GPIO_QUAL_SYNC);
    GPIO_setDirectionMode(config->sck_port, config->sck_pin, GPIO_DIR_MODE_OUT);

    // MOSI - 输出
    GPIO_setPinConfig(config->mosi_port, config->mosi_pin, config->mosi_mux);
    GPIO_setAnalogMode(config->mosi_port, config->mosi_pin, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(config->mosi_port, config->mosi_pin, GPIO_PIN_TYPE_STD);
    GPIO_setQualificationMode(config->mosi_port, config->mosi_pin, GPIO_QUAL_SYNC);
    GPIO_setDirectionMode(config->mosi_port, config->mosi_pin, GPIO_DIR_MODE_OUT);

    // MISO - 输入,配置上拉
    GPIO_setPinConfig(config->miso_port, config->miso_pin, config->miso_mux);
    GPIO_setAnalogMode(config->miso_port, config->miso_pin, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(config->miso_port, config->miso_pin, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(config->miso_port, config->miso_pin, GPIO_QUAL_SYNC);
    GPIO_setDirectionMode(config->miso_port, config->miso_pin, GPIO_DIR_MODE_IN);
}


4.3 SPI 配置 ns800_spi_configure()

根据 rt_spi_configuration 参数配置 SPI 硬件:

static rt_err_t ns800_spi_configure(struct rt_spi_device *device,
                                     struct rt_spi_configuration *configuration)
{
    // 解析 SPI 模式 (CPOL/CPHA)
    switch (configuration->mode & RT_SPI_MODE_3)
    {
    case RT_SPI_MODE_0: protocol = SPI_PROT_POL0PHA0; break;
    case RT_SPI_MODE_1: protocol = SPI_PROT_POL0PHA1; break;
    case RT_SPI_MODE_2: protocol = SPI_PROT_POL1PHA0; break;
    case RT_SPI_MODE_3: protocol = SPI_PROT_POL1PHA1; break;
    }

    // 解析数据位宽
    switch (configuration->data_width)
    {
    case 8:  data_width = SPI_BIT_WIDTH_8_BITS;  break;
    case 16: data_width = SPI_BIT_WIDTH_16_BITS; break;
    default: data_width = SPI_BIT_WIDTH_8_BITS;  break;
    }

    // 配置并使能 SPI
    SPI_disableModule(spi->config->Instance);
    SPI_setConfig(spi->config->Instance, protocol, SPI_MASTER_MODE,
                  SPI_FULL_DUPLEX_COMM_MODE, configuration->max_hz, data_width);
    SPI_resetFifo(spi->config->Instance);
    SPI_enableModule(spi->config->Instance);

    return RT_EOK;
}


4.4 数据传输 ns800_spi_xfer()

实现 RT-Thread SPI 框架的 xfer 回调,使用轮询方式逐字节收发:

static rt_ssize_t ns800_spi_xfer(struct rt_spi_device *device,
                                  struct rt_spi_message *message)
{
    while (length)
    {
        if (send_buf)
        {
            if (recv_buf)
            {
                recv_buf[0] = SPI_transmitReceive(instance, send_buf[0]);
                recv_buf++;      // 注意:recv_buf 必须递增!
            }
            else
            {
                SPI_transmitReceive(instance, send_buf[0]);
            }
            send_buf++;
        }
        else
        {
            if (recv_buf)
            {
                recv_buf[0] = SPI_transmitReceive(instance, 0xFF);
                recv_buf++;
            }
            else
            {
                SPI_transmitReceive(instance, 0xFF);
            }
        }
        length--;
    }
    return message->length;
}


4.5 SPI1 引脚配置

根据 NS800 官方 datasheet,SPI1 使用以下引脚:

信号

GPIO

引脚号

复用功能

SCK

GPIOA

PIN_18

ALT1

MOSI (SIMO)

GPIOA

PIN_16

ALT1

MISO (SOMI)

GPIOA

PIN_17

ALT1

#ifdef BSP_USING_SPI1
{
    .name = "spi1",
    .Instance = SPI1,
    .rx_irq_type = SPI1_RX_IRQn,
    .tx_irq_type = SPI1_TX_IRQn,
    .sck_port = GPIOA,
    .sck_pin = GPIO_PIN_18,
    .sck_mux = ALT1_FUNCTION,
    .mosi_port = GPIOA,
    .mosi_pin = GPIO_PIN_16,
    .mosi_mux = ALT1_FUNCTION,
    .miso_port = GPIOA,
    .miso_pin = GPIO_PIN_17,
    .miso_mux = ALT1_FUNCTION,
},
#endif


4.6 总线注册 rt_hw_spi_init()

int rt_hw_spi_init(void)
{
    for (i = 0; i < sizeof(spi_config) / sizeof(spi_config[0]); i++)
    {
        ns800_spi_clock_init(spi_config[i].Instance);  // 使能时钟
        spi_obj[i].config = &spi_config[i];
        spi_obj[i].spi_bus.parent.user_data = &spi_config[i];

        result = rt_spi_bus_register(&spi_obj[i].spi_bus,
                                      spi_config[i].name,
                                      &ns800_spi_ops);
    }
    return RT_EOK;
}
INIT_BOARD_EXPORT(rt_hw_spi_init);


5. 配置文件修改

5.1 SConscript - 添加编译条件

if GetDepend('BSP_USING_SPI'):
    src += ['drv_spi.c']


5.2 drivers/Kconfig - SPI 外设配置

menuconfig BSP_USING_SPI
    bool "Enable SPI"
    select RT_USING_SPI
    default n
    if BSP_USING_SPI
        menuconfig BSP_USING_SPI1
            bool "Enable SPI1"
            default n
        menuconfig BSP_USING_SPI2
            bool "Enable SPI2"
            default n
        # ... SPI3, SPI4 类似
    endif


5.3 board/Kconfig - 添加 SPI menu

menuconfig BSP_USING_SPI
    bool "Enable SPI"
    select RT_USING_SPI
    default n
    if BSP_USING_SPI
        menuconfig BSP_USING_SPI1
            bool "Enable SPI1"
            default n
        menuconfig BSP_USING_SPI2
            bool "Enable SPI2"
            default n
        # ... SPI3, SPI4 类似
    endif


5.4 rtconfig.h - 添加宏定义

#define RT_USING_SPI
#define RT_USING_SPI_ISR
#define BSP_USING_SPI
#define BSP_USING_SPI1


6. 测试代码

6.1 回环测试 main.c

#ifdef BSP_USING_SPI1
#include "spi.h"

static struct rt_spi_device spi_dev;
static struct rt_spi_configuration cfg;

static void spi_loopback_test(void)
{
    rt_err_t result;
    rt_uint8_t send_data[4] = {0x55, 0xAA, 0x5A, 0xA5};
    rt_uint8_t recv_data[4] = {0};

    /* 将 SPI 设备挂载到 spi1 总线 */
    result = rt_spi_bus_attach_device(&spi_dev, "spi10", "spi1", RT_NULL);
    if (result != RT_EOK)
    {
        rt_kprintf("spi loopback test: bus attach failed: %d\n", result);
        return;
    }

    /* 配置 SPI */
    cfg.data_width = 8;
    cfg.mode = RT_SPI_MODE_0 | RT_SPI_MSB;
    cfg.max_hz = 1000000;
    result = rt_spi_configure(&spi_dev, &cfg);
    if (result != RT_EOK)
    {
        rt_kprintf("spi loopback test: configure failed: %d\n", result);
        return;
    }

    /* 使能内部回环模式 */
    SPI_enableLoopback2(SPI1);

    rt_kprintf("spi loopback test: sending 0x%02X, 0x%02X, 0x%02X, 0x%02X\n",
               send_data[0], send_data[1], send_data[2], send_data[3]);

    /* 传输数据 */
    result = rt_spi_transfer(&spi_dev, send_data, recv_data, 4);

    if (result == 4)
    {
        rt_kprintf("spi loopback test: received 0x%02X, 0x%02X, 0x%02X, 0x%02X\n",
                   recv_data[0], recv_data[1], recv_data[2], recv_data[3]);

        if (rt_memcmp(send_data, recv_data, 4) == 0)
            rt_kprintf("spi loopback test: PASSED!\n");
        else
            rt_kprintf("spi loopback test: FAILED - data mismatch!\n");
    }
    else
    {
        rt_kprintf("spi loopback test: transfer failed: %d\n", result);
    }

    SPI_disableLoopback2(SPI1);
}
#endif

int main(void)
{
#ifdef BSP_USING_SPI1
    rt_thread_mdelay(500);
    spi_loopback_test();
#endif
    while (1) { /* LED blink */ }
}


7. 测试步骤

7.1 编译前配置

  1. 使用 menuconfig 启用 SPI:

On-chip Peripheral Drivers → BSP_USING_SPI → BSP_USING_SPI1


或手动修改 rtconfig.h 添加:

#define RT_USING_SPI
#define RT_USING_SPI_ISR
#define BSP_USING_SPI
#define BSP_USING_SPI1


7.2 编译

使用 MDK5 或 GCC 编译项目。

7.3 运行测试

下载到开发板,串口输出显示:

8. 常见问题

8.1 rt_spi_transfer 断言失败

错误信息:

(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex) assertion failed


原因: 直接对 SPI 总线操作,而没有先挂载 SPI 设备。

解决: 使用 rt_spi_bus_attach_device() 将设备挂载到总线,再对设备操作。

8.2 回环数据全为 0x00

原因: recv_buf 指针未递增,所有数据都写到 recv_buf[0]

解决:

recv_buf[0] = SPI_transmitReceive(instance, send_buf[0]);
recv_buf++;  // 必须递增


8.3 rt_device_find("spi1") 返回 NULL

原因: spi1 是 SPI 总线名称,不是设备名称。

解决:rt_spi_bus_attach_device() 挂载设备,再用 rt_device_find() 查找设备名称。

9. 扩展到其他 SPI 外设

SPI2~SPI4 的配置方法与 SPI1 类似,只需:

  1. board/Kconfig 中启用对应 SPI

  2. rtconfig.h 中添加 BSP_USING_SPIx

  3. drv_spi.cspi_config[] 数组中添加引脚配置(参考 NS800 datasheet)

10. 参考资料

  • RT-Thread SPI 设备驱动框架

  • NS800RT7XXX StdDriver SPI 头文件:packages/novosns-series-latest/NS800RT7XXX/StdDriver/Inc/spi.h

  • 其他 BSP 驱动参考:bsp/stm32/libraries/HAL_Drivers/drivers/drv_spi.c

附drv_spi.c源码:

/*
 * Copyright (c) 2006-2026, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2026-05-12     Codex        first version
 */

#include "board.h"
#include "drv_spi.h"
#include "drv_config.h"

#ifdef RT_USING_SPI

#define DRV_DEBUG
#define LOG_TAG             "drv.spi"
#include <drv_log.h>

#if !defined(BSP_USING_SPI1) && !defined(BSP_USING_SPI2) && !defined(BSP_USING_SPI3) && \
    !defined(BSP_USING_SPI4)
#error "Please define at least one BSP_USING_SPIx"
#endif

enum
{
#ifdef BSP_USING_SPI1
    SPI1_INDEX,
#endif
#ifdef BSP_USING_SPI2
    SPI2_INDEX,
#endif
#ifdef BSP_USING_SPI3
    SPI3_INDEX,
#endif
#ifdef BSP_USING_SPI4
    SPI4_INDEX,
#endif
};

#ifdef BSP_USING_SPI1
void SPI1_IRQHandler(void);
#endif
#ifdef BSP_USING_SPI2
void SPI2_IRQHandler(void);
#endif
#ifdef BSP_USING_SPI3
void SPI3_IRQHandler(void);
#endif
#ifdef BSP_USING_SPI4
void SPI4_IRQHandler(void);
#endif

static struct ns800_spi_config spi_config[] =
{
#ifdef BSP_USING_SPI1
    {
        .name = "spi1",
        .Instance = SPI1,
        .rx_irq_type = SPI1_RX_IRQn,
        .tx_irq_type = SPI1_TX_IRQn,
        .sck_port = GPIOA,
        .sck_pin = GPIO_PIN_18,
        .sck_mux = ALT1_FUNCTION,
        .mosi_port = GPIOA,
        .mosi_pin = GPIO_PIN_16,
        .mosi_mux = ALT1_FUNCTION,
        .miso_port = GPIOA,
        .miso_pin = GPIO_PIN_17,
        .miso_mux = ALT1_FUNCTION,
    },
#endif
#ifdef BSP_USING_SPI2
    {
        .name = "spi2",
        .Instance = SPI2,
        .rx_irq_type = SPI2_RX_IRQn,
        .tx_irq_type = SPI2_TX_IRQn,
        .sck_port = GPIOB,
        .sck_pin = GPIO_PIN_0,
        .sck_mux = ALT9_FUNCTION,
        .mosi_port = GPIOB,
        .mosi_pin = GPIO_PIN_1,
        .mosi_mux = ALT9_FUNCTION,
        .miso_port = GPIOB,
        .miso_pin = GPIO_PIN_2,
        .miso_mux = ALT9_FUNCTION,
    },
#endif
#ifdef BSP_USING_SPI3
    {
        .name = "spi3",
        .Instance = SPI3,
        .rx_irq_type = SPI3_RX_IRQn,
        .tx_irq_type = SPI3_TX_IRQn,
        .sck_port = GPIOC,
        .sck_pin = GPIO_PIN_0,
        .sck_mux = ALT7_FUNCTION,
        .mosi_port = GPIOC,
        .mosi_pin = GPIO_PIN_1,
        .mosi_mux = ALT7_FUNCTION,
        .miso_port = GPIOC,
        .miso_pin = GPIO_PIN_2,
        .miso_mux = ALT7_FUNCTION,
    },
#endif
#ifdef BSP_USING_SPI4
    {
        .name = "spi4",
        .Instance = SPI4,
        .rx_irq_type = SPI4_RX_IRQn,
        .tx_irq_type = SPI4_TX_IRQn,
        .sck_port = GPIOC,
        .sck_pin = GPIO_PIN_4,
        .sck_mux = ALT7_FUNCTION,
        .mosi_port = GPIOC,
        .mosi_pin = GPIO_PIN_5,
        .mosi_mux = ALT7_FUNCTION,
        .miso_port = GPIOC,
        .miso_pin = GPIO_PIN_6,
        .miso_mux = ALT7_FUNCTION,
    },
#endif
};

static struct ns800_spi spi_obj[sizeof(spi_config) / sizeof(spi_config[0])] = {0};

static void ns800_spi_gpio_init(const struct ns800_spi_config *config)
{
    RT_ASSERT(config != RT_NULL);

    GPIO_setPinConfig(config->sck_port, config->sck_pin, config->sck_mux);
    GPIO_setAnalogMode(config->sck_port, config->sck_pin, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(config->sck_port, config->sck_pin, GPIO_PIN_TYPE_STD);
    GPIO_setQualificationMode(config->sck_port, config->sck_pin, GPIO_QUAL_SYNC);
    GPIO_setDirectionMode(config->sck_port, config->sck_pin, GPIO_DIR_MODE_OUT);

    GPIO_setPinConfig(config->mosi_port, config->mosi_pin, config->mosi_mux);
    GPIO_setAnalogMode(config->mosi_port, config->mosi_pin, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(config->mosi_port, config->mosi_pin, GPIO_PIN_TYPE_STD);
    GPIO_setQualificationMode(config->mosi_port, config->mosi_pin, GPIO_QUAL_SYNC);
    GPIO_setDirectionMode(config->mosi_port, config->mosi_pin, GPIO_DIR_MODE_OUT);

    GPIO_setPinConfig(config->miso_port, config->miso_pin, config->miso_mux);
    GPIO_setAnalogMode(config->miso_port, config->miso_pin, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(config->miso_port, config->miso_pin, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(config->miso_port, config->miso_pin, GPIO_QUAL_SYNC);
    GPIO_setDirectionMode(config->miso_port, config->miso_pin, GPIO_DIR_MODE_IN);
}

static rt_err_t ns800_spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration)
{
    struct ns800_spi *spi;
    SPI_TransferProtocol protocol;
    SPI_BitWidth data_width;

    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(configuration != RT_NULL);

    spi = rt_container_of(device->bus, struct ns800_spi, spi_bus);

    ns800_spi_gpio_init(spi->config);

    switch (configuration->mode & RT_SPI_MODE_3)
    {
    case RT_SPI_MODE_0:
        protocol = SPI_PROT_POL0PHA0;
        break;
    case RT_SPI_MODE_1:
        protocol = SPI_PROT_POL0PHA1;
        break;
    case RT_SPI_MODE_2:
        protocol = SPI_PROT_POL1PHA0;
        break;
    case RT_SPI_MODE_3:
        protocol = SPI_PROT_POL1PHA1;
        break;
    default:
        protocol = SPI_PROT_POL0PHA0;
        break;
    }

    switch (configuration->data_width)
    {
    case 8:
        data_width = SPI_BIT_WIDTH_8_BITS;
        break;
    case 16:
        data_width = SPI_BIT_WIDTH_16_BITS;
        break;
    default:
        data_width = SPI_BIT_WIDTH_8_BITS;
        break;
    }

    SPI_disableModule(spi->config->Instance);

    SPI_setConfig(spi->config->Instance,
                  protocol,
                  SPI_MASTER_MODE,
                  SPI_FULL_DUPLEX_COMM_MODE,
                  configuration->max_hz,
                  data_width);

    SPI_resetFifo(spi->config->Instance);
    SPI_enableModule(spi->config->Instance);

    return RT_EOK;
}

static rt_ssize_t ns800_spi_xfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    struct ns800_spi *spi;
    SPI_TypeDef *instance;
    const rt_uint8_t *send_buf;
    rt_uint8_t *recv_buf;
    rt_size_t length;

    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(device->bus != RT_NULL);

    spi = rt_container_of(device->bus, struct ns800_spi, spi_bus);
    instance = spi->config->Instance;
    length = message->length;
    send_buf = message->send_buf;
    recv_buf = message->recv_buf;

    if (message->cs_take)
    {
        rt_pin_write(device->cs_pin, PIN_LOW);
    }

    while (length)
    {
        if (send_buf)
        {
            if (recv_buf)
            {
                recv_buf[0] = SPI_transmitReceive(instance, send_buf[0]);
                recv_buf++;
            }
            else
            {
                SPI_transmitReceive(instance, send_buf[0]);
            }
            send_buf++;
        }
        else
        {
            if (recv_buf)
            {
                recv_buf[0] = SPI_transmitReceive(instance, 0xFF);
                recv_buf++;
            }
            else
            {
                SPI_transmitReceive(instance, 0xFF);
            }
        }
        length--;
    }

    if (message->cs_release)
    {
        rt_pin_write(device->cs_pin, PIN_HIGH);
    }

    return message->length;
}

static const struct rt_spi_ops ns800_spi_ops =
{
    .configure = ns800_spi_configure,
    .xfer = ns800_spi_xfer,
};

static void ns800_spi_clock_init(SPI_TypeDef *Instance)
{
#ifdef BSP_USING_SPI1
    if (Instance == SPI1)
    {
        RCC_enableSpi1Clock();
        RCC_resetSpi1Module();
        RCC_releaseSpi1Module();
    }
#endif
#ifdef BSP_USING_SPI2
    if (Instance == SPI2)
    {
        RCC_enableSpi2Clock();
        RCC_resetSpi2Module();
        RCC_releaseSpi2Module();
    }
#endif
#ifdef BSP_USING_SPI3
    if (Instance == SPI3)
    {
        RCC_enableSpi3Clock();
        RCC_resetSpi3Module();
        RCC_releaseSpi3Module();
    }
#endif
#ifdef BSP_USING_SPI4
    if (Instance == SPI4)
    {
        RCC_enableSpi4Clock();
        RCC_resetSpi4Module();
        RCC_releaseSpi4Module();
    }
#endif
}

int rt_hw_spi_init(void)
{
    rt_size_t i;
    rt_err_t result;

    for (i = 0; i < sizeof(spi_config) / sizeof(spi_config[0]); i++)
    {
        ns800_spi_clock_init(spi_config[i].Instance);

        spi_obj[i].config = &spi_config[i];
        spi_obj[i].spi_bus.parent.user_data = &spi_config[i];

        result = rt_spi_bus_register(&spi_obj[i].spi_bus,
                                      spi_config[i].name,
                                      &ns800_spi_ops);
        if (result != RT_EOK)
        {
            LOG_E("rt_spi_bus_register(%s) failed: %d", spi_config[i].name, result);
        }
    }

    return RT_EOK;
}

INIT_BOARD_EXPORT(rt_hw_spi_init);

#endif


drv_spi.h

/*
 * Copyright (c) 2006-2026, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2026-05-12     Codex        first version
 */

#ifndef __DRV_SPI_H__
#define __DRV_SPI_H__

#include <rtthread.h>
#include "rtdevice.h"
#include <rthw.h>
#include <drv_common.h>

#ifdef RT_USING_SPI

struct ns800_spi_config
{
    const char *name;
    SPI_TypeDef *Instance;
    IRQn_Type rx_irq_type;
    IRQn_Type tx_irq_type;
    GPIO_TypeDef *sck_port;
    GPIO_PinNum sck_pin;
    GPIO_AltFunc sck_mux;
    GPIO_TypeDef *mosi_port;
    GPIO_PinNum mosi_pin;
    GPIO_AltFunc mosi_mux;
    GPIO_TypeDef *miso_port;
    GPIO_PinNum miso_pin;
    GPIO_AltFunc miso_mux;
};

struct ns800_spi
{
    SPI_TypeDef *Instance;
    struct rt_spi_bus spi_bus;
    struct ns800_spi_config *config;
};

int rt_hw_spi_init(void);

#endif

#endif /* __DRV_SPI_H__ */


七、纳芯微NS800RT7P65D上的SPI实践(张工)

作者:张工

原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的QSPI实践

源文章:https://club.rt-thread.org/ask/article/58f1c25ed63721ef.html

1.前言

从手册中可以看到该芯片有一路qspi,4路spi,由于spi驱动已经适配好了,所以本次适配纳芯微NS800RT7P65D芯片的qspi驱动。

2.准备工作

拉取当前rt-thread的最新sdk,最新的sdk中才有纳芯微的工程,使用命令git pull 即可。在bsp路径下会增加novosns文件夹,里面就是纳芯微的rt-thread 的适配工程。

使用env工具生成mdk工程,由于我的环境不知道什么原因,env工具生成不了keil工程。最终没有解决掉,找群友发了一个工程进行qspi驱动的适配。

3.适配过程

1、创建drv_qspi.h 和 drv_qspi.c这两个文件,

在drv_qspi.c中添加qspi的引脚初始化

static void qspi_pin_init(void)
{
    GPIO_setPinConfig(GPIO_15_QSPI_NCS);
    GPIO_setAnalogMode(GPIO_15, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(GPIO_15, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(GPIO_15, GPIO_QUAL_ASYNC);

    GPIO_setPinConfig(GPIO_16_QSPI_D0);
    GPIO_setAnalogMode(GPIO_16, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(GPIO_16, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(GPIO_16, GPIO_QUAL_ASYNC);
    GPIO_setDirectionMode(GPIO_16, GPIO_DIR_MODE_OUT);

    GPIO_setPinConfig(GPIO_17_QSPI_D1);
    GPIO_setAnalogMode(GPIO_17, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(GPIO_17, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(GPIO_17, GPIO_QUAL_ASYNC);
    GPIO_setDirectionMode(GPIO_17, GPIO_DIR_MODE_IN);

    GPIO_setPinConfig(GPIO_18_QSPI_D2);
    GPIO_setAnalogMode(GPIO_18, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(GPIO_18, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(GPIO_18, GPIO_QUAL_ASYNC);
    GPIO_setDirectionMode(GPIO_18, GPIO_DIR_MODE_IN);

    GPIO_setPinConfig(GPIO_20_QSPI_D3);
    GPIO_setAnalogMode(GPIO_20, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(GPIO_20, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(GPIO_20, GPIO_QUAL_ASYNC);
    GPIO_setDirectionMode(GPIO_20, GPIO_DIR_MODE_IN);

    GPIO_setPinConfig(GPIO_21_QSPI_SCLK);
    GPIO_setAnalogMode(GPIO_21, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(GPIO_21, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(GPIO_21, GPIO_QUAL_ASYNC);
}


2、添加qspi的时钟初始化

static void qspi_clock_enable(void)
{
    RCC_unlockRccRegister();
    RCC_enableQspiClock();
    RCC_resetQspiModule();
    RCC_releaseQspiModule();
    RCC_lockRccRegister();
}


3、qspi的初始化

static void qspi_controller_init(void)
{
    QSPI_open(EXTENDED_SPI_PROTOCOL,
              HLCK_DIV_48,
              ADDRESS_3_BYTES,
              DEFAULT_DUMMY_CYCLE,
              HIGH_LVL_4_QSCK,
              FAST_READ_QUAD_IN_OUT);
}


4、qspi的设备接口

static rt_err_t qspi_configure(struct rt_spi_device *device,
                               struct rt_spi_configuration *cfg)
{
    device->config = *cfg;
    return RT_EOK;
}


5、核心数据传输回调函数实现

static rt_ssize_t qspi_xfer(struct rt_spi_device *device,
                            struct rt_spi_message *message)
{
    struct rt_qspi_message *qspi_msg = rt_container_of(message, struct rt_qspi_message, parent);
    uint8_t *send_buf = (uint8_t *)message->send_buf;
    uint8_t *recv_buf = (uint8_t *)message->recv_buf;
    uint32_t send_len = (send_buf != NULL) ? message->length : 0;
    uint32_t recv_len = (recv_buf != NULL) ? message->length : 0;

    /* Build command buffer (instruction + address + dummy) */
    uint8_t cmd_buf[260];
    uint8_t *ptr = cmd_buf;

    if (qspi_msg->instruction.content)
        *ptr++ = qspi_msg->instruction.content;

    if (qspi_msg->address.size) {
        uint32_t addr = qspi_msg->address.content;
        uint8_t addr_bytes = qspi_msg->address.size / 8;
        for (uint8_t i = addr_bytes; i > 0; i--)
            *ptr++ = (addr >> (8*(i-1))) & 0xFF;
    }

    if (qspi_msg->dummy_cycles) {
        uint8_t dummy_bytes = qspi_msg->dummy_cycles / 8;
        for (uint8_t i = 0; i < dummy_bytes; i++)
            *ptr++ = 0xFF;
    }

    uint32_t cmd_len = ptr - cmd_buf;

    /* Execute transfer using direct API */
    if (send_len && recv_len) {
        /* Half-duplex not supported in this driver */
        return 0;
    }

    if (send_len) {
        /* Append send data to command buffer */
        if (cmd_len + send_len <= sizeof(cmd_buf)) {
            rt_memcpy(ptr, send_buf, send_len);
            QSPI_writeDirect(cmd_buf, cmd_len + send_len, 0);
        } else {
            /* Not enough buffer, split into two writes? Not needed for normal use */
            return 0;
        }
    } else if (recv_len) {
        /* Write command, keep CS active, then read data */
        QSPI_writeDirect(cmd_buf, cmd_len, 1);
        QSPI_readDirect(recv_buf, recv_len);
    } else {
        /* No data phase, just send command */
        QSPI_writeDirect(cmd_buf, cmd_len, 0);
    }

    return message->length;
}


6、自动初始化qspi

int rt_hw_qspi_init(void)
{
    rt_kprintf("Initializing QSPI...\n");
    qspi_clock_enable();
    qspi_pin_init();
    qspi_controller_init();

    qspi_cfg.parent.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
    qspi_cfg.parent.max_hz = 25000000;
    qspi_cfg.parent.data_width = 8;
    qspi_cfg.medium_size = 0x1000000;
    qspi_cfg.ddr_mode = 0;
    qspi_cfg.qspi_dl_width = 0;

    rt_qspi_bus_register(&qspi_bus, "qspi0", &qspi_ops);

    qspi_dev.parent.bus = &qspi_bus;
    qspi_dev.parent.config = qspi_cfg.parent;
    qspi_dev.config = qspi_cfg;
    rt_spi_bus_attach_device(&qspi_dev.parent, "qspi_dev", "qspi0", NULL);

    return 0;
}

INIT_DEVICE_EXPORT(rt_hw_qspi_init);


7、当前通过qpsi接口外接了w25q128 flash,用于测试qspi,操作命令定义

#define CMD_READ_STATUS     0x05
#define CMD_WRITE_ENABLE    0x06
#define CMD_SECTOR_ERASE    0x20
#define CMD_PAGE_PROGRAM    0x02
#define CMD_READ_DATA       0x03

#define TEST_ADDR           0x001FF000
#define TEST_SIZE           256


8、qspi等待准备完成

static void qspi_wait_ready(struct rt_qspi_device *qspi)
{
    struct rt_qspi_message msg = {0};
    uint8_t status;
    msg.instruction.content = CMD_READ_STATUS;
    msg.instruction.qspi_lines = 1;
    msg.address.size = 0;
    msg.dummy_cycles = 0;
    msg.qspi_data_lines = 1;
    msg.parent.send_buf = RT_NULL;
    msg.parent.recv_buf = &status;
    msg.parent.length = 1;
    msg.parent.cs_take = 1;
    msg.parent.cs_release = 1;

    do {
        if (rt_qspi_transfer_message(qspi, &msg) != 1) {
            rt_kprintf("Read status error\n");
            return;
        }
        if (status & 0x01) rt_thread_mdelay(1);
    } while (status & 0x01);
}


9、qspi写使能

static void qspi_write_enable(struct rt_qspi_device *qspi)
{
    struct rt_qspi_message msg = {0};
    msg.instruction.content = CMD_WRITE_ENABLE;
    msg.instruction.qspi_lines = 1;
    msg.parent.send_buf = RT_NULL;
    msg.parent.length = 0;
    msg.parent.cs_take = 1;
    msg.parent.cs_release = 1;
    rt_qspi_transfer_message(qspi, &msg);
}


10、qspi_erase_sector

static int qspi_erase_sector(struct rt_qspi_device *qspi, uint32_t addr)
{
    struct rt_qspi_message msg = {0};
    uint8_t cmd[4];
    cmd[0] = CMD_SECTOR_ERASE;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;
    qspi_write_enable(qspi);
    msg.parent.send_buf = cmd;
    msg.parent.length = 4;
    msg.parent.cs_take = 1;
    msg.parent.cs_release = 1;
    if (rt_qspi_transfer_message(qspi, &msg) != 4)
        return -1;
    qspi_wait_ready(qspi);
    return 0;
}


11、qspi_page_program

static int qspi_page_program(struct rt_qspi_device *qspi, uint32_t addr,
                             const uint8_t *data, uint32_t len)
{
    struct rt_qspi_message msg = {0};
    uint8_t cmd[4 + 256];
    if (len > 256) len = 256;
    cmd[0] = CMD_PAGE_PROGRAM;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;
    rt_memcpy(cmd + 4, data, len);
    qspi_write_enable(qspi);
    msg.parent.send_buf = cmd;
    msg.parent.length = 4 + len;
    msg.parent.cs_take = 1;
    msg.parent.cs_release = 1;
    if (rt_qspi_transfer_message(qspi, &msg) != 4 + len)
        return -1;
    qspi_wait_ready(qspi);
    return len;
}


12、qspi读数据

static int qspi_read_data(struct rt_qspi_device *qspi, uint32_t addr,
                          uint8_t *buf, uint32_t len)
{
    struct rt_qspi_message msg = {0};
    msg.instruction.content = CMD_READ_DATA;
    msg.instruction.qspi_lines = 1;
    msg.address.content = addr;
    msg.address.size = 24;
    msg.address.qspi_lines = 1;
    msg.dummy_cycles = 0;
    msg.qspi_data_lines = 1;
    msg.parent.send_buf = RT_NULL;
    msg.parent.recv_buf = buf;
    msg.parent.length = len;
    msg.parent.cs_take = 1;
    msg.parent.cs_release = 1;
    return (rt_qspi_transfer_message(qspi, &msg) == len) ? 0 : -1;
}


13、qspi读ID

static uint32_t qspi_read_jedec_id(struct rt_qspi_device *qspi)
{
    struct rt_qspi_message msg = {0};
    uint8_t id[3] = {0};
    msg.instruction.content = 0x9F;
    msg.instruction.qspi_lines = 1;
    msg.address.size = 0;
    msg.dummy_cycles = 0;
    msg.qspi_data_lines = 1;
    msg.parent.send_buf = RT_NULL;
    msg.parent.recv_buf = id;
    msg.parent.length = 3;
    msg.parent.cs_take = 1;
    msg.parent.cs_release = 1;
    if (rt_qspi_transfer_message(qspi, &msg) == 3)
        return (id[0] << 16) | (id[1] << 8) | id[2];
    return 0;
}


14、编写qspi驱动测试代码,并导入命令

void qspi_test(void)
{
    struct rt_device *dev = rt_device_find("qspi_dev");
    if (!dev) {
        rt_kprintf("QSPI device not found!\n");
        return;
    }
    struct rt_qspi_device *qspi = (struct rt_qspi_device *)dev;

    rt_kprintf("\n========== QSPI Test ==========\n");
    uint32_t id = qspi_read_jedec_id(qspi);
    rt_kprintf("JEDEC ID : 0x%06X\n", id);
    if (id != 0xEF4018) {
        rt_kprintf("Unexpected ID, check hardware or driver.\n");
        return;
    }

    uint8_t write_buf[TEST_SIZE];
    uint8_t read_buf[TEST_SIZE];
    for (int i = 0; i < TEST_SIZE; i++)
        write_buf[i] = i & 0xFF;

    rt_kprintf("Erasing sector at 0x%08X... ", TEST_ADDR);
    if (qspi_erase_sector(qspi, TEST_ADDR) != 0) {
        rt_kprintf("FAILED\n");
        return;
    }
    rt_kprintf("OK\n");

    rt_kprintf("Writing %d bytes... ", TEST_SIZE);
    if (qspi_page_program(qspi, TEST_ADDR, write_buf, TEST_SIZE) != TEST_SIZE) {
        rt_kprintf("FAILED\n");
        return;
    }
    rt_kprintf("OK\n");

    rt_kprintf("Reading back... ");
    if (qspi_read_data(qspi, TEST_ADDR, read_buf, TEST_SIZE) != 0) {
        rt_kprintf("FAILED\n");
        return;
    }
    rt_kprintf("OK\n");

    if (rt_memcmp(write_buf, read_buf, TEST_SIZE) == 0) {
        rt_kprintf("Data verification: PASSED\n");
    } else {
        rt_kprintf("Data verification: FAILED\n");
    }
    rt_kprintf("========== Test Finished ==========\n");
}
MSH_CMD_EXPORT(qspi_test, QSPI test);


15、配置文件更改,SConscript添加编译条件

if GetDepend('BSP_USING_QSPI'):
    src += ['drv_qspi.c']


16、配置文件更改,drivers/Kconfig - QSPI 外设配置

menuconfig BSP_USING_QSPI
    bool "Enable SPI"
    select RT_USING_QSPI
    default n
    endif


17、配置文件更改,board/Kconfig - 添加 QSPI menu

menuconfig BSP_USING_QSPI
    bool "Enable QSPI"
    select RT_USING_QSPI
    default n


18、rtconfig.h添加宏定义

#define RT_USING_QSPI


4.验证

代码修改好后,编译完成升级,查看输出信息,qspi初始化正常

在终端中输入list device,qspi设备初始化正常

在终端中输入help命令,qspi的测试代码初始化正常,可以看到测试命令

在终端中输入qspi的测试命令:qspi_test,输出以下字符,测试正常。

5.附件

完整代码:

1、drv_qspi.h

#ifndef __DRV_QSPI_H__
#define __DRV_QSPI_H__

#ifdef RT_USING_QSPI

#include <rtthread.h>
#include <rtdevice.h>

#ifdef __cplusplus
extern "C" {
#endif

int rt_hw_qspi_init(void);

#ifdef __cplusplus
}
#endif

#endif

#endif /* __DRV_QSPI_H__ */


2、drv_qspi.c

/*
 * QSPI driver for NS800RT7xxx + W25Q128
 * Fully supports RT-Thread QSPI (rt_qspi_transfer_message)
 * All transfers use QSPI_writeDirect / QSPI_readDirect (same as working direct API)
 */

#include <rtthread.h>
#include <rtdevice.h>
#include "qspi.h"
#include "board.h"

#ifdef RT_USING_QSPI

/*===========================================================================
 * GPIO initialization
 *===========================================================================*/
static void qspi_pin_init(void)
{
    GPIO_setPinConfig(GPIO_15_QSPI_NCS);
    GPIO_setAnalogMode(GPIO_15, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(GPIO_15, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(GPIO_15, GPIO_QUAL_ASYNC);

    GPIO_setPinConfig(GPIO_16_QSPI_D0);
    GPIO_setAnalogMode(GPIO_16, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(GPIO_16, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(GPIO_16, GPIO_QUAL_ASYNC);
    GPIO_setDirectionMode(GPIO_16, GPIO_DIR_MODE_OUT);

    GPIO_setPinConfig(GPIO_17_QSPI_D1);
    GPIO_setAnalogMode(GPIO_17, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(GPIO_17, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(GPIO_17, GPIO_QUAL_ASYNC);
    GPIO_setDirectionMode(GPIO_17, GPIO_DIR_MODE_IN);

    GPIO_setPinConfig(GPIO_18_QSPI_D2);
    GPIO_setAnalogMode(GPIO_18, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(GPIO_18, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(GPIO_18, GPIO_QUAL_ASYNC);
    GPIO_setDirectionMode(GPIO_18, GPIO_DIR_MODE_IN);

    GPIO_setPinConfig(GPIO_20_QSPI_D3);
    GPIO_setAnalogMode(GPIO_20, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(GPIO_20, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(GPIO_20, GPIO_QUAL_ASYNC);
    GPIO_setDirectionMode(GPIO_20, GPIO_DIR_MODE_IN);

    GPIO_setPinConfig(GPIO_21_QSPI_SCLK);
    GPIO_setAnalogMode(GPIO_21, GPIO_ANALOG_DISABLED);
    GPIO_setPadConfig(GPIO_21, GPIO_PIN_TYPE_PULLUP);
    GPIO_setQualificationMode(GPIO_21, GPIO_QUAL_ASYNC);
}

/*===========================================================================
 * QSPI clock & controller initialization
 *===========================================================================*/
static void qspi_clock_enable(void)
{
    RCC_unlockRccRegister();
    RCC_enableQspiClock();
    RCC_resetQspiModule();
    RCC_releaseQspiModule();
    RCC_lockRccRegister();
}

static void qspi_controller_init(void)
{
    QSPI_open(EXTENDED_SPI_PROTOCOL,
              HLCK_DIV_48,
              ADDRESS_3_BYTES,
              DEFAULT_DUMMY_CYCLE,
              HIGH_LVL_4_QSCK,
              FAST_READ_QUAD_IN_OUT);
}

/*===========================================================================
 * RT-Thread QSPI device interface
 *===========================================================================*/
static struct rt_spi_bus qspi_bus;
static struct rt_qspi_device qspi_dev;
static struct rt_qspi_configuration qspi_cfg;

static rt_err_t qspi_configure(struct rt_spi_device *device,
                               struct rt_spi_configuration *cfg)
{
    device->config = *cfg;
    return RT_EOK;
}

static rt_ssize_t qspi_xfer(struct rt_spi_device *device,
                            struct rt_spi_message *message)
{
    struct rt_qspi_message *qspi_msg = rt_container_of(message, struct rt_qspi_message, parent);
    uint8_t *send_buf = (uint8_t *)message->send_buf;
    uint8_t *recv_buf = (uint8_t *)message->recv_buf;
    uint32_t send_len = (send_buf != NULL) ? message->length : 0;
    uint32_t recv_len = (recv_buf != NULL) ? message->length : 0;

    /* Build command buffer (instruction + address + dummy) */
    uint8_t cmd_buf[260];
    uint8_t *ptr = cmd_buf;

    if (qspi_msg->instruction.content)
        *ptr++ = qspi_msg->instruction.content;

    if (qspi_msg->address.size) {
        uint32_t addr = qspi_msg->address.content;
        uint8_t addr_bytes = qspi_msg->address.size / 8;
        for (uint8_t i = addr_bytes; i > 0; i--)
            *ptr++ = (addr >> (8*(i-1))) & 0xFF;
    }

    if (qspi_msg->dummy_cycles) {
        uint8_t dummy_bytes = qspi_msg->dummy_cycles / 8;
        for (uint8_t i = 0; i < dummy_bytes; i++)
            *ptr++ = 0xFF;
    }

    uint32_t cmd_len = ptr - cmd_buf;

    /* Execute transfer using direct API */
    if (send_len && recv_len) {
        /* Half-duplex not supported in this driver */
        return 0;
    }

    if (send_len) {
        /* Append send data to command buffer */
        if (cmd_len + send_len <= sizeof(cmd_buf)) {
            rt_memcpy(ptr, send_buf, send_len);
            QSPI_writeDirect(cmd_buf, cmd_len + send_len, 0);
        } else {
            /* Not enough buffer, split into two writes? Not needed for normal use */
            return 0;
        }
    } else if (recv_len) {
        /* Write command, keep CS active, then read data */
        QSPI_writeDirect(cmd_buf, cmd_len, 1);
        QSPI_readDirect(recv_buf, recv_len);
    } else {
        /* No data phase, just send command */
        QSPI_writeDirect(cmd_buf, cmd_len, 0);
    }

    return message->length;
}

static const struct rt_spi_ops qspi_ops = {
    .configure = qspi_configure,
    .xfer = qspi_xfer,
};

/*===========================================================================
 * Initialization
 *===========================================================================*/
int rt_hw_qspi_init(void)
{
    rt_kprintf("Initializing QSPI...\n");
    qspi_clock_enable();
    qspi_pin_init();
    qspi_controller_init();

    qspi_cfg.parent.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
    qspi_cfg.parent.max_hz = 25000000;
    qspi_cfg.parent.data_width = 8;
    qspi_cfg.medium_size = 0x1000000;
    qspi_cfg.ddr_mode = 0;
    qspi_cfg.qspi_dl_width = 0;

    rt_qspi_bus_register(&qspi_bus, "qspi0", &qspi_ops);

    qspi_dev.parent.bus = &qspi_bus;
    qspi_dev.parent.config = qspi_cfg.parent;
    qspi_dev.config = qspi_cfg;
    rt_spi_bus_attach_device(&qspi_dev.parent, "qspi_dev", "qspi0", NULL);

    return 0;
}

INIT_DEVICE_EXPORT(rt_hw_qspi_init);

#endif


3、测试代码qspi_test.c

#include <rtthread.h>
#include <rtdevice.h>

#ifdef RT_USING_QSPI

#define CMD_READ_STATUS     0x05
#define CMD_WRITE_ENABLE    0x06
#define CMD_SECTOR_ERASE    0x20
#define CMD_PAGE_PROGRAM    0x02
#define CMD_READ_DATA       0x03

#define TEST_ADDR           0x001FF000
#define TEST_SIZE           256

static void qspi_wait_ready(struct rt_qspi_device *qspi)
{
    struct rt_qspi_message msg = {0};
    uint8_t status;
    msg.instruction.content = CMD_READ_STATUS;
    msg.instruction.qspi_lines = 1;
    msg.address.size = 0;
    msg.dummy_cycles = 0;
    msg.qspi_data_lines = 1;
    msg.parent.send_buf = RT_NULL;
    msg.parent.recv_buf = &status;
    msg.parent.length = 1;
    msg.parent.cs_take = 1;
    msg.parent.cs_release = 1;

    do {
        if (rt_qspi_transfer_message(qspi, &msg) != 1) {
            rt_kprintf("Read status error\n");
            return;
        }
        if (status & 0x01) rt_thread_mdelay(1);
    } while (status & 0x01);
}

static void qspi_write_enable(struct rt_qspi_device *qspi)
{
    struct rt_qspi_message msg = {0};
    msg.instruction.content = CMD_WRITE_ENABLE;
    msg.instruction.qspi_lines = 1;
    msg.parent.send_buf = RT_NULL;
    msg.parent.length = 0;
    msg.parent.cs_take = 1;
    msg.parent.cs_release = 1;
    rt_qspi_transfer_message(qspi, &msg);
}

static int qspi_erase_sector(struct rt_qspi_device *qspi, uint32_t addr)
{
    struct rt_qspi_message msg = {0};
    uint8_t cmd[4];
    cmd[0] = CMD_SECTOR_ERASE;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;
    qspi_write_enable(qspi);
    msg.parent.send_buf = cmd;
    msg.parent.length = 4;
    msg.parent.cs_take = 1;
    msg.parent.cs_release = 1;
    if (rt_qspi_transfer_message(qspi, &msg) != 4)
        return -1;
    qspi_wait_ready(qspi);
    return 0;
}

static int qspi_page_program(struct rt_qspi_device *qspi, uint32_t addr,
                             const uint8_t *data, uint32_t len)
{
    struct rt_qspi_message msg = {0};
    uint8_t cmd[4 + 256];
    if (len > 256) len = 256;
    cmd[0] = CMD_PAGE_PROGRAM;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;
    rt_memcpy(cmd + 4, data, len);
    qspi_write_enable(qspi);
    msg.parent.send_buf = cmd;
    msg.parent.length = 4 + len;
    msg.parent.cs_take = 1;
    msg.parent.cs_release = 1;
    if (rt_qspi_transfer_message(qspi, &msg) != 4 + len)
        return -1;
    qspi_wait_ready(qspi);
    return len;
}

static int qspi_read_data(struct rt_qspi_device *qspi, uint32_t addr,
                          uint8_t *buf, uint32_t len)
{
    struct rt_qspi_message msg = {0};
    msg.instruction.content = CMD_READ_DATA;
    msg.instruction.qspi_lines = 1;
    msg.address.content = addr;
    msg.address.size = 24;
    msg.address.qspi_lines = 1;
    msg.dummy_cycles = 0;
    msg.qspi_data_lines = 1;
    msg.parent.send_buf = RT_NULL;
    msg.parent.recv_buf = buf;
    msg.parent.length = len;
    msg.parent.cs_take = 1;
    msg.parent.cs_release = 1;
    return (rt_qspi_transfer_message(qspi, &msg) == len) ? 0 : -1;
}

static uint32_t qspi_read_jedec_id(struct rt_qspi_device *qspi)
{
    struct rt_qspi_message msg = {0};
    uint8_t id[3] = {0};
    msg.instruction.content = 0x9F;
    msg.instruction.qspi_lines = 1;
    msg.address.size = 0;
    msg.dummy_cycles = 0;
    msg.qspi_data_lines = 1;
    msg.parent.send_buf = RT_NULL;
    msg.parent.recv_buf = id;
    msg.parent.length = 3;
    msg.parent.cs_take = 1;
    msg.parent.cs_release = 1;
    if (rt_qspi_transfer_message(qspi, &msg) == 3)
        return (id[0] << 16) | (id[1] << 8) | id[2];
    return 0;
}

void qspi_test(void)
{
    struct rt_device *dev = rt_device_find("qspi_dev");
    if (!dev) {
        rt_kprintf("QSPI device not found!\n");
        return;
    }
    struct rt_qspi_device *qspi = (struct rt_qspi_device *)dev;

    rt_kprintf("\n========== QSPI Test ==========\n");
    uint32_t id = qspi_read_jedec_id(qspi);
    rt_kprintf("JEDEC ID : 0x%06X\n", id);
    if (id != 0xEF4018) {
        rt_kprintf("Unexpected ID, check hardware or driver.\n");
        return;
    }

    uint8_t write_buf[TEST_SIZE];
    uint8_t read_buf[TEST_SIZE];
    for (int i = 0; i < TEST_SIZE; i++)
        write_buf[i] = i & 0xFF;

    rt_kprintf("Erasing sector at 0x%08X... ", TEST_ADDR);
    if (qspi_erase_sector(qspi, TEST_ADDR) != 0) {
        rt_kprintf("FAILED\n");
        return;
    }
    rt_kprintf("OK\n");

    rt_kprintf("Writing %d bytes... ", TEST_SIZE);
    if (qspi_page_program(qspi, TEST_ADDR, write_buf, TEST_SIZE) != TEST_SIZE) {
        rt_kprintf("FAILED\n");
        return;
    }
    rt_kprintf("OK\n");

    rt_kprintf("Reading back... ");
    if (qspi_read_data(qspi, TEST_ADDR, read_buf, TEST_SIZE) != 0) {
        rt_kprintf("FAILED\n");
        return;
    }
    rt_kprintf("OK\n");

    if (rt_memcmp(write_buf, read_buf, TEST_SIZE) == 0) {
        rt_kprintf("Data verification: PASSED\n");
    } else {
        rt_kprintf("Data verification: FAILED\n");
    }
    rt_kprintf("========== Test Finished ==========\n");
}
MSH_CMD_EXPORT(qspi_test, QSPI test);

#endif


八、纳芯微NS800RT7P65D上的eCAP实践(程廷桢)

作者:程廷桢

原文标题:【纳芯微】纳芯微 NS800RT7P65 eCAP 模块应用笔记

源文章:https://club.rt-thread.org/ask/article/b122efd1dec8cf92.html

前言

NS800RT7P65 是纳芯微 NSSine™系列高性能实时控制 MCU,搭载 400MHz Cortex‑M7 内核,集成 7 路 ECAP(增强型捕获)模块,专为电机测速、脉冲周期 / 占空比测量、位置传感器信号采集等场景设计。本文基于 RT‑Thread 实时操作系统,从硬件特性、寄存器配置、RT‑Thread 驱动适配、实测验证、避坑总结五大维度,分享 ECAP 模块的开发与测评经验,为国产化电力电子、运动控制项目提供参考。

ECAP 驱动

NS800RT7P65 有7个 ECAP (增强型输入捕获)模块。用于精准捕获外部数字信号的边沿跳变、测量脉冲宽度、信号周期、占空比。

核心特色:内置 XBAR 交叉开关

  • ECAP 不绑定固定引脚,任意 GPIO 均可通过 XBAR 路由为 ECAP 输入

驱动文件

bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_ecap.c

硬件配置

驱动采用数组配置 + 条件编译管理多 ECAP 实例(ECAP1 ~ ECAP7),通过 BSP_USING_ECAPx 宏开关按需启用通道。初始化按照物理GPIO -> XBAR通道 -> ECAP 模块的顺序进行,默认对 ECAP 内部计数器进行 30*2 分频。

static const struct rt_ecap_config ecap_config[] =
{
#ifdef BSP_USING_ECAP1
    {
        .name         = "ecap1",
        .instance     = ECAP1,                                            /* 底层ECAP硬件外设基地址  */
        .irq_type     = ECAP1_IRQn,                                    /* 中断号 */
        .input_signal = ECAP_INPUT_XBAR_INPUT7,        /* ECAP 选择XBAR输入通道 */
        .input_xbar   = XBAR_INPUT7,                             /* XBAR 交叉开关通道号 */
        .input_source = GPIO_PIN_16,
        .pre_scaler   = 30U,                                                /* 实际预分频是 30*2 */
        .gpio_port    = GPIOA,
        .gpio_pin     = GPIO_PIN_16,
        .gpio_mux     = ALT0_FUNCTION,
        .irq_handler  = ECAP1_IRQHandler
    },
#endif
}


边沿配置

ECAP 模块可以设置 4个事件,每个事件可以配置一个跳变沿。每个事件发生的时候会记录相对应的定时器计数。比如说一个方波信号,起始信号为高电平,当方波信号跳变到下降沿时,产生Event1,并记录当前计数器的计数值。
驱动固定配置连续捕获模式,4 个事件对应边沿如下::

事件编号

触发边沿

触发时机说明

Event_1

下降沿 Falling

第 1 次电平由高→低

Event_2

上降沿 Rising

第 1 次电平由低→高

Event_3

下降沿 Falling

第 2 次电平由高→低

Event_4

上降沿 Rising

第 2 次电平由低→高

// 工作模式:连续捕获模式,以 EVENT4 为一轮结束点
ECAP_setCaptureMode(instance, ECAP_CONTINUOUS_CAPTURE_MODE, ECAP_EVENT_4);
// 每个事件触发后,计数器自动清零
ECAP_enableCounterResetOnEvent(instance, ECAP_EVENT_x);


计数器自动复位 :每一次边沿触发后,ECAP计数器清零重新计数

  • cap1 ~ cap4 ~ 存储的是 相邻两个边沿之间的计数值 ,而非绝对时间戳。

驱动中将 Event2 和 Event3 记为一个周期,period_total 可以通过计算得到所输入方波信号的周期。

cap->period_high  = cap->cap2;        // 高电平宽度
cap->period_low   = cap->cap3;        // 低电平宽度
cap->period_total = cap->cap2 + cap->cap3; // 信号总周期


注意:

rt_ecap_read 在获取不到由中断发出信号量时,会直接返回。也就意味着在应用层使用 rt_device_read 时需要判断读取的字节数来确认数据的实时性。

ECAP 应用

该代码是 RT-Thread 下 ECAP 驱动的上层应用示例 ,同时启用 ecap1ecap2 两路捕获,采用「中断回调 + 线程轮询读取」双方式获取捕获数据,打印脉冲 / 周期参数。

/*
 * Copyright (c) 2006-2026, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author           Notes
 * 2026-05-06     Jiawei.Deng      first version
 */

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include "drv_ecap.h"

/* defined the LED1 pin: GPIO_68 = PC4 */
#define LED1_PIN    PIN_NUM(GPIO_68)

#define ECAP_DEV        "ecap1"
#define ECAP_OTHER_DEV  "ecap2"

static struct rt_mutex ecap_lock;

typedef struct {
    rt_uint32_t cap1;
    rt_uint32_t cap2;
    rt_uint32_t cap3;
    rt_uint32_t cap4;
    rt_uint32_t period_low;
    rt_uint32_t period_high;
    rt_uint32_t period_total;
    rt_uint32_t status;

    rt_int32_t irq_cnt;

}usr_ecap_rx_struct;

void usr_ecap_rx_cb(struct rt_ecap_capture *capture, void *user_data)
{
    static rt_int32_t irq_cnt = 0;
    usr_ecap_rx_struct *usr_struct = (usr_ecap_rx_struct *)user_data;

    usr_struct->cap1 = capture->cap1;
    usr_struct->cap2 = capture->cap2;
    usr_struct->cap3 = capture->cap3;
    usr_struct->cap4 = capture->cap4;

    usr_struct->period_low = capture->period_low;
    usr_struct->period_high = capture->period_high;
    usr_struct->period_total = capture->period_total;
    usr_struct->status = capture->status;

    irq_cnt++;
    usr_struct->irq_cnt = irq_cnt;
}

void ecap_thread_entry(void *parg)
{
    usr_ecap_rx_struct usr_struct;
    rt_ssize_t val = 0;
    struct rt_device *ecap_dev = rt_device_find(ECAP_DEV);

    rt_kprintf("\n");

    while (1)
    {
        if (RT_NULL != ecap_dev) {
            rt_mutex_take(&ecap_lock, RT_WAITING_FOREVER);
            rt_memset(&usr_struct, sizeof(usr_struct), 0);
            val = rt_device_read (ecap_dev, 0, &usr_struct, sizeof(usr_struct));
            if (0 != val) {
                rt_kprintf("========= %s 第%d包 ==========\n", ECAP_DEV, ((usr_ecap_rx_struct *)parg)->irq_cnt);
                for (rt_int32_t i = 0; i < 4; i++) {
                    rt_kprintf("[cap%d]: %d\n", i+1, *(&usr_struct.cap1 + i));
                }

                rt_kprintf("[period_low]: %d\n", usr_struct.period_low);
                rt_kprintf("[period_high]: %d\n", usr_struct.period_high);
                rt_kprintf("[period_total]: %d\n", usr_struct.period_total);
                rt_kprintf("[status]: %d\n", usr_struct.status);

                rt_kprintf("=============================\n");
            }
            rt_mutex_release(&ecap_lock);
        }

        rt_thread_mdelay(20);
    }
}

void ecap_o_thread_entry(void *parg)
{
    usr_ecap_rx_struct usr_struct;
    rt_ssize_t val = 0;
    struct rt_device *ecap_dev = rt_device_find(ECAP_OTHER_DEV);

    rt_kprintf("\n");

    while (1)
    {
        if (RT_NULL != ecap_dev) {
            rt_mutex_take(&ecap_lock, RT_WAITING_FOREVER);
            rt_memset(&usr_struct, sizeof(usr_struct), 0);
            val = rt_device_read (ecap_dev, 0, &usr_struct, sizeof(usr_struct));
            if (0 != val) {
                rt_kprintf("========= %s 第%d包 ==========\n", ECAP_OTHER_DEV, ((usr_ecap_rx_struct *)parg)->irq_cnt);
                for (rt_int32_t i = 0; i < 4; i++) {
                    rt_kprintf("[cap%d]: %d\n", i+1, *(&usr_struct.cap1 + i));
                }

                rt_kprintf("[period_low]: %d\n", usr_struct.period_low);
                rt_kprintf("[period_high]: %d\n", usr_struct.period_high);
                rt_kprintf("[period_total]: %d\n", usr_struct.period_total);
                rt_kprintf("[status]: %d\n", usr_struct.status);

                rt_kprintf("=============================\n");
            }
            rt_mutex_release(&ecap_lock);
        }

        rt_thread_mdelay(20);
    }
}

int main(void)
{
    rt_thread_t tid;

    /* 打开 ecap 设备 */
    rt_err_t res = RT_ERROR;
    usr_ecap_rx_struct usr_struct = {0};
    usr_ecap_rx_struct usr_o_struct;
    struct rt_device *ecap_dev;
    struct rt_ecap_callback ecap_cb = {
        .callback = usr_ecap_rx_cb,
        .user_data = &usr_struct,
    };
    struct rt_ecap_callback ecap_o_cb = {
        .callback = usr_ecap_rx_cb,
        .user_data = &usr_o_struct,
    };

    rt_memset(&usr_struct, sizeof(usr_struct), 0);
    rt_memset(&usr_o_struct, sizeof(usr_o_struct), 0);
    rt_mutex_init(&ecap_lock, "elk", RT_IPC_FLAG_PRIO);

    ecap_dev = rt_device_find(ECAP_DEV);
    if (RT_NULL != ecap_dev) {
        res = rt_device_open(ecap_dev, RT_DEVICE_OFLAG_RDONLY);
        if (res != RT_EOK) {
            rt_kprintf("rt_device_open ecap failed\n");
        }
    }

    if (res == RT_EOK) {
        rt_device_control(ecap_dev, ECAP_CMD_SET_CALLBACK, &ecap_cb);
        rt_device_control(ecap_dev, ECAP_CMD_ENABLE_IRQ, RT_NULL);
        rt_device_control(ecap_dev, ECAP_CMD_ENABLE, RT_NULL);
    }

    /* 创建一个线程来读取 ecap 设备 */
    tid = rt_thread_create("ecap", ecap_thread_entry, &usr_struct, 512, 6, 20);
    if ((RT_NULL != tid) && (res == RT_EOK)) {
        rt_thread_startup(tid);
    }

    /* 第二个线程... */
    ecap_dev = rt_device_find(ECAP_OTHER_DEV);
    if (RT_NULL != ecap_dev) {
        res = rt_device_open(ecap_dev, RT_DEVICE_OFLAG_RDONLY);
        if (res != RT_EOK) {
            rt_kprintf("rt_device_open ecap failed\n");
        }
    }

    if (res == RT_EOK) {
        rt_device_control(ecap_dev, ECAP_CMD_SET_CALLBACK, &ecap_o_cb);
        rt_device_control(ecap_dev, ECAP_CMD_ENABLE_IRQ, RT_NULL);
        rt_device_control(ecap_dev, ECAP_CMD_ENABLE, RT_NULL);
    }

    tid = rt_thread_create("ecap_o", ecap_o_thread_entry, &usr_o_struct, 512, 7, 20);
    if ((RT_NULL != tid) && (res == RT_EOK)) {
        rt_thread_startup(tid);
    }

    /* 指示灯闪烁 */
    rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);

    while (1)
    {
/*        rt_kprintf("\r\n led1_thread_entry running! \r\n"); */
        rt_pin_write(LED1_PIN, PIN_HIGH);
        rt_thread_mdelay(1000);
        rt_pin_write(LED1_PIN, PIN_LOW);
        rt_thread_mdelay(1000);
    }
}


计算周期

ECAP 模块工作频率最高 200M Hz ,意味着1秒最高计数 :
2×1082\times10^82×10​8​​

驱动配置 302 分频* ,所以实际工作频率为:
f=fSYSprescaler=2×10830×2Hzf=\frac{f_{SYS}}{prescaler}=\frac{2\times10^8}{30\times2}\text{Hz}f=​prescaler​​f​SYS​​​​=​30×2​​2×10​8​​​​Hz

因此1秒需要计数:
PeriodTotal=2×108×30×2=12×109Period_{Total} = 2\times10^8\times30\times2=12\times10^9Period​Total​​=2×10​8​​×30×2=12×10​9​​

有这个等量关系,因此可得出测量周期的公式为:
T=PeriodTotal=12×10412×109=1105secondT=\frac{Period}{Total}=\frac{12\times10^4}{12\times10^9}=\frac{1}{10^5}\text{second}T=​Total​​Period​​=​12×10​9​​​​12×10​4​​​​=​10​5​​​​1​​second

频率为:
fsrc=1T=105Hz=100kHzf_{src}=\frac{1}{T}=10^5\text{Hz}=100\text{kHz}f​src​​=​T​​1​​=10​5​​Hz=100kHz

九、纳芯微NS800RT7P65D上的PWM实践(程廷桢)

作者:程廷桢

原文标题:【纳芯微】纳芯微 NS800RT7P65 ePWM 模块应用笔记

源文章:https://club.rt-thread.org/ask/article/6af183c56eeb0620.html

1. 实践目标

本章节整理 ePWM 模块在 RT-Thread BSP 中的适配思路。原文包含驱动片段,本文只保留驱动结构、参数换算关系和应用调用流程。

NS800RT7P65D 提供 18 对互补 ePWM 输出和 HRPWM 精细调节能力,适合电机控制、数字电源、逆变器和 DC/DC 等需要高精度 PWM 的场景。实践重点是把芯片侧 ePWM 能力映射到 RT-Thread 的 PWM 设备模型。

2. 驱动适配思路

驱动核心不是单个函数,而是一组静态配置表和初始化流程:

层次

关键内容

引脚层

为每个 ePWM 通道配置 A/B 输出引脚和复用功能

实例层

通过条件编译管理 EPWM1 到 EPWM18

时基层

配置计数模式、时钟分频和周期寄存器

输出层

配置比较值、互补输出、死区和动作限定

RT-Thread 层

对接 struct rt_pwm_configuration

2.1 参数换算关系

上层使用纳秒级 periodpulsedead_time 和角度制 phase。驱动需要把这些值换算为 ePWM 寄存器能够接受的时基周期、比较值、死区计数和相位偏移。

flowchart TD
    A[rt_pwm_configuration] --> B[周期和占空比检查]
    B --> C[读取 PCLK1 和计数模式]
    C --> D[换算 TBPRD 与 CMPA/CMPB]
    D --> E[配置死区和互补输出]
    E --> F[更新 ePWM 输出波形]


2.2 最小配置片段

下面只保留应用侧需要组织的关键字段。驱动内部再把纳秒、角度等上层语义换算成 ePWM 的时基、比较、死区和相位寄存器值。

struct rt_pwm_configuration cfg = {
    .channel = 0,
    .period = period_ns,
    .pulse = pulse_ns,
    .dead_time = dead_time_ns,
    .phase = phase_degree,
    .complementary = RT_TRUE,
};


这里的关键点是应用层不要直接暴露 TBPRDCMPA、死区计数等硬件细节;这些换算应收敛在 BSP PWM 驱动里。

3. 应用调用流程

应用侧只需要围绕 RT-Thread PWM 设备接口组织,不需要直接操作 ePWM 寄存器。

sequenceDiagram
    participant App as 应用
    participant PWM as RT-Thread PWM 设备
    participant Driver as NS800 ePWM 驱动
    participant HW as ePWM 硬件
    App->>PWM: 查找并打开 pwm 设备
    App->>PWM: 设置 period、pulse、dead_time
    PWM->>Driver: 下发配置
    Driver->>HW: 换算并写入时基、比较、死区寄存器
    App->>PWM: enable
    HW-->>App: 输出 PWM 或互补 PWM


4. 调试要点

  • 互补输出需要同时确认 A/B 两路引脚复用、死区配置和 complementary 标志。

  • 上下计数模式和单向计数模式的频率换算不同,波形异常时优先核对计数模式。

  • 多路相位同步需要明确主从设备关系,相位角是相对 master 设备的配置。

  • ePWM 常与 ADC、比较器、ECAP 通过 XBAR 协同,后续闭环控制建议保留同步触发点。

十、纳芯微NS800RT7P65D上的CAN实践(htang)

作者:htang

原文标题:NS800RT7P65 CAN/CAN-FD 驱动测试应用笔记

源文章:https://club.rt-thread.org/ask/article/c16961a3265c5aaa.html

1. 实践目标

本章节整理 CAN/CAN-FD 驱动测试应用笔记。原文提供了完整测试源码,本文改写为测试架构、关键路径和命令速查,避免直接粘贴大段代码。

NS800RT7P65D 片内 FLEXCAN 控制器支持经典 CAN 2.0B 和 CAN-FD。测试分为两类:无需外部硬件的环回测试,以及需要 USB-CAN 分析仪或另一个节点的真实总线对测。

2. 硬件和配置

项目

要点

CAN 设备

canfd1

CAN 引脚

PA4 作为 CAN_TX,PA10 作为 CAN_RX

串口调试

PA12/PA13,115200 8N1

外部硬件

标准 CAN 总线需要 CAN 收发器、共地和 120 欧姆终端电阻

RT-Thread 配置

启用 CAN、CAN-FD、硬件过滤和 CANFD1

3. 驱动调用路径

RT-Thread 侧 CAN 测试围绕标准设备接口展开,核心路径如下:

flowchart TD
    A[查找 canfd1] --> B[配置波特率、模式、CAN-FD 参数]
    B --> C[打开 INT_TX 与 INT_RX]
    C --> D[注册 RX 回调]
    D --> E[写入 CAN 或 CAN-FD 帧]
    E --> F[RX 回调释放信号量]
    F --> G[读取回环帧或真实总线帧]
    G --> H[比较 ID、DLC、数据和 FD 元数据]


3.1 最小收发骨架

测试代码可以压缩成“配置、打开、注册回调、发送、等待、读取”几个动作。下面片段只展示顺序和关键字段:

rt_device_t dev = rt_device_find("canfd1");
struct can_configure cfg = CANDEFAULTCONFIG;

cfg.baud_rate = 500000;
cfg.mode = RT_CAN_MODE_LOOPBACK;
cfg.enable_canfd = RT_TRUE;
cfg.baud_rate_fd = 2000000;

rt_device_control(dev, RT_DEVICE_CTRL_CONFIG, &cfg);
rt_device_open(dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);
rt_device_set_rx_indicate(dev, can_rx_ind);

rt_device_write(dev, 0, &tx_msg, sizeof(tx_msg));
rt_sem_take(&rx_sem, rt_tick_from_millisecond(1000));
rt_device_read(dev, 0, &rx_msg, sizeof(rx_msg));


真实总线测试时,把 RT_CAN_MODE_LOOPBACK 换成正常模式,并确保外部节点、收发器和终端电阻已经就绪。

4. 环回测试设计

环回测试的价值在于排除外部收发器和线缆因素,先验证设备模型、帧格式和驱动收发路径。经典 CAN 覆盖打开关闭、波特率、DLC、扩展帧、非阻塞发送、稳定性和压力测试;CAN-FD 额外覆盖 12 到 64 字节 DLC、BRS、FD 标志和混合帧。

4.1 同步机制

发送线程不直接轮询硬件状态,而是等待 RX 回调释放信号量:

sequenceDiagram
    participant Test as 测试线程
    participant Dev as CAN 设备
    participant ISR as RX 回调
    Test->>Dev: 发送测试帧
    Dev-->>ISR: 环回或总线收到帧
    ISR-->>Test: 释放接收信号量
    Test->>Dev: 读取帧
    Test->>Test: 比较 ID、DLC、payload、FD/BRS


4.2 CAN-FD DLC 处理

CAN-FD 的 DLC 不是简单字节数。DLC 0 到 8 直接对应 0 到 8 字节,DLC 9 到 15 分别对应 12、16、20、24、32、48、64 字节。测试用例需要同时检查 DLC 码、实际载荷长度和 FD/BRS 元数据。

static const rt_uint8_t dlc_to_len[16] = {
    0, 1, 2, 3, 4, 5, 6, 7,
    8, 12, 16, 20, 24, 32, 48, 64
};

payload_len = dlc_to_len[dlc & 0x0f];


5. 真实总线对测

真实设备测试建议按照“先收后发、先经典 CAN 后 CAN-FD”的顺序:

场景

NS800 侧命令

PC 或外部节点动作

经典 CAN 接收

can_test_rx count timeout baud

发送标准帧或扩展帧

经典 CAN 发送

can_test_tx count id baud dlc

分析仪监听并校验

CAN-FD 接收

can_test_fd_rx count timeout baud baud_fd

发送 FD 帧

CAN-FD 发送

can_test_fd_tx count id baud baud_fd dlc

分析仪校验 FD/BRS

总线监听

can_test_monitor duration baud

统计总线帧

6. 排错要点

  • TX 成功但 PC 端收不到时,优先检查收发器、终端电阻、共地和波特率。

  • 扩展帧收不到时,检查 RX mailbox 是否按标准帧和扩展帧分别配置。

  • CAN-FD 接收时,仲裁段和数据段速率要与分析仪设置匹配;本地 baud_fd 主要影响本机发送的数据段速率。

  • 原始驱动里曾出现发送 ID 固定的问题,修复思路是在每次发送前显式更新 mailbox 的 ID、DLC 和 FD 标志。

十一、纳芯微NS800RT7P65D上的IIC实践(孙晓辉)

作者:孙晓辉

原文标题:纳芯微NS800 硬件I2C驱动移植

源文章:https://club.rt-thread.org/ask/article/44ad05e9dbb16cd8.html

1. 实践目标

本章节整理 NS800 硬件 I2C 驱动移植。原文包含完整驱动文件,本文只保留驱动分层、关键初始化顺序、传输流程和排错要点。

2. 驱动架构

flowchart TD
    A[RT-Thread I2C Framework] --> B[drv_hard_i2c]
    B --> C[NS800 SDK I2C API]
    C --> D[I2C1/I2C2 硬件外设]
    B --> E[Kconfig 与 SConscript]
    B --> F[GPIO 复用和时钟配置]


文件或配置

作用

i2c_hard_config.h

描述 I2C 实例、引脚、复用和默认波特率

drv_hard_i2c.h

定义配置结构和控制命令

drv_hard_i2c.c

完成初始化、传输、超时和错误处理

SConscript

按 Kconfig 开关加入驱动源文件

Kconfig

暴露 I2C1、I2C2 和波特率配置

3. 关键逻辑

3.1 GPIO 初始化顺序

NS800 的 I2C 引脚需要按固定顺序配置:先设置复用,再禁用模拟模式,随后配置上拉、异步采样和输入方向。若顺序遗漏,常见表现是总线无响应或读取状态异常。

3.2 时钟和波特率

硬件 I2C 使用前必须打开对应外设时钟;波特率计算使用 RCC_getPclk2Frequency() 作为输入,而不是直接使用系统主频。这样可以避免 PCLK 分频变化后 I2C 实际速率偏离预期。

3.3 传输保护

每个等待状态都需要超时保护。超时后驱动应复位 I2C master 并重新使能,避免一次异常把总线永久卡住。

3.4 地址转换

RT-Thread I2C 消息使用 7 位地址,NS800 SDK 发送 START 时需要 8 位地址格式。因此驱动下发给 SDK 前要把地址左移一位,再叠加读写方向。

rt_uint8_t addr8 = (rt_uint8_t)(msg->addr << 1);

if (msg->flags & RT_I2C_RD) {
    addr8 |= 0x01;
}


4. 传输时序

sequenceDiagram
    participant App as 应用
    participant Bus as RT-Thread I2C 总线
    participant Drv as NS800 硬件 I2C 驱动
    participant HW as I2C 外设
    App->>Bus: rt_i2c_transfer(msgs)
    Bus->>Drv: master_xfer
    Drv->>HW: START + 8位地址
    HW-->>Drv: ACK 或 NACK
    Drv->>HW: 写数据或读数据
    Drv->>HW: STOP
    Drv-->>Bus: 返回成功消息数或错误码
    Bus-->>App: 传输结果


4.1 最小读寄存器片段

大多数 I2C 传感器读寄存器都可以抽象为先写寄存器地址、再重复起始读数据。应用侧保留这个组合即可:

struct rt_i2c_msg msgs[2] = {
    {
        .addr = dev_addr,
        .flags = RT_I2C_WR,
        .buf = &reg,
        .len = 1,
    },
    {
        .addr = dev_addr,
        .flags = RT_I2C_RD,
        .buf = data,
        .len = len,
    },
};

return rt_i2c_transfer(bus, msgs, 2) == 2 ? RT_EOK : -RT_EIO;


5. 使用和调试

动作

要点

配置

在 menuconfig 中启用 I2C,再选择 Hardware I2C1 或 Hardware I2C2

编译

使用 scons -j8 或项目既有构建命令

查找设备

通过 rt_i2c_bus_device_find 获取 hwi2c1hwi2c2

设备扫描

对 0 到 126 地址发起零长度写,收到 ACK 即认为有设备

日志

开启 drv.i2c 调试输出,确认 GPIO、时钟和 MCR/MSR 状态

6. 常见问题

  • STOP 超时:通常与从设备未响应、无上拉、SCL/SDA 断开有关。

  • NACK:优先确认 7 位地址、供电和从设备数据手册。

  • 初始化失败:检查 PCLKEN5 中对应 I2C 时钟位是否置位。

  • 编译缺符号:确认 RT_USING_I2C 已通过 Kconfig 或配置文件启用。

十二、纳芯微NS800RT7P65D上的RTC实践(朱文治)

作者:朱文治

原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的外设RTC实践

源文章:https://club.rt-thread.org/ask/article/13e043319b6194e7.html

1. 实践目标

本章节整理外接 DS1307 RTC 模块在 NS800RT7P65D 上的适配实践。最新代码已经改为通过 NS800 硬件 IIC 总线访问 DS1307,不再使用 GPIO 模拟 I2C。本文只保留硬件连接、关键协议流程和 RT-Thread RTC 设备层对接方式。

2. 硬件连接

项目

内容

主控

NS800RT7P65D,NSSinePad-NS800RT7P65x V3.1

RTC 芯片

DS1307,I2C 接口,7 位地址 0x68

SDA

GP34 / PB2,复用为硬件 IIC SDA

SCL

GP35 / PB3,复用为硬件 IIC SCL

供电

原文验证使用 3.3V 模块;实际使用前需确认模块电平兼容

3. 驱动分层

flowchart TD
    A[RT-Thread RTC 设备框架] --> B[drv_rtc.c]
    B --> C[DS1307 适配层]
    C --> D[RT-Thread 硬件 IIC 总线]
    D --> E[NS800 IIC 控制器]
    E --> F[PB2/PB3 IIC 引脚]
    F --> G[DS1307 寄存器]


文件

作用

ds1307.c

通过硬件 IIC 总线实现 DS1307 寄存器读写、BCD 转换和时间获取设置

ds1307.h

暴露 init/get_time/set_time 等接口

drv_rtc.c

将外接 RTC 芯片封装为 RT-Thread RTC 设备

drv_hard_i2c.c

提供 hwi2c1hwi2c2 硬件 IIC 总线

rtconfig.h

启用 RT_USING_I2C、硬件 IIC 和 BSP_USING_DS1307

4. 关键逻辑

4.1 硬件 IIC 总线绑定

DS1307 适配层初始化时先查找硬件 IIC 总线,例如 hwi2c1hwi2c2,后续寄存器读写统一通过 rt_i2c_transfer 完成。PB2/PB3 只需要按硬件 IIC 方式完成复用、上拉和输入输出能力配置,不再由 DS1307 驱动手动翻转 GPIO 电平。

static struct rt_i2c_bus_device *ds1307_bus;

ds1307_bus = rt_i2c_bus_device_find(DS1307_I2C_BUS_NAME);
if (ds1307_bus == RT_NULL) {
    return -RT_ENOSYS;
}


4.2 DS1307 访问流程

DS1307 时间寄存器采用 BCD 编码。驱动读取秒、分、时、日、月、年后要转换为 struct tm;写入时间时则反向转换为 BCD,并注意秒寄存器的 CH 位。

struct rt_i2c_msg msgs[2] = {
    {
        .addr = DS1307_ADDR,
        .flags = RT_I2C_WR,
        .buf = &reg,
        .len = 1,
    },
    {
        .addr = DS1307_ADDR,
        .flags = RT_I2C_RD,
        .buf = buf,
        .len = len,
    },
};

return rt_i2c_transfer(ds1307_bus, msgs, 2) == 2 ? RT_EOK : -RT_EIO;

sequenceDiagram
    participant RTC as RT-Thread RTC
    participant DS as DS1307 适配层
    participant Bus as RT-Thread 硬件 IIC 总线
    participant HW as NS800 IIC 控制器
    participant Chip as DS1307
    RTC->>DS: get_time 或 set_time
    DS->>Bus: rt_i2c_transfer
    Bus->>HW: START + 地址 0x68
    HW->>Chip: 选择时间寄存器
    alt 读取时间
        Chip-->>HW: 返回 BCD 时间字段
        HW-->>Bus: 原始寄存器值
        Bus-->>DS: 成功消息数
        DS-->>RTC: 转换后的 time_t
    else 设置时间
        HW->>Chip: 写入 BCD 时间字段
        Chip-->>HW: ACK
        HW-->>Bus: 传输结果
        Bus-->>DS: 成功消息数
    end


5. 调试和验证

  • 先确认 hwi2c1hwi2c2 已注册,再接入 RTC 设备框架。

  • 读写时间前确认 DS1307 地址为 0x68,SDA/SCL 已复用到硬件 IIC 且有上拉。

  • 如果读取时间不走秒,检查 DS1307 振荡器停止位和后备电池。

  • RT-Thread 侧验证重点是 RTC 设备能注册、能设置时间、能再次读回递增的时间。