This commit is contained in:
Thomas Laduguie committed 2026-09-04 09:57:30 +02:00
commit 75fbf8809d
489 files changed
+398278

No files matched your search

@@ -0,0 +1,48 @@
#include <CortexMMCUInstrumentation.hpp>
#include <touchgfx/hal/HAL.hpp>
namespace touchgfx
{
void CortexMMCUInstrumentation::init()
{
// See: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337e/CEGHJDCF.html
//
// [24] Read/write TRCENA This bit must be set to 1 to enable use of the trace and debug blocks:
// Data Watchpoint and Trace (DWT)
// Instrumentation Trace Macrocell (ITM)
// Embedded Trace Macrocell (ETM)
// Trace Port Interface Unit (TPIU).
// This enables control of power usage unless tracing is required. The application can enable this, for ITM use, or use by a debugger.
// Enable Debug Exception and Monitor Control Register
*((volatile unsigned int*)0xE000EDFC) |= 0x01000000;
// Enable Lock Access Register
*((volatile unsigned int*)0xE0001FB0) |= 0xC5ACCE55;
// Enable Data Watchpoint and Trace Control Register
*((volatile unsigned int*)0xE0001000) |= 1;
}
//Board specific clockfrequency
unsigned int CortexMMCUInstrumentation::getElapsedUS(unsigned int start, unsigned int now, unsigned int clockfrequency)
{
return ((now - start) + (clockfrequency / 2)) / clockfrequency;
}
unsigned int CortexMMCUInstrumentation::getCPUCycles()
{
return *((volatile unsigned int*)0xE0001004);
}
void CortexMMCUInstrumentation::setMCUActive(bool active)
{
if (active) //idle task sched out
{
uint32_t cc_temp = getCPUCycles() - cc_in;
cc_consumed += cc_temp;
}
else //idle task sched in
{
cc_in = getCPUCycles();
}
}
}
@@ -0,0 +1,74 @@
#ifndef CORTEXMMCUINSTRUMENTATION_HPP
#define CORTEXMMCUINSTRUMENTATION_HPP
#include <platform/core/MCUInstrumentation.hpp>
#include <stdint.h>
namespace touchgfx
{
/**
* @class CortexMMCUInstrumentation CortexMMCUInstrumentation.hpp platform/core/arm/cortex-m/CortexMMCUInstrumentation.hpp
*
* @brief Interface for instrumenting Cortex-M processors to measure MCU load via measured CPU
* cycles.
*
* Interface for instrumenting Cortex-M processors to measure MCU load via measured CPU
* cycles.
*
* @sa MCUInstrumentation
*/
class CortexMMCUInstrumentation : public MCUInstrumentation
{
public:
/**
* @fn virtual void CortexMMCUInstrumentation::init();
*
* @brief Initialization.
*
* Initialization.
*/
virtual void init();
/**
* @fn virtual unsigned int CortexMMCUInstrumentation::getElapsedUS(unsigned int start, unsigned int now, unsigned int clockfrequency);
*
* @brief Gets elapsed microseconds basedon clockfrequency.
*
* Gets elapsed microseconds basedon clockfrequency.
*
* @param start Start time.
* @param now Current time.
* @param clockfrequency Clock frequency of the system.
*
* @return Elapsed microseconds start and now.
*/
virtual unsigned int getElapsedUS(unsigned int start, unsigned int now, unsigned int clockfrequency);
/**
* @fn virtual unsigned int CortexMMCUInstrumentation::getCPUCycles();
*
* @brief Gets CPU cycles from register.
*
* Gets CPU cycles from register.
*
* @return CPU cycles.
*/
virtual unsigned int getCPUCycles();
/**
* @fn virtual void CortexMMCUInstrumentation::setMCUActive(bool active);
*
* @brief Register if MCU is active by measuring cpu cycles.
*
* Register if MCU is active by measuring cpu cycles. If user wishes to track
* MCU load, this method should be called whenever the OS Idle task is scheduled
* in or out. This method makes calls to a concrete implementation of GPIO
* functionality and a concrete implementation of cpu cycles.
*
* @param active If true, MCU is registered as being active, inactive otherwise.
*/
virtual void setMCUActive(bool active);
};
} // namespace touchgfx
#endif // CORTEXMMCUINSTRUMENTATION_HPP
@@ -0,0 +1,94 @@
/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : STM32TouchController.cpp
******************************************************************************
* This file was created by TouchGFX Generator 4.22.1. This file is only
* generated once! Delete this file from your project and re-generate code
* using STM32CubeMX or change this file manually to update it.
******************************************************************************
* @attention
*
* Copyright (c) 2023 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.
*
******************************************************************************
*/
/* USER CODE END Header */
/* USER CODE BEGIN STM32TouchController */
#include <touchgfx/hal/HAL.hpp>
#include <touchgfx/hal/Types.hpp>
#include <STM32TouchController.hpp>
#include "main.h"
volatile bool doSampleTouch = false;
extern "C" I2C_HandleTypeDef hi2c1;
using namespace touchgfx;
extern "C"
{
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == TP_IRQ_Pin)
{
/* Communication with TS is done via I2C.
Often the sw requires ISRs (interrupt service routines) to be quick while communication
with I2C can be considered relatively long (depending on SW requirements).
Considering that the TS feature don't need immediate reaction,
it is suggested to use polling mode instead of EXTI mode,
in order to avoid blocking I2C communication on interrupt service routines */
/* Here an example of implementation is proposed which is a mix between pooling and exit mode:
On ISR a flag is set (exti5_received), the main loop polls on the flag rather then polling the TS;
Mcu communicates with TS only when the flag has been set by ISR. This is just an example:
the users should choose they strategy depending on their application needs.*/
doSampleTouch = true;
return;
}
}
}
void STM32TouchController::init()
{
// No init needed
}
bool STM32TouchController::sampleTouch(int32_t& x, int32_t& y)
{
uint8_t touches = 0;
uint8_t buf[6];
const uint16_t STATUS_REG = 0x814E;
const uint16_t TOUCH_POS_REG = 0x8150;
uint8_t ZERO = 0;
NVIC_DisableIRQ(TP_IRQ_EXTI_IRQn);
if (doSampleTouch)
{
HAL_I2C_Mem_Read(&hi2c1, 0xBA, STATUS_REG, 2, buf, 1, HAL_MAX_DELAY);
touches = (0x0F & buf[0]);
HAL_I2C_Mem_Write(&hi2c1, 0xBA, STATUS_REG, 2, &ZERO, 1, HAL_MAX_DELAY);
doSampleTouch = false;
if (touches > 0)
{
HAL_I2C_Mem_Read(&hi2c1, 0xBA, TOUCH_POS_REG, 2, buf, 4, HAL_MAX_DELAY);
x = buf[0] + (buf[1] << 8);
y = buf[2] + (buf[3] << 8);
}
}
NVIC_EnableIRQ(TP_IRQ_EXTI_IRQn);
return (touches > 0);
}
/* USER CODE END STM32TouchController */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
@@ -0,0 +1,72 @@
/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : STM32TouchController.hpp
******************************************************************************
* This file was created by TouchGFX Generator 4.22.1. This file is only
* generated once! Delete this file from your project and re-generate code
* using STM32CubeMX or change this file manually to update it.
******************************************************************************
* @attention
*
* Copyright (c) 2023 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.
*
******************************************************************************
*/
/* USER CODE END Header */
/* USER CODE BEGIN STM32TouchController */
#ifndef STM32TOUCHCONTROLLER_HPP
#define STM32TOUCHCONTROLLER_HPP
#include <platform/driver/touch/TouchController.hpp>
/**
* @class STM32TouchController
*
* @brief This class specializes TouchController Interface.
*
* @sa touchgfx::TouchController
*/
class STM32TouchController : public touchgfx::TouchController
{
public:
STM32TouchController() {}
/**
* @fn virtual void STM32TouchController::init() = 0;
*
* @brief Initializes touch controller.
*
* Initializes touch controller.
*/
virtual void init();
/**
* @fn virtual bool STM32TouchController::sampleTouch(int32_t& x, int32_t& y) = 0;
*
* @brief Checks whether the touch screen is being touched, and if so, what coordinates.
*
* Checks whether the touch screen is being touched, and if so, what coordinates.
*
* @param [out] x The x position of the touch
* @param [out] y The y position of the touch
*
* @return True if a touch has been detected, otherwise false.
*/
virtual bool sampleTouch(int32_t& x, int32_t& y);
};
#endif // STM32TOUCHCONTROLLER_HPP
/* USER CODE END STM32TouchController */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
+176
View File
@@ -0,0 +1,176 @@
/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : TouchGFXGPIO.cpp
******************************************************************************
* This file was created by TouchGFX Generator 4.22.1. This file is only
* generated once! Delete this file from your project and re-generate code
* using STM32CubeMX or change this file manually to update it.
******************************************************************************
* @attention
*
* Copyright (c) 2023 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.
*
******************************************************************************
*/
/* USER CODE END Header */
#include <touchgfx/hal/GPIO.hpp>
/**
* GPIO_ID Enum
* The signals represented by this enum are used by TouchGFX framework to signal internal events.
*
* VSYNC_FREQ, /// Pin is toggled at each VSYNC
* RENDER_TIME, /// Pin is high when frame rendering begins, low when finished
* FRAME_RATE, /// Pin is toggled when the frame buffers are swapped.
* MCU_ACTIVE /// Pin is high when framework is utilizing the MCU.
*
* Configure GPIO's with the same name as the GPIO_IDs above, as output, in CubeMX to export
* the signals for performance measuring. See support.touchgfx.com for further details.
*
*/
/* USER CODE BEGIN TouchGFXGPIO.cpp */
#include "main.h"
using namespace touchgfx;
/*
* Perform configuration of IO pins.
*/
void GPIO::init()
{
}
/*
* Sets a pin high.
*/
void GPIO::set(GPIO_ID id)
{
switch (id)
{
case GPIO::VSYNC_FREQ:
#if defined(VSYNC_FREQ_GPIO_Port) && defined(VSYNC_FREQ_Pin)
HAL_GPIO_WritePin(VSYNC_FREQ_GPIO_Port, VSYNC_FREQ_Pin, GPIO_PIN_SET);
#endif
break;
case GPIO::RENDER_TIME:
#if defined(RENDER_TIME_GPIO_Port) && defined(RENDER_TIME_Pin)
HAL_GPIO_WritePin(RENDER_TIME_GPIO_Port, RENDER_TIME_Pin, GPIO_PIN_SET);
#endif
break;
case GPIO::FRAME_RATE:
#if defined(FRAME_RATE_GPIO_Port) && defined(FRAME_RATE_Pin)
HAL_GPIO_WritePin(FRAME_RATE_GPIO_Port, FRAME_RATE_Pin, GPIO_PIN_SET);
#endif
break;
case GPIO::MCU_ACTIVE:
#if defined(MCU_ACTIVE_GPIO_Port) && defined(MCU_ACTIVE_Pin)
HAL_GPIO_WritePin(MCU_ACTIVE_GPIO_Port, MCU_ACTIVE_Pin, GPIO_PIN_SET);
#endif
break;
}
}
/*
* Sets a pin low.
*/
void GPIO::clear(GPIO_ID id)
{
switch (id)
{
case GPIO::VSYNC_FREQ:
#if defined(VSYNC_FREQ_GPIO_Port) && defined(VSYNC_FREQ_Pin)
HAL_GPIO_WritePin(VSYNC_FREQ_GPIO_Port, VSYNC_FREQ_Pin, GPIO_PIN_RESET);
#endif
break;
case GPIO::RENDER_TIME:
#if defined(RENDER_TIME_GPIO_Port) && defined(RENDER_TIME_Pin)
HAL_GPIO_WritePin(RENDER_TIME_GPIO_Port, RENDER_TIME_Pin, GPIO_PIN_RESET);
#endif
break;
case GPIO::FRAME_RATE:
#if defined(FRAME_RATE_GPIO_Port) && defined(FRAME_RATE_Pin)
HAL_GPIO_WritePin(FRAME_RATE_GPIO_Port, FRAME_RATE_Pin, GPIO_PIN_RESET);
#endif
break;
case GPIO::MCU_ACTIVE:
#if defined(MCU_ACTIVE_GPIO_Port) && defined(MCU_ACTIVE_Pin)
HAL_GPIO_WritePin(MCU_ACTIVE_GPIO_Port, MCU_ACTIVE_Pin, GPIO_PIN_RESET);
#endif
break;
}
}
/*
* Toggles a pin.
*/
void GPIO::toggle(GPIO_ID id)
{
switch (id)
{
case GPIO::VSYNC_FREQ:
#if defined(VSYNC_FREQ_GPIO_Port) && defined(VSYNC_FREQ_Pin)
HAL_GPIO_TogglePin(VSYNC_FREQ_GPIO_Port, VSYNC_FREQ_Pin);
#endif
break;
case GPIO::RENDER_TIME:
#if defined(RENDER_TIME_GPIO_Port) && defined(RENDER_TIME_Pin)
HAL_GPIO_TogglePin(RENDER_TIME_GPIO_Port, RENDER_TIME_Pin);
#endif
break;
case GPIO::FRAME_RATE:
#if defined(FRAME_RATE_GPIO_Port) && defined(FRAME_RATE_Pin)
HAL_GPIO_TogglePin(FRAME_RATE_GPIO_Port, FRAME_RATE_Pin);
#endif
break;
case GPIO::MCU_ACTIVE:
#if defined(MCU_ACTIVE_GPIO_Port) && defined(MCU_ACTIVE_Pin)
HAL_GPIO_TogglePin(MCU_ACTIVE_GPIO_Port, MCU_ACTIVE_Pin);
#endif
break;
}
}
/*
* Gets the state of a pin.
*/
bool GPIO::get(GPIO_ID id)
{
GPIO_PinState bitstatus = GPIO_PIN_RESET;
switch (id)
{
case GPIO::VSYNC_FREQ:
#if defined(VSYNC_FREQ_GPIO_Port) && defined(VSYNC_FREQ_Pin)
bitstatus = HAL_GPIO_ReadPin(VSYNC_FREQ_GPIO_Port, VSYNC_FREQ_Pin);
#endif
break;
case GPIO::RENDER_TIME:
#if defined(RENDER_TIME_GPIO_Port) && defined(RENDER_TIME_Pin)
bitstatus = HAL_GPIO_ReadPin(RENDER_TIME_GPIO_Port, RENDER_TIME_Pin);
#endif
break;
case GPIO::FRAME_RATE:
#if defined(FRAME_RATE_GPIO_Port) && defined(FRAME_RATE_Pin)
bitstatus = HAL_GPIO_ReadPin(FRAME_RATE_GPIO_Port, FRAME_RATE_Pin);
#endif
break;
case GPIO::MCU_ACTIVE:
#if defined(MCU_ACTIVE_GPIO_Port) && defined(MCU_ACTIVE_Pin)
bitstatus = HAL_GPIO_ReadPin(MCU_ACTIVE_GPIO_Port, MCU_ACTIVE_Pin);
#endif
break;
}
return (bitstatus == GPIO_PIN_SET);
}
/* USER CODE END TouchGFXGPIO.cpp */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
+189
View File
@@ -0,0 +1,189 @@
/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : TouchGFXHAL.cpp
******************************************************************************
* This file was created by TouchGFX Generator 4.22.1. This file is only
* generated once! Delete this file from your project and re-generate code
* using STM32CubeMX or change this file manually to update it.
******************************************************************************
* @attention
*
* Copyright (c) 2023 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.
*
******************************************************************************
*/
/* USER CODE END Header */
#include <TouchGFXHAL.hpp>
/* USER CODE BEGIN TouchGFXHAL.cpp */
#include "FreeRTOS.h"
using namespace touchgfx;
namespace
{
LOCATION_PRAGMA_NOLOAD("TouchGFX_Framebuffer")
uint32_t animationBuffer[(800 * 480 * 2 + 3) / 4] LOCATION_ATTRIBUTE_NOLOAD("TouchGFX_Framebuffer");
}
void TouchGFXHAL::initialize()
{
// Calling parent implementation of initialize().
//
// To overwrite the generated implementation, omit call to parent function
// and implemented needed functionality here.
// Please note, HAL::initialize() must be called to initialize the framework.
TouchGFXGeneratedHAL::initialize();
setAnimationStorage((void*)animationBuffer); //enable the animation storage to allow slide animations
instrumentation.init();
setMCUInstrumentation(&instrumentation);
enableMCULoadCalculation(true);
}
/**
* Gets the frame buffer address used by the TFT controller.
*
* @return The address of the frame buffer currently being displayed on the TFT.
*/
uint16_t* TouchGFXHAL::getTFTFrameBuffer() const
{
// Calling parent implementation of getTFTFrameBuffer().
//
// To overwrite the generated implementation, omit call to parent function
// and implemented needed functionality here.
return TouchGFXGeneratedHAL::getTFTFrameBuffer();
}
/**
* Sets the frame buffer address used by the TFT controller.
*
* @param [in] address New frame buffer address.
*/
void TouchGFXHAL::setTFTFrameBuffer(uint16_t* address)
{
// Calling parent implementation of setTFTFrameBuffer(uint16_t* address).
//
// To overwrite the generated implementation, omit call to parent function
// and implemented needed functionality here.
TouchGFXGeneratedHAL::setTFTFrameBuffer(address);
}
/**
* 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().
*/
void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect)
{
// Calling parent implementation of flushFrameBuffer(const touchgfx::Rect& rect).
//
// To overwrite the generated implementation, omit call to parent function
// and implemented needed functionality here.
// Please note, HAL::flushFrameBuffer(const touchgfx::Rect& rect) must
// be called to notify the touchgfx framework that flush has been performed.
// To calculate he start adress of rect,
// use advanceFrameBufferToRect(uint8_t* fbPtr, const touchgfx::Rect& rect)
// defined in TouchGFXGeneratedHAL.cpp
TouchGFXGeneratedHAL::flushFrameBuffer(rect);
}
bool TouchGFXHAL::blockCopy(void* RESTRICT dest, const void* RESTRICT src, uint32_t numBytes)
{
return TouchGFXGeneratedHAL::blockCopy(dest, src, numBytes);
}
/**
* Configures the interrupts relevant for TouchGFX. This primarily entails setting
* the interrupt priorities for the DMA and LCD interrupts.
*/
void TouchGFXHAL::configureInterrupts()
{
// Calling parent implementation of configureInterrupts().
//
// To overwrite the generated implementation, omit call to parent function
// and implemented needed functionality here.
TouchGFXGeneratedHAL::configureInterrupts();
}
/**
* Used for enabling interrupts set in configureInterrupts()
*/
void TouchGFXHAL::enableInterrupts()
{
// Calling parent implementation of enableInterrupts().
//
// To overwrite the generated implementation, omit call to parent function
// and implemented needed functionality here.
TouchGFXGeneratedHAL::enableInterrupts();
}
/**
* Used for disabling interrupts set in configureInterrupts()
*/
void TouchGFXHAL::disableInterrupts()
{
// Calling parent implementation of disableInterrupts().
//
// To overwrite the generated implementation, omit call to parent function
// and implemented needed functionality here.
TouchGFXGeneratedHAL::disableInterrupts();
}
/**
* Configure the LCD controller to fire interrupts at VSYNC. Called automatically
* once TouchGFX initialization has completed.
*/
void TouchGFXHAL::enableLCDControllerInterrupt()
{
// Calling parent implementation of enableLCDControllerInterrupt().
//
// To overwrite the generated implementation, omit call to parent function
// and implemented needed functionality here.
TouchGFXGeneratedHAL::enableLCDControllerInterrupt();
}
bool TouchGFXHAL::beginFrame()
{
return TouchGFXGeneratedHAL::beginFrame();
}
void TouchGFXHAL::endFrame()
{
TouchGFXGeneratedHAL::endFrame();
}
extern "C"
{
portBASE_TYPE IdleTaskHook(void* p)
{
if ((int)p) //idle task sched out
{
touchgfx::HAL::getInstance()->setMCUActive(true);
}
else //idle task sched in
{
touchgfx::HAL::getInstance()->setMCUActive(false);
}
return pdTRUE;
}
}
/* USER CODE END TouchGFXHAL.cpp */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
+174
View File
@@ -0,0 +1,174 @@
/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : TouchGFXHAL.hpp
******************************************************************************
* This file was created by TouchGFX Generator 4.22.1. This file is only
* generated once! Delete this file from your project and re-generate code
* using STM32CubeMX or change this file manually to update it.
******************************************************************************
* @attention
*
* Copyright (c) 2023 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.
*
******************************************************************************
*/
/* USER CODE END Header */
#ifndef TouchGFXHAL_HPP
#define TouchGFXHAL_HPP
/* USER CODE BEGIN TouchGFXHAL.hpp */
#include <TouchGFXGeneratedHAL.hpp>
#include <CortexMMCUInstrumentation.hpp>
/**
* @class TouchGFXHAL
*
* @brief HAL implementation for TouchGFX.
*
* @sa HAL
*/
class TouchGFXHAL : public TouchGFXGeneratedHAL
{
public:
/**
* @fn TouchGFXHAL::TouchGFXHAL(touchgfx::DMA_Interface& dma, touchgfx::LCD& display, touchgfx::TouchController& tc, uint16_t width, uint16_t height) : TouchGFXGeneratedHAL(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.
*/
TouchGFXHAL(touchgfx::DMA_Interface& dma, touchgfx::LCD& display, touchgfx::TouchController& tc, uint16_t width, uint16_t height) : TouchGFXGeneratedHAL(dma, display, tc, width, height)
{
}
virtual void initialize();
/**
* @fn virtual void TouchGFXHAL::disableInterrupts();
*
* @brief Disables the DMA and LCD interrupts.
*
* Disables the DMA and LCD interrupts.
*/
virtual void disableInterrupts();
/**
* @fn virtual void TouchGFXHAL::enableInterrupts();
*
* @brief Enables the DMA and LCD interrupts.
*
* Enables the DMA and LCD interrupts.
*/
virtual void enableInterrupts();
/**
* @fn virtual void TouchGFXHAL::configureInterrupts();
*
* @brief Sets the DMA and LCD interrupt priorities.
*
* Sets the DMA and LCD interrupt priorities.
*/
virtual void configureInterrupts();
/**
* @fn virtual void TouchGFXHAL::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();
virtual bool beginFrame();
virtual void endFrame();
/**
* @fn virtual void TouchGFXHAL::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()
{
TouchGFXGeneratedHAL::flushFrameBuffer();
}
/**
* @fn virtual void TouchGFXHAL::flushFrameBuffer(const 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.
* On the STM32F7, make sure to clean and invalidate the data cache. This is to
* ensure that LTDC sees correct data when transferring to the display.
*
* @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 bool TouchGFXHAL::blockCopy(void* RESTRICT dest, const void* RESTRICT src, uint32_t numBytes);
*
* @brief This function performs a platform-specific memcpy.
*
* 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);
protected:
/**
* @fn virtual uint16_t* TouchGFXHAL::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 TouchGFXHAL::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);
private:
touchgfx::CortexMMCUInstrumentation instrumentation;
};
/* USER CODE END TouchGFXHAL.hpp */
#endif // TouchGFXHAL_HPP
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
@@ -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****/
+275
View 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>&copy; 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****/