基于STM32F407的智慧农业系统的设计与实现(华清远见——物联网项目综合实训报告)

此篇博客用来记录一周的STM32实训,由于小编在去年就已经学习过STM32F103最小系统板,并且所有技术都已学会,由于之前没有记录过自己学习的技术,所以本博客是简单记录一下在这一次的实训中复习到的知识点(因为这一周正在准备蓝桥杯的比赛就没有拓展其他功能模块),欢迎大家交流学习

然后小编现在掌握的MCU(stm32f103c8t6,stm32f03zet6(这是去年参加湖南省物联网技能赛所使用的),stm32g431rbt6(这是今年蓝桥杯参加的时候用的板子,用hal库编程 ,之前的都是标准库)),欢迎大家一起交流学习

一、实训时间

2024 年04月8日-----2024 年04月14日

二、实训地点

2106701班,电信楼317实验室

三、项目名称

基于STM32F407的智慧农业系统的设计与实现

四、实训目的

随着物联网技术的快速发展,智慧农业已成为了现代农业发展的新方向,因此,能够设计并实现一款具有复杂工程产品特征的智慧农业系统,是即将毕业的物联网工程专业学生响应农业现代化智能化建设需求的所需要具备的基本专业素质。
本物联网项目综合实训的专业目的是基于STM32F407核心技术栈来设计并实现一套智慧农业系统。系统基于STM32F407主控板,采用多种传感和动作执行设备,利用多种物联网通信技术、协议和方法,设计实现基于"端+边+管+云+用"架构的智慧农业系统,争取打通手机、 PC 、网络、云端、边缘计算设备、终端传感与执行、智慧大屏等多个环节,使物联网工程专业即将毕业的学生
初步具备规划、设计、开发、调试、测试并最终实现一款复杂工程项目:基于STM32F407的智慧农业系统的能力。训练学生撰写初步符合国家标准规范(学术论文编写规则,GB /T7713.2-2022)的技术项目报告的能力。

实训设备及平台

实现设备:

主控:STM32F407VET8。

使用到的模块:LED呼吸灯,蜂鸣器,AT24C02(EEPROM),DHT11温湿度传感器,OLED显示屏,4路独立按键,离线调试串口,光敏电阻。

六.项目功能说明

技术栈:系统SysTick滴答计时器,通用GPIO口,EXTI外部中断,基本定时器,通用定时器,高级定时器,ADC转换,PWM控制,IIC,USART协议,Flash的存储,时钟树,RTC实时时钟。

功能:

1. 检测环境指标:能够检测环境中的温度,湿度,光照,电位器电压信息。
2.自动调节风速:当环境温度高于30度,蜂鸣器报警三声。
3.本地显示功能:能在OLED屏幕上显示温度,湿度,光照,电位器电压四种数据。
4.远端显示功能:通过串口能在上位机显示温度,湿度,光照,电位器电压四种数据。
5.数据远端保存:能将数据通过串口保存到上位机文件中。
开机关机功能:按键1控制OLED屏幕显示开机信息,系统开机。按键2控制OLED显示关机,系统关机。
6.LED灯的使用:LED3号灯闪烁,表示系统正常运行。
7.远程控制功能:串口控制OLED显示相关提示信息,串口控制,蜂鸣器(PWM警报音),LED(PWM呼吸灯)等功能。

8.远程控制功能:串口控制 OLED 显示相关提示信息,串口控制,蜂鸣器(PWM警报音),LED(PWM呼吸灯),舵机转动等功能。

主控芯片采用STM32F407VET6,传感器模块采用DHT11温湿度传感器和光敏电阻模块,实时采集空气中的温度,湿度和光照度数据。DHT11传感器模块通过单总线接口与STM32进行通信,主控芯片通过配置ADC电路采集光敏电阻一端电压,并且将三种数据实时显示到OLED屏幕上,以及通过串口传输到远端上位机。

 OLED显示屏显示相关的信息:

 光敏电阻控制电压值通过OLED显示

 串口上位机接收温湿度信息

 串口控制LED3亮灭

 

 七.硬件系统设计实现与图表

