基于FRDM-MCXN947的LwIP通信与外设控制(MCUXpresso+LwIP+TSI)

👉 【Funpack3-3】
👉 Github: EmbeddedCamerata/FRDM-MCXN947_lwip_peripherals_control

项目介绍

基于 NXP FRDM-MCXN947 开发板,通过板卡上的 RJ45 以太网口与 PC 连接,使用 LwIP 协议与主机进行数据传输。开发板集成有温度传感器、按键、触摸按键等外设,板卡可将温度,触摸与按键信息发送至主机;主机可通过 LwIP 控制板卡 RGB LED。

👉 MCUXpresso
👉 MCUXpresso VSCode 扩展

硬件介绍

FRDM-MCXN947 是一款紧凑且可扩展的开发板,可让您快速基于 MCX N94x MCU 开展原型设计。板卡上集成行业标准的接口,可轻松使用 MCU 的I/O、集成的开放标准串行接口、外部闪存和板载 MCU-Link 调试器,支持各种附加版的扩展,为用户提供简单高效的评估和开发体验。

该板由一个 MCXN947 器件和一个64 mbit外部串行闪存组成。该板还具有 P3T1755DP I3C 温度传感器、TJA1057GTK/3Z CAN PHY、以太网 PHY、SDHC电路、RGB LED、触摸板、高速 USB 电路、按钮等 MCU-Link 调试器电路。该板提供 Arduino 扩展、PMOD 拓展,MicroBus 总线拓展。该板还支持摄像头模块和 NXP 低成本 LCD 模块 PAR-LCD-S035。板载 MCU-Link 调试器基于 LPC55S69 MCU。

板卡顶部视图

项目设计

开发环境

本项目使用 MCUXpresso VSCode 扩展开发。项目所用到的工具链、库如下:

  1. 编译器:arm-none-eabi-gcc v13.3.1
  2. 调试器:arm-none-eabi-gdb v14.2
  3. MCUXpresso SDK:SDK_2.x_FRDM-MCXN947 v2.16
  4. Linkserver:v1.6.121,启动和管理恩智浦硬件调试器的GDB服务器,并提供命令行目标闪存编程功能

总体流程图

首先初始化时钟与外设,而后创建三个外设相关的读取任务,用于周期性读取并记录它们的状态。之后进行 LwIP 初始化,进行以太网初始化、IPv4 地址设置,再创建主任务,socket 创建、绑定、监听,并在死循环中等待 PC 连接。当 PC 连接后,创建数据发送任务,每秒发送外设的状态信息;且接收 PC 发送的命令,进行字符串匹配,根据匹配结果控制对应 REB LED 。当 PC 断连后,删除数据发送任务,并等待下次连接。

工程总体流程图

功能实现

由于使用的是 MCUXpresso VSCode 扩展,因此本项目基于多个示例工程进行开发。本项目将参考官方提供的多个相关模块的示例代码进行移植与修改。当安装好对应开发板的 SDK 后,可通过扩展内的“Import Example from Repository”选项导入示例程序。

LED控制、按键读取

参考示例工程 frdmmcxn947_gpio_input_interrupt 中的对 GPIO 的配置、读写功能。

/* Define the init structure for the input switch pin */
gpio_pin_config_t sw_config = {
    kGPIO_DigitalInput,
    0,
};
GPIO_PinInit(BOARD_SW3_GPIO, BOARD_SW3_GPIO_PIN, &sw_config);
/* LED init */
LED_RED_INIT(LOGIC_LED_OFF);
LED_GREEN_INIT(LOGIC_LED_OFF);
LED_BLUE_INIT(LOGIC_LED_OFF);

针对 RGB LED 的操作可通过宏 LED_xxx_INITLED_xxx_ONLED_xxx_OFFLED_xxx_TOGGLE 完成,xxx 为对应颜色。针对 SW3,将其配置成输入,通过 GPIO_PinRead() 可读取其值,0表示按下。将按键读取功能单独写成函数,后续将作为一个单独的任务在 FreeRTOS 中创建。

/* Whether the SW button is pressed */
volatile uint32_t g_ButtonStatus = 0;
static void sw_scan_task(void *arg)
{
    while (1)
    {
        if ((g_ButtonStatus = GPIO_PinRead(BOARD_SW3_GPIO, BOARD_SW3_GPIO_PIN)) == 0)
        {
            PRINTF("%s is pressed\n", BOARD_SW3_NAME);
        }
        vTaskDelay(200 / portTICK_PERIOD_MS);
    }
    vTaskDelete(NULL);
}

