唯一ID程序学习:
/******************** (C) COPYRIGHT 2023青风电子 ******************** * 文件名 :main * 出品论坛 :www.qfv8.com * 实验平台:青云nRF52xx蓝牙开发板 * 描述 :串口输出 * 作者 :青风 * 店铺 :qfv5.taobao.com **********************************************************************/ #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include "app_uart.h" #include "app_error.h" #include "nrf_delay.h" #include "nrf.h" #include "bsp.h" #if defined (UART_PRESENT) #include "nrf_uart.h" #endif #if defined (UARTE_PRESENT) #include "nrf_uarte.h" #endif //#define ENABLE_LOOPBACK_TEST /**< if defined, then this example will be a loopback test, which means that TX should be connected to RX to get data loopback. */ #define MAX_TEST_DATA_BYTES (15U) /**< max number of test bytes to be used for tx and rx. */ #define UART_TX_BUF_SIZE 256 /**< UART TX buffer size. */ #define UART_RX_BUF_SIZE 256 /**< UART RX buffer size. */ void uart_error_handle(app_uart_evt_t * p_event) { if (p_event->evt_type == APP_UART_COMMUNICATION_ERROR) { APP_ERROR_HANDLER(p_event->data.error_communication); } else if (p_event->evt_type == APP_UART_FIFO_ERROR) { APP_ERROR_HANDLER(p_event->data.error_code); } } #define UART_HWFC APP_UART_FLOW_CONTROL_DISABLED /** * @brief Function for main application entry. */ int main(void) { uint32_t err_code; uint32_t id1,id2; id1=NRF_FICR->DEVICEID[0]; //读取id低31位 id2=NRF_FICR->DEVICEID[1];//读取id高31位 const app_uart_comm_params_t comm_params = { RX_PIN_NUMBER, TX_PIN_NUMBER, RTS_PIN_NUMBER, CTS_PIN_NUMBER, UART_HWFC, false, #if defined (UART_PRESENT) NRF_UART_BAUDRATE_115200 #else NRF_UARTE_BAUDRATE_115200 #endif }; APP_UART_FIFO_INIT(&comm_params, UART_RX_BUF_SIZE, UART_TX_BUF_SIZE, uart_error_handle, APP_IRQ_PRIORITY_LOWEST, err_code); APP_ERROR_CHECK(err_code); while (1) { printf("打印id:%lx%lxrn",id1,id2); nrf_delay_ms(1000); } } /** @} */
第一节
#include <stdbool.h> #include <stdint.h> #include <stdio.h> #include "app_uart.h" #include "app_error.h" #include "nrf_delay.h" #include "nrf.h" #include "bsp.h" #if defined (UART_PRESENT) #include "nrf_uart.h" #endif #if defined (UARTE_PRESENT) #include "nrf_uarte.h" #endif
1.1这段代码主要完成以下工作:
- 引入必要的头文件
#include <stdbool.h> #include <stdint.h> #include <stdio.h>引入标准 C 库头文件,提供基础数据类型和标准输入输出功能
- 引入 Nordic SDK 组件
#include "app_uart.h" #include "app_error.h" #include "nrf_delay.h" #include "nrf.h" #include "bsp.h"app_uart.h:提供 UART 应用层驱动app_error.h:错误处理机制nrf_delay.h:延时函数nrf.h:nRF52 芯片寄存器定义bsp.h:板级支持包
- 条件编译处理不同 UART 外设
#if defined (UART_PRESENT) #include "nrf_uart.h" #endif #if defined (UARTE_PRESENT) #include "nrf_uarte.h" #endifUART_PRESENT:表示芯片包含基本 UART 外设UARTE_PRESENT:表示芯片包含增强型 UARTE 外设 (带硬件流控制和 DMA)
1.2串口通信关键组件解析
1. UART vs UARTE
- UART:基本串口通信模块
- UARTE:增强型串口模块,相比 UART 增加了以下功能:
- 硬件流控制 (RTS/CTS)
- DMA 支持,减轻 CPU 负担
- 更高的通信可靠性
2. 应用层 UART 驱动 (app_uart.h)
Nordic SDK 提供的应用层 UART 驱动具有以下特点
- 基于事件驱动架构
- 支持 FIFO 缓冲区
- 提供错误回调机制
- 兼容 UART 和 UARTE 外设
3. 错误处理机制
app_error.h 中定义的错误处理宏:
APP_ERROR_CHECK(err_code):检查错误码,非零则触发错误处理APP_ERROR_HANDLER(error_code):错误处理函数,通常会:- 记录错误码
- 点亮错误指示灯
- 进入无限循环或复位系统
第二节
//#define ENABLE_LOOPBACK_TEST /**< if defined, then this example will be a loopback test, which means that TX should be connected to RX to get data loopback. */ #define MAX_TEST_DATA_BYTES (15U) /**< max number of test bytes to be used for tx and rx. */ #define UART_TX_BUF_SIZE 256 /**< UART TX buffer size. */ #define UART_RX_BUF_SIZE 256 /**< UART RX buffer size. */
这部分代码定义了串口通信的关键参数和测试配置,下面为你详细解释:
2.1. 回环测试宏定义
//#define ENABLE_LOOPBACK_TEST /**< if defined, then this example will be a loopback test, which means that TX should be connected to RX to get data loopback. */
- 功能:启用串口回环测试模式
- 使用方法:取消注释该行即可启用回环测试
- 工作原理:
- 将 TX 引脚与 RX 引脚物理连接
- 发送的数据会被立即回收到接收缓冲区
- 用于验证 UART 硬件和驱动是否正常工作
2.2. 数据传输相关宏定义
#define MAX_TEST_DATA_BYTES (15U) /**< max number of test bytes to be used for tx and rx. */
- 作用:定义测试时最大发送 / 接收数据字节数
- 典型应用场景:
uint8_t test_data[MAX_TEST_DATA_BYTES] = "Hello UART!"; for (int i = 0; i < MAX_TEST_DATA_BYTES; i++) { app_uart_put(test_data[i]); }
2.3. 缓冲区大小定义
#define UART_TX_BUF_SIZE 256 /**< UART TX buffer size. */ #define UART_RX_BUF_SIZE 256 /**< UART RX buffer size. */
- FIFO 缓冲区作用:
- 发送缓冲区:暂存待发送的数据,避免 CPU 等待串口发送完成
- 接收缓冲区:暂存接收到的数据,防止数据丢失
- 参数调整建议:
- 高速通信场景 (如 115200bps 以上):增大缓冲区
- 空间受限设备:适当减小缓冲区,但需注意:
// 缓冲区溢出风险示例 if (app_uart_get(&data) == NRF_SUCCESS) { // 及时处理数据,避免缓冲区溢出 }
2.4. 实际应用注意事项
回环测试的典型实现
#ifdef ENABLE_LOOPBACK_TEST // 发送测试数据 app_uart_put('A'); // 接收回环数据 uint8_t received; while(app_uart_get(&received) != NRF_SUCCESS); printf("Loopback test: received %cn", received); #endif
缓冲区溢出处理
static void uart_event_handle(app_uart_evt_t * p_event) { if (p_event->evt_type == APP_UART_COMMUNICATION_ERROR) { // 处理通信错误 } else if (p_event->evt_type == APP_UART_FIFO_ERROR) { // 处理FIFO溢出错误 } }
2.5. 参数优化建议
- 低功耗场景:
#define UART_TX_BUF_SIZE 64 // 减小发送缓冲区节省RAM #define UART_RX_BUF_SIZE 64 // 减小接收缓冲区节省RAM - 高速数据传输场景:
#define UART_TX_BUF_SIZE 512 // 增大发送缓冲区处理突发数据 #define UART_RX_BUF_SIZE 512 // 增大接收缓冲区处理突发数据
通过合理配置这些参数,可以在资源占用和通信性能之间取得最佳平衡。
第三
void uart_error_handle(app_uart_evt_t * p_event) { if (p_event->evt_type == APP_UART_COMMUNICATION_ERROR) { APP_ERROR_HANDLER(p_event->data.error_communication); } else if (p_event->evt_type == APP_UART_FIFO_ERROR) { APP_ERROR_HANDLER(p_event->data.error_code); } }
这个函数是 UART 错误处理回调函数,用于处理 Nordic SDK 中 UART 通信过程中出现的错误。下面为你详细解析:
函数功能概述
该函数作为 UART 事件回调的一部分,专门处理两种严重错误:
- 通信错误(如帧错误、奇偶校验错误等)
- FIFO 缓冲区错误(如溢出、下溢等)
当检测到这些错误时,会调用 Nordic SDK 的错误处理宏APP_ERROR_HANDLER进行处理。
3.1错误类型详解
1. 通信错误(APP_UART_COMMUNICATION_ERROR)
可能的错误代码包括:
NRF_ERROR_UART_OVERRUN:接收溢出错误(新数据覆盖未读取数据)NRF_ERROR_UART_PARITY:奇偶校验错误NRF_ERROR_UART_FRAMING:帧格式错误NRF_ERROR_UART_BREAK:接收到 BREAK 信号NRF_ERROR_UART_HW_FLOW:硬件流控制错误
2. FIFO 错误(APP_UART_FIFO_ERROR)
可能的错误代码包括:
NRF_ERROR_NO_MEM:FIFO 内存不足NRF_ERROR_INVALID_STATE:FIFO 状态无效NRF_ERROR_INVALID_PARAM:参数错误
3.2错误处理机制
1. 错误捕获方式
// 注册错误回调函数 APP_UART_FIFO_INIT(..., uart_error_handle, ...);
当 UART 驱动检测到错误时,会调用该回调函数。
2. 错误处理流程
void uart_error_handle(app_uart_evt_t * p_event) { if (p_event->evt_type == APP_UART_COMMUNICATION_ERROR) { // 通信错误处理 uint32_t error_code = p_event->data.error_communication; APP_ERROR_HANDLER(error_code); } else if (p_event->evt_type == APP_UART_FIFO_ERROR) { // FIFO错误处理 uint32_t error_code = p_event->data.error_code; APP_ERROR_HANDLER(error_code); } }
3. APP_ERROR_HANDLER 宏
#define APP_ERROR_HANDLER(ERR_CODE)
do
{
app_error_handler_bare((ERR_CODE));
} while (0)
该宏通常会:
- 停止所有中断
- 点亮错误指示灯(如果有配置)
- 进入无限循环或触发系统复位
- 调试模式下可能会打印错误代码
APP_ERROR_HANDLER宏定义是 Nordic nRF5 SDK 中用于错误处理的统一接口,下面我来详细解释它的设计和用途:
-
APP_ERROR_HANDLER(ERR_CODE)- 这是一个错误处理的统一入口,接收一个错误码
ERR_CODE - 通过
do {...} while(0)结构确保宏在任何上下文中都能正确展开 - 调用
app_error_handler_bare函数进行实际的错误处理
- 这是一个错误处理的统一入口,接收一个错误码
3.1其中:void app_error_handler_bare函数定义如下 :
void app_error_handler_bare(ret_code_t error_code)
{
error_info_t error_info =
{
.line_num = 0,
.p_file_name = NULL,
.err_code = error_code,
};
app_error_fault_handler(NRF_FAULT_ID_SDK_ERROR, 0, (uint32_t)(&error_info));
UNUSED_VARIABLE(error_info);
}
app_error_handler_bare 错误处理函数,它是 Nordic SDK(用于 Nordic Semiconductor 的 nRF 系列微控制器)中处理严重错误的核心机制之一, 是一个低级错误处理函数,当系统遇到不可恢复的错误(如硬件故障、内存访问错误或关键 API 调用失败)时被调用。它的主要作用是:
- 收集错误信息(如错误码)。
- 将错误信息传递给更高级的故障处理函数。
- 触发系统复位或进入无限循环,防止系统继续运行在不稳定状态。
3.2 app_error_handler_bare 参数解析
ret_code_t error_code:错误码,通常是 Nordic SDK 中定义的错误类型(如NRF_ERROR_INVALID_PARAM、NRF_ERROR_NO_MEM等)。error_info_t:这是一个自定义结构体,用于存储错误上下文信息,通常包含:line_num:错误发生的源代码行号(这里设为 0,表示未知)。p_file_name:错误发生的源文件名称(这里设为 NULL,表示未知)。err_code:具体的错误码(来自函数参数)。
error_info_t error_info = { .line_num = 0, .p_file_name = NULL, .err_code = error_code, };
3.2.1 error_info_t结构体详细解释:
这段代码定义了一个名为 error_info_t 的结构体,用于在 Nordic SDK(nRF 系列微控制器开发工具包)中存储错误信息。这个结构体是错误处理机制的核心组件,用于在系统发生故障时捕获和传递上下文信息。
1. uint32_t line_num
- 作用:记录错误发生的源代码行号。
- 说明:
- 通常由错误检查宏(如
APP_ERROR_CHECK)自动填充。 - 值为
0表示行号未知(如在app_error_handler_bare中)。
- 通常由错误检查宏(如
2. uint8_t const * p_file_name
- 作用:指向错误发生的源文件名称(字符串常量)。
- 说明:
- 使用
const确保不会修改文件名内容。 uint8_t等价于char,兼容 C 字符串。- 值为
NULL表示文件名未知。
- 使用
3. uint32_t err_code
- 作用:存储具体的错误码,指示错误类型。
- 说明:
- 错误码通常来自 Nordic SDK 的错误枚举(如
NRF_ERROR_INVALID_PARAM)。 - 不同的错误码对应不同的故障原因(如参数无效、内存不足等)。
- 错误码通常来自 Nordic SDK 的错误枚举(如
4.使用示例
在 Nordic SDK 中,这个结构体通常与错误处理函数配合使用。例如:
// 当检测到错误时,自动填充error_info_t结构体 #define APP_ERROR_CHECK(err_code) do { if ((err_code) != NRF_SUCCESS) { error_info_t error_info = { .line_num = __LINE__, // 当前行号(预处理器宏) .p_file_name = __FILE__, // 当前文件名(预处理器宏) .err_code = (err_code) // 具体错误码 }; app_error_handler((uint32_t)(err_code), __LINE__, __FILE__); } } while (0)
5.当调用 APP_ERROR_CHECK(nrf_gpio_pin_set(LED_PIN)) 时:
- 如果
nrf_gpio_pin_set()返回非零错误码,APP_ERROR_CHECK会自动捕获当前文件名(__FILE__)和行号(__LINE__)。 - 这些信息会被打包到
error_info_t结构体中,并传递给错误处理函数。
6.注意事项
- 内存占用:结构体大小为
4 + 4 + 4 = 12字节(32 位系统),设计紧凑。 - 字符串生命周期:
p_file_name指向的字符串必须是静态常量(如__FILE__),否则可能导致悬空指针。 - 简化版本:在资源受限场景(如中断处理)中,可能使用简化版的错误处理函数(如
app_error_handler_bare),此时line_num和p_file_name会被设为默认值(0 和 NULL)。
3.3 app_error_handler_bare函数中 app_error_fault_handler函数:
app_error_fault_handler(NRF_FAULT_ID_SDK_ERROR, 0, (uint32_t)(&error_info));
函数代码如下 :
__WEAK void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info) { __disable_irq(); NRF_LOG_FINAL_FLUSH(); #ifndef DEBUG NRF_LOG_ERROR("Fatal error"); #else switch (id) { #if defined(SOFTDEVICE_PRESENT) && SOFTDEVICE_PRESENT case NRF_FAULT_ID_SD_ASSERT: NRF_LOG_ERROR("SOFTDEVICE: ASSERTION FAILED"); break; case NRF_FAULT_ID_APP_MEMACC: NRF_LOG_ERROR("SOFTDEVICE: INVALID MEMORY ACCESS"); break; #endif case NRF_FAULT_ID_SDK_ASSERT: { assert_info_t * p_info = (assert_info_t *)info; NRF_LOG_ERROR("ASSERTION FAILED at %s:%u", p_info->p_file_name, p_info->line_num); break; } case NRF_FAULT_ID_SDK_ERROR: { error_info_t * p_info = (error_info_t *)info; NRF_LOG_ERROR("ERROR %u [%s] at %s:%urnPC at: 0x%08x", p_info->err_code, nrf_strerror_get(p_info->err_code), p_info->p_file_name, p_info->line_num, pc); NRF_LOG_ERROR("End of error report"); break; } default: NRF_LOG_ERROR("UNKNOWN FAULT at 0x%08X", pc); break; } #endif NRF_BREAKPOINT_COND; // On assert, the system can only recover with a reset. #ifndef DEBUG NRF_LOG_WARNING("System reset"); NVIC_SystemReset(); #else app_error_save_and_stop(id, pc, info); #endif // DEBUG }
app_error_fault_handler:Nordic SDK 提供的故障处理函数,负责:- 记录错误信息(可能通过串口、调试器或闪存)。
- 触发硬件断点(如果调试器连接)。
- 执行系统复位或进入无限循环。
- 参数说明:
NRF_FAULT_ID_SDK_ERROR:故障类型,表示这是一个 SDK 错误。0:附加参数(这里未使用)。(uint32_t)(&error_info):将错误信息结构体的地址转换为uint32_t类型传递。
3.3.1app_error_fault_handler函数详细说明:
这个函数 app_error_fault_handler 是 Nordic SDK 中的核心错误处理函数,当系统遇到严重故障(如断言失败、内存访问错误或未处理的错误码)时会被调用。它的主要作用是记录错误信息、暂停系统运行,并在必要时触发复位。
__WEAK void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info)
__WEAK:GCC 编译器属性,表示这个函数可以被用户代码重写(即用户可以提供自定义实现)。- 参数:
id:故障类型 ID(如NRF_FAULT_ID_SDK_ERROR、NRF_FAULT_ID_SDK_ASSERT)。pc:程序计数器(Program Counter)值,指示故障发生时的代码位置。info:额外的错误信息指针(通常是assert_info_t或error_info_t结构体)。
3.3.2函数执行流程详解
1. 禁用中断并刷新日志
__disable_irq(); NRF_LOG_FINAL_FLUSH();
__disable_irq():禁用所有中断,防止在错误处理过程中被打断。NRF_LOG_FINAL_FLUSH():确保所有日志信息被发送到输出设备(如串口)。
2. 错误信息记录(DEBUG 模式)
#ifdef DEBUG // 根据不同的故障ID输出详细错误信息 #else NRF_LOG_ERROR("Fatal error"); #endif
- DEBUG 模式:根据
id类型输出详细错误信息(如断言位置、错误码含义)。 - 非 DEBUG 模式:仅输出 “Fatal error”,减少资源消耗。
3. 故障类型分类处理
switch (id) { case NRF_FAULT_ID_SD_ASSERT: // 软设备断言失败 case NRF_FAULT_ID_SDK_ASSERT: // SDK断言失败,输出文件名和行号 case NRF_FAULT_ID_SDK_ERROR: // SDK错误,输出错误码、错误描述、位置和PC值 default: // 未知故障 }
NRF_FAULT_ID_SDK_ERROR:对应app_error_handler_bare传递的错误,会解析error_info_t结构体。nrf_strerror_get():将错误码转换为可读字符串(如NRF_ERROR_INVALID_PARAM→ “Invalid parameter”)。
4. 调试断点与系统暂停
NRF_BREAKPOINT_COND;
- 如果调试器已连接,程序会在此处暂停,允许开发者检查寄存器和内存状态。
5. 系统恢复策略
#ifdef DEBUG app_error_save_and_stop(id, pc, info); // 停留在错误状态 #else NVIC_SystemReset(); // 直接复位系统 #endif
- DEBUG 模式:调用
app_error_save_and_stop()进入无限循环,保留错误现场。 - 非 DEBUG 模式:调用
NVIC_SystemReset()强制系统复位,恢复运行。
6.关键技术细节
1. 错误信息结构体解析
assert_info_t(用于断言失败):typedef struct { uint32_t line_num; uint8_t const * p_file_name; } assert_info_t;error_info_t(用于错误码):typedef struct { uint32_t line_num; uint8_t const * p_file_name; uint32_t err_code; } error_info_t;
2. 调试与生产环境的区别
| 特性 | DEBUG 模式 | 非 |
|---|





没有回复内容