1. 简介

物理层(Physical Layer)是BLE协议栈最底层,它规定了BLE通信的基础射频参数,包括信号频率、调制方案等。

BLE工作频率是2.4GHz,它使用GFSK频率调制,并使用跳频机制来解决频道拥挤问题。

BLE 5的物理层有三种实现方案,分别是1M Sym/s的无编码物理层、2M Sym/s的无编码物理层和1M Sym/s的编码物理层。其中1M Sym/s的无编码物理层与BLE v4系列协议的物理层兼容,另外两种物理层则分别扩展了通信速率和通信距离。

2. 频段和跳频

大多数无线通信的频段需要申请授权才可以使用,不同地区开辟了少量免授权频段,只要产品满足当地无线电规范,即可免授权使用。下图展示了全球免授权的频段及其分布(链接):

Free frequency band in the world

图中2.4GHz的频段很强势,覆盖了整个地图,是专为工业(Industrial)、科学(Scientific)和医学(Medical)三个机构使用,称为ISM频段。全球范围都可以免授权使用ISM频段。

BLE即工作在2.4GHz频段。

2.4GHz频段信号有明显的优缺点,优点是免费、技术成熟,缺点是频段拥挤、信号传播特性差、遇水衰减。目前除了蓝牙信号外,WIFI、ZigBee、无线键盘、无线玩具甚至微波炉都工作在这个频段,当一个空间内同时运行着多个无线设备时,频带占用情况如下图(链接):

Crowded Frequency Band

其中绿色的脉冲为BLE信号,红色信号分别是WIFI、微波炉和无线设备,它们形成了干扰噪声。

BLE工作在2.400GHz – 2.480GHz频率区间,并将这个区间均匀分为40个频道,相邻频道间隔2MHz。在不引起误解的情况下,频道也称为信道,40个信道的频率和分布如下图:

BLE frequency channels

BLE使用跳频技术来解决频段拥挤问题,即令每次数据通信发生在不同的信道上,假如某个信道拥挤,则避开该信道,选择其他可用信道进行通信。

一个简单的跳频算法是:F(n+1) = [F(n) + hop] % 37,其中hop参数为物理层自己设定的跳频参数。

实际中使用自适应跳频算法来更新通信信道。

自适应跳频的工作机制是,如果某个信道拥挤则做上标记,工作时维护一张信道表以记录各信道的拥挤情况,并将拥挤信道映射到可用信道中,然后结合上述简单跳频算法共同完成信道选择。假如简单跳频算法结果指向一个拥挤信道,则进一步跳转到它映射的可用信道上,从而实现数据通信总是工作在可用信道上。

3. 调制

3.1 调制方式

物理层定义了两种调制方式。

一种方案采用高斯频移键控GFSK,具有1MSym/s的符号速率。第二种方式是与第一种相似(Similar),但是具有2MSym/s的符号速率。

第一种方式又分成两种类型:

  • LE 1M Uncoded PHY。该方式的比特率为1Mb/s,它是BLE v4版本协议保持兼容。
  • LE 1M Coded PHY。该方式对报文进行编码,使接收端收到的报文具有前向纠正的能力,在相同误码率条件下,能够显著降低误码重传次数,从而提高通信速率。 如果采用8符号编码,比特率为125kb/s,如果采用2符号编码,比特率为500kb/s。

LE 1M Uncoded PHY是BLE协议强制要求实现的物理层,而LE 1M Coded PHY则是可选方案。

这两种实现方式符号速率都是1MSym/s。

符号速率中的“符号”是指单次采样所得到的信息,这个信息可能包含多个比特,也可能多个信息等效于一个比特。比如一个电压幅度调制系统中,用+5V表示11b, +2V表示10b, -2V表示01b, -5V表示00b,那么采样一次电压可以获得两个比特信息,此时比特率是符号速率的两倍。在LE 1M Coded PHY机制中,用8个符号表示1个比特,此时比特率是符号速率的1/8。

第二种物理层实现方式为:

  • LE 2M Uncoded PHY。该方案的比特率为2Mb/s,是可选的实现方式。

官方文档使用LE 1M PHY、LE Coded PHY、LE 2M PHY来表示以上三种不同的物理层实现方式:

物理层 调制方式 编码方案
(报头部分)
编码方案
(有效载荷)
比特率
LE 1M PHY 1Msym/s 方式 无编码 无编码 1Mb/s
LE 2M PHY 2Msym/s 方式 无编码 无编码 2Mb/s
LE Coded PHY 1Msym/s 方式 编码S=8 编码S=8;

