STM32F103C8T6采 DS18B20,通过A7680C 4G模块不断发送短信到手机
🎯 实现效果





🎯 传感器用途
DS18B20作为Dallas半导体推出的单总线数字温度传感器,凭借其"单总线通信、无需AD转换、精准度高、布线极简"的核心优势,已广泛应用于工业控制、智能家居、农业养殖、医疗健康、交通运输、嵌入式科研等多个领域,涵盖环境测温、设备监控、温控联动等核心需求,具体细分场景如下:
- 工业控制与自动化领域
• 设备状态监测:电机绕组温度监测、配电柜/配电箱内部测温、变频器/PLC模块温度监控
• 工业制程温控:化工反应釜温度采集、冶金设备炉体测温、蒸汽管道温度监测;
• 冷链与仓储:工业冷库、冷链运输车的多点温度监测、药品/化工原料仓储温度追溯。
- 智能家居与生活电器领域
• 室温调控:智能空调、地暖系统的温度采集、智能音箱/中控屏的室温显示;
• 家电温控:冰箱/冰柜温度监测、热水器水温控制、烤箱/微波炉温度校准;
• 生活辅助:智能花盆温度监测、恒温酒柜/雪茄柜温度监控、浴室防烫测温。
- 农业与养殖领域
• 种植场景:温室大棚多点测温、育苗室温度监控、土壤温度采集;
• 养殖场景:水产养殖池水温监测、畜禽养殖舍温度监测、饲料仓储温度监测。
- 医疗健康领域
• 便携式测温:电子体温计、额温枪/耳温枪内部温度校准;
• 设备温控:疫苗冷藏箱/冷藏车温度监测、医用恒温箱、医疗仪器散热监测。
- 交通运输与汽车电子领域
• 汽车电子:发动机水温监测、座舱温度采集、动力电池包测温、轮胎温度监测;
• 轨道交通:列车车厢温度监测、制动系统温度监控。
🌟传感器的介绍
DS18B20是一款基于单总线(1-Wire)的数字式温度传感器,采用半导体集成工艺,内部集成了温度敏感元件、A/D转换器、ROM存储器、暂存器等核心模块,无需外部AD转换电路即可直接输出数字温度信号,具有"精准度高、功耗低、布线简单、支持组网"等核心优势
- 核心技术特性
• 测温范围宽:-55℃ ~ +125℃,覆盖绝大多数民用、工业场景;其中在-10℃ ~ +85℃的核心区间,测温精度可达±0.5℃,满足高精度测温需求,在-55℃ ~ -10℃和+85℃ ~ +125℃区间,精度可达±1℃
• 通信方式极简:采用单总线通信协议,仅需1根数据线即可实现与主控的双向通信,无需I2C、SPI等多线通信接口,大幅简化布线难度,尤其适合多点组网场景;
• 无需外部元件:正常工作时仅需接入电源、GND和DQ线,无需额外的AD转换模块、放大电路;硬件成本极低;
• 支持多点组网:每个DS18B20都有唯一的64位ROM地址,多个传感器可共用1根DQ线,通过ROM地址区分不同节点,最多可支持数百个传感器组网,适合大面积分布式测温;
• 低功耗设计:工作电流典型值为1mA,待机电流仅为1μA,可适配电池供电的便携式设备,延长续航时间;
• 抗干扰能力强:采用数字信号传输,相较于模拟温度传感器,可有效避免传输过程中的信号衰减、电磁干扰,适合长距离传输;
• 内置保护机制:内部集成CRC校验模块,可对读取的温度数据进行校验,避免数据错误;同时具备过温保护逻辑,可通过指令配置过温报警阈值。
- 内部工作原理
DS18B20的核心工作流程的是"温度采集→A/D转换→数字信号输出",内部结构主要包括4个核心模块:
• 温度敏感元件:采用半导体PN结测温原理,PN结的正向压降会随温度变化而线性变化,通过采集该压降信号实现温度感知;
• A/D转换器:将温度敏感元件输出的模拟信号转换为16位数字信号(对应温度值),无需外部AD模块,转换精度由分辨率配置决定;
• ROM存储器:存储唯一的64位ROM地址,用于多点组网时的节点识别,主控可通过指令读取该地址,实现对特定传感器的操作;
• 暂存器:用于存储A/D转换后的温度数据(2字节)、分辨率配置参数(1字节)、过温报警阈值(2字节)、CRC校验码(1字节)等,主控可通过指令读取或写入暂存器数据。
🎯 单片机连接硬件图