1.LED

原理图:

代码:

void LED_Init( void )
{
    //RCC_AHB1
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE );

    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10;
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStruct.GPIO_Speed = GPIO_Medium_Speed;
    GPIO_Init(GPIOE, &GPIO_InitStruct );
	GPIO_WriteBit(GPIOE,GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10,Bit_SET);
}
void LED_ON( void )
{
	GPIO_WriteBit(GPIOE,GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10,Bit_RESET);
}
void LED_OFF(void)
{
		GPIO_WriteBit(GPIOE,GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10,Bit_SET);
}

自我总结:

在使用标准库的操作的时候基本上没有什么问题,但是由于老师给我们讲解了一下关于直接操作寄存器实现控制的LED灯的实现,知道了关于对位操作的理解,以及GPIO寄存的基本了解,并且学会了看数据手册

2.KEY 

原理图:

代码:

void KEY_Init(void)
{
		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE );
		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE );

		GPIO_InitTypeDef GPIO_InitStruct;
		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
		GPIO_InitStruct.GPIO_Pin =GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6;
		GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
		GPIO_InitStruct.GPIO_Speed = GPIO_Medium_Speed;
		GPIO_Init(GPIOE, &GPIO_InitStruct );

//	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
		GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
//	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
//	GPIO_InitStruct.GPIO_Speed = GPIO_Medium_Speed;
		GPIO_Init(GPIOC, &GPIO_InitStruct );
}
char KEY_Scan(void)
{
	char key_num;
	if(GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4) == 0)
	{		
		delay_ms(5);
		key_num = 1;
	}
	if(GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_5) == 0)
		{		
		delay_ms(5);
		key_num = 2;
    }
	if(GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_6) == 0)
		{		
		delay_ms(5);
		key_num = 3;
	}
	if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) == 0)
		{		
		delay_ms(5);
		key_num = 4;
	}
	return key_num;
}

遇到的问题:时钟的开启没有注意

外部中断(四路按键)

代码:

void KEY_Interrupt(void)
{
	//GPIO时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE );
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE );

	//GPIO初始化
	KEY_Init();
	
	//外部中断线时钟初始化
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
	//GPIO映射:
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC,EXTI_PinSource13);
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource4);
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource5);
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource6);
	//外部中断外设的配置:解决中断线的问题
	EXTI_InitTypeDef EXTI_InitStruct;
	EXTI_InitStruct.EXTI_Line = EXTI_Line13|EXTI_Line4|EXTI_Line5|EXTI_Line6;
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;		//中断机制
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_Init(&EXTI_InitStruct);
	//NVIC:
	//NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStruct);
	NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn;
	NVIC_Init(&NVIC_InitStruct);
	NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn;
	NVIC_Init(&NVIC_InitStruct);
	NVIC_InitTypeDef NVIC_InitStruct1;
	NVIC_InitStruct1.NVIC_IRQChannel = EXTI9_5_IRQn;
	NVIC_InitStruct1.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct1.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct1.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStruct1);
	NVIC_InitTypeDef NVIC_InitStruct2;
	NVIC_InitStruct2.NVIC_IRQChannel = EXTI4_IRQn;
	NVIC_InitStruct2.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct2.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct2.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStruct2);
}
//按键中断服务函数:
void EXTI15_10_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line13) == 1)	//中断发生了
	{
		LED3_OFF();
		EXTI_ClearITPendingBit(EXTI_Line13);
	}		
}
void EXTI4_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line4) == 1)
	{
//		led_flag = !led_flag;
//		if(led_flag == 0) LED_OFF();
//		else LED_ON();
		LED1_ON();
		EXTI_ClearITPendingBit(EXTI_Line4);
	}		
}
void EXTI9_5_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line5) == 1)
	{
		LED3_ON();
		EXTI_ClearITPendingBit(EXTI_Line5);
	}		
	if(EXTI_GetITStatus(EXTI_Line6) == 1)
	{
		LED1_OFF();
		EXTI_ClearITPendingBit(EXTI_Line6);
	}		
}

