nRF52832调试相关记录

背景

以前业余用nRF52832做了个物联网小项目(蓝牙微微网),做了些相关的调试记录

nRF52832寄存器类型

  • Task: 任务寄存器,可以由程序或事件触发
  • Event: 事件寄存器,事件可以产生中断和触发任务
  • Register: 普通寄存器,和一般单片机的寄存器一样

keil5带的驱动过高为v6.16,手头上的jlink固件为V8,所以要下载4.9的驱动,最后用的是V6.12j,可以在jlink官网上下载。
替换MDK(MDK524)安装目录下的Segger文件夹,

报Clone,重刷jlink的V8固件,自定义ID,SN

  • 在KEIL中设置中使用jink报错:error:cannot load driver".....JL2CM3.dll"
    将keil安装目录的Segger路径,如D:\Keil_v5\ARM\Segger添加到系统环境变量

用Sergger包覆盖Keil安装目录下的

nRFgo Studio 和 Nrfjprog 无法找到JLinkARM.dll的解决方法:

手动修改注册表jlink安装的相应字段
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\SEGGER\J-Link\InstallPath
HKEY_CURRENT_USER\Software\SEGGER\J-Link\InstallPath
因为卸载/更新Segger的时候,注册表的相关的字段是不会被修改的

编译问题

警告

keil编译C++代码时,出现警告: >#1035-D: single-precision operand implicitly converted to double-precision

float代表浮点型数据类型,浮点型数据又分为单精度和双精度两种,1.0小写f或者大写F代表他是单精度的,如果1.0后面跟的是小写d后者大写D代表他是双精度的。
可以忽略这个警告,也可以在所有的浮点数字后面加上f,警告就会消失。比如:float a = 1.01f;

NRF52 SDK16.0编译问题:

https://blog.csdn.net/mygod2008ok/article/details/103444490
使用NordicSemiconductor.nRF_DeviceFamilyPack.8.29.0来代替
select software packs:
选择fixed和excluded,点OK确定
先将原先的Device下的Startup和StartupConfig不选中,点击OK确定,然后再次打开此窗口
manage run…:
选中Device下的选项,点OK确定

编译报错:
nRF5_SDK_16.0.0_98a08e2\modules\nrfx\mdk目录下的system_nrf52.c文件复制到nRF5_SDK_16.0.0_98a08e2\examples\ble_peripheral\ble_app_uart\pca10040\s112\arm5_no_packs\RTE\Device\nRF52832_xxAA目录下并替换掉此目录下的system_nrf52.c文件,然后重新编译

微微网

SDK16:
主机demo:examples\ble_central\ble_app_multilink_central
从机demo:examples\ble_peripheral\ble_app_blinky
sdk_config.h配置文件使能相应模块,可在下面切换成configuration Wizard选项卡勾选
添加相应的库或驱动文件到工程,并添加相应的文件路径(Options for Target选项卡的C/C++中)

DEBUG

RTT:

  1. sdk_config.h文件中配置:
    • NRF_LOG_BACKEND_RTT_ENABLED 置1
    • NRF_LOG_BACKEND_UART_ENABLED 置0
  2. keil配置
    Debug->Trace 中的 Enable 打钩
    打开MDK魔术棒,C/C++选项,Define中添加一个DEBUG,定义宏DEBUG
  3. 使用J-Link RTT Viewer查看

注:还是不行的话,重新插拔下JLINK,或者重启下电脑
J-Link安装的地方
NRF_LOG_INFO打印浮点数:
NRF_LOG_INFO("Lux:" NRF_LOG_FLOAT_MARKER "\r\n", NRF_LOG_FLOAT(*lux));

PPI

ADC:

PPI双缓冲中断采样ADC
在BLE中使用注意timer不能用Timer0,协议栈用了
https://blog.csdn.net/Smile_Smilling/article/details/91352391
双缓冲PPI采样:设置采样时间(这个采样时间是采样一个点的时间,如果采样时间设置为400ms,采集5个点,则全部的采样时间为400*5=2s。如果为两个通道,则400ms取两个点,取五次)
同时将ADC,PPI,TIMER都关掉,可以有效降低功耗
启动ADC

