1.流程图
2.PT100
1)信号采集的基本原理
① PT100的阻值会随温度的变化而成正比变化(温度越高阻值越大),但阻值变化很小,约等于0.385 Ω / 度;
② PT100的测温范围是﹣ 200 ℃ ﹣200℃,且在0℃时,阻值刚好等于100 Ω ;
③ PT100的工作电流要小于5 m A ;
④ PT100的阻值虽然随温度的变化而成正比变化,但在不同温度区间内其变化的速率(也就是K 值 K值K值)不一样。
2)PT100温度阻止变化表
3.PT100驱动电路
1)通过分压法,AD采集PT100电压从而得到阻值求出温度
常温(25 ℃ 25℃25℃)水中的PT100的阻值大概在109.89 Ω左右。
单片机输出3.3v电压,PT100分掉电压约为:
109.89 ∗ 0.005 = 0.54945 V
将其根据AD转换的换算公式换算成AD值大概为:
0.54945 / 3.3 ∗ 4096 = 681.98 ≈ 682
当温度上升一度,假设PT100的阻值刚好上升了0.385 Ω,那么其分掉的电压的变动值约等于:
0.385 ∗ 0.005 = 0.001925 V
将其根据AD转换的换算公式换算成AD值大概为:
0.001925 / 3.3 ∗ 4096 = 2.39 ≈ 2
实验中发现,由于stm32供电3.3v电压不稳定,ADC采集PT100电压波动较大,分压误差较大,优化方案成设计一款恒流源电路,通过采集PT100的电压以及恒流源的电流可以得出PT100的阻值,从而得出温度值。
2)基于LDO稳压器(MD5333)的恒流源电路
网上有很多测试PT100的驱动电路,如直流电桥电路,基于运算放大器的恒流源电路等等,笔者在选择驱动电路时也花费了许多时间,综合考虑打板的难度元器件数量等因素,最后选择了基于LDO稳压器(MD5333)的恒流源电路,其电路图如下所示:
至此硬件部分基本选择完毕,开发板方面用的是正点原子F10ZET6精英板
4.DS18B20模块
为了测试实时温度与PT100温度对比,加入了DS18B20模块进行校准对比测试
1)DS18B20简介
DS18B20是一款单总线的温度传感器,测试温度范围为-55~+125℃,精度为±0.5℃。现场温度直
接以单总线的数字方式传输,大大提高了系统的抗干扰性。它能直接读出被测温度,并且可根
据实际要求通过简单的编程实现 9~12 位的数字值读数方式。它的工作电压范围为 3~5.5V,采
用多种封装形式,从而使系统设置灵活、方便,设定分辨率以及用户设定的报警温度存储在
EEPROM 中,掉电后依然保存。
2)DS18B20工作时序简介
所有单总线器件要求采用严格的信号时序,以保证数据的完整性。DS18B20 共有 6 种信号 类型:复位脉冲、应答脉冲、写 0、写 1、读 0 和读 1。所有这些信号,除了应答脉冲以外,都 是由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前。
① 复位脉冲和应答脉冲
单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平时间至少要在 480us,以产生复位脉冲。接着主机释放总线,4.7K 的上拉电阻将单总线拉高,延时时间要在 15~60us,并进入接收模式(Rx)。接着 DS18B20 拉低总线 60~240us,以产生低电平应答脉冲。
② 写时序
写时序包括写 0 时序和写 1 时序。所有写时序至少需要 60us,且在两次独立的写时序之间
至少需要 1us 的恢复时间,两种写时序均起始于主机拉低总线。写 1 时序:主机输出低电平,
延时 2us,然后释放总线,延时 60us。写 0 时序:主机输出低电平,延时 60us,然后释放总线
延时 2us。
③ 读时序
单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,
必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要 60us,且在 2 次独立的读
时序之间至少需要 1us 的恢复时间。每个读时序都由主机发起,至少拉低总线 1us。主机在读时
序期间必须释放总线,并且在时序起始后的 15us 之内采样总线状态。典型的读时序过程为:主
机输出低电平延时 2us,然后主机转入输入模式延时 12us,然后读取单总线当前的电平,然后
延时 50us。
在了解单总线时序之后,我们来看一下 DS18B20 的典型温度读取过程,DS18B20 的典型
温度读取过程为:复位→发 SKIPROM(0xCC)→发开始转换命令(0x44)→延时→复位→发
送 SKIPROM 命令(0xCC)→发送存储器命令(0xBE)→连续读取两个字节数据(即温度)→
结束。
3)原理图及CUBEMAX配置
由原理图可知,DS18B20是由PG11口提供使能
打开串口打印温度信息
4)代码部分
代码部分移植正点原子的ds18b20库,稍作修改
#ifndef __DS18B20_H
#define __DS18B20_H
#include "tim.h"
/******************************************************************************************/
/* DS18B20引脚 定义 */
#define DS18B20_DQ_GPIO_PORT GPIOG
#define DS18B20_DQ_GPIO_PIN GPIO_PIN_11
#define DS18B20_DQ_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOG_CLK_ENABLE(); }while(0) /* PG口时钟使能 */
/******************************************************************************************/
/* IO操作函数 */
#define DS18B20_DQ_OUT(x) do{ x ? \
HAL_GPIO_WritePin(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* 数据端口输出 */
#define DS18B20_DQ_IN HAL_GPIO_ReadPin(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN) /* 数据端口输入 */
uint8_t ds18b20_init(void); /* 初始化DS18B20 */
uint8_t ds18b20_check(void); /* 检测是否存在DS18B20 */
short ds18b20_get_temperature(void);/* 获取温度 */
#endif
#include "tim.h"
#include "ds18b20.h"
#include "usart.h"
/**
* @brief 复位DS18B20
* @param data: 要写入的数据
* @retval 无
*/
static void ds18b20_reset(void)
{
DS18B20_DQ_OUT(0); /* 拉低DQ,复位 */
delay_us(750); /* 拉低750us */
DS18B20_DQ_OUT(1); /* DQ=1, 释放复位 */
delay_us(15); /* 延迟15US */
}
/**
* @brief 等待DS18B20的回应
* @param 无
* @retval 0, DS18B20正常
* 1, DS18B20异常/不存在
*/
uint8_t ds18b20_check(void)
{
uint8_t retry = 0;
uint8_t rval = 0;
while (DS18B20_DQ_IN && retry < 200) /* 等待DQ变低, 等待200us */
{
retry++;
delay_us(1);
}
if (retry >= 200)
{
rval = 1;
}
else
{
retry = 0;
while (!DS18B20_DQ_IN && retry < 240) /* 等待DQ变高, 等待240us */
{
retry++;
delay_us(1);
}
if (retry >= 240) rval = 1;
}
return rval;
}
/**
* @brief 从DS18B20读取一个位
* @param 无
* @retval 读取到的位值: 0 / 1
*/
static uint8_t ds18b20_read_bit(void)
{
uint8_t data = 0;
DS18B20_DQ_OUT(0);
delay_us(2);
DS18B20_DQ_OUT(1);
delay_us(12);
if (DS18B20_DQ_IN)
{
data = 1;
}
delay_us(50);
return data;
}
/**
* @brief 从DS18B20读取一个字节
* @param 无
* @retval 读到的数据
*/
static uint8_t ds18b20_read_byte(void)
{
uint8_t i, b, data = 0;
for (i = 0; i < 8; i++)
{
b = ds18b20_read_bit(); /* DS18B20先输出低位数据 ,高位数据后输出 */
data |= b << i; /* 填充data的每一位 */
}
return data;
}
/**
* @brief 写一个字节到DS18B20
* @param data: 要写入的字节
* @retval 无
*/
static void ds18b20_write_byte(uint8_t data)
{
uint8_t j;
for (j = 1; j <= 8; j++)
{
if (data & 0x01)
{
DS18B20_DQ_OUT(0); /* Write 1 */
delay_us(2);
DS18B20_DQ_OUT(1);
delay_us(60);
}
else
{
DS18B20_DQ_OUT(0); /* Write 0 */
delay_us(60);
DS18B20_DQ_OUT(1);
delay_us(2);
}
data >>= 1; /* 右移,获取高一位数据 */
}
}
/**
* @brief 开始温度转换
* @param 无
* @retval 无
*/
static void ds18b20_start(void)
{
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc); /* skip rom */
ds18b20_write_byte(0x44); /* convert */
}
/**
* @brief 初始化DS18B20的IO口 DQ 同时检测DS18B20的存在
* @param 无
* @retval 0, 正常
* 1, 不存在/不正常
*/
uint8_t ds18b20_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
DS18B20_DQ_GPIO_CLK_ENABLE(); /* 开启DQ引脚时钟 */
gpio_init_struct.Pin = DS18B20_DQ_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 开漏输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(DS18B20_DQ_GPIO_PORT, &gpio_init_struct); /* 初始化DS18B20_DQ引脚 */
/* DS18B20_DQ引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */
ds18b20_reset();
return ds18b20_check();
}
/**
* @brief 从ds18b20得到温度值(精度:0.1C)
* @param 无
* @retval 温度值 (-550~1250)
* @note 返回的温度值放大了10倍.
* 实际使用的时候,要除以10才是实际温度.
*/
short ds18b20_get_temperature(void)
{
uint8_t flag = 1; /* 默认温度为正数 */
uint8_t TL, TH;
short temp;
ds18b20_start(); /* ds1820 start convert */
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc); /* skip rom */
ds18b20_write_byte(0xbe); /* convert */
TL = ds18b20_read_byte(); /* LSB */
TH = ds18b20_read_byte(); /* MSB */
if (TH > 7)
{/* 温度为负,查看DS18B20的温度表示法与计算机存储正负数据的原理一致:
正数补码为寄存器存储的数据自身,负数补码为寄存器存储值按位取反后+1
所以我们直接取它实际的负数部分,但负数的补码为取反后加一,但考虑到低位可能+1后有进位和代码冗余,
我们这里先暂时没有作+1的处理,这里需要留意 */
TH = ~TH;
TL = ~TL;
flag = 0; /* 温度为负 */
}
temp = TH; /* 获得高八位 */
temp <<= 8;
temp += TL; /* 获得底八位 */
/* 转换成实际温度 */
if (flag == 0)
{ /* 将温度转换成负温度,这里的+1参考前面的说明 */
temp = (double)(temp+1) * 0.625;
temp = -temp;
}
else
{
temp = (double)temp * 0.625;
}
return temp;
}
main.c
5) 效果展示
5.红外遥控模块
1)无线模块编码协议
红外遥控的编码方式目前广泛使用的是:PWM(脉冲宽度调制)的 NEC 协议和 Philips PPM
(脉冲位置调制)的 RC-5 协议的。开发板配套的遥控器使用的是 NEC 协议,其特征如下:
1、8 位地址和 8 位指令长度;
2、地址和命令 2 次传输(确保可靠性);
3、PWM 脉冲位置调制,以发射红外载波的占空比代表“0”和“1”;
4、载波频率为 38Khz;
5、位时间为 1.125ms 或 2.25ms;
在 NEC 协议中,如何为协议中的数据‘0’或者‘1’?这里分开红外接收器和红外发射
器。
红外发射器:发送协议数据‘0’ = 发射载波信号 560us + 不发射载波信号 560us
发送协议数据‘1’ = 发射载波信号 560us + 不发射载波信号 1680us
红外发射器的位定义如下图所示
红外接收器:接收到协议数据‘0’ = 560us 低电平 + 560us 高电平
接收到协议数据‘1’ = 560us 低电平 + 1680us 高电平
NEC 遥控指令的数据格式为:同步码头、地址码、地址反码、控制码、控制反码。同步
码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码
均是 8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性 。
因此可以利用输入捕获来测量高电平的脉宽,从而实现遥控的解码。
2) 原理图及CUBEMAX配置
由原理图可知无线模块通过PB9管脚使能并通过TIM4的4通道进行采集:
TIM4_CH4的默认管脚不是PB9要手动就行设置,同时打开中断设置
3)代码部分
通过tim回调函数捕获上升沿
此时就可以得到解码信号:
此时数据较为复杂,可以对数据稍加处理:
效果如下:
后两位就是解码及其反码,此时就可以定义成为宏加以使用来调整温度阈值:
效果如下:
红外部分代码:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2024 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
#define MAXUP 157
#define MAXDOWN 87
#define MINUP 221
#define MINDOWN 61
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint32_t upCount=0;
uint16_t ValueUp=0;
uint16_t ValueDown=0;
uint8_t isUpCapt=1;
uint16_t width=0;
uint16_t buffer[128]={0};
uint16_t bufferId=0;
uint8_t rcvFalg=0;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
upCount++;
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(isUpCapt)//如果是上升沿捕获
{
ValueUp=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_4);
isUpCapt=0;
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_4,TIM_ICPOLARITY_FALLING);
upCount=0;
}
else{
ValueDown=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_4);
isUpCapt=1;
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_4,TIM_ICPOLARITY_RISING);
width=ValueDown+upCount*65536-ValueUp;
if(width>4400&&width<4600)
{
bufferId=0;
buffer[bufferId++]=width;
}
else if(bufferId>0)
{
buffer[bufferId++]=width;
if(bufferId>32)
{
rcvFalg=1;
bufferId=0;
}
}
}
}
void bitBuffer2num(char num[])
{
num[0]=0;
num[1]=0;
num[2]=0;
num[3]=0;
for(int i=0;i<32;i++)
{
if(buffer[i+1]<1000)
{
num[i/8]=num[i/8]<<1;
}
else
{
num[i/8]=num[i/8]<<1;
num[i/8]|=0x01;
}
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
char printbuff[128]={0};
char num[4]={0};
char key=0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM4_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
HAL_TIM_Base_Start_IT(&htim4);//定时器更新产生中断
HAL_TIM_IC_Start_IT(&htim4,TIM_CHANNEL_4);//
while (1)
{
if(rcvFalg)
{
for(int i=0;i<4;i++)
{
bitBuffer2num(num);
sprintf(printbuff,"0x%02x ",num[i]);
HAL_UART_Transmit(&huart1,printbuff,strlen(printbuff),HAL_MAX_DELAY);
}
// sprintf(printbuff,"%u ",buffer[i]);
// HAL_UART_Transmit(&huart1,printbuff,strlen(printbuff),HAL_MAX_DELAY);
// }
HAL_UART_Transmit(&huart1,"\r\n",2,HAL_MAX_DELAY);
rcvFalg=0;
}
printf("%d\r\n",num[3]);
if(num[3]==157)
{
printf("111111\r\n");
}
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
6.报警模块
如果温度超过设定的阈值,led红灯闪烁,蜂鸣器报警
CUBEMAX和代码源码:
链接:百度网盘 请输入提取码
提取码:cwrq