遇到的问题:对于这个外部中断的线的选择上,一根线只能够选择一个gpio的通道,NVIC不是外设,它是属于内核的

自我总结:外部中断的原理的理解不够深刻。概念:中断源事件打断CPU的执行顺序;CPU响应,来执行中断服务函数,处理完后返回继续执行原来的工作。过程:中断请求---中断响应---中断处理---中断返回,stm32f4xx的中断源:92个----10个内核中断,82个可屏蔽中断。中断源的管理:NVIC:嵌套向量中断控制器;决定每个中断源的优先级;

 

定时器(基本定时,通用定时PWM控制LED呼吸灯,高级定时pwm控制蜂鸣器):

代码:

void Timer_Init(void)
{
	//RCC开启时钟(TIM2用APB1)
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	//选择时基单元的时钟源
	TIM_InternalClockConfig(TIM2);
	//配置时基单元
	TIM_TimeBaseInitTypeDef TIM_2;
	TIM_2.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_2.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_2.TIM_Period = 1000000 -1;
	TIM_2.TIM_Prescaler = 84-1;
	TIM_2.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TIM_2);//更新中断会置更新中断位,一上电就置1
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);	//清除标志位,让stm32定时器复位后从0开始,而不是1
	//配置输出中断控制
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);	//溢出中断、更新中断的使能
	//配置NVIC
	NVIC_InitTypeDef NVIC_2;
	NVIC_2.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_2.NVIC_IRQChannelCmd = ENABLE;
	NVIC_2.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_2.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_2);	
	//运行控制(启动定时器)
	TIM_Cmd(TIM2, ENABLE);
}

这是基本定时器的配置,如果要使用输出PWM的话还要进行新的配置,并且,通用定时器的使用

遇到的问题:当使用当使用高级定时器的时候,没有pwm输出的现象,最后发现是高级定时器比通用定时器要另外调用两个函数去设置ARR和PSC的值。

 

//这是通用定时器的代码,是PWM控制LED呼吸灯

	//GPIO:
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE );
	
	//复用:
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource10,GPIO_AF_TIM2);
	
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
		GPIO_InitStruct.GPIO_Pin =GPIO_Pin_10;
		GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
		GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
		GPIO_InitStruct.GPIO_Speed = GPIO_Medium_Speed;
    GPIO_Init(GPIOB, &GPIO_InitStruct );
		
	//RCC开启时钟(TIM2用APB1)
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
		
	//选择时基单元的时钟源
	TIM_InternalClockConfig(TIM2);
	
	//配置时基单元
	TIM_TimeBaseInitTypeDef TIM_2;
	TIM_2.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_2.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_2.TIM_Period = 1000 -1;		//ARR
	TIM_2.TIM_Prescaler = 84-1;		//PSC
	TIM_2.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TIM_2);//更新中断会置更新中断位,一上电就置1
	
	//输出比较单元
	TIM_OCInitTypeDef TIM2_CH3;
	TIM_OCStructInit(&TIM2_CH3);
	TIM2_CH3.TIM_OCMode = TIM_OCMode_PWM1; 
	TIM2_CH3.TIM_OutputState = TIM_OutputState_Enable;
	TIM2_CH3.TIM_Pulse = 0;		//CCR初始值(是比较值,CNT一直在加到最大值后清零再继续加)
	TIM2_CH3.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC3Init(TIM2, &TIM2_CH3);
	
	//使能定时器
	TIM_Cmd(TIM2, ENABLE);     
}

