Category Archives: Laboratory

【C语言深入】陷阱:数组指针作为函数参数返回

再来看一个指针问题,同样的来自一个本科生的代码。这段代码想要实现将一个全是小写字母的字符串转换成对应的大写字母字符串:

char *covert_to_upper_case(char *string) {
    char p[100];
    int i = 0;
    
    for(; i < strlen(string); i++ ) {
            p[i] = string[i] - ('a' - 'A'); 
    }
    p[i] = '\0';
    
    return p;
    
}

然而这段代码没有能实现期望的功能。原因如下:

  1. 主程序调用convert_to_upper_case()函数后,堆栈为p分配了内存空间;
  2. 函数体正确修改了p对应字符数组的内容,并将p的首地址作为指针返回;
  3. 函数返回后,所有临时变量从堆栈中弹出,包括p[100];
  4. 主程序得到返回的指针,对其进行解析。然而指针指向的字符数组此时已经从堆栈中弹出,解析后的数据无法被定义。

要想正确实现对应的功能,应该将目标指针作为额外参数传递给该函数,并由上层调用者提供内存空间的创建。当然也可以使用malloc()将内存分配在堆中,但是需要注意使用对应的free()释放空间,否则会有内存泄露的问题。

【C语言深入】指针的一个错误赋值

关于指针总是有说不完的故事。

最近给本科的学生带Embedded System课程设计,遇到了一个非常奇怪的bug。有一段代码需要实现I2C通信,核心代码已经由软件库提供了,学生只需要设置结构体后调用API即可。一个学生的代码是这样的:

struct I2C_CONFIG {
  // ...
  char *i2c_buff;
  int length;
  // ...
};

struct I2C_CONFIG cfg;
char *i2c_buff;

void I2C_init() 
{
  // ...
  cfg.buff = i2c_buff;
  cfg.length = sizeof(buff);
  // ...
}

void I2C_send(new_buff)
{
  // ...
  i2c_buff = new_buff;
  I2C_MasterTransferData(LPC_I2C1, cfg);
  // ...
}

初看一下没有什么问题:在I2C_init()函数中首先对结构体cfg进行初始化,而在I2C_send()函数中设置了需要发送的数据指针,之后使用I2C的API发送数据。

因为代码一直无法实现期望的功能,我又仔细看了一下其中的蹊跷。我注意到,这段代码中使用了一个中间变量:char *i2c_buff。在I2C_init()中虽然将cfg.buff指向了i2c_buff,但是因为cfg.buff本身也是指针变量,而非"指向指针的指针",所以这里只实现了简单的按值传递,即将i2c_buff的值 (初始值为0) 赋给了cfg.buff。之后虽然在I2C_send()中修改了临时变量i2c_buff指向的位置,但却没有影响到cfg.buff中的内容,cfg.buff依然指向之前i2c_buff初始化时指向的内存地址,所以需要发送的缓冲指针new_buff其实并没有传递给之后的I2C_MasterTransferData()函数!为了解决这个问题,必须将更改后的i2c_buff的值再次赋给cfg.buff,即:

void I2C_send(new_buff)
{
  // ...
  i2c_buff = new_buff;
  cfg.buff = i2c_buff;
  I2C_MasterTransferData(LPC_I2C1, cfg);
  // ...
}

另外这段代码还有一个不容易注意的bug,就是在I2C_init()中使用了sizeof()来判断buffer的大小。因为sizeof()函数得到的只是数据类型的大小,所以对于指针char *i2c_buff来说,sizeof(i2c_buff) = 4,而不会返回buffer的实际大小。指针的大小并不等于指针指向缓冲的大小!

The Limitations of Classical PID Controller and Its Advanced Derivations

Since founded by N. Weiner in 1947, the control theory has been evolved for more than 60 years and is still full of challenges and opportunities. The most important principle of the control theory, in my opinion, is the feedback mechanism. Without feedback and closed-loop, almost no algorithm and control technique can be implied. The idea of feedback is that by comparing the reference input and the actual output, an error signal can be obtained and then can be used by the controller to trace and eliminate the difference between the input and the output. Apart from Watt’s steam engine, one could say that the first formally implication of (negative) feedback is the amplifier invented by H.S. Black. It is a genius idea when first came out in 1927 and was proved to be an extremely useful way to solve electronic and control problems. The idea of output feedback has also been extended to state feedback and error feedback to achieve state control and estimation in more advanced control techniques.

