单片机
单片机如果缺少调试功能,就好比失去了灵魂。
如今众多Cortex-M处理器能这么方便调试,在于有一项基于Arm Cortex-M处理器设备的CoreSight技术,该技术引入了强大的新调试(Debug)和跟踪(Trace)功能。
下面就来重点讲讲关于CoreSight中调试和跟踪的相关内容。
调试和跟踪功能
CoreSight两个主要功能就是调试和跟踪功能。
1.调试功能
- 运行处理器的控制,允许启动和停止程序
- 单步调试源码和汇编代码
- 在处理器运行时设置断点
- 即时读取/写入存储器内容和外设寄存器
- 编程内部和外部FLASH存储器
2.跟踪功能
- 串行线查看器(SWV)提供程序计数器(PC)采样,数据跟踪,事件跟踪和仪器跟踪信息
- 指令(ETM)跟踪直接流式传输到您的PC,从而实现历史序列的调试,软件性能分析和代码覆盖率分析
一张图了解整体内容:
JTAG
JTAG是行业标准的接口,用于下载和调试目标处理器上的程序以及许多其他功能。它提供了连接设备的简便方法,并且在所有基于Arm处理器的设备上都可用。JTAG接口可与基于Cortex-M的设备一起使用,以访问CoreSight调试功能。
1.JTAG历史
JTAG是联合测试工作组(Joint Test Action Group)的简称,是在名为标准测试访问端口和边界扫描结构的IEEE的标准1149.1的常用名称。此标准用于验证设计与测试生产出的印刷电路板功能。
1990年JTAG正式由IEEE的1149.1-1990号文档标准化,在1994年,加入了补充文档对边界扫描描述语言(BSDL)进行了说明。从那时开始,这个标准被全球的电子企业广泛采用,边界扫描几乎成为了JTAG的同义词。---引用维基百科
2.JTAG接口
JTAG的接口通常是4/5个接脚接口连到芯片上:
- TDI(测试数据输入)
- TDO(测试数据输出)
- TCK(测试时钟)
- TMS(测试模式选择)
- TRST(测试复位)可选
SWD串行线调试
SWD,Serial Wire Debug(串行线调试)模式是标准JTAG接口的替代方法,它仅使用两个引脚即可提供与JTAG相同的调试功能,而不会降低性能,并通过串行线查看器(SWV)引入了数据跟踪功能。
在JTAG引脚中包含SWD接口引脚,从而允许标准目标连接器中使用。引脚包含:
- TCLK-SWCLK(串行时钟)
- TMS-SWDIO(串行数据输入/输出)
- TDO-SWO(串行线输出-SWV使用)
关于SWV
SWV:Serial Wire Viewer,串行线查看器
基于Cortex-M3、 M4、 M7的设备能够根据所需信息或分析的类型,以多种方式提供高速数据跟踪信息。当系统处理器继续全速运行时,它通过SWO引脚传输。
可从ITM(仪器跟踪宏单元)和DWT(数据观察点和跟踪)单元获得信息,其中包括:
- PC(程序计数器)采样
- 显示CPU周期统计信息的事件计数器
- 具有定时统计信息的异常和中断执行
- 跟踪数据-用于时序分析的数据读取和写入
- 用于简单printf样式调试的ITM跟踪信息
拓展:下载调试器
之前给大家分享过《下载调试接口 SWD 和 JTAG的区别》,选择下载调试器时也可以对比一下二者差异。
支持JTAG和SWD模式的下载调试器很多,J-Link、 ST-LINK、 ULINK这些大众化的工具都支持,还有很多小众的下载调试器(比如e-Link、 GD-Link等)同样也支持SWD模式。
那么,我们该选择哪一种呢? 哪一种更香呢?
支持全面的一点的算J-Link了,但正版的价格,对于普通个人来说,有压力。
有多种版本,算下来好几千一个。
同样,正版ULINK也和J-Link一样,价格上千。当然,很多人买到的ULINK和J-Link都是盗版。
相比ULINK和J-Link而言,正版的ST-Link价格就很划算,一个正版ST-Link V2才两三百,但缺点就是只能针对STM8/32使用。
当然,ST-Link还有一个神操作:变成J-link,请参看我的文章《手把手教你 ST-Link 秒变 J-link》。
作者 | strongerHuang
微信公众号 | strongerHuang
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
正常的程序,都不会跳出main,但是,如果跳出了 main 函数,程序到底去哪儿了,你有相关这个问题吗?
一、问题提出
今天在单片机led模块定义函数中看到一个有趣的问题。提问者在进行基本的C51编程实验,编写了一个简单的C51程序如下:
#include <REGX51.H> void test(num) { switch(num) { case 1: P2_0=0; P2_1=0; break; } } void main(void) { test(1); }
程序执行完之后,可以看到实验板上的有两个LED被点亮,另外六个居然微微发亮。
如果在主程序中,增加一个无限循环:while(1); ,则电路板上的就不再会出现“微微点亮”的现象了。
#include <REGX51.H> void test(num) { switch(num) { case 1: P2_0=0; P2_1=0; break; } } void main(void) { test(1); while(1); }
上面两种情况的区别,在于第二个程序中主循环 main()函数始终没有退出,而第一个程序,main()函数退出了。似乎前面LED微微点亮 应该与主函数退出之后,单片机都干了些啥有关系。
那么就剩下一个问题:对于普通的嵌入式系统,C语言编程中main()函数退出之后,程序去哪儿了?
二、程序去哪儿了?
从上面提问者书写的代码来看,应该是一位C51的爱好者,使用的是C51的编译器,在一款C51开发板上愉快的进行实验。他一开始没有安装嵌入式程序开发的惯例 在主程序void main(void)中利用无限循环将程序控制在主程序函数中,就出现了前面实验结果中令人迷惑的情况。
“注:他是一个胆大心细的人,观察还挺仔细的。”
2.1 盘古开天辟地
对于C语言编程来说,所有的用户程序世界是从主程序main()开始的。给用户程序开天辟地的任务是由一小段盘古代码STARTUP.A51。
51单片机程序执行流程(STARTUP.A51管理Main函数的执行)
下面截取了STARTUP.A51 代码的一段,可以看到盘古在单片机RESET之后做了点准备工作(初始化全局变量、堆栈指针)之后,就直接跳转至:?C_START
NAME ?C_STARTUP ?C_C51STARTUP SEGMENT CODE ?STACK SEGMENT IDATA RSEG ?STACK DS 1 EXTRN CODE (?C_START) PUBLIC ?C_STARTUP CSEG AT 0 ?C_STARTUP: LJMP STARTUP1 RSEG ?C_C51STARTUP STARTUP1: IF IDATALEN <> 0 MOV R0,#IDATALEN - 1 CLR A IDATALOOP: MOV @R0,A DJNZ R0,IDATALOOP ENDIF IF XDATALEN <> 0 MOV DPTR,#XDATASTART MOV R7,#LOW (XDATALEN) IF (LOW (XDATALEN)) <> 0 MOV R6,#(HIGH (XDATALEN)) +1 ELSE MOV R6,#HIGH (XDATALEN) ENDIF CLR A XDATALOOP: MOVX @DPTR,A INC DPTR DJNZ R7,XDATALOOP DJNZ R6,XDATALOOP ENDIF IF PPAGEENABLE <> 0 MOV PPAGE_SFR,#PPAGE ENDIF IF PDATALEN <> 0 MOV R0,#LOW (PDATASTART) MOV R7,#LOW (PDATALEN) CLR A PDATALOOP: MOVX @R0,A INC R0 DJNZ R7,PDATALOOP ENDIF IF IBPSTACK <> 0 EXTRN DATA (?C_IBP) MOV ?C_IBP,#LOW IBPSTACKTOP ENDIF IF XBPSTACK <> 0 EXTRN DATA (?C_XBP) MOV ?C_XBP,#HIGH XBPSTACKTOP MOV ?C_XBP+1,#LOW XBPSTACKTOP ENDIF IF PBPSTACK <> 0 EXTRN DATA (?C_PBP) MOV ?C_PBP,#LOW PBPSTACKTOP ENDIF MOV SP,#?STACK-1 LJMP ?C_START END
上面的代码也被博文51单片机程序执行流程(STARTUP.A51)中进行逐步调试跟踪验证过:
2.2 世界尽头
由于进入main()函数是长跳转,所以main函数是不会正常返回到启动程序STARTUP.A51,那么程序去哪了?
在博文单片机C语言while(1)的问题中作者对于KEIL编译器和PIC的MAPLAB编译器对于main函数的最后时光进行了反汇编查看。
Keil编译器
在main函数的最后,程序增加了一下几行代码:
MOV R0, #0x7F CLR A MOV @R0, A DJNZ R0, (3) MOV SP, #0x0C LJMP main
这几条语句,前4条,是将我们单片机的内存的前128个地址清零,第5条,是定义堆栈,第6条,是将程序重新跳转到main函数的首行进行执行。
MAPLAB编译器
PIC 单片机语言程序进行跟踪,发现main() 函数最后一条语句为 reset,也就是单片机直接复位,这是 MAPLAB编译器根据 PIC 单片机特点增加的复位语句。
总结
对于嵌入式系统,如果没有运行RTOS,那么程序开发中的主函数(main())需要通过某种机制使其永远愉快的运行下去,它没有终点。如果想从main函数中退出,具体干什么是由所使用的C语言编译器决定的。
来源:TsinghuaJoking
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
简介
在过去的二十年里,智能小工具的问世促使电容式触摸表面或触摸板在人机界面(HMI)领域的应用得到了广泛普及。随着业界对触摸需求的日益增长,触摸传感技术也在不断朝着可靠、稳健的方向迈进。因此,对于消费类电子、可穿戴设备、家用电器、家庭自动化、工业、医疗和汽车领域的各种产品而言,电容式触摸界面无疑都是一项必不可少的用户需求。许多设备目前都已使用电容式触摸表面替代机械按钮。
本应用笔记将演示 AVR® DA 单片机的 2D 触摸表面实现,其中使用片上外设触摸控制器(PTC)和 Microchip 触摸库实现集成手势识别。具体将演示 AVR DA 单片机的高级低功耗触摸测量功能,以及如何使用 PTC 外设(令 CPU 处于待机休眠模式)以最大限度地降低功耗。
我们借助 Microchip AVR128DA48 Curiosity Nano、Curiosity Nano 触摸适配器、QT2 Xplained Pro 扩展板和Microchip 触摸库实现了基本的贪吃蛇演示程序来演示 2D 触摸表面手势。补充固件借助 Microchip 的 Atmel START 开发而成,QTouch®配置器(嵌入到 Atmel START 中)用于配置触摸表面参数。
详阅请点击下载《利用外设触摸控制器实现带内置表面手势识别的触摸板》
来源:Microchip微芯
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
单片机开发或多或少都会接触一些汇编代码,今天就来说说关于汇编在C中的定义和调用,以及举例说明嵌套汇编代码。
概述
有认真研究,或者说细心一点的读者应该都知道:C中定义汇编代码与编译器有关。
比如:你在core_cm4.h文件会看到如下的代码:
#if defined ( __CC_ARM ) #define __ASM __asm /*!< asm keyword for ARM Compiler */ #define __INLINE __inline /*!< inline keyword for ARM Compiler */ #define __STATIC_INLINE static __inline #elif defined ( __GNUC__ ) #define __ASM __asm /*!< asm keyword for GNU Compiler */ #define __INLINE inline /*!< inline keyword for GNU Compiler */ #define __STATIC_INLINE static inline #elif defined ( __ICCARM__ ) #define __ASM __asm /*!< asm keyword for IAR Compiler */ #define __INLINE inline /*!< inline keyword for IAR Compiler. Only available in High optimization mode! */ #define __STATIC_INLINE static inline #elif defined ( __TMS470__ ) #define __ASM __asm /*!< asm keyword for TI CCS Compiler */ #define __STATIC_INLINE static inline #elif defined ( __TASKING__ ) #define __ASM __asm /*!< asm keyword for TASKING Compiler */ #define __INLINE inline /*!< inline keyword for TASKING Compiler */ #define __STATIC_INLINE static inline #elif defined ( __CSMC__ ) #define __packed #define __ASM _asm /*!< asm keyword for COSMIC Compiler */ #define __INLINE inline /*use -pc99 on compile line !< inline keyword for COSMIC Compiler */ #define __STATIC_INLINE static inline #endif
如果你写过Keil C51,你还会发现有如下(通过预处理)嵌套汇编:
#pragma asm ; Assembler Code Here #pragma endasm
所以,你会发现,不同的编译器,汇编代码还是有差异。当然,这里主要是说C中嵌套汇编与编译器有关。
C中嵌套汇编代码
常见两种定义:
1.在C函数中定义一段汇编代码;
2.在C文件中定义一个汇编函数;
(当然,两个意思差不多,都是在C中嵌套汇编)
上面说了C中定义汇编代码与编译器有关,换句话说:不同编译器解析汇编代码的方式不同。
这里还是拿core_cm3.c来举例说明,定义一个__get_PSP函数。
在Keil MDK中定义:
__ASM uint32_t __get_PSP(void) { mrs r0, psp bx lr }
在IAR EWARM中定义:
uint32_t __get_PSP(void) { __ASM("mrs r0, psp"); __ASM("bx lr"); }
__asm(__ASM)关键字用于调用内联汇编程序,并且可在 C 或 C++ 语句合法时出现。
看到这里,推荐阅读我分享的一篇文章:单片机用汇编和C语言点灯程序的区别
举例
下面举一些常见例子。
1.FreeRTOS中portmacro.h文件下源代码:
static portFORCE_INLINE void vPortRaiseBASEPRI( void ) { uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { /* Set BASEPRI to the max syscall priority to effect a critical section. */ msr basepri, ulNewBASEPRI dsb isb } }
2.FreeRTOS中port.c文件下源代码:
__asm void xPortPendSVHandler( void ) { extern uxCriticalNesting; extern pxCurrentTCB; extern vTaskSwitchContext; PRESERVE8 mrs r0, psp isb ldr r3, =pxCurrentTCB /* Get the location of the current TCB. */ ldr r2, [r3] stmdb r0!, {r4-r11} /* Save the remaining registers. */ str r0, [r2] /* Save the new top of stack into the first member of the TCB. */ stmdb sp!, {r3, r14} mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0 dsb isb bl vTaskSwitchContext mov r0, #0 msr basepri, r0 ldmia sp!, {r3, r14} ldr r1, [r3] ldr r0, [r1] /* The first item in pxCurrentTCB is the task top of stack. */ ldmia r0!, {r4-r11} /* Pop the registers and the critical nesting count. */ msr psp, r0 isb bx r14 nop }
3.内核复位
这是之前分享过的一篇文章《STM32复位来源》中的代码:
__asm void NVIC_CoreReset_a(void) { LDR R0, =0xE000ED0C LDR R1, =0x05FA0001 STR R1, [R0] deadloop_Core B deadloop_Core }
举了这些常见例子,没有说透,但相信只要认真理解了都能明白。
来源:微信公众号 | 嵌入式专栏
作者 | strongerHuang
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
介绍
一种无OS的MCU实用软件框架,包括任务轮询管理,命令管理器、低功耗管理、环形缓冲区等实用模块。系统中广泛利用自定义段技术减少各个模块间的耦合关系,大大提供程序的可维护性。
主要功能
- 支持模块自动化管理,并提供不同优先等级初始化声明接口。
- 支持任务轮询管理,通过简单的宏声明即可实现,不需要复杂的声明调用。
- 支持低功耗管理,休眠与唤醒通知。
- 支持命令行解析,命令注册与执行。
- blink设备支持,统一管理LED、震动马达、蜂鸣器
使用说明
完整的代码可以参考工程文件,系统开发平台如下:
MCU:STM32F401RET6
IDE:IAR 7.4或者Keil MDK 4.72A
任务初始化及任务轮询管理(module)
使用此模块前需要系统提供滴答定时器,用于驱动任务轮询作业。(参考platform.c)
使用说明//定时器中断(提供系统滴答)
void SysTick_Handler(void)
{
systick_increase(SYS_TICK_INTERVAL); //增加系统节拍
}
注册初始化入口及任务(参考自key_task.c)
使用说明static void key_init(void)
{
/*do something*/
}
static void key_scan(void)
{
/*do something*/
}
module_init("key", key_init); //注册按键初始化接口
driver_register("key", key_scan, 20); //注册按键任务(20ms轮询1次)
命令管理器(cli)
适用于在线调试、参数配置等(参考使用cli_task.c),用户可以通过串口输出命令行控制设备行为、查询设备状态等功能。
命令格式
cli支持的命令行格式如下:
<cmd name> < param1> < param2> < paramn> < \r\n > <cmd name> ,< param1>, < param2>, < paramn>, < \r\n >
每行命令包含一个命令名称+命令参数(可选),命令名称及参数可以通过空格或者','进行分隔。
系统默认命令
cli系统自带了2条默认命令,分别是"?"与"help"命令,输入他们可以列出当前系统包含的命令列表,如下所示:
? - alias for 'help' help - list all command. pm - Low power control command reset - reset system sysinfo - show system infomation.
适配命令管理器
完整的例子可以参考cli_task.c.
static cli_obj_t cli; /*命令管理器对象 */ /* * @brief 命令行任务初始化 * @return none */ static void cli_task_init(void) { cli_port_t p = {tty.write, tty.read}; /*读写接口 */ cli_init(&cli, &p); /*初始化命令行对象 */ cli_enable(&cli); cli_exec_cmd(&cli,"sysinfo"); /*显示系统信息*/ } /* * @brief 命令行任务处理 * @return none */ static void cli_task_process(void) { cli_process(&cli); } module_init("cli", cli_task_init); task_register("cli", cli_task_process, 10); /*注册命令行任务*/
命令注册
以复位命令为例(参考cmd_devinfo.c):
#include "cli.h" //... /* * @brief 复位命令 */ int do_cmd_reset(struct cli_obj *o, int argc, char *argv[]) { NVIC_SystemReset(); return 0; }cmd_register("reset",do_cmd_reset, "reset system");
低功耗管理器(pm)
控制间歇运行,降低系统功耗。其基本的工作原理是通过轮询系统中各个模块是否可以允许系统进入低功耗。实际上这是一种判决机制,所有模块都具有有票否决权,即只要有一个模块不允许休眠,那么系统就不会进入休眠状态。pm模块在休眠前会统计出各个模块会返回最小允许休眠时长,并以最小休眠时长为单位进行休眠。
如何适配
使用前需要通过pm_init进行初始化适配,并提供当前系统允许的最大休眠时间,进入休眠的函数接口,基本的接口定义如下:
/*低功耗适配器 ---------------------------------------------------------*/ typedef struct { /** * @brief 系统最大休眠时长(ms) */ unsigned int max_sleep_time; /** * @brief 进入休眠状态 * @param[in] time - 期待休眠时长(ms) * @retval 实际休眠时长 * @note 休眠之后需要考虑两件事情,1个是需要定时起来给喂看门狗,否则会在休眠 * 期间发送重启.另外一件事情是需要补偿休眠时间给系统滴答时钟,否则会 * 造成时间不准。 */ unsigned int (*goto_sleep)(unsigned int time); }pm_adapter_t; void pm_init(const pm_adapter_t *adt); void pm_enable(void); void pm_disable(void); void pm_process(void);
完成的使用例子可以参考platform-lowpower.c,默认情况下是禁用低功耗功能的,读者可以去除工程中原来不带低功耗版本的platform.c,并加入platform-lowpower.c文件进行编译即可使用。
注册低功耗设备
以按键扫描为例,正常情况下,如果按键没有按下,那么系统休眠可以进入休眠状态,对按键功能是没有影响的。如果按键按下时,那么系统需要定时唤醒并轮询按键任务。
所以在一个低功耗系统下,为了不影响按键实时性需要处理好两个事情:
1)系统休眠状态下,如果有按键按下,那系统系统应立即唤醒,以便处理接下来的扫描工作。
2)如果按键按下时,系统可以进入休眠,但需要定时唤醒起来轮询按键任务。
对于第一种情况,将按键配置为边沿中断唤醒即可,以STM32F4为例(参考key_task.c),它支持外部中断唤醒功能。
/* * @brief 按键 io初始化 * PC0 -> key; * @return none */ static void key_io_init(void) { /* Enable GPIOA clock */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); gpio_conf(GPIOC, GPIO_Mode_IN, GPIO_PuPd_UP, GPIO_Pin_0); //低功耗模式下,为了能够检测到按键,配置为中断唤醒 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource0); exti_conf(EXTI_Line0, EXTI_Trigger_Falling, ENABLE); nvic_conf(EXTI0_IRQn, 0x0F, 0x0F); key_create(&key, readkey, key_event); /*创建按键*/ }
对于第二种情况,可以通过pm_dev_register来处理,当系统请求休眠时,如果此时按键按下,则返回下次唤醒时间即可,如下面的例子所示。
//参考key_task.c #include "pm.h" /* * @brief 休眠通知 */ static unsigned int key_sleep_notify(void) { return key_busy(&key) || readkey() ? 20 : 0; /* 非空闲时20ms要唤醒1次*/ } pm_dev_register("key", NULL, key_sleep_notify, NULL);
blink模块
具有闪烁特性(led, motor, buzzer)的设备(led, motor, buzzer)管理
使用步骤:
- 需要系统提供滴答时钟,blick.c中是通过get_tick()接口获取,依赖module模块
- 需要在任务中定时进行轮询
或者通过"module"模块的任务注册来实现
task_register("blink", blink_dev_process, 50); //50ms轮询1次
LED驱动
blink_dev_t led; //定义led设备 /* *@brief 红色LED控制(GPIOA.8) *@param[in] on - 亮灭控制 */ static void led_ctrl(int on) { if (on) GPIOA->ODR |= (1 << 8); else GPIOA->ODR &= ~(1 << 8); } /* *@brief led初始化程序 */ void led_init(void) { led_io_init(void); //led io初始化 blink_dev_create(&led, led_ctrl); //创建led设备 blink_dev_ctrl(&led, 50, 100, 0); //快闪(50ms亮, 100ms灭) }
按键管理模块
类似blink模块,使用之前有两个注意事项:
-
需要系统提供滴答时钟,key.c中是通过get_tick()接口获取,依赖module模块
-
需要在任务中定时进行轮询
key_t key; //定义按键管理器 /* *@brief 按键事件 *@param[in] type - 按键类型(KEY_PRESS, KEY_LONG_DOWN, KEY_LONG_UP) *@param[in] duration - 长按持续时间 */ void key_event(int type, unsigned int duration) { if (type == KEY_PRESS) { //短按 } else if (type == KEY_LONG_DOWN) { //长按 } } //读取键值(假设按键输出口为STM32 MCU PA8) int read_key(void) { return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8) == Bit_RESET; } /* *@brief 按键初始化 */ void key_init(void) { //打开GPIO 时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //配置成输入模式 gpio_conf(GPIOA, GPIO_Mode_IN, GPIO_PuPd_NOPULL, GPIO_Pin_8); //创建1个按键 key_create(&key, read_key, key_event); }
开源地址:
https://gitee.com/moluo-tech/CodeBrick
来源 | 魔罗技术
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
我以前开发过多款带有触摸按键功能的消费类电子产品,用的是现成的“触摸IC”。但后来才知道,有些芯片就是用低功耗单片机实现的。
下面,结合STM8和STM32来简单描述一下相关内容。
STM8触摸原理及开发库
先简述一下在STM8S中应用Resistor-Capacitor电阻电容(RC)采集原理。
硬件原理,可以参考官网:
使用MCU的资源:
官网提供STM8库相关开发资料:
https://www.st.com/en/embedded-software/stm8s-touch-lib.html
STM32触摸感应控制器
相比STM8而言,STM32的触摸按键功能就更高级。
在STM32中,部分MCU(F0、 F3、 L1)具有Touch sensing controller触摸感应控制器,简称TSC。
这里可以参看ST社区的一份关于文档《STM32F0—触摸按键》:
http://www.stmcu.org.cn/document/detail/index/id-213949
或者查看MCU对应的《参考手册》,可以知道触摸控制器:电荷转移的过程以及其原理。
-
利用电容储存电荷的特性;
-
电极上的待测电容Cx向采样电容Cs充电;
-
电荷转移过程中个模拟开关是硬件做在GPIO里的;
-
重复电荷转移的过程,直到采样电容Cs上的电压达到它说连GPIO的VIH门限值;
-
过程重复的次数就反映了待测电极上电容的大小;
-
电极被触摸时,重复次数N减小。
更多参考资料:
关于Touch触摸的标准外设库:
官方提供的一套关于Touch文档,值得参看:
https://www.st.com/content/ccc/resource/technical/document/user_manual/5d/e8/16/b7/a5/f5/47/bf/DM00075710.pdf/files/DM00075710.pdf/jcr:content/translations/en.DM00075710.pdf
作者:strongerHuang
来源:嵌入式专栏
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
STM32L4 单片机进入低功耗异常的一种情况
cathy 在 提交
在平常的项目开发过程中,会遇到各种各样的问题。下面分享一篇文章,是常见问题的解决思路和办法。
一、问题复现
稳定复现问题才能正确的对问题进行定位、解决以及验证。一般来说,越容易复现的问题越容易解决。
1.1 模拟复现条件
有的问题存在于特定的条件下,只需要模拟出现问题的条件即可复现。对于依赖外部输入的条件,如果条件比较复杂难以模拟可以考虑程序里预设直接进入对应状态。
1.2 提高相关任务执行频率
例如某个任务长时间运行才出现异常则可以提高该任务的执行频率。
1.3 增大测试样本量
程序长时间运行后出现异常,问题难以复现,可以搭建测试环境多套设备同时进行测试。
二、问题定位
缩小排查范围,确认引入问题的任务、函数、语句。
2.1 打印LOG
根据问题的现象,在抱有疑问的代码处增加LOG输出,以此来追踪程序执行流程以及关键变量的值,观察是否与预期相符。
2.2 在线调试
在线调试可以起到和打印LOG类似的作用,另外此方法特别适合排查程序崩溃类的BUG,当程序陷入异常中断(HardFault,看门狗中断等)的时候可以直接STOP查看call stack以及内核寄存器的值,快速定位问题点。
2.3 版本回退
使用版本管理工具时可以通过不断回退版本并测试验证来定位首次引入该问题的版本,之后可以围绕该版本增改的代码进行排查。
2.4 二分注释
二分注释即以类似二分查找法的方式注释掉部分代码,以此判断问题是否由注释掉的这部分代码引起。
具体方法为将与问题不相干的部分代码注释掉一半,看问题是否解决,未解决则注释另一半,如果解决则继续将注释范围缩小一半,以此类推逐渐缩小问题的范围。
2.5 保存内核寄存器快照
Cortex M内核陷入异常中断时会将几个内核寄存器的值压入栈中,如下图:
我们可以在陷入异常中断时将栈上的内核寄存器值写入RAM的一段复位后保留默认值的区域内,执行复位操作后再从RAM将该信息读出并分析,通过PC、LR确认当时执行的函数,通过R0-R3分析当时处理的变量是否异常,通过SP分析是否可能出现栈溢出等。
三、问题分析处理
结合问题现象以及定位的问题代码位置分析造成问题的原因。
3.1 程序继续运行
3.1.1 数值异常
3.1.1.1 软件问题
数组越界
写数组时下标超出数组长度,导致对应地址内容被修改。如下:
此类问题通常需要结合map文件进行分析,通过map文件观察被篡改变量地址附近的数组,查看对该数组的写入操作是否存在如上图所示不安全的代码,将其修改为安全的代码。
栈溢出
如上图,此类问题也需要结合map文件进行分析。假设栈从高地址往低地址增长,如果发生栈溢出,则g_val的值会被栈上的值覆盖。
出现栈溢出时要分析栈的最大使用情况,函数调用层数过多,中断服务函数内进行函数调用,函数内部申明了较大的临时变量等都有可能导致栈溢出。
解决此类问题有以下方法:
在设计阶段应该合理分配内存资源,为栈设置合适的大小;
将函数内较大的临时变量加”static”关键字转化为静态变量,或者使用malloc()动态分配,将其放到堆上;
改变函数调用方式,降低调用层数。
判断语句条件写错
判断语句的条件容易把相等运算符“==”写成赋值运算符“=”导致被判断的变量值被更改,该类错误编译期不会报错且总是返回真。
建议将要判断的变量写到运算符的右边,这样错写为赋值运算符时会在编译期报错。还可以使用一些静态代码检查工具来发现此类问题。
同步问题
例如操作队列时,出队操作执行的过程中发生中断(任务切换),并且在中断(切换后的任务)中执行入队操作则可能破坏队列结构,对于这类情况应该操作时关中断(使用互斥锁同步)。
优化问题
如上图程序,本意是等待irq中断之后不再执行foo()函数,但被编译器优化之后,实际运行过程中flg可能被装入寄存器并且每次都判断寄存器内的值而不重新从ram里读取flg的值,导致即使irq中断发生foo()也一直运行,此处需要在flg的申明前加“volatile”关键字,强制每次都从ram里获取flg的值。
3.1.1.2 硬件问题
芯片BUG
芯片本身存在BUG,在某些特定情况下给单片机返回一个错误的值,需要程序对读回的值进行判断,过滤异常值。
通信时序错误
例如电源管理芯片Isl78600,假设现在两片级联,当同时读取两片的电压采样数据时,高端芯片会以固定周期通过菊花链将数据传送到低端芯片,而低端芯片上只有一个缓存区.
如果单片机不在规定时间内将低端芯片上的数据读走那么新的数据到来时将会覆盖当前数据,导致数据丢失。此类问题需要仔细分析芯片的数据手册,严格满足芯片通信的时序要求。
3.1.2 动作异常
3.1.2.1 软件问题
设计问题
设计中存在错误或者疏漏,需要重新评审设计文档。
实现与设计不符
代码的实现与设计文档不相符需要增加单元测试覆盖所有条件分支,进行代码交叉review。
状态变量异常
例如记录状态机当前状态的变量被篡改,分析该类问题的方法同前文数值异常部分。
3.1.2.2 硬件问题
硬件失效
目标IC失效,接收控制指令后不动作,需要排查硬件。
通信异常
与目标IC通信错误,无法正确执行控制命令,需要使用示波器或逻辑分析仪去观察通信时序,分析是否发出的信号不对或者受到外部干扰。
3.2 程序崩溃
3.2.1 停止运行
3.2.1.1 软件问题
HardFault
以下情况会造成HardFault:
在外设时钟门未使能的情况下操作该外设的寄存器;
跳转函数地址越界,通常发生在函数指针被篡改,排查方法同数值异常;
解引用指针时出现对齐问题:
以小端序为例,如果我们声明了一个强制对齐的结构体如下:
此时a.val1的地址为0x00000001,如果以uint16_t类型去解引用此地址则会因为对齐问题进入HardFault,如果一定要用指针方式操作该变量则应当使用memcpy()。
中断服务函数中未清除中断标志
中断服务函数退出前不正确清除中断标志,当程序执行从中断服务函数内退出后又会立刻进入中断服务函数,表现出程序的“假死”现象。
NMI中断
调试时曾遇到SPI的MISO引脚复用NMI功能,当通过SPI连接的外设损坏时MISO被拉高,导致单片机复位后在把NMI引脚配置成SPI功能之前就直接进入NMI中断,程序挂死在NMI中断中。这种情况可以在NMI的中断服务函数内禁用NMI功能来使其退出NMI中断。
3.2.1.2 硬件问题
1)晶振未起振
2)供电电压不足
3)复位引脚拉低
3.2 .2 复位
3.2.2.1 软件问题
看门狗复位
除了喂狗超时导致的复位以外,还要注意看门狗配置的特殊要求,以Freescale KEA单片机为例,该单片机看门狗在配置时需要执行解锁序列(向其寄存器连续写入两个不同的值),该解锁序列必须在16个总线时钟内完成,超时则会引起看门狗复位。此类问题只能熟读单片机数据手册,注意类似的细节问题。
3.2.2.2 硬件问题
1)供电电压不稳
2)电源带载能力不足
四、回归测试
问题解决后需要进行回归测试,一方面确认问题是否不再复现,另一方面要确认修改不会引入其他问题。
五、经验总结
总结本次问题产生的原因及解决问题的方法,思考类似问题今后如何防范,对相同平台产品是否值得借鉴,做到举一反三,从失败中吸取经验。
作者:jozo_chen
原文地址:www.cnblogs.com/jozochen/p/8541714.html
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。