当前介绍的项目是基于 STM32F103ZET6 系列 MCU设计的数显热水器,通过显示屏来显示热水器的温度及其工作状态,通过 PT100 传感器来检测热水器的温度变化,并通过电加热片实现加热过程,以达到控制热水器温度的目的。
二、设计流程2.1 硬件选型STM32F103ZET6 系列 MCUOLED显示屏PT100 温度传感器电加热片继电器2.2 软件设计(1)显示屏
(资料图片仅供参考)
使用 OLED 显示屏来显示热水器的温度及其工作状态,通过 SPI接口与 STM32 芯片进行通讯。设计温度值及其单位、热水器工作状态等。
(2)温度传感器
使用 PT100 温度传感器来检测热水器内部温度的变化,并将数据通过 ADC转换后,传输给 STM32 芯片,以实现对热水器加热过程的控制。
(3)电加热片
使用电加热片模拟热水器加热过程,通过继电器控制电加热片的通断,以调节热水器的温度。
(4)控制系统
通过 STM32 芯片来实现对热水器的控制,读取温度传感器的数据。
三、代码设计3.1 OLED显示屏(1)SPI 接口初始化需要对 STM32F103ZET6 的 SPI 接口进行初始化配置,设置相关的时钟和模式,使其能够与 OLED 显示屏进行通讯。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE); // 打开SPI3时钟 SPI_InitTypeDef spi_init_type; spi_init_type.SPI_Direction = SPI_Direction_2Lines_FullDuplex; spi_init_type.SPI_Mode = SPI_Mode_Master; spi_init_type.SPI_DataSize = SPI_DataSize_8b; spi_init_type.SPI_CPOL = SPI_CPOL_Low; spi_init_type.SPI_CPHA = SPI_CPHA_1Edge; spi_init_type.SPI_NSS = SPI_NSS_Soft; spi_init_type.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; // 设置 SPI 时钟频率为 72 MHz / 32 = 2.25MHz spi_init_type.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI3, &spi_init_type); SPI_Cmd(SPI3, ENABLE);
(2)OLED 显示屏初始化以下是 OLED 显示屏的初始化代码:
void OLED_Init(void) { GPIO_SetBits(GPIOB, GPIO_Pin_6); //RST SET GPIO_ResetBits(GPIOB, GPIO_Pin_6); //RST RESET GPIO_SetBits(GPIOB, GPIO_Pin_6); //RST SET write_command(0xAE); // 关闭显示 write_command(0xD5); // 设置时钟分频因子,震荡频率 write_command(0x80); // 分频因子=1 ,震荡频率(fosc)=8MHz write_command(0xA8); // 设置驱动路数:MUX(复用方式) write_command(0x1F); // 1/32 duty (0x0F~0x3F) write_command(0xD3); // 设置显示偏移 write_command(0x00); // 不偏移 write_command(0x40); // 设置显示开始行[5:0], 对于设置了32行的液晶, // 这里的值为0表示从0行开始显示 write_command(0x8D); // 对比度设置 write_command(0x14); // AHB参考电压256等分 移位[3:0]100[n,1/256] write_command(0x20); // 水平方向上的寻址模式 write_command(0x00); // 垂直方向上的寻址模式 write_command(0xA1); // 设置段再映射 write_command(0xC0); // 设置COM扫描方向 write_command(0xDA); // 设置COM引脚硬件配置 write_command(0x12); write_command(0x81); // 对比度设置 write_command(0xBF); // 设置电荷泵电压 write_command(0xD9); // 设置预充电周期 write_command(0xF1); write_command(0xDB); // 设置VCOMH电压倍率 write_command(0x40); write_command(0xAF); // 打开显示 OLED_Clear(); // 清屏 }
(3)OLED 显示函数接下来编写 OLED 显示函数,实现字符和数字的显示功能。
void OLED_show_string(uint8_t x, uint8_t y, char *str) { uint8_t i = 0; while (str[i] != "") { OLED_show_char(x, y + i * 8, str[i]); ++i; } } void OLED_show_char(uint8_t x, uint8_t y, char ch) { uint8_t c = ch - 32; if (c >= 96) return; uint8_t* buffer = (uint8_t*)oled_buffer; uint8_t cx, cy; for(cy = 0; cy < 8; cy++) { uint8_t line = font[c][cy]; for (cx = 0; cx < 6; cx++) { if (line & 0x1) { buffer[(y + cy) * OLEDWIDTH + x + cx] = 1; } else { buffer[(y + cy) * OLEDWIDTH + x + cx] = 0; } line > >= 1; } } OLED_Draw_Pixel(x + 6, y, 0); OLED_Draw_Pixel(x + 6, y + 1, 0); OLED_Draw_Pixel(x + 6, y + 6, 0); OLED_Draw_Pixel(x + 6, y + 7, 0); }
(4)结果显示在代码中调用 OLED_show_string 函数和 OLED_show_char 函数显示数值和字符。
OLED_Init(); OLED_Clear(); OLED_show_string(0, 0, "HELLO WORLD!"); OLED_show_string(0, 16, "TEMP:20 C");
3.2 测温代码(1)引脚配置需要对 STM32F103ZET6 的 IO 口进行配置,将用于连接 PT100 温度传感器的引脚设置为输入模式。
这里以 PA0 引脚作为 PT100 传感器的连接口(即 PT100 三线连接中的 R3 端),代码如下:
GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入模式 GPIO_Init(GPIOA, &GPIO_InitStructure);
(2)ADC 配置接下来需要对 STM32F103ZET6 的 ADC 进行初始化配置,使其能够读取 PT100 温度传感器输出的电压信号。
这里以 ADC1 通道5 作为读取口,代码如下:
ADC_InitTypeDef ADC_InitStructure; RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 设置 ADC 时钟为 PCLK2 的 1/6 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 打开 ADC1 时钟 ADC_DeInit(ADC1); // 初始化 ADC1 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换模式 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_Cmd(ADC1, ENABLE); // 开启 ADC1
(3)温度转换函数根据 PT100 温度传感器输出电压与温度的关系,可使用线性函数计算出温度值。
转换公式如下:
Rt = (Vref - Vpt) / Ipt // Rt 为 PT100 的阻值,Vref 为基准电压,Vpt 为 PT100 输出电压,Ipt 为 PT100 驱动电流 Temp = a * Rt + b // Temp 为温度值,a 和 b 为经过拟合后的系数
其中 Rt 的计算需要使用差分运算放大器进行转换,这里不再赘述。假设已经得到 Rt 值,则温度转换函数代码如下:
float PT100_Get_Temperature(float Rt) { float a = 3.9083e-3f, b = -5.775e-7f, R0 = 100.0f; // 根据实际数据进行拟合得到 a、b 和 R0 的值 float Tem, delta; delta = pow(Rt / R0, 2) + a * (Rt / R0) + b; Tem = (delta > 0) ? (-R0*a + sqrt(delta)) / (2 * b) : 0; return Tem; }
(4)数据采集根据差分放大器输出的电压值得到 PT100 温度传感器的阻值,再根据阻值计算出实际温度,最后将温度值通过串口打印出来。以下是数据采集代码:
float ADC_Get_Voltage(void){ float voltage = 0; uint16_t adc_val = 0; ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1, ADC_SampleTime_239Cycles5); // 配置 ADC 通道5 ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能软件触发 ADC 转换 while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待转换结束 adc_val = ADC_GetConversionValue(ADC1); // 读取 ADC 转换结果 voltage = (float)adc_val * 3.3f / 4096; // 计算基准电压 return voltage;}float PT100_Get_Rt(float Vpt){ float Rsource = 10e3f, Rpt = 100.0f; // Rsource 为差分放大器输出电阻,Rpt 为 PT100 阻值 float Ipt = (3.3f - Vpt) / Rsource; // 计算 PT100 驱动电流 float Rt = (3.3f - Vpt) / Ipt; // 根据欧姆定律计算出 PT100 阻值 return Rt;}void USART1_Send_Float(float f){ char buf[32]; sprintf(buf, "%.1f", f); // 转换为字符串 while (*buf) { USART_SendData(USART1, *buf); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); buf++; }}int main(void){ ... while (1) { float Vpt = ADC_Get_Voltage(); // 获取差分放大器输出电压 float Rt = PT100_Get_Rt(Vpt); // 计算 PT100 阻值 float Temp = PT100_Get_Temperature(Rt); // 根据阻值计算温度 USART1_Send_Float(Temp); // 将温度值打印到串口 delay_ms(500); } ...}
审核编辑 黄宇