项目介绍
本项目依托Funpack3-4活动,基于FRDM MCX N94开发板,实现了基于MQTT协议远程数据采集及设备控制器的设计,其主要功能为开发板通过网线接口连接公共代理服务器,并向代理服务器上传按键、触摸板、板载温湿度数据,同时可通过电脑发送控灯指令到开发板,实现对开发板板载RGB灯的控制。
项目任务:
使用板卡上的以太网接口连接到电脑上并通过以太网和电脑通信,实现数据传输。
电脑可以获取到板卡上的温度,触摸和按键信息。
可以通过电脑控制板卡上的RGB LED灯。
设计思路
网络通信方面基于lwip相关历程,最终基于易用性及实现效率方面考虑选定了mqtt协议,NXP官方SDK中已有相关的example历程可以编译测试并在此基础上添加修改代码以实现项目任务。
因使用mqtt协议,电脑端可直接使用现有开源mqtt client软件,无需自己编写上位机代码,可加快项目完成时间,后期如有需要可酌情开发定制电脑上位机或手机APP。
触摸按键参考了tsi_v6_self_cap历程代码,此历程代码中分别用软件查询、软件中断、硬件中断三种方法实现了触摸按键状态的采集,我们实际项目中使用的是硬件中断周期性采集方式。
针对RGB控灯及按键输入采集,主要参考gpio_led_output例程代码,实现了灯颜色的控制,及安吉状态读取。
I3C温湿度数据采集相关代码参考了i3c_master_read_sensor_p3t1755历程及dm-industrial-panel-frdm-mcxn947的代码及引脚配置。
最后通过全局共享变量实现数据之间的传递,及最后功能实现。
硬件介绍
FRDM-MCXN947是一款紧凑且可扩展的开发板,可让您快速基于MCX N94x MCU开展原型设计。板卡上集成行业标准的接口,可轻松使用MCU的I/O、集成的开放标准串行接口、外部闪存和板载MCU-Link调试器。
开发板上集成了RGB状态灯及触摸和实体按键,还包括arduino扩展接口、pmod接口,可以插入一些扩展板实现更多功能。同时开发板自带网络通信接口,进一步扩展了板子的应用范围。此核心板同时也自带can接口,可以开发一些汽车应用相关项目。
以下是针对MCXN947芯片的详细介绍
内核平台
- Arm®Cortex®-M33@150MHz(双核)
- DSP加速器(PowerQUAD,带协处理器接口)
- SmartDMA(用于并行摄像头接口和键盘扫描等应用的协处理器)
- eIQ®Neutron N1-16神经处理单元
- 电力线通信(PLC)控制器
存储器
- 高达2MB的片上闪存(2个1MB Bank)
- 支持Flash交换和写入时读取
- 带有16KB RAM的缓存引擎
- 高达512KB的RAM,可配置为带ECC的416KB存储器(支持单位校正双位检测)
- 4个8KB ECC RAM即使在VBAT模式下也可以工作
- 带16KB缓存的FlexSPI,支持XIP、8路/4路SPI闪存、HyperFlash、HyperRAM、Xcela存储器类型
外设
- 模拟
- 4个16位ADC(单端)或2个16位(差分)
- 16位模式下最高2Msps,12位模式下最高3.3Msps
- 每个ADC集成温度传感器
- 3个高速比较器,带有17个输入引脚和8位DAC作为内部参考
- 2个CMP即使在深度断电模式下也能正常工作
- 2个12位DAC,采样率高达1.0MSample/sec
- 1个14位DAC,采样率高达10MSample/sec
- 3个功率放大器可配置为:
- 可编程增益放大器
- 差分放大器
- 仪表放大器
- 跨导放大器
- 高精度参考电压源,精度为±0.15%,温度漂移为15ppm/℃。
- 定时器
- 5个32位标准通用异步定时器/计数器,最多支持4个捕获输入和4个比较输出、PWM模式和外部计数输入。可选择特定的定时器事件,以生成DMA请求。
- SCTimer/PWM
- LPTimer
- 频率测量定时器
- 多速率定时器
- 窗口化看门狗定时器
- 带日历的RTC
- 微型计时器
- 操作系统事件定时器
- 通信接口
- USB高速接口(主机/设备),带片上HS PHY
- USB全速接口(主机/设备),带片上FS PHY、USB设备
- uSDHC(Micro SD高速卡接口)
- 10个LP Flexcomm,每个都支持SPI、I2C、UART
- 2个支持FD的FlexCAN接口、2个I3C、2个SAI
- 1个具有QoS的以太网接口
- 1个FlexIO可编程接口,可作为各种串行和并行接口,包括但不限于显示驱动程序和摄像头接口
- 2个EVM智能卡接口
- 可编程逻辑单元(PLU)
- 电机控制子系统
- 2个eFlexPWM,每个有4个子模块,提供12个PWM输出
- 2个正交编码/解码器(ENC)
- 1个事件生成器(AND/OR/INVERT)模块,支持多达8个输出触发器
- SINC滤波器模块(3阶,5通道,中断信号与PWM的连接)
安全
- EdgeLock®安全区域,核心配置
- 加密服务(包括AES-256、SHA-2、ECC NIST P-256、TRNG和密钥生成/派生)
- 具有密钥使用策略的安全密钥存储(保护平台完整性,制造和应用密钥)
- 设备唯一身份基于物理不可复制功能(PUF)
- 设备认证支持设备标识符组合引擎(DICE)
- 安全连接和TLS支持
- 与恩智浦EdgeLock 2GO预集成的无线密钥管理
- EdgeLock®加速器(公钥加密)
- ROM中不可变的安全启动代码
- 双重安全启动模式(非对称模式和快速后量子安全对称模式)
- 支持安全固件更新
- 设备生命周期管理包括安全认证调试
- 高性能动态内存加密,具有外部闪存附加认证
- 受保护的闪存区域(PFR)
- 安全监控
- 2个代码看门狗
- 入侵和篡改响应控制器(ITRC)
- 8个主动和被动篡改引脚检测
- 电压、温度、光和时钟篡改检测
- 电压故障检测
- 不可信工厂中的安全制造和IP盗窃保护
- Arm TrustZone®for Cortex®-M
从 上面的信息不难看出,这颗芯片的功能还是挺强大的,片上外设非常丰富。
软件流程逻辑图
因本次使用的是freertos操作系统,代码多线程运行,故绘制了软件流程示意图。
主要功能包括
系统初始化
各外设时钟初始化
各外设引脚配置,寄存器配置
线程创建
tcp协议栈初始化
mqtt线程创建
gpio线程创建
温湿度采集线程创建
操作系统运行
功能展示
温度数据上传
触摸按键按下
触摸按键松开
按键1 按下
按键1 松开
按键2 按下
按键2 松开
控制亮红灯
控制亮绿灯
控制亮蓝灯
控制亮彩灯
主要代码片段
main函数
int main(void)
{
CLOCK_EnableClock(kCLOCK_InputMux);
/* attach 12 MHz clock to FLEXCOMM0 (debug console) */
CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);
/* enable clock for GPIO*/
CLOCK_EnableClock(kCLOCK_Gpio0);// P0_10 P0_27
CLOCK_EnableClock(kCLOCK_Gpio1);// P1_2
/* Enables the clk_16k[1] */
CLOCK_SetupClk16KClocking(kCLOCK_Clk16KToVsys);
/* attach FRO HF to SCT */
CLOCK_SetClkDiv(kCLOCK_DivTsiClk, 1u);
CLOCK_AttachClk(kCLK_IN_to_TSI);
/* Attach PLL0 clock to I3C, 150MHz / 6 = 25MHz. */
CLOCK_SetClkDiv(kCLOCK_DivI3c1FClk, 6U);
CLOCK_AttachClk(kPLL0_to_I3C1FCLK);
BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitDebugConsole();
CLOCK_AttachClk(MUX_A(CM_ENETRMIICLKSEL, 0));
CLOCK_EnableClock(kCLOCK_Enet);
SYSCON0->PRESETCTRL2 = SYSCON_PRESETCTRL2_ENET_RST_MASK;
SYSCON0->PRESETCTRL2 &= ~SYSCON_PRESETCTRL2_ENET_RST_MASK;
MDIO_Init();
g_phy_resource.read = MDIO_Read;
g_phy_resource.write = MDIO_Write;
/* Define the init structure for the output LED pin*/
gpio_pin_config_t led_config = {
kGPIO_DigitalOutput,
1,
};
/* Init output LED GPIO. */
for (size_t i = 0; i < RGB_LED_NUM; i++)
{
GPIO_PinInit(rgb_config[i].port, rgb_config[i].pin, &led_config);
}
/* Init output KEY GPIO. */
led_config.pinDirection = kGPIO_DigitalInput;
for (size_t i = 0; i < BOARD_KEY_NUM; i++)
{
GPIO_PinInit(key_config[i].port, key_config[i].pin, &led_config);
}
/* init temp sensor */
P3T1755_InitTemperature();
/* Initialize lwIP from thread */
if (sys_thread_new("main", stack_init, NULL, INIT_THREAD_STACKSIZE, INIT_THREAD_PRIO) == NULL)
{
LWIP_ASSERT("main(): Task creation failed.", 0);
}
vTaskStartScheduler();
/* Will not get here unless a task calls vTaskEndScheduler ()*/
return 0;
}
协议栈初始化线程
static void stack_init(void *arg)
{
static struct netif netif;
ethernetif_config_t enet_config = {
.phyHandle = &phyHandle,
.phyAddr = EXAMPLE_PHY_ADDRESS,
.phyOps = EXAMPLE_PHY_OPS,
.phyResource = EXAMPLE_PHY_RESOURCE,
.srcClockHz = EXAMPLE_CLOCK_FREQ,
#ifdef configMAC_ADDR
.macAddress = configMAC_ADDR,
#endif
};
LWIP_UNUSED_ARG(arg);
/* Set MAC address. */
#ifndef configMAC_ADDR
(void)SILICONID_ConvertToMacAddr(&enet_config.macAddress);
#endif
tcpip_init(NULL, NULL);
netifapi_netif_add(&netif, NULL, NULL, NULL, &enet_config, EXAMPLE_NETIF_INIT_FN, tcpip_input);
netifapi_netif_set_default(&netif);
netifapi_netif_set_up(&netif);
netifapi_dhcp_start(&netif);
PRINTF("\r\n************************************************\r\n");
PRINTF(" MQTT client example\r\n");
PRINTF("************************************************\r\n");
while (ethernetif_wait_linkup(&netif, 5000) != ERR_OK)
{
PRINTF("PHY Auto-negotiation failed. Please check the cable connection and link partner setting.\r\n");
}
/* Wait for address from DHCP */
PRINTF("Getting IP address from DHCP...\r\n");
(void)ethernetif_wait_ipv4_valid(&netif, ETHERNETIF_WAIT_FOREVER);
mqtt_freertos_run_thread(&netif);
/* Initialize gpio control thread */
if (sys_thread_new("gpio", gpio_control, NULL, GPIO_THREAD_STACKSIZE, GPIO_THREAD_PRIO) == NULL)
{
PRINTF("main(): GPIO Task creation failed.");
}
/* Initialize temp read thread */
if (sys_thread_new("temp", temp_read, NULL, TEMP_SENSOR_THREAD_STACKSIZE, TEMP_SENSOR_THREAD_PRIO) == NULL)
{
PRINTF("main(): temp Task creation failed.");
}
vTaskDelete(NULL);
}
gpio控制线程
static void gpio_control(void *arg)
{
LWIP_UNUSED_ARG(arg);
PRINTF("\r\n--- gpio_control thread ---\r\n");
TSI_Init();
for (size_t i = 0; i < BOARD_KEY_NUM; i++)
{
key_state[i].last_state = 0;
key_state[i].current_state = 0;
}
g_rgb_value = 0;
while (1)
{
// switch state read
for (size_t i = 0; i < BOARD_KEY_NUM; i++)
{
key_state[i].last_state = key_state[i].current_state;
key_state[i].current_state = GPIO_PinRead(key_config[i].port, key_config[i].pin) ? 0 : 1;
if (key_state[i].last_state != key_state[i].current_state)
{
PRINTF("\r\n --- No %d key state change --> %s --- \r\n", i + 1, key_event[key_state[i].current_state]);
mqtt_topic_signal[mqtt_key_1 + i].updateSignal = 1;
mqtt_topic_signal[mqtt_key_1 + i].updateValue = key_state[i].current_state;
}
}
g_rgb_value %= 8;
// PRINTF("\r\n_rgb_value:%d \r\n", g_rgb_value);
// rgb cmd output
for (size_t i = 0; i < RGB_LED_NUM; i++)
{
/* code */
if (g_rgb_value & (1U << i))
{
GPIO_PortClear(rgb_config[i].port, 1U << rgb_config[i].pin);
}
else
{
GPIO_PortSet(rgb_config[i].port, 1U << rgb_config[i].pin);
}
}
sys_msleep(100U);
}
vTaskDelete(NULL);
}
温湿度读取线程
static void temp_read(void *arg)
{
LWIP_UNUSED_ARG(arg);
PRINTF("\r\n--- sensor temp read thread ---\r\n");
uint8_t last_temp = 0;
uint8_t current_temp = 0;
while (1)
{
last_temp = current_temp;
current_temp = P3T1755_get_temperature();
// PRINTF("\r\nTemperature:%d \r\n", current_temp);
if(last_temp != current_temp)
{
mqtt_topic_signal[mqtt_temp].updateSignal = 1;
mqtt_topic_signal[mqtt_temp].updateValue = current_temp;
}
sys_msleep(1000U);
}
vTaskDelete(NULL);
}
mqtt通信线程
static void app_thread(void *arg)
{
struct netif *netif = (struct netif *)arg;
err_t err;
int i;
PRINTF("\r\nIPv4 Address : %s\r\n", ipaddr_ntoa(&netif->ip_addr));
PRINTF("IPv4 Subnet mask : %s\r\n", ipaddr_ntoa(&netif->netmask));
PRINTF("IPv4 Gateway : %s\r\n\r\n", ipaddr_ntoa(&netif->gw));
/*
* Check if we have an IP address or host name string configured.
* Could just call netconn_gethostbyname() on both IP address or host name,
* but we want to print some info if goint to resolve it.
*/
if (ipaddr_aton(EXAMPLE_MQTT_SERVER_HOST, &mqtt_addr) && IP_IS_V4(&mqtt_addr))
{
/* Already an IP address */
err = ERR_OK;
}
else
{
/* Resolve MQTT broker's host name to an IP address */
PRINTF("Resolving \"%s\"...\r\n", EXAMPLE_MQTT_SERVER_HOST);
err = netconn_gethostbyname(EXAMPLE_MQTT_SERVER_HOST, &mqtt_addr);
}
if (err == ERR_OK)
{
/* Start connecting to MQTT broker from tcpip_thread */
err = tcpip_callback(connect_to_mqtt, NULL);
if (err != ERR_OK)
{
PRINTF("Failed to invoke broker connection on the tcpip_thread: %d.\r\n", err);
}
}
else
{
PRINTF("Failed to obtain IP address: %d.\r\n", err);
}
/* Publish some messages */
if (connected)
{
err = tcpip_callback(publish_message, NULL);
if (err != ERR_OK)
{
PRINTF("Failed to invoke publishing of a message on the tcpip_thread: %d.\r\n", err);
}
}
while(1)
{
for (size_t i = 0; i < mqtt_topic_num; i++)
{
if (mqtt_topic_signal[i].updateSignal && connected)
{
err = tcpip_callback(upload_message, NULL);
if (err != ERR_OK)
{
PRINTF("Failed to invoke upload of a message on the tcpip_thread: %d.\r\n", err);
}
}
}
}
vTaskDelete(NULL);
}
触摸按键初始化
void TSI_Init(void)
{
lptmr_config_t lptmrConfig;
memset((void *)&lptmrConfig, 0, sizeof(lptmrConfig));
/* Configure LPTMR */
LPTMR_GetDefaultConfig(&lptmrConfig);
tsi_selfCap_config_t tsiConfig_selfCap;
/* TSI default hardware configuration for self-cap mode */
TSI_GetSelfCapModeDefaultConfig(&tsiConfig_selfCap);
/* Initialize the LPTMR */
LPTMR_Init(LPTMR0, &lptmrConfig);
/* Initialize the TSI */
TSI_InitSelfCapMode(APP_TSI, &tsiConfig_selfCap);
/* Enable noise cancellation function */
TSI_EnableNoiseCancellation(APP_TSI, true);
/* Set timer period */
LPTMR_SetTimerPeriod(LPTMR0, USEC_TO_COUNT(LPTMR_USEC_COUNT, LPTMR_SOURCE_CLOCK));
NVIC_EnableIRQ(TSI0_IRQn);
TSI_EnableModule(APP_TSI, true); /* Enable module */
PRINTF("\r\nTSI_V6 Self-Cap mode Example Start!\r\n");
/********* CALIBRATION PROCESS ************/
memset((void *)&buffer, 0, sizeof(buffer));
TSI_SelfCapCalibrate(APP_TSI, &buffer);
/* Print calibrated counter values */
for (size_t i = 0U; i < FSL_FEATURE_TSI_CHANNEL_COUNT; i++)
{
PRINTF("Calibrated counters for channel %d is: %d \r\n", i, buffer.calibratedData[i]);
}
PRINTF("\r\nNOW, comes to the hardware trigger scan method!\r\n");
PRINTF("After running, touch pad %s each time, you will see LED toggles.\r\n", PAD_TSI_ELECTRODE_1_NAME);
TSI_EnableModule(APP_TSI, false);
TSI_EnableHardwareTriggerScan(APP_TSI, true);
TSI_EnableInterrupts(APP_TSI, kTSI_EndOfScanInterruptEnable);
TSI_ClearStatusFlags(APP_TSI, kTSI_EndOfScanFlag);
TSI_SetSelfCapMeasuredChannel(APP_TSI,
BOARD_TSI_ELECTRODE_1); /* Select BOARD_TSI_ELECTRODE_1 as detecting electrode. */
TSI_EnableModule(APP_TSI, true);
INPUTMUX_AttachSignal(INPUTMUX0, 0U, kINPUTMUX_Lptmr0ToTsiTrigger);
LPTMR_StartTimer(LPTMR0); /* Start LPTMR triggering */
}
触摸按键检测中断
void TSI0_IRQHandler(void)
{
#if BOARD_TSI_ELECTRODE_1 > 15
/* errata ERR051410: When reading TSI_COMFIG[TSICH] bitfield, the upper most bit will always be 0. */
if ((TSI_GetSelfCapMeasuredChannel(APP_TSI) | 0x10U) == BOARD_TSI_ELECTRODE_1)
#else
if (TSI_GetSelfCapMeasuredChannel(APP_TSI) == BOARD_TSI_ELECTRODE_1)
#endif
{
uint8_t touch_state = 0;
if (TSI_GetCounter(APP_TSI) > (uint16_t)(buffer.calibratedData[BOARD_TSI_ELECTRODE_1] + TOUCH_DELTA_VALUE))
{
/* Toggle the touch event indicating LED */
touch_state = 1;
s_tsiInProgress = false;
}
key_state[BOARD_KEY_NUM].last_state = key_state[BOARD_KEY_NUM].current_state;
key_state[BOARD_KEY_NUM].current_state = touch_state;
if (key_state[BOARD_KEY_NUM].last_state != key_state[BOARD_KEY_NUM].current_state)
{
PRINTF("\r\n --- touch pad state change --> %s --- \r\n", key_event[touch_state]);
mqtt_topic_signal[mqtt_touch_pad].updateSignal = 1;
mqtt_topic_signal[mqtt_touch_pad].updateValue = touch_state;
}
}
/* Clear endOfScan flag */
TSI_ClearStatusFlags(APP_TSI, kTSI_EndOfScanFlag);
SDK_ISR_EXIT_BARRIER;
}
mqtt接收回调函数(主要用于控制板载RGB相关指令接收)
/*!
* @brief Called when there is a message on a subscribed topic.
*/
static void mqtt_incoming_publish_cb(void *arg, const char *topic, u32_t tot_len)
{
LWIP_UNUSED_ARG(arg);
if (strcmp(topic, mqtt_topic_type[mqtt_rgb_set].topic_str) == 0)
{
g_rgb_flag = 1;
}
PRINTF("Received %u bytes from the topic \"%s\": \"", tot_len, topic);
}
/*!
* @brief Called when recieved incoming published message fragment.
*/
static void mqtt_incoming_data_cb(void *arg, const u8_t *data, u16_t len, u8_t flags)
{
int i;
LWIP_UNUSED_ARG(arg);
for (i = 0; i < len; i++)
{
if (isprint(data[i]))
{
PRINTF("%c", (char)data[i]);
}
else
{
PRINTF("\\x%02x", data[i]);
}
}
PRINTF("\r\g_rgb_flag:%d \r\n", g_rgb_flag);
if (len == 1 && g_rgb_flag)
{
g_rgb_flag = 0;
g_rgb_value = data[0] - '0';
}
if (flags & MQTT_DATA_FLAG_LAST)
{
PRINTF("\"\r\n");
}
}
心得体会
感谢主办方提供平台,使我们有机会体验nxp的芯片开发流程,这里总结了一些开发技巧和大家分享
使用代码管理软件记录代码变更历史,方便程序出现异常后回退到能运行的版本,此方法对我本次开发很有帮助,每每在我头绪不清的时候会指引正确的方向。
使用vscode配合本次开发,在代码编辑阶段可以使用vscode,编译和调试的时候再切换到nxp官方ide,此方法会加快开发效率,当让nxp官方也有针对vscode的插件,本项目中尚未使用此插件,有兴趣的小伙伴可以尝试一下。
遇到问题
线程启动创建顺序需要注意,本人在开发过程中发现,不正确的启动顺序会导致开发板获取不到动态IP,具体启动顺序请根据实际情况调整
触摸相关的tsi_v6历程初始化的时候顺序设置了三种按键检测方式,我们在代码一直的时候不要直接复制粘贴,一定要先把代码运行起来,理解每一个代码语句的目的后,再针对性的进行移植工作。
!!!代码上传的时候要先清理工程再压缩,否则压缩包会大于30MB。!!!
PS:遇到问题没思路的时候可以适当放松休息一下,休息过后一般思路都会闪现。
本期内容就到这里,期待下期再见。