这次做了个小东西,主要分为三个部分,一部分是正确接收DS18B20传感器的数据,另一部分是将接收到的数据显示到OLED上,最后一部分是使用RTOS来多任务执行,一个任务是从DS18B20传感器接收到数据后传到消息队列中,另一个任务是从消息队列中读取数据然后显示到OLED上。我们将分别讲述三个部分
代码链接,包含DS18B20的数据手册
提取码:bMGx
DS18B20传感器
cubemx配置

我们选择的是PA0引脚当作我们的数据线,所以我们先配置PA0
GPIO output level:配置为Low(表示默认输出低电平)
GPIO mode:Output Push Pull(推挽输出)
GPIO Pull-up/Pull-down:Pull-up(表示为上拉,其实在推挽输出选什么影响不大,这个选项仅在引脚处于输入态或开漏输出时起主要作用)
Maximum output speed:Low(这个选项配置最大输出速度,就是信号输出的频率)
时序配置图如下
因为DS18B20只使用一个数据线,所以我们还要自己写一个将PA0置为输入或输出的函数



RCC使用的是外部晶振时钟源

c
void DS18B20GPIO_Out(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
/*Configure GPIO pin : PA0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;//推挽输出
GPIO_InitStruct.Pull = GPIO_PULLUP;//上拉电阻
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;//低速
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
c
void DS18B20GPIO_Input(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
/*Configure GPIO pin : PA0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;//输入模式
GPIO_InitStruct.Pull = GPIO_PULLUP;//上拉电阻,在没有输入时为高电平
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;//低速
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
配置完GPIO口之后,我们就来看数据手册的事件序列

初始化

c
uint8_t DS18B20_Init(){
volatile uint8_t i=0;
DS18B20GPIO_Out();//切换为输出
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);//拉低总线
my_delay_us(550);//延迟至少480us
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);//释放总线,拉高
DS18B20GPIO_Input();//切换为输入模式
my_delay_us(30);//等待传感器拉低总线
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET&&i<240)//等待传感器拉低总线60-240us
{
i++;
my_delay_us(1);
}
if(i>=240) return 1;//如果一直是低电平代表没有应答成功,返回1表示连接失败
return 0;//返回0表示建立连接成功
}
ROM命令
当总线上的主设备检测到了存在脉冲后,就可以执行ROM命令。这些命令是对每个设备独一无二的64位ROM编码进行操作的,当总线上连接有多个设备时,可以通过这些命令识别各个设备。所以ROM命令查找到我们具体的设备,由于我们这里只有DS18B20,所以使用下面的命令

为了写我们的命令,也需要写一个函数遵循传感器的时序将命令写入传感器,这是传感器规定的写时段

c
void DS18B20_Write_Byte(uint8_t data)
{
uint8_t i,testb;
DS18B20GPIO_Out();//切换为输入模式
for(i=0;i<8;i++)
{
testb=data&0x01;//取出每一个八位里的每一位bit
data=data>>1;//数据右移准备下一次取bit,如果不想修改原始数据,可以多使用一个局部变量
if(testb)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);//拉低总线
my_delay_us(8);//在15us内释放总线
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);//释放总线
my_delay_us(58);//每个写时段必须要有60us的持续时间,58+8>60
}
else
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);//拉低总线
my_delay_us(70);//至少拉低60us
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);//释放总线
my_delay_us(2);//每个写时段至少有1us的恢复时间
}
}
}
DS18B20功能命令
当总线上的主设备通过ROM命令确定了哪个DS18B20能够进行通信时,主设备可以向其中一个DS18B20发送功能命令。这些命令使得主设备可以向DS18B20的暂存寄存器写入或者读出数据,初始化温度转换及定义供电模式。这里我们只列举读取温度的指令


在我们写入这个指令后,需要从传感器接数据,所以我们需要写关于读取的函数

c
uint8_t DS18B20_Read_Bit(void)
{
uint8_t data;
DS18B20GPIO_Out();//切换为输出模式操作总线
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);//拉低总线
my_delay_us(10);//至少拉低1us
DS18B20GPIO_Input();//切换为输入模式
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_SET)//判断输入的是1还是0
data=1;
else
data=0;
my_delay_us(55);//每个读时段最小必须有60us的持续时间且至少有1us的恢复时间,10+55>61
return data;
}
uint8_t DS18B20_Read_Byte(void)
{
uint8_t i,j,data=0;
for(i=0;i<8;i++)
{
j=DS18B20_Read_Bit();//每次接收一位bit
data=(data)|(j<<i);//将数据放入data中
}
return data;
}
有了这些单独的功能封装后,我们可以将其组装成一个函数,读取温度的函数,只需要调用就可以返回温度值,但是在此之前我们需要讲一下DS18B20传感器返回的数据是什么样的,精度是什么样的

我们看到上电后默认的精度就是12位精度,我们的程序并没有修改这个精度
在DS18B20中,转换的温度值以16位二进制补码形式存放在暂存寄存器的第0字节和第1字节,其中低字节存储温度的小数部分和整数低八位,高字节存储整数高八位和符号位,所以我们的程序中分别读取了这两个字节,最后拼接出16位温度数据
c
void DS18B20_Start(void){
DS18B20_Init();//初始化
DS18B20_Write_Byte(0XCC);//跳过ROM
DS18B20_Write_Byte(0X44);//温度转换
}
float DS18B20_Get_Temp(void)
{
uint8_t tpmsb,tplsb;//tpmsb表示高八位字节,tplsb表示低八位字节
volatile short s_tem;
volatile float f_tem;
DS18B20_Init();
DS18B20_Write_Byte(0XCC);//跳过ROM命令
DS18B20_Write_Byte(0X44);//传感器转换温度
DS18B20_Init();//重新初始化
DS18B20_Write_Byte(0XCC);//跳过ROM命令
DS18B20_Write_Byte(0XBE);//读取温度数据
tplsb=DS18B20_Read_Byte();//读取低八位
tpmsb=DS18B20_Read_Byte();//读取高八位
s_tem=tpmsb<<8;
s_tem=s_tem|tplsb;//将高八位和低八位拼成16位数据
if(s_tem<0)
f_tem=-(~s_tem + 1)*0.0625;
else
f_tem=(s_tem*0.0625);//上电后默认温度为12位转换精度,0.0625分辨率
return f_tem;//返回处理好的温度
}
OLED显示
这个部分我是直接移植了江协科技的OLED代码,不多讲解
多任务执行
要想让我们多任务执行,就要首先配置RTOS,在这里我们创建了两个任务(实际代码中我创建了三个,有一个加了个超过阈值LED亮灯的功能)

这里我们添加任务时不需要修改,使用默认的就行了

创建队列数据类型我使用了float类型,因为我返回数据就是float类型的,然后生成代码后我们打开项目里的freertos.c文件,然后在我们创建的任务函数里写上我们的功能
c
void StartTask02(void *argument) //OLED显示任务
{
/* USER CODE BEGIN StartTask02 */
float data;
for(;;)
{
taskENTER_CRITICAL();//进入临界区
OLED_ShowString(0, 0, "temperature", OLED_8X16);
OLED_Update();
osMessageQueueGet(myQueue01Handle,&data,0,0);//从队列中读取数据
OLED_ShowNum(0, 28, data, 5, OLED_8X16);//显示数据
OLED_Update();
taskEXIT_CRITICAL();//退出临界区
osDelay(1);
}
/* USER CODE END StartTask02 */
}
c
void StartTask03(void *argument) //读取温度任务
{
/* USER CODE BEGIN StartTask03 */
osThreadId_t self_task=osThreadGetId();//获得任务句柄
float temp;
/* Infinite loop */
for(;;)
{
osThreadSetPriority(self_task,osPriorityHigh);//设置高优先级
taskENTER_CRITICAL();//进入临界区
temp=DS18B20_Get_Temp();//读取温度
osMessageQueuePut(myQueue01Handle,&temp,0,0);//将数据放置到队列中
osThreadSetPriority(self_task,osPriorityLow);//恢复默认优先级
taskEXIT_CRITICAL();//退出临界区
osDelay(100);
}
/* USER CODE END StartTask03 */
}