Python으로 작성된 TCP 클라이언트 프로그램을 사용하여 Raspberry Pi Pico W에 연결된 LCD에 문자열을 출력하는 방법을 다룹니다.
2023. 2. 12 최초작성
1. 다음 포스트처럼 진행하여 LCD에 문자열이 출력되는지 확인하고 I2C LCD 연결을 유지합니다.
Raspberry Pi Pico C 프로그래밍 - I2C LCD 예제
https://webnautes.tistory.com/2094
2. 다음 포스트처럼 진행하여 문제 없이 동작하는 것을 확인합니다.
Raspberry Pi Pico W - TCP Server C 예제
https://webnautes.tistory.com/2095
3. 2번에서 사용한 프로젝트를 이제 수정하여 사용합니다.
먼저 CMakeLists.txt 파일에서 다음 2곳에 hardware_i2c을 추가합니다.
target_link_libraries(picow_tcpip_server_background
pico_cyw43_arch_lwip_threadsafe_background
pico_stdlib
hardware_i2c
)
target_link_libraries(picow_tcpip_server_poll
pico_cyw43_arch_lwip_poll
pico_stdlib
hardware_i2c
)
그다음 picow_tcp_server.c 코드를 다음 내용으로 대체합니다.
/** * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. * * SPDX-License-Identifier: BSD-3-Clause */ #include <stdio.h> #include "pico/stdlib.h" #include <string.h> #include <stdlib.h> #include "pico/stdlib.h" #include "pico/cyw43_arch.h" #include "lwip/pbuf.h" #include "lwip/tcp.h" #include "hardware/i2c.h" #include "pico/binary_info.h" /* Example code to drive a 16x2 LCD panel via a I2C bridge chip (e.g. PCF8574) NOTE: The panel must be capable of being driven at 3.3v NOT 5v. The Pico GPIO (and therefor I2C) cannot be used at 5v. You will need to use a level shifter on the I2C lines if you want to run the board at 5v. Connections on Raspberry Pi Pico board, other boards may vary. GPIO 4 (pin 6)-> SDA on LCD bridge board GPIO 5 (pin 7)-> SCL on LCD bridge board 3.3v (pin 36) -> VCC on LCD bridge board GND (pin 38) -> GND on LCD bridge board */ // commands const int LCD_CLEARDISPLAY = 0x01; const int LCD_RETURNHOME = 0x02; const int LCD_ENTRYMODESET = 0x04; const int LCD_DISPLAYCONTROL = 0x08; const int LCD_CURSORSHIFT = 0x10; const int LCD_FUNCTIONSET = 0x20; const int LCD_SETCGRAMADDR = 0x40; const int LCD_SETDDRAMADDR = 0x80; // flags for display entry mode const int LCD_ENTRYSHIFTINCREMENT = 0x01; const int LCD_ENTRYLEFT = 0x02; // flags for display and cursor control const int LCD_BLINKON = 0x01; const int LCD_CURSORON = 0x02; const int LCD_DISPLAYON = 0x04; // flags for display and cursor shift const int LCD_MOVERIGHT = 0x04; const int LCD_DISPLAYMOVE = 0x08; // flags for function set const int LCD_5x10DOTS = 0x04; const int LCD_2LINE = 0x08; const int LCD_8BITMODE = 0x10; // flag for backlight control const int LCD_BACKLIGHT = 0x08; const int LCD_ENABLE_BIT = 0x04; // By default these LCD display drivers are on bus address 0x27 static int addr = 0x27; // Modes for lcd_send_byte #define LCD_CHARACTER 1 #define LCD_COMMAND 0 #define MAX_LINES 2 #define MAX_CHARS 16 /* Quick helper function for single byte transfers */ void i2c_write_byte(uint8_t val) { #ifdef i2c_default i2c_write_blocking(i2c_default, addr, &val, 1, false); #endif } void lcd_toggle_enable(uint8_t val) { // Toggle enable pin on LCD display // We cannot do this too quickly or things don't work #define DELAY_US 600 sleep_us(DELAY_US); i2c_write_byte(val | LCD_ENABLE_BIT); sleep_us(DELAY_US); i2c_write_byte(val & ~LCD_ENABLE_BIT); sleep_us(DELAY_US); } // The display is sent a byte as two separate nibble transfers void lcd_send_byte(uint8_t val, int mode) { uint8_t high = mode | (val & 0xF0) | LCD_BACKLIGHT; uint8_t low = mode | ((val << 4) & 0xF0) | LCD_BACKLIGHT; i2c_write_byte(high); lcd_toggle_enable(high); i2c_write_byte(low); lcd_toggle_enable(low); } void lcd_clear(void) { lcd_send_byte(LCD_CLEARDISPLAY, LCD_COMMAND); } // go to location on LCD void lcd_set_cursor(int line, int position) { int val = (line == 0) ? 0x80 + position : 0xC0 + position; lcd_send_byte(val, LCD_COMMAND); } static void inline lcd_char(char val) { lcd_send_byte(val, LCD_CHARACTER); } void lcd_string(const char *s) { while (*s) { lcd_char(*s++); } } void lcd_init() { lcd_send_byte(0x03, LCD_COMMAND); lcd_send_byte(0x03, LCD_COMMAND); lcd_send_byte(0x03, LCD_COMMAND); lcd_send_byte(0x02, LCD_COMMAND); lcd_send_byte(LCD_ENTRYMODESET | LCD_ENTRYLEFT, LCD_COMMAND); lcd_send_byte(LCD_FUNCTIONSET | LCD_2LINE, LCD_COMMAND); lcd_send_byte(LCD_DISPLAYCONTROL | LCD_DISPLAYON, LCD_COMMAND); lcd_clear(); } #define TCP_PORT 4242 #define BUF_SIZE 2048 #define TEST_ITERATIONS 10 #define POLL_TIME_S 5 typedef struct TCP_SERVER_T_ { struct tcp_pcb *server_pcb; struct tcp_pcb *client_pcb; bool complete; uint8_t buffer_sent[BUF_SIZE]; uint8_t buffer_recv[BUF_SIZE]; int sent_len; int recv_len; int run_count; } TCP_SERVER_T; static TCP_SERVER_T* tcp_server_init(void) { TCP_SERVER_T *state = calloc(1, sizeof(TCP_SERVER_T)); if (!state) { printf("failed to allocate state\n"); return NULL; } return state; } static err_t tcp_server_close(void *arg) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; err_t err = ERR_OK; if (state->client_pcb != NULL) { tcp_arg(state->client_pcb, NULL); tcp_poll(state->client_pcb, NULL, 0); tcp_sent(state->client_pcb, NULL); tcp_recv(state->client_pcb, NULL); tcp_err(state->client_pcb, NULL); err = tcp_close(state->client_pcb); if (err != ERR_OK) { printf("close failed %d, calling abort\n", err); tcp_abort(state->client_pcb); err = ERR_ABRT; } state->client_pcb = NULL; } if (state->server_pcb) { tcp_arg(state->server_pcb, NULL); tcp_close(state->server_pcb); state->server_pcb = NULL; } return err; } static err_t tcp_server_result(void *arg, int status) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; if (status == 0) { printf("test success\n"); } else { printf("test failed %d\n", status); } state->complete = true; return tcp_server_close(arg); } static err_t tcp_server_sent(void *arg, struct tcp_pcb *tpcb, u16_t len) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; printf("tcp_server_sent %u\n", len); state->sent_len += len; if (state->sent_len >= BUF_SIZE) { // We should get the data back from the client state->recv_len = 0; printf("Waiting for buffer from client\n"); } return ERR_OK; } err_t tcp_server_send_data(void *arg, struct tcp_pcb *tpcb, char *recv_buf) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; strcpy(state->buffer_sent, recv_buf); lcd_clear(); lcd_set_cursor(0, 0); lcd_string(recv_buf); state->sent_len = strlen(recv_buf) + 1; // printf("Writing %ld bytes to client\n", BUF_SIZE); printf("Writing %ld bytes to client\n", strlen(state->buffer_sent)); // this method is callback from lwIP, so cyw43_arch_lwip_begin is not required, however you // can use this method to cause an assertion in debug mode, if this method is called when // cyw43_arch_lwip_begin IS needed cyw43_arch_lwip_check(); err_t err = tcp_write(tpcb, state->buffer_sent, strlen(recv_buf), TCP_WRITE_FLAG_COPY); if (err != ERR_OK) { printf("Failed to write data %d\n", err); return ERR_OK;//tcp_server_result(arg, -1); } return ERR_OK; } err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; if (!p) { return ERR_OK;//tcp_server_result(arg, -1); } // this method is callback from lwIP, so cyw43_arch_lwip_begin is not required, however you // can use this method to cause an assertion in debug mode, if this method is called when // cyw43_arch_lwip_begin IS needed cyw43_arch_lwip_check(); if (p->tot_len > 0) { printf("tcp_server_recv %d/%d err %d\n", p->tot_len, state->recv_len, err); // Receive the buffer const uint16_t buffer_left = BUF_SIZE - state->recv_len; state->recv_len += pbuf_copy_partial(p, state->buffer_recv + state->recv_len, p->tot_len > buffer_left ? buffer_left : p->tot_len, 0); tcp_recved(tpcb, p->tot_len); } pbuf_free(p); // Send buffer return tcp_server_send_data(arg, state->client_pcb, state->buffer_recv + (state->recv_len - p->tot_len)); } static err_t tcp_server_poll(void *arg, struct tcp_pcb *tpcb) { printf("tcp_server_poll_fn\n"); return tcp_server_result(arg, -1); // no response is an error? } static void tcp_server_err(void *arg, err_t err) { if (err != ERR_ABRT) { printf("tcp_client_err_fn %d\n", err); tcp_server_result(arg, err); } } static err_t tcp_server_accept(void *arg, struct tcp_pcb *client_pcb, err_t err) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; if (err != ERR_OK || client_pcb == NULL) { printf("Failure in accept\n"); tcp_server_result(arg, err); return ERR_VAL; } printf("Client connected\n"); state->client_pcb = client_pcb; tcp_arg(client_pcb, state); tcp_recv(client_pcb, tcp_server_recv); tcp_sent(client_pcb, tcp_server_sent); //tcp_poll(client_pcb, tcp_server_poll, POLL_TIME_S * 2); tcp_err(client_pcb, tcp_server_err); return ERR_OK;//tcp_server_send_data(arg, state->client_pcb); } static bool tcp_server_open(void *arg) { TCP_SERVER_T *state = (TCP_SERVER_T*)arg; printf("Starting server at %s on port %u\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), TCP_PORT); struct tcp_pcb *pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); if (!pcb) { printf("failed to create pcb\n"); return false; } err_t err = tcp_bind(pcb, NULL, TCP_PORT); if (err) { printf("failed to bind to port %d\n"); return false; } state->server_pcb = tcp_listen_with_backlog(pcb, 1); if (!state->server_pcb) { printf("failed to listen\n"); if (pcb) { tcp_close(pcb); } return false; } tcp_arg(state->server_pcb, state); tcp_accept(state->server_pcb, tcp_server_accept); return true; } void run_tcp_server_test(void) { TCP_SERVER_T *state = tcp_server_init(); if (!state) { return; } if (!tcp_server_open(state)) { tcp_server_result(state, -1); return; } while(1){//!state->complete) { // the following #ifdef is only here so this same example can be used in multiple modes; // you do not need it in your code #if PICO_CYW43_ARCH_POLL // if you are using pico_cyw43_arch_poll, then you must poll periodically from your // main loop (not from a timer) to check for WiFi driver or lwIP work that needs to be done. cyw43_arch_poll(); sleep_ms(1); // printf("Starting server at %s on port %u\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), TCP_PORT); #else // if you are not using pico_cyw43_arch_poll, then WiFI driver and lwIP work // is done via interrupt in the background. This sleep is just an example of some (blocking) // work you might be doing. sleep_ms(1000); // printf("Starting server at %s on port %u\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), TCP_PORT); #endif } free(state); } int main() { #if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) #warning i2c/lcd_1602_i2c example requires a board with I2C pins #else // This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico) i2c_init(i2c_default, 100 * 1000); gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); // Make the I2C pins available to picotool bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); lcd_init(); stdio_init_all(); if (cyw43_arch_init()) { printf("failed to initialise\n"); return 1; } cyw43_arch_enable_sta_mode(); printf("Connecting to WiFi...\n"); if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 30000)) { printf("failed to connect.\n"); return 1; } else { printf("Connected.\n"); } run_tcp_server_test(); cyw43_arch_deinit(); return 0; #endif } |
4. 다음 명령으로 다시 빌드하고 picow_tcpip_server_poll.uf2을 Raspberry Pi Pico W에 옮겨줍니다.
D:\work\pico>cd tcp_server\build
D:\work\pico\tcp_server\build>nmake
5. Raspberry Pi Pico를 시리얼 연결하여 확인된 IP 주소를 사용하여 Python 코드의 IP 주소를 수정합니다.
6. Python 코드를 실행하여 원하는 문자열을 입력후 엔터를 누릅니다.
잠시 후 Raspberry Pi Pico W에 연결된 LCD에 문자열이 출력됩니다. 이후 Python 프로그램에서 문자열을 입력하고 엔터를 누를때마다 LCD에 출력되는 문자열이 변경됩니다.
'Raspberry Pi Pico&Pico W' 카테고리의 다른 글
Raspberry Pi Pico C 프로그래밍 - 온보드 온도 센서 (0) | 2024.08.04 |
---|---|
Windows에 Raspberry Pi Pico C 개발 환경 만들기 (0) | 2024.08.03 |
Raspberry Pi Pico W - TCP Server C 예제 (0) | 2023.10.19 |
Raspberry Pi Pico C 프로그래밍 - I2C LCD 예제 (0) | 2023.10.19 |
Raspberry Pi Pico C 프로그래밍 - USB Serial 예제 (0) | 2023.10.19 |
시간날때마다 틈틈이 이것저것 해보며 블로그에 글을 남깁니다.
블로그의 문서는 종종 최신 버전으로 업데이트됩니다.
여유 시간이 날때 진행하는 거라 언제 진행될지는 알 수 없습니다.
영화,책, 생각등을 올리는 블로그도 운영하고 있습니다.
https://freewriting2024.tistory.com
제가 쓴 책도 한번 검토해보세요 ^^
그렇게 천천히 걸으면서도 그렇게 빨리 앞으로 나갈 수 있다는 건.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!