Implement printf() similar functions in the embedded

Most of the learning of C language starts with printf("hello world"), and the familiarity with printf is the highest. In embedded programming, a very standard way to implement the printf function is to implement putch and bind the corresponding serial port output , set the baud rate and enable the serial port. Using mircolib is more effective, but with the practice of the project, there are other requirements for use.

Embedded interface resources are relatively tight, and the general CPU also has four serial ports, often with many peripherals. If one serial port is used independently for debugging, such a waste of IO resources and cost is unbearable, so it can only be reused. The general serial port data transmission function interface is usartSend(char buf[],size_t len);//Indicates the data and length to be transmitted, this can well meet the interface requirements for communicating with peripherals, but it is very inconvenient when debugging . If I want to see the values ​​of several float variables, I can’t output them directly. The previous method is to convert the sprintf() format and output again. In this way, I need to write at least 3 lines of code for debugging. If I add debugging macros and necessary The delay waits for the sending to complete, which requires 5-6 lines of code. If you don't dislike it, you can do the same. After a year of implementation, I decided to change the method to reduce the amount of code during debugging.

The method is as follows (GNU compiler support is required, and the GNU compiler has been integrated in keil, which is very easy to use):

 (1) Use __attribute__ to extend the format attribute. For the extended syntax, you can read this article ( GNU C Extended Grammar_The Wind-like Hang Ge's Blog-CSDN Blog )

Give an example first: void LOG(const char * fmt,...) __attribute__((format(printf(1,2)));

This attribute tells the compiler to check the parameters of the LOG function according to the parameter format of the printf function. ... means variable parameters, so what if you read variable parameters and use them? Read on.

(2) Function implementation, the parameter list can be obtained by using the encapsulated macro, and 4 useful macros are provided in the header file <stdarg.h>. They are va_list, va_start, va_arg, va_end respectively.

va_list: variable type, used to create a va_list type variable to parse variable parameters. va_list args;
va_start(args, fmt): According to the address of the parameter fmt, get the address of the parameter after fmt, and save it in the args pointer variable.

The C library macro  void va_start(va_list ap, last_arg)  initializes  the ap  variable, which   is used together with the va_arg  and  va_end macros. last_arg  is the last known fixed argument passed to the function, the one before the ellipsis.

This macro must   be called before using va_arg  and  va_end .

va_arg(args,int): use va_arg macro and va_list variable to access each item in the parameter list, int means to automatically increase the length of sizeof (int), referring to other documents, it seems that only two types of int and double are supported, that is, integer They are all int, whether it is char or short, the floating-point type is double, and using float will not get the desired result.
va_end(args): Use the macro va_end to clean up the memory assigned to the va_list variable, and point to NULL.

The following is an example of traversing variable parameters of type double to implement the sum operation that returns all values. (The example of int type is well written in other posts).

void *fun01(double num, ...)
{
	int i;
	double res = 0;
	va_list v1;				//v1实际是一个字符指针,从头文件里可以找到 
	
	va_start(v1, num);		//使v1指向可变列表中第一个值,即num后的第一个参数 
	
	printf("*v = %lf\n",(double)*v1);
	
	for(i = 0; i < (int)num; i++)	//num 是为了防止下标超限 
	{
		res += va_arg(v1, typeof(num));		//该函数返回v1指向的值,并是v1向下移动一个int的距离,使其指向下一个int 
		printf("res = %lf, v1 = %p\n",res, v1); 
	} 
	va_end(v1);				//关闭v1指针,使其指向null
	return &res;
}

(3) Realize formatted output. After knowing the parameters, you can format the output. Originally, I used the sprintf function to process the following parameters, but the result was always wrong. After inquiry and reflection, I finally understood that the library provides a special function to process the variables of va_list, which is the vprintf series.

Members of the C language printf family of functions:

#include <stdio.h>

int printf(const char *format, ...); //output to standard output
int fprintf(FILE *stream, const char *format, ...); //output to file
int sprintf(char *str, const char *format, ...); //output to the string str
int snprintf(char *str, size_t size, const char *format, ...);
                                     //output to the string str according to the size of
  
the following functions It is the same as the above one-to-one correspondence, except that when the function is called, the variables corresponding to the above ... are replaced by va_list calls. Before the function is called, the ap is dynamically acquired through the va_start() macro.

#include <stdarg.h>

int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);     int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);

So there is this version:

void __attribute__((format(printf(1,2)))) my_printf(char *fmt, ...)
{
    va_list args;
    va_start(args,fmt);
    vprintf(fmt,args);
    va_end(args);
}

(4) Function embedded transplantation, the above version is almost usable, just replace vprintf with vsnprintf, and then call the embedded serial port sending function.

int vsnprintf (char * sbuf, size_t n, const char * format, va_list arg );

Parameters sbuf: character array used to cache formatted string results

Parameter n: Limit the maximum number of characters printed to the buffer sbuf to n-1, because vsnprintf also appends \0 at the end of the result. If the length of the format string is greater than n-1, the extra part is discarded. If the length of the format string is less than or equal to n-1, the string that can be formatted is completely printed to the buffer sbuf. Generally, the value passed here is the length of the sbuf buffer.

Parameter format: format limited string

Parameter arg: variable length argument list

Return: The number of characters successfully printed into sbuf, excluding the \0 appended at the end. If formatting parsing fails, a negative number is returned.

So the following version was produced.

#include "stdio.h"
#include "stdarg.h"
#include "string.h"

void __attribute__((format(printf(1,2)))) my_printf(char *fmt, ...)
{
#ifdef    __DEBUG
    char sendbuf[512]={0};
    va_list args;
    va_start(args,fmt);
    vsnprintf(sendbuf,sizeof(sendbuf),fmt,args);
    va_end(args);
    Usart(sendbuf,strlen(sendbuf));    // 调用串口发送函数,实际情况改动
    delayms(strlen(sendbuf));           // 延时确保发送结束,以9600波特率为参考
#endif
}

In the above code, __DEBUG means debug macro, and it is enough to close this macro when releasing the program. General global debugging macros are defined as shown in the figure below.

 Summary: Through the variable parameter function, the familiar printf function on the embedded system is realized, and it does not conflict with the officially released serial port transmission function. The cost is that it takes up more memory. It is OK to cancel the macro when releasing La.

I also saw variable parameter macros during the learning process, which probably looks like this.

 As long as you understand that ## is a connector, you will understand what it means.

Guess you like

Origin blog.csdn.net/weixin_41579872/article/details/128117784
Recommended