添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

前言

很久没有更新技术博客了。最近一年都在做低功耗蓝牙和物联网等相关的事情,想把一些经验和心得写成博客分享出来,也算对自己工作的一种总结。开一个系列讲解NXP的低功耗蓝牙SDK开发的方方面面,目前NXP官方仅有一个英文版的《BLE Application Developer Guide》介绍SDK的使用,对于国内开发者来说不太友好。希望这个系列文章能作为官方文档的补充,帮助到正在使用和将要使用NXP蓝牙的芯片的朋友们。

weil家深入这个通用的低功耗蓝牙SDK与协议栈,迅速掌握NXP低功耗蓝牙的开发。假设用户已经具备了基本的BLE概念,如Profile、GAP、GATT、HCI、LL等各个层次的职责,广播与连接的行为,基于GATT数据传输的方式等。如果读者对这个概念还不熟悉,可以阅读《Getting Started with Bluetooth Low Energy》一书的第1至3章共75页内容,利用业余时间在2-3天内可以轻松完成。

NXP BLE SoC概况

目前NXP面向物联网和可穿戴设备数据传输应用的BLE SoC芯片要有QN和KW两大系列(型号特点见下表),除了QN902x使用原Quintic自带的协议栈SDK外,其他几个型号都采用了NXP MCUXpresso SDK和NXP自研的Bluetooth协议栈,呈现给用户的编程接口完全一致。无论是开发超低功耗的QN9080,支持Zigbee/Thread/BLE多模的KW41,还是面向汽车应用的KW36,对固件工程师来说,掌握了一款芯片的开发,拿下其他的也就是轻而易举之事。

存储器资源

MCUXpresso SDK与NXP BLE协议栈

MCUXpresso SDK是NXP面向MCU市场推出的一套完整的软件开发套件,支持Kinetis, LPC, i.MX RT等通用微控制器以及QN和JN系列的无线连接微控制器。一个SDK包内包含了芯片硬件抽象层,外设驱动库,多个中间件库(根据芯片功能的不同包含不同的中间件,比如带Ethernet外设的MCU则会有lwip网络协议栈中间件),以及丰富的示例代码。

对于低功耗蓝牙MCU,它的BLE协议栈作为MCUXpresso SDK的一个中间件而存在。在用户解压缩SDK包后,在./middleware/wireless/bluetooth_x.y.z下可以找到协议栈的代码和库文件。低功耗蓝牙应用将包含这个目录下的文件,以使用协议栈提供的API和服务。所有低功耗蓝牙的示例代码可以再./boards/ /wireless_examples/bluetooth目录下找到。同时,低功耗蓝牙应用与通用MCU SDK还有一点差异,它需要一些额外的通用组件作为支撑,如OS环境抽象、非易失存储、队列管理,低功耗管理等,因此多引入了一个connectivity framework的中间件层。在./middleware/wireless/framework_x.y.z下可以找到framework包含的所有功能模块源代码。下图展示了SDK的文件结构中与低功耗蓝牙相关的目录。

如果基于MCUXpresso IDE开发,通常会导出某个示例工程到workspace目录下,被导出的工程与解压缩后的SDK的layout稍有不同,它仅包含了该示例代码所需的驱动和中间件,并以一个平铺的方式组织文件夹,但文件夹下的内容并无区别。

NXP BLE SDK系统架构

