本帖最后由 sujingliang 于 2025-5-7 19:12 编辑
感应按键和悬停触摸技术是PSOC 4000T系列套装的亮点[color=rgba(0, 0, 0, 0.9)]。英飞凌 PSOC 4000T 提供的感应按键和悬停触摸扩展板,以其卓越的性能和精致的设计,为按键控制带来了全新的体验。为了实现更灵活的控制功能,我精心设计并制作了一款 WIFI + BLE 模块连接扩展板。该扩展板能够将 MCU 开发板与感应按键及悬停触摸扩展板无缝连接,并借助串口无线模块实现信号的高效发送与接收。
在之前的文章《【英飞凌 PSOC 4000T DIY】3、液位监测之 MQTT》中,我已初步展示了该扩展板的应用潜力。本文将在此基础上进一步拓展,结合自制的扩展板和悬停触摸扩展板,实现一个有趣的交互功能:通过悬停触摸按键控制 WebServer 上的红色方块运动。这是交互设计的一次探索。
一、硬件连接&逻辑示意图
MCU端使用WIFI模块,通过http协议将按键指令发送Webserver,观察者可以登录浏览器查看结果。
二、Web服务器搭建(node.js)
1、安装依赖npm install express body-parser
2、创建server.js
这里采用无刷新实时更新,使用 Server-Sent Events (SSE) 实现这个功能。
const express = require('express');
const app = express();
// 存储最新数据(内存中)
let latestData = { value: null, time: null };
// 提供静态页面(可选)
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html'); // 见下方的HTML文件
});
// 接收传感器数据的API(WiFi模块POST到此接口)
app.post('/data', express.json(), (req, res) => {
latestData = {
sensor: req.body.sensor || 'unknown',
value: req.body.value|| 0,
time: new Date().toLocaleTimeString()
};
res.send('OK');
});
// SSE 路由 - 向浏览器推送数据
app.get('/sse', (req, res) => {
// 设置SSE所需的响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 首次连接立即发送当前数据
sendSSEData(res);
// 定时推送数据(示例:每2秒一次)
const intervalId = setInterval(() => {
sendSSEData(res);
}, 2000);
// 客户端断开连接时清理定时器
req.on('close', () => {
clearInterval(intervalId);
console.log('Client disconnected');
});
});
// 发送SSE数据(格式必须符合SSE协议)
function sendSSEData(res) {
const data = {
sensor: latestData.sensor || 'N/A',
value: latestData.value || 'N/A',
time: latestData.time || 'N/A'
};
// SSE数据格式要求:
// - "data:" 开头 + 实际数据 + 两个换行符
res.write(`data: ${JSON.stringify(data)}\n\n`);
}
// 启动服务器
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
3、创建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSE Example with Grid</title>
<style>
body { font-family: Arial; text-align: center; margin-top: 50px; }
#data { font-size: 24px; margin: 20px; padding: 20px; border: 0px solid #ddd; }
body {
margin: 0;
overflow: hidden;
}
#grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none; /* 防止网格干扰鼠标事件 */
}
.grid-line {
stroke: #ccc;
stroke-width: 1;
}
#square {
width: 50px;
height: 50px;
background-color: red;
position: absolute;
top: 0; /* 初始位置调整为网格起点 */
left: 0; /* 初始位置调整为网格起点 */
}
</style>
</head>
<body>
<svg id="grid"></svg>
<div id="square"></div>
<h1>利用PSOC™ 4000T Multi-Sense Kit隔空按键产生指令<br><small>发送给WebServer-控制红色方块运行</small></h1>
<div id="data">等待数据...</div>
<script>
const square = document.getElementById('square');
const step = 50; // 网格大小和方块大小一致
const gridSize = 50; // 网格大小
// 创建网格
const grid = document.getElementById('grid');
const width = window.innerWidth;
const height = window.innerHeight;
for (let x = 0; x <= width; x += gridSize) {
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute('x1', x);
line.setAttribute('y1', 0);
line.setAttribute('x2', x);
line.setAttribute('y2', height);
line.classList.add('grid-line');
grid.appendChild(line);
}
for (let y = 0; y <= height; y += gridSize) {
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
line.setAttribute('x1', 0);
line.setAttribute('y1', y);
line.setAttribute('x2', width);
line.setAttribute('y2', y);
line.classList.add('grid-line');
grid.appendChild(line);
}
var lasttime;
const eventSource = new EventSource('/sse');
eventSource.onmessage = function(event) {
const instruction = event.data;
const data = JSON.parse(event.data);
document.getElementById('data').innerHTML = `
<p>传感器: ${data.sensor || '无'}</p>
<p>数值: ${data.value || '0'}</p>
<p>更新时间: ${data.time || '未知'}</p>
`;
if(lasttime!=data.time)
{
let currentTop = parseInt(square.offsetTop, 10);
let currentLeft = parseInt(square.offsetLeft, 10);
switch (data.value) {
case 'up':
currentTop = Math.max(0, currentTop - step);
break;
case 'down':
currentTop = Math.min(height - square.offsetHeight, currentTop + step);
break;
case 'left':
currentLeft = Math.max(0, currentLeft - step);
break;
case 'right':
currentLeft = Math.min(width - square.offsetWidth, currentLeft + step);
break;
default:
break;
}
// 更新方块位置
square.style.top = `${currentTop}px`;
square.style.left = `${currentLeft}px`;
lasttime=data.time;
}
};
</script>
</body>
</html>
4、运行server.js
C:\Infineon\webserver>node server.js
Server running at http://localhost:3000
此时websever已搭建完成并运行。
打开浏览器访问:http://localhost:3000
可以看到红色方形已绘制,等待接收http协议的数据进行执行并改变位置
三、MCU软件部分
基于例程MSCLP_CAPSENSE_Hover_Touch修改
1、初始化串口并使能中断
/* Configure and enable the UART peripheral */
Cy_SCB_UART_Init(scb_0_HW, &scb_0_config, &CYBSP_UART_context);
Cy_SCB_UART_Enable(scb_0_HW);
/* 配置 UART 中断 */
Cy_SysInt_Init(&UART_IRQ_cfg, UART_InterruptHandler);
NVIC_EnableIRQ(UART_IRQ_cfg.intrSrc); // 使能 NVIC 中断
// Register the event callback function
//Cy_SCB_UART_RegisterCallback(scb_0_HW,(cy_cb_scb_uart_handle_events_t)UART_InterruptHandler, &CYBSP_UART_context);
// 当 RX FIFO 非空时启用 UART RX 中断
Cy_SCB_SetRxInterruptMask(scb_0_HW , CY_SCB_RX_INTR_NOT_EMPTY);
// 当 TX FIFO 未满时启用 UART TX 中断
Cy_SCB_SetTxInterruptMask (scb_0_HW ,CY_SCB_TX_INTR_LEVEL);
/* Enable global interrupts */
__enable_irq();
RX中断处理:
static void UART_InterruptHandler(void)
{
/* 检查是否 RX FIFO 非空 */
if (Cy_SCB_GetRxInterruptStatusMasked(scb_0_HW) & CY_SCB_UART_RX_NOT_EMPTY)
{
/* 读取接收到的数据 */
uint8_t rxData = Cy_SCB_UART_Get(scb_0_HW);
/* 存入缓冲区(可改为环形缓冲区) */
if (rxCount < RX_BUFFER_SIZE)
{
rxBuffer[rxCount++] = rxData;
}
/* 可选:检查结束符(如 '\n') */
if (rxData == '\n'&&rxCount>2)
{
// 可以设置标志位通知主循环
atResponseReady=true;
}
}
/* 清除中断标志 */
Cy_SCB_ClearRxInterrupt(scb_0_HW, CY_SCB_UART_RX_NOT_EMPTY);
}
2、发送AT指令函数
bool sendATCommand(const char* cmd, const char* expectedResponse, uint32_t timeout) {
Cy_SCB_UART_PutString(scb_0_HW, cmd);
Cy_SysLib_Delay(timeout);
return true;
}
3、连接wifi,关闭所有tcp连接
void wifiConnect(void)
{
sendATCommand("AT+RST\r\n", "OK", AT_TIMEOUT_MS);
sendATCommand("AT+CWJAP=ssid,passwordpassword\r\n", "OK", 5000);
//closeAllConnections();
sendATCommand("AT+CIPCLOSE=20\r\n", "OK", AT_TIMEOUT_MS);
}
4、建立TCP连接并保存linkid
bool tcpLink(const char* host)
{
char tcpCmd[128];
snprintf(tcpCmd, sizeof(tcpCmd), "AT+CIPSTART=TCP,%s,3000\r\n", host);
Cy_SCB_UART_PutString(scb_0_HW, tcpCmd);
while(!atResponseReady){}
sscanf(rxBuffer, "+CIPSTART:%d", &linkId);
atResponseReady=false;
rxCount=0;
memset(rxBuffer,0,RX_BUFFER_SIZE);
for(int i=0;i<linkId;i++)
{
Cy_GPIO_Set(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
Cy_SysLib_Delay(200);
Cy_GPIO_Clr(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
Cy_SysLib_Delay(200);
}
return true;
}
5、发送HTTP POST请求
// 发送HTTP POST请求
void sendHttpPost(const char* url, const char* host, const char* jsonData) {
char cipsendCmd[32];
char postHeader[256];
sendATCommand("AT+CIPCLOSE=20\r\n", "OK", 300);
// 1. 建立TCP连接
char tcpCmd[128];
tcpLink(host);
// 2. 透传,发送POST数据
int dataLen = strlen(jsonData);
snprintf(postHeader, sizeof(postHeader),
"POST %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Content-Type: application/json\r\n"
"Content-Length: %d\r\n\r\n"
"%s\r\n", url, host, dataLen, jsonData);
snprintf(cipsendCmd, sizeof(cipsendCmd), "AT+CIPSEND=%d,%d\r\n", linkId,strlen(postHeader));
sendATCommand(cipsendCmd, ">", 300);
sendATCommand(postHeader, "OK", 1000);
// 3. 关闭连接
snprintf(cipsendCmd, sizeof(cipsendCmd), "AT+CIPCLOSE=%d\r\n", linkId);
sendATCommand(cipsendCmd, "OK", 1000);
}
通过POST向服务器发送json数据
格式{"sensor":"direction","value":"up"}
void sendHttpData(uint8_t dat)
{
char* action[4]={
"up","down","left","right"
};
const char* url = "/data";
//const char* host = "192.168.31.45";
const char* host = "192.168.8.11";
//const char* jsonData = "{\"sensor\":\"temp\",\"value\":25}";
char jsonData[50]={0};
snprintf(jsonData, sizeof(jsonData),
"{\"sensor\":\"direction\",\"value\":\"%s\"}",
action[dat]);
sendHttpPost(url, host, jsonData);
}
6、悬停触摸处理
按下触摸执行点灯,并调用sendHttpData发送POST给webserver
void led_control(){
if(Cy_CapSense_IsWidgetActive(CY_CAPSENSE_BUTTON1_WDGT_ID, &cy_capsense_context)){
Cy_GPIO_Set(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
Cy_GPIO_Clr(CYBSP_CS_HT_LED2_PORT, CYBSP_CS_HT_LED2_PIN);
Cy_GPIO_Clr(CYBSP_CS_HT_LED3_PORT, CYBSP_CS_HT_LED3_PIN);
Cy_GPIO_Clr(CYBSP_CS_HT_LED4_PORT, CYBSP_CS_HT_LED4_PIN);
sendHttpData(0);
}
else if(Cy_CapSense_IsWidgetActive(CY_CAPSENSE_BUTTON2_WDGT_ID, &cy_capsense_context)){
Cy_GPIO_Clr(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
Cy_GPIO_Set(CYBSP_CS_HT_LED2_PORT, CYBSP_CS_HT_LED2_PIN);
Cy_GPIO_Clr(CYBSP_CS_HT_LED3_PORT, CYBSP_CS_HT_LED3_PIN);
Cy_GPIO_Clr(CYBSP_CS_HT_LED4_PORT, CYBSP_CS_HT_LED4_PIN);
sendHttpData(1);
}
else if(Cy_CapSense_IsWidgetActive(CY_CAPSENSE_BUTTON3_WDGT_ID, &cy_capsense_context)){
Cy_GPIO_Clr(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
Cy_GPIO_Clr(CYBSP_CS_HT_LED2_PORT, CYBSP_CS_HT_LED2_PIN);
Cy_GPIO_Set(CYBSP_CS_HT_LED3_PORT, CYBSP_CS_HT_LED3_PIN);
Cy_GPIO_Clr(CYBSP_CS_HT_LED4_PORT, CYBSP_CS_HT_LED4_PIN);
sendHttpData(2);
}
else if(Cy_CapSense_IsWidgetActive(CY_CAPSENSE_BUTTON4_WDGT_ID, &cy_capsense_context)){
Cy_GPIO_Clr(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
Cy_GPIO_Clr(CYBSP_CS_HT_LED2_PORT, CYBSP_CS_HT_LED2_PIN);
Cy_GPIO_Clr(CYBSP_CS_HT_LED3_PORT, CYBSP_CS_HT_LED3_PIN);
Cy_GPIO_Set(CYBSP_CS_HT_LED4_PORT, CYBSP_CS_HT_LED4_PIN);
sendHttpData(3);
}
else {
Cy_GPIO_Clr(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
Cy_GPIO_Clr(CYBSP_CS_HT_LED2_PORT, CYBSP_CS_HT_LED2_PIN);
Cy_GPIO_Clr(CYBSP_CS_HT_LED3_PORT, CYBSP_CS_HT_LED3_PIN);
Cy_GPIO_Clr(CYBSP_CS_HT_LED4_PORT, CYBSP_CS_HT_LED4_PIN);
}
}
四、运行效果
发送AT的串口输出:
|
|