void ADC_PWM_START(void)
{
	nrf_saadc_enable();
	nrf_saadc_task_trigger(NRF_SAADC_TASK_START);
	nrf_drv_timer_enable(&m_timer);
}

关闭ADC,需要将相关标志位清楚,不然会导致错误

void ADC_PWM_STOP(void)
{
	nrf_drv_timer_disable(&m_timer);
	nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
	nrf_saadc_event_clear(NRF_SAADC_EVENT_STARTED);
	nrf_saadc_event_clear(NRF_SAADC_EVENT_END);
	nrf_saadc_disable();
}
  1. 单次模式
  2. 连续模式
    使用ADC内部定时器实现定时采样
    使用nRF52832的通用定时器定时同PPI触发采样,实现连续采样
  3. 扫描模式
    当使能一个ADC通道,ADC工作于单次模式,当使能的通道数量大于1个,ADC进入扫描模式
    配置双缓冲区域,这个比较有意思,采样是按你上面初始化了的通道顺序循环的,这里我建议设置成前面通道数的倍数,不然你分不清是哪个通道的数据。比如这里我有3个通道,缓冲区域大小为6,那么每采集两组以后才会触发回调函数,采集之中CPU是不会工作的,而双缓冲的设置还保证了连续性

电池电量采集及多通道采集

RTC

  1. 新建一个1秒定时的APP_TIMER。
    优点:创建方便。
    缺点:实时性太差。
    APP_TIMER采用轮询执行而非抢占的方式,假如其它APP_TIMER耗时较长,例如有一个APP_TIMER在采集心率,万年历的APP_TIMER就必须等采集心率完成才能执行。
  2. 在APP_TIMER的中断中插入计算时间戳的代码
    优点:修改代码较少。
    缺点:代码耦合性高,易出现未知问题。
    我们都知道APP_TIMER的实现就是使用了RTC1,那么我们就可以利用上RTC1的特点,适当修改代码来实现我们的需求,但这样容易造成代码耦合性高,不易维护和管理。
  3. 用一个新的RTC来实现。
    优点:不会影响原来APP_TIMER的功能,模块独立,方便管理。
    缺点:需要深入了解芯片的RTC,根据RTC特性来实现自己的功能。

协议栈也是用到LFCLK时钟,所以在ble_stack_init函数中LFCLK已经被使能了
如实现8HZ的频率,则PRESCALER 寄存器应该设为
32768/8-1 = 4095
https://www.jianshu.com/p/1afe0d565825

内部温度采集

  1. 初始化
    ///内部温度传感器初始化
    nrf_temp_init();
  • 如果使用了SoftDevice,就要如下调用:
    int32_t temp;
    while (sd_temp_get(&temp)!=NRF_SUCCESS);
    
    // Die temperature in 0.25 degrees celsius.
    printf("Temp:%d", (int)temp*25/100);
  • 如果使用裸机,则如下调用:
    //开始温度测量
    NRF_TEMP->TASKS_START = 1;
    while(NRF_TEMP->EVENTS_DATARDY == 0)
    {
    }
    NRF_TEMP->EVENTS_DATARDY = 0;
    in_temp = nrf_temp_read()/4; /*寄存器0.25摄氏度一个进位级*/
    NRF_TEMP->TASKS_STOP = 1; /** Stop the temperature measurement. */
    

获取从机MAC

sd_ble_gap_addr_get( &addr);
https://www.jianshu.com/p/f56e0d9e9432

cJson

拷贝cJSON.ccJSON.hcJSON_Utils.ccJSON_Utils.h到工程中,malloc.h
常运行cJSON的测试例程需要3KB的heap,如果芯片内存足够,可以在启动文件(startup_XXX.s)里修改Heap_Size

串口-UARTE

https://blog.csdn.net/qq_36347513/article/details/104478737

芯片唯一ID

获取自己的MAC

#include "ble_gap.h"
ble_gap_addr_t addr;
uint32_t err_code = sd_ble_gap_addr_get(&addr);
APP_ERROR_CHECK(err_code);
addr.addr[5 - i];

时钟选择

低频时钟源

NRF_SDH_CLOCK_LF_SRC 
// <0=> NRF_CLOCK_LF_SRC_RC 
// <1=> NRF_CLOCK_LF_SRC_XTAL 
// <2=> NRF_CLOCK_LF_SRC_SYNTH 

