基于纳芯微传感器的监测仪
项目介绍
本项目使用带屏幕的十二指神探作为主控,采集传感器上的数据,并在屏幕中显示出来。传感器使用的是纳芯微的3种传感器:NSHT30、NSPAS3、NSM3013。pcb的设计使用kicad软件。
项目设计思路
十二指神探通过iic采集nsht30中的温湿度数据,通过adc采集nspas3和nsm3013中的数据。数据采集完成后,使用lvgl的ui框架,将数据显示出来。
代码框架
十二指神探的主控为树莓派的rp2040,他支持c、c++、python等语言。在本项目中,通过vscode中的platformio插件,使用arduino框架进行代码的编写。使用platformio编写的首要问题就是库的安装,这个过程是十分缓慢且易出问题。但是在一切安装好后,就可以愉快的编写代码,且具备代码提示和跳转功能,编写起来方便。但可惜的是,platformio中的arduino似乎不支持双核,不能很好的利用rp2040的资源。
ui
十二指神探使用lvgl,这个当前时髦流行的ui框架,给每个传感器数值创建了一个界面。界面之间可以通过十二指神探的波动开关进行切换。其中ui界面的绘制,通过nxp的gui guider软件进行设计。在gui guider中创建4个屏幕。
并在屏幕中填入对应的图像及文本。文本用于显示对应的数据,图像使用和当前数据类型相关的素材。素材可以在阿里开源的图标库(iconfont-阿里巴巴矢量图标库)中查找,里面有很多各种各样的素材。
gui guider设计好ui后可以生成对应的c代码,将生成的代码放入代码工程内,就可以运行了。
如果出现#include "lvgl.h"
的编译错误,那将生成代码中与lvgl头文件相关的部分,修改成工程中,lvgl的实际位置。
如果在设计ui时,添加了界面间切换的动画,但是烧录进去测试发现又没有。那是因为生成的代码直接将上一个界面给删除了,需要对生成的代码进行修改,将lv_obj_clean(act_scr);
注释,这样动画就能正常显示了。
void ui_load_scr_animation(lv_ui *ui, lv_obj_t **new_scr, bool new_scr_del, bool *old_scr_del, ui_setup_scr_t setup_scr,
lv_scr_load_anim_t anim_type, uint32_t time, uint32_t delay, bool is_clean, bool auto_del)
{
lv_obj_t *act_scr = lv_scr_act();
#if LV_USE_GUIDER_SIMULATOR && LV_USE_FREEMASTER
if (auto_del)
{
gg_edata_task_clear(act_scr);
}
#endif
if (auto_del && is_clean)
{
// lv_obj_clean(act_scr);
}
if (new_scr_del)
{
setup_scr(ui);
}
lv_scr_load_anim(*new_scr, anim_type, time, delay, auto_del);
*old_scr_del = auto_del;
}
界面切换后,需要将老界面移除组,并将新界面加入组中,避免波动开关的控制切换到其他界面。这个是生成代码中没有的,需要手动的在event_init.c文件中更改。
static void screen_1_event_handler(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
switch (code)
{
case LV_EVENT_SCREEN_LOADED:
{
// sensor_refresh();
ui_animation(guider_ui.screen_1_img_humi, 800, 0, lv_obj_get_y(guider_ui.screen_1_img_humi), 28, &lv_anim_path_overshoot, 0, 0, 0, 0, (lv_anim_exec_xcb_t)lv_obj_set_y, NULL, NULL, NULL);
ui_animation(guider_ui.screen_1_label_humi, 800, 0, lv_obj_get_y(guider_ui.screen_1_label_humi), 162, &lv_anim_path_overshoot, 0, 0, 0, 0, (lv_anim_exec_xcb_t)lv_obj_set_y, NULL, NULL, NULL);
break;
}
case LV_EVENT_PRESSED:
{
guider_ui.screen_1_label_humi = NULL;
lv_group_remove_obj(guider_ui.screen_1);
ui_load_scr_animation(&guider_ui, &guider_ui.screen_2, guider_ui.screen_2_del, &guider_ui.screen_1_del, setup_scr_screen_2, LV_SCR_LOAD_ANIM_MOVE_LEFT, 200, 200, true, true);
break;
}
default:
break;
}
}
void events_init_screen_1(lv_ui *ui)
{
lv_obj_add_event_cb(ui->screen_1, screen_1_event_handler, LV_EVENT_ALL, ui);
lv_group_add_obj(lv_group_get_default(), guider_ui.screen_1);
}
传感器驱动
nsht30
nsht30是温湿度传感器,它支持连续采集模式。在初始化中,通过iic指令,将其配置为联系采集。nsht30_read
是通过e000
指令,去读取传感器内部的数据,读取完成后,通过公式计算实际的温湿度数据。
arduino::MbedI2C i2c(p20, p21);
void nsht30_init(void)
{
i2c.begin();
i2c.beginTransmission(0x44);
i2c.write(0x20); // 发送一个字节的数据
i2c.write(0x32); // 发送一个字节的数据
i2c.endTransmission(); // 结束传输
delay(100); // 等待一段时间
}
float temperature, humi;
void nsht30_read(void)
{
uint8_t rec[6];
uint8_t cnt = 0;
i2c.begin();
i2c.beginTransmission(0x44);
i2c.write(0xe0); // 发送一个字节的数据
i2c.write(0x00); // 发送一个字节的数据
i2c.endTransmission(); // 结束传输
i2c.requestFrom(0x44, 6);
while (i2c.available())
{ // 等待数据到来
int c = i2c.read(); // 读取一个字节的数据
// Serial.println(c); // 通过串口打印出来
rec[cnt++] = c;
}
if (cnt == 6)
{
temperature = (uint16_t)(rec[0] << 8 | rec[1]);
temperature = -45 + 175 * temperature / 65535;
humi = (uint16_t)(rec[3] << 8 | rec[4]);
humi = 100 * humi / 65535;
}
}
nspas3
nspas3是输出模拟信号,属于可以直接采用arduino的库函数analogRead
进行读取,并通过公式转成实际的压力数据。
(float)analogRead(A1) / 1023 + 0.00095) / 0.008095
nsm3013a
nsm3013a也是输出模拟信号,同样直接使用analogRead
进行读取,通过简单的公式进行转换,这里并没有安装手册中的要求进行处理。
analogRead(A2) / 2
应用代码
首先初始化lvgl,注册一个编码器,并将编码器绑定到组中。这样实体的波动开关就能控制界面。
void lvglinit()
{
lv_init();
// // OR use this initializer (uncomment) if using a 1.14" 240x135 TFT:
tft.init(); // Init ST7789 240x135
// tft.setRotation(2);
// tft.setSPISpeed(40000000);
Serial.println(F("Initialized"));
tft.setRotation(0);
static lv_disp_draw_buf_t draw_buf;
const size_t DISP_BUF_SIZE = sizeof(lv_color_t) * (LV_HOR_RES * 40); // best operation.
// lv_color_t buf[DISP_BUF_SIZE];
static lv_color_t buf1[DISP_BUF_SIZE];
static lv_color_t buf2[DISP_BUF_SIZE];
lv_disp_draw_buf_init(&draw_buf, buf1, buf2, DISP_BUF_SIZE); /*Initialize the display buffer*/
/*-----------------------------------
* Register the display in LVGL
*----------------------------------*/
static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
/*Set up the functions to access to your display*/
/*Set the resolution of the display*/
disp_drv.hor_res = LV_HOR_RES;
disp_drv.ver_res = LV_VER_RES;
/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = my_disp_flush;
/*Set a display buffer*/
disp_drv.draw_buf = &draw_buf;
// disp_drv.full_refresh = 1;
/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);
enterButton.attachClick([]()
{ ENTER_BUTTON = true; });
prevButtton.attachClick([]()
{ PREV_BUTTON = true; });
nextButton.attachClick([]()
{ NEXT_BUTTON = true; });
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_ENCODER;
indev_drv.read_cb = lv_encoder_read;
// indev_drv.read_timer = button_read_timer;
// indev_drv.user_data = lv_input_event;
static lv_indev_t *lv_encoder_indev = lv_indev_drv_register(&indev_drv);
lv_group_t *g = lv_group_create();
lv_indev_set_group(lv_encoder_indev, g);
lv_group_set_default(g);
button_read_timer = lv_timer_create([](lv_timer_t *timer)
{
enterButton.tick();
nextButton.tick();
prevButtton.tick(); },
5, NULL);
}
注册一个1秒的定时器,实时采集对应界面的数据,并改变标签值,实现数据的显示。
lv_timer_t *timer = lv_timer_create(display_humi_and_temp, 1000, NULL);
void display_humi_and_temp(lv_timer_t *timer)
{
Serial.println("asdf");
{
Serial.println("zxv");
if (guider_ui.screen_label_temperature != NULL)
{
char buf[32];
nsht30_read();
sprintf(buf, "%2.2f", temperature);
lv_label_set_text(guider_ui.screen_label_temperature, buf);
}
}
{
Serial.println("afsghdg");
if (guider_ui.screen_1_label_humi != NULL)
{
char buf[32];
nsht30_read();
sprintf(buf, "%2.2f", humi);
lv_label_set_text(guider_ui.screen_1_label_humi, buf);
}
}
{
Serial.println("2q3");
static uint8_t count1 = 0;
if (guider_ui.screen_2_label_press != NULL)
{
char buf[32] = {0};
sprintf(buf, "%2.2fkpa", ((float)analogRead(A1) / 1023 + 0.00095) / 0.008095);
lv_label_set_text(guider_ui.screen_2_label_press, buf);
Serial.println(buf);
}
}
{
if (guider_ui.screen_3_label_1 != NULL)
{
char buf[32];
sprintf(buf, "%d", cal_angle(analogRead(A2) / 2));
lv_label_set_text(guider_ui.screen_3_label_1, buf);
}
}
// sprintf(buf, "press: %2.2fkpa", ((float)analogRead(A1) / 1023 + 0.00095) / 0.008095);
// lv_label_set_text(label_press, buf);
// sprintf(buf, "angle: %d", cal_angle(analogRead(A2) / 2));
// lv_label_set_text(label_angle, buf);
}
实物展示
上电后,屏幕就会自动的显示logo和数字,并周期刷新数值。通过十二指神探的波动开关,可以进行界面的切换,去获取其他信息。
十二指神探温度采集界面

十二指神探湿度采集界面
十二指神探压力采集界面
十二指神探磁力角度显示界面
传感器pcb板
整体连接
心得体会
这次活动所使用的传感器资料很全面,数据输出方式也是常见的类型,所以在数据采集上,不存在什么大问题。不过其中温湿度传感器的封装有点过于小了,导致手动焊接困难,如果想用这款传感器还是推荐smt。
本次活动,pcb设计要求使用kicad,这个软件和立创cad一样是免费的,但是操作逻辑上却有着很大不同,使用上会明显感觉有些不便,但是通过本次活动,也算是初步使用和了解了一下这个绘图软件,拓展了技能树。
主控我使用的是十二指神探,它的芯片是rp2040。这款单片机和常见的stm32这类单片机有着很大不同,它是双核的,不方便直接使用keil开发。这对于习惯stm32的开发流程的我来说,有一定的挑战。它可以使用c和python编程,由于实在对python不是很了解,最后我选择了使用c/c++ 语言。基于语言又确定了arduino框架,该框架帮助我迅速的完成了基础驱动的适配,使得我能更注重应用上的实现。