BC0002-CSR101x系列开发工具概述

CSR在传统蓝牙领域市场,尤其是高端蓝牙市场占据了半壁江山,其开发门槛也比较高,拿CSR8670来说,开发者不光要购买硬件开发套件,还必须为开发软件掏钱,有了开发套件,还必须要有一定的技术实力去消化吸收这个开发平台,这样高的门槛,把一部分客户就挡在了门外,但也正因为是这样的高门槛,才能筛选出最有实力的客户。同样,在最新的蓝牙4.0领域,早在2012年左右,CSR就早早推出了CSR1000、和CSR1001,但由于其用户可用程序空间非常小,目前新开发的项目早已经用CSR1010和CSR1011代替,当然目前已经出现了更多的型号–CSR1012、CSR1013等等,此类芯片的一个特点是单模,与市面上的CC2540、DA14580等都是同一类芯片,目前此类芯片可谓是百家争鸣,竞争非常激烈。

在激烈的竞争下,CSR一直保持着自己的一贯策略,对于一般客户,开发工具需要购买,这可能是CSR对自己蓝牙信心的一种体现。

关于软件的获得,CSR官方的support网站可以下载到,前提是你需要跟CSR签订保密协议,保密协议一般需要寻找当地的代理商签订,签订后将你网站注册账号发给代理商,由代理商向CSR申请开通,过程需要有一定的时间。(高通收购CSR后,签订流程稍有变化,但一般还是要找代理商)。

关于开发套件,我们以CSR1010为例介绍,当然,CSR1011、CSR1012等都有对应的版本。

标准的开发套件由三部分组成:

一个USB to SPI转接板,用于给芯片烧录程序;

一个CSR1010 demo板,内含CSR1010芯片;

一个CSR8510 USB dongle,用于PC端与demo板建立连接使用。

还有一个简化版:由一块单独的板子组成,内含USB to SPI芯片和CSR1010芯片。

当然还有一些Mesh开发的特别版,这边不做专门介绍,如果要开发Mesh可考虑选择。

所有的开发套件,都可以找代理商购买,前提是要签订保密协议。

软件部分,最主要的就是uEnergy,保密协议签订后可以从代理商或者网站下载,够买开发套件后也有资料光盘,内有uEnergy等一系列开发软件。

uEnergy是CSR101x系类开发的唯一环境,当前最新版本是2.6,安装方法也很简单,直接双击安装包即可,安装完毕后会在安装目录下看到一系列文件夹,如SDK2.5中有以下文件夹:

这里简单介绍一下文件夹的内容:

Apps—包含一些常用的例子工程;

Doc—包含一些说明文档,其中推荐用doc\reference\html\index.html打开所有html文件的总索引,可以查看一些API的说明;

Drivers—USB dongle的驱动;

Kes-demo—KES demo PC端应用程序;

Otau_demo—空中升级demo程序;

Profile_demo—uEnergyProfileDemo PC端应用程序;

Tools—一些常用工具,其中tools\bin下的e2cmd、CsConfig、csconfigcmd比较常用;

Xide—编译环境所在文件夹。

以上提到的工具,会在今后的章节中一一介绍,这里只做一个非常简短的说明。

好了,关于CSR101x的软硬件开发工具就暂时介绍到这。

最后再啰嗦一点,很多人都认为CSR101x系列的功耗非常高,其实并不完全是,其收发峰值功率是比部分芯片高,但它的信号强度有7.5dbm以上,7.5dbm能满足更多场合的需求;而休眠电流,各家芯片都能做的非常低。所以,功耗高不高,关键看场合,除非是对电池要求极端的场合,否则,大家都是BLE,功耗再高又会高到哪里去呢!

注:以上均只是个人观点,不代表任何官方立场。

更多信息请关注微信号TalkBT:

BC0003-CSR uEnergy安装及使用方法介绍

本文主要讲述uEnergy的一些最基本用法,当前最新版本是2.6.0.10,双击安装:

直接点next:

选择您所需要安装的目录:

一般都直接选择下一步即可:

驱动建议勾上:

等待安装完成,中途可能会跳出一些其他安装窗口,耐心等待即可:

安装完成后,在安装目录下找到SDK文件夹:

找到xide/bin/xide.exe双击打开(当然,也可以通过桌面或者开始菜单快捷方式打开)

打开后我们看到整个开发界面:

通过Project=>Open Workspace打开我们要的工程:

我们这里以hr_hensor为例:

在下一步操作之前,我们需要将开发板连接到电脑:

连接完毕后,点击以下图标download程序到目标板中,程序会自动编译并将其load到目标板中。

不一会,会看到底部的Debug窗口显示Done字样,表示烧录成功:

此时,我们可以打开手机APP去尝试与板子连接:

打开后选择Heart Rate:

正常的话可以搜索HR Sensor设备,点击该设备建立连接:

连接后,我们就能看到设备的BPM值以及其他一些参数:

好了,此时我们的测试完成,编译连接的整个过程就是这么简单明了。

BC0004-CSR uEnergy例子讲解之PIO操作

uEnergy安装完毕后,会在安装目录下看到apps文件夹,里面有很多的例子,从这一节开始我将会挑选部分例子进行讲解,掌握这些例子后,会对CSR uEnergy的开发有一个大致的了解。

今天我们以示例代码中的PIO为例,讲解PIO的一些用法。

首先,打开SDK的安装目录,找到PIO文件夹:

打开后,双击pio.xiw打开:

打开后会有以下界面:

左侧为Workspace,我们点击C Files,打开main.c文件:

打开后,我们会看到main.c文件的内容,这里重点讲几个函数:

1.      AppPowerOnReset

2.      AppInit

3.      AppProcessSystemEvent

4.      AppProcessLmEvent

首先来讲解AppPowerOnReset函数,在pio的例子中,此函数为空:

其实多数的应用中,此函数都为空。看官方的解释,此函数只有在reset或者Hibernate or Dormant sleep状态醒来才会被调用,而我们多数的应用中,将此函数留空即可。

AppInit函数:

此函数比较重要,整个程序起来后会进入此函数,用户可以在此函数中添加一些初始化函数,此函数只被调用一次,且不能在此函数中添加死循环,否则系统会崩溃。

AppProcessSystemEvent函数:

此函数是系统事件的入口,系统事件有4种sys_event_wakeup、sys_event_battery_low、sys_event_pio_changed、sys_event_pio_ctrlr:

Sys_event_wakeup:芯片通过wakeup引脚起来会产生此事件;

Sys_event_battery_low:电池电量低会触发此事件;

Sys_event_pio_changed:io口状态改变会触发;

Sys_event_pio_ctrlr:pio controller产生的中断会引起此事件。

最后一个函数AppProcessLmEvent:

此函数在pio的例子中也是空,因为此函数处理了一些蓝牙事件,此例子只是对PIO口操作,没有涉及到蓝牙,所以,此函数也必然为空,关于蓝牙事件,会在以后的章节中说明。

看完这些,相信你会明白哪里才是我们看代码的切入点了吧,自然是AppInit函数,这里为了看起来方便,我将解释放在了代码中:

void AppInit(sleep_state last_sleep_state)

{

//首先这里初始化两个LED口

//设置PIO_LED0和PIO_LED1口为pio_mode_user

    PioSetModes((1UL << PIO_LED0) | (1UL << PIO_LED1), pio_mode_user);

    //设置PIO_LED0口为输出

    PioSetDir(PIO_LED0, PIO_DIR_OUTPUT);

     //设置PIO_LED1口为输出

    PioSetDir(PIO_LED1, PIO_DIR_OUTPUT);

    //设置PIO_LED0和LED1为强上拉

    PioSetPullModes((1UL << PIO_LED0) | (1UL << PIO_LED1),

                    pio_mode_strong_pull_up);

    //接下去设置了一个按钮

    //设置BUTTON为pio_mode_user

    PioSetMode(PIO_BUTTON, pio_mode_user);

    //设置BUTTON口为输入

    PioSetDir(PIO_BUTTON, PIO_DIR_INPUT);

    //设置BUTTON口为弱上拉

    PioSetPullModes((1UL << PIO_BUTTON), pio_mode_weak_pull_up);  

    //设置上升沿和下降沿都触发Sys_event_pio_changed事件

 PioSetEventMask((1UL << PIO_BUTTON), pio_event_mode_both);

    restartLedSeq();

}

PIO口的操作基本上是在appinit函数中设置PIO口的模式,普通IO口为pio_mode_user,然后设置PIO口为输入或者输出,然后配置上拉下拉或者浮空,如果为输入,还可以配置是否触发Sys_event_pio_changed事件。

在初始化函数的最后,调用restartLedSeq函数并结束整个初始化:

restartLedSeq函数内,我们看到一个PioSets函数,从注释很容易理解是将LED0和LED1设置为0,即关闭LED。

这里,就告诉我们设置IO口状态可以用PioSets函数,关于此函数的具体说明,可以查看API说明。

接下来我们看按键被按下后程序做了哪些处理:

void AppProcessSystemEvent(sys_event_id id, void *data)

{

    //这里为sys_ event_pio_changed事件的入口

    if (id == sys_event_pio_changed)

    {

        const pio_changed_data *pPioData = (const pio_changed_data *)data;

        //判断是不是按键导致sys_event_pio_changed事件

        if (pPioData->pio_cause & (1UL << PIO_BUTTON))

        {

            bool validStateChange = FALSE;

            //判断当前是否为高,如果是高,则表明刚刚发生的事件是由低到高,即上升沿触发,表示按键是被松开的

            if (pPioData->pio_state & (1UL << PIO_BUTTON))

            {

                g_cur_button_state = button_state_up;

            }

            //相反,如果不是高,则表明按键是被按下

            else

            {

                if (g_cur_button_state == button_state_up)

                {

                    validStateChange = TRUE;

                }

                g_cur_button_state = button_state_down;

            }

            //如果一个完整的按键发生,则切换LED闪烁状态

            if (validStateChange)

            {

                goToNextLedSeq();

            }

        }

    }

}

关于goToNextLedSeq()函数,其中也没有新的PIO口操作知识点,这里就不做过多的解释了,读者可以自己去研究一下每一个LED状态是怎么样的。

好了,关于PIO口操作的解释就说明到这里,另外IO口不仅仅只有输入输出两种功能,还有UART功能,IIC功能,SPI功能等等,会在后续章节中逐一讲解。

BC0005-CSR uEnergy例子讲解之UART操作

本文档主要讲解SDK中uart例子,首先找到SDK中的uartio工程:

打开后,我们先看开发工具界面左侧的文件:

Byte_queue.c—主要处理一些数据buffer,纯C语音代码,与UART操作无直接关系,本文不做过多说明;

Main.c—主函数;

Uartio.c—包含UART操作函数。

首先,我们打开main.c文件,第一个函数void AppPowerOnReset(void),函数体为空,跟我们之前讲到的一样,一般此函数都为空;

第二个函数:

其中SleepModeChage将sleep_mode设置为never,如果芯片进入sleep,则可能会丢失部分UART收到的数据,所以,将其设置为永不休眠;当然,这样会导致芯片功耗上升,在一些低功耗应用场合,我们可以通过其他手段来即保证平时的低功耗,又保证能正确接受到数据,比如可以增加唤醒IO。

接下去的函数Start(),直接配置UART并结束AppInit函数。

Start()函数在uartio.c文件中,为了方便说明,这里把说明写在代码中:

void Start(sleep_state last_sleep_state)

{

//初始化UART,第一个和第二个参数是设置callback函数的,函数名称可以自己定义,每当收到或发送对应数量字节后会调用此callback函数;

//rx_buff、tx_buff为存放数据的缓存,RX_BUFF_SIZE、TX_BUFF_SIZE为buff长度

// uart_data_unpacked为数据不打包,一般都设置为此方式。

    UartInit(uartRxDataCallback,

             uartTxDataCallback,

             rx_buffer, RX_BUFFER_SIZE,

             tx_buffer, TX_BUFFER_SIZE,

             uart_data_unpacked);

       //使能Uart

    UartEnable(TRUE);

       //设置UART收到1byte就自动触发Callback函数

    UartRead(1, 0);

//发送Clear screen命令,此命令为PC软件端,对于CSR1010来说只是一个普通的字符,具体可以查看vt100ClearScreen函数是怎么写的

    vt100ClearScreen();

    //同样,此函数也只是打印一个sleep状态信息,对于芯片来说就是一个字符串

    printSleepState(last_sleep_state);

    const uint8 message[] = “\r\nType something: “;

    //将发送数据添加到队列中

    BQForceQueueBytes(message, sizeof(message)/sizeof(uint8));

    //此函数将Quenue中的数据拿出来放到Uart缓存中发送

    sendPendingData();

}

上面提到的sendPendingData函数中,有一个函数我们需要看一下:

UartWrite很好理解,就是将要发送的数据写入底层,写入正确返回TRUE。

在UART操作中,另外两个重要的函数如下:

uartRxDataCallback和uartTxDataCallback函数就是前面我们设置的Callback函数,关于在整个代码中的作用,代码的注释写非常清楚了,用户可以在这两个函数中增加自己的代码。

