这次来分享一次突发奇想的经历。文章主要是要实现将ADC模块获取的光敏电阻数值转换成标准单位勒克斯的光照强度,虽然说由于实验方法和实验环境,最终结果并不是很准确,但也算是一次创意小实验。
最终的代码可以来这里下载:
Android端
MCU端
最近在项目搞STM32和光敏电阻传感器,辛辛苦苦地找了厂家客服和很多资料,发现都没有光敏电阻阻值或者电压转换成光照强度的公式。
百度了一下,发现我还是太高估了光敏电阻的精确度了,大多数光敏电阻传感器只是提供一个大概的明暗程度的判断,有一些三线的光敏电阻传感器只是提供一个DO口,输出就是1位二进制表示的明和暗。四线的光敏电阻传感器就有提供一个AO口,输出的是12位二进制表示的电压,相比来说就准确了许多,但是不同厂家甚至是同一个厂家的不同光敏电阻,对于光照强度的转换相去甚远,所以基本上找不到一条符合所有光敏电阻的转换公式。如果要更加精准的光照强度,更多的是使用光敏二极管或者是数字照度仪等。
但是,我不甘心呀,项目需要的是光照强度,而到手的光敏电阻传感器我也不想就这么地废了。所以我就想:能不能自己给光敏电阻传感器测试一下,计算出属于它的公式。实际上,我只要有一个能够测光照强度的东西就可以实现。突然想到,手机不就有这个玩意吗?虽然手机的也不够精准,但还是值得一试的,于是,我就有一个大胆的思路:
首先在Android Studio中创建一个空白工程,因为本实验比较简单,所以只需要一个MainActivity就可以了。在activity_main.xml中添加简单的文字说明、两个按钮(开始和删除数据)以及ListView(用于展示数据)。
除此之外还要完成一个listview_item.xml的界面,这个就是用在ListView的每个item中的界面,在Adapter中适配。
这里虽然我们要存储的数据很简单,只有光照强度这一个浮点数值,但是为了方便操作数据库,还是要创建一个类,同时为了和MCU端收集的数据进行匹配,也添加了一个自增的变量id。
package com.peanuo.lighttest;
public class Light {
private int id;
private double lux;
public double getLux() {
return lux;
}
public int getId() {
return id;
}
public void setLux(double lux) {
this.lux = lux;
}
public void setId(int id) {
this.id = id;
}
}
这里继承了SQLiteOpenHelper类来完成我们自定义的数据库的创建。在构造方法中创建数据库,在重写的onCreate方法中使用SQL语句创建数据表。然后是增加两个方法insertData和deleteData来方便操作数据库。最后的query方法是查询数据表的中全部,也就是用来给ListView展示数据的。
package com.peanuo.lighttest.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.peanuo.lighttest.Light;
import java.util.ArrayList;
import java.util.List;
public class LightHelper extends SQLiteOpenHelper {
private SQLiteDatabase sqLiteDatabase;
public LightHelper(Context context) {
super(context,"lightDB",null, 1);
sqLiteDatabase = this.getWritableDatabase();
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE light(id INTEGER PRIMARY KEY AUTOINCREMENT, light REAL)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }
public boolean insertData(double light) {
ContentValues values = new ContentValues();
values.put("light",light);
return sqLiteDatabase.insert("light", null, values)>0;
}
public boolean deleteData(Context context){
return context.deleteDatabase("lightDB");
}
public List<Light> query(){
List<Light> list = new ArrayList<Light>();
Cursor cursor = sqLiteDatabase.query("light", null, null, null, null, null, "id desc");
if (cursor != null){
while (cursor.moveToNext()){
Light info = new Light();
int id = cursor.getInt(cursor.getColumnIndex("id"));
double lux = cursor.getDouble(cursor.getColumnIndex("light"));
info.setId(id);
info.setLux(lux);
list.add(info);
}
cursor.close();
}
return list;
}
}
创建这个适配器就是用来给契合ListView和数据库的。这里要继承BaseAdapter类,然后实现getCount、getItem、getItemId、getView这些方法,最后的ViewHoled类是使用了优化加载list的方法。
package com.peanuo.lighttest.database;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.peanuo.lighttest.Light;
import com.peanuo.lighttest.R;
import java.util.List;
public class LightAdapter extends BaseAdapter {
private LayoutInflater layoutInflater;
private List<Light> list;
public LightAdapter(Context context, List<Light> list){
this.layoutInflater = LayoutInflater.from(context);
this.list = list;
}
@Override
public int getCount() {
return list==null ? 0 :list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null){
convertView = layoutInflater.inflate(R.layout.listview_item, null);
holder = new ViewHolder();
holder.id = convertView.findViewById(R.id.item_id);
holder.light = convertView.findViewById(R.id.item_light);
convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
Light info = (Light) getItem(position);
holder.id.setText(String.valueOf(info.getId()));
holder.light.setText(String.valueOf(info.getLux()));
return convertView;
}
class ViewHolder{
TextView id,light;
}
}
MainAcivity中要先创建SensorManager和Sensor,分别是所有传感器服务的变量以及具体传感器的变量。TYPE_LIGHT就是指向光线传感器的。在activity处于onResume时,要注册传感器服务,在处于onPause时要注销。
sensorManager = (SensorManager)this.getSystemService(SENSOR_SERVICE);
assert sensorManager != null;
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
@Override
protected void onResume() {
super.onResume();
sensorManager.registerListener(sensorEventListener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(sensorEventListener);
}
创建自定义的线程来实现测试,同时在其中使用runOnUiThread来更新界面数据。(由于我对Android的线程还不太了解,所以此处这样处理并不是最佳的)
//开始测试的线程
private class TestThread extends Thread{
@Override
public void run() {
super.run();
while (isTesting){
try {
Thread.sleep(4000);
if (lightHelper.insertData(light)) {
Log.i("Light","光照强度:"+light);
}
} catch (InterruptedException e) {
e.printStackTrace();
Log.i("Thread","测试线程已停止");
//break;
}
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
showData();
}
});
}
}
}
首先根据自己的MCU型号创建工程,然后在SYS中根据自己使用的调试器选择Debug。我这里是使用ST-Link,所以选择Serial Wire。
接着,就是配置ADC和USAR了。根据自己的板子随便选,我这里选择的是ADC1 IN8和USART1。这两个外设的配置都保持默认的参数就行了。
最后在生成代码之前,要去Project设置好名称、文件位置和对应的IDE。然后点击靠近右上角的GENERATE CODE就行了。
注意一下,可以点击Code Generator里面在下面图示的位置打一个钩,这样生成的代码文件就会分类放好,如果没有打勾的话,就会全部放在main文件中。
找到STM32CubeMX生成的代码文件,打开Keil工程文件。
首先,我们要将芯片的启动文件添加进去工程。(不知道此处是我的Keil配置问题还是STM32Cube本身的问题,生成的代码总是得自己亲手添加启动文件进去工程)
注意,在使用STM32CubeMX生成的代码,在Keil中编辑的时候自己添加的代码要在BEGIN注释和END注释之间,否则用STM32CubeMX重新生成代码之后,不在两个注释之间的代码会被清除。
要现在usart.h头文件中声明函数void u1_printf(char* fmt, …);
然后在usart.c文件中添加串口发送的函数。这里使用的是类似于printf的方法。
void u1_printf(char* fmt,...)
{
uint8_t i,j;
va_list ap;
va_start(ap,fmt);
vsprintf((char*)USART1_TX_BUF,fmt,ap);
va_end(ap);
i=strlen((const char*)USART1_TX_BUF); //此次发送数据的长度
for(j=0;j<i;j++) //循环发送数据
{
while((USART1->SR&0X40)==0); //循环发送,直到发送完毕
USART1->DR=USART1_TX_BUF[j];
}
}
同样,首先要在头文件中声明函数uint16_t read_adc(void);
然后到c文件中添加函数。
注意此函数读取的数字不是标准单位的电压伏特值,而是ADC读取得使用12位二进制表示的电压,还未按照比例换算成电压伏特值。由于我的目标是转换成光照强度,所以先按比例转换成电压没有必要,所以就直接使用ADC读取的数值了。
uint16_t read_adc(void)
{
uint16_t temp;
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 50);
if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC))
{
temp = HAL_ADC_GetValue(&hadc1);
}
return temp;
}
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(3000);
id++;
light = read_adc();
u1_printf("id: %d light: %d\r\n", id, light);
}
/* USER CODE END 3 */