搜索
bottom↓
回复: 27

请教,sprintf能否被中断,大循环同时调用?

[复制链接]

出0入0汤圆

发表于 2018-12-24 23:03:48 | 显示全部楼层 |阅读模式

如题:
大循环里面调用了sprintf,把数值转换成字符串,通过串口3发送。
中断里面调用sprintf,把数值转换成字符串,通过串口1发送。

注意:存放字符串的数组是2个不同的全局变量

经过对比发现,如果大循环里面调用了sprintf,中断里面也调用了,会导致输出的字符串不正常,且有时会跑飞,应该是越界,请问这个方法要怎么解决?

具体代码如下
void U3_printf(char* fmt,...)  
{   
        u16 i,j;
        va_list ap;
        va_start(ap,fmt);
        vsprintf((char*)USART3_TX_BUF,fmt,ap);
        va_end(ap);
        i=strlen((const char*)USART3_TX_BUF);                //此次发送数据的长度
        
        for(j=0;j<i;j++)                                                        //循环发送数据
        {
                while(USART_GetFlagStatus(USART3,USART_FLAG_TXE)==RESET); //等待发送缓冲区非空写入数据
                USART_SendData(USART3,USART3_TX_BUF[j]);
        }
        while(USART_GetFlagStatus(USART3,USART_FLAG_TC)==RESET); //等待发送完成
}

阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。

出0入0汤圆

发表于 2018-12-24 23:18:08 来自手机 | 显示全部楼层
楼主应该考虑你的USART_SendData函数是否可以重入?

出0入0汤圆

 楼主| 发表于 2018-12-24 23:26:54 | 显示全部楼层
chaled 发表于 2018-12-24 23:18
楼主应该考虑你的USART_SendData函数是否可以重入?

测试的时候只有sprintf,其他的已经屏蔽。

出0入362汤圆

发表于 2018-12-24 23:49:02 | 显示全部楼层
看一下你的sprintf实现,是不是不可重入?尽量别在中断里用printf吧。

或者变通一下,其中一个用第三方的版本,比如elm-chan的xsprintf。不过他这个不支持浮点。。。

出615入1076汤圆

发表于 2018-12-25 03:41:46 来自手机 | 显示全部楼层
可以重入,但 printf 核心函數比較吃內存,重入時雙份消耗,可能會導致堆棧溢出,請排查。

出0入93汤圆

