本实践案例基于小熊派开发板:
实践光强传感器的开发,我们需要带上一个扩展模块:E53_SC1,如下图所示,最终连接的效果:
再来看看这个拓展板以及主板上对应的硬件接口,后面我们才能够去配置相应的硬件管脚,达到驱动使用的目的:
转接板E53_SC1在主板上的电路原理图:
BH1750光强传感器简介
BH1750是一种用于两线式串行总线接口的数字型光强度传感器集成电路。这种集成电路可以根据收集的光线强度数据来调整液晶或者键盘背景的的亮度,利用它的高分辨率可以探测较大范围的光强度变化。(1lx-65535lx)
特点:
要控制这个传感器,当然要了解传感器支持的协议,以及一些指令,这是一个基于I2C接口的传感器。
I2C是(Inter-Integrated Circuit)的英文缩写,是Philips公司开发的一个通信协议,只有两根线(SDA/SCL)是用来通信的。
BH1750支持的命令:
BH1750从机地址计算:
根据文档提示,我们了解到光强传感器的从机地址是0100011
当主机向从机发送写命令时为:
0100011(从机地址:7位) 0(写数据位:1位) ===> 0x46
当主机向从机发送读命令时为:
0100011(从机地址:7位) 1(写数据位:1位) ===> 0x47
工程实践:stm32cubMx
1、芯片选型,这里选择stm32l431rctx
2、配置rcc时钟以及串行调试接口
3、配置调试LED
4、配置串口调试
方便根据调试信息查看程序执行流程(默认即可)。
5、配置I2C1(PC6/PC7位于I2C1)
默认即可。
6、生成Keil5基础工程
实际开发建议硬件外设分模块,这样看起来不要把所有的生成全部都挤到main.c里面去了,这点让我非常讨厌,所以生成工程时候习惯点击设置以下这一项:
接下来点击生成代码:
工程实践:编写代码
1、将multi_timer添加到keil5工程
2、创建一个Package目录,将multi_timer的程序文件添加进来
3、编写代码
由于篇幅限制,只看我自己代码添加的位置:
usart.h
添加重定向打印
/* USER CODE BEGIN 1 */ int fputc(int ch, FILE *file) { return HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000); } /* USER CODE END 1 */
stm32l4xx_it.c
添加multi_timer计数
/** * @brief This function handles System tick timer. */ void SysTick_Handler(void) { /* USER CODE BEGIN SysTick_IRQn 0 */ timer_ticks(); /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ /* USER CODE END SysTick_IRQn 1 */ }
main.h
添加相应的头文件
/* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include "multi_timer.h" /* USER CODE END Includes */
main.c
在程序中宏定义相应的值
/* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /*接收光强超时值*/ #define TIMEOUT 200 /*光强值检测阈值*/ #define LIGHT_SENSOR_THREHOLD 30 /*mode:模式选择*/ //连续高分辨率模式精度 1 lux #define LUX_1_MODE 0x10 //连续高分辨率模式2精度 0.5 lux #define LUX_0_5_MODE 0x11 //低分辨率模式 #define LUX_LOW_MODE 0x13 #define WRITE_ADDRESS 0x46 //0100 011 0 #define READ_ADDRESS 0x47 //0100 011 1 /* USER CODE END PD */
光强采集结构体
typedef struct light_sensor { /*光强值*/ int Lux ; /*multi_timer定时器句柄*/ Timer timer1 ; /*定时器计数值*/ uint16_t Timer_Count ; /*是否采集完成标志*/ uint8_t Conver_completed ; /*定时回调*/ void (*timeout_cb)(void); } light_sensor_TypeDef; light_sensor_TypeDef lsensor ;
定时回调函数以及结构体初始化
/*定时器回调函数*/ void timer1_callback(void) { ++lsensor.Timer_Count; } /*初始化参数*/ void Init_BH750(void) { lsensor.Lux = 0 ; lsensor.Timer_Count = 0 ; lsensor.Conver_completed = 0 ; lsensor.timeout_cb = timer1_callback ; }
这里的读光强没有用小熊派例程里直接延时然后读取的方法,小熊派的读光强函数是这么写的:
而我是这么写的:
/*读取光强*/ void ReadBH1750(uint8_t mode) { float lux = 0; uint8_t ReadData[2] = {0}; static uint8_t Sensor_Status = 0 ; /*读取光强流程*/ switch(Sensor_Status) { /*1、发送检测光强模式的指令*/ case 0: if(HAL_OK != HAL_I2C_Master_Transmit(&hi2c1, WRITE_ADDRESS, (uint8_t *)&mode, 1, 0xff)) return ; /*切换为读地址状态*/ Sensor_Status = 1 ; lsensor.Timer_Count = 0 ; lsensor.Conver_completed = 0 ; break ; case 1: /* 2、发送命令后延时200ms等待读取 定时200ms,判断是否已经到了 这里相当于取代了延时等待,不占用CPU */ if(TIMEOUT == lsensor.Timer_Count) { lsensor.Timer_Count = 0 ; //3、开始读取光强,发送读光强指令 if(HAL_OK == HAL_I2C_Master_Receive(&hi2c1, READ_ADDRESS, ReadData, 2, 0xff)) { lux=(float)((ReadData[0]<<8)|ReadData[1]); lux=(double)lux/1.2; lsensor.Lux = (int)lux ; /*4、转换完成*/ lsensor.Conver_completed = 1 ; Sensor_Status = 0 ; /*切换为写地址状态*/ } } break ; default: break; } }
这里利用了switch case+multi_timer产生一个200ms的定时计数,通过状态1流程判断计数器是否到达设定值,从而达到延时的效果,这段程序在主程序的while循环中几乎不会占用CPU,非常高效!
主程序逻辑:
/** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ /*串口初始化后加这个延时,防止后面的printf打印乱码*/ HAL_Delay(200); printf("光强读取测试实验\n"); Init_BH750(); timer_init(&lsensor.timer1, lsensor.timeout_cb, 1, 1); timer_start(&lsensor.timer1); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ //采用连续高分辨率模式精度 1 lux ReadBH1750(LUX_1_MODE); if(1 == lsensor.Conver_completed) /*如果转换完了才会执行*/ { printf("当前光强值:%d\n", lsensor.Lux); //当光强值大于设定的光强值阈值,则关灯,否则开灯 if(lsensor.Lux > LIGHT_SENSOR_THREHOLD) { HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); } } timer_loop(); } /* USER CODE END 3 */ }
运行结果:
状态机思想前变万化,学会灵活应用则一定能够写出非常高效,简单易维护的程序。
实际例程下载:
链接:https://pan.baidu.com/s/1GA29ua5yVfvGFfRCcS52LA 提取码:afe5 复制这段内容后打开百度网盘手机App,操作更方便哦
往期精彩
RT-Thread UART设备驱动框架初体验(中断方式接收带\r\n的数据)
开源按键组件MultiButton支持菜单操作(事件驱动型)