爆改串口实现OneWire驱动DS18B20

本文详细介绍了如何使用STM32F103的USART2接口模拟OneWire协议,包括发送reset信号、数据传输时隙和DS18B20温度传感器的应用。通过串口实现的OneWire协议简化了软件编写,但牺牲了部分硬件资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

OneWire简介

  • OneWire是美信(Maxim)开发的一种单总线通信接口,一根双向数据线,还有一根共同电压参考地线。
  • 低速(15kbps),串行半双工通信
  • 可以支持100米长距离通信
  • 单主多从总线结构,最多支持63个从机挂接在同一个总线上
  • 总是由主机发起通信过程,从机是被动参与通信
  • 每个字节数据传输时,位按照LSB顺序
  • 每个OneWire从机都有一个出厂就设定好的,固化在ROM中的不可更改的64位器件唯一ID,其作用类似于I2C的从机ID
  • 主机和从机的数据线驱动引脚都要配置为开漏输出模式。总线通过一个外部上拉电阻(典型值为4.7K)拉高,因此总线空闲时为高电平。总线使用“线与”逻辑,只要一个设备拉低了总线,总线将变为低电平

总线上的基本信号与时隙

  • 主流发送reset信号,接收从机响应的presence信号
  • 主机发送逻辑0和逻辑1到总线
  • 主机从总线读取逻辑0和逻辑1

主机发送复位信号并接收存在信号

为了开启一次通信事物,主机必须先发送总线复位信号(reset signal),第一个目的是为了唤醒并通知所有从机,另一个目的是为了检查总线上是否有从机存在。当从机收到复位信号后,他们将同步进行响应一个存在信号(presence signal),主机收到存在信号才知道总线上有从机存在,可以开始选中某个从机进行通信。

主机写逻辑0时隙

写“0”时隙的起始信号是主机将总线拉低,且至少维持60us(这个时间是为了保证从设备能来得及采样总线上的信号)。然后主机释放总线,等待总线在上拉电阻作用下恢复高电平状态,然后才能进行下一个bit的传输。等待总线恢复高电平的时间用TREC表示,最低为1us,最长为无穷大。

主机写逻辑1时隙

写“1”时隙的起始信号是主机将总线拉低,且至少维持1us,然后主机释放总线,总线在上拉电阻作用下恢复高电平状态,从拉低到恢复高电平要在15us内完成。然后等待从设备采样,等待时间最低持续15us,最长持续45us。当然,如果主机愿意,它可以等待更长的时间。

主机读逻辑0和读逻辑1时隙

每个读时隙至少持续60us并附加一个至少1us的TREC时间。

起始信号同样是主机先将总线拉低至少1us,然后主机释放总线。接下来准备读取从设备发送到总线上的信号。从设备在检测到下降沿后,便开始将信号输出到总线上并维持15us的时间。因此主机从拉低总线然后读取数据要在15us内完成。如果主机读取到总线是低电平,则读取到了逻辑0;如果主机读取到总线是高电平,则读取到了逻辑1。

软件模拟OneWire存在的问题

使用GPIO软件模拟某种接口,英语里使用bit-banging一词来表示。有时候为了降低成本,使用的主控不具有某种通信接口,而不得不使用GPIO去模拟实现。这种设计存在一些问题:

  • 为了模拟时序,需要软件延迟等待,浪费了CPU时钟
  • 不同CPU处理速度不一样,调整时序时软件延时的代码需要根据CPU速度来反复调试确定,不方便代码编写和移植
  • 软件模拟的时隙容易被中断、任务切换等机制打断,导致传输信号受损(虽然关闭全局中断和临界区是一种比较粗鲁的解决方案)

使用串口实现OneWire的优点

在使用串口实现OneWire协议时,是用一个串口字符帧的时序来实现OneWire的基本时隙。在使用串口在发送一个字符时,软件只需要将数据写入到发送缓冲寄存器,此后由串口内部的发送电路去实现发送过程,其过程由串口硬件自动完成,是原子性的,不用担心被打断。

另一方面,通过使用特定熟知串口波特率来满足OneWire时隙要求,在各个单片机平台都是通用且容易实现的,无需软件调试软件延时,代码实现更方便。

需要注意的是,OneWire协议中,无论是发送还是接收逻辑位,相邻的两个bit数据时隙数据之间的间隔 要满足 1us< tREC < 无穷大,因此,通过串口实现OneWire协议时,只需满足连续的两个串口字符之间有至少1us的间隔就行,有更大的间隔也不影响OneWire通信。

这种方案,硬件代价比较大,牺牲了一个串口,以此换来软件编写的方便。

串口转OneWire硬件设计

OneWire规定接入到总线的引脚必须为开漏输出模式。串口的RX引脚是输入引脚(在stm32f103上配置为浮空输入),它没有将总线拉低的能力,因此它可以直接接入到OnwWire总线。而串口的TX引脚在串口通信空闲时为高电平,因此需要外加电路阻止TX高电平信号向外传输。下面图1是Maxim官方介绍的原理图,图2是我测试使用的原理图。

