常见的几种通信接口
一线式总线
定义
- 一线式:说明CPU和外设之间数据通信只需一根信号线,此信号线必然是数据线,并且数据线连接了一个上拉电阻,默认为高电平
- 串行:说明CPU和外设的数据通信一个时钟周期传输一个bit位
问:没有时钟控制信号线,哪来的时钟呢,怎么去传输1个bit位呢?不像I2C总线的"低放高取",
因为它有时钟控制信号线
答:看协议
- 总线:说明这根数据线上可以连接挂接多个外设
硬件设计
DS18B20硬件特性
DS18B20芯片内部集成了64bit容量的ROM(只读存储器),存储每一个DS18B20唯一的序列码(类似身份证号)所以:CPU将来要想访问某个DS18B20,只需通过ROM中的序列码即可访问,也就是CPU只需向总线发送对应的DS18B20的序列码即可访问某个外设
ROM用来区分不同的设备
根据芯片手册P10,访问DS18B20遵循以下三步骤:
- 第一步CPU向总线发送初始化复位信号,类似I2C的START信号,UART的起始位
- 第二步CPU向总线发送ROM命令,为了找到要访问的外设,类似I2C总线发送设备地址
- 第三步CPU向总线发送功能命令(一旦找到外设以后,下面就是读还是写还是其他功能)
代码部分
初始化
// 函数定义
void DS18B20_Init(void){
// 1.打开GPIOG控制器时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE);
// 2.配置PG11 - 推挽输出, 50MHz
GPIO_InitTypeDef GPIO_Config;
GPIO_Config.GPIO_Pin = DS18B20_PIN;
GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(DS18B20_PORT, &GPIO_Config);
}
// 配置PG11 - 推挽输出, 50MHz
void DS18B20_OUT(void){
GPIO_InitTypeDef GPIO_Config;
GPIO_Config.GPIO_Pin = DS18B20_PIN;
GPIO_Config.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Config.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(DS18B20_PORT, &GPIO_Config);
}
// 配置PG11 - 上拉输入
void DS18B20_IN(void){
GPIO_InitTypeDef GPIO_Config;
GPIO_Config.GPIO_Pin = DS18B20_PIN;
GPIO_Config.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_Init(DS18B20_PORT, &GPIO_Config);
}
CPU向总线发送初始化复位信号

