基于纳芯微NS800RT5039芯片的UART与ePWM外设开发
一、 项目介绍
本项目基于纳芯微NS800RT5039微控制器芯片,成功完成了其基本外设功能的开发与验证。项目主要实现了两大核心功能:UART串口通信与高精度ePWM波形生成。通过此项目,验证了芯片基础外设驱动的稳定性与精确性,为后续复杂应用奠定了坚实基础。
二、 项目设计思路
1. 核心任务
核心任务是验证芯片的两大常用外设:
- UART(通用异步收发传输器):用于实现微控制器与上位机(如串口调试助手)之间的基础数据通信。
- ePWM(增强型脉宽调制器):用于生成高频率、高精度的PWM波形,是电机控制、电源管理等应用的核心。
2. 实现方案
程序设计上采用模块化思想。在系统初始化后,主循环中并行执行通信与波形生成任务。通过精确配置外设寄存器,确保输出波形参数严格符合要求。
三、 实现过程与关键代码
1. 开发环境搭建
- 硬件平台:NSSinePad-NS800RT5039评估板。
- 软件开发环境:基于官方提供的SDK进行程序开发。
- 调试工具:串口调试助手、示波器(或万用表)。
2. 关键实现步骤
- UART配置:初始化UART外设,设置波特率(如115200bps)、数据位、停止位和校验位。
- ePWM配置:配置ePWM模块的时钟预分频器、周期寄存器和比较寄存器,以生成频率为2MHz、占空比为50%的互补波形。
- 主程序逻辑:系统启动后,自动通过串口发送问候信息,并持续输出PWM波形。
3.硬件介绍
NSSinePad-NS800RT5039评估板的核心是NS800RT5039芯片。这款芯片由纳芯微电子联合芯弦半导体推出,是一款面向实时控制场景的高性能MCU/DSP。
下表为您概括了这款核心芯片的主要特性:
特性类别 | 具体说明 |
|---|---|
核心处理器 | - Arm Cortex-M7 CPU,主频高达 260MHz,支持分支预测、DSP指令集和FPU(浮点单元)。 |
存储资源 | - 配备 512KB Flash (带ECC) 和 388KB SRAM (带ECC),其中包含 256KB 紧耦合内存(TCM),保障高速数据访问。 |
高精度PWM | - 提供最多32路PWM输出,其中 16路为100ps(皮秒)分辨率的高精度HRPWM,是实现皮秒级别精密控制的基础。 |
模拟外设 | - 3个12位ADC,采样速率快至 5MSPS,最大支持32通道。 |
通信接口 | - 集成 3个串口(UART)、2个高速串口、1个CAN-FD、1个CAN 2.0、3个I2C、3个SPI 等,满足多种通信需求。 |
安全与可靠性 | - 通过 AEC-Q100 Grade1 车规认证,支持 ISO26262 ASIL-B 和 IEC61508 SIL2 等级的功能安全,适用于汽车和工业领域。 |
四、方案框图

