/*
* 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)
{
uint8_t* sptr = sys_bufP->lep_bufferP;
uint16_t* tptr = sys_bufP->lep_telemP;
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++ = (uint8_t) t16 & 0xFF;
}
sys_bufP->lep_min_val = min;
sys_bufP->lep_max_val = max;
// Optionally load telemetry
sys_bufP->telem_valid = includeTelemetry;
if (includeTelemetry) {
lptr = &lepTelem[0];
while (lptr < &lepTelem[LEP_TEL_WORDS]) {
*tptr++ = *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;
}
}