• 实物图




🌟驱动思路
步骤1:单总线初始化
单总线初始化是主控与传感器建立通信的第一步,核心是"主控拉低总线→ 传感器回应→ 主控检测回应",时序要求严格:
• 主控(单片机)主动拉低DQ总线,持续时间≥480μs(最小480μs,建议500μs,确保传感器能检测到总线拉低);
• 主控释放总线(将DQ拉高),进入等待状态;
• DS18B20检测到总线拉低后,会在15~60μs内拉低DQ总线,作为回应信号;
• 主控检测DQ引脚电平,若检测到低电平(持续15~60μs),则初始化成功;若未检测到低电平,说明初始化失败;
传感器释放总线后,DQ会自动拉高,等待主控后续指令。
步骤2:单总线写位/写字节
• 单总线的写操作分为"写0"和"写1",每次写位耗时≥60μs,写字节需逐位发送(8位,低位在前):
• 写0时序:主控拉低DQ总线≥1μs(最小1μs)→ 保持低电平≥60μs(确保传感器读取到0)→ 释放总线(拉高DQ);
• 写1时序:主控拉低DQ总线≥1μs(最小1μs)→ 立即释放总线(拉高DQ)→ 保持高电平≥60μs(确保传感器读取到1);
• 写字节逻辑:将待发送的字节拆分为8位(低位在前),逐位调用写位函数,相邻两位的写操作间隔≥1μs。
步骤3:单总线读位/读字节
• 单总线的读操作需主控主动拉低总线触发,然后读取总线电平,每次读位耗时≥60μs,读字节需逐位读取(8位,低位在前):
• 读位时序:主控拉低DQ总线≥1μs(最小1μs)→ 立即释放总线(拉高DQ)→ 等待15μs(确保传感器数据稳定)→ 读取DQ引脚电平(低电平=0,高电平=1)→ 保持总线高电平至60μs结束;
• 读字节逻辑:逐位读取8位数据,将读取到的位依次拼接(低位在前),得到完整字节数据;相邻两位的读操作间隔≥1μs。
步骤4:读取温度的完整指令流程
• 执行单总线初始化,确认初始化成功;
• 发送「跳过ROM指令」(0xCC):无需读取传感器的64位ROM地址,直接对总线上的传感器进行操作;
• 发送「温度转换指令」(0x44):触发DS18B20开始采集温度并进行A/D转换,转换时间与分辨率相关,需等待转换完成;
• 再次执行单总线初始化(转换完成后需重新建立通信);
• 再次发送「跳过ROM指令」(0xCC);
• 发送「读取暂存器指令」(0xBE):指令发送后,可读取传感器暂存器的9字节数据(暂存器地址0~8);
• 读取9字节数据:重点关注第0字节(温度低字节)和第1字节(温度高字节),其余字节为分辨率配置、过温阈值、CRC校验码;
• 解析温度数据:将温度高字节和低字节拼接为16位数据,根据符号位(高字节最高位)处理正负温度,再除以16得到实际温度值。
🎯 单片机程序代码
main.c
c
#include "stm32f10x.h"
#include "string.h"
#include "stdio.h"
#include "delay.h"
#include "bsp_usart.h"
#include "oled.h"
#include "A7680C.h"
#include "bsp_ds18b20.h"
float temp=0.0;
char buff[20];
int cnt=0;
int main(void)
{
NVIC_PriorityGroupConfig (NVIC_PriorityGroup_2);
SysTick_Init(72); //系统时钟初始化
usart1_init(115200);//串口1初始化
printf("USART1 OK!\r\n");
usart2_init(115200);//串口1初始化
usart3_init(115200);//串口3初始化
OLED_Init();
A7680C_Init();
DS18B20_Init();
OLED_Clear();
while(1)
{
cnt++;
if(cnt>=200)
{
cnt=0;
temp = DS18B20_GetTemp_SkipRom();
printf("温度值:%.2f℃\r\n", temp); // 打印温度,保留2位小数
sprintf(buff,"temp:%.2f", temp);
OLED_ShowString(0,2,buff);
A7680C_SendMessage(buff);
}
delay_ms(10); // 1秒读取一次
}
}
ds18b20.c
c
#include "bsp_ds18b20.h"
#include "delay.h"
/* 可以在下面的宏定义中把后面的延时函数替换换SysTick的延时函数,就是想用那个就换成那个的 */
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 );
/**
* @brief DS18B20 初始化函数
* @param 无
* @retval 无
*/
uint8_t DS18B20_Init(void)
{
DS18B20_GPIO_Config ();
DS18B20_DQ_1;
DS18B20_Rst();
return DS18B20_Presence ();
}
/*
* 函数名:DS18B20_GPIO_Config
* 描述 :配置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_Mode_IPU
* 描述 :使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_Mode_Out_PP
* 描述 :使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();
//DHT11_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,低位先行
*/
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)
{
DS18B20_DQ_0;
/* 1us < 这个延时 < 15us */
delay_us(8);
DS18B20_DQ_1;
delay_us(58);
}
else
{
DS18B20_DQ_0;
/* 60us < Tx 0 < 120us */
delay_us(70);
DS18B20_DQ_1;
/* 1us < Trec(恢复时间) < 无穷大*/
delay_us(2);
}
}
}
/**
* @brief 跳过匹配 DS18B20 ROM
* @param 无
* @retval 无
*/
static void DS18B20_SkipRom ( void )
{
DS18B20_Rst();
DS18B20_Presence();
DS18B20_WriteByte(0XCC); /* 跳过 ROM */
}
/**
* @brief 执行匹配 DS18B20 ROM
* @param 无
* @retval 无
*/
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
*/
/**
* @brief 在跳过匹配 ROM 情况下获取 DS18B20 温度值
* @param 无
* @retval 温度值
*/
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); /* 读温度值 */
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;
}
/**
* @brief 在匹配 ROM 情况下获取 DS18B20 温度值
* @param ds18b20_id:用于存放 DS18B20 序列号的数组的首地址
* @retval 无
*/
void DS18B20_ReadId ( uint8_t * ds18b20_id )
{
uint8_t uc;
DS18B20_WriteByte(0x33); //读取序列号
for ( uc = 0; uc < 8; uc ++ )
ds18b20_id [ uc ] = DS18B20_ReadByte();
}
/**
* @brief 在匹配 ROM 情况下获取 DS18B20 温度值
* @param ds18b20_id:存放 DS18B20 序列号的数组的首地址
* @retval 温度值
*/
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;
}
/*************************************END OF FILE******************************/
A7680C.c
cpp
#include "A7680C.h"
#include "string.h"
#include "bsp_usart.h"
#include "oled.h"
#include "delay.h"
char *strx,*extstrx;
/*****************************************************
清空模块反馈的信息
*****************************************************/
void Clear_Buffer(void)//清空缓存
{
printf("%s\r\n",buf_uart2.buf);
buf_uart2.index=0;
memset(buf_uart2.buf,0,BUFLEN);
}
/*****************************************************
初始化模块 和单片机连接,获取卡号和信号质量
*****************************************************/
void A7680C_Init(void)
{
//打印初始化信息
printf("start init A7680C\r\n");
//发第一个命令ATE1
USART2_SendStr("ATE1\r\n");
delay_ms(300);
printf("%s\r\n",buf_uart2.buf); //打印串口收到的信息
strx=strstr((const char*)buf_uart2.buf,(const char*)"OK");//返回OK
Clear_Buffer();
while(strx==NULL)
{
printf("单片机正在连接模块......\r\n");
Clear_Buffer();
USART2_SendStr("ATE1\r\n");
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"OK");//返回OK
}
printf("****单片机和模块连接成功*****\r\n");
OLED_ShowString(0,2,(char *)" ");
OLED_ShowString(0,2,(char *)"Connet 4G [OK]");
USART2_SendStr("ATI\r\n");//获取模块的版本
delay_ms(300);
Clear_Buffer();
USART2_SendStr("AT+CICCID\r\n");//获取卡的序列号
delay_ms(300);
Clear_Buffer();
USART2_SendStr("AT+CGSN\r\n");//获取模块的imei
delay_ms(300);
Clear_Buffer();
USART2_SendStr("AT+CIMI\r\n");//获取卡号,类似是否存在卡的意思,比较重要。
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"460");//返460,表明识别到卡了
while(strx==NULL)
{
Clear_Buffer();
USART2_SendStr("AT+CIMI\r\n");//获取卡号,类似是否存在卡的意思,比较重要。
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"460");//返回OK,说明卡是存在的
}
printf("我的卡号是 : %s \r\n",buf_uart2.buf+8);
Clear_Buffer();
OLED_ShowString(0,2,(char *)" ");
OLED_ShowString(0,2,(char *)"SIMCARD [OK]");
USART2_SendStr("AT+CGATT?\r\n");//查询激活状态
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"+CGATT: 1");//返1
Clear_Buffer();
while(strx==NULL)
{
Clear_Buffer();
USART2_SendStr("AT+CGATT?\r\n");//获取激活状态
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"+CGATT: 1");//返回1,表明注网成功
}
OLED_ShowString(0,2,(char *)" ");
OLED_ShowString(0,2,(char *)"REGISTER[OK]");
Clear_Buffer();
USART2_SendStr("AT+CSQ\r\n");//查看获取CSQ值
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"+CSQ:");//返回CSQ
if(strx)
{
printf("信号质量是:%s 注意:信号最大值是31 \r\n",buf_uart2.buf+14);
}
Clear_Buffer();
OLED_ShowString(0,2,(char *)" ");
OLED_ShowString(0,2,(char *)" SINGAL [OK]");
/*****************************************************/
USART2_SendStr("AT+CMGF=1\r\n");//将短信模式设置为 text mode
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"OK");//返OK
Clear_Buffer();
while(strx==NULL)
{
Clear_Buffer();
USART2_SendStr("AT+CMGF=1\r\n");//将短信模式设置为 text mode
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"OK");//返回OK,表明设置成功
}
Clear_Buffer();
}
char ATSTR[128];
void A7680C_SendMessage(char *Data)
{
Clear_Buffer();
sprintf(ATSTR,"AT+CMGS=\"%s\"\r\n",PhoneNumber);
printf("电话号码: %s \r\n",PhoneNumber);
USART2_SendStr(ATSTR);//发送短信
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)">");
printf("收到的信息: %s \r\n",buf_uart2.buf);
Clear_Buffer();
while(strx==NULL)
{
Clear_Buffer();
USART2_SendStr(ATSTR);//发送短信
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)">");//返回OK,表明设置成功
printf("收到的信息: %s \r\n",buf_uart2.buf);
}
Clear_Buffer();
memset(ATSTR,0,BUFLEN);
USART2_SendStr(Data);
delay_ms(300);
USART2_Send_byte(0x1A);
strx=strstr((const char*)buf_uart2.buf,(const char*)"OK");
Clear_Buffer();
while(strx==NULL)
{
Clear_Buffer();
USART2_SendStr(ATSTR);//发送短信
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"OK");//返回OK,表明设置成功
printf("收到的信息: %s \r\n",buf_uart2.buf);
}
}
🌟代码下载链接