[Funpack3-4][MCXN947]以太网测温&RGB控制
该项目使用了FRDM-MCXN947,RT-Thread,实现了TCP服务端的设计,它的主要功能为:测量板卡上温度和按键/触摸信息,并接收来自上位机对RGB颜色的控制。
标签
嵌入式系统
Funpack活动
开发板
KurehaTian
更新2024-09-09
中国计量大学
188



任务要求

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

实现思路

1. 系统配置

为了便于利用板卡的以太网功能,在本次项目实现中选择了使用RT-Thread实时操作系统。

为了使用板载的温度传感器,需焊接板上的R51 R52上拉电阻,并在env中将P1_16、P1_17引脚配置为I2C。

为了使用以太网功能,须在env中开启ETH硬件驱动、LwIP和SAL(Socket抽象层)。

配置完成后,使用scons工具进行构建,并输出MDK工程。

2. 系统结构

从业务逻辑上区分,可分为RGB LED控制,接收和发送三个部分。此外,还有RT-Thread及其组件提供的系统、以太网收发协议等底层逻辑,按下不表。

其中,RGB LED控制部分中设计一三通道比较器,以1ms的软件延时驱动计数器自增,与R、G、B三个通道的预设值进行比较,进而控制LED的三个通道在16ms周期中打开的占空比,即可对RGB三个通道的亮度进行调节。最终实现R、G、B各可以进行16级亮度调节,共4096色的颜色控制。

接收部分负责接受来自上位机的信息,进行解析后根据控制数据对RGB LED颜色进行控制,更改RGB LED中的设定值。接收动作是阻塞的,且仅在已连接的状态下进行recv动作。解析部分与接收部分各开启一个线程。

发送部分负责定期采集并向上位机发送温度、按键和触摸状态。当socket创建并建立连接后,每隔200ms按照csv格式向上位机发送上述数据。

上位机使用Vofa+,使用一个circular_gauge控件显示温度,使用两个light控件分别显示按键状态和触摸状态,使用三个slider控件对RGB LED的亮度进行调节。当亮度数据发生变化时,发生变化的数据将会传送至板卡。

[ ch ]: [ value ]

硬件介绍

MCX N947是NXP推出的高集成度的双核MCU。它采用两个Cortex-M33内核,运行频率高达150MHz,集成了2MB Flash和512KB RAM,为各类应用提供了充裕的资源。 此外,它还含有DSP协处理器和eIQ Neutron NPU,在机器学习的推理上比传统方案有至高42倍的提升。

充足的接口资源也为它提供了更多可能。10个LPFlexcomm接口,每个接口分别可以配置成UART/I2C/SPI,避免了某类接口充裕的同时另一类接口短缺的情况,提高了接口使用效率。

与其他Armv8-m架构的MCU相同,MCXN947在安全方面也下足了功夫,与前代产品相比安全性能大幅提高。


FRDM-MCXN947是一款紧凑且可扩展的开发板,可供快速基于MCXN94x开展原型设计,具有丰富的外部组件。

提供行业标准的接口,可轻松访问MCU的I/O、集成的开放标准串行接口、外部闪存和板载MCU-Link调试器。

特性

描述

微控制器

MCX-N947有两个Arm™ Cortex™-M33内核,每个内核的主频均为150MHz,优化了性能效率,高达2MB的双块Flash存储器,带可选的全ECC RAM,外部闪存、神经处理单元、PowerQuad、Smart DMA等

存储器扩展

Micro SD卡插槽,SPI Flash

连接

以太网PHY和连接器,HS USB Type-C连接器,SPI/I2C/UART连接器(PMOD/mikroBUS、DNP),WIFI连接器(PMOD/mikroBUS、DNP),CAN-FD收发器

传感器

P3T1755 I3C/I2C温度传感器,触摸板

扩展选项

Arduino®接头,FRDM接头,FlexIO/LCD接头,SmartDMA/摄像头接头,Pmod™,mikroBUS™

用户接口

RGB用户LED,加上复位、ISP、唤醒按钮

软件流程

应用部分

1. RGB LED控制线程

typedef struct
{
    volatile uint32_t R; //R比较值
    volatile uint32_t G; //G比较值
    volatile uint32_t B; //B比较值
    volatile uint32_t cnt; //计数值
} rgb_para_t;
rgb_para_t rgb_para;
/**
@brief 对RGB LED相关引脚进行初始化,并以白色闪烁一下,然后启动控制线程
**/
void RGB_init()
{
//初始化IO口 
rt_pin_mode(RGB_R_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(RGB_G_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(RGB_B_PIN, PIN_MODE_OUTPUT);

//控制IO口拉低-亮起
rt_pin_write(RGB_R_PIN, PIN_LOW);
rt_pin_write(RGB_G_PIN, PIN_LOW);
rt_pin_write(RGB_B_PIN, PIN_LOW);
rt_thread_mdelay(1000);

//控制IO口拉高-熄灭
rt_pin_write(RGB_R_PIN, PIN_HIGH);
rt_pin_write(RGB_G_PIN, PIN_HIGH);
rt_pin_write(RGB_B_PIN, PIN_HIGH);
rt_thread_mdelay(1000);

//创建RGB控制线程
rt_thread_t tid = rt_thread_create("RGB", RGB_ctrl, &rgb_para, 512, RT_THREAD_PRIORITY_MAX / 3, 2);

//启动线程
rt_thread_startup(tid);
}


/**
@brief 持续运行,按照设定的占空比调节各通道何时亮,何时不亮,以16ms为周期
**/
void RGB_ctrl(void *parameter)
{
while (1)
{
//对R通道进行比较控制
if (rgb_para.cnt < rgb_para.R)
rt_pin_write(RGB_R_PIN, PIN_LOW);
else
rt_pin_write(RGB_R_PIN, PIN_HIGH);

//对G通道进行比较控制
if (rgb_para.cnt < rgb_para.G)
rt_pin_write(RGB_G_PIN, PIN_LOW);
else
rt_pin_write(RGB_G_PIN, PIN_HIGH);

//对G通道进行比较控制
if (rgb_para.cnt < rgb_para.B)
rt_pin_write(RGB_B_PIN, PIN_LOW);
else
rt_pin_write(RGB_B_PIN, PIN_HIGH);

//计数器自增
rgb_para.cnt++;

//溢出则重新开始
if (rgb_para.cnt >= 16)
rgb_para.cnt = 0;

//软件定时器1ms
rt_thread_mdelay(1);
}
}

2. 发送线程

在接收和发送逻辑开启之前,首先开启socket并绑定至端口。

对服务端进行初始化

void server_init()
{
isConnected = 0;
rt_ringbuffer_init(&rb, rb_pool, 512);
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
/* 创建失败的错误处理 */
rt_kprintf("Socket error\n");
return;
}
int32_t nonblock = 1;

/* 初始化服务端地址 */
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(5000); /* 服务端工作的端口 */
server_addr.sin_addr.s_addr = INADDR_ANY;
rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));

//将socket绑定至端口
if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
/* 绑定失败 */
rt_kprintf("Unable to bind\n");
return;
}


//开启端口监听
if (listen(sock, 5) == -1)
{
rt_kprintf("Listen error\n");
return;
}
rt_kprintf("\nTCPServer Waiting for client on port 5000...\n");

//等待客户端连接并接受连接
server_handle();
}

void server_handle()
{
int32_t ret;
rt_thread_t tRx, tTx, tCmd;
//创建发送、接收和命令解析线程
tRx = rt_thread_create("TCP RX", func_recv, RT_NULL, 2048, 17, 150);
tTx = rt_thread_create("TCP TX", func_send, RT_NULL, 2048, 18, 150);
tCmd = rt_thread_create("RX CMD", rx_cmd, RT_NULL, 2048, 20, 150);

//启动发送、接收和命令解析线程
rt_thread_startup(tRx);
rt_thread_startup(tCmd);
rt_thread_startup(tTx);
while (1)
{
sin_size = sizeof(struct sockaddr_in);
//接收连接
connected = accept(sock, (struct sockaddr *)&client_addr, &sin_size);

// 连接失败
if (connected < 0)
{
rt_kprintf("accept connection failed! errno = %d\n", errno);
continue;
}
rt_kprintf("I got a connection from (%s , %d)\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
isConnected = 1;
// 至此,与客户端建立起了链接。以下是在该链接上进行的处理
//向客户端发送连接握手信息
ret = send(connected, send_buf, 34, 0);
if (ret < 0)
{
/* 发送失败,关闭这个连接 */
closesocket(connected);
isConnected = 0;
rt_kprintf("\nsend error,close the socket.\r\n");
break;
}
if (ret == 0)
rt_kprintf("\n Send warning,send function return 0.\r\n");

//阻塞,直至连接断开后重新开始接收流程
while (isConnected)
;
}
}

以下为发送线程。

void func_send()
{
int32_t ret;
while (1)
{
if (isConnected == 0)
{
rt_thread_mdelay(100);
continue;
}
sprintf(send_buf, "data:%.2f,%d,%d\n", P3T1755DP_read_temperature(), key_get(), touch_get());
send_len = strlen(send_buf);
ret = send(connected, send_buf, send_len, 0);
if (ret < 0)
{
closesocket(connected);
isConnected = 0;
rt_kprintf("\nsend error,close the socket.\r\n");
break;
}
if (ret == 0)
rt_kprintf("\n Send warning,send function return 0.\r\n");
rt_thread_mdelay(500);
}
}

3. 接收线程(含解析)

/**
@brief socket阻塞式接收,将接收到的结果存入环形缓冲
**/
void func_recv()
{
int32_t ret, bytes_received;
uint8_t buf[256];
rt_memset(buf, 0, 256);

while (1)
{
//若当前连接断开则不占用CPU
if (isConnected == 0)
{
rt_thread_mdelay(100);
continue;
}

//阻塞式接收
bytes_received = recv(connected, buf, 256, 0);

if (bytes_received < 0)
{
//接收出错,断开连接
closesocket(connected);
isConnected = 0;
continue;
}
else if (bytes_received == 0)
{
//接收出错,断开连接
rt_kprintf("\nReceived warning,recv function return 0.\r\n");
closesocket(connected);
isConnected = 0;
continue;
}
//将数据写入环形缓冲区
rt_ringbuffer_put(&rb, buf, bytes_received);
rt_memset(buf, 0, 256);
}
}

/**
@brief 将环形缓冲区中的指令读出,并进行解析
**/
void rx_cmd()
{
uint8_t buf;
while (1)
{
// 若环形缓冲区为空, 则等待并不占用CPU
while (RT_RINGBUFFER_EMPTY == rt_ringbuffer_data_len(&rb))
rt_thread_mdelay(10);

//将环形缓冲区数据读出
rt_ringbuffer_get(&rb, rcv_cmd + rcv_len, 1);
rcv_len++;

//直至读到换行为止,开始解析
if (rcv_cmd[rcv_len - 1] == '\n')
{
rcv_cmd[rcv_len] = 0;

// 对R通道亮度进行配置
if (strstr(rcv_cmd, "R:") != NULL)
{
sscanf(rcv_cmd, "R:%d", &rgb_para.R);
rt_kprintf("the volume of RGB-R is set to %d\r\n", rgb_para.R);
}

// 对R通道亮度进行配置
if (strstr(rcv_cmd, "G:") != NULL)
{
sscanf(rcv_cmd, "G:%d", &rgb_para.G);
rt_kprintf("the volume of RGB-G is set to %d\r\n", rgb_para.G);
}

// 对R通道亮度进行配置
if (strstr(rcv_cmd, "B:") != NULL)
{
sscanf(rcv_cmd, "B:%d", &rgb_para.B);
rt_kprintf("the volume of RGB-B is set to %d\r\n", rgb_para.B);
}
rcv_len = 0;
rt_memset(rcv_cmd, 0, 32);
}
}
}

驱动部分

1. 传感器

struct rt_i2c_bus_device *i2c_bus;

/**
@brief 创建i2c总线句柄
**/
void P3T1755DP_init()
{
i2c_bus = (struct rt_i2c_bus_device *)rt_device_find("i2c1");
if (i2c_bus == RT_NULL)
{
rt_kprintf("can't find i2c1 device!\r\n");
}
}


/**
@brief 读取温度
**/
float P3T1755DP_read_temperature()
{
int32_t ret = 0;
//要发送的数据,即温度寄存器地址
uint8_t buf[5] = {0x00, 0x00};

//将发送-接收动作数据构造成结构体
struct rt_i2c_msg dat[2];
dat[0].addr = 0x48;
dat[1].addr = 0x48;
dat[0].buf = buf;
dat[1].buf = buf;
dat[0].flags = RT_I2C_WR;
dat[1].flags = RT_I2C_RD;
dat[0].len = 1;
dat[1].len = 2;

//进行i2c通信
ret = rt_i2c_transfer(i2c_bus, dat, 2);

//将整数形式温度转换成浮点数
int16_t temperature = (buf[0] << 8) | buf[1];
float real_value = (temperature >> 4) * 0.0625f;

return real_value;
}

2. 触摸板

#define BOARD_TSI_ELECTRODE_1 3U
/* Available PAD names on board */
#define PAD_TSI_ELECTRODE_1_NAME "E1"

/* Define the delta value to indicate a touch event */
#define TOUCH_DELTA_VALUE 50U

/* Array of TSI peripheral base address. */
tsi_calibration_data_t tsi_cali_data;

/**
@brief 对触摸板进行初始化
**/
void touch_init()
{
tsi_selfCap_config_t tsiConfig_selfCap;

// 0. Initialize Phase

//获取自电容默认配置
TSI_GetSelfCapModeDefaultConfig(&tsiConfig_selfCap);

//以自电容模式初始化TSI
TSI_InitSelfCapMode(TSI0, &tsiConfig_selfCap);

//使能抗噪功能
TSI_EnableNoiseCancellation(TSI0, true);

//开启TSI模块
TSI_EnableModule(TSI0, true); /* Enable module */
rt_kprintf("TSI_V6 Self-Cap mode Example Start\r\n");

// 1. 校准阶段,获取触摸板自身数据
memset((void *)&tsi_cali_data, 0, sizeof(tsi_cali_data));
TSI_SelfCapCalibrate(TSI0, &tsi_cali_data);

//将校准数据打印出来
for (uint8_t i = 0U; i < FSL_FEATURE_TSI_CHANNEL_COUNT; i++)
{
rt_kprintf("Calibrated counters for channel %d is: %d.\r\n", i, tsi_cali_data.calibratedData[i]);
}


// 2. 配置软件触发,轮询方式读取
rt_kprintf("TSI in polling method setup\r\n");
//关闭硬件触发扫描
TSI_EnableHardwareTriggerScan(TSI0, false); /
//关闭中断
TSI_DisableInterrupts(TSI0, kTSI_EndOfScanInterruptEnable);
//清除扫描完成标志
TSI_ClearStatusFlags(TSI0, kTSI_EndOfScanFlag);
//选择自电容测量通道
TSI_SetSelfCapMeasuredChannel(TSI0, BOARD_TSI_ELECTRODE_1);
//开始软件触发
TSI_StartSoftwareTrigger(TSI0);

//等待转换完成
while (!(TSI_GetStatusFlags(TSI0) & kTSI_EndOfScanFlag))
{
}
rt_kprintf("Channel %d Normal mode counter is: %d \r\n", BOARD_TSI_ELECTRODE_1, TSI_GetCounter(TSI0));
//清除扫描完成标志
TSI_ClearStatusFlags(TSI0, kTSI_EndOfScanFlag | kTSI_OutOfRangeFlag);
}

/**
@brief 获取触摸板数据
**/
uint8_t touch_get()
{
uint16_t res = 0;
//软件触发
TSI_StartSoftwareTrigger(TSI0);

//等待扫描完成
while (!(TSI_GetStatusFlags(TSI0) & kTSI_EndOfScanFlag))
;

//读取扫描结果
res = TSI_GetCounter(TSI0);

//清除扫描完成标志
TSI_ClearStatusFlags(TSI0, kTSI_EndOfScanFlag | kTSI_OutOfRangeFlag);

//判断触摸板是否被按下
if (res > (uint16_t)(tsi_cali_data.calibratedData[BOARD_TSI_ELECTRODE_1] + TOUCH_DELTA_VALUE))
return 1;
else
return 0;
}

流程图

功能展示

RGB调光

获取温度和按键/触摸信息

连接并启动TCP服务端

心得体会

不出意外,此次又是早早开始准备,因为各种事情搁置,最后2天疯狂赶ddl的模式(逃

这次活动确实是一个契机,让我比较系统的了解RTOS,在思维上也实现了一个裸机阶段啥都要自己搓到组件框架下提高代码复用率,和线程思维的一个转变。在此也感谢群友们的答疑解惑。

初次浏览MCXN947的数据手册时,实在惊讶。这真的是这个时代的MCU吗?

查了一下芯片的价格,好吧,它属于下个时代。

也希望含有NPU的MCU可以早日百花齐放。

相关链接



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