APM32芯得 EP.33 | 栈回溯方法自动分析定位APM32 Hardfault错误
《APM32芯得》系列内容为用户使用APM32系列产品的经验总结,均转载自21ic论坛极海半导体专区,全文未作任何修改,未经原文作者授权禁止转载。
1. 前言
在调试代码的时候,我们时不时就会遇到 Hard Fault 错误,导致程序崩溃。
对于 Cortex-M3/M4 内核来说,通常我们都知道可以通过内核的 Hard Fault 故障状态寄存器的值来进行分析具体的原因,以及定位故障代码的地址,和分析代码调用关系。
通常的方法就是使用仿真器,然后观察进入 Hard Fault 中断时,CPU内部的寄存器,然后手动分析(单步调试、观察内存地址值、函数调用栈等),一步步定位代码的错误位置,还原函数调用的逻辑关系。
手动分析过程繁琐,我们可以使用栈回溯原理,把进入 Hard Fault 中断前一刻的CPU内部寄存器保存下来,然后自动回溯栈内容,把进入 Hard Fault 之前的函数调用关系分析出来,并且根据故障状态寄存器进行故障结果诊断。
下面是使用栈回溯方法自动分析定位 Hard Fault 错误。主要实现的功能有:
1、保存发生错误瞬间的CPU寄存器
2、自动诊断 Hard Fault 故障原因
3、自动回溯进入 Hard Fault中断之前的函数调用关系(当然要精确定位到哪个文件、哪一行需要借助gcc工具链)
对于所有 Cortex-M3/M4 内核的 MCU ,该代码都是适用的。下面以 APM32F411 为例进行分析。
2. 栈回溯分析的原理
2.1 函数调用时压栈
函数调用,本质就是一条跳转指令,对应的汇编就是 BL / BLX 指令。
对于BL指令,该指令会自动保存下一条指令的地址到LR寄存器,然后再对LR寄存器进行压栈,当函数返回时,就把LR寄存器的值,赋值给PC,这样就可以返回执行下一条指令了。
比如说,我在main函数调用了test_debug函数:
main()
{
test_debug();
}
下面是test_debug函数的反汇编代码:
可以看到,进入该函数后,首先会把lr、还有一些其他如果会在该函数用到的CPU内部寄存器进行压栈。
那么,当有多个函数调用关系时,比如 func3 -> func2 -> func1 的调用关系,栈的具体排布如下:
2.2 中断/异常处理过程压栈
函数在调用过程中,为了保存跳转到另一个函数前那一瞬间,CPU内部的寄存器值不被另一个函数破坏,会先进行压栈,当函数返回时,再把保存在栈中的值恢复回CPU内部。
对于 Cortex-M 内核的 MCU,对异常/中断的处理过程,也是类似的。CPU在跳转到异常/中断程序之前,也需要把这些不**被中断程序所改变的CPU内部寄存器的值保存到栈中。对于 ARM 架构来说,调用过程遵循 ATPCS (ARM-Thumb Produce Call Standard),ARM-Thumb过程调用标准。
该标准规定了硬件会自动保存CPU内部哪些寄存器的值,以及这些寄存器的压栈顺序。根据这份标准,R0-R3, R12, R14(SP), PC, PSR 这8个寄存器的值,是Cortex-M架构硬件决定的,跳到异常处理函数之前会自动进行压栈处理的,而且压栈顺序固定。
但是对于 R4-R11 这几个寄存器,称为 “被调用者保存的寄存器”(对于中断而言,那就是中断服务函数是被调用者),也就是说这几个寄存器是被调用的子程序需要确保在执行该函数时确保数值不会发生变化。所以这几个寄存器是不会硬件自动压栈的。如果我们想要保存R0-R11,那么就需要我们自己写代码手动压栈。
下图就是 ATPCS 标准规定的,函数调用过程中寄存器的使用情况:
那么根据这一份标准,我们要保存进入异常瞬间时,CPU内部所有寄存器的值,就只需要软件对 R4-R11 寄存器进行压栈就行,其余的寄存器是硬件会自动进行压栈的。
最终,中断/异常处理过程的压栈情况是:
我们根据异常处理的压栈情况,就可以获取到进入 Hard Fault 异常的瞬间,CPU内部的所有寄存器值。
然后再根据其他函数的压栈情况,根据函数执行完后的返回地址,就可以自动分析出函数的调用关系了。
3. 保存发生 Hard Fault 瞬间的寄存器
根据前面的中断/异常处理过程压栈分析,我们要保存发生 Hard Fault 瞬间的CPU内部所有寄存器信息,只需要获取到 MSP 的地址值,然后去读取该地址的内容即可。
3.1 修改HardFault_Handler 中断处理函数
HardFault_Handler 中断处理函数,在 .s 文件会有一个默认的弱定义汇编函数,我们需要更改该汇编函数。主要要实现的功能是:
1、软件压栈保存R4-R11、EXC_RETURN的值
2、获取到 MSP/PSP 的值(可根据异常返回值 EXC_RETURN bit2位进行判断,使用的是MSP还是PSP栈)
3、把MSP/PSP的值作为函数参数,跳转执行最终的C处理代码
具体代码如下:
IMPORT hard_fault_handler_c
EXPORT HardFault_Handler
HardFault_Handler PROC
; get sp to r0
TST lr, #0x04 ; if(!EXC_RETURN[2])
ITE EQ
MRSEQ r0, msp ; [2]=0 ==> Z=1, get fault context from handler.
MRSNE r0, psp ; [2]=1 ==> Z=0, get fault context from thread.
; softwore save r4-r11, lr
STMFD r0!, {r4 - r11} ; push r4 - r11 register
STMFD r0!, {lr} ; push exec_return register
; update sp register
TST lr, #0x04 ; if(!EXC_RETURN[2])
ITE EQ
MSREQ msp, r0 ; [2]=0 ==> Z=1, update stack pointer to MSP.
MSRNE psp, r0 ; [2]=1 ==> Z=0, update stack pointer to PSP.
; execute hard fault handler
BL hard_fault_handler_c
JumpToMyself
B JumpToMyself ; while(1)
ENDP
代码分析:
1、该汇编代码第一部分,进来就先判断使用的是MSP(主栈)还是PSP(线程栈),一般来说如果是裸机程序,使用的都是MSP。如果使用了RTOS,而且异常是发生在RTOS启动之后,那么使用的就是PSP。然后把栈指针的值赋值给R0寄存器。
2、第二部分,就是软件压栈,软件把R4-R11、EXC_RETURN的值保存到栈中。
3、第三部分。因为软件压栈过程中,栈会向下自动生长的,对于上面的代码 R0 的值就是软件压栈之后,SP指针应该要要位于的位置。所以我们需要把 R0 的值更新到MSP/PSP中。
4、做完这些之后,就执行BL指令,跳转到具体的 C 处理函数 hard_fault_handler_c 。其中R0的值,其实就是目前的SP的值。
hard_fault_handler_c 函数原型是:void hard_fault_handler_c(struct exception_info *exception_info);
最后跳转到这个函数运行,传递给这个函数的参数就是R0的值,也就是MSP的指针,然后我们根据这个地址值以及前面分析的栈的排布顺序,我们定义的结构体类型如下:
struct exception_info
{
uint32_t exc_return;
uint32_t r4;
uint32_t r5;
uint32_t r6;
uint32_t r7;
uint32_t r8;
uint32_t r9;
uint32_t r10;
uint32_t r11;
uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t r3;
uint32_t r12;
uint32_t lr;
uint32_t pc;
uint32_t psr;
};
这个结构体的成员排布顺序,就是根据中断/异常处理过程压栈的顺序定义的,然后我们又知道了这个结构体的变量又是指向MSP的,这样就可以打印出所有的CPU寄存器值了。
4. 自动诊断 Hard Fault 故障原因
4.1 故障状态寄存器
对于 Cortex-M3/M4 内核来说,内核定义了 Hard Fault 故障状态寄存器,根据这些故障状态寄存器的值,我们就可以分析出产生Hard Fault 的故障原因。下面是参考Cortex-M3/M4权威指南这本书,关于Hard Fault 故障状态寄存器的定义。
主要有可配置故障状态寄存器、硬件错误状态寄存器、调试错误状态寄存器。其中配置故障状态寄存器又划分为3类,分别占用不同的字节空间。如下图:
对于最低字节,是内存管理错误状态;第二字节是总线错误状态;最高两字节是用法错误状态。
总之,我们根据这些故障状态寄存器,就可以分析出产生 Hard Fault 的具体原因了。
4.2 寄存器的封装
根据手册,把这些寄存器的位域进行封装起来,以供我们代码调用分析。
/**
* Cortex-M3/M4 Registers for Fault Status and Address Information
*/
typedef struct hard_fault_regs {
union {
volatile uint8_t value;
struct {
volatile uint8_t IACCVIOL : 1; // Instruction access violation
volatile uint8_t DACCVIOL : 1; // Data access violation
volatile uint8_t Reserved1 : 1;
volatile uint8_t MUNSTKERR : 1; // Unstacking error
volatile uint8_t MSTKERR : 1; // Stacking error
volatile uint8_t MLSPERR : 1; // Floating point lazy stacking error
volatile uint8_t Reserved2 : 1;
volatile uint8_t MMARVALID : 1; // Indicates the MMAR is valid
} bits;
} CFSR_MFSR; // Memory Management Fault Status Register (0xE000ED28)
union {
volatile uint8_t value;
struct {
volatile uint8_t IBUSERR : 1; // Instruction access error
volatile uint8_t PRECISERR : 1; // Precise data access error
volatile uint8_t IMPREISERR : 1; // Imprecise data access error
volatile uint8_t UNSTKERR : 1; // Unstacking error
volatile uint8_t STKERR : 1; // Stacking error
volatile uint8_t LSPERR : 1; // Floating point lazy stacking error
volatile uint8_t Reserved : 1;
volatile uint8_t BFARVALID : 1; // Indicates BFAR is valid
} bits;
} CFSR_BFSR; // Bus Fault Status Register (0xE000ED29)
union {
volatile uint16_t value;
struct {
volatile uint16_t UNDEFINSTR : 1; // Attempts to execute an undefined instruction
volatile uint16_t INVSTATE : 1; // Attempts to switch to an invalid state (e.g., ARM)
volatile uint16_t INVPC : 1; // Attempts to do an exception with a bad value in the EXC_RETURN number
volatile uint16_t NOCP : 1; // Attempts to execute a coprocessor instruction
volatile uint16_t Reserved : 4;
volatile uint16_t UNALIGNED : 1; // Indicates that an unaligned access fault has taken place
volatile uint16_t DIVBYZERO0 : 1; // Indicates a divide by zero has taken place (can be set only if DIV_0_TRP is set)
} bits;
} CFSR_UFSR; // Usage Fault Status Register (0xE000ED2A)
union {
volatile uint32_t value;
struct {
volatile uint32_t Reserved1 : 1;
volatile uint32_t VECTBL : 1; // Indicates hard fault is caused by failed vector fetch
volatile uint32_t Reserved2 : 28;
volatile uint32_t FORCED : 1; // Indicates hard fault is taken because of bus fault/memory management fault/usage fault
volatile uint32_t DEBUGEVT : 1; // Indicates hard fault is triggered by debug event
} bits;
} HFSR; // Hard Fault Status Register (0xE000ED2C)
union {
volatile uint32_t value;
struct {
volatile uint32_t HALTED : 1; // The processor is halted is by debugger request (including single step)
volatile uint32_t BKPT : 1; // The debug event is caused by a breakpoint
volatile uint32_t DWTTRAP : 1; // The debug event is caused by a watchpoint
volatile uint32_t VCATCH : 1; // The debug event is caused by a vector catch
volatile uint32_t EXTERNAL : 1; // The debug event is caused by an external signal
volatile uint32_t Reserved : 27;
} bits;
} DFSR; // Debug Fault Status Register (0xE000ED30)
volatile uint32_t MMAR; // Memory Management Fault Address Register (0xE000ED34)
volatile uint32_t BFAR; // Bus Fault Manage Address Register (0xE000ED38)
volatile uint32_t AFSR; // Auxiliary Fault Status Register (0xE000ED3C), Vendor controlled
} hard_fault_regs_t;
/* hard fault registers base address */
#define HARD_FAULT_REGS((hard_fault_regs_t *) 0xE000ED28)
这些故障状态寄存器的基地址是 0xE000ED28 ,所以我们最后定义 HARD_FAULT_REGS 这个宏,指向该地址,就可以读取到全部的寄存器值了。然后调用下面的函数,自动诊断 Hard Fault 故障原因。
static void hard_fault_diagnosis(void)
{
if (HARD_FAULT_REGS->HFSR.bits.VECTBL)
{
printf("Hard fault is caused by failed vector fetch.\r\n");
}
/* bus fault/memory management fault/usage fault */
if (HARD_FAULT_REGS->HFSR.bits.FORCED)
{
/* Memory Management Fault */
if (HARD_FAULT_REGS->CFSR_MFSR.value)
{
memory_management_fault_diagnosis();
}
/* Bus Fault */
if (HARD_FAULT_REGS->CFSR_BFSR.value)
{
bus_fault_diagnosis();
}
/* Usage Fault */
if (HARD_FAULT_REGS->CFSR_UFSR.value)
{
uasge_fault_diagnosis();
}
}
/* debug fault */
if (HARD_FAULT_REGS->HFSR.bits.DEBUGEVT)
{
debug_fault_diagnosis();
}
}
5. 自动回溯错误现场函数调用关系
当发生 Hard Fault 异常时,我们还想知道进入 Hard Fault 之前的函数调用关系。
根据前面的栈回溯原理分析,只要我们知道了 SP 指针之后,我们就可以获取到整个栈的内容了,而且每个函数的栈内容,第一条都是这个函数执行完之后的返回地址。只要我们知道了这个地址值,那么我们就可以找到进入异常之前的函数调用关系了。
下面以一部分汇编代码的调用过程来说明会更加清晰明了。下图是 main 函数的反汇编代码:
上面的main函数,当想要在main函数调用 test_debg 函数时,首先会把下一条指令的地址保存到栈里面,也就是0x0800142e 这个地址值(当然,Cortex-M架构使用的是 ARM Thumb 指令集,所以实际保存的值会 + 1)。而这个地址值的前面一条指令,就是 BL 指令,即跳转到 test_debg 函数。这样我们根据BL指令,就可以找到所有的函数调用关系了。
也就是说,栈里面保存的函数执行完之后的返回地址值,是最关键所在。但是主要的问题就是我们如何在整片栈里面把这个返回地址值给挑出来?
我们从上面的汇编代码观察,这个值必定满足下面的几个条件:
1、一定是位于可执行域的地址区间。对于APM32F411就是0x08000000开头的地址
2、因为 Cortex-M架构使用的是 ARM Thumb 指令集,那么这个返回地址值,它的 bit0 位必定是1
3、这个返回地址值的前面,所保存的一条指令,它必定是一条跳转指令(BL/BLX)。也就是我们读取返回地址的前面一个地址值(返回地址 - 1 - 4)的Flash内容,它的指令码肯定是满足 BL/BLX 指令格式的。
根据上面的这几个条件,我们就可以在整片栈的内容中,挑出跳转指令的地址值了。然后根据跳转指令,我们就可以回溯函数的调用关系了。
具体代码如下:
/* analysis call stack information */
static void backtrace_call_stack_info(struct exception_info *exception_info)
{
/* Stack base and end address and length */
extern const int * STACK$Base;
extern const int * STACK$Limit;
extern const int * STACK$Length;
/* .tetx section base address and length */
extern int * Image$ER_IROM1$Base;
extern int * Image$ER_IROM1$Length;
uint32_t lr;
uint32_t pc;
uint32_t sp = (uint32_t)(exception_info + 1);
uint32_t stack_len = (uint32_t)&STACK$Length;
uint32_t code_start = (uint32_t)&Image$ER_IROM1$Base;
uint32_t code_len = (uint32_t)&Image$ER_IROM1$Length;
printf("You can use the command to get more callback information: \r\n");
printf("arm-none-eabi-addr2line -e yourself.axf -a -f ");
printf("%08x ", exception_info->pc);
printf("%08x ", exception_info->lr - 1 - 4);
for ( ; sp < (uint32_t)(exception_info + 1) + stack_len; sp += 4 )
{
/* the first value is lr register */
lr = *((uint32_t *) sp);
/* the Cortex-M using thumb instruction, so lr register bit0 must is 1 */
if ((lr & 0x01) != 0x01)
{
continue;
}
/* get lr previous a value */
pc = lr - 1 - 4;
/* use this value as the address to read flash, the value read must be a BL/BLX instruction */
if ((pc >= code_start) && (pc < code_start + code_len) && \
disassembly_ins_is_bl_blx(pc))
{
printf("%08x ", pc);
}
}
printf("\r\n");
}
上述代码,会使用到 MDK-Keil 链接器定义的一些符号,通过这些符号去获取到可执行域的代码地址和大小,还有栈的起始地址和大小。
其中最关键的部分就是for循环,它会遍历整个栈空间,然后把BL/BLX指令的地址值给找出来。
找出地址值之后,根据这个地址值然后通过反汇编文件,我们可以了解到函数的调用关系了。但是如果不想看反汇编文件,就想知道在哪个文件、哪一行调用的,就需要借助 gcc 的编译工具链其中的一个工具,arm-none-eabi-addr2line 。使用这个工具执行下面的命令,就可以打印出那个文件,哪一行调用了哪个函数了。
arm-none-eabi-addr2line -e yourself.axf -a -f xxx xxx xxx
其中 yourself.axf 文件的名字,是你自己工程编译出来的文件名(IAR平台是xxx.out格式文件)。xxx后面指的就是我们代码找出来的跳转指令的地址值。
6. 使用示例
6.1、 制造 Hard Fault 错误
我们故意插入一段制造 Hard Fault 错误的代码,然后让程序进入 Hard Fault 异常,去执行我们的 Hard Fault 分析代码。
/* test function_1 divide by 0 error */
void test_func1(void)
{
int a, b, c;
a = 10;
b = 0;
/* Divide by 0 error */
c = a / b;
/* dummy */
(void) c;
}
void test_debug(void)
{
/* SCB->CCR address 0xE000ED14 */
volatile int *SCB_CCR = (volatile int *)0xE000ED14;
/* SCB->CCR bit4 : DIV_0_TRP */
*SCB_CCR |= (1 << 4); // open divide by 0 error
test_func1();
}
在 test_func1 函数中,执行一条除0运算,这样会触发CPU的除0错误。
6.2、观察打印CPU寄存器的信息,错误诊断信息等。
执行了上述代码之后,在串口终端打印的信息如下:
6.3、使用arm-none-eabi-addr2line 工具,去获取详细的函数调用信息。
arm-none-eabi-addr2line 根据属于gcc工具链的其中一个工具,可以去官网下载这个工具就行。gun的官网我打不开,然后我是从下面的网站下载的。
https://launchpad.net/gcc-arm-embedded/5.0/5-2016-q3-update
然后,我们打开windows命令行窗口,然后输入下面命令:
arm-none-eabi-addr2line -e stack_backtrace_hardfault.axf -a -f 080015e8 080015d8 0800142a 08000278
可以看到打印出了详细的函数调用过程,而且最后调用的 test_func1 函数,是运行了该函数的第66行,就导致了 Hard Fault 异常。
注:文章作者在原帖中提供了工程文件,有需要请至原文21ic论坛下载 点击前往原文
- |
- +1 赞 0
- 收藏
- 评论 0
本文由小布丁转载自Geehy极海半导体公众号,原文标题为:APM32芯得 EP.33 | 栈回溯方法自动分析定位APM32 Hardfault错误,本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。
相关推荐
【经验】如何在JFLASH中添加极海半导体Geehy APM32系列MCU
本文主要介绍极海半导体MCU产品如何使用第三方Segger公司的JFLASH配合J-Link仿真器对指定型号MCU的Flash进行擦除、写入及读取操作的目的。
【经验】APM32F4 MCU在RT-Thread系统上添加以太网驱动和使用LwIP网络组件的方法
本文详细介绍了如何将极海半导体APM32F4 MCU在RT-Thread系统上添加以太网驱动和使用LwIP网络组件,其他APM32带有以太网控制器的MCU,在RT-Thread使用LwIP网络功能也是大同小异的,按照这个过程基本都可以把网络功能应用起来。
APM32芯得 | 基于极海APM32E103系列MCU的SPI转CAN芯片MCP2515移植测试
极海半导体APM32E103系列MCU支持CAN协议2.0A和2.0B,通信波特率最大为1Mbit/s,并且拥有双CAN接口,能适应更多的应用场合。将杜邦线按照引脚配置,接好线后仿真就能测试回环模式下收发数据了。可以看到断点打到接收部分,可以接收到CAN数据,与发送的数据一致。
tandby模式下,如何唤醒MCU APM32的RTC与WKUP?
APM32F103系列低功耗模式有三种:睡眠模式、停止模式和待机模式。通过关闭内核、时钟源、设置调压器来降低功耗。本文极海半导体解析了APM32的tandby模式下的RTC唤醒与WKUP唤醒功能如何实现。
极海半导体32位MCU-M0选型表
极海半导体的APM32系列是基于Arm® Cortex®-M0+/M3/M4内核的优质国产32位通用MCU,具有低功耗、高性能、高集成度以及快速移植等特性。凭借优异的系统性能、丰富的协处理功能以及灵活的使用体验,有助于用户缩短产品设计时间、降低开发成本、实现性能最优化。
产品型号
|
品类
|
内核
|
Frequency(MHz)
|
FLASH(KB)
|
SRAM(KB)
|
I/Os
|
Vmin(V)
|
Vmax(V)
|
GPTMR(16bit)
|
GP TMR(32bit)
|
Advanced TMR(16bit)
|
Basic TMR
|
Systick(24bit)
|
ADC 12-bit Cell
|
ADC 12-bit channels
|
DAC 12-bit Cell
|
DAC 12-bit channels
|
Analog Comparator
|
TSC (Channels)
|
SPI
|
I2S
|
I2C
|
U(S)ART
|
CAN
|
SDIO
|
USB Device
|
Package
|
对照型号
|
APM32F072V8T6
|
32位MCU
|
ARM Cortex-M0
|
48MHz
|
64KB
|
16KB
|
87
|
2V
|
3.6V
|
5
|
1
|
1
|
2
|
1
|
1
|
16
|
1
|
2
|
2
|
24
|
2
|
2
|
2
|
4
|
1
|
0
|
1
|
LQFP 100
|
-
|
选型表 - 极海半导体 立即选型
APM32芯得 EP.38 | TinyMaix赋予APM32F411 AI推理能力
TinyMaix是矽速科技开发的轻量级机器学习库,适用于微控制器,能在资源受限的MCU上运行深度学习模型。它支持多种芯片架构和模型转换,具有低内存消耗和用户友好接口。文章介绍了TinyMaix在APM32F411 MCU上的移植过程,包括源码准备、工程配置、编译器设置、解决编译错误和实现计时函数。移植后,TinyMaix能够成功运行手写数字识别、人像检测和图片分类等实例。
【应用】极海半导体MCU APM32F103系列用于HMI人机界面,可满足HMI功耗调整、蓝牙通信等需求
本文推荐采用极海半导体推出的APM32F103系列MCU作为HMI人机界面的主控,最高96MHz工作频率,FLASH 256K,SRAM 64K,支持FPU单元;资源丰富,有CRC/RTC/DMA通道,两个DMA;通信接口丰富。
【经验】极海MCU APM32F103 IAP的实现方式
拿到了一块APM32F103VC的MINI开发板,在学习了一段时间后发现其有非常丰富的外设资源,主频能达到96Mhz。最近在项目中使用到了IAP(In Application Programming)功能,特来评估一下APM32F103的IAP实现方式。
APM32芯得 | 基于APM32F411控制的一个软开关电路设计分享
本文介绍的软开关电路,并不是开关电源里面的软开关概念,而是系统供电的开关,结合MCU等控制芯片,控制系统供电的开启或关断。
极海半导体32位MCU-M3选型表
极海半导体的APM32系列是基于Arm® Cortex®-M0+/M3/M4内核的优质国产32位通用MCU,具有低功耗、高性能、高集成度以及快速移植等特性。凭借优异的系统性能、丰富的协处理功能以及灵活的使用体验,有助于用户缩短产品设计时间、降低开发成本、实现性能最优化。
产品型号
|
品类
|
内核
|
Frequency(MHz)
|
FLASH(KB)
|
SRAM(KB)
|
SDRAM
|
FPU
|
I/Os
|
Vmin(V)
|
Vmax(V)
|
GPTMR(16bit)
|
GP TMR(32bit)
|
Advanced TMR(16bit)
|
Basic TMR
|
Systick(24bit)
|
ADC 12-bit Cell
|
ADC 12-bit channels
|
DAC 12-bit Cell
|
DAC 12-bit channels
|
Analog Comparator
|
EMMC
|
SPI
|
I2S
|
I2C
|
U(S)ART
|
CAN
|
SDIO
|
Package
|
对照型号
|
APM32E103CET6
|
32位MCU
|
ARM Cortex-M3
|
120MHz
|
512KB
|
128KB
|
0
|
1
|
37
|
2V
|
3.6V
|
4
|
0
|
1
|
2
|
1
|
2
|
10
|
2
|
2
|
0
|
0
|
3
|
2
|
2
|
3
|
2
|
0
|
LQFP48
|
STM32F103RET6
|
选型表 - 极海半导体 立即选型
【选型】APM32F411 MCU的电机控制方案可实现PMSM的无感FOC双电机控制,支持三电阻、单电阻电流采样
面向电机市场,极海半导体APM32F411 双电机控制系统提供了高适用性与高性价比的单芯片控制方案,以满足高端消费电子与工业控制领域的不同需求。随着电机应用产品智能化及物联网升级,极海半导体将持续在产品、方案与支持等各方面寻求创新,为用户打造优质的应用生态环境。
APM32芯得 EP.35 | APM32F411为什么要有ISP,你知道多少?
APM32F411支持ISP启动,适合在不同编程阶段使用。ISP不占额外flash,节省成本,且适用于量产和远程维护。该芯片支持USART/I2C/SPI/USB等多种通信总线进行flash编程,使用Geehy评估软件可轻松完成程序下载与运行。
还可以这样玩?极海半导体APM32F411系列MCU与pyocd的火花
前段时间笔者学习了一下如何使用pyocd配合APM32F411VCTINY板在命令行下给它进行各种骚操作,在使用一段时间后就想着:pyocd是基于python的,那是不是也可以使用python脚本+pyocd使用起来呢?本文中极海半导体与大家分享能够自动化完成重复操作的设计经验。
【应用】国产工业级高性能MCU APM32F407VGT6用于PLC工控板,主频168MHz,通信外设丰富
某客户主要做各种工业自动控制系统装置,其中一款国产PLC工控板主控要更换成国产物料,推荐极海工业级高性能MCU APM32F407系列,主频高达168MHz、通信外设资源丰富,工作温度范围-40℃到+85℃。
基于极海半导体APM32F411工业级高适配型MCU的移动电源控制板应用方案
随着技术的发展,消费者对移动电源产品品质和体验提出了更高要求,除了储电量、安全性、便携度等基本要求,更具美观、多功能、人性化等优势的产品才能赢得消费者的青睐。采用极海APM32F411RET6工业级高适配型MCU作为主控的移动电源控制板方案,满足性能、功能、体积等综合设计需求,可帮助客户打造更具性价比和市场竞争力的移动电源产品。
电子商城
现货市场
服务
可定制显示屏的尺寸0.96”~15.6”,分辨率80*160~3840*2160,TN/IPS视角,支持RGB、MCU、SPI、MIPI、LVDS、HDMI接口,配套定制玻璃、背光、FPCA/PCBA。
最小起订量: 1000 提交需求>
可烧录IC封装SOP/MSOP/SSOP/TSOP/TSSOP/PLCC/QFP/QFN/MLP/MLF/BGA/CSP/SOT/DFN;IC包装Tray/Tube/Tape;IC厂商不限,交期1-3天。支持IC测试(FT/SLT),管装、托盘装、卷带装包装转换,IC打印标记加工。
最小起订量: 1pcs 提交需求>
登录 | 立即注册
提交评论