Classical control is the foundation of control theory and it is more concentrated on analysing the stability and performance of a controlled plant. However, only linear and SISO systems have been discussed in classical control theory. Although traditional control techniques such as PID controller are still widely used in industry, they cannot handle more complex engineering scenarios such as aerospace, chemistry and biology. Another problem of classical control is that all parameters are designed and tuned based on the current system model, in which case the system will be more vulnerable to further disturbance and parameters varying.

In order to solve these problems of classical PID controller which mentioned before, more advanced approaches have been derived nowadays. If using classical approach to control a MIMO system, one should divide the system into different modes and control each mode separately. However if the system inputs and outputs are coupled with each other, it cannot be decoupled and this method will not be practicable anymore. Here comes the state-space method, which solved the limitation of classical control by using state variables. The advantage of state-space is that it can be represented by matrices and such is very computer-friendly. State-space representation is actually defined in time domain instead of frequency domain and every state can have some extend of physical meaning which gives some clues about what is happening inside a controlled plant. One milestone which makes the state-space method more practicable is the invention of Kalman filter. Kalman filter uses a series of history measurements in the presence of noise to estimate the current state of the system. Kalman filter can work as a state estimator or simply a special filter which uses the physical system model to remove the process and the measurement noise.

Optimal control method such as MPC and LQR is another derivation of classical control. In most circumstances, there are more than one possible control inputs which can drive the system to work properly, but we need is to find the optimal one. Optimal control actually transforms the control problem into an optimal problem which tries to minimise an objective function to get the best outcome. Another advantage of optimal control is that it can take constraints into consideration. One defect of PID controller is that it cannot handle system constraints like actuator saturation or output limitation. In the optimal case control, design a controller with constraints could be feasible.

It is also known that no system is constant and some parameters are likely to vary with time or to the working condition. In classical control, the controller is designed just for the current system model and thus may loss performance or even be unstable due to the system change and uncertainties. In such aspect, adaptive control or robust control may be more applicable. Both adaptive control and robust control are designed to cope with uncertainties. The difference is that adaptive control identifies the system model and changes its parameters in real-time, but robust control fixed its parameters after deployed to the plant. For the truth that adaptive control has to calculate the system model every few periods, it needs much more computational time. What’s more, since the control parameters in the adaptive controller are changing every time, it may be difficult to prove its stability.  On the other hand, the gain of robust controller has already been designed before applied to the system, so it doesn’t need to do additional calculation during the operation. Since robust controller is globally optimised and especially designed to handle uncertainties, it may not have a performance as good as other controllers. But since the real control problems are always not ideal, it is meaningful to take uncertainties and disturbance into the system model.

Some more advanced control techniques such as neural network and expert control are being discussed today. In my opinion, these new approaches have the potential to be the next generation of control theory. With the developing of computer science, it is now possible to model extremely complex networks. This kind of controller can actually take all the possible system states and its corresponding solutions into a database and each time just search for the best solution according to the current system data.  New techniques such as machine learning can also be absorbed into the controller and make the controller more flexible which can handle different control problems using a same configuration.

However, no matter how powerful the control method is, there are rarely situations where we do not need to make trade-offs. As human-beings, we always need to make decisions and balance the income and the expense. Being too greedy is like giving an infinite gain to a helicopter, which may work at the beginning but will suddenly crash whenever there is any disturbance. So push yourself while keep in mind that you have limitation. Take it easy, be adaptive to the environment and always try to get the optimal solution of your life.

REFERENCES

[1] R.C. Dorf & R.H. Bishop, Modern Control Systems (Twelfth Edition), Pearson, USA.

[2] Wikipedia, Harold Stephen Black. Available at: http://en.wikipedia.org/wiki/Harold_Stephen_Black. Last accessed 26th Mar 2014

[3] E.F. Camacho and C. Bordons, Modern Predictive Control, Springer, London, 2003

【RPi树莓派使用指南】树莓派官方7寸屏入门指南

1. 引子

在树莓派官方触摸屏发布之前,市场上可用的屏幕有以下三种:

  • 直接和GPIO插口对接的屏幕,使用SPI与CPU进行通信。需要特殊的驱动程序将framebuffer的内容发送到LCD控制器上,一般带有触屏功能,大小以3.5寸为主流。受限于SPI通信速度,刷新速率不高;
  • 专用USB接口的屏幕,如RoboPeak Mini USB Display。这类屏幕通过USB连接,需要本地运行驱动程序;
  • 通用LCD屏幕,通过HDMI和树莓派连接。因其通用性不需要特殊的驱动程序,但是很多都不支持触屏功能,而且都需要额外的转接板,体积较大;

