STM32 Uart 实现printf函数

        在《STM32 Uart 接收变长数据》的结尾,我们觉得每次使用这样的形式来输出信息感觉好麻烦,也不方便调试。

    HAL_UART_Transmit(huart, &RxLenHi, 1, 1000); // 发送长度高位

    HAL_UART_Transmit(huart, &RxLenlo, 1, 1000); // 发送长度低位

    HAL_UART_Transmit(huart, uart4Rx, uart4RxLength, 1000); // 发送接收到的数据

        所以我们就想想办法,看可不可以像标准C语言一样,使用printf往控制台打印信息。

        在这一篇,我们提供了三种方法。

        方法1:简单粗暴,直接写三个函数,一个负责输出字符串,名myPrintfChar;一个负责输出十进制数,名myPrintfDec;一个输出十六进制数,名myPrintfHex;

void myPrintfChar(uint8_t* buf)
{
	uint8_t * pDebBuf;
	uint16_t debugLen = 0;

	pDebBuf = debugBuffer;

	if(buf == NULL)
		return;

	// Put string to debugBuffer;
	while(1)
	{
		if(*buf=='\0'||debugLen>=MAX_DEBBUF)
		{
			break;
		}

		*pDebBuf = *buf;
		pDebBuf++;
		buf++;
		debugLen++;
	}

	// Output the debugBuffer to uart;
	if(debugLen)
	{
		HAL_UART_Transmit(printfUart, debugBuffer, debugLen, 1000);
	}
}

void myPrintfHex(uint32_t num)
{
	uint16_t debugLen = 0;

	// Translate num to DEC string;
	debugLen = itoa(num, debugBuffer, 16);

	// Output the string to uart
	if(debugLen)
	{
		HAL_UART_Transmit(printfUart, debugBuffer, debugLen, 1000);
	}
}

void myPrintfDec(uint32_t num)
{
	uint16_t debugLen = 0;

	// Translate num to HEX string, ignore 0x;
	debugLen = itoa(num, debugBuffer, 10);

	// Output the string to uart
	if(debugLen)
	{
		HAL_UART_Transmit(printfUart, debugBuffer, debugLen, 1000);
	}
}

        里面有个函数,itoa,作用是把数字转成字符串,参数有三个,1.数字;2.字符串地址;3.控制,10就是十进制,16就是16进制,返回值是字符串的长度。函数的实现如下:

static uint8_t itoa(uint32_t num, uint8_t *str, uint8_t format)
{
	uint8_t i,j;
	uint8_t strLen = 0;

	switch (format){
		case 10:{
			i = 0;
			do{
				str[i] = num%10+'0';
				i++;
				num/=10;
			}while(num>0);

			strLen = i;

			// Exchange Str abcdef... to ...fedcba;
			if(strLen>=2){
				j = 0; i--;
				do{
					str[i] = str[i]^str[j];
					str[j] = str[i]^str[j];
					str[i] = str[i]^str[j];
					j++; i--;
				}while(i/2);
			}
		}
			break;

		case 16:{
			i = 0;
			do{
				str[i] =  ((num&0xf)>=10)?((num&0xf)%10+'A'):((num&0xf)+'0');
				i++;
				num>>=4;
			}while(num>0);
			
			strLen = i;

			// Exchange Str abcdef... to ...fedcba;
			if(strLen>=2){
				j = 0; i--;
				do{
					str[i] = str[i]^str[j];
					str[j] = str[i]^str[j];
					str[i] = str[i]^str[j];
					j++; i--;
				}while(i/2);
			}
		}
			break;

		default:
			break;
	}

	str[strLen] = '\0';

	return strLen;
}

       

        方法2:写一个不定参数的函数,类似于printf(char *,...),名为 myPrintf(char* str, ...)。

        如何实现不定参数的函数呢?

        首先,在#include 里面,记得有两个非常重要的宏定义va_start和va_arg,这是实现不定参数函数的关键。

        va_start(ap, fmt):作用是指向参数中第一个可变参数,va_arg(ap, xxx):作用是返回这个参数并指向下一个可变参数。

        myPrintf(char* str, ...)函数的实现如下:

