sujingliang 发表于 2025-5-7 14:06

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

本帖最后由 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、安装依赖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 = 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;
      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;
    char postHeader;

    sendATCommand("AT+CIPCLOSE=20\r\n", "OK", 300);
    // 1. 建立TCP连接
    char tcpCmd;
    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={
                        "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={0};
      snprintf(jsonData, sizeof(jsonData),
                         "{\"sensor\":\"direction\",\"value\":\"%s\"}",
                         action);

      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的串口输出:



页: [1]
查看完整版本: 【英飞凌PSOC 4000T DIY】4、悬停触摸按键控制WebServer红色方块运动