发表于 2018-12-25 06:13:52 | 显示全部楼层
中断里面调用sprintf,这个最好能别用就别用。
while(USART_GetFlagStatus(... 这个是中断的死敌,用了出现任何问题都算是正常的。这个是重点,圈起来要考的。要么他自己也开一个中断,要么换到主程序中去执行,要么使用DMA。
悄悄告诉你,要不您试试C++,这时你就会发现,用sprintf出问题的用了C++的流后了反而不出问题了,而且代码量也小了,内存占用也小了,速度也快了,效率也高了。

出870入263汤圆

发表于 2018-12-25 08:04:49 | 显示全部楼层
sprintf是线程安全的库函数,如果你使用keil的话:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入22汤圆

发表于 2018-12-25 08:32:41 来自手机 | 显示全部楼层
takashiki 发表于 2018-12-25 06:13
中断里面调用sprintf,这个最好能别用就别用。
while(USART_GetFlagStatus(... 这个是中断的死敌,用了出现 ...

cpp有这么好?

出0入93汤圆

发表于 2018-12-25 08:45:05 | 显示全部楼层

相对于C语言的printf/scanf系函数(具体来说就是printf, sprintf, wsprintf, swprintf, vsnprintf等等一大堆)来说,C++的流操作把各种判断参数的过程全部使用操作符重载代替了,从而变得非常轻量。这也是我所知道的C++和C完成相同功能时,C++唯一比C强的地方。还有些强的地方就是有点差强人意了,比如编译期计算等等,最后就是一群非得让C看起来像是面向对象而搞的四不像的。

出0入4汤圆

发表于 2018-12-25 08:56:47 | 显示全部楼层
takashiki 发表于 2018-12-25 08:45
相对于C语言的printf/scanf系函数(具体来说就是printf, sprintf, wsprintf, swprintf, vsnprintf等等一 ...

用C还是用C++

出0入93汤圆

发表于 2018-12-25 09:00:24 | 显示全部楼层
您这句话没有语境,我都不知道您的意思。是问我用C还是C++吗?在ARM编程中,C/C++是混用的,启动代码还是汇编的,其中大多数是C++的。不支持C++的编译器就没办法了。

出0入4汤圆

发表于 2018-12-25 09:07:45 | 显示全部楼层
takashiki 发表于 2018-12-25 09:00
您这句话没有语境,我都不知道您的意思。是问我用C还是C++吗?在ARM编程中,C/C++是混用的,启动代码还是汇 ...

好利害,这种程序能发个学习下吗

出0入0汤圆

发表于 2018-12-25 09:13:03 | 显示全部楼层
感觉用队列+DMA比较好

出0入93汤圆

发表于 2018-12-25 09:16:37 | 显示全部楼层
ztrx 发表于 2018-12-25 09:07
好利害,这种程序能发个学习下吗

没啥,先用C写,然后要用到C++特性时把.c后缀名改成.cpp就好了。头文件中相互调用的部分用
  1. #ifdef __cplusplus
  2. extern "C" {
  3. #endif
复制代码
之类的包围起来就行了。不用特意为了C++而去写C++,用到时弄个就可以了。

出0入0汤圆

发表于 2018-12-25 09:18:50 | 显示全部楼层
楼主试过在光在中断调用sprintf会出现乱码吗?是的话就不是是否可重入的问题。而且像楼上说的,MDK的sprintf是可重入。

你的问题更像是栈对其问题,使用stdarg.h库必须保证SP 8字节对齐,我之前也试过在中断调用sprintf时如果SP不是8字节对齐就会打出奇怪字符。

具体见下面文档的2.2.2部分
http://infocenter.arm.com/help/t ... _ABI_Advisory_1.pdf

出0入0汤圆

发表于 2018-12-25 11:36:48 | 显示全部楼层
考虑栈双节对齐问题,很多库函数默认必须满足双字对齐(这里的字是4字节)

出0入0汤圆

 楼主| 发表于 2018-12-25 23:31:26 | 显示全部楼层
ilcvm 发表于 2018-12-25 09:18
楼主试过在光在中断调用sprintf会出现乱码吗?是的话就不是是否可重入的问题。而且像楼上说的,MDK的sprint ...

感谢您的回复,根据您的提示,找到一份资料,其中描述的现象和我出现的非常相像,但是不是很清楚要怎么解决

经过测试,如果大循环sprintf,中断sprintf都是%d则不会出错,而%f,即浮点都出错



文中所说:
一.为什么要保证堆栈8字节对齐
AAPCS规则要求堆栈保持8字节对齐。如果不对齐,调用一般的函数也是没问题的。但是当调用需要严格遵守AAPCS规则的函数时可能会出错。
例如调用sprintf输出一个浮点数时,栈必须是8字节对齐的,否则结果可能会出错。

六.总结
综上所述,为了能够安全的使用严格遵守AAPCS规则的函数(比如sprintf)需要做到以下几点:
1.保证MSP在初始的时候是8字节对齐的
2.如果用到OS的话需要保证给每个任务分配的栈是保持8字节对齐的
3.如果用的是基于CM3内核的处理器需将NVIC配置控制寄存器的STKALIGN置位

https://www.cnblogs.com/sky1991/archive/2012/10/13/2722482.html

出0入0汤圆

 楼主| 发表于 2018-12-25 23:46:49 | 显示全部楼层
关于Cortex-M3核心、 SysTick定时器和NVIC的详细说明,请参考另一篇ST的文档和一篇ARM的文档:
《 STM32F10xxx Cortex-M3编程手册》和《 Cortex™-M3技术参考手册》。

SCB->CCR|=1<<9;



加上后暂未发现数据乱掉,明天还得在详细测下。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2018-12-25 23:48:52 | 显示全部楼层
takashiki 发表于 2018-12-25 08:45
相对于C语言的printf/scanf系函数(具体来说就是printf, sprintf, wsprintf, swprintf, vsnprintf等等一 ...

能否详细说下要怎么用,目前已经把串口波特率开到4.5M(F103最高速度)加上DMA,发送时间约50us,但是sprintf却用时380us。

出615入1076汤圆

发表于 2018-12-26 00:10:34 | 显示全部楼层
本帖最后由 dukelec 于 2018-12-26 03:58 编辑
W872529868 发表于 2018-12-25 23:48
能否详细说下要怎么用,目前已经把串口波特率开到4.5M(F103最高速度)加上DMA,发送时间约50us,但是sprin ...


首先 103 不支持硬件浮點,所有浮點要軟件模擬,比較影響速度,另外,sprintf 只支持 double 精度,即使你傳的是 float 型,也會先轉成 double 精度,這麼下來就更加消耗時間了。。。
建議不要在 mcu 用 printf 打印浮點,需要的話,試試:
  1. // printf float value without enable "-u _printf_float"
  2. // e.g.: printf("%d.%.2d\n", P_2F(2.14));
  3. #define P_2F(x) (int)(x), abs(lroundf(((x)-(int)(x))*100))  // "%d.%.2d"
  4. #define P_3F(x) (int)(x), abs(lroundf(((x)-(int)(x))*1000)) // "%d.%.3d"
复制代码


要想進一步節省時間,可以自己寫個簡化的 printf,只打印 16 進制可以快非常多。

如果是 gcc 編譯,可以加 -Wdouble-promotion 參數,任何默認轉換 double 的地方都會報告,方便排查。
(譬如 a 是一個 int 型變量,b 是 float 型變量,b = a / 2.3; 這裏的 2.3 默認會轉換爲 double 型精度,要寫成 2.3f 防止轉 double.)

另外,C++ 坑比較多,Linux 內核都是純 C 完成的,寫個 mcu 代碼有必要麼。。。
C++ 速度更快我表示 1 萬點懷疑。。。

出0入93汤圆

发表于 2018-12-26 07:07:47 | 显示全部楼层
dukelec 发表于 2018-12-26 00:10
首先 103 不支持硬件浮點,所有浮點要軟件模擬,比較影響速度,另外,sprintf 只支持 double 精度,即使 ...

我不需要你信,我只要你服。我给个例子,请您自己测试。另外,请认真看我6楼和9楼的文字,不要断章取义,我说的是相对于printf、scanf系函数来说,C++更快,因为无需参数判断过程。MDK 4.73下编译,STM32F103C8T6,Heap设为0,Stack设为0x200。
  1. // C版本 sprintf函数,结果:Program Size: Code=700 RO-data=252 RW-data=0 ZI-data=512
  2. int main() {
  3.     char s[50];
  4.     sprintf(s, "Hello, %s, 3 + 3 = %d\r\n", "Boy", 3 + 3);
  5. }
复制代码
  1. // C++版本 自定义了一个类,暂时只支持整数和字符串,想支持更多,自己往里面塞,什么都可以。结果:Program Size: Code=336 RO-data=252 RW-data=0 ZI-data=512  

  2. class StrPrint {
  3. private:
  4.     char* ptr;
  5. public:
  6.     inline StrPrint(char* InplaceStr) : ptr(InplaceStr) {
  7.         ptr[0] = '\0';
  8.     }
  9.    
  10.     inline StrPrint(char* InplaceStr, char* Str) : ptr(InplaceStr) {
  11.         strcpy(ptr, Str);
  12.     }
  13.    
  14.     inline StrPrint& operator = (const char* Str) {
  15.         strcpy(ptr, Str);
  16.         return *this;
  17.     }
  18.    
  19.     inline char* c_str () {
  20.         return ptr;
  21.     }
  22.    
  23.     inline void Clear() {
  24.         ptr[0] = '\0';
  25.     }
  26.    
  27.     inline int Length() const {
  28.         return strlen(ptr);
  29.     }
  30.    
  31.     inline StrPrint& operator << (const char* Str) {
  32.         strcat(ptr, Str);
  33.         return *this;
  34.     }
  35.    
  36.     // 哎,我的MDK不支持itoa,只支持atoi,只好自己搞了,杯具
  37.     inline StrPrint& operator << (int n) {
  38.         char s[16];
  39.         int i,j,sign;
  40.         if((sign=n)<0)//记录符号
  41.             n=-n;//使n成为正数
  42.         
  43.         i=0;
  44.         do {
  45.             s[i++]=n%10+'0';//取下一个数字
  46.         } while ((n/=10)>0);//删除该数字
  47.         
  48.         if(sign<0)
  49.             s[i++]='-';
  50.         
  51.         int k = Length();
  52.         for(j=i - 1; j>=0; j--)//生成的数字是逆序的,所以要逆序输出
  53.             ptr[k] = s[j], k++;
  54.         
  55.         ptr[k] = '\0';
  56.         
  57.         return *this;
  58.     }

  59. };   

  60. int main() {
  61.     char ss[50];
  62.    
  63.     StrPrint str(ss);
  64.     str << "Hello, " << "Boy" << ", 3 + 3 = " << 3 + 3;
  65. }
复制代码

效率我就不比较了,你自己实际跑一跑。我还贴个反汇编,你给指导指导:
我为什么要把"Hello, "、"Boy"、", 3 + 3 = "分开来呢,就是怕你抬杠,反汇编里你可以看到,实际上对应的都是strcat。
当然这个类还可以更高效,不用另外声明字符串,但是要用到模板,我就不贴了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出615入1076汤圆

发表于 2018-12-26 10:57:36 | 显示全部楼层
本帖最后由 dukelec 于 2018-12-26 12:10 编辑
takashiki 发表于 2018-12-26 07:07
我不需要你信,我只要你服。我给个例子,请您自己测试。另外,请认真看我6楼和9楼的文字,不要断章取义, ...


好吧,我以爲你說的是 std::cout 呢。。。

你這個相當於我上面說的 自己寫個簡化的 printf, 你測下我用於 stm8 的 極簡版本:


  1. /*
  2. * Software License Agreement (BSD License)
  3. *
  4. * Copyright (c) 2018, DUKELEC, Inc.
  5. * All rights reserved.
  6. *
  7. * Author: Duke Fong <duke@dukelec.com>
  8. */

  9. #include "arch_wrapper.h"
  10. #include "dprintf.h"


  11. void dputs(char *str)
  12. {
  13.     while (*str != '\0')
  14.         dputc(*str++);
  15. }

  16. void dputh16(uint16_t val)
  17. {
  18.     const char tlb[] = "0123456789abcdef";
  19.     int i;
  20.     for (i = 0; i < 4; i++) {
  21.         dputc(tlb[val >> 12]);
  22.         val <<= 4;
  23.     }
  24. }

  25. void dputh32(uint32_t val)
  26. {
  27.     dputh16(val >> 16);
  28.     dputh16(val & 0xffff);
  29. }

  30. void dprintf(char *fmt, ...)
  31. {
  32.     va_list va;
  33.     char ch;

  34.     va_start(va,fmt);

  35.     while ((ch = *(fmt++))) {
  36.         if (ch != '%') {
  37.             dputc(ch);
  38.             continue;
  39.         }

  40.         ch = *(fmt++);
  41.         switch (ch) {
  42.         case 'x':
  43.             dputh16(va_arg(va, uint16_t));
  44.             break;
  45.         case 'X':
  46.             dputh32(va_arg(va, uint32_t));
  47.             break;
  48. #if 0
  49.         case 's' :
  50.             dputs(va_arg(va, char *));
  51.             break;
  52.         case '%' :
  53.             dputc('%');
  54.         default:
  55.             break;
  56. #endif
  57.         }
  58.     }
  59.     va_end(va);
  60. }
复制代码




你竟然把 atoi 給 inline 了,我是服氣的。。。
雖然你這個代碼節省了參數類型判斷,但你計算一下 if、while 循環判斷的總次數,看一共節省了百分之多少。。。
(較真的話 c 也可以,寫一個 struct 對象,和兩個成員函數分別負責打印字符串和 int 就可以避免判斷了,但沒必要。。。)

還有,C++ 的 << 方式打印可讀性比較差,又臭又長,而且有時會很麻煩,譬如沒法指定打印浮點數的精度(e.g. %.3f),打印 hex 時沒法指定長度等等(e.g. %08x)。。。

另外,開優化,只調用一次,很多東西看不出來,至少調用兩次看看。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入93汤圆

发表于 2018-12-26 13:26:56 | 显示全部楼层
dukelec 发表于 2018-12-26 10:57
好吧,我以爲你說的是 std::cout 呢。。。

你這個相當於我上面說的 自己寫個簡化的 printf, 你測下我用 ...


atoi内联是写习惯了,我也不知道Keil中竟然没这个东西。本来直接内联atoi就好。果然这里您开始抬杠了。
打印精度啥的都不是事,您可以查看一下C++流中的setw之类的方法,因为C++操作符重载可以接受任意数据类型,所以可以把这个格式化的东西做成一个单独的类进行控制。
您不喜欢<<,那就改成+好了,操作符那么多,选一个您喜欢的。
标准库用在单片机上面实在是太大了,他要考虑的内容过多,所以要重写。C++有专门的字符串库<sstream>,但这个考虑得过于周全,效率很差,但不会出现sprintf之类可能出现的栈溢出问题。

出0入0汤圆

发表于 2018-12-26 14:17:54 | 显示全部楼层
armstrong 发表于 2018-12-25 08:04
sprintf是线程安全的库函数,如果你使用keil的话:

学习了。之前看的很多资料里都提到了标准库函数的所有函数都是不可重入的,为了解决这个问题我还专门重写了个sprintf就是为了多线程使用,另外能否把您这个文档的出处给发一下,感谢。

出615入1076汤圆

发表于 2018-12-26 14:36:28 | 显示全部楼层
本帖最后由 dukelec 于 2018-12-26 15:01 编辑
takashiki 发表于 2018-12-26 13:26
atoi内联是写习惯了,我也不知道Keil中竟然没这个东西。本来直接内联atoi就好。果然这里您开始抬杠了。
...


我知道可以,但是可讀性呢?開銷呢?你試試用 stream 方式代替 printf 打印試試:
  1. printf("%.1f %.2f 0x%02x\n", 1.48, 12.3, 1); // 輸出:"1.5 12.30 0x01"
复制代码


哪怕是最簡單的打印,C 和 C++ 對比:
  1. int a = 1, b = 2, c = 3;
  2. cout << "val: " << a << " " << b << " " << c << endl;
  3. printf("val: %d %d %d\n", a, b, c);
复制代码

改什麼符號都好不到哪去。

高級語言譬如 python, javascript 等等,打印基本上都用類似 printf 的方式和 template 方式,不會用 stream 的方式,譬如 python:
  1. print("val: %d %d %d" % (a, b, c))
  2. print("val: {} {} {}".format(a, b, c))
  3. print(f"val: {a} {b} {c}")
复制代码


另,擔心 sprintf 溢出可以換 snprintf 啊。

出870入263汤圆

发表于 2018-12-26 15:49:32 | 显示全部楼层
技术宅 发表于 2018-12-26 14:17
学习了。之前看的很多资料里都提到了标准库函数的所有函数都是不可重入的,为了解决这个问题我还专门重写 ...

远在天边,近在眼前!
就在keil的hlp目录下。
名叫“armlib.chm”

出0入0汤圆

 楼主| 发表于 2018-12-26 22:29:35 | 显示全部楼层
问题解决了,非常感谢各位的帮助,谢谢。


现象
大循环里面调用了sprintf,把浮点数转换成字符串,通过串口3发送。
中断里面调用sprintf,把浮点数转换成字符串,通过串口1发送。

会出打印出来的数据偶尔错乱,且其他全局变量会被异常篡改,但是只要不在中断里面用sprintf,或者打印整数,或者字符串,就不会出现上述问题。

原因
使用sprintf要遵循AAPCS规则要求堆栈保持8字节对齐
在“startup_stm32f10x_hd.s”即启动文件中有“PRESERVE8”指令,使堆栈8字节对其,所以,在大循环里面调用sprintf打印浮点数不会出错。

但是,中断可能会导致堆栈不是8字节对其,这是导致sprintf数据错乱的原因!!!

解决方法:
程序初始前化加上一行代码:
SCB->CCR|=1<<9;

这样保证中断中的栈是8字节对其。

另外,网上找到一篇文章其中说到“ CORTEX-M3 中断控制器的栈对齐调整功能(该功能在r2p0版本以后的内核中均默认开启,STKALIGN位默认为1)”,“r2p0版本”是指硬件还是软件?目前我用的程序模板都是基于3.5标准库。

附找到的相关资料
https://www.cnblogs.com/sky1991/archive/2012/10/13/2722482.html

http://www.cnblogs.com/reload/archive/2013/06/27/3159053.html



出0入0汤圆

 楼主| 发表于 2018-12-26 22:32:27 | 显示全部楼层
sprintf速度慢,我觉得终极解决方法是不要让单片机来转换,应该串口直接发送16进制日志数据,让上位机把这些数据按照格式转换成对应的数据,但是目前没有找到类似的串口工具。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-19 00:39

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表