GPIO输入中断接口使用

添加相关nrf_drv_gpiote.h

uint32_t err_code = NRF_SUCCESS;
err_code = nrf_drv_gpiote_init();											// GPIOE驱动初始化,如有其它GPIO中断只调用一次
APP_ERROR_CHECK(err_code);

nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(false);//// 双边沿中断触发 //true表示高精度,false表示低精度
in_config.pull = NRF_GPIO_PIN_PULLUP;
//in_config.sense = NRF_GPIOTE_POLARITY_TOGGLE;
err_code = nrf_drv_gpiote_in_init(KEY1, &in_config, key_event_handler);
err_code = nrf_drv_gpiote_in_init(KEY2, &in_config, key_event_handler);
APP_ERROR_CHECK(err_code);  

nrf_drv_gpiote_in_event_enable(KEY1, true);
nrf_drv_gpiote_in_event_enable(KEY2, true);

void in_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{
  nrf_drv_gpiote_out_toggle(LED_2);  
}

低功耗

2.6mA->(切换为DCDC、使用内部RC、关闭Log)->2.1mA->(动态关闭ADC)->180uA->(去掉LDO)->13uA->去掉LED初始化(GPIO)->3uA

默认PIN码:0000

串口不定长接收

https://blog.csdn.net/qq_33784286/article/details/104899319
DMA的一次接收的数据长度还是一次发送数据长度都不得大于255
libuarte:
examples\peripheral\libuarte\main.c

定时器:

  1. 加入驱动文件
  2. sdk_config.h中打开配置

watchdog

  1. sdk_config.h中打开配置WDT_ENABLED
  2. 添加看门狗驱动
    modules\nrfx\drivers\src\nrfx_wdt.c
  3. 添加头文件nrf_drv_wdt.h
    integration\nrfx\legacy\nrf_drv_wdt.h
  4. 添加相关初始化和处理代码
    nrf_drv_wdt_channel_id m_channel_id;
    void wdt_event_handler(void)
    {
        bsp_board_leds_off();
    
        //NOTE: The max amount of time we can spend in WDT interrupt is two cycles of 32768[Hz] clock - after that, reset occurs
    }
    /*************************************************************
    * @brief     看门狗初始化
    * @param  
    * @param   
    * @retval None
    *************************************************************/
    void WDT_Init(void)
    {
            uint32_t err_code = NRF_SUCCESS;
    
            //Configure WDT.
            nrf_drv_wdt_config_t config = NRF_DRV_WDT_DEAFULT_CONFIG;
            err_code = nrf_drv_wdt_init(&config, wdt_event_handler);
            APP_ERROR_CHECK(err_code);
            err_code = nrf_drv_wdt_channel_alloc(&m_channel_id);
            APP_ERROR_CHECK(err_code);
            nrf_drv_wdt_enable();// 使能WDT
    }
    /*************************************************************
    * @brief     喂狗函数
    * @param  
    * @param   
    * @retval None
    *************************************************************/
    void FEED_DOG(void)
    {
      nrf_drv_wdt_channel_feed(m_channel_id);
    }
  5. WDT_CONFIG_RELOAD_VALUE为喂狗最大周期,单位为ms

注意喂狗时机

扫描

扫描函数sd_ble_gap_scan_start

https://blog.csdn.net/weixin_42396877/article/details/85327788

注意:
调用函数 sd_ble_gap_scan_start() 时,需要应用通过 p_adv_report_buffer 保持 memory pointed, 直到 p_adv_report_buffer 被释放。当扫描被停止,或这个功能被调用,且有另一个缓冲区, p_adv_report_buffer 将被释放。

在下列情况,扫描将被自动停止.

  • sd_ble_gap_scan_stop() 函数被调用
  • sd_ble_gap_connect() 数被调用
  • BLE_GAP_EVT_TIMEOUT被设置,且 BLE_GAP_TIMEOUT_SRC_SCAN 事件出现
  • BLE_GAP_EVT_ADV_REPORT 事件出现,且 ble_gap_adv_report_type_t::status 没有被设置为 BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA;此条件下,扫描是被暂停,以便应用获取接收到的数据。应用需调用继续扫描,或是调用 sd_ble_gap_scan_stop() 来停止扫描。

