commit 4dee2856b3889d882f932b94600317396be93077 Author: Bruno Rybársky Date: Thu Feb 27 21:51:10 2025 +0100 init diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..f912847 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "windows-gcc-x86", + "includePath": [ + "${workspaceFolder}/**" + ], + "compilerPath": "C:/MinGW/bin/gcc.exe", + "cStandard": "${default}", + "cppStandard": "${default}", + "intelliSenseMode": "windows-gcc-x86", + "compilerArgs": [ + "" + ] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e7984ff --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "C/C++ Runner: Debug Session", + "type": "cppdbg", + "request": "launch", + "args": [], + "stopAtEntry": false, + "externalConsole": true, + "cwd": "d:/Users/micha/Desktop/Projects/CanSat/Programming/lepton_2", + "program": "d:/Users/micha/Desktop/Projects/CanSat/Programming/lepton_2/build/Debug/outDebug", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3e5eb95 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,59 @@ +{ + "C_Cpp_Runner.cCompilerPath": "gcc", + "C_Cpp_Runner.cppCompilerPath": "g++", + "C_Cpp_Runner.debuggerPath": "gdb", + "C_Cpp_Runner.cStandard": "", + "C_Cpp_Runner.cppStandard": "", + "C_Cpp_Runner.msvcBatchPath": "", + "C_Cpp_Runner.useMsvc": false, + "C_Cpp_Runner.warnings": [ + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wshadow", + "-Wformat=2", + "-Wcast-align", + "-Wconversion", + "-Wsign-conversion", + "-Wnull-dereference" + ], + "C_Cpp_Runner.msvcWarnings": [ + "/W4", + "/permissive-", + "/w14242", + "/w14287", + "/w14296", + "/w14311", + "/w14826", + "/w44062", + "/w44242", + "/w14905", + "/w14906", + "/w14263", + "/w44265", + "/w14928" + ], + "C_Cpp_Runner.enableWarnings": true, + "C_Cpp_Runner.warningsAsError": false, + "C_Cpp_Runner.compilerArgs": [], + "C_Cpp_Runner.linkerArgs": [], + "C_Cpp_Runner.includePaths": [], + "C_Cpp_Runner.includeSearch": [ + "*", + "**/*" + ], + "C_Cpp_Runner.excludeSearch": [ + "**/build", + "**/build/**", + "**/.*", + "**/.*/**", + "**/.vscode", + "**/.vscode/**" + ], + "C_Cpp_Runner.useAddressSanitizer": false, + "C_Cpp_Runner.useUndefinedSanitizer": false, + "C_Cpp_Runner.useLeakSanitizer": false, + "C_Cpp_Runner.showCompilationTime": false, + "C_Cpp_Runner.useLinkTimeOptimization": false, + "C_Cpp_Runner.msvcSecureNoWarnings": false +} \ No newline at end of file diff --git a/lepton_img2serial.ino b/lepton_img2serial.ino new file mode 100644 index 0000000..e7ee3af --- /dev/null +++ b/lepton_img2serial.ino @@ -0,0 +1,182 @@ +#include +#include "esp_system.h" + +#include "src/lipton/cci.h" +#include "src/lipton/vospi.h" +#include "src/lipton/lepton_system.h" +#include "src/lipton/lepton_utilities.h" + + +#include "mbedtls/base64.h" + + +// +// LEP Task constants +// + +// Number of consecutive VoSPI resynchronization attempts before attempting to reset +#define LEP_SYNC_FAIL_FAULT_LIMIT 10 + +// Reset fail delay before attempting a re-init (seconds) +#define LEP_RESET_FAIL_RETRY_SECS 5 + + +// +// picture functions +// +char* base64_encode_lep_image(uint16_t* lep_bufferP, size_t* base64_length) { + size_t base64_obj_len = 0; + char* base64_lep_data = NULL; + + // Get the necessary length for base64 encoding + mbedtls_base64_encode(NULL, 0, &base64_obj_len, (const unsigned char*)lep_bufferP, LEP_NUM_PIXELS * 2); + + // Allocate memory for the base64 encoded data + base64_lep_data = (char*)malloc(base64_obj_len); + if (base64_lep_data == NULL) { + return NULL; // Memory allocation failed + } + + // Base64 encode the camera data + if (mbedtls_base64_encode((unsigned char*)base64_lep_data, base64_obj_len, &base64_obj_len, (const unsigned char*)lep_bufferP, LEP_NUM_PIXELS * 2) != 0) { + free(base64_lep_data); + return NULL; // Encoding failed + } + + *base64_length = base64_obj_len; + + return base64_lep_data; +} + +void pic_to_uart(uint16_t* pic_buf) { + size_t base64_length = 0; + char* base64_encoded_data = base64_encode_lep_image(pic_buf, &base64_length); + + if (base64_encoded_data != NULL) { + // Output the base64 encoded string and its length + printf("data:base64,%s\n", base64_encoded_data); + + // Remember to free the allocated memory + free(base64_encoded_data); + } +} + +// +// Code start +// +int vsync_count = 0; +int sync_fail_count = 0; +int reset_fail_count = 0; +int64_t vsyncDetectedUsec; + +void setup() { + printf("[MAIN] Start task\n"); + + LEP_CONFIG = { + .agc_set_enabled = false, + .emissivity = 100, + .gain_mode = LEP_GAIN_AUTO + }; + + // initialize spi, i2c and gpio for lepton + if (!lepton_io_init()) { + printf("[MAIN] Error: I/O init failed"); + while(1) {delay(100);} + } + + // allocate lepton buffers + if (!lepton_buffer_init()) { + printf("[MAIN] Error: Memory init failed"); + while(1) {delay(100);} + } + + // wait for lepton to initialize + delay(1000); + + // Attempt to initialize the VoSPI interface + if (vospi_init() != ESP_OK) { + printf("[MAIN] Error: Lepton VoSPI initialization failed\n"); + while(1) {delay(100);} + } + + // initialize lepton + if (!lepton_init()) { + printf("[MAIN] Error: Lepton CCI initialization failed\n"); + while(1) {delay(100);} + } + + // disable data from lepton + digitalWrite(LEP_CSN_PIN, 1); +} + +uint16_t* take_picture() { + digitalWrite(LEP_CSN_PIN, 0); + delay(10); + + while (1) { + // Spin waiting for vsync to be asserted + int t = 0; + while (digitalRead(LEP_VSYNC_PIN) == 0 && t++ < 500) {} + + vsyncDetectedUsec = esp_timer_get_time(); + + // Attempt to process a segment + if (t >= 500 || vospi_transfer_segment(vsyncDetectedUsec)) { + // Got image + vsync_count = 0; + + // Copy the frame to the current half of the shared buffer and let rsp_task know + vospi_get_frame(&rsp_lep_buffer); + + // Hold fault counters reset while operating + sync_fail_count = 0; + + // disable lepton again + digitalWrite(LEP_CSN_PIN, 1); + return rsp_lep_buffer.lep_bufferP; + } else { + if (++vsync_count >= 36) { + vsync_count = 0; + printf("[MAIN] Could not get lepton image\n"); + + delay(185); + + if (sync_fail_count++ == LEP_SYNC_FAIL_FAULT_LIMIT) { + return nullptr; + } + } + } + } + + return nullptr; +} + +int lepton_restart() { + while (1) { + delay(LEP_RESET_FAIL_RETRY_SECS*1000); + + // Assert hardware reset + digitalWrite(LEP_RESET_PIN, LEP_RESET_ON); + delay(10); + digitalWrite(LEP_RESET_PIN, LEP_RESET_OFF); + + + // Delay for Lepton internal initialization (max 950 mSec) + delay(1000); + + // if successfully initialized, break out + if (lepton_init()) + break; + } +} + +void loop() { + delay(5000); + + uint16_t* picture = take_picture(); + if (picture) { + pic_to_uart(picture); + } else { + lepton_restart(); + } +} diff --git a/src/lipton/cci.cpp b/src/lipton/cci.cpp new file mode 100644 index 0000000..9100263 --- /dev/null +++ b/src/lipton/cci.cpp @@ -0,0 +1,794 @@ +/* + * Lepton CCI Module + * + * Contains the functions to configure the Lepton via I2C. + * + * Copyright 2020-2022 Dan Julio + * + * This file is part of tCam. + * + * tCam is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * tCam is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with tCam. If not, see . + * + */ +#include "cci.h" +#include "i2c.h" +#include +#include +#include + +#define CCI_MAX_WAIT_TICKS 5000 + +// +// CCI Variables +// +static bool cci_last_status_error; +static uint16_t cci_last_status; + +// Statically allocated burst read/write I2C buffer sized for up to 512 16-bit words +// plus register starting address +static uint8_t burst_buf[1026]; + + + +// +// Forward declarations for primitive access methods +// +static int cci_write_register(uint16_t reg, uint16_t value); +static int cci_write_burst(uint16_t start, uint16_t word_len, uint16_t* buf); +static uint16_t cci_read_register(uint16_t reg); +static int cci_read_burst(uint16_t start, uint16_t word_len, uint16_t* buf); +static uint32_t cci_wait_busy_clear(); +static void cci_wait_busy_clear_check(char* cmd); + + + +// +// CCI API +// + + +/** + * Write 0 (equivalent to run_cmd) to 512 16-bit words to the lepton and issue + * the specified command. Lengths > 16 words are written to the BLOCK data buffer. + */ +void cci_set_reg(uint16_t cmd, int len, uint16_t* buf) +{ + char cmd_buf[11]; // sized for 'cmd 0xNNNN' + int ret = 1; + + cci_last_status_error = false; + + cci_wait_busy_clear(); + + if ((len > 0) && (len <= 16)) { + ret = cci_write_burst(CCI_REG_DATA_0, len, buf); + } else if ((len > 16) && (len <= 512)) { + ret = cci_write_burst(CCI_BLOCK_BUF_0, len, buf); + } else if (len > 512) { + ret = 0; + } + + if (ret == 1) { + if (len > 0) { + sprintf(cmd_buf, "CMD 0x%4x\n", cmd); + cci_write_register(CCI_REG_DATA_LENGTH, len); + } else { + sprintf(cmd_buf, "RUN 0x%4x\n", cmd); + } + + cci_write_register(CCI_REG_COMMAND, cmd); + cci_wait_busy_clear_check(cmd_buf); + } else { + cci_last_status = 0; + cci_last_status_error = true; + } +} + + +/** + * Read up to 512 16-bit words form the lepton with the specified command. Lengths > 16 + * words are read from the BLOCK data buffer. + */ +void cci_get_reg(uint16_t cmd, int len, uint16_t* buf) +{ + char cmd_buf[11]; // sized for 'cmd 0xNNNN' + + sprintf(cmd_buf, "CMD 0x%4x", cmd); + + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, len); + cci_write_register(CCI_REG_COMMAND, cmd); + cci_wait_busy_clear_check(cmd_buf); + if ((len > 0) && (len <= 16)) { + (void) cci_read_burst(CCI_REG_DATA_0, len, buf); + } else if ((len > 16) && (len <= 512)) { + (void) cci_read_burst(CCI_BLOCK_BUF_0, len, buf); + } +} + + +/** + * Return true if previous command succeeded as detected by cci_wait_busy_clear_check + */ +bool cci_command_success(uint16_t* status) +{ + *status = cci_last_status; + return !cci_last_status_error; +} + + +/** + * Ping the camera. + * Returns 0 for a successful ping + * Returns the absolute (postive) 8-bit non-zero LEP_RESULT for a failure + * Returns 0x100 (256) for a communications failure + */ +uint32_t cci_run_ping() +{ + uint32_t res; + uint8_t lep_res; + + cci_wait_busy_clear(); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_SYS_RUN_PING); + res = cci_wait_busy_clear(); + + lep_res = (res & 0x000FF00) >> 8; // 8-bit Response Error Code: 0=LEP_OK + if (res == 0x00010000) { + return 0x100; + } else if (lep_res == 0x00) { + return 0; + } else { + // Convert negative Lepton Response Error Code to a positive number to return + lep_res = ~lep_res + 1; + return lep_res; + } +} + + +/** + * Request that a flat field correction occur immediately. + */ +void cci_run_ffc() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_SYS_RUN_FFC); + cci_wait_busy_clear_check("CCI_CMD_SYS_RUN_FFC"); +} + + +/** + * Get the system uptime. + */ +uint32_t cci_get_uptime() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_SYS_GET_UPTIME); + cci_wait_busy_clear_check("CCI_CMD_SYS_GET_UPTIME"); + + uint16_t ls_word = cci_read_register(CCI_REG_DATA_0); + uint16_t ms_word = cci_read_register(CCI_REG_DATA_1); + return ms_word << 16 | ls_word; +} + + +/** + * Get the AUX (case) temperature in Kelvin x 100 (16-bit result). + */ +uint32_t cci_get_aux_temp() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_SYS_GET_AUX_TEMP); + cci_wait_busy_clear_check("CCI_CMD_SYS_GET_AUX_TEMP"); + + uint16_t ls_word = cci_read_register(CCI_REG_DATA_0); + uint16_t ms_word = cci_read_register(CCI_REG_DATA_1); + return ms_word << 16 | ls_word; +} + + +/** + * Get the FPA (sensor) temperature in Kelvin x 100 (16-bit result). + */ +uint32_t cci_get_fpa_temp() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_SYS_GET_FPA_TEMP); + cci_wait_busy_clear_check("CCI_CMD_SYS_GET_FPA_TEMP"); + + uint16_t ls_word = cci_read_register(CCI_REG_DATA_0); + uint16_t ms_word = cci_read_register(CCI_REG_DATA_1); + return ms_word << 16 | ls_word; +} + + +/** + * Change the telemetry enable state. + */ +void cci_set_telemetry_enable_state(cci_telemetry_enable_state_t state) +{ + uint32_t value = state; + + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_0, value & 0xffff); + cci_write_register(CCI_REG_DATA_1, value >> 16 & 0xffff); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_SYS_SET_TELEMETRY_ENABLE_STATE); + cci_wait_busy_clear_check("CCI_CMD_SYS_SET_TELEMETRY_ENABLE_STATE"); +} + + +/** + * Get the telemetry enable state. + */ +uint32_t cci_get_telemetry_enable_state() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_SYS_GET_TELEMETRY_ENABLE_STATE); + cci_wait_busy_clear_check("CCI_CMD_SYS_GET_TELEMETRY_ENABLE_STATE"); + + uint16_t ls_word = cci_read_register(CCI_REG_DATA_0); + uint16_t ms_word = cci_read_register(CCI_REG_DATA_1); + return ms_word << 16 | ls_word; +} + + +/** + * Change the telemetry location. + */ +void cci_set_telemetry_location(cci_telemetry_location_t location) +{ + uint32_t value = location; + + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_0, value & 0xffff); + cci_write_register(CCI_REG_DATA_1, value >> 16 & 0xffff); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_SYS_SET_TELEMETRY_LOCATION); + cci_wait_busy_clear_check("CCI_CMD_SYS_SET_TELEMETRY_LOCATION"); +} + + +/** + * Get the telemetry location. + */ +uint32_t cci_get_telemetry_location() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_SYS_GET_TELEMETRY_LOCATION); + cci_wait_busy_clear_check("CCI_CMD_SYS_GET_TELEMETRY_LOCATION"); + + uint16_t ls_word = cci_read_register(CCI_REG_DATA_0); + uint16_t ms_word = cci_read_register(CCI_REG_DATA_1); + return ms_word << 16 | ls_word; +} + + +/** + * Get the Gain Mode + */ +void cci_set_gain_mode(cc_gain_mode_t mode) +{ + uint32_t value = mode; + + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_0, value & 0xffff); + cci_write_register(CCI_REG_DATA_1, value >> 16 & 0xffff); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_SYS_SET_GAIN_MODE); + cci_wait_busy_clear_check("CCI_CMD_SYS_SET_GAIN_MODE"); +} + + +/** + * Set the gain mode + */ +uint32_t cci_get_gain_mode() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_SYS_GET_GAIN_MODE); + cci_wait_busy_clear_check("CCI_CMD_SYS_GET_GAIN_MODE"); + + uint16_t ls_word = cci_read_register(CCI_REG_DATA_0); + uint16_t ms_word = cci_read_register(CCI_REG_DATA_1); + return ms_word << 16 | ls_word; +} + + +/** + * Change the radiometry enable state. + */ +void cci_set_radiometry_enable_state(cci_radiometry_enable_state_t state) +{ + uint32_t value = state; + + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_0, value & 0xffff); + cci_write_register(CCI_REG_DATA_1, value >> 16 & 0xffff); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_RAD_SET_RADIOMETRY_ENABLE_STATE); + cci_wait_busy_clear_check("CCI_CMD_RAD_SET_RADIOMETRY_ENABLE_STATE"); +} + + +/** + * Get the radiometry enable state. + */ +uint32_t cci_get_radiometry_enable_state() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_RAD_GET_RADIOMETRY_ENABLE_STATE); + cci_wait_busy_clear_check("CCI_CMD_RAD_GET_RADIOMETRY_ENABLE_STATE"); + + uint16_t ls_word = cci_read_register(CCI_REG_DATA_0); + uint16_t ms_word = cci_read_register(CCI_REG_DATA_1); + return ms_word << 16 | ls_word; +} + + +/** + * Set the radiometry flux parameters + */ +void cci_set_radiometry_flux_linear_params(cci_rad_flux_linear_params_t* params) +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_0, params->sceneEmissivity); + cci_write_register(CCI_REG_DATA_1, params->TBkgK); + cci_write_register(CCI_REG_DATA_2, params->tauWindow); + cci_write_register(CCI_REG_DATA_3, params->TWindowK); + cci_write_register(CCI_REG_DATA_4, params->tauAtm); + cci_write_register(CCI_REG_DATA_5, params->TAtmK); + cci_write_register(CCI_REG_DATA_6, params->reflWindow); + cci_write_register(CCI_REG_DATA_7, params->TReflK); + cci_write_register(CCI_REG_DATA_LENGTH, 8); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_RAD_SET_RADIOMETRY_FLUX_LINEAR_PARAMS); + cci_wait_busy_clear_check("CCI_CMD_RAD_SET_RADIOMETRY_FLUX_LINEAR_PARAMS"); +} + + +/** + * Get the radiometry flux parameters + */ +bool cci_get_radiometry_flux_linear_params(cci_rad_flux_linear_params_t* params) +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 8); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_RAD_GET_RADIOMETRY_FLUX_LINEAR_PARAMS); + cci_wait_busy_clear_check("CCI_CMD_RAD_GET_RADIOMETRY_FLUX_LINEAR_PARAMS"); + params->sceneEmissivity = cci_read_register(CCI_REG_DATA_0); + params->TBkgK = cci_read_register(CCI_REG_DATA_1); + params->tauWindow = cci_read_register(CCI_REG_DATA_2); + params->TWindowK = cci_read_register(CCI_REG_DATA_3); + params->tauAtm = cci_read_register(CCI_REG_DATA_4); + params->TAtmK = cci_read_register(CCI_REG_DATA_5); + params->reflWindow = cci_read_register(CCI_REG_DATA_6); + params->TReflK = cci_read_register(CCI_REG_DATA_7); + + return !cci_last_status_error; +} + + +/** + * Change the radiometry TLinear enable state. + */ +void cci_set_radiometry_tlinear_enable_state(cci_radiometry_tlinear_enable_state_t state) +{ + uint32_t value = state; + + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_0, value & 0xffff); + cci_write_register(CCI_REG_DATA_1, value >> 16 & 0xffff); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_RAD_SET_RADIOMETRY_TLINEAR_ENABLE_STATE); + cci_wait_busy_clear_check("CCI_CMD_RAD_SET_RADIOMETRY_TLINEAR_ENABLE_STATE"); +} + + +/** + * Get the radiometry TLinear enable state. + */ +uint32_t cci_get_radiometry_tlinear_enable_state() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_RAD_GET_RADIOMETRY_TLINEAR_ENABLE_STATE); + cci_wait_busy_clear_check("CCI_CMD_RAD_GET_RADIOMETRY_TLINEAR_ENABLE_STATE"); + + uint16_t ls_word = cci_read_register(CCI_REG_DATA_0); + uint16_t ms_word = cci_read_register(CCI_REG_DATA_1); + return ms_word << 16 | ls_word; +} + + +/** + * Set the radiometry TLinear Auto Resolution + */ +void cci_set_radiometry_tlinear_auto_res(cci_radiometry_tlinear_auto_res_state_t state) +{ + uint32_t value = state; + + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_0, value & 0xffff); + cci_write_register(CCI_REG_DATA_1, value >> 16 & 0xffff); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_RAD_SET_RADIOMETRY_TLINEAR_AUTO_RES); + cci_wait_busy_clear_check("CCI_CMD_RAD_SET_RADIOMETRY_TLINEAR_AUTO_RES"); +} + + +/** + * Get the radiometry TLinear Auto Resolution + */ +uint32_t cci_get_radiometry_tlinear_auto_res() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_RAD_GET_RADIOMETRY_TLINEAR_AUTO_RES); + cci_wait_busy_clear_check("CCI_CMD_RAD_GET_RADIOMETRY_TLINEAR_AUTO_RES"); + + uint16_t ls_word = cci_read_register(CCI_REG_DATA_0); + uint16_t ms_word = cci_read_register(CCI_REG_DATA_1); + return ms_word << 16 | ls_word; +} + + +/** + * Set the Radiometry Spotmeter Region-of-interest + */ +void cci_set_radiometry_spotmeter(uint16_t r1, uint16_t c1, uint16_t r2, uint16_t c2) +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_0, r1); + cci_write_register(CCI_REG_DATA_1, c1); + cci_write_register(CCI_REG_DATA_2, r2); + cci_write_register(CCI_REG_DATA_3, c2); + cci_write_register(CCI_REG_DATA_LENGTH, 4); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_RAD_SET_RADIOMETRY_SPOT_ROI); + cci_wait_busy_clear_check("CCI_CMD_RAD_SET_RADIOMETRY_SPOT_ROI"); +} + + +/** + * Get the Radiometry Spotmeter Region-of-interest + */ +bool cci_get_radiometry_spotmeter(uint16_t* r1, uint16_t* c1, uint16_t* r2, uint16_t* c2) +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 4); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_RAD_GET_RADIOMETRY_SPOT_ROI); + cci_wait_busy_clear_check("CCI_CMD_RAD_GET_RADIOMETRY_SPOT_ROI"); + *r1 = cci_read_register(CCI_REG_DATA_0); + *c1 = cci_read_register(CCI_REG_DATA_1); + *r2 = cci_read_register(CCI_REG_DATA_2); + *c2 = cci_read_register(CCI_REG_DATA_3); + + return !cci_last_status_error; +} + + +/** + * Get the AGC enable state. + */ +uint32_t cci_get_agc_enable_state() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_AGC_GET_AGC_ENABLE_STATE); + cci_wait_busy_clear_check("CCI_CMD_AGC_GET_AGC_ENABLE_STATE"); + + uint16_t ls_word = cci_read_register(CCI_REG_DATA_0); + uint16_t ms_word = cci_read_register(CCI_REG_DATA_1); + return ms_word << 16 | ls_word; +} + + +/** + * Set the AGC enable state. + */ +void cci_set_agc_enable_state(cci_agc_enable_state_t state) +{ + uint32_t value = state; + + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_0, value & 0xffff); + cci_write_register(CCI_REG_DATA_1, value >> 16 & 0xffff); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_AGC_SET_AGC_ENABLE_STATE); + cci_wait_busy_clear_check("CCI_CMD_AGC_SET_AGC_ENABLE_STATE"); +} + + +/** + * Get the AGC calc enable state. + */ +uint32_t cci_get_agc_calc_enable_state() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_AGC_GET_CALC_ENABLE_STATE); + cci_wait_busy_clear_check("CCI_CMD_AGC_GET_CALC_ENABLE_STATE"); + + uint16_t ls_word = cci_read_register(CCI_REG_DATA_0); + uint16_t ms_word = cci_read_register(CCI_REG_DATA_1); + return ms_word << 16 | ls_word; +} + + +/** + * Set the AGC calc enable state. + */ +void cci_set_agc_calc_enable_state(cci_agc_enable_state_t state) +{ + uint32_t value = state; + + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_0, value & 0xffff); + cci_write_register(CCI_REG_DATA_1, value >> 16 & 0xffff); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_AGC_SET_CALC_ENABLE_STATE); + cci_wait_busy_clear_check("CCI_CMD_AGC_SET_CALC_ENABLE_STATE"); +} + +/** + * Run the Reboot command + */ +void cc_run_oem_reboot() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_OEM_RUN_REBOOT); + // Sleep to allow camera to reboot and run FFC + delay(6000); + cci_wait_busy_clear_check("CCI_CMD_OEM_RUN_REBOOT"); +} + + +/** + * Get the GPIO mode. + */ +uint32_t cci_get_gpio_mode() +{ + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_OEM_GET_GPIO_MODE); + cci_wait_busy_clear_check("CCI_CMD_OEM_GET_GPIO_MODE"); + + uint16_t ls_word = cci_read_register(CCI_REG_DATA_0); + uint16_t ms_word = cci_read_register(CCI_REG_DATA_1); + return ms_word << 16 | ls_word; +} + + +/** + * Set the GPIO mode. + */ +void cci_set_gpio_mode(cci_gpio_mode_t mode) +{ + uint32_t value = mode; + + cci_wait_busy_clear(); + cci_write_register(CCI_REG_DATA_0, value & 0xffff); + cci_write_register(CCI_REG_DATA_1, value >> 16 & 0xffff); + cci_write_register(CCI_REG_DATA_LENGTH, 2); + cci_write_register(CCI_REG_COMMAND, CCI_CMD_OEM_SET_GPIO_MODE); + cci_wait_busy_clear_check("CCI_CMD_OEM_SET_GPIO_MODE"); +} + + +/** + * Get the FLIR systems part number + * - call with a 32-character buffer + */ +void cci_get_part_number(char* pn) +{ + bool low_half = true; + int i = 0; + uint16_t cci_buf[16]; + + int t = 0; // maximum tick count + + cci_get_reg(CCI_CMD_OEM_GET_PART_NUM, 16, cci_buf); + + *pn = (char) (cci_buf[0] & 0xFF); + while ((*pn != 0) && (i<16) && (t++ < CCI_MAX_WAIT_TICKS)) { + low_half = !low_half; + if (low_half) { + *(++pn) = (char) (cci_buf[i] & 0xFF); + } else { + *(++pn) = (char) (cci_buf[i] >> 8); + i++; + } + } + *(++pn) = 0; +} + + + +// +// Primitive access methods +// + +/** + * Write a CCI register. + */ +static int cci_write_register(uint16_t reg, uint16_t value) +{ + // Write the register address and value + uint8_t write_buf[4] = { + reg >> 8 & 0xff, + reg & 0xff, + value >> 8 & 0xff, + value & 0xff + }; + if (i2c_master_write_slave(CCI_ADDRESS, write_buf, sizeof(write_buf)) != ESP_OK) { + printf("[CCI] Error: failed to write CCI register %02x with value %02x\n", reg, value); + return -1; + }; + + return 1; +} + + +/** + * Burst write a group of CCI data registers + */ +static int cci_write_burst(uint16_t start, uint16_t word_len, uint16_t* buf) +{ + int i; + + // Create the i2c transaction buffer + burst_buf[0] = start >> 8; + burst_buf[1] = start & 0xFF; + for (i=1; i<=word_len; i++) { + burst_buf[i*2] = *buf >> 8; + burst_buf[i*2 + 1] = *buf++ & 0xFF; + } + + // Execute the burs + if (i2c_master_write_slave(CCI_ADDRESS, burst_buf, word_len*2 + 2) != ESP_OK) { + + printf("[CCI] Error: failed to burst write CCI register %02x with length %d\n", start, word_len); + return -1; + }; + + return 1; +} + + +/** + * Read a CCI register. + */ +static uint16_t cci_read_register(uint16_t reg) +{ + uint8_t buf[2]; + + // Write the register address + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + if (i2c_master_write_slave(CCI_ADDRESS, buf, sizeof(buf)) != ESP_OK) { + printf("[CCI] Error: failed to write CCI register %02x\n", reg); + return -1; + } + + // Read + if (i2c_master_read_slave(CCI_ADDRESS, buf, sizeof(buf)) != ESP_OK) { + printf("[CCI] Error: failed to read from CCI register %02x\n", reg); + } + + return buf[0] << 8 | buf[1]; +} + + +/** + * Burst read a group of CCI data registers + */ +static int cci_read_burst(uint16_t start, uint16_t word_len, uint16_t* buf) +{ + int i; + + // Write the starting address + burst_buf[0] = start >> 8; + burst_buf[1] = start & 0xFF; + if (i2c_master_write_slave(CCI_ADDRESS, burst_buf, 2) != ESP_OK) { + printf("[CCI] Error: failed to write CCI register %02x\n", start); + return -1; + } + + // Read + if (i2c_master_read_slave(CCI_ADDRESS, burst_buf, word_len*2) != ESP_OK) { + printf("[CCI] Error: failed to burst read from CCI register %02x with length %d\n", start, word_len); + return -1; + } + + // Copy data out + for (i=0; i= CCI_MAX_WAIT_TICKS) { + err = true; + break; + } + + // Write STATUS register address + buf[0] = 0x00; + buf[1] = 0x02; + + if (i2c_master_write_slave(CCI_ADDRESS, buf, sizeof(buf)) != ESP_OK) { + printf("[CCI] Error: failed to set STATUS register\n"); + err = true; + }; + + // Read register - low bits in buf[1] + if (i2c_master_read_slave(CCI_ADDRESS, buf, sizeof(buf)) != ESP_OK) { + printf("[CCI] Error: failed to read STATUS register\n"); + err = true; + } + } + + if (err) { + return 0x00010000; + } else { + return (buf[0] << 8) | buf[1]; + } +} + + +/** + * Wait for busy to be clear in the status register and check the result + * printing an error if detected + */ +static void cci_wait_busy_clear_check(char* cmd) +{ + int8_t response; + uint32_t t32; + + cci_last_status_error = false; + + t32 = cci_wait_busy_clear(); + cci_last_status = t32 & 0xFFFF; + if (t32 == 0x00010000) { + printf("[CCI] Error: cmd: %s failed wait_busy_clear\n", cmd); + cci_last_status_error = true; + } else { + response = (int8_t) ((t32 & 0x0000FF00) >> 8); + if (response < 0) { + printf("[CCI] Error: %s returned %d\n", cmd, response); + } + } +} diff --git a/src/lipton/cci.h b/src/lipton/cci.h new file mode 100644 index 0000000..997d998 --- /dev/null +++ b/src/lipton/cci.h @@ -0,0 +1,216 @@ +/* + * Lepton CCI Module + * + * Contains the functions to configure the Lepton via I2C. + * + * Copyright 2020-2022 Dan Julio + * + * This file is part of tCam. + * + * tCam is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * tCam is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with tCam. If not, see . + * + */ +#pragma once + +#include +#include + + +// +// CCI constants +// + +// Device characteristics +#define CCI_WORD_LENGTH 0x02 +#define CCI_ADDRESS 0x2A + +// CCI register locations +#define CCI_REG_STATUS 0x0002 +#define CCI_REG_COMMAND 0x0004 +#define CCI_REG_DATA_LENGTH 0x0006 +#define CCI_REG_DATA_0 0x0008 +#define CCI_REG_DATA_1 0x000A +#define CCI_REG_DATA_2 0x000C +#define CCI_REG_DATA_3 0x000E +#define CCI_REG_DATA_4 0x0010 +#define CCI_REG_DATA_5 0x0012 +#define CCI_REG_DATA_6 0x0014 +#define CCI_REG_DATA_7 0x0016 +#define CCI_REG_DATA_8 0x0018 +#define CCI_REG_DATA_9 0x001A +#define CCI_REG_DATA_10 0x001C +#define CCI_REG_DATA_11 0x001E +#define CCI_REG_DATA_12 0x0020 +#define CCI_REG_DATA_13 0x0022 +#define CCI_REG_DATA_14 0x0024 +#define CCI_REG_DATA_15 0x0026 +#define CCI_BLOCK_BUF_0 0xF800 +#define CCI_BLOCK_BUF_1 0xFC00 + +// Commands +#define CCI_CMD_SYS_RUN_PING 0x0202 +#define CCI_CMD_SYS_GET_UPTIME 0x020C +#define CCI_CMD_SYS_GET_AUX_TEMP 0x0210 +#define CCI_CMD_SYS_GET_FPA_TEMP 0x0214 +#define CCI_CMD_SYS_GET_TELEMETRY_ENABLE_STATE 0x0218 +#define CCI_CMD_SYS_SET_TELEMETRY_ENABLE_STATE 0x0219 +#define CCI_CMD_SYS_GET_TELEMETRY_LOCATION 0x021C +#define CCI_CMD_SYS_SET_TELEMETRY_LOCATION 0x021D +#define CCI_CMD_SYS_RUN_FFC 0x0242 +#define CCI_CMD_SYS_GET_GAIN_MODE 0x0248 +#define CCI_CMD_SYS_SET_GAIN_MODE 0x0249 + +#define CCI_CMD_RAD_GET_RADIOMETRY_ENABLE_STATE 0x4E10 +#define CCI_CMD_RAD_SET_RADIOMETRY_ENABLE_STATE 0x4E11 +#define CCI_CMD_RAD_GET_RADIOMETRY_FLUX_LINEAR_PARAMS 0x4EBC +#define CCI_CMD_RAD_SET_RADIOMETRY_FLUX_LINEAR_PARAMS 0x4EBD +#define CCI_CMD_RAD_GET_RADIOMETRY_TLINEAR_ENABLE_STATE 0x4EC0 +#define CCI_CMD_RAD_SET_RADIOMETRY_TLINEAR_ENABLE_STATE 0x4EC1 +#define CCI_CMD_RAD_GET_RADIOMETRY_TLINEAR_AUTO_RES 0x4EC8 +#define CCI_CMD_RAD_SET_RADIOMETRY_TLINEAR_AUTO_RES 0x4EC9 +#define CCI_CMD_RAD_GET_RADIOMETRY_SPOT_ROI 0x4ECC +#define CCI_CMD_RAD_SET_RADIOMETRY_SPOT_ROI 0x4ECD + +#define CCI_CMD_AGC_GET_AGC_ENABLE_STATE 0x0100 +#define CCI_CMD_AGC_SET_AGC_ENABLE_STATE 0x0101 +#define CCI_CMD_AGC_GET_CALC_ENABLE_STATE 0x0148 +#define CCI_CMD_AGC_SET_CALC_ENABLE_STATE 0x0149 + +#define CCI_CMD_OEM_RUN_REBOOT 0x4842 + +#define CCI_CMD_OEM_GET_GPIO_MODE 0x4854 +#define CCI_CMD_OEM_SET_GPIO_MODE 0x4855 + +#define CCI_CMD_OEM_GET_PART_NUM 0x481C + + +// +// Macros +// +#define WAIT_FOR_BUSY_DEASSERT() cci_wait_busy_clear() + + +// +// Enums +// + +// Telemetry Modes for use with CCI_CMD_SYS_SET_TELEMETRY_* +typedef enum { + CCI_TELEMETRY_DISABLED, + CCI_TELEMETRY_ENABLED +} cci_telemetry_enable_state_t; + +typedef enum { + CCI_TELEMETRY_LOCATION_HEADER, + CCI_TELEMETRY_LOCATION_FOOTER +} cci_telemetry_location_t; + +// Gain Modes for use with CCI_CMD_SYS_SET_GAIN_MODE */ +typedef enum { + LEP_SYS_GAIN_MODE_HIGH, + LEP_SYS_GAIN_MODE_LOW, + LEP_SYS_GAIN_MODE_AUTO + } cc_gain_mode_t; + +// Radiometry Modes for use with CCI_CMD_RAD_SET_RADIOMETRY* +typedef enum { + CCI_RADIOMETRY_DISABLED, + CCI_RADIOMETRY_ENABLED +} cci_radiometry_enable_state_t; + +typedef enum { + CCI_RADIOMETRY_TLINEAR_DISABLED, + CCI_RADIOMETRY_TLINEAR_ENABLED +} cci_radiometry_tlinear_enable_state_t; + +typedef enum { + CCI_RADIOMETRY_AUTO_RES_DISABLED, + CCI_RADIOMETRY_AUTO_RES_ENABLED +} cci_radiometry_tlinear_auto_res_state_t; + +// AGC Modes for use with CCI_CMD_AGC_SET_AGC* +typedef enum { + CCI_AGC_DISABLED, + CCI_AGC_ENABLED +} cci_agc_enable_state_t; + +// GPIO Modes for use with CCI_CMD_OEM_SET_GPIO_MODE* +typedef enum { + LEP_OEM_GPIO_MODE_GPIO = 0, + LEP_OEM_GPIO_MODE_I2C_MASTER = 1, + LEP_OEM_GPIO_MODE_SPI_MASTER_VLB_DATA = 2, + LEP_OEM_GPIO_MODE_SPIO_MASTER_REG_DATA = 3, + LEP_OEM_GPIO_MODE_SPI_SLAVE_VLB_DATA = 4, + LEP_OEM_GPIO_MODE_VSYNC = 5 +} cci_gpio_mode_t; + +// Radiometry Flux Linear Params +typedef struct { + uint16_t sceneEmissivity; + uint16_t TBkgK; + uint16_t tauWindow; + uint16_t TWindowK; + uint16_t tauAtm; + uint16_t TAtmK; + uint16_t reflWindow; + uint16_t TReflK; +} cci_rad_flux_linear_params_t; + + + +// +// CCI API +// + +// Generic access +void cci_set_reg(uint16_t cmd, int len, uint16_t* buf); +void cci_get_reg(uint16_t cmd, int len, uint16_t* buf); +bool cci_command_success(uint16_t* status); + +// Module: SYS +uint32_t cci_run_ping(); +void cci_run_ffc(); +uint32_t cci_get_uptime(); +uint32_t cci_get_aux_temp(); +uint32_t cci_get_fpa_temp(); +void cci_set_telemetry_enable_state(cci_telemetry_enable_state_t state); +uint32_t cci_get_telemetry_enable_state(); +void cci_set_telemetry_location(cci_telemetry_location_t location); +uint32_t cci_get_telemetry_location(); +void cci_set_gain_mode(cc_gain_mode_t mode); +uint32_t cci_get_gain_mode(); + +// Module: RAD +void cci_set_radiometry_enable_state(cci_radiometry_enable_state_t state); +uint32_t cci_get_radiometry_enable_state(); +void cci_set_radiometry_flux_linear_params(cci_rad_flux_linear_params_t* params); +bool cci_get_radiometry_flux_linear_params(cci_rad_flux_linear_params_t* params); +void cci_set_radiometry_tlinear_enable_state(cci_radiometry_tlinear_enable_state_t state); +uint32_t cci_get_radiometry_tlinear_enable_state(); +void cci_set_radiometry_tlinear_auto_res(cci_radiometry_tlinear_auto_res_state_t state); +uint32_t cci_get_radiometry_tlinear_auto_res(); +void cci_set_radiometry_spotmeter(uint16_t r1, uint16_t c1, uint16_t r2, uint16_t c2); +bool cci_get_radiometry_spotmeter(uint16_t* r1, uint16_t* c1, uint16_t* r2, uint16_t* c2); + +// Module: AGC +void cci_set_agc_enable_state(cci_agc_enable_state_t state); +uint32_t cci_get_agc_enable_state(); +void cci_set_agc_calc_enable_state(cci_agc_enable_state_t state); +uint32_t cci_get_agc_calc_enable_state(); + +// Module: OEM +void cc_run_oem_reboot(); +uint32_t cci_get_gpio_mode(); +void cci_set_gpio_mode(cci_gpio_mode_t mode); +void cci_get_part_number(char* pn); diff --git a/src/lipton/i2c.cpp b/src/lipton/i2c.cpp new file mode 100644 index 0000000..d86969e --- /dev/null +++ b/src/lipton/i2c.cpp @@ -0,0 +1,110 @@ +/* + * I2C Module + * + * Provides I2C Access routines for other modules/tasks. Provides a locking mechanism + * since the underlying ESP IDF routines are not thread safe. + * + * Copyright 2020-2022 Dan Julio + * + * This file is part of tCam. + * + * tCam is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * tCam is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with tCam. If not, see . + * + */ +// #include "system_config.h" +#include "i2c.h" +#include "driver/i2c.h" +#include "lepton_system.h" +// #include "freertos/FreeRTOS.h" +// #include "freertos/semphr.h" + + +// +// I2C API +// + +/** + * i2c master initialization + */ +esp_err_t i2c_master_init(int scl_pin, int sda_pin) +{ + int i2c_master_port = I2C_MASTER_NUM; + i2c_config_t conf; + + // Configure the I2C controller in master mode using the pins provided + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = sda_pin; + conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + conf.scl_io_num = scl_pin; + conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + conf.master.clk_speed = I2C_MASTER_FREQ_HZ; + conf.clk_flags = 0; + esp_err_t err = i2c_param_config((i2c_port_t)i2c_master_port, &conf); + if (err != ESP_OK) { + return err; + } + + // Install the I2C driver + return i2c_driver_install((i2c_port_t)i2c_master_port, conf.mode, + I2C_MASTER_RX_BUF_LEN, + I2C_MASTER_TX_BUF_LEN, 0); +} + + +/** + * Read esp-i2c-slave + * + * _______________________________________________________________________________________ + * | start | slave_addr + rd_bit +ack | read n-1 bytes + ack | read 1 byte + nack | stop | + * --------|--------------------------|----------------------|--------------------|------| + * + */ +esp_err_t i2c_master_read_slave(uint8_t addr7, uint8_t *data_rd, size_t size) +{ + if (size == 0) { + return ESP_OK; + } + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (addr7 << 1) | I2C_MASTER_READ, ACK_CHECK_EN); + if (size > 1) { + i2c_master_read(cmd, data_rd, size - 1, ACK_VAL); + } + i2c_master_read_byte(cmd, data_rd + size - 1, NACK_VAL); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin((i2c_port_t)I2C_MODE_MASTER, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + return ret; +} + + +/** + * Write esp-i2c-slave + * + * ___________________________________________________________________ + * | start | slave_addr + wr_bit + ack | write n bytes + ack | stop | + * --------|---------------------------|----------------------|------| + * + */ +esp_err_t i2c_master_write_slave(uint8_t addr7, uint8_t *data_wr, size_t size) +{ + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (addr7 << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); + i2c_master_write(cmd, data_wr, size, ACK_CHECK_EN); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin((i2c_port_t)I2C_MODE_MASTER, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + return ret; +} diff --git a/src/lipton/i2c.h b/src/lipton/i2c.h new file mode 100644 index 0000000..1c91689 --- /dev/null +++ b/src/lipton/i2c.h @@ -0,0 +1,50 @@ +/* + * I2C Module + * + * Provides I2C Access routines for other modules/tasks. Provides a locking mechanism + * since the underlying ESP IDF routines are not thread safe. + * + * Copyright 2020-2022 Dan Julio + * + * This file is part of tCam. + * + * tCam is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * tCam is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with tCam. If not, see . + * + */ +#pragma once + +#include +#include "esp_system.h" +#include "driver/i2c.h" + +// +// I2C constants +// + +// Slave buffer size (not used) +#define I2C_MASTER_TX_BUF_LEN 0 +#define I2C_MASTER_RX_BUF_LEN 0 + +#define ACK_CHECK_EN 0x1 +#define ACK_CHECK_DIS 0x0 +#define ACK_VAL (i2c_ack_type_t)0x0 +#define NACK_VAL (i2c_ack_type_t)0x1 + + +// +// I2C API +// +esp_err_t i2c_master_init(int scl_pin, int sda_pin); +esp_err_t i2c_master_read_slave(uint8_t addr7, uint8_t *data_rd, size_t size); +esp_err_t i2c_master_write_slave(uint8_t addr7, uint8_t *data_wr, size_t size); \ No newline at end of file diff --git a/src/lipton/lepton_system.cpp b/src/lipton/lepton_system.cpp new file mode 100644 index 0000000..ea467b2 --- /dev/null +++ b/src/lipton/lepton_system.cpp @@ -0,0 +1,79 @@ +#include "esp_system.h" +#include +#include "driver/spi_master.h" +#include "driver/spi_slave.h" +#include "lepton_system.h" +#include "lepton_utilities.h" +#include "i2c.h" +#include "vospi.h" +#include +#include + + +// lepton image and telem buffer +lep_buffer_t rsp_lep_buffer; + +/** + * Initialize the ESP32 GPIO and internal peripherals + */ +bool lepton_io_init() +{ + esp_err_t ret; + spi_bus_config_t spi_buscfg; + + printf("[LEP SYS] Lepton I/O Initialization\n"); + + // Attempt to initialize the I2C Master + ret = i2c_master_init(I2C_MASTER_SCL_PIN, I2C_MASTER_SDA_PIN); + if (ret != ESP_OK) { + printf("[LEP SYS] Error: I2C Master initialization failed\n"); + return false; + } + + // Attempt to initialize the SPI Master used by the lep_task + memset(&spi_buscfg, 0, sizeof(spi_bus_config_t)); + spi_buscfg.miso_io_num=LEP_MISO_PIN; + spi_buscfg.mosi_io_num=-1; + spi_buscfg.sclk_io_num=LEP_SCK_PIN; + spi_buscfg.max_transfer_sz=LEP_PKT_LENGTH; + spi_buscfg.quadwp_io_num=-1; + spi_buscfg.quadhd_io_num=-1; + + if (spi_bus_initialize(LEP_SPI_HOST, &spi_buscfg, LEP_DMA_NUM) != ESP_OK) { + printf("[LEP SYS] Error: Lepton SPI initialization failed\n"); + return false; + } + + // initialize GPIO pins + pinMode(LEP_VSYNC_PIN, INPUT); + pinMode(LEP_RESET_PIN, OUTPUT); + digitalWrite(LEP_RESET_PIN, LEP_RESET_ON); + delay(10); + digitalWrite(LEP_RESET_PIN, LEP_RESET_OFF); + + return true; +} + + +/** + * Allocate lepton buffers on heap + */ +bool lepton_buffer_init() +{ + printf("[LEP SYS] Buffer Allocation\n"); + + // Allocate the LEP/RSP task lepton frame and telemetry ping-pong buffers + rsp_lep_buffer.lep_bufferP = (uint16_t*)malloc(LEP_NUM_PIXELS*sizeof(uint16_t)); + if (rsp_lep_buffer.lep_bufferP == NULL) { + printf("[LEP SYS] Error: malloc RSP lepton shared image buffer failed\n"); + return false; + } + + rsp_lep_buffer.lep_telemP = (uint16_t*)malloc(LEP_TEL_WORDS*sizeof(uint16_t)); + if (rsp_lep_buffer.lep_telemP == NULL) { + printf("[LEP SYS] Error: malloc RSP lepton shared telemetry buffer failed\n"); + return false; + } + + return true; +} diff --git a/src/lipton/lepton_system.h b/src/lipton/lepton_system.h new file mode 100644 index 0000000..877d9da --- /dev/null +++ b/src/lipton/lepton_system.h @@ -0,0 +1,55 @@ +#pragma once + +// +// Hardware Configuration +// +// ESP // LEPTON +// #define LEP_SCK_PIN 14 // SPI_CLK 18 +// #define LEP_CSN_PIN 15 // SPI_CS 10 +// #define LEP_VSYNC_PIN 13 // GPIO3/VSYNC 15 +// #define LEP_MISO_PIN 12 // SPI_MISO 12 +// #define LEP_RESET_PIN 27 // RESET_L 17 + +// #define I2C_MASTER_SDA_PIN 21 // SDA 5 +// #define I2C_MASTER_SCL_PIN 22 // SCL 8 + +#define LEP_SCK_PIN 14 // SPI_CLK 18 +#define LEP_CSN_PIN 15 // SPI_CS 10 +#define LEP_VSYNC_PIN 13 // GPIO3/VSYNC 15 +#define LEP_MISO_PIN 12 // SPI_MISO 12 +#define LEP_RESET_PIN 16 // RESET_L 17 + +#define I2C_MASTER_SDA_PIN 2 // SDA 5 +#define I2C_MASTER_SCL_PIN 4 // SCL 8 + +// I2C +#define I2C_MASTER_NUM 1 +#define I2C_MASTER_FREQ_HZ 100000 + +// SPI +// Lepton uses HSPI (no MOSI) +// Host SPI Slave uses VSPI (no MOSI) +#define LEP_SPI_HOST HSPI_HOST +#define LEP_DMA_NUM 2 +#define LEP_SPI_FREQ_HZ 16000000 + + +// +// Lepton Buffers Typedef +// +typedef struct +{ + bool telem_valid; + uint16_t lep_min_val; + uint16_t lep_max_val; + uint16_t *lep_bufferP; + uint16_t *lep_telemP; +} lep_buffer_t; + +extern lep_buffer_t rsp_lep_buffer; + +// +// System init functions +// +bool lepton_io_init(); +bool lepton_buffer_init(); \ No newline at end of file diff --git a/src/lipton/lepton_utilities.cpp b/src/lipton/lepton_utilities.cpp new file mode 100644 index 0000000..237881f --- /dev/null +++ b/src/lipton/lepton_utilities.cpp @@ -0,0 +1,287 @@ +/* + * Lepton related utilities + * + * Contains utility and access functions for the Lepton. + * + * Note: I noticed that on occasion, the first time some commands run on the lepton + * will fail either silently or with an error response code. The access routines in + * this module attempt to detect and retry the commands if necessary. + * + * Copyright 2020 Dan Julio + * + * This file is part of tCam. + * + * tCam is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * tCam is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with tCam. If not, see . + * + */ +#include "lepton_utilities.h" +#include "cci.h" +#include "i2c.h" +#include "esp_system.h" +#include "vospi.h" +#include +#include + + +static bool lep_is_radiometric = false; +static int lep_type; + +// default lepton config +lepton_config_t LEP_CONFIG = { + .agc_set_enabled = false, + .emissivity = 100, + .gain_mode = LEP_GAIN_HIGH +}; + +// +// Lepton Utilities API +// +bool lepton_init() +{ + char pn[33]; + uint32_t val, rsp; + lepton_config_t* lep_stP = &LEP_CONFIG; + + // Attempt to ping the Lepton to validate communication + // If this is successful, we assume further communication will be successful + rsp = cci_run_ping(); + if (rsp != 0) { + printf("[LEP UTIL] Error: Lepton communication failed (%d)\n", rsp); + return false; + } + + // Get the Lepton type (part number) to determine if it's a Lepton 3.0 or 3.5 + cci_get_part_number(pn); + printf("[LEP UTIL] Found Lepton, part number: %s\n", pn); + if (strncmp(pn, "500-0771-01", 32) == 0) { + printf(" Radiometric Lepton 3.5\n"); + lep_is_radiometric = true; + lep_type = LEP_TYPE_3_5; + } else if (strncmp(pn, "500-0758-99", 32) == 0) { + printf(" Radiometric Lepton 3.1\n"); + lep_is_radiometric = true; + lep_type = LEP_TYPE_3_1; + } else if (strncmp(pn, "500-0726-01", 32) == 0) { + printf(" Non-radiometric Lepton 3.0\n"); + lep_is_radiometric = false; + lep_type = LEP_TYPE_3_0; + } else { + printf(" Unsupported Lepton"); + lep_is_radiometric = true; + lep_type = LEP_TYPE_UNK; + } + + if (lep_is_radiometric) { + // Configure Radiometry for TLinear enabled, auto-resolution + cci_set_radiometry_enable_state(CCI_RADIOMETRY_ENABLED); + rsp = cci_get_radiometry_enable_state(); + printf("[LEP UTIL] Lepton Radiometry = %d\n", rsp); + if (rsp != CCI_RADIOMETRY_ENABLED) { + // Make one more effort + delay(10); + printf("[LEP UTIL] Retry Set Lepton Radiometry\n"); + cci_set_radiometry_enable_state(CCI_RADIOMETRY_ENABLED); + rsp = cci_get_radiometry_enable_state(); + printf("[LEP UTIL] Lepton Radiometry = %d\n", rsp); + if (rsp != CCI_RADIOMETRY_ENABLED) { + printf("[LEP UTIL] Error: Lepton communication failed (%d)\n", rsp); + return false; + } + } + + // TLinear depends on AGC + val = (lep_stP->agc_set_enabled) ? CCI_RADIOMETRY_TLINEAR_DISABLED : CCI_RADIOMETRY_TLINEAR_ENABLED; + cci_set_radiometry_tlinear_enable_state((cci_radiometry_tlinear_enable_state_t)val); + rsp = cci_get_radiometry_tlinear_enable_state(); + printf("[LEP UTIL] Lepton Radiometry TLinear = %d\n", rsp); + if (rsp != val) { + printf("[LEP UTIL] Error: Lepton communication failed (%d)\n", rsp); + return false; + } + + cci_set_radiometry_tlinear_auto_res(CCI_RADIOMETRY_AUTO_RES_ENABLED); + rsp = cci_get_radiometry_tlinear_auto_res(); + printf("[LEP UTIL] Lepton Radiometry Auto Resolution = %d\n", rsp); + if (rsp != CCI_RADIOMETRY_AUTO_RES_ENABLED) { + printf("[LEP UTIL] Error: Lepton communication failed (%d)\n", rsp); + return false; + } + } + + // Enable AGC calcs for a smooth transition between modes + cci_set_agc_calc_enable_state(CCI_AGC_ENABLED); + rsp = cci_get_agc_calc_enable_state(); + printf("[LEP UTIL] Lepton AGC Calcs = %d\n", rsp); + if (rsp != CCI_AGC_ENABLED) { + printf("[LEP UTIL] Error: Lepton communication failed (%d)\n", rsp); + return false; + } + + // AGC + val = (lep_stP->agc_set_enabled) ? CCI_AGC_ENABLED : CCI_AGC_DISABLED; + cci_set_agc_enable_state((cci_agc_enable_state_t)val); + rsp = cci_get_agc_enable_state(); + printf("[LEP UTIL] Lepton AGC = %d\n", rsp); + if (rsp != val) { + printf("[LEP UTIL] Error: Lepton communication failed (%d)\n", rsp); + return false; + } + + // Enable telemetry + cci_set_telemetry_enable_state(CCI_TELEMETRY_ENABLED); + rsp = cci_get_telemetry_enable_state(); + printf("[LEP UTIL] Lepton Telemetry = %d\n", rsp); + if (rsp != CCI_TELEMETRY_ENABLED) { + printf("[LEP UTIL] Error: Lepton communication failed (%d)\n", rsp); + return false; + } + vospi_include_telem(true); + + // GAIN + switch (lep_stP->gain_mode) { + case LEP_GAIN_HIGH: + val = LEP_SYS_GAIN_MODE_HIGH; + break; + case LEP_GAIN_LOW: + val = LEP_SYS_GAIN_MODE_LOW; + break; + default: + val = LEP_SYS_GAIN_MODE_AUTO; + } + cci_set_gain_mode((cc_gain_mode_t)val); + rsp = cci_get_gain_mode(); + printf("[LEP UTIL] Lepton Gain Mode = %d\n", rsp); + if (rsp != val) { + printf("[LEP UTIL] Error: Lepton communication failed (%d)\n", rsp); + return false; + } + + // Emissivity + if (lep_is_radiometric) { + lepton_emissivity(lep_stP->emissivity); + printf("[LEP UTIL] Lepton Emissivity = %d%%\n", lep_stP->emissivity); + } + + // Finally enable VSYNC on Lepton GPIO3 + cci_set_gpio_mode(LEP_OEM_GPIO_MODE_VSYNC); + rsp = cci_get_gpio_mode(); + printf("[LEP UTIL] Lepton GPIO Mode = %d\n", rsp); + if (rsp != LEP_OEM_GPIO_MODE_VSYNC) { + printf("[LEP UTIL] Error: Lepton communication failed (%d)\n", rsp); + return false; + } + + return true; +} + + +bool lepton_is_radiometric() +{ + return lep_is_radiometric; +} + + +int lepton_get_model() +{ + return lep_type; +} + + +void lepton_agc(bool en) +{ + if (en) { + if (lep_is_radiometric) { + cci_set_radiometry_tlinear_enable_state(CCI_RADIOMETRY_TLINEAR_DISABLED); + } + cci_set_agc_enable_state(CCI_AGC_ENABLED); + } else { + if (lep_is_radiometric) { + cci_set_radiometry_tlinear_enable_state(CCI_RADIOMETRY_TLINEAR_ENABLED); + } + cci_set_agc_enable_state(CCI_AGC_DISABLED); + } +} + + +void lepton_ffc() +{ + cci_run_ffc(); +} + + +void lepton_gain_mode(uint8_t mode) +{ + cc_gain_mode_t gain_mode; + + if (lep_is_radiometric) { + switch (mode) { + case LEP_GAIN_HIGH: + gain_mode = LEP_SYS_GAIN_MODE_HIGH; + break; + case LEP_GAIN_LOW: + gain_mode = LEP_SYS_GAIN_MODE_LOW; + break; + default: + gain_mode = LEP_SYS_GAIN_MODE_AUTO; + } + cci_set_gain_mode(gain_mode); + } +} + + +void lepton_spotmeter(uint16_t r1, uint16_t c1, uint16_t r2, uint16_t c2) +{ + if (lep_is_radiometric) { + cci_set_radiometry_spotmeter(r1, c1, r2, c2); + } +} + + +void lepton_emissivity(uint16_t e) +{ + cci_rad_flux_linear_params_t set_flux_values; + + if (lep_is_radiometric) { + // Scale percentage e into Lepton scene emissivity values (1-100% -> 82-8192) + if (e < 1) e = 1; + if (e > 100) e = 100; + set_flux_values.sceneEmissivity = e * 8192 / 100; + + // Set default (no lens) values for the remaining parameters + set_flux_values.TBkgK = 29515; + set_flux_values.tauWindow = 8192; + set_flux_values.TWindowK = 29515; + set_flux_values.tauAtm = 8192; + set_flux_values.TAtmK = 29515; + set_flux_values.reflWindow = 0; + set_flux_values.TReflK = 29515; + + cci_set_radiometry_flux_linear_params(&set_flux_values); + } +} + + +uint32_t lepton_get_tel_status(uint16_t* tel_buf) +{ + return (tel_buf[LEP_TEL_STATUS_HIGH] << 16) | tel_buf[LEP_TEL_STATUS_LOW]; +} + + +/** + * Convert a temperature reading from the lepton (in units of K * 100) to C + */ +float lepton_kelvin_to_C(uint32_t k, float lep_res) +{ + return (((float) k) * lep_res) - 273.15; +} diff --git a/src/lipton/lepton_utilities.h b/src/lipton/lepton_utilities.h new file mode 100644 index 0000000..61b8ad8 --- /dev/null +++ b/src/lipton/lepton_utilities.h @@ -0,0 +1,181 @@ +/* + * Lepton related utilities + * + * Contains utility and access functions for the Lepton. + * + * Copyright 2020 Dan Julio + * + * This file is part of tCam. + * + * tCam is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * tCam is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with tCam. If not, see . + * + */ +#pragma once + +#include +#include + +// +// Lepton Utilities Constants +// + +// +// Telemetry words +// +// From Row A (words 0-79) +#define LEP_TEL_REV 0 +#define LEP_TEL_TC_LOW 1 +#define LEP_TEL_TC_HIGH 2 +#define LEP_TEL_STATUS_LOW 3 +#define LEP_TEL_STATUS_HIGH 4 +#define LEP_TEL_SN_0 5 +#define LEP_TEL_SN_1 6 +#define LEP_TEL_SN_2 7 +#define LEP_TEL_SN_3 8 +#define LEP_TEL_SN_4 9 +#define LEP_TEL_SN_5 10 +#define LEP_TEL_SN_6 11 +#define LEP_TEL_SN_7 12 +#define LEP_TEL_REV_0 13 +#define LEP_TEL_REV_1 14 +#define LEP_TEL_REV_2 15 +#define LEP_TEL_REV_3 16 +#define LEP_TEL_FC_LOW 20 +#define LEP_TEL_FC_HIGH 21 +#define LEP_TEL_FRAME_MEAN 22 +#define LEP_TEL_FPA_T_CNT 23 +#define LEP_TEL_FPA_T_K100 24 +#define LEP_TEL_HSE_T_CNT 25 +#define LEP_TEL_HSE_T_K100 26 +#define LEP_TEL_LAST_FPA_T 29 +#define LEP_TEL_LAST_TC_LOW 30 +#define LEP_TEL_LAST_TC_HIGH 31 +#define LEP_TEL_LAST_HST_T 32 +#define LEP_TEL_AGC_ROI_0 34 +#define LEP_TEL_AGC_ROI_1 35 +#define LEP_TEL_AGC_ROI_2 36 +#define LEP_TEL_AGC_ROI_3 37 +#define LEP_TEL_AGC_CL_HIGH 38 +#define LEP_TEL_AGC_CL_LOW 39 +#define LEP_TEL_VID_FMT_LOW 72 +#define LEP_TEL_VID_FMT_HIGH 73 +#define LEP_TEL_FFC_FR_LOG2 74 + +// From Row B (words 80-159) +#define LEP_TEL_EMISSIVITY 99 +#define LEP_TEL_BG_T_K100 100 +#define LEP_TEL_ATM_TRANS 101 +#define LEP_TEL_ATM_T_K100 102 +#define LEP_TEL_WND_TRANS 103 +#define LEP_TEL_WND_REFL 104 +#define LEP_TEL_WND_T_K100 105 +#define LEP_TEL_WND_REFL_T_K100 106 + +// From Row C (words 160-239) +#define LEP_TEL_GAIN_MODE 165 +#define LEP_TEL_EFF_GAIN_MODE 166 +#define LEP_TEL_GAIN_MODE_DES 167 +#define LEP_TEL_GAIN_TH_H2LC 168 +#define LEP_TEL_GAIN_TH_L2HC 169 +#define LEP_TEL_GAIN_TH_H2LK 170 +#define LEP_TEL_GAIN_TH_L2HK 171 +#define LEP_TEL_GAIN_POP_H2L 174 +#define LEP_TEL_GAIN_POP_L2H 175 +#define LEP_TEL_GAIN_MODE_ROI_0 182 +#define LEP_TEL_GAIN_MODE_ROI_1 183 +#define LEP_TEL_GAIN_MODE_ROI_2 184 +#define LEP_TEL_GAIN_MODE_ROI_3 185 +#define LEP_TEL_TLIN_ENABLE 208 +#define LEP_TEL_TLIN_RES 209 +#define LEP_TEL_SPOT_MEAN 210 +#define LEP_TEL_SPOT_MAX 211 +#define LEP_TEL_SPOT_MIN 212 +#define LEP_TEL_SPOT_POP 213 +#define LEP_TEL_SPOT_Y1 214 +#define LEP_TEL_SPOT_X1 215 +#define LEP_TEL_SPOT_Y2 216 +#define LEP_TEL_SPOT_X2 217 + + + +// +// Telemetry Status DWORD mask +// +#define LEP_STATUS_FFC_DESIRED 0x00000008 +#define LEP_STATUS_FFC_STATE 0x00000030 +#define LEP_STATUS_AGC_STATE 0x00001000 +#define LEP_STATUS_SHTR_LO 0x00008000 +#define LEP_STATUS_OT_IMM 0x00100000 + +// +// Telemetry Status FFC State +// +#define LEP_FFC_STATE_IDLE 0x00000000 +#define LEP_FFC_STATE_IMM 0x00000010 +#define LEP_FFC_STATE_RUN 0x00000020 +#define LEP_FFC_STATE_CMPL 0x00000030 + +// +// Lepton Types +// +#define LEP_TYPE_3_5 0 +#define LEP_TYPE_3_0 1 +#define LEP_TYPE_3_1 2 +#define LEP_TYPE_UNK 3 + +// +// Lepton Gain mode +// +#define LEP_GAIN_HIGH 0 +#define LEP_GAIN_LOW 1 +#define LEP_GAIN_AUTO 2 + +// +// Lepton Reset mode +// +#define LEP_RESET_ON 0 +#define LEP_RESET_OFF 1 + +// +// Lepton Power mode +// +#define LEP_POWER_ON 1 +#define LEP_POWER_OFF 0 + +// +// Lepton configuration +// +typedef struct { + bool agc_set_enabled; // Set when agc_enabled + int emissivity; // Integer percent 1 - 100 + int gain_mode; // LEP_GAIN_HIGH / LEP_GAIN_LOW / LEP_GAIN_AUTO +} lepton_config_t; + +extern lepton_config_t LEP_CONFIG; + +// +// Lepton Utilities API +// +bool lepton_init(); +bool lepton_is_radiometric(); +int lepton_get_model(); +void lepton_agc(bool en); +void lepton_ffc(); +void lepton_gain_mode(uint8_t mode); +void lepton_spotmeter(uint16_t r1, uint16_t c1, uint16_t r2, uint16_t c2); +void lepton_emissivity(uint16_t e); + +uint32_t lepton_get_tel_status(uint16_t* tel_buf); + +float lepton_kelvin_to_C(uint32_t k, float lep_res); \ No newline at end of file diff --git a/src/lipton/vospi.cpp b/src/lipton/vospi.cpp new file mode 100644 index 0000000..22b1cf9 --- /dev/null +++ b/src/lipton/vospi.cpp @@ -0,0 +1,320 @@ +/* + * Lepton VoSPI Module + * + * Contains the functions to get frames from a Lepton 3.5 via its SPI port. + * Optionally supports collecting telemetry when enabled as a footer (does not + * support telemetry enabled as a header). + * + * Copyright 2020-2022 Dan Julio + * + * This file is part of tCam. + * + * tCam is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * tCam is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with tCam. If not, see . + * + */ +#include +#include +#include +#include +#include +#include "esp_system.h" +#include "esp_timer.h" +#include "driver/spi_master.h" +#include "lepton_system.h" +#include "vospi.h" + +// +// VoSPI Variables +// + +// SPI Interface +static spi_device_handle_t spi; +static spi_transaction_t lep_spi_trans; + +// Pointer to allocated array to store one Lepton packet (DMA capable) +static uint8_t* lepPacketP; + +// Lepton Frame buffer (16-bit values) +static uint16_t lepBuffer[LEP_NUM_PIXELS]; + +// Lepton Telemetry buffer (16-bit values) +static uint16_t lepTelem[LEP_TEL_WORDS]; + +// Processing State +static int curSegment = 1; +static int curLinesPerSeg = LEP_NOTEL_PKTS_PER_SEG; +static int curWordsPerSeg = LEP_NOTEL_WORDS_PER_SEG; +static bool validSegmentRegion = false; +static bool includeTelemetry = false; + + + + +// +// VoSPI Forward Declarations for internal functions +// +static bool transfer_packet(uint8_t* line, uint8_t* seg); +static void copy_packet_to_lepton_buffer(uint8_t line); +static void copy_packet_to_telem_buffer(uint8_t line); + + + +// +// VoSPI API +// + +/** + * Initialise the VoSPI interface. + */ +int vospi_init() +{ + esp_err_t ret; + + spi_device_interface_config_t devcfg = { + .command_bits = 0, + .address_bits = 0, + .mode = 3, + .cs_ena_pretrans = 10, + .clock_speed_hz = LEP_SPI_FREQ_HZ, + .spics_io_num = LEP_CSN_PIN, + .flags = SPI_DEVICE_HALFDUPLEX, + .queue_size = 1 + }; + + if ((ret=spi_bus_add_device(LEP_SPI_HOST, &devcfg, &spi)) != ESP_OK) { + printf("[VOSPI] Error: failed to add lepton spi device\n"); + } else { + // Allocate DMA capable memory for the lepton packet + lepPacketP = (uint8_t*) heap_caps_malloc(LEP_PKT_LENGTH, MALLOC_CAP_DMA); + if (lepPacketP != NULL) { + ret = ESP_OK; + } else { + printf("[VOSPI] Error: failed to allocate lepton DMA packet buffer\n"); + ret = ESP_FAIL; + } + } + + // Setup our SPI transaction + memset(&lep_spi_trans, 0, sizeof(spi_transaction_t)); + lep_spi_trans.tx_buffer = NULL; + lep_spi_trans.rx_buffer = lepPacketP; + lep_spi_trans.rxlength = LEP_PKT_LENGTH*8; + + return ret; +} + + +/** + * Attempt to read a complete segment from the Lepton + * - Data loaded into lepBuffer + * - Returns true when last successful segment read, false otherwise + */ +bool vospi_transfer_segment(uint64_t vsyncDetectedUsec) +{ + uint8_t line, prevLine; + uint8_t segment; + bool done = false; + bool beforeValidData = true; + bool success = false; + + prevLine = 255; + + while (!done) { + if (transfer_packet(&line, &segment)) { + // Saw a valid packet + if (line == prevLine) { + // This is garbage data since line numbers should always increment + done = true; + } else { + // Check for termination or completion conditions + if (line == 20) { + // Check segment + if (!validSegmentRegion) { + // Look for start of valid segment data + if (segment == 1) { + beforeValidData = false; + validSegmentRegion = true; + } + } else if ((segment < 2) || (segment > 4)) { + // Hold/Reset in starting position (always collecting in segment 1 buffer locations) + validSegmentRegion = false; // In case it was set + curSegment = 1; + } + } + + // Copy the data to the lepton frame buffer or telemetry buffer + // - beforeValidData is used to collect data before we know if the current segment (1) is valid + // - then we use validSegmentRegion for remaining data once we know we're seeing valid data + if (includeTelemetry && validSegmentRegion && (curSegment == 4) && (line >= 57)) { + copy_packet_to_telem_buffer(line - 57); + } + else if ((beforeValidData || validSegmentRegion) && (line < curLinesPerSeg)) { + copy_packet_to_lepton_buffer(line); + } + + if (line == (curLinesPerSeg-1)) { + // Saw a complete segment, move to next segment or complete frame aquisition if possible + if (validSegmentRegion) { + if (curSegment < 4) { + // Setup to get next segment + curSegment++; + } else { + // Got frame + success = true; + + // Setup to get the next frame + curSegment = 1; + validSegmentRegion = false; + } + } + done = true; + } + } + prevLine = line; + } else if ((esp_timer_get_time() - vsyncDetectedUsec) > LEP_MAX_FRAME_XFER_WAIT_USEC) { + // Did not see a valid packet within this segment interval + done = true; + } + } + + return success; +} + + +/** + * Load the a system buffer from our buffers for another task + */ +void vospi_get_frame(lep_buffer_t* sys_bufP) +{ + uint16_t* sptr = sys_bufP->lep_bufferP; + uint16_t* lptr = &lepBuffer[0]; + uint16_t min = 0xFFFF; + uint16_t max = 0x0000; + uint16_t t16; + + // Load lepton image data + while (lptr < &lepBuffer[LEP_NUM_PIXELS]) { + t16 = *lptr++; + if (t16 < min) min = t16; + if (t16 > max) max = t16; + *sptr++ = t16; + } + sys_bufP->lep_min_val = min; + sys_bufP->lep_max_val = max; + + // Optionally load telemetry + sys_bufP->telem_valid = includeTelemetry; + if (includeTelemetry) { + sptr = sys_bufP->lep_telemP; + lptr = &lepTelem[0]; + while (lptr < &lepTelem[LEP_TEL_WORDS]) { + *sptr++ = *lptr++; + } + } +} + + +/** + * Configure the pipeline to include telemetry or not. + * This should be done during initialization + */ +void vospi_include_telem(bool en) +{ + includeTelemetry = en; + curLinesPerSeg = (en) ? LEP_TEL_PKTS_PER_SEG : LEP_NOTEL_PKTS_PER_SEG; + curWordsPerSeg = (en) ? LEP_TEL_WORDS_PER_SEG : LEP_NOTEL_WORDS_PER_SEG; +} + + + +// +// VoSPI Forward Declarations for internal functions +// + +/** + * Attempt to read one packet from the lepton + * - Return false for discard packets + * - Return true otherwise + * - line contains the packet line number for all valid packets + * - seg contains the packet segment number if the line number is 20 + */ +static bool transfer_packet(uint8_t* line, uint8_t* seg) +{ + bool valid = false; + esp_err_t ret; + + // *seg will be set if possible + *seg = 0; + + // Get a packet + ret = spi_device_polling_transmit(spi, &lep_spi_trans); + //ret = spi_device_transmit(spi, &lep_spi_trans); + ESP_ERROR_CHECK(ret); + + // Repeat as long as the frame is not valid, equals sync + if ((*lepPacketP & 0x0F) == 0x0F) { + valid = false; + } else { + *line = *(lepPacketP + 1); + + // Get segment when possible + if (*line == 20) { + *seg = (*lepPacketP >> 4); + } + + valid = true; + } + + return(valid); +} + + +/** + * Copy the lepton packet to the raw lepton frame + * - line specifies packet line number + */ +static void copy_packet_to_lepton_buffer(uint8_t line) +{ + uint8_t* lepPopPtr = lepPacketP + 4; + uint16_t* acqPushPtr = &lepBuffer[((curSegment-1) * curWordsPerSeg) + (line * (LEP_WIDTH/2))]; + uint16_t t; + + while (lepPopPtr <= (lepPacketP + (LEP_PKT_LENGTH-1))) { + t = *lepPopPtr++ << 8; + t |= *lepPopPtr++; + *acqPushPtr++ = t; + } +} + + +/** + * Copy the lepton packet to the telemetry buffer + * - line specifies packet line number (only 0-2 are valid, do not call with line 3) + */ +static void copy_packet_to_telem_buffer(uint8_t line) +{ + uint8_t* lepPopPtr = lepPacketP + 4; + uint16_t* telPushPtr = &lepTelem[line * (LEP_WIDTH/2)]; + uint16_t t; + + if (line > 2) return; + + while (lepPopPtr <= (lepPacketP + (LEP_PKT_LENGTH-1))) { + t = *lepPopPtr++ << 8; + t |= *lepPopPtr++; + *telPushPtr++ = t; + } +} + + diff --git a/src/lipton/vospi.h b/src/lipton/vospi.h new file mode 100644 index 0000000..8246cd0 --- /dev/null +++ b/src/lipton/vospi.h @@ -0,0 +1,74 @@ +/* + * Lepton VoSPI Module + * + * Contains the functions to get frames from a Lepton 3.5 via its SPI port. + * Optionally supports collecting telemetry when enabled as a footer (does not + * support telemetry enabled as a header). + * + * Copyright 2020-2022 Dan Julio + * + * This file is part of tCam. + * + * tCam is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * tCam is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with tCam. If not, see . + * + */ +#pragma once + +#include +#include +#include "lepton_system.h" + + +// +// VoSPI Constants +// + +// LEP_FRAME_USEC is the per-frame period from the Lepton (interrupt rate) +#define LEP_FRAME_USEC 9450 +// LEP_MAX_FRAME_XFER_WAIT_USEC specifies the maximum time we should wait in +// vospi_transfer_segment() to read a valid frame. It should be LEP_FRAME_USEC - +// (maximum ISR latency + transfer_packet() code path overhead) +// than LEP_FRAME_USEC - maximum ISR latency) +#define LEP_MAX_FRAME_XFER_WAIT_USEC 9250 + +#define LEP_WIDTH 160 +#define LEP_HEIGHT 120 +#define LEP_NUM_PIXELS (LEP_WIDTH * LEP_HEIGHT) +#define LEP_PKT_LENGTH 164 + +// Telemetry related +#define LEP_TEL_PACKETS 3 +#define LEP_TEL_PKT_LEN (LEP_PKT_LENGTH - 4) +#define LEP_TEL_WORDS (LEP_TEL_PACKETS * LEP_TEL_PKT_LEN / 2) + +// Dynamic values depending if telemetry is included or not +#define LEP_TEL_PKTS_PER_SEG 61 +#define LEP_NOTEL_PKTS_PER_SEG 60 +#define LEP_TEL_WORDS_PER_SEG (LEP_TEL_PKTS_PER_SEG * LEP_WIDTH / 2) +#define LEP_NOTEL_WORDS_PER_SEG (LEP_NOTEL_PKTS_PER_SEG * LEP_WIDTH / 2) + +/* Lepton frame error return */ +enum LeptonReadError { + NONE, DISCARD, SEGMENT_ERROR, ROW_ERROR, SEGMENT_INVALID +}; + + + +// +// VoSPI API +// +int vospi_init(); +bool vospi_transfer_segment(uint64_t vsyncDetectedUsec); +void vospi_get_frame(lep_buffer_t* sys_bufP); +void vospi_include_telem(bool en);