任务要求:
具体要求:
- 至少使用一种自控算法控制,如PID等
- 可用按键或拨轮控制目标温度
- 可在LCD屏上显示目标温度及实
项目分析:
- 通过IIC获取NSHT30的温度数值
- LCD显示,显示目标温度和设定温度。
项目流程图:
环境配置:
Thonny:
可以在官网进行下载,简单易上手。在硬禾学堂有讲师手把手教学【ヾ(^▽^*)))】,参考了2023年寒假在家一起练,链接如下:直播1:基于STEP Pico的嵌入式系统学习平台板卡介绍+环境搭建 (eetree.cn)
作为平时写程序的主要工具,对于界面的舒适性肯定要高一些,初始界面有点太亮了,长时间看会感到光线有点刺眼。
可以在这里调节一下Thonny的主题:
这里是我现在常用的主题,大家可以根据自己的喜好进行选择去选择 (づ ̄ 3 ̄)づ
主要程序代码分析:
#sht30.py
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"
在sht30.py 部分对于引脚所接温湿度传感器进行温度读取及数据处理。
main.py主要包括三个部分:
1.引入库文件
from machine import I2C, Pin
import time
import uos
import test.st7789 as st7789
from test.fonts import vga2_8x8 as font1
from test.fonts import vga1_16x32 as font2
import random
import framebuf
from machine import Pin, SPI, ADC,PWM
import time, math,array
from utime import sleep_ms
import struct
from simple_pid import PID
2.外设引脚定义
#lcd
st7789_res = 0
st7789_dc = 1
disp_width = 240
disp_height = 240
CENTER_Y = int(disp_width/2)
CENTER_X = int(disp_height/2)
spi_sck=Pin(2)
spi_tx=Pin(3)
spi0=SPI(0,baudrate=4000000, phase=1, polarity=1, sck=spi_sck, mosi=spi_tx)
display = st7789.ST7789(spi0, disp_width, disp_width,
reset=machine.Pin(st7789_res, machine.Pin.OUT),
dc=machine.Pin(st7789_dc, machine.Pin.OUT),
xstart=0, ystart=0, rotation=0)
display.fill(st7789.BLUE)
#sht30
i2c = I2C(0,sda=Pin(20), scl=Pin(21))
def read_sht30():
# 定义SHT30的I2C地址
SHT30_I2CADDR = 0x44
# 定义读取温度和湿度的命令
CMD_MEASURE = bytearray([0x2C, 0x06])
# 向SHT30发送测量命令
i2c.writeto(SHT30_I2CADDR, CMD_MEASURE)
# 延时等待测量结果
time.sleep(0.015)
# 读取测量结果,共6字节,包含温度、湿度和各自的校验值
data = i2c.readfrom(SHT30_I2CADDR, 6)
# 计算温度和湿度
temp = -45 + 175 * ((data[0] << 8 | data[1]) / 65535)
temp = round(temp, 2)
humidity = 100 * ((data[3] << 8 | data[4]) / 65535)
humidity = (humidity, 2)
return temp, humidity
#key
buttonM = Pin(5,Pin.IN, Pin.PULL_UP) #B
buttonS = Pin(6,Pin.IN, Pin.PULL_UP) #A
buttonL = Pin(7,Pin.IN, Pin.PULL_UP) #A
buttonPRESS = Pin(8,Pin.IN, Pin.PULL_UP) #A
buttonR = Pin(9,Pin.IN, Pin.PULL_UP) #A
3.功能实现函数
首先开始与SHT30进行通信,将温湿度的数据显示在LCD上。
temp, humidity = read_sht30()
display.text(font1, "TEM:"+str(temp), 10, 90)
3.主循环
在主循环中先对SHT30进行温度数据的采集及数据处理,之后在LCD上进行温度的显示,通过PID算法计算输出PWM的占空比从而控制MOS管开关进而控制加热电阻的温度,从而实现恒温控制。
while True:
heating_pin.freq(200)
pid = PID(30,8.0, 0.1, setpoint=set_temp,output_limits = (0, 65535))
buttonValueM = buttonM.value()
buttonValueS = buttonS.value()
buttonValueL = buttonL.value()
buttonValuePRESS = buttonPRESS.value()
buttonValueR = buttonR.value()
temp, humidity = read_sht30()
heating_pin.duty_u16(math.ceil(heating_out))
print(heating_out)
heating_out = abs(pid(temp)*1024)
display.text(font1, "TEM:"+str(temp), 10, 90)
display.text(font1, "SET:"+str(set_temp), 10, 100)
if(buttonM.value() == 0):
time.sleep_ms(45)
set_temp = set_temp - 1
if(buttonS.value() == 0):
time.sleep_ms(45)
set_temp = set_temp + 1
while(buttonPRESS.value() == 0):
heating_pin.duty_u16(0)
PID算法部分
本次使用到了Python库中的PID算法
使用遇到的问题:
因为在最初不熟悉这个库,所以在使用时发现当接近但小于目标温度时温控系统正常,但当大于目标温度时又会快速升高温度,而且加热电阻处于功率较高的状态,后续通过阅读pid.py的源码发现需要限制输出范围,保证不会输出负值,因为在主循环计算时将输出值进行了绝对值求取,所以出现了本段中的问题。
因此,在初始化部分修改一下,调整一下PID参数,即可实现良好的温控效果。
MCU简介:
RP2040是树莓派针对嵌入式应用设计发布的一款低功耗可嵌入式设备。可以通过C/C++/MicroPython进行设计开发。
在开发语言方面已知可用的有两种方式:
方式一:
通过C/C++进行程序的设计开发
方式二:
通过MicroPython进行程序的设计开发
最终方案:
使用MicroPython进行程序的开发,理由如下:
树莓派RP2024对于MicroPython的支持较为完善,可以直接在MicroPython官网进行固件的下载。
MicroPython程序简练,无需将时间大量花在程序的书写之中。
MicroPython有很多的库参考,可以将时间聚焦于程序的实现方法上。
MCU处理器的处理速度不断增强,Python的应用也扩展到嵌入式应用之中。
设计过程中遇到的问题及解决方法:
在使用最开始时计划使用C++进行开发设计,后来通过原理图了解到RP2040的串口未引出,数据无法通过串口进行打印,调试较为不便,于是选用MicroPython进行开发设计。
未来的计划:
计划将Pico扩展板中的其他传感器加以利用,构建一个智能家居系统。
使用板载麦克风检测周围环境音量,利用LCD进行显示。