void pwm_set_CCR(u32 cmp)
{
	//设置占空比
	TIM_SetCompare3(TIM2, cmp);		//改变的是CCR(比较值),来改变占空比
}
//这是高级定时器,pwm控制蜂鸣器
void Timer1_CH1_Init(void)
{

	//GPIO:
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE );
	
	//复用:
	GPIO_PinAFConfig(GPIOE,GPIO_PinSource9,GPIO_AF_TIM1);
	
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_9;
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStruct.GPIO_Speed = GPIO_Medium_Speed;
    GPIO_Init(GPIOE, &GPIO_InitStruct );
		
	//RCC开启时钟(TIM1用APB2)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
		
	//选择时基单元的时钟源
	TIM_InternalClockConfig(TIM1);
	


	//配置时基单元
	TIM_TimeBaseInitTypeDef TIM_1;
	TIM_1.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_1.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_1.TIM_Period = 1000 -1;		//ARR
	TIM_1.TIM_Prescaler = 168-1;		//PSC
	TIM_1.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM1, &TIM_1);//更新中断会置更新中断位,一上电就置1
	
	//输出比较单元
	TIM_OCInitTypeDef TIM1_CH1;
	TIM_OCStructInit(&TIM1_CH1);
	TIM1_CH1.TIM_OCMode = TIM_OCMode_PWM1; 
	TIM1_CH1.TIM_OutputState = TIM_OutputState_Enable;
	TIM1_CH1.TIM_Pulse = 0;		//CCR初始值(是比较值,CNT一直在加到最大值后清零再继续加)
	TIM1_CH1.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC1Init(TIM1, &TIM1_CH1);
	
	//使能输出比较预装载(CCR):
	TIM_OC1PreloadConfig(TIM1, 0);
	//使能自动重装载的预装载寄存器允许位:
		TIM_ARRPreloadConfig(TIM1, ENABLE);
	//使能TIM1的PWM输出,TIM1和TIM8有效:
	TIM_CtrlPWMOutputs(TIM1, ENABLE);
	
	//使能定时器
	TIM_Cmd(TIM1, ENABLE);     
	
}

OLED:

原理:OLED是通过IIC协议实现的,IIC是一个通信协议,半双工的方式

 

//一些简单代码,仅作参考
//(x, y)x列y行
void OLed_Show_String(char* str, u8 x, u8 y)
{
	while(*str)
	{
		for(int i = 0; i < 2; i ++)	//确定行
		{
			Set_Position(x,y+i);
			for(int j = 0; j < 8; j ++)	//确定列
			{
				WriteOLedData(F8X16[(*str-32)*16 +j + 8*i]);
			}
		}
		x += 8;
		str ++;
	}
}
void OLed_Show_Chinese(u8 arr[], u8 x, u8 y)
{
	//while(*str)
	//{
		for(int i = 0; i < 2; i ++)
		{
			Set_Position(x, y+i);
			for(int j = 0; j < 16; j ++)
			{
				WriteOLedData(arr[j+16*i]);
			}
		}
		//str++;
	//}
}

光敏电阻(测量电压):

原理图:

//ADC123_IN0---PA0
void adc_init(void)
{
GPIO_InitTypeDef  GPIO_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;     //通用初始化结构体
ADC_InitTypeDef       ADC_InitStructure;		   //ADC外设初始化结构体

//时钟:GPIOA、ADC1
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); 

//GPIO引脚复用:因为不是作为复用功能模式(是模拟),所以不需要调用GPIO_PinAFConfig	
//GPIO初始化:模拟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;			
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;		//模拟输入
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;   //不带上下拉,引脚电平完全由外部决定
GPIO_Init(GPIOA, &GPIO_InitStructure);	