rpilcd-front-with-base
▲ 图.  树莓派官方7寸屏实拍

我自己的需求是将树莓派作为信息显示中心,在屏幕上显示我的HP服务器的运行信息,另外提供一些快捷的传感器监控和控制操作接口。最初一直在官方屏幕和HDMI屏幕之间犹豫,最后还是选择了官方触摸屏。归结起来主要有几个原因:

  • 官方屏的LCD模组最有保证,淘宝上的HDMI LCD一般成像质量不高;
  • 官方屏的触摸功能在所有方案中是支持的最好的,有十点电容触摸(目前Raspbian还只支持单点,以后会升级),且不需要额外驱动。而HDMI接口的LCD如果有触摸功能,都需要额外接一根USB用于提供触摸控制;
  • 官方触屏和树莓派3可以直接通过铜柱物理连接,无需额外的驱动电路板。连线也非常少,只需要一根DSI软排线和供电接口即可。

总体上来说,虽然官方屏的价格高了一些,但是却是所有方案中最可靠、简洁的,所以最后也没有多犹豫就从网上下单了。rpilcd-front-without-base
▲ 图. 树莓派官方LCD屏实拍 - 正面

 

2. 入手简评

这款屏幕官方公布的主要参数有:

  • 分辨率800 x 480像素,刷新率60fps;
  • 10点电容触摸,但是目前只支持单点触控;
  • 背后有可供背挂的安装定位孔;
  • 可视角度70度 ,可视区域大约为155 x 86mm;
  • 非方形像素 - 大约为0.19 x 0.175mm;
  • 电源功耗:455mA - 470mA之间,约为2.3W。

虽然说这款屏幕是相对来说比较好的选择,但是拿到手之后还是发现了很多存在的问题:

  • 首先是这款屏幕的分辨率只有800 x 480,是WVGA标准,很多应用软件的界面都无法完全显示,使用时要经常拖动窗口来显示需要的信息;同样的,如果是自己开发图形界面,能够显示内容的空间也会比较有限(尤其考虑到任务栏也占用了一部分空间);
  • 其次是这个屏幕的可视角不大。现在主流的LCD屏幕可视角都是120度以上了,而这款屏只有70度,工作时只要稍微变换一下角度屏幕的内容就看不清楚了。可视角的问题在仰视时还可以接受,俯视的时候就非常明显了;
  • 最后就是这个屏幕比较吃电,如果和树莓派供用电源,会影响到树莓派的供电。如果出现供电不足的情况,屏幕的右上角就会时不时出现一个方形的彩虹图标。

总体来说这款屏幕从设置到使用还是比较方便的,但是作为官方发布的屏幕,同时再考虑其600块钱的定价,整体性价比就显得不高了。

 

3. 使用方法

3.1 注意事项(使用前必读)

1) 这款屏幕主要支持的树莓派型号是Model A+, B+, Pi 2和Pi 3。 Model A和B虽然也可以使用,但是会牺牲I2C的功能(因为这两个型号只有一组I2C,而其他型号有两组,可用其中一组作触屏接口);

2) 至于操作系统,推荐使用最新版原生Raspbian OS。目前对NOOBS系统支持不好,所以还是推荐安装Raspbian;如果已经在使用旧版Raspbian,但是不想重新安装最新的系统,可以使用以下命令升级系统:

sudo apt-get update
sudo apt-get install --reinstall libraspberrypi0 libraspberrypi-{bin,dev,doc} raspberrypi-bootloader
sudo reboot

以下是官方公布的操作系统支持情况:
- Raspbian - Supported
- Ubuntu MATE - Supported
- RetroPie - Supported
- OpenElec - Supported
- OSMC - Supported
- Arch - Display works, Touch may be tricky: https://www.raspberrypi.org/forums/viewtopic.php?f=108&t=128452
- Kano OS - Not supported

3) 确保使用官方电源(或其他品牌电流供应能力 > 2A以上的电源),保证屏幕可以正常供电;

4) FPC连接头很脆,安装的时候需要小心一点。确保FPC连接头可靠链接,连接端口的卡口扣下。

 

3.2 安装方法

打开包装后,屏幕背面接口的情况如下图所示:

rpilcd-back-without-base00
▲ 图. 树莓派官方LCD屏背面接口

这里主要需要连接的就是LCD软排线和电源供电接口。LCD软排线连接的时候问题不大,主要就是注意接口触电的方向是否正确,不要装反了。至于电源接口,这款屏幕提供三种供电方式:

1) GPIO引脚供电(树莓派向LCD供电)

可以直接从树莓派跳VCC和GND线进行连接,然后给树莓派供电,但是这样就无法使用其他IO扩展板了(如SenseHat)。

rpilcd-back-without-base1
▲图. GPIO供电连接方法

2) USB串联供电(LCD向树莓派供电)

将LCD的Power Out端连接至树莓派,然后给LCD电源接口供电。经测试,这种方式供电经常会遇到之前说的供电不足的情况,屏幕右上角会出现彩虹图标。

rpilcd-back-without-base2
▲图. USB串联供电连接方法

3) 双USB同时供电

两个USB都单独供电,但是要保证两个USB可以同时上电,否则会出现开机没有初始化屏幕,而没有图形显示的问题。

电源和软排线连线完成后就可以固定树莓派了。树莓派的安装方法有两种:正装和反装。所谓正装,就是将树莓派以相同方向安装在LCD的铜柱固定柱上。而反装,则是将树莓派面朝LCD驱动板进行安装。反装可以节省空间,但是反装就无法再使用GPIO引脚了,所以一般情况下都是正装的。

官方屏不含支架,可以去网上购买专用的LCD支架,像我买的这款用起来就不错:

rpilcd-back-with-base
▲图. LCD亚克力支架

 

3.3 使用虚拟键盘

对于想使用触摸屏进行全部操作的朋友,可以安装一个虚拟键盘进行文字输入:

3.3.1 Florence

Suggested on the Pi forums by Hove is Florence: http://xmodulo.com/onscreen-virtual-keyboard-linux.html. Install with:

sudo apt-get install florence

 

3.3.2 Matchbox

Suggested by Alex ( the almighty @raspitv ), and scattered on various blogs, is Matchbox, which you can install like so:

sudo apt-get install matchbox-keyboard

And then find in Accessories > Keyboard.

 

4. FAQ问题解答

Q: 屏幕上下180度翻转

A: 打开SD卡中的/boot/config.txt文件,增加以下一行:

lcd_rotate=2

也可以直接使用以下命令:

echo "lcd_rotate=2" | sudo tee -a /boot/config.txt

 

Q: 程序控制背光

A: 打开背光:

echo 0 > /sys/class/backlight/rpi_backlight/bl_power

关闭背光:

echo 1 > /sys/class/backlight/rpi_backlight/bl_power

 

Q: 在Pi A, B上使用

