基于纳芯微NSHT30与NSPAS3芯片设计的环境检测桌搭
该项目使用了纳芯微NSHT30与NSPAS3芯片、KiCad软件和C语言,实现了一个环境检测桌搭的设计,它的主要功能为:环境温湿度监测,环境气压监测,能够分别展示温度、湿度、气压的数据曲线,并同时可以将这些数据实时上传至电脑上位机展示。
标签
KiCad
纳芯微
WeDesign
Pulsar2
更新2024-12-25
137

基于纳芯微NSHT30与NSPAS3芯片设计的环境检测桌搭

一、项目介绍

本项目是使用了纳芯微的NSHT30与NSPAS3芯片搭配十二指神探设计的一个环境监测桌搭,原理图与PCB绘制均采用KiCad绘制。本项目主要功能包括:环境温湿度监测,环境气压监测,能够分别展示温度、湿度、气压的数据曲线,并同时可以将这些数据实时上传至电脑上位机展示。

二、项目设计思路

1、芯片选型

首先是温湿度传感器,我选择了NSHT30。这是一款高精度、低功耗的I2C数字接口温湿度传感器,DFN封装,焊接难度不高。NSHT30在单芯片上集成了一个完整的传感器系统,因此在设计电路时基本不需要额外的外围电路,可以很方便的集成到各种应用当中。此外,NSHT30的I2C接口具有两个独特的、可选择的I2C地址,通信速率最高可支持1MHz,宽电压工作范围,使NSHT30在各种应用环境中更加兼容。同时具有可编程的中断阈值,可以提供报警和系统唤醒,而不需要微控制器来持续监控系统。
其次是气压传感器,我选择的是NSPAS3。该芯片采用汽车级信号调理芯片对MEMS芯体输出进行校准和补偿,能将10kPa 至400kPa的压力信号转换为可自定义输出范围的模拟输出信号。同时,调理过的产品可在温度范围内提供精度范围内的标准输出,免去了对传感器进行校准的工作。

2、主控选型

主控选择了带屏十二指神探,这款主控配备了一块240*240分辨率的LCD彩屏以及两个可程控按键和一个拨轮,丰富了人机交互功能,方便信息观察、界面切换等使用方式。这个模块是通过Type C的USB接口提供供电、下载以及通信的功能,板上有5V转3.3V,最高支持800mA的电压变换器,在12根引脚上也将5V和3.3V引出,方便对其它外设板供电。主控芯片采用树莓派Pico核心芯片RP2040,双Arm Cortex M0+内核,可以运行到133MHz,264KBSRAM,板卡上外扩2MBFlash。

3、原理图绘制

选型完毕后就可以开始绘制原理图了,原理图的绘制相对比较简单,这主要是得力于所选芯片集成度很高,基本不需要外围电路的原因
NSHT30的原理图如下,由于PICO的GPIO引脚可以配置内部上拉,因此可以省略I2C总线上的上拉电阻。

NSPAS3的原理图如下,这里的参考电压VDDHV我选择使用3.3V,这样出来的电压也是在3.3V以内,不用分压,可以直接接入PICO的ADC引脚

十二指神探的接口原理图如下,只需连接电源、I2C和ADC即可,只是在焊接时需要注意反着焊接

4、PCB绘制与制板

原理图绘制完毕后即可开始绘制PCB并制板了,本项目的PCB绘制难度主要在于芯片封装的制作,不过还好芯片数据手册上有详细的芯片尺寸数据,可以对照着绘制PCB,绘制好的PCB如下图所示

绘制好PCB后即可从Kicad工程中导出gerber文件打包后发送给打板厂商,之后等待收货就可以进行焊接与测试了

5、焊接与调试

收货后即可开始尝试焊接了,虽然本项目需要焊接的芯片很好,但由于温湿度传感器NSHT30尺寸比较小,在焊接过程中还是焊废了两颗料,气压传感器NSPAS3倒是比较好焊接。
焊接好后可以使用十二指神探自带的micropython程序进行测试,只需添加少量的I2C和ADC的代码即可在屏幕上显示当前温湿度和气压。不过为了效率很更高的自由度,本项目是使用C语言和C SDK进行开发的。

