IMX6ULL深度学习(LABVIEW TCP/IP通讯) 、IMX6ULL wifi文件传输(无需网线)

前言

        忙了几天终于把项目做完了,内容没有很多,但是第一次接触TCP/IP并且实现labview和imx6ull网络通讯对我而言还是很有难度的,好在最后完成了;

        第二点就是,之前在学习imx6ull包括驱动和应用部分,都是需要网线,必须有网线才能用网络挂载zImage、设备树还有根文件系统,这样调试是很方便的,只需要在Ubuntu上把编译好的模块或者可执行文件复制到根文件系统中,即可在板子上执行,问题就是:有时我觉得调试好了,把程序烧到板子里面之后,如果又有问题需要修改代码,这样就又需要网线;但是我具体做实验的时候实验室没有网线可用,这就大大限制了我对板子的使用;为了解决这个问题,正好买板子正点原子送了一块wifi模块,我就把wifi模块学起来、用起来,最后也是实现了用wifi的方式从ubuntu向板子发送文件,这样根本不需要网线了,如果你的终端也不需要在主机上显示的话,下位机和上位机就可以完全实现无线传输了,非常方便!

一、Labview与imx6ull实现TCP/IP网络通讯,以及imx6ull深度学习模型调用

        首先说一下做这个项目的动机:

1.一方面是比较功利的,因为发现网络通讯以及网络协议部分是比较重要的内容,32裸机和带RTOS看的差不多了,然后前面Linux驱动有了一定的基础,就决定好好研究一下Linux应用,前面学习了一下一些I/O操作、信号、进程(还没看完)、线程(还没看!后面一定要看完!但是这个项目中已经用到了多线程)、TCP/IP、socket套接字网络编程

2.另一方面,学习了这么多内容,如果只是学习的话那也太没劲了,正好我的课题中有一部分内容可以结合,就是在python构建tflite模型(主要是用于定量分析)、将tflite模型部署到imx6ull、labview通过TCP向imx6ull发送待定量数据、imx6ull调用tflite模型进行定量、定量结果和上位机发送的数据以csv形式保存在板子上、imx6ull通过TCP将定量结果发送至上位机(这些是第一个线程的任务);第二个线程主要是针对有时labview会卡死,虽然我在labview上已经加了自动备份的功能,但是由于备份定时的设置,还是会丢数据,这是可以在ubuntu上开一个客户端,向板子发送请求数据,第二个线程就会将之前保存的两个csv发送给ubuntu,这样数据就都丢不了啦。

好了好了,说了这么多,直接进入代码部分,labview部分我就直接上图了

1.imx6ull与labview之间的发送与接收

Labview发送数据:

Labview接收数据:

起始发送与接收的labview程序我都是直接参照范例里面的最简单的那个例子,一个客户端一个服务器之间通讯,因为我这里要下发的也是一个一维数组,和范例是一致的,所以我直接采用了他的代码(我的很丑,懒得整理了,大家需要的直接去看范例的代码)

imx6ull接收数据与发送数据:

这部分我就直接上代码了,具体需要解释的部分我会详细解释(只放重要的线程和函数)