A: 首先需要将树莓派的IIC线与LCD控制板的IIC总线手动连线在一起,包括SDA ( http://pinout.xyz/pinout/pin3_gpio2) 和 SCL (http://pinout.xyz/pinout/pin5_gpio3)。之后修改配置,在IIC总线上识别LCD:

ignore_lcd=0

注意: 其他IIC设备将无法使用。

 

Q: 屏幕左上角出现方形彩虹图标

A: 电源供电不足,请使用电流供应能力 > 2A的电源。

 

Q: 如何使用Kivy图形库

A: Kivy介绍:Kivy is a Python GUI development system for cross-platform applications. It is designed to work with touchscreen devices (phones and tablets), but also runs on the Raspberry Pi. To install Kivy onto your Pi follow the instructions at https://kivy.org/docs/installation/installation-rpi.html.

如果需要在树莓派上正常识别触摸屏输入,需要将触摸屏在Kivy中配置成为输入源。打开配置文件 ~/.kivy/config.ini ,在 [input] 一栏增加以下命令:

mouse = mouse
mtdev_%(name)s = probesysfs,provider=mtdev
hid_%(name)s = probesysfs,provider=hidinput

 

Reference

[1] Official 7” Raspberry Pi Touch Screen FAQ, PIMORONI, http://forums.pimoroni.com/t/official-7-raspberry-pi-touch-screen-faq/959

[2] Getting Started with the Pi 7" Touchscreen LCD, PIMORONI, http://learn.pimoroni.com/tutorial/pi-lcd/getting-started-with-raspberry-pi-7-touchscreen-lcd

【C语言深入】C/C++变量命名规范

目前主流的C/C++命名风格有两种:一种是Windows风格的匈牙利命名法,主要是采用类型前缀 + 变量名首字母大写,另一个就是Unix/Linux命名习惯。我自己采用的是基于Unix的变种,融合了匈牙利命名法的一些优点,在这里分享给大家。

变量名的组成:(模块名) +  (作用域) + (类型前缀) + 变量名 + (变量名后缀),解释如下:

  • 变量名 以小写的英文字母构成,词与词之间用下划线连接,如key_value, data_src; 不可使用数字,不混用大小写;
  • 模块名 声明该变量属于的模块,防止模块与模块的命名冲突。如timer_prescalar_value, DMA_channel_name等;
  • 作用域前缀 (Scope Prefix) 标注变量的作用域,提高代码可读性:
    g_: 全局变量;
    n_: 局部变量;
    t_: 中间变量;
    s_: static静态变量;
  • 类型前缀 (Type Prefix) 指明变量的数据类型:
    ptr_: 指针变量,在程序中临时需要使用指针时,也常简写为p_,如*p_src;
    h_: 句柄,如h_file;
    n_: 整形,s_: 短整形,l_: 长整形, u_: 无符号整型,可增加数据位数,如u32;
    ch_: 字符型变量;
    f_: 浮点,d_: 双精度浮点;
    b_: boolean;
    by_: byte字节型(关注数据的位特性,需要位操作的情况下使用);
    reg_: 表示寄存器;
  • 后缀 (Suffix) 指明变量的性质:
    _src: 源,_dst: 目的;
    _str: 字符串;
    _t: 在声明数据类型时使用,表示为自定义的数据类型,如u32_t;
    _st: 表示为结构体;
    _buff: 数据缓冲, msg_buff;
    _arr, _a, _m: 数组或矩阵;

变量名的取名规则:

  • 循环控制变量 i, j, k, m, n,除循环控制外应避免使用这些变量名称;
  • 函数名 使用(模块名 + )动词 + 名词的形式,同样小写 + 下划线:sys_find_file(), IO_get_data(). 后者因为IO为专用名词故破例使用大写;
  • 类名或结构体名 使用首字母大写加下划线连接:如Mystring, Datetime_type;
  • 私有类成员 Private使用下划线_前缀,如_data_src_ptr, _init_module();
  • 宏定义或常量 使用全部大写:如MAX_NUMBER, LOOP_NUMBER;
  • 缩写 使用能广泛接受的缩写:如add, ans, avg, chk, cnt, col, ctrl, def, del, dst, disp, err, freq, idx, init, len, min, max, mid, msg, num, opt, pos, ptr, recv, res, ret, src, str, sub, num, ts (timestamp), val等。

本网站的所有实例代码和项目程序都将按此命名规范进行编写。

【RPi树莓派使用指南】树莓派3代介绍及历代树莓派比较

树莓派自从12年02月最初发布之后,目前已突破800万的总销量。作为树莓派的早期支持者,云飞实验室也一直在关注着它的发展。如今在经历了4年的设计迭代之后,树莓派于16年02月推出了最新一代的树莓派3。树莓派3的本次发布与之前的2代只相差了整整一年时间,但是得益于目前芯片行业的快速发展,树莓派3的性能将会有很大的提升。这主要表现在以下几点:

  • 更高的处理速度。树莓派3首次采用了64位处理器:基于Cortex-A53的博通BCM2837。BCM2837为四处理器核心,主频也由树莓派2的900MHz提高到了1.2GHz。根据官方提供的数据,这将使树莓派3的处理速度较2代提高50%。如果和1代的700MHz单核相比,提升大约在3 - 4倍。更高的CPU速度使得树莓派可以胜任更大负荷的运算工作:如科学计算,机器人路径规划等。
  • 更高的互联性。树莓派3使用了集成蓝牙4.0和WiFi的设计。集成通信的设计的意义是多方面的。首先,使用者无需再购买额外的USB设备,从一定程度上来说,鼓励了用户在自己的设计中使用这些通信功能;其次,集成的通信模块可以进行更好的功耗管理,同时IO吞吐的性能也会得到提高;最后,可以更进一步的优化内核,只针对板载的芯片专门进行优化。避免可能出现的兼容性或者未优化的驱动导致通信性能下降问题。

rasp-pi-3-board
图1. 树莓派3外观

rasp-3_Model_B
图2. 树莓派3外观 (设计渲染图) 

从这两点来看,树莓派3代将很有可能再次扩展自己的使用领域,同时在物联网和机器人中得到应用。同类产品如果还是单从硬件角度进行提升,已经无法再与之竞争。因为性能的显著提升且维持原价,在树莓派2代出来时没有入手的玩家,这一次也难免蠢蠢欲动。同时因其性价比的纯粹提升,树莓派1代与2代的销量会大幅下降。因为2代只发布了1年,所以很可能成为绝版,如果出于收藏的目的可以尽早买之。总体上来说,这次树莓派3已经具备了IoT所需要的基本条件(性价比,互联性,体积与功耗)。今后的方向可能是增加更多的IO功能,如PWM和ADC等,是被大多数玩家需要却还没有被支持的。

以下是我对历代树莓派版本的整理与对比:

表1. 发布时间及主要特点

型号 发布时间 主要特点
PI 1 Model B 2012年02月 第一代树莓派。Model A不含以太网。
PI Compute Module 2014年04月 模块化设计,使用SODIMM大小的金手指接口。
PI 1 Model B+ 2014年07月 增加了2个USB接口,增加了9个GPIO:26脚->40脚。
使用MicroSD卡。
PI 2 Model B 2015年02月 升级处理器:四核900MHz Cortex-A9。升级为1GB RAM。
PI Zero 2015年11月 无网络通信功能,廉价,小尺寸。
PI 3 2016年02月 升级处理器:64bit四核1.2GHz Cortex-A53。
内置蓝牙4.0和WiFi。

 

表2. 树莓派历代版本硬件比较

型号 处理器 主频 内存 GPIO 互联性 功耗级别
PI 1 Model B BCM2835
(ARM11)
700MHz 512MB 26 2 USB
HDMI
10/100M Ethernet
700mA (3.5W)
PI Compute Module BCM2835
(ARM11)
700MHz 512MB 0 无。需要配合扩展板使用。 200mA (1W)
PI 1 Model B+ BCM2835
(ARM11)
700MHz 512MB 40 4 USB
HDMI
10/100M Ethernet
600mA (3W)
PI 2 Model B BCM2836
(Cortex-A9 四核)
900 MHz 1GB 40 4 USB
HDMI
10/100M Ethernet
800mA (4W)
PI Zero BCM2835
(ARM11)
1 GHz 512MB 40 (无排针) 1 Micro-USB
Mini HDMI
无板载网卡
160mA (0.8W)
PI 3 BCM2837
(Cortex-A53 64位四核)
1.2 GHz 1GB 40 4 USB
HDMI
Bluetooth 4.1
WiFi 802.11n
10/100M Ethernet
800mA (4W)

 

Read more »

【C语言深入】C语言的函数指针

函数指针是C语言指针中的一个分支:函数指针是指向函数地址的指针。和一般的指针一样,函数指针可以大大增强编程时的灵活性。这篇博文根据我的理解,简单介绍了自己对于函数指针的理解。

一、函数名的本质

在介绍函数指针之前,我们先来理解一下究竟什么是函数。以下一段代码定义了名为fun1(),接受int并返回int参数的函数:

int fun1(int x) {	// fun1是函数名
	return x + 1;	// 函数体
}

从一般的理解角度来看,fun1是函数的函数名。之所以要定义函数名,是因为在程序的其他位置要调用该函数时,可以直接使用这个别名。这种使用别名的方法和变量相似,但又不完全一样。对一个变量x而言,其意义如下:

物理地址 数值  说明
0x20000000        30 <-- x是该数值单元的别名

x是内存单元0x20000000所对应的内存单元。此处x = 30,而x的地址&x = 0x20000000.

那么对一个函数而言,函数名又代表什么呢?假设之前的fun1函数在编译后被放置在0x08000200地址,则fun1对应的内存结构如下所示:

物理地址 数值 说明
fun1 (0x08000200) fun1_entry <-- fun1是函数的入口地址

在这里,函数名代表一个记录了函数入口地址的存储单元的物理地址。可见,函数名的本质是地址。在编译阶段,函数名被转化成为对应的地址。在使用 xxxx() 函数调用的语法时,该地址被载入程序计数器PC,函数参数及当前现场被弹入堆栈。最后进行函数的实际跳转和执行。

二、函数指针是什么

对于一般变量而言,指针可以指向变量的地址,并修改变量的内容:

物理地址 数值  说明
0x20000000         30 <-- x是整形变量
0x20000004  0x20000000 <-- p是指向x的指针

这里p是指向x的指针,(即p = &x)。此时p的内存单元所存储的是x单元的物理地址,通过 *p 解析地址之后就可以访问或修改x单元的内容。同样的,函数也有其对应的指针 - 函数指针。函数指针是一种特殊的指针,其指向的对象不是变量而是函数。函数指针指向目标函数的入口地址(首地址)。这里我们定义一个指向fun1函数的函数指针pf:

int fun1(int);		// fun1是一个(含int输入参数和int返回参数的)函数
int (*pf)(int);		// pf是(指向返回int型,含int参数函数的)函数指针
pf = &fun1;

/* 也可以直接写成: */
int (*pf)(int) = &fun1;

/* 如果需要调用函数指针对应的函数,可以写为:*/
(*pf)();		// 等价于fun1()

到这里,我们回顾上一节中说到的函数名。其实函数名也可以理解成为const型的函数指针。所以在c语言中,以下调用也是合法的:

(*fun1)()		// 等价于fun1()
pf = fun1		// 等价于pf = &fun1;
/* 但是fun1的值不能被修改 */
fun1 = fun2		// 错误!fun1是const类型的指针

函数指针的声明较为冗长,如果需要定义多个同类型的函数指针。可以通过typedef定义一个函数指针类型,从而进行简化:

typedef int (*PF)(int); // 声明PF是一个函数指针类型
PF pf1 = fun1;
PF pf2 = fun2;

三、函数指针的应用场景

以上说明了如何定义函数指针,下面介绍函数指针的应用场景。函数指针最常见的应用还是作为回调函数的参数。一般在事件驱动的程序框架中,当对应事件发生时,需要触发对应的处理函数。以下代码实现了在初始化阶段,将事件与对应的处理函数关联(假设set_event_callback()是实现该功能的系统函数):

typedef int (*CALLBACK)(int);

#define EVENT_1		(0x01)
#define EVENT_2		(0x02)

int set_event_callback(const int e, CALLBACK);

void init_callbacks() {
	CALLBACK pf1, pf2;
	set_event_callback(EVENT_1, pf1);
	set_event_callback(EVENT_2, pf2);
}

在set_event_callback中,函数指针pf作为参数传递给函数使用。在基于事件编程的框架中(如一般的GUI库),函数指针经常以作为回调函数的方式出现。函数指针的另一个应用就是,根据当前程序进程的不同,要在不同条件下调用不同的处理函数:

void change_function(int nEvent, PF *ppf) {

	switch (nEvent) {
		case 0: 
			*ppf = fun1;
			break;
		case 1: 
			*ppf = fun2;
			break;
		default:
			*ppf = fun3;
	}
}

这里利用了函数指针指向的函数是可以改变的(而非函数名是const型的)。change_function() 将函数指针的地址(指向函数指针的指针)作为参数传入,并根据当前nEvent的情况更改原函数指针指向的函数。

函数指针的另一个应用场景,是允许程序在只知道函数物理地址(但不知道具体的函数名)的情况下进行函数跳转。相信大家都还记得这个经典的C语言面试题:

(*(void(*)(void))0)();
// 或
((void(*)(void))0)();

此语句的本质就是将0内存位置强制转换成为了函数指针,并调用了该函数指针指向的函数。以下代码解释了此处是如何将0地址转换成函数指针的:

typedef void (*PF)(void);

// (*(void(*)(void))0)() 等价于
(*(PF)0)()
// 或
((PF)0)()

函数指针的更高级用法就是组成函数指针数组,或和指向函数指针的指针配合使用。在此不再继续介绍,感兴趣的读者可自行研究。

参考资料

[1] Kenneth A.Reek, C和指针(第二版), 2008, 人民邮电出版社

[2] Brian W. Kernighan / Dennis M. Ritchie, The C Programming Language (Second Edition), 1989, Prentice Hall

[3] C语言中文网,C语言函数名与函数指针详解http://c.biancheng.net/cpp/html/496.html

Py | Weather Report and Forecasting with Python

OpenWeatherMap.org is a free weather forecast website, where you can get up-to-date weather reports. One nice thing about this website is that it does provide weather APIs which you can use to fetch current weather data, and use them to build your own weather-forecasting program. In this post, I will describe how to use python to access the weather APIs and fetch the current weather information.

The full documentation of the APIs can be find here: http://openweathermap.org/api. To access the APIs, you have to register a new account to obtain an API key. There are two categories of APIs that can be used for free: Current and 5 day forecast. The Current API provides up-to-date weather data, which includes current temperature, humidity, wind, etc. The 5 day forecast gives information of the weather data for the next 5 days in every 3 hours. So you will get 40 forecasts in total. The usage of the APIs is simple, just send request to specific URLs, and get data in return given in the form of json or XML.

Here I give an example of how to use the Current API:

a) Fetch data from the URL provided by the document:

# this is where you should put your OpenWeatherMap API key
# get your own key from http://openweathermap.org/
API_KEY = "5eb19937b8ba9aa2029fe7e290b8d799"

# this is the city you want to enquiry
CITY_NAME = "York,uk"

# get weather data from weather.com
try:
	f = urllib2.urlopen('http://api.openweathermap.org/data/2.5/weather?q=' \
                    + CITY_NAME + '&units=metric' + '&APPID=' +  API_KEY)
	respond_raw = f.read()
except:
        print 'Execption: URL is not correct!'
        raise

b) The default data format is json, we can unpack it with the json library:

# unpack json package into python dictionary
weather_dict = json.loads(respond_raw)

# print raw data with pprint
pp.pprint(weather_dict)

what you will get is something like this:

{   u'base': u'cmc stations',
    u'clouds': {   u'all': 36},
    u'cod': 200,
    u'coord': {   u'lat': 53.96, u'lon': -1.08},
    u'dt': 1447431162,
    u'id': 2633352,
    u'main': {   u'grnd_level': 1016.58,
                 u'humidity': 89,
                 u'pressure': 1016.58,
                 u'sea_level': 1027.66,
                 u'temp': 6.99,
                 u'temp_max': 6.99,
                 u'temp_min': 6.99},
    u'name': u'York',
    u'sys': {   u'country': u'GB',
                u'message': 0.0106,
                u'sunrise': 1447399715,
                u'sunset': 1447430881},
    u'weather': [   {   u'description': u'scattered clouds',
                        u'icon': u'03n',
                        u'id': 802,
                        u'main': u'Clouds'}],
    u'wind': {   u'deg': 257.001, u'speed': 10.62}}

