init
This commit is contained in:
commit
2c2202e60d
2491 files changed
+1295990
No files matched your search
@@ -0,0 +1,370 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* File Name : DirectFrameBufferVideoController.hpp
|
||||
******************************************************************************
|
||||
* This file is generated by TouchGFX Generator 4.26.1. Please, do not edit!
|
||||
******************************************************************************
|
||||
* @attention
|
||||
*
|
||||
* Copyright (c) 2026 STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file
|
||||
* in the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
#ifndef TOUCHGFX_DIRECTFRAMEBUFFERVIDEOCONTROLLER_HPP
|
||||
#define TOUCHGFX_DIRECTFRAMEBUFFERVIDEOCONTROLLER_HPP
|
||||
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/VideoWidget.hpp>
|
||||
#include <MJPEGDecoder.hpp>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Strategy:
|
||||
* Decode directly into the framebuffer in draw.
|
||||
* Tick will decide if we are going to a new frame.
|
||||
*/
|
||||
template <uint32_t no_streams, touchgfx::Bitmap::BitmapFormat output_format>
|
||||
class DirectFrameBufferVideoController : public touchgfx::VideoController
|
||||
{
|
||||
public:
|
||||
DirectFrameBufferVideoController()
|
||||
: VideoController(), allowSkipFrames(true)
|
||||
{
|
||||
assert((no_streams > 0) && "Video: Number of streams zero!");
|
||||
|
||||
// Clear arrays
|
||||
memset(mjpegDecoders, 0, sizeof(mjpegDecoders));
|
||||
}
|
||||
|
||||
virtual Handle registerVideoWidget(touchgfx::VideoWidget& widget)
|
||||
{
|
||||
// Find stream handle for Widget
|
||||
Handle handle = getFreeHandle();
|
||||
|
||||
streams[handle].isActive = true;
|
||||
|
||||
//Set Widget buffer format and address
|
||||
widget.setVideoBufferFormat(output_format, 0, 0);
|
||||
widget.setVideoBuffer((uint8_t*)0);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
virtual void unregisterVideoWidget(const Handle handle)
|
||||
{
|
||||
streams[handle].isActive = false;
|
||||
}
|
||||
|
||||
virtual void setFrameRate(const Handle handle, uint32_t ui_frames, uint32_t video_frames)
|
||||
{
|
||||
assert(handle < no_streams);
|
||||
Stream& stream = streams[handle];
|
||||
|
||||
// Reset counters
|
||||
stream.frameCount = 0;
|
||||
stream.tickCount = 0;
|
||||
|
||||
// Save requested frame rate ratio
|
||||
stream.frame_rate_ticks = ui_frames;
|
||||
stream.frame_rate_video = video_frames;
|
||||
}
|
||||
|
||||
virtual void setVideoData(const Handle handle, const uint8_t* movie, const uint32_t length)
|
||||
{
|
||||
assert(handle < no_streams);
|
||||
|
||||
// Reset decoder to first frame
|
||||
mjpegDecoders[handle]->setVideoData(movie, length);
|
||||
|
||||
// Lower flag to show the first frame
|
||||
Stream& stream = streams[handle];
|
||||
stream.frameNumber = mjpegDecoders[handle]->getCurrentFrameNumber();
|
||||
stream.doDecodeNextFrame = false;
|
||||
|
||||
// Stop playing
|
||||
setCommand(handle, PAUSE, 0);
|
||||
}
|
||||
|
||||
virtual void setVideoData(const Handle handle, touchgfx::VideoDataReader& reader)
|
||||
{
|
||||
assert(handle < no_streams);
|
||||
|
||||
// Reset decoder to first frame
|
||||
mjpegDecoders[handle]->setVideoData(reader);
|
||||
|
||||
// Lower flag to show the first frame
|
||||
Stream& stream = streams[handle];
|
||||
stream.frameNumber = mjpegDecoders[handle]->getCurrentFrameNumber();
|
||||
stream.doDecodeNextFrame = false;
|
||||
|
||||
// Stop playing
|
||||
setCommand(handle, PAUSE, 0);
|
||||
}
|
||||
|
||||
virtual void setCommand(const Handle handle, Command cmd, uint32_t param)
|
||||
{
|
||||
assert(handle < no_streams);
|
||||
Stream& stream = streams[handle];
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case PLAY:
|
||||
// Cannot Play without movie
|
||||
if (mjpegDecoders[handle]->hasVideo())
|
||||
{
|
||||
stream.isPlaying = true;
|
||||
stream.isShowingOneFrame = false;
|
||||
// Reset counters
|
||||
stream.frameCount = 0;
|
||||
stream.tickCount = 0;
|
||||
// If non-repeating video stopped at the end, kick to next frame
|
||||
if (!stream.repeat)
|
||||
{
|
||||
MJPEGDecoder* const decoder = mjpegDecoders[handle];
|
||||
if (decoder->getCurrentFrameNumber() == decoder->getNumberOfFrames())
|
||||
{
|
||||
decoder->gotoNextFrame();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PAUSE:
|
||||
stream.isPlaying = false;
|
||||
stream.isShowingOneFrame = false;
|
||||
break;
|
||||
case SEEK:
|
||||
stream.seek_to_frame = param;
|
||||
// Reset counters
|
||||
stream.frameCount = 0;
|
||||
stream.tickCount = 0;
|
||||
break;
|
||||
case SHOW:
|
||||
stream.seek_to_frame = param;
|
||||
stream.isShowingOneFrame = true;
|
||||
stream.doDecodeNextFrame = true;
|
||||
// Reset counters
|
||||
stream.frameCount = 0;
|
||||
stream.tickCount = 0;
|
||||
break;
|
||||
case STOP:
|
||||
stream.isPlaying = false;
|
||||
stream.isShowingOneFrame = false;
|
||||
stream.seek_to_frame = 1;
|
||||
// Reset counters
|
||||
stream.frameCount = 0;
|
||||
stream.tickCount = 0;
|
||||
break;
|
||||
case SET_REPEAT:
|
||||
stream.repeat = (param > 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool updateFrame(const Handle handle, touchgfx::VideoWidget& widget)
|
||||
{
|
||||
assert(handle < no_streams);
|
||||
Stream& stream = streams[handle];
|
||||
|
||||
bool hasMoreFrames = true;
|
||||
|
||||
if (stream.isPlaying || stream.isShowingOneFrame)
|
||||
{
|
||||
// Increase tickCount
|
||||
stream.tickCount += HAL::getInstance()->getLCDRefreshCount();
|
||||
|
||||
// Lower flag
|
||||
stream.isShowingOneFrame = false;
|
||||
|
||||
if (stream.doDecodeNextFrame)
|
||||
{
|
||||
MJPEGDecoder* const decoder = mjpegDecoders[handle];
|
||||
// Invalidate to get widget redrawn
|
||||
widget.invalidate();
|
||||
// Seek or increment video frame
|
||||
if (stream.seek_to_frame > 0)
|
||||
{
|
||||
decoder->gotoFrame(stream.seek_to_frame);
|
||||
hasMoreFrames = (stream.seek_to_frame < decoder->getNumberOfFrames());
|
||||
stream.seek_to_frame = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (stream.skip_frames > 0)
|
||||
{
|
||||
decoder->gotoFrame(decoder->getCurrentFrameNumber() + stream.skip_frames);
|
||||
stream.frameCount += stream.skip_frames;
|
||||
stream.skip_frames = 0;
|
||||
}
|
||||
if (stream.repeat)
|
||||
{
|
||||
hasMoreFrames = decoder->gotoNextFrame();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (decoder->getCurrentFrameNumber() < decoder->getNumberOfFrames())
|
||||
{
|
||||
hasMoreFrames = decoder->gotoNextFrame();
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.isPlaying = false;
|
||||
hasMoreFrames = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stream.frameNumber = decoder->getCurrentFrameNumber();
|
||||
stream.frameCount++;
|
||||
}
|
||||
|
||||
// Save decode status for next frame
|
||||
stream.doDecodeNextFrame = decodeForNextTick(stream);
|
||||
}
|
||||
|
||||
return hasMoreFrames;
|
||||
}
|
||||
|
||||
virtual void draw(const Handle handle, const touchgfx::Rect& invalidatedArea, const touchgfx::VideoWidget& widget)
|
||||
{
|
||||
assert(handle < no_streams);
|
||||
|
||||
if (output_format != Bitmap::RGB565 && output_format != Bitmap::RGB888 && output_format != Bitmap::ARGB8888)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (mjpegDecoders[handle]->hasVideo())
|
||||
{
|
||||
uint8_t* wbuf = (uint8_t*)touchgfx::HAL::getInstance()->lockFrameBufferForRenderingMethod(touchgfx::HAL::HARDWARE);
|
||||
touchgfx::Rect absolute = widget.getAbsoluteRect();
|
||||
DisplayTransformation::transformDisplayToFrameBuffer(absolute);
|
||||
|
||||
// Get frame buffer pointer to upper left of widget in framebuffer coordinates
|
||||
switch (output_format)
|
||||
{
|
||||
case Bitmap::RGB565:
|
||||
wbuf += (absolute.x + absolute.y * touchgfx::HAL::FRAME_BUFFER_WIDTH) * 2;
|
||||
break;
|
||||
case Bitmap::RGB888:
|
||||
wbuf += (absolute.x + absolute.y * touchgfx::HAL::FRAME_BUFFER_WIDTH) * 3;
|
||||
break;
|
||||
case Bitmap::ARGB8888:
|
||||
wbuf += (absolute.x + absolute.y * touchgfx::HAL::FRAME_BUFFER_WIDTH) * 4;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Decode relevant part of the frame to the framebuffer
|
||||
mjpegDecoders[handle]->decodeFrame(invalidatedArea, wbuf, touchgfx::HAL::FRAME_BUFFER_WIDTH);
|
||||
// Release frame buffer
|
||||
touchgfx::HAL::getInstance()->unlockFrameBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void addDecoder(MJPEGDecoder& decoder, uint32_t index)
|
||||
{
|
||||
assert(index < no_streams);
|
||||
mjpegDecoders[index] = &decoder;
|
||||
}
|
||||
|
||||
virtual uint32_t getCurrentFrameNumber(const Handle handle)
|
||||
{
|
||||
assert(handle < no_streams);
|
||||
Stream& stream = streams[handle];
|
||||
|
||||
return stream.frameNumber;
|
||||
}
|
||||
|
||||
virtual void getVideoInformation(const Handle handle, touchgfx::VideoInformation* data)
|
||||
{
|
||||
assert(handle < no_streams);
|
||||
mjpegDecoders[handle]->getVideoInfo(data);
|
||||
}
|
||||
|
||||
virtual bool getIsPlaying(const Handle handle)
|
||||
{
|
||||
assert(handle < no_streams);
|
||||
Stream& stream = streams[handle];
|
||||
return stream.isPlaying;
|
||||
}
|
||||
|
||||
virtual void setVideoFrameRateCompensation(const bool allow)
|
||||
{
|
||||
allowSkipFrames = allow;
|
||||
}
|
||||
|
||||
private:
|
||||
class Stream
|
||||
{
|
||||
public:
|
||||
Stream()
|
||||
: frameCount(0), frameNumber(0), tickCount(0),
|
||||
frame_rate_video(0), frame_rate_ticks(0),
|
||||
seek_to_frame(0),
|
||||
isActive(false), isPlaying(false), isShowingOneFrame(false), repeat(true),
|
||||
doDecodeNextFrame(false)
|
||||
{
|
||||
}
|
||||
uint32_t frameCount; // Video frames decoded since play
|
||||
uint32_t frameNumber; // Video frame showed number
|
||||
uint32_t tickCount; // UI frames since play
|
||||
uint32_t frame_rate_video; // Ratio of frames wanted counter
|
||||
uint32_t frame_rate_ticks; // Ratio of frames wanted divider
|
||||
uint32_t seek_to_frame; // Requested next frame number
|
||||
uint32_t skip_frames; // Number of frames to skip to keep frame rate
|
||||
bool isActive;
|
||||
bool isPlaying;
|
||||
bool isShowingOneFrame;
|
||||
bool repeat;
|
||||
bool doDecodeNextFrame; // High if we should go to next frame in next tick
|
||||
};
|
||||
|
||||
MJPEGDecoder* mjpegDecoders[no_streams];
|
||||
Stream streams[no_streams];
|
||||
bool allowSkipFrames;
|
||||
|
||||
/**
|
||||
* Return true, if new video frame should be decoded for the next tick (keep video decode framerate low)
|
||||
*/
|
||||
bool decodeForNextTick(Stream& stream)
|
||||
{
|
||||
// Running in UI thread
|
||||
|
||||
// Compare tickCount/frameNumber to frame_rate_ticks/frame_rate_video
|
||||
if ((stream.tickCount * stream.frame_rate_video) > (stream.frame_rate_ticks * stream.frameCount))
|
||||
{
|
||||
if (allowSkipFrames)
|
||||
{
|
||||
stream.skip_frames = (stream.tickCount * stream.frame_rate_video - stream.frame_rate_ticks * stream.frameCount) / stream.frame_rate_ticks;
|
||||
if (stream.skip_frames > 0)
|
||||
{
|
||||
stream.skip_frames--;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Handle getFreeHandle()
|
||||
{
|
||||
for (uint32_t i = 0; i < no_streams; i++)
|
||||
{
|
||||
if (streams[i].isActive == false)
|
||||
{
|
||||
return static_cast<VideoController::Handle>(i);
|
||||
}
|
||||
}
|
||||
|
||||
assert(0 && "Unable to find free video stream handle!");
|
||||
return static_cast<VideoController::Handle>(0);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // TOUCHGFX_DIRECTFRAMEBUFFERVIDEOCONTROLLER_HPP
|
||||
@@ -0,0 +1,934 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* File Name : HardwareMJPEGDecoder.cpp
|
||||
******************************************************************************
|
||||
* This file is generated by TouchGFX Generator 4.26.1. Please, do not edit!
|
||||
******************************************************************************
|
||||
* @attention
|
||||
*
|
||||
* Copyright (c) 2026 STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file
|
||||
* in the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <HardwareMJPEGDecoder.hpp>
|
||||
#include <touchgfx/hal/BlitOp.hpp>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <string.h>
|
||||
#include <stm32h7rsxx_hal.h>
|
||||
|
||||
uint32_t JPEG_Decode_DMA(JPEG_HandleTypeDef* hjpeg, uint8_t* input, uint32_t chunkSizeIn, uint8_t* output);
|
||||
uint32_t JPEG_OutputHandler(JPEG_HandleTypeDef* hjpeg);
|
||||
void HAL_JPEG_DecodeCpltCallback(JPEG_HandleTypeDef* hjpeg);
|
||||
void HAL_JPEG_ErrorCallback(JPEG_HandleTypeDef* hjpeg);
|
||||
void HAL_JPEG_DataReadyCallback(JPEG_HandleTypeDef* hjpeg, uint8_t* pDataOut, uint32_t OutDataLength);
|
||||
void DMA2D_CropBuffer(JPEG_Data_BufferTypeDef& job);
|
||||
void DMA2D_CopyBuffer(JPEG_Data_BufferTypeDef& job);
|
||||
void DMA2D_ExternalJobCompleted(JPEG_Data_BufferTypeDef& job);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
uint8_t* FrameBufferAddress;
|
||||
uint32_t JPEG_InputImageIndex;
|
||||
uint32_t JPEG_InputImageSize_Bytes;
|
||||
uint32_t JPEG_InputImageAddress;
|
||||
volatile uint32_t Jpeg_HWDecodingEnd = 0;
|
||||
volatile uint32_t JPEG_output_is_paused = 0;
|
||||
volatile uint32_t JpegProcessing_End = 0;
|
||||
uint32_t MCU_TotalNb = 0;
|
||||
touchgfx::DMA_Interface* DMA2D_reference;
|
||||
volatile uint32_t JPEG_OUT_Write_BufferIndex = 0;
|
||||
uint32_t FrameBufferWidth;
|
||||
volatile uint32_t line_count = 0;
|
||||
}
|
||||
|
||||
#define MCU_WIDTH_PIXELS ((uint32_t)16)
|
||||
#define MCU_HEIGHT_PIXELS ((uint32_t)16)
|
||||
#define MCU_CHROMA_420_SIZE_BYTES ((uint32_t)384)
|
||||
|
||||
#define CHUNK_SIZE_IN ((uint32_t)(1024*52)) /* Max block size */
|
||||
#define CHUNK_SIZE_OUT ((uint32_t)(MCU_CHROMA_420_SIZE_BYTES * ((800 + MCU_WIDTH_PIXELS - 1) / MCU_WIDTH_PIXELS)))
|
||||
|
||||
LOCATION_PRAGMA_32("TouchGFX_MCU_Data_OutBuffer0")
|
||||
uint8_t MCU_Data_OutBuffer0[CHUNK_SIZE_OUT] LOCATION_ATTRIBUTE_32("TouchGFX_MCU_Data_OutBuffer0");
|
||||
LOCATION_PRAGMA_32("TouchGFX_MCU_Data_OutBuffer1")
|
||||
uint8_t MCU_Data_OutBuffer1[CHUNK_SIZE_OUT] LOCATION_ATTRIBUTE_32("TouchGFX_MCU_Data_OutBuffer1");
|
||||
|
||||
uint8_t MCU_Cropping_Buffer[MCU_WIDTH_PIXELS * MCU_HEIGHT_PIXELS * 2];
|
||||
|
||||
__IO uint32_t MCU_BlockIndex = 0;
|
||||
|
||||
SEM_TYPE semDecodingDone;
|
||||
|
||||
extern JPEG_ConfTypeDef* JPEG_Info;
|
||||
extern JPEG_HandleTypeDef hjpeg;
|
||||
|
||||
volatile uint32_t JPEG_OUT_Read_BufferIndex = 0;
|
||||
volatile uint32_t DMA2D_CopyBufferEnd = 0;
|
||||
|
||||
JPEG_Data_BufferTypeDef Jpeg_OUT_BufferTab[NB_OUTPUT_DATA_BUFFERS] =
|
||||
{
|
||||
{JPEG_BUFFER_EMPTY, MCU_Data_OutBuffer0, 0, 0, NULL, false, false, 0, false},
|
||||
{JPEG_BUFFER_EMPTY, MCU_Data_OutBuffer1, 0, 0, NULL, false, false, 0, false},
|
||||
};
|
||||
|
||||
static struct JPEG_MCU_RGB_Converter
|
||||
{
|
||||
uint32_t WidthExtend;
|
||||
uint32_t ScaledWidth;
|
||||
uint32_t LastLineHeight;
|
||||
uint32_t MCU_pr_line;
|
||||
uint32_t bytes_pr_pixel;
|
||||
uint32_t startY;
|
||||
uint32_t endY;
|
||||
uint32_t startX;
|
||||
uint32_t endX;
|
||||
uint32_t MCUStart;
|
||||
uint32_t MCUEnd;
|
||||
uint32_t MCU_pr_job;
|
||||
uint32_t firstColOffset;
|
||||
uint32_t firstRowOffset;
|
||||
uint32_t lastColOffset;
|
||||
uint32_t lastRowOffset;
|
||||
} JPEG_ConvertorParams;
|
||||
|
||||
HardwareMJPEGDecoder::HardwareMJPEGDecoder()
|
||||
: frameNumber(0), currentMovieOffset(0), indexOffset(0), firstFrameOffset(0), lastFrameEnd(0), movieLength(0), movieData(0),
|
||||
reader(0), aviBuffer(0), aviBufferLength(0), aviBufferStartOffset(0), lastError(AVI_NO_ERROR)
|
||||
{
|
||||
/* Clear video info */
|
||||
videoInfo.frame_height = 0;
|
||||
videoInfo.frame_width = 0;
|
||||
videoInfo.ms_between_frames = 0;
|
||||
videoInfo.number_of_frames = 0;
|
||||
|
||||
/* Create decoding semaphore */
|
||||
semDecodingDone = SEM_CREATE();
|
||||
}
|
||||
|
||||
int HardwareMJPEGDecoder::compare(const uint32_t offset, const char* str, uint32_t num)
|
||||
{
|
||||
const char* src;
|
||||
if (reader != 0)
|
||||
{
|
||||
/* Assuming data is in buffer! */
|
||||
src = reinterpret_cast<const char*>(aviBuffer + (offset - aviBufferStartOffset));
|
||||
}
|
||||
else
|
||||
{
|
||||
src = (const char*)movieData + offset;
|
||||
}
|
||||
return strncmp(src, str, num);
|
||||
}
|
||||
|
||||
inline uint32_t HardwareMJPEGDecoder::getU32(const uint32_t offset)
|
||||
{
|
||||
if (reader != 0)
|
||||
{
|
||||
/* Assuming data is in buffer! */
|
||||
const uint32_t index = offset - aviBufferStartOffset;
|
||||
return aviBuffer[index + 0] | (aviBuffer[index + 1] << 8) | (aviBuffer[index + 2] << 16) | (aviBuffer[index + 3] << 24);
|
||||
}
|
||||
else
|
||||
{
|
||||
volatile const uint8_t* const d = movieData + offset;
|
||||
uint32_t val = 0U;
|
||||
val |= d[0];
|
||||
val |= d[1] << 8;
|
||||
val |= d[2] << 16;
|
||||
val |= d[3] << 24;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32_t HardwareMJPEGDecoder::getU16(const uint32_t offset)
|
||||
{
|
||||
if (reader != 0)
|
||||
{
|
||||
/* Assuming data is in buffer! */
|
||||
const uint32_t index = offset - aviBufferStartOffset;
|
||||
return aviBuffer[index + 0] | (aviBuffer[index + 1] << 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
volatile const uint8_t* const d = movieData + offset;
|
||||
uint32_t val = 0U;
|
||||
val |= d[0];
|
||||
val |= d[1] << 8;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t* HardwareMJPEGDecoder::readData(uint32_t offset, uint32_t length)
|
||||
{
|
||||
if (reader != 0)
|
||||
{
|
||||
if (length > aviBufferLength)
|
||||
{
|
||||
lastError = AVI_ERROR_FILE_BUFFER_TO_SMALL;
|
||||
assert(!"Buffer to small");
|
||||
}
|
||||
|
||||
reader->seek(offset);
|
||||
if (!reader->readData(aviBuffer, length))
|
||||
{
|
||||
lastError = AVI_ERROR_EOF_REACHED;
|
||||
}
|
||||
|
||||
aviBufferStartOffset = offset;
|
||||
return aviBuffer;
|
||||
}
|
||||
|
||||
return movieData + offset;
|
||||
}
|
||||
|
||||
bool HardwareMJPEGDecoder::decodeNextFrame(uint8_t* buffer, uint16_t buffer_width, uint16_t buffer_height, uint32_t buffer_stride)
|
||||
{
|
||||
assert((frameNumber > 0) && "HardwareMJPEGDecoder decoding without frame data!");
|
||||
|
||||
/* find next frame and decode it */
|
||||
readData(currentMovieOffset, 8);
|
||||
uint32_t streamNo = getU16(currentMovieOffset);
|
||||
uint32_t chunkType = getU16(currentMovieOffset + 2);
|
||||
uint32_t chunkSize = getU32(currentMovieOffset + 4);
|
||||
|
||||
const uint16_t STREAM0 = 0x3030;
|
||||
const uint16_t TYPEDC = 0x6364;
|
||||
|
||||
bool isCurrentFrameLast;
|
||||
/* play frame if we have it all */
|
||||
if (currentMovieOffset + 8 + chunkSize < movieLength)
|
||||
{
|
||||
if (streamNo == STREAM0 && chunkType == TYPEDC && chunkSize > 0)
|
||||
{
|
||||
currentMovieOffset += 8;
|
||||
/* decode frame */
|
||||
const uint8_t* chunk = readData(currentMovieOffset, chunkSize);
|
||||
decodeMJPEGFrame(chunk, chunkSize, buffer, buffer_width, buffer_height, buffer_stride);
|
||||
frameNumber++;
|
||||
}
|
||||
|
||||
isCurrentFrameLast = false;
|
||||
|
||||
/* Advance to next frame */
|
||||
currentMovieOffset += chunkSize;
|
||||
if (chunkSize == 0) /* Empty frame - Skip */
|
||||
{
|
||||
currentMovieOffset += 8;
|
||||
}
|
||||
currentMovieOffset = (currentMovieOffset + 1) & 0xFFFFFFFE; /* pad to next word */
|
||||
|
||||
if (currentMovieOffset == lastFrameEnd)
|
||||
{
|
||||
frameNumber = 1;
|
||||
currentMovieOffset = firstFrameOffset; /* start over */
|
||||
isCurrentFrameLast = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
frameNumber = 1;
|
||||
currentMovieOffset = firstFrameOffset; /* start over */
|
||||
isCurrentFrameLast = true;
|
||||
}
|
||||
return !isCurrentFrameLast;
|
||||
}
|
||||
|
||||
bool HardwareMJPEGDecoder::gotoNextFrame()
|
||||
{
|
||||
assert((frameNumber > 0) && "HardwareMJPEGDecoder decoding without frame data!");
|
||||
|
||||
readData(currentMovieOffset, 8);
|
||||
uint32_t chunkSize = getU32(currentMovieOffset + 4);
|
||||
|
||||
/* increment until next video frame */
|
||||
while (currentMovieOffset + 8 + chunkSize < movieLength)
|
||||
{
|
||||
/* increment one frame */
|
||||
currentMovieOffset += chunkSize + 8;
|
||||
currentMovieOffset = (currentMovieOffset + 1) & 0xFFFFFFFE; /* pad to next word */
|
||||
frameNumber++;
|
||||
|
||||
/* next chunk */
|
||||
readData(currentMovieOffset, 8);
|
||||
/* check it is a video frame */
|
||||
uint32_t streamNo = getU16(currentMovieOffset);
|
||||
uint32_t chunkType = getU16(currentMovieOffset + 2);
|
||||
chunkSize = getU32(currentMovieOffset + 4);
|
||||
const uint16_t STREAM0 = 0x3030;
|
||||
const uint16_t TYPEDC = 0x6364;
|
||||
|
||||
if (streamNo == STREAM0 && chunkType == TYPEDC)
|
||||
{
|
||||
/* Found next frame */
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* skip back to first frame */
|
||||
frameNumber = 1;
|
||||
currentMovieOffset = firstFrameOffset; /* start over */
|
||||
return false;
|
||||
}
|
||||
|
||||
void HardwareMJPEGDecoder::setVideoData(const uint8_t* movie, const uint32_t length)
|
||||
{
|
||||
movieData = movie;
|
||||
movieLength = length;
|
||||
reader = 0; /* not using reader */
|
||||
|
||||
readVideoHeader();
|
||||
}
|
||||
|
||||
void HardwareMJPEGDecoder::setVideoData(touchgfx::VideoDataReader& reader)
|
||||
{
|
||||
this->reader = &reader;
|
||||
movieData = 0;
|
||||
movieLength = reader.getDataLength();
|
||||
|
||||
readVideoHeader();
|
||||
}
|
||||
|
||||
bool HardwareMJPEGDecoder::hasVideo()
|
||||
{
|
||||
return (reader != 0) || (movieData != 0);
|
||||
}
|
||||
|
||||
void HardwareMJPEGDecoder::readVideoHeader()
|
||||
{
|
||||
/* Start from the start */
|
||||
currentMovieOffset = 0;
|
||||
lastError = AVI_NO_ERROR;
|
||||
|
||||
/* Make header available in buffer */
|
||||
readData(0, 72);
|
||||
|
||||
/* Decode the movie header to find first frame */
|
||||
/* Must be RIFF file */
|
||||
if (compare(currentMovieOffset, "RIFF", 4))
|
||||
{
|
||||
lastError = AVI_ERROR_NOT_RIFF;
|
||||
assert(!"RIFF header not found");
|
||||
}
|
||||
|
||||
/* skip fourcc and length */
|
||||
currentMovieOffset += 8;
|
||||
if (compare(currentMovieOffset, "AVI ", 4))
|
||||
{
|
||||
lastError = AVI_ERROR_AVI_HEADER_NOT_FOUND;
|
||||
assert(!"AVI header not found");
|
||||
}
|
||||
|
||||
currentMovieOffset += 4;
|
||||
if (compare(currentMovieOffset, "LIST", 4))
|
||||
{
|
||||
lastError = AVI_ERROR_AVI_LIST_NOT_FOUND;
|
||||
assert(!"AVI LIST not found");
|
||||
}
|
||||
|
||||
/* save AVI List info */
|
||||
const uint32_t aviListSize = getU32(currentMovieOffset + 4);
|
||||
const uint32_t aviListOffset = currentMovieOffset;
|
||||
assert(aviListSize);
|
||||
|
||||
/* look into header to find frame rate */
|
||||
bool foundFrame = true;
|
||||
uint32_t offset = currentMovieOffset + 8;
|
||||
if (compare(offset, "hdrl", 4))
|
||||
{
|
||||
lastError = AVI_ERROR_AVI_HDRL_NOT_FOUND;
|
||||
foundFrame = false;
|
||||
}
|
||||
|
||||
offset += 4;
|
||||
if (compare(offset, "avih", 4))
|
||||
{
|
||||
lastError = AVI_ERROR_AVI_AVIH_NOT_FOUND;
|
||||
foundFrame = false;
|
||||
}
|
||||
|
||||
if (foundFrame)
|
||||
{
|
||||
offset += 8; /* skip fourcc and cb in AVIMAINHEADER */
|
||||
videoInfo.ms_between_frames = getU32(offset) / 1000;
|
||||
videoInfo.number_of_frames = getU32(offset + 16);
|
||||
videoInfo.frame_width = getU32(offset + 32);
|
||||
videoInfo.frame_height = getU32(offset + 36);
|
||||
}
|
||||
/* skip rest of AVI header, start from end of AVI List */
|
||||
|
||||
/* look for list with 'movi' header */
|
||||
uint32_t listOffset = aviListOffset + aviListSize + 8;
|
||||
readData(listOffset, 12);
|
||||
while (compare(listOffset + 8, "movi", 4) && (lastError == AVI_NO_ERROR) && listOffset < movieLength)
|
||||
{
|
||||
const uint32_t listSize = getU32(listOffset + 4) + 8;
|
||||
listOffset += listSize;
|
||||
readData(listOffset, 12);
|
||||
}
|
||||
|
||||
if (lastError != AVI_NO_ERROR)
|
||||
{
|
||||
lastError = AVI_ERROR_MOVI_NOT_FOUND;
|
||||
return;
|
||||
}
|
||||
|
||||
/* save first frame and end of last frame */
|
||||
currentMovieOffset = listOffset + 8 + 4; /* skip LIST and 'movi' */
|
||||
lastFrameEnd = listOffset + 8 + getU32(listOffset + 4);
|
||||
|
||||
/* find idx */
|
||||
const uint32_t listSize = getU32(listOffset + 4) + 8;
|
||||
listOffset += listSize;
|
||||
readData(listOffset, 4);
|
||||
if (!compare(listOffset, "idx1", 4))
|
||||
{
|
||||
indexOffset = listOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = AVI_ERROR_IDX1_NOT_FOUND;
|
||||
return;
|
||||
}
|
||||
|
||||
/* start on first frame */
|
||||
frameNumber = 1; /* next frame number is 1 */
|
||||
firstFrameOffset = currentMovieOffset;
|
||||
}
|
||||
|
||||
void HardwareMJPEGDecoder::decodeMJPEGFrame(const uint8_t* const mjpgdata, const uint32_t length, uint8_t* outputBuffer, uint16_t bufferWidth, uint16_t bufferHeight, uint32_t bufferStride)
|
||||
{
|
||||
if (length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (outputBuffer) /* only decode if buffers are assigned. */
|
||||
{
|
||||
/* Update JPEG conversion parameters */
|
||||
JPEG_ConvertorParams.bytes_pr_pixel = 2;
|
||||
JPEG_ConvertorParams.WidthExtend = videoInfo.frame_width;
|
||||
if ((JPEG_ConvertorParams.WidthExtend % 16) != 0)
|
||||
{
|
||||
JPEG_ConvertorParams.WidthExtend += 16 - (JPEG_ConvertorParams.WidthExtend % 16);
|
||||
}
|
||||
JPEG_ConvertorParams.ScaledWidth = 800 * JPEG_ConvertorParams.bytes_pr_pixel;
|
||||
JPEG_ConvertorParams.MCU_pr_line = JPEG_ConvertorParams.WidthExtend / MCU_WIDTH_PIXELS;
|
||||
JPEG_ConvertorParams.LastLineHeight = (videoInfo.frame_height % MCU_HEIGHT_PIXELS) == 0 ? 0 : MCU_HEIGHT_PIXELS - (videoInfo.frame_height % MCU_HEIGHT_PIXELS);
|
||||
|
||||
FrameBufferWidth = bufferStride / 2;
|
||||
FrameBufferAddress = outputBuffer;
|
||||
|
||||
JPEG_Decode_DMA(&hjpeg, const_cast<uint8_t*>(mjpgdata), length, outputBuffer);
|
||||
DMA2D_reference = dma;
|
||||
do
|
||||
{
|
||||
JpegProcessing_End = JPEG_OutputHandler(&hjpeg);
|
||||
|
||||
/* If nothing to do, allow other tasks */
|
||||
if (JpegProcessing_End == 2)
|
||||
{
|
||||
SEM_WAIT(semDecodingDone);
|
||||
}
|
||||
} while (JpegProcessing_End != 1);
|
||||
|
||||
/* reset flag */
|
||||
Jpeg_HWDecodingEnd = 0;
|
||||
DMA2D_CopyBufferEnd = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool HardwareMJPEGDecoder::decodeFrame(const touchgfx::Rect& area, uint8_t* frameBuffer, uint32_t framebuffer_width)
|
||||
{
|
||||
/* Assuming that chunk is available and streamNo and chunkType is correct. */
|
||||
/* Check by gotoNextFrame */
|
||||
readData(currentMovieOffset, 8);
|
||||
const uint32_t length = getU32(currentMovieOffset + 4);
|
||||
|
||||
/* Ensure whole frame is read */
|
||||
const uint8_t* mjpgdata = readData(currentMovieOffset + 8, length);
|
||||
|
||||
/* Update JPEG conversion parameters */
|
||||
JPEG_ConvertorParams.bytes_pr_pixel = 2;
|
||||
JPEG_ConvertorParams.WidthExtend = videoInfo.frame_width;
|
||||
if ((JPEG_ConvertorParams.WidthExtend % 16) != 0)
|
||||
{
|
||||
JPEG_ConvertorParams.WidthExtend += 16 - (JPEG_ConvertorParams.WidthExtend % 16);
|
||||
}
|
||||
JPEG_ConvertorParams.ScaledWidth = 800 * JPEG_ConvertorParams.bytes_pr_pixel;
|
||||
JPEG_ConvertorParams.MCU_pr_line = JPEG_ConvertorParams.WidthExtend / MCU_WIDTH_PIXELS;
|
||||
JPEG_ConvertorParams.startY = area.y;
|
||||
JPEG_ConvertorParams.endY = MIN((uint32_t)area.bottom(), videoInfo.frame_height);
|
||||
JPEG_ConvertorParams.startX = area.x;
|
||||
JPEG_ConvertorParams.endX = MIN((uint32_t)area.right(), videoInfo.frame_width);
|
||||
JPEG_ConvertorParams.MCUStart = JPEG_ConvertorParams.startX / MCU_WIDTH_PIXELS;
|
||||
JPEG_ConvertorParams.MCUEnd = (JPEG_ConvertorParams.endX + MCU_WIDTH_PIXELS - 1) / MCU_WIDTH_PIXELS; // Ceil division
|
||||
JPEG_ConvertorParams.MCU_pr_job = JPEG_ConvertorParams.MCUEnd - JPEG_ConvertorParams.MCUStart;
|
||||
JPEG_ConvertorParams.firstColOffset = JPEG_ConvertorParams.startX % MCU_WIDTH_PIXELS;
|
||||
JPEG_ConvertorParams.firstRowOffset = JPEG_ConvertorParams.startY % MCU_HEIGHT_PIXELS;
|
||||
JPEG_ConvertorParams.lastColOffset = (JPEG_ConvertorParams.endX % MCU_WIDTH_PIXELS) == 0 ? 0 : MCU_WIDTH_PIXELS - (JPEG_ConvertorParams.endX % MCU_WIDTH_PIXELS);
|
||||
JPEG_ConvertorParams.lastRowOffset = (JPEG_ConvertorParams.endY % MCU_HEIGHT_PIXELS) == 0 ? 0 : MCU_HEIGHT_PIXELS - (JPEG_ConvertorParams.endY % MCU_HEIGHT_PIXELS);
|
||||
|
||||
FrameBufferWidth = framebuffer_width;
|
||||
FrameBufferAddress = frameBuffer;
|
||||
|
||||
JPEG_Decode_DMA(&hjpeg, const_cast<uint8_t*>(mjpgdata), length, frameBuffer);
|
||||
DMA2D_reference = dma;
|
||||
do
|
||||
{
|
||||
JpegProcessing_End = JPEG_OutputHandler(&hjpeg);
|
||||
|
||||
/* If nothing to do, wait */
|
||||
if (JpegProcessing_End == 2)
|
||||
{
|
||||
SEM_WAIT(semDecodingDone);
|
||||
}
|
||||
} while (JpegProcessing_End != 1);
|
||||
|
||||
/* reset flag */
|
||||
Jpeg_HWDecodingEnd = 0;
|
||||
DMA2D_CopyBufferEnd = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HardwareMJPEGDecoder::decodeThumbnail(uint32_t frameno, uint8_t* buffer, uint16_t width, uint16_t height)
|
||||
{
|
||||
assert(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
void HardwareMJPEGDecoder::gotoFrame(uint32_t frameNumber)
|
||||
{
|
||||
if (frameNumber == 0)
|
||||
{
|
||||
frameNumber = 1;
|
||||
}
|
||||
|
||||
if (frameNumber > getNumberOfFrames())
|
||||
{
|
||||
frameNumber = getNumberOfFrames();
|
||||
}
|
||||
|
||||
uint32_t offset = indexOffset + 8 + (frameNumber - 1) * 16;
|
||||
|
||||
readData(offset, 16);
|
||||
|
||||
currentMovieOffset = getU32(offset + 8) + firstFrameOffset - 4;
|
||||
this->frameNumber = frameNumber;
|
||||
}
|
||||
|
||||
uint32_t HardwareMJPEGDecoder::getNumberOfFrames()
|
||||
{
|
||||
return videoInfo.number_of_frames;
|
||||
}
|
||||
|
||||
void HardwareMJPEGDecoder::setRepeatVideo(bool repeat)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void HardwareMJPEGDecoder::getVideoInfo(touchgfx::VideoInformation* data)
|
||||
{
|
||||
*data = videoInfo;
|
||||
}
|
||||
|
||||
/* C HELPER FUNCTIONS */
|
||||
|
||||
extern "C"
|
||||
{
|
||||
/**
|
||||
* @brief Decode_DMA
|
||||
* @param hjpeg: JPEG handle pointer
|
||||
* @param JPEGImageBufferAddress : jpg image buffer Address.
|
||||
* @param JPEGImageSize_Bytes : jpg image size in bytes.
|
||||
* @param DestAddress : ARGB8888 destination Frame Buffer Address.
|
||||
* @retval None
|
||||
*/
|
||||
uint32_t JPEG_Decode_DMA(JPEG_HandleTypeDef* hjpeg, uint8_t* input, uint32_t chunkSizeIn /* length */, uint8_t* output)
|
||||
{
|
||||
JPEG_output_is_paused = 0;
|
||||
JPEG_OUT_Read_BufferIndex = 0;
|
||||
JPEG_OUT_Write_BufferIndex = 0;
|
||||
JPEG_InputImageIndex = 0;
|
||||
JPEG_InputImageAddress = (uint32_t)input;
|
||||
JPEG_InputImageSize_Bytes = chunkSizeIn;
|
||||
MCU_BlockIndex = 0;
|
||||
line_count = 0;
|
||||
|
||||
/* Init buffers */
|
||||
for (uint32_t i = 0; i < NB_OUTPUT_DATA_BUFFERS; ++i)
|
||||
{
|
||||
Jpeg_OUT_BufferTab[i].State = JPEG_BUFFER_EMPTY;
|
||||
Jpeg_OUT_BufferTab[i].DataBufferSize = 0;
|
||||
Jpeg_OUT_BufferTab[i].MCU_index = 0;
|
||||
Jpeg_OUT_BufferTab[i].OutputBuffer = NULL;
|
||||
Jpeg_OUT_BufferTab[i].MCU_drawn = 0;
|
||||
Jpeg_OUT_BufferTab[i].DoCropping = false;
|
||||
Jpeg_OUT_BufferTab[i].FirstJob = false;
|
||||
Jpeg_OUT_BufferTab[i].LastJob = false;
|
||||
}
|
||||
Jpeg_OUT_BufferTab[0].FirstJob = true;
|
||||
if (JPEG_ConvertorParams.firstRowOffset != 0)
|
||||
{
|
||||
Jpeg_OUT_BufferTab[0].DoCropping = true;
|
||||
}
|
||||
|
||||
/* Do not return from this function until done with decoding all chunks. */
|
||||
HAL_JPEG_Decode_DMA(hjpeg, input, CHUNK_SIZE_IN, Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].DataBuffer, MCU_CHROMA_420_SIZE_BYTES * JPEG_ConvertorParams.MCU_pr_line);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief JPEG Info ready callback
|
||||
* @param hjpeg: JPEG handle pointer
|
||||
* @param pInfo: JPEG Info Struct pointer
|
||||
* @retval None
|
||||
*/
|
||||
void HAL_JPEG_InfoReadyCallback(JPEG_HandleTypeDef* hjpeg, JPEG_ConfTypeDef* pInfo)
|
||||
{
|
||||
uint32_t hMCU, vMCU;
|
||||
|
||||
if (pInfo->ChromaSubsampling == JPEG_420_SUBSAMPLING)
|
||||
{
|
||||
if ((pInfo->ImageWidth % 16) != 0)
|
||||
{
|
||||
pInfo->ImageWidth += (16 - (pInfo->ImageWidth % 16));
|
||||
}
|
||||
|
||||
if ((pInfo->ImageHeight % 16) != 0)
|
||||
{
|
||||
pInfo->ImageHeight += (16 - (pInfo->ImageHeight % 16));
|
||||
}
|
||||
|
||||
hMCU = (pInfo->ImageWidth / MCU_WIDTH_PIXELS);
|
||||
vMCU = (pInfo->ImageHeight / MCU_HEIGHT_PIXELS);
|
||||
MCU_TotalNb = (hMCU * vMCU);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pInfo->ChromaSubsampling == JPEG_422_SUBSAMPLING)
|
||||
{
|
||||
if ((pInfo->ImageWidth % 16) != 0)
|
||||
{
|
||||
pInfo->ImageWidth += (16 - (pInfo->ImageWidth % 16));
|
||||
}
|
||||
|
||||
if ((pInfo->ImageHeight % 8) != 0)
|
||||
{
|
||||
pInfo->ImageHeight += (8 - (pInfo->ImageHeight % 8));
|
||||
}
|
||||
}
|
||||
|
||||
if (pInfo->ChromaSubsampling == JPEG_444_SUBSAMPLING)
|
||||
{
|
||||
if ((pInfo->ImageWidth % 8) != 0)
|
||||
{
|
||||
pInfo->ImageWidth += (8 - (pInfo->ImageWidth % 8));
|
||||
}
|
||||
|
||||
if ((pInfo->ImageHeight % 8) != 0)
|
||||
{
|
||||
pInfo->ImageHeight += (8 - (pInfo->ImageHeight % 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief JPEG Get Data callback.
|
||||
* @param hjpeg: JPEG handle pointer
|
||||
* @param NbDecodedData: Number of decoded (consummed) bytes from input buffer
|
||||
* @retval None
|
||||
*/
|
||||
void HAL_JPEG_GetDataCallback(JPEG_HandleTypeDef* hjpeg, uint32_t NbDecodedData)
|
||||
{
|
||||
/* Input buffer has been consumed by the peripheral and to ask for a new data chunk if the operation (encoding/decoding) has not been complete yet. */
|
||||
JPEG_InputImageIndex += NbDecodedData;
|
||||
if (JPEG_InputImageIndex < JPEG_InputImageSize_Bytes)
|
||||
{
|
||||
JPEG_InputImageAddress = JPEG_InputImageAddress + NbDecodedData;
|
||||
uint32_t inDataLength = JPEG_InputImageSize_Bytes - JPEG_InputImageIndex;
|
||||
HAL_JPEG_ConfigInputBuffer(hjpeg, (uint8_t*)JPEG_InputImageAddress, inDataLength >= CHUNK_SIZE_IN ? CHUNK_SIZE_IN : inDataLength);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief JPEG Data Ready callback. Data has been converted from JPEG to YCbCr.
|
||||
* @param hjpeg: JPEG handle pointer
|
||||
* @param pDataOut: pointer to the output data buffer
|
||||
* @param OutDataLength: length of output buffer in bytes
|
||||
* @retval None
|
||||
*/
|
||||
void HAL_JPEG_DataReadyCallback(JPEG_HandleTypeDef* hjpeg, uint8_t* pDataOut, uint32_t OutDataLength)
|
||||
{
|
||||
line_count += MCU_HEIGHT_PIXELS;
|
||||
|
||||
Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].OutputBuffer = FrameBufferAddress;
|
||||
|
||||
/* Increment framebuffer */
|
||||
FrameBufferAddress += FrameBufferWidth * MCU_HEIGHT_PIXELS * JPEG_ConvertorParams.bytes_pr_pixel;
|
||||
|
||||
/* Decode until we reach area to draw */
|
||||
if (line_count <= JPEG_ConvertorParams.startY)
|
||||
{
|
||||
HAL_JPEG_ConfigOutputBuffer(hjpeg, Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].DataBuffer, MCU_CHROMA_420_SIZE_BYTES * JPEG_ConvertorParams.MCU_pr_line);
|
||||
return;
|
||||
}
|
||||
|
||||
Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].State = JPEG_BUFFER_FULL;
|
||||
Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].DataBufferSize = OutDataLength;
|
||||
Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].MCU_drawn = 0;
|
||||
|
||||
/* Left column requires cropping */
|
||||
if (JPEG_ConvertorParams.firstColOffset != 0)
|
||||
{
|
||||
Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].DoCropping = true;
|
||||
}
|
||||
|
||||
if (line_count < JPEG_ConvertorParams.endY)
|
||||
{
|
||||
Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].LastJob = false;
|
||||
|
||||
JPEG_OUT_Write_BufferIndex++;
|
||||
if (JPEG_OUT_Write_BufferIndex >= NB_OUTPUT_DATA_BUFFERS)
|
||||
{
|
||||
JPEG_OUT_Write_BufferIndex = 0;
|
||||
}
|
||||
|
||||
/* if the other buffer is full, then ui thread might be converting it */
|
||||
if (Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].State != JPEG_BUFFER_EMPTY)
|
||||
{
|
||||
HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_OUTPUT);
|
||||
JPEG_output_is_paused = 1;
|
||||
}
|
||||
|
||||
HAL_JPEG_ConfigOutputBuffer(hjpeg, Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].DataBuffer, MCU_CHROMA_420_SIZE_BYTES * JPEG_ConvertorParams.MCU_pr_line);
|
||||
}
|
||||
|
||||
/* Stop decoding when we exit area to draw */
|
||||
if (line_count >= JPEG_ConvertorParams.endY)
|
||||
{
|
||||
Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].LastJob = true;
|
||||
Jpeg_HWDecodingEnd = 1;
|
||||
|
||||
HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_OUTPUT);
|
||||
}
|
||||
|
||||
/* Signal Hardware Decoding to wake up */
|
||||
if (!DMA2D_reference->isDMARunning())
|
||||
{
|
||||
SEM_POST(semDecodingDone);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief JPEG Error callback
|
||||
* @param hjpeg: JPEG handle pointer
|
||||
* @retval None
|
||||
*/
|
||||
void HAL_JPEG_ErrorCallback(JPEG_HandleTypeDef* hjpeg)
|
||||
{
|
||||
__disable_irq();
|
||||
while (1)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief JPEG Decode complete callback
|
||||
* @param hjpeg: JPEG handle pointer
|
||||
* @retval None
|
||||
*/
|
||||
void HAL_JPEG_DecodeCpltCallback(JPEG_HandleTypeDef* hjpeg)
|
||||
{
|
||||
Jpeg_HWDecodingEnd = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief JPEG Ouput Data BackGround Postprocessing .
|
||||
* @param hjpeg: JPEG handle pointer
|
||||
* @retval 1 : if JPEG processing has finished, 0 : if JPEG processing still ongoing
|
||||
*/
|
||||
uint32_t JPEG_OutputHandler(JPEG_HandleTypeDef* hjpeg)
|
||||
{
|
||||
/* Decode frame complete */
|
||||
if (Jpeg_HWDecodingEnd && DMA2D_CopyBufferEnd)
|
||||
{
|
||||
/* Abort any ongoing operations */
|
||||
if (HAL_JPEG_GetState(hjpeg) == HAL_JPEG_STATE_BUSY_DECODING)
|
||||
{
|
||||
HAL_JPEG_Abort(hjpeg);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Try to start DMA2D video transfer if next buffer if full */
|
||||
if (!DMA2D_reference->isDMARunning() && (Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].State == JPEG_BUFFER_FULL) && (DMA2D_CopyBufferEnd == 0))
|
||||
{
|
||||
DMA2D_reference->start();
|
||||
}
|
||||
|
||||
/* Start JPEG IP if paused and next buffer is empty */
|
||||
if ((JPEG_output_is_paused == 1) && (Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].State == JPEG_BUFFER_EMPTY) && (Jpeg_HWDecodingEnd == 0))
|
||||
{
|
||||
JPEG_output_is_paused = 0;
|
||||
HAL_JPEG_Resume(hjpeg, JPEG_PAUSE_RESUME_OUTPUT);
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configures external DMA2D job to copy YCbCr data to RGB buffer(s)
|
||||
* @param job: External job reference
|
||||
* @retval None
|
||||
*/
|
||||
void DMA2D_CopyBuffer(JPEG_Data_BufferTypeDef& job)
|
||||
{
|
||||
const uint32_t width = JPEG_ConvertorParams.MCU_pr_job * MCU_WIDTH_PIXELS - job.MCU_drawn * MCU_WIDTH_PIXELS - JPEG_ConvertorParams.lastColOffset;
|
||||
const uint32_t scaledWidth = (width % MCU_WIDTH_PIXELS) == 0 ? 0 : MCU_WIDTH_PIXELS - (width % MCU_WIDTH_PIXELS);
|
||||
const uint32_t srcOffset = (JPEG_ConvertorParams.MCUStart + job.MCU_drawn) * MCU_CHROMA_420_SIZE_BYTES;
|
||||
const uint32_t dstOffset = JPEG_ConvertorParams.MCUStart * MCU_WIDTH_PIXELS * JPEG_ConvertorParams.bytes_pr_pixel
|
||||
+ job.MCU_drawn * MCU_WIDTH_PIXELS * JPEG_ConvertorParams.bytes_pr_pixel;
|
||||
|
||||
/* Mark job as fully drawn */
|
||||
job.MCU_drawn = JPEG_ConvertorParams.MCU_pr_job;
|
||||
|
||||
/* DMA2D OPFCCR register configuration */
|
||||
WRITE_REG(DMA2D->OPFCCR, DMA2D_OUTPUT_RGB565);
|
||||
|
||||
/* Configure DMA2D data size */
|
||||
if (job.LastJob) /* Last line of frame */
|
||||
{
|
||||
WRITE_REG(DMA2D->NLR, (MCU_HEIGHT_PIXELS - JPEG_ConvertorParams.lastRowOffset) | (width << DMA2D_NLR_PL_Pos));
|
||||
}
|
||||
else
|
||||
{
|
||||
WRITE_REG(DMA2D->NLR, MCU_HEIGHT_PIXELS | (width << DMA2D_NLR_PL_Pos));
|
||||
}
|
||||
|
||||
/* Configure DMA2D destination address */
|
||||
WRITE_REG(DMA2D->OMAR, reinterpret_cast<uint32_t>(job.OutputBuffer + dstOffset));
|
||||
|
||||
/* DMA2D OOR register configuration */
|
||||
WRITE_REG(DMA2D->OOR, FrameBufferWidth - width);
|
||||
|
||||
/* DMA2D FGOR register configuration */
|
||||
WRITE_REG(DMA2D->FGOR, scaledWidth);
|
||||
|
||||
/* DMA2D FGPFCCR register configuration */
|
||||
WRITE_REG(DMA2D->FGPFCCR, DMA2D_INPUT_YCBCR | (DMA2D_CSS_420 << DMA2D_FGPFCCR_CSS_Pos) | (DMA2D_REPLACE_ALPHA << DMA2D_FGPFCCR_AM_Pos) | (0xFFU << DMA2D_FGPFCCR_ALPHA_Pos));
|
||||
|
||||
/* Configure DMA2D source address */
|
||||
WRITE_REG(DMA2D->FGMAR, reinterpret_cast<uint32_t>(job.DataBuffer + srcOffset));
|
||||
|
||||
/* Configure DMA2D contol register */
|
||||
WRITE_REG(DMA2D->CR, DMA2D_M2M_PFC | DMA2D_IT_TC | DMA2D_CR_START | DMA2D_IT_CE | DMA2D_IT_TE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configures external DMA2D job to copy and crop YCbCr data to an RGB cropping buffer
|
||||
* @param job: External job reference
|
||||
* @retval None
|
||||
*/
|
||||
void DMA2D_CropBuffer(JPEG_Data_BufferTypeDef& job)
|
||||
{
|
||||
const uint32_t colLeftOffset = job.MCU_drawn == 0 ? JPEG_ConvertorParams.firstColOffset : 0;
|
||||
const uint32_t colRightOffset = job.MCU_drawn == JPEG_ConvertorParams.MCU_pr_job - 1 ? JPEG_ConvertorParams.lastColOffset : 0;
|
||||
const uint32_t rowTopOffset = job.FirstJob ? JPEG_ConvertorParams.firstRowOffset : 0;
|
||||
const uint32_t rowBottomOffset = job.LastJob ? JPEG_ConvertorParams.lastRowOffset : 0;
|
||||
const uint32_t srcOffset = (JPEG_ConvertorParams.MCUStart + job.MCU_drawn) * MCU_CHROMA_420_SIZE_BYTES;
|
||||
const uint32_t dstOffset = JPEG_ConvertorParams.MCUStart * MCU_WIDTH_PIXELS * JPEG_ConvertorParams.bytes_pr_pixel
|
||||
+ job.MCU_drawn * MCU_WIDTH_PIXELS * JPEG_ConvertorParams.bytes_pr_pixel
|
||||
+ rowTopOffset * JPEG_ConvertorParams.bytes_pr_pixel * FrameBufferWidth
|
||||
+ colLeftOffset * JPEG_ConvertorParams.bytes_pr_pixel;
|
||||
const uint32_t cropSrcOffset = colLeftOffset * JPEG_ConvertorParams.bytes_pr_pixel
|
||||
+ rowTopOffset * JPEG_ConvertorParams.bytes_pr_pixel * MCU_HEIGHT_PIXELS;
|
||||
|
||||
/* Update job and assert if more cropping is needed */
|
||||
job.MCU_drawn++;
|
||||
if ((JPEG_ConvertorParams.firstRowOffset == 0) || !job.FirstJob)
|
||||
{
|
||||
job.DoCropping = false;
|
||||
}
|
||||
|
||||
/* Configure BlitOp */
|
||||
touchgfx::BlitOp blitOp;
|
||||
blitOp.operation = touchgfx::BLIT_OP_COPY;
|
||||
blitOp.pSrc = reinterpret_cast<uint16_t*>(MCU_Cropping_Buffer + cropSrcOffset);
|
||||
blitOp.nSteps = MCU_WIDTH_PIXELS - colLeftOffset - colRightOffset;
|
||||
blitOp.nLoops = MCU_HEIGHT_PIXELS - rowTopOffset - rowBottomOffset;
|
||||
blitOp.srcLoopStride = MCU_WIDTH_PIXELS;
|
||||
blitOp.dstLoopStride = FrameBufferWidth;
|
||||
blitOp.pDst = reinterpret_cast<uint16_t*>(job.OutputBuffer + dstOffset);
|
||||
blitOp.srcFormat = touchgfx::Bitmap::RGB565;
|
||||
blitOp.dstFormat = touchgfx::Bitmap::RGB565;
|
||||
DMA2D_reference->addToQueue(blitOp);
|
||||
|
||||
/* DMA2D OPFCCR register configuration */
|
||||
WRITE_REG(DMA2D->OPFCCR, DMA2D_OUTPUT_RGB565);
|
||||
|
||||
/* Configure DMA2D data size */
|
||||
WRITE_REG(DMA2D->NLR, MCU_HEIGHT_PIXELS | (MCU_WIDTH_PIXELS << DMA2D_NLR_PL_Pos));
|
||||
|
||||
/* Configure DMA2D destination address */
|
||||
WRITE_REG(DMA2D->OMAR, reinterpret_cast<uint32_t>(MCU_Cropping_Buffer));
|
||||
|
||||
/* DMA2D OOR register configuration */
|
||||
WRITE_REG(DMA2D->OOR, 0);
|
||||
|
||||
/* DMA2D FGOR register configuration */
|
||||
WRITE_REG(DMA2D->FGOR, 0);
|
||||
|
||||
/* DMA2D FGPFCCR register configuration */
|
||||
WRITE_REG(DMA2D->FGPFCCR, DMA2D_INPUT_YCBCR | (DMA2D_CSS_420 << DMA2D_FGPFCCR_CSS_Pos) | (DMA2D_REPLACE_ALPHA << DMA2D_FGPFCCR_AM_Pos) | (0xFFU << DMA2D_FGPFCCR_ALPHA_Pos));
|
||||
|
||||
/* Configure DMA2D source address */
|
||||
WRITE_REG(DMA2D->FGMAR, reinterpret_cast<uint32_t>(job.DataBuffer + srcOffset));
|
||||
|
||||
/* Configure DMA2D contol register */
|
||||
WRITE_REG(DMA2D->CR, DMA2D_M2M_PFC | DMA2D_IT_TC | DMA2D_CR_START | DMA2D_IT_CE | DMA2D_IT_TE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief External DMA2D job complete callback
|
||||
* @param job: External job reference
|
||||
* @retval None
|
||||
*/
|
||||
void DMA2D_ExternalJobCompleted(JPEG_Data_BufferTypeDef& job)
|
||||
{
|
||||
/* Mark job done if all MCUs are drawn */
|
||||
if (job.MCU_drawn == JPEG_ConvertorParams.MCU_pr_job)
|
||||
{
|
||||
job.State = JPEG_BUFFER_EMPTY;
|
||||
job.DataBufferSize = 0;
|
||||
job.DoCropping = false;
|
||||
job.FirstJob = false;
|
||||
|
||||
JPEG_OUT_Read_BufferIndex++;
|
||||
if (JPEG_OUT_Read_BufferIndex >= NB_OUTPUT_DATA_BUFFERS)
|
||||
{
|
||||
JPEG_OUT_Read_BufferIndex = 0;
|
||||
}
|
||||
|
||||
/* Check if last line */
|
||||
if (job.LastJob)
|
||||
{
|
||||
DMA2D_CopyBufferEnd = 1;
|
||||
}
|
||||
|
||||
/* Signal decoder thread to wake up and continue decoding */
|
||||
SEM_POST(semDecodingDone);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* File Name : HardwareMJPEGDecoder.hpp
|
||||
******************************************************************************
|
||||
* This file is generated by TouchGFX Generator 4.26.1. Please, do not edit!
|
||||
******************************************************************************
|
||||
* @attention
|
||||
*
|
||||
* Copyright (c) 2026 STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file
|
||||
* in the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
#ifndef TOUCHGFX_HARDWAREMJPEGDECODER_HPP
|
||||
#define TOUCHGFX_HARDWAREMJPEGDECODER_HPP
|
||||
|
||||
#include <MJPEGDecoder.hpp>
|
||||
#include <STM32DMA.hpp>
|
||||
|
||||
#include "cmsis_os2.h"
|
||||
#if defined(osCMSIS) && (osCMSIS < 0x20000)
|
||||
#define MUTEX_CREATE() osMutexCreate(0)
|
||||
#define MUTEX_LOCK(m) osMutexWait(m, osWaitForever)
|
||||
#define MUTEX_TYPE osMutexId
|
||||
#define MUTEX_UNLOCK(m) osMutexRelease(m)
|
||||
#define SEM_CREATE() osSemaphoreCreate(0, 1)
|
||||
#define SEM_POST(s) osSemaphoreRelease(s)
|
||||
#define SEM_TYPE osSemaphoreId
|
||||
#define SEM_WAIT(s) osSemaphoreWait(s, osWaitForever)
|
||||
#else
|
||||
#define MUTEX_CREATE() osMutexNew(0)
|
||||
#define MUTEX_LOCK(m) osMutexAcquire(m, osWaitForever)
|
||||
#define MUTEX_TYPE osMutexId_t
|
||||
#define MUTEX_UNLOCK(m) osMutexRelease(m)
|
||||
#define SEM_CREATE() osSemaphoreNew(1, 0, 0)
|
||||
#define SEM_POST(s) osSemaphoreRelease(s)
|
||||
#define SEM_TYPE osSemaphoreId_t
|
||||
#define SEM_WAIT(s) osSemaphoreAcquire(s, osWaitForever)
|
||||
#endif
|
||||
|
||||
class HardwareMJPEGDecoder : public MJPEGDecoder
|
||||
{
|
||||
public:
|
||||
HardwareMJPEGDecoder();
|
||||
|
||||
//Set video data for the decoder
|
||||
virtual void setVideoData(const uint8_t* movie, const uint32_t length);
|
||||
virtual void setVideoData(touchgfx::VideoDataReader& reader);
|
||||
virtual bool hasVideo();
|
||||
//Increment position to next frame and decode
|
||||
virtual bool decodeNextFrame(uint8_t* frameBuffer, uint16_t width, uint16_t height, uint32_t framebuffer_width);
|
||||
//Increment position to next frame and decode
|
||||
virtual bool gotoNextFrame();
|
||||
//Decode part of the current frame
|
||||
virtual bool decodeFrame(const touchgfx::Rect& area, uint8_t* frameBuffer, uint32_t framebuffer_width);
|
||||
virtual bool decodeThumbnail(uint32_t frameno, uint8_t* buffer, uint16_t width, uint16_t height);
|
||||
virtual void gotoFrame(uint32_t frameno);
|
||||
virtual uint32_t getCurrentFrameNumber() const
|
||||
{
|
||||
return frameNumber;
|
||||
}
|
||||
virtual uint32_t getNumberOfFrames();
|
||||
virtual void setRepeatVideo(bool repeat);
|
||||
virtual void getVideoInfo(touchgfx::VideoInformation* data);
|
||||
|
||||
void setAVIFileBuffer(uint8_t* buffer, uint32_t size)
|
||||
{
|
||||
aviBuffer = buffer, aviBufferLength = size;
|
||||
}
|
||||
|
||||
virtual AVIErrors getLastError()
|
||||
{
|
||||
return lastError;
|
||||
}
|
||||
void addDMA(touchgfx::DMA_Interface& dma)
|
||||
{
|
||||
this->dma = &dma;
|
||||
}
|
||||
private:
|
||||
void readVideoHeader();
|
||||
void decodeMJPEGFrame(const uint8_t* const mjpgdata, const uint32_t length, uint8_t* buffer, uint16_t width, uint16_t height, uint32_t stride);
|
||||
int compare(const uint32_t offset, const char* str, uint32_t num);
|
||||
uint32_t getU32(const uint32_t offset);
|
||||
uint32_t getU16(const uint32_t offset);
|
||||
const uint8_t* readData(uint32_t offset, uint32_t length);
|
||||
|
||||
touchgfx::VideoInformation videoInfo;
|
||||
uint32_t frameNumber;
|
||||
uint32_t currentMovieOffset;
|
||||
uint32_t indexOffset;
|
||||
uint32_t firstFrameOffset;
|
||||
uint32_t lastFrameEnd;
|
||||
uint32_t movieLength;
|
||||
const uint8_t* movieData;
|
||||
touchgfx::VideoDataReader* reader;
|
||||
uint8_t* aviBuffer;
|
||||
uint32_t aviBufferLength;
|
||||
uint32_t aviBufferStartOffset;
|
||||
AVIErrors lastError;
|
||||
touchgfx::DMA_Interface* dma;
|
||||
};
|
||||
|
||||
#endif // TOUCHGFX_HARDWAREMJPEGDECODER_HPP
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* File Name : MJPEGDecoder.hpp
|
||||
******************************************************************************
|
||||
* This file is generated by TouchGFX Generator 4.26.1. Please, do not edit!
|
||||
******************************************************************************
|
||||
* @attention
|
||||
*
|
||||
* Copyright (c) 2026 STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file
|
||||
* in the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
#ifndef TOUCHGFX_MJPEGDECODER_HPP
|
||||
#define TOUCHGFX_MJPEGDECODER_HPP
|
||||
|
||||
#include <touchgfx/hal/Types.hpp>
|
||||
#include <touchgfx/hal/VideoController.hpp>
|
||||
|
||||
class MJPEGDecoder
|
||||
{
|
||||
public:
|
||||
virtual ~MJPEGDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
//Set video data for the decoder
|
||||
virtual void setVideoData(const uint8_t* movie, const uint32_t length) = 0;
|
||||
|
||||
//Set video data for the decoder
|
||||
virtual void setVideoData(touchgfx::VideoDataReader& reader) = 0;
|
||||
|
||||
/**
|
||||
* Check if MJPEGDecoder has a video.
|
||||
*
|
||||
* @return Returns true if the MJPEGDecoder has a video.
|
||||
*/
|
||||
virtual bool hasVideo() = 0;
|
||||
|
||||
//Increment position to next frame and decode and convert to RGB
|
||||
virtual bool decodeNextFrame(uint8_t* buffer, uint16_t width, uint16_t height, uint32_t stride) = 0;
|
||||
|
||||
//Increment position to next frame and decode. Used with decodeFrame.
|
||||
virtual bool gotoNextFrame() = 0;
|
||||
|
||||
//Decode part of the current frame, framebuffer is locked, area is drawn relative to frameBuffer
|
||||
virtual bool decodeFrame(const touchgfx::Rect& area, uint8_t* frameBuffer, uint32_t framebufferStride) = 0;
|
||||
|
||||
//Decode thumbnail, assumes buffer stride is width
|
||||
virtual bool decodeThumbnail(uint32_t frameno, uint8_t* buffer, uint16_t width, uint16_t height) = 0;
|
||||
|
||||
//Set current frame number
|
||||
virtual void gotoFrame(uint32_t frameno) = 0;
|
||||
|
||||
//Get current frame number
|
||||
virtual uint32_t getCurrentFrameNumber() const = 0;
|
||||
|
||||
//Get number of frames in video
|
||||
virtual uint32_t getNumberOfFrames() = 0;
|
||||
|
||||
//Read video information
|
||||
virtual void getVideoInfo(touchgfx::VideoInformation* data) = 0;
|
||||
|
||||
enum AVIErrors
|
||||
{
|
||||
AVI_NO_ERROR,
|
||||
AVI_NO_BUFFERS,
|
||||
AVI_NO_FILE,
|
||||
AVI_ERROR_NOT_RIFF,
|
||||
AVI_ERROR_AVI_HEADER_NOT_FOUND,
|
||||
AVI_ERROR_AVI_LIST_NOT_FOUND,
|
||||
AVI_ERROR_AVI_HDRL_NOT_FOUND,
|
||||
AVI_ERROR_AVI_AVIH_NOT_FOUND,
|
||||
AVI_ERROR_AVI_HEADER_TO_SHORT, //not full header provided
|
||||
AVI_ERROR_FILE_BUFFER_TO_SMALL,
|
||||
AVI_ERROR_MOVI_NOT_FOUND,
|
||||
AVI_ERROR_IDX1_NOT_FOUND,
|
||||
AVI_ERROR_FRAMENO_TO_HIGH,
|
||||
AVI_ERROR_EOF_REACHED
|
||||
};
|
||||
|
||||
AVIErrors virtual getLastError() = 0;
|
||||
};
|
||||
|
||||
#endif // TOUCHGFX_MJPEGDECODER_HPP
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* File Name : OSWrappers.cpp
|
||||
******************************************************************************
|
||||
* This file is generated by TouchGFX Generator 4.26.1. Please, do not edit!
|
||||
******************************************************************************
|
||||
* @attention
|
||||
*
|
||||
* Copyright (c) 2026 STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file
|
||||
* in the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/hal/OSWrappers.hpp>
|
||||
|
||||
#include <cmsis_os2.h>
|
||||
#include <cassert>
|
||||
|
||||
static osSemaphoreId_t frame_buffer_sem = NULL;
|
||||
static osMessageQueueId_t vsync_queue = NULL;
|
||||
|
||||
// Just a dummy value to insert in the VSYNC queue.
|
||||
static uint32_t dummy = 0x5a;
|
||||
|
||||
using namespace touchgfx;
|
||||
|
||||
/*
|
||||
* Initialize frame buffer semaphore and queue/mutex for VSYNC signal.
|
||||
*/
|
||||
void OSWrappers::initialize()
|
||||
{
|
||||
// Create a queue of length 1
|
||||
frame_buffer_sem = osSemaphoreNew(1, 1, NULL); // Binary semaphore
|
||||
assert((frame_buffer_sem != NULL) && "Creation of framebuffer semaphore failed");
|
||||
|
||||
// Create a queue of length 1
|
||||
vsync_queue = osMessageQueueNew(1, 4, NULL);
|
||||
assert((vsync_queue != NULL) && "Creation of vsync message queue failed");
|
||||
}
|
||||
|
||||
/*
|
||||
* Take the frame buffer semaphore. Blocks until semaphore is available.
|
||||
*/
|
||||
void OSWrappers::takeFrameBufferSemaphore()
|
||||
{
|
||||
osSemaphoreAcquire(frame_buffer_sem, osWaitForever);
|
||||
}
|
||||
|
||||
/*
|
||||
* Release the frame buffer semaphore.
|
||||
*/
|
||||
void OSWrappers::giveFrameBufferSemaphore()
|
||||
{
|
||||
osSemaphoreRelease(frame_buffer_sem);
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to obtain the frame buffer semaphore. If semaphore is not available, do
|
||||
* nothing.
|
||||
*
|
||||
* Note must return immediately! This function does not care who has the taken the semaphore,
|
||||
* it only serves to make sure that the semaphore is taken by someone.
|
||||
*/
|
||||
void OSWrappers::tryTakeFrameBufferSemaphore()
|
||||
{
|
||||
osSemaphoreAcquire(frame_buffer_sem, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Release the frame buffer semaphore in a way that is safe in interrupt context. Called
|
||||
* from ISR.
|
||||
*
|
||||
* Release the frame buffer semaphore in a way that is safe in interrupt context.
|
||||
* Called from ISR.
|
||||
*/
|
||||
void OSWrappers::giveFrameBufferSemaphoreFromISR()
|
||||
{
|
||||
osSemaphoreRelease(frame_buffer_sem);
|
||||
}
|
||||
|
||||
/*
|
||||
* Signal that a VSYNC has occurred. Should make the vsync queue/mutex available.
|
||||
*
|
||||
* Note This function is called from an ISR, and should (depending on OS) trigger a
|
||||
* scheduling.
|
||||
*/
|
||||
void OSWrappers::signalVSync()
|
||||
{
|
||||
osMessageQueuePut(vsync_queue, &dummy, 0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Signal that the rendering of the frame has completed. Used by
|
||||
* some systems to avoid using any previous vsync.
|
||||
*/
|
||||
void OSWrappers::signalRenderingDone()
|
||||
{
|
||||
// Empty implementation for CMSIS V2
|
||||
}
|
||||
|
||||
/*
|
||||
* This function blocks until a VSYNC occurs.
|
||||
*
|
||||
* Note This function must first clear the mutex/queue and then wait for the next one to
|
||||
* occur.
|
||||
*/
|
||||
void OSWrappers::waitForVSync()
|
||||
{
|
||||
uint32_t dummyGet;
|
||||
// First make sure the queue is empty, by trying to remove an element with 0 timeout.
|
||||
osMessageQueueGet(vsync_queue, &dummyGet, 0, 0);
|
||||
|
||||
// Then, wait for next VSYNC to occur.
|
||||
osMessageQueueGet(vsync_queue, &dummyGet, 0, osWaitForever);
|
||||
}
|
||||
|
||||
/*
|
||||
* A function that causes executing task to sleep for a number of milliseconds.
|
||||
*
|
||||
* A function that causes executing task to sleep for a number of milliseconds.
|
||||
* This function is OPTIONAL. It is only used by the TouchGFX in the case of
|
||||
* a specific frame refresh strategy (REFRESH_STRATEGY_OPTIM_SINGLE_BUFFER_TFT_CTRL).
|
||||
* Due to backwards compatibility, in order for this function to be useable by the HAL
|
||||
* the function must be explicitly registered:
|
||||
* hal.registerTaskDelayFunction(&OSWrappers::taskDelay)
|
||||
*
|
||||
* see HAL::setFrameRefreshStrategy(FrameRefreshStrategy s)
|
||||
* see HAL::registerTaskDelayFunction(void (*delayF)(uint16_t))
|
||||
*/
|
||||
void OSWrappers::taskDelay(uint16_t ms)
|
||||
{
|
||||
osDelay(static_cast<uint32_t>(ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that causes the executing task to yield control to
|
||||
* another thread. This function is used by the framework when it
|
||||
* is necessary to wait a little before continuing (e.g. drawing).
|
||||
*
|
||||
* The implementation should typically request the operating
|
||||
* system to change to another task of similar priority. When
|
||||
* running without an operating system, the implementation can run
|
||||
* a very short task and return.
|
||||
*/
|
||||
void OSWrappers::taskYield()
|
||||
{
|
||||
osThreadYield();
|
||||
}
|
||||
|
||||
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* File Name : STM32DMA.hpp
|
||||
******************************************************************************
|
||||
* This file is generated by TouchGFX Generator 4.26.1. Please, do not edit!
|
||||
******************************************************************************
|
||||
* @attention
|
||||
*
|
||||
* Copyright (c) 2026 STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file
|
||||
* in the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef STM32DMA_HPP
|
||||
#define STM32DMA_HPP
|
||||
|
||||
#include <touchgfx/Bitmap.hpp>
|
||||
#include <touchgfx/hal/DMA.hpp>
|
||||
|
||||
#define JPEG_BUFFER_EMPTY 0
|
||||
#define JPEG_BUFFER_FULL 1
|
||||
#define NB_OUTPUT_DATA_BUFFERS 2
|
||||
|
||||
extern volatile uint32_t JPEG_OUT_Read_BufferIndex;
|
||||
extern volatile uint32_t DMA2D_CopyBufferEnd;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t State;
|
||||
uint8_t* DataBuffer;
|
||||
uint32_t DataBufferSize;
|
||||
uint32_t MCU_index;
|
||||
uint8_t* OutputBuffer;
|
||||
bool FirstJob;
|
||||
bool LastJob;
|
||||
uint32_t MCU_drawn;
|
||||
bool DoCropping;
|
||||
} JPEG_Data_BufferTypeDef;
|
||||
|
||||
extern JPEG_Data_BufferTypeDef Jpeg_OUT_BufferTab[NB_OUTPUT_DATA_BUFFERS];
|
||||
|
||||
extern "C" void DMA2D_CropBuffer(JPEG_Data_BufferTypeDef& job);
|
||||
extern "C" void DMA2D_CopyBuffer(JPEG_Data_BufferTypeDef& job);
|
||||
extern "C" void DMA2D_ExternalJobCompleted(JPEG_Data_BufferTypeDef& job);
|
||||
|
||||
/**
|
||||
* @class STM32DMA
|
||||
*
|
||||
* @brief This class specializes DMA_Interface for the STM32 processors.
|
||||
*
|
||||
* @sa touchgfx::DMA_Interface
|
||||
*/
|
||||
class STM32DMA : public touchgfx::DMA_Interface
|
||||
{
|
||||
/**
|
||||
* @typedef touchgfx::DMA_Interface Base
|
||||
*
|
||||
* @brief Defines an alias representing the base.
|
||||
*
|
||||
Defines an alias representing the base.
|
||||
*/
|
||||
typedef touchgfx::DMA_Interface Base;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @fn STM32DMA::STM32DMA();
|
||||
*
|
||||
* @brief Default constructor.
|
||||
*
|
||||
* Default constructor.
|
||||
*/
|
||||
STM32DMA();
|
||||
|
||||
/**
|
||||
* @fn STM32DMA::~STM32DMA();
|
||||
*
|
||||
* @brief Destructor.
|
||||
*
|
||||
* Destructor.
|
||||
*/
|
||||
virtual ~STM32DMA();
|
||||
|
||||
/**
|
||||
* @fn DMAType touchgfx::STM32DMA::getDMAType()
|
||||
*
|
||||
* @brief Function for obtaining the DMA type of the concrete DMA_Interface implementation.
|
||||
*
|
||||
* Function for obtaining the DMA type of the concrete DMA_Interface implementation.
|
||||
* As default, will return DMA_TYPE_CHROMART type value.
|
||||
*
|
||||
* @return a DMAType value of the concrete DMA_Interface implementation.
|
||||
*/
|
||||
virtual touchgfx::DMAType getDMAType(void)
|
||||
{
|
||||
return touchgfx::DMA_TYPE_CHROMART;
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn touchgfx::BlitOperations STM32DMA::getBlitCaps();
|
||||
*
|
||||
* @brief Gets the blit capabilities.
|
||||
*
|
||||
* Gets the blit capabilities.
|
||||
*
|
||||
* This DMA supports a range of blit caps: BLIT_OP_COPY, BLIT_OP_COPY_ARGB8888,
|
||||
* BLIT_OP_COPY_ARGB8888_WITH_ALPHA, BLIT_OP_COPY_A4, BLIT_OP_COPY_A8.
|
||||
*
|
||||
*
|
||||
* @return Currently supported blitcaps.
|
||||
*/
|
||||
virtual touchgfx::BlitOperations getBlitCaps();
|
||||
|
||||
/**
|
||||
* @fn void STM32DMA::initialize();
|
||||
*
|
||||
* @brief Perform hardware specific initialization.
|
||||
*
|
||||
* Perform hardware specific initialization.
|
||||
*/
|
||||
virtual void initialize();
|
||||
|
||||
/**
|
||||
* @fn void STM32DMA::signalDMAInterrupt()
|
||||
*
|
||||
* @brief Raises a DMA interrupt signal.
|
||||
*
|
||||
* Raises a DMA interrupt signal.
|
||||
*/
|
||||
virtual void signalDMAInterrupt()
|
||||
{
|
||||
if (!started_by_external_job)
|
||||
{
|
||||
executeCompleted();
|
||||
|
||||
/* Start new external job if next buffer is full */
|
||||
if (Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].State == JPEG_BUFFER_FULL && !DMA2D_CopyBufferEnd && !isRunning)
|
||||
{
|
||||
started_by_external_job = true;
|
||||
externalJobExecute();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
externalJobCompleted();
|
||||
|
||||
/* Prioritize BlitOps if there are any pending */
|
||||
if (!queue.isEmpty() && isAllowed)
|
||||
{
|
||||
started_by_external_job = false;
|
||||
execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void start()
|
||||
{
|
||||
if (!queue.isEmpty() && isAllowed && !isRunning)
|
||||
{
|
||||
started_by_external_job = false;
|
||||
execute();
|
||||
}
|
||||
else if ((Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].State == JPEG_BUFFER_FULL) && !isRunning)
|
||||
{
|
||||
started_by_external_job = true;
|
||||
externalJobExecute();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @fn virtual void STM32DMA::setupDataCopy(const touchgfx::BlitOp& blitOp);
|
||||
*
|
||||
* @brief Configures the DMA for copying data to the frame buffer.
|
||||
*
|
||||
* Configures the DMA for copying data to the frame buffer.
|
||||
*
|
||||
* @param blitOp Details on the copy to perform.
|
||||
*/
|
||||
virtual void setupDataCopy(const touchgfx::BlitOp& blitOp);
|
||||
|
||||
/**
|
||||
* @fn virtual void STM32DMA::setupDataFill(const touchgfx::BlitOp& blitOp);
|
||||
*
|
||||
* @brief Configures the DMA for "filling" the frame-buffer with a single color.
|
||||
*
|
||||
* Configures the DMA for "filling" the frame-buffer with a single color.
|
||||
*
|
||||
* @param blitOp Details on the "fill" to perform.
|
||||
*/
|
||||
virtual void setupDataFill(const touchgfx::BlitOp& blitOp);
|
||||
|
||||
/**
|
||||
* @fn void STM32DMA::externalJobCompleted();
|
||||
*
|
||||
* @brief Handle DMA2D when an external job has been executed
|
||||
*
|
||||
* @param None
|
||||
*/
|
||||
void externalJobCompleted()
|
||||
{
|
||||
if (isRunning)
|
||||
{
|
||||
isRunning = false;
|
||||
DMA2D_ExternalJobCompleted(Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void STM32DMA::externalJobExecute();
|
||||
*
|
||||
* @brief Executes an external DMA2D job
|
||||
*
|
||||
* @param None
|
||||
*/
|
||||
void externalJobExecute()
|
||||
{
|
||||
isRunning = true;
|
||||
if (Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].DoCropping)
|
||||
{
|
||||
DMA2D_CropBuffer(Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex]);
|
||||
}
|
||||
else
|
||||
{
|
||||
DMA2D_CopyBuffer(Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
touchgfx::LockFreeDMA_Queue dma_queue;
|
||||
touchgfx::BlitOp queue_storage[96];
|
||||
bool started_by_external_job;
|
||||
|
||||
/**
|
||||
* @fn void STM32DMA::getChromARTInputFormat()
|
||||
*
|
||||
* @brief Convert Bitmap format to ChromART Input format.
|
||||
*
|
||||
* @param format Bitmap format.
|
||||
*
|
||||
* @return ChromART Input format.
|
||||
*/
|
||||
|
||||
inline uint32_t getChromARTInputFormat(touchgfx::Bitmap::BitmapFormat format);
|
||||
|
||||
/**
|
||||
* @fn void STM32DMA::getChromARTOutputFormat()
|
||||
*
|
||||
* @brief Convert Bitmap format to ChromART Output format.
|
||||
*
|
||||
* @param format Bitmap format.
|
||||
*
|
||||
* @return ChromART Output format.
|
||||
*/
|
||||
inline uint32_t getChromARTOutputFormat(touchgfx::Bitmap::BitmapFormat format);
|
||||
};
|
||||
|
||||
#endif // STM32DMA_HPP
|
||||
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* File Name : TouchGFXConfiguration.cpp
|
||||
******************************************************************************
|
||||
* This file is generated by TouchGFX Generator 4.26.1. Please, do not edit!
|
||||
******************************************************************************
|
||||
* @attention
|
||||
*
|
||||
* Copyright (c) 2026 STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file
|
||||
* in the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <texts/TypedTextDatabase.hpp>
|
||||
#include <fonts/ApplicationFontProvider.hpp>
|
||||
#include <gui/common/FrontendHeap.hpp>
|
||||
#include <BitmapDatabase.hpp>
|
||||
#include <touchgfx/VectorFontRendererImpl.hpp>
|
||||
#include <touchgfx_nema/LCDGPU2D_AXI.hpp>
|
||||
extern "C"
|
||||
{
|
||||
#include <nema_hal.h>
|
||||
#include <nema_vg.h>
|
||||
}
|
||||
#include <STM32DMA.hpp>
|
||||
#include <TouchGFXHAL.hpp>
|
||||
#include <STM32TouchController.hpp>
|
||||
#include <stm32h7rsxx_hal.h>
|
||||
|
||||
extern "C" void touchgfx_init();
|
||||
extern "C" void touchgfx_taskEntry();
|
||||
extern "C" void touchgfx_components_init();
|
||||
|
||||
static STM32TouchController tc;
|
||||
static STM32DMA dma;
|
||||
static LCDGPU2D_AXI display;
|
||||
static VectorFontRendererImpl vectorFontRenderer;
|
||||
|
||||
static ApplicationFontProvider fontProvider;
|
||||
static Texts texts;
|
||||
static TouchGFXHAL hal(dma, display, tc, 800, 480);
|
||||
|
||||
void touchgfx_init()
|
||||
{
|
||||
Bitmap::registerBitmapDatabase(BitmapDatabase::getInstance(), BitmapDatabase::getInstanceSize());
|
||||
TypedText::registerTexts(&texts);
|
||||
Texts::setLanguage(0);
|
||||
|
||||
display.setFrameBufferFormat(Bitmap::RGB565);
|
||||
|
||||
display.setVectorFontRenderer(&vectorFontRenderer);
|
||||
|
||||
FontManager::setFontProvider(&fontProvider);
|
||||
|
||||
FrontendHeap& heap = FrontendHeap::getInstance();
|
||||
/*
|
||||
* we need to obtain the reference above to initialize the frontend heap.
|
||||
*/
|
||||
(void)heap;
|
||||
|
||||
/*
|
||||
* Initialize TouchGFX
|
||||
*/
|
||||
hal.initialize();
|
||||
}
|
||||
|
||||
void touchgfx_components_init()
|
||||
{
|
||||
nema_init();
|
||||
nema_reg_write(0xFFC, 0x7E); /* Enable bus error interrupts */
|
||||
nema_vg_init_stencil_pool(800, 480, 1);
|
||||
nema_vg_handle_large_coords(1, 1);
|
||||
nema_ext_hold_enable(2);
|
||||
nema_ext_hold_irq_enable(2);
|
||||
nema_ext_hold_enable(3);
|
||||
nema_ext_hold_irq_enable(3);
|
||||
}
|
||||
|
||||
void touchgfx_taskEntry()
|
||||
{
|
||||
/*
|
||||
* Main event loop. Will wait for VSYNC signal, and then process next frame. Call
|
||||
* this function from your GUI task.
|
||||
*
|
||||
* Note This function never returns
|
||||
*/
|
||||
hal.taskEntry();
|
||||
}
|
||||
|
||||
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
|
||||
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* File Name : TouchGFXGeneratedHAL.cpp
|
||||
******************************************************************************
|
||||
* This file is generated by TouchGFX Generator 4.26.1. Please, do not edit!
|
||||
******************************************************************************
|
||||
* @attention
|
||||
*
|
||||
* Copyright (c) 2026 STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file
|
||||
* in the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <TouchGFXGeneratedHAL.hpp>
|
||||
#include <touchgfx/hal/OSWrappers.hpp>
|
||||
#include <gui/common/FrontendHeap.hpp>
|
||||
#include <touchgfx/hal/GPIO.hpp>
|
||||
|
||||
#include <touchgfx_nema/GPU2DVectorRenderer.hpp>
|
||||
|
||||
#include <HardwareMJPEGDecoder.hpp>
|
||||
#include <DirectFrameBufferVideoController.hpp>
|
||||
#include <stm32h7rsxx_hal.h>
|
||||
|
||||
HardwareMJPEGDecoder mjpegdecoder1;
|
||||
|
||||
namespace
|
||||
{
|
||||
DirectFrameBufferVideoController<1, Bitmap::RGB565> videoController;
|
||||
}
|
||||
|
||||
//Singleton Factory
|
||||
VideoController& VideoController::getInstance()
|
||||
{
|
||||
return videoController;
|
||||
}
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
VectorRenderer* VectorRenderer::getInstance()
|
||||
{
|
||||
static GPU2DVectorRenderer renderer;
|
||||
|
||||
return &renderer;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
|
||||
#include "stm32h7rsxx.h"
|
||||
#include "stm32h7rsxx_hal_ltdc.h"
|
||||
|
||||
using namespace touchgfx;
|
||||
|
||||
namespace
|
||||
{
|
||||
// Use the section "TouchGFX_Framebuffer" in the linker script to specify the placement of the buffer
|
||||
LOCATION_PRAGMA_NOLOAD("TouchGFX_Framebuffer")
|
||||
uint32_t frameBuf[(800 * 480 * 2 + 3) / 4 * 2] LOCATION_ATTRIBUTE_NOLOAD("TouchGFX_Framebuffer");
|
||||
static uint16_t lcd_int_active_line;
|
||||
static uint16_t lcd_int_porch_line;
|
||||
}
|
||||
|
||||
void TouchGFXGeneratedHAL::initialize()
|
||||
{
|
||||
HALGPU2D::initialize(16384);
|
||||
registerEventListener(*(Application::getInstance()));
|
||||
setFrameBufferStartAddresses((void*)frameBuf, (void*)(frameBuf + sizeof(frameBuf) / (sizeof(uint32_t) * 2)), (void*)0);
|
||||
|
||||
/*
|
||||
* Add DMA2D to hardware decoder
|
||||
*/
|
||||
mjpegdecoder1.addDMA(dma);
|
||||
|
||||
/*
|
||||
* Add hardware decoder to video controller
|
||||
*/
|
||||
videoController.addDecoder(mjpegdecoder1, 0);
|
||||
}
|
||||
|
||||
void TouchGFXGeneratedHAL::configureInterrupts()
|
||||
{
|
||||
NVIC_SetPriority(DMA2D_IRQn, 9);
|
||||
NVIC_SetPriority(LTDC_IRQn, 9);
|
||||
NVIC_SetPriority(GPU2D_IRQn, 9);
|
||||
}
|
||||
|
||||
void TouchGFXGeneratedHAL::enableInterrupts()
|
||||
{
|
||||
NVIC_EnableIRQ(DMA2D_IRQn);
|
||||
NVIC_EnableIRQ(LTDC_IRQn);
|
||||
NVIC_EnableIRQ(GPU2D_IRQn);
|
||||
}
|
||||
|
||||
void TouchGFXGeneratedHAL::disableInterrupts()
|
||||
{
|
||||
NVIC_DisableIRQ(DMA2D_IRQn);
|
||||
NVIC_DisableIRQ(LTDC_IRQn);
|
||||
NVIC_DisableIRQ(GPU2D_IRQn);
|
||||
}
|
||||
|
||||
void TouchGFXGeneratedHAL::enableLCDControllerInterrupt()
|
||||
{
|
||||
lcd_int_active_line = (LTDC->BPCR & LTDC_BPCR_AVBP_Msk) - 1;
|
||||
lcd_int_porch_line = (LTDC->AWCR & LTDC_AWCR_AAH_Msk) - 1;
|
||||
|
||||
/* Sets the Line Interrupt position */
|
||||
LTDC->LIPCR = lcd_int_active_line;
|
||||
/* Line Interrupt Enable */
|
||||
LTDC->IER |= LTDC_IER_LIE;
|
||||
}
|
||||
|
||||
bool TouchGFXGeneratedHAL::beginFrame()
|
||||
{
|
||||
return HALGPU2D::beginFrame();
|
||||
}
|
||||
|
||||
void TouchGFXGeneratedHAL::endFrame()
|
||||
{
|
||||
HALGPU2D::endFrame();
|
||||
}
|
||||
|
||||
void TouchGFXGeneratedHAL::submitGPU2D()
|
||||
{
|
||||
HALGPU2D::submitExecBuffer();
|
||||
}
|
||||
|
||||
uint16_t* TouchGFXGeneratedHAL::getTFTFrameBuffer() const
|
||||
{
|
||||
return (uint16_t*)LTDC_Layer1->CFBAR;
|
||||
}
|
||||
|
||||
void TouchGFXGeneratedHAL::setTFTFrameBuffer(uint16_t* adr)
|
||||
{
|
||||
LTDC_Layer1->CFBAR = (uint32_t)adr;
|
||||
|
||||
/* Reload immediate */
|
||||
LTDC->SRCR = (uint32_t)LTDC_SRCR_IMR;
|
||||
}
|
||||
|
||||
void TouchGFXGeneratedHAL::flushFrameBuffer(const touchgfx::Rect& rect)
|
||||
{
|
||||
HALGPU2D::flushFrameBuffer(rect);
|
||||
}
|
||||
|
||||
bool TouchGFXGeneratedHAL::blockCopy(void* RESTRICT dest, const void* RESTRICT src, uint32_t numBytes)
|
||||
{
|
||||
return HALGPU2D::blockCopy(dest, src, numBytes);
|
||||
}
|
||||
|
||||
void TouchGFXGeneratedHAL::InvalidateCache()
|
||||
{
|
||||
// Because DMA2D access main memory directly, the DCache must be invalidated
|
||||
// becuase it could hold a wrong image of the framebuffer. That's done
|
||||
// using the function SCB_CleanInvalidateDCache(). Remember to enable
|
||||
// "CPU Cache" in the "System Core" settings for "Cortex M7" in CubeMX
|
||||
// in order for this function call to work.
|
||||
if (SCB->CCR & SCB_CCR_DC_Msk)
|
||||
{
|
||||
SCB_CleanInvalidateDCache();
|
||||
}
|
||||
}
|
||||
|
||||
void TouchGFXGeneratedHAL::FlushCache()
|
||||
{
|
||||
// If the framebuffer is placed in Write-Back cached memory (e.g. SRAM) then
|
||||
// the DCache must be flushed prior to DMA2D accessing it. That's done
|
||||
// using the function SCB_CleanInvalidateDCache(). Remember to enable
|
||||
// "CPU Cache" in the "System Core" settings for "Cortex M7" in CubeMX in
|
||||
// order for this function call to work.
|
||||
if (SCB->CCR & SCB_CCR_DC_Msk)
|
||||
{
|
||||
SCB_CleanInvalidateDCache();
|
||||
}
|
||||
}
|
||||
|
||||
void TouchGFXGeneratedHAL::InvalidateTextureCache()
|
||||
{
|
||||
// Hook to invalidate texture cache(s) prior to drawing to a framebuffer.
|
||||
//
|
||||
// When drawing into other buffers in RAM (e.g., dynamic bitmaps), the GPU2D
|
||||
// texture cache may be incoherent with the main memory.
|
||||
HAL_ICACHE_Invalidate();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
{
|
||||
void HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef* hltdc)
|
||||
{
|
||||
if (!HAL::getInstance())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (LTDC->LIPCR == lcd_int_active_line)
|
||||
{
|
||||
//entering active area
|
||||
HAL_LTDC_ProgramLineEvent(hltdc, lcd_int_porch_line);
|
||||
HAL::getInstance()->vSync();
|
||||
OSWrappers::signalVSync();
|
||||
|
||||
// Swap frame buffers immediately instead of waiting for the task to be scheduled in.
|
||||
// Note: task will also swap when it wakes up, but that operation is guarded and will not have
|
||||
// any effect if already swapped.
|
||||
HAL::getInstance()->swapFrameBuffers();
|
||||
GPIO::set(GPIO::VSYNC_FREQ);
|
||||
}
|
||||
else
|
||||
{
|
||||
//exiting active area
|
||||
HAL_LTDC_ProgramLineEvent(hltdc, lcd_int_active_line);
|
||||
|
||||
// Signal to the framework that display update has finished.
|
||||
HAL::getInstance()->frontPorchEntered();
|
||||
GPIO::clear(GPIO::VSYNC_FREQ);
|
||||
}
|
||||
}
|
||||
}
|
||||
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* File Name : TouchGFXGeneratedHAL.hpp
|
||||
******************************************************************************
|
||||
* This file is generated by TouchGFX Generator 4.26.1. Please, do not edit!
|
||||
******************************************************************************
|
||||
* @attention
|
||||
*
|
||||
* Copyright (c) 2026 STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file
|
||||
* in the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
#ifndef TouchGFXGeneratedHAL_HPP
|
||||
#define TouchGFXGeneratedHAL_HPP
|
||||
|
||||
#include <touchgfx_nema/HALGPU2D.hpp>
|
||||
|
||||
/**
|
||||
* @class TouchGFXGeneratedHAL
|
||||
*
|
||||
* @brief HAL implementation for TouchGFXGenerated.
|
||||
*
|
||||
* @sa HAL
|
||||
*/
|
||||
class TouchGFXGeneratedHAL : public touchgfx::HALGPU2D
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @fn TouchGFXGeneratedHAL::TouchGFXGeneratedHAL(touchgfx::DMA_Interface& dma, touchgfx::LCD& display, touchgfx::TouchController& tc, uint16_t width, uint16_t height) : touchgfx::HALGPU2D(dma, display, tc, width, height)
|
||||
*
|
||||
* @brief Constructor.
|
||||
*
|
||||
* Constructor. Initializes members.
|
||||
*
|
||||
* @param [in,out] dma Reference to DMA interface.
|
||||
* @param [in,out] display Reference to LCD interface.
|
||||
* @param [in,out] tc Reference to Touch Controller driver.
|
||||
* @param width Width of the display.
|
||||
* @param height Height of the display.
|
||||
*/
|
||||
TouchGFXGeneratedHAL(touchgfx::DMA_Interface& dma, touchgfx::LCD& display, touchgfx::TouchController& tc, uint16_t width, uint16_t height) :
|
||||
touchgfx::HALGPU2D(dma, display, tc, width, height)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn void TouchGFXGeneratedHAL::initialize();
|
||||
*
|
||||
* @brief This function is responsible for initializing the entire framework.
|
||||
*
|
||||
* This function is responsible for initializing the entire framework.
|
||||
*/
|
||||
virtual void initialize();
|
||||
|
||||
/**
|
||||
* @fn virtual void TouchGFXGeneratedHAL::configureInterrupts();
|
||||
*
|
||||
* @brief Sets the DMA, LCD, and GPU2D (if enabled) interrupt priorities.
|
||||
*
|
||||
* Sets the DMA, LCD, and GPU2D (if enabled) interrupt priorities.
|
||||
*/
|
||||
virtual void configureInterrupts();
|
||||
|
||||
/**
|
||||
* @fn virtual void TouchGFXGeneratedHAL::enableInterrupts();
|
||||
*
|
||||
* @brief Enables the DMA, LCD, and GPU2D (if enabled) interrupts.
|
||||
*
|
||||
* Enables the DMA, LCD, and GPU2D (if enabled) interrupts.
|
||||
*/
|
||||
virtual void enableInterrupts();
|
||||
|
||||
/**
|
||||
* @fn virtual void TouchGFXGeneratedHAL::disableInterrupts();
|
||||
*
|
||||
* @brief Disables the DMA, LDC, and GPU2D (if enabled) interrupts.
|
||||
*
|
||||
* Disables the DMA, LDC, and GPU2D (if enabled) interrupts.
|
||||
*/
|
||||
virtual void disableInterrupts();
|
||||
|
||||
/**
|
||||
* @fn virtual void TouchGFXGeneratedHAL::enableLCDControllerInterrupt();
|
||||
*
|
||||
* @brief Configure the LCD controller to fire interrupts at VSYNC.
|
||||
*
|
||||
* Configure the LCD controller to fire interrupts at VSYNC. Called automatically
|
||||
* once TouchGFX initialization has completed.
|
||||
*/
|
||||
virtual void enableLCDControllerInterrupt();
|
||||
|
||||
/**
|
||||
* @fn virtual void TouchGFXGeneratedHAL::flushFrameBuffer();
|
||||
*
|
||||
* @brief This function is called whenever the framework has performed a complete draw.
|
||||
*
|
||||
* This specialization is only in place to keep compilers happy. Base impl. will call the
|
||||
* Rect version.
|
||||
* @see HAL::flushFrameBuffer
|
||||
*/
|
||||
virtual void flushFrameBuffer()
|
||||
{
|
||||
HAL::flushFrameBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn virtual void TouchGFXGeneratedHAL::unlockFrameBuffer();
|
||||
*
|
||||
* @brief Unlocks the framebuffer.
|
||||
*
|
||||
* This specialization is marks DMA2D as un-reserved by the framwork.
|
||||
* @see HAL::unlockFrameBuffer
|
||||
*/
|
||||
virtual void unlockFrameBuffer()
|
||||
{
|
||||
HAL::unlockFrameBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn virtual void TouchGFXGeneratedHAL::flushFrameBuffer(const touchgfx::Rect& rect);
|
||||
*
|
||||
* @brief This function is called whenever the framework has performed a partial draw.
|
||||
*
|
||||
* This function is called whenever the framework has performed a partial draw.
|
||||
*
|
||||
* @param rect The area of the screen that has been drawn, expressed in absolute coordinates.
|
||||
*
|
||||
* @see flushFrameBuffer().
|
||||
*/
|
||||
virtual void flushFrameBuffer(const touchgfx::Rect& rect);
|
||||
|
||||
/**
|
||||
*
|
||||
* @fn virtual void TouchGFXGeneratedHAL::blockCopy();
|
||||
*
|
||||
* This function performs a platform-specific memcpy, if supported by the hardware.
|
||||
*
|
||||
* @param [out] dest Pointer to destination memory.
|
||||
* @param [in] src Pointer to source memory.
|
||||
* @param numBytes Number of bytes to copy.
|
||||
*
|
||||
* @return true if the copy succeeded, false if copy was not performed.
|
||||
*/
|
||||
virtual bool blockCopy(void* RESTRICT dest, const void* RESTRICT src, uint32_t numBytes);
|
||||
|
||||
/**
|
||||
* @fn virtual void TouchGFXGeneratedHAL::beginFrame();
|
||||
*
|
||||
* @brief Called when beginning to rendering a frame.
|
||||
*
|
||||
* Called when beginning to rendering a frame.
|
||||
*
|
||||
* @return true if rendering can begin, false otherwise.
|
||||
*/
|
||||
virtual bool beginFrame();
|
||||
|
||||
/**
|
||||
* @fn virtual void TouchGFXGeneratedHAL::endFrame();
|
||||
*
|
||||
* @brief Called when a rendering pass is completed.
|
||||
*
|
||||
* Called when a rendering pass is completed.
|
||||
*/
|
||||
virtual void endFrame();
|
||||
|
||||
/**
|
||||
* @fn virtual void TouchGFXGeneratedHAL::submitGPU2D();
|
||||
*
|
||||
* @brief This function can be used to explicitly submit any GPU2D operations that
|
||||
* might be queued in the command list. Can be called if e.g. the task is
|
||||
* about to sleep, to ensure GPU2D operations are running in the background.
|
||||
*/
|
||||
virtual void submitGPU2D();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @fn virtual uint16_t* TouchGFXGeneratedHAL::getTFTFrameBuffer() const;
|
||||
*
|
||||
* @brief Gets the frame buffer address used by the TFT controller.
|
||||
*
|
||||
* Gets the frame buffer address used by the TFT controller.
|
||||
*
|
||||
* @return The address of the frame buffer currently being displayed on the TFT.
|
||||
*/
|
||||
virtual uint16_t* getTFTFrameBuffer() const;
|
||||
|
||||
/**
|
||||
* @fn virtual void TouchGFXGeneratedHAL::setTFTFrameBuffer(uint16_t* adr);
|
||||
*
|
||||
* @brief Sets the frame buffer address used by the TFT controller.
|
||||
*
|
||||
* Sets the frame buffer address used by the TFT controller.
|
||||
*
|
||||
* @param [in,out] adr New frame buffer address.
|
||||
*/
|
||||
virtual void setTFTFrameBuffer(uint16_t* adr);
|
||||
|
||||
virtual void InvalidateCache();
|
||||
|
||||
virtual void FlushCache();
|
||||
|
||||
virtual void InvalidateTextureCache();
|
||||
};
|
||||
#endif // TouchGFXGeneratedHAL_HPP
|
||||
|
||||
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
|
||||
@@ -0,0 +1,275 @@
|
||||
|
||||
/**
|
||||
******************************************************************************
|
||||
* @file nema_hal.c
|
||||
* @author MCD Application Team
|
||||
* @brief NemaGFX Interfaces and Platform Specific APIs with FreeRTOS CMSISV2
|
||||
* support.
|
||||
* This file provides NemaGFX functions to manage the following
|
||||
* functionalities of the NemaGFX library:
|
||||
* + Initialization and de-initialization functions
|
||||
* + GPU2D Registers access operation functions
|
||||
* + Buffer Creation, Destruction, MMAP and Flusing functions
|
||||
* + Interrupt management functions
|
||||
* + Memory management functions
|
||||
* + Locking/Unlocking resources functions
|
||||
******************************************************************************
|
||||
* @attention
|
||||
*
|
||||
* <h2><center>© Copyright (c) 2020 STMicroelectronics.
|
||||
* All rights reserved.</center></h2>
|
||||
*
|
||||
* This software component is licensed by ST under BSD 3-Clause license,
|
||||
* the "License"; You may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at:
|
||||
* opensource.org/licenses/BSD-3-Clause
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <touchgfx/hal/Config.hpp>
|
||||
#include <nema_sys_defs.h>
|
||||
#include <nema_core.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <stm32h7rsxx_hal.h>
|
||||
|
||||
#include <cmsis_os2.h>
|
||||
|
||||
#include "tsi_malloc.h"
|
||||
|
||||
#define RING_SIZE 1024 /* Ring Buffer Size in byte */
|
||||
#define NEMAGFX_MEM_POOL_SIZE 24320 /* NemaGFX byte pool size in byte */
|
||||
#define NEMAGFX_STENCIL_POOL_SIZE 389120 /* NemaGFX stencil buffer pool size in byte */
|
||||
|
||||
LOCATION_PRAGMA_NOLOAD("Nemagfx_Memory_Pool_Buffer")
|
||||
static uint8_t nemagfx_pool_mem[NEMAGFX_MEM_POOL_SIZE] LOCATION_ATTRIBUTE_NOLOAD("Nemagfx_Memory_Pool_Buffer"); /* NemaGFX memory pool */
|
||||
|
||||
LOCATION_PRAGMA_NOLOAD("Nemagfx_Stencil_Buffer")
|
||||
static uint8_t nemagfx_stencil_buffer_mem[NEMAGFX_STENCIL_POOL_SIZE] LOCATION_ATTRIBUTE_NOLOAD("Nemagfx_Stencil_Buffer"); /* NemaGFX stencil buffer memory */
|
||||
|
||||
static nema_ringbuffer_t ring_buffer_str;
|
||||
volatile static int last_cl_id = -1;
|
||||
extern GPU2D_HandleTypeDef hgpu2d;
|
||||
|
||||
static osSemaphoreId_t nema_irq_sem = NULL; // Declare CL IRQ semaphore
|
||||
|
||||
#if (USE_HAL_GPU2D_REGISTER_CALLBACKS == 1)
|
||||
static void GPU2D_CommandListCpltCallback(GPU2D_HandleTypeDef* hgpu2d, uint32_t CmdListID)
|
||||
#else /* USE_HAL_GPU2D_REGISTER_CALLBACKS = 0 */
|
||||
void HAL_GPU2D_CommandListCpltCallback(GPU2D_HandleTypeDef* hgpu2d, uint32_t CmdListID)
|
||||
#endif /* USE_HAL_GPU2D_REGISTER_CALLBACKS = 1 */
|
||||
{
|
||||
/* Prevent unused argument(s) compilation warning */
|
||||
UNUSED(hgpu2d);
|
||||
|
||||
last_cl_id = CmdListID;
|
||||
|
||||
/* Return a token back to a semaphore */
|
||||
osSemaphoreRelease(nema_irq_sem);
|
||||
}
|
||||
|
||||
void HAL_GPU2D_ErrorCallback(GPU2D_HandleTypeDef *hgpu2d)
|
||||
{
|
||||
uint32_t val = nema_reg_read(GPU2D_SYS_INTERRUPT); /* clear the ER interrupt */
|
||||
nema_reg_write(GPU2D_SYS_INTERRUPT, val);
|
||||
|
||||
/* external GPU2D cache maintenance */
|
||||
if (val & (1UL << 2))
|
||||
{
|
||||
HAL_ICACHE_Disable();
|
||||
nema_ext_hold_deassert_imm(2);
|
||||
}
|
||||
if (val & (1UL << 3))
|
||||
{
|
||||
HAL_ICACHE_Enable();
|
||||
HAL_ICACHE_Invalidate();
|
||||
nema_ext_hold_deassert_imm(3);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void platform_disable_cache(void)
|
||||
{
|
||||
nema_ext_hold_assert(2, 1);
|
||||
}
|
||||
|
||||
void platform_invalidate_cache(void)
|
||||
{
|
||||
nema_ext_hold_assert(3, 1);
|
||||
}
|
||||
|
||||
int32_t nema_sys_init(void)
|
||||
{
|
||||
int error_code = 0;
|
||||
|
||||
/* Setup GPU2D Callback */
|
||||
#if (USE_HAL_GPU2D_REGISTER_CALLBACKS == 1)
|
||||
/* Register Command List Comlete Callback */
|
||||
HAL_GPU2D_RegisterCommandListCpltCallback(&hgpu2d, GPU2D_CommandListCpltCallback);
|
||||
#endif /* USE_HAL_GPU2D_REGISTER_CALLBACKS = 1 */
|
||||
|
||||
/* Create IRQ semaphore */
|
||||
nema_irq_sem = osSemaphoreNew(1, 1, NULL);
|
||||
assert(nema_irq_sem != NULL);
|
||||
|
||||
/* Initialise Mem Space */
|
||||
error_code = tsi_malloc_init_pool_aligned(0, (void*)nemagfx_pool_mem, (uintptr_t)nemagfx_pool_mem, NEMAGFX_MEM_POOL_SIZE, 1, 8);
|
||||
assert(error_code == 0);
|
||||
error_code = tsi_malloc_init_pool_aligned(1, (void*)nemagfx_stencil_buffer_mem, (uintptr_t)nemagfx_stencil_buffer_mem, NEMAGFX_STENCIL_POOL_SIZE, 1, 8);
|
||||
assert(error_code == 0);
|
||||
|
||||
/* Allocate ring_buffer memory */
|
||||
ring_buffer_str.bo = nema_buffer_create(RING_SIZE);
|
||||
assert(ring_buffer_str.bo.base_virt);
|
||||
|
||||
/* Initialize Ring Buffer */
|
||||
error_code = nema_rb_init(&ring_buffer_str, 1);
|
||||
if (error_code < 0)
|
||||
{
|
||||
return error_code;
|
||||
}
|
||||
|
||||
/* Reset last_cl_id counter */
|
||||
last_cl_id = 0;
|
||||
|
||||
return error_code;
|
||||
}
|
||||
|
||||
uint32_t nema_reg_read(uint32_t reg)
|
||||
{
|
||||
return HAL_GPU2D_ReadRegister(&hgpu2d, reg);
|
||||
}
|
||||
|
||||
void nema_reg_write(uint32_t reg, uint32_t value)
|
||||
{
|
||||
HAL_GPU2D_WriteRegister(&hgpu2d, reg, value);
|
||||
}
|
||||
|
||||
int nema_wait_irq(void)
|
||||
{
|
||||
/* Wait indefinitely for a free semaphore */
|
||||
osSemaphoreAcquire(nema_irq_sem, osWaitForever);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nema_wait_irq_cl(int cl_id)
|
||||
{
|
||||
while (last_cl_id < cl_id)
|
||||
{
|
||||
(void)nema_wait_irq();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nema_wait_irq_brk(int brk_id)
|
||||
{
|
||||
while (nema_reg_read(GPU2D_BREAKPOINT) == 0U)
|
||||
{
|
||||
(void)nema_wait_irq();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void nema_host_free(void* ptr)
|
||||
{
|
||||
tsi_free(ptr);
|
||||
}
|
||||
|
||||
void* nema_host_malloc(unsigned size)
|
||||
{
|
||||
return tsi_malloc(size);
|
||||
}
|
||||
|
||||
nema_buffer_t nema_buffer_create(int size)
|
||||
{
|
||||
nema_buffer_t bo;
|
||||
memset(&bo, 0, sizeof(bo));
|
||||
bo.base_virt = tsi_malloc(size);
|
||||
bo.base_phys = (uint32_t)bo.base_virt;
|
||||
bo.size = size;
|
||||
assert(bo.base_virt != 0 && "Unable to allocate memory in nema_buffer_create");
|
||||
|
||||
return bo;
|
||||
}
|
||||
|
||||
nema_buffer_t nema_buffer_create_pool(int pool, int size)
|
||||
{
|
||||
nema_buffer_t bo;
|
||||
memset(&bo, 0, sizeof(bo));
|
||||
bo.base_virt = tsi_malloc_pool(pool, size);
|
||||
bo.base_phys = (uint32_t)bo.base_virt;
|
||||
bo.size = size;
|
||||
bo.fd = 0;
|
||||
assert(bo.base_virt != 0 && "Unable to allocate memory in nema_buffer_create_pool");
|
||||
|
||||
return bo;
|
||||
}
|
||||
|
||||
void* nema_buffer_map(nema_buffer_t* bo)
|
||||
{
|
||||
return bo->base_virt;
|
||||
}
|
||||
|
||||
void nema_buffer_unmap(nema_buffer_t* bo)
|
||||
{
|
||||
/* Prevent unused argument(s) compilation warning */
|
||||
UNUSED(bo);
|
||||
}
|
||||
|
||||
void nema_buffer_destroy(nema_buffer_t* bo)
|
||||
{
|
||||
if (bo->fd == -1)
|
||||
{
|
||||
return; /* Buffer weren't allocated! */
|
||||
}
|
||||
|
||||
tsi_free(bo->base_virt);
|
||||
|
||||
bo->base_virt = (void*)0;
|
||||
bo->base_phys = 0;
|
||||
bo->size = 0;
|
||||
bo->fd = -1; /* Buffer not allocated */
|
||||
}
|
||||
|
||||
uintptr_t nema_buffer_phys(nema_buffer_t* bo)
|
||||
{
|
||||
return bo->base_phys;
|
||||
}
|
||||
|
||||
void nema_buffer_flush(nema_buffer_t* bo)
|
||||
{
|
||||
/* Prevent unused argument(s) compilation warning */
|
||||
UNUSED(bo);
|
||||
}
|
||||
|
||||
int nema_mutex_lock(int mutex_id)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
/* USER CODE BEGIN nema_mutex_lock */
|
||||
/* Prevent unused argument(s) compilation warning */
|
||||
UNUSED(mutex_id);
|
||||
/* USER CODE END nema_mutex_lock */
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int nema_mutex_unlock(int mutex_id)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
/* USER CODE BEGIN nema_mutex_unlock */
|
||||
/* Prevent unused argument(s) compilation warning */
|
||||
UNUSED(mutex_id);
|
||||
/* USER CODE END nema_mutex_unlock */
|
||||
|
||||
return retval;
|
||||
}
|
||||
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
|
||||
Reference in new issue
Block a user