main函数
int main(int argc , char ** argv) {

    /*1.参数校验*/
	if(argc !=2)
	{
		printf("Wrong input!!!\r\n");	
		printf("Please enter : ./App .tflite file  \r\n");
		return -1;
	}

    //加载模型
    filename_spec = argv[1];

    //服务器变量
    int server_fd1, server_fd2;
    struct sockaddr_in address1, address2;
    int opt = 1;

    //创建多线程
    pthread_t spectrum_thread, server_thread;

    // 创建第一个套接字
    if ((server_fd1 = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置第一个套接字选项
    if (setsockopt(server_fd1, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    // 配置第一个套接字地址
    address1.sin_family = AF_INET;
    address1.sin_addr.s_addr = INADDR_ANY;
    address1.sin_port = htons(PORT1);

    // 绑定第一个套接字到端口
    if (bind(server_fd1, (struct sockaddr *)&address1, sizeof(address1)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听第一个端口
    if (listen(server_fd1, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    // 创建第二个套接字
    if ((server_fd2 = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置第二个套接字选项
    if (setsockopt(server_fd2, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    // 配置第二个套接字地址
    address2.sin_family = AF_INET;
    address2.sin_addr.s_addr = INADDR_ANY;
    address2.sin_port = htons(PORT2);

    // 绑定第二个套接字到端口
    if (bind(server_fd2, (struct sockaddr *)&address2, sizeof(address2)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听第二个端口
    if (listen(server_fd2, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    // 创建处理红外光谱数据的线程
    if (pthread_create(&spectrum_thread, NULL, process_spectrum_data, (void *)(intptr_t)server_fd1) != 0) {
        perror("Failed to create infrared thread");
        exit(EXIT_FAILURE);
    }

    // 创建处理其他请求的线程
    if (pthread_create(&server_thread, NULL, network_server, (void *)(intptr_t)server_fd2) != 0) {
        perror("Failed to create request thread");
        exit(EXIT_FAILURE);
    }

    pthread_join(spectrum_thread, NULL);
    pthread_join(server_thread, NULL);

    return 0;
}

首先是main函数,主要需要完成的任务是:

1.接收tflite文件名

2.创建两个套接字用于两个客户端

3.创建两个线程:一个处理数据,一个上位机需要备份数据时上s

数据处理线程
void* process_spectrum_data(void *arg) {
    std::unique_ptr<tflite::FlatBufferModel> model = 
    tflite::FlatBufferModel::BuildFromFile(filename_spec);
    TFLITE_MINIMAL_CHECK(model != nullptr);
    //初始化服务器变量
    int server_fd = (int)(intptr_t)arg;
    int new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);

    spec prediction_result[3];//定量结果存储数组

    while (1) {
        if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
            perror("accept");
            exit(EXIT_FAILURE);
        }
        printf("LabVIEW connect successful!\r\n");
        while(1){
            // 接受数据大小
            if (read(new_socket, &count, sizeof(int)) <= 0) {
                break; // 连接关闭或出错,跳出循环
            }

            //转换字序为大端序号
            count = ntohl(count);

            printf("the total number of data is %d\r\n",count);

            // 确保数据长度正确
            if (count != 1090 * 8) {
                fprintf(stderr, "Unexpected data size: %d\n", count);
                break;
            }

            char *data_buffer = (char *)malloc(count);
            if (data_buffer == NULL) {
                perror("Failed to allocate memory");
                break;
            }

            // 接受红外光谱数据
            int total_received = 0;
            while (total_received < count) {
                int bytes_received = recv(new_socket, data_buffer + total_received, count - total_received, 0);
                if (bytes_received <= 0) {
                    perror("Failed to read data");
                    free(data_buffer);
                    break;
                }
                total_received += bytes_received;
            }

            if(total_received == count){
                printf("Received %d bytes of data.\r\n", total_received);
                for (int i = 0; i < SINGBUFF_LENTH; ++i) {
                    uint64_t temp;
                    memcpy(&temp, data_buffer + i * 8, 8);
                    temp = ntohll(temp);  // 字节序转换
                    double value;
                    memcpy(&value, &temp, sizeof(double));
                    UL_Second_Buff_Data[i] = value;
                }
                //判断输入数据长度是否为定长1090 (2帧头+1数据长度+1084数据+1校验+2帧尾)
                if(count == (1090*8)){
                    New_Meassage = 1;
                    printf("Received one frame of data!\r\n");
                    UL_Second_Buff_To_UL_Valid();
                    UL_Valid_Message_Def valid_data_temp = {0};
                    if(UL_Valid_Message_Para.Valid_Buff_New_Meassage == 1)
                    {
                        printf("Valid Message!\r\n");
                        /* 循环解析,直到读写指针相等 */
                        while(UL_Valid_Message_Para.Valid_W_Ptr != UL_Valid_Message_Para.Valid_R_Ptr)
                        {
                            //printf("1\r\n");
                            valid_data_temp = UL_Valid_Message[UL_Valid_Message_Para.Valid_R_Ptr];
                            // Build the interpreter with the InterpreterBuilder.
                            // Note: all Interpreters should be built with the InterpreterBuilder,
                            // which allocates memory for the Interpreter and does various set up
                            // tasks so that the Interpreter can read the provided model.
                            tflite::ops::builtin::BuiltinOpResolver resolver;
                            tflite::InterpreterBuilder builder(*model, resolver);
                            std::unique_ptr<tflite::Interpreter> interpreter;
                            builder(&interpreter);

                            TFLITE_MINIMAL_CHECK(interpreter != nullptr);

                            // Allocate tensor buffers.
                            TFLITE_MINIMAL_CHECK(interpreter->AllocateTensors() == kTfLiteOk);
                            //printf("2\r\n");
                            int input_index = interpreter->inputs()[0];
                            spec* input_data = interpreter->typed_tensor<spec>(input_index);
                            for(int j=0;j<SPECTRUM_LENGTH;j++){
                                input_data[j] = valid_data_temp.data[j];
                            }
                            save_data_to_csv(input_data, "data.csv");

                            // Run inference
                            TFLITE_MINIMAL_CHECK(interpreter->Invoke() == kTfLiteOk);
                            int output_index = interpreter->outputs()[0];
                            spec* output_data = interpreter->typed_tensor<spec>(output_index);

                            double quan_arr[QUAN_LENGTH] = {0};
                            for (int i = 0; i < QUAN_LENGTH; i++) {
                                quan_arr[i] = output_data[i];
                            }
                            //将定量数据保存到csv中
                            save_quantification_to_csv(quan_arr,"quantifications.csv");

                            UL_Valid_Message_Para.Valid_R_Ptr++;
                            
                            if(UL_Valid_Message_Para.Valid_R_Ptr == VALID_MESSAGE_NUMBER)
                            {
                                UL_Valid_Message_Para.Valid_R_Ptr = 0;
                            }

                            free(data_buffer);
                            // 发送预测结果大小
                            int result_size = QUAN_LENGTH * sizeof(double);

                            // 将结果大小转换为小端字节顺序
                            int result_size_le = htonl(result_size);

                            if (write(new_socket, &result_size_le, sizeof(int)) <= 0) {
                                perror("Failed to send result size");
                                break;
                            }

                            //创建一个缓冲区,用于存储所有浮点数的小端字节序数据
                            uint64_t buffer[QUAN_LENGTH];
                            for (int i = 0; i < QUAN_LENGTH; ++i) {
                                buffer[i] = htond(quan_arr[i]);
                            }

                            if (write(new_socket, buffer, result_size) <= 0) {
                                perror("Failed to send prediction result");
                                break;
                            }

                        }
                        UL_Valid_Message_Para.Valid_Buff_New_Meassage = 0; 
                    }
                    else printf("inValid Message!\r\n");
                    count = 0;
                }
              }
            }
        close(new_socket); // 关闭当前连接,等待新的连接
    }
    close(server_fd);
}

这个线程里我遇到了很多的坑,接下来详解一下

代码中的TCP/IP通讯框架大家可以直接看我的main函数,在线程中,只要调用accept函数等待客户端发起通讯即可。

第一个问题:上位机发送数据长度count接收错误

因为labview在发送数据时,会先发送有多少字节的数据,再发送具体数据,所以接收时,也是先接收数据大小,再接收具体数据,这里我上位机下发的数据字节数应该是8720个字节,对应1090个8字节数据,但是下位机接收时发现这个count值非常大,这里举个例子:上位机发送大小为16712,下位机接收到的数值是1212219392

这里的关键是(我也是查资料才发现),labview是小端字序,下位机是大端字序!!!!

16712 = 0X 00 00 41 78

1212219392=0X 78 41 00 00

所以这导致了上位机下发的数据长度,在下位机接收时出现异常

解决方法是:ntohl() 函数,直接用这个函数就可以转换字序了,很方便

转换后接收数据长度值正常

第二个问题:具体数据转换错误

这个问题和上一个问题类似,也是字序问题,只不过这里是浮点数出现问题,解决方法就是一个字节一个字节大小去处理,我把具体代码放在下面

// 字节序转换函数
uint64_t ntohll(uint64_t value) {
    uint32_t high_part = ntohl((uint32_t)(value >> 32));
    uint32_t low_part = ntohl((uint32_t)(value & 0xFFFFFFFFLL));
    return ((uint64_t)low_part << 32) | high_part;
}
    
 for (int i = 0; i < SINGBUFF_LENTH; ++i) {
                    uint64_t temp;
                    memcpy(&temp, data_buffer + i * 8, 8);
                    temp = ntohll(temp);  // 字节序转换
                    double value;
                    memcpy(&value, &temp, sizeof(double));
                    UL_Second_Buff_Data[i] = value;}
第三个问题:tflite模型定量结果保持不变

这个问题我不太清楚具体导致的原因,这里就把正确定量的模型框架放在这里,大家需要的话可以直接用,根据自己的数据类型修改一下代码就可以

//加载模型
std::unique_ptr<tflite::FlatBufferModel> model = 
tflite::FlatBufferModel::BuildFromFile(filename_spec);
TFLITE_MINIMAL_CHECK(model != nullptr);

//构建模型
tflite::ops::builtin::BuiltinOpResolver resolver;
tflite::InterpreterBuilder builder(*model, resolver);
std::unique_ptr<tflite::Interpreter> interpreter;
builder(&interpreter);

TFLITE_MINIMAL_CHECK(interpreter != nullptr);

TFLITE_MINIMAL_CHECK(interpreter->AllocateTensors() == kTfLiteOk);


int input_index = interpreter->inputs()[0];
spec* input_data = interpreter->typed_tensor<spec>(input_index);
for(int j=0;j<SPECTRUM_LENGTH;j++){
    input_data[j] = valid_data_temp.data[j];
}

//运行模型
TFLITE_MINIMAL_CHECK(interpreter->Invoke() == kTfLiteOk);

//获得输出
int output_index = interpreter->outputs()[0];
pec* output_data = interpreter->typed_tensor<spec>(output_index);
第四个问题:回传的定量数据应该是一个一维数组,但是在labview中只获得了一个数值,且数值错误

这个问题应该是数据类型混乱不清导致回传时出现问题,大家在具体写代码的时候,一定要弄清要使用何种数据类型,因为涉及到字序的问题,如果数据类型错误的话,是会出问题的。

这里要注意的是,我的代码要回传的数据是double类型,这里注意要回传给labview,是要从大端字序变为小端字序,不然上位机接收到的数据是错误的

// 字节序转换函数
uint64_t ntohll(uint64_t value) {
    uint32_t high_part = ntohl((uint32_t)(value >> 32));
    uint32_t low_part = ntohl((uint32_t)(value & 0xFFFFFFFFLL));
    return ((uint64_t)low_part << 32) | high_part;
}

//创建一个缓冲区,用于存储所有浮点数的小端字节序数据
uint64_t buffer[QUAN_LENGTH];
for (int i = 0; i < QUAN_LENGTH; ++i) {
    buffer[i] = htond(quan_arr[i]);
}

if (write(new_socket, buffer, result_size) <= 0) {
    perror("Failed to send prediction result");
    break;
}

二、使用wifi实现ubuntu和imx6ull文件传输,极大便利调试

话不多说,直接上代码!大家直接拿去用,根据自己的需求稍作更改即可

接收端(unbuntu)

这里的端口号(PORT)以及IP地址要跟板子的端口号和IP地址对应,板子的IP地址的话,大家可以去学习一下wifi模块如何使用,我是直接看的正点原子的代码,分配好ip地址后,在板子上用ifconfig命令可以查看ip地址;端口号的话,这个比较随意,只要客户端和服务器端的端口号一致即可

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8082
#define BUFFER_SIZE 1024

int main(int argc, char const *argv[]) {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};
    FILE *file;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <file_path>\n", argv[0]);
        return -1;
    }

    const char *file_path = argv[1];
    const char *file_name = strrchr(file_path, '/'); // 提取文件名
    if (file_name == NULL) {
        file_name = file_path; // 文件路径中没有'/',整个路径即文件名
    } else {
        file_name++; // 跳过'/'
    }

    // 创建socket文件描述符
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation error");
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 转换IP地址
    if (inet_pton(AF_INET, "1xx.1xx.1xx.1xx", &serv_addr.sin_addr) <= 0) {
        perror("Invalid address/ Address not supported");
        return -1;
    }

    // 连接服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Connection Failed");
        return -1;
    }

    // 发送文件名,包含文件名的长度
    int file_name_length = strlen(file_name);
    send(sock, &file_name_length, sizeof(file_name_length), 0); // 发送文件名长度
    send(sock, file_name, file_name_length, 0); // 发送文件名

    // 打开文件以读取
    file = fopen(file_path, "rb");
    if (file == NULL) {
        perror("Failed to open file");
        close(sock);
        return -1;
    }

    // 发送文件数据
    int bytes_read;
    while ((bytes_read = fread(buffer, sizeof(char), BUFFER_SIZE, file)) > 0) {
        send(sock, buffer, bytes_read, 0);
    }

    printf("File sent successfully: %s\n", file_name);

    // 关闭文件和socket
    fclose(file);
    close(sock);

    return 0;
}

服务器端(imx6ull)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8082
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    FILE *file;
    char file_name[BUFFER_SIZE] = {0};

    // 创建socket文件描述符
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置socket选项
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定socket到端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听端口
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    // 接受客户端连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    // 接收文件名长度
    int file_name_length;
    if (read(new_socket, &file_name_length, sizeof(file_name_length)) <= 0) {
        perror("Failed to receive file name length");
        close(new_socket);
        close(server_fd);
        return -1;
    }

    // 接收文件名
    if (read(new_socket, file_name, file_name_length) <= 0) {
        perror("Failed to receive file name");
        close(new_socket);
        close(server_fd);
        return -1;
    }

    // 确保文件名以空字符结尾
    file_name[file_name_length] = '\0';

    // 打开文件以写入
    file = fopen(file_name, "wb");
    if (file == NULL) {
        perror("Failed to open file");
        close(new_socket);
        close(server_fd);
        return -1;
    }

    // 接收文件数据
    int bytes_read;
    while ((bytes_read = read(new_socket, buffer, BUFFER_SIZE)) > 0) {
        fwrite(buffer, sizeof(char), bytes_read, file);
    }

    printf("File received successfully: %s\n", file_name);

    // 关闭文件和socket
    fclose(file);
    close(new_socket);
    close(server_fd);

    return 0;
}

使用时先运行服务器端,再运行客户端

总结

还有一些内容没有涉及,例如另一个用于发送备份数据的线程,其实和wifi传输文件的代码是很相似的,如果大家有需要可以私聊,可能还有一些细节没有涉及到,暂时就想到了这些,把问题详细整理了一下,好累,休息去啦,大家加油啊!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值