扒一扒中断为什么不能调printf?

嵌入式资讯精选 2021-08-16 11:46

前面说会写一下Modbus-RTU的实现,写了1000多字了,有兴趣的稍等一下哈。前面在一个群里看到一个朋友在一个串口接收中断里打印遇到了问题,今天聊下这个话题。

扒一扒printf

对于单片机中printf到底向哪里打印,这个不同的编译器会有不同的处理方式。比如IAR的printf,如果是在线调试,有可能通过c-spy打印到IAR的调试终端,如果已经将printf重映射到串口,那么会从指定的串口打印出去。

以IAR ARM开发环境为例,来撸一下printf背后究竟是怎么实现的:

首先写一个简单的hello world开始:

#include <stdio.h>
int main()
{
    printf("Hello world");
    return 0;
}

接着来查找一下printf的出处,在stdio.h中找到了其声明:

__EFF_NW1  __ATTRIBUTES   void perror(const char *);
__EFF_NW1  __DEPREC_PRINTF int  printf(const char *_Restrict, ...);
__EFF_NW1  __ATTRIBUTES   int  puts(const char *);
__EFF_NW1  __DEPREC_SCANF  int  scanf(const char *_Restrict, ...);
__EFF_NR1NW2 __DEPREC_PRINTF int  sprintf(char *_Restrict,                                              const char *_Restrict, ...);
__EFF_NW1NW2 __DEPREC_SCANF int  sscanf(const char *_Restrict, 

到这里好像无法再进行下去了,先看看map文件,这里只放了map的一部分:

dl7M_tln.a: [3]
    XShttio.o     60  3  9
    abort.o       6
    exit.o        4
    low_level_init.o   4
    printf.o     40
    putchar.o    32
    xfail_s.o      64        1        4
    xprintffull_nomb.o   3 618
    xprout.o   22
    -------------------------------------------------
    Total:       3 850        4       13
    
......
printf  0x00001be9   0x28  Code  Gb  printf.o [3]
putchar  0x00001c6d   0x20  Code  Gb  putchar.o [3]

看到了有一个printf.o模块被编译了,有这个文件,那么应该有源文件,试着在IAR的安装目录下找找,果然有:

.\IAR Systems\Embedded Workbench 8.0\arm\src\lib\dlib\file\printf.c

int printf(const char * _Restrict fmt, ...)
/* print formatted to stdout */
  int ans;
  va_list ap;  
  va_start(ap, fmt);
  ans = _Printf(&_Prout, (void *)1, fmt, &ap, 0);
  va_end(ap);
  return ans;
}

printf通过使用va_list/va_start/va_end,在这里进行可变参数的解析,而真正实现最终打印的函数是哪一个呢?是下面这句话在起作用:

_Printf(&_Prout, (void *)1, fmt, &ap, 0);

_Printf的原型是怎样的呢?在.\IAR Systems\Embedded Workbench 8.0\arm\src\lib\dlib\DLib.h中发现:

__ATTRIBUTES int _Printf(_PrintfPfnType *, void *, const char *, __Va_list *,int);

_PrintfPfnType这个是啥玩意?继续撸下去:

#if _DLIB_PRINTF_CHAR_BY_CHAR
  typedef void *(_PrintfPfnType)(void *, char);
#else
  typedef void *(_PrintfPfnType)(void *, const char *, _Sizet);
#endif

明白了,这个是一个函数指针,根据打印方式是否是逐字符打印,函数指针分了两种模式:逐字符模式或者缓冲区模式。

在回到printf的定义处,发现这个指针传的是_Prout。好接着扒下去,在

.\arm\src\lib\dlib\formatters\xprout.c发现了其具体的实现:

#if _DLIB_PRINTF_CHAR_BY_CHAR
void *_Prout(void *str, char c)
{
   return (putchar(c) == c ? str : 0);
}
#else
  #if _DLIB_FILE_DESCRIPTOR
  void *_Prout(void *str, const char *buf, size_t n)
  {
    return fwrite(buf, 1, n, stdout) == n ? str : 0;
  }
  #else
  void *_Prout(void *str, const char *buf, size_t n)
  {
    return __write(_LLIO_STDOUT, (unsigned char const *)buf, n) == n 
        ? str : 0;
  }
  #endif
#endif

_DLIB_PRINTF_CHAR_BY_CHAR 宏是根据IAR的DLIB配置做定义。