c) Once you have the data stored in a python dictionary, you can generate something more readable and customized:

# print human-readable weather message
print('\n\n' + weather_dict['name'] + ' is ' + \
      weather_dict['weather'][0]['description'] + \
      ', temperature: ' + str(weather_dict['main']['temp']) + \
      ', humidity: ' + str(weather_dict['main']['humidity']) + '%.' )

which produces:

York is scattered clouds, temperature: 6.99, humidity: 89%.

Accessing 5 days forecast is very similar. But personally I think that API is not very useful, because it packs every 3 hour weather data in a single entity. This means in order to give a day-wide forecast, you have to process the data a little bit (day info is consisted of 8 entities) to find out the maximum and minimum temperatures. Also if you get more than one forecasts spreading in different times of the day (e.g., sunshine between 13:00-18:00 but raining between 10:00-13:00), you have to consider how to fairly describe the weather on that particular day . Due to these issues, I didn't spent more efforts on that but instead switch to other weather providers. If you are more interested in forecasting, just have a try and let me know if it works.

Appendix. Full Python Code

weather_from_openweather.py

#!/usr/bin/python

import os
import urllib2 
import json
import pprint

pp = pprint.PrettyPrinter(indent=4)

# this is where you should put your OpenWeatherMap API key
# get your own key from http://openweathermap.org/
API_KEY = "5eb19937b8ba9aa2029fe7e290b8d799"

# this is the city you want to enquiry
CITY_NAME = "York,uk"

# get weather data from weather.com
try:
	f = urllib2.urlopen('http://api.openweathermap.org/data/2.5/weather?q=' \
                    + CITY_NAME + '&units=metric' + '&APPID=' +  API_KEY)
	respond_raw = f.read()
except:
        print 'Execption: URL is not correct!'
        raise

# unpack json package into python dictionary
weather_dict = json.loads(respond_raw)

# print raw data with pprint
pp.pprint(weather_dict)

# print human-readable weather message
print('\n\n' + weather_dict['name'] + ' is ' + \
      weather_dict['weather'][0]['description'] + \
      ', temperature: ' + str(weather_dict['main']['temp']) + \
      ', humidity: ' + str(weather_dict['main']['humidity']) + '%.' )