以太网鼠标

本文介绍了如何使用PICO的Core0进行TCP通信,通过ADC读取操纵杆位置并将数据发送到PC服务器,同时Core1负责接收并显示数据,实现在LCD上通过操纵杆控制光标。项目还涉及Wi-Fi连接和ADC值的解析以实现鼠标功能。
使用 TCP 通信的鼠标光标控制

转发: Ethernet Mouse


项目介绍

在之前的项目中,我们使用PICO的两个核心实现了一个TCP客户端,Core0负责TCP通信,Core1负责实现2.2英寸LCD。

通过该项目,我们使用操纵杆实现了以太网鼠标。

典型的微调变阻器将电源和GND分别连接到引脚1和3,并且由于电压分布规律,引脚2上读取的电压值将根据微调器的位置而变化。

操纵杆可以利用它来表示带有变阻器的操纵杆在 X 轴和 Y 轴上的位置。

模拟操纵杆

它将读取相应的 ADC 值到 Rpi PICO 的 ADC0 和 ADC1 中,并通过 W5100S 将这些值传回 TCP 服务器(本项目中的 PC)。

#include <stdio.h>
#include "pico/multicore.h"
#include <string.h>
#include "hardware/adc.h"
#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "mode0/mode0.h"
#include "port_common.h"
#include "wizchip_conf.h"
#include "w5x00_spi.h"
#include "socket.h"
#include "ili9341.h"


#define SERVER_IP {192, 168, 11, 197} // 서버 IP 주소
#define SERVER_PORT 5000 // 서버 포트 번호
#define CLIENT_SOCKET 1 // 클라이언트 소켓 번호
#define PLL_SYS_KHZ (133 * 1000)
#define ETHERNET_BUF_MAX_SIZE (1024 * 2)
#define MAX_TEXT 100  // 최대 문자 수

static wiz_NetInfo g_net_info = {
    .mac = {0x00, 0x08, 0xDC, 0x12, 0x34, 0x57},
    .ip = {192, 168, 11, 3},
    .sn = {255, 255, 255, 0},
    .gw = {192, 168, 11, 1},
    .dns = {8, 8, 8, 8},
    .dhcp = NETINFO_STATIC
};

static void set_clock_khz(void) {
    set_sys_clock_khz(PLL_SYS_KHZ, true);
    clock_configure(
        clk_peri,
        0,
        CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
        PLL_SYS_KHZ * 1000,
        PLL_SYS_KHZ * 1000
    );
}


static uint8_t g_buf[1024] = {0,};

// core1에서 실행될 함수
static void display_received_data_core1() {
    ili9341_init(); // Initialize the mode0 library
    mode0_set_cursor(0, 0);
    mode0_set_foreground(MODE0_WHITE);  // Set default text color to white
    mode0_set_background(MODE0_BLACK);  // Set default background color to black

    while (true) {
        char* received_data = (char*)multicore_fifo_pop_blocking();

        // 받은 데이터를 LCD에 출력
        mode0_print(received_data);
        mode0_print("\n");
    }
}

static void send_message_to_core1(const char* message) {
    multicore_fifo_push_blocking((uint32_t)message);
}

void adc_init_pins() {
    adc_init();
    adc_gpio_init(26); // GP26을 ADC0으로 초기화
    adc_gpio_init(27); // GP27을 ADC1으로 초기화
}

// ADC 값을 읽고 서버로 전송하는 함수
void send_adc_values_to_server(uint8_t sn, uint8_t* buf) {
    adc_select_input(0); // ADC0 선택
    uint16_t adc_value0 = adc_read(); // ADC0 값 읽기

    adc_select_input(1); // ADC1 선택
    uint16_t adc_value1 = adc_read(); // ADC1 값 읽기

    // 값 포매팅 및 버퍼에 저장
    int len = snprintf((char*)buf, ETHERNET_BUF_MAX_SIZE, "ADC0: %u, ADC1: %u\n", adc_value0, adc_value1);

    // 서버로 데이터 전송
    send(sn, buf, len);
}