所以IAR编译的时候会包含DLib_Defaults.h,这里就定义了逐字符模式宏,如果要采用文件方式则需要修改配置。但是一般单片机里不会这么干。所以真正的 _Prout的实现就是这样的了:


void *_Prout(void *str, char c)
{
  return (putchar(c) == c ? str : 0);
}

这样就定位到最终实现字符打印的函数是putchar了,而putchar是在哪里声明的呢?在stdio.h中发现了它的踪迹:

__ATTRIBUTES int  putchar(int);

来了一个好像没见过的函数前缀,再继续找一下,在.\arm\inc\c\yvals.h中找到了

#define __ATTRIBUTES  __intrinsic __nounwind

这两个关键字是编译内部使用的,文档里没有说明这个是怎么使用的,但是我猜想编译器在编译时可能会检测这个函数是否用户定义了同名函数,如定义了就使用用户定义的,没定义就使用系统库。放一个空的putchar来验证一下:

#include <stdio.h>
int putchar(int c)
{
    return(c);
}

int main()
{
    printf("Hello world");
    return 0;
}

然后再看看map文件:

dl7M_tln.a: [3]
    abort.o         6
    exit.o          4
    low_level_init.o    4
    printf.o         40
    xfail_s.o       64    4
    xprintffull_nomb.o   3 618
    xprout.o        22
    ------------------------------------------------
    Total:       3 758   4
    .......
 putchar   0x00001bbd    0x2  Code  Gb  main.o [1]   

putchar使用了main.o的实现。而如果使用库实现的,从前面的map文件看到putchar.o,一找发现了putchar.c文件:

int putchar(int c)
/* put character to stdout */
     unsigned char uc = c;
     if (__write(_LLIO_STDOUT, &uc, 1) == 1)
     {
        return uc;
     }
     return EOF;
}

系统原来是调用了__write函数,在.\IAR Systems\Embedded Workbench 8.0\arm\inc\c\LowLevelIOInterface.h中找到了:

 __ATTRIBUTES size_t __write(intconst unsigned char *, size_t);

到这里不继续了,你如果再找就发现

.\8.0\arm\RTOS\SEGGER\NXP\LPC4357\Start_LPC4357_CMSIS\Setup\SEGGER_RTT_Syscalls_IAR.c

有它的实现:

size_t __write(int handle, const unsigned char * buffer, size_t size) {
  (void) handle;  /* Not used, avoid warning */
  SEGGER_RTT_Write(0, (const char*)buffer, size);
  return size;
}

其实就是各种底层具体输出的实现了,比如打印到c-spy,或者打印到串口。

比如在:

.\8.0\arm\src\flashloader\ST\FlashSTM32F10x\Flash_stm32f10xx.c

int putchar(int c)
{
    USART1->DR = c;
    while(0 == (USART1->SR & (1UL << 7)));
    return(c);
}

这就是printf重映射到串口的实现,这个是一个同步查询单字节串口输出函数。大致就上面的分析,总结成一个图就是这样:

当然这里仅仅分析了逐字符打印的串口的情况。下面回到问题本身,为什么中断里不能调用printf?

为啥ISR不能printf

首先中断里肯定不适合调用printf,那么为什么呢?就比如上面的串口实现方式,就以9600,1个起始位,1个停止位,8个数据位的常见方式为例:




你看,传输一个字节要1个毫秒,如果打印好几个字节就是好几个毫秒了,所以答案几乎就已经很清楚了,在中断函数里打印,会增加中断函数执行的时间。中断需要快进快出!比如是一个串口逐字节接收中断函数,外部的报文逐字节输入,而中断函数先打印一点日志,好几个毫秒就过去了。如果UART外设是一个单字节的接收寄存器,那完了,报文指定被冲掉了。有的UART可能有多字节FIFO,但是即便是这样,也有很大的概率会被冲掉。

这是一个中断里不能调用printf的主要原因,执行费时!

在IAR的文档里也阔以看到,如果要实现printf的重定向,需要用户实现底层的__write函数,那为啥前面又是实现的putchar呢,其实putchar最终是调用的__write函数,所以直接覆盖putchar肯定也是可以的。

另外如果编译环境配置printf不一样,这个内部实现也可能需要很多的存储空间。这对单片机而言也是不合算的。来比较一下,把printf去掉:

int main()
{
    return 0;
}

编译出来的结果是:

    152 bytes of readonly  code memory
  1024 bytes of readwrite data memory

