Funpack3-4:基于MCXN947与Lwip的http简易httpd服务器
该项目使用了Lwip开源协议栈,实现了一个NXP MCXN947为核心的HTTPD网页服务器的设计,它的主要功能为:可以使用任意浏览器访问MCXN947创建的HTTPD服务器,并能够控制板载LED灯,也可以动态获取按键,i3c温度传感器p3t1755以及板载TouchPad "E1"的触摸状态。
标签
FunPack3-4
MCX N94
LwIp
HTTPD
TSI
I3C
littlestudent
更新2024-09-09
93

感谢硬禾学堂和digikey联合赞助的Funpack活动。NXP是在嵌入式域非常知名的一家国际厂商,这次非常感谢Funpack活动提供的宝贵的活动机会,得以体验NXP强大的第MCX-N系列产品。本次完成的目标任务是:


任务二:

• 使用板卡上的以太网接口连接到电脑上并通过以太网和电脑通信,实现数据传输。要求电脑可以获取到板卡上的温度,触摸和按键信息,并可以通过电脑控制板卡上的RGB LED灯


一、项目框图

如下是系统简易框图。

板卡实物照片:


二、传感器等外设驱动

2.1 板卡介绍:

FRDM-MCXN947是一款紧凑且可扩展的开发板,可让您快速基于MCX N94x MCU开展原型设计。板卡上集成行业标准的接口,可轻松使用MCU的I/O、集成的开放标准串行接口、外部闪存和板载MCU-Link调试器。

image.png


本次用到的外设模块:RED LED, GREEN LED, BUTTON SW3, I3C p3t1755, TouchPad "E1"


2.2 LED:

FRDM-MCXN947开发板有个RGB的三色小灯,电路原理图如下:

初始化代码的引脚配置部分如下:

    const port_pin_config_t port0_10_pinB12_config = {/* Internal pull-up/down resistor is disabled */
kPORT_PullDisable,
/* 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,
/* Pin is configured as PIO0_10 */
kPORT_MuxAlt0,
/* Digital input enabled */
kPORT_InputBufferEnable,
/* Digital input is not inverted */
kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
kPORT_UnlockRegister};
/* PORT0_10 (pin B12) is configured as PIO0_10 */
PORT_SetPinConfig(PORT0, 10U, &port0_10_pinB12_config);

const port_pin_config_t port0_27_pinE10_config = {/* Internal pull-up/down resistor is disabled */
kPORT_PullDisable,
/* 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,
/* Pin is configured as PIO0_10 */
kPORT_MuxAlt0,
/* Digital input enabled */
kPORT_InputBufferEnable,
/* Digital input is not inverted */
kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
kPORT_UnlockRegister};
/* PORT0_27 (pin E10) is configured as PIO0_27 */
PORT_SetPinConfig(PORT0, 27U, &port0_27_pinE10_config);

const port_pin_config_t port1_2_pinC4_config = {/* Internal pull-up/down resistor is disabled */
kPORT_PullDisable,
/* 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,
/* Pin is configured as PIO1_2 */
kPORT_MuxAlt0,
/* Digital input enabled */
kPORT_InputBufferEnable,
/* Digital input is not inverted */
kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
kPORT_UnlockRegister};
/* PORT1_2 (pin C4) is configured as PIO1_2 */
PORT_SetPinConfig(PORT1, 2U, &port1_2_pinC4_config);


之后调用初始化函数:

		LED_RED_INIT(LOGIC_LED_OFF);
LED_GREEN_INIT(LOGIC_LED_OFF);
LED_BLUE_INIT(LOGIC_LED_OFF);

至此,LED部分的驱动完成。在后面的设计中,红色和绿色小灯将由网页直接控制,蓝色小灯会有触摸板E1来控制。


2.3 按键

FRDM开发板的SW3可以用作ISP功能,也可以当作普通的按键。

配置引脚:

    const port_pin_config_t port0_6_pinC14_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,
/* Pin is configured as PIO0_6 */
kPORT_MuxAlt0,
/* Digital input enabled */
kPORT_InputBufferEnable,
/* Digital input is not inverted */
kPORT_InputNormal,
/* Pin Control Register fields [15:0] are not locked */
kPORT_UnlockRegister};
/* PORT0_6 (pin C14) is configured as PIO0_6 */
PORT_SetPinConfig(PORT0, 6U, &port0_6_pinC14_config);

接下来类似LED,进行初始化SW3:

/********************* SW3 INIT BEGIN *************************************************************/		

/* Define the init structure for the input switch pin */
gpio_pin_config_t sw_config = {
kGPIO_DigitalInput,
0,
};

GPIO_PinInit(BOARD_SW_GPIO, BOARD_SW_GPIO_PIN, &sw_config);
/********************* SW3 INIT DONE *************************************************************/


2.4 温度传感器

FRDM-MCXN947开发板上有一颗i3c接口的温度传感器。这个i3c是i2c的升级版。

I3C,全称为Improved Inter-Integrated Circuit,是一种由MIPI联盟发布的通信总线接口标准。作为I2C(Inter-Integrated Circuit)的升级版,I3C不仅继承了I2C的2线传输特性,还在性能、功耗和扩展性方面进行了显著的改进。

