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

被折叠的 条评论
为什么被折叠?