加上后,编译出来是这样:

  7470 bytes of readonly  code memory
     34 bytes of readonly  data memory
  1037 bytes of readwrite data memory

看就这么一句printf,code区增加了近7K字节!当然如果你选择其他的printf配置,可能会小一些,比如:

不同的单片机编译器对printf的处理会不相同,具体可以查查相关文档。

不安全

这个printf内部再很多编译环境下,有可能是线程安全的。如果函数实现内部有加锁,在应用程序中调用了printf,但还没有执行完。但此时中断来了,转而执行中断,中断时是无法获取这个锁的,此时程序就挂了。

解决办法

可以自己实现一个print系统,开辟一个环形缓冲区。如果想在中断里打印一点数据,不要同步打印,先将数据打印到内存,再设置一个标志,然后再中断外面实现真正的串口输出。

  • 如果是裸机程序,只需要在主循环里检测缓冲区是否有数据,有就输出到真正的串口。
  • 如果是RTOS应用,可以开辟一个任务,将优先级设的低一点,在任务内管理这个缓冲区,如果有数据就输出到串口。需要注意的是,就如前面所说,调用接口是不能加锁的,否则就不能在中断里使用。

有了这个思路,要实现就不难了。

1.国产嵌入式操作系统,你最看好哪一个?

2.杨福宇专栏|温州特斯拉失控案EDR数据的深度解读

3.【视频上线】嵌入式实时操作系统学习之道

4.学习STM32单片机,绕不开的串口

5.RISC-V产业的现状与未来~

6.当MCU死机了,先把硬件抓过来~

免责声明:本文系网络转载,版权归原作者所有。如涉及作品版权问题,请与我们联系,我们将根据您提供的版权证明材料确认版权并支付稿酬或者删除内容。

