STM8 In Application Programming
IAP编写的三个要点:
- 分析STM8启动过程和C运行时环境建立
- 规划bootloader和application以及各自向量表在内存中的分布
- 如何重定位STM8中断向量表
思路:
要编写IAP程序首先需要解决的问题是程序在运行过程中,当发生异常时,如何保证程序正常的跳转到相应的异常服务函数(不论程序是运行在bootloader还是application)。
但STM8没有类似NVIC之类的中断控制器管理中断向量的地址,STM8的向量表固定在0x008000,因此在IAP中需要重定位向量表来实现(为了bootLoader和application都可以使用中断,因此,选择将向量表重定位到RAM中)
规划内存分布
针对于STM8L052C6(2K RAM/32K FLASH)
FLASH分布
[tr][/tr]
0x8000 | bootloader_start |
| bootloader |
0xBFFF | bootloader_end |
0xC000 | app_start |
| app |
0xFFFF | app_end |
在0x8000开始的前128个字节,放置着bootloader重定位过的向量表(bootloader的真正的向量表放置在另外的地方)
在0xC000开始的前128个字节,放置着application的向量表
RAM分布
[tr][/tr]
0x0000 |
|
| reload_vector |
0x0080 |
|
| .data/.bss/.textrw |
0x07FF |
|
在0x000000开始的128个字节,放置着真正的向量表。(因此,不论在bootloader还是application都要在编译阶段告知链接器保留0x00~0x80这段内存空间)
利用RAM的特性(rwx),当bootloader运行时,放置bootloader的向量表,当application运行时,放置application的向量表。
详细的分布规则,请参考config目录下的lnkstm8l052c6.icf文件
链接脚本语法请参考IARforSTM8/stm8/doc目录下的EWSTM8_DevelopmentGuide.pdf文件
重定位STM8中断向量表
由于STM8的向量表固定在0x008000~0x008080的位置,想要实现重定位向量表,则必须在固定的向量表中填入真正的向量表地址,方法如下:(参考src目录下的stm8l15x_interrupt.s文件)
/*
* The interrupt vector table.
*/
SECTION `.intvec`:CONST
define_vector MACRO
DC8 0x82
DC24 _interrupt_1
ENDM
PUBLIC __intvec
EXTERN __iar_program_start
__intvec:
DC8 0x82
DC24 __iar_program_start ;; RESET 0x8000
DC8 0x82
DC24 0x0004
DC8 0x82
DC24 0x0008
DC8 0x82
DC24 0x000C
DC8 0x82
DC24 0x0010
DC8 0x82
DC24 0x0014
DC8 0x82
DC24 0x0018
DC8 0x82
DC24 0x001C
DC8 0x82
DC24 0x0020
DC8 0x82
DC24 0x0024
DC8 0x82
DC24 0x0028
DC8 0x82
DC24 0x002C
DC8 0x82
DC24 0x0030
DC8 0x82
DC24 0x0034
DC8 0x82
DC24 0x0038
DC8 0x82
DC24 0x003C
DC8 0x82
DC24 0x0040
DC8 0x82
DC24 0x0044
DC8 0x82
DC24 0x0048
DC8 0x82
DC24 0x004C
DC8 0x82
DC24 0x0050
DC8 0x82
DC24 0x0054
DC8 0x82
DC24 0x0058
DC8 0x82
DC24 0x005C
DC8 0x82
DC24 0x0060
DC8 0x82
DC24 0x0064
DC8 0x82
DC24 0x0068
DC8 0x82
DC24 0x006C
DC8 0x82
DC24 0x0070
DC8 0x82
DC24 0x0074
DC8 0x82
DC24 0x0078
DC8 0x82
DC24 0x007C
汇编语法请参考IARforSTM8/stm8/doc目录下的EWSTM8_AssemblerReference.pdf文件
说明:
以TRAP中断为例,当发送TRAP中断时,PC指针首先指向0x008004地址(FLASH/硬件自动完成)去取指,取到的指令操作码为 82 00 00 04,这段操作码对应的指令为INT 0x000004,操作为将目标地址(0x000004)加载到PC寄存器中,效果等于PC指针指向0x000004地址(RAM)去取指,同样的,在0x000004地址处(RAM)存放着指令操作码82 00 b8 db,其中在0x00b8db地址处(FLASH)存放着真正的TRAP中断服务程序,当PC指针指向0x00b8db地址后,从而执行TRAP中断服务函数。
在RAM中放置bootloader的向量表
typedef void (INTERRUPT *interrupt_handler_t)(void);
struct interrupt_vector {
unsigned char interrupt_instruction;
unsigned char reserve;
interrupt_handler_t interrupt_handler;
};
struct interrupt_vector isr_handler[32] @".memvectab" = {
{0x82, 0x00, __iar_program_start},
{0x82, 0x00, TRAP_IRQHandler},
{0x82, 0x00, NMI_IRQHandler},
{0x82, 0x00, FLASH_IRQHandler},
{0x82, 0x00, DMA1_CHANNEL0_1_IRQHandler},
{0x82, 0x00, DMA1_CHANNEL2_3_IRQHandler},
{0x82, 0x00, RTC_CSSLSE_IRQHandler},
{0x82, 0x00, EXTIE_F_PVD_IRQHandler},
{0x82, 0x00, EXTIB_G_IRQHandler},
{0x82, 0x00, EXTID_H_IRQHandler},
{0x82, 0x00, EXTI0_IRQHandler},
{0x82, 0x00, EXTI1_IRQHandler},
{0x82, 0x00, EXTI2_IRQHandler},
{0x82, 0x00, EXTI3_IRQHandler},
{0x82, 0x00, EXTI4_IRQHandler},
{0x82, 0x00, EXTI5_IRQHandler},
{0x82, 0x00, EXTI6_IRQHandler},
{0x82, 0x00, EXTI7_IRQHandler},
{0x82, 0x00, LCD_AES_IRQHandler},
{0x82, 0x00, SWITCH_CSS_BREAK_DAC_IRQHandler},
{0x82, 0x00, ADC1_COMP_IRQHandler},
{0x82, 0x00, TIM2_UPD_OVF_TRG_BRK_USART2_TX_IRQHandler},
{0x82, 0x00, TIM2_CC_USART2_RX_IRQHandler},
{0x82, 0x00, TIM3_UPD_OVF_TRG_BRK_USART3_TX_IRQHandler},
{0x82, 0x00, TIM3_CC_USART3_RX_IRQHandler},
{0x82, 0x00, TIM1_UPD_OVF_TRG_COM_IRQHandler},
{0x82, 0x00, TIM1_CC_IRQHandler},
{0x82, 0x00, TIM4_UPD_OVF_TRG_IRQHandler},
{0x82, 0x00, SPI1_IRQHandler},
{0x82, 0x00, USART1_TX_TIM5_UPD_OVF_TRG_BRK_IRQHandler},
{0x82, 0x00, USART1_RX_TIM5_CC_IRQHandler},
{0x82, 0x00, I2C1_SPI2_IRQHandler}
};
同时,在链接脚本中,将.memvectab这个section 放置在RAM的0x000000地址处。
STM8启动过程
在上面两部分中介绍的,在RAM的0x00~0x80地址处会在不同阶段时放置bootloader和application的两张向量表,因此必然涉及到两张向量表的互相覆盖。
在STM8启动时,会执行__iar_program_start这个函数(此函数由IAR提供),这个函数负责建立C运行时环境和数据的拷贝,因此,在这个阶段会将bootloader真正的向量表从FLASH拷贝到RAM的0x00 ~ 0x80地址处(由链接脚本指定),当需要跳转到application时,运行以下代码将application的向量表拷贝到RAM的0x00 ~ 0x80地址处然后跳转:
void reload_interrupr_vectortable(void)
{
uint8_t *check = (uint8_t *)APPLICATION_ADDRESS;
if(*check == 0x82) {
uint8_t *src = (uint8_t *)APPLICATION_ADDRESS;
uint8_t *dst = (uint8_t *)VECTAB_RELOAD_START;
uint16_t cnt = sizeof(isr_handler);
/* disable interrupt, interrupt will be enable in application */
sim();
platform_peripherals_deinit();
/* reload interrupt vector table(application) from flash to memory */
for(; cnt > 0; cnt--) {
*dst++ = *src++;
}
/* reset stack pointer (lower byte - because compiler decreases SP with some bytes) */
asm("LDW X, SP ");
asm("LD A, $FF");
asm("LD XL, A ");
asm("LDW SP, X ");
asm("JPF $C000 "); /* APPLICATION_ADDRESS */
}
}
通过汇编指令asm("JPF $C000")跳转到application的起始地址处,开始运行application。
当复位时,会重新执行__iar_program_start函数,将bootloader的向量表再次拷贝到RAM的0x00~0x80地址处,覆盖application的向量表。
至此,IAP编写的难点都已解决。
附连接脚本
/
// Example ILINK command file for
// STM8 IAR C/C++ Compiler and Assembler.
//
// Copyright 2017 HinsShum.
//
// $Revision: 1623 $
//
/
/*-Symbol-*/
define symbol __intvec_start__ = 0x000000;
define symbol __region_TINY_start__ = 0x000000;
define symbol __region_TINY_end__ = 0x0000FF;
define symbol __region_ROM_start__ = 0x008000;
define symbol __region_ROM_end__ = 0x00BFFF; /* 16K bootloader flash region */
define symbol __region_RAM_start__ = 0x000000;
define symbol __region_RAM_end__ = 0x000FFF; /* 4K RAM region */
define symbol __RAMCACHE_start__ = 0x000100;
/*-Memory Regions-*/
define memory mem with size = 16M;
define region TinyData = mem:[from __region_TINY_start__ to __region_TINY_end__];
define region ROM_region = mem:[from __region_ROM_start__ to __region_ROM_end__];
define region RAM_region = mem:[from __region_RAM_start__ to __region_RAM_end__];
/
define block CSTACK with size = _CSTACK_SIZE, alignment = 1 {};
define block HEAP with size = _HEAP_SIZE, alignment = 1 {};
define block INTVEC with size = 0x80, alignment = 4 { ro section .intvec };
define block INTVECMEM with size = 0x80, alignment = 4 { rw section .memvectab };
define block RAMCACHE with size = 0x40, alignment = 1 {};
define block MD5API with size = 0x06, alignment = 2 { ro section .md5_api };
initialize by copy { rw section .*.textrw, rw data, zi };
do not initialize { rw section vregs, rw section .noinit };
// Keep section
keep { section .memvectab };
keep { block RAMCACHE };
keep { section .md5_api };
// Placement
place at start of ROM_region { block INTVEC };
place in ROM_region { ro code, ro data }; // includes ro code and data
place at end of ROM_region { block MD5API }; // provide md5 api to application
place at address mem: __intvec_start__ { block INTVECMEM };
place in TinyData { rw section .vregs };
place at address mem: __RAMCACHE_start__ { block RAMCACHE };
place in RAM_region { rw code, rw data, rw section .*.textrw };
place in RAM_region { zi };
place in RAM_region { block HEAP };
place at end of RAM_region { block CSTACK };
/
STM8 In Application Programming
IAP编写的三个要点:
- 分析STM8启动过程和C运行时环境建立
- 规划bootloader和application以及各自向量表在内存中的分布
- 如何重定位STM8中断向量表
思路:
要编写IAP程序首先需要解决的问题是程序在运行过程中,当发生异常时,如何保证程序正常的跳转到相应的异常服务函数(不论程序是运行在bootloader还是application)。
但STM8没有类似NVIC之类的中断控制器管理中断向量的地址,STM8的向量表固定在0x008000,因此在IAP中需要重定位向量表来实现(为了bootLoader和application都可以使用中断,因此,选择将向量表重定位到RAM中)
规划内存分布
针对于STM8L052C6(2K RAM/32K FLASH)
FLASH分布
[tr][/tr]
0x8000 | bootloader_start |
| bootloader |
0xBFFF | bootloader_end |
0xC000 | app_start |
| app |
0xFFFF | app_end |
在0x8000开始的前128个字节,放置着bootloader重定位过的向量表(bootloader的真正的向量表放置在另外的地方)
在0xC000开始的前128个字节,放置着application的向量表
RAM分布
[tr][/tr]
0x0000 |
|
| reload_vector |
0x0080 |
|
| .data/.bss/.textrw |
0x07FF |
|
在0x000000开始的128个字节,放置着真正的向量表。(因此,不论在bootloader还是application都要在编译阶段告知链接器保留0x00~0x80这段内存空间)
利用RAM的特性(rwx),当bootloader运行时,放置bootloader的向量表,当application运行时,放置application的向量表。
详细的分布规则,请参考config目录下的lnkstm8l052c6.icf文件
链接脚本语法请参考IARforSTM8/stm8/doc目录下的EWSTM8_DevelopmentGuide.pdf文件
重定位STM8中断向量表
由于STM8的向量表固定在0x008000~0x008080的位置,想要实现重定位向量表,则必须在固定的向量表中填入真正的向量表地址,方法如下:(参考src目录下的stm8l15x_interrupt.s文件)
/*
* The interrupt vector table.
*/
SECTION `.intvec`:CONST
define_vector MACRO
DC8 0x82
DC24 _interrupt_1
ENDM
PUBLIC __intvec
EXTERN __iar_program_start
__intvec:
DC8 0x82
DC24 __iar_program_start ;; RESET 0x8000
DC8 0x82
DC24 0x0004
DC8 0x82
DC24 0x0008
DC8 0x82
DC24 0x000C
DC8 0x82
DC24 0x0010
DC8 0x82
DC24 0x0014
DC8 0x82
DC24 0x0018
DC8 0x82
DC24 0x001C
DC8 0x82
DC24 0x0020
DC8 0x82
DC24 0x0024
DC8 0x82
DC24 0x0028
DC8 0x82
DC24 0x002C
DC8 0x82
DC24 0x0030
DC8 0x82
DC24 0x0034
DC8 0x82
DC24 0x0038
DC8 0x82
DC24 0x003C
DC8 0x82
DC24 0x0040
DC8 0x82
DC24 0x0044
DC8 0x82
DC24 0x0048
DC8 0x82
DC24 0x004C
DC8 0x82
DC24 0x0050
DC8 0x82
DC24 0x0054
DC8 0x82
DC24 0x0058
DC8 0x82
DC24 0x005C
DC8 0x82
DC24 0x0060
DC8 0x82
DC24 0x0064
DC8 0x82
DC24 0x0068
DC8 0x82
DC24 0x006C
DC8 0x82
DC24 0x0070
DC8 0x82
DC24 0x0074
DC8 0x82
DC24 0x0078
DC8 0x82
DC24 0x007C
汇编语法请参考IARforSTM8/stm8/doc目录下的EWSTM8_AssemblerReference.pdf文件
说明:
以TRAP中断为例,当发送TRAP中断时,PC指针首先指向0x008004地址(FLASH/硬件自动完成)去取指,取到的指令操作码为 82 00 00 04,这段操作码对应的指令为INT 0x000004,操作为将目标地址(0x000004)加载到PC寄存器中,效果等于PC指针指向0x000004地址(RAM)去取指,同样的,在0x000004地址处(RAM)存放着指令操作码82 00 b8 db,其中在0x00b8db地址处(FLASH)存放着真正的TRAP中断服务程序,当PC指针指向0x00b8db地址后,从而执行TRAP中断服务函数。
在RAM中放置bootloader的向量表
typedef void (INTERRUPT *interrupt_handler_t)(void);
struct interrupt_vector {
unsigned char interrupt_instruction;
unsigned char reserve;
interrupt_handler_t interrupt_handler;
};
struct interrupt_vector isr_handler[32] @".memvectab" = {
{0x82, 0x00, __iar_program_start},
{0x82, 0x00, TRAP_IRQHandler},
{0x82, 0x00, NMI_IRQHandler},
{0x82, 0x00, FLASH_IRQHandler},
{0x82, 0x00, DMA1_CHANNEL0_1_IRQHandler},
{0x82, 0x00, DMA1_CHANNEL2_3_IRQHandler},
{0x82, 0x00, RTC_CSSLSE_IRQHandler},
{0x82, 0x00, EXTIE_F_PVD_IRQHandler},
{0x82, 0x00, EXTIB_G_IRQHandler},
{0x82, 0x00, EXTID_H_IRQHandler},
{0x82, 0x00, EXTI0_IRQHandler},
{0x82, 0x00, EXTI1_IRQHandler},
{0x82, 0x00, EXTI2_IRQHandler},
{0x82, 0x00, EXTI3_IRQHandler},
{0x82, 0x00, EXTI4_IRQHandler},
{0x82, 0x00, EXTI5_IRQHandler},
{0x82, 0x00, EXTI6_IRQHandler},
{0x82, 0x00, EXTI7_IRQHandler},
{0x82, 0x00, LCD_AES_IRQHandler},
{0x82, 0x00, SWITCH_CSS_BREAK_DAC_IRQHandler},
{0x82, 0x00, ADC1_COMP_IRQHandler},
{0x82, 0x00, TIM2_UPD_OVF_TRG_BRK_USART2_TX_IRQHandler},
{0x82, 0x00, TIM2_CC_USART2_RX_IRQHandler},
{0x82, 0x00, TIM3_UPD_OVF_TRG_BRK_USART3_TX_IRQHandler},
{0x82, 0x00, TIM3_CC_USART3_RX_IRQHandler},
{0x82, 0x00, TIM1_UPD_OVF_TRG_COM_IRQHandler},
{0x82, 0x00, TIM1_CC_IRQHandler},
{0x82, 0x00, TIM4_UPD_OVF_TRG_IRQHandler},
{0x82, 0x00, SPI1_IRQHandler},
{0x82, 0x00, USART1_TX_TIM5_UPD_OVF_TRG_BRK_IRQHandler},
{0x82, 0x00, USART1_RX_TIM5_CC_IRQHandler},
{0x82, 0x00, I2C1_SPI2_IRQHandler}
};
同时,在链接脚本中,将.memvectab这个section 放置在RAM的0x000000地址处。
STM8启动过程
在上面两部分中介绍的,在RAM的0x00~0x80地址处会在不同阶段时放置bootloader和application的两张向量表,因此必然涉及到两张向量表的互相覆盖。
在STM8启动时,会执行__iar_program_start这个函数(此函数由IAR提供),这个函数负责建立C运行时环境和数据的拷贝,因此,在这个阶段会将bootloader真正的向量表从FLASH拷贝到RAM的0x00 ~ 0x80地址处(由链接脚本指定),当需要跳转到application时,运行以下代码将application的向量表拷贝到RAM的0x00 ~ 0x80地址处然后跳转:
void reload_interrupr_vectortable(void)
{
uint8_t *check = (uint8_t *)APPLICATION_ADDRESS;
if(*check == 0x82) {
uint8_t *src = (uint8_t *)APPLICATION_ADDRESS;
uint8_t *dst = (uint8_t *)VECTAB_RELOAD_START;
uint16_t cnt = sizeof(isr_handler);
/* disable interrupt, interrupt will be enable in application */
sim();
platform_peripherals_deinit();
/* reload interrupt vector table(application) from flash to memory */
for(; cnt > 0; cnt--) {
*dst++ = *src++;
}
/* reset stack pointer (lower byte - because compiler decreases SP with some bytes) */
asm("LDW X, SP ");
asm("LD A, $FF");
asm("LD XL, A ");
asm("LDW SP, X ");
asm("JPF $C000 "); /* APPLICATION_ADDRESS */
}
}
通过汇编指令asm("JPF $C000")跳转到application的起始地址处,开始运行application。
当复位时,会重新执行__iar_program_start函数,将bootloader的向量表再次拷贝到RAM的0x00~0x80地址处,覆盖application的向量表。
至此,IAP编写的难点都已解决。
附连接脚本
/
// Example ILINK command file for
// STM8 IAR C/C++ Compiler and Assembler.
//
// Copyright 2017 HinsShum.
//
// $Revision: 1623 $
//
/
/*-Symbol-*/
define symbol __intvec_start__ = 0x000000;
define symbol __region_TINY_start__ = 0x000000;
define symbol __region_TINY_end__ = 0x0000FF;
define symbol __region_ROM_start__ = 0x008000;
define symbol __region_ROM_end__ = 0x00BFFF; /* 16K bootloader flash region */
define symbol __region_RAM_start__ = 0x000000;
define symbol __region_RAM_end__ = 0x000FFF; /* 4K RAM region */
define symbol __RAMCACHE_start__ = 0x000100;
/*-Memory Regions-*/
define memory mem with size = 16M;
define region TinyData = mem:[from __region_TINY_start__ to __region_TINY_end__];
define region ROM_region = mem:[from __region_ROM_start__ to __region_ROM_end__];
define region RAM_region = mem:[from __region_RAM_start__ to __region_RAM_end__];
/
define block CSTACK with size = _CSTACK_SIZE, alignment = 1 {};
define block HEAP with size = _HEAP_SIZE, alignment = 1 {};
define block INTVEC with size = 0x80, alignment = 4 { ro section .intvec };
define block INTVECMEM with size = 0x80, alignment = 4 { rw section .memvectab };
define block RAMCACHE with size = 0x40, alignment = 1 {};
define block MD5API with size = 0x06, alignment = 2 { ro section .md5_api };
initialize by copy { rw section .*.textrw, rw data, zi };
do not initialize { rw section vregs, rw section .noinit };
// Keep section
keep { section .memvectab };
keep { block RAMCACHE };
keep { section .md5_api };
// Placement
place at start of ROM_region { block INTVEC };
place in ROM_region { ro code, ro data }; // includes ro code and data
place at end of ROM_region { block MD5API }; // provide md5 api to application
place at address mem: __intvec_start__ { block INTVECMEM };
place in TinyData { rw section .vregs };
place at address mem: __RAMCACHE_start__ { block RAMCACHE };
place in RAM_region { rw code, rw data, rw section .*.textrw };
place in RAM_region { zi };
place in RAM_region { block HEAP };
place at end of RAM_region { block CSTACK };
/
举报