int fputc(int ch, FILE *f)
{
while((USART1->SR&0x40)==0);
{
USART1->DR = (u8) ch;
}
return ch;
}
fputc中将ch通过USART1发出。这样,我们在调用printf的时候,相应的信息就会从USART1打印出来。
“上面你说的这些,我都知道,有什么新鲜的!”确实,通过串口打印信息是我们司空见惯的。那么,下面的fputc你见过吗?
int fputc(int ch, FILE *f)
{
LCD_DispChar(x,y,ch);
x++;
if(x>=X_MAX)
{
x=0;y++;
if(y>=Y_MAX)*
{
y=0;
}
}
return ch;
}
这个fputc将字符显示在了液晶上(同时维护了字符的显示位置信息),这样当我们调用printf的时候,信息会直接显示在液晶上。
说白了,fputc 就是对数据进行了定向输出。这样我们可以把 printf 变得更灵活,来应对更多样的应用需求。在振南经历的项目中,曾经有过这样的情况:单片机有多个串口,串口1用于打印调试信息,串口2与ESP8266 WIFI模块通信,串口3与SIM800 GPRS模块通信。3个串口都需要格式化输出,但printf只有一个,这该怎么办?我们解决方法是,修改fputc使得printf可以由3个串口分时复用,具体实现如下:
unsigned char us=0;
int fputc(int ch,FILE *f)
{
switch(us)
{
case 0:
while((USART1->SR&0x40)==0);
USART1->DR=(u8)ch;
break;
case 1:
while((USART2->SR&0x40)==0);
USART2->DR=(u8)ch;
break;
case 2:
while((USART3->SR&0x40)==0);
USART3->DR=(u8)ch;
break;
}
return ch;
}
在调用的时候,根据需要将us赋以不同的值,printf就归谁所用了。
#define U_TO_DEBUG us=0;
#define U_TO_ESP8266 us=1;
#define U_TO_SIM800 us=2;
U_TO_DEBUG
printf(“hello world!”);
U_TO_ESP8266
printf(“AT
”);
U_TO_SIM800
printf(“AT
”);
七、关于浮点数的传输
unsigned short a=0x1234;
UART_Send_Byte(((unsigned char *)&a)[0]);
UART_Send_Byte(((unsigned char *)&a)[1]);
也就是将构成整型的若干字节顺序发送即可。当然,接收方一定要知道如何还原数据,也就是说,它要知道自己接收到的若干字节拼在一起是什么类型,这是由具体通信协议来保障的。
unsigned char buf[2];
usnigned short a;
UART_Receive_Byte(buf+0);
UART_Receive_Byte(buf+1);
a=(*(usnigned short *)buf);
OK,关于整型比较容易理解。但换成float,很多人就有些迷糊了。因为float的数值表达方式有些复杂。有些人使用下面的方法来进行浮点的发送:
float a=3.14;
char str[10]={0};
ftoa(str,a); //浮点转为字符串 *即3.14*转为"3.14"
UART_Send_Str(str); //**通过串口将字符串发出
很显然,这种方法非常的“业余”。
还有人问我:“浮点小数字前后的数字可以发送,但是小数点怎么发?”这赤裸裸的体现了他对浮点类型的误解。
不要被float数值的表象迷惑,它实质上只不过是4个字节而已,如下图所示:
所以,正确的发送浮点数的方法是这样的:
float a=3.14;
UART_Send_Byte(((unsigned char *)&a)[0]);
UART_Send_Byte(((unsigned char *)&a)[1]);
UART_Send_Byte(((unsigned char *)&a)[2]);
UART_Send_Byte(((unsigned char *)&a)[3]);
接收者将数据还原为浮点:
unsigned char buf[4];
float a;
UART_Receive_Byte(buf+0);
UART_Receive_Byte(buf+1);
UART_Receive_Byte(buf+2);
UART_Receive_Byte(buf+3);
a=*((float *)buf);
其实我们应该发现数据类型的实质:不论是什么数据类型,它的基本组成无非就是内存中存储的若干个字节。只是我们人为的赋予了这些字节特定的编码方式或数值表达。看穿了这些,我们就认识到了数据的本质了,我们甚至可以直接操作数据。
八、关于数据的直接操作
直接操作数据?我们来举个例子:取一个整型数的相反数。一般的实现方法是这样的:
int a=10;
int b=-a; //-1*a;
这样的操作可能会涉及到一次乘法运算,花费更多的时间。当我们了解了整型数的实质,就可以这样来作:
int a=10;
int b=(~a)+1;
这也许还不足以说明问题,那我们再来看一个例子:取一个浮点数的相反数。似乎只能这样来作:
float a=3.14;
float b=a*-1.0;
其实,我们可以这样来作:
float a=3.14;
float b;
((unsigned char *)&a)[3]^=0x80;
b=a;
没错,我们可以直接修改浮点在内存中的高字节的符号位。这比乘以-1.0的方法要高效的多。
当然,这些操作都需要你对 C 语言中的指针有炉火纯青的掌握。
九、浮点的四舍五入与比较
我们先说第一个问题:如何实现浮点的四舍五入?很多人遇到过这个问题,其实很简单,只需要把浮点+0.5然后取整即可。OK,第二个问题:浮点的比较。这个问题还有必要好好说一下。首先我们要知道,C语言中的判等,即==,是一中强匹配的行为。也就是,比较双方必须每一个位都完全一样,才认定它们相等。这对于整型来说,是可以的。但是float类型则不适用,因为两个看似相等的浮点数,其实它们的内存表达不能保证每一个位都完全一样。这个时候,我们作一个约定:两个浮点只要它们之差m足够小,则认为它们相等,m一般取10e-6。也就是说,只要两个浮点小数点后6位相同,则认为它们相等。也正是因为这个约定,很多C编译器把float的精度设定为小数点后7位,比如ARMCC(MDK的编译器)。
float a,b;
if(a==b)
… //**错误
if(fabs(a-b) <= 0.000001)
…//**正确
十、出神入化的for循环
for循环我们再熟悉不过了,通常我们使用它都是中规中矩的,如下例:
int i;
for(i=0;i<100;i++)
{…}
但是,如果我们对for循环的本质有更深刻的理解的话,就可以把它用得出神入化。
for后面的括号中的东西我称之为“循环控制体”,分为三个部分,如下图所示:A、B、C三个部分,其实随意性很大,可以是任意一个表达式。所以,我们可以这样写一个死循环:
for(1;1;1) //1**本身就是一个表达式:常量表达式
{
…
}
当然,我们经常会把它简化成:
for(;😉
{
…
}
既然循环控制体中的A只是在循环开始前作一个初始化的操作,那我这样写应该也没毛病:
int i=0;
for(printf(“Number:
”);i<10;i++)
{
printf(" %d
",i);
}
B是循环执行的条件,而C是循环执行后的操作,那我们就可以把一个标准的if语句写成for的形式,而实现同样的功能:
if(strstr(“hello world!”,“abc”))
{
printf(“Find Sub-string”);
}
char *p;
for(p=strstr(“hello world!”,“abc”);p;p=NULL)
{
printf(“Find Sub-string”);
}
以上的例子可能有些鸡肋,“一个if能搞定的事情,我为什么要用for?”,没错。我们这里主要是为了解释for循环的灵活用法。深入理解了它的本质,有助于我们在实际开发中让工作事半功倍,以及看懂别人的代码。
以下我再列举几个for循环灵活应用的例子,供大家回味。例1:
char *p;
for(p=“abcdefghijklmnopqrstuvwxyz”; printf§; p++)
printf(“
”);
提示:printf我们太熟悉了,但有几个人知道printf是有返回值的?输出应该是怎样的?
例2:
char *p;
unsigned char n;
for(p=“ablmnl45ln”,n=0;((*p==‘l’)?(n++):0),*p;p++);
提示:还记得C语言中的三目运算和逗号表达式吗?n应该等于几?
例3:
unsigned char *index=“C[XMZA[C[NK[RDEX@”;
char *alphabet="EHUIRZWXABYPOMQCTGSJDFKLNV ";
int i=0;
for(😭(‘@’!=index[i])?1:(printf(“!!Onz
”),0));i++)
{
printf(“%c”,alphabet[index[i]-‘A’]);
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
cW1-1712959914313)]
[外链图片转存中…(img-ZZdXfR3W-1712959914313)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-sdigNb79-1712959914314)]