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

系统总线框图


2. 芯片层面 Uart 资源 - 解析
UART 支持全双工、异步、NRZ 串行通信,由波特率发生器、发送器和接收器组成。发送器和接收器独立工作,尽管它们使 用相同的波特率发生器。其资源分布主要如下:
从1.3.1 模块框图 可以看出,该芯片总共支持4路Uart;
根据1.3.2 系统总线框图 可以观察到,芯片的Uart1 / 2分布在APB 2,Uart3 / 4分布在APB4,总线最大时钟频率200Mhz;
根据参考手册,Uart模块具有以下特性,此处不再赘述:

3. 芯片层面 Uart 配置 - 说明
从 数据手册 Page 63页 开始,查看GPIO引脚复用信息,根据自身板卡情况,决定板级引脚复用配置,下图仅列出部分引脚复用情况;

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

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

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

注意: 上述表格对应的 UART 时钟频率为 200M !!!
当前板卡使用的是默认配置;


4. 代码层面 - Uart 功能测试
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;
}
测试结果:

在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
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
新增
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. 后续可优化点
将波特率和停止位等 基础配置 信息,分别映射到 每一路Uart 上,而不是所有Uart共用;
后续可以考虑 是否可以 将 引脚复用 和 引脚配置 也映射到 Menuconfig 中;
二、纳芯微NS800RT7P65D上的GPIO实践(熊治坤)
作者:熊治坤
原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的GPIO实践
源文章:https://club.rt-thread.org/ask/article/d7a4e5d88394553f.html
硬件
我的硬件版本是1.3,如下图所示:

不同的版本,硬件会有所不同,需要特别注意。
源代码下载
由于我之前已经下载过,需要进行更新即可。
git pull命令就行。

会提示是否最新。
MDK工程生成

这里为了验证中断功能。


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

这里成功后会有如上提示。
这里可以从文件时间可以查看是否是最新生成的

代码编写与调试
打开工程后进行编译
成功后会有如下提示

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

GPIO部分代码较长,这里不展示。后面根据GPIO验证,对main文件进行说明。
这里根据板子上的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. 实践目标
在本工程/博客中,解决了以下几个问题:
基于NS800的SDK,实现RT-Thread的ADC外设移植与测试
实现了ADC拓展外设:irq中断,PPB后处理模块的移植与测试
梳理实现思路,即“如何从基础SDK到RT-Thread ADC框架的移植”

2. 背景与目标
本次实践的目标是在 RT-Thread 主线 BSP
bsp/novosns/ns800中补充 NS800RT7P65D 的 ADC 驱动支持,并在本地 demo 工程中完成基础功能验证。最终 PR 侧代码以主线仓库中的如下文件为准:bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_adc.cbsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_adc.hbsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig
3. 资料收集与代码参考
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 读取通道。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. 驱动代码设计思路
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);
这样不需要知道内部配置结构与具体实现也能快速调用。
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指向静态硬件配置。pins和pin_count描述 ADC 输入 GPIO。enabled_mask记录哪些 ADC 通道已使能。mode记录当前读取模式:普通、PPB、IRQ。active_channel记录当前活跃通道,IRQ 模式依赖该字段绑定 EOC SOC。
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 配置描述芯片封装引脚复用,两者不是同一层概念。
硬件初始化集中在 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 用于避免重复初始化。
普通读取的核心流程是:
检查通道号是否合法。
将 channel 映射到同编号 SOC。
配置 SOC 为软件触发。
将
ADC_INT_NUMBER1的中断源设置为该 SOC。清 pending。
ADC_forceMultipleSOC()触发转换。轮询等待 EOC。
读取结果寄存器。
清除 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;
}驱动内部定义三种模式:
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;
}
这部分是驱动结构的关键点:
模式配置由
control完成,读取由read/convert完成。应用层切换模式后继续调用
rt_adc_read(),不需要直接接触 SOC、PPB 或中断寄存器。
IRQ 模式使用 NS800_ADC_CMD_ENABLE_IRQ 进入。应用层可传入:
struct ns800_adc_callback
{
ns800_adc_irq_callback_t callback;
void *user_data;
};
驱动内部会:
保存 callback 和 user data。
根据
active_channel配置软件触发 SOC。将
ADC_INT_NUMBER1绑定到该 SOC 的 EOC。注册
ADCA_CONV_IRQn的 ISR。使能 ADC 中断与 NVIC 中断。
模式切换为
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 模式时会卡死。
简介
这是一个新的ADC外设拓展,PPB(Post-Processing Block)后处理块
可以实现一系列常用的拓展功能,由硬件进行操作,降低算力负担
如:
过限检测(上下限)
过零检测
偏置
误差计算
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 内部流程:
Reset repeater。
将 SOC 配置为 repeater trigger。
ADC_setupPPB()绑定 PPB 与 SOC。配置 PPB trip limit、event 和 offset。
配置 repeater 为 oversampling mode。
设置 PPB count limit、compare source、OSINT source 和 shift。
触发 repeater。
读取 count、sum、min、max、min_index、max_index。
计算 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;
}核心代码:
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()测试代码中的回调:
//我们手动实现的回调函数
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。
测试代码:
//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 实现思路与应用方法
主线 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。
主线驱动编译接入位于:
bsp/novosns/ns800/libraries/HAL_Drivers/drivers/SConscript
ADC 条件编译:
if GetDepend('BSP_USING_ADC'):
src += ['drv_adc.c']
这样可以保证未开启 ADC 时不会引入 ADC 驱动和对应 HAL 依赖,减少对其他 BSP 配置的影响。
为了让主线 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 未覆盖的问题。
主线 BSP:
cd /path/to/rt-thread/bsp/novosns/ns800/ns800rt7p65-nssinepad
source ~/.env/env.sh
scons --menuconfig
在 menuconfig 中打开:
On-chip Peripheral Drivers
Enable ADC
保存后生成 .config 和 rtconfig.h,然后构建:

scons -j4
7. 运行时调用说明
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);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。
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 处理后的值。
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,不负责系统级向量表初始化。
命令 | 参数 | 作用 |
|---|---|---|
| channel | 使能指定 ADC 通道 |
| channel | 关闭指定 ADC 通道 |
|
| 获取 ADC 分辨率 |
|
| 获取参考电压,单位 mV |
|
| 切换到默认 PPB oversampling 模式 |
|
| 切换到 IRQ 模式并注册回调 |
|
| 仅设置 IRQ callback |
|
| 退出 PPB/IRQ 扩展模式,恢复 normal |
四、纳芯微NS800RT7P65D上的HWTimer实践(刘协泉)
作者:刘协泉
原文标题:【纳芯微NS800RT7P65D】基于RT-Thread的硬件定时器实践
源文章:https://club.rt-thread.org/ask/article/9eb602c9d20cf27b.html
前提知识
RTT的硬件定时器框架,本质上是通过计算出误差最小的分频系数和计数值来实现定时功能,使用硬件定时加软件计数(如果计时超过了定时器能支持的最大单次时间)的方式实现计时功能。
另外框架层其实更倾向于硬件定时器跑在1M的速率,若做不到,则设置为最低主频(这个可以在clock time框架层代码的init函数中见到),个人猜测是功耗方面的考量,毕竟现在的硬件定时器,主频都动不动几十上百兆的,其实可以做的更加精确。
最后,由于最终的主频和预分频系数是框架层设置的,因此驱动层最好是能做到可以设置任意的预分频系数,否则硬件定时器的计时功能就大概率误差大。
基于这些信息,如果要很好的适配上RTT的硬件定时器框架,硬件定时器就得有如下功能:
定时器的主频最好是大于或等于1M
定时器支持中断事件
最好定时器的分频系数可以任意设置(有效范围内的整数分频系数任意设置)
资料收集
通过查询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。
虽然名字变更了,但是实际上接口变化不算大,对于驱动来说,基本上就三个变更项:
把HWTIMER宏变更为CLOCK_TIMER
hwtimer变更为clock_timer
驱动文件名由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这个文件加在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 --updatemenuconfig后选择如下内容后保存

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驱动部分就知道能力大致在哪。最主要的是,整个资料全中文编写,降低了开发者的理解门槛。
问题点处理
原因是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. 概述
本文档记录将 RT-Thread WDG 总线驱动移植到 NS800RT7P65X BSP 的完整过程。NS800 芯片有 1 个 iwdg1 外设,本移植以iwdg1的中断和复位测试进行验证。
内部集成两个高安全性的独立看门狗定时器(IWDG1),每个 CPU 控制一个,其计时时钟源都为 MIRC1。 该独立看门狗可检测并解决由软件错误导致的故障,并在计数器从给定的超时值计数到 0 时触发系统复位;该独立看门狗还支持窗口刷新功能,用户只能在设定的计时窗口内刷计数器。用户可配置 IWDG1 在低功耗模式下继续运行或暂停运行。
IWDG1 的溢出周期由 PSC 及 TOPS 共同控制,其溢出周期的范围为 102.4us ~ 13.42s。


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. 移植思路
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代表看门狗超时进入中断。
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工程
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.文件源码
/*
* 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 *//*
* 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】基于RT-Thread的SPI实践
源文章:https://club.rt-thread.org/ask/article/119a830b1b1d5b0e.html
NS800 RT-Thread SPI 驱动移植指南
本文档记录将 RT-Thread SPI 总线驱动移植到 NS800RT7P65X BSP 的完整过程。NS800 芯片有 4 个 SPI 外设 (SPI1~SPI4),本移植以 SPI1 为例,配置为内部回环模式进行测试验证。
RT-Thread 提供了一套标准 SPI 框架,核心结构包括:
结构体 | 说明 |
|---|---|
| SPI 总线对象,封装底层硬件 |
| SPI 设备对象,挂载到总线上 |
| SPI 操作函数集,包含 |
| SPI 配置参数(模式、位宽、频率) |
驱动实现流程:
INIT_BOARD_EXPORT(rt_hw_spi_init)
→ 遍历 spi_config[] 数组
→ 初始化时钟和 GPIO
→ rt_spi_bus_register() 注册 SPI 总线创建驱动文件 -
drv_spi.h和drv_spi.c实现 GPIO 初始化 - 配置 SCK/MOSI/MISO 引脚及复用功能
实现 SPI 配置 - 设置 CPOL/CPHA、位宽、时钟频率
实现数据传输 - 基于轮询的
SPI_transmitReceive()实现更新构建系统 - 修改
SConscript配置 Kconfig - 添加 SPI 配置选项
更新 rtconfig.h - 启用 SPI 宏定义
编写测试代码 - SPI 回环测试
文件路径 | 说明 |
|---|---|
| SPI 驱动头文件 |
| SPI 驱动源文件 |
文件路径 | 修改内容 |
|---|---|
| 添加 |
| 添加 SPI1~4 配置选项 |
| 添加 SPI menuconfig |
| 添加 |
| 添加 SPI 回环测试代码 |
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;
}根据 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);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#define RT_USING_SPI
#define RT_USING_SPI_ISR
#define BSP_USING_SPI
#define BSP_USING_SPI16.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 */ }
}使用
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使用 MDK5 或 GCC 编译项目。
下载到开发板,串口输出显示:

错误信息:
(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex) assertion failed
原因: 直接对 SPI 总线操作,而没有先挂载 SPI 设备。
解决: 使用 rt_spi_bus_attach_device() 将设备挂载到总线,再对设备操作。
原因: 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() 查找设备名称。
SPI2~SPI4 的配置方法与 SPI1 类似,只需:
在
board/Kconfig中启用对应 SPI在
rtconfig.h中添加BSP_USING_SPIx宏在
drv_spi.c的spi_config[]数组中添加引脚配置(参考 NS800 datasheet)
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);
}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);
}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;
}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 256static 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);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#define RT_USING_QSPI4.验证
代码修改好后,编译完成升级,查看输出信息,qspi初始化正常

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

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

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

5.附件
完整代码:
#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__ *//*
* 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#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 驱动的上层应用示例 ,同时启用 ecap1 、 ecap2 两路捕获,采用「中断回调 + 线程轮询读取」双方式获取捕获数据,打印脉冲 / 周期参数。
/*
* 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×108
驱动配置 302 分频* ,所以实际工作频率为:
f=fSYSprescaler=2×10830×2Hzf=\frac{f_{SYS}}{prescaler}=\frac{2\times10^8}{30\times2}\text{Hz}f=prescalerfSYS=30×22×108Hz
因此1秒需要计数:
PeriodTotal=2×108×30×2=12×109Period_{Total} = 2\times10^8\times30\times2=12\times10^9PeriodTotal=2×108×30×2=12×109
有这个等量关系,因此可得出测量周期的公式为:
T=PeriodTotal=12×10412×109=1105secondT=\frac{Period}{Total}=\frac{12\times10^4}{12\times10^9}=\frac{1}{10^5}\text{second}T=TotalPeriod=12×10912×104=1051second
频率为:
fsrc=1T=105Hz=100kHzf_{src}=\frac{1}{T}=10^5\text{Hz}=100\text{kHz}fsrc=T1=105Hz=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 层 | 对接 |
上层使用纳秒级 period、pulse、dead_time 和角度制 phase。驱动需要把这些值换算为 ePWM 寄存器能够接受的时基周期、比较值、死区计数和相位偏移。
flowchart TD
A[rt_pwm_configuration] --> B[周期和占空比检查]
B --> C[读取 PCLK1 和计数模式]
C --> D[换算 TBPRD 与 CMPA/CMPB]
D --> E[配置死区和互补输出]
E --> F[更新 ePWM 输出波形]下面只保留应用侧需要组织的关键字段。驱动内部再把纳秒、角度等上层语义换算成 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,
};
这里的关键点是应用层不要直接暴露 TBPRD、CMPA、死区计数等硬件细节;这些换算应收敛在 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 或互补 PWM4. 调试要点
互补输出需要同时确认 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 设备 |
|
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 元数据]测试代码可以压缩成“配置、打开、注册回调、发送、等待、读取”几个动作。下面片段只展示顺序和关键字段:
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 标志和混合帧。
发送线程不直接轮询硬件状态,而是等待 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/BRSCAN-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];真实设备测试建议按照“先收后发、先经典 CAN 后 CAN-FD”的顺序:
场景 | NS800 侧命令 | PC 或外部节点动作 |
|---|---|---|
经典 CAN 接收 |
| 发送标准帧或扩展帧 |
经典 CAN 发送 |
| 分析仪监听并校验 |
CAN-FD 接收 |
| 发送 FD 帧 |
CAN-FD 发送 |
| 分析仪校验 FD/BRS |
总线监听 |
| 统计总线帧 |
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 实例、引脚、复用和默认波特率 |
| 定义配置结构和控制命令 |
| 完成初始化、传输、超时和错误处理 |
| 按 Kconfig 开关加入驱动源文件 |
| 暴露 I2C1、I2C2 和波特率配置 |
3. 关键逻辑
NS800 的 I2C 引脚需要按固定顺序配置:先设置复用,再禁用模拟模式,随后配置上拉、异步采样和输入方向。若顺序遗漏,常见表现是总线无响应或读取状态异常。
硬件 I2C 使用前必须打开对应外设时钟;波特率计算使用 RCC_getPclk2Frequency() 作为输入,而不是直接使用系统主频。这样可以避免 PCLK 分频变化后 I2C 实际速率偏离预期。
每个等待状态都需要超时保护。超时后驱动应复位 I2C master 并重新使能,避免一次异常把总线永久卡住。
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: 传输结果大多数 I2C 传感器读寄存器都可以抽象为先写寄存器地址、再重复起始读数据。应用侧保留这个组合即可:
struct rt_i2c_msg msgs[2] = {
{
.addr = dev_addr,
.flags = RT_I2C_WR,
.buf = ®,
.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 |
编译 | 使用 |
查找设备 | 通过 |
设备扫描 | 对 0 到 126 地址发起零长度写,收到 ACK 即认为有设备 |
日志 | 开启 |
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 位地址 |
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 寄存器]文件 | 作用 |
|---|---|
| 通过硬件 IIC 总线实现 DS1307 寄存器读写、BCD 转换和时间获取设置 |
| 暴露 |
| 将外接 RTC 芯片封装为 RT-Thread RTC 设备 |
| 提供 |
| 启用 |
4. 关键逻辑
DS1307 适配层初始化时先查找硬件 IIC 总线,例如 hwi2c1 或 hwi2c2,后续寄存器读写统一通过 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;
}DS1307 时间寄存器采用 BCD 编码。驱动读取秒、分、时、日、月、年后要转换为 struct tm;写入时间时则反向转换为 BCD,并注意秒寄存器的 CH 位。
struct rt_i2c_msg msgs[2] = {
{
.addr = DS1307_ADDR,
.flags = RT_I2C_WR,
.buf = ®,
.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先确认
hwi2c1或hwi2c2已注册,再接入 RTC 设备框架。读写时间前确认 DS1307 地址为
0x68,SDA/SCL 已复用到硬件 IIC 且有上拉。如果读取时间不走秒,检查 DS1307 振荡器停止位和后备电池。
RT-Thread 侧验证重点是 RTC 设备能注册、能设置时间、能再次读回递增的时间。