温度传感器测量与读取

参考示例工程 frdmmcxn947_lpadc_temperature_measurement 中初始化温度传感器、读取温度的代码,该工程通过设置 ADC 软件中断进行温度测量, g_LpadcConversionCompletedFlag 用于表示温度测量是否完成,测得的温度保存至 g_CurrentTemperature 中。读取温度数值的代码单独写成函数,后续将作为一个单独的任务在 FreeRTOS 中创建。

volatile bool g_LpadcConversionCompletedFlag = false;
float g_CurrentTemperature = 0.0f;
static void temp_measurement_task(void *arg)
{
    while (1)
    {
        g_LpadcConversionCompletedFlag = false;
        LPADC_DoSoftwareTrigger(LPADC_BASE, 1U); /* 1U is trigger0 mask. */
        while (false == g_LpadcConversionCompletedFlag)
            ;
        vTaskDelay(500 / portTICK_PERIOD_MS);
    }
    vTaskDelete(NULL);
}

触摸按键读取

参考示例工程 frdmmcxn947_tsi_v6_self_cap 中提供的软件中断触发扫描(示例还提供了硬件触发扫描、软件轮询触发扫描的方法)的方法实现对触摸按键的读取检测。除了打开必要的时钟外,三种扫描方法对TSI初始化的配置是相同的:

tsi_calibration_data_t buffer;
void TSI0_IRQHandler(void)
{
	...
}

int main(void)
{
	/* 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 */
	/*********  CALIBRATION PROCESS ************/
	memset((void *)&buffer, 0, sizeof(buffer));
	TSI_SelfCapCalibrate(APP_TSI, &buffer);
}

当触摸按键未被按下时,s_tsiInProgress 为 true ,当按下超过一定时长后,触发软件中断,在回调函数 TSI0_IRQHandler 中,s_tsiInProgress 置 false 。按下过程中,将触发多次软件中断。当松手后,不会触发软件中断,s_tsiInProgress 将置 true,将检测触摸按键的代码单独写成函数,后续将作为一个单独的任务在 FreeRTOS 中创建。

