一.所选任务介绍
1、使用开发板完成基础任务:
- 调用Uart串口,完成在串口输出 “Hello, NOVOSENSE Wedesign project.” 字符串任务。(难度系数 *)
- 调用ePWM外设,实现在对应输出端口,输出一对互补PWM波形,波形频率2MHz,占空比50%。
2、使用开发板完成额外任务:
- 使用原厂NSSinePad-NS800RT5039开发板,并使用硬件EMATH功能和麦克风,配合LED屏或者LCD屏,完成一个音乐律动器。
二、项目描述:
使用纳芯微的NS800RT5039开发板完成任务,基于Keil集成开发环境。任务主要分为两部分,基础任务主要是纯配置单片机,通过简单配置寄存器即可实现功能。额外任务是进阶版,需要配置内置的EMATH功能用于进行FFT,ADC用于采集麦克风音频信号,还需要配置I2C或者SPI来驱动屏幕,以及可以对UI进行设计。
三、芯片选型&硬件介绍
1、主控制器NS800RT5039

纳芯微的NS800RT5039应为TI C2000 DSP的对标产品,其EPWM和TI的结构高度相似,对于底层基本不需要额外学习。但是NS800RT5039的EPWM又有所升级,每个EPWM单元可产生4路PWM,总共可以产生32路PWM。除此之外,NS800RT5039的EPWM还支持高精度周期,这对于谐振变换器的高精度变频控制非常有帮助。
NS800RT5039的核心是cortx-m7,ARM相对于C2000还是存在一定的差异。由于该芯片拥有EMATH,通过硬件加速计算。该芯片的其余外设也是非常丰富,USART、SPI、I2C、CAN等这些通讯外设很丰富,4.375MSPS的ADC、DAC、PGA等模拟外设对于数字电源的控制也是绰绰有余。
2、麦克风放大器MAX4466

MAX4466是微功率运算放大器,经过优化,可用作麦克风前置放大器。它们提供了优化的增益带宽产品与电源电流的理想组合,以及超小型封装中实现低电压工件环境。 MAX4466具有增益稳定特性,仅需24μA的电源电流即可提供200kHz的增益带宽。经过解压缩,可实现+5V/V的最小稳定增益,并提供600KHZ增益带宽。此外这些放大器具有轨到轨输出,高 AVOL ,以及出色的电源抑制和共模抑制比,适合在嘈杂环境中工作。广泛应用于蜂窝电话、数字复读装置、耳机、助听器、麦克风前置放大器、便携计算机和语音识别系统中。
四、硬件连接
MAX4466模块3.3V供电,OUT连接NS800RT5039评估板的A6口。OLED屏的I2C SCL连接NS800RT5039评估板的GPIO27,SDA连接GPIO26。串口使用板载CMSIS调试器,对应GPIO28、GPIO29两个口。ADC基准Vref脚连接3.3V。PWM输出使用GPIO0和GPIO1。

五、方案&设计思路&软件