BLE_GAP_EVT_ADV_REPORT 事件出现,且 ble_gap_adv_report_type_t::status 被设置为 BLE_GAP_ADV_DATA_STATUS_INCOMPLETE_MORE_DATA;此条件下,扫描继续进行,应用将从广播事件中获取更多报告。这些报告将包括旧的和新的接收到数据。

主机的扫描参数

static ble_gap_scan_params_t m_scan_param =											/**< Scan parameters requested for scanning and connection. */
{
    .active        = 0x01,															// 主动扫描
    .interval      = NRF_BLE_SCAN_SCAN_INTERVAL,									// 扫描间隔
    .window        = NRF_BLE_SCAN_SCAN_WINDOW,										// 扫描窗口
    .filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL,
    .timeout       = NRF_BLE_SCAN_SCAN_DURATION,									// 扫描超时
    .scan_phys     = BLE_GAP_PHY_1MBPS,
    .extended      = true,
};
  • active:是否主动扫描,配置为1则是主动扫描,0则是被动扫描
  • interval:扫描间隔,控制器间隔多长时间扫描一次,也就是两个连续的扫描窗口开始时间的时间间隔。在 NRF 上设置为 0x0004 and 0x4000 in 0.625ms units(2.5ms 到 10.24s)
  • window:扫描窗口,每次扫描所持续的时间,在持续时间内,扫描设备一直在广播信道上运行。在 NRF 上设置为 0x0004 and 0x4000 in 0.625ms units(2.5ms 到 10.24s),两个连续的扫描窗口的起始时间之间的时间差,包括扫描休息的时间和扫描进行的时间
  • filter_policy:扫描筛选策略,也就是说接受任何广播数据或者仅仅接受白名单设备的广播数据包。实际上就决定是否使用白名单过滤广播数据包。这里注意一点,如果定向广播数据包中的目的地址并非是自己的,那么该数据必须抛弃,即使广播数据包的发送者在自己的白名单中。
  • timeout:扫描超时,超过指定的时间后,没有扫描到设备将停止扫描。在 NRF 上设置为 0x0001 and 0xFFFF in seconds,设置为 0 则认为没有 timeout
  • scan_phys:扫描的物理层速度

从机广播间隔

主机连接间隔

MIN_CONN_INTERVAL
https://blog.csdn.net/sinat_23338865/article/details/51533312
nrf52832 连接参数更新过程
ble 连接参数更新过程如下, 一般分三个过程:

  1. 主机发起连接(带有一个连接参数,一般都是 7.5ms)
  2. 主机更新连接参数 (举例:NRF CONNECT 安卓app软件 45ms)。
  3. 从机更新连接参数(一般有延时机制,4S,更新 程序设置的 最大连接间隔 MAX_CONN_INTERVAL)
    nRF5 SDK 中 有决定 第三步 是否更新连接参数宏 NRF_BLE_CONN_PARAMS_ENABLED sdk_config.h 文件中。

cJSON

堆栈大小
内存分配
字节对齐
cJSON_Delete使用

慎用动态分配内存

LOG

NRF_LOG_FINAL_FLUSH();

主要是在sdk_config.h中配置两个宏:

  • NRF_LOG_ENABLED
  • NRF_LOG_BACKEND_RTT_ENABLED
    将这两个值 的宏定义从0改为1,然后程序中使用 NRF_LOG_INFO(“test info”),即可在debug时在调试窗口看到打印的log。
    最后别忘了在主循环中使用:NRF_LOG_PROCESS(); 这个函数,否则也不会有打印信息出来。

nRF_Log还有一个功能:如果不使能Deferred,那么调用NRF_LOG_INFO等API的时候,立马就Flush,即把日志打印出去;如果使能了Deferred,那么调用NRF_LOG_INFO等API的时候,只是把打印数据放在RAM中,真正的打印由main函数中的NRF_LOG_PROCESS完成,相关宏可以在sdk_config.h中找到 , 即NRF_LOG_DEFERRED

原文链接:https://blog.csdn.net/behold1942/article/details/90692763

报错

<error> nrf_ble_gq: SD GATT procedure (1) failed on connection handle 1 with error: 0x00000008.
<error> app: ERROR 8 [NRF_ERROR_INVALID_STATE] at ..\..\..\ble_btf.c:174