源码移植
更新时间:2017-08-08 阅读:6624
目录
前言
昆天科源代码基于昆天科公司生产的开发套件。
昆天科的开发套件分为 QBlue DK 和 MiniDK ,QBlue DK 又分为 MotherBoard 和 Evaluation Board 两部分,在QBlue1.3.5中均有介绍。
QBlue DK外设较多,并且使用了NXP LPC1768作为主控;Mini DK则外设较少。
昆天科开发套件可以直接运行昆天科提供的所有源代码,但是,也存在如下几个缺点:
- 开发套件中使用的是QN9020,由于QN9021相对QN9020拥有跟小的封装,所以IO分布也不一样,恰好开发套件里面使用的IO与QN9021是冲突的。如果考虑到各种因素需要使用QN9021开发产品,在开发套件上开发的程序并不能直接适用于产品。
- 开发套件价格方面比较高,官方零售价为69美元。
- 昆天科只提供了SDK中英文开发手册供开发者使用,并不做开源化管理,学习环境比较单一。
FireBLE与DK的区别:
- QN9021单芯片解决方案,无须外加MCU。
- 外设丰富,Joysticks摇杆、OLED屏接口、板载MPU6050 6轴G-Sensor。
- 开源化管理,提供中文学习教程、论坛、社区以及开源项目开发。
准备工作
获取源代码
使用git工具可以从远程服务器上克隆FireBLE源代码,该源代码基于官方SDK做了移植,移植方法在本篇后面会详细介绍。小编非常希望用户能够用上并且喜欢上用git工具来管理和维护代码,更希望用户能参与到开源项目的开发与维护中去,在其中学习到更多的东西,开阔自己的视野,而不是自己一个人孤军奋战。下面我来介绍一下什么是git,虽然很是粗浅,但是小编已经尽全力,请不要介意。
官方下载界面获取源代码
源代码基于QBlue1.3.5,其中主要修改了button、led的引脚,增加了OLED屏的驱动和MPU6050的驱动。
在bitbucket上用git工具克隆代码
使用以下命令可以直接将代码克隆到本地。(克隆前请先在命令行中切换到需要放置代码的目录,再克隆,如果不切换目录,直接在~目录下克隆,那么克隆下来的文件夹将会存放在C:/Users/xxx下).
git clone https://TeeFirefly@bitbucket.org/T-Firefly/FireBLE.git
先介绍一下什么是git,为什么要使用它。
git代码管理工具介绍
Git是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。
从一般开发者的角度来看,git有以下功能:
- 从服务器上克隆数据库(包括代码和版本信息)到单机上。
- 在自己的机器上创建分支,修改代码。
- 在单机上自己创建的分支上提交代码。
- 在单机上合并分支。
- 新建一个分支,把服务器上最新版的代码fetch下来,然后跟自己的主分支合并。
- 生成补丁(patch),把补丁发送给主开发者。
- 看主开发者的反馈,如果主开发者发现两个一般开发者之间有冲突(他们之间可以合作解决的冲突),就会要求他们先解决冲 突,然后再由其中一个人提交。如果主开发者可以自己解决,或者没有冲突,就通过。
- 一般开发者之间解决冲突的方法,开发者之间可以使用pull 命令解决冲突,解决完冲突之后再向主开发者提交补丁。
从主开发者的角度(假设主开发者不用开发代码)看,git有以下功能:
- 查看邮件或者通过其它方式查看一般开发者的提交状态。
- 打上补丁,解决冲突(可以自己解决,也可以要求开发者之间解决以后再重新提交,如果是开源项目,还要决定哪些补丁有用,哪些不用)。
- 向公共服务器提交结果,然后通知所有开发人员
简单的说,方便公布代码给其他开发者下载和开发、方便在本机上对代码的修改做详细记录和备份(理论上git不被破坏,可以回退任意时刻的提交记录的版本)、方便分享代码给其他开发者、方便对代码任意修改而不怕破坏代码(只要是在分支上折腾就行了)、方便在源代码基础上用多种方法实现多种功能,而不至于混乱。
总而言之,git对开发者来说是一个十分方便,能提升不少效率的工具,优点多多。
文资料太少太少,学习周期长。但是一旦学会,你会喜欢并且依赖上它的。
关于更多git的说明和学习,推荐此网站,学习git so easy:http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000
git管理代码的习惯建议
- 用git做代码维护的时候要养成完成一个小功能就提交一次的习惯,尽量不要做了一大堆的事情然后再提交,除非你能保证每一次都是完美成功的,否则一旦出现差错,你想回到某个时间点的时候才发现自己没有那个时间点的提交,完全reset又会删掉之前完成的一些小功能,然后纠结七天七夜而亡。
- reset虽然很危险,但是也很好用,所以这部分有必要仔细阅读,灵活运用。
- commit的名称尽量使用英文(没办法,linux平台的外国软件,既跨平台又跨国界,编码问题实在蛋疼),而且尽量清晰、统一格式的描述这一次提交所做的内容。
- 命令行与图形界面同时使用,交替使用,既有Linux的高效,也有windows的友好,个人觉得这样上手快……
- 完成一个功能之前,先开一个分支,不要懒惰直接在master上做开发,貌似的捷径必然充满陷阱,你不信?纠结的反正不是我。
- 不抛弃,不放弃,我现在对git80%以上的功能还不熟悉,但是,现在没有git我已经很不习惯了,我觉得只要把上文中廖雪峰大神的网站多看几遍,基本掌握了,就差不多了。
源代码结构分析
git clone成功后,在当前目录下将会看到一个FireBLE命名的文件夹,这就是刚才克隆下来的工程。
打开工程可以看到,里面有许多以prj_xxx命名的文件夹,每一个工程都是独立的,而且如果需要新建工程,建议只在此目录下创建,因为在keil的配置中,已经配置好了相对路径,如果任意改变路径,需要对keil工程进行重新配置,否则会出错无法编译。
另外还有一个src命名的文件夹,主要包含了各个工程中通用的一些文件,例如app、profile、driver等等。
group | 分类说明 | group | 分类说明 |
---|---|---|---|
startup | 汇编启动配置代码 | profile | profile是把蓝牙中标准的服务实现规范成一个特定服务实现,app与profile通过msg通信协同工作,实现特定功能。基本不会改动,在有需求,并且对此非常熟悉的时候,可以自己定制。 |
main | 主函数,一般无须修改。 | drivers | 系统底层驱动。 |
usr | 用户定义文件,与工程要实现的功能息息相关。一般只需改动此文件夹下的代码。 | lib | 系统封装库,封装昆天科保密的代码。 |
app | BLE的app端,调用不同的服务组成和实现一个特定的功能,可直接使用昆天科实现的app,也可自己定制。 | qnevb | 系统外设驱动文件。 |
retarget | 实现了重定义getchar()等等,实现串口标准化输出。 |
- 对于主机设备来说,我们更多的是在app目录下做开发;对于从机设备来说,我们更多的在usr目录下做开发。
源代码移植
- 源代码的移植指的是从昆天科原厂的代码修改为FireBLE开发板能够运行的代码,移植的主要内容的是底层的外设驱动。昆天科源码中的最底层外设有三个:
- LED
- Button
- Buzz
以prj_proxr为例对移植做说明。
移植说明
以按键驱动为例,昆天科源码中对按键有如下引脚定义(FireBLE/BLE/src/qnevb/button.h):
#if !defined(QN_9021_MINIDK) #define LED1_PIN (GPIO_P05) #define LED2_PIN (GPIO_P04) #define LED3_PIN (GPIO_P03) #define LED4_PIN (GPIO_P02) #define LED5_PIN (GPIO_P01) #else #define LED1_PIN (GPIO_P03) #define LED2_PIN (GPIO_P13) #define LED3_PIN (GPIO_P02) // no pin in QN9021 #define LED4_PIN (GPIO_P02) // no pin in QN9021 #define LED5_PIN (GPIO_P02) // no pin in QN9021 #endif
只需打开QN_9021_MINIDK宏定义即可更改引脚配置,这是程序配置中一般使用方法。找到QN_9021_MINIDK的宏定义: FireBLE/BLE/src/app/app_config.h
/// Evaluation Board Indication #if (defined(CFG_9021_MINIDK)) #define QN_9021_MINIDK #endif
看来打开QN_9021_MINIDK宏定义必须要先定义CFG_9021_MINIDK.查找CFG_9021_MINIDK:
FireBLE/BLE/pro_xxx/src/usr_config.h
/// Evaluation board indication // The GPIOs used for QN9021 miniDK's LED and button are different from QN9020 miniDK. // If the QN9021 miniDK is used, the following macro shall be defined. #define CFG_9021_MINIDK
以上是对QN_9021_MINIDK的适配,我们可以依葫芦画瓢,在 FireBLE/BLE/src/app/app_config.h 下面做如下定义
/// FireBLE Board Indication #if (defined(CFG_FireBLE)) #define FireBLE_platform #endif
在 FireBLE/BLE/pro_xxx/src/usr_config.h 中做如下定义
/// Evaluation board indication // The GPIOs used for FireBLE's LED and button are different from FireBLE. // If the FireBLE is used, the following macro shall be defined. #define CFG_FireBLE
这样,我们定义了一个FireBLE_platform的开发平台,在昨晚本章所有操作后,以后我们只需打开 CFG_FireBLE 的宏定义就可以在工程中使用源码中的底层驱动了。
现在我们来看,为什么在定义QN_9021_MINIDK之前要定义CFG_9021_MINIDK,这不是多次一举吗?不然,其实这正是为了更好的维护代码,分隔代码相关性。
app_config.h是属于公共配置文件,存放在多个工程公用的src文件夹中的app文件夹内,每一个工程使用的都是同一份代码。从理论上来说,为了代码的移植性考虑,我们希望只要修改工程内的usr_config.h就能实现对开发平台不同的切换并且不影响到其他开发平台的使用。也就是说,在不这样设计的情况下,如果我在A工程中使用的是FireBLE,B工程中是用的是QBlue 9021 MINI DK,如果我因为要在B中使用QBlue 9021 MINI DK,我需要在app_config中关闭了FireBLE_platform的宏定义,开启QBlue 9021 MINI DK,那么,A工程就无法正常运行了,反之,则两个工程十分独立,完全无影响。
添加配置定义
在FireBLE/BLE/pro_xxx/src/usr_config.h中添加如下定义
/// Evaluation board indication // The GPIOs used for FireBLE's LED and button are different from FireBLE. // If the FireBLE is used, the following macro shall be defined. #define CFG_FireBLE ///defined it when used SWD Debug,and LED2/LED3 will do not work. #define CFG_SWD ///FireBLE Joystick button #define CFG_JOYSTICKS
其中打开宏CFG_FireBLE表示进入FireBLE平台,代码可以适用于FireBLE开发板。打开CFG_SWD表示需要使用SWD进行硬件仿真,此时LED2和LED3不能使用,指示灯微亮是因为作为仿真接口使用时该引脚被配置为下拉状态。打开CFG_JOYSTICKS宏表示需要使用到五向按键。
在FireBLE/BLE/src/app/app_config.h文件中添加如下定义
/// FireBLE Board Indication #if (defined(CFG_FireBLE)) #define FireBLE_platform #if (defined(CFG_SWD)) #define FB_SWD 1 #else #define FB_SWD 0 #endif #if (defined(CFG_JOYSTICKS)) #define FB_JOYSTICKS 1 #else #define FB_JOYSTICKS 0 #endif #endif
在FireBLE/BLE/src/app/app_env.h中加入头文件
#if (FB_JOYSTICKS) #include "joysticks.h" #endif
LED适配
LED适配非常简单,只需更改LED引脚定义如下(FireBLE/BLE/src/qnevb/led.h):
#if !defined(QN_9021_MINIDK) #if !defined(FireBLE_platform) #define LED1_PIN (GPIO_P05) #define LED2_PIN (GPIO_P04) #define LED3_PIN (GPIO_P03) #define LED4_PIN (GPIO_P02) #define LED5_PIN (GPIO_P01) #else #define LED1_PIN (GPIO_P27) #if !(FB_SWD) #define LED2_PIN (GPIO_P06) #define LED3_PIN (GPIO_P07) #else #define LED2_PIN (GPIO_P02) // no pin in QN9021 #define LED3_PIN (GPIO_P02) // no pin in QN9021 #endif #define LED4_PIN (GPIO_P02) // no pin in QN9021 #define LED5_PIN (GPIO_P02) // no pin in QN9021 #endif #else #define LED1_PIN (GPIO_P03) #define LED2_PIN (GPIO_P13) #define LED3_PIN (GPIO_P02) // no pin in QN9021 #define LED4_PIN (GPIO_P02) // no pin in QN9021 #define LED5_PIN (GPIO_P02) // no pin in QN9021 #endif
关于宏定义FB_SWD说明:默认关闭SWD仿真,需要使用SWD仿真,需要在usr_config.h中打开CFG_SWD的宏定义
///defined it when used SWD Debug,and LED2/LED3 will do not work. #define CFG_SWD
此时GPIO的定义也要根据实际的应用场合去修改,由于SystemIOCfg属于usr用户组,是用户自定义文件,请用户根据自己需求定义,示例如下
static void SystemIOCfg(void) { // pin mux syscon_SetPMCR0(QN_SYSCON, P00_UART0_TXD_PIN_CTRL | P01_GPIO_1_PIN_CTRL | P02_GPIO_2_PIN_CTRL | P03_GPIO_3_PIN_CTRL | P04_GPIO_4_PIN_CTRL | P05_GPIO_5_PIN_CTRL #if !(FB_SWD) | P06_GPIO_6_PIN_CTRL | P07_GPIO_7_PIN_CTRL #else | P06_SW_DAT_PIN_CTRL | P07_SW_CLK_PIN_CTRL #endif
Joysticks适配
Joysticks适配相比刚才要复杂许多。首先修改Button引脚定义(FireBLE/BLE/src/qnevb/button.h):
#if !defined(QN_9021_MINIDK) #if !defined(FireBLE_platform) #define BUTTON1_PIN (GPIO_P14) #define BUTTON2_PIN (GPIO_P15) #else #define BUTTON1_PIN (GPIO_P12) #define BUTTON2_PIN (GPIO_P02) // no pin in QN9021 #endif #else #define BUTTON1_PIN (GPIO_P12) #define BUTTON2_PIN (GPIO_P10) #endif
如果是基于proxr例程修改此程序,程序此时已经可以编译成功,并且能够正常执行,无论Joysticks往哪一个方向按下,都开启和关闭BLE广播,并且LED1在广播开启时会不停闪烁。但是由于是Joysticks,我们可不能满足于此,接下来还需要加入Joysticks方向的判断。Joysticks通过adc检测GPIO_P30的电平变化来确定Joysticks的方向(有兴趣的可以去看原理图),我们可以加入一个宏定义,在不需要使用Joysticks的情况下,可以不做这方面的识别。
在FireBLE/BLE/prj_xxx/src/usr_config.h中加入
///define it when used Joysticks #define CFG_JOYSTICKS
当打开此项宏定义时表示需要使用到Joysticks。
和LED一样,由于使用到ADC作为反向识别,所以需要修改引脚配置,示例,在FireBLE/BLE/prj_xxx/src/system.c中加入GPIO的引脚配置,配置GPIO_P30位ADC的输入通道。
#if !(FB_JOYSTICKS) | P30_GPIO_24_PIN_CTRL #else | P30_AIN0_PIN_CTRL #endif
添加qnevb文件夹中的joysticks.c文件,该文件实现了对Joysticks的方向识别。在task_app.c和task_app.h中加入如下信息。
app_task.c
#if (FB_JOYSTICKS) {APP_KEY_PROCESS_TIMER, (ke_msg_func_t) app_key_process_timer_handler}, {APP_KEY_SCAN_TIMER, (ke_msg_func_t) app_key_scan_timer_handler}, #endif
app_task.h
#if (FB_JOYSTICKS) APP_KEY_PROCESS_TIMER, APP_KEY_SCAN_TIMER, #endif
先编译一遍,看看会有些什么错误出现。
没有包含adc驱动,实际上还有一个analog.c的文件也是要包含的(在工程目录的上一级目录的src中的driver文件夹中)。
再次运行,无报错了,貌似是可以用了,但是,实际是不行的,还需如下几步:
- 五向按键用到了ADC采样,所以还需要注册按键中ADC采样完成中断事件。
#if (FB_JOYSTICKS) if(KE_EVENT_OK != ke_evt_callback_set(EVENT_ADC_KEY_SAMPLE_CMP_ID, app_event_adc_key_sample_cmp_handler)) { ASSERT_ERR(0); } #endif
- 在按键事件处理函数app_event_button1_press_handler中添加一个定时器事件跳转到Ioysticks识别处理函数,并且屏蔽掉之前的按键处理函数。
void app_event_button1_press_handler(void) { #if ((QN_DEEP_SLEEP_EN) && (!QN_32K_RCO)) if (sleep_env.deep_sleep) { sleep_env.deep_sleep = false; // start 32k xtal wakeup timer wakeup_32k_xtal_start_timer(); } #endif // delay 20ms to debounce #if (FB_JOYSTICKS) ke_timer_set(APP_KEY_SCAN_TIMER,TASK_APP,2); #else ke_timer_set(APP_SYS_BUTTON_1_TIMER, TASK_APP, 2); #endif ke_evt_clear(1UL << EVENT_BUTTON1_PRESS_ID); }
- 按键唤醒后需要初始化一些参数,在usr_button1_cb函数中加入如下代码:
void usr_button1_cb(void) { // If BLE is in the sleep mode, wakeup it. if(ble_ext_wakeup_allow()) { #if ((QN_DEEP_SLEEP_EN) && (!QN_32K_RCO)) if (sleep_env.deep_sleep) { wakeup_32k_xtal_switch_clk(); } #endif sw_wakeup_ble_hw(); // #if (QN_DEEP_SLEEP_EN) // // prevent deep sleep // if(sleep_get_pm() == PM_DEEP_SLEEP) // { // sleep_set_pm(PM_SLEEP); // } // #endif } #if (FB_JOYSTICKS) usr_button_env.button_st = button_press; #endif // key debounce: // We can set a soft timer to debounce. // After wakeup BLE, the timer is not calibrated immediately and it is not precise. // So We set a event, in the event handle, set the soft timer. ke_evt_set(1UL << EVENT_BUTTON1_PRESS_ID); }
这样,Joysticks的驱动就算是移植完成了,可以在usr_key_process_timer_handler中实现按键触发后需要执行的操作。
OLED适配
FireBLE可以使用官网上配套的OLED,OLED有两种通信方式,IIC协议和SPI协议,可通过更改少量OLED背部的器件位置来更改其通信方式。其中开发板背面OLED接口的右边有日期字样的,如图,既可以使用IIC版的OLED,也可以使用SPI版的OLED;如果没有日期字样,则表示版本较老,仅支持IIC点屏。
- 首先将oled.c oled.h以及oledfont.h拷贝到BLE/src/qnevb目录下。
- 在usr_config.h中添加开启OLED的宏定义,可以选择屏幕采用IIC协议通信或者是SPI协议通信,由于IIC协议是软件模拟的通信协议,刷屏速度略慢于SPI协议。
///choose SPI OLED or IIC OLED (CFG_IIC_OLED , CFG_SPI_OLED) #define CFG_IIC_OLED
- 在app_config.h中添加OLED使用的宏定义。
/// FireBLE Board Indication #if (defined(CFG_FireBLE)) #define FireBLE_platform #if (defined(CFG_SWD)) #define FB_SWD 1 #else #define FB_SWD 0 #endif #if (defined(CFG_JOYSTICKS)) #define FB_JOYSTICKS 1 #else #define FB_JOYSTICKS 0 #endif #if (defined(CFG_IIC_OLED) || defined(CFG_SPI_OLED)) #define FB_OLED 1 #if defined(CFG_IIC_OLED) #define FB_IIC_OLED 1 #else #define FB_IIC_OLED 0 #endif #if defined(CFG_SPI_OLED) #define FB_SPI_OLED 1 #else #define FB_SPI_OLED 0 #endif #else #define FB_OLED 0 #endif #endif
- 在app_env.h中加入头文件
#if (FB_OLED) #include "oled.h" #endif
- 在app_task.c中添加定时器事件任务
#if (FB_OLED) {APP_OLED_DISPLAY_TIMER, (ke_msg_func_t) app_oled_display_timer_handler}, {APP_OLED_STATE_DISPlAY_TIMER, (ke_msg_func_t) app_oled_state_display_timer_handler}, {APP_OLED_CLEAR_KEY_DISPLAY_TIMER, (ke_msg_func_t) app_oled_clear_key_display_timer_handler}, #endif
- 在app_task.h中声明事件
#if (FB_OLED) APP_OLED_DISPLAY_TIMER, APP_OLED_STATE_DISPlAY_TIMER, APP_OLED_CLEAR_KEY_DISPLAY_TIMER, #endif
- 在usr_design.h中声明如下函数
#if FB_OLED extern int app_oled_clear_key_display_timer_handler(ke_msg_id_t const msgid, void const *param, ke_task_id_t const dest_id, ke_task_id_t const src_id); extern int app_oled_state_display_timer_handler(ke_msg_id_t const msgid, void *param, ke_task_id_t const dest_id, ke_task_id_t const src_id); #endif
- 在usr_design.c中实现该函数
#if FB_OLED /** **************************************************************************************** * @brief Handles oled display status. * * @param[in] msgid APP_OLED_STATE_DISPlAY_TIMER * @param[in] param Null * @param[in] dest_id TASK_APP * @param[in] src_id TASK_NONE * * @return If the message was consumed or not. **************************************************************************************** */ int app_oled_state_display_timer_handler(ke_msg_id_t const msgid, void *param, ke_task_id_t const dest_id, ke_task_id_t const src_id) { // Stop proxr alert switch(ke_state_get(TASK_APP)) { case APP_ADV : { OLED_ShowString(0,2, (uint8_t *)" "); OLED_ShowString(0,2,(uint8_t *)" Advertising "); }break; case APP_IDLE : { if(app_proxr_env->enabled) { OLED_ShowString(0,2, (uint8_t *)" "); OLED_ShowString(0,2,(uint8_t *)" Connected "); } else { OLED_ShowString(0,2, (uint8_t *)" "); OLED_ShowString(0,2,(uint8_t *)" unConected "); } }break; case APP_INIT : { OLED_ShowString(0,2, (uint8_t *)" "); OLED_ShowString(0,2,(uint8_t *)" Init! "); }break; default : { OLED_ShowString(0,2, (uint8_t *)" "); OLED_ShowString(0,2,(uint8_t *)" Init! "); }break; } return (KE_MSG_CONSUMED); } int app_oled_clear_key_display_timer_handler(ke_msg_id_t const msgid, void const *param, ke_task_id_t const dest_id, ke_task_id_t const src_id) { OLED_ShowString(0,4," "); return(KE_MSG_CONSUMED); } #endif
- 在system.c中初始化OLED
#if (FB_OLED) OLED_Init(); //Init OLED OLED_Clear(); //Clear OLED,Black will be covered with the entire screen OLED_ShowString(0,0," Firefly Team "); OLED_ShowString(0,2," Wait ... ");