//ADC1初始化
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;	//独立模式
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//两个采样阶段之间的延迟5个时钟
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; 	  //DMA(直接存储器,一般在中断使用)失能
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;			 //4分频;ADCCLK=APB2clk/4 = 84/4=21Mhz;ADC时钟最好不超过36Mhz(容易导致结果准确度下降)
ADC_CommonInit(&ADC_CommonInitStructure);						
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;    //12位模式,表示分辨率
ADC_InitStructure.ADC_ScanConvMode = DISABLE;             //非扫描模式(扫描:对于多个通道转换使用)	
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		  //关闭连续转换
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;		//禁止触发检测,使用软件触发ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;    //转换结果右对齐;存储在16位数据寄存器中	
ADC_InitStructure.ADC_NbrOfConversion = 1;                //转换次数
ADC_Init(ADC1, &ADC_InitStructure);			
ADC_Cmd(ADC1, ENABLE);//开启ADC外设	
}

//函数功能:获得ADC一次转换的值
//参数channel: ADC通道,取值范围为0-15,表示ADC_Channel_0~ADC_Channel_15;光敏是ADC1的通道0 
//返回值:通道转换结果

uint16_t ADC_Get_Data(u8 channel)
{

	/*****函数说明:
	功能: 对ADC规则组通道配置
	参数1:ADC1外设
	参数2:ADC转换通道,通道0-18
	参数3:转换顺序(一个ADC的多个通道同时采集时,会有内部的转换机制进行分配优先顺序以及转换时间);分为序列1~序列16,数字越大优先顺序越后。
	参数4:采样/转换时间,时间越长,精度越高,但会降低转换速率
	******/


ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_480Cycles);
ADC_SoftwareStartConv(ADC1); //使能指定的ADC1的软件转换启动功能
while( !ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));  //等待转换结束,EOC标志位被置位
return ADC_GetConversionValue(ADC1);			  //返回ADC1规则组的转换结果
}


//函数功能:获取ADC多次转换的值
//参数channel: 要转换的通道,光敏是ADC1的通道0
//参数times:转换次数
//返回值:转换times次的结果平均值


double getAdcAverage(u8 channel,u8 times)
{
	u32 value=0;
	u8 i;
	for(i=0; i<times; i++)
	{
		value += ADC_Get_Data(channel);
		delay_ms(5);
	}
	return (value/times)*3.3/4096;
} 

 自我总结:关于DMA搬运可以再深入了解一下,结合ADC一起使用

 

串口(上位机接收发送信息)

uint8_t Serial_RxData;
uint8_t Serial_RxFlag;
char Serial_RxPacket[100];

void Serial_Init(void)
{
	//RCC
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	
	//复用功能:
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
	
	//GPIO TX:PA9、复用推挽输出, RX:PA10、上拉或浮空输入
	GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_9;
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	//GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStruct.GPIO_Speed = GPIO_High_Speed;
    GPIO_Init(GPIOA, &GPIO_InitStruct );
	
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_10;
	//GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStruct.GPIO_Speed = GPIO_High_Speed;
    GPIO_Init(GPIOA, &GPIO_InitStruct );
	
	//USART帧格式设置
	USART_InitTypeDef USART_1;
	USART_1.USART_BaudRate = 9600;							//波特率
	USART_1.USART_HardwareFlowControl = USART_HardwareFlowControl_None;		//硬件流控制
	USART_1.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_1.USART_Parity = USART_Parity_No;					//校验
	USART_1.USART_StopBits = USART_StopBits_1;				//停止位
	USART_1.USART_WordLength = USART_WordLength_8b;			//数据帧字长
	USART_Init(USART1, &USART_1);
	
    //接收
	//开启RXNE到NVIC的输出
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	//配置NVIC
		//优先级分组
	//NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
		//结构体初始化
	NVIC_InitTypeDef NVIC_RXNE;
	NVIC_RXNE.NVIC_IRQChannel = USART1_IRQn;
	NVIC_RXNE.NVIC_IRQChannelCmd = ENABLE;
	NVIC_RXNE.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_RXNE.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVIC_RXNE);

	//Cmd 
	USART_Cmd(USART1, ENABLE);
}
void Serial_SendByte(uint8_t Data)
{
	//发送数据
	USART_SendData(USART1, Data);		
	
	//等待发送完成
	while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) ;	
}
//3:字符串
void Serial_SendString(char *String)
{
	uint8_t i;
	for(i = 0; String[i] != 0; i ++)
	{
		Serial_SendByte(String[i]);
	}
}

