STM32学习日记:探索光敏传感器模块
文章目录
前言
这是我学习32的心理路程
我会尽量把内容写的仔细
对于一些简单模块的使用,我不打算深究其原理,我只要会用它就欧克。所以看见我的32博客的同志们请注意这一点。如果同志您觉得内容有改进的地方,那就请多多指教啦~~~本人一定认真听取。
一、光敏传感器引脚
我这里使用的光敏传感器是4针的。
AO | 模拟输出:光敏传感器将采集的光线变成一个连续的模拟信号从AO引脚输出 |
---|---|
DO | 数字输出:大于光线阈值,DO引脚输出1(高电平);反之输出0。关于光线阈值,应该是调节模块上面那个十字架旋钮 |
GND | 接地 |
VCC | 3·3v或者5v |
注意:这是我第一次使用这个模块,所以语言描述可能有bug。
3针的光敏传感器就好像没有DO引脚。其他的是一样的。
二、光敏传感器小实验1
1·读取DO引脚的高低电平来控制一个led的亮灭。
这个小实验很简单哈。
lightsensor.c
#include "stm32f10x.h" // Device header
#define LED_OFF() GPIO_SetBits(GPIOB, GPIO_Pin_1 )
#define LED_NO() GPIO_ResetBits(GPIOB, GPIO_Pin_1 )
/**
* @brief 光敏传感器DO引脚所连接32的PC14引脚的初始化,
PC14设置为输入模式,读取DO传过来的电平。
* @param 无
* @retval 无
*/
void Light_Sensor_GPIOinit(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitTypeDef Light_Sensor_GPIO_InitStruct;
Light_Sensor_GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU ;
Light_Sensor_GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
Light_Sensor_GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&Light_Sensor_GPIO_InitStruct);
}
/**
* @brief 光敏传感器所控制的led的初始化,这里我是自己外接的led
* @param 无
* @retval 无
*/
void Test_Sensor_LEDinit(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef Test_Sensor_LEDinit;
Test_Sensor_LEDinit.GPIO_Mode = GPIO_Mode_Out_PP ;
Test_Sensor_LEDinit.GPIO_Pin = GPIO_Pin_1;
Test_Sensor_LEDinit.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB,&Test_Sensor_LEDinit);
LED_OFF(); //high--->led off
}
/**
* @brief 功能函数,读取DO电平,来控制外接LED
* @param
* @retval
*/
void Sensor_Contral_LED(void)
{
//uint8_t value = 0;
if( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_14) == 0 )
{
LED_OFF();
}
else
LED_NO();
}
main.c
#include "stm32f10x.h" // Device header
#include "lightsensor.h"
int main (void)
{
Light_Sensor_GPIOinit();
Test_Sensor_LEDinit();
while(1)
{
//GPIO_ResetBits(GPIOB, GPIO_Pin_1 );// 测试外接led单独能不能亮
Sensor_Contral_LED();
}
}
注意:lightsensor.h就是将lightsensor.c中的函数名复制一下就欧克了,这里就不多展示 了哈!
对于这个实验的总结:
开始测试外接led能不能单独亮的时候,led死活不亮,我还以为是引脚复用的毛病,但是我差了一下资料发现并不是这个问题,结果回去仔细看了一下引脚初始化才发现led要推挽输出(),以及引脚初始化结构体的问题。我麻了。后来修正后就成功点亮了,虽然这个问题是小小的不起眼的问题,至少还是需要总结一下,避免以后再犯~~~
2·这里提一下GPIO的工作模式
4种输出模式:
(1)推挽输出:使用推挽输出目的是增大电流,即提高输出引脚的驱动能力,提高电路负载能力。【驱动led】
(2)开漏输出:由于只有下拉MOS管没有上拉MOS管,所以开漏输出模式下,IO引脚只能输出低电平。如果要输出高电平,则需要外接上拉电阻。
(3)复用推挽,复用开漏输出:IO引脚做复用功能时,可以选则复用推挽或者复用开漏输出模式,在选择复用开漏输出模式时,需要外接上拉电阻。
4种输入模式:
(1)上拉输入模式:引脚内部有一个上拉电阻,通过开关连接到电源VDD,当IO引脚无输入信号时,默认输入高电平。【外接按键】
(2)下拉输入模式:与上拉输入模式相反。当IO引脚无输入信号时,默认输入低电平。
(3)浮空输入:引脚内部即不接上拉也不接下拉电阻。浮空输入模式下的引脚电平是不确定的,外部信号是上面电平,MCU引脚就输入什么电平。【USART,IIC等通信协议】
(4)模拟输入模式:引脚内部即不接上拉也不接下拉电阻。【A/D模拟输入实现对外部信号的采集】
3·这里提一下GPIO的输出速度
(1)输出速度并不是输出信号的的速度,而是IO口驱动电路的响应速度。“我简称为:反应能力”。
(2)32 的输出速度有3种: 2MHz, 10MHz, 50MHz,对应的就是低速反应能力,中速反应能力,高速反应能力。
当输出配置为高速时,噪声大,功耗高,电磁干扰强;当输出为低速时,噪声小,功耗低,电磁干扰弱。当输出较高频率的信号时,应该选用较高频率响应速度的驱动模块,否则容易出现信号失真现象。
一般常用的外设(例如led,蜂鸣器等)建议采用2MHz的输出速度,而作为IIC,SPI等复用功能的输出引脚时,尽量选择高响应速度,如10mhz,50hmz。
GPIO引脚做输入模式时,不需要配置引脚的输出速度。
三、光敏传感器小实验2
稍微提升一下小实验1的难度。
1·光敏传感器DO的高低电平变化来切换OLED的显示。
这里我就不贴OLED的代码了哈。
这个实验就是在实验1的lightsensor.c中加一个函数就行。
void SensorContral_LED_OLED(void)
{
//uint8_t value = 0;
if( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_14) == 0 )
{
LED_OFF();
OLED_Clear();
OLED_ShowString(2,2,"light hight");
}
else
{
LED_NO();
OLED_Clear();
OLED_ShowString(2,2,"light low");
}
}
main.c
int main (void)
{
Light_Sensor_GPIOinit();
Test_Sensor_LEDinit();
OLED_Init();
while(1)
{
SensorContral_LED_OLED();
}
}
实验总结:这个实验和实验1其实本质上上没有什么大差别,只是将led换成了OLED而已,我就想着慢慢来使用OLED屏幕,后面会慢慢提升到32用ADC采样采取光敏传感器的AO的模拟输出信号,用OLED来显示更加精确的亮度值。
对于实验2 ,我遇到一个问题:OLED出现闪屏现象。我猜测应该是主函数中的while(1)在一直循环SensorContral_LED_OLED这个函数,这个函数内部对OLED进行了清屏操作,所以就出现了闪屏现象。现在正在解决,若是解决了就放修正后代码。
修正代码的思路:(1)由于我们OLED 是静态显示,所以就不需要清屏刷新.因为在光敏传感器里面如果调用OLED的清屏函数,加上主函数while循环里面调用这个功能函数,就相当于while循环会一直有OLED清屏,所以会出现闪屏现象。(2)light hight 与 light low 长度不一样,在显示的时候会出现light lowht , 所以在light low 后面补充两个空格符,方便静态显示的时候不会出现交叠。
这刚好是OLED的静态显示所以不需要清屏刷新,但是遇到动态实时显示,需要清屏刷新怎么办捏????不需要
四、光敏传感器小实验3
1·串口打印出 .光敏AO模拟输出 .经过32的ADC1 通道0 显示光强数据。
接线:光敏的AO接32的PA0(PA0是ADC1通道0,所以已经在adc模块里面初始化了,不需要再初始化)
.
上面函数:获取串口状态标志。其中第二个传参就是下面这张图。
无法打开串口的情况:串口调试助手没有关闭,导致串口被占用,烧录不了。
STM32F103C8T6 的引脚资源表。
myusart.c
#include "myusart.h"
void MyUsart_init(void)
{
GPIO_InitTypeDef MyUsart_GPIO_InitStruct ;
USART_InitTypeDef MyUSART_InitStruct;
// 这两个结构体的定义一定要放在时钟的之前,否者编译器警告
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 ,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA,ENABLE);
// usart1 Tx PA9
MyUsart_GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP ;
MyUsart_GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
MyUsart_GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, & MyUsart_GPIO_InitStruct);
//usart1 Rx PA10
MyUsart_GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING ;
MyUsart_GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, & MyUsart_GPIO_InitStruct);
// 初始化串口2
MyUSART_InitStruct.USART_BaudRate = 115200 ;// 波特率
MyUSART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
MyUSART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;// 收和发模式
MyUSART_InitStruct.USART_Parity = USART_Parity_No;// 无奇偶校验位
MyUSART_InitStruct.USART_StopBits = USART_StopBits_1;// 一位停止位
MyUSART_InitStruct.USART_WordLength = USART_WordLength_8b;// 字长为8位的数据模式
USART_Init(USART1, &MyUSART_InitStruct);
USART_Cmd(USART1, ENABLE);// 使能串口2
}
// 发送一个字符
void Usart_send_byte(USART_TypeDef* USARTx,uint16_t Data)
{
USART_SendData(USARTx,Data);
while( USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET );
}
// 发送字符串,遇到字符串结尾标志‘\0’结束
void Usart_send_string(USART_TypeDef* USARTx,char *arr)
{
uint16_t i = 0;
do
{
Usart_send_byte(USARTx,*(arr + i));
i++;
}while(*(arr + i) != '\0');
while( USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET );
}
//重定向 printf
int fputc(int ch,FILE *f)
{
USART_SendData(USART1,(uint8_t)ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
return (ch);
}
//重定向 输入
int fgetc(FILE *f)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
adc.c
#include "stm32f10x.h" // Device header
void ADC_init(void)
{
GPIO_InitTypeDef ADC_GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStruct;// 结构体的定义需要先于RCC的开启,否者报警告
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO ,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
// 由于ADC的最大时钟不超过14MHz,所以需要把时钟降下来。
// ADC时钟分频器;六分频;时钟从APB2总线来,最大为72MHz,所以需要六分频后12MHz。
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 使用的是 ADC1 初始化 PA0 用于采集光敏传感器的模拟输出
ADC_GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// 模拟输入
ADC_GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 由于是输入模式,所以不需要配置Speed.
GPIO_Init(GPIOA,&ADC_GPIO_InitStructure);
// 初始化ADC1
ADC_InitStruct.ADC_ScanConvMode = DISABLE;//扫描多通道还是单通道?单通道。
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;// 连续转换还是单次转换?连续。
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;// 数据对齐方式。右对齐。
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//定义用于启动模拟的外部触发器到常规频道的数字转换。
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//将ADC配置为独立还是多模?
ADC_InitStruct.ADC_NbrOfChannel = 1;//通道数量
ADC_Init(ADC1, &ADC_InitStruct);
// ADC规则组配置
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
// ADC1,通道0,需要转换数是1,采样周期是55.5。
ADC_Cmd(ADC1,ENABLE);
ADC_ResetCalibration(ADC1);//对ADC进行复位
while(ADC_GetResetCalibrationStatus(ADC1));//等待ADC复位完成
ADC_StartCalibration(ADC1);//校验ADC
while(ADC_GetCalibrationStatus(ADC1));//等待ADC校验完成
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开启ADC的软件触发
}
float ADC_GetValue(void)
{
uint32_t Value = 0;
float ret = 0.0;
for(uint8_t i = 0;i < 30;i++)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));//转换结束标志, 转换完成该标志就清零
Value += ADC_GetConversionValue(ADC1);
}
Value /= 30; // 这里求的是平均值
ret = 30.303 * (3.3 - Value *(3.3/4095));
// 整体上表示模数转换后的光度;4095表示12为的ADC的精度(不能变);
// 3.3表示这个引脚最大承受电压
return ret;
}
/*这里的 ret的运算就小小的运用了一下小算法,
如果PA0不接光敏传感器,PA0引脚还是有电压的,
(光敏传感器就是一个光敏电阻收到光线的影响来改变了电阻的阻值,
从而改变了电压,所以知道PA0的电压就可以类比出此刻的光线的强度)
我们以100作为光线最强的情况,0作为光线最弱的情况。
PA0引脚所承受的最大电压是3.3V,所以我们需要将3.3V 与 100的光强进行一个类比。
所以100 除以 3.3 等于 30.303。所以3.3V里面的一个单位1对应的就是100里面的30.303
这也是为什么要乘以一个30.303的原因。
*/
main.c
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include "lightsensor.h"
#include "myusart.h"
#include "Delay.h"
#include "adc.h"
int main (void)
{
Light_Sensor_GPIOinit();
MyUsart_init();
ADC_init();
while(1)
{
printf("ADC = %.3f \n",ADC_GetValue());
Delay_ms(500);
}
}
这里延时半秒是:避免串口调试助手疯狂答应导致卡死。
实验现象:
五、光敏传感器小实验4
1· 在小实验4的基础上,用OLED显示ADC采集的数据
因为ADC采集的数据会出现小数点,所以我需要一个可以显示浮点型数据的OLED功能函数。
代码如下:
OLED.c :部分代码
/**
* @brief OLED显示一个字符
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~16
* @param Char 要显示的一个字符,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容
}
}
//OLED 显示浮点型数据
// Line : 第几行
// Colum: 第几列
// Number:需要显示的浮点数(需要把浮点数据扩大为它的整型)
void OLED_FloatNum1(uint8_t Line, uint8_t Column, uint32_t Number)
{
OLED_ShowChar(Line,Column + 0,(Number/10000) + '0');// 最高位
OLED_ShowChar(Line,Column + 1,(Number/1000%10) + '0');
OLED_ShowChar(Line,Column + 2,'.');// 固定显示
OLED_ShowChar(Line,Column + 3,(Number/100%10) + '0');
OLED_ShowChar(Line,Column + 4,(Number/10%10) + '0');
OLED_ShowChar(Line,Column + 5,(Number%10) + '0');
}
就是用这两个函数来显示浮点型数据。
ADC.c:采样功能代码,实验3这个部分和这里大致没有差别,这个代针对OLED,所以返回值会有一个小小的处理。
float ADC_GetValue(void)
{
uint32_t Value = 0;
float ret = 0.0;
for(uint8_t i = 0;i < 30;i++)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));
//转换结束标志, 转换完成该标志就清零
Value += ADC_GetConversionValue(ADC1);
}
Value /= 30; // 这里求的是平均值
ret = 30.303 * (3.3 - Value *(3.3/4095));
//整体上表示模数转换后的光度;4095表示12为的ADC的精度;100就表示最大光度。
return ret*1000;
// 先把浮点型数据给扩大为它的整型,有利于OLED的功能函数处理显示这个数据
}
总结:主要是OLED显示浮点型数据那一块费了一些些时间。
处理浮点型数据的思路:(1)刚开始我就是简单的以为把浮点型数据分为整数部分和小数部分,整数部分就经常调用显示十进制函数,小数部分就是先把小数扩大成整数,然后固定显示小数点,把扩大后的小数以整数的形式显示在小数点后面,事实证明,只能显示写死的浮点型数据,不能显示总是刷新的数据,小数部分显示不出来。
(2)在我大哥的指点下,用了他的思路:先把浮点型数据转换为整型的,然后直接传这个整型的数据,再一位一位的显示(就是取最高位到最低位,挨着取出来显示,调用显示一个字符的函数。)。
欧克,结束了,学到很多。。。。加油!!