static void tsi_scan_task(void *arg)
{
    while (1)
    {
        while (s_tsiInProgress)
        {
            TSI_StartSoftwareTrigger(APP_TSI);
        }
        s_tsiInProgress = true;
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
    vTaskDelete(NULL);
}

LwIP通信

本项目主体代码参考示例工程 frdmmcxn947_lwip_ping_freertos,该示例提供了完整的IPv4地址初始化、网口初始化、FreeRTOS配置等,后续只需要将示例中所使用的 ping 功能替换为我们的业务代码即可。
LwIP参考:[野火]LwIP应用开发实战指南—基于STM32

首先配置开发板的IPv4地址 configIP_ADDR0/1/2/3 与所连接的网关的地址 configGW_ADDR0/1/2/3

#define TCP_PORT 5001
static void lwip_tcp_task(void *arg)
{
    LWIP_UNUSED_ARG(arg);
    int err, sock;
    struct timeval timeout = {.tv_sec = 1, .tv_usec = 0};
    struct sockaddr_in server_addr, client_addr;
    int connected = -1;
    socklen_t sin_size = sizeof(struct sockaddr_in);
    char *recv_cmd;
    ssize_t recv_cmd_len;
    char unknown_cmd_str[] = "Unknown command\n";

    recv_cmd = (char *)pvPortMalloc(LWIP_RECV_DATA_SIZE);
    LWIP_ERROR("no memory.", recv_cmd != NULL, goto __exit;);

    sock = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    LWIP_ERROR("socket build failed.", sock >= 0, return;);

    err = lwip_setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
    LWIP_ERROR("setting receive timeout failed.", err == 0, goto __exit;);

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = lwip_htons(TCP_PORT);
    memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

    /* Socket bind */
    err = lwip_bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
    LWIP_ERROR("bind failed.", err == 0, goto __exit;);

    /* Listen */
    err = lwip_listen(sock, 5);
    LWIP_ERROR("listen failed.", err == 0, goto __exit;);

    while (1)
    {
		...
	}

    vTaskDelete(NULL);

__exit:
    if (recv_cmd)
        vPortFree(recv_cmd);
    if (sock >= 0)
        lwip_close(sock);

    vTaskDelete(NULL);
}

通过 lwip_socketlwip_setsockopt 创建一个 socket 接口并配置,lwip_bind 将申请成功的 socket 与网卡信息进行绑定,绑定到5001端口。recv_cmd 后续将接收从 PC 发送来的数据。后续,在 while(1) 循环中,通过 lwip_accept 判断 PC 是否发出连接请求,若请求则返回描述符。若连接,则创建一个发送任务,用于定时向 PC 发送传感器、按键信息。

while (1)
{
    connected = lwip_accept(sock, (struct sockaddr *)&client_addr, &sin_size);
    if (connected > 0)
    {
        PRINTF("new client connected from (%s, %d)\n",
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        if (send_data_task_handle == NULL)
        {
            if (xTaskCreate(send_data_task, "send_data_task", 512,
                            (void *)&connected, 4, &send_data_task_handle) != pdPASS)
            {
                PRINTF("lwip_tcp_task(): Task creation failed!\n");
                while (1)
                    ;
            }
        }
        while (1)
        {
            ...
        }
    else
    {
        if (send_data_task_handle != NULL)
        {
            vTaskDelete(send_data_task_handle);
            send_data_task_handle = NULL;
        }
    }
    vTaskDelay(500 / portTICK_PERIOD_MS);
}

同时,当 PC 连接后,阻塞接收从 PC 传输的数据,并进行字符串匹配,根据匹配的结果“blue”/“green”/"red"控制对应的 LED 状态翻转;匹配到“close”则关闭连接;未匹配则发送字符串至 PC 进行提示。

#define LWIP_RECV_DATA_SIZE 128
while (1)
{
    memset(recv_cmd, 0, LWIP_RECV_DATA_SIZE);
    recv_cmd_len = lwip_recv(connected, recv_cmd, LWIP_RECV_DATA_SIZE, 0);
    if (recv_cmd_len <= 0)
        break;
    /* Command match */
    if (strcmp(recv_cmd, "close") == 0)
    {
        err = lwip_close(connected);
        LWIP_ERROR("close failed", err == 0, goto __exit;);
        connected = -1;
        break;
     }
     else if (strcmp(recv_cmd, "blue") == 0)
     {
         LED_BLUE_TOGGLE();
     }
     else if (strcmp(recv_cmd, "green") == 0)
     {
         LED_GREEN_TOGGLE();
     }
     else if (strcmp(recv_cmd, "red") == 0)
     {
         LED_RED_TOGGLE();
     }
     else // Send unknown command message
     {
         lwip_write(connected, (void *)unknown_cmd_str, strlen(unknown_cmd_str));
     }
}

该任务实现每秒通过 lwip_write 向 PC 发送温度传感器读数、按键、触摸按键信息。它在 LwIP 连接到 PC 后创建(若不存在则创建),若连接关闭则任务将被删除。

#define LWIP_SEND_DATA_SIZE 128
static void send_data_task(void *arg)
{
    int connected = *(int *)arg;
    int temp_int, temp_deci;
    char *send_data;
    send_data = (char *)pvPortMalloc(LWIP_SEND_DATA_SIZE);
    LWIP_ERROR("no memory", send_data != NULL, goto __exit;);

    LWIP_UNUSED_ARG(arg);

    while (1)
    {
        temp_int = (int)g_CurrentTemperature;
        temp_deci = (int)(100 * (g_CurrentTemperature - temp_int));
        sprintf(send_data, "temp: %02d.%02d, touch: %d, sw3: %lu\n",
                temp_int, temp_deci, 1 - s_tsiInProgress, 1 - g_ButtonStatus);

        lwip_write(connected, (void *)send_data, strlen(send_data));
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    vTaskDelete(NULL);

__exit:
    if (send_data)
        vPortFree(send_data);
    vTaskDelete(NULL);
}

编译配置

需要根据前述所参考的工程,将它们的 pin_mux.cboard.h 综合起来,这里面主要是针对 GPIO 或板卡硬件相关参数的定义。同时,还需要为 armgcc/config.cmake 添加额外用到的模块,具体可通过前述示例工程内的 armgcc/config.cmake 进行比较可得。将它们汇总至本工程下 armgcc/config.cmake 即可。

set(CONFIG_COMPILER gcc)
set(CONFIG_TOOLCHAIN armgcc)
set(CONFIG_USE_COMPONENT_CONFIGURATION false)
set(CONFIG_USE_driver_phy-device-lan8741 true)
set(CONFIG_USE_driver_vref_1 true)
set(CONFIG_USE_driver_crc true)
set(CONFIG_USE_utility_debug_console_lite true)
set(CONFIG_USE_utility_assert_lite true)
set(CONFIG_USE_driver_lpadc true)
set(CONFIG_USE_driver_flashiap true)
set(CONFIG_USE_component_silicon_id true)
set(CONFIG_USE_middleware_lwip_mcx_ethernetif true)
set(CONFIG_USE_middleware_lwip true)
set(CONFIG_USE_driver_mcx_enet true)
set(CONFIG_USE_middleware_freertos-kernel_cm33_non_trustzone true)
set(CONFIG_USE_middleware_lwip_contrib_ping true)
set(CONFIG_USE_driver_clock true)
set(CONFIG_USE_middleware_freertos-kernel_heap_3 true)
set(CONFIG_USE_driver_common true)
set(CONFIG_USE_driver_inputmux true)
set(CONFIG_USE_driver_power true)
set(CONFIG_USE_driver_reset true)
set(CONFIG_USE_device_MCXN947_CMSIS true)
set(CONFIG_USE_device_MCXN947_startup true)
set(CONFIG_USE_driver_lptmr true)
set(CONFIG_USE_driver_tsi_v6 true)
set(CONFIG_USE_driver_lpuart true)
set(CONFIG_USE_driver_gpio true)
set(CONFIG_USE_driver_port true)
set(CONFIG_USE_component_lpuart_adapter true)
set(CONFIG_USE_component_lists true)
set(CONFIG_USE_driver_reset true)
set(CONFIG_USE_utilities_misc_utilities true)
set(CONFIG_USE_utility_str true)
set(CONFIG_USE_driver_phy-common true)
set(CONFIG_USE_middleware_lwip_template true)
set(CONFIG_USE_component_gpio_adapter true)
set(CONFIG_USE_middleware_freertos-kernel true)
set(CONFIG_USE_middleware_freertos-kernel_template true)
set(CONFIG_USE_middleware_freertos-kernel_extension true)
set(CONFIG_USE_driver_mcx_spc true)
set(CONFIG_USE_driver_inputmux_connections true)
set(CONFIG_USE_CMSIS_Include_core_cm true)
set(CONFIG_USE_device_MCXN947_system true)
set(CONFIG_USE_driver_lpflexcomm true)
set(CONFIG_CORE cm33)
set(CONFIG_DEVICE MCXN947)
set(CONFIG_BOARD frdmmcxn947)
set(CONFIG_KIT frdmmcxn947)
set(CONFIG_DEVICE_ID MCXN947)
set(CONFIG_FPU SP_FPU)
set(CONFIG_DSP DSP)
set(CONFIG_CORE_ID cm33_core0)

由于直接使用的 MCUX VSCode 扩展,因此未安装 newlib 库,头一次编译将报错。这时,将 armgcc/CMakePresets.json 中有关 NEWLIB 的配置删除即可。

同时,还有可能遇到成功编译、下载,但是程序卡死的情况。修改 armgcc/flags.cmake 中 linker 链接标志,位于 CMAKE_EXE_LINKER_FLAGS_DEBUGCMAKE_EXE_LINKER_FLAGS_RELEASE,修改 --defsym=__stack_size__=2000--defsym=__heap_size__=38400 后再编译、运行,应该可以解决问题。

功能展示

首先通过网线将板卡与 PC 连接,在 PC 上连接该有线网。此时,串口将打印 IP 等信息。

LwIP连接
通过 TCP 连接板卡,连接后,板卡每秒钟将通过 LwIP 发送温度、按键等信息。如下图所示,按下触摸按键,“touch btn”状态为1。

板卡通过LwIP发送数据
PC 也可通过 LwIP 发送数据至板卡以控制 LED。如下图所示,输入“red”,板卡红灯亮。

在这里插入图片描述
👉 详细展示参见:B站:基于FRDM-MCXN947的LwIP通信与外设控制

项目总结

本次项目基于 FRDM-MCXN947 与 LwIP 实现了与 PC 的通信与外设控制,了解了 LwIP 的基本使用方法、触摸按键等外设的读取。MCUX 扩展功能集成度高,项目的编译、下载均可通过扩展完成,调试也可以使用 gdb 在 VSCode 中实现。

  • 22
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值