va_list,va_start,stm32串口格式化输出

va_list,va_start,stm32串口格式化输出

在stm32的串口输出中,可以将串口寄存器->DR的数据通过一下形式格式化输出;

 1 __align(8) char Usart1_TxBuff[USART1_TXBUFF_SIZE];//内存对齐  
 2 void u1_printf(char* fmt,...) 
 3 {  
 4     unsigned int i,length;   
 6     va_list ap;
 7     va_start(ap,fmt);
 8     vsprintf(Usart1_TxBuff,fmt,ap);
 9     va_end(ap);    
10     
11     length=strlen((const char*)Usart1_TxBuff);        
12     while((USART1->SR&0X40)==0);
13     for(i = 0;i < length;i ++)
14     {            
15         USART1->DR = Usart1_TxBuff[i];
16         while((USART1->SR&0X40)==0);    
17     }    
18 }

下面我们来解析为什么这样可以完成格式化输出;

首先需要理解

va_list,va_start,va_arg,va_end,vsprintf,
 

几个宏:

 C中变长实参头文件stdarg.h提供了一个数据类型va_list和三个宏(va_startva_argva_end),用它们在被调用函数不知道参数个数和类型时对可变参数表进行测试,从而为访问可变参数提供了方便且有效的方法。va_list是一个char类型的指针,当被调用函数使用一个可变参数时,它声明一个类型为va_list的变量,该变量用来指向va_arg和va_end所需信息的位置。下面给出va_list在C中的源码:

typedef char *  va_list; 

 

void va_start(va-list ap,lastfix)是一个宏,它使va_list类型变量ap指向被传递给函数的可变参数表中的第一个参数,在第一次调用va_arg和va_end之前,必须首先调用该宏。va-start的第二个参数lastfix是传递给被调用函数的最后一个固定参数的标识符

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )  
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )   //得到可变参数中第一个参数的首地址  

 

 

type va_arg(va_list ap,type)也是一个宏,其有双重目的,第一个是返回ap所指对象的值,第二个是修改参数指针ap使其增加以指向表中下一个参数。va_arg的第二个参数提供了修改参数指针所必需的信息。在第一次使用va_arg时,它返回可变参数表中的第一个参数,后续的调用都返回表中的下一个参数,下面给出va_arg在C中的源码:

#define va_arg(ap,type)    ( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) )    //将参数转换成需要的类型,并使ap指向下一个参数  

注意:type如果依次为依次为char型、char * 型、int型和float型时,在va-arg中它们的类型则应分别为int、char *、int和double.(对齐原则)

 

void va-end(va-list ap)也是一个宏,该宏用于被调用函数完成正常返回,功能就是把指针ap赋值为0,使它不指向内存的变量。下面给出va_end在C中的源码:

#define va_end(ap)      ( ap = (va_list)0 )  

  va-end必须在va-arg读完所有参数后再调用,否则会产生意想不到的后果。

然后再看上面的代码:

va_list ap;
va_start(ap,fmt);
vsprintf(Usart1_TxBuff,fmt,ap);

这部分首先定义一个char * ap,初始化为0,然后通过va_start将ap指向可变参数中的第一个参数,例如下面:

void myput(int d, char* n, ...) {
    va_list ap;
    va_start(ap, n);  
    int b = va_arg(ap, int);//每次递增八个字节
    va_end(ap);   
}

ap在经过va_start后指向了char* n后面的第一个参数,需要注意的是:

函数参数是以栈的形式存取,从右至左入栈。

先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:
void func(int x, float y, char z);   
那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。
 

然后你要输出的字符串和参数,通过vsprintf格式化,vsprintf的作用是把参数装载进buf,在vsprintf,通过使用va_list指针,不断解析字符串,从而将

可变参数全部放进buf中,例如:“%d,%d”,18,18,在buf中就直接变成了18,18,

代码最后就是简单的串口发送程序了。这个不用多说。

 

参考博客:https://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html

     https://www.cnblogs.com/chaunceyctx/p/7372794.html



 
posted @ 2020-08-11 20:19  且将新火试新茶  阅读(1765)  评论(0)    收藏  举报