# onewire **Repository Path**: levijia/onewire ## Basic Information - **Project Name**: onewire - **Description**: 这是一个基于STM32的单总线通信库,实现了单线主从半双工通讯协议。该库支持主机和从机模式,可以移植用于各种mcu之间的单线通信。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 1 - **Created**: 2025-12-21 - **Last Updated**: 2025-12-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 单总线通信库 (OW Library) ## 简介 这是一个基于STM32的单总线通信库,实现了单线主从半双工通讯协议。该库支持主机和从机模式,可以移植用于各种mcu之间的单线通信。 ## 特性 - 支持主机模式发送数据 - 支持从机模式接收数据 - 支持多从机通信,具有地址管理功能 - 从机可在响应窗口内发送响应数据 - 可配置的通信引脚(默认为PB1) - 使用外部4.7kΩ上拉电阻 - 精确的微秒级延时控制 - 全局调试变量便于调试 ## 新增功能 ### 多从机支持 - **地址管理机制**:支持1字节设备地址(可扩展至多字节),最多支持256个从机设备 - **目标地址发送**:主机可向指定地址的从机发送命令 - **地址匹配检查**:从机只处理目标地址匹配或广播地址的命令 - **响应窗口机制**:主机发送命令后开启Tslot响应窗口(默认300ms),从机在此窗口内响应 ### 从机响应功能 - **统一发送格式**:从机响应使用与主机相同的通信格式(引导码+校准位+数据+结束码) - **响应触发机制**:从机只在"自己地址匹配"或"主机明确点名"时才回发 - **响应数据队列**:支持队列响应数据,确保及时发送 ### 通信协议增强 - **命令结构**:支持指定地址命令和广播命令 - **响应确认**:主机可确认是否接收到从机响应 - **错误处理**:完善的错误状态反馈 ## 文件结构 ``` lib/ ├── ow.h # 头文件 ├── ow.c # 源文件 └── README.md # 使用说明 ``` ## 移植说明 ### 1. 硬件连接 - 使用任意GPIO引脚作为单总线通信引脚(默认为PB1) - 在总线上连接4.7kΩ上拉电阻到VCC - 确保主机和从机共地 ### 2. 软件配置 #### 2.1 引脚配置 修改 `ow.h` 中的引脚定义: ```c #define OW_PORT GPIOB // 修改为实际使用的GPIO端口 #define OW_PIN GPIO_PIN_1 // 修改为实际使用的GPIO引脚 ``` #### 2.2 GPIO初始化配置 单总线通信需要在程序初始化时正确配置GPIO模式。在您的主程序初始化部分,需要配置相应的GPIO为开漏输出模式,并且不使用内部上拉电阻,依赖外部4.7kΩ上拉电阻: ```c GPIO_InitTypeDef GPIO_InitStruct = {0}; // 使能GPIO时钟 __HAL_RCC_GPIOB_CLK_ENABLE(); // 根据实际使用的端口修改 // 首先配置为输入模式,避免在配置过程中拉低总线 GPIO_InitStruct.Pin = OW_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 先设为输入模式 GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(OW_PORT, &GPIO_InitStruct); // 然后再配置为开漏输出模式,但立即切换到输入模式 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出 GPIO_InitStruct.Pull = GPIO_NOPULL; // 不使用内部上拉 HAL_GPIO_Init(OW_PORT, &GPIO_InitStruct); // 确保引脚为高电平后立即切换到输入模式 HAL_GPIO_WritePin(OW_PORT, OW_PIN, GPIO_PIN_SET); // 设置为高电平 // 最后切换到输入模式,让外部上拉电阻控制总线 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式 GPIO_InitStruct.Pull = GPIO_NOPULL; // 不使用内部上拉 HAL_GPIO_Init(OW_PORT, &GPIO_InitStruct); ``` #### 2.3 通信过程中的IO模式切换 在单总线通信过程中,库函数会根据通信需要动态切换IO模式。当需要发送数据时,会临时将IO设置为开漏输出模式(`GPIO_MODE_OUTPUT_OD`)来驱动总线;当需要接收数据时,会将IO切换回输入模式(`GPIO_MODE_INPUT`)并禁用内部上下拉电阻,依赖外部4.7kΩ上拉电阻控制总线电平。 OW库提供了以下函数来处理IO模式切换: - `OW_SetPinInput()` - 切换IO为输入模式(内部上拉禁用) - `OW_SetPinOutput()` - 切换IO为开漏输出模式 - `OW_SetPin(level)` - 设置引脚输出电平 这些函数在通信过程中自动调用,无需用户干预。其内部实现会确保在切换模式前先设置引脚到正确的电平状态,避免对总线产生意外影响: ```c void OW_SetPinInput(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 在切换到输入模式前,先设置为高电平 HAL_GPIO_WritePin(OW_PORT, OW_PIN, GPIO_PIN_SET); // 重新配置为输入模式,不使用内部上拉,依赖外部上拉电阻 GPIO_InitStruct.Pin = OW_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式 GPIO_InitStruct.Pull = GPIO_NOPULL; // 不设置内部上拉 HAL_GPIO_Init(OW_PORT, &GPIO_InitStruct); } void OW_SetPinOutput(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 在切换到输出模式前,先确保输出高电平(释放总线) HAL_GPIO_WritePin(OW_PORT, OW_PIN, GPIO_PIN_SET); // 重新配置为开漏输出 GPIO_InitStruct.Pin = OW_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出 GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(OW_PORT, &GPIO_InitStruct); } ``` #### 2.4 时钟使能 确保在主程序中使能对应GPIO端口的时钟。 ### 3. 软件集成 #### 3.1 添加文件 将 `ow.h` 和 `ow.c` 添加到您的项目中。 #### 3.2 包含头文件 在需要使用单总线通信的源文件中包含头文件: ```c #include "ow.h" ``` #### 3.3 初始化 在主程序初始化部分调用: ```c OW_Init(); // 初始化单总线接口 ``` ## 使用方法 ### 主机模式 在主程序中定义以下宏: ```c #define OW_MODE_MASTER // 启用主机模式 ``` #### 发送带地址的命令: ```c // 配置设备地址 ow_device_addr_t target_addr = {0x23}; // 目标从机地址 // 构建命令结构 ow_command_t cmd; cmd.cmd_type = OW_CMD_SPECIFIC; // 指定地址命令 cmd.target_addr[0] = 0x23; // 目标地址 cmd.data = your_data; // 要发送的数据 cmd.data_len = sizeof(your_data); // 数据长度 // 发送命令 uint8_t result = OW_MasterSendCommand(&cmd); ``` #### 接收从机响应: ```c // 发送命令后立即接收响应 uint8_t response_data[32]; uint8_t response_len = OW_SlaveReceive(response_data, sizeof(response_data)); if(response_len > 0) { // 成功接收到从机响应 // 响应数据存储在response_data中 } ``` ### 从机模式 不定义任何模式宏(默认为从机模式) #### 设置从机地址: ```c ow_device_addr_t my_addr = {0x23}; // 本机地址 OW_SetDeviceAddress(my_addr); // 设置本机设备地址 OW_EnableSlaveResponse(1); // 启用从机响应功能 ``` #### 处理主机命令并发送响应: ```c // 在主循环中接收主机命令 uint8_t received_data[32]; uint8_t received_len = OW_SlaveReceive(received_data, sizeof(received_data)); if(received_len > 0) { // 如果命令目标地址匹配本机地址,准备响应数据 uint8_t response_data[] = {0xAA, 0x55, 0x01, 0x23}; OW_QueueResponse(response_data, sizeof(response_data)); } ``` ### 调试功能 库提供了以下全局调试变量: - `g_debug_received_length`: 接收到的数据长度 - `g_debug_receive_status`: 接收状态 - `g_debug_data_0/1/2`: 接收到的前3个字节数据 - `g_debug_loop_counter`: 循环计数器 - `g_host_received_data[32]`: 主机接收到的响应数据数组 - `g_host_received_length`: 主机接收到的响应数据长度 - `g_host_receive_status`: 主机接收状态 ## 使用示例 ### 示例1:主机发送带地址的命令并接收响应 ```c #include "main.h" #include "ow.h" int main(void) { // 系统初始化 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); // 初始化单总线接口 OW_Init(); // 设置本机为主机模式地址 ow_device_addr_t master_addr = {OW_DEVICE_BROADCAST}; OW_SetDeviceAddress(master_addr); uint8_t send_counter = 0; uint8_t response_data[32]; while(1) { // 构造带地址的命令数据 uint8_t cmd_data[6]; cmd_data[0] = 0x55; // 固定模式 cmd_data[1] = 0xAA; // 固定模式 cmd_data[2] = send_counter; // 自增计数器 cmd_data[3] = ~send_counter; // 自增计数器的反码 cmd_data[4] = send_counter + 0x12; // 计数器加偏移 cmd_data[5] = send_counter ^ 0xFF; // 计数器异或 // 构建命令结构 ow_command_t cmd; cmd.cmd_type = OW_CMD_SPECIFIC; // 指定地址命令 cmd.target_addr[0] = 0x23; // 目标从机地址 cmd.data = cmd_data; cmd.data_len = 6; // 发送命令 uint8_t result = OW_MasterSendCommand(&cmd); send_counter++; // 递增计数器 // 等待并接收从机响应 uint8_t response_len = OW_SlaveReceive(response_data, sizeof(response_data)); if(response_len > 0) { // 成功接收到从机响应 // 更新全局调试变量 g_host_received_length = response_len; for(int i = 0; i < response_len && i < 32; i++) { g_host_received_data[i] = response_data[i]; } g_host_receive_status = 1; } // 发送指示 HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); HAL_Delay(2000); // 2秒发送一次 } } ``` ### 示例2:从机接收指定地址命令并发送响应 ```c #include "main.h" #include "ow.h" int main(void) { // 系统初始化 HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); // 初始化单总线接口 OW_Init(); // 设置本机地址 ow_device_addr_t my_addr = {0x23}; // 本机地址为0x23 OW_SetDeviceAddress(my_addr); OW_EnableSlaveResponse(1); // 启用从机响应功能 uint8_t received_data[8]; uint8_t last_received_len = 0; while(1) { // 接收主机命令 uint8_t received_len = OW_SlaveReceive(received_data, sizeof(received_data)); if(received_len > 0 && received_len != last_received_len) { // 检查是否是发给本机的命令(OW_SlaveReceive内部已处理地址匹配) if(received_len >= 4) { uint8_t counter = received_data[2]; uint8_t counter_inv = received_data[3]; // 验证反码是否正确 if((counter ^ counter_inv) == 0xFF) { // 准备响应数据 uint8_t response_data[] = {0xAA, 0x55, counter, ~counter}; OW_QueueResponse(response_data, sizeof(response_data)); // 数据验证成功,点亮LED HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); HAL_Delay(100); HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); } } last_received_len = received_len; } // 短暂延时 HAL_Delay(10); } } ``` ## API参考 ### 初始化函数 ```c uint8_t OW_Init(void); ``` 初始化单总线接口。 ### 主机发送函数 ```c uint8_t OW_MasterSend(uint8_t *data, uint8_t len); uint8_t OW_MasterSendCommand(ow_command_t *cmd); ``` 主机模式发送数据或带地址的命令。 ### 从机接收函数 ```c uint8_t OW_SlaveReceive(uint8_t *data, uint8_t max_len); ``` 从机模式接收数据。 ### 从机响应函数 ```c uint8_t OW_SlaveProcessCommand(uint8_t *data, uint8_t len); uint8_t OW_SlaveSendResponse(void); uint8_t OW_QueueResponse(uint8_t *data, uint8_t len); ``` 从机处理命令、发送响应相关函数。 ### 地址管理函数 ```c void OW_SetDeviceAddress(ow_device_addr_t addr); void OW_GetDeviceAddress(ow_device_addr_t addr); uint8_t OW_CheckAddressMatch(ow_device_addr_t addr1, ow_device_addr_t addr2); void OW_EnableSlaveResponse(uint8_t enable); ``` 设备地址管理和响应控制函数。 ### 调试相关函数 ```c uint8_t* OW_GetLastReceivedData(void); uint8_t OW_GetLastReceivedLength(void); uint8_t OW_GetLastReceiveStatus(void); void OW_UpdateDebugVariables(void); ``` ## 注意事项 1. 确保使用外部上拉电阻(推荐4.7kΩ) 2. 单总线通信距离不宜过长 3. 所有设备需要共地 4. 通信速率相对较低,请根据应用需求合理设计 5. 使用DWT外设进行精确延时,需要在启动代码中启用 6. 多从机系统中,每个从机必须有唯一的地址 7. 从机响应必须在主机开启的响应窗口(Tslot)内完成 ## 协议说明 - 引导码:低电平约20ms,高电平约40ms - 校准位:低电平约2ms,高电平约2ms - 数据位:建立时间约10us,逻辑1高电平约200us,逻辑0高电平约60us - 结束码:低电平约20ms - 响应窗口:主机发送结束后开启约300ms窗口供从机响应 ## 故障排除 - 如果无法通信,请检查硬件连接和上拉电阻 - 如果接收不到数据,请检查时序是否正确 - 多从机通信时,确认从机地址设置正确 - 确认从机只在地址匹配时响应 - 可使用逻辑分析仪验证波形 - 可通过调试变量监控通信状态