2024年寒假练
—— 基于搭配12指神探的传感器扩展版制作的恒温控制系统
一.项目介绍
本项目依托2024寒假在家一起练,基于RP2040平台,实现了搭配12指神探的传感器扩展版制作的恒温控制系统。本项目基于RP2040主控,通过加热电阻控制环境温度,温湿度传感器感应环境温度,用按键控制目标温度,并在LCD屏幕上显示目标温度及实时温度,取得了良好的效果,具有一定的价值。
项目任务:
- 至少使用一种自控算法控制,如PID等
- 可用按键或拨轮控制目标温度
- 可在LCD屏上显示目标温度及实时温度
二.设计思路
- 通过GPIO控制可升高加热电阻温度,即升高环境温度
- 加热电阻工作时间越长,环境温度越高,即将温度的变量转换为加热电阻工作时间长短的变量
- 传感器传回当前温度和目标温度,并由PID算法控制加热时间长短
- 温湿度传感器感应当前环境温度,并通过I2C传输协议将数据传回主控
- LCD屏幕动态显示目标温度和实时温度变化,按键波动控制目标温度大小
三.硬件介绍
1.带屏12指神探
这个模块是通过Type C的USB接口提供供电、下载以及通信的功能,板上有5V转3.3V,最高支持800mA的电压变换器,在12根引脚上也将5V和3.3V引出,方便对其它外设板供电。
主控芯片:采用树莓派Pico核心芯片RP2040
- 双Arm Cortex M0+内核,可以运行到133MHz
- 264KBSRAM,板卡上外扩2MBFlash
- 性能强大、高度灵活的可编程IO(PIO)可用于高速数字接口
- 拥有2个UART、2个SPI、2个I2C、16个PWM通道以及4路12位精度ADC
- 支持MicroPython、C、C++编程
- 拖拽UF2固件至U盘的方式烧录固件,方便快捷
板上功能
- TYPE-C接口用于供电和数据传输
- 一个boot按键用于进入boot模式
- 两个可程控按键和一个拨轮用于自定义功能
- 搭载240*240分辨率的LCD彩屏,通过SPI接口进行通信,控制器为常用的ST7789芯片,例程丰富便于开发
- 扩展接口包含5v、3.3v输出、GND。9个GPIO,可同时使能最多三个通道ADC
2.传感器扩展版
扩展板搭载了几款常见传感器和功能模块,包括为初学者准备的麦克风、蜂鸣器、、红外收发、霍尔效应开关、加热电阻,为进阶操作准备的温湿度传感器、六轴传感器、接近/环境光/IR传感器、颜色传感器。其中温湿度传感器、六轴传感器、接近传感器、颜色传感器可拆卸为单个模块,通过杜邦线等连接线延伸其使用的空间范围。
加热电阻通过GPIO口控制。当IO22处于高电平输出状态时,加热电阻工作,同时LED指示灯亮起。
NSHT30温湿度传感器模块是通过IIC驱动,I2C 总线只使用两条总线线路,SDA、SCL。每个连接到总线的设备都有一个独立的地址,主控可以利用这个地址进行不同设备之间的访问。
蜂鸣器通过GPIO口控制。对IO23口输出PWM波,通过对其设置频率和幅度控制蜂鸣器工作音量的高低和大小。
四.功能介绍
- 左键控制升高温度,右键控制降低温度
- 升高温度时,蜂鸣器随之工作
- 随着目标温度的变化,PID算法控制加热电阻随之使用/禁用
- 具体使用详见视频
五.主要代码
1.NSHT30.py通过I2C协议工作
此处使用的开源库读取传感器。参考【https://space.bilibili.com/1349435951?spm_id_from=333.1007.0.0】
"""
https://space.bilibili.com/1349435951?spm_id_from=333.1007.0.0
"""
from machine import Pin,I2C
import time
# I2C address B 0x44 ADDR (pin 2) connected to GND
DEFAULT_I2C_ADDRESS = 0x44
class SHT30:
POLYNOMIAL = 0x131 # P(x) = x^8 + x^5 + x^4 + 1 = 100110001
ALERT_PENDING_MASK = 0x8000 # 15
HEATER_MASK = 0x2000 # 13
RH_ALERT_MASK = 0x0800 # 11
T_ALERT_MASK = 0x0400 # 10
RESET_MASK = 0x0010 # 4
CMD_STATUS_MASK = 0x0002 # 1
WRITE_STATUS_MASK = 0x0001 # 0
# MSB = 0x2C LSB = 0x06 Repeatability = High, Clock stretching = enabled
MEASURE_CMD = b'\x2C\x10'
STATUS_CMD = b'\xF3\x2D'
RESET_CMD = b'\x30\xA2'
CLEAR_STATUS_CMD = b'\x30\x41'
ENABLE_HEATER_CMD = b'\x30\x6D'
DISABLE_HEATER_CMD = b'\x30\x66'
def __init__(self, i2c=None, delta_temp=0, delta_hum=0, i2c_address=None):
if i2c is None:
raise ValueError('An I2C object is required.')
self.i2c = i2c
self.i2c_addr = i2c_address
self.set_delta(delta_temp, delta_hum)
time.sleep_ms(50)
def is_present(self):
"""
Return true if the sensor is correctly conneced, False otherwise
"""
return self.i2c_addr in self.i2c.scan()
def set_delta(self, delta_temp=0, delta_hum=0):
"""
Apply a delta value on the future measurements of temperature and/or humidity
The units are Celsius for temperature and percent for humidity (can be negative values)
"""
self.delta_temp = delta_temp
self.delta_hum = delta_hum
def _check_crc(self, data):
# calculates 8-Bit checksum with given polynomial
crc = 0xFF
for b in data[:-1]:
crc ^= b
for _ in range(8, 0, -1):
if crc & 0x80:
crc = (crc << 1) ^ SHT30.POLYNOMIAL
else:
crc <<= 1
crc_to_check = data[-1]
return crc_to_check == crc
def send_cmd(self, cmd_request, response_size=6, read_delay_ms=100):
"""
Send a command to the sensor and read (optionally) the response
The responsed data is validated by CRC
"""
try:
self.i2c.writeto(self.i2c_addr, cmd_request)
if not response_size:
return
time.sleep_ms(read_delay_ms)
data = self.i2c.readfrom(self.i2c_addr, response_size)
for i in range(response_size//3):
if not self._check_crc(data[i*3:(i+1)*3]): # pos 2 and 5 are CRC
raise SHT30Error(SHT30Error.CRC_ERROR)
if data == bytearray(response_size):
raise SHT30Error(SHT30Error.DATA_ERROR)
return data
except OSError:
raise SHT30Error(SHT30Error.BUS_ERROR)
except Exception as ex:
raise ex
def clear_status(self):
"""
Clear the status register
"""
return self.send_cmd(SHT30.CLEAR_STATUS_CMD, None)
def reset(self):
"""
Send a soft-reset to the sensor
"""
return self.send_cmd(SHT30.RESET_CMD, None)
def status(self, raw=False):
"""
Get the sensor status register.
It returns a int value or the bytearray(3) if raw==True
"""
data = self.send_cmd(SHT30.STATUS_CMD, 3, read_delay_ms=20)
if raw:
return data
status_register = data[0] << 8 | data[1]
return status_register
def measure(self, raw=False):
"""
If raw==True returns a bytearrya(6) with sensor direct measurement otherwise
It gets the temperature (T) and humidity (RH) measurement and return them.
The units are Celsius and percent
"""
data = self.send_cmd(SHT30.MEASURE_CMD, 6)
if raw:
return data
t_celsius = (((data[0] << 8 | data[1]) * 175) / 0xFFFF) - 45 + self.delta_temp
rh = (((data[3] << 8 | data[4]) * 100.0) / 0xFFFF) + self.delta_hum
return t_celsius, rh
def measure_int(self, raw=False):
"""
Get the temperature (T) and humidity (RH) measurement using integers.
If raw==True returns a bytearrya(6) with sensor direct measurement otherwise
It returns a tuple with 4 values: T integer, T decimal, H integer, H decimal
For instance to return T=24.0512 and RH= 34.662 This method will return
(24, 5, 34, 66) Only 2 decimal digits are returned, .05 becomes 5
Delta values are not applied in this method
The units are Celsius and percent.
"""
data = self.send_cmd(SHT30.MEASURE_CMD, 6)
if raw:
return data
aux = (data[0] << 8 | data[1]) * 175
t_int = (aux // 0xffff) - 45
t_dec = (aux % 0xffff * 100) // 0xffff
aux = (data[3] << 8 | data[4]) * 100
h_int = aux // 0xffff
h_dec = (aux % 0xffff * 100) // 0xffff
return t_int, t_dec, h_int, h_dec
class SHT30Error(Exception):
"""
Custom exception for errors on sensor management
"""
BUS_ERROR = 0x01
DATA_ERROR = 0x02
CRC_ERROR = 0x03
def __init__(self, error_code=None):
self.error_code = error_code
super().__init__(self.get_message())
def get_message(self):
if self.error_code == SHT30Error.BUS_ERROR:
return "Bus error"
elif self.error_code == SHT30Error.DATA_ERROR:
return "Data error"
elif self.error_code == SHT30Error.CRC_ERROR:
return "CRC error"
else:
return "Unknown error"
2.使能加热电阻和蜂鸣器
#定义第22引脚为heat,设置为输出模式
heat = Pin(22, Pin.OUT)
#heat加热高电平电阻工作
heat.value(1)
#PWM波控制蜂鸣器工作
mic = machine.PWM(Pin(23))
#设置输出频率控制音调
mic.freq(50)
#设置占空比控制音量
mic.duty_u16(80)
3.PID算法控制加热时间(温度高低)
PID算法最常用在电机转速控制中,此处可以类比到温度控制上。不同的是电机转速可以人为调低,温度降低只能禁用电阻,因此在调整加热电阻使用时间时候需要调整公式使其合适。如在增量输出时,手动调整原先的输出量为其1/30,再加上增量则可以较好控制温度变化。参考【http://t.csdnimg.cn/U3J6y】
(1)P值作用
如果当前的温度值和目标值相差很大,我们增加加热时间,让温度快速达到目标值,PID中的P就扮演着重要的作用,这里的P我使用Position_KP代替,PWM=Position_KP*(target-position),当前的温度和目标相差越大,输出的PWM值就会越大,可以快速达到目标速度值。
(2)I值作用
PID中的I的作用是为了消除静差,设置一个积分量。只要偏差存在,就不断地对偏差进行积分(累加),并反应在调节力度上。KI的值越大,积分时乘的系数就越大,积分效果越明显。
(3)D值作用
比较接近目标时,P的控制作用就比较小了。D的作用就是让物理量的速度趋于0,只要什么时候,这个量具有了速度,D就向相反的方向用力,尽力抑制住振荡的变化。令Bias=taget-position,令上一次求得的偏差值为Last_Bias。D项的计算公式为Position_KD*(Bias-Last_Bias)。
Pwm=Position_KP*Bias+Position_KI*Integral_bias+Position_KD*(Bias-Last_Bias) #位置式PID控制器
Last_Bias=Bias
Integral_bias+=Bias
#增量输出
buttonValueL=Pwm+buttonValueL/30
4.传感器的使用
有了NSHT30库,对传感器进行基本的使用
while True:
try :
# 判断温湿度传感器是否连接上
if sht.is_present() != True:
print("error")
else:
temp,huml = sht.measure() # 测量温湿度
#取整便于LCD显示
round_temp=round(temp)
round_huml=round(huml)
#电脑print结果
print("temperature: %.2f ℃"%temp)
print("humidity: %.2f "%huml + str("%"))
time.sleep(0.1)
except SHT30Error as ex: # 捕获异常
print(ex)
RuntimeError
5.温度计外形参考【https://www.eetree.cn/project/1050】
六.活动总结与未来计划
通过对本项目的学习,对I2C又有了进一步的了解,本次项目锻炼了我的编程能力,对嵌入式开发有了新的体会,受益匪浅。未来期望再次参加活动,开拓电子芯片在生活中运用的视野,改变思维模式。