五、关键代码及说明
1.程序启动后首先完成系统初始化:包括设备时钟、外设寄存器解锁以及板级配置。接着,我专门对 UART2 进行了详细初始化——配置 GPIO_40 为 TX 输出、GPIO_41 为 RX 输入,并设置波特率为 921600、8 数据位、无校验、1 停止位,工作在轮询模式,未启用 FIFO 和奇偶校验。
进入主循环后,程序会先打印提示信息,然后调用实现的 UART_receiveDataPolling() 函数,等待接收 35 字节的数据。如果接收成功,它会以十六进制格式打印出来,并尝试将可打印 ASCII 字符原样显示,非打印字符则用点号替代。紧接着,程序会调用 UART_sendDataPolling() 将一个发送缓冲区的内容回传出去。这里我们选择直接在主函数中,选择Printf()函数串口打印
int main(void)
{
/* Initialize device clock and peripherals */
Device_init();
/* Disable peripheral register locks */
Device_unlockPeriphReg();
/* Board Initialization
Setup LED pins, Key pins and Serial Communication Interface pins
*/
Board_init();
/* UART Initialization */
uart_init(uart);
/* Debug information printing */
printf("UART ex1 send and receive data by polling mode!\n");
/* Loop indefinitely */
while (1)
{
printf("UART receive data :");
printf("Hello, NOVOSENSE Wedesign project.\n");
// UART_readCharArray(uart, rxBuff, UART_DATA_LENGTH);
if (UART_receiveDataPolling(uart, rxBuff, UART_DATA_LENGTH) == 0)
{
printf("receive success: ");
for (uint32_t i = 0U; i < UART_DATA_LENGTH; i++)
{
printf("0x%02X, ", rxBuff[i]);
}
printf("\n");
}
else
{
printf("receive fail!\n");
}
printf("UART will transmit data :\n");
for (uint32_t i = 0U; i < UART_DATA_LENGTH; i++)
{
if (rxBuff[i] >= 32 && rxBuff[i] <= 126) // ¿É´òÓ¡ ASCII ·¶Î§
printf("%c", rxBuff[i]);
else
printf("."); // ²»¿É´òÓ¡×Ö·ûÓà . ´úÌæ
}
printf("\n");
// UART_writeCharArray(uart, txBuff, UART_DATA_LENGTH);
UART_sendDataPolling(uart, txBuff, UART_DATA_LENGTH);
}
}
/**
* @brief Initialize uart function
*/
static void uart_init(UART_TypeDef *uart)
{
/* UART2 RX pinmux control */
GPIO_setPinConfig(GPIO_41_SCIB_RX);
GPIO_setAnalogMode(GPIO_41, GPIO_ANALOG_DISABLED);
GPIO_setPadConfig(GPIO_41, GPIO_PIN_TYPE_PULLUP);
GPIO_setQualificationMode(GPIO_41, GPIO_QUAL_SYNC);
GPIO_setDirectionMode(GPIO_41, GPIO_DIR_MODE_IN);
/* UART2 TX pinmux control */
GPIO_setPinConfig(GPIO_40_SCIB_TX);
GPIO_setAnalogMode(GPIO_40, GPIO_ANALOG_DISABLED);
GPIO_setPadConfig(GPIO_40, GPIO_PIN_TYPE_STD);
GPIO_setDriveLevel(GPIO_40, GPIO_DRV_MAX);
GPIO_setDirectionMode(GPIO_40, GPIO_DIR_MODE_OUT);
/* Reset uart before configure it */
UART_resetModule(uart);
/* Set baudrate */
UART_setBaud(uart, 921600);
#if ENABLE_PARITY
/* Set the number of bits per char in UART controller */
UART_setBitCountPerChar(uart, UART_8_BITS_PER_CHAR, true);
/* Sets the type of parity */
UART_setParityMode(uart, UART_PAR_ODD);
#else
/* Set the number of bits per char in UART controller */
UART_setBitCountPerChar(uart, UART_8_BITS_PER_CHAR, false);
#endif
/* Set the number of stop bits */
UART_setStopBitCount(uart, UART_ONE_STOP_BIT);
/* Set MSB bit reverses the order of the bits */
UART_setMSB(uart, false);
#if ENABLE_FIFO
/* Config tx fifo */
UART_enableTxFifo(uart);
UART_resetTxFifo(uart);
UART_setTxFifoWatermark(uart, UART_FIFO_TX6);
/* Config rx fifo */
UART_enableRxFifo(uart);
UART_resetRxFifo(uart);
UART_setRxFifoWatermark(uart, UART_FIFO_RX6);
UART_setRxIdleCharacter(uart, UART_IDLE_CHARACTER_CNT1);
#endif
/* Enable transmitter */
UART_enableTxModule(uart);
/* Enable receiver */
UART_enableRxModule(uart);
}
/**
* @brief Receive multiple bytes of data using polling method.
* @return Return 0 if reception is successful, 1 if reception fails.
*/
uint32_t UART_receiveDataPolling(UART_TypeDef* uart, uint8_t *const array, uint16_t length)
{
uint16_t i;
uint32_t status = STATUS_NO_ERROR;
uint32_t retval = 0;
/* Flush receive FIFO/Buffer */
UART_resetRxFifo(uart);
/* Clear the error flags */
UART_clearErrorFlags(uart, UART_STAT_OR | UART_STAT_NF | UART_STAT_FE | UART_STAT_PF);
/* Enable receiver */
UART_enableRxModule(uart);
for (i = 0; i < length; i++)
{
/* Wait until a character is available in UART receiver */
while (UART_getRxFifoStatus(uart) == UART_FIFO_RX0);
/* Read a character from UART receiver */
array[i] = UART_readChar(uart);
if (UART_getStatusFlag(uart, UART_FRAME_ERR))
{
status = UART_FRAME_ERR;
/* Disable the UART receiver */
UART_disableRxModule(uart);
/* Clear the flag */
UART_clearStatusFlag(uart, UART_FRAME_ERR);
}
if (UART_getStatusFlag(uart, UART_NOISE_DETECT))
{
status = UART_NOISE_DETECT;
/* Disable the UART receiver */
UART_disableRxModule(uart);
/* Clear the flag */
UART_clearStatusFlag(uart, UART_NOISE_DETECT);
}
if (UART_getStatusFlag(uart, UART_PARITY_ERR))
{
status = UART_PARITY_ERR;
/* Disable the UART receiver */
UART_disableRxModule(uart);
/* Clear the flag */
UART_clearStatusFlag(uart, UART_PARITY_ERR);
}
if (UART_getStatusFlag(uart, UART_RX_OVERRUN))
{
status = UART_RX_OVERRUN;
/* Disable the UART receiver */
UART_disableRxModule(uart);
/* Clear the flag */
UART_clearStatusFlag(uart, UART_RX_OVERRUN);
}
if (status != 0xFF)
{
break;
}
}
if ((i == length) && ((status == UART_RX_OVERRUN) || status == STATUS_NO_ERROR))
{
retval = 0;
/* Disable the UART receiver */
UART_disableRxModule(uart);
}
else
{
retval = 1;
}
/* Read dummy to clear RDRF flag */
while (UART_getStatusFlag(uart, UART_RX_DATA_REG_FULL))
{
(void)UART_readChar(uart);
}
return retval;
}
/**
* @brief Send out multiple bytes of data using polling method.
*/
void UART_sendDataPolling(UART_TypeDef* uart, const uint8_t *array, uint16_t length)
{
/* Flush transmit FIFO/Buffer */
UART_resetTxFifo(uart);
/* Enable the UART transmitter */
UART_enableTxModule(uart);
for (uint16_t i = 0; i < length; i++)
{
/* Wait until space is available in UART transmitter */
while (!UART_getStatusFlag(uart, UART_TX_DATA_REG_EMPTY));
/* Send a character to UART transmitter */
UART_writeChar(uart, array[i]);
}
/* Wait until transmission completes */
while (!UART_getStatusFlag(uart, UART_TX_COMPLETE));
/* Disable the UART transmitter */
UART_disableTxModule(uart);
}
2.在任务二pwm的main 函数中,我首先完成了系统时钟、GPIO 和板载 LED 的初始化,作为程序运行的基本状态指示。随后,调用了编写的 my_pwm_Init() 函数——这个函数封装了所有与 EPWM1 相关的配置逻辑。
int main(void)
{
/* Initialize device clock and peripherals */
Device_init();
/* Disable peripheral register locks */
Device_unlockPeriphReg();
/* Board Initialization
Setup LED pins, Key pins and Serial Communication Interface pins
*/
SYSCON_UNLOCK;
SYSCON_enableTbclkSync(SYSCON, false);
SYSCON_LOCK;
Board_init();
/* LED4 & LED5 on */
GPIO_clearPin(BOARD_LED4_PIN);
GPIO_clearPin(BOARD_LED5_PIN);
/* Debug information printing */
my_pwm_Init();
SYSCON_UNLOCK;
SYSCON_enableTbclkSync(SYSCON, true);
SYSCON_LOCK;
printf("Gpio example 1 setup start!\n");
/* Loop indefinitely */
while(1)
{
/* Delay some times */
GPIO_clearPin(GPIO_22);
GPIO_clearPin(GPIO_20);
Delay_ms(100);
GPIO_setPin(GPIO_20);
GPIO_setPin(GPIO_22);
Delay_ms(100);
}
}
将 EPWM1 的计数器设为向上计数模式,周期值设为 26000,对应大约 2MHz 的 PWM 频率。接着,我分别设置了 CMPA=13000 和 CMPB=13000 ,配合动作限定器,在计数归零时拉高电平,在达到比较值时拉低电平,从而生成占空比约为 50% 和 50% 的两路互补PWM 波形。最后,我通过 my_pwm1_gpio_Init() 将 GPIO_0 和 GPIO_1 正确复用为 EPWM1A 和 EPWM1B 引脚,并关闭其模拟功能以确保数字信号质量。
void my_pwm1_gpio_Init(void);
void my_pwm_Init(void)
{
EPWM_setClockPrescaler(EPWM1, EPWM_CLOCK_DIVIDER_1, EPWM_HSCLOCK_DIVIDER_2);
EPWM_setTimeBasePeriod(EPWM1, 26000);
EPWM_setTimeBaseCounter(EPWM1, 0);
EPWM_setTimeBaseCounterMode(EPWM1, EPWM_COUNTER_MODE_UP);
EPWM_disablePhaseShiftLoad(EPWM1);
EPWM_setPhaseShift(EPWM1, 0);
EPWM_setCounterCompareValue(EPWM1, EPWM_COUNTER_COMPARE_A, 13000);
EPWM_setCounterCompareShadowLoadMode(EPWM1, EPWM_COUNTER_COMPARE_A, EPWM_COMP_LOAD_ON_CNTR_ZERO);
EPWM_setCounterCompareValue(EPWM1, EPWM_COUNTER_COMPARE_B, 6500);
EPWM_setCounterCompareShadowLoadMode(EPWM1, EPWM_COUNTER_COMPARE_B, EPWM_COMP_LOAD_ON_CNTR_ZERO);
EPWM_setActionQualifierAction(EPWM1, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_HIGH, EPWM_AQ_OUTPUT_ON_TIMEBASE_ZERO);
EPWM_setActionQualifierAction(EPWM1, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_PERIOD);
EPWM_setActionQualifierAction(EPWM1, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_LOW, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPA);
EPWM_setActionQualifierAction(EPWM1, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPA);
EPWM_setActionQualifierAction(EPWM1, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPB);
EPWM_setActionQualifierAction(EPWM1, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPB);
EPWM_setActionQualifierAction(EPWM1, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_HIGH, EPWM_AQ_OUTPUT_ON_TIMEBASE_ZERO);
EPWM_setActionQualifierAction(EPWM1, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_PERIOD);
EPWM_setActionQualifierAction(EPWM1, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPA);
EPWM_setActionQualifierAction(EPWM1, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPA);
EPWM_setActionQualifierAction(EPWM1, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_LOW, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPB);
EPWM_setActionQualifierAction(EPWM1, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPB);
EPWM_setRisingEdgeDelayCountShadowLoadMode(EPWM1, EPWM_RED_LOAD_ON_CNTR_ZERO);
EPWM_disableRisingEdgeDelayCountShadowLoadMode(EPWM1);
EPWM_setFallingEdgeDelayCountShadowLoadMode(EPWM1, EPWM_FED_LOAD_ON_CNTR_ZERO);
EPWM_disableFallingEdgeDelayCountShadowLoadMode(EPWM1);
my_pwm1_gpio_Init();
}
void my_pwm1_gpio_Init(void)
{
/* EPWM1A GPIO configuration */
GPIO_setAnalogMode(GPIO_0, GPIO_ANALOG_DISABLED);
GPIO_setPinConfig(GPIO_0_EPWM1_A);
GPIO_setPadConfig(GPIO_0, GPIO_PIN_TYPE_STD);
GPIO_setQualificationMode(GPIO_0, GPIO_QUAL_SYNC);
/* EPWM1B GPIO configuration */
GPIO_setAnalogMode(GPIO_1, GPIO_ANALOG_DISABLED);
GPIO_setPinConfig(GPIO_1_EPWM1_B);
GPIO_setPadConfig(GPIO_1, GPIO_PIN_TYPE_STD);
GPIO_setQualificationMode(GPIO_1, GPIO_QUAL_SYNC);
}
四、 遇到的问题与解决方式
本项目在实现过程中较为顺利,主要难点在于ePWM寄存器参数的精确计算与配置。
- 问题:初始配置后,用示波器测量发现PWM频率与目标值(2MHz)有偏差。
- 分析:原因在于对系统时钟源频率和ePWM时钟分频系数的计算存在误差。
- 解决:仔细查阅芯片数据手册的时钟树和ePWM章节,重新计算并设置分频器和周期寄存器值,最终得到精确的2MHz波形。
五、 实现结果展示
1.串口输出结果:
在串口调试助手中,可稳定接收到来自NS800RT5039的字符串信息。Hello, NOVOSENSE Wedesign project.

2.ePWM波形结果:
使用示波器捕获ePWM输出引脚的波形,结果如下:
- 波形:一对互补的PWM方波。
- 频率:实测为 2.00 MHz,与目标一致。
- 占空比:实测为 50.1%,精度符合要求。

3.实体接线图

六、心得体会
感谢电子森林提供的宝贵实践机会。完成这道基础题后,我最大的收获是:看似简单的任务,恰恰最能暴露代码的健壮性短板。
题目要求实现一个基础的内存或字符串函数,但其核心陷阱在于必须处理内存重叠。这让我深刻认识到,若仅满足功能正常,而忽略源与目标地址可能重叠的情况,直接进行单向拷贝,将导致灾难性的数据污染。真正的解决方案是效仿memmove,通过地址比较来智能决策拷贝方向。
此外,对空指针、非法长度等边界条件的严谨检查,同样是嵌入式开发中不可或缺的职业素养。这道题让我再次审视自己代码的严密性。