在我们日常的开发过程中,经常使用到的一个功能就是串口打印功能。在ESP8266的IDF框架中,提供了类似控制台的printf操作,可以向串口打印一些信息,但是ESP8266的printf函数被封装经过了简化,不支持浮点数的格式控制符%f。IDF框架中拥有ets_printf函数可以替代封装的printf函数,这个函数在SDK中有源码,可以供我们学习和修改。这篇文章就是总结一下我自己对ESP-IDF工程中的ets_printf.c文件的修改,以实现在ESP8266系统中ets_printf对浮点数的格式控制符的支持。
目录
一、启用ets_printf函数
二、修改ets_printf函数
三、直接使用
使用ets_printf函数需要在工程配置里设置一下,在linux终端工程目录下输入
make menuconfig
进入工程配置页面
选择Component config选项卡 Enter进入 修改ESP8266工程宏定义配置
选择ESP8266-specific选项 Enter进入
选择 Using new ets_vprintf instead of rom code 选项 按下“空格”勾选。
最后选择 < Save > Enter 确认 ,
然后一直选择< Exit >退出配置页。
至此,ets_printf.c 文件中的内容就生效,替换了原来rom中的ets_printf函数了。
官方库里提供的ets_printf函数仍然不支持浮点数的格式控制符,但是源码已经给出了,我们可以在源码基础上修改,使它支持格式控制符“%f”
浮点数的格式控制包括“f”、“0”、“.”、数字几个操作,其中“0”、“.”和数字控制符都已经在格式控制系统内了,我们只需要写“f”对应的函数,并作为分支插入格式控制处理的switch结构里就可以了。
编写的过程参照了%d的格式处理过程:
case 'd':
attr.value.val32 = va_arg(va, int);
if (attr.value.val32 < 0) {
ets_putc('-');
attr.value.val32 = -attr.value.val32;
}
ets_printf_int(&attr, 10);
break;
由%d的 处理我发现需要在attr结构体里添加double型的变量,用来缓存变参数列表里的float或者double类型的变量。
原结构体类型是下面这样的:
typedef union _val_cache {
uint8_t val8;
int32_t val32;
uint32_t val32u;
const char *valcp;
} val_cache_t;
添加double类型的变量valfloat作为浮点数的缓存变量。新结构体类型如下:
typedef union _val_cache {
uint8_t val8;
int32_t val32;
uint32_t val32u;
const char *valcp;
double valfloat;
} val_cache_t;
接下来,我们就可以在扫描格式控制的函数里,仿照%d的格式控制方式添加%f的格式控制了
涉及到的函数是
int ets_vprintf(const char *fmt, va_list va)
在遍历扫描的switch里添加
case 'f':
在确定了本次所有格式控制操作之后的执行将数值写入打印缓存的switch操作里添加对应的float打印操作
case 'f':
attr.value.valfloat = va_arg(va, double);
if (attr.value.valfloat < 0) {
ets_putc('-');
attr.value.valfloat = -attr.value.valfloat;
}
ets_printf_float(&attr);
break;
首先获取可变参列表里的double数据,判断是否为负,并把绝对值传递给专门的float打印函数。
专门的float型数据打印处理函数ets_printf_float(&attr)是我自己根据这套打印结构的特点写的,参照了int型数据的处理过程,内容如下:
#define FLOAT_decimals_MAX_NUM 9
#define VFLOAT_STR_MAX 20
static int ets_printf_float(val_attr_t * const attr)
{
char buf[VFLOAT_STR_MAX];
unsigned char offset = VFLOAT_STR_MAX;
int32_t integer=attr->value.valfloat;
double decimals = (attr->value.valfloat-integer);
if (attr->precision!=0) {
for (int i =0; iprecision;i++) {
decimals=decimals*10.0;
}
integer=decimals;
if(decimals-integer>0.5)//末位四舍五入
integer++;
for (int i =0; iprecision;i++) {
unsigned char c = integer % 10;
buf[--offset] = c + '0';
integer /= 10;
}
}
else
{
int i =0;
for (i =0; i0.5)
integer++;
for (; i>0;i--) {
unsigned char c = integer % 10;
buf[--offset] = c + '0';
integer /= 10;
}
}
buf[--offset] = '.';
integer=attr->value.valfloat;
if (integer != 0) {
for (; integer > 0; integer /= 10) {
unsigned char c = integer % 10;
buf[--offset] = c + '0';
}
} else
buf[--offset] = '0';
if (fill_num(attr)) {
char fill_data = isfill_0(attr) ? '0' : ' ';
unsigned char len = fill_num(attr) - (VFLOAT_STR_MAX - offset);
unsigned char left = fill_num(attr) > (VFLOAT_STR_MAX - offset) ? len : 0;
if (!isfill_left(attr)) {
ets_printf_ch_mutlti(fill_data, left);
}
ets_printf_buf(&buf[offset], VFLOAT_STR_MAX - offset);
if (isfill_left(attr)) {
fill_data = ' ';
ets_printf_ch_mutlti(fill_data, left);
}
} else {
ets_printf_buf(&buf[offset], VFLOAT_STR_MAX - offset);
}
return 0;
}
有了这个函数就可以实现ESP8266的浮点数打印操作了。其他地方完全不用修改的。其中我设置了小数点后最大位数为9位,最大字符长度为20位,如果需要超过这个长度的数据打印,可以修改此处宏定义。(实际上double型的数据可以很长很长……)
有一点需要注意的是,格式控制符中有效数字个数包括小数点。这是和计算机中的printf格式控制保持一致的。
对于不关注技术实现细节的小伙伴可以直接下载我写好的文件,直接替换掉components/esp8266/source路径下原来的ets_printf.c文件即可实现浮点数的打印。
文件在我的github上(https://github.com/gengyuchao)。
欢迎关注我的博客和github呀~希望能够和各路大佬一起讨论技术问题~