{"id":4168,"date":"2022-05-23T12:12:15","date_gmt":"2022-05-23T10:12:15","guid":{"rendered":"https:\/\/msalamon.pl\/?p=4168"},"modified":"2025-12-27T15:43:24","modified_gmt":"2025-12-27T14:43:24","slug":"interrupts-not-working-on-stm32g0-what-is-vtor-hal-bug","status":"publish","type":"post","link":"https:\/\/msalamon.pl\/en\/interrupts-not-working-on-stm32g0-what-is-vtor-hal-bug\/","title":{"rendered":"Interrupts not working on STM32G0? What is VTOR? (HAL bug)"},"content":{"rendered":"\n<p>I haven\u2019t had much experience with the STM32G0 family yet, but my course participants have. Recently, we were solving together a <strong>problem with interrupts not working<\/strong> on the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/mikrokontroler-stm32g031j6m6-arm-cortex-m0-so8n\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=vtor&amp;utm_content=Text\" target=\"_blank\" rel=\"noopener\">STM32G031J6M6 <\/a>known from the <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/stm32-discovery-stm32g0316-disco\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=vtor&amp;utm_content=Text\" target=\"_blank\" rel=\"noopener\">STM32G0316-DISCO<\/a> board. It was an interesting problem, so I decided to share it more broadly.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Handling interrupts via NVIC in STM32<\/h1>\n\n\n\n<p>NVIC is an abbreviation \u2013 Nested Vector Interrupt Controller. It is the <strong>controller responsible for interrupts<\/strong> in STM32 microcontrollers. It is much more complex than what we used to have, for example in AVR. But there\u2019s no need to be afraid of it \ud83d\ude42<\/p>\n\n\n\n<p>I\u2019ll discuss only what is needed to describe the problem that I was dealing with together with a participant. We\u2019re interested in the letter V in this abbreviation, i.e. <strong>Vector<\/strong>. In other words, it is an <strong>array of pointers.<\/strong> Why do we need it?<\/p>\n\n\n\n<p>Several dozen <strong>interrupt lines<\/strong> go into the NVIC controller. Each of these lines triggers a different interrupt. Each interrupt has its own <strong>handler<\/strong>. And this is exactly where the interrupt vector comes in. It is nothing more than an <strong>array of pointers to functions<\/strong> that implement specific interrupts.<\/p>\n\n\n\n<p>This vector <strong>is laid out<\/strong> according to how the chip manufacturer directly connected the interrupt lines to the NVIC. We really don\u2019t want to change that order \ud83d\ude42<\/p>\n\n\n\n<p>What does such a vector defined in a program look like? It is written in assembly <strong>in the startup file<\/strong> for each microcontroller. For the discussed <a href=\"https:\/\/sklep.msalamon.pl\/produkt\/mikrokontroler-stm32g031j6m6-arm-cortex-m0-so8n\/?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=vtor&amp;utm_content=Text\" target=\"_blank\" rel=\"noopener\">STM32G031J6M6 <\/a> it looks as follows:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/******************************************************************************\n*\n* The minimal vector table for a Cortex M0.  Note that the proper constructs\n* must be placed on this to ensure that it ends up at physical address\n* 0x0000.0000.\n*\n******************************************************************************\/\n  .section .isr_vector,\"a\",%progbits\n  .type g_pfnVectors, %object\n  .size g_pfnVectors, .-g_pfnVectors\n\ng_pfnVectors:\n  .word _estack\n  .word Reset_Handler\n  .word NMI_Handler\n  .word HardFault_Handler\n  .word 0\n  .word 0\n  .word 0\n  .word 0\n  .word 0\n  .word 0\n  .word 0\n  .word SVC_Handler\n  .word 0\n  .word 0\n  .word PendSV_Handler\n  .word SysTick_Handler\n  .word WWDG_IRQHandler                   \/* Window WatchDog              *\/\n  .word PVD_IRQHandler                    \/* PVD through EXTI Line detect *\/\n  .word RTC_TAMP_IRQHandler               \/* RTC through the EXTI line    *\/\n  .word FLASH_IRQHandler                  \/* FLASH                        *\/\n  .word RCC_IRQHandler                    \/* RCC                          *\/\n  .word EXTI0_1_IRQHandler                \/* EXTI Line 0 and 1            *\/\n  .word EXTI2_3_IRQHandler                \/* EXTI Line 2 and 3            *\/\n  .word EXTI4_15_IRQHandler               \/* EXTI Line 4 to 15            *\/\n  .word 0                                 \/* reserved                     *\/\n  .word DMA1_Channel1_IRQHandler          \/* DMA1 Channel 1               *\/\n  .word DMA1_Channel2_3_IRQHandler        \/* DMA1 Channel 2 and Channel 3 *\/\n  .word DMA1_Ch4_5_DMAMUX1_OVR_IRQHandler \/* DMA1 Channel 4 to Channel 5, DMAMUX1 overrun *\/\n  .word ADC1_IRQHandler                   \/* ADC1                        *\/\n  .word TIM1_BRK_UP_TRG_COM_IRQHandler    \/* TIM1 Break, Update, Trigger and Commutation *\/\n  .word TIM1_CC_IRQHandler                \/* TIM1 Capture Compare         *\/\n  .word TIM2_IRQHandler                   \/* TIM2                         *\/\n  .word TIM3_IRQHandler                   \/* TIM3                         *\/\n  .word LPTIM1_IRQHandler                 \/* LPTIM1                       *\/\n  .word LPTIM2_IRQHandler                 \/* LPTIM2                       *\/\n  .word TIM14_IRQHandler                  \/* TIM14                        *\/\n  .word 0                                 \/* reserved                     *\/\n  .word TIM16_IRQHandler                  \/* TIM16                        *\/\n  .word TIM17_IRQHandler                  \/* TIM17                        *\/\n  .word I2C1_IRQHandler                   \/* I2C1                         *\/\n  .word I2C2_IRQHandler                   \/* I2C2                         *\/\n  .word SPI1_IRQHandler                   \/* SPI1                         *\/\n  .word SPI2_IRQHandler                   \/* SPI2                         *\/\n  .word USART1_IRQHandler                 \/* USART1                       *\/\n  .word USART2_IRQHandler                 \/* USART2                       *\/\n  .word LPUART1_IRQHandler                \/* LPUART1                      *\/\n  .word 0                                 \/* reserved                     *\/\n<\/pre>\n\n\n\n<p>What we see here are <strong>function names, i.e. pointers to them.<\/strong> These functions are the interrupt handlers at the appropriate positions in the vector. You can notice that some positions are empty. That\u2019s because ST did not connect any interrupt at those offsets. So there is no way to use that line.<\/p>\n\n\n\n<p>We can compare this vector with what we have in the documentation. The <strong>interrupt vector<\/strong> together with positions and interrupt priorities <strong>can be found in the <a href=\"\/Users\/mateu\/Downloads\/rm0444-stm32g0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf\" target=\"_blank\" rel=\"noopener\">Reference Manual<\/a>.<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2022\/05\/vector_table.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"702\" src=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2022\/05\/vector_table-1024x702.png\" alt=\"\" class=\"wp-image-2165\" srcset=\"https:\/\/msalamon.pl\/wp-content\/uploads\/2022\/05\/vector_table-1024x702.png 1024w, https:\/\/msalamon.pl\/wp-content\/uploads\/2022\/05\/vector_table-300x206.png 300w, https:\/\/msalamon.pl\/wp-content\/uploads\/2022\/05\/vector_table-768x527.png 768w, https:\/\/msalamon.pl\/wp-content\/uploads\/2022\/05\/vector_table.png 1336w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<div class=\"wp-block-image is-style-default\">\n<figure class=\"aligncenter size-large is-resized\"><\/figure>\n<\/div>\n\n\n\n<p>What happens when the NVIC detects an interrupt? <strong>It jumps to the address given in the interrupt vector.<\/strong> If the interrupt was on line #5, it jumps to the function in the vector at position 5. Simple, isn\u2019t it?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">VTOR<\/h2>\n\n\n\n<p><strong>How does the NVIC know where in memory this vector is located?<\/strong> Is it permanently planned and assigned from the top down? Well, no.<\/p>\n\n\n\n<p>This vector can be in different locations. This is used, for example, <strong>when writing bootloaders.<\/strong> A bootloader is a separate program on the microcontroller that calls the target application. That application is located elsewhere in memory and has its own interrupt vector.<\/p>\n\n\n\n<p>So you have to <strong>tell the NVIC where it should jump in order to handle an interrupt.<\/strong> Now an important thing. <strong>The NVIC is part of the core,<\/strong> which is designed by ARM. It is not a module added by ST or another Cortex-M microcontroller manufacturer.<\/p>\n\n\n\n<p><strong>The core documentation<\/strong> (and NVIC) is a separate file from the Reference Manual. <strong>For Cortex-M0 and M0+ it is <a href=\"https:\/\/www.st.com\/resource\/en\/programming_manual\/pm0223-cortexm0-programming-manual-for-stm32l0-stm32g0-stm32wl-and-stm32wb-series-stmicroelectronics.pdf\" target=\"_blank\" rel=\"noopener\">HERE<\/a>. <\/strong><\/p>\n\n\n\n<p>Getting to the point: There is a special register in the microcontroller core that stores the address where the NVIC should look for the vector table. And that\u2019s it \ud83d\ude42<\/p>\n\n\n\n<p><strong>You have to fill this register.<\/strong> When? Before the main program starts.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">BUG in STM32G0 HAL<\/h2>\n\n\n\n<p>If you use HAL and you\u2019re not writing a bootloader, then you probably don\u2019t care about setting this register. This is done <strong>in the function <em>void SystemInit(void)<\/em>,<\/strong> which is called in the startup file.<\/p>\n\n\n\n<p><strong>You don\u2019t care until\u2026 when<\/strong> <strong>VTOR is not being set.<\/strong><\/p>\n\n\n\n<p>Well, in the HAL for STM32G0 there is <strong>a bug that by default does not set the vector table.<\/strong> It is specifically for the example on the STM32G031J6M6, but I also found it in the HAL core itself.<\/p>\n\n\n\n<p>Participants told me that other G0s work correctly. It\u2019s quite possible that for specific microcontrollers something is different in their examples.<\/p>\n\n\n\n<p>The bug is weird. This is what the SystemInit function looks like:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void SystemInit(void)\n{\n  \/* Configure the Vector Table location -------------------------------------*\/\n#if defined(USER_VECT_TAB_ADDRESS)\n  SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; \/* Vector Table Relocation *\/\n#endif \/* USER_VECT_TAB_ADDRESS *\/\n}<\/pre>\n\n\n\n<p>There is this clever define here: <strong>USER_VECT_TAB_ADDRESS<\/strong><\/p>\n\n\n\n<p>And unfortunately, it <strong>is not defined anywhere. This whole VTOR setting is disabled by default.<\/strong><\/p>\n\n\n\n<p>In HALs for older chips it looks a bit different. Here even that constant is named <strong>as if it were meant for user needs.<\/strong> But where is the default setting?<\/p>\n\n\n\n<p>I have a theory.<\/p>\n\n\n\n<p>They probably wanted to add a unified relocation of the interrupt vector. If we write a specific bootloader or a program completely without it, then we have the default setting.<\/p>\n\n\n\n<p>If we want to write the main program that the bootloader jumps to, then we set <strong>our own user table.<\/strong><\/p>\n\n\n\n<p>Only\u2026 it\u2019s implemented halfway. There is the user part, and <strong>the default one is missing \ud83d\ude42<\/strong> Something went wrong.<\/p>\n\n\n\n<p><strong>What will happen if VTOR is not set?<\/strong><\/p>\n\n\n\n<p>We have 2 options:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>No interrupts (mild)<\/li>\n\n\n\n<li>The program will end up in HardFault when trying to enter the interrupt handler (worse)<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Solution<\/h2>\n\n\n\n<p>What to do? I submitted a fix on GitHub: <a href=\"https:\/\/github.com\/STMicroelectronics\/STM32CubeG0\/pull\/33\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/STMicroelectronics\/STM32CubeG0\/pull\/33<\/a><\/p>\n\n\n\n<p>They accepted it, so we\u2019ll see when they fix it. What to do as of today (May 23, 2022)? <strong>There are 3 options:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><span style=\"font-weight: 400;\">Add, in the SystemInit() function, an assignment of the default base address: <strong>SCB->VTOR = <\/strong><\/span><strong>VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET;<br><\/strong>Unfortunately, this is not a user section in CubeMX, so after regenerating it the solution will disappear.<\/li>\n\n\n\n<li><span style=\"font-weight: 400;\">Add a global definition of USER_VECT_TAB_ADDRESS. Quite a good idea. You can do this in the compiler settings, or in the compile command with the -D flag.<\/span><\/li>\n\n\n\n<li>Pull my fix into the HAL. That would be best, but maybe it\u2019s worth waiting until ST makes the fix on their side \ud83d\ude42<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>As you can see, HAL can still have annoying bugs. However, I still think it is a very good tool and it\u2019s worth using.<\/p>\n\n\n\n<p>Setting VTOR will be useful when we write a bootloader. <strong>Let me know in the comments if you\u2019d like me to show how to write one.<\/strong><\/p>\n\n\n\n<p>If you would like to learn STM32 programming with me, I invite you to my course (the course content is conducted in Polish): <a href=\"https:\/\/kursstm32.pl\" target=\"_blank\" rel=\"noopener\">https:\/\/kursstm32.pl<\/a><\/p>\n\n\n\n<p>Do you have gaps or don\u2019t know the C language at all? On June 1st I\u2019ll tell you how to learn C for microcontrollers. Sign up for the free webinar (the webinar content is conducted in Polish) at <a href=\"https:\/\/cdlamikrokontrolerow.pl\/webinar\" target=\"_blank\" rel=\"noopener\">https:\/\/cdlamikrokontrolerow.pl\/webinar<\/a><\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;4168&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;0&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;0&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;0&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;0\\\/5 - (0 votes)&quot;,&quot;size&quot;:&quot;24&quot;,&quot;title&quot;:&quot;Interrupts not working on STM32G0? What is VTOR? (HAL bug)&quot;,&quot;width&quot;:&quot;0&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} - ({count} {votes})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 0px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 0px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 24px; height: 24px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 19.2px;\">\n            <span class=\"kksr-muted\"><\/span>\n    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>I haven\u2019t had much experience with the STM32G0 family yet, but my course participants have. Recently, we were solving together a problem with interrupts not working on the STM32G031J6M6 known from the STM32G0316-DISCO board. It was an interesting problem, so I decided to share it more broadly. Handling interrupts via [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3738,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[160],"tags":[175,178,176,174,177],"class_list":["post-4168","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-stm32","tag-electronics","tag-kursstm32","tag-programming","tag-stm32","tag-stm32cubemx"],"_links":{"self":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4168","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/comments?post=4168"}],"version-history":[{"count":3,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4168\/revisions"}],"predecessor-version":[{"id":4171,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/posts\/4168\/revisions\/4171"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/media\/3738"}],"wp:attachment":[{"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/media?parent=4168"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/categories?post=4168"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/msalamon.pl\/en\/wp-json\/wp\/v2\/tags?post=4168"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}