初始化代码:

/********************* i3c INIT BEGIN *************************************************************/		

PRINTF("\r\nI3C master read sensor data example.\r\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)
{
PRINTF("\r\nP3T1755 set dynamic address failed.\r\n");
}

p3t1755Config.writeTransfer = I3C_WriteSensor;
p3t1755Config.readTransfer = I3C_ReadSensor;
p3t1755Config.sensorAddress = SENSOR_ADDR;
P3T1755_Init(&p3t1755Handle, &p3t1755Config);

char tempVal[15]={'\0'};
/********************* i3c INIT END *************************************************************/


2.5 触摸板E1

FRDM开发板上的E1是一个触摸板,原理图如下:


TSI模块初始化:

/********************* tsi touch pad INIT BEGIN *************************************************************/		
/* 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]);
}
/********** HARDWARE TRIGGER SCAN ********/
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 */


其中,TSI模块开启硬件中断:


void TSI0_IRQHandler(void)
{
if (TSI_GetSelfCapMeasuredChannel(APP_TSI) == BOARD_TSI_ELECTRODE_1)
{
if (TSI_GetCounter(APP_TSI) > (uint16_t)(buffer.calibratedData[BOARD_TSI_ELECTRODE_1] + TOUCH_DELTA_VALUE))
{
LED1_TOGGLE(); /* Toggle the touch event indicating LED */
s_tsiInProgress = true;
}
}

/* Clear endOfScan flag */
TSI_ClearStatusFlags(APP_TSI, kTSI_EndOfScanFlag);
SDK_ISR_EXIT_BARRIER;
}

至此,TSI模块初始化完毕。


三、增加LwIp HTTPD服务器:

FRDM-MCXN947开发板的固件库提供了Lwip httpsrv这个例程,里面有个简单的http服务器,本文拿来参考其中的初始化代码:

		
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;

time_init();

/* Set MAC address. */
#ifndef configMAC_ADDR
(void)SILICONID_ConvertToMacAddr(&enet_config.macAddress);
#endif

/* Get clock after hardware init. */
enet_config.srcClockHz = EXAMPLE_CLOCK_FREQ;

#if LWIP_IPV4
IP4_ADDR(&netif_ipaddr, configIP_ADDR0, configIP_ADDR1, configIP_ADDR2, configIP_ADDR3);
IP4_ADDR(&netif_netmask, configNET_MASK0, configNET_MASK1, configNET_MASK2, configNET_MASK3);
IP4_ADDR(&netif_gw, configGW_ADDR0, configGW_ADDR1, configGW_ADDR2, configGW_ADDR3);
#endif /* LWIP_IPV4 */

lwip_init();

#if LWIP_IPV4
netif_add(&netif, &netif_ipaddr, &netif_netmask, &netif_gw, &enet_config, EXAMPLE_NETIF_INIT_FN, ethernet_input);
#else
netif_add(&netif, &enet_config, EXAMPLE_NETIF_INIT_FN, ethernet_input);
#endif /* LWIP_IPV4 */
netif_set_default(&netif);
netif_set_up(&netif);

#if LWIP_IPV6
netif_create_ip6_linklocal_address(&netif, 1);
#endif /* LWIP_IPV6 */

while (ethernetif_wait_linkup(&netif, 5000) != ERR_OK)
{
PRINTF("PHY Auto-negotiation failed. Please check the cable connection and link partner setting.\r\n");
}

//httpd_init();
http_server_init();

#if LWIP_IPV6
set_ipv6_valid_state_cb(netif_ipv6_callback);
#endif /* LWIP_IPV6 */

PRINTF("\r\n***********************************************************\r\n");
PRINTF(" HTTP Server example\r\n");
PRINTF("***********************************************************\r\n");
#if LWIP_IPV4
PRINTF(" IPv4 Address : %u.%u.%u.%u\r\n", ((u8_t *)&netif_ipaddr)[0], ((u8_t *)&netif_ipaddr)[1],
((u8_t *)&netif_ipaddr)[2], ((u8_t *)&netif_ipaddr)[3]);
PRINTF(" IPv4 Subnet mask : %u.%u.%u.%u\r\n", ((u8_t *)&netif_netmask)[0], ((u8_t *)&netif_netmask)[1],
((u8_t *)&netif_netmask)[2], ((u8_t *)&netif_netmask)[3]);
PRINTF(" IPv4 Gateway : %u.%u.%u.%u\r\n", ((u8_t *)&netif_gw)[0], ((u8_t *)&netif_gw)[1],
((u8_t *)&netif_gw)[2], ((u8_t *)&netif_gw)[3]);
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
print_ipv6_addresses(&netif);
#endif /* LWIP_IPV6 */
PRINTF("***********************************************************\r\n");

while (1)
{

/* Poll the driver, get any outstanding frames */
ethernetif_input(&netif);

sys_check_timeouts(); /* Handle all system timeouts for all core protocols */
}


在本设计中,使用CGI和SSI技术,来实现网页控制LED灯,以及把温度传感器数据上报到网页的目标。

其中CGI部分是嵌入在网页html代码中的,定义如下:

<form method="get" action="/leds.cgi"><input value="1" name="led" type="checkbox">RED ON<br>
<input value="2" name="led" type="checkbox">RED OFF<br>
<input value="3" name="led" type="checkbox">GREEN ON<br>
<input value="4" name="led" type="checkbox">GREEN OFF
<br>
<br>
<input value="Send" type="submit"> </form>

网页预览如下:

在勾选RED ON,然后点击SEND后,MCXN947就会接受到相应的以太网报文并进行解析:

const char * LEDS_CGI_Handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
{
//printf("\r\n inside httpd_cgi_ssi.c line 132. LEDS_CGI_Handler was Called.");
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n *************************************************************"));
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\ninside httpd_cgi_ssi.c line 132. LEDS_CGI_Handler was Called."));

uint32_t i=0;

/* We have only one SSI handler iIndex = 0 */
if (iIndex==0)
{
/* All LEDs off */
LED_RED_OFF();
LED_GREEN_OFF();

/* Check cgi parameter : example GET /leds.cgi?led=2&led=4 */
for (i=0; i<iNumParams; i++)
{
/* check parameter "led" */
if (strcmp(pcParam[i] , "led")==0)
{
//printf("\r\n inside httpd_cgi_ssi.c line 148. led received.");

/* Switch LED1 ON if 1 */
if(strcmp(pcValue[i], "1") ==0) {
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n ********>>>> LED FOUND <<<<<<**"));
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n matched led1"));
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n ***************************"));
LED_RED_ON();
}


/* Switch LED2 ON if 2 */
else if(strcmp(pcValue[i], "2") ==0){
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n ********>>>> LED FOUND <<<<<<**"));
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n matched led2"));
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n ***************************"));
LED_RED_OFF();
}
//BSP_LED_On(LED2);

/* Switch LED3 ON if 3 */
else if(strcmp(pcValue[i], "3") ==0){
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n ********>>>> LED FOUND <<<<<<**"));
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n matched led3"));
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n ***************************"));
LED_GREEN_ON();
}

/* Switch LED4 ON if 4 */
else if(strcmp(pcValue[i], "4") ==0){
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n ********>>>> LED FOUND <<<<<<**"));
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n matched led4"));
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n ***************************"));
LED_GREEN_OFF();
}//<--END OF IF -->
}
}//<--END OF FOOR LOOP-->
}//<--END OF IF(INDEX == 0)-->
/* uri to send after cgi call*/
return "/NXPMCXN947LED.html";
}


SSI技术用于把触摸,按键以及温度信息上报给浏览器:

注意<!--#b-->这其中的b就是一个变量,后面可以在代码里去更新这个值,然后由于网页是定时刷新的,所以浏览器那边也可以看到最近更新的值。

利用这种方式,b代表button; t代表温度;s代表触摸传感器。

SSI部分对应的代码:

u16_t Button_Handler(int iIndex, char *pcInsert, int iInsertLen)
{
/* We have only 3 SSI handler, iIndex = 0 */
static uint32_t tempCnt = 0;
static double temperature;

switch(iIndex){
case 0:
if((0 == GPIO_PinRead(BOARD_SW3_GPIO, BOARD_SW3_GPIO_PIN))){
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n ********>>>> BUTTON PRESSED <<<<<<**"));
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n SW3 (P0_6)"));
LWIP_DEBUGF(HTTPD_DEBUG, ("\r\n ***************************"));
/* prepare data to be inserted in html */
*pcInsert = (char)(66);
*(pcInsert + 1) = (char)(84);
*(pcInsert + 2) = (char)(78);
*(pcInsert + 3) = (char)(45);
*(pcInsert + 4) = (char)(79);
*(pcInsert + 5) = (char)(78);
*(pcInsert + 6) = (char)(32);
}else{
/* prepare data to be inserted in html */
*pcInsert = (char)(66);
*(pcInsert + 1) = (char)(84);
*(pcInsert + 2) = (char)(78);
*(pcInsert + 3) = (char)(45);
*(pcInsert + 4) = (char)(79);
*(pcInsert + 5) = (char)(70);
*(pcInsert + 6) = (char)(70);
}

/* 7 characters need to be inserted in html*/
return 7;
break;

case 1:
P3T1755_ReadTemperature(&p3t1755Handle, &temperature);
sprintf(pcInsert, "%.3f", temperature);
return strlen(pcInsert);
break;

case 2:
if(true == s_tsiInProgress){
sprintf(pcInsert, "%s", "Touch Detected..");
s_tsiInProgress = false;
}else{
sprintf(pcInsert, "%s", "..");
}
return strlen(pcInsert);
break;

default:
break;

}
return 0;
}


实物展示请参考开头视频。



心得体会

通过本次活动,自己第一次了解并使用了lwip协议,初次体验了以太网这种通信方式,非常多的新知识需要学习和了解。很开心能够完成任务,期待后续能够加深以太网底层的了解。


祝Funpack越办越好!



附件下载
LWIP-LED-BTN.zip
团队介绍
电子发烧中、
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号