本帖最后由 xhackerustc 于 2025-5-11 22:04 编辑
#申请原创#
@21小跑堂
STM32N6集成有硬件H264 Encoder,也集成了sdmc控制器,板级设计上又加了tf卡槽,所以自然而然想到摄像头拍摄录制视频存入sd卡,所以笔者很想实现下这个功能。会偷懒的程序员才是好的程序员,ST官方有没有实现了这个功能呢?还真给笔者找到了,在STM32Cube_FW_N6的
Projects/STM32N6570-DK/Applications/VENC/VENC_SDCard/
Projects/STM32N6570-DK/Applications/VENC/VENC_SDCard_ThreadX/
前者是裸机环境,后者为ThreadX RTOS环境,笔者现在对ThreadX不是太感冒,兴趣完全迁移到了zephyr RTOS了,所以就VENC_SDCard吧。可能因STM32N6很新的缘故,这个VENC_SDCard还未来得及加上fatfs的支持,编码好的H264流是当字节流直接存在tf卡上的,无任何文件系统,fatfs的支持可以放TODO List中。
因VENC_SDCard已自包含,笔者不想再架起CubeMX这种大杀器了,直接写了个CMakeLists.txt,内容如下:
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_VERSION 1)
set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")
cmake_minimum_required(VERSION 3.20)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_AR arm-none-eabi-ar)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
set(CMAKE_OBJDUMP arm-none-eabi-objdump)
set(SIZE arm-none-eabi-size)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
add_compile_options(-mthumb -mcpu=cortex-m55 -mfloat-abi=hard -mfpu=fpv5-d16 -mcmse -fsingle-precision-constant)
add_compile_options(-ffunction-sections -fdata-sections -fno-builtin -fno-common -Wdouble-promotion -Werror -Wno-unused-parameter -Wno-incompatible-pointer-types)
add_compile_options(-O2)
project(stm32n6 C ASM)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 99)
include_directories(Drivers/CMSIS/Include
Drivers/CMSIS/Device/ST/STM32N6xx/Include
Drivers/STM32N6xx_HAL_Driver/Inc
Drivers/BSP/STM32N6570-DK
Drivers/BSP/Components/Common
Utilities/lcd
Drivers/BSP/Components/imx335
Middlewares/ST/STM32_ISP_Library/isp/Inc
Middlewares/ST/STM32_ISP_Library/evision/Inc
Middlewares/Third_Party/VideoEncoder/inc
Middlewares/Third_Party/VideoEncoder/source/common
Middlewares/Third_Party/VideoEncoder/source/h264
Projects/STM32N6570-DK/Applications/VENC/VENC_SDCard/Appli/Inc)
add_definitions(-DSTM32N657xx)
file(GLOB_RECURSE SOURCES
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_cortex.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_xspi.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_dma.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_dma_ex.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_rcc.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_rcc_ex.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_gpio.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_pwr.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_pwr_ex.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_exti.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_uart.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_ltdc.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_dma2d.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_rif.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_jpeg.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_dcmipp.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_tim.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_tim_ex.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_sd.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_ll_venc.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_ll_sdmmc.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_i2c.c"
"Drivers/STM32N6xx_HAL_Driver/Src/stm32n6xx_hal_i2c_ex.c"
"Drivers/BSP/STM32N6570-DK/stm32n6570_discovery.c"
"Drivers/BSP/STM32N6570-DK/stm32n6570_discovery_lcd.c"
"Drivers/BSP/STM32N6570-DK/stm32n6570_discovery_sd.c"
"Drivers/BSP/STM32N6570-DK/stm32n6570_discovery_camera.c"
"Drivers/BSP/STM32N6570-DK/stm32n6570_discovery_bus.c"
"Drivers/BSP/Components/imx335/*.c"
"Utilities/lcd/stm32_lcd.c"
"Middlewares/ST/VideoEncoder_EWL/*.c"
"Middlewares/ST/STM32_ISP_Library/isp/Src/isp_algo.c"
"Middlewares/ST/STM32_ISP_Library/isp/Src/isp_core.c"
"Middlewares/ST/STM32_ISP_Library/isp/Src/isp_services.c"
"Middlewares/ST/STM32_ISP_Library/isp/Src/isp_tool_com.c"
"Middlewares/ST/STM32_ISP_Library/isp/Src/isp_cmd_parser.c"
"Middlewares/Third_Party/VideoEncoder/source/h264/*.c"
"Middlewares/Third_Party/VideoEncoder/source/common/*.c"
"Projects/STM32N6570-DK/Applications/VENC/VENC_SDCard/Appli/Src/*.c"
"Projects/STM32N6570-DK/Applications/VENC/VENC_SDCard/STM32CubeIDE/Appli/Src/*.c"
"Projects/STM32N6570-DK/Applications/VENC/VENC_SDCard/STM32CubeIDE/Appli/Startup/startup_stm32n657xx.s"
)
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/Projects/STM32N6570-DK/Applications/VENC/VENC_SDCard/STM32CubeIDE/Appli/STM32N657XX_LRUN.ld)
add_link_options(
-mthumb -mcpu=cortex-m55 -mfloat-abi=hard -mfpu=fpv5-d16 -mcmse
-Wl,--gc-sections,--print-memory-usage,-Map,${PROJECT_NAME}.map
--specs=nano.specs
--specs=nosys.specs)
add_link_options(-T ${LINKER_SCRIPT})
add_executable(${PROJECT_NAME}.elf ${SOURCES} ${LINKER_SCRIPT})
# target_link_libraries(${PROJECT_NAME}.elf printfloat)
target_link_libraries(${PROJECT_NAME}.elf ${CMAKE_SOURCE_DIR}/Middlewares/ST/STM32_ISP_Library/evision/Lib/libn6-evision-st-ae_gcc.a)
target_link_libraries(${PROJECT_NAME}.elf ${CMAKE_SOURCE_DIR}/Middlewares/ST/STM32_ISP_Library/evision/Lib/libn6-evision-awb_gcc.a)
set(HEX_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.hex)
set(BIN_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.bin)
set(LST_FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.lst)
add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -Oihex [ DISCUZ_CODE_467 ]lt;TARGET_FILE:${PROJECT_NAME}.elf> ${HEX_FILE}
COMMAND ${CMAKE_OBJCOPY} -Obinary [ DISCUZ_CODE_467 ]lt;TARGET_FILE:${PROJECT_NAME}.elf> ${BIN_FILE}
COMMAND ${CMAKE_OBJDUMP} --all-headers --demangle --disassemble [ DISCUZ_CODE_467 ]lt;TARGET_FILE:${PROJECT_NAME}.elf> > ${LST_FILE}
COMMAND ${SIZE} --format=berkeley [ DISCUZ_CODE_467 ]lt;TARGET_FILE:${PROJECT_NAME}.elf>
)
充分利用大sram
咱直接把代码加载进sram跑,尽量避免烧录xspi nor flash,这是不是有linux开发那味儿了。不过这么一来有个问题,FSBL有部分初始化code未运行,所以需要把FSBL/Src/system_stm32n6xx_fsbl.c的SystemInit()的代码完全拷贝添加到Appli/Src/system_stm32n6xx_s.c的SystemInit()中。
编译
cmake -B /tmp/build
cmake --build /tmp/build -j8
在/tmp/build/目录下即有stm32n6.bin生成,此文件即我们要加载进sram运行的bin文件。万事具备上酸菜,sorry上openocd
加载到sram运行
一终端执行
openocd/src/openocd -f interface/stlink.cfg -c "transport select dapdirect_swd" -f target/stm32n6.cfg -c "adapter speed 8000"
另一终端执行
然后在此openocd的telnet命令session下依次执行如下命令
halt
load_image /tmp/build/stm32n6.bin 0x34000400
reg pc 0x34019038
resume
现在LCD应该亮屏并显示摄像头拍到的东西,如图
错误出现与解决
但是如果观察STLINK的虚拟串口的输出,会发现奇怪的错误
What, "error initializing encoder -4"什么意思,难道VENC_SDCard代码有bug?但是VENC_SDCard的README文件明明提到可以运行,甚至怎么从tf卡dump h264数据流步骤都写清楚了。这里又是一个大坑,笔者花了很长时间调试跟踪,最后发现这确实是VENC_SDCard的代码中一个bug,至少对于arm-none-eabi-gcc toolchain来说确实有bug。VENC_SDCard依赖很多中间件,其中之一是VideoEncoder,这个库会用到malloc()函数从堆上分配内存,熟悉arm-none-eabi-gcc toolchain的工程师都知道,其libc是newlibc,它的malloc()实现需要用户提供sbrk()函数,但是我们看下VENC_SDCard给arm-none-eabi-gcc提供的sbrk()函数,它的位置在STM32CubeIDE/Appli/Src/sysmem.c
再看看ld文件,它的位置在STM32CubeIDE/Appli/STM32N657XX_LRUN.ld
这里栈放在了STM32N6的DTCM中,即0x30000000开始的128KB TCM,而STM32N6的secure world的SRAM都位于0x34000000,所以下面这个判断总是成立:
即总会认为堆已经用尽,快要增长到栈中去了,所以总是返回-1, errno被设成了ENOMEM,从而libc的malloc()会失败,于是下面这段代码EWLcalloc()会失败:
于是代码返回H264ENC_MEMORY_ERROR,这个H264ENC_MEMORY_ERROR的值正是-4, 这也就是上面"error initializing encoder -4"的来历。问题出在什么地方,linkscript把栈放在DTCM中,但是sysmem.c为做对应调整,仍然还是用的原来栈在SRAM中的版本。知道了原因修改就很简单了,笔者把STM32CubeIDE/Appli/Src/sysmem.c sbrk()函数修改如下
void *_sbrk(ptrdiff_t incr)
{
extern uint8_t _end; /* Symbol defined in the linker script */
extern uint8_t _estack; /* Symbol defined in the linker script */
extern uint32_t _Min_Heap_Size; /* Symbol defined in the linker script */
const uint32_t stack_limit = (uint32_t)&_end + (uint32_t)_Min_Heap_Size;
const uint8_t *max_heap = (uint8_t *)stack_limit;
uint8_t *prev_heap_end;
/* Initialize heap end at first call */
if (NULL == __sbrk_heap_end)
{
__sbrk_heap_end = &_end;
}
/* Protect heap from growing into the reserved MSP stack */
if (__sbrk_heap_end + incr > max_heap)
{
errno = ENOMEM;
return (void *)-1;
}
prev_heap_end = __sbrk_heap_end;
__sbrk_heap_end += incr;
return (void *)prev_heap_end;
}
再次编译加载到sram运行,一切变得那么丝滑
串口截图
录制结束把tf卡插读卡器接入电脑,运行如下命令读出
dd if=/dev/sdc of=/tmp/dump.bin bs=512 count=25000
这个dump.bin其实已经是正儿八经的H264视频文件,file探测下文件显示:
用mpv等软件也能播放,只是提示无timestamp:
可运行如下命令设置下30帧/s的帧率copy成转成另外一个视频文件:
ffmpeg -f h264 -framerate 30 -i /tmp/dump.bin -c copy /tmp/out.mp4
这个文件再用mpv软件播放已经不抱怨无timestamp了
|