[CAPSENSE™] 【英飞凌PSOC 4000T DIY】4、悬停触摸按键控制WebServer红色方块运动

[复制链接]
 楼主| sujingliang 发表于 2025-5-7 14:06 | 显示全部楼层 |阅读模式
<
本帖最后由 sujingliang 于 2025-5-8 16:24 编辑

感应按键和悬停触摸技术是PSOC 4000T系列套装的亮点。英飞凌 PSOC 4000T 提供的感应按键和悬停触摸扩展板,以其卓越的性能和精致的设计,为按键控制带来了全新的体验。为了实现更灵活的控制功能,我设计并制作了一个 WIFI + BLE 模块连接扩展板。该扩展板能够将 MCU 开发板与感应按键及悬停触摸扩展板连接,并借助串口无线模块实现信号的发送与接收。
在之前的文章《【英飞凌 PSOC 4000T DIY】3、液位监测之 MQTT》中,我已初步展示了该扩展板的应用潜力。本文将在此基础上进一步拓展,结合自制的扩展板和悬停触摸扩展板,实现一个有趣的交互功能:通过悬停触摸按键控制 WebServer 上的红色方块运动。这是交互设计的一次探索。




一、硬件连接&逻辑示意图




MCU端使用WIFI模块,通过http协议将按键指令发送Webserver,观察者可以登录浏览器查看结果。

二、Web服务器搭建(node.js)
1、安装依赖
  1. npm install express body-parser

2、创建server.js

这里采用无刷新实时更新,使用 Server-Sent Events (SSE) 实现这个功能。
  1. const express = require('express');
  2. const app = express();

  3. // 存储最新数据(内存中)
  4. let latestData = { value: null, time: null };

  5. // 提供静态页面(可选)
  6. app.get('/', (req, res) => {
  7.   res.sendFile(__dirname + '/index.html'); // 见下方的HTML文件
  8. });

  9. // 接收传感器数据的API(WiFi模块POST到此接口)
  10. app.post('/data', express.json(), (req, res) => {
  11.   latestData = {
  12.          sensor: req.body.sensor || 'unknown',
  13.     value: req.body.value|| 0,
  14.     time: new Date().toLocaleTimeString()
  15.   };
  16.   res.send('OK');
  17. });

  18. // SSE 路由 - 向浏览器推送数据
  19. app.get('/sse', (req, res) => {
  20.   // 设置SSE所需的响应头
  21.   res.setHeader('Content-Type', 'text/event-stream');
  22.   res.setHeader('Cache-Control', 'no-cache');
  23.   res.setHeader('Connection', 'keep-alive');

  24.   // 首次连接立即发送当前数据
  25.   sendSSEData(res);

  26.   // 定时推送数据(示例:每2秒一次)
  27.   const intervalId = setInterval(() => {
  28.     sendSSEData(res);
  29.   }, 2000);

  30.   // 客户端断开连接时清理定时器
  31.   req.on('close', () => {
  32.     clearInterval(intervalId);
  33.     console.log('Client disconnected');
  34.   });
  35. });

  36. // 发送SSE数据(格式必须符合SSE协议)
  37. function sendSSEData(res) {
  38.   const data = {
  39.         sensor: latestData.sensor || 'N/A',
  40.     value: latestData.value || 'N/A',
  41.     time: latestData.time || 'N/A'
  42.   };
  43.   // SSE数据格式要求:
  44.   // - "data:" 开头 + 实际数据 + 两个换行符
  45.   res.write(`data: ${JSON.stringify(data)}\n\n`);
  46. }

  47. // 启动服务器
  48. const PORT = 3000;
  49. app.listen(PORT, () => {
  50.   console.log(`Server running at http://localhost:${PORT}`);
  51. });
3、创建index.html
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>SSE Example with Grid</title>
  7.     <style>
  8.           body { font-family: Arial; text-align: center; margin-top: 50px; }
  9.           #data { font-size: 24px; margin: 20px; padding: 20px; border: 0px solid #ddd; }
  10.         
  11.         body {
  12.             margin: 0;
  13.             overflow: hidden;
  14.         }
  15.         #grid {
  16.             position: absolute;
  17.             top: 0;
  18.             left: 0;
  19.             width: 100%;
  20.             height: 100%;
  21.             pointer-events: none; /* 防止网格干扰鼠标事件 */
  22.         }
  23.         .grid-line {
  24.             stroke: #ccc;
  25.             stroke-width: 1;
  26.         }
  27.         #square {
  28.             width: 50px;
  29.             height: 50px;
  30.             background-color: red;
  31.             position: absolute;
  32.             top: 0; /* 初始位置调整为网格起点 */
  33.             left: 0; /* 初始位置调整为网格起点 */
  34.         }
  35.     </style>
  36. </head>
  37. <body>
  38.     <svg id="grid"></svg>
  39.     <div id="square"></div>
  40.         
  41.         <h1>利用PSOC™ 4000T Multi-Sense Kit隔空按键产生指令<br><small>发送给WebServer-控制红色方块运行</small></h1>
  42.         <div id="data">等待数据...</div>
  43.   
  44.     <script>
  45.         const square = document.getElementById('square');
  46.         const step = 50; // 网格大小和方块大小一致
  47.         const gridSize = 50; // 网格大小

  48.         // 创建网格
  49.         const grid = document.getElementById('grid');
  50.         const width = window.innerWidth;
  51.         const height = window.innerHeight;

  52.         for (let x = 0; x <= width; x += gridSize) {
  53.             const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
  54.             line.setAttribute('x1', x);
  55.             line.setAttribute('y1', 0);
  56.             line.setAttribute('x2', x);
  57.             line.setAttribute('y2', height);
  58.             line.classList.add('grid-line');
  59.             grid.appendChild(line);
  60.         }

  61.         for (let y = 0; y <= height; y += gridSize) {
  62.             const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
  63.             line.setAttribute('x1', 0);
  64.             line.setAttribute('y1', y);
  65.             line.setAttribute('x2', width);
  66.             line.setAttribute('y2', y);
  67.             line.classList.add('grid-line');
  68.             grid.appendChild(line);
  69.         }
  70.                 var lasttime;
  71.         const eventSource = new EventSource('/sse');
  72.         eventSource.onmessage = function(event) {
  73.             const instruction = event.data;
  74.                         
  75.                         
  76.                         
  77.                          const data = JSON.parse(event.data);
  78.                         
  79.                          document.getElementById('data').innerHTML = `
  80.                                 <p>传感器: ${data.sensor || '无'}</p>
  81.                                 <p>数值: ${data.value || '0'}</p>
  82.                                 <p>更新时间: ${data.time || '未知'}</p>
  83.                           `;
  84.                         
  85.                         if(lasttime!=data.time)
  86.                         {
  87.                         
  88.             let currentTop = parseInt(square.offsetTop, 10);
  89.             let currentLeft = parseInt(square.offsetLeft, 10);

  90.             switch (data.value) {
  91.                 case 'up':
  92.                     currentTop = Math.max(0, currentTop - step);
  93.                     break;
  94.                 case 'down':
  95.                     currentTop = Math.min(height - square.offsetHeight, currentTop + step);
  96.                     break;
  97.                 case 'left':
  98.                     currentLeft = Math.max(0, currentLeft - step);
  99.                     break;
  100.                 case 'right':
  101.                     currentLeft = Math.min(width - square.offsetWidth, currentLeft + step);
  102.                     break;
  103.                                 default:
  104.                                        
  105.                                         break;
  106.             }

  107.                         
  108.             // 更新方块位置
  109.             square.style.top = `${currentTop}px`;
  110.             square.style.left = `${currentLeft}px`;
  111.                         lasttime=data.time;
  112.                         }
  113.         };
  114.     </script>
  115. </body>
  116. </html>

4、运行server.js
  1. C:\Infineon\webserver>node server.js
  2. Server running at http://localhost:3000
此时websever已搭建完成并运行。
打开浏览器访问:http://localhost:3000

可以看到红色方形已绘制,等待接收http协议的数据进行执行并改变位置
三、MCU软件部分
基于例程MSCLP_CAPSENSE_Hover_Touch修改

1、初始化串口并使能中断
  1.    /* Configure and enable the UART peripheral */
  2.     Cy_SCB_UART_Init(scb_0_HW, &scb_0_config, &CYBSP_UART_context);
  3.     Cy_SCB_UART_Enable(scb_0_HW);

  4.     /* 配置 UART 中断 */
  5.         Cy_SysInt_Init(&UART_IRQ_cfg, UART_InterruptHandler);
  6.         NVIC_EnableIRQ(UART_IRQ_cfg.intrSrc);  // 使能 NVIC 中断

  7.         // Register the event callback function
  8.         //Cy_SCB_UART_RegisterCallback(scb_0_HW,(cy_cb_scb_uart_handle_events_t)UART_InterruptHandler, &CYBSP_UART_context);

  9.         // 当 RX FIFO 非空时启用 UART RX 中断
  10.         Cy_SCB_SetRxInterruptMask(scb_0_HW , CY_SCB_RX_INTR_NOT_EMPTY);


  11.         // 当 TX FIFO 未满时启用 UART TX 中断
  12.         Cy_SCB_SetTxInterruptMask (scb_0_HW ,CY_SCB_TX_INTR_LEVEL);

  13.     /* Enable global interrupts */
  14.     __enable_irq();
RX中断处理:
  1. static void UART_InterruptHandler(void)
  2. {
  3.     /* 检查是否 RX FIFO 非空 */
  4.     if (Cy_SCB_GetRxInterruptStatusMasked(scb_0_HW) & CY_SCB_UART_RX_NOT_EMPTY)
  5.     {
  6.         /* 读取接收到的数据 */
  7.         uint8_t rxData = Cy_SCB_UART_Get(scb_0_HW);

  8.         /* 存入缓冲区(可改为环形缓冲区) */
  9.         if (rxCount < RX_BUFFER_SIZE)
  10.         {
  11.             rxBuffer[rxCount++] = rxData;
  12.         }

  13.         /* 可选:检查结束符(如 '\n') */
  14.         if (rxData == '\n'&&rxCount>2)
  15.         {
  16.             // 可以设置标志位通知主循环

  17.                 atResponseReady=true;
  18.         }

  19.     }

  20.     /* 清除中断标志 */
  21.     Cy_SCB_ClearRxInterrupt(scb_0_HW, CY_SCB_UART_RX_NOT_EMPTY);
  22. }



2、发送AT指令函数
  1. bool sendATCommand(const char* cmd, const char* expectedResponse, uint32_t timeout) {
  2.     Cy_SCB_UART_PutString(scb_0_HW, cmd);
  3.     Cy_SysLib_Delay(timeout);
  4.         return true;
  5. }
3、连接wifi,关闭所有tcp连接
  1. void wifiConnect(void)
  2. {
  3.         sendATCommand("AT+RST\r\n", "OK", AT_TIMEOUT_MS);
  4.         sendATCommand("AT+CWJAP=ssid,passwordpassword\r\n", "OK", 5000);


  5.         //closeAllConnections();
  6.         sendATCommand("AT+CIPCLOSE=20\r\n", "OK", AT_TIMEOUT_MS);
  7. }
4、建立TCP连接并保存linkid
  1. bool tcpLink(const char* host)
  2. {
  3.         char tcpCmd[128];
  4.         snprintf(tcpCmd, sizeof(tcpCmd), "AT+CIPSTART=TCP,%s,3000\r\n", host);

  5.         Cy_SCB_UART_PutString(scb_0_HW, tcpCmd);
  6.         while(!atResponseReady){}

  7.         sscanf(rxBuffer, "+CIPSTART:%d", &linkId);
  8.         atResponseReady=false;
  9.         rxCount=0;
  10.         memset(rxBuffer,0,RX_BUFFER_SIZE);

  11.         for(int i=0;i<linkId;i++)
  12.         {
  13.                 Cy_GPIO_Set(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
  14.                 Cy_SysLib_Delay(200);
  15.                 Cy_GPIO_Clr(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
  16.                 Cy_SysLib_Delay(200);

  17.         }
  18.         return true;
  19. }

5、发送HTTP POST请求
  1. // 发送HTTP POST请求
  2. void sendHttpPost(const char* url, const char* host, const char* jsonData) {
  3.     char cipsendCmd[32];
  4.     char postHeader[256];

  5.     sendATCommand("AT+CIPCLOSE=20\r\n", "OK", 300);
  6.     // 1. 建立TCP连接
  7.     char tcpCmd[128];
  8.     tcpLink(host);

  9.     // 2. 透传,发送POST数据
  10.     int dataLen = strlen(jsonData);
  11.     snprintf(postHeader, sizeof(postHeader),
  12.         "POST %s HTTP/1.1\r\n"
  13.         "Host: %s\r\n"
  14.         "Content-Type: application/json\r\n"
  15.         "Content-Length: %d\r\n\r\n"
  16.         "%s\r\n", url, host, dataLen, jsonData);
  17.     snprintf(cipsendCmd, sizeof(cipsendCmd), "AT+CIPSEND=%d,%d\r\n", linkId,strlen(postHeader));
  18.     sendATCommand(cipsendCmd, ">", 300);
  19.     sendATCommand(postHeader, "OK", 1000);

  20.     // 3. 关闭连接
  21.     snprintf(cipsendCmd, sizeof(cipsendCmd), "AT+CIPCLOSE=%d\r\n", linkId);
  22.     sendATCommand(cipsendCmd, "OK", 1000);
  23. }
通过POST向服务器发送json数据
格式{"sensor":"direction","value":"up"}
  1. void sendHttpData(uint8_t dat)
  2. {
  3.         char* action[4]={
  4.                         "up","down","left","right"
  5.         };
  6.         const char* url = "/data";
  7.         //const char* host = "192.168.31.45";
  8.         const char* host = "192.168.8.11";
  9.         //const char* jsonData = "{"sensor":"temp","value":25}";
  10.         char jsonData[50]={0};
  11.         snprintf(jsonData, sizeof(jsonData),
  12.                          "{"sensor":"direction","value":"%s"}",
  13.                          action[dat]);

  14.         sendHttpPost(url, host, jsonData);
  15. }


6、悬停触摸处理

按下触摸执行点灯,并调用sendHttpData发送POST给webserver

  1. void led_control(){

  2.     if(Cy_CapSense_IsWidgetActive(CY_CAPSENSE_BUTTON1_WDGT_ID, &cy_capsense_context)){
  3.         Cy_GPIO_Set(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
  4.         Cy_GPIO_Clr(CYBSP_CS_HT_LED2_PORT, CYBSP_CS_HT_LED2_PIN);
  5.         Cy_GPIO_Clr(CYBSP_CS_HT_LED3_PORT, CYBSP_CS_HT_LED3_PIN);
  6.         Cy_GPIO_Clr(CYBSP_CS_HT_LED4_PORT, CYBSP_CS_HT_LED4_PIN);
  7.         sendHttpData(0);
  8.     }
  9.     else if(Cy_CapSense_IsWidgetActive(CY_CAPSENSE_BUTTON2_WDGT_ID, &cy_capsense_context)){
  10.         Cy_GPIO_Clr(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
  11.         Cy_GPIO_Set(CYBSP_CS_HT_LED2_PORT, CYBSP_CS_HT_LED2_PIN);
  12.         Cy_GPIO_Clr(CYBSP_CS_HT_LED3_PORT, CYBSP_CS_HT_LED3_PIN);
  13.         Cy_GPIO_Clr(CYBSP_CS_HT_LED4_PORT, CYBSP_CS_HT_LED4_PIN);
  14.         sendHttpData(1);
  15.     }
  16.     else if(Cy_CapSense_IsWidgetActive(CY_CAPSENSE_BUTTON3_WDGT_ID, &cy_capsense_context)){
  17.         Cy_GPIO_Clr(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
  18.         Cy_GPIO_Clr(CYBSP_CS_HT_LED2_PORT, CYBSP_CS_HT_LED2_PIN);
  19.         Cy_GPIO_Set(CYBSP_CS_HT_LED3_PORT, CYBSP_CS_HT_LED3_PIN);
  20.         Cy_GPIO_Clr(CYBSP_CS_HT_LED4_PORT, CYBSP_CS_HT_LED4_PIN);
  21.         sendHttpData(2);
  22.     }
  23.     else if(Cy_CapSense_IsWidgetActive(CY_CAPSENSE_BUTTON4_WDGT_ID, &cy_capsense_context)){
  24.         Cy_GPIO_Clr(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
  25.         Cy_GPIO_Clr(CYBSP_CS_HT_LED2_PORT, CYBSP_CS_HT_LED2_PIN);
  26.         Cy_GPIO_Clr(CYBSP_CS_HT_LED3_PORT, CYBSP_CS_HT_LED3_PIN);
  27.         Cy_GPIO_Set(CYBSP_CS_HT_LED4_PORT, CYBSP_CS_HT_LED4_PIN);
  28.         sendHttpData(3);
  29.     }
  30.     else {
  31.         Cy_GPIO_Clr(CYBSP_CS_HT_LED1_PORT, CYBSP_CS_HT_LED1_PIN);
  32.         Cy_GPIO_Clr(CYBSP_CS_HT_LED2_PORT, CYBSP_CS_HT_LED2_PIN);
  33.         Cy_GPIO_Clr(CYBSP_CS_HT_LED3_PORT, CYBSP_CS_HT_LED3_PIN);
  34.         Cy_GPIO_Clr(CYBSP_CS_HT_LED4_PORT, CYBSP_CS_HT_LED4_PIN);
  35.     }
  36. }



四、运行效果



发送AT的串口输出:



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
星辰大海不退缩 发表于 2025-5-26 17:38 | 显示全部楼层
非常不错的控制教学
小夏天的大西瓜 发表于 2025-5-27 10:03 | 显示全部楼层
感应按键和悬停触摸技术是PSOC 4000T系列套装的亮点
封存into 发表于 2025-5-29 13:48 | 显示全部楼层
悬停触摸是非接触么?》
喂什么玩意 发表于 2025-6-29 00:56 | 显示全部楼层
可将电极划分为 “上、下、左、右” 四个方向区,手指悬停或滑动时,电极电容变化被实时捕捉。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

84

主题

146

帖子

3

粉丝
快速回复 在线客服 返回列表 返回顶部