嵌入式资讯精选 掌握最鲜资讯,尽领行业新风
评论 (0)
  • 在智能硬件设备趋向微型化的背景下,语音芯片方案厂商针对小体积设备开发了多款超小型语音芯片方案,其中WTV系列和WT2003H系列凭借其QFN封装设计、高性能与高集成度,成为微型设备语音方案的理想选择。以下从封装特性、功能优势及典型应用场景三个方面进行详细介绍。一、超小体积封装:QFN技术的核心优势WTV系列与WT2003H系列均提供QFN封装(如QFN32,尺寸为4×4mm),这种封装形式具有以下特点:体积紧凑:QFN封装通过减少引脚间距和优化内部结构,显著缩小芯片体积,适用于智能门铃、穿戴设备
    广州唯创电子 2025-04-30 09:02 242浏览
  • 在电子电路设计和调试中,晶振为电路提供稳定的时钟信号。我们可能会遇到晶振有电压,但不起振,从而导致整个电路无法正常工作的情况。今天凯擎小妹聊一下可能的原因和解决方案。1. 误区解析在硬件调试中,许多工程师在测量晶振时发现两端都有电压,例如1.6V,但没有明显的压差,第一反应可能是怀疑短路。晶振电路本质上是一个交流振荡电路。当晶振未起振时,两端会静止在一个中间电位,通常接近电源电压的一半。万用表测得的是稳定的直流电压,因此没有压差。这种情况一般是:晶振没起振,并不是短路。2. 如何判断真
    koan-xtal 2025-04-28 05:09 245浏览
  • 晶振在使用过程中可能会受到污染,导致性能下降。可是污染物是怎么进入晶振内部的?如何检测晶振内部污染物?我可不可以使用超声波清洗?今天KOAN凯擎小妹将逐一解答。1. 污染物来源a. 制造过程:生产环境不洁净或封装密封不严,可能导致灰尘和杂质进入晶振。b. 使用环境:高湿度、温度变化、化学物质和机械应力可能导致污染物渗入。c. 储存不当:不良的储存环境和不合适的包装材料可能引发化学物质迁移。建议储存湿度维持相对湿度在30%至75%的范围内,有助于避免湿度对晶振的不利影响。避免雨淋或阳光直射。d.
    koan-xtal 2025-04-28 06:11 149浏览
  • 文/Leon编辑/cc孙聪颖‍2023年,厨电行业在相对平稳的市场环境中迎来温和复苏,看似为行业增长积蓄势能。带着对市场向好的预期,2024 年初,老板电器副董事长兼总经理任富佳为企业定下双位数增长目标。然而现实与预期相悖,过去一年,这家老牌厨电企业不仅未能达成业绩目标,曾提出的“三年再造一个老板电器”愿景,也因市场下行压力面临落空风险。作为“企二代”管理者,任富佳在掌舵企业穿越市场周期的过程中,正面临着前所未有的挑战。4月29日,老板电器(002508.SZ)发布了2024年年度报告及2025
    华尔街科技眼 2025-04-30 12:40 210浏览
  • 一、智能家居的痛点与创新机遇随着城市化进程加速,现代家庭正面临两大核心挑战:情感陪伴缺失:超60%的双职工家庭存在“亲子陪伴真空期”,儿童独自居家场景增加;操作复杂度攀升:智能设备功能迭代导致用户学习成本陡增,超40%用户因操作困难放弃高阶功能。而WTR096-16S录音语音芯片方案,通过“语音交互+智能录音”双核驱动,不仅解决设备易用性问题,更构建起家庭成员间的全天候情感纽带。二、WTR096-16S方案的核心技术突破1. 高保真语音交互系统动态情绪语音库:支持8种语气模板(温柔提醒/紧急告警
    广州唯创电子 2025-04-28 09:24 191浏览
  • 你是不是也有在公共场合被偷看手机或笔电的经验呢?科技时代下,不少现代人的各式机密数据都在手机、平板或是笔电等可携式的3C产品上处理,若是经常性地需要在公共场合使用,不管是工作上的机密文件,或是重要的个人信息等,民众都有防窃防盗意识,为了避免他人窥探内容,都会选择使用「防窥保护贴片」,以防止数据外泄。现今市面上「防窥保护贴」、「防窥片」、「屏幕防窥膜」等产品就是这种目的下产物 (以下简称防窥片)!防窥片功能与常见问题解析首先,防窥片最主要的功能就是用来防止他人窥视屏幕上的隐私信息,它是利用百叶窗的
    百佳泰测试实验室 2025-04-30 13:28 331浏览
  • 在CAN总线分析软件领域,当CANoe不再是唯一选择时,虹科PCAN-Explorer 6软件成为了一个有竞争力的解决方案。在现代工业控制和汽车领域,CAN总线分析软件的重要性不言而喻。随着技术的进步和市场需求的多样化,单一的解决方案已无法满足所有用户的需求。正是在这样的背景下,虹科PCAN-Explorer 6软件以其独特的模块化设计和灵活的功能扩展,为CAN总线分析领域带来了新的选择和可能性。本文将深入探讨虹科PCAN-Explorer 6软件如何以其创新的模块化插件策略,提供定制化的功能选
    虹科汽车智能互联 2025-04-28 16:00 184浏览
  • 文/郭楚妤编辑/cc孙聪颖‍越来越多的企业开始蚕食动力电池市场,行业“去宁王化”态势逐渐明显。随着这种趋势的加强,打开新的市场对于宁德时代而言至关重要。“我们不希望被定义为电池的制造者,而是希望把自己称作新能源产业的开拓者。”4月21日,在宁德时代举行的“超级科技日”发布会上,宁德时代掌门人曾毓群如是说。随着宁德时代核心新品骁遥双核电池的发布,其搭载的“电电增程”技术也走进业界视野。除此之外,经过近3年试水,宁德时代在换电业务上重资加码。曾毓群认为换电是一个重资产、高投入、长周期的产业,涉及的利
    华尔街科技眼 2025-04-28 21:55 169浏览
  • 一、gao效冷却与控温机制‌1、‌冷媒流动设计‌采用低压液氮(或液氦)通过毛细管路导入蒸发器,蒸汽喷射至样品腔实现快速冷却,冷却效率高(室温至80K约20分钟,至4.2K约30分钟)。通过控温仪动态调节蒸发器加热功率,结合温度传感器(如PT100铂电阻或Cernox磁场不敏感传感器),实现±0.01K的高精度温度稳定性。2、‌宽温区覆盖与扩展性‌标准温区为80K-325K,通过降压选件可将下限延伸至65K(液氮模式)或4K(液氦模式)。可选配475K高温模块,满足材料在ji端温度下的性能测试需求
    锦正茂科技 2025-04-30 13:08 259浏览
  • 4月22日下午,备受瞩目的飞凌嵌入式「2025嵌入式及边缘AI技术论坛」在深圳深铁皇冠假日酒店盛大举行,此次活动邀请到了200余位嵌入式技术领域的技术专家、企业代表和工程师用户,共享嵌入式及边缘AI技术的盛宴!1、精彩纷呈的展区产品及方案展区是本场活动的第一场重头戏,从硬件产品到软件系统,从企业级应用到高校教学应用,都吸引了现场来宾的驻足观看和交流讨论。全产品矩阵展区展示了飞凌嵌入式丰富的产品线,从嵌入式板卡到工控机,从进口芯片平台到全国产平台,无不体现出飞凌嵌入式在嵌入式主控设备研发设计方面的
    飞凌嵌入式 2025-04-28 14:43 157浏览
  • 网约车,真的“饱和”了?近日,网约车市场的 “饱和” 话题再度引发热议。多地陆续发布网约车风险预警,提醒从业者谨慎入局,这背后究竟隐藏着怎样的市场现状呢?从数据来看,网约车市场的“过剩”现象已愈发明显。以东莞为例,截至2024年12月底,全市网约车数量超过5.77万辆,考取网约车驾驶员证的人数更是超过13.48万人。随着司机数量的不断攀升,订单量却未能同步增长,导致单车日均接单量和营收双双下降。2024年下半年,东莞网约出租车单车日均订单量约10.5单,而单车日均营收也不容乐
    用户1742991715177 2025-04-29 18:28 225浏览
  • 贞光科技代理品牌紫光国芯的车规级LPDDR4内存正成为智能驾驶舱的核心选择。在汽车电子国产化浪潮中,其产品以宽温域稳定工作能力、优异电磁兼容性和超长使用寿命赢得市场认可。紫光国芯不仅确保供应链安全可控,还提供专业本地技术支持。面向未来,紫光国芯正研发LPDDR5车规级产品,将以更高带宽、更低功耗支持汽车智能化发展。随着智能网联汽车的迅猛发展,智能驾驶舱作为人机交互的核心载体,对处理器和存储器的性能与可靠性提出了更高要求。在汽车电子国产化浪潮中,贞光科技代理品牌紫光国芯的车规级LPDDR4内存凭借
    贞光科技 2025-04-28 16:52 260浏览
  •  探针台的维护直接影响其测试精度与使用寿命,需结合日常清洁、环境控制、定期校准等多维度操作,具体方法如下:一、日常清洁与保养1.‌表面清洁‌l 使用无尘布或软布擦拭探针台表面,避免残留清洁剂或硬物划伤精密部件。l 探针头清洁需用非腐蚀性溶剂(如异丙醇)擦拭,检查是否弯曲或损坏。2.‌光部件维护‌l 镜头、观察窗等光学部件用镜头纸蘸取wu水jiu精从中心向外轻擦,操作时远离火源并保持通风。3.‌内部防尘‌l 使用后及时吹扫灰尘,防止污染物进入机械滑
    锦正茂科技 2025-04-28 11:45 111浏览
  • 浪潮之上:智能时代的觉醒    近日参加了一场课题的答辩,这是医疗人工智能揭榜挂帅的国家项目的地区考场,参与者众多,围绕着医疗健康的主题,八仙过海各显神通,百花齐放。   中国大地正在发生着激动人心的场景:深圳前海深港人工智能算力中心高速运转的液冷服务器,武汉马路上自动驾驶出租车穿行的智慧道路,机器人参与北京的马拉松竞赛。从中央到地方,人工智能相关政策和消息如雨后春笋般不断出台,数字中国的建设图景正在智能浪潮中徐徐展开,战略布局如同围棋
    广州铁金刚 2025-04-30 15:24 193浏览
  • 随着电子元器件的快速发展,导致各种常见的贴片电阻元器件也越来越小,给我们分辨也就变得越来越难,下面就由smt贴片加工厂_安徽英特丽就来告诉大家如何分辨的SMT贴片元器件。先来看看贴片电感和贴片电容的区分:(1)看颜色(黑色)——一般黑色都是贴片电感。贴片电容只有勇于精密设备中的贴片钽电容才是黑色的,其他普通贴片电容基本都不是黑色的。(2)看型号标码——贴片电感以L开头,贴片电容以C开头。从外形是圆形初步判断应为电感,测量两端电阻为零点几欧,则为电感。(3)检测——贴片电感一般阻值小,更没有“充放
    贴片加工小安 2025-04-29 14:59 228浏览
我要评论
0
1
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