static void connect_to_server(uint8_t sn, uint8_t* buf, uint8_t* server_ip, uint16_t port) {
    int32_t ret;
    static bool isConnected = false;

    switch(getSn_SR(sn)) {
        case SOCK_INIT:
            if ((ret = connect(sn, server_ip, port)) != SOCK_OK) {
                if (!isConnected) {
                    send_message_to_core1("No connection\n");
                    isConnected = false;
                }
                return;
            }
            break;
        case SOCK_ESTABLISHED:
            if (!isConnected) {
                send_message_to_core1("Server Connected\n");
                isConnected = true;
            }

            while (isConnected) { // 서버 연결이 유지되는 동안 반복
                send_adc_values_to_server(sn, buf);

                // 서버로 데이터 전송
                if ((ret = send(sn, buf, strlen((char*)buf))) <= 0) {
                    close(sn);
                    isConnected = false;
                    break;
                }
                sleep_ms(5); // 데이터 전송 간격 (1초)
            }
            break;
        case SOCK_CLOSED:
            if (isConnected) {
                send_message_to_core1("Lost connection\n");
                isConnected = false;
            }
            if ((ret = socket(sn, Sn_MR_TCP, port, 0x00)) != sn) {
                return;
            }
            break;
    }
}

int main() {
    set_clock_khz();
    stdio_init_all();
	adc_init_pins();
    // Core1에서 display_received_data_core1 함수 실행
    multicore_launch_core1(display_received_data_core1); 
    send_message_to_core1("IP : 192.168.11.3\n");
    sleep_ms(10);
    memset(g_buf, 0, sizeof(g_buf)); // g_buf 초기화
    send_message_to_core1("PORT : 5000\n");
    sleep_ms(10);
    memset(g_buf, 0, sizeof(g_buf)); // g_buf 초기화

    wizchip_spi_initialize();
    wizchip_cris_initialize();
    wizchip_reset();
    wizchip_initialize();
    wizchip_check();

    network_initialize(g_net_info);


    
    uint8_t server_ip[] = SERVER_IP;

    while (1) {

        connect_to_server(CLIENT_SOCKET, g_buf, server_ip, SERVER_PORT);
		send_adc_values_to_server(CLIENT_SOCKET, g_buf);
    }
}

Core0负责TCP通信,使用connect_to_server函数和send_adc_values_to_server函数读取ADC值,格式化它们,并将其发送到TCP服务器。

它还调用send_message_to_core1函数将数据发送到Core1。 该函数内部使用 multicore_fifo_push_blocking 将消息发送到 Core1 的 FIFO 队列。(multicore_fifo_push_blocking 函数负责将数据发送到 Core1 的 FIFO 队列。当您在 Core0 上调用该函数时,它会将要发送的数据放入 Core1 的 FIFO 队列。如果有 队列中没有空间,Core0 会一直等待,直到有空间为止。此时,Core0 的工作会暂时停止,这会导致程序性能较差)。

Core1主要负责更新显示。 这是通过 display_received_data_core1 函数完成的。 Core1通过multicore_fifo_pop_blocking接收Core0发送的数据并显示在LCD上。

TCP服务器(PC)接收PICO和W5100S发送的数据,使用Python解析该数据,并根据解析的数据实现鼠标控制逻辑。

from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLineEdit, QPushButton, QLabel
from PyQt5.QtCore import pyqtSignal
import sys
import socket
import pyautogui
import threading

def move_mouse_relative(adc0, adc1, dead_zone_start, dead_zone_end, smooth_factor=0.05):
    screen_width, screen_height = pyautogui.size()

    if dead_zone_start <= adc0 <= dead_zone_end and dead_zone_start <= adc1 <= dead_zone_end:
        return

    scale_factor = 0.1

    if adc0 < dead_zone_start:
        y_move = (adc0 - dead_zone_start) * scale_factor
    elif adc0 > dead_zone_end:
        y_move = (adc0 - dead_zone_end) * scale_factor
    else:
        y_move = 0

    if adc1 > dead_zone_end:
        x_move = -(adc1 - dead_zone_end) * scale_factor
    elif adc1 < dead_zone_start:
        x_move = -(adc1 - dead_zone_start) * scale_factor
    else:
        x_move = 0

    pyautogui.moveRel(x_move, -y_move, duration=smooth_factor)

