想做的利用串口打印出数据,在PC上的超级终端上显示出来。由于发送数据的未知和不确定性,所以不可能开辟一个数组来发送这些字符。
所以要重定义printf来作为串口的打印输出,需加头文件stdio.h
1.勾选usb micro lib
2.在程序中添加:(头文件需要添加#include <stdio.h>)
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)模拟串口使用printf函数
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
USART_SendData(USART1, (u8) ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
return ch;
}
很明显这个方法使用printf()只能输出到一个usart,如果需要使用多个usart呢,肯定不能都是用printf()。
方法如下是继续是用usart2的printf()功能:
1.勾选usb micro lib,跟上面类似
2.添加头文件#include <stdarg.h>,编写USART2的printf函数:
void USART2_printf (char *fmt, ...)
{
char buffer[CMD_BUFFER_LEN+1]; // CMD_BUFFER_LEN长度自己定义吧
u8 i = 0;
va_list arg_ptr;
va_start(arg_ptr, fmt);
vsnprintf(buffer, CMD_BUFFER_LEN+1, fmt, arg_ptr);
while ((i < CMD_BUFFER_LEN) && buffer[i])
{
USART_SendData(USART2, (u8) buffer[i++]);
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
}
va_end(arg_ptr);
}
用法与printf类似,如int i=123;USART2_printf("%d",i);
如果需要使用的printf()函数自以此类推!
以上代码都是网上流传的一种写法,但在这里发送打印的不是采用中断发送的,即有好多时间都浪费在了循环检测发送完成位上。
在这个函数中有个叫不定参数的概念,相应的标准头文件为stdarg.h,是C语言中C标准函数库的标头档,stdarg是由stdandard(标准) arguments(参数)简化而来,主要目的为让函数能够接收不定量参数。不定参数函数的参数数量是可变动的,它使用省略号来忽略之后的参数。stdarg.h宏有以下几个:
va_start    使va_list指向起始的参数
va_arg      检索参数
va_end      释放va_list
va_copy    拷贝va_list的内容
各个va_xxx的作用。
va_list arg_ptr:定义一个指向个数可变的参数列表指针;
va_start(arg_ptr, argN):使参数列表指针arg_ptr指向函数参数列表中的第一个可选参数,说明:argN是位于第一个可选参数之前的固定参数,(或者说,最后一个固定参数;…之前的一个参数),函数参数列表中参数在内存中的顺序与函数声明时的顺序是一致的。如果有一va函数的声明是void va_test(char a, char b, char c, …),则它的固定参数依次是a,b,c,最后一个固定参数argN为c,因此就是va_start(arg_ptr, c)。
va_arg(arg_ptr, type):返回参数列表中指针arg_ptr所指的参数,返回类型为type,并使指针arg_ptr指向参数列表中下一个参数。
va_copy(dest, src):dest,src的类型都是va_list,va_copy()用于复制参数列表指针,将
dest初始化为src。
va_end(arg_ptr):清空参数列表,并置参数指针arg_ptr无效。说明:指针arg_ptr被置无效后,可以通过调用va_start()、va_copy()恢复arg_ptr。每次调用va_start() / va_copy()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列表中随意地来回移动,但必须在va_start() … va_end()之内。
(以下内容出自戎亚新)
然而printf到底是怎样取第一个参数后面的参数值的呢,请看如下代码
2. printf 函数的实现
//ac env.h typedef char *va_lis t; #define _AUPBND (s izeof
(acp i_nativ e_int) - 1) #define _ADNBND (s izeof (acpi_n ative_int) - 1) #defin e _bnd(X, bnd) (((s izeof (X)) + (bnd)) & (~(bnd))) #def ine
va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd
(T,_ADNBND)))) #def ine v a_end(ap) (vo id) 0 #def ine v a_s tart(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND)))) //s tart.c
s tatic char s print_buf[1024]; int printf(char *fmt, ...) { va_lis t args; int n; va_s tart(args, fmt); n = vs printf(s print_buf, fmt, args);
va_end(args); wr ite(s tdout, s print_buf, n); return n; } //unis td.h
s tatic inline long write(int fd, const char *buf, off_t count) { return s ys_write(fd, buf, count); }
3. 分析从上面的代码来看,printf似乎并不复杂,它通过一个宏v a_start 把所有的可变参数放到了由args指向的一块内存中,然后再调用v sprintf. 真正的参数个数以及格式的确定是在v sprintf搞定的了。由于v sprintf的代码比较复杂,也不是我们这里要讨论的重点,所以下面就不再列出了。我们这里要讨论的重点是v a_start(ap, A)宏的实现,它对定位从参数A后面的参数有重大的制导意义。现在把#def ine v a_start(ap, A) (v oid) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND)))) 的含义解释一下如下:
va_s tart(ap, A) { char *ap = ((ch ar *)(&A)) + s izeof(A)并int类型大小地址对齐}
在printf的v a_start(args, fmt)中,fmt的类型为char *, 因此对于一个32为系统sizeof(char *) = 4, 如果int大小也是32,则v a_start(args, fmt);相当于char *args = (char *)(&fmt) + 4; 此时args的值正好为fmt后第一个参数的地址。对于如下的可变参数函数void fun(double d,...) { v a_lis t args; int n; va_s tart(args, d); }
则v a_start(args, d);相当于char *args = (char *)&d + s izeof(double);
此时args正好指向d后面的第一个参数。可变参数函数的实现与函数调用的栈结构有关,正常情况下c/c++的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈。
***************************************************************************************************** *printf函数大概就这么个东西吧,反正也是模糊概念的在这,关于使用串口中断发送的方法再做研究
在printf中有个v sprintf的函数,参数如下
int vsnprintf(
char *buffer, // 输出参数
size_t count, // 最大可写字符数
const char *format,// 格式字符串
va_list argptr // 参数列表指针
);
这是个复杂的问题,以后待研究,现在做保留