0 前言
🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。
为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是
🚩 毕业设计 基于单片机的便携式智能显示肺活量测量仪
🥇学长这里给一个题目综合评分(每项满分5分)
- 难度系数:3分
- 工作量:3分
- 创新点:3分
🧿 选题指导, 项目分享:
1 简介
使用IDT的流量传感器设计出一款便携式的,带有智能显示的肺活量测量仪。这款测量仪不仅携带方便,而且可以实时显示测量过程中呼出气体的流量波形。同时,该测量仪支持蓝牙功能。可以通过手机上开发的微信小程序通过蓝牙连接该测量仪,获取测量结果。注:由于该传感器量程的限制,要测量正常人的肺活量要连续呼气1分多钟,这个测试条件比较困难,所以视频中只是进行演示性测试,最终的结果并不是一个正常人的肺活量(男性3500,女性2000)。
2 主要器件
- NRF52832蓝牙SOC
- LCD模块
- FS2012气体传感器
- 锂电池
3 实现效果
4 设计原理
4.1 硬件设计
硬件框图
电源
3.7V锂电池供电,经过升压提供5V1A的电源给FS2012流量传感器供电;经过降压输出3.3V300mA给MCU和LCD供电;同时有一个1A充电电流的3.7锂电池充电电路。如下图所示:
传感器
IDT提供的FS2012气体流量传感器,最大量程为2SLPM(2000SCCM)即每分钟2L(2000ml)的气体流量。可以通过I2C数字接口或者模拟输出电压的方式获取数据(本次设计中通过ADC测量模拟输出电压的方式)。如下图所示:
蓝牙LCD模块
蓝牙SOC采用的是NRF52832,是Nordic Semiconductor公司的一款蓝牙SOC,CM4内核。显示使用的是SPI接口的LCD,像素为128*64。针对该应用,对模块做了相应的修改,把5V供电引脚到稳压芯片的0Ω电阻去掉,作为ADC输入引脚使用。如下图所示:
从nRF51822/nRF51422升级而来,知名的nRF51822是一颗Bluetooth Low Energy的SoC,它拥有Cortex-M0内核,运行频率为16Mhz。而nRF52832则是Cortex-M4F内核,并且拥有更大的RAM和Flash,还有更多的功能以及更好的RF性能。
下面我们来看看这些参数中有哪些亮点。
功耗
每一代新的BLE SoC出来,必定在功耗上面做些优化,nRF52832更是在nRF51822的基础上几乎将功耗降低了一半:
Active-mode RX:5.5 mA
Active-mode TX @ 0dBm: 5.5mA
Active-mode TX @ 4dBm: 7.7mA
这些功耗数据对比TI的CC26XX很有优势,即使是对比其他家的产品也是如此。
真正使用时的功耗还依赖于很多其他因素,比如运行的频率或者BLE的参数(连接或者广播的间隔)
Cortex-M4F 内核
新一代的nRF52832加入了很多新的功能。比如Cortex-M4F的内核,它能够更强大的运算能力以及浮点运算的技术。现在很多的穿戴设备或者工业化设备需要内置非常复杂的算法,所以需要MCU有更快的运行速度。这颗Cortex-M4F的内核运行期64Mhz,比其他厂家的芯片提高了很多。
这颗SoC有512KB的Flash和64KB的RAM,这也超出其他厂家的芯片一大截。Nordic称将会有400KB的Flash可以用于应用程序。如果你曾经开发过BLE的产品就会知道,BLE的协议栈至少要占80KB以上的Flash。如果像大多数的BLE SoC那样只提供128KB的Flash的话,应用程序只有大概40KB的空间,不过对于一般的传感器采集的任务来说是差不多的。之前的nRF51822最高提供256KB的Flash,这在当时也是比较少见的,我们很高兴看到Nordic再次提升了Flash的空间。
额外的Flash和RAM空间也意味着nRF52832可以支持多协议,并且在运行时自动切换。现在有很多产品已经支持了多协议这个特性(见上篇文章),看来这似乎会变成业界标准,因为目前并不是只有BLE一个无线协议。
BLE协议栈
Nordic将Cortex-M0内核升级到Cortex-M4F内核,可以保证BLE协议栈无需更改。这就意味着开发者可以使用一个更加稳定的协议栈,并且直接运行之前的几十种例程。
Nordic目前没有提到关于Bluetooth 4.2方面的事情,不过可以肯定是,既然新的硬件已经出来了,通过升级软件来做到更多的事情是肯定的。Bluetooth 4.2(见下一篇文章)提供了更快的传输速度、更高的安全性以及更低的功耗。
RF效率
我们必须谈一下RF的部分,毕竟所有的数据都是要通过这里的。
新的nRF52832提供了-96dBm的灵敏度,这个数据非常接近于TI的-97dBm,更高的灵敏度意味着更远的传输距离。
另外一项改进就是内置了Balun芯片。在之前的nRF51822设计中,必须加入Balun匹配电路,或者分立的或者Balun芯片,来匹配天线的50欧姆阻抗。现在nRF52832集成了这个功能,既节省了空间又节约了成本。并不适合只有Nordic一家这么干,dialog和TI都一样,都把能够集成进来的全部弄进来。
外部只需要一个额外的电感和一个电容,用来微调这些参数。总之,内置Balun大概能节约5-6个外部阻容器件。
多协议支持和NFC
现在很多的这类芯片都能提供多协议的支持,比如BLE, ANT, zigbee这些协议。目前在物联网领域还没有绝对的王者,因为每种无线协议都有自己的优势。现在看来实际的应用中更加趋向于不同的应用场景使用不同的技术,多协议支持就解决了这个问题。因为无线的硬件是一样的,只需要更换协议栈就行了,或者把多个协议集成进一个协议栈,对用户来说就像是同时运行这几个无线协议一样。
nRF52832支持Bluetooth Smart (Low Energy), ANT/ANT+ and proprietary 2.4GHz多种协议,这样就可以连接 nRF51, nRF24AP and nRF24L之类的产品。不过,它不支持Zigbee or Thread ,这两个协议在长距离传输上有更大的优势。Google正在力推Thread技术,Thread也慢慢的在智能家居的应用场景下变成BLE的对手之一。不过也许Bluetooth的Mesh网络技术能够解决这个问题。
Nordic同时引进了NFC技术,它支持NFC-A,也就是可以作为“标签”(Tag)来使用。配合智能手机可以用于近距离的安全连接工作,毕竟有一些应用中需要这种安全的方法,集成进来之后对整体成本和体积都有好处。不过新的Bluetooth 4.2协议中已经有了新的安全规范。
4.2 软件设计
1、固件说明
固件是在NRF52832官方SDK提供的蓝牙串口例程的基础上进行修改,添加SPI和ADC外设驱动即可。SPI驱动主要是控制LCD显示,ADC用来采集传感器数据。开发平台为Keil,如下图所示:
2、小程序说明
采用微信小程序进行蓝牙连接通信,微信提供开发工具以及蓝牙控制相关的接口,直接调用即可。这属于前端的开发,使用JS写逻辑控制,wxml进行布局排版,这里不做过多的说明。如下图所示:
5 部分核心代码
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
/* log日志需要的头文件 */
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
/* 定时器需要的头文件 */
#include "app_timer.h"
#include "bsp_btn_ble.h"
/* 蓝牙协议栈管理需要的头文件 */
#include "nrf_sdh.h"
#include "nrf_sdh_soc.h"
#include "nrf_sdh_ble.h"
/* 电源管理需要的头文件 */
#include "nrf_pwr_mgmt.h"
/* 排序写入模块需要的头文件 */
#include "nrf_ble_qwr.h"
/* GATT属性配置协议需要的头文件 */
#include "nrf_ble_gatt.h"
/* 建立连接参数需要的头文件 */
#include "ble_conn_params.h"
/* 蓝牙广播需要的头文件 */
#include "ble_advdata.h"
#include "ble_advertising.h"
/* 蓝牙的初始化宏定义 */
#define DEVICE_NAME "Hz" //设备名称
#define APP_BLE_OBSERVER_PRIO 3 //应用程序事件监视者优先级(应用程序不可修改)
#define APP_BLE_CONN_CFG_TAG 1 //ble配置标签
/* 连接时间的时间间隔是1.25ms的倍数(7.5ms~4000ms) */
#define MIN_CONN_INTERVAL MSEC_TO_UNITS(100,UNIT_1_25_MS) //设置最小连接时间
#define MAX_CONN_INTERVAL MSEC_TO_UNITS(200,UNIT_1_25_MS) //设置最小连接时间
#define SLAVE_LATENCY 0 //从机延时时间(可以利用跳过连接)
/* 监控超时时间是以10ms为倍数,100~32000ms */
#define CONN_SUP_TIMER MSEC_TO_UNITS(4000,UNIT_10_MS) //监控超时时间
/* 蓝牙参数延时时间定义 */
#define FIRST_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(5000) //首次调用连接参数更新延时时间5s
#define NEXT_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(30000) //每次调用连接参数更新间隔延时时间30s
#define MAX_CONN_PARAMS_UPDATE_COUNT 3 //设置放弃连接前尝试连接数
/* 广播时间间隔是以0.625的倍数 */
#define APP_ADV_INTERVAL 300 //广播间隔,187.5ms
#define APP_ADV_DURATION 0 //广播持续时间(0表示不超时)
#define DEAD_BEEF 0xDEADBEEF //应用于错误代码退出栈时确定堆栈位置
NRF_BLE_GATT_DEF(gatt); //创建gatt实例
NRF_BLE_QWR_DEF(qwr); //创建排队写入实例
BLE_ADVERTISING_DEF(advertising); //创建广播实例
static uint16_t conn_handle = BLE_CONN_HANDLE_INVALID; //创建连接句柄(初始状态为无设备连接)
/**
* gap参数初始化函数(通用访问规范)
*/
static void gap_params_init(void)
{
ret_code_t err;
ble_gap_conn_params_t gap_conn_params; //创建gap连接参数结构体
ble_gap_conn_sec_mode_t sec_mode; //设置gap安全模式1
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
/* 设置蓝牙设备名称 */
err = sd_ble_gap_device_name_set(&sec_mode,(const uint8_t *)DEVICE_NAME,strlen(DEVICE_NAME));
APP_ERROR_CHECK(err);
memset(&gap_conn_params,0,sizeof(gap_conn_params)); //先将连接参数清零
/* 设置gap连接参数 */
gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL; //设置最小连接时间
gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL; //设置最大连接时间
gap_conn_params.slave_latency = SLAVE_LATENCY; //设置从机延时时间
gap_conn_params.conn_sup_timeout = CONN_SUP_TIMER; //设置监督超时时间
err = sd_ble_gap_ppcp_set(&gap_conn_params); //初始化gap
APP_ERROR_CHECK(err);
}
/**
* gatt初始化函数(属性配置协议)
*/
static void gatt_init(void)
{
ret_code_t err;
err = nrf_ble_gatt_init(&gatt,NULL);
APP_ERROR_CHECK(err);
}
/**
* 空闲状态处理函数
*/
static void idle_state_handle(void)
{
//挂起log
if(NRF_LOG_PROCESS() == false)
{
//运行电源管理,以实现低功耗
nrf_pwr_mgmt_run();
}
}
/**
* timer定时器初始化函数
*/
static void timer_init(void)
{
ret_code_t err = app_timer_init();
APP_ERROR_CHECK(err);
}
/**
*电源管理初始化函数
*/
static void power_management_init(void)
{
ret_code_t err;
err = nrf_pwr_mgmt_init();
APP_ERROR_CHECK(err);
}
/**
* 连接参数协商模块错误处理事件回调函数
*/
static void conn_params_error_handle(uint32_t nrf_error)
{
APP_ERROR_CHECK(nrf_error);
}
/**
* 连接参数协商模块事件回调函数
*/
static void on_conn_params_event_handle(ble_conn_params_evt_t * event)
{
ret_code_t err;
//连接参数协商失败
if(event->evt_type == BLE_CONN_PARAMS_EVT_FAILED)
{
err = sd_ble_gap_disconnect(conn_handle,BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
APP_ERROR_CHECK(err);
}
//连接参数协商成功
else if(event->evt_type == BLE_CONN_PARAMS_EVT_SUCCEEDED)
{
}
}
/**
* 连接参数协商模块初始化
*/
static void conn_params_init(void)
{
ret_code_t err;
ble_conn_params_init_t cp_init; //创建连接参数结构体
memset(&cp_init,0,sizeof(cp_init)); //连接参数结构体数据清零
cp_init.p_conn_params = NULL; //从主机获取连接参数
cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY; //首次连接更新参数请求时间
cp_init.next_conn_params_update_delay = NEXT_CONN_PARAMS_UPDATE_DELAY; //连接参数请求更新间隔时间
cp_init.max_conn_params_update_count = MAX_CONN_PARAMS_UPDATE_COUNT; //放弃连接前尝试连接次数
cp_init.start_on_notify_cccd_handle = BLE_GATT_HANDLE_INVALID; //连接参数更新从连接事件开始计时
cp_init.evt_handler = on_conn_params_event_handle; //注册连接参数更新回调函数
cp_init.error_handler = conn_params_error_handle; //注册连接参数更新错误回调函数
cp_init.disconnect_on_fail = false; //连接参数更新失败不断开连接
err = ble_conn_params_init(&cp_init);
APP_ERROR_CHECK(err);
}
/**
* BLE事件处理函数
*/
static void ble_evt_handler(ble_evt_t const * event,void * p_connect)
{
ret_code_t err;
switch(event->header.evt_id)
{
/* 断开连接事件 */
case BLE_GAP_EVT_DISCONNECTED:
NRF_LOG_INFO("GAP disconnect");
break;
/* 连接成功事件 */
case BLE_GAP_EVT_CONNECTED:
NRF_LOG_INFO("GAP Connect");
conn_handle = event->evt.gap_evt.conn_handle; //配置连接事件回调函数
err = nrf_ble_qwr_conn_handle_assign(&qwr,conn_handle); //将排队写入实例与连接关联
APP_ERROR_CHECK(err);
break;
/* PHY更新事件 */
case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
{
NRF_LOG_INFO("PHY update request");
ble_gap_phys_t const phys = { //创建phy结构体
.rx_phys = BLE_GAP_PHY_AUTO,
.tx_phys = BLE_GAP_PHY_AUTO
};
err = sd_ble_gap_phy_update(event->evt.gap_evt.conn_handle,&phys);
APP_ERROR_CHECK(err);
}
break;
/* GATT客户端响应超时事件 */
case BLE_GATTC_EVT_TIMEOUT:
NRF_LOG_INFO("GATT Client timeout"); //从机响应超时,断开连接
err = sd_ble_gap_disconnect(event->evt.gattc_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err);
break;
/* GATT服务器响应超时事件 */
case BLE_GATTS_EVT_TIMEOUT:
NRF_LOG_INFO("GATT server Timeout"); //主机响应超时,断开连接
err = sd_ble_gap_disconnect(event->evt.gattc_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err);
break;
default :
break;
}
}
/**
* 蓝牙协议栈初始化函数
*/
static void ble_stack_init(void)
{
ret_code_t err;
err = nrf_sdh_enable_request(); //蓝牙协议栈请求使能
APP_ERROR_CHECK(err);
uint32_t ram_start = 0;
err = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG,&ram_start); //初始化蓝牙协议栈(默认配置)
APP_ERROR_CHECK(err);
err = nrf_sdh_ble_enable(&ram_start); //蓝牙协议栈使能
APP_ERROR_CHECK(err);
NRF_SDH_BLE_OBSERVER(ble_observer,APP_BLE_OBSERVER_PRIO,ble_evt_handler,NULL); //配置蓝牙监测者,检测蓝牙协议栈事件
}
/**
* 广播事件回调函数
*/
static void adv_evt_handler(ble_adv_evt_t event)
{
ret_code_t err;
switch(event)
{
case BLE_ADV_EVT_FAST: //快速广播启动事件
NRF_LOG_INFO("fast advertising");
break;
case BLE_ADV_EVT_IDLE: //广播超时事件
err = bsp_indication_set(BSP_INDICATE_IDLE);
APP_ERROR_CHECK(err);
break;
default:
break;
}
}
/**
* 广播初始化函数
*/
static void advertising_init(void)
{
ret_code_t err;
ble_advertising_init_t init;
memset(&init,0,sizeof(init));
init.advdata.name_type = BLE_ADVDATA_FULL_NAME; //全名
init.advdata.include_appearance = true; //包含外观
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE; //设置为一般可发现模式,不支持BR/EDR
init.config.ble_adv_fast_enabled = true; //设置为快速广播模式
init.config.ble_adv_fast_interval = APP_ADV_INTERVAL; //广播时间间隔
init.config.ble_adv_fast_timeout = APP_ADV_DURATION; //广播持续时间
init.evt_handler = adv_evt_handler; //广播事件回调函数
err = ble_advertising_init(&advertising,&init); //广播初始化函数
APP_ERROR_CHECK(err);
ble_advertising_conn_cfg_tag_set(&advertising,APP_BLE_CONN_CFG_TAG);
}
/**
* 启动广播函数
*/
static void advertising_start(void)
{
ret_code_t err;
err = ble_advertising_start(&advertising,BLE_ADV_MODE_FAST);
APP_ERROR_CHECK(err);
}
/**
* 排队写入事件错误回调函数
*/
static void nrf_qwr_error_handler(uint32_t nrf_error)
{
APP_ERROR_CHECK(nrf_error);
}
/**
* 服务初始函数
*/
static void services_init(void)
{
ret_code_t err;
nrf_ble_qwr_init_t qwr_init = {0};
qwr_init.error_handler = nrf_qwr_error_handler;
err = nrf_ble_qwr_init(&qwr,&qwr_init); //初始化排队写入模块
APP_ERROR_CHECK(err);
}
static void log_init(void)
{
ret_code_t err = NRF_LOG_INIT(NULL);
APP_ERROR_CHECK(err);
NRF_LOG_DEFAULT_BACKENDS_INIT();
}
int main(void)
{
log_init();
timer_init();
power_management_init();
ble_stack_init();
gap_params_init();
gatt_init();
advertising_init();
services_init();
conn_params_init();
NRF_LOG_INFO("已完成初始化配置");
advertising_start();
NRF_LOG_FLUSH();
while(1)
{
idle_state_handle();
}
}