class NetmouseApp(QWidget):
    update_status_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.server_thread = None
        self.server_running = [False]
        self.initUI()

    def initUI(self):
        self.setWindowTitle('Netmouse Server Configuration')
        layout = QVBoxLayout()

        self.statusLabel = QLabel(self)
        layout.addWidget(self.statusLabel)

        self.ipInput = QLineEdit(self)
        self.ipInput.setPlaceholderText("Enter Server IP")
        self.ipInput.setText("192.168.11.197")  # 기본 IP 설정
        layout.addWidget(self.ipInput)

        self.portInput = QLineEdit(self)
        self.portInput.setPlaceholderText("Enter Server Port")
        self.portInput.setText("5000")  # 기본 포트 설정
        layout.addWidget(self.portInput)

        self.startButton = QPushButton("Start Server", self)
        self.startButton.clicked.connect(self.startServer)
        layout.addWidget(self.startButton)

        self.stopButton = QPushButton("Stop Server", self)
        self.stopButton.clicked.connect(self.stopServer)
        layout.addWidget(self.stopButton)

        self.exitButton = QPushButton("EXIT", self)
        self.exitButton.clicked.connect(self.close)
        layout.addWidget(self.exitButton)

        self.setLayout(layout)
        self.update_status_signal.connect(self.updateStatusLabel)

    def startServer(self):
        if self.server_thread and self.server_thread.is_alive():
            return

        ip = self.ipInput.text()
        port = int(self.portInput.text())
        self.server_running[0] = True
        self.server_thread = threading.Thread(target=self.run_netmouse_server, args=(ip, port))
        self.server_thread.start()

    def stopServer(self):
        if self.server_thread and self.server_thread.is_alive():
            self.server_running[0] = False
            self.server_thread.join()
            self.update_status_signal.emit("Server stopped")

    def updateStatusLabel(self, message):
        self.statusLabel.setText(message)

    def closeEvent(self, event):
        self.stopServer()
        event.accept()    

    def run_netmouse_server(self, ip, port):
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.bind((ip, port))
                s.listen()
                self.update_status_signal.emit("Waiting for connection...")

                conn, addr = s.accept()
                with conn:
                    self.update_status_signal.emit(f"Connected by {addr}")

                    buffer = ""
                    while self.server_running[0]:
                        data = conn.recv(1024)
                        if not data:
                            break

                        buffer += data.decode()

                        # 메시지 분리
                        while "\n" in buffer:
                            message, buffer = buffer.split("\n", 1)
                            # 버퍼의 나머지 부분은 버림
                            buffer = ""
                            try:
                                adc0_val, adc1_val = [int(value.split(":")[1].strip()) for value in message.split(",")]
                                move_mouse_relative(adc0_val, adc1_val, 2000, 2100)
                            except (IndexError, ValueError, AttributeError):
                                print("Invalid data format")
                
                    self.update_status_signal.emit("Client disconnected")

        except Exception as e:
            self.update_status_signal.emit(f"Server error: {e}")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = NetmouseApp()
    ex.show()
    sys.exit(app.exec_())

设置TCP服务器并等待连接:在run_netmouse_server函数中设置TCP服务器。 服务器等待客户端在指定的 IP 地址和端口上进行连接。

处理客户端连接:当客户端连接时,打印出所连接客户端的地址信息并开始接收数据。

接收数据并管理缓冲区:服务器从客户端接收数据并将其存储在缓冲区中。 该数据的格式应为“ADC0: xxxx, ADC1: xxxx\n”。

解析数据:接收到的数据根据换行符(\n)分成消息。 每个消息都有一个逗号分隔的值,用作 ADC0 和 ADC1 的值。

计算并执行鼠标移动:ADC0和ADC1的解析值传递给move_mouse_relative函数。 此函数相对于 ADC 值移动鼠标。 移动量通过scale_factor缩放,鼠标的实际移动是通过pyautogui.moveRel函数执行的。

错误处理:数据解析过程中可能发生的任何异常(格式无效、转换错误等)都会通过适当的错误消息进行处理。

根据应用,该项目可以用作控制电机或其他设备的项目的基础。

以后我会更新这个计划,目标是控制PC以外的其他设备。

谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值