图一和图二都有相同点:

  • TX引脚只能拉低总线,让电流流入,不能作为电流源输出电流到总线上(开漏输出)
  • 主机串口的RX和TX引脚连接在一起,主机发送的每个串口字节数据,同时会被自己收到,主机从串口收到的字节数据是叠加了onewire从机响应信号后的数据。这利用了串口全双工模式的特点,收发同时进行,互不影响。所以主机从串口收到的数据就可以反应出从机响应的数据。

 

串口转OneWire软件设计

主机发送总线复位信号并接收从机响应的存在信号

主机串口需要配置成9600波特率,1起始位,8数据位,无校验,1停止位。

首先,主机发送0xF0。在9600波特率时,一个串口位占用104us。由于标准串口的数据发送时也是LSB顺序,因此起始位加4个0会将总线拉低5x104=520us,满足OneWire总线的最低480us的要求。接着4个1加结束位将释放总线,在这个期间,当从机响应presence时,会拉低总线,因此主机串口接收到的数据就不再是0xF0,而是0xE0或者其他数据,如下图所示。

当主机收到的是0xF0时,说明总线上没有从机响应。当主机没有收到任何串口数据时,说明RX和TX线断开了,可以说明硬件线路故障。

当主机检测到从机响应presence时,要修改串口波特率为115200,帧格式不变,为后面收发数据准备。

主机发送逻辑0和逻辑1

发送逻辑0:主机在115200波特率下发送0x00来产生主机发送逻辑0时序。115200波特率时,一个bit约8.6us,所以起始位加8个0会拉低总线9x8.6=77.4us,满足OneWire规定的60~120us要求,最后一个结束位占用8.6us来释放总线,也满足最低1us的要求。

发送逻辑1:主机在115200波特率下发送0xFF来产生主机发送逻辑1时序。起始位会拉低总线8.6us,满足OneWire规定的1~15us要求,8个1加结束位用来释放总线9x8.6=77.4us。

主机从总线读取逻辑0和逻辑1  

主机在115200波特率下发送0xFF来读取总线上的bit数据。起始信号会将总线拉低8.6us。

读逻辑0:如果从机响应的是逻辑0,它会拉低总线一段时间,使得主机从串口读取到的数据不再是0xFF。

读逻辑1:如果从机响应的是逻辑1,则剩下的9个串口位周期内,总线都将保持高电平,那么主机将从串口原样收到自己发送出去的0xFF;

基于串口的OneWire协议实现

下面使用STM32F103的USART2(TX-PA2,RX-PA3)实现串口转OneWire来驱动DS18B20。

注意:

  • 发送串口数据时要依据TC标志而不是TXE标志,保证数据发送完成,并产生足够的TREC时间间隔。如果单片机不支持TC,则串口发送之间加适当的延时
  • 不需要用到任何串口中断,使用查询法发送和接收
//===========================OneWire========================

//主机发送reset信号,并接收从机presence信号
//如果没有从机响应,则返回0
//如果有从机响应,则返回1
uint8_t OneWire_reset(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	//配置USART2的RX和TX引脚
	/*PA2 -- USART2_TX*/
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;       
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    /*PA3 -- USART2_RX*/
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;  
    GPIO_Init(GPIOA,&GPIO_InitStructure);

	//主机配置9600波特率来发送reset信号
	USART_Cmd(USART2, DISABLE);                             
		USART_InitStructure.USART_BaudRate = 9600;
		USART_InitStructure.USART_WordLength = USART_WordLength_8b;
		USART_InitStructure.USART_StopBits = USART_StopBits_1;
		USART_InitStructure.USART_Parity = USART_Parity_No;
		USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
		USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
		USART_Init(USART2, &USART_InitStructure); 
	USART_Cmd(USART2, ENABLE);
	
	//主机发送reset信号
    USART_ClearFlag(USART2,USART_FLAG_TC);
	USART_SendData(USART2,0xF0);
	while(SET != USART_GetFlagStatus(USART2,USART_FLAG_TC));  //等待发送完成
	
	//主机读取收到的串口数据
	while(SET != USART_GetFlagStatus(USART2,USART_FLAG_RXNE) );
	if(0xF0 == USART_ReceiveData(USART2))  //总线上没有从设备响应,返回错误代码
	{
		return 0;
	}
	else  //总线上有从设备响应,则重新修改波特率为115200
	{
		USART_Cmd(USART2, DISABLE);                             
			USART_InitStructure.USART_BaudRate = 115200;
			USART_Init(USART2, &USART_InitStructure); 
		USART_Cmd(USART2, ENABLE);
		return 1;
	}
}
 
//主机发送逻辑1或逻辑0
//传递参数0来发送逻辑0
//传递参数非0来发送逻辑1
void OneWire_write_bit(uint8_t bit)
{
    USART_ClearFlag(USART2,USART_FLAG_TC);
	USART_SendData(USART2,bit ? 0xFF : 0x00); 
	while(SET!=USART_GetFlagStatus(USART2,USART_FLAG_TC));
	
	//这里仅仅是为了消除RXNE标志,防止ORE
	while(SET!=USART_GetFlagStatus(USART2,USART_FLAG_RXNE)); 
	USART_ReceiveData(USART2);
}
 
//主机从总线上读取1个bit的数据
//返回0代表读取到逻辑0
//返回1代表读取到逻辑1
uint8_t OneWire_read_bit(void)
{
    USART_ClearFlag(USART2,USART_FLAG_TC);
	USART_SendData(USART2,0xFF );    
	while(SET!=USART_GetFlagStatus(USART2,USART_FLAG_TC));
	
	while(SET!=USART_GetFlagStatus(USART2,USART_FLAG_RXNE)); 
	return ((0xFF == USART_ReceiveData(USART2)) ?  1 : 0 );
}
 
 
//主机写一个字节的数据到总线
//lsb frist
void OneWire_write_byte(uint8_t dat)
{
	uint8_t i;
	for ( i = 0 ; i < 8 ; ++i ) 	
	{
        OneWire_write_bit(dat & 0x01);
		dat = dat >> 1;
    }
}
 
//主机从总线读一个字节数据返回
//lsb frist
uint8_t OneWire_read_byte(void)
{
	uint8_t  i;
	uint8_t  dat = 0;
     
	for ( i = 0 ; i < 8 ; ++i) 
	{   
		dat>>=1;
		if(OneWire_read_bit())
			dat|=0x80;
	}
	return dat;
}

//===============================DS18B20======================

#define DS18B20_READ_ROM     ((uint8_t)0x33)
#define DS18B20_MATCH_ROM    ((uint8_t)0x55)
#define DS18B20_SKIP_ROM     ((uint8_t)0xCC)
 
#define DS18B20_CONVERT_T    ((uint8_t)0x44)
#define DS18B20_READ_SPAD    ((uint8_t)0xBE)
#define DS18B20_WRITE_SPAD   ((uint8_t)0x4E)
#define DS18B20_COPY_SPAD    ((uint8_t)0x48)
 
//向DS18B20发送一条温度转换指令convert T
uint8_t DS18B20_convert_T(void)
{
    if(!OneWire_reset()) return 0;   //如果总线错误
    OneWire_write_byte(DS18B20_SKIP_ROM);
    OneWire_write_byte(DS18B20_CONVERT_T);
    return 1;
}
 
//读取DS18B20的内部温度数据,然后转换成摄氏度温度
uint8_t DS18B20_get_temperature(float*temp)
{
    uint8_t pad0,pad1;
	uint16_t     t;
	
    if(!OneWire_reset()) return 0;  //如果总线错误
    OneWire_write_byte(DS18B20_SKIP_ROM);
    OneWire_write_byte(DS18B20_READ_SPAD);
    
    pad0 = OneWire_read_byte();  //读pad0
    pad1 = OneWire_read_byte();  //读pad1
    
	//如果主机不想继续读取后面的ScratchPad字节,则用一个reset信号来表示读取ScratchPad结束。
    OneWire_reset();       
	
	t = pad1;
	t = (t<<8)|pad0;
	*temp =   ((int)(t)) * 0.0625; 
	
    return 1;
}

参考资料

Using a UART to Implement a 1-Wire Bus Master | Maxim Integr

Implementing 1-Wire Buses In imp-enabled Devices | Dev Center

DS18B20是单总线数字传感器,共有6种信号类型:复位脉冲、应答脉冲、写0、写1、读0和读1。所有这些信号,除了应答脉冲以外,都由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前。 这几个信号的时序如下: 1)复位脉冲和应答脉冲 单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平时间至少480us,以产生复位脉冲。接着主机释放总线,4.7K的上拉电阻将单总线拉高,延时15~60us,并进入接收模式(Rx)。接着DS18B20拉低总线60~240us,以产生低电平应答脉冲,若为低电平,再延时480us。 2)写时序 写时序包括写0时序和写1时序。所有写时序至少需要60us,且在2次独立的写时序之间至少需要1us的恢复时间,两种写时序均起始于主机拉低总线。写1时序:主机输出低电平,延时2us,然后释放总线,延时60us。写0时序:主机输出低电平,延时60us,然后释放总线,延时2us。 3)读时序 单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要60us,且在2次独立的读时序之间至少需要1us的恢复时间。每个读时序都由主机发起,至少拉低总线1us。主机在读时序期间必须释放总线,并且在时序起始后的15us之内采样总线状态。典型的读时序过程为:主机输出低电平延时2us,然后主机转入输入模式延时12us,然后读取单总线当前的电平,然后延时50us。 DS18B20的温度读取过程一般为:复位->发SKIPROM命令(0XCC)->发开始转换命令(0X44)->延时->复位->发送SKIPROM命令(0XCC)->发读存储器命令(0XBE)->连续读出两个字节数据(即温度)->结束。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值