1.1 ARM 串口输出函数uart_printf
ARM如果能使用C函数库自带的printf函数格式输出,那多方便,但是默认的printf都是定位到stdout终端,而不是串口,本文章讲述的是如何定位到ARM的串口。
1.1.1 函数主要代码有在Mini2440开发板上验证过
//*****************main.c*******************************
#include"serial.h"
int Main()
{
unsignedint plck_val = 50000000;
unsignedint buad_val = 115200;
unsignedint ch_val = 0;
char*string="Hello,http://blog.csdn.net/wfq0624";
uart_init();
uart_printf("\n\r%s\n",string);
uart_printf("\rUse uart0\n\rParameter:PCLK is %d,buad is %d,uart_port is %d \n",plck_val,buad_val,ch_val);
uart_printf("\r该函数不能打印浮点数!");
return 0;
}
//***********************serial.c**************************
#include"s3c2440.h"
#include"serial.h"
#include <stdarg.h> //需要包含此stdarg.h头文件
#include <stdio.h>
//需要包涵此stdio.h头文件
#define TXD0READY (1<<2)
#defineRXD0READY (1)
#definePCLK 50000000 // init.c中的clock_init函数设置PCLK为50MHz
#defineUART_CLK PCLK // UART0的时钟源设为PCLK
#defineUART_BAUD_RATE 115200 // 波特率
#defineUART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)
voiduart_init()
{ //UART初始化:端口使能、功能设定、波特率、设置数据格式
GPHCON = (GPHCON & ~(0xfff<<4)) |(0xaaa<<4);//端口配置成uart0、uart1,uart3
GPHUP = 0x38; //端口GPH禁止上拉
UFCON0 = 0x0; //禁止FIFO
UMCON0 = 0x0; //禁止AutoFlow Control
//Normal:No parity:One stop:8-bits 中断响应 UART clock: PCLK
ULCON0 = 0x03; // 8N1(8个数据位,无较验,1个停止位)
UCON0 = 0x05; // 查询方式,UART时钟源为PCLK
UBRDIV0 = UART_BRD; // 波特率为115200
}
voiduart_send_byte(char data)
{
while (!(UTRSTAT0 & TXD0READY));
UTXH0 = data;
}
voiduart_send_string(char *string)
{
while(*string)
{
uart_send_byte(*string++);
}
}
void uart_printf(char *fmt,...) //这个才是本文重点
{
va_listap;
charstring[256];
va_start(ap,fmt);
vsprintf(string,fmt,ap);
uart_send_string(string);
va_end(ap);
}
//***************************************************
1.1.2 uart_printf分析这里涉及到一个重要概念,变参函数,比如C库函数int printf(char *fmt, ...),就是一个典型的变参函数。
我们即将编写的uart_printf毫无疑问,也是一个变参函数。
可变参数入栈顺序
在进程中,堆栈地址是从高到低分配的.当执行一个函数的时候,将参数列表入栈,压入堆栈的高地址部分,然后入栈函数的返回地址,接着入栈函数的执行代码,这个入栈过程,堆栈地址不断递减,一些黑客就是在堆栈中修改函数返回地址,执行自己的代码来达到执行自己插入的代码段的目的.
总之,函数在堆栈中的分布情况是:地址从高到低,依次是:函数参数列表,函数返回地址,函数执行代码段.
堆栈中,各个函数的分布情况是倒序的.即最后一个参数在列表中地址最高部分,第一个参数在列表地址的最低部分.参数在堆栈中的分布情况如下:
最后一个参数
倒数第二个参数
...
第一个参数
函数返回地址
函数代码段
va_list/ va_start/ va_arg/va_end 介绍
C语言标准库中头文件stdarg.h索引的接口包含了一组能够遍历变参数列表的宏。主要包含下面几个:
1.va_list:在函数里定义一个va_list型的变量,这个变量是指向参数的指针
2.va_start:用va_start宏初始化刚定义的va_list变量,让它指向可变参数表里面的第一个参数,
3.va_arg:每次调用时都会返回当前指针指向的变量,并将指针挪至下一个位置,参数的类型需要在这个调用的第二个参数来指定,va_arg也是根据这个参数来判断偏移的距离。
4.va_end:获取所有的参数之后,我们有必要将这个指针关掉,以免发生危险,方法是调用 va_end,置为 NULL,应该养成获取完参数表之后关闭指针的习惯。
void uart_printf(char *fmt,...) //这个才是本文重点
{
va_listap; //定义一个 va_list 指针来访问参数表
charstring[256];
va_start(ap,fmt); //初始化 ap,让它指向第一个变参[也就是fmt参数后面的参数]
vsprintf(string,fmt,ap); //将带参数的字符串按照参数列表格式化到string中
uart_send_string(string);
va_end(ap); //结束变量列表,和va_start成对使用
}
可变参数在编译器中的处理
va_list ,va_start,va_arg,va_end是在stdarg.h中被定义成宏的,以VC++中stdarg.h里x86平台的宏定义摘录如
=================================================================
typedef char * va_list;
#define_INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
#defineva_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#defineva_arg(ap,t) ( *(t *)((ap +=_INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0
=================================================================
è#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统
我们知道对于X86,sizeof(int)一定是4的整数倍,所以~(sizeof(int) - 1) )的值一定是右面[sizeof(n)-1]/2位为0,整个这个宏也就是保证了右面[sizeof(n)-1]/2位为0,其余位置为1,所以_INTSIZEOF(n)的值只有可能是4,8,16,......等等,实际上是实现了内存对齐。
举例:#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
目的在于把sizeof(n)的结果变成至少是sizeof(int)的整倍数,这个一般用来在结构中实现按int的倍数对齐。
如果sizeof(int)是4,那么,当sizeof(n)的结果在1~4之间是,_INTSIZEOF(n)的结果会是4;当sizeof(n)的结果在5~8时,_INTSIZEOF(n)的结果会是8;当sizeof(n)的结果在9~12时,_INTSIZEOF(n)的结果会是12;……总之,会是sizeof(int)的倍数。
è#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
ap指向参数v之后的那个参数的地址,即 ap指向第一个可变参数在堆栈的地址。
è#define va_arg(ap,t) ( *(t *)((ap +=_INTSIZEOF(t)) - _INTSIZEOF(t)) )
取出当前ap指针所指的值,并使ap指向下一个参数。
用va_arg返回可变的参数,va_arg的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用va_arg获取各个参数
è#define va_end(ap) (ap = (va_list)0
清空va_list ap.
使用VA_LIST应该注意的问题:
(1)因为va_start,va_arg, va_end等定义成宏,并不能智能地识别不同参数的个数和类型. 也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.这个方法存在漏洞:输入参数的类型随意性,使得参数很容易以一个不正确的类型获取一个值(譬如输入一个float,却以int型去获取他),这样做会出现莫名其妙的运行结果
(2)另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码。
(3)由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量,或作为函数或数组类型。
Per-vertex渲染技术是针对每个顶点进行渲染计算,然后把计算得到的颜色值和这个顶点关联起来。然后在多边形的面上进行颜色插值以后的平滑的渲染效果。这也叫做Gouraud Shading。在早起的OpenGL版本中,它是默认的渲染技术。
有的时候我们需要达到遮掩过一种效果:一个多边形上只有一种颜色,而不是有这种插值方法得到的平滑效果。这个时候的渲染就叫做Flat Shading。
下面的图显示两种渲染效果的对比:
Gouraud Shading
Flat Shading
在早起的OpenGL版本中,Flat shading效果是通过下列函数调用实现的:
glShadeModel(GL_FLAT)。
而且,当前多边形面所使用的颜色是这个多边形的顶点中最后一个被渲染的顶点的颜色。
在OpenGL4.0中,flat shading效果可以通过对着色器的输入和输出变量使用一个修饰符flat很方便的实现。
在顶点着色器的输出变量和片断着色器要使用作为颜色的输入变量前使用这个修饰符即可。
这个修饰符表明了这个值在传递到片断着色器的时候没有插值发生。
最后多边形的颜色可能是其顶点中最先或最后被渲染的顶点的颜色。可以通过下列函数调用来控制:
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
或者是
glProvokingVertex(GL_LAST_VERTEX_CONVENTION);
参考资料:
http://www.futuretech.blinkenlights.nl/gouraud.html
http://graphics.wikia.com/wiki/Flat_shading
http://en.wikipedia.org/wiki/Gouraud_shading
http://www.cs.cmu.edu/~fp/courses/02-graphics/pdf-color/08-shading.pdf
前一阵子在维护一个项目时看到以前同事写的代码,看到他把所有的[[NSNotificationCenter defaultCenter] removeObserver:self];方法都放到了viewController的dealloc方法中,添加observer放到了init中,当时并没有想太多。
后来在写相关代码时发现,当某个notification被post之后,观察者的方法被多次调用。于是想到,难道是多次添加了观察者,而没有删除他?于是看了下苹果官方文档中的代码,发现官方例子中是在viewWillAppear的时候添加,viewWillDisappear的时候remove。
现在我们来深入思考下为什么不能在dealloc中调用[[NSNotificationCenter defaultCenter] removeObserver:self]。首先,让我们来想在添加观察者的时候,我们观察者的retainCount被+1了吗?如果没有被+1,那么当这个类会在retainCount为0时被销毁,通知中心就无法通知到该类,那么remove方法还有意义吗?所以在添加观察者的时候,通知中心必然会将该观察者的retainCount+1,既然通知中心retain了这个观察者,那么很不幸,这个观察者的dealloc方法永远不会被调用,他的retainCount最少也是1,因为通知中心retain了一次,结果[[NSNotificationCenter defaultCenter] removeObserver:self];就永远不被调用。
所以正确的做法是按照官方例子中的方法来做,而这么做的时候我们不妨多想想为什么应该那么做