NXP提供了一套完整的BLE协议栈和编程框架,并已通过了Bluetooth Core Spec 5.0的认证。用户第一次开发自己的应用时,应当先大致了解整个系统的组成,知道每个部分的职责,分别有哪些文件。有了一些基础,再通过查询API文档,很快便能掌握BLE应用开发。下图为BLE SDK总体的框架图,图中使用了几种不同的颜色来区分:

  • 应用(灰)
  • 低功耗蓝牙服务框架(黄)
  • Connectivtiy Framework(绿)
  • BLE协议栈(蓝)
  • 通用SDK(白)
  • OSA

    OSA属于Connectivity Framework,单独介绍它是因为OSA服务于整个SDK和协议栈,并且也是系统复位后的入口。整个蓝牙协议栈和SDK的设计使用了RTOS下才有的多任务,消息和事件等服务。为了减少代码大小和栈的内存,适配一些资源紧张的SoC,在设计时才引入了OSA。

    OSA提供一套接口封装,底层可以使用FreeRTOS或者Bare Metal前后台两种实现。如果底层为FreeRTOS,OSA只是简单的将FreeRTOS API转换为OSA API。如果底层底层为Bare Metal系统,OSA则内部实现了一套多任务、信号量、事件等RTOS API的模拟实现,当用户调用OSA_Start启动多任务调度环境时,实际上是陷入了一个while(1)的循环,在这个循环中会不断的检查任务队列的状态并选择优先级最高的任务来运行,这些任务之间无法抢占,因此每个任务的设计需要额外小心,占用CPU的时间最好不要超过2ms,以确保处理BLE事件的任务能及时得到调度。在编写OSA的任务代码时,有几个不同与一般RTOS的地方需要注意。

  • 任务体while(1)循环的最后需要加上一个条件判断,如果是gUseRtos_c==0,则直接跳出循环,相当于每次任务只执行一次。
  • 任务体while(1)循环之前的初始化工作需要特殊化处理,定义一个静态或者全局变量initailized的,并对它进行测试,确保在当OSA运行在Bare Metal配置时,这些初始化代码仅会运行一次。
  • OSA_EventWait, OSA_SemphoreWait, OSA_MutexLock等函数在OSA的FreeRTOS实现时会阻塞任务,而在Bare Metal实现中,调用这些API不会阻塞,而是通过内部一个状态机来检测资源,如果资源目前是不可获得状态,则直接返回。
  • OSA_TimeDelay函数在BM实现时是不会切换到其他任务运行的,而是在原地等待直到超时。
  • OSA的源码可以在实现./middleware/wireless/framework_x.y.z/OSAbstraction下找到,如感兴趣可以深入研读。

    SDK提供的每一份BLE示例代码中都提供了freertos和bm两个工程。

    Connectivity Framework

    Connectivity Framework包含了众多的子模块,服务于应用和BLE协议栈。本系列文章将会抽取其中几个比较重要的几个子模块作为单独的文章来分析。本文希望先给读者一个总体概念,通过表格列出了各个模块的职责和必要性。标注为“必要”的模块如果缺失,最后整个BLE工程构建将会失败,说明该模块已被协议栈或者其他的代码引用了。标注为“非必要”的模块目的是服务应用层,如果用户的应用不需要这个功能,则可以移除。

    BLE协议栈

    BLE协议栈以闭源库的形式提供,分为Host和Controller两部分。下图描述了组成Stack的主要文件和相互之间的依赖关系。 image.png

    Controller Stack负责与硬件和时序相关的蓝牙链路层处理,它通过HCI接口与Host Stack进行协同工作完成BLE数据交互。由于使用BLE SDK时几乎不需要直接与Controller Stack打交道,我们知道它的存在即可。

    Host Stack是一个与硬件无关的C库,它通过HCI接口向Controller Stack发送命令和接收消息,负责完成Bluetooth Core Spec中的L2CAP, ATT, SM, GATT, GAP等多个层次职责。Host Stack通过一组头文件暴露出协议栈各个层的API和回调事件接口,理解Host Stack提供的服务和数据结构对于掌握BLE SDK开发至关重要。本系列专题将以独立文章分享各个部分服务的具体应用,这里先将协议栈的两类主要接口介绍给大家。

  • API
    API即应用程序编程接口,是外部应用主动向协议栈发起请求的入口。协议栈所提供的绝大部分API都是异步的,利用Messaging消息队列向协议栈的Host任务发送一条后便返回。实际的处理是在Host任务中完成,再通过某个注册的callback回调函数来反馈执行的结果。

  • Callback注册回调函数
    Callback注册回调函数是由应用程序提供的一个钩子函数,在协议栈内部处理中如果有需要通知到用户层事件发生或者数据变化时,将调用注册的钩子函数通知应用程序做出响应动作。

    下面一段示例代码演示了Gap_Connect()和Gap_Disconnect()两个异步API的行为和他们所触发的callback回调函数后对应事件的响应。通过这段代码可以大致了解应用代码如何与协议栈进行交互。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    // 1. initiate API to request and connection and register a callback
    bleResult_t rt;
    rt = Gap_Connect(&gConnReqParams, Gap_ConnectionCallback);
    if (rt != gBleSuccess_c) {
    printf("Gap_Connect API fail, reason = %d\r\n", rt);
    }
    // when PC(R15) arrives here, the connection may NOT be setup
    ...
    // 2. initiate API to request a disconnection action.
    rt = Gap_Disconnect(peerDeviceId);
    if (rt != gBleSuccess_c) {
    printf("Gap_Disconnect fail, reason = %d\r\n", rt);
    }
    // when PC(R15) arrives here, the connection may NOT be disconnected
    ...
    // 3. Event will be triggered when connection and disconnection happens
    void Gap_ConnectionCallback(deviceId_t peerDeviceId, gapConnectionEvent_t* pConnectionEvent)
    {
    switch (pConnectionEvent->eventType)
    {
    case gConnEvtConnected_c:
    // async event to notify that link is connected
    printf("Connected to device (%d)\r\n", peerDeviceId);
    break;
    case gConnEvtDisconnected_c:
    // async event to notify that link is disconnected
    printf("Disconnected to device (%d)\r\n", peerDeviceId);
    break;
    ...

    低功耗蓝牙服务框架

    低功耗蓝牙服务框架这个名称是笔者取给SDK中非协议栈部分的蓝牙代码取一个名字,涉及内容主要包括有HCI、FSCI、Profiles、Connection Manager, Service Discovery, App Thread, BLE Initialization, Stack Runtime Environment等几个部分,顾名思义是这些代码都是为实现BLE应用而服务的,目的是简化用户开发的难度。

  • Stack Runtime Environment
  • 上一小节介绍的Host和Controller协议栈库提供的是一组可链接的符号,需要外部创建任务以提供运行时环境和创建必要的队列和事件资源,这就是Stack Runtime Environment的职责。协议栈提的了两个任务处理函数Host_TaskHandler()和Controller_TaskHandler(),分别在创建的Host_task和Controller_task里调用。另外任务处理函数还需要用到队列和事件资源,用户任务与Host_task交互、以及Host_task与Controller_task的交互都依赖于他们。下表说明了所创建资源的用途。

  • App Thread
  • App Thread是在系统启动后由OSA创建的第一个任务,它在完成BLE系统初始化后创建了一个应用后台,主要目的是服务Host stack触发的各类callback回调事件。通过Host_stack提供的Registeration API注册的回调函数被调用的上下文都是前面介绍的Host task里,如果在这些callback做一些耗时较长的用户动作(比如上面的printf打印输出),Host task其他部分会得不到响应,从而影响BLE协议交互。因此很有必要将这些callback回调事件的处理放到一个相对低优先级任务中,这个任务就是App Thread。首先Applmain.c中对Host stack提供的回调函数接口都做了实现,这些实现并不作实际处理,而是转发一个消息到App Thread。App Thread一直处于等待事件的状态,收到事件后再分发到各个自定义的callback中作相应处理。App Thread同时还可以通过等待另外一个队列来接收其他任务发送的Application消息并处理,以方便用户实现类似于协议栈的异步处理机制。下表列出了App Thread的队列和时间资源以及他们的用途。

    下面代码段以App_Connect为例来展示了代码是如何通过App Thread来处理Host stack回调函数的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    // User call App_Connect, instead of Gap_Connect
    bleResult_t App_Connect(gapConnectionRequestParameters_t* pParameters,
    gapConnectionCallback_t connCallback)
    {
    pfConnCallback = connCallback;
    return Gap_Connect(pParameters, App_ConnectionCallback);
    }
    // Simplified App_ConnectionCallback, the function insert a message and notify App Thread
    void App_ConnectionCallback (deviceId_t peerDeviceId, gapConnectionEvent_t* pConnectionEvent)
    {
    appMsgFromHost_t *pMsgIn = NULL;
    uint8_t msgLen = GetRelAddr(appMsgFromHost_t, msgData) + sizeof(connectionMsg_t);
    pMsgIn = MSG_Alloc(msgLen);
    // use Applmain defined message type
    pMsgIn->msgType = gAppGapConnectionMsg_c;
    pMsgIn->msgData.connMsg.deviceId = peerDeviceId;
    FLib_MemCpy(&pMsgIn->msgData.connMsg.connEvent, pConnectionEvent, sizeof(gapConnectionEvent_t));
    MSG_Queue(&mHostAppInputQueue, pMsgIn);
    OSA_EventSet(mAppEvent, gAppEvtMsgFromHostStack_c);
    }
    // Pseduo code of App_Thread
    void App_Thread (uint32_t param)
    {
    while(1) {
    OSA_EventWait(mAppEvent, osaEventFlagsAll_c, FALSE, osaWaitForever_c , &event);
    if (event & gAppEvtMsgFromHostStack_c) {
    while (MSG_Pending(&mHostAppInputQueue)) {
    pMsgIn = MSG_DeQueue(&mHostAppInputQueue);
    // check msgType and dispatch callback handler
    if (pMsg->msgType == gAppGapConnectionMsg_c) {
    pfConnCallback(pMsg->msgData.connMsg.deviceId, &pMsg->msgData.connMsg.connEvent);
    } else if (pMsg->msgType == ...) {

    通过下图可以全面的了解到App Thread, Host task和Controller task三者之间是如何完成交互的。掌握了这张图的消息交互流程,开发基于NXP BLE SDK的应用也就变的非常容易了。

  • HCI
    HCI是低功耗蓝牙Controller协议栈和Host协议栈之间的沟通桥梁,对于SoC,两个协议栈都运行在同一芯片上,这部分功能实际上是简单的函数调用。当BLE SDK被配置为只作为controller Only模式(用于DTM),或者Host Only模式(controller使用另外一颗芯片)时,HCI的就需要与通过SerialManager来完成与外界通讯。

  • FSCI
    FSCI是 F ramework S erial C ommunication I nterface的缩写,该模块定义了一套统一的接口将无线通讯微控制器(BLE,Thread,Zigbee)所提供的服务提供给另外一颗主处理器或者PC系统。对于BLE SoC而言,片上运行了完整的BLE Host和Controller协议栈,此时处于Network Processor网络处理器模式。外部处理器在只需要遵循FSCI协议便能控制SoC发起广播、建立连接和进行数据交换。

  • Profiles
    在GATT之上,Bluetooth SIG定义了多个标准的GATT based Profile帮助应用层互联互通,同时每个厂家也会定义一些简单的私有Profile来实现raw数据传输、OTA等功能。由于所有的Callback回调事件处理都是在应用中完成的,Profile在NXP BLE SDK中职责比较简单,主要负责完成Profile数据到GATT数据库的操作转换。

  • Conneciton Manager Service Discovery
    连接建立和服务发现是每个BLE应用都需要经历的过程,BLE SDK将这部分通用的代码从应用层分离了出来,形成两对独立的.c/.h文件ble_conn_manager.c/h和ble_service_discovery.c/h。这样减少了应用层重复的代码,提高代码可维护性。这两个部分服务的功能在后面介绍蓝牙SDK具体编程的文章中都会有所涉及。

  • GATT Database
    通过一套宏定义的方法,完成静态或者动态GATT数据库的建立。第一次看到gatt_db.h会很难以理解里面的特殊格式,但要是“照猫画虎”的增加自己的service或者characeteristic还是比较简单的,这就是它的神奇之处。写一篇文章专门讲解gatt_db模块到底是如何通过一系列的宏定义来成这个工作的。

    应用

    BLE SDK的每个应用示例都包含了十分类似的应用层文件,下表简单描述了各个文件的职责。其中 .c/h是整个应用的核心。

    处理器都会带有JTAG/SWD的调试端口进行侵入式的调试,但是在许多应用场景下这种方式仍是不太适合的。比如调试一个无线通讯系统,你希望捕获其中一些讯息但又无法停下来处理器来进行断点观察,因为一旦暂停则无法处理正在进行交互的信息而导致系统工作不正常。这种情况在代码中嵌入printf打印代码来调试最佳。

    但当软件成规模的时候,控制台会有一堆的打印log产生,可能会造成几个负面影响:

  • 影响代码体积
  • 难以找到需要关注的信息
  • 如果手动去禁止和打开log无疑是不可取的,那么有什么简单而有效的来管理这些log呢?以下代码提供了一个很好的解决方案。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <stdio.h>
    #include "debug_config.h"
    #if defined (CFG_MODULE_x_DEBUG_EN) && (CFG_MODULE_x_DEBUG_EN > 0)
    #define MODULE_x_DEBUG printf
    #else
    #define MODULE_x_DEBUG 1 ? (void) 0 : (void) printf
    #endif
    void func(void)
    {
    MODULE_x_DEBUG("Hey I'm module x prompts\r\n");
    }

    上面打印语句在

    CFG_MODULE_x_DEBUG_EN 1```的时候等同于
    1
    2
    ```
    printf("Hey I'm module x prompts\r\n");

    而当

    CFG_MODULE_x_DEBUG_EN 0```或者没有定义的时候等同于
    1
    2
    ```
    1 ? (void) 0 : (void)printf("Hey I'm module x prompts\r\n");

    这句话句在编译阶段就会C编译器优化并不会对代码体积或者运行速度有影响。

    通过这个方法在 debug_config.h" 中对于多个 CFG_MODULE_x_DEBUG_EN 宏定义进行管理可以很好做到单独使能或者禁止各个模块的打印信息。

    I2C几乎是嵌入系统中最为通用串行总线,MCU周边的各种器件只要对速度要求不高都可以使用。优点是兼容性好(几乎所有MCU都有I2C主机控制器,没有也可以用IO模拟),管脚占用少,芯片实现简单。I2C协议虽然简单,实际使用过程中小毛病还不少。今天先来看一个平日最为常见的问题:I2C从机挂死。

    很多事情不难而且经常碰到,每次自认为懂了但最终让你站出来说清楚的时候却总是不能自圆其说,很难受。所以我决定写博客的时候就想尽量把内容写清楚详细甚至是透彻,希望让每一个阅读博文的同学都能看得明明白白,学会一点小知识。如果还有不清楚的可以留言交流.

    I2C规范与特性

    I2C是什么,我相信99%的同学能点到这篇博文对I2C也有了一定的了解,这里附上一份I2C鼻祖NXP (前Philips半导体)的一份权威手册: I2C-bus specification and user manual v.6

    描述一下I2C最重要的几个特性,为了后面描述问题和解决方案作一些铺垫。

  • I2C是由两根线(时钟SCL + 数据SDA)组成的多主多从串行同步通信总线。
  • 规范要求接入I2C的器件,SCL时钟和SDA数据线都必须是双向开漏结构的,通过总线上的上拉电阻拉到逻辑高电平。这样的结构可以实现 线与(&) 功能。
  • 一般情况下I2C的SDA只有在SCL为低电平的时候才能改变,为高电平的时候需要保持。对应到芯片设计上则是上升沿采样,下降沿变化。
  • 两个例外情况由主机发出的总线起始条件START(SCL为高时SDA由高变低)和停止条件STOP(SCL为高时SDA由低变高)
  • 挂死 = 挂了 + 死机

    挂死这个词应该来源于英文 hangs : To cause (a computer system) to halt so that input devices, such as the keyboard or the mouse, do not function .

    前面提到因为 线与& 结构,是I2C总线设计上最关键的特征,用了这种结构才能实现

  • 多主机仲裁同步
  • 慢从机同步快主机
  • 因为这个特性,只要总线上任何一个器件拉低了SDA或者SCL,其他器件都无法拉高它们,看到的都是低电平。如果有器件不释放总线,则整个总线上的通讯都会被暂停,我们成为 I2C bus hangs :I2C总线挂死

    因为I2C主机一般是可编程的器件,受我们控制,如果主机主动拉低了总线,我们可以通过调试代码了解原因,也可以很方便的通过复位I2C外设或者复位芯片来退出这种状态。而I2C从机往往不带RESET引脚,如果挂死了总线即使整个系统复位都无法解除,仅重新上下电才可以恢复。很多系统上是不可接受的,因此我们需要更加小心的处理I2C从机挂死的情况,下面分析也是针对I2C从机挂死来写的。

    SDA挂死

    先来看下哪些情况下I2C从机会需要拉低SDA线。

  • 主机向从机写数据或地址时,从机如果发出ACK应答,则会第9个CLK的期间拉低SDA
  • 主机读数据的时候,从机会在bit为0时对应的CLK期间拉低SDA
  • 那什么情况I2C从机又可能钳住SDA线呢?我们先来看一个典型的I2C主机发起对某一器件地址读操作,读到的数据为10011000b,MSB在先也就是0x98。在图中地址字节第9个CLK期间从机拉低SDA表示对地址进行应答,在返回的数据字节的第2,3,6,7,8几个CLK器件从机拉低SDA输出逻辑0电平。

    根据上面讲的I2C协议SCL为高的时候,SDA电平应保持,而等到SCL为低后(也就是下降沿后)才能发生改变。如果在上面几个CLK的前半个周期SCL拉高后主机不再拉低呢?从机会有什么动作?YES,从机会持续拉低着SDA, 直到见到下一个他应该输出高电平 的下降沿。

    最常见的情况就是主机在通讯的过程中产生了复位。由于复位动作通常会立刻执行,外设状态机都恢复到默认状态,也就发不出完整的CLK了。那么等到主机复位完成回来后,SCL为高,SDA被从机拉低。主机无法发起START起始条件,不能开始下一次与从机的通讯,这称为SDA挂死。

    要想办法恢复,我们先得知道从机什么时候会释放SDA。由于刚刚的SCL下降沿没有给出来,恢复总线要做的第一件事情就是在想办法用GPIO在SCL线上模拟一个下降沿,让从机状态机继续走下去。只发一个下降沿并不一定能将SDA释放,因为我们并不清楚当主机复位异常发生时刻从机到底处于图中哪一个状态,所以需要逐个CLK去探测,直到见到SDA被释放了,我们才终止并且发送STOP条件告诉从机这次坑爹的通讯结束了。

    网上通常的传授的方法是模拟9个连续的CLK,但是我更喜欢上面的方法,一是速度快,二是具备可确定性。发送9个CLK我主要担心从机在最后一个CLK时又拉低了SDA,还是需要用到上面的方法来释放。

    通过模拟几种情形来实际体会一下(从机对SDA的操作红色表示):

    如果在地址字节第9个CLK拉高后主机复位。在模拟的第一个时钟低电平期间就可以看到SDA的释放,随后主机先拉低SDA,再模拟一个STOP结束条件。

    在数据字节第2个CLK拉高后主机复位,在第二个模拟的时钟低电平期间才看到SDA释放

    在数据字节第6个CLK拉高后主机复位,在第三个模拟的时钟低电平期间才看到SDA释放

    通过以上三种情况的分析,想必你已经非常清楚改如何处理了,最后附上一个程序处理流程图:

    SCL挂死

    I2C从机主动拉低SCL线在规范中是一个合法的行为,称之为Clock Stretching(时钟扩展,我一般叫他时钟同步)。通常是主机请求数据( 收或者发)后从机需要一些时间处理,且没有多余Buffer可以接收接或者提供接下来的数据的时候从机则会拉低SCL 一段时间 直到有新的数据准备好。

    SCL挂死(也就是前面所说一直拉低SCL)这种情况在标准I2C从器件上基本不会出现,因为只要芯片还在正常工作buffer总算有准备好的时候,自然就就释放SCL了。往往是使用用户使用MCU作为I2C从机时,程序设计上的问题导致MCU无法读取&填充buffer而导致,重点分析MCU I2C中断服务程序。

  • I2C中断服务程序被意外屏蔽
  • 中断服务程序中陷入了一些标志位查询的 while(flag != xxx) 死循环
  • I2C功能系统被意外禁止
  • 查看支持分辨率

    第一次安装了Raspbian后启动系统会模式使用显示器“兼容模式”,这是一个VGA @ 60Hz的设置保证所有的HDMI连接到任何显示设备都能有显示。进入桌面后用户可以通过系统配置文件/boot/config.txt来修改分辨率

    首先启动Shell输入命令查看系统所支持的模式:
    如果用的是显示器:

    1
    /opt/vc/bin/tvservice -m DMT

    如果接的是电视:

    1
    /opt/vc/bin/tvservice -m CEA

    在我的DELL显示器上给出的列表中:

    1
    2
    3
    4
    5
    6
    Group DMT has 11 modes:
    mode 4: 640x480 @ 60Hz 4:3, clock:25MHz progressive
    mode 6: 640x480 @ 75Hz 4:3, clock:31MHz progressive
    ...
    (prefer) mode 82: 1920x1080 @ 60Hz 16:9, clock:148MHz progressive
    mode 83: 1600x900 @ 60Hz 16:9, clock:108MHz progressive

    选择合适分辨率

    一般选择最优的模式,即1920x1080 @ 60Hz。
    下面命令修改并确保参数是正确的,CTRL+O 回车保存,CTRL+X退出。

    1
    sudo nano /boot/config.txt

    1
    2
    3
    4
    5
    ...
    #hdmi_safe=1 // 禁止safe模式
    hdmi_group=2 // 1=电视, 2=显示器
    hdmi_mode=82 // 1920x1080@60Hz
    ...
    1
    Reboot

    作死

    手痒试一试选择不支持的分辨率(e.g. mode=3)…果然…黑屏了….

    也不用担心,默认启动的时候按住SHIFT进入Recovery状态,Edit Config中也可以编辑/boot/config.txt文件,修改回正确数值即可。

    思考三层云服务的合理比喻

    在网上看到一些把三层云服务比喻成吃披萨,开养猪场等例子,相当生动形象。 知乎: 谁能举个通俗易懂的例子告诉我IAAS,SAAS,PAAS的区别? 但有一点似乎都被大家都忽略了,三层云服务所面对的客户是有所区别的。我觉得有必要把比喻打得更加贴切一些。写下自己的理解,从买一杯咖啡开始。

    SaaS:软件即服务 = 开个咖啡店

    SaaS 是 S oftware a s a S ervice 的缩写

    在穿过海岸城广场去往地铁站的路上,一字排开的临街商铺前总热闹非凡。各式各样的门店来满足着消费者的生活需求:

    SaaS 服务提供商跟这些商铺一样,也是云服务中与终端消费者日常接触的一层,来满足各种不同的应用需求。困了我会买一杯香浓的咖啡,不需要自己选咖啡豆、买咖啡机再翻出各种攻略来研究怎么煮一上壶。同样我有文件要存储共享我会使用云盘,而不是自己建个存储平台再搭个FTP什么的,为了可靠安全的存储我可能会选择一些大公司产品,比如iCloud Drive,所以付点钱也是值得的。

    除了办公、存储、社交、视频这些面向个人消费者的 SaaS 云服务,还有很大一部分是面向企业的SaaS,如Saleforce, GoToMeeting等。如果把企业看做一个消费者,这之间没有什么实质差别。

    角色扮演: