Hi
I am using the nRF7002 DK with NCS 2.7, based on the sample project `nrf70-wifi-ble-image-transfer-demo`. I modified the code to embed a fixed image as a header file stored in flash memory, and then used a function to transmit the entire image via TCP to a computer for reception.
The first send code is
void video_preview(void)
{
// /* 只用状态,不用 ping-pong */
// static bool inited = false;
// /* 是否已对某个 client 发出“开帧头”(仅用于连续 stream 时避免重复发头) */
// static bool sock_frame_open = false;
// static bool ble_frame_open = false;
// if (!inited) {
// sock_frame_open = false;
// ble_frame_open = false;
// inited = true;
// }
// /* ========= 处理“开始请求” ========= */
// if (client_check_start_request(&client_state_socket)) {
// sock_frame_open = false; /* 下一次循环会重新发帧头 */
// }
// if (client_check_start_request(&client_state_ble)) {
// ble_frame_open = false;
// }
// /* ========= Socket (WiFi/TCP) 发送一帧 =========
// * 发送格式:
// * [FF AA][0x01][len(4, little endian)][payload...][FF BB]
// *
// * 说明:你之前 PC 端已经改成按 length 截取 payload,
// * 所以帧尾 FF BB 可要可不要。
// * 这里我保留帧尾,方便你兼容旧逻辑。
// */
// if (client_state_socket.stream_active) {
// if (!sock_frame_open) {
// /* 帧头 + type */
// socket_head_and_tail[2] = 0x01; /* Capture/Video frame */
// cam_send(&socket_head_and_tail[0], 3); /* FF AA 01 */
// /* len(小端) */
// uint32_t len_le = (uint32_t)FIXED_IMAGE_SIZE;
// cam_send((uint8_t *)&len_le, 4);
// sock_frame_open = true;
// }
// /* 关键:整帧一次性发(不要 1024 切块) */
// cam_send((uint8_t *)fixed_image_data, FIXED_IMAGE_SIZE);
// /* 帧尾(可选) */
// cam_send(&socket_head_and_tail[3], 2); /* FF BB */
// sock_frame_open = false; /* 一帧结束 */
// // /* 单帧模式:如果 req_stream_stop 被置位,则真正停流 */
// // if (client_state_socket.req_stream_stop) {
// // client_check_stop_request(&client_state_socket);
// // }
// /* 连续推流:如果你希望不断发同一张图当 video stream,
// * 那么不需要 stop_request,下一轮 while(1) 会再次进入这里发下一帧。
// * 如果你想降低占用,可以在 main loop 加一个 k_sleep(K_MSEC(x)).
// */
// }
// /* ========= BLE 发送一帧 =========
// * BLE 通常需要先发 header(长度),然后分片发送 payload。
// * 你这边 app_bt_send_picture_data() 内部会按 MTU 切片,所以我们直接整帧交给它。
// */
// if (client_state_ble.stream_active) {
// if (!ble_frame_open) {
// app_bt_send_picture_header(FIXED_IMAGE_SIZE);
// ble_frame_open = true;
// }
// /* 整帧交给 BLE 层,内部自己切片 */
// app_bt_send_picture_data(fixed_image_data, FIXED_IMAGE_SIZE);
// ble_frame_open = false; /* 一帧结束 */
// if (client_state_ble.req_stream_stop) {
// client_check_stop_request(&client_state_ble);
// }
// }
// /* ========= 外部 STOP 兜底 ========= */
// client_check_stop_request(&client_state_ble);
// client_check_stop_request(&client_state_socket);
// }
At this time, when I receive the data and compare in the BEYOND COMPARE software, I noticed that from a certain point onward, the received data becomes corrupted, appearing as garbled characters—similar to what happens with UDP. It seems that TCP is not performing handshake or error correction properly.

Subsequently, I introduced a ping-pong buffer mechanism, splitting the image into multiple 4 KB buffers and sending them sequentially. I suspected that excessive data might be causing issues with TCP transmission, and breaking the data into smaller chunks might help resolve the problem.
The sending code is:
/* ========== Socket RX ========= */
void socket_rx_callback(uint8_t *data, uint16_t len)
{
static struct app_command_t app_cmd_socket = {.type = APPCMD_SOCKET_RX};
LOG_DBG("SOCKET RX callback");
uint16_t cpy = len > sizeof(app_cmd_socket.data) ? sizeof(app_cmd_socket.data) : len;
memcpy(app_cmd_socket.data, data, cpy);
register_app_command(&app_cmd_socket);
}
void video_preview(void)
{
static bool inited = false;
static bool s_sock_frame_open = false;
static bool s_ble_frame_open = false; /* 本帧帧头是否已发 */
if (!inited) {
pp2_init(&s_pp_sock);
pp2_init(&s_pp_ble);
s_img_pos_sock = 0;
s_img_pos_ble = 0;
s_sock_frame_open = false;
s_ble_frame_open = false;
inited = true;
}
/* ===== Socket:是否需要开新帧 ===== */
if (client_check_start_request(&client_state_ble)) {
/* 准备开始一帧:先清 ping-pong & 位置,再等下面发帧头 */;
s_ble_frame_open = false;
}
if (client_check_start_request(&client_state_socket)) {
s_sock_frame_open = false;
}
/* 若流处于 active 但本帧还没开,则发帧头+长度 */
if (client_state_ble.stream_active && !s_ble_frame_open) {
pp2_init(&s_pp_ble);
s_img_pos_ble = 0;
app_bt_send_picture_header(FIXED_IMAGE_SIZE);
s_ble_frame_open = true;
// k_yield();
}
/* 若流处于 active 但本帧还没开,则发帧头+长度 */
if (client_state_socket.stream_active && !s_sock_frame_open) {
/* <<< 新增:每次开帧都清状态,保证能从 0 开始写 >>> */
pp2_init(&s_pp_sock);
s_img_pos_sock = 0;
socket_head_and_tail[2] = 0x01;
cam_send(&socket_head_and_tail[0], 3);
uint32_t len_le = FIXED_IMAGE_SIZE; /* PC 若按网络序,请改 htonl */
cam_send((uint8_t *)&len_le, 4);
s_sock_frame_open = true;
// k_yield();
}
/* ===== 先生产:只有“本帧已开启”时才去填数据 ===== */
if (client_state_socket.stream_active && s_sock_frame_open &&
(s_img_pos_sock < FIXED_IMAGE_SIZE)) {
size_t remain = FIXED_IMAGE_SIZE - s_img_pos_sock;
size_t wrote = pp2_try_fill(&s_pp_sock, &fixed_image_data[s_img_pos_sock], remain);
s_img_pos_sock += wrote;
/* 帧末:最后一个未满块也要 flush 成 ready */
if (s_img_pos_sock >= FIXED_IMAGE_SIZE) {
uint8_t cur = s_pp_sock.wr;
if (s_pp_sock.len[cur] > 0 && !s_pp_sock.ready[cur]) {
s_pp_sock.ready[cur] = 1;
}
}
/* 如果 wrote 为 0,说明两块都 ready,等待下一轮消费者清空即可 */
}
/* ===== 先生产:只有“本帧已开启”时才去填数据 ===== */
if (client_state_ble.stream_active && s_ble_frame_open &&
(s_img_pos_ble < FIXED_IMAGE_SIZE)) {
size_t remain = FIXED_IMAGE_SIZE - s_img_pos_ble;
size_t wrote = pp2_try_fill(&s_pp_ble, &fixed_image_data[s_img_pos_ble], remain);
s_img_pos_ble += wrote;
/* 帧末:最后一个未满块也要 flush 成 ready */
if (s_img_pos_ble >= FIXED_IMAGE_SIZE) {
uint8_t cur = s_pp_ble.wr;
if (s_pp_ble.len[cur] > 0 && !s_pp_ble.ready[cur]) {
s_pp_ble.ready[cur] = 1;
}
}
/* 如果 wrote 为 0,说明两块都 ready,等待下一轮消费者清空即可 */
}
/* ===== 后消费:把 ready 块全部发掉 ===== */
if (client_state_socket.stream_active && s_sock_frame_open) {
pp2_try_send_socket(&s_pp_sock);
}
/* 发送 ready 块 —— BLE */
if (client_state_ble.stream_active && s_ble_frame_open) {
pp2_try_send_ble(&s_pp_ble); // 不用 while 循环
}
if (client_state_ble.stream_active &&
s_ble_frame_open &&
(s_img_pos_ble >= FIXED_IMAGE_SIZE) &&
pp2_all_empty(&s_pp_ble)) {
s_ble_frame_open = false;
// if (client_state_ble.req_stream_stop) {
// client_check_stop_request(&client_state_ble); // 单帧模式
// } else {
// pp2_init(&s_pp_ble); // 连续推流:复位生产侧
// s_img_pos_ble = 0;
// // 下一轮会在 "!s_ble_frame_open" 分支里重新发帧头
// }
}
/* ===== 帧尾:当本帧数据已写完 且 两块都空,发送帧尾 ===== */
if (client_state_socket.stream_active &&
s_sock_frame_open &&
(s_img_pos_sock >= FIXED_IMAGE_SIZE) &&
pp2_all_empty(&s_pp_sock)) {
cam_send(&socket_head_and_tail[3], 2); /* 0xFF,0xBB 帧尾 */
s_sock_frame_open = false; /* 结束本帧 */
// if (client_state_socket.req_stream_stop) {
// /* 单帧/STOP:真正停流 */
// client_check_stop_request(&client_state_socket);
// } else {
// /* 连续推流:下一帧由上面的逻辑自动开启(下一轮会发帧头) */
// /* 此处不立即发帧头,给 PC/网络栈一点喘息,更稳 */
// }
}
// /* 兜底:处理外部停流 */
client_check_stop_request(&client_state_ble);
client_check_stop_request(&client_state_socket);
}
The issue of data loss still occurs, although fortunately, it only happens once somewhere in the middle of the transmission—the remaining data can still be matched to the original image. However, if the ping-pong buffer is not used, all subsequent data from a certain point onward becomes completely inconsistent with the original image.

Afterward, I modified the maximum number of bytes that can be sent in each call to `camsend`, reducing it from 1024 to 66. I observed that the frequency of data misalignment increased, but the number of misaligned bytes per instance decreased.

I can’t think of any other factors that might be causing this issue. Why would data loss occur when using the TCP protocol?
My project config is:
# # Copyright (c) 2022 Nordic Semiconductor ASA # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # # Wi-Fi CONFIG_WIFI=y CONFIG_WIFI_NRF700X=y # WPA supplicant CONFIG_WPA_SUPP=y # Soft AP Mode CONFIG_NRF700X_AP_MODE=y CONFIG_WPA_SUPP_AP=y CONFIG_WPA_SUPP_LOG_LEVEL_INF=y # CONFIG_WIFI_READY_LIB=y CONFIG_FILE_SYSTEM=y # CONFIG_SOFTAP_SAMPLE_5GHz=y # CONFIG_SOFTAP_SAMPLE_CHANNEL=36 CONFIG_SOFTAP_SAMPLE_5GHz=n CONFIG_SOFTAP_SAMPLE_CHANNEL=6 CONFIG_SOFTAP_SAMPLE_REG_DOMAIN="NO" CONFIG_SAMPLE_SCOKET_TCP=n CONFIG_SOFTAP_SAMPLE_SSID="WiFi_Cam_Demo_AP" CONFIG_SOFTAP_SAMPLE_PASSWORD="nRF7002DK" # Station Mode # Configure the Wi-Fi credentials CONFIG_WIFI_CREDENTIALS=y CONFIG_SHELL_BACKEND_SERIAL=y CONFIG_WIFI_CREDENTIALS_STATIC=n CONFIG_WIFI_CREDENTIALS_BACKEND_SETTINGS=y CONFIG_WIFI_CREDENTIALS_STATIC_SSID="iPhone 15C" CONFIG_WIFI_CREDENTIALS_STATIC_PASSWORD="13659022001" CONFIG_FLASH=y CONFIG_FLASH_PAGE_LAYOUT=y CONFIG_FLASH_MAP=y CONFIG_NVS=y CONFIG_SETTINGS=y CONFIG_SETTINGS_NVS=y # Enable support for shell commands CONFIG_SHELL=y CONFIG_SHELL_STACK_SIZE=8192 CONFIG_WIFI_CREDENTIALS_SHELL=y # Networking Management API # Enable the Network Management relevant configurations CONFIG_NET_MGMT=y CONFIG_NET_MGMT_EVENT=y CONFIG_NET_MGMT_EVENT_INFO=y CONFIG_NET_MGMT_EVENT_STACK_SIZE=25000 CONFIG_NET_CONNECTION_MANAGER=y #CONFIG_L2_WIFI_CONNECTIVITY=y # Logging CONFIG_LOG=y CONFIG_LOG_DEFAULT_LEVEL=3 CONFIG_USE_SEGGER_RTT=y CONFIG_LOG_BACKEND_RTT=y CONFIG_LOG_BACKEND_UART=y CONFIG_LOG_PRINTK=y CONFIG_ASSERT=y # DK library CONFIG_DK_LIBRARY=y # Networking CONFIG_NETWORKING=y CONFIG_NET_NATIVE=y CONFIG_NET_SOCKETS=y CONFIG_NET_LOG=y CONFIG_NET_IPV4=y CONFIG_NET_TCP=y CONFIG_NET_UDP=y CONFIG_NET_DHCPV4=y CONFIG_NET_PKT_RX_COUNT=6 CONFIG_NET_PKT_TX_COUNT=6 # Below section is the primary contributor to SRAM and is currently # tuned for performance, but this will be revisited in the future. CONFIG_NET_BUF_RX_COUNT=6 CONFIG_NET_BUF_TX_COUNT=6 CONFIG_NET_BUF_DATA_SIZE=1500 CONFIG_HEAP_MEM_POOL_SIZE=200000 CONFIG_NET_TC_TX_COUNT=1 CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=1 CONFIG_NET_MAX_CONTEXTS=5 CONFIG_NET_CONTEXT_SYNC_RECV=y CONFIG_INIT_STACKS=y CONFIG_NET_L2_ETHERNET=y CONFIG_NET_CONFIG_SETTINGS=y CONFIG_NET_CONFIG_INIT_TIMEOUT=0 CONFIG_NET_SOCKETS_POLL_MAX=10 CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.168.1.1" CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" CONFIG_NET_CONFIG_MY_IPV4_GW="192.168.1.1" # printing of scan results puts pressure on queues in new locking # design in net_mgmt. So, use a higher timeout for a crowded # environment. CONFIG_NET_MGMT_EVENT_QUEUE_TIMEOUT=5000 # Memories CONFIG_MAIN_STACK_SIZE=4096 CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 CONFIG_NET_TX_STACK_SIZE=4096 CONFIG_NET_RX_STACK_SIZE=4096 # Bluetooth CONFIG_BT=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_GATT_CLIENT=y CONFIG_BT_DEVICE_NAME="WiFi_BLE_Cam_DemoZZY" CONFIG_BT_DEVICE_APPEARANCE=833 CONFIG_BT_MAX_CONN=1 CONFIG_BT_MAX_PAIRED=1 CONFIG_BT_BUF_ACL_RX_SIZE=502 CONFIG_BT_ATT_PREPARE_COUNT=2 CONFIG_BT_L2CAP_TX_BUF_COUNT=10 CONFIG_BT_L2CAP_TX_MTU=498 CONFIG_BT_CONN_TX_MAX=10 CONFIG_BT_BUF_ACL_TX_COUNT=10 CONFIG_BT_BUF_ACL_TX_SIZE=502 CONFIG_BT_USER_DATA_LEN_UPDATE=y CONFIG_BT_USER_PHY_UPDATE=y CONFIG_BT_HCI_CORE_LOG_LEVEL_INF=y CONFIG_BT_RX_STACK_SIZE=8192 # Kernel options CONFIG_ENTROPY_GENERATOR=y # # Arducam Mega SPI Camera # CONFIG_SPI=y # CONFIG_VIDEO=y # CONFIG_VIDEO_INIT_PRIORITY=72 # CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=8192 # CONFIG_VIDEO_BUFFER_POOL_NUM_MAX=3 CONFIG_DEBUG=y # #需要跑满BLE带宽 # CONFIG_BT_CTLR_PHY_2M=y # CONFIG_BT_USER_PHY_UPDATE=y # #需要功率控制 # CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y # CONFIG_BT_CTLR_ADVANCED_FEATURES=y # SB_CONFIG_NETCORE_HCI_IPC=y #FEM general CONFIG_MPSL_FEM=y CONFIG_MPSL=y CONFIG_MPSL_FEM_NRF21540_GPIO=y CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y CONFIG_MPSL_FEM_NRF21540_TX_GAIN_DB=20 CONFIG_MPSL_FEM_NRF21540_RX_GAIN_DB=13 CONFIG_MPSL_FEM_NRF21540_RUNTIME_PA_GAIN_CONTROL=y #打印浮点数 CONFIG_CBPRINTF_FP_SUPPORT=y # CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y # CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC_CALIBRATION=y # CONFIG_CLOCK_CONTROL_NRF_K32SRC_500PPM=y #打开TCP socket CONFIG_SAMPLE_SCOKET_TCP=y
I’ve attached my code and kindly request your assistance. Thank you so much!