三、开发过程中遇到的问题与解决方式

本项目在开发过程中主要遇到了两个问题,都是PCB设计上的问题,这直接导致我打了两次板。
第一个问题是NSHT30芯片尺寸实在是过小,在绘制封装时引脚长度留的比较短,这导致在使用刀头烙铁焊接的时候经常一边焊上了,另一边的焊盘留出来的就很少。很容易虚焊和漏焊。
第二个问题是第一次绘制的时候把NSHT30底部的PAD焊盘也画上去了,但根据官方指导文档该芯片下方最好不要铺铜,这导致初版的NSHT30芯片的ADDR引脚悬空了,本应该接地的,再次也应该接VCC,不能悬空。
最终只能再绘制一版PCB,第二次就是加长的NSHT30引脚的长度,并取消了底部的PAD焊盘,方便ADDR引脚接地。

四、实现结果展示

1、菜单界面,总共实现了四个按钮

2、温度曲线展示界面,能够展示实时温度和1分钟内的历史温度曲线

3、湿度曲线展示界面,能够展示实时湿度和1分钟内的历史湿度曲线

4、气压展示曲线,能够展示实时气压和1分钟内的历史气压曲线

5、上传展示界面与上位机界面,上传界面会展示实时当前温湿度和气压,而上位机啧展示120秒内的数据曲线

五、关键代码及说明

1、main函数,初始化所有资源后,在死循环中执行lvgl的时间函数并定时读取温湿度和气压值,并直接将这些参数添加到ui界面的曲线中

int main() {
int8_t temp=0;
int8_t humi=0;
uint8_t p=0;
stdio_init_all();
ui_init();
nsht30_init();
key_cb_set(btn_return);
adc_init();
adc_gpio_init(26);
adc_select_input(0);
uint32_t time_to_wait_1s = 0;
while (true) {
//printf("RUNNING!\n");
//nsht30_read();
lv_timer_handler();
key_scan();
if(try_to_wait(&time_to_wait_1s, 1000)==0){
nsht30_read(&temp,&humi);
p = baro_read();
ui_temp_add_value(temp);
ui_humi_add_value(humi);
ui_baro_add_value(p);

if(get_page_index()==4)ui_update_refresh(temp,humi,p);
// if(get_page_index()==1)ui_temp_add_value(temp);
// else if(get_page_index()==2)ui_temp_add_value(humi);

}
sleep_us(1000);
}
}

2、菜单界面实现,菜单界面总共四个界面,这里在界面上创建四个按钮即可,每次切换时将选中的按钮显示到屏幕中央即可

