常见的几种通信接口
一线式总线
定义
- 一线式:说明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 - 推挽输出, 50MHzGPIO_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-60usDS18B20_IO_OUT = 1; // 拉高 delay_us(30);// 3.DS18B20若在总线上, 拉低, 60-240us -> 存在脉冲 DS18B20_IN(); // 输入 // 判断DS18B20是否拉低, PG11 = 0// 1.tempTime >= 240// 2.DS18B20_IO_IN == 0while(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拉低, >1usDS18B20_IO_OUT = 0;delay_us(2);// 2.PG11拉高, 60us DS18B20_IO_OUT = 1;delay_us(60);}else{ // 发送1bit的0// 1.PG11拉低, 60usDS18B20_IO_OUT = 0;delay_us(60);// 2.PG11拉高, 2usDS18B20_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, 2usDS18B20_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命令 - 0X33DS18B20_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.休眠800msdelay_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(); // byte0temp_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值,再写入到寄存器中进行匹配,匹配后才能读取到正确的数据