一、项目描述
使用板卡上的以太网接口连接到电脑上并通过以太网和电脑通信,实现数据传输。要求电脑可以获取到板卡上的温度,触摸和按键信息,并可以通过电脑控制板卡上的RGB LED灯。
板卡是来自NXP的FRDM-MCXN947:
FRDM-MCXN947是一款紧凑且可扩展的开发板,可让您快速基于MCX N94和N54 MCU开展原型设计。它们提供行业标准的接口,可轻松访问MCU的I/O、集成的开放标准串行接口、外部闪存和板载MCU-Link调试器。主要结构框图:
板载资源:
传感器
I3C/I²C数字温度。传感器
P3T1755DP: I3C/I²C总线精确数字温度传感器,精度为±0.5 °C
处理器和微控制器
MCX N系列微控制器
MCX-N94X-N54X: MCX N94x/54x高度集成的多核MCU,具有片上加速器、智能外设和先进的安全性
微控制器
MCX-N947有两个Arm™ Cortex™-M33内核,每个内核的主频均为150MHz,优化了性能效率,高达2MB的双块Flash存储器,带可选的全ECC RAM,外部闪存
加速器:
神经处理单元、PowerQuad、Smart DMA等
存储器扩展
*DNP Micro SD卡插槽。
连接
以太网PHY和连接器
HS USB Type-C连接器
SPI/I2C/UART连接器(PMOD/mikroBUS、DNP)
WIFI连接器(PMOD/mikroBUS、DNP)
CAN-FD收发器
调试
带有CMSIS-DAP的板载MCU-Link调试器
JTAG/SWD连接器
传感器
P3T1755 I3C/I2C温度传感器,触摸板
扩展选项
Arduino®接头(带FRDM扩展行)
FRDM接头
FlexIO/LCD接头
SmartDMA/摄像头接头
Pmod™ *DNP
mikroBUS™
用户接口
RGB用户LED,加上复位、ISP、唤醒按钮
由于项目用到网络通信,考虑到以前学习过RT-Thread,RT-Thread仓库也加入了对该板卡的支持,所以项目软件基于RT-Thread实时操作系统来开发,起到事半功倍的效果。下面是RT-Thread的简介:
RT-Thread,全称是 Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,允许多个任务同时运行并不意味着处理器在同一时刻真地执行了多个任务。事实上,一个处理器核心在某一时刻只能运行一个任务,由于每次对一个任务的执行时间很短、任务与任务之间通过任务调度器进行非常快速地切换(调度器根据优先级决定此刻该执行的任务),给人造成多个任务在一个时刻同时运行的错觉。在 RT-Thread 系统中,任务通过线程实现的,RT-Thread 中的线程调度器也就是以上提到的任务调度器。
RT-Thread 主要采用 C 语言编写,浅显易懂,方便移植。它把面向对象的设计方法应用到实时系统设计中,使得代码风格优雅、架构清晰、系统模块化并且可裁剪性非常好。针对资源受限的微控制器(MCU)系统,可通过方便易用的工具,裁剪出仅需要 3KB Flash、1.2KB RAM 内存资源的 NANO 版本(NANO 是 RT-Thread 官方于 2017 年 7 月份发布的一个极简版内核);而对于资源丰富的物联网设备,RT-Thread 又能使用在线的软件包管理工具,配合系统配置工具实现直观快速的模块化裁剪,无缝地导入丰富的软件功能包,实现类似 Android 的图形界面及触摸滑动效果、智能语音交互效果等复杂功能。
相较于 Linux 操作系统,RT-Thread 体积小,成本低,功耗低、启动快速,除此以外 RT-Thread 还具有实时性高、占用资源小等特点,非常适用于各种资源受限(如成本、功耗限制等)的场合。虽然 32 位 MCU 是它的主要运行平台,实际上很多带有 MMU、基于 ARM9、ARM11 甚至 Cortex-A 系列级别 CPU 的应用处理器在特定应用场合也适合使用 RT-Thread。
二、软件流程图及各功能对应的主要代码片段及说明
开发软件使用KEIL MDK,软件工程结构如下:
主线程创建了按键事件接收处理线程,主线程完成温度传感器的数据读取和上报,在tcp服务器接收回调中处理电脑上位机发送的RGB LED控制指令,主要流程图如下:
网络使用到了RT-Thread的软件包tcpserver,创建一个tcp服务器,电脑上位机作为客户端连接服务器与它通信,
变量定义:
static struct tcpserver *serv;
tcpclient_t hclient;
tcp服务器初始化,绑定5002端口:
serv = tcpserver_create(NULL, 5002);
tcpserver_set_notify_callback(serv, tcpserver_event_notify);
RGB LED
板载了RGB LED,原理图如下:
RGB LED管脚定义:
#define LEDR_PIN ((0*32)+10)
#define LEDG_PIN ((0*32)+27)
#define LEDB_PIN ((1*32)+2)
RGB LED管脚初始化:
rt_pin_mode(LEDR_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(LEDG_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(LEDB_PIN, PIN_MODE_OUTPUT);
rt_pin_write(LEDR_PIN, PIN_HIGH);
rt_pin_write(LEDG_PIN, PIN_HIGH);
rt_pin_write(LEDB_PIN, PIN_HIGH);
网络数据接收以及RGB LED控制:
static void tcpserver_event_notify(tcpclient_t client, rt_uint8_t event)
{
int ret;
switch (event)
{
case TCPSERVER_EVENT_CONNECT:
LOG_D("client connect:%d", client->sock);
hclient = client;
break;
case TCPSERVER_EVENT_RECV:
rt_memset(buf,0,sizeof(buf));
ret = tcpserver_recv(client, buf, sizeof(buf), -1);
if (ret > 0)
{
LOG_D("[%d]recv:%s", client->sock,buf);
if(rt_strcmp(buf,"LED_R=0")==0)
{
rt_pin_write(LEDR_PIN, PIN_HIGH);
rt_kprintf("red led off\n");
}
else if(rt_strcmp(buf,"LED_R=1")==0)
{
rt_pin_write(LEDR_PIN, PIN_LOW);
rt_kprintf("red led on\n");
}
else if(rt_strcmp(buf,"LED_G=0")==0)
{
rt_pin_write(LEDG_PIN, PIN_HIGH);
rt_kprintf("green led off\n");
}
else if(rt_strcmp(buf,"LED_G=1")==0)
{
rt_pin_write(LEDG_PIN, PIN_LOW);
rt_kprintf("green led on\n");
}
else if(rt_strcmp(buf,"LED_B=0")==0)
{
rt_pin_write(LEDB_PIN, PIN_HIGH);
rt_kprintf("blue led off\n");
}
else if(rt_strcmp(buf,"LED_B=1")==0)
{
rt_pin_write(LEDB_PIN, PIN_LOW);
rt_kprintf("blue led on\n");
}
}
break;
case TCPSERVER_EVENT_DISCONNECT:
LOG_D("client disconnect:%d", client->sock);
//tcpserver_close(hclient);
hclient = NULL;
break;
default:
break;
}
}
按键
板载了2个普通按键和1个电容触摸按键,按键原理图如下:
按键定义:
#define BUTTON_PIN ((0*32)+23)
#define SW3_BUTTON_PIN ((0*32)+6)
按键初始化,设置管脚为输入上拉模式,并开启GPIO中断:
rt_pin_mode(BUTTON_PIN, PIN_MODE_INPUT_PULLUP);
rt_pin_attach_irq(BUTTON_PIN, PIN_IRQ_MODE_FALLING, sw_pin_cb, RT_NULL);
rt_pin_irq_enable(BUTTON_PIN, 1);
rt_pin_mode(SW3_BUTTON_PIN, PIN_MODE_INPUT_PULLUP);
rt_pin_attach_irq(SW3_BUTTON_PIN, PIN_IRQ_MODE_FALLING, sw3_pin_cb, RT_NULL);
rt_pin_irq_enable(SW3_BUTTON_PIN, 1);
按键回调,发送按键事件:
static void sw_pin_cb(void *args)
{
rt_kprintf("sw2 pressed\n");
rt_event_send(&event, EVENT_FLAG3);
}
static void sw3_pin_cb(void *args)
{
rt_kprintf("sw3 pressed\n");
rt_event_send(&event, EVENT_FLAG5);
}
按键处理线程收到按键事件后调用网络发送接口发送数据:
static void thread1_entry(void *parameter)
{
while (1)
{
rt_uint32_t e;
//接收事件,任意一个可以触发线程,接收完后清除事件标志
if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5 | EVENT_FLAG7),
RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
RT_WAITING_FOREVER, &e) == RT_EOK)
{
rt_kprintf("OR recv event 0x08%x\n", e);
if(e==EVENT_FLAG3)
{
uint8_t tx_buf[] = "sw2 press";
rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
if(hclient != NULL)
tcpserver_send(hclient, tx_buf, sizeof(tx_buf), 10);
rt_mutex_release(dynamic_mutex);
}
else if(e==EVENT_FLAG5)
{
uint8_t tx_buf[] = "sw3 press";
rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
if(hclient != NULL)
tcpserver_send(hclient, tx_buf, sizeof(tx_buf), 10);
rt_mutex_release(dynamic_mutex);
}
else if(e==EVENT_FLAG7)
{
uint8_t tx_buf[] = "touch press";
rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
if(hclient != NULL)
tcpserver_send(hclient, tx_buf, sizeof(tx_buf), 10);
rt_mutex_release(dynamic_mutex);
}
}
}
}
温度传感器
板载了一颗I3C接口的温度传感器p3t1755,原理图如下:
驱动来自SDK_2_16_000_FRDM-MCXN947\boards\frdmmcxn947\driver_examples\i3c\master_read_sensor_p3t1755
温度传感器初始化:
double temperature;
int temp_sensor_init(void)
{
status_t result = kStatus_Success;
i3c_master_config_t masterConfig;
p3t1755_config_t p3t1755Config;
/* Attach FRO 12M to FLEXCOMM4 (debug console) */
CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 1);
//CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);
/* Attach PLL0 clock to I3C, 150MHz / 6 = 25MHz. */
CLOCK_SetClkDiv(kCLOCK_DivI3c1FClk, 6U);
CLOCK_AttachClk(kPLL0_to_I3C1FCLK);
//BOARD_InitPins();
//BOARD_InitBootClocks();
//BOARD_InitDebugConsole();
rt_kprintf("I3C master read sensor data example.\n");
I3C_MasterGetDefaultConfig(&masterConfig);
masterConfig.baudRate_Hz.i2cBaud = EXAMPLE_I2C_BAUDRATE;
masterConfig.baudRate_Hz.i3cPushPullBaud = EXAMPLE_I3C_PP_BAUDRATE;
masterConfig.baudRate_Hz.i3cOpenDrainBaud = EXAMPLE_I3C_OD_BAUDRATE;
masterConfig.enableOpenDrainStop = false;
masterConfig.disableTimeout = true;
I3C_MasterInit(EXAMPLE_MASTER, &masterConfig, I3C_MASTER_CLOCK_FREQUENCY);
/* Create I3C handle. */
I3C_MasterTransferCreateHandle(EXAMPLE_MASTER, &g_i3c_m_handle, &masterCallback, NULL);
result = p3t1755_set_dynamic_address();
if (result != kStatus_Success)
{
rt_kprintf("P3T1755 set dynamic address failed.\n");
}
p3t1755Config.writeTransfer = I3C_WriteSensor;
p3t1755Config.readTransfer = I3C_ReadSensor;
p3t1755Config.sensorAddress = SENSOR_ADDR;
P3T1755_Init(&p3t1755Handle, &p3t1755Config);
}
主线程读取温度值并通过网络发送到电脑上位机:
int temp_sensor(void)
{
if (P3T1755_ReadTemperature(&p3t1755Handle, &temperature) != kStatus_Success)
{
rt_kprintf("P3T1755 read temperature failed.\n");
}
else
{
//rt_kprintf("Temperature:%d\n", (int)temperature);
rt_memset(buf,0,sizeof(buf));
rt_sprintf(buf,"Temperature:%d\n",(int)temperature);
rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
if(hclient != NULL)
tcpserver_send(hclient, buf, sizeof(buf), 10);
rt_mutex_release(dynamic_mutex);
}
}
电容触摸按键驱动使用来自SDK_2_16_000_FRDM-MCXN947\boards\frdmmcxn947\driver_examples\tsi_v6\self_cap
,主要修改点是在中断中增加rt_interrupt_enter()和rt_interrupt_leave()接口,发送电容触摸事件,tsi_v6_self_cap.c文件内容如下:
#include "pin_mux.h"
#include "board.h"
#include "fsl_tsi_v6.h"
#include "fsl_lptmr.h"
#include "fsl_inputmux.h"
#include <stdbool.h>
#include <rtthread.h>
#define PRINTF rt_kprintf
/* Available PAD names on board */
#define PAD_TSI_ELECTRODE_1_NAME "E1"
/* IRQ related redefinitions for specific SOC */
#define TSI0_IRQHandler TSI_END_OF_SCAN_IRQHandler
#define TSI0_IRQn TSI_END_OF_SCAN_IRQn
/* Define the delta value to indicate a touch event */
#define TOUCH_DELTA_VALUE 100U
/* Get source clock for LPTMR driver */
#define LPTMR_SOURCE_CLOCK (16000)
/* Define LPTMR microseconds count value */
#define LPTMR_USEC_COUNT (100000)
tsi_calibration_data_t buffer;
static volatile bool s_tsiInProgress = true;
/* Array of TSI peripheral base address. */
#if defined(TSI0)
#define APP_TSI TSI0
#elif defined(TSI)
#define APP_TSI TSI
#endif
void TSI0_IRQHandler(void)
{
rt_interrupt_enter();
#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
{
if (TSI_GetCounter(APP_TSI) > (uint16_t)(buffer.calibratedData[BOARD_TSI_ELECTRODE_1] + TOUCH_DELTA_VALUE))
{
//PRINTF("touch pad E1 pressed\r\n");
#define EVENT_FLAG7 (1 << 7)
extern struct rt_event event;
rt_event_send(&event, EVENT_FLAG7);//电容触摸按下,则发送事件通知按键事件处理线程
s_tsiInProgress = false;
}
}
/* Clear endOfScan flag */
TSI_ClearStatusFlags(APP_TSI, kTSI_EndOfScanFlag);
SDK_ISR_EXIT_BARRIER;
rt_interrupt_leave();
}
int touch_test(void)
{
volatile uint32_t i = 0;
tsi_selfCap_config_t tsiConfig_selfCap;
lptmr_config_t lptmrConfig;
memset((void *)&lptmrConfig, 0, sizeof(lptmrConfig));
/* Initialize standard SDK demo application pins */
/* Enables the clock for INPUTMUX: Enables clock */
// CLOCK_EnableClock(kCLOCK_InputMux0);
/* attach FRO 12M to FLEXCOMM4 (debug console) */
// CLOCK_SetClkDiv(kCLOCK_DivFlexcom4Clk, 1u);
// CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);
/* 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);
// BOARD_InitPins();
// BOARD_InitBootClocks();
// BOARD_InitDebugConsole();
/* Init tsi Leds in Demo app */
//LED1_INIT();
//LED2_INIT();
/* Configure LPTMR */
LPTMR_GetDefaultConfig(&lptmrConfig);
/* 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 (i = 0U; i < FSL_FEATURE_TSI_CHANNEL_COUNT; i++)
{
//PRINTF("Calibrated counters for channel %d is: %d \r\n", i, buffer.calibratedData[i]);
}
#if 0
/********** SOFTWARE TRIGGER SCAN USING POLLING METHOD ********/
PRINTF("\r\nNOW, comes to the software trigger scan using polling method!\r\n");
TSI_EnableHardwareTriggerScan(APP_TSI, false); /* Enable software trigger scan */
TSI_DisableInterrupts(APP_TSI, kTSI_EndOfScanInterruptEnable);
TSI_ClearStatusFlags(APP_TSI, kTSI_EndOfScanFlag);
TSI_SetSelfCapMeasuredChannel(APP_TSI, BOARD_TSI_ELECTRODE_1);
TSI_StartSoftwareTrigger(APP_TSI);
while (!(TSI_GetStatusFlags(APP_TSI) & kTSI_EndOfScanFlag))
{
}
PRINTF("Channel %d Normal mode counter is: %d \r\n", BOARD_TSI_ELECTRODE_1, TSI_GetCounter(APP_TSI));
#if (defined(PAD_TSI_ELECTRODE_2_ENABLED) && PAD_TSI_ELECTRODE_2_ENABLED)
TSI_ClearStatusFlags(APP_TSI, kTSI_EndOfScanFlag);
TSI_SetSelfCapMeasuredChannel(APP_TSI, BOARD_TSI_ELECTRODE_2);
TSI_StartSoftwareTrigger(APP_TSI);
while (!(TSI_GetStatusFlags(APP_TSI) & kTSI_EndOfScanFlag))
{
}
PRINTF("Channel %d Normal mode counter is: %d \r\n", BOARD_TSI_ELECTRODE_2, TSI_GetCounter(APP_TSI));
#endif
TSI_ClearStatusFlags(APP_TSI, kTSI_EndOfScanFlag | kTSI_OutOfRangeFlag);
/********** SOFTWARE TRIGGER SCAN USING INTERRUPT METHOD ********/
PRINTF("\r\nNOW, comes to the software trigger scan using interrupt method!\r\n");
TSI_EnableInterrupts(APP_TSI, kTSI_GlobalInterruptEnable);
TSI_EnableInterrupts(APP_TSI, kTSI_EndOfScanInterruptEnable);
TSI_ClearStatusFlags(APP_TSI, kTSI_EndOfScanFlag);
TSI_SetSelfCapMeasuredChannel(APP_TSI, BOARD_TSI_ELECTRODE_1);
while (s_tsiInProgress)
{
TSI_StartSoftwareTrigger(APP_TSI);
}
s_tsiInProgress = true;
PRINTF("Channel %d Normal mode counter is: %d \r\n", BOARD_TSI_ELECTRODE_1, TSI_GetCounter(APP_TSI));
#if (defined(PAD_TSI_ELECTRODE_2_ENABLED) && PAD_TSI_ELECTRODE_2_ENABLED)
TSI_SetSelfCapMeasuredChannel(APP_TSI, BOARD_TSI_ELECTRODE_2);
TSI_StartSoftwareTrigger(APP_TSI);
while (s_tsiInProgress)
{
TSI_StartSoftwareTrigger(APP_TSI);
}
PRINTF("Channel %d Normal mode counter is: %d \r\n", BOARD_TSI_ELECTRODE_2, TSI_GetCounter(APP_TSI));
#endif
#endif
/********** HARDWARE TRIGGER SCAN ********/
PRINTF("TSI_V6 Self-Cap 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 */
}
三、功能展示及说明
电脑上位机网络调试助手开启TCP客户端,连接开发板的TCP服务器成功并收到温度数据上报:
普通按键信息上报:
电容按键信息上报:
RGB LED的控制,电脑上位机发送指令控制亮灭:
发送LED_B=1点亮蓝色LED
发送LED_B=0熄灭蓝色LED:
发送LED_R=1点亮红色LED:
发送LED_R=0熄灭红色LED:
发送LED_G=1点亮绿色LED:
发送LED_G=0熄灭绿色LED:
四、对本活动的心得体会
活动很好,学习周期很长,又有老师直播课程,能学到许多知识;活动选择的是新出热门的板卡,可玩性很高。