void Send_String(char *str)
{
	
}

//封装标志位
uint8_t Serial_GetRxFlag(void)
{
	if(Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

//封装接收到的数据
uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;
}
u16 rx_buf[20];
u16 rx_byte[2];
u8 cnt = 0;
//接收
void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;
	static uint8_t nRxPacket = 0;
	if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
	{
		USART_ClearITPendingBit(USART1, USART_FLAG_RXNE);	//清除标志位
		uint16_t RxData = USART_ReceiveData(USART1);
		
		if(RxState == 0)	//等待包头
		{
			if(RxData == '#')
			{
				RxState = 1;
				nRxPacket = 0;
			}
		}
		else if(RxState == 1)		//接收数据
		{
			
			if(RxData == '\r')
			{
				RxState = 2;
			}
			else
			{
				Serial_RxPacket[nRxPacket] = RxData;
				nRxPacket ++;
			}
		}
		else if(RxState == 2)		//等待包尾
		{
			if(RxData == '\n')
			{
				Serial_RxPacket[nRxPacket] = '\0';
				RxState = 0;
				Serial_RxFlag = 1;
			}
		}			
		
	}
}

遇到的问题:在调用中断源的时候遇到了一些问题,就是关于中断标志位的理解不够深刻

自我总结:了解了两种串口中断接收数据和进行处理的方式,一种是使用状态机,另外一种就是定义一个接收数组一次性全部接受,但是第二种方式不能用来接收那种大量数据的模式,状态机的话就可以用来接收大量数据

 

温湿度传感器(IIC协议)

#include "delay.h"

unsigned char buf[5];	//湿度.湿度 温度.温度 校验和
unsigned char humi[2];	//湿度
unsigned char temp[2];	//温度
void DHT11_Init(void)
{
	//PA3, AHB1总线
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_A3;
	GPIO_A3.GPIO_Pin = GPIO_Pin_3;
	GPIO_A3.GPIO_Mode = GPIO_Mode_OUT;	//开漏输出有上拉电阻
	GPIO_A3.GPIO_OType = GPIO_OType_OD;
	GPIO_A3.GPIO_PuPd = GPIO_PuPd_NOPULL;	//不要上拉电阻
	GPIO_A3.GPIO_Speed = GPIO_Low_Speed;
	GPIO_Init(GPIOA, &GPIO_A3);
	GPIO_SetBits(GPIOA, GPIO_Pin_3);
}
void DHT11_InMode(void)
{
	GPIO_InitTypeDef GPIO_A3;
	GPIO_A3.GPIO_Pin = GPIO_Pin_3;
	GPIO_A3.GPIO_Mode = GPIO_Mode_IN;	//开漏输出有上拉电阻
	GPIO_A3.GPIO_Speed = GPIO_Low_Speed;
	GPIO_Init(GPIOA, &GPIO_A3);
	GPIO_SetBits(GPIOA, GPIO_Pin_3);
}

