基于纳芯微NSHT30-QDNR芯片设计的FPC手腕式RGB测温仪
该项目使用了NSHT30-QDNR,实现了温度仪的设计,它的主要功能为:基于RP2040作为主控,将配件佩戴在手上测量人体的温度。
标签
嵌入式系统
NSHT30
RP2040游戏机
FPC板
鲍飞.
更新2024-12-25
43


项目介绍

本次项目使用的是温湿度传感器NSHT30带屏12指神探完成本次的活动,可以将检测仪通过类似戴手表的方式戴在手腕处来检测当前的人体温度和佩戴处的湿度。

实现的功能如下

  1. 显示当前设备的状态(温度、湿度、LED、BEEP)
  2. 设置当前设备的参数(温度上限,温度下限,湿度上限,湿度下限,是否上传数据到上位机)
  3. 在12指神探上显示波形(温度波形、湿度波形)
  4. 在上位机上显示波形(温度波形、湿度波形)

项目设计思路

因为本次活动需要绘制PCB并且进行打样,所以需要学习并考虑一下硬件的部分,主要考虑了以下三个点。
在软件方面,考虑主控的编写以及上位机的编写,都选择Python,俗话说“人生苦短,我用python”😂。
image.png

搜集素材的思路

  1. 传感器资料:纳芯微传感器芯片 (eetree.cn)
  2. 原理图和PCB设计:Documentation | KiCad
  3. 开发板 - 官网资料:带显示屏的、基于RP2040的多功能嵌入式编程学习、硬件调试平台 (eetree.cn)
  4. 开发板 - 语言库使用:MicroPython 库 — MicroPython中文 1.17 文档
  5. 开发板使用 - IDE:Thonny, Python IDE for beginners
  6. 上位机 - 文档:我们的文档 |Python.org
    以上是完成本次活动,主要参考的文档!
    主要根据需求来搜索所用的资料,比如需要绘制原理图和PCB,那就需要去学习如何使用KiCAD的操作,以及相关所用到芯片的资料(查找到资料1和2),其他操作和思路也就类似了。

硬件问题及解决方法

在进行硬件设计和焊接的时候,只要遇到的问题是:焊接水平忒差了😒,所以选择了封装稍微大一点的温湿度传感器 - NSHT30但是焊接了好几块,通过microphone中的i2c.scan()来验证是否焊接成功,要是扫不到设备就说明焊接有问题,还是一个就是把RGB的封装反了1.2.3.4脚弄成4.3.2.1了,一开始还没有发现,因为系统电源是3V3,RGB灯的是5V,一直点不亮灯,在无意之间摸到了RGB发现温度咋有点高,经过排查才发现封装反了,好在RGB灯光附近的空间足够大,把它整个反了一个身刚好可以使用。

实现结果展示(调试过程中遇到什么问题)

主要遇到的问题是,波形刷新速率跟不上,每一秒跟新波形数据,需要清空当前的页面,根据数组中的数据就行画线,如果需要绘制的波形的区域的像素为220,那么数组就会有220个元素,通过不断读取整个里面的数据,进行连线操作,呈现出来的效果就会是如下:每更新一次,就会看到这波形一根根的画出来,非常的不爽。后面通过查找文档发现了framebuf这个库,可以在刷新一帧前将数据写到缓冲区中,然后流畅的刷新出来,但是还有一个问题是库中Text文本只支持一种的文字大小,所以我在需要快速刷新的时候使才用缓冲。

演示视频见B站链接: 【WeDesign6 纳芯微NSHT30传感器项目】

关键代码及说明

  • 这是下位机绘制波形的函数,通过ShowSwitch来判断当前需要刷新的是温度波形还是湿度波形
def draw_waveform():
fb.fill_rect(21, 0, 219, 220, st7789.BLACK)
for i in range(disp_width - 20 - 1):
if ShowSwitch == 2:
coordXY("S","C",waveformMax,CUR_Temp)
y1 = int(temps[i])
y2 = int(temps[i+1])
elif ShowSwitch == 3:
coordXY("%","C",waveformMax,CUR_Humi)
y1 = int(humiditys[i])
y2 = int(humiditys[i+1])
fb.line(i+20, 219-y1, i + 21, 219-y2, st7789.WHITE)
  • 这是将传感器数据上传到PC,开启定时器后开发上传数据,通过按键启用或者停止定时器来控制是否上传
# 创建一个定时器对象, -1 表示使用任意可用的硬件定时器
timer = Timer(-1)

# 是否开启定时器上传数据到上位机
if UpLoadState is True:
timer.init(period=1000, mode=Timer.PERIODIC, callback=UpLoad_ontimer)
else:
timer.deinit()

# 定义帧头和帧尾
FRAME_HEADER = 0xFD
FRAME_TAIL = 0xFE
def UpLoad_ontimer(timer):
data_str = f"{FRAME_HEADER}-{CUR_Temp}-{CUR_Humi}-{FRAME_TAIL}"
# 转换为字节
#data_bytes = data_str.encode('utf-8')
print(data_str)
  • NSHT30的CRC计算函数


    image.png
