- 2025-01-13
-
回复了主题帖:
【回顾2024,展望2025】新年抢楼活动来啦!
希望论坛活动更丰富多彩!
-
发表了主题帖:
【MCXA156开发板测评】-7-EDMA3与LPADC1协作搬运
本帖最后由 慕容雪花 于 2025-1-13 14:13 编辑
前面测评:
【MCXA156开发板测评】-1-开发环境搭建与串口回显 https://bbs.eeworld.com.cn/thread-1303370-1-1.html
【MCXA156开发板测评】-2-ADC https://bbs.eeworld.com.cn/thread-1303898-1-1.html
【MCXA156开发板测评】-3-LPTMR体验 https://bbs.eeworld.com.cn/thread-1304161-1-1.html
【MCXA156开发板测评】-4-LPI2C驱动OLED屏幕 https://bbs.eeworld.com.cn/thread-1304186-1-1.html
【MCXA156开发板测评】-5-LPADC中断模式快速配置 https://bbs.eeworld.com.cn/thread-1304340-1-1.html
【MCXA156开发板测评】-6-EDMA3例程代码分析 https://bbs.eeworld.com.cn/thread-1304375-1-1.html
MCX-A156的例程库提供了lpi2c, lpspi,lpuart等模块与edma3模块协作搬运数据的例程,但是没有看到adc与dma的例程。
所以本次实验来尝试在恩智浦mcuconfigtool里面配置edma3模块,并尝试去搬运adc获取到数据。
一、配置adc发送dma请求
在前面的测评文章中,提到了FIFO watermark可以用来作为一个阈值来发起中断请求或者DMA触发。
二、使能并配置dma3
本次实验使用dma0:
配置eDMA通道0的request source等参数:
三、适当修改代码
在生成的ADC1初始化代码中,看到已经使能了DMA请求:
static void ADC1_init(void) {
/* Initialize LPADC converter */
LPADC_Init(ADC1_PERIPHERAL, &ADC1_config);
/* Perform auto calibration */
LPADC_DoAutoCalibration(ADC1_PERIPHERAL);
/* Enable DMA request on FIFO watermark event */
LPADC_EnableFIFOWatermarkDMA(ADC1_PERIPHERAL, true);
/* Configure conversion command 1. */
LPADC_SetConvCommandConfig(ADC1_PERIPHERAL, 1, &ADC1_commandsConfig[0]);
/* Configure trigger 0. */
LPADC_SetConvTriggerConfig(ADC1_PERIPHERAL, 0, &ADC1_triggersConfig[0]);
/* Interrupt vector ADC1_IRQn priority settings in the NVIC. */
NVIC_SetPriority(ADC1_IRQN, ADC1_IRQ_PRIORITY);
/* Enable interrupts from LPADC */
LPADC_EnableInterrupts(ADC1_PERIPHERAL, (kLPADC_FIFO0WatermarkInterruptEnable));
/* Enable interrupt ADC1_IRQN request in the NVIC */
EnableIRQ(ADC1_IRQN);
}
查看dma0的初始化代码:
edma_config_t DMA0_config = {
.enableMasterIdReplication = false,
.enableGlobalChannelLink = true,
.enableHaltOnError = true,
.enableDebugMode = false,
.enableRoundRobinArbitration = false
};
/* Tansactional transfer configurations */
edma_transfer_config_t DMA0_CH0_Transfers_config[1];
edma_handle_t DMA0_CH0_Handle;
/* TCD pool initialization */
EDMA_ALLOCATE_TCD(DMA0_CH0_TCD_pool, DMA0_CH0_TCD_SIZE);
static void DMA0_init(void) {
status_t status;
(void)status;
/* Channel CH0 initialization */
/* Set the kDma0RequestMuxAdc1FifoRequest request */
EDMA_SetChannelMux(DMA0_DMA_BASEADDR, DMA0_CH0_DMA_CHANNEL, DMA0_CH0_DMA_REQUEST);
/* Create the eDMA DMA0_CH0_Handle handle */
EDMA_CreateHandle(&DMA0_CH0_Handle, DMA0_DMA_BASEADDR, DMA0_CH0_DMA_CHANNEL);
/* DMA0 channel 0 reset */
EDMA_ResetChannel(DMA0_DMA_BASEADDR, DMA0_CH0_DMA_CHANNEL);
/* DMA0 channel 0 peripheral request */
EDMA_EnableAsyncRequest(DMA0_DMA_BASEADDR, DMA0_CH0_DMA_CHANNEL, true);
/* Allocate TCD memory pool for scatter-gather mode */
EDMA_InstallTCDMemory(&DMA0_CH0_Handle, DMA0_CH0_TCD_pool, DMA0_CH0_TCD_SIZE);
/* DMA callback initialization */
EDMA_SetCallback(&DMA0_CH0_Handle, DMA_Callback, NULL);
/* DMA0 transfer CH0_TRANSFER0 configuration */
EDMA_PrepareTransferConfig(&DMA0_CH0_TRANSFER0_CONFIG, (void *) (uint32_t)(&(ADC1->RESFIFO)) , 1 << kEDMA_TransferSize4Bytes, 0, (void *) &destAddr[0], 1 << kEDMA_TransferSize4Bytes, sizeof(destAddr[0]), 4U, 80U);
DMA0_CH0_TRANSFER0_CONFIG.dstMajorLoopOffset = 4;
/* DMA0 loop transfer submit */
status = EDMA_SubmitLoopTransfer(&DMA0_CH0_Handle, DMA0_CH0_Transfers_config, 1U);
assert(status == kStatus_Success);
/* DMA0 hardware channel 0 request auto stop */
EDMA_EnableAutoStopRequest(DMA0_DMA_BASEADDR, DMA0_CH0_DMA_CHANNEL, true);
/* DMA0 channel 0 peripheral request */
EDMA_EnableChannelRequest(DMA0_DMA_BASEADDR, DMA0_CH0_DMA_CHANNEL);
}
定义相关的目标数组:
extern edma_handle_t DMA0_CH0_Handle;
volatile bool g_transferDone = false;
uint32_t destAddr[20];
/* DMA0 TCD CH0_TRANSFER0 destination address */
AT_NONCACHEABLE_SECTION_ALIGN_INIT(uint32_t destAddr[], 4);
/* DMA channel DMA0_CH0 callback function */
void DMA_Callback(edma_handle_t *handle, void *param, bool transferDone, uint32_t tcds) {
/* Place your code here */
if (transferDone)
{
g_transferDone = true;
}
PRINTF("\r\n DMA_Callback Done");
}
启动DMA:
EDMA_StartTransfer(&DMA0_CH0_Handle);
之后等待DMA传输完成标志位在DMA CALLBAKC函数中置位:
/* Wait for EDMA transfer finish */
while (!g_transferDone)
{
LPADC_DoSoftwareTrigger(ADC1_PERIPHERAL, 1U);
}
/* Print destination buffer */
PRINTF("\r\n\r\nEDMA transfer ADC1 finish.\r\n\r\n");
PRINTF("Destination Buffer:\r\n");
for (uint8_t i = 0; i < 20; i++)
{
PRINTF("%d\t", (uint16_t)(destAddr[i]& ADC_RESFIFO_D_MASK));
}
PRINTF("\r\n END TEST");
while(1){
}
四、运行
ADC的输入信号是连接到3.3V电源上,这样简便些。
采集一下声音传感器数据:
五、总结
eDMA是恩智浦在MCXA156产品上提供的增强型DMA控制器,可以在不占用CPU资源来进行数据搬运。这种工作模式可以大大减轻CPU的负担。
借助恩智浦的MCU CONFIGTOOL,可以方便的配置eDMA模块和各种外设模式,自动生成驱动代码。用户只需要添加数行业务代码即可轻松使用eDMA模块。
参考:
Using Configtools to set up ADC and DMA from scratch - Sample Project LPC55S69 https://community.nxp.com/t5/MCUXpresso-Config-Tools/Using-Configtools-to-set-up-ADC-and-DMA-from-scratch-Sample/m-p/1183770
-
发表了主题帖:
【MCXA156开发板测评】-6-EDMA3例程代码分析
本帖最后由 慕容雪花 于 2025-1-12 20:16 编辑
例程:edma3_ping_pong_transfer
1. 首先进行EDMA3初始化:
/*!
* brief Initializes the eDMA peripheral.
*
* This function ungates the eDMA clock and configures the eDMA peripheral according
* to the configuration structure.
*
* param base eDMA peripheral base address.
* param config A pointer to the configuration structure, see "edma_config_t".
* note This function enables the minor loop map feature.
*/
void EDMA_Init(EDMA_Type *base, const edma_config_t *config)
{
assert(config != NULL);
assert(FSL_FEATURE_EDMA_INSTANCE_CHANNELn(base) != -1);
uint32_t tmpreg, i = 0U;
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/* Ungate EDMA peripheral clock */
CLOCK_EnableClock(s_edmaClockName[EDMA_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
#if defined(EDMA_RESETS_ARRAY)
RESET_ReleasePeripheralReset(s_edmaResets[EDMA_GetInstance(base)]);
#endif
#if defined(FSL_EDMA_SOC_IP_EDMA) && FSL_EDMA_SOC_IP_EDMA
/* clear all the enabled request, status to make sure EDMA status is in normal condition */
EDMA_BASE(base)->ERQ = 0U;
EDMA_BASE(base)->INT = 0xFFFFFFFFU;
EDMA_BASE(base)->ERR = 0xFFFFFFFFU;
/* Configure EDMA peripheral according to the configuration structure. */
tmpreg = EDMA_BASE(base)->CR;
tmpreg &= ~(DMA_CR_ERCA_MASK | DMA_CR_HOE_MASK | DMA_CR_CLM_MASK | DMA_CR_EDBG_MASK);
tmpreg |= (DMA_CR_ERCA(config->enableRoundRobinArbitration) | DMA_CR_HOE(config->enableHaltOnError) |
DMA_CR_CLM(config->enableContinuousLinkMode) | DMA_CR_EDBG(config->enableDebugMode) | DMA_CR_EMLM(1U));
EDMA_BASE(base)->CR = tmpreg;
#else
tmpreg = EDMA_MP_BASE(base)->MP_CSR;
#if defined FSL_FEATURE_EDMA_HAS_GLOBAL_MASTER_ID_REPLICATION && FSL_FEATURE_EDMA_HAS_GLOBAL_MASTER_ID_REPLICATION
tmpreg = (tmpreg & ~(DMA_MP_CSR_HAE_MASK | DMA_MP_CSR_ERCA_MASK | DMA_MP_CSR_EDBG_MASK | DMA_MP_CSR_GCLC_MASK |
DMA_MP_CSR_GMRC_MASK | DMA_MP_CSR_HALT_MASK)) |
DMA_MP_CSR_GMRC(config->enableMasterIdReplication) | DMA_MP_CSR_HAE(config->enableHaltOnError) |
DMA_MP_CSR_ERCA(config->enableRoundRobinArbitration) | DMA_MP_CSR_EDBG(config->enableDebugMode) |
DMA_MP_CSR_GCLC(config->enableGlobalChannelLink);
#else
tmpreg = (tmpreg & ~(DMA_MP_CSR_HAE_MASK | DMA_MP_CSR_ERCA_MASK | DMA_MP_CSR_EDBG_MASK | DMA_MP_CSR_GCLC_MASK |
DMA_MP_CSR_HALT_MASK)) |
DMA_MP_CSR_HAE(config->enableHaltOnError) | DMA_MP_CSR_ERCA(config->enableRoundRobinArbitration) |
DMA_MP_CSR_EDBG(config->enableDebugMode) | DMA_MP_CSR_GCLC(config->enableGlobalChannelLink);
#endif
EDMA_MP_BASE(base)->MP_CSR = tmpreg;
#if defined FSL_FEATURE_EDMA_HAS_CHANNEL_CONFIG && FSL_FEATURE_EDMA_HAS_CHANNEL_CONFIG
/* channel transfer configuration */
for (i = 0U; i < (uint32_t)FSL_FEATURE_EDMA_INSTANCE_CHANNELn(base); i++)
{
if (config->channelConfig[i] != NULL)
{
EDMA_InitChannel(base, i, config->channelConfig[i]);
}
}
#endif
#endif
}
2. 紧接着创建一个edma句柄,用于后续操作edma:
EDMA_CreateHandle(&g_DMA_Handle, EXAMPLE_DMA_BASEADDR, DEMO_DMA_CHANNEL_0);
/*!
* brief Creates the eDMA handle.
*
* This function is called if using the transactional API for eDMA. This function
* initializes the internal state of the eDMA handle.
*
* param handle eDMA handle pointer. The eDMA handle stores callback function and
* parameters.
* param base eDMA peripheral base address.
* param channel eDMA channel number.
*/
void EDMA_CreateHandle(edma_handle_t *handle, EDMA_Type *base, uint32_t channel)
{
assert(handle != NULL);
assert(FSL_FEATURE_EDMA_INSTANCE_CHANNELn(base) != -1);
assert(channel < (uint32_t)FSL_FEATURE_EDMA_INSTANCE_CHANNELn(base));
uint32_t edmaInstance;
edma_tcd_t *tcdRegs;
/* Zero the handle */
(void)memset(handle, 0, sizeof(*handle));
handle->channel = channel;
/* Get the DMA instance number */
edmaInstance = EDMA_GetInstance(base);
s_EDMAHandle[edmaInstance][channel] = handle;
/* Enable NVIC interrupt */
(void)EnableIRQ(s_edmaIRQNumber[edmaInstance][channel]);
handle->tcdBase = EDMA_TCD_BASE(base, channel);
handle->channelBase = EDMA_CHANNEL_BASE(base, channel);
handle->base = base;
/*
Reset TCD registers to zero. Unlike the EDMA_TcdReset(DREQ will be set),
CSR will be 0. Because in order to suit EDMA busy check mechanism in
EDMA_SubmitTransfer, CSR must be set 0.
*/
tcdRegs = handle->tcdBase;
EDMA_TCD_SADDR(tcdRegs, EDMA_TCD_TYPE(base)) = 0;
EDMA_TCD_SOFF(tcdRegs, EDMA_TCD_TYPE(base)) = 0;
EDMA_TCD_ATTR(tcdRegs, EDMA_TCD_TYPE(base)) = 0;
EDMA_TCD_NBYTES(tcdRegs, EDMA_TCD_TYPE(base)) = 0;
EDMA_TCD_SLAST(tcdRegs, EDMA_TCD_TYPE(base)) = 0;
EDMA_TCD_DADDR(tcdRegs, EDMA_TCD_TYPE(base)) = 0;
EDMA_TCD_DOFF(tcdRegs, EDMA_TCD_TYPE(base)) = 0;
EDMA_TCD_CITER(tcdRegs, EDMA_TCD_TYPE(base)) = 0;
EDMA_TCD_DLAST_SGA(tcdRegs, EDMA_TCD_TYPE(base)) = 0;
EDMA_TCD_CSR(tcdRegs, EDMA_TCD_TYPE(base)) = 0;
EDMA_TCD_BITER(tcdRegs, EDMA_TCD_TYPE(base)) = 0;
}
3. 设置传输完成后的回调函数:
EDMA_SetCallback(&g_DMA_Handle, DMA_Callback, NULL);
/* User callback function for EDMA transfer. */
void DMA_Callback(edma_handle_t *handle, void *param, bool transferDone, uint32_t tcds)
{
if (transferDone)
{
g_transferDone = true;
}
}
4. 关联TCD 即传输控制描述符
EDMA_InstallTCDMemory(&g_DMA_Handle, g_DMA_Tcd, 2);
/*!
* brief Installs the TCDs memory pool into the eDMA handle.
*
* This function is called after the EDMA_CreateHandle to use scatter/gather feature. This function shall only be used
* while users need to use scatter gather mode. Scatter gather mode enables EDMA to load a new transfer control block
* (tcd) in hardware, and automatically reconfigure that DMA channel for a new transfer.
* Users need to prepare tcd memory and also configure tcds using interface EDMA_SubmitTransfer.
*
* param handle eDMA handle pointer.
* param tcdPool A memory pool to store TCDs. It must be 32 bytes aligned.
* param tcdSize The number of TCD slots.
*/
void EDMA_InstallTCDMemory(edma_handle_t *handle, edma_tcd_t *tcdPool, uint32_t tcdSize)
{
assert(handle != NULL);
assert(((uint32_t)tcdPool & 0x1FU) == 0U);
/* Initialize tcd queue attribute. */
/* header should initial as 1, since that it is used to point to the next TCD to be loaded into TCD memory,
* In EDMA driver IRQ handler, header will be used to calculate how many tcd has done, for example,
* If application submit 4 transfer request, A->B->C->D,
* when A finshed, the header is 0, C is the next TCD to be load, since B is already loaded,
* according to EDMA driver IRQ handler, tcdDone = C - A - header = 2 - header = 2, but actually only 1 TCD done,
* so the issue will be the wrong TCD done count will pass to application in first TCD interrupt.
* During first submit, the header should be assigned to 1, since 0 is current one and 1 is next TCD to be loaded,
* but software cannot know which submission is the first one, so assign 1 to header here.
*/
handle->header = 1;
handle->tcdUsed = 0;
handle->tcdSize = (int8_t)tcdSize;
handle->tcdPool = tcdPool;
}
5. 设置传输的源地址、目的地址、传输数据量等:
EDMA_PrepareTransfer(&transferConfig[0], &srcAddr[0], sizeof(srcAddr[0]), &destAddr[0], sizeof(destAddr[0]),
sizeof(uint32_t) * HALF_BUFFER_LENGTH, sizeof(uint32_t) * HALF_BUFFER_LENGTH,
kEDMA_MemoryToMemory);
EDMA_PrepareTransfer(&transferConfig[1], &srcAddr[4], sizeof(srcAddr[0]), &destAddr[4], sizeof(destAddr[0]),
sizeof(uint32_t) * HALF_BUFFER_LENGTH, sizeof(uint32_t) * HALF_BUFFER_LENGTH,
kEDMA_MemoryToMemory);
EDMA_SubmitLoopTransfer(&g_DMA_Handle, transferConfig, 2);
EDMA_StartTransfer(&g_DMA_Handle);
EDMA进行传输前,Destination Buffer内容为:
第一次DMA传输完成后:
第二次EDMA传输完成后:
- 2025-01-11
-
发表了主题帖:
【MCXA156开发板测评】-5-LPADC中断模式快速配置
本帖最后由 慕容雪花 于 2025-1-12 11:11 编辑
上期测评:【MCXA156开发板测评】-4-LPI2C驱动OLED屏幕 https://bbs.eeworld.com.cn/thread-1304186-1-1.html
本次体验使用恩智浦MCUXPRESSO内置的CONFIGTOOL来配置LPADC的中断模式。LPADC的FIFO Control Register (FCTRL0)可以设置“Watermark Level Selection”。
当ADC转换结果数量大于设置的阈值FIFO 0 watermark后:
触发ISR(在FWMIE置位的情况下)
或者进行DMA请求(在FWMDE置位的情况下)
在IRQ服务程序中可以设置一个转换完成标志,方便在主程序中获取转换结果。
一、CONFIGTOOL配置
设置FIFO0WATERMARK为0:
选择中断源,并使能中断向量。
二、代码适配
初始化代码:
/***********************************************************************************************************************
* ADC1 initialization code
**********************************************************************************************************************/
/* clang-format off */
/* TEXT BELOW IS USED AS SETTING FOR TOOLS *************************************
instance:
- name: 'ADC1'
- type: 'lpadc'
- mode: 'LPADC'
- custom_name_enabled: 'false'
- type_id: 'lpadc_2.8.1'
- functional_group: 'BOARD_InitPeripherals'
- peripheral: 'ADC1'
- config_sets:
- fsl_lpadc:
- lpadcConfig:
- clockSource: 'AsynchronousFunctionClock'
- clockSourceFreq: 'ClocksTool_DefaultInit'
- enableInDozeMode: 'false'
- conversionAverageMode: 'kLPADC_ConversionAverage8'
- offsetCalibration: 'no'
- autoCalibrate: 'true'
- enableAnalogPreliminary: 'true'
- powerUpDelay: '0x80'
- referenceVoltageSource: 'kLPADC_ReferenceVoltageAlt3'
- powerLevelMode: 'kLPADC_PowerLevelAlt4'
- triggerPriorityPolicy: 'kLPADC_ConvPreemptImmediatelyNotAutoResumed'
- enableConvPause: 'false'
- convPauseDelay: '0'
- FIFOWatermark: '0'
- FIFOWatermarkDMA: 'false'
- lpadcConvCommandConfig:
- 0:
- user_commandId: ''
- commandId: '1'
- chainedNextCommandNumber: '0'
- sampleChannelMode: 'kLPADC_SampleChannelSingleEndSideA'
- channelNumber: 'A.21'
- enableAutoChannelIncrement: 'false'
- loopCount: '0'
- hardwareAverageMode: 'kLPADC_HardwareAverageCount1'
- sampleTimeMode: 'kLPADC_SampleTimeADCK3'
- hardwareCompareMode: 'kLPADC_HardwareCompareDisabled'
- hardwareCompareValueHigh: '0'
- hardwareCompareValueLow: '0'
- conversionResoultuionMode: 'kLPADC_ConversionResolutionHigh'
- enableWaitTrigger: 'false'
- lpadcConvTriggerConfig:
- 0:
- user_triggerId: ''
- triggerId: '0'
- targetCommandId: '1'
- delayPower: '0'
- priority: 'false'
- enableHardwareTrigger: 'false'
- IRQ_cfg:
- interrupt_type: 'kLPADC_FIFO0WatermarkInterruptEnable'
- enable_irq: 'true'
- adc_interrupt:
- IRQn: 'ADC1_IRQn'
- enable_interrrupt: 'enabled'
- enable_priority: 'true'
- priority: '0'
- enable_custom_name: 'false'
* BE CAREFUL MODIFYING THIS COMMENT - IT IS YAML SETTINGS FOR TOOLS **********/
/* clang-format on */
const lpadc_config_t ADC1_config = {
.enableInDozeMode = false,
.conversionAverageMode = kLPADC_ConversionAverage8,
.enableAnalogPreliminary = true,
.powerUpDelay = 0x80UL,
.referenceVoltageSource = kLPADC_ReferenceVoltageAlt3,
.powerLevelMode = kLPADC_PowerLevelAlt4,
.triggerPriorityPolicy = kLPADC_ConvPreemptImmediatelyNotAutoResumed,
.enableConvPause = false,
.convPauseDelay = 0UL,
.FIFOWatermark = 0UL,
};
lpadc_conv_command_config_t ADC1_commandsConfig[1] = {
{
.sampleChannelMode = kLPADC_SampleChannelSingleEndSideA,
.channelNumber = 21U,
.chainedNextCommandNumber = 0,
.enableAutoChannelIncrement = false,
.loopCount = 0UL,
.hardwareAverageMode = kLPADC_HardwareAverageCount1,
.sampleTimeMode = kLPADC_SampleTimeADCK3,
.hardwareCompareMode = kLPADC_HardwareCompareDisabled,
.hardwareCompareValueHigh = 0UL,
.hardwareCompareValueLow = 0UL,
.conversionResolutionMode = kLPADC_ConversionResolutionHigh,
.enableWaitTrigger = false
}
};
lpadc_conv_trigger_config_t ADC1_triggersConfig[1] = {
{
.targetCommandId = 1,
.delayPower = 0UL,
.priority = 1,
.enableHardwareTrigger = false
}
};
static void ADC1_init(void) {
/* Initialize LPADC converter */
LPADC_Init(ADC1_PERIPHERAL, &ADC1_config);
/* Perform auto calibration */
LPADC_DoAutoCalibration(ADC1_PERIPHERAL);
/* Configure conversion command 1. */
LPADC_SetConvCommandConfig(ADC1_PERIPHERAL, 1, &ADC1_commandsConfig[0]);
/* Configure trigger 0. */
LPADC_SetConvTriggerConfig(ADC1_PERIPHERAL, 0, &ADC1_triggersConfig[0]);
/* Interrupt vector ADC1_IRQn priority settings in the NVIC. */
NVIC_SetPriority(ADC1_IRQN, ADC1_IRQ_PRIORITY);
/* Enable interrupts from LPADC */
LPADC_EnableInterrupts(ADC1_PERIPHERAL, (kLPADC_FIFO0WatermarkInterruptEnable));
/* Enable interrupt ADC1_IRQN request in the NVIC */
EnableIRQ(ADC1_IRQN);
}
设置转换完成标志位。
/* ADC1_IRQn interrupt handler */
void ADC1_IRQHANDLER(void) {
/* Place your code here */
if (LPADC_GetConvResult(ADC1_PERIPHERAL, &mLpadcResultConfigStruct))
{
g_LpadcConversionCompletedFlag = true;
}
#if defined __CORTEX_M && (__CORTEX_M == 4U)
__DSB();
#endif
}
在主程序中:
if(g_LpadcConversionCompletedFlag)
{
PRINTF("%d\r\n", ((mLpadcResultConfigStruct.convValue)));
g_LpadcConversionCompletedFlag = false;
}
三、实物测试
MIKROBUS P3_30(功能:LPADC1_A21)引脚接入模拟声音传感器。
传感器型号:DFROBOT SEN0487。
这是一款超小体积的MEMS麦克风。放大器增益为66,当没有检测到声音时,输出电压在1.5V左右浮动,在说话时通过ADC采样可以很容易看到声音的波形。兼容3.3V/5V,可以很轻松的用在各类主控上。引脚说明:
串口输出采集到的声音传感器数据并发送到上位机:
四、总结
使用恩智浦MCUXPRESSO以及MCUCONFIGTOOL非常方便的配置LPADC的各种工作模式与参数,自动化生成代码节省了大量的时间,方便快速上述ADC模块。当然LPADC还有低功耗模式等多个特点有待探索。
-
回复了主题帖:
【MCXA156开发板测评】-2-ADC
补充一下ADC1的配置:
- 2025-01-10
-
回复了主题帖:
【回顾2024,展望2025】新年抢楼活动来啦!
立一个FLAG:争取拿到测评邀请券:pleased:
-
发表了主题帖:
【MCXA156开发板测评】-4-LPI2C驱动OLED屏幕
在DIY小制作的时候,串口打印是查看调试信息的好方式,还可以增加一块具有I2C接口的小屏幕在板子上,展示一些数据或者打印一些调试信息,也是不错的选择。
本次体验了
在NXP官方IDE MCUXPRESSO中使用内置的CONFIGTOOL来配置I2C的时钟、外设参数,然后自动生成初始化代码;
利用上次体验的LPTMR0设计简易延迟函数
增加OLED驱动代码并适配MCAX156的I2C传输方式
成功点亮屏幕,并设计了简易计时器
一、OLED屏幕介绍
引脚介绍:
1. GND 电源地
2. VCC 电源正(3~5.5V)
3. SCL OLED 的D0 脚,在IIC 通信中为时钟管脚
4. SDA OLED 的D1 脚,在IIC 通信中为数据管脚
二、MCU CONFIG TOOL配置I2C
本次实验使用的I2C是LPI2C3,开发板已经引出SCL到Mikro BUS接口的P3_27和SDA到Mikro BUS接口的P3_28
配置LPI2C3时钟:
然后配置外设引脚:
配置LPI2C3外设参数:
注意:
上述Follower Address: 0x3C是OLED屏幕的7-bit地址,并且没有Sub-address。
BufferSize设置为2是对应OLED写数据是0x40 + uint8_t,写指令是0x00 + uint8_t。这两种情况都是2个byte
三、生成I2C初始化代码
点击update code ,自动生成初始化代码:
/***********************************************************************************************************************
* LPI2C3 initialization code
**********************************************************************************************************************/
/* clang-format off */
/* TEXT BELOW IS USED AS SETTING FOR TOOLS *************************************
instance:
- name: 'LPI2C3'
- type: 'lpi2c'
- mode: 'master'
- custom_name_enabled: 'false'
- type_id: 'lpi2c_2.2.0'
- functional_group: 'BOARD_InitPeripherals'
- peripheral: 'LPI2C3'
- config_sets:
- main:
- clockSource: 'Lpi2cClock'
- clockSourceFreq: 'ClocksTool_DefaultInit'
- interrupt_vector: []
- master:
- mode: 'transfer'
- config:
- enableMaster: 'true'
- enableDoze: 'true'
- debugEnable: 'false'
- ignoreAck: 'false'
- pinConfig: 'kLPI2C_2PinOpenDrain'
- baudRate_Hz: '100000'
- busIdleTimeout_ns: '0'
- pinLowTimeout_ns: '0'
- sdaGlitchFilterWidth_ns: '0'
- sclGlitchFilterWidth_ns: '0'
- hostRequest:
- enable: 'false'
- source: 'kLPI2C_HostRequestExternalPin'
- polarity: 'kLPI2C_HostRequestPinActiveHigh'
- edmaRequestSources: ''
- transfer:
- blocking: 'false'
- enable_custom_handle: 'false'
- callback:
- name: ''
- userData: ''
- flags: ''
- slaveAddress: '0x3C'
- direction: 'kLPI2C_Write'
- subaddress: '0'
- subaddressSize: '0'
- blocking_buffer: 'false'
- enable_custom_buffer: 'false'
- dataSize: '2'
* BE CAREFUL MODIFYING THIS COMMENT - IT IS YAML SETTINGS FOR TOOLS **********/
/* clang-format on */
const lpi2c_master_config_t LPI2C3_masterConfig = {
.enableMaster = true,
.enableDoze = true,
.debugEnable = false,
.ignoreAck = false,
.pinConfig = kLPI2C_2PinOpenDrain,
.baudRate_Hz = 100000UL,
.busIdleTimeout_ns = 0UL,
.pinLowTimeout_ns = 0UL,
.sdaGlitchFilterWidth_ns = 0U,
.sclGlitchFilterWidth_ns = 0U,
.hostRequest = {
.enable = false,
.source = kLPI2C_HostRequestExternalPin,
.polarity = kLPI2C_HostRequestPinActiveHigh
}
};
lpi2c_master_transfer_t LPI2C3_masterTransfer = {
.flags = kLPI2C_TransferDefaultFlag,
.slaveAddress = 0x3C,
.direction = kLPI2C_Write,
.subaddress = 0,
.subaddressSize = 0,
.data = LPI2C3_masterBuffer,
.dataSize = 2
};
lpi2c_master_handle_t LPI2C3_masterHandle;
uint8_t LPI2C3_masterBuffer[LPI2C3_MASTER_BUFFER_SIZE];
static void LPI2C3_init(void) {
LPI2C_MasterInit(LPI2C3_PERIPHERAL, &LPI2C3_masterConfig, LPI2C3_CLOCK_FREQ);
LPI2C_MasterTransferCreateHandle(LPI2C3_PERIPHERAL, &LPI2C3_masterHandle, NULL, NULL);
}
四、适配OLED驱动
OLED在初始化的时候用到了延迟,利用上次体验的LPTMR0来制作N毫秒的延迟:
void HAL_Delay(uint32_t N_ms){
static uint32_t i;
i = cnt;
while((cnt - i) < N_ms){
;
}
}
cnt在LPTMR0_ISR中1ms更新一次数值:
void LPTMR0_IRQHANDLER(void) {
uint32_t intStatus;
/* Reading all interrupt flags of status register */
intStatus = LPTMR_GetStatusFlags(LPTMR0_PERIPHERAL);
LPTMR_ClearStatusFlags(LPTMR0_PERIPHERAL, intStatus);
/* Place your code here */
cnt++;
if(cnt%1000 == 0){
/* Toggle pin connected to LED */
GPIO_PortToggle(GPIO3, 1u << 0);
PRINTF("\r\n GPIO3_0 RED LED TOOGLED, testVal = %d", testVal++);
}
/* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F
Store immediate overlapping exception return operation might vector to incorrect interrupt. */
#if defined __CORTEX_M && (__CORTEX_M == 4U)
__DSB();
#endif
}
这样就快速实现了N毫秒延迟。
接下来就是怎么把指令或者数据通过LPI2C3总线发送出去:
/***************************************************
I2C总线传出数据函数:
addr : 要写入的地址(OLED的地址一般为0x40;指令地址为0x00)
data : 要写入的数据
***************************************************/
extern lpi2c_master_transfer_t LPI2C3_masterTransfer;
extern uint8_t LPI2C3_masterBuffer[LPI2C3_MASTER_BUFFER_SIZE];
void NXP_I2C_WriteByte(uint8_t addr,uint8_t data)
{
LPI2C3_masterBuffer[0] = addr;
LPI2C3_masterBuffer[1] = data;
LPI2C_MasterTransferBlocking(LPI2C3_PERIPHERAL, &LPI2C3_masterTransfer);
}
五、添加应用代码
主函数代码:
/*
* @brief Application entry point.
*/
int main(void) {
/* Init board hardware. */
BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitBootPeripherals();
//SysTick_Config(CLOCK_GetSystickClkFreq());
/* Init FSL debug console. */
BOARD_InitDebugConsole();
PRINTF("Hello World\r\n");
PRINTF("CLOCK_GetCoreSysClkFreq = %8d\r\n", CLOCK_GetCoreSysClkFreq());
PRINTF("CLOCK_GetMainClk = %8d\r\n", CLOCK_GetMainClk());
PRINTF("CLOCK_GetSystickClkFreq = %8d\r\n", CLOCK_GetSystickClkFreq());
OLED_Init();
OLED_Refresh();
OLED_Clear();
OLED_ShowString(0, 2, "NXP 2025-01-10", 8, 1);
OLED_DrawLine(0,13, 127,13,1);
OLED_ShowString(44, 16, "10:00", 16, 1);
OLED_ShowString(86, 24, "00", 8, 1);
OLED_DrawLine(0,33, 127,33,1);
OLED_ShowString(0, 36, "Drive by HwIIC", 8, 1);
OLED_ShowString(0, 46, "Dev Addr: 0x3C", 8, 1);
OLED_ShowString(0, 55, "Good Day", 8, 1);
//OLED_ShowString(0, 12, "WB09KE", 8, 1);
OLED_Refresh();
/* Force the counter to be placed into memory. */
/* Enter an infinite loop, just incrementing a counter. */
while(1) {
sprintf(testValString, "%2dh:%2dmin:%2ds", testVal/3600, (testVal%3600)/60, (testVal%3600)%60);
OLED_ShowString(24, 16, testValString,12,0);
OLED_Refresh();
}
return 0 ;
}
-
回复了主题帖:
【MCXA156开发板测评】-3-LPTMR体验
Jacktang 发表于 2025-1-10 07:47
添加该模块后,进入到配置界面,然后的操作好像有点麻烦了
确实,这个芯片的功能还是很强大的,也意味着更多的参数需要去了解,配置。
- 2025-01-09
-
发表了主题帖:
【MCXA156开发板测评】-3-LPTMR体验
本帖最后由 慕容雪花 于 2025-1-10 07:04 编辑
MCXA156片上定时器外设可谓是非常丰富,有Standart Counter/Timers 即CTimer, 还有Windowed Watchdog Timer,Micro-Tick Timer, OS Event Timer,Wake Timer。
在NXP的论坛上,也找到了一些对不同种类的NXP片上定时器的介绍:链接
1)SCTimer/PWM: can generate complicated PWM signal to control even motor, have capture function, can generate interrupt with programmable interval, can be a frequency divider.
2)CTimer: can generate simple PWM signal, can generate interrupt with programmable interval, has capture function, can be a frequency divider.
3)Multi-Rate Timer (MRT): can only generate interrupt
4)Repetitive Interrupt Timer (RIT): can only generate interrupt
5)System tick timer (SYSTICK): can generate interrupt for core
6)Micro-tick Timer (UTICK): can generate interrupt, and have capture function
本次体验一下使用NXP的工具全家桶MCUXPRESSO IDE配合内置的MCU CONFIG TOOL来快速制作一个1ms时基的中断服务程序,来闪烁小灯,串口输出。
首先点击使能LPTMR的时钟:
点击IDE右上角的USB符号,进入到外设配置界面。
之后可以输入LPTMR0进行搜索,添加该模块后,进入到配置界面,比如运行模式,频率,是否开启中断,是否要在初始化函数中启动LPTMR0等。
区别于别的IDE,在MCU ConfigTool里面还提供了ISR模板,可以点击“Copy to clipboard”来复制然后粘贴到自己对应用代码中。
如下所示即为添加了闪烁小灯与串口打印后的ISR。
/* LPTMR0_IRQn interrupt handler */
void LPTMR0_IRQHANDLER(void) {
uint32_t intStatus;
/* Reading all interrupt flags of status register */
intStatus = LPTMR_GetStatusFlags(LPTMR0_PERIPHERAL);
LPTMR_ClearStatusFlags(LPTMR0_PERIPHERAL, intStatus);
/* Place your code here */
static uint32_t cnt = 0;
cnt++;
if(cnt%1000 == 0){
/* Toggle pin connected to LED */
GPIO_PortToggle(GPIO3, 1u << 0);
PRINTF("\r\n GPIO3_0 RED LED TOOGLED, cnt = %d", cnt);
}
/* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F
Store immediate overlapping exception return operation might vector to incorrect interrupt. */
#if defined __CORTEX_M && (__CORTEX_M == 4U)
__DSB();
#endif
}
下面是IDE自动生成的LPTMR0的配置及初始化代码:
const lptmr_config_t LPTMR0_config = {
.timerMode = kLPTMR_TimerModeTimeCounter,
.pinSelect = kLPTMR_PinSelectInput_0,
.pinPolarity = kLPTMR_PinPolarityActiveHigh,
.enableFreeRunning = false,
.bypassPrescaler = true,
.prescalerClockSource = kLPTMR_PrescalerClock_3,
.value = kLPTMR_Prescale_Glitch_0
};
static void LPTMR0_init(void) {
/* Initialize the LPTMR */
LPTMR_Init(LPTMR0_PERIPHERAL, &LPTMR0_config);
/* Set LPTMR period */
LPTMR_SetTimerPeriod(LPTMR0_PERIPHERAL, LPTMR0_TICKS);
/* Configure timer interrupt */
LPTMR_EnableInterrupts(LPTMR0_PERIPHERAL, kLPTMR_TimerInterruptEnable);
/* Interrupt vector LPTMR0_IRQn priority settings in the NVIC. */
NVIC_SetPriority(LPTMR0_IRQN, LPTMR0_IRQ_PRIORITY);
/* Enable interrupt LPTMR0_IRQN request in the NVIC */
EnableIRQ(LPTMR0_IRQN);
/* Start the LPTMR timer */
LPTMR_StartTimer(LPTMR0_PERIPHERAL);
}
烧录,全速运行,看到串口大概1s输出一次。
换个带时间戳的串口助手,看的更细节:
- 2025-01-07
-
发表了主题帖:
【MCXA156开发板测评】-2-ADC
本帖最后由 慕容雪花 于 2025-1-9 10:25 编辑
MCXA156具有非常强大的模拟外设ADC (2路16 bit ADC0与ADC1)与一路12 bit DAC,还配有一路运放单元,2路LPCMP,还有一个温度传感器。
ADC有ADC0与ADC1.
开发板的mikro-bus接口上引出了I2C接口可以后续驱动OLED,还有个ADC引脚P3_30: ANALOG-ADC1_A21,可以作为数据采集入口。
温度传感器TMP235介绍
Adafruit退出的TMP235温度传感器有三个引脚,分别是GND, VCC和模拟温度信号。其中,模块可以3v-5v供电,在外界温度为-50°C时,模拟温度信号的输出为0v;在外界温度为125°C时,模拟温度信号的输出为1.75v
此外,手册还提供了一个通过电压计算温度的公式: Temp °C = 100*(reading in V) - 50。因此,接下来通过ADC获取温度值后,可以方便的进行温度监测等后续功能开发。
硬件实物连接图:
在代码中指定使用ADC1_A21通道:
#define DEMO_LPADC_BASE ADC1
#define DEMO_LPADC_USER_CHANNEL 21U
#define DEMO_LPADC_USER_CMDID 1U /* CMD1 */
#define DEMO_LPADC_VREF_SOURCE kLPADC_ReferenceVoltageAlt3 /* VDDA */
#define DEMO_LPADC_DO_OFFSET_CALIBRATION true
#define DEMO_LPADC_USE_HIGH_RESOLUTION true
使用ADC模块的第一步是初始化一个ADC默认配置结构体。
LPADC_GetDefaultConfig(&mLpadcConfigStruct);
mLpadcConfigStruct.enableAnalogPreliminary = true;
#if defined(DEMO_LPADC_VREF_SOURCE)
mLpadcConfigStruct.referenceVoltageSource = DEMO_LPADC_VREF_SOURCE;
#endif /* DEMO_LPADC_VREF_SOURCE */
#if defined(FSL_FEATURE_LPADC_HAS_CTRL_CAL_AVGS) && FSL_FEATURE_LPADC_HAS_CTRL_CAL_AVGS
mLpadcConfigStruct.conversionAverageMode = kLPADC_ConversionAverage128;
#endif /* FSL_FEATURE_LPADC_HAS_CTRL_CAL_AVGS */
LPADC_Init(DEMO_LPADC_BASE, &mLpadcConfigStruct);
其中,enableAnalogPreliminary是预先使能ADC相关的模拟电路从而快速开始转换,代价是较高的电流消耗。
bool enableAnalogPreliminary; /*!< ADC analog circuits are pre-enabled and ready to execute conversions without
startup delays(at the cost of higher DC current consumption). */
其次,mLpadcConfigStruct.conversionAverageMode = kLPADC_ConversionAverage128,是在存储转换结果之前在硬件电路上进行均值处理。
接下来配置ADC转换命令:
LPADC_GetDefaultConvCommandConfig(&mLpadcCommandConfigStruct);
mLpadcCommandConfigStruct.channelNumber = DEMO_LPADC_USER_CHANNEL;
#if defined(DEMO_LPADC_USE_HIGH_RESOLUTION) && DEMO_LPADC_USE_HIGH_RESOLUTION
mLpadcCommandConfigStruct.conversionResolutionMode = kLPADC_ConversionResolutionHigh;
#endif /* DEMO_LPADC_USE_HIGH_RESOLUTION */
LPADC_SetConvCommandConfig(DEMO_LPADC_BASE, DEMO_LPADC_USER_CMDID, &mLpadcCommandConfigStruct);
设置触发方式:
/* Set trigger configuration. */
LPADC_GetDefaultConvTriggerConfig(&mLpadcTriggerConfigStruct);
mLpadcTriggerConfigStruct.targetCommandId = DEMO_LPADC_USER_CMDID;
mLpadcTriggerConfigStruct.enableHardwareTrigger = false;
LPADC_SetConvTriggerConfig(DEMO_LPADC_BASE, 0U, &mLpadcTriggerConfigStruct); /* Configurate the trigger0. */
获取ADC转换结果并打印:
while (!LPADC_GetConvResult(DEMO_LPADC_BASE, &mLpadcResultConfigStruct))
{
}
static uint32_t tempAdcRaw;
static float tempAdcVolt;
static float Temp;
tempAdcRaw = ((mLpadcResultConfigStruct.convValue) >> g_LpadcResultShift);
tempAdcVolt = 3.3*tempAdcRaw/65536;
Temp = (100.0*tempAdcVolt-50);
PRINTF("ADC value: %d, Voltage: %.3f Volt, Temperature_C: %.2f degree_C\r\n", tempAdcRaw, tempAdcVolt, Temp);
烧录测试:
采样电压值跟温度均为浮点数,没有正常显示,猜测是跟浮点数打印相关的开关没有使能。在fsl_debug_console.h中:
/*! [url=home.php?mod=space&uid=159083]@brief[/url] Definition to printf the float number. */
#ifndef PRINTF_FLOAT_ENABLE
#define PRINTF_FLOAT_ENABLE 1U
#endif /* PRINTF_FLOAT_ENABLE */
再次测试,能够正常获取温度:
- 2024-12-31
-
发表了主题帖:
【MCXA156开发板测评】-1-开发环境搭建与串口回显
本帖最后由 慕容雪花 于 2024-12-31 22:24 编辑
非常感谢EEWorld与NXP提供的本次MCXA156开发板的测评体验机会。在今年最后一天先开个箱,搭建一下环境。剩下的交给明年了!
外包装盒新款的NXP三色log
板子布局紧凑合理,不仅提供了多种接口比如Arduino UNO R3接口、PMOD接口还有Mikro BUS接口,以及NXP自家的Flexio LCD接口,可以方便的跟NXP Semiconductors LCD-PAR-S035模块搭配使用。
主控芯片上MCXA156,这是一颗主频可以高达96MHz的Cortex-m33内核的高性能、高安全特性NXP新品。主要应用在工业控制以及电机控制领域。板子的调试芯片上LPC55S69,双核Cortex-M33,感觉比MCXA156更厉害。
开发板上电,首先查看设备管理器中是否有MCU-Link VCom Port (COM xx),xx是随机的,每台机器或许不一样。
这个虚拟串口就是LPC55S69当作MCU-Link,起到USB跟板载LPUART0的一个桥接作用。
看到D12 RGB小灯1s间隔闪烁,D6小灯在呼吸。
[localvideo]c783d03af006271b2439324aa08bf7f9[/localvideo]
板载的连接器资源可以参考如下官方图:
开发环境的搭建一定要遵循新板子使用新软件,这样最新的BSP以及BUG都得到了很好的修复,可以让用户专注于业务代码的开发,避免在官方已经修复的BUG上面重复浪费自己宝贵的开发时间。
安装成功后,打开最新版本的IDE,选择开发板型号,开始远程导入官方SDK。
在导入工程界面,选择一个经典的“hello world”。NXP官方的IDE都是内置了自家的配置工具叫:Config Tool。
打开导入的项目后,点击mex后缀的文件即可进入图形化配置界面。
查看本次即将体验的LPUART0打印HELLO WORLD的外设配置:
代码:
void BOARD_InitPins(void)
{
/* PORT0: Peripheral clock is enabled */
CLOCK_EnableClock(kCLOCK_GatePORT0);
/* LPUART0 peripheral is released from reset */
RESET_ReleasePeripheralReset(kLPUART0_RST_SHIFT_RSTn);
/* PORT0 peripheral is released from reset */
RESET_ReleasePeripheralReset(kPORT0_RST_SHIFT_RSTn);
const port_pin_config_t port0_2_pin78_config = {/* Internal pull-up resistor is enabled */
kPORT_PullUp,
/* Low internal pull resistor value is selected. */
kPORT_LowPullResistor,
/* Fast slew rate is configured */
kPORT_FastSlewRate,
/* Passive input filter is disabled */
kPORT_PassiveFilterDisable,
/* Open drain output is disabled */
kPORT_OpenDrainDisable,
/* Low drive strength is configured */
kPORT_LowDriveStrength,
/* Normal drive strength is configured */
kPORT_NormalDriveStrength,
/* Pin is configured as LPUART0_RXD */
kPORT_MuxAlt2,
/* Digital input enabled */
kPORT_InputBufferEnable,
/* Digital input is not inverted */
kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
kPORT_UnlockRegister};
/* PORT0_2 (pin 78) is configured as LPUART0_RXD */
PORT_SetPinConfig(PORT0, 2U, &port0_2_pin78_config);
const port_pin_config_t port0_3_pin79_config = {/* Internal pull-up resistor is enabled */
kPORT_PullUp,
/* Low internal pull resistor value is selected. */
kPORT_LowPullResistor,
/* Fast slew rate is configured */
kPORT_FastSlewRate,
/* Passive input filter is disabled */
kPORT_PassiveFilterDisable,
/* Open drain output is disabled */
kPORT_OpenDrainDisable,
/* Low drive strength is configured */
kPORT_LowDriveStrength,
/* Normal drive strength is configured */
kPORT_NormalDriveStrength,
/* Pin is configured as LPUART0_TXD */
kPORT_MuxAlt2,
/* Digital input enabled */
kPORT_InputBufferEnable,
/* Digital input is not inverted */
kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
kPORT_UnlockRegister};
/* PORT0_3 (pin 79) is configured as LPUART0_TXD */
PORT_SetPinConfig(PORT0, 3U, &port0_3_pin79_config);
}
串口初始化:
status_t DbgConsole_Init(uint8_t instance, uint32_t baudRate, serial_port_type_t device, uint32_t clkSrcFreq)
{
hal_uart_config_t usrtConfig;
if (kSerialPort_Uart != device)
{
return kStatus_Fail;
}
/* Set debug console to initialized to avoid duplicated initialized operation. */
s_debugConsole.serial_port_type = device;
usrtConfig.srcClock_Hz = clkSrcFreq;
usrtConfig.baudRate_Bps = baudRate;
usrtConfig.parityMode = kHAL_UartParityDisabled;
usrtConfig.stopBitCount = kHAL_UartOneStopBit;
usrtConfig.enableRx = 1U;
usrtConfig.enableTx = 1U;
usrtConfig.enableRxRTS = 0U;
usrtConfig.enableTxCTS = 0U;
usrtConfig.instance = instance;
#if (defined(HAL_UART_ADAPTER_FIFO) && (HAL_UART_ADAPTER_FIFO > 0u))
usrtConfig.txFifoWatermark = 0U;
usrtConfig.rxFifoWatermark = 0U;
#endif
/* Enable clock and initial UART module follow user configure structure. */
(void)HAL_UartInit((hal_uart_handle_t)&s_debugConsole.uartHandleBuffer[0], &usrtConfig);
/* Set the function pointer for send and receive for this kind of device. */
s_debugConsole.putChar = HAL_UartSendBlocking;
s_debugConsole.getChar = HAL_UartReceiveBlocking;
return kStatus_Success;
}
主代码:
int main(void)
{
char ch;
/* Init board hardware. */
BOARD_InitPins();
BOARD_InitBootClocks();
BOARD_InitDebugConsole();
PRINTF("hello eeworld.\r\n");
while (1)
{
ch = GETCHAR();
PUTCHAR(ch);
}
}
点击菜单栏上的锤子按钮,编译:
Building target: frdmmcxa156_hello_world.axf
Invoking: MCU Linker
arm-none-eabi-gcc -nostdlib -Xlinker -no-warn-rwx-segments -Xlinker -Map="frdmmcxa156_hello_world.map" -Xlinker --gc-sections -Xlinker -print-memory-usage -Xlinker --sort-section=alignment -Xlinker --cref -mcpu=cortex-m33 -mfpu=fpv5-sp-d16 -mfloat-abi=hard -mthumb -T frdmmcxa156_hello_world_Debug.ld -o "frdmmcxa156_hello_world.axf" ./utilities/fsl_assert.o ./utilities/fsl_debug_console.o ./utilities/fsl_memcpy.o ./utilities/fsl_str.o ./startup/startup_mcxa156.o ./source/hello_world.o ./source/semihost_hardfault.o ./drivers/fsl_clock.o ./drivers/fsl_common.o ./drivers/fsl_common_arm.o ./drivers/fsl_gpio.o ./drivers/fsl_lpuart.o ./drivers/fsl_reset.o ./drivers/fsl_spc.o ./device/system_MCXA156.o ./component/uart/fsl_adapter_lpuart.o ./component/lists/fsl_component_generic_list.o ./board/board.o ./board/clock_config.o ./board/pin_mux.o
Memory region Used Size Region Size %age Used
PROGRAM_FLASH: 11820 B 1 MB 1.13%
SRAM: 8444 B 120 KB 6.87%
SRAMX: 0 GB 12 KB 0.00%
Finished building target: frdmmcxa156_hello_world.axf
Performing post-build steps
arm-none-eabi-size 'frdmmcxa156_hello_world.axf'; arm-none-eabi-objcopy -v -O binary 'frdmmcxa156_hello_world.axf' 'frdmmcxa156_hello_world.bin'
text data bss dec hex filename
11812 8 8436 20256 4f20 frdmmcxa156_hello_world.axf
copy from `frdmmcxa156_hello_world.axf' [elf32-littlearm] to `frdmmcxa156_hello_world.bin' [binary]
22:04:59 Build Finished. 0 errors, 0 warnings. (took 10s.918ms)
点击蓝色小虫子 debug按钮,然后全速运行 。打开串口助手:
- 2024-12-19
-
回复了主题帖:
【Follow me第二季第4期】任务汇总
Jacktang 发表于 2024-12-18 07:34
6轴传感器这个任务整的真不错
感谢大佬点评,感谢前期分享经验的朋友,一起体验好玩的电子世界
-
回复了主题帖:
测评入围名单: NXP MCX A系列 FRDM-MCXA156开发板
个人信息无误,确认可以完成测评分享计划
- 2024-12-17
-
发表了主题帖:
【Follow me第二季第4期】- 任务准备:底板制作与开发环境搭建
本帖最后由 慕容雪花 于 2024-12-16 22:03 编辑
这次活动中,特意购买了一个屏幕扩展板用于显示一些传感器数据。 本文主要介绍底板扩展板的设计,以及Arduino开发环境搭建,最后展示成功驱动屏幕。
硬件:
主开发板:Nano RP2040 Connect https://docs.arduino.cc/hardware/nano-rp2040-connect/#features
LCD扩展板:Pico LCD 1.14 https://www.waveshare.com/wiki/Pico-LCD-1.14
下图展示的是微雪的屏幕扩展板与树莓派PICO的接口图。
接下来的第一步就是找到微雪开发板与树莓派PICO的引脚关系,然后再找到对应到Arduino Nano RP2040 Connect的引脚。
有一点需要注意,Arduino Nano RP2040 Connect开发板的VUSB引脚与JP2扩展接口实际上默认是未焊接状态。因此需要手工焊接上去。
设计原理图与PCB:
JLC标准版,设计简单的连接。
主控板是Arduino Nano的尺寸,DY方向间距:15.24mm,DX方向间距:2.54mm
扩展板是树莓派PICO的尺寸:
两层如下:
3D展示:
打样回来的板子:
搭建开发环境与屏幕驱动:
接下来Arduino IDE 2以上版本,安装开发板驱动:
选择板卡:
使用的屏幕驱动是:
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <SPI.h>
指定屏幕SPI相关引脚:
#define TFT_CS D10 // Chip select control pin
#define TFT_RST D4 // Reset pin (could connect to Arduino RESET pin)
#define TFT_DC D12 // Data Command control pin
#define TFT_MOSI D11
#define TFT_SCLK D13
#define TFT_MISO D15
核心代码:
void setup(void) {
Serial.begin(9600);
Serial.print(F("Hello! ST77xx TFT Test"));
// OR use this initializer (uncomment) if using a 1.14" 240x135 TFT:
tft.init(135, 240); // Init ST7789 240x135
// SPI speed defaults to SPI_DEFAULT_FREQ defined in the library, you can override it here
// Note that speed allowable depends on chip and quality of wiring, if you go too fast, you
// may end up with a black screen some times, or all the time.
//tft.setSPISpeed(40000000);
Serial.println(F("Initialized"));
uint16_t time = millis();
tft.fillScreen(ST77XX_BLACK);
time = millis() - time;
Serial.println(time, DEC);
delay(500);
// large block of text
tft.fillScreen(ST77XX_BLACK);
testdrawtext("HELLO THERE Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur adipiscing ante sed nibh tincidunt feugiat. Maecenas enim massa, fringilla sed malesuada et, malesuada sit amet turpis. Sed porttitor neque ut ante pretium vitae malesuada nunc bibendum. Nullam aliquet ultrices massa eu hendrerit. Ut sed nisi lorem. In vestibulum purus a tortor imperdiet posuere. ", ST77XX_WHITE);
delay(1000);
// tft print function!
tftPrintTest();
delay(4000);
// a single pixel
tft.drawPixel(tft.width()/2, tft.height()/2, ST77XX_GREEN);
delay(500);
// line draw test
testlines(ST77XX_YELLOW);
delay(500);
// optimized lines
testfastlines(ST77XX_RED, ST77XX_BLUE);
delay(500);
testdrawrects(ST77XX_GREEN);
delay(500);
testfillrects(ST77XX_YELLOW, ST77XX_MAGENTA);
delay(500);
tft.fillScreen(ST77XX_BLACK);
testfillcircles(10, ST77XX_BLUE);
testdrawcircles(10, ST77XX_WHITE);
delay(500);
testroundrects();
delay(500);
testtriangles();
delay(500);
mediabuttons();
delay(500);
Serial.println("done");
delay(1000);
}
编译代码:
实物验证:
-
发表了主题帖:
【Follow me第二季第4期】任务汇总
本帖最后由 慕容雪花 于 2025-1-10 18:18 编辑
物料清单:
物料1:ARDUINO NANO RP2040 CONNECT
物料2:ST7789 LCD 1.14" 显示 Raspberry Pi Pico 平台评估扩展板
物料实物:
往期分享:
【Follow me第二季第4期】- 任务准备:底板制作与开发环境搭建 https://bbs.eeworld.com.cn/thread-1302013-1-1.html
视频链接: https://training.eeworld.com.cn/video/41983
代码:
[localvideo]e9c793ec9a9e9681d72702fac516b47e[/localvideo]
任务一:
任务目标:搭建环境并开启第一步Blink三色LED / 串口打印Hello DigiKey & EEWorld!
开发板上除了有右上角的单色BUILTIN小灯,还有一组RGB,不过是连接到NINA模块引脚上。
在Arduino开发环境下,首先要想使用NINA WiFi模组,需要安装如下库:
之后需要包含如下的头文件:
#include <WiFiNINA.h>
任务程序流程图:
任务核心代码:
void setup(void) {
Serial.begin(9600);
Serial.print(F("Hello! ST77xx TFT Test"));
//Init LED rgb PINS.
pinMode(LEDR, OUTPUT);
pinMode(LEDG, OUTPUT);
pinMode(LEDB, OUTPUT);
digitalWrite(LEDR, HIGH); //RED
digitalWrite(LEDG, HIGH); //GREEN
digitalWrite(LEDB, HIGH); //BLUE
tft.init(135, 240); // Init ST7789 240x135
Serial.println(F("Initialized"));
uint16_t time = millis();
tft.fillScreen(ST77XX_BLACK);
time = millis() - time;
Serial.println(time, DEC);
delay(500);
tft.fillScreen(ST77XX_BLACK);
}
void loop() {
Serial.println("Hello DigiKey & EEWorld!");
testdrawtext("EEWORLD", ST77XX_RED);
Serial.println("red led up");
digitalWrite(LEDR, HIGH); //RED
digitalWrite(LEDG, LOW); //GREEN
digitalWrite(LEDB, LOW); //BLUE
delay(500);
testdrawtext("EEWORLD", ST77XX_GREEN);
Serial.println("green led up");
digitalWrite(LEDR, LOW); //RED
digitalWrite(LEDG, HIGH);
digitalWrite(LEDB, LOW); //BLUE
delay(500);
testdrawtext("EEWORLD", ST77XX_BLUE);
Serial.println("blue led up");
digitalWrite(LEDR, LOW); //RED
digitalWrite(LEDG, LOW); //GREEN
digitalWrite(LEDB, HIGH);
delay(500);
}
任务实现效果:三色LED依次闪烁,屏幕有相应颜色提示,串口有数据输出。
任务二、学习IMU基础知识,调试IMU传感器,通过串口打印六轴原始数据
开发板上安装了一颗6轴惯性测量单元传感器,其位置如下:
LSM6DSOXTR 是一款 6 轴 IMU(惯性测量单元)系统级封装,具有 3 轴数字加速度计和 3 轴数字陀螺仪,在高性能模式下以 0.55 mA 的速度提高性能,并支持始终开启的低功耗功能,为消费者提供最佳运动体验。
LSM6DSOXTR支持主要的操作系统要求,提供 9 KB 的真实、虚拟和批处理传感器,用于动态数据批处理。意法半导体的 MEMS 传感器模块系列利用了已经用于生产微机械加速度计和陀螺仪的稳健而成熟的制造工艺。各种传感元件采用专门的微加工工艺制造,而 IC 接口则使用 CMOS 技术开发,允许设计专用电路,该电路经过调整以更好地匹配传感元件的特性。
LSM6DSOXTR的满量程加速度范围为 ±2/±4/±8/±16 g,角速率范围为 ±125/±250/±500/±1000/±2000 dps。
3轴加速度计是用来测量加速度的机电装置,可以是静态的重力加速度或者运动物体的时刻变化的加速度等。利用加速度传感器能够计算出板子倾斜的方向或者角度。
3轴数字陀螺仪:
陀螺仪传感器是一种能够测量和保持物体的方向和角速度的装置。陀螺仪比加速度计更先进,因为它们可以测量物体的倾斜和横向方向,而加速度计只能测量物体的直线运动。
陀螺仪图示。
陀螺仪传感器也被称为“角速率传感器”或“角速度传感器”。角速度是单位时间内物体旋转角度的变化量,单位是每秒的度数。
在Arduino开发环境中,需要使用到“Arduino_LSM6DS3”来驱动该传感器。程序流程图:
获取6轴数据的代码如下:
#include <Arduino_LSM6DSOX.h>
float Ax, Ay, Az;
float Gx, Gy, Gz;
void setup() {
Serial.begin(9600);
while(!Serial);
if (!IMU.begin()) {
Serial.println("Failed to initialize IMU!");
while (1);
}
Serial.print("Accelerometer sample rate = ");
Serial.print(IMU.accelerationSampleRate());
Serial.println("Hz");
Serial.println();
Serial.print("Gyroscope sample rate = ");
Serial.print(IMU.gyroscopeSampleRate());
Serial.println("Hz");
Serial.println();
}
void loop() {
if (IMU.accelerationAvailable()) {
IMU.readAcceleration(Ax, Ay, Az);
Serial.println("Accelerometer data: ");
Serial.print(Ax);
Serial.print('\t');
Serial.print(Ay);
Serial.print('\t');
Serial.println(Az);
Serial.println();
}
if (IMU.gyroscopeAvailable()) {
IMU.readGyroscope(Gx, Gy, Gz);
Serial.println("Gyroscope data: ");
Serial.print(Gx);
Serial.print('\t');
Serial.print(Gy);
Serial.print('\t');
Serial.println(Gz);
Serial.println();
}
delay(500);
}
烧录验证:
串口有三轴加速度与角速度传感器数据。
数据的波形也可以方便的通过串口绘图工具展示:
为了增加任务二的可玩性,尝试了使用Eigen库在TFT屏幕上绘制了变化的立方体,立方体的旋转角度是根据MadgwickAHRS算法计算出来的实时变化的数据。
pitch是围绕Y轴旋转,也叫做俯仰角。当X轴的正半轴位于过坐标原点的水平面之上(抬头)时,俯仰角为正,否则为负,如下图所示:
yaw是围绕Z轴旋转,也叫偏航角。即机头右偏航为正,反之为负。如下图所示:
roll是围绕X轴旋转,也叫翻滚角。机体向右滚为正,反之为负。如下图所示:
在上位机方面,采用了VOFA+这个工具里面有个立体方块,可以配置开发板的旋转来一起high起来。
更新后软件流程图如下所示:
其中,立方体是通过矩阵的方式描述,下面是8个顶点与12条边。
pt << -1, -1, 1, 1, //A 0
1, -1, 1, 1, //B 1
1, 1, 1, 1, //c 2
-1, 1, 1, 1, //d 3
-1, -1, -1, 1, //e 4
1, -1, -1, 1, //f 5
1, 1, -1, 1, //g 6
-1, 1, -1, 1; //h 7
tp << 0, 1, //A-B
1, 2, //b-c
2, 3, //c-d
3, 0, //d-a
0, 4, //a-e
1, 5, //b-f
2, 6, //c-g
3, 7, //d-h
4, 5, //e-f
5, 6, //f-g
6, 7, //g-h
7, 4; //h-e
在TFT屏幕上绘制立方体:
void drawTube(const MatrixXd& m, uint16_t color) {
int nr = tp.rows(); //行数
for (int i = 0; i < nr; i++) {
auto start = m.row(tp(i, 0));
auto end = m.row(tp(i, 1));
tft.drawLine(start(0), start(1), end(0), end(1), color);
}
}
根据6轴数据计算开发板旋转角度的时候,本次上位机是VOFA+,要注意其专有点协议格式。
// Prints rotation angles (roll, pitch, and yaw) calculated using the
// Madgwick algorithm.
// Note: Yaw is relative, not absolute, based on initial starting position.
// Calculating a true yaw (heading) angle requires an additional data source,
// such as a magnometer.
void printRotationAngles() {
char buffer[5]; // string buffer for use with dtostrf() function
if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable()
&& IMU.readAcceleration(ax, ay, az) && IMU.readGyroscope(gx, gy, gz)) {
filter.updateIMU(gx, gy, gz, ax, ay, az); // update roll, pitch, and yaw values
// Print rotation angles
float data[3];
data[0] = filter.getRoll();
data[1] = filter.getPitch();
data[2] = filter.getYaw();
Serial.write((char *)data, sizeof(float) * 3);
// 发送帧尾
char tail[4] = {0x00, 0x00, 0x80, 0x7f};
Serial.write(tail, 4);
}
}
如果上位机选择的是VOFA+,注意使用JustFloat协议要遵循一定的格式。
然后再左侧“控件”栏中选择立方体,然后拖拽到右侧窗口,然后分别指定每个通道的数据来源。
任务三、学习PDM麦克风技术知识,调试PDM麦克风,通过串口打印收音数据和音频波形
开发板上有一个ST公司的PDM麦克风:
PDM是一种调制形式,用于表示数字域中的模拟信号。它是1位数字采样的高频数据流。在PDM信号中,脉冲的相对密度对应于模拟信号的幅度。大量的1s对应于高(正)幅度值,而大量的0s对应于低(负)幅度值,交替的1s和0s对应于幅度值0。
电路原理图如下:
相关参数如下:
Signal-to-noise ratio: 64dB
Sensitivity: -26dBFS ±3dB
Temperature range: -40 to 85°C
程序流程图:
核心代码:
#include <WiFiNINA.h>
#include <PDM.h>
bool LED_SWITCH = false;
// default number of output channels
static const char channels = 1;
// default PCM output frequency
static const int frequency = 20000;
// Buffer to read samples into, each sample is 16-bits
short sampleBuffer[512];
// Number of audio samples read
volatile int samplesRead;
void setup() {
Serial.begin(9600);
pinMode(LEDB, OUTPUT);
while (!Serial);
// Configure the data receive callback
PDM.onReceive(onPDMdata);
// Optionally set the gain
// Defaults to 20 on the BLE Sense and -10 on the Portenta Vision Shields
// PDM.setGain(30);
// Initialize PDM with:
// - one channel (mono mode)
// - a 16 kHz sample rate for the Arduino Nano 33 BLE Sense
// - a 32 kHz or 64 kHz sample rate for the Arduino Portenta Vision Shields
if (!PDM.begin(channels, frequency)) {
Serial.println("Failed to start PDM!");
while (1);
}
}
void loop() {
// Wait for samples to be read
if (samplesRead) {
// Print samples to the serial monitor or plotter
for (int i = 0; i < samplesRead; i++) {
if (channels == 2) {
Serial.print("L:");
Serial.print(sampleBuffer);
Serial.print(" R:");
i++;
}
Serial.println(sampleBuffer);
if (sampleBuffer > 10000 || sampleBuffer <= -10000) {
LED_SWITCH = !LED_SWITCH;
if (LED_SWITCH) {
Serial.println();
digitalWrite(LEDB, HIGH);
Serial.println("ON!");
Serial.println();
delay(1000);
}
else {
Serial.println();
digitalWrite(LEDB, LOW);
Serial.println("OFF!");
Serial.println();
delay(1000);
}
}
}
// Clear the read count
samplesRead = 0;
}
}
/**
Callback function to process the data from the PDM microphone.
NOTE: This callback is executed as part of an ISR.
Therefore using `Serial` to print messages inside this function isn't supported.
* */
void onPDMdata() {
// Query the number of available bytes
int bytesAvailable = PDM.available();
// Read into the sample buffer
PDM.read(sampleBuffer, bytesAvailable);
// 16-bit, 2 bytes per sample
samplesRead = bytesAvailable / 2;
}
安静状态下,打一个响指,可以通过串口工具捕捉到如下波形:
对着开发板说几句话,可以捕捉到如下波形:
总结:
非常感谢本次活动!让我体验了6轴传感器,体验了PDM与arduino nano rp2040。后续会继续体验多核与rtos的用法,改善项目效果。
-
上传了资料:
FOLLOW ME第二季第四期源码
-
加入了学习《【Follow me第二季第4期】ARDUINO NANO RP2040 CONNECT》,观看 PDM功能演示-ARDUINO NANO RP2040 CONNECT
-
加入了学习《【Follow me第二季第4期】任务汇总》,观看 【Follow me第二季第4期】任务汇总
-
回复了主题帖:
【Follow me第二季第4期】任务汇总
又快又好,很有借鉴意义,感谢分享!
- 2024-12-04
-
回复了主题帖:
【Follow me第二季第4期】IMU 传感器
大佬,流程图用啥软件画