《嵌入式-STM32开发指南》第三部分 外设篇 - 第5章 光敏传感器
Posted Bruceoxl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《嵌入式-STM32开发指南》第三部分 外设篇 - 第5章 光敏传感器相关的知识,希望对你有一定的参考价值。
5.1 理论分析
5.1.1光敏电阻概述
光敏电阻或光导管,常用的制作材料为硫化镉,另外还有硒、硫化铝、硫化铅和硫化铋等材料。这些制作材料具有在特定波长的光照射下,其阻值迅速减小的特性,其工作原理是基于内光电效应。这是由于光照产生的载流子都参与导电,在外加电场的作用下作漂移运动,电子奔向电源的正极,空穴奔向电源的负极,从而使光敏电阻器的阻值迅速下降。
光敏电阻对光线十分敏感,其在无光照时,呈高阻状态,暗电阻一般可达1.5MΩ。光敏电阻的特殊性能,随着科技的发展将得到极其广泛应用。
光敏电阻器一般用于光的测量、光的控制和光电转换(将光的变化转换为电的变化)。常用的光敏电阻器硫化镉光敏电阻器,它是由半导体材料制成的。光敏电阻器对光的敏感性(即光谱特性)与人眼对可见光(0.4~0.76)μm的响应很接近,只要人眼可感受的光,都会引起它的阻值变化。设计光控电路时,都用白炽灯泡(小电珠)光线或自然光线作控制光源,使设计大为简化。
通常,光敏电阻器都制成薄片结构,以便吸收更多的光能。当它受到光的照射时,半导体片(光敏层)内就激发出电子—空穴对,参与导电,使电路中电流增强。为了获得高的灵敏度,光敏电阻的电极常采用梳状图案,它是在一定的掩膜下向光电导薄膜上蒸镀金或铟等金属形成的。一般光敏电阻器结构如下图所示。
光敏电阻器通常由光敏层、玻璃基片(或树脂防潮膜)和电极等组成。光敏电阻器在电路中用字母“R”或“RL”、“RG”表示。
光敏电阻常用硫化镉(CdS)制成。它分为环氧树脂封装和金属封装两款,同属于导线型(DIP型),环氧树脂封装光敏电阻按陶瓷基板直径分为Ø3mm、Ø4mm、Ø5mm、Ø7mm、Ø11mm、Ø12mm、Ø20mm、Ø25mm 。
5.2 实验详解
5.2.1实验目的
1.通过实验掌握STM32 ADC获取方法
5.2.2实验设备
硬件:PC 机一台;STM32开发板一套;光敏电阻传感器模块一个
软件:Windows 10系统,Keil5集成开发环境
5.2.3硬件连接
光敏电阻传感器模块电路如下图所示:
DO:数字开关量输出(0 和 1),DO 输出端可以与单片机直接相连,通过单片机来检测高低电平,由此来检测环境的光线亮度改变
AO:模拟信号输出,最简单的模拟输出的电路如下:
光敏电阻传感器模块一般有有三线和四线的接法,四线相比三线多了AO。AO的使用相对比较复杂,需要用到ADC,本文将讲解如何使用AO来控制LED。DO就不讲了,就一个检测高低电平,比较简单。
本文使用的光敏电阻模块如下所示:
模块接线图如下:
STM32F103ZET6的ADC通道表如下:
本文将使用STM32的PC2作为光敏电阻的模拟输入,这里选择ADC1的通道12。
5.2.4 Lib_V3.5.0库实现
光敏电阻的本质就是将光信号转化为电信号,通过ADC的值来反映环境的灯光状态。
1.硬件初始化
SysTick_Init();
//LED初始化
LED_GPIO_Config();
/* USART1 配置模式为 115200 8-N-1,中断接收 */
USART1_Config();
/* ADC 初始化 */
ADCx_Init();
当然这里主要就是ADC的初始化,前面的章节也有讲过,但是这里还是再来看看。
首先是GPIO 的初始化,GPIO设置为模拟输入。
/**
* @brief ADC GPIO 初始化
* @param None
* @retval None
*/
static void ADCx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 打开 ADC IO端口时钟 */
ADC_GPIO_APBxClock_FUN(ADC_GPIO_CLK, ENABLE );
/* 配置 ADC IO 引脚模式 */
GPIO_InitStructure.GPIO_Pin = ADC_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
/* 初始化 ADC IO */
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
接下来就是配置ADC和DMA。
/**
* @brief 配置ADC工作模式
* @param None
* @retval None
*/
static void ADCx_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
/* 打开ADC时钟 */
ADC_APBxClock_FUN(ADC_CLK,ENABLE );
/* 打开DMA时钟 */
RCC_AHBPeriphClockCmd(ADC_DMAx_CLK, ENABLE);
/* 复位DMA控制器 */
DMA_DeInit(ADC_DMA_CHANNEL);
/* 配置 DMA 初始化结构体 */
/* 外设基址为:ADC 数据寄存器地址 */
DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t)(&(ADCx->DR));
/* 存储器地址,实际上就是一个内部SRAM的变量 */
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue;
/* 数据源来自外设 */
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
/* 数据长度 */
DMA_InitStructure.DMA_BufferSize = 1;
/* 外设寄存器只有一个,地址不用递增 */
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
/* 存储器地址固定 */
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
/* 外设数据大小为半字,即两个字节 */
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
/* 内存数据大小也为半字,跟外设数据大小相同 */
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
/* 循环传输模式 */
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
/* DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响 */
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
/* 禁止存储器到存储器模式,因为是从外设到存储器 */
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
/* 初始化DMA */
DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
/* 使能 DMA 通道 */
DMA_Cmd(ADC_DMA_CHANNEL , ENABLE);
/* ADC 模式配置 */
/* 只使用一个ADC,属于单模式 */
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
/* 禁止扫描模式,多通道才要,单通道不需要 */
ADC_InitStructure.ADC_ScanConvMode = DISABLE ;
/* 连续转换模式 */
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
/* 不用外部触发转换,软件开启即可 */
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
/* 转换结果右对齐 */
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
/* 转换通道1个 */
ADC_InitStructure.ADC_NbrOfChannel = 1;
/* 初始化ADC */
ADC_Init(ADCx, &ADC_InitStructure);
/* 配置ADC时钟为PCLK2的8分频,即9MHz */
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
/* 配置 ADC 通道转换顺序为1,第一个转换,采样时间为55.5个时钟周期 */
ADC_RegularChannelConfig(ADCx, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);
/* 使能ADC DMA 请求 */
ADC_DMACmd(ADCx, ENABLE);
/* 开启ADC ,并开始转换 */
ADC_Cmd(ADCx, ENABLE);
/* 初始化ADC 校准寄存器 */
ADC_ResetCalibration(ADCx);
/*等待校准寄存器初始化完成 */
while(ADC_GetResetCalibrationStatus(ADCx));
/* ADC开始校准*/
ADC_StartCalibration(ADCx);
/*等待校准完成 */
while(ADC_GetCalibrationStatus(ADCx));
/* 由于没有采用外部触发,所以使用软件触发ADC转换 */
ADC_SoftwareStartConvCmd(ADCx, ENABLE);
}
2.ADC读取
ADC_ConvertedValueLocal =(float)ADC_ConvertedValue*3.3/4096;
printf("AD转换原始值 = 0x%04X \\r\\n", ADC_ConvertedValue);
printf("计算得出电压值 = %f V \\r\\n",ADC_ConvertedValueLocal);
在main函数的循环中使用不断获取ADC的值,并打印出来。值得注意的是,这里使用的DMA读取,好处就是可以让出CPU干其他的事情。
3.控制LED
根据读取的ADC值来控制LED的状态。
最简单的实现如下:
if(ADC_ConvertedValueLocal > 2.0)
{
LED1_ON;
}
else
{
LED1_OFF;
}
当时这样存在一个问题,不管LED是否开着,只要读取的ADC的值大于阈值都会去操作LED,显然有些不合理,因此需要去读取LED的状态。
/**
* @brief 读取LED的状态
* @param LED_number:LED编号
* @retval LEDState_OFF:关
* LEDtate_ON: 开
*/
uint8_t LED_GetState(uint8_t LED_number)
{
uint8_t led_state = LEDState_ON;
switch(LED_number)
{
case 0:
if((GPIOB->ODR & GPIO_Pin_0) == 0
led_state = LEDState_OFF;
break;
default:
led_state = LEDState_ON;
break;
}
return led_state;
}
这得注意的是,不同的板子的电路有些不一样,有的是输出高电平点亮,有的是低电平,因此LED的亮灭需要根据自己的板子调整以下代码。
if((GPIOB->ODR & GPIO_Pin_0) == 0
笔者这里是高电平点亮LED
最后整个主函数的代码如下:
/**
* @brief mian
* @param None
* @retval int
*/
int main(void)
{
SysTick_Init();
//LED初始化
LED_GPIO_Config();
/* USART1 配置模式为 115200 8-N-1,中断接收 */
USART1_Config();
/* ADC 初始化 */
ADCx_Init();
printf("光敏电阻传感器\\r\\n");
/* 无限循环读取ADC */
while (1)
{
ADC_ConvertedValueLocal =(float)ADC_ConvertedValue*3.3/4096;
printf("AD转换原始值 = 0x%04X \\r\\n", ADC_ConvertedValue);
printf("计算得出电压值 = %f V \\r\\n",ADC_ConvertedValueLocal);
if(ADC_ConvertedValueLocal > 2.0)
{
if(LED_GetState(0) == 0)
LED1_ON;
}
else
{
if(LED_GetState(0) == 1)
LED1_OFF;
}
Delay_ms(1000);
}
}
5.2.5 HAL库实现
我们在串口的例子的基础上进行配置。
打开工程,打开Analog选项,配置ADC参数。
具体配置参数如下
使能连续转换模式(Continuous Conversion Mode)。设置转换周期。其他为默认设置。
值得注意的是,ADC 的输入时钟不得超过14MHz,它是由PCLK2经分频产生,需要调整ADC输入的分频系数。
根据DMA通道预览可以知道,我们用的ADC1对应DMA1 的通道1。
点击DMASettings 点击 Add 添加通道,选择ADC1传输速率设置为中速,DMA传输模式为正常模式,DMA内存地址自增,每次增加一个word(字)。
值得注意的是,STM32cubeMX默认开启了DMA的中断。
还需要设置中断优先级,进入NVIC配置表,简单配置下即可,如下图所示。
另外还需要将LED的引脚配置为输出。
最后生成代码即可。
接下来是实现代码。
HAL库的好处是用户不用谢太多代码,主需要在主函数开启DMA传输即可。
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&ADC_ConvertedValue, 1);
其他就可标准库是一样的逻辑了。
最后给出主函数代码:
/**
* @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_DMA_Init();
MX_USART1_UART_Init();
MX_ADC1_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&ADC_ConvertedValue, 1);
printf("光敏电阻传感器\\r\\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
ADC_ConvertedValueLocal =(float)ADC_ConvertedValue*3.3/4096;
printf("AD转换原始值 = 0x%04X \\r\\n", ADC_ConvertedValue);
printf("计算得出电压值 = %f V \\r\\n",ADC_ConvertedValueLocal);
if(ADC_ConvertedValueLocal > 2.0)
{
if(LED_GetState(0) == 0)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);;
}
else
{
if(LED_GetState(0) == 1)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);;
}
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
是不是很简单。
5.2.6实验现象
打开串口助手,将模块放在不同的光照条件下,串口会打印出ADC的值,也就代表了光的强度。
ADC的值越小,说明光照越小。
代码获取方法
1.长按下面二维码,关注公众号[嵌入式实验楼]
2.在公众号回复关键词[STM32F1]获取资料
欢迎访问我的网站
BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书
以上是关于《嵌入式-STM32开发指南》第三部分 外设篇 - 第5章 光敏传感器的主要内容,如果未能解决你的问题,请参考以下文章
《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距
《嵌入式-STM32开发指南》第三部分 外设篇 - 第4章 超声波测距
《嵌入式-STM32开发指南》第三部分 外设篇 - 第5章 光敏传感器
《嵌入式-STM32开发指南》第三部分 外设篇 - 第5章 光敏传感器