另外,关于UART的API函数,可以查看API的说明文档:

其中UartConfig可以设置UART的速率,以及一些其他参数,比如奇偶校验,停止位等等。。。

好了,代码分析就到这,当然,整个工程中还有很多写的不错的地方,特别是Byte_queue.c,用户有空可以研究一下此代码。

BC0006-CSR uEnergy例子讲解之timer用法

定时器在所有的MCU中都扮演着十分重要的角色,在CSR101x系列SOC中也不例外,下面我就以SDK中的例子来简单说明timer在CSR101x开发中的用法。

首先打开我们要的工程:

整个工程就一个文件—main.c,打开该文件,首先我们会看到三个宏定义:

第一个是MAX_TIMERS,它定义了整个程序同时所用到的timer数量,这里我们只用到了一个,所以只定义了1.

TIMER_TIMEROUT1和TIMER_TIMEOUT2分别定义了timer溢出的时间。

接下去定义的是定时器的缓存:

以上这些定义都会在下面的代码中用到。

接下去直接切入主题,看到初始化函数:

void AppInit(sleep_state last_sleep_state)

{

    //初始化Debug串口

    DebugInit(1, NULL, NULL);

    //发送Debug字符串

    DebugWriteString(“\r\nInitialising timers”);

    //初始化timer,这里用到了前面的定义,timer的初始化就是这么简单,但是这步是必须的

    TimerInit(MAX_TIMERS, (void *)app_timers);

    //向串口打印当前系统time信息

    printCurrentTime();

    //开启第一个定时器

    startTimer(TIMER_TIMEOUT1, timerCallback1);

}

下面我们来看一下开启定时器函数startTimer是怎么写的:

static void startTimer(uint32 timeout, timer_callback_arg handler)

{

    //开启timer定时器,TimerCreate函数需要输入timer溢出时间和回调函数两个参数,中间的参数一般就设置为TRUE好了,然后如果timer开启成功,会返回一个timer_id值,这个值建议存在一个变量中。

    const timer_id tId = TimerCreate(timeout, TRUE, handler);

    //如果timer创建失败,则向系统报错

    if (tId == TIMER_INVALID)

    {

        DebugWriteString(“\r\nFailed to start timer”);

        /* Panic with panic code 0xfe */

        Panic(0xfe);

    }

}

前面我们提到过TIMER_TIMEOUT1定义的时间为1s,如果程序运行正常,1s后会自动运行timerCallback1函数,下面我们再看看timerCallback1中发生了什么:

static void timerCallback1(timer_id const id)

{

    ……

    //上面上面省略的代码不做过多解释,反正就是根据一些条件往串口打印一些数据

    //这里重新开启另一个timer,当时间溢出后调用timerCallback2回调函数

    startTimer((TIMER_TIMEOUT2 – TIMER_TIMEOUT1), timerCallback2);

}

同样,我们来看timerCallback2函数:

static void timerCallback2(timer_id const id)

{

    …

           //这里timer2结束后重新开启一个timer1

    startTimer(TIMER_TIMEOUT1, timerCallback1);

}

就这样,整个程序就是执行完timerCallback1后执行timerCallback2,执行完timerCallback2后继续执行timerCallback1,如此周而复始。。。

由以上例子可以看出,timerCallback函数只会在timer溢出时执行一次,如果要让他继续执行,则必须要在函数结束前再重新开启一遍timer。

当然,我们还可以在timerCallback函数执行前销毁timer,这样timerCallback函数就不会被执行,销毁timer可以调用TimerDelete(timer_id),这里需要用到创建timer时候返回的timer_id号,如果创建的时候未保存,那就不能delete了啦。此函数在本例子中没有说明,具体使用方法在API中有详细说明。

这里强调一点,在这样的简单程序中,以上写法没啥大问题,因为你能清楚地掌控所有timer,但是如果是一个复杂程序,这样写显得不严谨,容易给你的debug造成很大困扰,因为你根本不知道何时程序会开启一个timer,何时程序会去销毁一个timer,所以,当我们在开启timer的时候必须要将timerId保存到一个变量中,当销毁一个timer的时候要将次变量中的timerId清除,当执行一个timerCallback函数的时候,要判断timerId是否相符。。。在使用timer的时候必须保持良好的习惯,这样程序才不容易出错。当然,如果你有十足把握掌控所有timer,那可以忽略这点。