2024-09-10 08:12 AM - edited 2024-09-10 11:56 AM
Hi All,
I've been trying to bring up a custom external loader for well over a week and I'm just not getting anywhere.
My processor is an STM32L496VGT6 and its connected via quadSPI to an MT25QL128A 16MB external flash memory. I'm running the clock at 80MHz, and my QUADSPI is setup like this (both in my custom loader, and in the application code I'm developing):
Following mostly along with the "External QSPI loader how to" (from here External QSPI loader how to - 05 – QSPI driver coding - YouTube), and utilizing the files in the contrib branch of the stm external loader github repository (GitHub - STMicroelectronics/stm32-external-loader at contrib), I've put together what seems like a working driver for the MT25QL128. I got there by starting with the quadspi.h/c files in the stm32-custom-loader/QSPI_Drivers/MT25QL512/ folder, and then modifying them using the micro datasheet for the part to swap different commands where necessary. I pulled in test code from the main.c file in stm32-external-loader-contrib\Loader_Files\other devices\, and also used the Loader_Src.c, Dev_Inf.h/c, an linker.ld files from the same folder. The following test code runs with no issues when debugging:
/* USER CODE BEGIN 2 */
uint8_t buffer_test[MEMORY_SECTOR_SIZE];
uint32_t var = 0;
CSP_QUADSPI_Init();
for (var = 0; var < MEMORY_SECTOR_SIZE; var++) {
buffer_test[var] = (var & 0xff);
}
for (var = 0; var < SECTORS_COUNT; var++) {
if (CSP_QSPI_EraseSector(var * MEMORY_SECTOR_SIZE, (var + 1) * MEMORY_SECTOR_SIZE - 1) != HAL_OK) {
while (1)
; //breakpoint - error detected
}
if (CSP_QSPI_WriteMemory(buffer_test, var * MEMORY_SECTOR_SIZE, sizeof(buffer_test)) != HAL_OK) {
while (1)
; //breakpoint - error detected
}
}
if (CSP_QSPI_EnableMemoryMappedMode() != HAL_OK) {
while (1)
; //breakpoint - error detected
}
for (var = 0; var < SECTORS_COUNT; var++) {
if (memcmp(buffer_test, (uint8_t*) (QSPI_BASE + var * MEMORY_SECTOR_SIZE), MEMORY_SECTOR_SIZE) != HAL_OK) {
while (1)
; //breakpoint - error detected - otherwise QSPI works properly
}
}
/* USER CODE END 2 */
I then modified my project options to generate the stldr file per the youtube video, changed to the "linker.ld" file, and copied the stldr file it into my STM32CubeProgrammer ExternalLoader folder. I used the STM32CubeProgrammer to read memory (starting at 0x90000000), and write to the same memory area using the "testbinary1M.bin" file from \stm32-external-loader-contrib\QSPI_testing\.
Everything up to this point seems to work without problems so far.
Where my problem begins is when I attempt to use the new external loader I generated in my debug sessions while trying to develop my main application. Here I've gone into the debug sessions, pointed to my generated loader file, and started to debug.
With the external loader enabled as above, the code stops on the first use of:
HAL_Delay(1);
If I drill down a little with the debugger, the code is stuck within HAL_Delay() in the while loop:
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
/* Add a period to guaranty minimum wait */
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)uwTickFreq;
}
while ((HAL_GetTick() - tickstart) < wait) // <===== STUCK HERE!
{
}
}
Where HAL_GetTick() constantly returns 0, which doesn't seem right.
When I disable the external loader, everything goes back to working fine. What do I check next? Is my external loader really just not working in some untested way? Or is there some initialization I need to do in my main() initialization when using an external loader? Or maybe some deinitialization I didn't do in my loader?
Solved! Go to Solution.
2024-09-13 02:11 PM
Holy Carp!
My external loader was more or less working all along. I just needed to not have "initialize" checked in the external loader debug settings.
An ST field applications engineer took a look at it and that was all it was. I'll try to post a follow up with a few more details on Monday, but that was pretty much it. Many thanks for those of you who offered advice, and a special thanks to @Tesla DeLorean for trying to help me work through it.
2024-09-10 08:28 AM
Check if uwTickFreq (used in the HAL_Delay() function) has the correct value and ensure that the SysTick timer has been set to the expected frequency (e.g., 1 ms tick).
2024-09-10 10:26 AM
Thank you for the quick response! uwTickFreq appears to be HAL_TICK_FREQ_1KHZ, or at least that is what the debugger says it is when I run the code. That seems to be the value whether I have the external loader enabled or not.
2024-09-10 10:38 AM
Really shouldn't be using interrupts in the loader, and systick is an interrupt that would need enabling, and have a functional vector table and handlers to be viable.
Perhaps use TIM2->CNT as a proxy for a software counter to mark time?
2024-09-10 10:42 AM
Sorry, I realize that I may not have responded completely. As far as verifying the Systick timer, I added a GPIO toggle command to the SysTick_Handler() and measured the frequency with an oscilloscope.
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
HAL_GPIO_TogglePin(TP_PA9_GPIO_Port, TP_PA9_Pin);
/* USER CODE END SysTick_IRQn 1 */
}
Without the external loader enabled: 500 Hz square wave (Systick_Handler fires at 1KHz)
With the external loader enabled: The Systick_Handler never fires.
2024-09-10 10:52 AM
Thanks for that suggestion. To be clear, the uwTickFreq and Systick info I just posted was from my app code, not the external loader itself.
Here's my Init() function from the Loader_Src file, pretty much just taken straight from the \stm32-external-loader-contrib\ (I've attached Loader_Src.h/c and Dev_Inf.c for a full code listing):
int Init(void) {
*(uint32_t*)0xE000EDF0 = 0xA05F0000; //enable interrupts in debug
SystemInit();
/* ADAPTATION TO THE DEVICE
*
* change VTOR setting for H7 device
* SCB->VTOR = 0x24000000 | 0x200;
*
* change VTOR setting for other devices
* SCB->VTOR = 0x20000000 | 0x200;
*
* */
SCB->VTOR = 0x20000000 | 0x200;
__set_PRIMASK(0); //enable interrupts
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
__HAL_RCC_QSPI_FORCE_RESET(); //completely reset peripheral
__HAL_RCC_QSPI_RELEASE_RESET();
if (CSP_QUADSPI_Init() != HAL_OK) {
__set_PRIMASK(1); //disable interrupts
return LOADER_FAIL;
}
if (CSP_QSPI_EnableMemoryMappedMode() != HAL_OK) {
__set_PRIMASK(1); //disable interrupts
return LOADER_FAIL;
}
__set_PRIMASK(1); //disable interrupts
return LOADER_OK;
}
Should I remove the __set_PRIMASK() statements?
2024-09-10 10:58 AM
You'd need a viable Vector Table, so one properly aligned, and communicated to the core via SCB->VTOR
My money would be to pretend it's at 0x20000000, and lose the Initial Stack Pointer entry entirely as the loader is parked at 0x20000004 on the STM32L4
Better solution would be to enable a TIM maximal counting, perhaps at 1 MHz, not interrupting, and using the 16 or 32-bit count to mark the elapse of time
2024-09-10 11:29 AM
Supposing hypothetically speaking that I were to have copied the above Init() function (and for that matter, all of the code recommended in the STM external loader YouTube tutorial mentioned above), and further supposing for a moment that I'm well below average intelligence and were to have very little understanding of said code,
Is the line "SCB->VTOR = 0x20000000 | 0x200;" the vector table that I should leave parked? And if that were to be true, which line is the initial stack pointer I should be losing?
I might not be understanding all (any) of what you are suggesting, at least in a practical sense (hypothetically, of course)
2024-09-10 01:33 PM
Let me ask this question more generally - Why would an external loader affect the operation of the code that was loaded after its operation completed? Doesn't ram get completely overwritten in the MCU? Why then would my Systick behaviour change after the loader has run? I know I'm missing something key here.
2024-09-10 09:15 PM
The SCB->VTOR doesn't get reset, it'll stick with the last value set. Or if you disable interrupts