STM32F1与STM32CubeIDE编程实例-设备驱动-One-Wire总线驱动实现

One-Wire总线驱动实现

1-Wire 是一种通信系统,旨在通过单线接口连接简单的传感器和设备。该系统用于低速和低功耗的通信设备。有两种可用的操作模式:标准速度和超速。标准速度下可实现的数据速率为 16.3 kbit/s,而超速模式通信以标准速度的 10 倍完成。

该协议使用一条数据线从一个设备到另一个设备的数据传输。总线是半双工的,因此数据可以双向移动,但不能同时移动。需要时,还可以使用额外的电线为从设备供电。

该协议支持总线上的一个从站(单点)或多个从站(多点)。总线上还有一个主控器,它控制总线上的信息传输。主机启动数据线上的所有传输。只能在主机和从机之间传输数据,因此不能在从机之间传输数据。

该协议不需要时钟,因为每个从机都由与总线下降沿同步的内部振荡器提供时钟。

传输字节时,首先传输最低有效位。

1-Wire的总线拓扑结构如下:

在这里插入图片描述

1、One-Wire 总线要求

每个输出引脚必须为漏极开路,并且必须将弱上拉连接到信号,这样,如果至少有一个设备将总线驱动为低电平,则总线被驱动为低电平。 该协议允许在总线上的两个设备之间传输数据,而其他设备处于空闲状态。 上拉的强度可以由用户根据以下因素来决定:

  • 如果数据速率不需要很高并且系统上的走线长度不高,则外部供电的设备可能具有 10K 或更大范围内的上拉值。
  • 如果数据速率很高,尤其是在过驱动模式下或系统上的走线长度较长,则外部供电的设备可能具有小于 1K 的上拉值。
  • 被寄生供电的设备在被选中后可能需要有源驱动器,以便终端设备可以有足够的能量来执行终端操作。

2、One-Wire总线供电

从设备可以在两种模式下供电:

  • 外部供电:从设备上的电源引脚用于为总线上的从设备供电。 当从设备有高功率需求时使用此拓扑。
  • 寄生供电:从机由数据线供电。 从设备有一个内部电容器,当总线空闲并被弱上拉拉高时,该电容器可以存储此能量。

3、One-Wire总线通信时序

One-Wire的数据线上有以下四种信号类型:

  • 具有复位脉冲和复位应答 (ATR) 的复位序列
  • 将 0 位写入总线
  • 将1位写入总线
  • 读取位

下面将详细介绍这四种信号类型。

3.1 具有复位脉冲和复位应答 (ATR) 的复位序列

复位脉冲用于将所有设备置于已知状态。 从机通过发送 ATR 信号来确认它们的存在,这是通过将线路保持为低电平来完成的。 主设备对总线进行采样,如果总线读数为低,则至少存在一个从设备。

操作 描述 实现
Reset 复位 1-Wire 总线从设备并为命令做好准备。 将总线驱动为低电平 480 μs 以复位所有从设备。 然后主机在接下来的 240 μs 内对总线进行采样,而从机响应复位 (ATR)。

当总线上至少有 1 个从设备时复位序列总线时序如下:

在这里插入图片描述

当总线上没有从机时复位序列总线时序如下:

在这里插入图片描述

3.2 将 0 位写入总线

操作 描述 实现
写入 0 位 向 1-Wire 从机发送 0 位 将总线驱动为低电平 60 μs

总线时序图如下:

在这里插入图片描述

3.3 将1位写入总线

操作 描述 实现
写入1位 向 1-Wire 从机发送 1 位 将总线驱动为低电平 < 15 μs。 典型时间约为 6 μs。 在下降沿后 60 μs 释放总线。

在这里插入图片描述

3.4 读取位

从从机读取一位。 读位信号类似于写“1”信号,除了主机读而不是写。

操作 描述 实现
读取位 从 1-Wire 从机读取一位数据 将总线从 1 μs 拉低至 15 μs。 在下降沿后 15μs 采样总线以从从机读取位。

读取1时序图如下:

在这里插入图片描述

读取0时序图如下:

在这里插入图片描述

4、One-Wire总线设备地址格式

来自制造商的 1-Wire 从设备中存储了一个唯一的 64 位地址,也称为 ROM 编号。 地址的最低有效 8 位给出了器件的系列代码。 接下来的至少 48 位给出了设备的序列号。 最高有效 8 位给出从最低 56 位生成的 CRC。如下图所示:

在这里插入图片描述

注意:CRC 用于检查数据是否正确接收。对于未在硬件中实现的设备,需要在软件中实现。

5、One-Wire总线上的典型通信流程

One-Wire典型的通信流程如下:

  • 从重置信号开始
  • 如果主设备必须确定总线上存在哪些从设备,则主设备应执行搜索以检测从设备的 ROM 编号。
  • 在对设备执行操作之前,必须使用 ROM 命令配置和/或选择设备。 一些可用的功能 ROM 命令如下:
    • **Read ROM [0x33]**:仅在总线上有单个从机时使用。 该命令读取总线上唯一从机的 ROM 编号。
    • **Match ROM [0x55]**:此命令后跟一个 64 位 ROM 编号选择具有匹配 ROM 编号的从机。 所有其他设备等到下一个复位脉冲。
    • **Search ROM [0xF0]**:该命令用于获取多个设备的 ROM 号,并通知从设备,主设备将进行搜索。 然后通过从从站读取一个位及其 ROM 编号的补码并发送回一个适当的位来执行搜索。与主设备具有相同位的从设备保持活动状态,而其他设备则等待下一次复位。备注:地址搜索算法在这里不展开描述,可参考相关文档
    • **Skip ROM [0xCC]**:可以在主机不知道 ROM 编号的情况下寻址设备。 在向所有设备发出通用命令时,此命令很有帮助。
    • **Overdrive Skip ROM [0x3C]**:此命令仅用于单点。 此命令与 Skip ROM 命令相同,只是只有可以在超速模式下运行的设备保持活动状态并进入超速模式。 无法在超速模式下运行的设备将等待下一次复位。
    • **Overdrive Match ROM [0x69]**:此命令与 Match ROM 命令相同,只是设备仅在可以在超速模式下运行时才匹配。 所有其他设备等待下一次重置。
  • 选择所需的设备后,可以发出特定于设备的命令来执行所需的操作。
  • 通常在每次操作之后,都会发出一个复位脉冲。

6、One-Wire驱动实现

1)OneWire总线基本定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// onewire.h
#ifndef ONEWIRE\_H
#define ONEWIRE\_H

#include "gpio.h"

//
// One-Wire总线定义
//
typedef struct {
GPIO_TypeDef\* GPIOx; // 总线端口
uint16\_t GPIO_Pin; // 总线引脚
uint8\_t LastDiscrepancy;
uint8\_t LastFamilyDiscrepancy;
uint8\_t LastDeviceFlag;
uint8\_t ROM_NO[8]; // 8 字节 ROM 地址,最后找到的设备
} OneWire_t;

//
// One-Wire 命令
//
#define ONEWIRE\_CMD\_RSCRATCHPAD 0xBE
#define ONEWIRE\_CMD\_WSCRATCHPAD 0x4E
#define ONEWIRE\_CMD\_CPYSCRATCHPAD 0x48
#define ONEWIRE\_CMD\_RECEEPROM 0xB8
#define ONEWIRE\_CMD\_RPWRSUPPLY 0xB4
#define ONEWIRE\_CMD\_SEARCHROM 0xF0
#define ONEWIRE\_CMD\_READROM 0x33
#define ONEWIRE\_CMD\_MATCHROM 0x55
#define ONEWIRE\_CMD\_SKIPROM 0xCC

//
// 总线初始化
//
void OneWire\_Init(OneWire_t\* OneWireStruct, GPIO_TypeDef\* GPIOx, uint16\_t GPIO_Pin);

//
// 总线重置
//
uint8\_t OneWire\_Reset(OneWire_t\* OneWireStruct);

//
// 设备搜索
//
void OneWire\_ResetSearch(OneWire_t\* OneWireStruct);
uint8\_t OneWire\_First(OneWire_t\* OneWireStruct);
uint8\_t OneWire\_Next(OneWire_t\* OneWireStruct);
uint8\_t OneWire\_Search(OneWire_t\* OneWireStruct, uint8\_t command);

//
// 总线数据读写
//
void OneWire\_WriteBit(OneWire_t\* OneWireStruct, uint8\_t bit);
uint8\_t OneWire\_ReadBit(OneWire_t\* OneWireStruct);
void OneWire\_WriteByte(OneWire_t\* OneWireStruct, uint8\_t byte);
uint8\_t OneWire\_ReadByte(OneWire_t\* OneWireStruct);

//
// ROM操作
//
void OneWire\_GetFullROM(OneWire_t\* OneWireStruct, uint8\_t \*firstIndex);
void OneWire\_Select(OneWire_t\* OneWireStruct, uint8\_t\* addr);
void OneWire\_SelectWithPointer(OneWire_t\* OneWireStruct, uint8\_t\* ROM);

//
// CRC校验
//
uint8\_t OneWire\_CRC8(uint8\_t\* addr, uint8\_t len);

#endif


OneWire时序需要使用延时,这里使用DWT实现精确延时,请参考前面文章:

驱动函数使用的到一些辅助函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// onewire.c
#include "onewire.h"
#include "dwt/dwt\_delay.h"
//
// One-Wire总线时序延时
//
void OneWire\_Delay(uint16\_t us)
{
dwt\_delay\_us(us);
}

//
// 将One-Wire总线GPIO设置为输入
//
void OneWire\_BusInputDirection(OneWire_t \*onewire)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
GPIO_InitStruct.Pin = onewire->GPIO_Pin;
HAL\_GPIO\_Init(onewire->GPIOx, &GPIO_InitStruct);
}
//
// 将One-Wire总线GPIO设置为输出
//
void OneWire\_BusOutputDirection(OneWire_t \*onewire)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
GPIO_InitStruct.Pin = onewire->GPIO_Pin;
HAL\_GPIO\_Init(onewire->GPIOx, &GPIO_InitStruct);
}

//
// 将总线GPIO引脚设置为低电平
//
void OneWire\_OutputLow(OneWire_t \*onewire)
{
onewire->GPIOx->BSRR = onewire->GPIO_Pin << 16; // 重置One-Wire引脚
}
//
// 将总线GPIO引脚设置为高电平
//
void OneWire\_OutputHigh(OneWire_t \*onewire)
{
onewire->GPIOx->BSRR = onewire->GPIO_Pin; // 设置为高电平
}

2)总线初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// onewire.c
//
// One-Wire 总线初始化
//
void OneWire\_Init(OneWire_t\* onewire, GPIO_TypeDef\* GPIOx, uint16\_t GPIO_Pin)
{
onewire->GPIOx = GPIOx;
onewire->GPIO_Pin = GPIO_Pin;

// 按时序图初始化
OneWire\_BusOutputDirection(onewire);
OneWire\_OutputHigh(onewire);
HAL\_Delay(100);
OneWire\_OutputLow(onewire);
HAL\_Delay(100);
OneWire\_OutputHigh(onewire);
HAL\_Delay(200);
}


3)总线重置信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// onewire.c
//
// One-Wire总线重置信号
//
// 返回:
// 0 - 重置成功
// 1 - 重置失败
//
uint8\_t OneWire\_Reset(OneWire_t\* onewire)
{
uint8\_t i;

OneWire\_OutputLow(onewire); // 将总线引脚接低
OneWire\_BusOutputDirection(onewire);
OneWire\_Delay(480); // 等待 480us 复位

OneWire\_BusInputDirection(onewire); // 释放总线并切换为输入
OneWire\_Delay(70);
// 检查总线是否低电平,如果总线没有设备,则为高电平
i = HAL\_GPIO\_ReadPin(onewire->GPIOx, onewire->GPIO_Pin);
OneWire\_Delay(410);

return i;
}