void DS18B20_Reset(void){
u8 tempTime = 0;
// 1.CPU将PG11拉低, >=480us -> 复位脉冲
DS18B20_OUT();
DS18B20_IO_OUT = 0; // 拉低
delay_us(500);
// 2.CPU将PG11拉高, 15-60us
DS18B20_IO_OUT = 1; // 拉高
delay_us(30);
// 3.DS18B20若在总线上, 拉低, 60-240us -> 存在脉冲
DS18B20_IN(); // 输入
// 判断DS18B20是否拉低, PG11 = 0
// 1.tempTime >= 240
// 2.DS18B20_IO_IN == 0
while(DS18B20_IO_IN && tempTime < 240){
tempTime++;
delay_us(1);
}
if(tempTime >= 240){
printf("RESET FAILED\n");
}else{
printf("RESET SUCCESS\n");
tempTime = 0;
}
}
CPU向总线写数据
// 从低位开始发送 ,
// 参数 : data 要发送的数据, 命令(0XCC,0XBE,...)
void DS18B20_Write_Byte(u8 data){
u8 i;
DS18B20_OUT();
for(i = 0; i < 8; i++){
if(data & 0x01){ // 发送1bit的1
// 1.PG11拉低, >1us
DS18B20_IO_OUT = 0;
delay_us(2);
// 2.PG11拉高, 60us
DS18B20_IO_OUT = 1;
delay_us(60);
}else{ // 发送1bit的0
// 1.PG11拉低, 60us
DS18B20_IO_OUT = 0;
delay_us(60);
// 2.PG11拉高, 2us
DS18B20_IO_OUT = 1;
delay_us(2);
}
data >>= 1;
}
}
CPU从总线读数据
// 功能 : 读取1字节数据
// 读取bit, 循环读8次, 读取数据从低位读
u8 DS18B20_Read_Byte(void){
u8 i = 0, data = 0;// 暂存读取到的数据
for(i = 0; i < 8; i++){
// 1.CPU拉低PG11, 2us
DS18B20_OUT();
DS18B20_IO_OUT = 0;
delay_us(2);
// 2.配置为输入, 释放总线权限
// 外设发0, 主动将PG11拉低
// 外设发1, 外设什么不做, 上拉电阻拉高, 高电平
DS18B20_IN();
// 延时等待, 等待高电平/低电平
delay_us(8);
// ----> 现在检测PG11高低电平, 高电平/低电平
data |= DS18B20_IO_IN << i;
delay_us(50);
}
return data;
}
读取ROM函数
// 定义读取ROM的函数(64bit - 8byte)
// 当做测试函数用(命令)
void DS18B20_ReadRom(void){
// 1.初始化
DS18B20_Reset();
// 2.发送读ROM命令 - 0X33
DS18B20_Write_Byte(0X33);
// 3.循环读取8字节数据
u8 i;
for(i = 0; i < 8; i++){
rom[i] = DS18B20_Read_Byte();
printf("%#x ", rom[i]);
}
printf("\n");
}
匹配ROM函数
// 定义匹配ROM的函数
void DS18B20_MatchRom(void){
// 1.发送MATCH ROM (0X55)
DS18B20_Write_Byte(0X55);
// 2.发送ROM值
u8 i;
for(i = 0; i < 8; i++)
DS18B20_Write_Byte(rom[i]);
}
获取温度值
这里的难点是解决温度值的转换
第15:11位是表示正负的,(temp & 0xf800) == 0xf800满足这个条件就说明是负数
可以通过这个方法实现转换:取反加一, 转十添负
第0位是1/16就是0.0625,再乘以多少个就得到温度值了
//获取温度
// 初始化 - MATCH ROM - CONVERT T - 延时
// 初始化 - MATCH ROM - READ - 读两个字节
float DS18B20_GetTemperature(void){
u8 temp_lsb = 0, temp_msb = 0;
u16 temp = 0;
float value; // 暂存转换后的温度值
//1.初始化
DS18B20_Reset();
//2.CPU发送MATCH ROM(0X55)
DS18B20_MatchRom();
//3.CPU发CONVERT T(0X44)
DS18B20_Write_Byte(0X44);
//4.休眠800ms
delay_ms(800);
///5.初始化
DS18B20_Reset();
//6.CPU发送MATCH ROM(0X55)
DS18B20_MatchRom();
//7.CPU发READ SCRATCHPAD(0XBE)
DS18B20_Write_Byte(0XBE);
//8.读数据(读2次即可)
temp_lsb = DS18B20_Read_Byte(); // byte0
temp_msb = DS18B20_Read_Byte(); // byte1
// 低位和高位数据的合并, temp读取到的温度值
temp = temp_msb << 8 | temp_lsb;
// 判断零上/零下
if((temp & 0xf800) == 0xf800){ // 负数/零下
temp = (~temp) + 1; // 获取temp的绝对值
value = temp * (-0.0625);
}else{// 零上/正数
value = temp * 0.0625;
}
return value;
}
测试代码
void DS18B20_Test(void){
float temp = DS18B20_GetTemperature();
printf("CURRENT TEMPERATURE : %.3f\n", temp);
}
将温度传感器初始化和测试代码加入到初始化函数和cmd函数中
init.c中
static PINIT_T init_func[] = {
LED_Init, // led灯初始化
BEEP_Init, // beep初始化
Systick_init, // 滴答定时器初始化
KEY_Init, // 按键初始化
My_EXTI_Init, // 中断初始化
UART_Init, // 串口初始化
AT24C02_Init, // AT24C02初始化
DS18B20_Init, // 温度传感器的初始化
0
};
cmd.c中
cmd_t cmd[] = {
{"led on", LED_On},
{"led off", LED_Off},
{"beep on", BEEP_On},
{"beep off", BEEP_Off},
{"EEPROM R", AT24C02_ReadOne}, // 读取单字节
{"EEPROM W", AT24C02_WriteOne}, //写入单字节
{"EEPROM RS", AT24C02_ReadMul}, // 读取多字节
{"EEPROM WS", AT24C02_WriteMul}, // 写入多字节
{"temp", DS18B20_Test}, // 获取温度命令
{"rom", DS18B20_ReadRom}, // 读取ROM值命令
};
结果展示
每个DS8B20的ROM都是不一样的,执行前需要读取下自己手中的DS8B20的ROM值,再写入到寄存器中进行匹配,匹配后才能读取到正确的数据