void myPrintf(char* str, ...)
{
	va_list ap;
	uint8_t * pDebBuf;
	uint16_t debugLen = 0;
	uint16_t valLen = 0;

	char *p, *sval;
	uint32_t ival;

	pDebBuf = debugBuffer;

	va_start(ap, str);
	for(p=str; *p; p++)
	{
		if(*p!='%'){
			*pDebBuf = *p; pDebBuf++;
			debugLen++;
			continue;
		}

		switch(*++p){
			case 'd':{
				ival = va_arg(ap, uint32_t);
				valLen = itoa(ival, pDebBuf, 10);
				pDebBuf += valLen; debugLen += valLen;
			}
				break;

			case 'x':{
				ival = va_arg(ap, uint32_t);
				valLen = itoa(ival, pDebBuf, 16);
				pDebBuf += valLen; debugLen += valLen;
			}
				break;

			case 's':{
				for(sval = va_arg(ap, char *); *sval; sval++){
					*pDebBuf = *sval;
					pDebBuf++; debugLen++;
				}
			}
				break;

			default:{
 				*pDebBuf = *p; pDebBuf++;
				debugLen++;	
			}
 				break;
		}
	}
	va_end(ap);

	if(debugLen)
	{
		HAL_UART_Transmit(printfUart, debugBuffer, debugLen, 1000);
	}
	
}

       

        方法3:printf重定向,只需要重写fputc便可,简单实用,以后,我们都用这种方式吧,对了,记得检查一下微库有没有钩上,请看图:

STM32 Uart 实现printf函数_第1张图片

        重写的fputc函数实现如下:

int fputc(int ch, FILE *f)
{
	HAL_UART_Transmit(printfUart, (uint8_t *)&ch, 1, 1000);
	return ch;
}

        有没有发现,不管哪种方式,调用的HAL_UART_Transmit(printfUart, ..........)函数,都带有printfUart这个参数,记得,把这个参数换成你板子上对应的Uart:

UART_HandleTypeDef *printfUart = &huart4;

 

        最后,我们测试一下这三种方法,如《STM32 Uart 接收不定长数据》里所写,在idle中断的回调函数里打印字符串,以及收到的字符长度十进制显示和十六进制显示,测试代码如下:

void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
   	 __HAL_UART_CLEAR_IDLEFLAG(huart);

#ifdef RCV_DMAIDLE_PROCESS
	uart4RxLength = UART4_BUF_MAX-__HAL_DMA_GET_COUNTER(&hdma_uart4_rx);

	myPrintfChar("Test myPrintfChar/myPrintfDec/myPrintfHex\r\n");
	myPrintfChar("Receive ");
	myPrintfDec(uart4RxLength);
	myPrintfChar("(0x");
	myPrintfHex(uart4RxLength);
	myPrintfChar(") Bytes Wow\r\n");
	
	myPrintfChar("Test myPrintf\r\n");
	myPrintf("Receive %d(0x%x) %s Wow Wow. \r\n", uart4RxLength, uart4RxLength, "Bytes");

	printf("Test printf\r\n");
	printf("Receive %d(0x%x) %s Wow Wow Wow. \r\n", uart4RxLength, uart4RxLength, "Bytes");
	
	__HAL_DMA_DISABLE(&hdma_uart4_rx);
#endif
}

        其实,我们不建议在中断里面打印信息,在中断里面,最好只接收数据,做一些简单的判断,尽量让中断程序简短,执行时间短,一般情况下,我们会把处理数据放在主循环里面,下一篇的《STM32 Uart @调试命令的实现》,就把处理数据放在主循环里面处理,以后,我们都这么做吧。

        老套路,编译,烧录,重启开发板,打开串口调试工具,发送一串数据看一看:

STM32 Uart 实现printf函数_第2张图片

        我们已经实现了打印,实现在从STM32串口发送信息到PC机的功能,但要调试还是不够的,我们还要从PC往STM32传送各种命令,各种参数啊,咋办?请看下一篇《STM32 Uart @调试命令的实现》。

 

     整个工程及代码呢,请上百度网盘上下载:

     链接:https://pan.baidu.com/s/19usUcgZPX8cCRTKt_NPcfg

     密码:07on

     文件夹:\Stm32CubeMx\Code\Uart_Printf.rar

 

上一篇:《STM32 Uart 接收不定长数据》

下一篇:《STM32 Uart @调试命令的实现》

回目录:《目录》

你可能感兴趣的:(单片机)