对于基础任务,USART和EPWM输出,直接调用对应的外设即可。音乐节奏灯则涉及ADC、DMA、EMATH,驱动屏幕需要I2C。
USART的配置如下代码,使用UART1,配置GPIO28、GPIO29两个口,波特率115200,开启fifo。
#define BOARD_SERIALCOM_TX_PIN GPIO_29
#define BOARD_SERIALCOM_RX_PIN GPIO_28
#define BOARD_SERIALCOM_BAUDRATE (115200UL)
#define BOARD_SERIALCOM UART1
void SerialCOM_init(void)
{
/* uart1 rx control */
GPIO_setAnalogMode(BOARD_SERIALCOM_RX_PIN, GPIO_ANALOG_DISABLED);
GPIO_setPadConfig(BOARD_SERIALCOM_RX_PIN, GPIO_PIN_TYPE_PULLUP);
GPIO_setQualificationMode(BOARD_SERIALCOM_RX_PIN, GPIO_QUAL_SYNC);
GPIO_setQualificationPeriod(BOARD_SERIALCOM_RX_PIN, GPIO_SMP_SYSCLK_DIV_1);
GPIO_setDirectionMode(BOARD_SERIALCOM_RX_PIN, GPIO_DIR_MODE_IN);
/* uart1 tx control */
GPIO_setAnalogMode(BOARD_SERIALCOM_TX_PIN, GPIO_ANALOG_DISABLED);
GPIO_setPadConfig(BOARD_SERIALCOM_TX_PIN, GPIO_PIN_TYPE_STD);
GPIO_setDriveLevel(BOARD_SERIALCOM_TX_PIN, GPIO_DRV_LOW);
GPIO_setPin(BOARD_SERIALCOM_TX_PIN);
GPIO_setDirectionMode(BOARD_SERIALCOM_TX_PIN, GPIO_DIR_MODE_OUT);
/* Reset uart before configure it */
UART_resetModule(BOARD_SERIALCOM);
/* Set baudrate */
UART_setBaud(BOARD_SERIALCOM, BOARD_SERIALCOM_BAUDRATE);
/* Set the number of stop bits */
UART_setStopBitCount(BOARD_SERIALCOM, UART_ONE_STOP_BIT);
/* Set MSB bit reverses the order of the bits */
UART_setMSB(BOARD_SERIALCOM, false);
/* Config tx fifo */
UART_enableTxFifo(BOARD_SERIALCOM);
UART_resetTxFifo(BOARD_SERIALCOM);
UART_setTxFifoWatermark(BOARD_SERIALCOM, UART_FIFO_TX6);
/* Enable transmitter */
UART_enableTxModule(BOARD_SERIALCOM);
/* Enable receiver */
UART_enableRxModule(BOARD_SERIALCOM);
}
USART打印输出时使用printf
printf("Hello, NOVOSENSE Wedesign project.");
产生2MHz,50%占空比,互补PWM使用EPWM1模块。主频260MHz,计数方式单增,周期为260/2=130,比较值COMPA设置130/65可产生50%占空比,同时设置互补即可,死去时间设置10个时钟周期。配置代码如下
EPWM_setClockPrescaler(myEPWM1_BASE, EPWM_CLOCK_DIVIDER_1, EPWM_HSCLOCK_DIVIDER_1);
EPWM_setTimeBasePeriod(myEPWM1_BASE, 129);
EPWM_setTimeBaseCounter(myEPWM1_BASE, 0);
EPWM_setTimeBaseCounterMode(myEPWM1_BASE, EPWM_COUNTER_MODE_UP);
EPWM_disablePhaseShiftLoad(myEPWM1_BASE);
EPWM_setPhaseShift(myEPWM1_BASE, 0);
EPWM_setCounterCompareValue(myEPWM1_BASE, EPWM_COUNTER_COMPARE_A, 65);
EPWM_setCounterCompareShadowLoadMode(myEPWM1_BASE, EPWM_COUNTER_COMPARE_A, EPWM_COMP_LOAD_ON_CNTR_ZERO);
EPWM_setCounterCompareValue(myEPWM1_BASE, EPWM_COUNTER_COMPARE_B, 65);
EPWM_setCounterCompareShadowLoadMode(myEPWM1_BASE, EPWM_COUNTER_COMPARE_B, EPWM_COMP_LOAD_ON_CNTR_ZERO);
EPWM_setActionQualifierAction(myEPWM1_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_LOW, EPWM_AQ_OUTPUT_ON_TIMEBASE_ZERO);
EPWM_setActionQualifierAction(myEPWM1_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_PERIOD);
EPWM_setActionQualifierAction(myEPWM1_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_HIGH, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPA);
EPWM_setActionQualifierAction(myEPWM1_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_LOW, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPA);
EPWM_setActionQualifierAction(myEPWM1_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPB);
EPWM_setActionQualifierAction(myEPWM1_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPB);
EPWM_setActionQualifierAction(myEPWM1_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_LOW, EPWM_AQ_OUTPUT_ON_TIMEBASE_ZERO);
EPWM_setActionQualifierAction(myEPWM1_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_PERIOD);
EPWM_setActionQualifierAction(myEPWM1_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPA);
EPWM_setActionQualifierAction(myEPWM1_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_NO_CHANGE, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPA);
EPWM_setActionQualifierAction(myEPWM1_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_HIGH, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPB);
EPWM_setActionQualifierAction(myEPWM1_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_LOW, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPB);
EPWM_setDeadBandDelayPolarity(myEPWM1_BASE, EPWM_DB_FED, EPWM_DB_POLARITY_ACTIVE_LOW);
EPWM_setDeadBandDelayMode(myEPWM1_BASE, EPWM_DB_RED, true);
EPWM_setRisingEdgeDelayCountShadowLoadMode(myEPWM1_BASE, EPWM_RED_LOAD_ON_CNTR_ZERO);
EPWM_disableRisingEdgeDelayCountShadowLoadMode(myEPWM1_BASE);
EPWM_setRisingEdgeDelayCount(myEPWM1_BASE, 10);
EPWM_setDeadBandDelayMode(myEPWM1_BASE, EPWM_DB_FED, true);
EPWM_setFallingEdgeDelayCountShadowLoadMode(myEPWM1_BASE, EPWM_FED_LOAD_ON_CNTR_ZERO);
EPWM_disableFallingEdgeDelayCountShadowLoadMode(myEPWM1_BASE);
EPWM_setFallingEdgeDelayCount(myEPWM1_BASE, 10);
音乐节奏灯部分,使用ADC采集麦克风模拟信号,需要EPWM2模块进行触发,以50kHz进行触发。之后通过DMA搬运512个数据送入EMATH模块进行FFT运算。由于EMATH最大只能进行512个点,所以没办法进行更精细地计算。转换后可以涉及UI,绘制频谱曲线,增加峰值保持等效果。最后通过I2C传输到OLED上。
EPWM2需要50kHz,使用增减计数,设置周期为26000000/2/50000=2600,CMPA设置1300。程序如下
/* Enable EPWM2SOCA */
EPWM_enableADCTrigger(EPWM2, EPWM_SOC_A);
/* Enable TBCTR=CMPA event to genarate SOCA */
EPWM_setADCTriggerSource(EPWM2, EPWM_SOC_A, EPWM_SOC_TBCTR_U_CMPA);
/* Configure the SOC to occur on the first up-count event */
EPWM_setADCTriggerEventPrescale(EPWM2, EPWM_SOC_A, 1);
/* Set EPWM2 PRD = 2599U */
EPWM_setTimeBasePeriod(EPWM2, 2599);
/* Set EPWM2 CMPA = 1300U */
EPWM_setCounterCompareValue(EPWM2, EPWM_COUNTER_COMPARE_A, 1300);
/* Set the local ePWM module clock divider to /2 */
EPWM_setClockPrescaler(EPWM2, EPWM_CLOCK_DIVIDER_1, EPWM_HSCLOCK_DIVIDER_1);
/* Configure ePWM Counter runs in up-count mode. */
EPWM_setTimeBaseCounterMode(EPWM2, EPWM_COUNTER_MODE_UP_DOWN);
/* SYSCON configurations */
SYSCON_UNLOCK;
/* Enable SYSCON EPWM2SOCAEN */
SYSCON_enableEpwmSocA(SYSCON, SYSCON_EPWM2SOCAEN, true);
ADC部分使用A6脚,由于MAX4466输出0-3.3V,所以使用外部基准,将Vref直接连接3.3V即可。详细配置程序如下
void myADC0_init(void)
{
/* ADC Initialization: Write ADC configurations and power up the ADC.
Set the analog voltage reference selection and ADC module's offset trims.
This function sets the analog voltage reference to internal
(with the reference voltage of 1.65V or 2.5V) or external for ADC */
ADC_setVREF(myADC0_BASE, ADC_REFERENCE_EXTERNAL, NULL);
/* Configures the analog-to-digital converter module prescaler. */
ADC_setPrescaler(myADC0_BASE, ADC_CLK_DIV_4);
/* Sets the timing of the end-of-conversion pulse */
ADC_setInterruptPulsePosMode(myADC0_BASE, ADC_PULSE_END_OF_CONV);
/* Powers up the analog-to-digital converter core. */
ADC_enableConverter(myADC0_BASE);
/* Delay for 1ms to allow ADC time to power up */
Delay_us(1000);
/* SOC Configuration: Setup ADC EPWM channel and trigger settings */
/* Disables SOC burst mode. */
ADC_disableBurstMode(myADC0_BASE);
/* Sets the priority mode of the SOCs. */
ADC_setSOCPriority(myADC0_BASE, ADC_PRI_ALL_ROUND_ROBIN);
/* Start of Conversion 0 Configuration
Configures a start-of-conversion (SOC) in the ADC and its interrupt SOC trigger.
SOC number : 0
Trigger : ADC_TRIGGER_EPWM1_SOCA
Channel : ADC_CH_ADCIN0
Sample Window : 8 SYSCLK cycles
Interrupt Trigger : ADC_INT_SOC_TRIGGER_NONE */
ADC_setupSOC(myADC0_BASE, ADC_SOC_NUMBER0, ADC_TRIGGER_EPWM2_SOCA, ADC_CH_ADCIN6, myADC_SAMPLE_WINDOW);
ADC_setInterruptSOCTrigger(myADC0_BASE, ADC_SOC_NUMBER0, ADC_INT_SOC_TRIGGER_NONE);
/* ADC Interrupt 1 Configuration
Source : ADC_SOC_NUMBER1
Interrupt Source : enabled
Continuous Mode : disabled */
ADC_setInterruptSource(myADC0_BASE, ADC_INT_NUMBER1, ADC_SOC_NUMBER0);
ADC_clearInterruptStatus(myADC0_BASE, ADC_INT_NUMBER1);
ADC_disableContinuousMode(myADC0_BASE, ADC_INT_NUMBER1);
ADC_enableInterrupt(myADC0_BASE, ADC_INT_NUMBER1);
/* Enable ADCA INT1_DMA_EN */
ADCA->ADCDMA.BIT.INT1_DMA_EN = 1;
}
DMA一次搬运512个点,详细配置代码如下
volatile uint16_t adcBuffer[512] __attribute__((aligned(256)));//对齐
void DMA_adcToBuffer(void)
{
DMA_CommonConfig edma_config = {.enableContinuousLinkMode = 0,
.enableHaltOnError = 1,
.enableRoundRobinArbitration = 0,
.enableDebugMode = 0,
.enableEmlm = 0
};
DMA_TransferConfig edma_trans_config = {.channel = DMA1_channel_0,
.srcAddr = (uint32_t)ADCARESULT_BASE,
.destAddr = (uint32_t)(void *)adcBuffer,
.srcTransferSize = DMA_TRANSFERSIZE2BYTES,
.destTransferSize = DMA_TRANSFERSIZE2BYTES,
.srcOffset = 0,
.destOffset = 2,
.minorLoopBytes = 2,
.majorLoopCounts = 512,
.slast = 0,
.dlast = -1024,
.enMajorInt = 1,
.enDreq = 0,
.enTrigger = 0,
.enErrInt = 0,
.startMode = 1
};
for(uint16_t i=0;i<512;i++)
{
adcBuffer[i] = 0x0000;
}
DMAMUX_configModule(DMAMUX1);
DMAMUX_setSource(DMAMUX1, DMA1_channel_0, DMAMUX1_ADCA1_REQ);
DMAMUX_enableChannel(DMAMUX1, DMA1_channel_0);
DMA_configModule(DMA1, &edma_config);
DMA_configChannel(DMA1, &edma_trans_config);
DMA_startTransfer(DMA1, DMA1_channel_0);
}
DMA搬运512个点后会触发DMA完成中断,在完成中断中可以绘制频谱曲线。首先需要得到FFT转换后的各频率幅值↓
int k=0;
for (k=0;k<512;k++){
input[k]=adcBuffer[k];
}
EMATH_setFormat(EMATH_CP_FFT, EMATH_32Bit, 9, 0);
EMATH_transformRFFT(input, 512, output);
EMATH_waitDone();
int l=0;
volatile int32_t A=0,B=0;
for(l=1;l<512;l++){
output2[l]=EMATH_sqrtF32(output[2*l]/256*output[2*l]/256+output[2*l+1]/256*output[2*l+1]/256);
}
output2[0]=EMATH_sqrtF32(output[0]/512*output[0]/512+output[1]/512*output[1]/512);
制作频谱的峰值点保持效果,需要存储上一次的FFT并和这一次的FFT结果进行对比。如果这一次的FFT频谱柱比峰值点高,需要把峰值点抛弃来,向上的速度可以用两次差值乘上系数,这里模拟上抛运动的初速度,当然这个速度要保存下来。以后每一帧对这个速度固定减小一个值,这个值对应重力加速度,公式是v=v0-gt。同时,峰值点的坐标为上一次坐标加上速度,对应公式为s=s0+vt。至此,就将峰值点模拟重力的效果做出来了。程序如下:
int i=0;
for(i=0;i<128;i++){
FFT_Result[i]=(int)(9.2f * EMATH_lnF32(1 + (output2[i+1]>>1)));//柱子
}
for(i=0;i<128;i++){
if(FFT_Result[i]<FFT_Result_point[i]){
FFT_Result_point[i]-=v[i];//峰值点重力降落
v[i]+=g;//重力加速度
}
else {
FFT_Result_point[i]=FFT_Result[i];
v[i]=kv0*(FFT_Result[i]-FFT_Result_last[i]);
}
FFT_Result_point2[i]=FFT_Result_point[i];//转char类型
if(FFT_Result_point2[i]>64)FFT_Result_point2[i]=64;
}
for(i=0;i<128;i++){
if(FFT_Result[i]<FFT_Result_display[i])FFT_Result_display[i]-=2.0;//柱子缓降
else FFT_Result_display[i]=FFT_Result[i];
FFT_Result_int[i]=FFT_Result_display[i];//转char类型
}
OLED_FFT_Function(FFT_Result_int); //柱子添加缓存
OLED_FFT_Function_point(FFT_Result_point2); //点添加缓存
OLED_DISPLAY_RAM();
通过I2C传出数据控制OLED,I2C部分配置代码如下:
static void i2c_init (I2C_TypeDef *i2c)
{
/* I2C1 SCL pinmux control */
GPIO_setPinConfig(GPIO_27_I2CA_SCL);
GPIO_setAnalogMode(GPIO_27, GPIO_ANALOG_DISABLED);
//GPIO_setPadConfig(GPIO_1, GPIO_PIN_TYPE_OD);
GPIO_setPadConfig(GPIO_27, GPIO_PIN_TYPE_PULLUP);
GPIO_setQualificationMode(GPIO_27, GPIO_QUAL_ASYNC);
GPIO_setDirectionMode(GPIO_27, GPIO_DIR_MODE_IN);
/* I2C1 SDA pinmux control */
GPIO_setPinConfig(GPIO_26_I2CA_SDA);
GPIO_setAnalogMode(GPIO_26, GPIO_ANALOG_DISABLED);
//GPIO_setPadConfig(GPIO_0, GPIO_PIN_TYPE_OD);
GPIO_setPadConfig(GPIO_26, GPIO_PIN_TYPE_PULLUP);
GPIO_setQualificationMode(GPIO_26, GPIO_QUAL_ASYNC);
GPIO_setDirectionMode(GPIO_26, GPIO_DIR_MODE_IN);
/* Reset module before configuring it. */
I2C_resetMaster(i2c);
/* Disable debug mode */
/* Disable doze mode */
I2C_disableMasterDebug(i2c);
I2C_disableMasterDoze(i2c);
/* Set master water marks. */
I2C_setMasterWatermarks(i2c, I2C_MASTER_WATERMARK_0, I2C_MASTER_WATERMARK_0);
/* Configure master glitch filters.
And set FILTSDA to 0 disables the fileter, so the min value is 1. */
I2C_setMasterGlitchFilter(i2c, I2C_MASTER_FILTER_PCLK1, I2C_MASTER_FILTER_PCLK1);
/* Configure baudrate after the SDA/SCL glitch filter setting,
since the baudrate calculation needs them as parameter. */
I2C_configMasterBaudRate(i2c, RCC_getPclk2_4Frequency(), 400000UL);//200k速率
/* Configure bus idle timeouts after baudrate setting.
The value is equal to BUSIDLE cycles of functional clock divided by prescaler.
And set BUSIDLE to 0 disables the fileter, so the min value is 1. */
I2C_setMasterBusIdleTimeout(i2c, 1U);
/* Configure pin low timeouts after baudrate setting.
The value is equal to PINLOW cycles of functional clock divided by prescaler.
And set PINLOW to 0 disables the fileter, so the min value is 1. */
I2C_setMasterPinLowTimeout(i2c, I2C_MASTER_PINLOW_SCLSDA, 0U);
/* Config master data match mode, match code and rxmatchonly. */
I2C_configMasterDataMatch(i2c, I2C_MASTER_MATCHMODE_DISABLED, 0U, 0U, I2C_MASTER_RXMATCHDATAONLY_DISABLE);
/* enable master module */
I2C_enableMasterModule(i2c);
}
除此之外,还需要针对I2C写一段发送字节的函数
void I2C_SAND_BUFFER(u8 SlaveAddr,u8 WriteAddr,u8* pBuffer,u16 NumByteToWrite){
status = I2C_sendStart(hi2c, SlaveAddr, I2C_DIRECTION_WRITE);
if (I2C_STATUS_SUCCESS == status)
{
status = I2C_sendSubAddrMaster(hi2c, WriteAddr, I2C_SUBADDRSIZE_1BYTE);
}
if (I2C_STATUS_SUCCESS == status)
{
status = I2C_sendDataMaster(hi2c, pBuffer, NumByteToWrite);
}
if (I2C_STATUS_SUCCESS == status)
{
status = I2C_sendStop(hi2c);
}
}
void I2C_SAND_BYTE(u8 SlaveAddr,u8 writeAddr,u8 pBuffer){
I2C_SAND_BUFFER(SlaveAddr,writeAddr,&pBuffer,1);
}
OLED驱动部分的程序网上有大量代码,此处不再赘述。
至此,程序部分已经全部介绍完了。
五、实物演示及说明
上电后串口自动输出一次“Hello, NOVOSENSE Wedesign project.”。

上电后PWM自动输出2Mhz方波,占空比为50%,由于设置了死区,适合实际应用,所以占空比显示为42% 。

音乐频谱演示

六、遇到的难点及解决方法
1、开发环境
最早打开例程,总是编译不了。详细阅读开发文档之后,才知道是由于Keil版本太低,通过升级Keil版本完美解决。
2、频谱效果
在b站看到有商家出售成品音乐频谱,其中用到了这种峰值保持的效果。但是商家肯定是不会开源的,而且网上也找不到这种效果的源代码。通过多次观察,发现其具有上抛运动的特性,于是按照物理公式写代码,最后完成了这种效果。
七、对本次活动的心得体会(包括意见或建议)
通过本次活动体验了纳芯微的微控制器NS800RT5039,该控制器和TI的C2000在很多模块上具有相似性,所以迁移上手很快。通过体验,感觉这款芯片性能还是比较强劲的,能够胜任数字电源或电机应用的开发,希望后面可以用这套板卡完成更高级的项目。