使用STM32 在实现温湿度LCD显示并蓝牙透传

项目需求

通过温湿度传感器将值传到LCD1602,并实时通过蓝牙透传到手机。

硬件介绍

LCD1602

使用STM32 在实现温湿度LCD显示并蓝牙透传_第1张图片

是一种工业字符型液晶,能够同时显示16x02即32字符 (16列两行)

DHT11 

使用STM32 在实现温湿度LCD显示并蓝牙透传_第2张图片

DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。

HC-01

蓝牙模块。

关于以上这三个模块的使用和详细介绍可以看前面的章节,这里直接进行代码的移植。

硬件接线

LCD1602

  • D0~D7 --> A0~A7
  • VDD, A --> 5v
  • VSS, VO, K --> GND
  • RS --> B1
  • RW --> B2
  • E --> B10

DHT11

  • VCC --> 5v
  • GND --> GND
  • DATA --> B7

HC-01

  • VCC --> 5v
  • GND --> GND
  • RXD --> TX1
  • TXD --> RX1

使用STM32 在实现温湿度LCD显示并蓝牙透传_第3张图片

 

CubeMX

1. 惯例配置 + 开串口1 + GPIO口

 注意,DHT11的DATA线既作为输入也作为输出,因此不能在CubeMX中进行配置!

使用STM32 在实现温湿度LCD显示并蓝牙透传_第4张图片  

2. 开启Timer2:

计数一次经过的时间是 (PSC + 1) / Tclk , 因此如果我想要计数1微秒,即0.000001s, 已知Tclk = 72 000 000, 那么PSC就应该设置为 71。然后在main.c中就可以定义出一个实现微秒级延时的函数: 

使用STM32 在实现温湿度LCD显示并蓝牙透传_第5张图片

2. 惯例配置生成代码:

 

 

Keil

1. 打开MICRO-LIB

使用STM32 在实现温湿度LCD显示并蓝牙透传_第6张图片

2. 编写代码:

 关于上面提到的DHT的DATA引脚,由于有时需要作为输入,有时需要输出,所以不能在Cube中进行定义,而是在Keil中自己敲代码定义,可以直接参考main函数中main.c的MX_GPIO_Init()函数,经过跳转可以看到详细信息:

使用STM32 在实现温湿度LCD显示并蓝牙透传_第7张图片

 如图,需要:

  • 定义一个结构体
  • 使能时钟
  • 赋初值(这步可省略)
  • 对于结构体的成员变量进行赋值,分别是GPIO_InitStruct.Pin; GPIO_InitStruct.Mode; 
  • GPIO_InitStruct.Speed,其中GPIO_InitStruct.Mode就是需要根据情况修改的
  • 调用HAL_GPIO_Init 函数
void DHT_GPIO_Init(uint32_t Mode)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	__HAL_RCC_GPIOB_CLK_ENABLE();
	GPIO_InitStruct.Pin = GPIO_PIN_7;
	GPIO_InitStruct.Mode = Mode;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}

然后,

就可以在需要往DATA写数据的时候写:DHT_GPIO_Init(GPIO_MODE_OUTPUT_PP);

在需要从DATA读数据的时候写:DHT_GPIO_Init(GPIO_MODE_INPUT);

并且,由于89C52和STM32的硬件差别,使用STM32来驱动LCD1602时,是不需要检测BUSY信号的!!!

但是32驱动LCD的时候,在写命令或内容时,拉高E和拉低E的间隔也需要从微妙级变成毫秒级,不然无法成功驱动!!!

#include "stdio.h"

char flag_busy = 0;

char buffer;
char datas[5];
char temp[9];
char huma[9];

void TIM2_Delay_us(uint16_t n_us) //使用TIM2来做us级延时函数
{
/* 使能定时器2计数 */
	__HAL_TIM_ENABLE(&htim2);
	__HAL_TIM_SetCounter(&htim2, 0);
	while(__HAL_TIM_GetCounter(&htim2) < ((1 * n_us)-1) );
/* 关闭定时器2计数 */
	__HAL_TIM_DISABLE(&htim2);
}

void DHT_GPIO_Init(uint32_t Mode)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	__HAL_RCC_GPIOB_CLK_ENABLE();
	GPIO_InitStruct.Pin = GPIO_PIN_7;
	GPIO_InitStruct.Mode = Mode;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}


