From 356652e1fe0654b13f2bcb4ad7b65839d8d5245d Mon Sep 17 00:00:00 2001 From: Miaow <3703781@qq.com> Date: Sun, 16 Jan 2022 13:44:50 +0800 Subject: [PATCH] original version --- .gitignore | 67 ++++++ LISCENSE | 1 + README.md | 37 ++++ binary/target | Bin 0 -> 60043 bytes protocol/下位机和上位机通信协议.md | 34 ++++ source/.gitignore | 66 ++++++ source/.vscode/c_cpp_properties.json | 24 +++ source/.vscode/settings.json | 76 +++++++ source/Makefile | 86 ++++++++ source/camera_trigger.c | 141 +++++++++++++ source/camera_trigger.h | 29 +++ source/delay.s | 18 ++ source/encoder.c | 130 ++++++++++++ source/encoder.h | 35 ++++ source/gpio_common.c | 73 +++++++ source/gpio_common.h | 90 ++++++++ source/host_computer.c | 293 +++++++++++++++++++++++++++ source/host_computer.h | 45 ++++ source/main.c | 244 ++++++++++++++++++++++ source/main.h | 6 + source/queue_reference.c | 167 +++++++++++++++ source/queue_reference.h | 46 +++++ source/queue_uint64.c | 195 ++++++++++++++++++ source/queue_uint64.h | 48 +++++ source/valve.c | 212 +++++++++++++++++++ source/valve.h | 35 ++++ 26 files changed, 2198 insertions(+) create mode 100644 .gitignore create mode 100644 LISCENSE create mode 100644 README.md create mode 100644 binary/target create mode 100644 protocol/下位机和上位机通信协议.md create mode 100644 source/.gitignore create mode 100644 source/.vscode/c_cpp_properties.json create mode 100644 source/.vscode/settings.json create mode 100644 source/Makefile create mode 100644 source/camera_trigger.c create mode 100644 source/camera_trigger.h create mode 100644 source/delay.s create mode 100644 source/encoder.c create mode 100644 source/encoder.h create mode 100644 source/gpio_common.c create mode 100644 source/gpio_common.h create mode 100644 source/host_computer.c create mode 100644 source/host_computer.h create mode 100644 source/main.c create mode 100644 source/main.h create mode 100644 source/queue_reference.c create mode 100644 source/queue_reference.h create mode 100644 source/queue_uint64.c create mode 100644 source/queue_uint64.h create mode 100644 source/valve.c create mode 100644 source/valve.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3480399 --- /dev/null +++ b/.gitignore @@ -0,0 +1,67 @@ +# ===Windows=== +# Windows thumbnail cache files +**/Thumbs.db +**/Thumbs.db:encryptable +**/ehthumbs.db +**/ehthumbs_vista.db + +# Dump file +**/*.stackdump + +# Folder config file +**/[Dd]esktop.ini + +# Recycle Bin used on file shares +**/$RECYCLE.BIN/ + +# Windows Installer files +**/*.cab +**/*.msi +**/*.msix +**/*.msm +**/*.msp + +# Windows shortcuts +**/*.lnk + +# ===Linux=== + +**/*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +**/.fuse_hidden* + +# KDE directory preferences +**/.directory + +# Linux trash folder which might appear on any partition or disk +**/.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +**/.nfs* + +# ===MacOS=== +# General +**/.DS_Store +**/.AppleDouble +**/.LSOverride + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk \ No newline at end of file diff --git a/LISCENSE b/LISCENSE new file mode 100644 index 0000000..d69270b --- /dev/null +++ b/LISCENSE @@ -0,0 +1 @@ +闭源,私有,保密 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a9f105c --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# 下位机 + +下位机根据传送带脉冲等触发相机,接收上位机给的数据,按其要求控制阀板,用在各种分选机上。 + +## 目录结构 + +- binary为编译好的可执行文件 +- script为配置系统、安装环境、安装可执行文件、卸载可执行文件等的脚本 +- protocol为上位机和下位机通信的协议 +- hardware下位机主板、接口板、底板等的硬件设计 +- source为可执行文件的源程序 + +## 版本 + +由于经常有不同类型的新要求出现,比如分选糖果、分选烟梗、同为糖果也具有不同的参数,因此不同的下位机型号(注意不是更新,比如同一台机器需要设置新的参数)应建立不同的分支,**主分支无实际意义** + +分支命名规则(不使用中文,小写无空格) + +```shell +b分支编号-p生产环境项目名-t分选对象[-其他特点1[-其他特点2...]] +``` + +中括号在这里表示可省略的项,中括号本身不应出现在实际命名中,其他特点应字母打头,可有多个,"-"相连 + +使用Git的tag功能定义版本(注意连着tag一起push),Github仓库的release功能同步发布最新版本 + +版本号遵循定义如下(不使用中文,小写无空格) + +```shell +b分支编号-n编译号-h硬件版本-p协议版本-s脚本版本-r代码版本 +``` + +分支编号和分支命名中编号一致 + +## 作者 + +作者觉得还是不说明作者是谁比较好,免得毕业后有提着示波器的师弟师妹来问问题 \ No newline at end of file diff --git a/binary/target b/binary/target new file mode 100644 index 0000000000000000000000000000000000000000..29dd2f86a4f19db5428c3998fa854f342927cb3e GIT binary patch literal 60043 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", + "Date Version Author Description", + "{date} 1.0 miaow 内容", + "", + ], + // 文件注释的组成及其排序 + "doxdocgen.file.fileOrder": [ + "file", // @file + "brief", // @brief 简介 + "author", // 作者 + "version", // 版本 + "date", // 日期 + "empty", // 空行 + "copyright",// 版权 + "empty", + "custom" // 自定义 + ], + // 下面时设置上面标签tag的具体信息 + "doxdocgen.file.fileTemplate": "@file {name}", + "doxdocgen.file.versionTag": "@version 1.0", + "doxdocgen.generic.authorEmail": "3703781@qq.com", + "doxdocgen.generic.authorName": "miaow", + "doxdocgen.generic.authorTag": "@author {author} ({email})", + // 日期格式与模板 + "doxdocgen.generic.dateFormat": "YYYY/MM/DD", + "doxdocgen.generic.dateTemplate": "@date {date}", + + // 根据自动生成的注释模板(目前主要体现在函数注释上) + "doxdocgen.generic.order": [ + "brief", + "tparam", + "param", + "return" + ], + "doxdocgen.generic.paramTemplate": "@param {param}", + "doxdocgen.generic.returnTemplate": "@return {type} ", + "doxdocgen.generic.splitCasingSmartText": true +} + diff --git a/source/Makefile b/source/Makefile new file mode 100644 index 0000000..31b1c4e --- /dev/null +++ b/source/Makefile @@ -0,0 +1,86 @@ +#makefile for file_ioctl +CROSS_COMPILE ?= /home/miaow/software/arm-2011.03/bin/arm-none-linux-gnueabi- +TARGET := target +BUILD_DIR := build + +ifeq ("$(origin V)", "command line") + KBUILD_VERBOSE = $(V) +endif +ifndef KBUILD_VERBOSE + KBUILD_VERBOSE = 0 +endif + +ifeq ($(KBUILD_VERBOSE),1) + quiet = + Q = +else + quiet=quiet_ + Q = @ +endif + +ifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-4 +ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),) + quiet=silent_ +endif +else # make-3.8x +ifneq ($(filter s% -s%,$(MAKEFLAGS)),) + quiet=silent_ +endif +endif + + +SRC := $(wildcard *.c) +ASM_SRC := $(wildcard *.s) +OBJ := $(addprefix $(BUILD_DIR)/, $(notdir $(SRC:.c=.o))) +ASM_OBJ := $(addprefix $(BUILD_DIR)/, $(notdir $(ASM_SRC:.s=.o))) +DIS := $(addprefix $(BUILD_DIR)/, $(notdir $(SRC:.c=.dis))) +ASM_DIS := $(addprefix $(BUILD_DIR)/, $(notdir $(ASM_SRC:.s=.dis))) + +_TARGET := $(BUILD_DIR)/$(TARGET) +TARGET_DIS := $(BUILD_DIR)/$(TARGET).dis + +LD = $(CROSS_COMPILE)ld +CC = $(CROSS_COMPILE)gcc +CPP = $(CC) -E +AR = $(CROSS_COMPILE)ar +LDR = $(CROSS_COMPILE)ldr +STRIP = $(CROSS_COMPILE)strip +OBJCOPY = $(CROSS_COMPILE)objcopy +OBJDUMP = $(CROSS_COMPILE)objdump +CFLAGS = -g -std=gnu99 -Wall -I. +LDFLAGS = -lpthread -lc -lm -lrt -marmelf_linux_eabi + +.SECONDARY: + +.PHONY:all +all: $(_TARGET) $(DIS) $(ASM_DIS) $(TARGET_DIS) + +$(BUILD_DIR)/%.i:%.c %.h Makefile | $(BUILD_DIR) + $(Q)$(CC) -E $(CFLAGS) $< -o $@ +$(BUILD_DIR)/%.s:$(BUILD_DIR)/%.i Makefile | $(BUILD_DIR) + $(Q)$(CC) -S $(CFLAGS) $< -o $@ +$(BUILD_DIR)/%.o:$(BUILD_DIR)/%.s Makefile | $(BUILD_DIR) + $(Q)$(CC) -c $(CFLAGS) $< -o $@ +$(BUILD_DIR)/%.o:%.s Makefile | $(BUILD_DIR) + $(Q)$(CC) -c $(CFLAGS) $< -o $@ +$(BUILD_DIR)/%.dis:$(BUILD_DIR)/%.o Makefile | $(BUILD_DIR) + $(Q)$(OBJDUMP) -s -d $< > $@ +$(TARGET_DIS):$(_TARGET) Makefile | $(BUILD_DIR) + $(Q)$(OBJDUMP) -s -d $< > $@ +$(_TARGET):$(OBJ) $(ASM_OBJ) Makefile | $(BUILD_DIR) + $(Q)$(CC) $(OBJ) $(ASM_OBJ) $(LDFLAGS) -o $@ + + +.PHONY:clean +clean: + $(Q)$(RM) $(BUILD_DIR)/* -f + +.PHONY:install +install:$(TARGET) + $(Q)chmod 777 $(TARGET) + +.PHONY:$(BUILD_DIR) +$(BUILD_DIR): + $(Q)if [ ! -d $(BUILD_DIR) ]; then mkdir -p $@; fi + + \ No newline at end of file diff --git a/source/camera_trigger.c b/source/camera_trigger.c new file mode 100644 index 0000000..bc91aa4 --- /dev/null +++ b/source/camera_trigger.c @@ -0,0 +1,141 @@ +/** + * @file camera_trigger.c + * @brief Control the camera to grab frames + * @author miaow (3703781@qq.com) + * @version 1.0 + * @date 2022/01/09 + * + * @copyright Copyright (c) 2022 miaow + * + * @par Changelog: + * + *
Date Version Author Description + *
2022/01/09 1.0 miaow Write this file + *
+ */ +#include +#include +#include +#include +#include +#include + +// Write to the file desc (global variable `gpo_value_fd` in gpio_common.c) to operate a gpio. +// So gpo_value_fd should be initialized in valve_init with great care. +// Also, gpo_value_fd/gpi_value_fd is used in other .c files (read pluse of encoder, etc). +#define __GPO_SET_BIT(pin_t) __GPO_SET(pin_t, GPIO_VALUE_HIGH) +#define __GPO_CLR_BIT(pin_t) __GPO_SET(pin_t, GPIO_VALUE_LOW) +#define __GPO_SET(pin_t, value_t) write(gpo_value_fd[GPIO_PINDEF_TO_INDEX(pin_t)], gpio_pin_value_str[GPIO_VALUEDEF_TO_INDEX(value_t)], gpio_pin_value_str_len[GPIO_VALUEDEF_TO_INDEX(value_t)]) + +/** + * @brief Variables definition used in this module + */ +typedef struct +{ + sem_t need_send; // Value >= 0 will cause a the camera grabbing one frame + sem_t is_sending; // value >= 0 means the last trigger signal is sent + pthread_mutex_t loop_thread_mutex; + int need_exit; // loop_thread joins to parent-thread at need_exit==1 + pthread_t loop_thread; // The sending thread +} cameratrigger_global_t; + +static cameratrigger_global_t _global_structure; + +static void *loop_thread_func(void *param); + +/** + * @brief Initialize camera trigger gpo and start loop_thread which keeps listening the trig signal + * @return 0 - success, -1 - error + */ +int cameratrigger_init() +{ + int trig_line_index = GPIO_PINDEF_TO_INDEX(TRIG_LINE); + // export the trigger line + int fd_export = open(GPIO_EXPORT_PATH, O_WRONLY); + ON_ERROR_RET(fd_export, GPIO_EXPORT_PATH, "export in cameratrigger_init()", -1); + + if (!is_file_exist(gpio_value_file_gpo_list[trig_line_index])) + { + int ret = write(fd_export, gpo_pin_str[trig_line_index], gpo_pin_str_len[trig_line_index]); + ON_ERROR_RET(ret, gpo_pin_str[trig_line_index], "open value file in cameratrigger_init()", -1); + } + close(fd_export); + + gpo_value_fd[trig_line_index] = open(gpio_value_file_gpo_list[trig_line_index], O_RDWR); + ON_ERROR_RET(gpo_value_fd[trig_line_index], gpio_value_file_gpo_list[trig_line_index], "open value file in cameratrigger_init()", -1); + __GPO_SET_BIT(TRIG_LINE); + + sem_init(&_global_structure.need_send, 0, 0); + sem_init(&_global_structure.is_sending, 0, 1); + pthread_mutex_init(&_global_structure.loop_thread_mutex, NULL); + + int ret = pthread_create(&_global_structure.loop_thread, NULL, loop_thread_func, NULL); + ON_ERROR_RET(ret, "thread create error in cameratrigger_init()", "", -1); + + return 0; +} + +/** + * @brief This function runs in child thread and triggles the camera to grab one frame + */ +void *loop_thread_func(void *param) +{ + printf("loop_thread in %s start\r\n", __FILE__); + int need_exit = 0; + struct timespec ts; + int ret = 0; + while (!need_exit) + { + + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 1; + ret = sem_timedwait(&_global_structure.need_send, &ts); + if (ret == 0) + { + __GPO_CLR_BIT(TRIG_LINE); + usleep(200); + __GPO_SET_BIT(TRIG_LINE); + sem_post(&_global_structure.is_sending); + } + pthread_mutex_lock(&_global_structure.loop_thread_mutex); + need_exit = _global_structure.need_exit; + pthread_mutex_unlock(&_global_structure.loop_thread_mutex); + } + printf("loop_thread in %s exit\r\n", __FILE__); + return NULL; +} + +/** + * @brief Trigger a frame grabbing of the camera + * @note This function will wait until last grabbing accomplished + * @return 0 - success + */ +int cameratrigger_trig() +{ + sem_wait(&_global_structure.is_sending); + sem_post(&_global_structure.need_send); + return 0; +} + +/** + * @brief Deinitialize and release all resources of this module + * @note This function DOES BLOCKS 1s at most and DOES NOT UNEXPORT gpo + * @return 0 - success, -1 - error + */ +int cameratrigger_deinit() +{ + sem_wait(&_global_structure.is_sending); + pthread_mutex_lock(&_global_structure.loop_thread_mutex); + _global_structure.need_exit = 1; + pthread_mutex_unlock(&_global_structure.loop_thread_mutex); + pthread_join(_global_structure.loop_thread, NULL); + pthread_mutex_destroy(&_global_structure.loop_thread_mutex); + sem_destroy(&_global_structure.is_sending); + sem_destroy(&_global_structure.need_send); + _global_structure.need_exit = 0; + + int ret = close(gpo_value_fd[GPIO_PINDEF_TO_INDEX(TRIG_LINE)]); + ON_ERROR_RET(ret, "close value file in cameratrigger_deinit()", "", -1); + + return 0; +} \ No newline at end of file diff --git a/source/camera_trigger.h b/source/camera_trigger.h new file mode 100644 index 0000000..5121d5c --- /dev/null +++ b/source/camera_trigger.h @@ -0,0 +1,29 @@ +/** + * @file camera_trigger.c + * @brief Control the camera to grab frames + * @author miaow (3703781@qq.com) + * @version 1.0 + * @date 2022/01/09 + * + * @copyright Copyright (c) 2022 miaow + * + * @par Changelog: + * + *
Date Version Author Description + *
2022/01/09 1.0 miaow Write this file + *
+ */ +#ifndef __CAMERA_TRIGGER_H +#define __CAMERA_TRIGGER_H +#include + +typedef enum +{ + TRIG_LINE=GPIO_PINDEF_TO_INDEX(GPO6) +}cameratrigger_pin_enum_t; + +int cameratrigger_init(void); +int cameratrigger_trig(void); +int cameratrigger_deinit(void); + +#endif \ No newline at end of file diff --git a/source/delay.s b/source/delay.s new file mode 100644 index 0000000..d0258c0 --- /dev/null +++ b/source/delay.s @@ -0,0 +1,18 @@ +.global delay_us +.func delay_us +delay_us: + cmp r0, #0 + moveq pc, lr + stmfd sp!, {r1, r2, fp, lr} + mov r1, r0 + big_loop: + ldr r2, =266 + loop: + sub r2, r2, #1 + cmp r2, #0 + bne loop + sub r1, r1, #1 + cmp r1, #0 + bne big_loop + ldmfd sp!, {r1, r2, fp, pc} +.endfunc diff --git a/source/encoder.c b/source/encoder.c new file mode 100644 index 0000000..dd3c2bd --- /dev/null +++ b/source/encoder.c @@ -0,0 +1,130 @@ +/** + * @file encoder.c + * @brief Manage the encoder and realize a callback function + * @author miaow (3703781@qq.com) + * @version 1.0 + * @date 2022/01/09 + * + * @copyright Copyright (c) 2022 miaow + * + * @par Changelog: + * + *
Date Version Author Description + *
2022/01/09 1.0 Miaow Write this module + *
+ */ +#include +#include +#include +#include +#include +#include + + +#define __GPI_GET(pin_t) read(gpi_value_fd[GPIO_PINDEF_TO_INDEX(pin_t)], _global_structure.buf, sizeof(_global_structure.buf)) + +/** + * @brief Variables definition used in this module + */ +typedef struct +{ + int need_exit; // loop_thread joins to parent-thread at need_exit==1 + pthread_t loop_thread; // The main deamon thread + encoder_callback callback_func; // Restore the pointer to callback function + pthread_mutex_t loop_thread_mutex; // Used in the main deamon thread and deinit function, surrounding the need_exit variable + char buf[1]; // Buffer for reading the file descripter +} encoder_global_t; + + +static encoder_global_t _global_structure; //! the global variables used in this file (module) + +static void *loop_thread_func(void *param); + +/** + * @brief Initialize the encoder related gpio and thread + * @param func The callback function, which is called at rising edge of the encoder + * @return 0-success, -1 - failed + */ +int encoder_init(encoder_callback func) +{ + + int phase_b_index = GPIO_PINDEF_TO_INDEX(ENCODER_PHASEB); + // export + int fd_export = open(GPIO_EXPORT_PATH, O_WRONLY); + ON_ERROR_RET(fd_export, GPIO_EXPORT_PATH, "export in encoder_init()", -1); + + if (!is_file_exist(gpio_value_file_gpi_list[phase_b_index])) // do not export if value file exist + { + int ret = write(fd_export, gpi_pin_str[phase_b_index], gpi_pin_str_len[phase_b_index]); + ON_ERROR_RET(ret, gpi_pin_str[phase_b_index], "open value file in encoder_init()", -1); + } + close(fd_export); + + // open edge file + int edge_fd = open(gpio_edge_file_gpi_list[phase_b_index], O_RDWR); + ON_ERROR_RET(edge_fd, gpio_edge_file_gpi_list[phase_b_index], "open edge file in encoder_init()", -1); + write(edge_fd, "rising", 7); + close(edge_fd); + + // open value file + gpi_value_fd[phase_b_index] = open(gpio_value_file_gpi_list[phase_b_index], O_RDWR); + ON_ERROR_RET(gpi_value_fd[phase_b_index], gpio_value_file_gpi_list[phase_b_index], "open value file in encoder_init()", -1); + + _global_structure.callback_func = func; + + // start loop thread + pthread_create(&_global_structure.loop_thread, NULL, loop_thread_func, NULL); + + return 0; +} + +/** + * @brief Deinitialize the encoder module, stop the thread and release resources + * @return 0-success, -1 - failed + */ +int encoder_deinit() +{ + // stop loop_thread + pthread_mutex_lock(&_global_structure.loop_thread_mutex); + _global_structure.need_exit = 1; + pthread_mutex_unlock(&_global_structure.loop_thread_mutex); + // wait loop_thread to stop + pthread_join(_global_structure.loop_thread, NULL); + pthread_mutex_destroy(&_global_structure.loop_thread_mutex); + _global_structure.need_exit = 0; + // close value file + int ret = close(gpi_value_fd[GPIO_PINDEF_TO_INDEX(ENCODER_PHASEB)]); + ON_ERROR_RET(ret, "close value file in encoder_init()", "", -1); + gpi_value_fd[GPIO_PINDEF_TO_INDEX(ENCODER_PHASEB)] = 0; + return 0; +} + +/** + * @brief Call the callback function set when initialization at rising edge of the encoder pulse + * @param param Not used + * @return 0 + */ +static void *loop_thread_func(void *param) +{ + // 调用一次encoder_callback func + printf("loop thread in %s start\r\n", __FILE__); + struct pollfd fds[1]; + fds[0].fd = gpi_value_fd[GPIO_PINDEF_TO_INDEX(ENCODER_PHASEB)]; + fds[0].events = POLLPRI; + int need_exit = 0; + + while (!need_exit) + { + if (poll(fds, 1, 1000) && (fds[0].revents & POLLPRI)) + { + __GPI_GET(ENCODER_PHASEB); + _global_structure.callback_func(); + } + pthread_mutex_lock(&_global_structure.loop_thread_mutex); + need_exit = _global_structure.need_exit; + pthread_mutex_unlock(&_global_structure.loop_thread_mutex); + } + printf("loop thread in %s exit\r\n", __FILE__); + return (void *)NULL; +} + diff --git a/source/encoder.h b/source/encoder.h new file mode 100644 index 0000000..d0ccc37 --- /dev/null +++ b/source/encoder.h @@ -0,0 +1,35 @@ +/** + * @file encoder.h + * @brief Manage the encoder and realize a callback function + * @author miaow (3703781@qq.com) + * @version 1.0 + * @date 2022/01/09 + * + * @copyright Copyright (c) 2022 miaow + * + * @par Changelog: + * + *
Date Version Author Description + *
2022/01/09 1.0 Miaow Write this module + *
+ */ +#ifndef __ENCODER_H +#define __ENCODER_H +#include + +/** + * @brief Pin definition + * @note Actually, only ENCODER_PHASEB is used + */ +typedef enum +{ + ENCODER_PHASEA=GPIO_PINDEF_TO_INDEX(GPI0), + ENCODER_PHASEB=GPIO_PINDEF_TO_INDEX(GPI2) +}encoder_pin_enum_t; + +typedef void (*encoder_callback)(void); // Callback funtion prototype. + +int encoder_init(encoder_callback func); +int encoder_deinit(void); + +#endif diff --git a/source/gpio_common.c b/source/gpio_common.c new file mode 100644 index 0000000..0c4a6a1 --- /dev/null +++ b/source/gpio_common.c @@ -0,0 +1,73 @@ +/** + * @file gpio_common.c + * @brief Operate the GPIO port of Zhou Ligong linux industrial control board + * @details is_file_exist(const char *file_path) determine whether the specified file exists + * print_array(int *array, int count) used to print out the value of the queue buffer, easy to debug and use + * @mainpage github.com/NanjingForestryUniversity + * @author miaow + * @email 3703781@qq.com + * @version v0.9.0 + * @date 2021/12/25 merry christmas + */ +#include + +char perror_buffer[1024] = {0}; + +char *gpio_value_file_gpo_list[8] = {GPIO_GET_VALUE_FILE(52), GPIO_GET_VALUE_FILE(53), + GPIO_GET_VALUE_FILE(54), GPIO_GET_VALUE_FILE(55), + GPIO_GET_VALUE_FILE(56), GPIO_GET_VALUE_FILE(57), + GPIO_GET_VALUE_FILE(58), GPIO_GET_VALUE_FILE(59)}; + +char *gpio_value_file_gpi_list[8] = {GPIO_GET_VALUE_FILE(44), GPIO_GET_VALUE_FILE(45), + GPIO_GET_VALUE_FILE(46), GPIO_GET_VALUE_FILE(47), + GPIO_GET_VALUE_FILE(48), GPIO_GET_VALUE_FILE(49), + GPIO_GET_VALUE_FILE(50), GPIO_GET_VALUE_FILE(51)}; + +char *gpio_edge_file_gpi_list[8] = {GPIO_GET_EDGE_FILE(44), GPIO_GET_EDGE_FILE(45), + GPIO_GET_EDGE_FILE(46), GPIO_GET_EDGE_FILE(47), + GPIO_GET_EDGE_FILE(48), GPIO_GET_EDGE_FILE(49), + GPIO_GET_EDGE_FILE(50), GPIO_GET_EDGE_FILE(51)}; + +char *gpo_pin_str[8] = {"52", "53", "54", "55", "56", "57", "58", "59"}; +int gpo_pin_str_len[8] = {2, 2, 2, 2, 2, 2, 2, 2}; +char *gpi_pin_str[8] = {"44", "45", "46", "47", "48", "49", "50", "51"}; +int gpi_pin_str_len[8] = {2, 2, 2, 2, 2, 2, 2, 2}; +char *gpio_pin_value_str[2] = {"0", "1"}; +int gpio_pin_value_str_len[2] = {1, 1}; +int gpo_value_fd[8] = {0}; +int gpi_value_fd[8] = {0}; + +/** + * @brief determine whether the specified file exists + * @param file_path file path + * @return 1 - success, -1 - error + */ +int is_file_exist(const char *file_path) +{ + if (file_path == NULL) + return -1; + if (access(file_path, F_OK) == 0) + return 1; + return -1; +} + +/** + * @brief Put the processed host computer data into the queue + * @param array Buffer pointer in the queue + * @param count The number of data in the buffer + */ +void print_array(int *array, int count) +{ + if (count == 0) + { + printf("[]\r\n"); + return; + } + printf("["); + int i; + for (i = 0; i < count - 1; i++) + { + printf("%d,", array[i]); + } + printf("%d]\r\n", array[i]); +} diff --git a/source/gpio_common.h b/source/gpio_common.h new file mode 100644 index 0000000..8fcc124 --- /dev/null +++ b/source/gpio_common.h @@ -0,0 +1,90 @@ +#ifndef __GPIO_COMMON_H +#define __GPIO_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPIO_EXPORT_PATH "/sys/class/gpio/export" +#define GPIO_GET_PIN_STR(pin) #pin +#define GPIO_GET_VALUE_FILE(pin) "/sys/class/gpio/gpio" #pin "/value" +#define GPIO_GET_EDGE_FILE(pin) "/sys/class/gpio/gpio" #pin "/edge" +#define GPIO_PINDEF_TO_INDEX(pin_t) ((int)pin_t) +#define GPIO_VALUEDEF_TO_INDEX(value_t) ((int)value_t) + +#define ON_ERROR(res, message1, message2) \ + if (res < 0) \ + { \ + sprintf(perror_buffer, "error %d at %s:%d, %s, %s", res, __FILE__, __LINE__, message1, message2); \ + perror(perror_buffer); \ + } + +#define ON_ERROR_RET_VOID(res, message1, message2) \ + ON_ERROR(res, message1, message2); \ + if (res < 0) \ + { \ + res = 0; \ + return; \ + } + +#define ON_ERROR_RET(res, message1, message2, retval) \ + ON_ERROR(res, message1, message2); \ + if (res < 0) \ + { \ + res = 0; \ + return retval; \ + } + +typedef enum +{ + GPO0 = 0, + GPO1 = 1, + GPO2 = 2, + GPO3 = 3, + GPO4 = 4, + GPO5 = 5, + GPO6 = 6, + GPO7 = 7 +} gpo_pin_enum_t; + +typedef enum +{ + GPI0 = 0, + GPI1 = 1, + GPI2 = 2, + GPI3 = 3, + GPI4 = 4, + GPI5 = 5, + GPI6 = 6, + GPI7 = 7 +} gpi_pin_enum_t; + +typedef enum +{ + GPIO_VALUE_LOW = 0, + GPIO_VALUE_HIGH = 1 +} gpio_value_enum_t; + +int is_file_exist(const char *file_path); +extern char perror_buffer[]; +extern char *gpio_value_file_gpo_list[]; +extern char *gpio_value_file_gpi_list[]; +extern char *gpio_edge_file_gpi_list[]; +extern char *gpo_pin_str[]; +extern char *gpi_pin_str[]; +extern int gpo_pin_str_len[]; +extern int gpi_pin_str_len[]; +extern char *gpio_pin_value_str[]; +extern int gpo_value_fd[]; +extern int gpi_value_fd[]; +extern int gpio_pin_value_str_len[]; +void print_array(int *array, int count); + +#endif \ No newline at end of file diff --git a/source/host_computer.c b/source/host_computer.c new file mode 100644 index 0000000..f910e09 --- /dev/null +++ b/source/host_computer.c @@ -0,0 +1,293 @@ +/** + * @file host_computer.c + * @brief Commnunicate with host computer. Protocal is described in hostcomputer通信协议.md + * @author miaow (3703781@qq.com) + * @version 1.0 + * @date 2022/01/16 + * + * @copyright Copyright (c) 2022 miaow + * + * @par Changelog: + * + *
Date Version Author Description + *
2022/01/16 1.0 miaow Write this file + *
+ */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief Queue handle structure + */ +typedef struct +{ + queue_uint64_msg_t *data_q; // A pointer to the queue for valve data + queue_uint64_msg_t *cmd_q; // A pointer to the queue for commands + int socket_fd; // The socket fd for receiving commands and data + int need_exit; // The flag variable to indicate whether to exit the loop_thread in this file + pthread_t loop_thread; // The main routine of this module, which parses commands and data from host, puts them into the queue + pthread_mutex_t loop_thread_mutex; // The mutex for loop_thread +} hostcomputer_t; + +static hostcomputer_t _global_structure; +void *loop_thread_func(void *param); + +/** + * @brief Pre initialize host computer module + * @param data_q A pointer to the queue storing the valve data from host computer + * @param cmd_q A pointer to the queue storing the cmd from host computer + * @return 0 - success + */ +int hostcomputer_init(queue_uint64_msg_t *data_q, queue_uint64_msg_t *cmd_q) +{ + _global_structure.data_q = data_q; + _global_structure.cmd_q = cmd_q; + + pthread_mutex_init(&_global_structure.loop_thread_mutex, NULL); + pthread_create(&_global_structure.loop_thread, NULL, loop_thread_func, NULL); + + return 0; +} + +/** + * @brief Receive `size` bytes from a socket. If no more bytes are available at the socket, this function return -1 when timeout reaches. + * @param fd The socket fd + * @param buf Received bytes + * @param size Number of bytes to receive + * @return These calls return the number of bytes received, or -1 if time out occurred + */ +static int recvn(int fd, char *buf, int size) +{ + char *pt = buf; + int count = size; + while (count > 0) + { + int len = recv(fd, pt, count, 0); + // if (len == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) + // { + // // printf("recv timeout\r\n"); + // } + if (len == -1) + return -1; + else if (len == 0) + return size - count; + pt += len; + count -= len; + } + return size; +} + +/** + * @brief To inspect the status of TCP connection + * @param sock_fd The socket + * @return 0 - Not connected, 1 - connected + */ +static int is_connected(int sock_fd) +{ + struct tcp_info info; + int len = sizeof(info); + getsockopt(sock_fd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); + return info.tcpi_state == TCP_ESTABLISHED; +} + +/** + * @brief This function runs in child thread and handles communication with host computer + * @param param NULL + * @return NULL + */ +void *loop_thread_func(void *param) +{ + printf("loop thread in %s start\r\n", __FILE__); + int need_exit = 0; + char pre; + uint16_t n_bytes; + char type[2]; + char data[99999]; + char check[2]; + while (!need_exit) + { + pthread_mutex_lock(&_global_structure.loop_thread_mutex); + need_exit = _global_structure.need_exit; + pthread_mutex_unlock(&_global_structure.loop_thread_mutex); + // reconnect if not connected + if (!is_connected(_global_structure.socket_fd)) + { + _global_structure.socket_fd = socket(AF_INET, SOCK_STREAM, 0); + struct timeval timeout = {.tv_sec = 10, .tv_usec = 0}; + setsockopt(_global_structure.socket_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + ON_ERROR_RET(_global_structure.socket_fd, "hostcomputer_init", "", NULL); + struct sockaddr_in serAddr; + serAddr.sin_family = AF_INET; + serAddr.sin_addr.s_addr = inet_addr(HOST_COMPUTER_IP); + serAddr.sin_port = htons(HOST_COMPUTER_PORT); + printf("Connecting host computer..."); + fflush(stdout); + if (connect(_global_structure.socket_fd, (struct sockaddr *)&serAddr, sizeof(struct sockaddr_in)) == -1) + { + sleep(2); + close(_global_structure.socket_fd); + printf("FAILED\r\n"); + continue; + } + printf("OK\r\n"); + } + + // =======================parse the protocal========================================= + + if (recvn(_global_structure.socket_fd, (char *)&pre, 1) > 1) + { + // close(_global_structure.socket_fd); + printf("pre_len!=1\r\n"); + continue; + } + if (pre != 0xAA) + { + // close(_global_structure.socket_fd); + // printf("%X ", (int)pre); + fflush(stdout); + continue; + } + if (recvn(_global_structure.socket_fd, (char *)&n_bytes, 2) != 2) + { + // close(_global_structure.socket_fd); + printf("n_bytes_len!=2\r\n"); + continue; + } + n_bytes = ntohs(n_bytes); + if (n_bytes > 4096 || n_bytes < 2) + { + // close(_global_structure.socket_fd); + printf("n_bytes>4096 or n_bytes<2\r\n"); + continue; + } + if (recvn(_global_structure.socket_fd, (char *)type, 2) != 2) + { + // close(_global_structure.socket_fd); + printf("type!=2\r\n"); + continue; + } + if (recvn(_global_structure.socket_fd, (char *)data, n_bytes - 2) != n_bytes - 2) + { + // close(_global_structure.socket_fd); + printf("data_len!=n_bytes-2\r\n"); + continue; + } + + data[n_bytes - 2] = 0; + if (recvn(_global_structure.socket_fd, (char *)check, 2) != 2) + { + // close(_global_structure.socket_fd); + printf("check_len!=2\r\n"); + continue; + } + if (recvn(_global_structure.socket_fd, (char *)&pre, 1) != 1) + { + // close(_global_structure.socket_fd); + printf("end_len!=1\r\n"); + continue; + } + if (pre != 0xBB) + { + // close(_global_structure.socket_fd); + printf("end!=0xBB\r\n"); + continue; + } + + // =======================parse the commands========================================= + // commands are reformed as an uint64_t, 0x--------xxxxxxxx, where `-` refers its paramter and `x` is HOSTCOMPUTER_CMD + if (type[0] == 'd' && type[1] == 'a') + { + // printf("%dbytes of data put to data queue\r\n", (int)n_bytes - 2); + if (n_bytes - 2 != 6 * HOST_COMPUTER_PICTURE_ROW_NUM) + { + printf("n_bytes-2!=%d\r\n", 6 * HOST_COMPUTER_PICTURE_ROW_NUM); + continue; + } + int data_index = 0; + uint64_t tmp_one_line_data = 0; + // valve arange(nth in rank) 6th 5th 4th 3th 2th 1th + // byte arange(nth received) (6*x)th (5*x)th (4*x)th (3*x)th (2*x)th xth + // where x in range(500) + // + + for (int i = 0; i < HOST_COMPUTER_PICTURE_ROW_NUM; i++) + { + tmp_one_line_data = 0ul; + for (int j = 0; j < 6; j++) + { + tmp_one_line_data <<= 8; + tmp_one_line_data |= data[data_index++]; + } + queue_uint64_put(_global_structure.data_q, tmp_one_line_data); + } + } + else if (type[0] == 's' && type[1] == 't') + { + // printf("Start put to cmd queue, param:%d\r\n", (int)atoll(data)); + queue_uint64_put(_global_structure.cmd_q, (atoll(data) << 32) | HOSTCOMPUTER_CMD_START); + } + else if (type[0] == 's' && type[1] == 'p') + { + // printf("Stop put to cmd queue, param:%d\r\n", (int)atoll(data)); + queue_uint64_put(_global_structure.cmd_q, (atoll(data) << 32) | HOSTCOMPUTER_CMD_STOP); + } + else if (type[0] == 't' && type[1] == 'e') + { + // printf("Test put to cmd queue, param:%d\r\n", (int)atoll(data)); + queue_uint64_put(_global_structure.cmd_q, (atoll(data) << 32) | HOSTCOMPUTER_CMD_TEST); + } + else if (type[0] == 'p' && type[1] == 'o') + { + // printf("Power on put to cmd queue, param:%d\r\n", (int)atoll(data)); + queue_uint64_put(_global_structure.cmd_q, (atoll(data) << 32) | HOSTCOMPUTER_CMD_POWERON); + } + else if (type[0] == 's' && type[1] == 'c') + { + // printf("Set camera triggle pulse count put to cmd queue, param:%d\r\n", (int)atoll(data)); + queue_uint64_put(_global_structure.cmd_q, (atoll(data) << 32) | HOSTCOMPUTER_CMD_SETCAMERATRIGPULSECOUNT); + } + else if (type[0] == 's' && type[1] == 'v') + { + // printf("Set valve pulse count put to cmd queue, param:%d\r\n", (int)atoll(data)); + queue_uint64_put(_global_structure.cmd_q, (atoll(data) << 32) | HOSTCOMPUTER_CMD_SETVALVETRIGPULSECOUNT); + } + else if (type[0] == 's' && type[1] == 'd') + { + // printf("Set camera to valve pulse count put to cmd queue, param:%d\r\n", (int)atoll(data)); + queue_uint64_put(_global_structure.cmd_q, (atoll(data) << 32) | HOSTCOMPUTER_CMD_SETCAMERATOVALVEPULSECOUNT); + } + else + { + printf("Unknown command received"); + } + } + printf("loop thread in %s exit\r\n", __FILE__); + return NULL; +} + +/** + * @brief Deinitialize and release resources used by host computer module + * @return int + */ +int hostcomputer_deinit() +{ + pthread_mutex_lock(&_global_structure.loop_thread_mutex); + _global_structure.need_exit = 1; + pthread_mutex_unlock(&_global_structure.loop_thread_mutex); + pthread_join(_global_structure.loop_thread, NULL); + pthread_mutex_destroy(&_global_structure.loop_thread_mutex); + + close(_global_structure.socket_fd); + _global_structure.socket_fd = 0; + _global_structure.need_exit = 0; + _global_structure.cmd_q = NULL; + _global_structure.data_q = NULL; + return 0; +} diff --git a/source/host_computer.h b/source/host_computer.h new file mode 100644 index 0000000..19fbaf5 --- /dev/null +++ b/source/host_computer.h @@ -0,0 +1,45 @@ +/** + * @file host_computer.h + * @brief Commnunicate with host computer. Protocal is described in hostcomputer通信协议.md + * @author miaow (3703781@qq.com) + * @version 1.0 + * @date 2022/01/16 + * + * @copyright Copyright (c) 2022 miaow + * + * @par Changelog: + * + *
Date Version Author Description + *
2022/01/16 1.0 miaow Write this file + *
+ */ +#ifndef __HOST_COMPUTER_H +#define __HOST_COMPUTER_H + +#include +#include +#include + +#define HOST_COMPUTER_IP "192.168.2.10" +#define HOST_COMPUTER_PORT 13452 +#define HOST_COMPUTER_PICTURE_ROW_NUM 500 + +/** + * @brief The commonds, ref hostcomputer通信协议.md + */ +enum HOSTCOMPUTER_CMD +{ + HOSTCOMPUTER_CMD_START = 2, + HOSTCOMPUTER_CMD_STOP = 3, + HOSTCOMPUTER_CMD_TEST = 4, + HOSTCOMPUTER_CMD_POWERON = 5, + HOSTCOMPUTER_CMD_SETCAMERATRIGPULSECOUNT = 6, + HOSTCOMPUTER_CMD_SETVALVETRIGPULSECOUNT = 7, + HOSTCOMPUTER_CMD_SETCAMERATOVALVEPULSECOUNT = 8 + +}; + +int hostcomputer_init(queue_uint64_msg_t *data_q, queue_uint64_msg_t *cmd_q); +int hostcomputer_deinit(void); + +#endif diff --git a/source/main.c b/source/main.c new file mode 100644 index 0000000..8e68429 --- /dev/null +++ b/source/main.c @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief Value of state machine + */ +typedef enum +{ + NOT_INITIALIZED = 0, + INITIALIZED = 1, + RUNNING = 2, + SLEEPING = 3, + STOPPED = 4 +} status_enum_t; + +valvedata_t valvedata = {0}; +queue_uint64_msg_t data_queue = {0}; +queue_uint64_msg_t cmd_queue = {0}; + +static int count_valve = 1, count_camera = 0, count_valve_should_be = 2; +static uint64_t count_continues = 0UL, count_valve_continues = 0UL, count_camera_continues = 0UL; +static status_enum_t status = NOT_INITIALIZED; +static int camera_trigger_pulse_count = 0; +static int valve_should_trigger_pulse_count = 0; +static int valve_trigger_pulse_count = 0; +static int camera_to_valve_pulse_count = 0; + +#define ROTATE_UINT64_RIGHT(x, n) ((x) >> (n)) | ((x) << ((64) - (n))) +#define ROTATE_UINT64_LEFT(x, n) ((x) << (n)) | ((x) >> ((64) - (n))) + +void on_encoder(void); +void valve_test(float ms_for_each_channel); +void valve_test2(float ms_for_each_channel, int which_channel); +void valve_test3(float ms_for_each_channel); +void process_cmd(uint64_t *cmd); + +int main(int argc, char *argv[]) +{ + queue_uint64_init(&data_queue, 99999); + queue_uint64_init(&cmd_queue, 99999); + + // valve_init(); + // printf("testing valve....."); + // fflush(stdout); + // valve_test3(100.0f); + // valve_test2(200.0f, 0); + // valve_test(200.0f); + // printf("OK\r\n"); + // valve_deinit(); + + hostcomputer_init(&data_queue, &cmd_queue); + uint64_t cmd; + int TRUE = 1; + while (TRUE) + { + if (queue_uint64_get(&cmd_queue, &cmd) == 0) + { + process_cmd(&cmd); + usleep(100000); + } + } + hostcomputer_deinit(); + queue_uint64_deinit(&data_queue); + queue_uint64_deinit(&cmd_queue); + return 0; +} + +void process_cmd(uint64_t *cmd) +{ + int tmp_cmd = (int)*cmd; + int tmp_data = (int)(*cmd >> 32); + if (status == SLEEPING) + { + if (tmp_cmd == HOSTCOMPUTER_CMD_START) + { + valve_should_trigger_pulse_count = camera_trigger_pulse_count / HOST_COMPUTER_PICTURE_ROW_NUM; + for (int i = 0; i < camera_to_valve_pulse_count * HOST_COMPUTER_PICTURE_ROW_NUM / camera_trigger_pulse_count; i++) + queue_uint64_put(&data_queue, 0); + valve_init(); + cameratrigger_init(); + encoder_init(on_encoder); + status = RUNNING; + printf("\r\n>>>>>\r\nstatus==RUNNING\r\n<<<<<\r\n\r\n"); + } + else if (tmp_cmd == HOSTCOMPUTER_CMD_TEST) + { + valve_init(); + valve_test(500.0f); + valve_deinit(); + } + } + else if (status == NOT_INITIALIZED) + { + if (tmp_cmd == HOSTCOMPUTER_CMD_SETCAMERATRIGPULSECOUNT) + { + camera_trigger_pulse_count = tmp_data; + } + else if (tmp_cmd == HOSTCOMPUTER_CMD_SETVALVETRIGPULSECOUNT) + { + valve_trigger_pulse_count = tmp_data; + } + else if (tmp_cmd == HOSTCOMPUTER_CMD_SETCAMERATOVALVEPULSECOUNT) + { + camera_to_valve_pulse_count = tmp_data; + } + else if (tmp_cmd == HOSTCOMPUTER_CMD_TEST) + { + valve_init(); + valve_test(500.0f); + valve_deinit(); + } + if (camera_trigger_pulse_count != 0 && valve_trigger_pulse_count != 0 && camera_to_valve_pulse_count != 0) + { + status = INITIALIZED; + printf("\r\n>>>>>\r\nstatus==INITIALIZED\r\ncamera_trigger_pulse_count=%d\r\nvalve_trigger_pulse_count=%d\r\ncamera_to_valve_pulse_count=%d\r\n<<<<<\r\n\r\n", camera_trigger_pulse_count, valve_trigger_pulse_count, camera_to_valve_pulse_count); + } + } + else if (status == INITIALIZED) + { + if (tmp_cmd == HOSTCOMPUTER_CMD_START) + { + valve_should_trigger_pulse_count = camera_trigger_pulse_count / HOST_COMPUTER_PICTURE_ROW_NUM; + printf("valve_should_trigger_pulse_count=%d", valve_should_trigger_pulse_count); + for (int i = 0; i < camera_to_valve_pulse_count * HOST_COMPUTER_PICTURE_ROW_NUM / camera_trigger_pulse_count; i++) + queue_uint64_put(&data_queue, 0); + valve_init(); + cameratrigger_init(); + encoder_init(on_encoder); + status = RUNNING; + printf("\r\n>>>>>\r\nstatus==RUNNING\r\n<<<<<\r\n\r\n"); + } + else if (tmp_cmd == HOSTCOMPUTER_CMD_TEST) + { + valve_init(); + valve_test(500.0f); + valve_deinit(); + } + } + else if (status == RUNNING) + { + if (tmp_cmd == HOSTCOMPUTER_CMD_STOP) + { + encoder_deinit(); + cameratrigger_deinit(); + valve_deinit(); + queue_uint64_clear(&data_queue); + status = SLEEPING; + printf("\r\n>>>>>\r\nstatus==SLEEPING\r\n<<<<<\r\n\r\n"); + } + } +} + +void valve_test(float ms_for_each_channel) +{ + uint64_t valve_data = 1ul; + for (int i = 0; i < 48; i++) + { + usleep((useconds_t)(ms_for_each_channel * 500.0f)); + valvedata.valvedata_1 = valve_data << i; + valve_sendmsg(&valvedata); + usleep((useconds_t)(ms_for_each_channel * 500.0f)); + valvedata.valvedata_1 = 0; + valve_sendmsg(&valvedata); + } +} + +void valve_test2(float ms_for_each_channel, int which_channel) +{ + uint64_t valve_data = 1ul; + for (int i = 0; i < 10; i++) + { + usleep((useconds_t)(ms_for_each_channel * 500.0f)); + valvedata.valvedata_1 = valve_data << which_channel; + valve_sendmsg(&valvedata); + usleep((useconds_t)(ms_for_each_channel * 500.0f)); + valvedata.valvedata_1 = 0; + valve_sendmsg(&valvedata); + } +} + +void valve_test3(float ms_for_each_channel) +{ + valvedata.valvedata_1 = 0x5555555555555555ul; + for (int i = 0; i < 9999; i++) + { + usleep((useconds_t)(ms_for_each_channel * 250.0f)); + valvedata.valvedata_1 = 0x5555555555555555ul; + valve_sendmsg(&valvedata); + usleep((useconds_t)(ms_for_each_channel * 250.0f)); + + valvedata.valvedata_1 = 0; + valve_sendmsg(&valvedata); + usleep((useconds_t)(ms_for_each_channel * 250.0f)); + + valvedata.valvedata_1 = 0xaaaaaaaaaaaaaaaaul; + valve_sendmsg(&valvedata); + usleep((useconds_t)(ms_for_each_channel * 250.0f)); + + valvedata.valvedata_1 = 0; + valve_sendmsg(&valvedata); + usleep((useconds_t)(ms_for_each_channel * 250.0f)); + } +} + +void on_encoder() +{ + count_continues++; + + if (++count_valve == valve_trigger_pulse_count + 1) + { + count_valve = 1; + count_valve_continues++; + valve_sendmsg(&valvedata); + + // printf("data:%llx send to valve, queue length is %d\r\n", valvedata.valvedata_1, data_queue.nData); + // printf("%016llx ", valvedata.valvedata_1); + fflush(stdout); + } + + if (++count_valve_should_be == valve_should_trigger_pulse_count + 2) + { + count_valve_should_be = 2; + valvedata.valvedata_1 = 0; + queue_uint64_get(&data_queue, &(valvedata.valvedata_1)); + // if (data_queue.nData == 0) + // { + // printf("sb\r\n"); + // } + } + + if (++count_camera == camera_trigger_pulse_count) + { + // printf("camera triggled\r\n"); + count_camera = 0; + count_camera_continues++; + cameratrigger_trig(); + } +} diff --git a/source/main.h b/source/main.h new file mode 100644 index 0000000..7ecc416 --- /dev/null +++ b/source/main.h @@ -0,0 +1,6 @@ +#ifndef __MAIN_H +#define __MAIN_H + + + +#endif diff --git a/source/queue_reference.c b/source/queue_reference.c new file mode 100644 index 0000000..902a536 --- /dev/null +++ b/source/queue_reference.c @@ -0,0 +1,167 @@ +/** + * @file queue_reference.c + * @brief Thread safe queue, which stores void* pointers + * @details Call queue_init(queue_reference_msg_t *q, int max_count) paired with queue_deinit(queue_reference_msg_t *q) as their names imply, queue_initstruct(queue_reference_msg_t *q)Initialize the message queue structure,*queue_get(queue_reference_msg_t *q) and queue_put(queue_reference_msg_t *q, void *data) In and out of the team operation + * @author miaow (3703781@qq.com) + * @version 1.0 + * @date 2021/12/25 merry christmas + * + * @copyright Copyright (c) 2022 miaow + * + * @par Changelog: + * + *
Date Version Author Description + *
2022/01/09 1.0 miaow Write this file + *
+ */ + +#include +#include +#ifdef QUEUE_REFERENCE_DEBUG +#include +#include +#endif +#include + +/** + * @brief Take out the first item from the circular queue + * @param q The queue handler + * @return A pointer to the item + */ +void *queue_reference_get(queue_reference_msg_t *q) +{ + void *data = NULL; + pthread_mutex_lock(&q->_mux); + // while (q->lget == q->lput && 0 == q->nData) + // { + // // The reason program goes here: assuming there are 2 consmer threads block in this function + // // One wakes first and consumes 2 data quickly before another wakes + // // In the circumstances that the queue contains 2 items formerly, the second thread should not get data from an empty queue + // // This may happen when 2 queue_puts was called by producers and at that moment 2 consmer threads have been blocked + + // // It is designed as a circular queue, where lget==lput means: + // // 1:nData!=0,a full queue + // // 2:nData为0,an empty queue + // q->nEmptyThread++; + // pthread_cond_wait(&q->_cond_get, &q->_mux); + // q->nEmptyThread--; + // } + if (q->nData == 0) + { + pthread_mutex_unlock(&q->_mux); + return NULL; + } +#ifdef QUEUE_REFERENCE_DEBUG + printf("get data! lget:%d, ", q->lget); +#endif + data = (q->buffer)[q->lget++]; +#ifdef QUEUE_REFERENCE_DEBUG + printf("data:% lld", *((uint64_t *)data)); +#endif + if (q->lget == q->size) + { + // this is a circular queue + q->lget = 0; + } + q->nData--; +#ifdef QUEUE_REFERENCE_DEBUG + printf(", nData:%d\r\n", q->nData); +#endif + // if (q->nFullThread) + // { + // // call pthread_cond_signal only when necessary, enter the kernel state as little as possible + // pthread_cond_signal(&q->_cond_put); + // } + pthread_mutex_unlock(&q->_mux); + return data; +} + +/** + * @brief Initialize the queue with a size (maximum count of items) specified in q->size + * @param q The queue hander to be initialized + * @return 0 - success, -1 - failed + * @note q->size should be set before calling this function + */ +int queue_reference_initstruct(queue_reference_msg_t *q) +{ + q->buffer = malloc(q->size * sizeof(void *)); + if (q->buffer == NULL) + return -1; + pthread_mutex_init(&q->_mux, NULL); + // pthread_cond_init(&q->_cond_get, NULL); + // pthread_cond_init(&q->_cond_put, NULL); + return 0; +} + +/** + * @brief Initialize the queue + * @param q The queue hander to be initialized + * @param max_count Maximum count of items in the queue + * @return 0 - success, -1 - failed + */ +int queue_reference_init(queue_reference_msg_t *q, int max_count) +{ + q->size = max_count; + return queue_reference_initstruct(q); +} + +/** + * @brief Deinitialize the queue + * @param q The queue handle + * @return 0 - success + */ +int queue_reference_deinit(queue_reference_msg_t *q) +{ + free(q->buffer); + q->buffer = NULL; + pthread_mutex_destroy(&q->_mux); + // pthread_cond_destroy(&q->_cond_get); + // pthread_cond_destroy(&q->_cond_put); + q->size = 0; + q->nData = 0; + q->lget = 0; + q->lput = 0; + // q->nEmptyThread = 0; + // q->nFullThread = 0; + return 0; +} + +/** + * @brief Put one item into the circular queue + * @param q The queue handle + * @param data A pointer to the item + * @return 0 - success, -1 - failed + */ +int queue_reference_put(queue_reference_msg_t *q, void *data) +{ + pthread_mutex_lock(&q->_mux); + // while (q->lget == q->lput && q->nData) + // { + // q->nFullThread++; + // pthread_cond_wait(&q->_cond_put, &q->_mux); + // q->nFullThread--; + // } + if (q->lget == q->lput && q->nData) + { + pthread_mutex_unlock(&q->_mux); + return -1; + } +#ifdef QUEUE_REFERENCE_DEBUG + printf("put data! lput:%d, data:%lld", q->lput, *((uint64_t *)data)); +#endif + (q->buffer)[q->lput++] = data; + if (q->lput == q->size) + { + q->lput = 0; + } + q->nData++; +#ifdef QUEUE_REFERENCE_DEBUG + printf(" nData:%d\n", q->nData); +#endif + // if (q->nEmptyThread) + // { + // pthread_cond_signal(&q->_cond_get); + // } + pthread_mutex_unlock(&q->_mux); + return 0; +} \ No newline at end of file diff --git a/source/queue_reference.h b/source/queue_reference.h new file mode 100644 index 0000000..958450a --- /dev/null +++ b/source/queue_reference.h @@ -0,0 +1,46 @@ +/** + * @file queue_reference.h + * @brief Thread safe queue, which stores void pointers + * @details Call queue_init(queue_reference_msg_t *q, int max_count) paired with queue_deinit(queue_reference_msg_t *q) as their names imply, queue_initstruct(queue_reference_msg_t *q)Initialize the message queue structure,*queue_get(queue_reference_msg_t *q) and queue_put(queue_reference_msg_t *q, void *data) In and out of the team operation + * @author miaow (3703781@qq.com) + * @version 1.0 + * @date 2021/12/25 merry christmas + * + * @copyright Copyright (c) 2022 miaow + * + * @par Changelog: + * + *
Date Version Author Description + *
2022/01/09 1.0 miaow Write this file + *
+ */ +#if !defined(__QUEUE_REFERENCE_H) +#define __QUEUE_REFERENCE_H + +#include + +/** + * @brief Queue handle structure + */ +typedef struct +{ + void **buffer; // 缓冲数据, .buffer = msg + int size; // 队列大小,使用的时候给出稍大的size,可以减少进入内核态的操作 + int lget; // 取队列数据的偏移量 + int lput; // 放队列数据的偏移量 + int nData; // 队列中数据的个数,用来判断队列满/空 + // int nFullThread; // 由于队列满而阻塞在put_queue的线程个数 + // int nEmptyThread; // 由于队列空而阻塞在get_queue的线程个数 + pthread_mutex_t _mux; + // pthread_cond_t _cond_get, _cond_put; +} queue_reference_msg_t; + +// #define QUEUE_REFERENCE_DEBUG + +void *queue_reference_get(queue_reference_msg_t *q); +int queue_reference_put(queue_reference_msg_t *q, void *data); +int queue_reference_initstruct(queue_reference_msg_t *q); +int queue_reference_init(queue_reference_msg_t *q, int max_count); +int queue_reference_deinit(queue_reference_msg_t *q); + +#endif // __QUEUE_REFERENCE_H diff --git a/source/queue_uint64.c b/source/queue_uint64.c new file mode 100644 index 0000000..7ad5d0a --- /dev/null +++ b/source/queue_uint64.c @@ -0,0 +1,195 @@ + +/** + * @file queue_uint64.c + * @brief Thread safe queue, which stores uint64_t + * @details Call queue_init(queue_uint64_msg_t *q, int max_count) paired with queue_deinit(queue_uint64_msg_t *q) as their names imply, queue_initstruct(queue_uint64_msg_t *q)Initialize the message queue structure,*queue_get(queue_uint64_msg_t *q) and queue_put(queue_uint64_msg_t *q, void *data) In and out of the team operation + * @author miaow (3703781@qq.com) + * @version 1.0 + * @date 2021/01/10 + * + * @copyright Copyright (c) 2022 miaow + * + * @par Changelog: + * + *
Date Version Author Description + *
2022/01/09 1.0 miaow Write this file + *
+ */ + +#include +#include +#ifdef QUEUE_UINT64_DEBUG +#include +#include +#endif +#include + +/** + * @brief Take out the first item from the circular queue + * @param q The queue handler + * @param data A buffer of uint64_t[1] to store the data taken out + * @return 0 - success, -1 - failed + */ +int queue_uint64_get(queue_uint64_msg_t *q, uint64_t *data) +{ + pthread_mutex_lock(&q->_mux); + // while (q->lget == q->lput && 0 == q->nData) + // { + // // The reason program goes here: assuming there are 2 consmer threads block in this function + // // One wakes first and consumes 2 data quickly before another wakes + // // In the circumstances that the queue contains 2 items formerly, the second thread should not get data from an empty queue + // // This may happen when 2 queue_puts was called by producers and at that moment 2 consmer threads have been blocked + + // // It is designed as a circular queue, where lget==lput means: + // // 1:nData!=0,a full queue + // // 2:nData为0,an empty queue + // q->nEmptyThread++; + // pthread_cond_wait(&q->_cond_get, &q->_mux); + // q->nEmptyThread--; + // } + if (q->nData == 0) + { + pthread_mutex_unlock(&q->_mux); + return -1; + } +#ifdef QUEUE_UINT64_DEBUG + printf("get data! lget:%d, ", q->lget); +#endif + *data = (q->buffer)[q->lget++]; +#ifdef QUEUE_UINT64_DEBUG + printf("data:% lld", *data); +#endif + if (q->lget == q->size) + { + // this is a circular queue + q->lget = 0; + } + q->nData--; +#ifdef QUEUE_UINT64_DEBUG + printf(", nData:%d\r\n", q->nData); +#endif + // if (q->nFullThread) + // { + // // call pthread_cond_signal only when necessary, enter the kernel state as little as possible + // pthread_cond_signal(&q->_cond_put); + // } + pthread_mutex_unlock(&q->_mux); + return 0; +} + +/** + * @brief Initialize the queue with a size (maximum count of items) specified in q->size + * @param q The queue hander to be initialized + * @return 0 - success, -1 - failed + * @note q->size should be set before calling this function + */ +int queue_uint64_initstruct(queue_uint64_msg_t *q) +{ + q->buffer = malloc(q->size * sizeof(uint64_t)); + if (q->buffer == NULL) + return -1; + pthread_mutex_init(&q->_mux, NULL); + // pthread_cond_init(&q->_cond_get, NULL); + // pthread_cond_init(&q->_cond_put, NULL); + return 0; +} + +/** + * @brief Initialize the queue + * @param q The queue hander to be initialized + * @param max_count Maximum count of items in the queue + * @return 0 - success, -1 - failed + */ +int queue_uint64_init(queue_uint64_msg_t *q, int max_count) +{ + q->size = max_count; + return queue_uint64_initstruct(q); +} + +/** + * @brief Deinitialize the queue + * @param q The queue handle + * @return 0 - success + */ +int queue_uint64_deinit(queue_uint64_msg_t *q) +{ + free(q->buffer); + q->buffer = NULL; + pthread_mutex_destroy(&q->_mux); + // pthread_cond_destroy(&q->_cond_get); + // pthread_cond_destroy(&q->_cond_put); + q->size = 0; + q->nData = 0; + q->lget = 0; + q->lput = 0; + // q->nEmptyThread = 0; + // q->nFullThread = 0; + return 0; +} + +/** + * @brief Put one item into the circular queue + * @param q The queue handle + * @param data The item to put + * @return 0 - success, -1 - failed + */ +int queue_uint64_put(queue_uint64_msg_t *q, uint64_t data) +{ + pthread_mutex_lock(&q->_mux); + // while (q->lget == q->lput && q->nData) + // { + // q->nFullThread++; + // pthread_cond_wait(&q->_cond_put, &q->_mux); + // q->nFullThread--; + // } + if (q->lget == q->lput && q->nData) + { + pthread_mutex_unlock(&q->_mux); + return -1; + } +#ifdef QUEUE_UINT64_DEBUG + printf("put data! lput:%d, data:%lld", q->lput, data); +#endif + (q->buffer)[q->lput++] = data; + if (q->lput == q->size) + { + q->lput = 0; + } + q->nData++; +#ifdef QUEUE_UINT64_DEBUG + printf(" nData:%d\n", q->nData); +#endif + // if (q->nEmptyThread) + // { + // pthread_cond_signal(&q->_cond_get); + // } + pthread_mutex_unlock(&q->_mux); + return 0; +} + +/** + * @brief Clear the circular queue + * @param q The queue handle + * @return 0 - success, -1 - failed + */ +int queue_uint64_clear(queue_uint64_msg_t *q) +{ + pthread_mutex_lock(&q->_mux); + // while (q->lget == q->lput && q->nData) + // { + // q->nFullThread++; + // pthread_cond_wait(&q->_cond_put, &q->_mux); + // q->nFullThread--; + // } + q->lget = q->lput = q->nData = 0; +#ifdef QUEUE_UINT64_DEBUG + printf("clear!\r\n"); +#endif + + // if (q->nEmptyThread) + // { + // pthread_cond_signal(&q->_cond_get); + // } + pthread_mutex_unlock(&q->_mux); + return 0; +} \ No newline at end of file diff --git a/source/queue_uint64.h b/source/queue_uint64.h new file mode 100644 index 0000000..80eed69 --- /dev/null +++ b/source/queue_uint64.h @@ -0,0 +1,48 @@ +/** + * @file queue_uint64.h + * @brief Thread safe queue, which stores uint64_t + * @details Call queue_init(queue_uint64_msg_t *q, int max_count) paired with queue_deinit(queue_uint64_msg_t *q) as their names imply, queue_initstruct(queue_uint64_msg_t *q)Initialize the message queue structure,*queue_get(queue_uint64_msg_t *q) and queue_put(queue_uint64_msg_t *q, void *data) In and out of the team operation + * @author miaow (3703781@qq.com) + * @version 1.0 + * @date 2021/01/10 + * + * @copyright Copyright (c) 2022 miaow + * + * @par Changelog: + * + *
Date Version Author Description + *
2022/01/09 1.0 miaow Write this file + *
+ */ +#if !defined(__QUEUE_UINT64_H) +#define __QUEUE_UINT64_H + +#include +#include + +/** + * @brief Queue handle structure + */ +typedef struct +{ + uint64_t *buffer; // 缓冲数据, .buffer = msg + int size; // 队列大小,使用的时候给出稍大的size,可以减少进入内核态的操作 + int lget; // 取队列数据的偏移量 + int lput; // 放队列数据的偏移量 + int nData; // 队列中数据的个数,用来判断队列满/空 + // int nFullThread; // 由于队列满而阻塞在put_queue的线程个数 + // int nEmptyThread; // 由于队列空而阻塞在get_queue的线程个数 + pthread_mutex_t _mux; + // pthread_cond_t _cond_get, _cond_put; +} queue_uint64_msg_t; + +// #define QUEUE_UINT64_DEBUG + +int queue_uint64_get(queue_uint64_msg_t *q, uint64_t *data); +int queue_uint64_put(queue_uint64_msg_t *q, uint64_t data); +int queue_uint64_initstruct(queue_uint64_msg_t *q); +int queue_uint64_init(queue_uint64_msg_t *q, int max_count); +int queue_uint64_clear(queue_uint64_msg_t *q); +int queue_uint64_deinit(queue_uint64_msg_t *q); + +#endif // __QUEUE_UINT64_H diff --git a/source/valve.c b/source/valve.c new file mode 100644 index 0000000..260132c --- /dev/null +++ b/source/valve.c @@ -0,0 +1,212 @@ +/** + * @file valve.c + * @brief Operate the valveboard with Linux application + * @details Call valve_init() paired with valve_deinit() as their names imply, valve_send() can be executed several times to operate up to 6 valveboards between valve_init() and valve_deinit() + * @mainpage github.com/NanjingForestryUniversity + * @author miaow + * @email 3703781@qq.com + * @version v0.9.0 + * @date 2021/12/25 merry christmas + */ + +#include +#include +#include +#include + + +// Write to the file desc (global variable `gpo_value_fd` in gpio_common.c) to operate a gpio. +// So gpo_value_fd should be initialized in valve_init with great care. +// Also, gpo_value_fd/gpi_value_fd is used in other .c files (read pluse of encoder, etc). +#define __GPO_SET_BIT(pin_t) __GPO_SET(pin_t, GPIO_VALUE_HIGH) +#define __GPO_CLR_BIT(pin_t) __GPO_SET(pin_t, GPIO_VALUE_LOW) +#define __GPO_SET(pin_t, value_t) write(gpo_value_fd[GPIO_PINDEF_TO_INDEX(pin_t)], gpio_pin_value_str[GPIO_VALUEDEF_TO_INDEX(value_t)], gpio_pin_value_str_len[GPIO_VALUEDEF_TO_INDEX(value_t)]) + +typedef struct +{ + int need_send; // Set this variable to 1 will cause a packet of sending + pthread_mutex_t need_send_mutex; + uint64_t data[6]; // Encoded data for sending + pthread_mutex_t data_mutex; // don't use, use need_send_mutex instead + int need_exit; // loop_thread joins to parent-thread at need_exit==1 + pthread_mutex_t need_exit_mutex; // don't use, use need_send_mutex instead + pthread_t loop_thread; // The sending thread + pthread_cond_t is_sending; +} valve_global_t; + +static valve_global_t _global_structure; +valve_pin_enum_t valveboard_x_sdata[] = {VALVE_SDATA_1, VALVE_SDATA_2, VALVE_SDATA_3, VALVE_SDATA_4, VALVE_SDATA_5, VALVE_SDATA_6}; + +static const int _delay = 1000 / SCLK_FREQUENCE_KHZ + 1; +static const int _delay_on_2 = 500 / SCLK_FREQUENCE_KHZ + 1; + +extern int delay_us(int us); +static void *loop_thread_func(void *param); + +/** + * @brief Initialize valve-related gpos and start loop_thread which keeps communicating with valveboards, SEN/SCLK/SDATA1/SDATA2/SDATA3/SDATA4/SDATA5/SDATA6 + * @return 0 - success, -1 - error + */ +int valve_init() +{ + //打开GPIO + int fd_export = open(GPIO_EXPORT_PATH, O_WRONLY); + ON_ERROR_RET(fd_export, GPIO_EXPORT_PATH, "export in valve_init()", -1); + for (int i = 0; i < 6; i++) + { + if (is_file_exist(gpio_value_file_gpo_list[i])) + continue; + int ret = write(fd_export, gpo_pin_str[i], gpo_pin_str_len[i]); + ON_ERROR_RET(ret, gpo_pin_str[i], "open value file in valve_init()", -1); + } + for (int i = 0; i < 6; i++) + { + gpo_value_fd[i] = open(gpio_value_file_gpo_list[i], O_RDWR); + ON_ERROR_RET(gpo_value_fd[i], gpio_value_file_gpo_list[i], "open value file in valve_init()", -1); + } + + close(fd_export); + pthread_mutex_init(&_global_structure.need_send_mutex, NULL); + pthread_mutex_init(&_global_structure.data_mutex, NULL); + pthread_mutex_init(&_global_structure.need_exit_mutex, NULL); + pthread_cond_init(&_global_structure.is_sending, NULL); + + int ret = pthread_create(&_global_structure.loop_thread, NULL, loop_thread_func, NULL); + ON_ERROR_RET(ret, "thread create error in valve_init()", "", -1); + + return 0; +} + +/** + * @brief This function runs in child thread and handles communication with valveboard + */ +void *loop_thread_func(void *param) +{ + printf("loop_thread in %s start\r\n", __FILE__); + int need_exit = 0; + while (!need_exit) + { + pthread_mutex_lock(&_global_structure.need_send_mutex); + + + if (_global_structure.need_send == 0) + { + __GPO_CLR_BIT(VALVE_SCLK); + delay_us(_delay); + __GPO_SET_BIT(VALVE_SCLK); + delay_us(_delay); + } + else + { + int i = 48; + delay_us(_delay_on_2); + __GPO_SET_BIT(VALVE_SEN); + while (i--) + { + __GPO_CLR_BIT(VALVE_SCLK); + delay_us(_delay_on_2); + __GPO_SET(VALVE_SDATA_1, (_global_structure.data[0] & 1UL)); + __GPO_SET(VALVE_SDATA_2, (_global_structure.data[1] & 1UL)); + __GPO_SET(VALVE_SDATA_3, (_global_structure.data[2] & 1UL)); + __GPO_SET(VALVE_SDATA_4, (_global_structure.data[3] & 1UL)); + // __GPO_SET(VALVE_SDATA_5, (_global_structure.data[4] & 1UL)); + // __GPO_SET(VALVE_SDATA_6, (_global_structure.data[5] & 1UL)); + _global_structure.data[0] >>= 1; + _global_structure.data[1] >>= 1; + _global_structure.data[2] >>= 1; + _global_structure.data[3] >>= 1; + // _global_structure.data[4] >>= 1; + // _global_structure.data[5] >>= 1; + delay_us(_delay_on_2); + __GPO_SET_BIT(VALVE_SCLK); + delay_us(_delay); + } + __GPO_CLR_BIT(VALVE_SEN); + _global_structure.need_send = 0; + pthread_cond_signal(&_global_structure.is_sending); + } + + // pthread_mutex_lock(&_global_structure.need_exit_mutex); + need_exit = _global_structure.need_exit; + // pthread_mutex_unlock(&_global_structure.need_exit_mutex); + + pthread_mutex_unlock(&_global_structure.need_send_mutex); + } + printf("loop_thread in %s exit\r\n", __FILE__); + return NULL; +} + +/** + * @brief Set valve value in forms of array. + * @param valve_data An array with size of 6, + * for example, valve_data[0]=64'h0000_FFFF_FFFF_FFFF represents the first valveboard all on + * valve_data[5]=64'h0000_0000_0000_0001 represents the last valveboard turn on its first valve + * @return 0 - success, -1 - error + */ +int valve_send(uint64_t *valve_data) +{ + pthread_mutex_lock(&_global_structure.need_send_mutex); + while (_global_structure.need_send == 1) + pthread_cond_wait(&_global_structure.is_sending, &_global_structure.need_send_mutex); + + for (int i = 0; i < 6; i++) + { + _global_structure.data[i] = ~valve_data[i]; // 1 represents on in parameter of this function while off when putting data on the bus + } + _global_structure.need_send = 1; // Set this variable to 1 will cause a sending packet + pthread_mutex_unlock(&_global_structure.need_send_mutex); + return 0; +} + +/** + * @brief Set valve value in forms of struct. + * @param valve_data the valve_data struct + * @return 0 - success, -1 - error + */ +int valve_sendmsg(valvedata_t *valve_data) +{ + pthread_mutex_lock(&_global_structure.need_send_mutex); + while (_global_structure.need_send == 1) + pthread_cond_wait(&_global_structure.is_sending, &_global_structure.need_send_mutex); + + _global_structure.data[0] = ~valve_data->valvedata_1; // 1 represents on in parameter of this function while off when putting data on the bus + _global_structure.data[1] = ~valve_data->valvedata_2; + _global_structure.data[2] = ~valve_data->valvedata_3; + _global_structure.data[3] = ~valve_data->valvedata_4; + _global_structure.data[4] = ~valve_data->valvedata_5; + _global_structure.data[5] = ~valve_data->valvedata_6; + + _global_structure.need_send = 1; // Set this variable to 1 will cause a sending packet + pthread_mutex_unlock(&_global_structure.need_send_mutex); + return 0; +} + +/** + * @brief Deinitialize and turn off all the valve. + * @note This function DOES BLOCKS 100000 us at least and DOES NOT UNEXPORT gpos + * @return 0 - success, -1 - error + */ +int valve_deinit() +{ + uint64_t tmp[6] = {0}; + valve_send(tmp); + usleep(100000); + pthread_mutex_lock(&_global_structure.need_send_mutex); + _global_structure.need_exit = 1; + pthread_mutex_unlock(&_global_structure.need_send_mutex); + pthread_join(_global_structure.loop_thread, NULL); + pthread_mutex_destroy(&_global_structure.need_exit_mutex); + pthread_mutex_destroy(&_global_structure.need_send_mutex); + pthread_mutex_destroy(&_global_structure.data_mutex); + pthread_cond_destroy(&_global_structure.is_sending); + memset((void *)_global_structure.data, 0, sizeof(_global_structure.data)); + _global_structure.need_exit = 0; + _global_structure.need_send = 0; + + for (int i = 0; i < 6; i++) + { + int ret = close(gpo_value_fd[i]); + ON_ERROR_RET(ret, "close value file in valve_deinit()", "", -1); + } + return 0; +} \ No newline at end of file diff --git a/source/valve.h b/source/valve.h new file mode 100644 index 0000000..4e1f710 --- /dev/null +++ b/source/valve.h @@ -0,0 +1,35 @@ +#ifndef __VALVE_INIT_H +#define __VALVE_INIT_H +#include + +typedef enum +{ + VALVE_SEN=GPIO_PINDEF_TO_INDEX(GPO1), + VALVE_SCLK=GPIO_PINDEF_TO_INDEX(GPO2), + VALVE_SDATA_1=GPIO_PINDEF_TO_INDEX(GPO0), + VALVE_SDATA_2=GPIO_PINDEF_TO_INDEX(GPO3), + VALVE_SDATA_3=GPIO_PINDEF_TO_INDEX(GPO4), + VALVE_SDATA_4=GPIO_PINDEF_TO_INDEX(GPO5), + VALVE_SDATA_5=GPIO_PINDEF_TO_INDEX(GPO6), + VALVE_SDATA_6=GPIO_PINDEF_TO_INDEX(GPO7) +}valve_pin_enum_t; + +typedef struct +{ + uint64_t valvedata_1; + uint64_t valvedata_2; + uint64_t valvedata_3; + uint64_t valvedata_4; + uint64_t valvedata_5; + uint64_t valvedata_6; +} valvedata_t; + + +#define SCLK_FREQUENCE_KHZ 10000 + +int valve_init(void); +int valve_send(uint64_t* valve_data); +int valve_deinit(void); +int valve_sendmsg(valvedata_t* valve_data); + +#endif \ No newline at end of file