4)One-Wire总线写0和1位操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// onewire.c
//
// One-Wire总线写0和1位操作
//
void OneWire\_WriteBit(OneWire_t\* onewire, uint8\_t bit)
{
if (bit) // 写 '1',
{
OneWire\_OutputLow(onewire); // 将总线拉低
OneWire\_BusOutputDirection(onewire);
OneWire\_Delay(6);

OneWire\_BusInputDirection(onewire); // 释放总线,切换为输入
OneWire\_Delay(64);
}
else // 写 '0'
{
OneWire\_OutputLow(onewire); // 将总线拉低
OneWire\_BusOutputDirection(onewire);
OneWire\_Delay(60);

OneWire\_BusInputDirection(onewire); // 释放总线,切换为输入
OneWire\_Delay(10);
}
}

5)One-Wire总线读写1字节操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// onewire.c
uint8\_t OneWire\_ReadBit(OneWire_t\* onewire)
{
uint8\_t bit = 0; // 默认读取位状态为低

OneWire\_OutputLow(onewire); // 将总线拉低,开始读取
OneWire\_BusOutputDirection(onewire);
OneWire\_Delay(2);

OneWire\_BusInputDirection(onewire); // 释放总线,等待从设备响应
OneWire\_Delay(10);

if (HAL\_GPIO\_ReadPin(onewire->GPIOx, onewire->GPIO_Pin)) // 读取总线状态
bit = 1;

OneWire\_Delay(50); // 等待读取周期结束

return bit;
}

void OneWire\_WriteByte(OneWire_t\* onewire, uint8\_t byte)
{
uint8\_t i = 8;

do
{
OneWire\_WriteBit(onewire, byte & 1); // LSB优先
byte >>= 1;
} while(--i);
}

uint8\_t OneWire\_ReadByte(OneWire_t\* onewire)
{
uint8\_t i = 8, byte = 0;

do{
byte >>= 1;
byte |= (OneWire\_ReadBit(onewire) << 7); // LSB优先
} while(--i);

return byte;
}

6)One-Wire总线设备查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// onewire.c
// 重置搜索
void OneWire\_ResetSearch(OneWire_t\* onewire)
{
// 重置搜索结果
onewire->LastDiscrepancy = 0;
onewire->LastDeviceFlag = 0;
onewire->LastFamilyDiscrepancy = 0;
}

