一、项目概述
本项目使用纳芯微的磁角度传感器芯片NSM3013,设计了一款电机测速系统。
该系统由磁角度传感器、树莓派PICO开发板及扩展板、电机驱动板以及电机等部件构成。
通过PICO扩展板上按键,开发板可对电机进行调速,电机带动磁铁转动,改变磁场方向。开发板根据磁角度传感器检测到的模拟量信号变化,计算电机转速,并通过扩展板上的屏幕显示。
此外,传感器电路板上还使用纳芯微NSHT30传感器,设计了温湿度检测电路。
二、系统设计
系统设计方案如下图所示
本系统控制板采用树莓派PICO,通过扩展板连接SPI接口的彩色显示屏。扩展板按键信号接入控制板,可控制电机转速。
电机上安装了一块磁铁,电机转动时,磁场方向发生变化。NSM3013磁角度传感器,将磁场方向信息,通过模拟量电压信号,发送到树莓派控制板。
温度传感器NSHT30,通过I2C接口和控制板通讯。
扩展板上的按键可实现电机速度控制、计数复位、翻页等功能。
系统用到的主要模块信息如下:
序号 | 型号 | 名称 | 生产单位 | 备注 |
1 | PICO | PICO开发板 | 树莓派 | 芯片PR2040 |
2 | PICO扩展板 | PICO扩展板 | 自制 |
|
3 | 2.4寸 320*240彩屏 | 彩色屏(SPI接口) | 国产 | 驱动芯片ILI9341 |
4 | DRI0044 | 微型双路直流电机驱动模块 | DFRobot | 芯片TB6612FNG |
5 | N20 | 微型直流减速电机 | 国产 | 3-6V供电 |
6 | 传感器电路板 | 传感器电路板 | 自制 | 芯片纳芯微NSM3013A-Q15PR 和NSHT30 |
系统接线图如下
三、传感器电路板设计
NSM301x 是一款高集成度的霍尔旋转磁角度传感器芯片,使用平面霍尔传感器将垂直于芯片表面的磁场分量转换为电压,主要用于电机旋转等角度测量领域。 芯片主要将来自霍尔传感器的信号通过放大和滤波后再被模数转换器(ADC)转换,将ADC 的输出作为 DSP 的 CORDIC 算法模块的输入进行处理,以计算磁场矢量的角度和大小。再通过自动增益控制(AGC)使得磁场强度得以调整放大,从而补偿温度和磁场的变化。用户可以在 SPI 数字输出和 PWM 编码数字输出之间进行选择。
NSM301x 通过工业标准的 SPI,OWI 接口编程,以写入片上非易失性存储器(MTP)。此接口可用于编程零角度(起始位置)和最大角度(停止位置),将输出完全映射到整个 0 到 360 度范围。
磁角度传感器电路,核心芯片是纳芯微的磁角度传感器芯片NSM3013。本次使用的是NSM3013A-Q1SPR。
根据芯片手册典型电路进行设计,芯片电压为5V,输出信号为模拟量电压信号。
NSHT30是一款基于CMOS-MEMS的相对湿度(%RH)和温度(T)传感器。其I2C接口的通信方式、极小的封装和低功耗特性使得NSHT30可以更广泛地集成到各种应用中。NSHT30的推荐工作环境应控制在0-80°C的温度范围内,相对湿度应控制在20-80%RH的范围内。
下面是电路原理图。
电源电路,输入电压为5V,从开发板引线。通过指示灯D1显示通电状态。
磁角度传感器电路如下,根据芯片手册典型电路进行设计。为了适应不同的开发板,对模拟量输出信号进行了分压处理。OUT1为0-5V输出,OUT2为0-3.3V输出,树莓派PICO接OUT2.
温湿度传感器电路如下,通过I2C口和开发板通讯。注意芯片周围开孔,芯片周围不覆铜,降低热传导。
系统用到了DfRobot的电机驱动模块,模块主芯片为TB6612FNG,模块可控制两路电机。本系统只用到了第1路。
由控制板发送方向和速度信号。
下面为电路板PCB效果图。
电路板上方为磁角度传感器电路,J1为磁角度传感器电源及信号接口。
电路板中部为温湿度传感器电路,J2为温湿度传感器电源及信号接口。JP1为地址选择端,中心点接地时,地址为0x44;中心点接电源时,地址为0x45.
电路板下部为电机驱动板插座。
各部分的供电电压都是5V,P1为总电源开关,P2、P3、P4可分别控制各传感器供电,便于单独调试。
下面为传感器电路板实物图
四、软件开发
软件平台及库文件
编程环境:Arduino 2.3.3
开发板库:
库文件:
准备工作
显示屏和开发板的对应引脚,需要在TFT_eSPI库的安装目录中,正确修改配置文件。
LVGL库,也需要正确配置lv_conf.h文件。
温度传感器驱动使用Adafruit的SHT31库,可适用于多种类似温湿度芯片。
代码讲解
LVGL界面绘制代码
//画面1 编码器数值及圈数 电机速度
void create_page1() {
page1 = lv_obj_create(NULL);
arc=lv_arc_create(page1);//创建一个圆弧
lv_arc_set_range(arc,0,360);
lv_arc_set_angles(arc,0,360);
lv_arc_set_bg_angles(arc,0,360);
lv_arc_set_rotation(arc,90);
lv_obj_align( arc, LV_ALIGN_CENTER, -75, 0 );
//Angle标签
lv_obj_t *label1 = lv_label_create(page1);
lv_label_set_text( label1, "Angle");
lv_obj_align( label1, LV_ALIGN_CENTER, -75, -90 );
//Angle数值显示
text_angle= lv_label_create(page1);
lv_obj_align(text_angle, LV_ALIGN_CENTER, -75, 0 );
//圈数标签
lv_obj_t *label2 = lv_label_create(page1);
lv_label_set_text(label2 ,"Count");
lv_obj_align( label2, LV_ALIGN_CENTER, -75, 80 );
//Angle数值显示
text_count= lv_label_create(page1);
lv_obj_align(text_count, LV_ALIGN_CENTER, -75, 100 );
lv_obj_t *label3 = lv_label_create( page1);
lv_label_set_text( label3, "Speed");
lv_obj_align( label3, LV_ALIGN_CENTER, 75, -90 );
lv_obj_t *label4 = lv_label_create( page1);
lv_label_set_text( label4, "r/min");
lv_obj_align( label4, LV_ALIGN_CENTER, 75, 100 );
lable_speed= lv_label_create(page1 );
lv_obj_align(lable_speed, LV_ALIGN_CENTER, 75, 80 );
/*创建仪表盘*/
meter = lv_meter_create(page1);
lv_obj_set_width(meter, scr_act_height() * 0.6); /* 设置仪表宽度 */
lv_obj_set_height(meter, scr_act_height() * 0.6); /* 设置仪表高度 */
lv_obj_align( meter, LV_ALIGN_CENTER, 75, 0 );
/* 设置仪表刻度 */
lv_meter_scale_t* scale = lv_meter_add_scale(meter); /* 定义并添加刻度 */
/* 设置小刻度数量为41,宽度为1,长度为屏幕高度除以80,颜色为蓝色 */
lv_meter_set_scale_ticks(meter, scale, 41, 1, scr_act_height() / 80,lv_palette_main(LV_PALETTE_BLUE));
/* 设置主刻度的步长为8,宽度为1,长度为屏幕高度除以60,颜色为黑色,刻度与数值的间距为屏幕高度除以30 */
lv_meter_set_scale_major_ticks(meter, scale, 8, 1, scr_act_height() / 60,lv_color_black(), scr_act_height() / 30);
lv_meter_set_scale_range(meter, scale, 0, 25, 270, 135);
/* 添加仪表指针,该指针宽度为4,颜色为灰色,长度-10 */
indic = lv_meter_add_needle_line(meter, scale, 4, lv_palette_main(LV_PALETTE_GREY), -10);
//设置区间颜色
lv_meter_indicator_t* indic_green;
indic_green=lv_meter_add_arc(meter,scale,10,lv_palette_main(LV_PALETTE_GREEN),10);
lv_meter_set_indicator_start_value(meter,indic_green,0);
lv_meter_set_indicator_end_value(meter,indic_green,10);
lv_meter_indicator_t* indic_yellow;
indic_yellow=lv_meter_add_arc(meter,scale,10,lv_palette_main(LV_PALETTE_YELLOW),10);
lv_meter_set_indicator_start_value(meter,indic_yellow,10);
lv_meter_set_indicator_end_value(meter,indic_yellow,20);
lv_meter_indicator_t* indic_red;
indic_red=lv_meter_add_arc(meter,scale,10,lv_palette_main(LV_PALETTE_RED),10);
lv_meter_set_indicator_start_value(meter,indic_red,20);
lv_meter_set_indicator_end_value(meter,indic_red,25);
}
void create_page2() {
page2 = lv_obj_create(NULL);
arc2=lv_arc_create(page2);//创建一个圆弧
lv_arc_set_range(arc2,0,50);
lv_arc_set_angles(arc2,0,270);
lv_arc_set_bg_angles(arc2,0,270);
lv_arc_set_rotation(arc2,135);
lv_obj_align( arc2, LV_ALIGN_CENTER, -75, 0 );
lv_obj_t *label1= lv_label_create(page2);
lv_label_set_text( label1, "Temp");
lv_obj_align(label1, LV_ALIGN_CENTER, -75, 0 );
lv_obj_t *label2= lv_label_create(page2);
lv_label_set_text( label2, "Humi");
lv_obj_align(label2, LV_ALIGN_CENTER, 70, 0 );
text_temp= lv_label_create(page2);
lv_obj_align(text_temp, LV_ALIGN_CENTER, -75, 20 );
text_humi= lv_label_create(page2);
lv_obj_align(text_humi, LV_ALIGN_CENTER, 70, 20);
}
翻页代码
void changePage()
{
//检测到翻页按键按下时 切换界面
if ((bChangePage==LOW) && (bLastChangePage==HIGH))
{
if (iPageID<2)
{iPageID=iPageID+1;}
else
{
iPageID=1;
}
Serial.println( "PageID" );
Serial.println( iPageID);
if (iPageID==1)
{lv_scr_load(page1);
}
else if (iPageID==2)
{lv_scr_load(page2);
}
}
bLastChangePage=bChangePage;
}
电机控制代码
void MotorControl()
{
//检测SW7按键变化 调速
if ((bSpeedKeyState==LOW) && (bLastSpeedKeyState==HIGH))
{iSpeedLevel=iSpeedLevel+1;
}
bLastSpeedKeyState=bSpeedKeyState;
if (iSpeedLevel>3)
{
iSpeedLevel=0;
}
//电机控制
iMotorSpeedSet=iSpeedLevel*40;
digitalWrite(Motor_DIR_PIN,HIGH);
analogWrite(Motor_PWM_PIN,iMotorSpeedSet);//最大255
}
圈数统计及测速函数
void MotorSpeedCalc()
{
iCurrentData=iSensorAngle/90; //360度分成4个象限
if ((iCurrentData==0) && (iLastData==3)) //经过1和4象限分界线时
{
iCountData=iCountData+1;//圈数累积
if (iCountData%2==0) //圈数为偶数时,记录当时时间到Millis_a,圈数为奇数时,记录时间到Millis_b,交替记录
{
Millis_a=millis();
}
else
{
Millis_b=millis();
}
Motor_time=abs((int)(Millis_b-Millis_a));//电机周期
Serial.println( "Motor T is" );
Serial.println( Motor_time );
float Motor_time_s;
Motor_time_s=Motor_time/1000.0;
MotorSpeedTest=60/Motor_time_s;//换算成转速 r/min
Serial.println( "Motor Speed is" );
Serial.println( MotorSpeedTest );
}
iLastData=iCurrentData;
//圈数计数复位
if (bCountReset==LOW)
{
iCountData=0;}
//转速显示复位
if (iSpeedLevel==0)
{
MotorSpeedTest=0;
}
}
温度传感器程序
void sht31Read()
{
if ((iAddPage2 % 10) ==0)
{
temp = sht31.readTemperature();
humi = sht31.readHumidity();
if (! isnan(temp)) { // check if 'is not a number'
Serial.print("Temp *C = "); Serial.print(temp); Serial.print("\t\t");
} else {
Serial.println("Failed to read temperature");
}
if (! isnan(humi)) { // check if 'is not a number'
Serial.print("Hum. % = "); Serial.println(humi);
} else {
Serial.println("Failed to read humidity");
}
}
}
五、运行效果
通过扩展板上的按键KEY7,可控制电机转速,电机具有低、中、高 三挡速度。
扩展板上的按键KEY6,可对圈数进行复位。
扩展板上的按键KEY8,可控制界面翻页。
下面为LVGL开发的界面显示效果,界面左侧显示传感器角度及圈数,右侧仪表盘显示电机转速。
按下翻页键,可跳转到温湿度显示界面
六、心得体会
通过此次项目,有以下收获
1.磁铁安装时,需确定磁铁的充磁方向,N极和S极的连线需和芯片平行,N极和S极连线的中心应在芯片中心点上方。
2.传感器输出信号为0-5V,而树莓派ADC输入信号为0-3.3V,需要对传感器信号进行处理。
此项目中是使用1k和2k的电阻进行分压。
3.显示屏调试时,需要打开屏幕背光。
4.LVGL库需要选择合适的版本,本项目使用的是8.3.8版本。
5.温度传感器第1版PCB使用了kicad自带封装,没有根据芯片手册绘制,未注意芯片底部散热片。封装错误引起芯片短路,调试失败。第2版重新绘制了封装,调试成功。
6.温度传感器初始化程序,需要放到LVGL初始化后面,否则无法读数,可能底层代码有冲突。
7.此次将程序放在100ms的循环程序里,后续要研究任务调度处理,不同程序的优先级不同。