一、架构说明
模式 | 描述 | 优势 |
---|---|---|
主从一体 | 1. 多通道通信 2. 动态调频 | 1对7 |
框架功能 | 1. RTX5 2.功能分层 | 架构清晰,集成部分功能方便开发 |
附带功能 | BootLoader(uart)/电源管理/灯光管理/按键管理 | - |
【注意】 CMSIS版本不能低于5.8.0,否则程序将不正常运行。 ARM.CMSIS.5.8.0.pack
5.8.0更新了 [GCC LinkerDescription,GCC 汇编程序启动] [为工具链 ARM、GCC 添加了 ARMv8-M 堆栈密封(到链接器、启动)]
[项目结构]
+---Master_Slave(alone)
| +---app
| | +---app_led [LED线程任务]
| | +---led_gpio [LED_GPIO操作]
| | +---app_adc [将ADC值进行计算]
| | +---app_soft_voltameter [软电量计算法]
| | \---app_key [按键扫描]
| +---bsp
| | \---nrf52 [BSP层公共接口函数]
| +---chip
| | +---nRF_Drivers [芯片驱动函数]
| | +---nRF_Libraries [芯片支持函数]
| | \---nRF5_SDK_17.0.2_d674dde [芯片SDK版本]
| +---lib
| | \---queue [消息队列]
| +---os
| | \---rtx5 [OS层公共接口函数]
| +---platform
| | +---log [日记函数]
| | \---SEGGER_RTT [RTT库]
| +---sys
| | \---nrf52 [SYS层公共接口函数]
| +---main_HandWriteBoard [项目业务层]
| │ sdk_config.h (NRF功能配置)
| │ business_gpio.h (引脚定义)
| │ business_function.h (功能定义/业务宏)
| │ app_main.c (主业务功能)
| │ biz_led.c (灯光功能:电量灯/射频灯)
| │ biz_key.c (按钮功能:电源键)
| │ biz_power.c (电源管理:充放电状态/电池信息/电源开关)
| │ biz_uart.c (串口数据接收:决定串口通道对应的协议)
| │ biz_flash.c (Flash信息存储和读取)
| +---public [项目公共层]
| │ biz_esb.c (2.4G功能函数:发送端/接收端)
| │ biz_fds.c (重新封装fds函数)
| │ biz_low_power.c (空闲检测逻辑)
[FLASH结构]
nRF52810 192 KB Flash, 24 KB RAM
0x30000 0x6000
使用boot时:keil需要在Options-c/c++-Define 添加宏定义 MBR_PRESENT
boot文件:public_code\nrf52810_boot_uart.hex
名称 地址 大小(字节)
|----------------------------------------------------------
MBR: 0x0000 0x1000 (mbr_nrf52_2.4.1_mbr.hex)
APP: 0x1000 0x30000 - boot_size - RSV1_SIZE - RSV2_SIZE - mbr_size = 0x27000
Boot: 0x28000 0x6000
RSV1: 0x2E000 0x1000 (settings.hex)--Bootloader setting
RSV2: 0x2F000 0x1000 (settings.hex)--MBR parameters
END: 0x30000
|-----------------------------------------------------------
boot+app addr: 0x0
fds addr:0x27000 - 0x0C00 = 0x26400
fds_size: 3 * 0x0400 = 0x0C00
二、ESB工作原理
- RADIO 数据包配置
PREAMBLE: 前导码或者说帧头,一个字节长度(除了2M/s 蓝牙模式),前导码自动配置的,用户不用设置。
ADDRESS: 广播地址由基地址(base)+前缀地址(prefix )两部分组成。基地址长度:2-4字节,其长度可以通过寄存器PCNF1的BALEN位配置。
支持星状网络拓扑实现一拖多的双向链路是nrf52的一个特点,nrf52一个接收端能最多支持8个发送端。
8个逻辑通道(pipe)拥有单独特定的传输物理地址,所以保证了数据不会错乱。8个逻辑地址是如何和物理地址对应呢,如下图逻辑地址的定义,第一章我们说了物理地址由base+prefix组成,所以通道0的地址是BASE0+prefix[0],其他的地址是BASE1+prefix[1]~[7]。然后发送端发送自己逻辑地址,接收端接收判断收到数据包逻辑通道。
static nrf_esb_payload_t tx_payload = NRF_ESB_CREATE_PAYLOAD(0, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00);
static nrf_esb_payload_t rx_payload = {0};
// 下面四个数组可以通过uart或者其他方式来修改频率和地址
static uint8_t base_addr_0[4] = {0x20, 0x21, 0x05, 0x19};
static uint8_t base_addr_1[4] = {0x95, 0x81, 0x69, 0x6f};
static uint8_t addr_prefix[8] = {0x78, 0x75, 0x65, 0x6a, 0x69, 0x61, 0x6e, 0x71};
__attribute__((aligned(4))) uint8_t rf_freq_table[RF_FREQ_MAX_VAL] = {0x6c, 0x69, 0x73, 0x75, 0x6e};
1. 通道地址分配如下(主机和从机base_addr_0、base_addr_1、addr_prefix必须一致才能8组设备通信)
通道0:base_addr_0(4字节) + addr_prefix[0]
通道1:base_addr_1(4字节) + addr_prefix[1]
通道2:base_addr_1(4字节) + addr_prefix[2]
通道3:base_addr_1(4字节) + addr_prefix[3]
通道4:base_addr_1(4字节) + addr_prefix[4]
通道5:base_addr_1(4字节) + addr_prefix[5]
通道6:base_addr_1(4字节) + addr_prefix[6]
通道7:base_addr_1(4字节) + addr_prefix[7]
2. 每个设备必须编号0-7,且每个通道只能用特定编号通道发送。(例如:设备5,对通道5发送数据,则设备0-4,6-7,都能收到通道5发来的消息)
4. 自定义通信协议
字节0 字节1 字节2 字节3 -- (n-1) 字节n
要发送的通道(0-7) 命令值 数据长度 数据内容 校验合(数据内容累加,取低八位)
-
占用资源:
Radio (NRF_RADIO)
Timer: NRF_TIMER2 //这里用到了发送数据的时候等待应答以及延时重发过程
PPI channels 10, 11, 12, and 13 //与NRF_TIMER2联合用到发送数据的时候等待应答以及延时重发Software interrupt 0 -
如何判断数据来自来个通道?
/**@brief Enhanced ShockBurst payload.
* @details 所述有效载荷用于传输和确认带有有效载荷的接收数据包。
*/
typedef struct
{
uint8_t length; //报文长度(最大值为@ref NRF_ESB_MAX_PAYLOAD_LENGTH)。
uint8_t pipe; //当前消息通道号
int8_t rssi; //收到的报文为RSSI。
uint8_t noack; //指示此包将不被确认的标志。 当启用选择性自动应答时,该标志被忽略。
uint8_t pid; //在通信期间分配的PID。
uint8_t data[NRF_ESB_MAX_PAYLOAD_LENGTH]; //数据数组(可以是发送数据,也可以是接收数据)
} nrf_esb_payload_t;
----------------------------------
/**
* @brief [ESB回调事件] 接收通知收发事件,并处理事件
* @param *p_event: esb事件类型
*/
void nrf_esb_event_handler(nrf_esb_evt_t const *p_event)
{
switch (p_event->evt_id)
{
case NRF_ESB_EVENT_RX_RECEIVED:
if (nrf_esb_read_rx_payload(&rx_payload) == NRF_SUCCESS)
{
if (get_comm_connect_state() == SYS_CONNECT_TYPE_RF)
{
LOG_D("ESB_CH_RX:%d\r\n", rx_payload.pipe);
esb_clean_rx_heart_time();
esb_set_rx_connect_state(true);
// 跳转业务处理函数
if (g_rx_dispose_callback)
{
g_rx_dispose_callback(rx_payload.data, rx_payload.length);
}
}
}
break;
}
}
- 如何给对应通道发送数据?
/**
* @brief [ESB操作][发送端][发送数据] 发送一包数据(异步)
* @note 异步发送
* @param *pdata: 待发送的数据指针
* @param len: 数据长度
* @retval 发送状态
*/
uint32_t esb_tx_send(uint8_t ch, uint8_t *pdata, uint8_t len)
{
tx_payload.pipe = ch;
tx_payload.length = len;
memcpy(tx_payload.data, pdata, len);
return nrf_esb_write_payload(&tx_payload);
}
/**
* @brief 处理消息队列中的消息,通过rf发送(放在空闲线程使用)
*/
bool rf_data_dispose(void)
{
if (!queue_de(&m_rf_tx_q, g_rf_tx_data))
{
return false;
}
esb_set_rf_and_start_tx();
esb_tx_send(g_rf_tx_data[0], g_rf_tx_data, g_rf_tx_data[2]);
return true;
}
- 数据包识别与鉴定
tx发送数据包的时候通过crc和pid进行验证。这里说一下数据包前面的识别码。
动态数据长度发送协议:用户的数据打包过程中在底层是从m_tx_payload_buffer[2]开始的,m_tx_payload_buffer[0]用来存储字节长度,m_tx_payload_buffer[1]后两位用来存pid和应答标识,pid用来区分包是否为重发的包,如果不是重传的包存入RX缓存,如果是重发的包丢掉。
三、 管道和地址
几个约定:
pipe:管道
Preamble:前导码
Base address :基地址
Prefix:字首
节点上的每个逻辑地址称为管道。每个管道映射一个空中地址以发送或接收数据。
空中地址由2-4个字节的长的基地址和一个字节的的字首(prefix)地址组成,nRF5无线电使用0和1的交替序列作为分组的前导码(preamble)。因此,对于要正确接收的数据包,基地址的最高有效字节不能是0和1的交替序列,也就是说,基地址的最高有效字节不能是0x55或0xAA。
管道0具有其自己唯一的基址(基址0),而管道1-7使用相同的基址(基址1)。 8个管道中的每一个管道都具有唯一的一个字节的字首(prefix)地址。
在广播中,首先发送每个地址字节的最高有效位。 2-4字节长的基地址的最高有效字节是第一个发送的地址字节,而字首的那一个字节是 最后发送的。
地址不能包含0x00前缀和格式为0x00XXXXXX(长度4)/ 0x0000XXXX(长度5)的地址。 这样的零地址将导致返回错误代码NRF_ERROR_INVALID_PARAM。
注意:ESB中的字节排序和nRF5无线电外设不一样,因为地址字节在ESB中重新排列以匹配nRF24L无线电
PTX FIFO处理
在PTX模式下启用ESB时,上传到TX FIFO的任何数据包将在下次机会传输
当PTX从PRX成功接收到ACK时,PTX假定有效载荷已成功接收并添加到PRX的RX FIFO。 成功传输的数据包将从TX FIFO中删除,以便可以传输FIFO中的下一个数据包。
如果PTX接收的ACK包含有效载荷,则该有效载荷将被添加到PTX的RX FIFO。
PRX FIFO处理
当在PTX模式中使能ESB中时,PTX将会监视所有已启用的管道(地址)的传入数据包
如果接收到之前未添加到PRX的RX FIFO的新数据包,并且RX FIFO具有该数据包的可用空间,则将数据包添加到RX FIFO并发送ACK返回给PTX,如果PRX的TX FIFO包含任何数据包,则TX FIFO中的下一个可维护数据包作为有效负载附加在ACK数据包中。 请注意,在收到数据包之前,必须已将此TX数据包上载到TX FIFO。
Event处理
当频率上有一个事件时,ESB模块会分析其原因,且如果有必要的话,会将该事件排入应用程序,这个事件指示的可能时成功或者失败的操作或者在RX FIFO中有新的数据可以获得。
事件是作为一个flag排队的,且在第一次触发软件中断时被读出。因此,在发送给应用程序的每个事件可能包含多个频率的中断。例如NRF_ESB_EVENT_TX_SUCCESS 和NRF_ESB_EVENT_TX_FAILED事件可能分别指示着多个成功或者失败,NRF_ESB_EVENT_RX_RECEIVED事件表示RX FIFO中至少有一个新数据包;事件处理程序应确保在适当情况下完全清空RX FIFO
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端