1.串口通信
1.1介绍
串口是串行接口(serial port)的简称,也称为串行通信接口或COM接口。
串口通信是指采用串行通信协议(serial communication)在一条信号线上将数据一个比特一个比特地逐位进行传输的通信模式。
串口按电气标准及协议来划分,包括RS-232-C、RS-422、RS485等。
1.1.1 串行同步通信
同步通信(SYNC:synchronous data communication)是指在约定的通信速率下,发送端和接收端的时钟信号频率和相位始终保持一致(同步),这样就保证了通信双方在发送和接收数据时具有完全一致的定时关系。
同步通信把许多字符组成一个信息组(信息帧),每帧的开始用同步字符来指示,一次通信只传送一帧信息。在传输数据的同时还需要传输时钟信号,以便接收方可以用时针信号来确定每个信息位。
同步通信的优点是传送信息的位数几乎不受限制,一次通信传输的数据有几十到几千个字节,通信效率较高。同步通信的缺点是要求在通信中始终保持精确的同步时钟,即发送时钟和接收时钟要严格的同步(常用的做法是两个设备使用同一个时钟源)。
总结:时钟必须同步,传输速率快,通信效率高。
1.1.2 串行异步通信
异步通信(ASYNC:asynchronous data communication),又称为起止式异步通信,是以字符为单位进行传输的,字符之间没有固定的时间间隔要求,而每个字符中的各位则以固定的时间传送。
在异步通信中,收发双方取得同步是通过在字符格式中设置起始位和停止位的方法来实现的。具体来说就是,在一个有效字符正式发送之前,发送器先发送一个起始位,然后发送有效字符位,在字符结束时再发送一个停止位,起始位至停止位构成一帧。停止位至下一个起始位之间是不定长的空闲位,并且规定起始位为低电平(逻辑值为0),停止位和空闲位都是高电平(逻辑值为1),这样就保证了起始位开始处一定会有一个下跳沿,由此就可以标志一个字符传输的起始。而根据起始位和停止位也就很容易的实现了字符的界定和同步。
显然,采用异步通信时,发送端和接收端可以由各自的时钟来控制数据的发送和接收,这两个时钟源彼此独立,可以互不同步。
总结:通过起始位和停止位的方式来实现传输,要发送起始和停止位,增加了负载,传输速率也比较低,但是实现起来比较简单。
1.2异步通信
1.2.1 异步通信的数据格式
异步通信规定传输的数据格式由起始位(start bit)、数据位(data bit)、奇偶校验位(parity bit)和停止位(stop bit)组成。
注意:该图中未画出奇偶校验位,因为奇偶检验位不是必须有的,如果有奇偶检验位,则奇偶检验位应该在数据位之后,停止位之前。
波特率:就是通讯的速率。在异步通讯中由于没有时钟信号,所以两个通讯设备之间需要预定好波特率,只有在波特率一致的情况下,才能保证接收方和发送方获取同样的数据。波特率,可以通俗的理解为一个设备在一秒内发送(或接收)了多少码元的数据。常见的波特率为9600、19200、115200
(1)起始位:起始位必须是持续一个比特时间的逻辑0电平,标志传输一个字符的开始,接收方可用起始位使自己的接收时钟与发送方的数据同步。
(2)数据位:数据位紧跟在起始位之后,是通信中的真正有效信息。数据位的位数可以由通信双方共同约定,一般可以是5位、7位或8位,标准的ASCII码是0~127(7位),扩展的ASCII码是0~255(8位)。传输数据时先传送字符的低位,后传送字符的高位。
(3)奇偶校验位:奇偶校验位仅占一位,用于进行奇校验或偶校验,奇偶检验位不是必须有的。如果是奇校验,需要保证传输的数据总共有奇数个逻辑高位;如果是偶校验,需要保证传输的数据总共有偶数个逻辑高位。
比如一个8位长的有效数据为:10101001,此时共有4个“1”,为达到奇校验效果,校验位为“1””,此时传输的数据有9位;偶检验则正好相反,在上述例子中补校验位为“0””,即可达到偶校验的效果;
由此可见,奇偶校验位仅是对数据进行简单的置逻辑高位或逻辑低位,不会对数据进行实质的判断,这样做的好处是接收设备能够知道一个位的状态,有可能判断是否有噪声干扰了通信以及传输的数据是否同步。
(4)停止位:停止位可以是是1位、1.5位或2位,可以由软件设定。它一定是逻辑1电平,标志着传输一个字符的结束。
(5)空闲位:空闲位是指从一个字符的停止位结束到下一个字符的起始位开始,表示线路处于空闲状态,必须由高电平来填充。
1.2.2 异步通信数据发生过程
清楚了异步通信的数据格式之后,就可以按照指定的数据格式发送数据了,发送数据的具体步骤如下:
(1)初始化后或者没有数据需要发送时,发送端输出逻辑1,可以有任意数量的空闲位。
(2)当需要发送数据时,发送端首先输出逻辑0,作为起始位。
(3)接着就可以开始输出数据位了,发送端首先输出数据的最低位D0,然后是D1,最后是数据的最高位。
(4)如果设有奇偶检验位,发送端输出检验位。
(5)最后,发送端输出停止位(逻辑1)。
(6)如果没有信息需要发送,发送端输出逻辑1(空闲位),如果有信息需要发送,则转入步骤(2)。
1.2.3 异步通信的数据接收过程
在异步通信中,接收端以接收时钟和波特率因子决定每一位的时间长度。下面以波特率因子等于16(接收时钟每16个时钟周期使接收移位寄存器移位一次)为例来说明。
(1)开始通信,信号线为空闲(逻辑1),当检测到由1到0的跳变时,开始对接收时钟计数。
(2)当计到8个时钟的时候,对输入信号进行检测,若仍然为低电平,则确认这是起始位,而不是干扰信号。
(3)接收端检测到起始位后,隔16个接收时钟对输入信号检测一次,把对应的值作为D0位数据。
(4)再隔16个接收时钟,对输入信号检测一次,把对应的值作为D1位数据,直到全部数据位都输入。
(5)检验奇偶检验位。
(6)接收到规定的数据位个数和校验位之后,通信接口电路希望收到停止位(逻辑1),若此时未收到逻辑1,说明出现了错误,在状态寄存器中置“帧错误”标志;若没有错误,对全部数据位进行奇偶校验,无校验错时,把数据位从移位寄存器中取出送至数据输入寄存器,若校验错,在状态寄存器中置“奇偶错”标志。
(7)本帧信息全部接收完,把线路上出现的高电平作为空闲位。
(8)当信号再次变为低时,开始进入下一帧的检测。
注:
(1)发送时钟:发送数据时,首先将要发送的数据送入移位寄存器,然后在发送时钟的控制下,将该并行数据逐位移位输出。
(2)接收时钟:在接收串行数据时,接收时钟的上升沿对接收数据采样,进行数据位检测,并将其移入接收器的移位寄存器中,最后组成并行数据输出。
(3)波特率因子:波特率因子是指发送或接收1个数据位所需要的时钟脉冲个数。
1.3 串口接口
常用的串行通信接口标准有RS-232C、RS-422、RS-423和RS-485。其中,RS-232C作为串行通信接口的电气标准定义了数据终端设备(DTE:data terminal equipment)和数据通信设备(DCE:data communication equipment)间按位串行传输的接口信息,合理安排了接口的电气信号和机械要求,在世界范围内得到了广泛的应用。
1.3.1 电气特性
RS-232C对电器特性、逻辑电平和各种信号功能都做了规定,如下:
在TXD和RXD数据线上:
(1)逻辑1为-3~-15V的电压
(2)逻辑0为3~15V的电压
在RTS、CTS、DSR、DTR和DCD等控制线上:
(1)信号有效(ON状态)为3~15V的电压
(2)信号无效(OFF状态)为-3~-15V的电压
由此可见,RS-232C是用正负电压来表示逻辑状态,与晶体管-晶体管逻辑集成电路(TTL)以高低电平表示逻辑状态的规定正好相反。
1.3.2 信号线分配
S-232C标准接口有25条线,其中,4条数据线、11条控制线、3条定时线以及7条备用和未定义线。那么,这些信号线在9针串口和25针串口的管脚上是如何分配的呢?9针串口和25针串口信号线分配如图4所示。
RS-232信号线。它是在最初的应用中,RS- 232串口标准常用于计算机、路由器与调制解调器(MODEN,俗称“猫”)之间的通讯。它的全名是“数据终端设备(DTE)和数据通讯设备(DCE)直接串行二进制数据交换接口技术标准”。该标准原先采用25个脚的DB-25连接器,后来逐渐淘汰,现在一般采用的是“DB-9”连接器,而工业控制的RS-232口一般只使用RXD、TXD、GND三条线。“DB9”串口线如图 15-2,接线口以针式引出的信号线称为公头,以孔式引出信号线的称为母头。
简单介绍:
(1)数据装置准备好(DSR),有效状态(ON)表示数据通信设备处于可以使用状态。
(2)数据终端准备好(DTR),有效状态(ON)表示数据终端设备处于可以使用状态。
这两个设备状态信号有效,只表示设备本身可用,并不说明通信链路可以开始进行通信了,能否开始进行通信要由下面的一些控制信号决定。
(3)请求发送(RTS),用来表示数据终端设备(DTE)请求数据通信设备(DCE)发送数据。
(4)允许发送(CTS),用来表示数据通信设备(DCE)已经准备好了数据,可以向数据终端设备(DTE)发送数据,是对请求发送信号RTS的响应。
请求发送(RTS)和允许发送(CTS)用于半双工的通信系统中,在全双工的系统中,不需要使用请求发送(RTS)和允许发送(CTS)信号,直接将其置为ON即可。
(5)数据载波检出(DCD),用于表示数据通信设备(DCE)已接通通信链路,告知数据终端设备(DTE)准备接收数据。
(6)振铃指示(RI),当数据通信设备收到交换台送来的振铃呼叫信号时,使该信号有效(ON),通知终端,已被呼叫。
(7)发送数据(TXD),数据终端设备(DTE)通过该信号线将串行数据发送到数据通信设备(DCE)。
(8)接收信号(RXD),数据终端设备(DTE)通过该信号线接收从数据通信设备(DCE)发来的串行数据。
(9)地线(SG、PG),分别表示信号地和保护地信号线。
1.4 stm32的USART
STM32芯片拥有多个USART外设用于串口通讯,英文全称是:UniversalSynchronous Asynchronous Receiver and Transmitter。在阅读过《STM32F10xxx编程参考手册2010(中文)》(附件3)的同学就会发现,STM32的串口非常强大,它不仅支持最基本的通用串口同步、串口通信,还有LIN总线功能((局域互联网)、lRDA功能(红外通信)、SmartCard功能。
下面介绍基本的也是最常用的功能,利用串口来输出调试信息,从而达到帮助我们调试程序的目的。另外,它还具有UART外设,英文全称是: Universal Asynchronous Receiver and Transmitter。和USART不同的是,UART在 USART基础上裁剪掉了同步通信的功能,只有异步通信。同步通信中传输方和接收方使用同步时钟。我们平时用的串口通信基本都是UART。
USART满足外部设备对工业标准NRZ异步串行数据格式的要求,并且使用了小数波特率发生器,可以提供多种波特率,使它的应用更加广泛。USART发送接收有三种基本方式,轮询、中断和 DMA。它的串口外设架构如图
1.4.1引脚功能
TX:发送数据输出引脚。
Rx:接收数据输入引脚。
sw_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。
nRTS:请求以发送(Request To Send),n表示低电平有效。如果使能RTS流控制,当USART接收器准备好接收新数据时就会将nRTS变成低电平;当接收寄存器已满时,nRTS将被设置为高电平。该引脚只适用于硬件流控制。
nCTS:清除以发送(Clear To Send),n表示低电平有效。如果使能CTS流控制,发送器在发送下一帧之前会检测nCTS引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。
IRDA_OUT/IRDA_IN:用于红外传输数据。
SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。
1.4.2数据寄存器
USART 数据寄存器(USART_DR)只有低9位有效,并且第9位数据是否有效取决于USART控制寄存1(USART_CR1)的M位设置,当M位为0时表示8位数据字长,当M位为1表示9位数据字长,我们一般使用8位数据字长。
USART_DR包含了已发送的数据或者接收到的数据。USART_DR实际是包含了两个寄存器,一个专门用于发送的可写TDR,另一个专门用于接收的可读RDR。当需要发送数据时,内核或DMA外设会把数据从内存写入到发送数据寄存器TDR。因为 TDR和 RDR都是介于系统总线和移位寄存器之间,发送时,TDR的数据转移到发送移位寄存器,然后从移位寄存器一位一位地发送出去。接收数据就是一个逆过程,数据一位一位地输入接收移位寄存器,然后转移到RDR,最后使用内核指令或DMA读取到内存中。
1.4.3控制器
USART有专门的控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等。使用USART之前需要向USART_CR1寄存器中的UE位置1使能USART。
- 发送器:
当发送使能位(TE)置1时,发送移位寄存器中的数据在TX脚上输出,如果是同步模式,在 SCLK引脚也输出时钟脉冲。每个字符前都有一个低电平的起始位;之后跟着的停止位,数目可通过USART控制寄存器2(USART_CR2)的STOP[1:0]位控制,可选0.5个、1个、1.5个和2个停止位,默认使用1个停止位。2个停止位可用于常规USART模式,单线模式以及调制解调器模式;0.5个和1.5个停止位用于智能卡模式。具体发送字符时序如图
在发送数据时,发送器对TE位置1(发送使能),发送一个空闲帧作为第一次数据发送,把要发送的数据写进USART_DR寄存器,在写入最后一个数据字后,等待TC=1(发送完成),表示最后一个数据帧的传输结束。如果USART_CR1寄存器中的TCIE位被实设置,(发送完成中断使能)则会产生中断。
- 接收器:
设置USART_CR1的RE位(接收使能)。激活接收器,使它开始寻找起始位。在确定到起始位后就根据RX线电平状态把数据存放在接收移位寄存器内。接收完成后将移位寄存器数据移到RDR内,并把 USART_SR寄存器的RXNE位置1(确定收到数据),表明数据已经被接收并且可以被读出。如果USART_CR1中的 RXNEIE位被置1,表示产生中断。 接受完成 产生中断
1.4.4波特率控制
波特率,即每秒传输的二进制位数,用b/s (bps)表示,通过对时钟的控制可以改变波特率。在配置波特率时,我们在比率寄存器 USART_BRR写入参数,修改了串口时钟的分频值USARTDIV。USART_BRR寄存器包括两部分,分别是DIv_Mantissa ( USARTDIV的整数部分)和 DIv_Fraction (USARTDIV的小数)部分,最终,计算公式为USARTDIV = DIv_Mantissa+(DIv_Fraction/16)。例如,DIV_Mantissa=27,DIV_Fraction=12 (USART_BRR=Ox1BC)
于是:
Mantissa (USARTDIV)=27
Fraction (USARTDIV) =12/16=0.75
所以USARTDIV=27.75
1.5 标准库分析
有5个 串行接口
我们使用第一个
初始化结构体:
typedef struct
{
uint32_t USART_BaudRate; //设置波特率
uint16_t USART_WordLength; //设置字长 一般为8
uint16_t USART_StopBits; //设置停止位 根据需求设置 查看手册
uint16_t USART_Parity; //校验位
uint16_t USART_Mode; //模式 接收还是发送
uint16_t USART_HardwareFlowControl; //硬件流控制 不管
} USART_InitTypeDef;
主要使用的函数:
void USART_Init(USART_TypeDef USARTx, USART_InitTypeDef USART_InitStruct) 初始化
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState) 使能
1.6 串口指令控制
我们在学习c语言时,经常使用c语言标准函数库输入输出函数,比如 printf、scanf、getchar、sprintf等,这些函数在我们调试程序时,或者需要显示某些模块数据,而没有显示屏时,便直接使用串口,也能满足需求。为了让开发板支持这些函数,我们需要把USART发送和接收函数添加到这些函数的内部函数内。可以在串口调试助手输入指令,命令开发板执行任务。
用户能定义自己的C语言库函数,连接器在连接时自动使用这些新的功能函数。这个过程叫做重定向c语言库函数。
举例子来说,我们的USART口,本来库函数fqutc()是把字符输出到调试器控制窗口去的,但由于我们把输出设备改成了 UART端口,这样一来,所有基于fputc()系列函数输出都被重定向到uART端口上去了。int fputc()作用是重定向c库函数printf到DEBUG_USARTx ; int fgetc()作用是重定向C库函数到getchar 、 scanf到DEBUG_USARTx。在 bsp_debug_usart.h文件中包含“stdio.h”文件(标准库的输入输出头文件)。还需要在”魔术棒”的Target选项卡勾选”Use MicroLlB”它是缺省c库的备份库。
总结:(1)int fputc()作用是重定向c库函数printf
(2)int fgetc()作用是重定向C库函数到getchar 、 scanf
(3)在”魔术棒”的Target选项卡勾选”Use MicroLlB
(4) 包含头文件 #include
1.6.1 fputc
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到调试串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
/* 等待串口数据发送完毕 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
1.6.2 fgetc
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}
1.7 初始化函数
#include "usart.h"
/**
* 函数功能: 板载调试串口参数配置.
* 输入参数: 无
* 返 回 值: 无
*/
void DEBUG_USART_Init(void)
{
/* 定义IO硬件初始化结构体变量 */
GPIO_InitTypeDef GPIO_InitStructure;
/* 定义USART初始化结构体变量 */
USART_InitTypeDef USART_InitStructure;
/* 使能USART时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
/* 使能USART功能GPIO时钟,RX和TX对应的引脚 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO,ENABLE);
/* 调试USART功能GPIO初始化 */
/* 设定USART发送对应IO编号 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
/* 设定USART发送对应IO模式:复用推挽输出 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
/* 设定USART发送对应IO最大操作速度 :GPIO_Speed_50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/* 初始化USART发送对应IO */
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 设定USART接收对应IO编号 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
/* 设定USART发送对应IO模式:浮空输入 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
/* 其他没有重新赋值的成员使用与串口发送相同配置 */
/* 初始化USART接收对应IO */
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* USART工作环境配置 */
/* USART波特率:115200 */
USART_InitStructure.USART_BaudRate = 115200;
/* USART字长(有效位):8位 */
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
/* USART停止位:1位 */
USART_InitStructure.USART_StopBits = USART_StopBits_1;
/* USART校验位:无 */
USART_InitStructure.USART_Parity = USART_Parity_No ;
/* USART硬件数据流控制(硬件信号控制传输停止):无 */
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
/* USART工作模式使能:允许接收和发送 */
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
/* 初始化USART */
USART_Init(USART1, &USART_InitStructure);
/* 使能USART */
USART_Cmd(USART1, ENABLE);
}
/**
* 函数功能: 重定向c库函数printf到USARTx
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到调试串口 */
USART_SendData(USART1, (uint8_t) ch);
/* 等待串口数据发送完毕 */
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
/**
* 函数功能: 重定向c库函数getchar,scanf到USARTx
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
main
#include "stm32f10x.h"
#include "clock.h"
#include "usart.h"
int main(void)
{
Set_SysClockTo72M();
DEBUG_USART_Init();
printf("A");
while(1)
{
};
}
2. DS18B20
2.1 DS18B20简介
DS18B20 是由 DALLAS 半导体公司推出的一种的“一线总线”接口的温度传感器。与传统的热敏电阻等测温元件相比,它是一种新型的体积小、适用电压宽、与微处理器接口简单的数字化温度传感器。一线总线结构具有简洁且经济的特点,可使用户轻松地组建传感器网络,从而为测量系统的构建引入全新概念,测量温度范围为-55~+125℃ ,精度为±0.5℃。现场温度直接以“一线总线”的数字方式传输,大大提高了系统的抗干扰性。它能直接读出被测温度,并且可根据实际要求通过简单的编程实现 9~l2 位的数字值读数方式。它工作在 3—5. 5 V 的电压范围,采用多种封装形式,从而使系统设计灵活、方便,设定分辨率及用户设定的报警温度存储在 EEPROM 中,掉电后依然保存。
内部结构如下:
DS18B20的通讯方式是单总线的,
其中DQ就是主要的通讯线路,对DS的读取和写入都需要主机来控制DQ线路的DQ高低电平的时间来确定。
2.2控制流程
根据DS18B20的通信协议,DS18B20只能作为从机,而单片机系统作为主机,单片机控制DS18B20完成一次温度转换必须经过3个步骤:复位、发送ROM指令、发送RAM指令。每次对DS18B20的操作都要进行以上三个步骤。
复位过程为:单片机将数据线拉低至少480uS,然后释放数据线,等待15-60uS让DS18B20接收信号,DS18B20接收到信号后,会把数据线拉低60-240uS,主机检测到数据线被拉低后标识复位成功;
发送ROM指令:ROM指令表示主机对系统上所接的全部DS18B20进行寻址,以确定对那一个DS18B20进行操作,或者是读取某个DS18B20的ROM序列号。
发送RAM指令:RAM指令用于单片机对DS18B20内部RAM进行操作,如读取寄存器的值,或者设置寄存器的值。
2.2.1 一些ROM指令
2.2.2 一些RAM指令
2.3 时序图
2.3.1 初始化时序图
主机首先发出一个480-960微秒的低电平脉冲,然后释放总线变为高电平,并在随后的480微秒时间内对总线进行检测,如果有低电平出现说明总线上有器件已做出应答。若无低电平出现一直都是高电平说明总线上无器件应答。
做为从器件的DS18B20在一上电后就一直在检测总线上是否有480-960微秒的低电平出现,如果有,在总线转为高电平后等待15-60微秒后将总线电平拉低60-240微秒做出响应存在脉冲,告诉主机本器件已做好准备。若没有检测到就一直在检测等待。
2.3.2 写时序图
写周期最少为60微秒,最长不超过120微秒。写周期一开始做为主机先把总线拉低1微秒表示写周期开始。随后若主机想写0,则继续拉低电平最少60微秒直至写周期结束,然后释放总线为高电平。若主机想写1,在一开始拉低总线电平1微秒后就释放总线为高电平,一直到写周期结束。而做为从机的DS18B20则在检测到总线被拉底后等待15微秒然后从15us到45us开始对总线采样,在采样期内总线为高电平则为1,若采样期内总线为低电平则为0。
2.3.3 读时序图
对于读数据操作时序也分为读0时序和读1时序两个过程。读时隙是从主机把单总线拉低之后,在1微秒之后就得释放单总线为高电平,以让DS18B20把数据传输到单总线上。DS18B20在检测到总线被拉低1微秒后,便开始送出数据,若是要送出0就把总线拉为低电平直到读周期结束。若要送出1则释放总线为高电平。主机在一开始拉低总线1微秒后释放总线,然后在包括前面的拉低总线电平1微秒在内的15微秒时间内完成对总线进行采样检测,采样期内总线为低电平则确认为0。采样期内总线为高电平则确认为1。完成一个读时序过程,至少需要60us才能完成
注意:每次读写前对 DS18B20 进行复位初始化。复位要求主 CPU 将数据线下拉 500us ,然后释放, DS18B20 收到信号后等待 16us~60us 左右,然后发出60us~240us 的存在低脉冲,主 CPU 收到此信号后表示复位成功。
2.4 具体实例
现在我们要做的是让DS18B20进行一次温度的转换,那具体的操作就是:
1、主机先作个复位操作,
2、主机再写跳过ROM的操作(CCH)命令,
3、然后主机接着写个转换温度的操作命令,后面释放总线至少一秒,让DS18B20完成转换的操作。在这里要注意的是每个命令字节在写的时候都是低字节先写,例如CCH的二进制为11001100,在写到总线上时要从低位开始写,写的顺序是“零、零、壹、壹、零、零、壹、壹”。整个操作的总线状态如下图。
读取RAM内的温度数据。同样,这个操作也要接照三个步骤。
1、主机发出复位操作并接收DS18B20的应答(存在)脉冲。
2、主机发出跳过对ROM操作的命令(CCH)。
3、主机发出读取RAM的命令(BEH),随后主机依次读取DS18B20发出的从第0一第8,共九个字节的数据。如果只想读取温度数据,那在读完第0和第1个数据后就不再理会后面DS18B20发出的数据即可。同样读取数据也是低位在前的。整个操作的总线状态如下图:
2.5 代码实现
硬件图
注意我们控制时序
使用的是systick时钟
移植代码
看懂代码~~~
需要注意的是:
stm32的IO口是准双向IO口,在不同情况要配置不同的输入或者输出功能
注意设置输出模式的时候要设置为开漏输出
读取的时候要使用GPIO_ReadInputDataBit
以下是用到的宏定义
#define DS18B20_DQ_SCK_APBxClock_FUN RCC_APB2PeriphClockCmd //时钟
#define DS18B20_DQ_GPIO_CLK RCC_APB2Periph_GPIOD //端口
#define DS18B20_DQ_GPIO_PORT GPIOD //端口
#define DS18B20_DQ_GPIO_PIN GPIO_Pin_3 //引脚
/************************** DS18B20 函数宏定义********************************/
#define DS18B20_DQ_0 GPIO_ResetBits(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN) //输入0
#define DS18B20_DQ_1 GPIO_SetBits(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN) //输入1
#define DS18B20_DQ_IN() GPIO_ReadInputDataBit(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN) //读取
如果温度为负数:
二进制中的前面d5位是符号位,如果测得的温度大于0,这5位为0,只要将测得到的数值乘0.0625即可得到实际温度;如果温度小于0,这5位位1,测得的数值需要取反加1再乘以0.0625即可得到实际温度。例如+125℃的数字输出为07D0H,+25.0625的数字输出为0191H,—25.0625℃的数字输出为FE6FH,-55℃的数字输出为FC90H。
详情可参考:对应的程序文件
#include "ds18b20.h"
static void DS18B20_GPIO_Config(void);
static void DS18B20_Mode_IPU(void);
static void DS18B20_Mode_Out_PP(void);
static void DS18B20_Rst(void);
static uint8_t DS18B20_Presence(void);
static uint8_t DS18B20_ReadBit(void);
static uint8_t DS18B20_ReadByte(void);
static void DS18B20_WriteByte(uint8_t dat);
static void DS18B20_SkipRom(void);
static void DS18B20_MatchRom(void);
/* 函数体 --------------------------------------------------------------------*/
/**
* 函数功能: DS18B20 初始化函数
* 输入参数: 无
* 返 回 值: 0:初始化成功,检测到传感器,1:初始化失败
* 说 明:无
*/
uint8_t DS18B20_Init(void)
{
DS18B20_GPIO_Config ();
DS18B20_DQ_1;
DS18B20_Rst();
return DS18B20_Presence ();
}
/**
* 函数功能: 配置DS18B20用到的I/O口
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
static void DS18B20_GPIO_Config(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启DS18B20_DQ_GPIO_PORT的外设时钟*/
DS18B20_DQ_SCK_APBxClock_FUN ( DS18B20_DQ_GPIO_CLK, ENABLE);
/*选择要控制的DS18B20_DQ_GPIO_PORT引脚*/
GPIO_InitStructure.GPIO_Pin = DS18B20_DQ_GPIO_PIN;
/*设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化DS18B20_DQ_GPIO_PORT*/
GPIO_Init ( DS18B20_DQ_GPIO_PORT , &GPIO_InitStructure );
}
/**
* 函数功能: 使DS18B20-DATA引脚变为输入模式
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
static void DS18B20_Mode_IPU(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*选择要控制的DS18B20_DQ_GPIO_PORT引脚*/
GPIO_InitStructure.GPIO_Pin = DS18B20_DQ_GPIO_PIN;
/*设置引脚模式为浮空输入模式*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
/*调用库函数,初始化DS18B20_DQ_GPIO_PORT*/
GPIO_Init(DS18B20_DQ_GPIO_PORT, &GPIO_InitStructure);
}
/**
* 函数功能: 使DS18B20-DATA引脚变为输出模式
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
static void DS18B20_Mode_Out_PP(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*选择要控制的DS18B20_DQ_GPIO_PORT引脚*/
GPIO_InitStructure.GPIO_Pin = DS18B20_DQ_GPIO_PIN;
/*设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化DS18B20_DQ_GPIO_PORT*/
GPIO_Init(DS18B20_DQ_GPIO_PORT, &GPIO_InitStructure);
}
/**
* 函数功能: 主机给从机发送复位脉冲
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
static void DS18B20_Rst(void)
{
/* 主机设置为推挽输出 */
DS18B20_Mode_Out_PP();
DS18B20_DQ_0; //下拉为低电平
/* 主机至少产生480us的低电平复位信号 */
Delay_us(750);
/* 主机在产生复位信号后,需将总线拉高 */
DS18B20_DQ_1; //上拉为高电平
/*从机接收到主机的复位信号后,会在15~60us后给主机发一个存在脉冲*/
Delay_us(15);
}
/**
* 函数功能: 检测从机给主机返回的存在脉冲
* 输入参数: 无
* 返 回 值: 0:成功,1:失败
* 说 明:无
*/
static uint8_t DS18B20_Presence(void)
{
uint8_t pulse_time = 0;
/* 主机设置为上拉输入 */
DS18B20_Mode_IPU();
/* 等待存在脉冲的到来,存在脉冲为一个60~240us的低电平信号
* 如果存在脉冲没有来则做超时处理,从机接收到主机的复位信号后,会在15~60us后给主机发一个存在脉冲
*/
while( DS18B20_DQ_IN() && pulse_time<100 ) //做超时处理
{
pulse_time++;
Delay_us(1);
}
/* 经过100us后,存在脉冲都还没有到来*/
if( pulse_time >=100 )
return 1;
else
pulse_time = 0;
/* 存在脉冲到来,且存在的时间不能超过240us */
while( !DS18B20_DQ_IN() && pulse_time<240 )
{
pulse_time++;
Delay_us(1);
}
if( pulse_time >=240 )
return 1;
else
return 0;
}
/**
* 函数功能: 从DS18B20读取一个bit
* 输入参数: 无
* 返 回 值: 读取到的数据
* 说 明:无
*/
static uint8_t DS18B20_ReadBit(void)
{
uint8_t dat;
/* 读0和读1的时间至少要大于60us */
DS18B20_Mode_Out_PP();
/* 读时间的起始:必须由主机产生 >1us <15us 的低电平信号 */
DS18B20_DQ_0;
Delay_us(10);
/* 设置成输入,释放总线,由外部上拉电阻将总线拉高 */
DS18B20_Mode_IPU();
//Delay_us(2);
if( DS18B20_DQ_IN() == SET )
dat = 1;
else
dat = 0;
/* 这个延时参数请参考时序图 */
Delay_us(45);
return dat;
}
/**
* 函数功能: 从DS18B20读一个字节,低位先行
* 输入参数: 无
* 返 回 值: 读到的数据
* 说 明:无
*/
static uint8_t DS18B20_ReadByte(void)
{
uint8_t i, j, dat = 0;
for(i=0; i<8; i++)
{
j = DS18B20_ReadBit();
dat = (dat) | (j<<i);
}
return dat;
}
/**
* 函数功能: 写一个字节到DS18B20,低位先行
* 输入参数: dat:待写入数据
* 返 回 值: 无
* 说 明:注意是从最低位开始写的
*/
static void DS18B20_WriteByte(uint8_t dat)
{
uint8_t i, testb;
DS18B20_Mode_Out_PP();
for( i=0; i<8; i++ )
{
testb = dat & 0x01;
dat = dat >> 1;
/* 写0和写1的时间至少要大于60us */
if (testb)
{
//写1
DS18B20_DQ_0;
/* 1us < 这个延时 < 15us */
Delay_us(8);
DS18B20_DQ_1;
Delay_us(58);
}
else
{
//写0
DS18B20_DQ_0;
/* 60us < Tx 0 < 120us */
Delay_us(70);
DS18B20_DQ_1;
/* 1us < Trec(恢复时间) < 无穷大*/
Delay_us(2);
}
}
}
/**
* 函数功能: 跳过匹配 DS18B20 ROM
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
static void DS18B20_SkipRom ( void )
{
//复位
DS18B20_Rst();
//检测是否回应
DS18B20_Presence();
DS18B20_WriteByte(0XCC); /* 跳过 ROM */
}
/**
* 函数功能: 执行匹配 DS18B20 ROM
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
static void DS18B20_MatchRom ( void )
{
DS18B20_Rst();
DS18B20_Presence();
DS18B20_WriteByte(0X55); /* 匹配 ROM */
}
/*
* 存储的温度是16 位的带符号扩展的二进制补码形式
* 当工作在12位分辨率时,其中5个符号位,7个整数位,4个小数位
*
* |---------整数----------|-----小数 分辨率 1/(2^4)=0.0625----|
* 低字节 | 2^3 | 2^2 | 2^1 | 2^0 | 2^(-1) | 2^(-2) | 2^(-3) | 2^(-4) |
*
*
* |-----符号位:0->正 1->负-------|-----------整数-----------|
* 高字节 | s | s | s | s | s | 2^6 | 2^5 | 2^4 |
*
*
* 温度 = 符号位 + (整数 + 小数)*0.0625
*/
/**
* 函数功能: 在跳过匹配 ROM 情况下获取 DS18B20 温度值
* 输入参数: 无
* 返 回 值: 温度值
* 说 明:无
*/
float DS18B20_GetTemp_SkipRom ( void )
{
uint8_t tpmsb, tplsb;
short s_tem;
float f_tem;
DS18B20_SkipRom ();
DS18B20_WriteByte(0X44); /* 开始转换 */
DS18B20_SkipRom ();
DS18B20_WriteByte(0XBE); /* 读温度值 */
//读取低8位
tplsb = DS18B20_ReadByte();
//读取高8位
tpmsb = DS18B20_ReadByte();
//合起来
s_tem = tpmsb << 8;
s_tem = s_tem | tplsb;
if( s_tem < 0 ) /* 负温度 */
f_tem = (~s_tem + 1) * 0.0625;
else
f_tem = s_tem * 0.0625;
return f_tem;
}
/**
* 函数功能: 在匹配 ROM 情况下获取 DS18B20 温度值
* 输入参数: ds18b20_id:用于存放 DS18B20 序列号的数组的首地址
* 返 回 值: 无
* 说 明:无
*/
void DS18B20_ReadId ( uint8_t * ds18b20_id )
{
uint8_t uc;
DS18B20_WriteByte(0x33); //读取序列号
for ( uc = 0; uc < 8; uc ++ )
ds18b20_id [ uc ] = DS18B20_ReadByte();
}
/**
* 函数功能: 在匹配 ROM 情况下获取 DS18B20 温度值
* 输入参数: ds18b20_id:存放 DS18B20 序列号的数组的首地址
* 返 回 值: 温度值
* 说 明:无
*/
float DS18B20_GetTemp_MatchRom ( uint8_t * ds18b20_id )
{
uint8_t tpmsb, tplsb, i;
short s_tem;
float f_tem;
DS18B20_MatchRom (); //匹配ROM
for(i=0;i<8;i++)
DS18B20_WriteByte ( ds18b20_id [ i ] );
DS18B20_WriteByte(0X44); /* 开始转换 */
DS18B20_MatchRom (); //匹配ROM
for(i=0;i<8;i++)
DS18B20_WriteByte ( ds18b20_id [ i ] );
DS18B20_WriteByte(0XBE); /* 读温度值 */
tplsb = DS18B20_ReadByte();
tpmsb = DS18B20_ReadByte();
s_tem = tpmsb<<8;
s_tem = s_tem | tplsb;
if( s_tem < 0 ) /* 负温度 */
f_tem = (~s_tem+1) * 0.0625;
else
f_tem = s_tem * 0.0625;
return f_tem;
}
main
#include "stm32f10x.h"
#include "clock.h"
#include "usart.h"
#include "systick.h"
#include "ds18b20.h"
int main(void)
{
//存放id
uint8_t i, DS18B20ID[8];
//存放温度
float temperature;
//初始化时钟
Set_SysClockTo72M();
//初始化串口
DEBUG_USART_Init();
//滴答时钟初始化
SysTick_Init();
//检测是否存在
while(DS18B20_Init())
{
printf("DS18B20温度传感器不存在\n");
}
printf("检测到DS18B20温度传感器,并初始化成功\n");
//读取id
DS18B20_ReadId(DS18B20ID);
//通过串口输出
printf("DS18B20的序列号是:0x");
for ( i = 0; i < 8; i ++ )
printf ( "%.2X", DS18B20ID[i]);
printf("\n");
while(1)
{
//读取温度
temperature=DS18B20_GetTemp_MatchRom(DS18B20ID);
//串口输出
printf("获取该序列号器件的温度:%.1f\n", temperature);
//间隔1s读一次
Delay_ms(1000);
};
}
运行结果: