赞
踩
在项目中使用的52840搭载的FreeRTOS操作系统,使用的是SES开发环境而非Keil(因为Nordic买了SES的版权,使用SES不会有版权问题)
NRF52840使用记录
在项目中使用的52840搭载的FreeRTOS操作系统,使用的是SES开发环境而非Keil(因为Nordic买了SES的版权,使用SES不会有版权问题)
由于52840支持NFC功能,因此该两个引脚默认为NFC引脚,在项目中如果用作普通的GPIO,则需要进行设置。
需要在system_nrf52.c中加宏定义
#define CONFIG_NFCT_PINS_AS_GPIOS
在nrf52840做主机时,需要使用DMA进行发送,但是由于SPI的easyDMA(不只是SPI)的长度限制为不超过256字节,因此,在我的项目中,由于使用的是每包512字节的机制,所以需要进行分包发送。还出现过uninit时,while循环导致看门狗重启,由从机导致。需要注意此点。
目前有很多单片机中的I2C变成了TWI。TWI全兼容I2C,但并不局限于I2C,双线发送均可使用TWI进行操作。
TWI在操作上较I2C复杂,需要自己写一个I2C的接口,使用TWI进行封装
nrf52840的例程中,使用的heap较小,当开辟较大数据时,容易出现开辟失败的情况,如果需要的话,需要对heap进行扩容。也需要保证malloc和free的匹配性,当然也可以使用官方的内存管理机制,为原子操作,更放心,但是需要对其较为了解。
GPIO本来是只支持8个引脚的中断,但是使用PORT,还有sdk_config.h中的宏,来进行规避此问题,例如我的项目中就高达10个中断。
使用该方式时,有可能需要将结构体元素中的hi_accuracy进行赋值为1,否则可能会有检测不到中断的可能性!!!但会增加部分功耗(10uA)
nrf芯片可以记录复位原因,包括复位引脚,上电,看门狗,也可以用户在需要重启的时候写入寄存器,自定义原因,在上电时,就可以进行分别处理,我的项目中,是做了非用户操作的重启,则用户不感知重启的功能。重启不清零数据的内存地址放到了 attribute((section(“.non_init”)));如果有bootloader,需要同步设置
这个可以通过map文件搜索,和debug模式下搜索,可以找到具体的代码,进行定位问题。
或者使用objdump对elf进行反汇编。需要安装
arm-none-eabi 工具。如下命令:
arm-none-eabi-objdump -D “T7400_1.0.0.4.elf” > T7400_1.0.0.4.S
在生成汇编文件后,即可进行查看汇编代码查看具体问题
默认队列为4,当队列用完时,会返回错误码,此处导致的问题需要注意;
nrf_fstorage_write flash等函数,操作数据源地址(*p_src)也需要四字节对齐,可使用 attribute((aligned(4)))进行对齐。并且flash写入是异步操作,所以数据源地址(*p_src)要注意不要使用局部变量;
由于我的项目中,软件测试是用串口接收打印,而不是使用Jlink,因此需要将数据通过串口输出,但是当使用串口进行输出时,出现的异常重启则不可以进行打印,导致分析出现问题。因此需要将APP_ERROR_CHECK和HardFault的数据也通过串口输出出来。
下面为我的调试代码:用于重大错误即将重启之前的调用
nrf_drv_uart_t m_uart = NRF_DRV_UART_INSTANCE(UART_LOG_INSTANCE); static volatile bool m_xfer_done; //发送完成中断函数 static void uart_evt_handler(nrf_drv_uart_event_t *p_event, void *p_context) { m_xfer_done = true; } //重大错误调用函数,在HardFault中也可调用 void _log_error(char *fmt, uint16_t len) { //反初始化串口,这个地方需要注意,因为不知道有没有进行开始串口,内部可通过标志位或者事件进行对应处理 uart_driver_deinit(); //初始化串口 nrf_drv_uart_config_t config = NRF_DRV_UART_DEFAULT_CONFIG; config.pseltxd = UART_LOG_TX_PIN; //只需要用到发送串口 config.pselrxd = NRF_UART_PSEL_DISCONNECTED; config.pselcts = NRF_UART_PSEL_DISCONNECTED; config.pselrts = NRF_UART_PSEL_DISCONNECTED; config.baudrate = (nrf_uart_baudrate_t)NRF_LOG_BACKEND_UART_BAUDRATE; nrf_drv_uart_init(&m_uart, &config, uart_evt_handler); //日志进行格式化,带颜色较为清晰 uint8_t err_buf[256]; snprintf(err_buf, sizeof(err_buf), "\r\n\033[31m<<<<<reboot>>>>>:\033[0m\033[33m %s\033[0m", fmt); m_xfer_done = false; //进行数据输出 nrf_drv_uart_tx(&m_uart, (uint8_t *)err_buf, strlen(err_buf)); //tick用来确保中断被关闭时,也能正常退出 uint32_t tick = 1000000; while (m_xfer_done == false && tick-- != 0) { } nrf_drv_uart_uninit(&m_uart); //调试使用,连上jlink时,也可进行jlink输出 NRF_LOG_FLUSH(); }
使用jlink调试,可以查看所有内存信息和堆栈调用。参考博客 https://github.com/meishaoming/blog/issues/53
外设 | 运行功耗 |
---|---|
CPU | 3~6mA |
Radio | 4~16mA |
SAADC | 1.24mA |
PWM | 0.2mA |
WDT | 0.003mA |
GPIOTE | 0.017mA |
DMA | 2mA |
FPU | 6mA |
I2C | 0.1mA |
SPI | 0.1mA |
adv | 0dBm 1s广播 0.022mA |
在所有使用freertos的工程中,一般默认推荐的都是使用的heap_4这个内存管理机制。
该内存管理机制是用FreeRTOS进行统一管理,FreeRTOS使用的所有内存都从一个数组中进行开辟。而Nordic官方使用的heap_1作为例程。当进行任务开辟时,非常容易由于总共的堆栈空间分配不足,导致失败。因此我后来也是选用了heap_4这个内存管理机制
但是当任务堆栈不足时,也会导致任务开辟失败。可通过修改configTOTAL_HEAP_SIZE的值进行处理。默认为4k,几个任务就不够了。
需要注意的问题是,你任务中使用的所有变量,局部变量、函数调用占用的空间等,都是占用的任务的堆栈空间,malloc函数占用的是程序的堆空间。因此要保证开辟任务时,保证开辟的任务堆栈空间不足,否则就会导致重启,可通过configCHECK_FOR_STACK_OVERFLOW为1,在钩子内进行打印堆栈溢出的任务名字,进行确认。还可以通过使用INCLUDE_uxTaskGetStackHighWaterMark的方式来查看任务的最深调用。当接近0时,需要增加任务堆栈,当一直远远大于0时,可考虑进行减少任务堆栈。
需要注意可能并不是所有任务都初始化时就开始,有些任务可能开辟,不久就释放,所以测试时,需要进行全部任务开启测试,保证运行中开辟任务时,出现内存不够的问题。
由于FreeRTOS的机制问题,需要区分中断的快速操作处理还是非中断模式下的可延时等待处理,因此给了两个函数,中断中需要调用带FromISR的函数,因此在写代码时需要非常注意。但是可以通过一个函数来进行判断0 == __get_CONTROL()
if (0 == __get_CONTROL()) // in isr handler
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(log_QueueHandle, &info, &xHigherPriorityTaskWoken);
}
else
{
xQueueSend(log_QueueHandle, &info, wait_time);
}
由于nordic移植的函数 configUSE_IDLE_HOOK一般为0,不会有功耗问题,但是打印不全,因为没有位置调用NRF_LOG_FLUSH函数,进行日志输出。但是当该值设置为1时,打印完全,但是芯片却没有休眠(8mA)线程未休眠都是8mA左右,一直在执行该函数。
知道该原因也就好解决了,我的采用方式是,定义自己的日志输出函数,通过串口输出,当自己的日志进行输出时,也直接调用NRF_LOG_FLUSH函数。
nordic的任务优先级configMAX_PRIORITIES中最大为3 ,可能不满足任务要求,推荐经典值16 ,与uCOSII不同,该值越大,优先级越高
当高优先级的任务产生时,会直接打断正在运行的低优先级任务,因此很多函数可能需要做好重入处理。比如外设的init和uninit函数。否则由于该问题导致的重启就很难找到。
nordic的定时器任务深度默认只有80,当项目中增加定时器,需要注意此处导致的重启configTIMER_TASK_STACK_DEPTH,我的项目中应用的为1k
nordic的默认名字configMAX_TASK_NAME_LEN长度为4 ,推荐根据具体项目具体更改。
建议看门狗单独做一个任务,且任务优先级为1,不能和其他任务同时使用,否则则可能出现任务中的其他功能阻塞掉看门狗任务,导致看门狗重启。
nordic默认 configTICK_RATE_HZ 宏为1024,但是如果使用该值,所有延时时间都会提前结束,一秒钟提前几个毫秒,累加提前,十分钟大概12~13s。需要将该宏修改为1000,或者延时,乘上节拍系数 。该值为portTICK_PERIOD_MS 。但是如果任务调度过多,该方式可能会导致延时稍晚结束。但比1024准确很多,如需必须很精准定时,需要使用rtc时间
可以在hard fault函数里打印当前任务名字,当前堆栈值,可通过堆栈值进行判断。堆栈深度和堆栈初始值,可以判断是否堆栈溢出,部分堆栈溢出不会调用钩子,直接被hard fault了。
在task.c里添加如下函数即可。
void xTaskStackShow(TCB_t *pxTCB) { volatile unsigned long *stack; unsigned long addr1; unsigned long addr2; unsigned long addr3; unsigned long addr4; if (pxTCB == NULL) return; stack = (volatile unsigned long *)pxTCB->pxTopOfStack; //打印当前任务所有堆栈信息 _log_error("dump task:[%s] pxStack %x,depth %x", pxTCB->pcTaskName, pxTCB->pxStack, pxTCB->uxStackDepth); while (stack < ((unsigned long *)(pxTCB->pxStack) + (UBaseType_t)(pxTCB->uxStackDepth))) { addr1 = *stack++; addr2 = *stack++; addr3 = *stack++; addr4 = *stack++; _log_error("[<%08lx>] [<%08lx>] [<%08lx>] [<%08lx>] ", addr1, addr2, addr3, addr4); } //打印当前任务名字 char * xTaskGetCurrentTaskName(void) { TCB_t *task = xTaskGetCurrentTaskHandle(); return task->pcTaskName; } //打印当前任务信息 void xTaskStackShowCurrentTask(void) { xTaskStackShow(xTaskGetCurrentTaskHandle()); }
uxStackDepth该成员变量官方库并未给出,需要自己在TCB_t中添加
注意添加位置和添加类型.。再创建任务时进行赋值
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。