void ui_main_screen_init(void)
{
ui_main = lv_obj_create(NULL);
lv_obj_clear_flag(ui_main, LV_OBJ_FLAG_SCROLLABLE); /// Flags
lv_obj_set_style_bg_img_src(ui_main, &bg, LV_PART_MAIN | LV_STATE_DEFAULT);

g_main_indev_group = lv_group_create();
lv_indev_set_group(indev, g_main_indev_group);

label_app_name = lv_label_create(ui_main);
lv_obj_align(label_app_name, LV_ALIGN_BOTTOM_MID, 0, -24);
lv_obj_set_style_text_color(label_app_name,lv_color_make(255,0,0),LV_PART_MAIN);
lv_obj_set_style_text_font(label_app_name,&lv_font_song_24,LV_PART_MAIN);
lv_label_set_text(label_app_name,app_name[0]);

container = lv_obj_create(ui_main);
lv_obj_set_size(container, LV_PCT(100), LV_PCT(100));
lv_obj_set_scrollbar_mode(container, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_flex_flow(container, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_scroll_snap_x(container, LV_SCROLL_SNAP_CENTER);
lv_obj_set_style_bg_opa(container, LV_OPA_0, LV_PART_MAIN);
lv_obj_set_style_bg_color(container, lv_color_black(), LV_PART_MAIN);
lv_obj_set_style_border_width(container, 0, LV_PART_MAIN);
lv_obj_set_style_pad_column(container, 30, LV_PART_MAIN); //图标之间的间隙
lv_obj_center(container);

//生成演示按钮
for (int i = 0; i < MAX_APP_NUM; i++)
{
lv_obj_t* btn = lv_btn_create(container);
lv_obj_set_size(btn, 80, 80);
lv_obj_set_style_bg_img_src(btn,btn_image[i],LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_add_event_cb(btn, button_event_cb, LV_EVENT_ALL, NULL);//只注册获取到焦点、按下的消息LV_EVENT_FOCUSED
//lv_obj_add_event_cb(btn, button_event_cb, LV_EVENT_PRESSED, NULL);

lv_group_add_obj(g_main_indev_group, btn);
}
lv_obj_scroll_to_view(lv_obj_get_child(container, 0), LV_ANIM_ON);
lv_obj_set_size(lv_obj_get_child(container, 1), 64, 64);
lv_obj_set_style_bg_opa(lv_obj_get_child(container, 1), LV_OPA_80, LV_PART_MAIN);
}

/**
* @brief 处理按钮事件的回调函数
* @param event
*/
static void button_event_cb(lv_event_t* event)
{
lv_obj_t* current_btn = lv_event_get_current_target(event);
uint32_t current_btn_index = lv_obj_get_index(current_btn);
uint32_t btn_cnt = lv_obj_get_child_cnt(container);
lv_event_code_t code = lv_event_get_code(event);
if (code == LV_EVENT_FOCUSED)
{
lv_obj_set_size(current_btn, 100, 100);
lv_obj_set_style_bg_opa(current_btn, LV_OPA_100, LV_PART_MAIN);

lv_label_set_text(label_app_name,app_name[current_btn_index]);
if(current_btn_index==0){
if(btn_cnt!=1){
lv_obj_set_size(lv_obj_get_child(container, 1), 64, 64);
lv_obj_set_style_bg_opa(lv_obj_get_child(container, 1), LV_OPA_80, LV_PART_MAIN);
}
}
else if(current_btn_index==MAX_APP_NUM-1){
lv_obj_set_size(lv_obj_get_child(container, current_btn_index-1), 64, 64);
lv_obj_set_style_bg_opa(lv_obj_get_child(container, current_btn_index - 1), LV_OPA_80, LV_PART_MAIN);
}
else{
lv_obj_set_size(lv_obj_get_child(container, current_btn_index - 1), 64, 64);
lv_obj_set_size(lv_obj_get_child(container, current_btn_index + 1), 64, 64);
lv_obj_set_style_bg_opa(lv_obj_get_child(container, current_btn_index - 1), LV_OPA_80, LV_PART_MAIN);
lv_obj_set_style_bg_opa(lv_obj_get_child(container, current_btn_index + 1), LV_OPA_80, LV_PART_MAIN);
}
}
else if (code == LV_EVENT_PRESSED)
{
/*启动相应任务*/
//lv_scr_load_anim(ui_temperature, LV_SCR_LOAD_ANIM_OVER_TOP, 500, 0, false);
page = current_btn_index+1;
lv_scr_load_anim(ui_array[current_btn_index], LV_SCR_LOAD_ANIM_OVER_TOP, 500, 0, false);
//_ui_screen_change(ui_array[current_btn_index],LV_SCR_LOAD_ANIM_OVER_LEFT,500,0);
//lv_indev_set_group(indev, ui_indev_group_array[current_btn_index]);
}
}

3、曲线界面,首先在界面上创建一个chart用来展示曲线,然后创建两个scale用来展示xy轴。在获取到温湿度和气压等参数时使用ui_temp_add_value函数在chart上打点展示。

void ui_temperature_screen_init(void)
{
ui_temperature = lv_obj_create(NULL);
lv_obj_clear_flag(ui_temperature, LV_OBJ_FLAG_SCROLLABLE); /// Flags
lv_obj_set_style_bg_img_src(ui_temperature, &bg, LV_PART_MAIN | LV_STATE_DEFAULT);

g_temperature_indev_group = lv_group_create();
//lv_indev_set_group(indev, g_temperature_indev_group);

label = lv_label_create(ui_temperature);
lv_label_set_text(label, label_str);
lv_obj_set_style_text_color(label, lv_color_make(255,0,0), LV_PART_MAIN);
lv_obj_set_style_text_font(label,&lv_font_montserrat_20,LV_PART_MAIN);
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 0);

chart = lv_chart_create(ui_temperature);
lv_chart_set_type(chart, LV_CHART_TYPE_LINE);
lv_chart_set_point_count(chart, 60);
lv_chart_set_range(chart,LV_CHART_AXIS_PRIMARY_Y,0,50);
lv_chart_set_range(chart,LV_CHART_AXIS_PRIMARY_X,0,60);
lv_obj_set_style_pad_all(chart, 0, 0);
lv_obj_set_style_radius(chart, 0, 0);
//lv_obj_set_style_size(chart, 3, 3, LV_PART_INDICATOR);
//lv_obj_set_style_pad_column(chart, 2, 0);
lv_chart_set_div_line_count(chart, 6, 7);
lv_obj_set_size(chart, 200, 200);
lv_obj_align(chart, LV_ALIGN_CENTER, 10, 0);
lv_chart_set_update_mode(chart, LV_CHART_UPDATE_MODE_SHIFT);

/*Create a scale also with 100% width*/
lv_obj_t * scale_left = lv_scale_create(ui_temperature);
lv_scale_set_mode(scale_left, LV_SCALE_MODE_VERTICAL_LEFT);
lv_obj_set_size(scale_left, 25, 200);
lv_scale_set_total_tick_count(scale_left, 26);
lv_scale_set_major_tick_every(scale_left, 5);
lv_obj_set_style_length(scale_left, 3, LV_PART_ITEMS);
lv_obj_set_style_length(scale_left, 6, LV_PART_INDICATOR);
lv_obj_set_style_line_color(scale_left,lv_color_make(255,0,0),LV_PART_INDICATOR);
lv_obj_set_style_text_color(scale_left,lv_color_make(255,0,0),LV_PART_INDICATOR);
//lv_obj_set_style_pad_ver(scale_left, 20, 0);
lv_obj_align(scale_left, LV_ALIGN_LEFT_MID, 5, 0);

static const char * y_lable[] = {"0","10", "20", "30", "40", "50", NULL};
lv_scale_set_text_src(scale_left, y_lable);

lv_obj_t * scale_bottom = lv_scale_create(ui_temperature);
lv_scale_set_mode(scale_bottom, LV_SCALE_MODE_HORIZONTAL_BOTTOM);
lv_obj_set_size(scale_bottom, 200, 20);
lv_scale_set_total_tick_count(scale_bottom, 31);
lv_scale_set_major_tick_every(scale_bottom, 5);
lv_obj_set_style_length(scale_bottom, 3, LV_PART_ITEMS);
lv_obj_set_style_length(scale_bottom, 6, LV_PART_INDICATOR);
lv_obj_set_style_line_color(scale_bottom,lv_color_make(255,0,0),LV_PART_INDICATOR);
lv_obj_set_style_text_color(scale_bottom,lv_color_make(255,0,0),LV_PART_INDICATOR);
//lv_obj_set_style_pad_hor(scale_bottom, 20, 0);
lv_obj_align(scale_bottom, LV_ALIGN_BOTTOM_MID, 10, 0);

static const char * x_lable[] = {"0","10", "20", "30", "40", "50", "60", NULL};
lv_scale_set_text_src(scale_bottom, x_lable);

ser = lv_chart_add_series(chart, lv_color_hex(0xff0000), LV_CHART_AXIS_PRIMARY_Y);

lv_chart_refresh(chart);
}

void ui_temp_add_value(uint8_t value)
{
lv_chart_set_next_value(chart, ser, value);
memset(label_str,0,sizeof(label_str));
snprintf(label_str,sizeof(label_str),"Temp:%2d C",value);
lv_label_set_text(label, label_str);
}

4、数据上传界面这个界面比较简单,只是在界面上实时展示当前的温湿度和气压值,主要是在进入这个界面后会将这些参数上传值上位机

void ui_upload_screen_init(void)
{
ui_upload = lv_obj_create(NULL);
lv_obj_clear_flag(ui_upload, LV_OBJ_FLAG_SCROLLABLE); /// Flags
lv_obj_set_style_bg_img_src(ui_upload, &bg, LV_PART_MAIN | LV_STATE_DEFAULT);

g_upload_indev_group = lv_group_create();

lv_obj_t* label_1 = lv_label_create(ui_upload);
lv_obj_align(label_1, LV_ALIGN_LEFT_MID, 0, -30);
lv_obj_set_style_text_color(label_1,lv_color_make(255,0,0),LV_PART_MAIN);
lv_obj_set_style_text_font(label_1,&lv_font_song_24,LV_PART_MAIN);
lv_label_set_text(label_1,"温度");
lv_obj_t* label_2 = lv_label_create(ui_upload);
lv_obj_align(label_2, LV_ALIGN_LEFT_MID, 0, 0);
lv_obj_set_style_text_color(label_2,lv_color_make(255,0,0),LV_PART_MAIN);
lv_obj_set_style_text_font(label_2,&lv_font_song_24,LV_PART_MAIN);
lv_label_set_text(label_2,"湿度");
lv_obj_t* label_3 = lv_label_create(ui_upload);
lv_obj_align(label_3, LV_ALIGN_LEFT_MID, 0, 30);
lv_obj_set_style_text_color(label_3,lv_color_make(255,0,0),LV_PART_MAIN);
lv_obj_set_style_text_font(label_3,&lv_font_song_24,LV_PART_MAIN);
lv_label_set_text(label_3,"气压");

label_temp = lv_label_create(ui_upload);
lv_obj_align(label_temp, LV_ALIGN_LEFT_MID, 60, -30);
lv_obj_set_style_text_color(label_temp,lv_color_make(255,0,0),LV_PART_MAIN);
lv_obj_set_style_text_font(label_temp,&lv_font_montserrat_20,LV_PART_MAIN);

label_humi = lv_label_create(ui_upload);
lv_obj_align(label_humi, LV_ALIGN_LEFT_MID, 60, 0);
lv_obj_set_style_text_color(label_humi,lv_color_make(255,0,0),LV_PART_MAIN);
lv_obj_set_style_text_font(label_humi,&lv_font_montserrat_20,LV_PART_MAIN);

label_baro = lv_label_create(ui_upload);
lv_obj_align(label_baro, LV_ALIGN_LEFT_MID, 60, 30);
lv_obj_set_style_text_color(label_baro,lv_color_make(255,0,0),LV_PART_MAIN);
lv_obj_set_style_text_font(label_baro,&lv_font_montserrat_20,LV_PART_MAIN);

}


void ui_update_refresh(uint8_t temp, uint8_t humi, uint8_t baro)
{
char str[10] = {0};
snprintf(str,sizeof(str),"%d C",temp);
lv_label_set_text(label_temp,str);
memset(str,0,sizeof(str));
snprintf(str,sizeof(str),"%d%%",humi);
lv_label_set_text(label_humi,str);
memset(str,0,sizeof(str));
snprintf(str,sizeof(str),"%d kPa",baro);
lv_label_set_text(label_baro,str);
printf("{\"temp\":%d,\"humi\":%d,\"baro\":%d}",temp,humi,baro);
}

KiCad文件
使用说明
全屏
附件下载
C_SRC.rar
PICO源码,使用了CSDK和lvgl,压缩包中不包含CSDK但包含LVGL,编译时需配置CSDK路径
QtCode.rar
Qt上位机源码,版本为Qt6,低版本无法编译
app.uf2
可以直接拖拽下载的代码
团队介绍
个人团队
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号