编码S=2

125kb/s;

500kb/s

表中的S=8表示8个符号编码成1个比特。

3.2 GFSK

频率调制是将低频数据信号加载到高频载波上,数据的变化反映为调制波频率的疏密变化,如下图所示:

frequency modulation

数字化的信号仅有0、1变化,在调制时,可以定义载波频率正向偏移视为1,负向偏移视为0。这种调制方式称为“频移键控(FSK)”。数字信号发生0/1变换时,会产生大量噪声,引入高斯滤波器能够延展0/1变换时间,从而降低噪声。这种做法称为“高斯频移键控(GFSK)”。

GFSK技术成熟,实现简单,适合低功耗BLE的需求。

BLE协议规定,中心频率正向偏移大于等于185kHz视为比特1, 负向偏移大于等于185kHz视为比特0。如果选择2402MHz作为中心频率,比特1的频率应为2402.185MHz, 比特0的频率应为2401.815MHz。

4. 发射机

4.1 发射机框图

RF Transmitter

图中信号从左向右流动,基带信号经过GFSK调制分成同相(I信号)和正交(Q信号)两路信号,再依次经过DA转换和低通滤波器,然后利用频率合成器进行频率上转换,再将两个信号分量合成后通过PA放大将信号推送到天线上。

I/Q相位分量并行操作用以抑制镜像频率,PLL驱动的频率合成器可以产生稳定和精确的频率信号,其他的滤波和变换则比较容易理解。(链接

4.2 发射机参数

(1)发射功率

最小输出功率 最大输出功率
0.01mW (-20dBm) 100mW (+20dBm)

当两个设备首次连接时,应该避免将输出功率调至最大,这可能导致对端设备的接收器瞬间饱和,造成通信失败。

BLE协议按照输出功率将BLE设备分成如下几类:

功率等级 最大输出功率 最小输出功率
1 100mW (+20dBm) 10mW (+10dBm)
1.5 10mW (+10dBm) 0.01mW (-20dBm)
2 2.5mW (+4dBm) 0.01mW (-20dBm)
3 1mW (0dBm) 0.01mW (-20dBm)

第一等级值得注意,如果设备最大输出功率为+20dBm,那么最小功率等于+10dBm。

(2)调制参数

调制方式:GFSK

带宽时间积BT:0.5

调制因子:0.45-0.55

有效频率偏移为:±185kHz

时钟精度:±50ppm

(3)杂散波

使用某个频率发射随机数据,在相邻±2MHz的频点位置,杂散波功率应小于-20dBm,在相邻±3MHz以上的频点位置,杂散波功率应小于-30dBm。

(4)射频容限

中心频率偏移小于等于±150kHz。

最大频率漂移小于等于±50kHz。

最大频率漂移速率小于等于400Hz/us。

由于射频频率稳定性跟晶振有直接关联,所以频偏参数限定了外部射频晶振的误差值。

举个例子,使用16MHz的外部石英晶振为射频提供时钟,16MHz扩频到2.4GHz需要放大150倍,其误差也将一同放大150倍。假如它的误差为±50ppm,即16MHz × ±50ppm = ±800Hz,放大150倍后变成±120kHz,这几乎达到±150kHz的频偏限制,因此许多芯片都限制射频晶振的误差要小于50ppm。

假如使用24MHz的晶振,扩频倍数降低,那么相同的误差等级的晶振,将获得更优良的射频频偏参数。(不过16MHz的晶体更便宜和常用。)

5. 接收机

5.1 接收机框图

接收过程是发射过程的逆过程,但相比于发射机而言更加复杂,相关研究文献也更加丰富。

这篇项目报告(链接)详细论述了蓝牙接收机的解调器,截取其中的接收机架构框图如下:

RF Receiver

蓝牙信号进入到芯片内部,首先经过低噪声放大器(LNA),仍然是分成I/Q两个相位分量,再通过带通滤波器,使用VGA(Variable Gain Amplifier)进行放大,最后转成数字信号传入处理器中,这里框图省略了GFSK的解调过程,它位于AD转换器之后。

5.2 接收机参数

(1)误码率

BLE通信过程,可能因为外部干扰而导致数据发送失败,使用误码率BER(Bit Error Rate)表征比特传输失败的几率。

误码率过高会影响通信速率,BLE协议要求传输小型报文时,误码率要低于0.1%。这也是BLE设备RF性能测试的一个标准。

BLE协议规定了传输不同长度报文的误码率的阈值:

最大支持的Payload长度 BER阈值(%)
≤ 37 0.1
38 ~ 63 0.064
64 ~ 127 0.034
≥ 128 0.017

(2)接收灵敏度

BLE协议对于编码型和非编码型物理层给出了不同的接收灵敏度要求:

物理层类型 接收灵敏度(dBm)
LE Uncoded PHY ≤ -70
LE Coded PHY with S=2 coding ≤ -75
LE Coded PHY with S=8 coding ≤ -82

市面上的BLE芯片大多都宣称达到-90dBm甚至更低的接收灵敏度,某BLE 5的芯片其接收灵敏度甚至高达-97dBm。

在理想的条件下,假设发射机输出功率是0dBm,接收机灵敏度是-90dBm,发射机输出信号经过一段路径到达接收机,功率衰减到-90dBm,意味着这段路径上的路径损耗等于90dB。如果输出功率是20dBm,当衰减至-90dBm时,路径损耗就是110dB。

路径损耗与通信距离有如下相关性:

path loss = 40 + 25 × log(distance)

做成表格将更加直观:

路径损耗(path loss) 通信距离(distance)
50dB 2.5m
60dB 6.3m
70dB 16m
80dB 40m
90dB 100m
100dB 250m
110dB 630m

如果通信距离为630m,通信系统需要能够承受110dB的路径损耗。

当发射功率为默认0dBm,接收灵敏度为BLE协议规定的最小值-70dBm,那么可实现的最大距离为16m,这也是许多文档认为BLE是一个10米范围的通信技术的原因。考虑到大多数BLE芯片的接收灵敏度都优于-90dBm,实际通信距离应大于10米。

BLE 5的推出,极大提升了通信距离的潜力,各芯片厂商正努力提升通信距离。最近Nordic和TI针对自家BLE 5芯片做了实际测试,在良好的环境下通信距离超过1000米,令人吃惊。

(3) 抗干扰能力

一个接收机的同频道噪声抵抗能力为21dB,相邻的1MHz频点处的噪声抵抗能力为15dB。

1M PHY与2M PHY的要求略有不同。

对带外(2.4GHz范围外的频段)噪声,也应有相应的抵抗能力。

(4)最大有效功率

接收机至少在-10dBm下能够正常工作,保证BER优于0.1%。

6. 收发机

前面介绍了发射机和接收机,在实际的BLE芯片中,接收机和发射机放在同一个电路中,称为收发机(Transceiver),下图是一个2.4GHz产品框图,有实际的参考价值(链接):

RF Transceiver

(完)

读了一本介绍GCC的书《An Introduction to GCC》,这里记录一下关于GCC的功能和用法。

1. 概念

GCC(GNU C Compiler)早期是专为C语言而开发的编译器自由软件,后来加入了C++语言的支持,再后来增加了大量其他语言的支持,包括Fortran,Object-C,Java等,因此GCC这个词的涵义升级为GNU Compiler Collection。

GCC诞生于1987年,今天已有30岁高龄。

2. gcc.exe的用法

gcc.exe是GCC套件中的C语言编译器,它能够编译、调试和链接源代码并输出可执行文件。

2.1 基础用法

最简单的用法为:

gcc main.c

在命令行提示符窗口中输入这条命令,将编译main.c代码并生成一个可执行文件a.exe。

这里假设main.c中的内容是:

#include <stdio.h>
int main(void)
{
    printf("Hello World!\n");
    return 0;
}

那么在命令行中输入a.exe即可运行并打印出“Hello World!”字样。

2.2 显示警告

-Wall(Warning all)参数可以显示出编译过程中遇到的警告信息。

将上述编译命令改成

gcc -Wall main.c

此时如果源代码中有不合理的代码,则会在编译过程显示警告信息。这些警告信息包括:嵌套块注释,printf等函数的格式参数不匹配,未被使用的变量,隐式声明,返回值不匹配。

推荐所有的编译过程都使用-Wall参数。

2.3 指定输出文件

默认的输出文件名为a.exe,使用-o(output)指令可以输出指定输出文件的文件名:

gcc -Wall main.c -o main.exe

这样就能够生成main.exe而不是a.exe

2.4 编译多文件项目

假如项目中存在多个源文件,比如main.c中调用了hello.c的头文件hello.h,使用了它的函数SayHello()。

那么在命令行中执行:

gcc -Wall main.c hello.c -o main.exe

就能够正确的编译这个项目。

2.5 分步编译

-c(compile)参数用于将源代码文件编译成.o对象文件,然后再将所有的对象文件链接成可执行文件。

gcc -Wall -c main.c hello.c

输出main.o和hello.o两个对象文件。对象文件是二进制文件,不能直接使用文本编辑器读取。

命令行中main.c和hello.c的先后顺序不可颠倒。

利用这些对象文件,可以生成最终的可执行文件:

gcc main.o hello.o -o main.exe

2.6 Makefile

当项目中文件比较多,而这时更改了hello.c,希望只重新编译该文件,而不是重新编译整个项目,使用2.5中的命令行将会异常繁琐。

Makefile使用脚本的方式,将2.5中的编译过程列举出来并依次执行。

2.5中的两行命令可以用Makefile表达:

CC=gcc
CFLAGS=-Wall
main:main.o hello.o

clean:
    del /f main.exe main.o hello.o

第一行CC(C Compiler)表示C编译器类型,第二行CFLAGS表示参数开关,第三行表示输出文件是main.exe,依赖文件为main.o和hello.o,这两个文件会自动从对应的.c文件中编译生成。最后的clean是一个make.exe的命令函数。注意,最后一行del前需要是一个Tab,而不是空格。

将文件保存在当前目录下,文件名为Makefile,没有扩展名,在命令行中执行:

make

即运行make命令,不带任何参数,将会自动读取当前目录下的Makefile并执行内容。执行完毕将生成main.exe文件。

还可以执行Makefile中设定的clean函数:

make clean

则自动执行删除main.exe, main.o和hello.o文件的操作。

现代的IDE都会使用Makefile来编译项目文件。

2.7 链接外部库

假如使用的是MinGW版本的GCC,自带的库文件通常放在:\MinGW32\lib或\MinGW32\lib32目录下。

观察其中的库名称,会发现所有的库文件名都以lib作为前缀,扩展名为.a。

假如源代码中使用了math.h库,这个数学库需要调用标准库libm.a文件,则需要在命令行中指明:

gcc -Wall main.c -lm -o main.exe

这里的-lm要拆开理解,-l(Library)表示链接外部库,m表示文件libm.a去除了lib前缀和.a扩展名的剩余部分。假如库名字为libABC.a,调用时候就是-lABC。

如果不显式调用库文件,则会造成编译失败。

同时,要注意-lm参数是放在main.c的后面,表示与该文件的调用关系,顺序不能颠倒,位置不能出错。

2.8 库和头文件路径

假如库或者头文件放在其他目录,有两种办法可以通知编译器:

  • 将那些目录加入到环境变量
  • 中命令行中指定那些目录

命令行中指定头文件目录参数是-I(Include_Path),库目录参数是-L(Library_Path),注意都是大写。

假如main.c调用的头文件中父目录的include目录中,库文件在父目录的lib目录中,那么命令行可以写成:

gcc -Wall -I/../include -L/../lib -lm main.c

2.9 静态库和动态链接库

静态库的后缀是.a,编译时会将其完整的复制到最终的可执行文件中,即静态库最终会成为可执行文件的一部分。

动态链接库后缀是.so(这里仅考虑GNU编译平台,不考虑Windows的DLL),仅在执行前将库文件中需要的那部分代码加载到系统内存中,执行结束则释放,是一个动态的链接过程。这部分内存代码,可以共享给多个不同程序使用,所以也叫共享库。

动态链接库可以让程序体积减小,如果更新库本身,只要接口维持不变,则不会影响应用程序。

如果平台针对一个功能同时提供静态库和动态链接库,那么编译器会优先使用动态链接库。

动态链接库的使用方法与静态链接库相同。(此处存疑,因为书中介绍不甚清楚。)

2.10 指定标准

C语言有C89,C99,有ANSI纯净版和GNU扩展版,各个版本略有不同。

-std(standard)参数可以指定C语言标准版本:

gcc -Wall -std=gnu99 main.c

2.11 宏开关

假设代码中使用了宏开关:

#ifdef ON
{//xxxxxx}
#end

-D 参数可以可以指定这个ON宏:

gcc -Wall -DON main.c

这等同于在代码中使用:

#define ON

2.12 宏参数

假设代码中使用了宏参数:

#if OPEN==1
{//xxxxxx}
#endif

仍然在命令行中使用-D参数:

gcc -Wall -DOPEN=1 main.c

这等同于执行代码:

#define OPEN (1)

如果希望OPEN等于空,那么可以使用双引号进行包裹空字符:

-DOPEN=""

3. gdb.exe的用法

gdb是C语言调试工具。在生成了main.exe之后,执行:

gdb main.exe

这样就打开了gdb的子环境。在这个子环境中,q表示退出,在任何时候输入q字母并回车,就退出gdb环境。

  • 在gdb中输入file main,表示读取main.exe程序中的符号列表。
  • 运行run命令,将开始执行main.exe。
  • 假如main中有一些全局变量,则可以使用print xxx来打印出来。
  • 使用break 函数名来设置断点,然后step表示单步执行。

跟常用的IDE上的调试功能一致。

gdb的使用值得使用一本书来介绍,这里点到为止。

4. 代码优化

4.1 基本手段

代码层面的优化包括:

  • 表达式消除
  • 函数内联化
  • 循环展开
  • 指令调度

比如在代码中用到的加减乘除运算,在编译器会直接得到结果再进行编译,这样就减少了可执行程序的运算步骤。

一个”小“函数,如果频繁的被调用,则将其内联到具体的调用者体内。

所谓循环展开是指将某些赋值循环展开成扁平的赋值操作。因为一个循环,必然存在检测退出机制,而大多检测操作是无用的操作,所以展开后能够减少指令数。

指令调度则是重新排列指令顺序,以加快执行速度。

4.2 优化选项

上述的一些优化措施,已经集成到了gcc内部,以-o(Optimize)参数形式提供:

  • -o0
  • -o1
  • -o2
  • -o3
  • -os

-o0表示没有任何优化,等同于不使用该参数。适合调试时候使用。

-o1清除代码层面上的冗余,未做深入优化。这个是最常用的优化级别。

-o2使用了指令调度优化,它没有增加最终文件的大小,却提高了速度,所以适合做程序发布时候的优化项。

-o3使用了较多技术,增加了最终程序的大小,带来了速度上的提升。这种优化项有时候可能不稳定。

-os专为为减小最终程序大小而做的优化项。

书中举了一个例子(链接),在函数中做大量、复杂的数学运算。如果不做优化,程序执行时间为13秒,不同的优化选项能带来不同的提升,-o3选项下,程序执行时间减小到5.4秒,改善比较可观。

5. 汇编文件

使用cpp.exe可以将源代码文件进行预处理,生成.i文件。

cpp main.c > main.i

这个main.i文件可以进一步转成汇编文件:

gcc -Wall -S main.i

在当前目录下将生成main.s文件,使用编辑器打开可以看到汇编代码。

进一步可以使用as.exe将汇编文件打包成对象文件:

as main.s -o main.o

然后使用链接器ld.exe生成可执行文件,但是ld命令的参数比较复杂,通常直接利用gcc把这些事情给做了。

6. 其他功能

6.1 输出符号列表

nm.exe可以输出可执行文件的符号表:

nm main.exe > main.map

输出的map文件包含了各个符号的存储地址和类型。

6.2 执行时间

gprof.exe可以记录各个函数的调用次数、执行时间等。

gprof main.exe

这个main.exe需要预先使用-pg指令进行处理:

gcc -Wall -pg main.c -o main.exe

可以参考这个示例(链接)进行操作。

6.3 代码执行次数

gcov用于生成测试报告,记录源代码中每一行被执行的次数,如果代码行未被执行,也会被统计出来,所以叫做覆盖性测试。

先生成特殊处理的可执行文件:

gcc -Wall -fprofile-arcs -ftest-coverage main.c

执行main.exe,生成必要的中间文件:

main.exe

然后再使用gcov.exe读取main.c:

gcov main.c

此时观察源代码所在目录,会有一个main.c.gcov的文件,打开它就能够看到各个代码行被执行了多少次。

7. 小结

至此,对gcc的各功能有了概念性的了解,以后再遇到编译器选项、符号表、优化等问题,可以再做深入探究。

这本书是免费书,阅读地址:http://www.network-theory.co.uk/docs/gccintro/index.html

比较喜欢这种100页的书,一两天就读完了,好开心。

(完)