uint8\_t OneWire\_Search(OneWire_t\* onewire, uint8\_t command)
{
uint8\_t id_bit_number;
uint8\_t last_zero, rom_byte_number, search_result;
uint8\_t id_bit, cmp_id_bit;
uint8\_t rom_byte_mask, search_direction;

id_bit_number = 1;
last_zero = 0;
rom_byte_number = 0;
rom_byte_mask = 1;
search_result = 0;

if (!onewire->LastDeviceFlag) // 如果未设置最后一个设备标志
{
if (OneWire\_Reset(onewire)) // 复位总线
{
// 如果重置时出错 - 重置搜索结果
onewire->LastDiscrepancy = 0;
onewire->LastDeviceFlag = 0;
onewire->LastFamilyDiscrepancy = 0;
return 0;
}

OneWire\_WriteByte(onewire, command); // 发送搜索命令

// 搜索循环
do
{
id_bit = OneWire\_ReadBit(onewire); // 读取一位
cmp_id_bit = OneWire\_ReadBit(onewire); // 读取补码

if ((id_bit == 1) && (cmp_id_bit == 1)) // 数据出错
{
break;
}
else
{
if (id_bit != cmp_id_bit)
{
search_direction = id_bit; // 用于搜索的位写入值
}
else // 搜索设备
{
// 搜索路径方向
if (id_bit_number < onewire->LastDiscrepancy)
{
search_direction = ((onewire->ROM_NO[rom_byte_number] & rom_byte_mask) > 0);
}
else
{
// 如果等于最后 - 选择 1,否则选择0
search_direction = (id_bit_number == onewire->LastDiscrepancy);
}

if (search_direction == 0) // 如果选择了 0,则将其写入 LastZero
{
last_zero = id_bit_number;

if (last_zero < 9) // 检查最后一次差异
{
onewire->LastFamilyDiscrepancy = last_zero;
}
}
}

if (search_direction == 1)
{
onewire->ROM_NO[rom_byte_number] |= rom_byte_mask; // 设置 ROM 字节 rom\_byte\_number 中的位
}
else
{
onewire->ROM_NO[rom_byte_number] &= ~rom_byte_mask; // 清除 ROM 字节 rom\_byte\_number 中的位
}

OneWire\_WriteBit(onewire, search_direction); // 搜索方向写入位

id_bit_number++; // 下一位搜索-增加id
rom_byte_mask <<= 1; // 为下一位移动掩码

if (rom_byte_mask == 0) //如果掩码为 0,则表示读取了整个字节
{
rom_byte_number++; //下一个字节数
rom_byte_mask = 1; // 重置掩码 - 第一位
}
}
} while(rom_byte_number < 8); // 读取 8 个字节

if (!(id_bit_number < 65)) // 如果所有读取的位数低于 65(8 个字节)
{
onewire->LastDiscrepancy = last_zero;

if (onewire->LastDiscrepancy == 0) // 如果最后一个差异为 0 - 找到最后一个设备
{
onewire->LastDeviceFlag = 1; // 设置标志
}

search_result = 1; // 搜索成功
}
}

// 如果未找到设备 - 重置搜索数据并返回 0
if (!search_result || !onewire->ROM_NO[0])
{
onewire->LastDiscrepancy = 0;
onewire->LastDeviceFlag = 0;
onewire->LastFamilyDiscrepancy = 0;
search_result = 0;
}

return search_result;
}

//
// 返回 One-Wire 总线上的第一个设备
//
uint8\_t OneWire\_First(OneWire_t\* onewire)
{
OneWire\_ResetSearch(onewire);

return OneWire\_Search(onewire, ONEWIRE_CMD_SEARCHROM);
}

//
// 返回 One-Wire 总线上的下一个设备
//
uint8\_t OneWire\_Next(OneWire_t\* onewire)
{
/\* 忽略搜索状态 \*/
return OneWire\_Search(onewire, ONEWIRE_CMD_SEARCHROM);
}

//
// 按地址选择总线上的设备
//
void OneWire\_Select(OneWire_t\* onewire, uint8\_t\* addr)
{
uint8\_t i;
OneWire\_WriteByte(onewire, ONEWIRE_CMD_MATCHROM); // 匹配 ROM 命令

for (i = 0; i < 8; i++)
{
OneWire\_WriteByte(onewire, \*(addr + i));
}
}

//
// 通过指向 ROM 地址的指针选择总线上的设备
//
void OneWire\_SelectWithPointer(OneWire_t\* onewire, uint8\_t \*ROM)
{
uint8\_t i;
OneWire\_WriteByte(onewire, ONEWIRE_CMD_MATCHROM); // 匹配 ROM 命令

for (i = 0; i < 8; i++)
{
OneWire\_WriteByte(onewire, \*(ROM + i));
}
}

//
// 获取找到的设备的ROM
//
void OneWire\_GetFullROM(OneWire_t\* onewire, uint8\_t \*firstIndex)
{
uint8\_t i;
for (i = 0; i < 8; i++) {
\*(firstIndex + i) = onewire->ROM_NO[i];
}
}


7)CRC校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//
// 计算 CRC
//
uint8\_t OneWire\_CRC8(uint8\_t \*addr, uint8\_t len) {
uint8\_t crc = 0, inbyte, i, mix;

while (len--)
{
inbyte = \*addr++;
for (i = 8; i; i--)
{
mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix)
{
crc ^= 0x8C;
}
inbyte >>= 1;
}
}

return crc;
}

文章来源: https://iotsmart.blog.csdn.net/article/details/125253953