def crc8(data, poly=0x31):
crc = 0xFF # 初始化CRC寄存器
for byte in data:
crc ^= byte # 将当前字节与CRC寄存器异或
for _ in range(8): # 对每一位进行处理
if crc & 0x80: # 如果最高位是1
crc = (crc << 1) ^ poly # 左移一位并与多项式异或
else:
crc <<= 1 # 否则只左移一位
crc &= 0xFF # 确保结果保持在8位内
return crc
  • 通过IIC读取NSHT30的数据(这段代码调了很久...不太懂python中字节、十进制、十六禁止等转换,采集到的数据都在转来转去的)
def ReadDate():
print(i2c.scan())
global CUR_Humi
global CUR_Temp
Humi = 0
Temp = 0
try:
# 向 I2C 设备发送数据
data_to_send = bytearray([0x24, 0x16])
i2c.writeto(0x44, data_to_send, False)

# 从 I2C 设备读取数据
sleep_ms(10)
received_data = i2c.readfrom(0x44, 6)

# 将数据转换为十六进制
hex_data_list = ['{:02X}'.format(byte) for byte in received_data]
#print("All Data: ", hex_data_list)

calculated_crc1 = crc8([int(hex_data_list[0], 16), int(hex_data_list[1], 16)])
calculated_crc2 = crc8([int(hex_data_list[3], 16), int(hex_data_list[4], 16)])
#print("CRC1: ", calculated_crc1, "CRC2: ", calculated_crc2)

if '{:02X}'.format(calculated_crc1) == hex_data_list[2]:
Temp = int(hex_data_list[1], 16) | (int(hex_data_list[0], 16) << 8)
if '{:02X}'.format(calculated_crc2) == hex_data_list[5]:
Humi = int(hex_data_list[4], 16) | (int(hex_data_list[3], 16) << 8)

CUR_Humi = 100 * Humi/65535
CUR_Temp = -45 + 175*Temp/65535
#print(Humi,Temp)
#print(CUR_Humi,CUR_Temp)


except Exception as e:
print("Error occurred:", e)
return
  • 按键消抖
buttonValueX = buttonX.value()
if buttonValueX==0:
sleep_ms(20)
if buttonX.value()==0:
//执行相关代码
  • 一些简单的外设
# RGB LED定义
np = neopixel.NeoPixel(machine.Pin(25), 8)
def LED_RefreshColor(R,G,B):
np[0] = (R,G,B, 128)
np[1] = (R,G,B, 128)
np[2] = (R,G,B, 128)
np[3] = (R,G,B, 128)
np[4] = (R,G,B, 128)
np[5] = (R,G,B, 128)
np[6] = (R,G,B, 128)
np[7] = (R,G,B, 128)
np.write()

# LED定义
LED = Pin(22,Pin.OUT)
def LED_ON():
LED.value(1)
def LED_OFF():
LED.value(0)

# PWM蜂鸣器
pwm = PWM(26)
def BEEP_ON():
pwm.init(freq=5000, duty_ns=5000)
CUR_BEEP=1
def BEEP_OFF():
pwm.deinit()
CUR_BEEP=0
  • PC上位机代码
  • 创建坐标轴:x - 时间,y1和y2 - 温度和湿度
  • 接收串口的数据,并且解析
  • 更新相关的数据,并且进行显示
import serial
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from itertools import count

# 初始化一个计数器
index = count()

# 串口设置
ser = serial.Serial(
port='COM15', # 根据实际情况修改端口号
baudrate=115200, # 设置波特率,需要与发送端一致
timeout=1
)

# 初始化数据列表
x_values = []
y1_values = []
y2_values = []

# 创建图形和子图
fig, ax = plt.subplots()
line1, = ax.plot([], [], lw=2, label='TEMPERATURE(℃)')
line2, = ax.plot([], [], lw=2, label='HUMIDITY(%)')
ax.set_xlim(0, 20) # 设置X轴范围
ax.set_ylim(0, 90) # 设置Y轴范围,根据实际信号调整
ax.grid(True)
ax.legend() # 添加图例

def init():
"""初始化函数"""
ax.set_ylim(0, 90) # 设置y轴的范围
line1.set_data([], [])
line2.set_data([], [])
return line1, line2

def update(frame):
"""更新函数,每帧调用一次"""
if ser.in_waiting > 0:
# 读取一行数据并解码为字符串
data = ser.readline().decode('utf-8').strip()
# 使用 '-' 分割字符串成列表
data_parts = data.split('-')
print(data,data_parts[0],data_parts[1],data_parts[2],data_parts[3]) # 253-30-20-254 253 30 20 254

x = next(index)

# 假设data_parts[1]和data_parts[2]分别代表两个波形的值
y1 = int(data_parts[1])
y2 = int(data_parts[2])

# 更新数据列表
x_values.append(x)
y1_values.append(y1)
y2_values.append(y2)

# 更新线的数据
line1.set_data(x_values, y1_values)
line2.set_data(x_values, y2_values)

# 如果超过设定的范围,移动窗口
if x > 20:
ax.set_xlim(x - 20, x)

return line1, line2

# 创建动画
ani = animation.FuncAnimation(fig, update, frames=None,
init_func=init, blit=True, interval=100)

plt.title('Temperature and humidity data from RP2040 USB-CDC')
plt.xlabel('Time')
plt.ylabel('Amplitude')

# 显示图形
plt.show()

# 在程序结束前关闭串口
ser.close()

实物展示






KiCad文件
使用说明
全屏
附件下载
KICAD_NSHT30.7z
KICAD8.0
main.py
RP2040程序
PC.py
上位机程序
团队介绍
个人
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号