1. 项目描述
本次参加硬禾学堂举办的2024寒假一起练的活动,选择的任务是基于搭配带屏12指神探的传感器扩展板实现恒温控制系统。
2. 硬件资源
本次任务中,使用到了带屏版的12指神探和传感器扩展板:
- 带屏版的12指神探
它是在原板基础上,配备了一块240*240分辨率的LCD彩屏以及两个可程控按键和一个拨轮,丰富了人机交互功能,方便信息观察、界面切换等使用方式。此外还配备了白色外壳,精心设计的包装不仅使板卡日常使用时更加美观也便于板卡的站立以及使用安全。 - 传感器扩展板
扩展板搭载了几款常见传感器和功能模块,包括为初学者准备的麦克风、蜂鸣器、、红外收发、霍尔效应开关、加热电阻,为进阶操作准备的温湿度传感器、六轴传感器、接近/环境光/IR传感器、颜色传感器。其中温湿度传感器、六轴传感器、接近传感器、颜色传感器可拆卸为单个模块,通过杜邦线等连接线延伸其使用的空间范围。
为了实现一个恒温控制系统,用到了下列硬件资源:
- 两个按键,实现对目标温度升温和降温
- 屏幕,用于显示当前温度和目标温度
- 温度传感器,用于测量当前的温度
- 加热电阻:通过IO控制可升高电阻温度,通过配合温湿度传感器并辅以PID等自控算法可实现恒温恒湿效果
3. 需求分析
- 带屏版的12指神探是基于树莓派Pico核心芯片RP2040开发的,RP2040 性能强劲,外设资源丰富:同时 RP2040 的软件生态也非常丰富,可以基于 micropython 进行快速开发,本次拿到的带屏版的12指神探已经默认烧录了
micropython固件,可以通过Thonny进行开发。
本次恒温系统的开发,直接通过python进行开发,屏幕驱动,按键检测,PWM控制和温度读取都在同一个脚本文件中完成,整个开发过程仅耗费两个小时左右。 - 双Arm Cortex M0+内核,可以运行到133MHz
- 264KBSRAM,板卡上外扩2MBFlash
- 性能强大、高度灵活的可编程IO(PIO)可用于高速数字接口
- 拥有2个UART、2个SPI、2个I2C、16个PWM通道以及4路12位精度ADC
- 支持MicroPython、C、C++编程
- 拖拽UF2固件至U盘的方式烧录固件,方便快捷
- 温度测量:使用的是传感器扩展板上的一颗型号为
NSHT30的芯片,来自纳芯微电子,可以参考AN-12-0010(10.07 改) (novosns.com)实现驱动开发。这款芯片的通信接口使用I2C,可以使用micropythonmachine模块中的I2C接口开发驱动。 - 按键控制:可以调用
micropythonmachine模块中的Pin接口进行控制。 - 屏幕控制:带屏版的12值神探出厂固件中,自带了
st7789芯片的示例代码,可以在示例代码的基础上进行修改。 - 加热电阻:通过调用
micropythonmachine模块中的PWM接口进行控制,通过改变占空比,实现温度的升降。 - 恒温控制算法:采用增量式 PID 算法进行控制。
4. 软件流程图
5. 项目开发
5.1 屏幕控制驱动
板卡上的屏幕是 240x240 分辨率的 TFT-LCD,使用 *ST7789 芯片进行驱动,ST7789 使用 SPI 作为通讯接口,使用了 RP2040 的 IO0/IO1/IO2/IO3 这四个管脚连接如下:
软件开发可以基于板卡自带的 st7789.py,并参考 main.py 中的示例代码进行调整,我在屏幕上仅显示当前温度和目标温度,代码如下:
################################################################################
# st7789 driver 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=Pin(st7789_res, Pin.OUT),
dc=Pin(st7789_dc, Pin.OUT),
xstart=0, ystart=0, rotation=0)
display.fill(st7789.BLACK)
display.text(font2, "Current: ", 10, 60)
display.text(font2, " Target: ", 10, 120)
cur_temp = 10.0
target_temp = 25.0
display.text(font2, "{:2.1f}".format(cur_temp), 140, 60)
display.text(font2, "{:2.1f}".format(target_temp), 140, 120)
5.2 按键检测
本次项目中使用到了开发板上的 Key_M 和 Key_S,分别连接到到 RP2040 的 IO5 和 IO6,将检测到 Key_M 被按下时,将目标温度增加一度,当检测到 Key_S 被按下时,将目标温度减小一度,代码如下:
################################################################################
# button init
################################################################################
buttonM = Pin(5,Pin.IN, Pin.PULL_UP) #B
buttonS = Pin(6,Pin.IN, Pin.PULL_UP) #A
### 检测代码
buttonValueM = buttonM.value()
buttonValueS = buttonS.value()
if buttonValueM == 0 and target_temp < 99.0:
target_temp += 1.0
if buttonValueS == 0 and target_temp > 1.0:
target_temp -= 1.0
5.3 温度传感器
使用传感器板上的的一颗 NSHT30 芯片,它是一款基于CMOS-MEMS的相对湿度 (%RH)和温度(T)传感器。它在单芯片上 集成了一个完整的传感器系统,包括电容式的 相对湿度传感器、COMS温度传感器和信号处 理器以及I2C数字通信接口。
参考官方的 AN-12-0010 温湿度传感器NSHT30使用设计指南 ,进行驱动开发,代码如下:
################################################################################
# NSHT30 temperature sensor
################################################################################
def read_nsht30():
# 定义SHT30的I2C地址
SHT30_I2CADDR = 0x44
# 定义读取温度和湿度的命令
CMD_MEASURE = bytearray([0x24, 0x16])
# 向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
# 创建I2C对象
i2c = I2C(0, scl=Pin(21), sda=Pin(20), freq=100000)
5.4 加热电阻
可以通过 PWM 来控制加热电阻升温和降温,加热电阻通过 PWM(脉冲宽度调制)控制的原理是利用周期性地改变电源的开关状态来控制电流流经电阻的平均值。当 PWM 信号处于高电平时,电源连接到加热电阻上,导致电流流过电阻并产生热量。当PWM信号处于低电平时,电源断开,电流停止流动,从而减少了平均功率和热量输出。
通过调整 PWM 信号的占空比(高电平时间占一个周期的比例),可以控制加热电阻的平均功率和热量输出。较高的占空比会导致更多的电流流过电阻,产生更多的热量,而较低的占空比则会减少热量输出。这种方法提供了一种有效且精确地控制加热电阻的温度的方式。
加热电阻与 RP2040 的 IO22
代码如下:
################################################################################
# heater pwm
################################################################################
pwm_duty = 10
pwm = PWM(Pin(22))
pwm.freq(10)
pwm.duty_u16(pwm_duty)
# pwm_duty 后面通过 PID 算法进行更新
5.5 PID 算法
增量式 PID(比例-积分-微分)算法是一种用于控制恒温系统的常见方法。其原理如下:
- 比例(P)控制:根据实际温度和设定温度之间的偏差,计算出一个比例项。这个项表示了当前偏差的大小,可以帮助系统更快地达到设定温度。
- 积分(I)控制:积分项根据偏差的累积值来调整输出,以减小稳态误差。它对长时间内的偏差进行补偿,确保系统更精确地维持在设定温度附近。
- 微分(D)控制:微分项考虑偏差变化速率,可以帮助系统减少振荡和过冲现象。通过检测偏差变化的速度来调整输出,使系统更加稳定。
增量式 PID 控制将上述三个项的计算结果相加,得到最终的控制输出。在每个控制周期内,只需计算当前时刻的控制增量值,然后加到上一个时刻的控制输出上,而无需单独计算PID的三个部分。
- 增量式PID
E(t) = 设定值 - t时刻采样值 - 增量d计算公式
d = KP*[E(t)-E(t-1)]+KI*E(t)+KD*[E(t)-2E(t-1)+E(t-2)]
代码如下:
################################################################################
# PID instance
################################################################################
pid = PID_Inc(KP=1, KI=1, KD=0.5)
def pid_update(target_temp, cur_temp):
global pwm_duty
d = pid.update(target_temp, cur_temp)
pwm_duty = int(pwm_duty + d)
if pwm_duty > 32768:
pwm_duty = 32768
if pwm_duty < 0:
pwm_duty = 0
pwm.duty_u16(int(pwm_duty))
print("{},{},{}".format(target_temp, cur_temp, pwm_duty))
while True:
cur_temp = read_nsht30()[0]
pid_update(target_temp*100, cur_temp*100)
6. 功能展示
见视频。