一、硬件介绍:
温度传感器 NST461-DQNR。这是一款“远程+本地”双通道数字温度传感器,主打小尺寸、高精度、低功耗。能提供12-bit ADC,无论本地还是远程通道,都能以 0.0625 °C 步进输出。测量范围-40 °C ~ +125 °C(芯片本体及远程通道均适用)。I²C 接口、1 mm² 级封装”的高精度温度传感器。
绝压传感器 NSPAD1N200DR04。是纳芯微(NOVOSENSE)车规级 NSPAD1N 系列的一款“绝对压力传感器”。以真空为基准,输出当前环境的绝对压力值,量程 10 kPa – 400 kPa(可按需求定制)。精度为-20 °C – 115 °C 范围内数字输出 ≤ ±1 %F.S..;全温区 -40 °C – 125 °C 工作。同时支持 I²C / SPI 数字输出。
12指神探。这是一个基于RP2040微控制器、搭配240*240分辨率的LCD彩屏、两个轻触按键和一个拨轮,12根管脚支持对外供电、3个ADC模拟信号输入、最多9根通用数字IO,适合嵌入式系统学习、控制、调试,支持C、C++以及MicroPython编程。
二、任务介绍:
使用NST461-DQNR制作电路模块和任意单片机,读取本地温度和远程温度。
使用NSPAD1N200DR04制作电路模块,配置成I2C输出方式,完成测试不同气压的压力值的任务。
这个带屏的12指神探很漂亮,想着给她增加上述两个传感器,做成一个桌面市内环境信息展示的摆件很不错。于是就按这个想法,想着给12指神探增加两个传感器。12指神探有3D打印的外壳,里边有空闲空间,纳芯微的两个传感器芯片都非常小巧,正好可以装在壳内的空闲空间里。

系统框架很简单,从传感器读取环境数据,然后展示到12指神探的屏幕上。
三、硬件制作:

温度传感器与绝压传感器都可以使用I2C总线,所以在电路上两个传感器都放在了同一组I2C总线上。温度传感器使用3.3V供电,绝压传感器使用5v供电。I2C总线上拉电阻,拉到了3.3v上。温度传感器的远端使用了一颗S8550三极管。绝压传感器的I2C和模拟接口都有接出。
问题:在最后的实际测试过程中发现,当绝压传感器使用5V供电时,I2C总线读取数据会异常,最终两个传感器都使用了3.3V供电。绝压传感器的模拟接口(AD),因为这个NSPAD1N200DR04是颗数字芯片,不支持AD模拟输出,所以最终,模拟引脚没有用上。


最终这块电路板是需要放入12指神探的外壳内的,所以电路板画的非常小,只有18X13mm,为了防止过高,PCB使用0.8mm厚度。

四、硬件测试
板子打好后,先再焊盘上焊上引线对板子做初步测试。首先是使用I2C扫描,看看是否能读取到两颗传感器芯片的I2C地址。

很遗憾,只能读到一个传感器的地址。先是怀疑板子焊接问题,仔仔细细、来来回回检查了PCB板子,确认了焊脚都是OK的。于是向群里各位老师求教。老师们介绍这个12指神探能做逻辑分析仪,可以抓取I2C波形图来寻找问题所在。

抓了波形图,发现只有和温度传感器的通讯波形,没看见和绝压传感器的通讯波形图。查看官方的文档,最终发现问题所在。这颗绝压传感器NSPAD1N200DR04芯片的I2C地址为“0X7F”,而0X7F这个地址,在 7-bit 地址空间里属于“保留”区,所以I2C扫描程序,都会忽略这个地址,这也是为何上边I2C波形图中没有找到和绝压传感器通讯的波形。
找到原因就好办多了。用arduino重写I2C扫描程序,留意将扫描地址拓宽到0X7F即可。

#include <Wire.h>
#include "SPI.h"
#include "TFT_eSPI.h"
#include "U8g2_for_TFT_eSPI.h"
arduino::MbedI2C Wire0(20, 21);
TFT_eSPI tft = TFT_eSPI(); // tft instance
U8g2_for_TFT_eSPI u8f; // U8g2 font instance
void setup()
{
Wire0.begin();
tft.begin();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
u8f.setFontMode(0); // use u8g2 none transparent mode
u8f.setFontDirection(0); // left to right (this is default)
u8f.setForegroundColor(TFT_WHITE); // apply color
u8f.setFont(u8g2_font_wqy15_t_gb2312);
u8f.begin(tft); // connect u8g2 procedures to TFT_eSPI
Serial.begin(115200);
Serial.println("\nI2C Scanner");
}
void loop()
{
byte error, address;
int nDevices;
char buf[20];
Serial.println("Scanning...");
u8f.setCursor(0, 20);
u8f.print("Scanning...");
nDevices = 0;
for (address = 1; address <= 127; address++)
{
// The i2c_scanner uses the return value of
// the Write.endTransmisstion to see if
// a device did acknowledge to the address.
Wire0.beginTransmission(address);
error = Wire0.endTransmission();
if (error == 0)
{
Serial.print("I2C device found at address 0x");
u8f.setCursor(0, 40 + 20 * nDevices);
sprintf(buf, "I2C device address 0x%02x", address);
u8f.print(buf);
if (address < 16)
Serial.print("0");
Serial.print(address, HEX);
Serial.println(" !");
nDevices++;
}
else if (error == 4)
{
Serial.print("Unknow error at address 0x");
if (address < 16)
Serial.print("0");
Serial.println(address, HEX);
}
}
if (nDevices == 0)
Serial.println("No I2C devices found\n");
else
Serial.println("done\n");
delay(5000); // wait 5 seconds for next scan
}

