打印
[CAPSENSE™]

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

[复制链接]
33|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 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的串口输出:



使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

61

主题

119

帖子

0

粉丝