一、项目介绍
当前介绍的项目是基于 STM32F103ZET6 系列 MCU 设计的数显热水器,通过显示屏来显示热水器的温度及其工作状态,通过 PT100 传感器来检测热水器的温度变化,并通过电加热片实现加热过程,以达到控制热水器温度的目的。
二、设计流程
2.1 硬件选型
- STM32F103ZET6 系列 MCU
- OLED 显示屏
- 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] != '\0') {
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\r\n", 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);
}
...
}