五、项目实现
搞定了硬件,接下来的开发就变得很简单了。

读取温度。温度分两个温度:一个本地温度(即通过芯片自身测量的温度),一个远端温度(通过三极管测量的温度)。远端温度通过读取的温度值和实际温度做比较,高了约1.83℃。查阅官方手册,可以通过寄存器进行校准,不过可惜是寄存器校准,这就意味着一旦断电,就需要重新校正,所以这里将这个1.83℃的温差,写死在了程序里。

// 手工测的 远端与本地温度相差为1.83°C 将偏差温度写入寄存器
void setTempOffset()
{
uint8_t buf[2];
float offset = -1.83;
buf[0] = (int8_t)(floor(offset));
buf[1] = (uint8_t)((offset - floor(offset)) * 16);
writeReg8(REG_R_OFFS_H, buf[0]);
writeReg8(REG_R_OFFS_L, buf[1]);
}
// 读取远本地温度,返回单位为 °C 的 float
float readLocalTemp()
{
uint8_t buf[2];
/* 读本地温度 */
Wire0.beginTransmission(NST_ADDR);
Wire0.write(REG_L_TEMP_H);
Wire0.endTransmission(false);
Wire0.requestFrom(NST_ADDR, 2);
buf[0] = Wire0.read();
buf[1] = Wire0.read();
return (int16_t)((buf[0] << 8 | buf[1]) >> 4) * 0.0625f;
}
// 读取远端温度,返回单位为 °C 的 float
float readRemoteTemp()
{
uint8_t buf[2];
/* 读远端温度 */
Wire0.beginTransmission(NST_ADDR);
Wire0.write(REG_R_TEMP_H);
Wire0.endTransmission(false);
Wire0.requestFrom(NST_ADDR, 2);
buf[0] = Wire0.read();
buf[1] = Wire0.read();
return (int16_t)((buf[0] << 8 | buf[1]) >> 4) * 0.0625f;
}
绝压传感器读取也是比较简单的,直接读取出气压值。
// --------------绝压传感器NSPAD1N200DR04------------------------------------------
/* 向寄存器写 1 字节 */
bool nspad1n_writeReg(uint8_t reg, uint8_t val)
{
Wire0.beginTransmission(I2C_ADDR);
Wire0.write(reg);
Wire0.write(val);
return Wire0.endTransmission(true) == 0;
}
/* 从寄存器连续读 n 字节 */
bool nspad1n_readRegs(uint8_t reg, uint8_t *buf, uint8_t len)
{
Wire0.beginTransmission(I2C_ADDR);
Wire0.write(reg);
if (Wire0.endTransmission(false) != 0)
return false; // restart
Wire0.requestFrom(I2C_ADDR, len);
if (Wire0.available() != len)
return false;
for (uint8_t i = 0; i < len; ++i)
buf[i] = Wire0.read();
return true;
}
/* 触发转换并等待完成,最大等待 100 ms */
bool nspad1n_triggerAndWait(void)
{
if (!nspad1n_writeReg(REG_CMD, 0x0A))
return false;
uint8_t status = 0;
unsigned long t = millis();
do
{
if (!nspad1n_readRegs(REG_CMD, &status, 1))
return false;
if (status & 0x02)
return true; // bit1 = 1 表示转换完成
} while (millis() - t < 100);
return false; // 超时
}
/* 读取压力并返回单位 kPa,失败返回 NAN */
float nspad1n_readPressure(void)
{
uint8_t buf[3];
if (!nspad1n_triggerAndWait())
return NAN;
if (!nspad1n_readRegs(REG_PDATA, buf, 3))
return NAN;
int32_t code = ((uint32_t)buf[0] << 16) | ((uint16_t)buf[1] << 8) | buf[2];
if (code > 8388607)
code -= 16777216; // 符号扩展
return (float)code * A / 8388607.0f + B;
}
读取传感器数据后,使用U8g2_for_TFT_eSPI库来绘制屏幕,简单地在屏幕上绘制两个温度信息和气压信息。
六、装入外壳
PCB焊接完成后,整体也是非常的薄。将传感器板子贴在12指神探板子的背面,用胶固定好。然后焊接4根线(3.3v、gnd、scl、sda)到12指神探的排针接口上。因为这里是90度的排针,为了方便焊接,选择GPIO25(SCL)、GPIO28(SDA)这一组管脚做为I2C总线进行接线。最后盖上外壳,严丝合缝,非常漂亮。
in

七、效果演示
上边是两个温度值,温度1是芯片自身温度;温度2是通过三极管测量的温度(已校正)。下边是当前大气压的值。


串口也同时有输出。但是也存在一个问题,由于RP2040工作时会产生温度积累,传感器芯片又被封在壳子内,导致很快感知到的温度就会高于室温,暂时无法解决。

八、心得体会:
感谢硬核科技和纳芯微电子提供的这次机会,接触到优秀的传感器芯片。这次项目中挑战了自己手工焊接QFN封装芯片,感受到QFN芯片并不是那么难焊接,还是有挺大把握将它焊好的。接触到I2C地址为0X7F的器件,由这个特殊地址带来的问题,并一一解决,获得了满满的成就感。