void DHT11_OutMode(void)
{
	GPIO_InitTypeDef GPIO_A3;
	GPIO_A3.GPIO_Pin = GPIO_Pin_3;
	GPIO_A3.GPIO_Mode = GPIO_Mode_OUT;	//开漏输出有上拉电阻
	GPIO_A3.GPIO_OType = GPIO_OType_OD;
	GPIO_A3.GPIO_PuPd = GPIO_PuPd_NOPULL;	//不要上拉电阻
	GPIO_A3.GPIO_Speed = GPIO_Low_Speed;
	GPIO_Init(GPIOA, &GPIO_A3);
	GPIO_SetBits(GPIOA, GPIO_Pin_3);
}
//检测响应信号;80us的低电平
//检测成功:返回0 
//检测失败:返回1
u8 DHT11_Check_Ack(void)
{
	uint16_t time=0;
	while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) && time < 200) //越过80us之前的高电平信号(发送起始信号后,电平信号不确定) 
	{ 
		time++;
		delay_us(1);   //大约一次循环1us
	}
	if(time>=200) //如果200us之后,PA3还是高电平,就返回检测响应信号失败
		return 1;
	else
		time=0;
	//此时说明数据线变成了低电平
	while(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3)  && time < 80) //越过80us低电平
	{
		time++;
		delay_us(1);	
	}
	if(time>=80)
		return 1;
	//变成高电平,开始等待接收数据,高电平交由下一次数据接收之前做处理
	return 0;   //响应成功,可以接收数据了	
}


//读取一字节数据, 50us的低电平+ '0'是26-28us高电平; '1'是70us高电平 ,根据高电平宽度判断0/1
unsigned char DHT11_ReadDataBit()
{
	unsigned char readbit = 0;
	for(int i = 0; i < 8; i ++)
	{
		readbit <<= 1;
		//越过50us低电平之前的不确定信号/高电平信号,可加超时判断
		while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3));
		//越过50us的低电平
		while(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3));
		//从机高电平(读取响应信号为高电平,则DHT11没有响应)
		if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3))
		{
			//跳过高电平30us
			delay_us(30);
		}
		//如果读取到的信号还是1,就说明发送的数据就是1
		if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3))
			readbit |= 0x01;
		else
			readbit |= 0x00;
	}
	return readbit;
}

unsigned char DHT11_ReadData(void)
{
//起始信号
	//主机输出低电平 20ms
	GPIO_ResetBits(GPIOA, GPIO_Pin_3);
	delay_ms(20);
	//主机输出高电平 30us
	GPIO_SetBits(GPIOA, GPIO_Pin_3);
	delay_us(30);
	
//等待从机的响应信号:主机发送开始信号后,可以切换到输入模式,或者输出高电平均可, 总线由上拉电阻拉高。
	//上拉电阻拉高电平(等待准备)
	while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3));	
//响应信号:
//	//从机输出低电平 
//	while(!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3));
//	//从机输出高电平 
//	while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3));
	if(DHT11_Check_Ack()==0)
	{
		//接收从机输出数据 5个字节
		for(int i = 0; i < 5; i ++)
		{
			buf[i] = DHT11_ReadDataBit();
		}
	if(buf[4] == buf[0] + buf[1] + buf[2] + buf[3])
	{
		humi[0] = buf[0];
		humi[1] = buf[1];
		temp[0] = buf[2];
		temp[1] = buf[3];
	}
		else
			return 2;   //校验失败
	}
	else
		return 1;   //响应失败
	return 0;		//检测数据成功
}

遇到的问题:对于时序的理解不够深刻,在使用IIC协议进行通信的时候注意电平的跳变是需要一个稳定的时间的。

自我总结:IIC协议的理解对未来掌握更多的传感器有很大的作用

 八、软件系统设计实现与流程图

九、调试过程中出现的问题及相应解决办法

在本次项目过程中我们出现的问题有:

1.程序代码反复核对没有出现问题但实验板没有出现实验现象,最后经过更换实验板和更换电脑进行对比发现是实验板和模块之间的连接出现接触不良的问题,更换了实验板后调试成功。

2.通信串口无法建立连接,我们通过串口调试功能,实现了串口通信。

3.温湿度数据有误,通过串口调试,实现温湿度精准读取。
4.打印输出失败通常我们可以借助于串口助手做打印输出。如果使用STM32虚拟串口,注意PC端的虚拟串口驱动程序安装正常

十、实训心得体会

1.培养了自己的动手能力
对于我们这些计算机专业的学生来说,平时的大部分任务都是在电脑上完成的。在这次实训中,我们首先感悟到了动手能力的重要。很多操作都需要实际调试,如果仅仅凭借理论掌握知识点,那么很难真正掌握STM32单片机应用开发的技能。
2.学会了自我解决问题的能力
很多时候,我们在进行实际操作时,会遇到各种各样的问题,例如程序无法编译通过、程序运行出现异常等。这些问题又不一定能在课本上找到答案。因此,在解决这些问题时,我们必须学会独立思考、自我解决问题。这样不仅让我们的STM32开发技能更加熟练,在以后的学习和工作中也能更好地解决问题。
3.感受团队协作的重要性
在项目开发中,我们需要合理地规划、分配每位小组成员的工作。这样才能达到最终的项目目标。因此,我们学会了团队协作,学会了如何在小组内分工合作、相互配合,从而使整个项目进展更为顺利。
三、总结
在STM32实训中,我们不仅仅是学习了STM32应用开发的知识,更重要的是培养了自己的动手能力、自我解决问题的能力以及团队协作能力。这些能力对我们以后的学习和工作都有着重要的帮助作用。因此,在学习中,我们不仅仅要注重理论的学习,更要注重实践的训练,这样才能使我们真正掌握知识,更好地应用于实际生活中。

  • 附:部分源代码
    int main(void)
    {
    	delay_init(168);
    	LED_Init();
    	BEEP_Init();
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    	Timer2_CH3_Init();
    	KEY_Init();
    	Serial_Init();
    	DHT11_Init();
    	IIC_init();
    	MyI2C_Init();
    	oled_init();
    	adc_init();
    	while(1)
    	{
    		//光敏:
    		adc = getAdcAverage(ADC_Channel_0,6);
    		//温湿度:
    		hum = humi[0] + humi[1]*0.1;
    		tem = temp[0] + temp[1]*0.1;
    		//eeprom:	
    		eeprom_write(0x00,humi[0]);
    		delay_ms(5);
    		h1 = eeprom_read(0x00);
    		eeprom_write(0x01,humi[1]);
    		delay_ms(5);
    		h2 = eeprom_read(0x01);
    		delay_ms(5);
    		eeprom_write(0x03,temp[0]);
    		delay_ms(5);
    		t1 = eeprom_read(0x03);
    		eeprom_write(0x04,temp[1]);
    		delay_ms(5);
    		t2 = eeprom_read(0x04);
    		delay_ms(5);
    		
    		key_proc();
    		oled_proc();
    		uart_proc();
    		pwm_proc();
    		
    		if(!DHT11_ReadData())
    		{
    			delay_ms(10);
    		}
    		
    		if(tem > tem_set)
    		{
    			
    			if(temp_flag == 0)
    			{
    			temp_flag = 1;
    			//BEEP_ON();`																																																				
    			pwm_set_CCR(1000);
    			delay_ms(100);
    			//BEEP_OFF();
    			pwm_set_CCR(0);
    			}
    		}
    		if(tem <= tem_set)
    		{
    			temp_flag = 0;
    		}
    	}
    }

    十二、参考文献

    [1]无于良波.基于LoRa的无线传感器通信系统设计与实现[D].重庆:重庆邮电大学,2020.

    [2]许晓丽,赵明涛.无线通信原理[M].北京:北京大学出版社,2014.

    [3]陈俊杰.通信有线传输的技术特点及发展分析[J].通讯世界,2019,26(04):128-129.

    [4]李旭,刘颖.物联网通信技术[M].北京清华大学出版社,2014.

    总结:由于实训这一周很忙,所以小编就没有拓展其他功能,现在实训结束,小编将这些知识点总结了一下,欢迎大家交流学习

  • 60
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值