void write(char flag_w, char cmd) //write(1,cmd)代表写内容;write(0,cmd)代表写指令
{
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET); //RW全程置0
	
	if(flag_w == 1){
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); //RS置1写内容
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET); //E置0
		TIM2_Delay_us(2);
		
		GPIOA->ODR = cmd; //根据时序图,E为0时开始传内容
		
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET); //E置1
		HAL_Delay(5);
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET); //E置0
		HAL_Delay(5);
	}
	
	if(flag_w == 0){
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); //RS置0写指令
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET); //E置0
		TIM2_Delay_us(2);
		
		GPIOA->ODR = cmd; //根据时序图,E为0时开始传内容
		
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET); //E置1
		HAL_Delay(5);
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET); //E置0
		HAL_Delay(5);
	}
	
}
 

 
void Init()
{
	HAL_Delay(15);
	write(0,0x38);
	HAL_Delay(5);
	
	//detectBusy();
	write(0,0x38);
	//detectBusy();
	write(0,0x08);
	//detectBusy();
	write(0,0x01);
	//detectBusy();
	write(0,0x06);
	//detectBusy();
	write(0,0x0C);
	
}
 
void showStr(char *msg, char hang, char lie)
{
	char pos;
	
	if(hang == 0){//如果第一行
			pos = 0x80 + 0x00 + lie; 
	}else if(hang == 1){//如果第二行
			pos = 0x80 + 0x40 + lie;
	}
	//detectBusy();
	write(0,pos); //只要定下开始的位置,之后光标会自行移动
	
	while(*msg != '\0'){
		//detectBusy();
		write(1,*msg);
		msg++;
	}
		
}


void Start()
{	
	DHT_GPIO_Init(GPIO_MODE_OUTPUT_PP);//设置为输出模式!!
	
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
	HAL_Delay(30);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
	
	
	DHT_GPIO_Init(GPIO_MODE_INPUT);//设置为输入模式!!
	
	while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET);//不断读取直到DHT再次变低,说明模块响应
	while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_RESET); //不断读取直到DHT再次拉高,80us之后,再经过50us低电平,就代表要开始变高并开始传输数据了
	while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET);//不断读取直到DHT再次变低,说明数据传输前的50us低电平开始了
 
}

void showDataFromDHT()
{
	int i;//轮
	int j;//每一轮读多少次
  char tmp;
	char flag;
	
	Start();
	for(i= 0;i < 5;i++){ //5组数据
		for(j=0;j<8;j++){ //每组数据8位
			while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_RESET);//等待上拉
			TIM2_Delay_us(40);//高电平持续26-28us是‘0’,高电平持续70us是'1',所以delay40us之后观察还是否是高电平
			if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET){
				flag = 1;
				while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET);//不断读取下拉的一瞬间,因为要延迟80us,如果提前结束,下一次detect01就会出错,因为detect01是从判断上拉开始的
			}else{
				flag = 0;
			} 
			tmp = tmp << 1; //tmp左移一位,再最右侧空出一位
			tmp |= flag; //将Flag的值写入空出的一位,这样经历8次,就得到了完整的tmp,且第一个读入的数据就是最高位,符合DHT的数据传输逻辑
			//!!使用这种移位再赋值的方法可以得到的一个完整的char型数据,而不是像之前char xxx[8]一样,得到的是8个char型的字符串,不利于数据转化!!
		}
		datas[i] = tmp;
	}
	
}
 
void Build_Datas() //由于刚刚存储的是8位char型的数字,但是不管是蓝牙还是LCD,需要显示的都是字符,而比如数字“9”和字符"9"是不同的二进制表示,所以,需要转化
{
	huma[0] = 'H';
	huma[1] = ':';
	huma[2] = datas[0]/10 + 0x30; //查阅ASCII码可知,数字Y加上0x30就是字符Y
	huma[3] = datas[0]%10 + 0x30; //同时由于不管整数或小数,都使用两位数字来表示,因此需要对‘十位’和‘个位’分别进行提取再转化成字符
	huma[4] = '.';
	huma[5] = datas[1]/10 + 0x30;
	huma[6] = datas[1]%10 + 0x30;
	huma[7] = '%';
	huma[8] = '\0';
	
	temp[0] = 'T';
	temp[1] = ':';
	temp[2] = datas[2]/10 + 0x30;
	temp[3] = datas[2]%10 + 0x30;
	temp[4] = '.';
	temp[5] = datas[3]/10 + 0x30;
	temp[6] = datas[3]%10 + 0x30;
	temp[7] = 'C';
	temp[8] = '\0';
	
}

int fputc(int a, FILE *f) //一个字符一个字符发送
{
	unsigned char temp[1] = {a};
	HAL_UART_Transmit(&huart1, temp, 1, 0xffff);
	return a;
}

int main(void)
{
	
  Init(); //LCD1602初始化

  while (1)
  {
		HAL_Delay(1000);
		showDataFromDHT();	
		Build_Datas();
		
		printf(temp);
		printf("\r\n");
		printf(huma);
		printf("\r\n");
		
		showStr(temp,0,4);
		showStr(huma,1,4);
  }

}

实现效果

 

 

你可能感兴趣的:(stm32,单片机,c语言)