基于Stm32和Esp8266的智能药箱设计与阿里云物联网集成

基于Stm32和Esp8266的阿里云物联网设计(智能药箱)

本系统以STM32为核心,利用温湿度传感器,光敏传感器,语音模块,物联网时钟,Wi-Fi模块Esp8266共同实现完成的。


文章目录

  • 基于Stm32和Esp8266的阿里云物联网设计(智能药箱)
  • 前言
  • 一、硬件模块
  • 二、接线
  • 1. esp8266接线
  • 2. DHT11接线
  • 3. 光敏传感器接线
  • 4. 语音模块接线
  • 5. OLED屏幕接线
  • 三、ESP8266固件烧录
  • 1. 下载固件
  • 2. 下载烧录软件
  • 3. 固件烧录
  • 4. 固件测试
  • 四、串口调试助手调试ESP8266
  • 1. 查看连接设备的MQTT参数
  • 2. 发送MQTT用户信息
  • 3. 发送MQTT ID信息
  • 4. 发送MQTT URL信息
  • 5. 发送数据到云端
  • 四、stm32串口调试ESP8266
  • 1. MQTT 参数
  • 2. MQTT 连接
  • 3. MQTT 发送
  • 4. MQTT 接收
  • 5. 完整源码
  • 五、传感器数据读取
  • 1. DHT11(单总线)
  • 2. 光敏传感器(电平输出)
  • 3. AS-PRO语音模块(电平读取)
  • 4. OLED屏幕(IIC)
  • 六、软件外设配置
  • 1. 串口配置
  • 2. 定时器配置
  • 3. 延时函数配置
  • 八、主函数编写
  • 九、语音模块配置
  • 十、Web端制作

  • 前言

    提示:这里可以添加本文要记录的大概内容:

    本系统以STM32为核心,利用温湿度传感器,光敏传感器,语音模块,物联网时钟,Wi-Fi模块Esp8266共同实现完成的。



    一、硬件模块

    1. stm32f103c8t6单片机
    2. esp8266模块
    3. DHT11
    4. 光敏传感器
    5. ASR-PRO语音模块
    6. 0.96OLED
    7. st-link,ch340串口模块、线材、面包板灯等

    二、接线

    1. esp8266接线

    esp8266模块—————————–stm32模块
    GND引脚———————————GND引脚
    3V3引脚———————————–3V3引脚
    Rx引脚————————————–A9引脚(Tx)
    Tx引脚————————————-A10引脚(Rx)

    2. DHT11接线

    DHT11模块—————————–stm32模块
    GND引脚———————————GND引脚
    VCC引脚———————————–3V3引脚
    DATA引脚———————————-B15引脚

    3. 光敏传感器接线

    DHT11模块—————————–stm32模块
    GND引脚———————————GND引脚
    VCC引脚———————————–3V3引脚
    DO引脚————————————-A0引脚

    4. 语音模块接线

    AS-PRO模块—————————–stm32模块
    GND引脚———————————GND引脚
    VCC引脚———————————–5V引脚
    A1引脚————————————-A2引脚
    A2引脚————————————-A3引脚

    外设接线
    喇叭正负对应SPK+和SPK-
    麦克风正负对应MIC+和MIC-

    烧录接线
    AS-PRO模块—————————-CH340模块
    GND引脚———————————GND引脚
    VCC引脚———————————–5V引脚
    PB5引脚————————————Rx引脚
    PB6引脚————————————Tx引脚

    具体参考链接:天问ASRPRO资料汇总

    5. OLED屏幕接线

    OLED模块—————————–stm32模块
    GND引脚———————————GND引脚
    VCC引脚———————————-3V3引脚
    SCL引脚————————————8引脚
    SDA引脚————————————9引脚

    三、ESP8266固件烧录

    参考教程链接: 开源 单片机通过ESP8266连接阿里云(1)固件烧录

    1. 下载固件

    固件链接: MQTT透传AT固件(固件号:1471)

    2. 下载烧录软件

    烧录软件链接: 烧录WiFi固件工具:ESP_DOWNLOAD_TOOL

    3. 固件烧录

    4. 固件测试

    通过串口调试助手在波特率为115200, 发送如下指令

    #设置模式
    AT+CWMODE=1
    #复位
    AT+RST
    # 连接WIFI 第一项为自己的WIFI名字 第二项为WIFI密码
    AT+CWJAP="WIFI","12345678"
    # 查看现在的时间
    AT+CIPSNTPTIME?
    AT+CIPSNTPCFG=1,8,"ntp.aliyun.com"
    

    均能正常返回OK则说明固件烧录成功

    四、串口调试助手调试ESP8266

    1. 查看连接设备的MQTT参数

    2. 发送MQTT用户信息

    # 格式: AT+MQTTUSERCFG=0,1,"NULL","username","passwd",0,0,""
    AT+MQTTUSERCFG=0,1,"NULL","ESP-xxxxxxxx","723xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",0,0,""
    

    3. 发送MQTT ID信息

    # 格式: AT+MQTTCLIENTID=0,"产品名.设备名|securemode=x\,signmethod=xxxxxxxx\,timestamp=xxxxxxxxxx|"
    # 注意需要在对应位置的逗号","前面添加一个反斜杠"\"
    AT+MQTTCLIENTID=0,"k0xxxxxx.ESP-01S|securemode=x\,signmethod=xxxxxx\,timestamp=xxxxxxxxxx|"
    

    4. 发送MQTT URL信息

    # 格式: AT+MQTTCONN=0,"mqttHostUrl",port,1
    AT+MQTTCONN=0,"iot-xxxxxxxxxx.mqtt.iothub.aliyuncs.com",1883,1
    

    均返回OK, 说明已经连接上阿里云, 在平台也可以看到设备上线

    5. 发送数据到云端

    Topic查看

    # 格式 AT+MQTTPUB=0,"/sys/产品名/设备名/thing/event/property/post","{\"params\":{\"数据标签\":数据}}",1,0
    AT+MQTTPUB=0,"/sys/k0txxxxxxx/ESP-01S/thing/event/property/post","{\"params\":{\"temperature\":25.0}}",1,0
    

    四、stm32串口调试ESP8266

    1. MQTT 参数

    #define clientId "k0xxxxx.ESP-01S|securemode=x\\,signmethod=xxxxxxx\\,timestamp=xxxxxxxxxx|"
    #define username "ESP-01S&k0xxxxxxxxxxxxxxx"
    #define passwd "72xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    #define mqttHostUrl "iot-xxxxxxxxxxxxx.mqtt.iothub.aliyuncs.com"
    #define port "1883"
    #define post "/sys/k0xxxx/ESP-01S/thing/event/property/post"
    
    #define WIFI_Name "WIFI"                   
    #define WIFI_Pass "12345678"
    

    2. MQTT 连接

    注意相比于上面串口调试助手发送,在引号内(即字符串内)嵌套的 引号以及反斜杠 均需加转义字符即"表示"和\表示\

    void ESP8266_Init(void)
    {
    	// 定义一个足够大的字符数组来存储合并后的字符串     
    	char command[140];  
    	memset(command, 0, sizeof(command));    //清空command数组
    
    	//发送WIFI模式信息
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "WIFI_MODE");
    	sprintf(command, "AT+CWMODE=1\r\n");
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	ESP8266_Init_Send(command, "OK1", "OR1");
    
    	//发送WIFI连接信息
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "WIFI_CONN");           
    	sprintf(command, "AT+CWJAP=\"%s\",\"%s\"\r\n", WIFI_Name, WIFI_Pass); 
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	ESP8266_Init_Send(command, "OK2", "OR2");
    
    	//发送MQTT连接用户信息
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "MQTT_USER");  
    	sprintf(command, "AT+MQTTUSERCFG=0,1,\"NULL\",\"%s\"\,\"%s\"\,0,0,\"\"\r\n",username,passwd);
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	ESP8266_Init_Send(command, "OK3", "OR3");
    	
    	//发送MQTT连接ID信息
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "MQTT_ID"); 
    	sprintf(command, "AT+MQTTCLIENTID=0,\"%s\"\r\n",clientId);
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	ESP8266_Init_Send(command, "OK4", "OR4");
    
    	//发送MQTT连接URL信息
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "MQTT_URL");
    	sprintf(command, "AT+MQTTCONN=0,\"%s\",%s,1\r\n",mqttHostUrl,port);
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	ESP8266_Init_Send(command, "OK5", "OR5");
    
    	//获取连接的时间服务器
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "TIME_CONN");
    	sprintf(command, "AT+CIPSNTPCFG=1,8,\"ntp.aliyun.com\"\r\n");
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	ESP8266_Init_Send(command, "OK6", "OR6");
    
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "TIME_GET");
    	Delay_s(1);
    
    }
    
    

    3. MQTT 发送

    void ESP8266_Send(char *property1, float Data1, char *property2, u8 Data2, char *property3, u8 Data3, char *property4, u8 Data4, char *property5, u8 Data5, char *property6, u8 Data6)
    {	
    	// 定义一个足够大的字符数组来存储合并后的字符串
    	char command[260];
    	memset(command, 0, sizeof(command)); 
    	// 定义消息发送格式
    	//sprintf(command, "AT+MQTTPUB=0,\"%s\",\"{\\\"params\\\":{\\\"%s\\\":%d.%d}}\",1,0\r\n", post, property, Data_int, Data_decimal);  //第四个参数设置为0表示不要响应
    	sprintf(command, "AT+MQTTPUB=0,\"%s\",\"{\\\"params\\\":{\\\"%s\\\":%.1f\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\}}\",0,0\r\n", post, property1, Data1, property2, Data2, property3, Data3, property4, Data4, property5, Data5, property6, Data6);  //第四个参数设置为0表示不要响应
    	// 发送消息
    	Serial_SendString(command);
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	// 释放内存空间
        free(command);
    
    	// 在OLED上显示OK_S
    	// OLED_ShowString(1,8,"OK_S");
    }
    

    4. MQTT 接收

    u8 ESP8266_Received(u8 *hour_set, u8 *minute_set, u8 *temp_set, u8 *humi_set, u8 *hour_set2, u8 *minute_set2)
    // void ESP8266_Received(u8 *hour_set, u8 *minute_set, u8 *second_set)
    {
    
    	char String[250];
    	memset(String, 0, sizeof(String));    //清空String数组
    	Serial_ReceiveString(String);
    	// Serial2_SendString(String);             //发送串口2给电脑看指令情况
    	//解析出属性名对应的值  例如解析出LEDSwitch的值 {"LEDSwitch":0},
    	//查找PRO在String中的位置 例如查找字符串 "LEDSwitch" 在String中的位置, 返回指向第一次出现的位置的指针 即字符串第一个字符引号的位置
    	char *params_start = strstr(String, "\"params\":{");
    
        if (params_start != NULL) {
    
    		char *hour_start = strstr(params_start, "\"hour_set\":");
            if (hour_start != NULL) {
                int hour;
                sscanf(hour_start, "\"hour_set\":%d", &hour);
    			*hour_set = hour;
            }
    
            char *minute_start = strstr(params_start, "\"minute_set\":");
            if (minute_start != NULL) {
                int minute;
                sscanf(minute_start, "\"minute_set\":%d", &minute);
    			*minute_set = minute;
            }
    
    		char *temp_set_start = strstr(params_start, "\"temp_set\":");
            if (temp_set_start != NULL) {
                int temp;
                sscanf(temp_set_start, "\"temp_set\":%d", &temp);
    			*temp_set = temp;
            }
    
    		char *humi_set_start = strstr(params_start, "\"humi_set\":");
            if (humi_set_start != NULL) {
                int humi;
                sscanf(humi_set_start, "\"humi_set\":%d", &humi);
    			*humi_set = humi;
            }
    
    		char *hour_start2 = strstr(params_start, "\"hour_set2\":");
            if (hour_start2 != NULL) {
                int hour2;
                sscanf(hour_start2, "\"hour_set2\":%d", &hour2);
    			*hour_set2 = hour2;
            }
    
            char *minute_start2 = strstr(params_start, "\"minute_set2\":");
            if (minute_start2 != NULL) {
                int minute2;
                sscanf(minute_start2, "\"minute_set2\":%d", &minute2);
    			*minute_set2 = minute2;
            }
    
    		return 1;
        }
    	return 0;
    }
    

    5. 完整源码

    1. Esp8266.h
    #ifndef __ESP8266_H
    #define __ESP8266_H
    
    /*MQTT 连接参数*/
    #define clientId "k0xxxxx.ESP-01S|securemode=x\\,signmethod=xxxxxxx\\,timestamp=xxxxxxxxxx|"
    #define username "ESP-01S&k0xxxxxxxxxxxxxxx"
    #define passwd "72xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    #define mqttHostUrl "iot-xxxxxxxxxxxxx.mqtt.iothub.aliyuncs.com"
    #define port "1883"
    #define post "/sys/k0xxxx/ESP-01S/thing/event/property/post"
    
    #define WIFI_Name "WIFI"                   
    #define WIFI_Pass "12345678"
    
    
    void ESP8266_Init_Send(char *command, char *OK, char *OR);                         //初始化发送函数
    void ESP8266_Init(void);                                                           //初始化函数
    
    void ESP8266_Time_Get(u8 *hour, u8 *minute, u8 *second);                           //获取时间函数
    
    u8 Serial_ReceiveString_Flag(void);                                                //串口接收标志位
    
    // void ESP8266_Send(char *property1, float Data1, char *property2, float Data2, char *property3, u8 Data3, char *property4, u8 Data4, char *property5, u8 Data5, char *property6, u8 Data6, char *property7, u8 Data7, char *property8, u8 Data8);     //发送函数
    
    void ESP8266_Send(char *property1, float Data1, char *property2, u8 Data2, char *property3, u8 Data3, char *property4, u8 Data4, char *property5, u8 Data5, char *property6, u8 Data6);
    
    u8 ESP8266_Received(u8 *hour_set, u8 *minute_set, u8 *temp_set, u8 *humi_set, u8 *hour_set2, u8 *minute_set2);                        //接收函数
    
    // void ESP8266_Received(u8 *hour_set, u8 *minute_set, u8 *second_set);  
    #endif
    
    
    1. Esp8266.c
    #include "stm32f10x.h"                  // Device header
    #include "Serial.h"
    #include "Esp8266.h"
    #include "Delay.h"
    #include "OLED.h"
    #include <string.h>
    
    
    
    
    /*************************
     * 函数名称:ESP8266_Init
     * 函数功能:初始化ESP8266
     * 输入参数:无
     * 输出参数:无
     * 调用示例:ESP8266_Init();
     **************************/
    void ESP8266_Init(void)
    {
    	// 定义一个足够大的字符数组来存储合并后的字符串     
    	char command[140];  
    	memset(command, 0, sizeof(command));    //清空command数组
    
    	//发送WIFI模式信息
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "WIFI_MODE");
    	sprintf(command, "AT+CWMODE=1\r\n");
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	ESP8266_Init_Send(command, "OK1", "OR1");
    
    	//发送WIFI连接信息
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "WIFI_CONN");           
    	sprintf(command, "AT+CWJAP=\"%s\",\"%s\"\r\n", WIFI_Name, WIFI_Pass); 
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	ESP8266_Init_Send(command, "OK2", "OR2");
    
    	//发送MQTT连接用户信息
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "MQTT_USER");  
    	sprintf(command, "AT+MQTTUSERCFG=0,1,\"NULL\",\"%s\"\,\"%s\"\,0,0,\"\"\r\n",username,passwd);
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	ESP8266_Init_Send(command, "OK3", "OR3");
    	
    	//发送MQTT连接ID信息
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "MQTT_ID"); 
    	sprintf(command, "AT+MQTTCLIENTID=0,\"%s\"\r\n",clientId);
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	ESP8266_Init_Send(command, "OK4", "OR4");
    
    	//发送MQTT连接URL信息
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "MQTT_URL");
    	sprintf(command, "AT+MQTTCONN=0,\"%s\",%s,1\r\n",mqttHostUrl,port);
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	ESP8266_Init_Send(command, "OK5", "OR5");
    
    	//获取连接的时间服务器
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "TIME_CONN");
    	sprintf(command, "AT+CIPSNTPCFG=1,8,\"ntp.aliyun.com\"\r\n");
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	ESP8266_Init_Send(command, "OK6", "OR6");
    
    	OLED_ShowString(3,1, "                 ");
    	OLED_ShowString(3,1, "TIME_GET");
    	Delay_s(1);
    
    }
    
    
    /*************************
     * 函数名称:ESP8266_Time_Get
     * 函数功能:ESP8266的获取网络时间
     * 输入参数:u8 *hour, u8 *minute, u8 *second  时分秒
     * 输出参数:无
     * 调用示例:ESP8266_Time_Get(&hour, &minute, &second);
     **************************/
    
    void ESP8266_Time_Get(u8 *hour, u8 *minute, u8 *second)
    {
    	//定义一个足够大的字符数组来存储合并后的字符串
    	char command[140];
    	memset(command, 0, sizeof(command));    //清空command数组
    	//获取时间
    	sprintf(command, "AT+CIPSNTPTIME?\r\n");
    	Serial_SendString(command);             //发送串口1发给ESP8266
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	//解析返回的时间
    	//定义一个足够大的字符数组来存储接收到的字符串
    	char String[170];
    	memset(String, 0, sizeof(String));    //清空String数组
    	//定义一个计数器
    	u8 i = 0;
    	while(1)
    	{
    		//等待串口接收到数据
    		while(Serial_GetRxFlag() == 0);
    		//将接收到的数据存储到String数组中
    		String[i] = Serial_GetRxData();
    		//计数器加1
    		i++;
    		//判断接收到的数据是否为OK
    		if(String[i-1] == 'K' && String[i-2] == 'O')
    		{
    			//数组末端加上换行符
    			String[i] = '\r';
    			String[i+1] = '\n';
    			// Serial2_SendString(String);             //发送串口2给电脑看指令情况
    
    			char *time_start = strstr(String, ":");
    			if (time_start == NULL) {
    				// 如果没有找到冒号,解析失败,返回
    				OLED_ShowString(3,11,"OR7");
    				break;
    			}
    			char *time_second = strstr(time_start+1, ":");
    			if (time_second == NULL) {
    				// 如果没有找到冒号,解析失败,返回
    				OLED_ShowString(3,11,"OR7");
    				break;
    			}
    
                // Serial2_SendNumber(*time_second, 2);
    
    			//解析出时间
    			char hour_str[2];
    			char minute_str[2];
    			char second_str[2];
    			memset(hour_str, 0, sizeof(hour_str));
    			memset(minute_str, 0, sizeof(minute_str));
    			memset(second_str, 0, sizeof(second_str));
    			strncpy(hour_str, time_second-2, 2);
    			strncpy(minute_str, time_second+1, 2);
    			strncpy(second_str, time_second+4, 2);
    
    			// Serial2_SendString(hour_str);
    			// Serial2_SendString(":");
    			// Serial2_SendString(minute_str);
    			// Serial2_SendString(":");
    			// Serial2_SendString(second_str);
    
    			*hour = atoi(hour_str);
    			*minute = atoi(minute_str);
    			*second = atoi(second_str);
    			
    			// Serial2_SendNumber(*hour, 2);
    			// Serial2_SendString(":");
    			// Serial2_SendNumber(*minute, 2);
    			// Serial2_SendString(":");
    			// Serial2_SendNumber(*second, 2);
    			// Serial2_SendString("\r\n");
    
    			// OLED_ShowNum(4,1,12, 2);
    			// OLED_ShowString(4,3,":");
    			// OLED_ShowNum(4,4,*minute, 2);
    			// OLED_ShowString(4,6,":");
    			// OLED_ShowNum(4,7,*second, 2);
    
    			//在OLED上显示OK
    			OLED_ShowString(3,11,"OK7");
    			//返回1
    			// return 1;
    			break;
    		}
    		//判断接收到的数据是否为RO
    		else if(String[i-1] == 'R' && String[i-2] == 'O') 
    		{	
    			//数组末端加上换行符
    			String[i] = '\r';
    			String[i+1] = '\n';
    			// Serial2_SendString(String);             //发送串口2给电脑看指令情况
    			//返回0
    			// return 0;
    			break;
    		}
    	}
    
    	
    	Delay_ms(1000);
    }
    
    /*************************
     * 函数名称:Serial_ReceiveString_Flag
     * 函数功能:对串口接收到的数据进行处理
     * 输入参数:无
     * 输出参数:u8  1/0  1表示接收到OK,0表示接收到RO
     * 调用示例:Serial_ReceiveString_Flag();
    **************************/
    u8 Serial_ReceiveString_Flag(void)
    {
    	//定义一个足够大的字符数组来存储接收到的字符串
    	char String[170];
    	memset(String, 0, sizeof(String));    //清空String数组
    	//定义一个计数器
    	u8 i = 0;
    	while(1)
    	{
    		//等待串口接收到数据
    		while(Serial_GetRxFlag() == 0);
    		//将接收到的数据存储到String数组中
    		String[i] = Serial_GetRxData();
    		//计数器加1
    		i++;
    		//判断接收到的数据是否为OK
    		if(String[i-1] == 'K' && String[i-2] == 'O')
    		{
    			//数组末端加上换行符
    			String[i] = '\r';
    			String[i+1] = '\n';
    			// Serial2_SendString(String);             //发送串口2给电脑看指令情况
    			//返回1
    			return 1;
    		}
    		//判断接收到的数据是否为RO
    		else if(String[i-1] == 'R' && String[i-2] == 'O') 
    		{	
    			//数组末端加上换行符
    			String[i] = '\r';
    			String[i+1] = '\n';
    			// Serial2_SendString(String);             //发送串口2给电脑看指令情况
    			//返回0
    			return 0;
    		}
    	}
    }
    
    /*************************
     * 函数名称:ESP8266_Init_Send
     * 函数功能:初始化ESP8266
     * 输入参数:char *command, char *OK, char *OR  指令,OK,OR
     * 输出参数:无
     * 调用示例:ESP8266_Init_Send(command, "OK1", "OR1");
     **************************/
    void ESP8266_Init_Send(char *command, char *OK, char *OR)
    {
    	//用于判断是否接收到OK的标志位
    	u8 OK_OR_flag = 0;               	
    	//发送指令
    	Serial_SendString(command);
    	//检测接收的数据是否为OK,将接收标志位赋值给OK_OR_flag
    	OK_OR_flag = Serial_ReceiveString_Flag();  
    	//判定接收的最后两个字符是不是OK,如果是
    	if (OK_OR_flag == 1)
    	{	
    		//在OLED上显示OK
    		OLED_ShowString(3,11, OK);
    	}
    	//如果不是,再发送1次指令
    	else
    	{
    		//再发送一次指令
    		Delay_ms(1000); 
    		Serial_SendString(command);
    		//再次检测接收的数据是否为OK,将接收标志位赋值给OK_OR_flag
    		OK_OR_flag = Serial_ReceiveString_Flag();
    		if (OK_OR_flag == 1)
    		{
    			//在OLED上显示OK
    			OLED_ShowString(3,11, OK);
    		}
    		else
    		{
    			//在OLED上显示OR
    			OLED_ShowString(3,11, OR);
    		}
    		
    	}
    	Delay_ms(1000); 
    }
    
    
    /*************************
     * 函数名称:ESP8266_Send
     * 函数功能:发送数据到云端
     * 输入参数:char *property1, float Data1, char *property2, float Data2, char *property3, u8 Data3, char *property4, u8 Data4, char *property5, u8 Data5, char *property6, u8 Data6, char *property7, u8 Data7, char *property8, u8 Data8
     * 输出参数:无
     * 调用示例:ESP8266_Send("temperature", 25.5, "humidity", 50.5, "light", 1, "hour_set", 12, "minute_set", 30, "second_set", 0, "temp_set", 30, "humi_set", 50);
    **************************/
    
    // void ESP8266_Send(char *property1, float Data1, char *property2, float Data2, char *property3, u8 Data3, char *property4, u8 Data4, char *property5, u8 Data5, char *property6, u8 Data6, char *property7, u8 Data7, char *property8, u8 Data8)
    // {	
    // 	// 定义一个足够大的字符数组来存储合并后的字符串
    // 	char command[270];
    // 	memset(command, 0, sizeof(command)); 
    // 	// 定义消息发送格式
    // 	//sprintf(command, "AT+MQTTPUB=0,\"%s\",\"{\\\"params\\\":{\\\"%s\\\":%d.%d}}\",1,0\r\n", post, property, Data_int, Data_decimal);  //第四个参数设置为0表示不要响应
    // 	sprintf(command, "AT+MQTTPUB=0,\"%s\",\"{\\\"params\\\":{\\\"%s\\\":%.1f\\, \\\"%s\\\":%.1f\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\}}\",0,0\r\n", post, property1, Data1, property2, Data2, property3, Data3, property4, Data4, property5, Data5, property6, Data6, property7, Data7, property8, Data8);  //第四个参数设置为0表示不要响应
    // 	// 发送消息
    // 	Serial_SendString(command);
    // 	Serial2_SendString(command);             //发送串口2给电脑看指令情况
    
    // 	// 在OLED上显示OK_S
    // 	// OLED_ShowString(1,8,"OK_S");
    // }
    
    void ESP8266_Send(char *property1, float Data1, char *property2, u8 Data2, char *property3, u8 Data3, char *property4, u8 Data4, char *property5, u8 Data5, char *property6, u8 Data6)
    {	
    	// 定义一个足够大的字符数组来存储合并后的字符串
    	char command[260];
    	memset(command, 0, sizeof(command)); 
    	// 定义消息发送格式
    	//sprintf(command, "AT+MQTTPUB=0,\"%s\",\"{\\\"params\\\":{\\\"%s\\\":%d.%d}}\",1,0\r\n", post, property, Data_int, Data_decimal);  //第四个参数设置为0表示不要响应
    	sprintf(command, "AT+MQTTPUB=0,\"%s\",\"{\\\"params\\\":{\\\"%s\\\":%.1f\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\, \\\"%s\\\":%d\\}}\",0,0\r\n", post, property1, Data1, property2, Data2, property3, Data3, property4, Data4, property5, Data5, property6, Data6);  //第四个参数设置为0表示不要响应
    	// 发送消息
    	Serial_SendString(command);
    	// Serial2_SendString(command);             //发送串口2给电脑看指令情况
    	// 释放内存空间
        free(command);
    
    	// 在OLED上显示OK_S
    	// OLED_ShowString(1,8,"OK_S");
    }
    
    /*************************
     * 函数名称:ESP8266_Received
     * 函数功能:接收云端消息 并解析数据格式
     * 输入参数:char *PRO  属性名
     * 输出参数:无
     * 调用示例:ESP8266_Received("temperature");
     * 数据格式示例: "/sys/k0twlspmAZo/ESP-01S/thing/service/property/set",99,{"method":"thing.service.property.set","id":"159665729","params":{"LEDSwitch":0},"version":"1.0.0"} 解析出LEDSwitch的值
     * **************************/ 
    // u8 ESP8266_Received(char *PRO, u8 *value)
    // {
    // 	//判断串口1中断标志位是否被设置
    // 	// if(Serial_GetRxFlag() == 1)
    // 	// {
    // 		char String[180];
    // 		memset(String, 0, sizeof(String));    //清空String数组
    // 		Serial_ReceiveString(String);
    // 		Serial2_SendString(String);             //发送串口2给电脑看指令情况
    // 		//解析出属性名对应的值  例如解析出LEDSwitch的值 {"LEDSwitch":0},
    // 		//查找PRO在String中的位置 例如查找字符串 "LEDSwitch" 在String中的位置, 返回指向第一次出现的位置的指针 即字符串第一个字符引号的位置
    // 		char *p = strstr(String, PRO);
    // 		//如果找到了
    // 		if(p != NULL)
    // 		{
    // 			//获取字符串"LEDSwitch"的长度
    // 			u8 len = strlen(PRO);
    // 			// Serial2_SendNumber(len, 2);
    // 			// Serial2_SendString("\r\n");
    // 			//查找第一个}在String中的位置
    // 			char *q = strstr(String, "}");
    // 			if(q == NULL)
    // 			{
    // 				return 0;
    // 			}
    // 			//定义一个足够大的字符数组来存储解析出的数据
    // 			char Data[20];
    // 			memset(Data, 0, sizeof(Data));                 //清空Data数组 没有这个 第一次会有bug
    // 			// Serial2_SendString(Data); 
    // 			// Serial2_SendString("\r\n");
    
    // 			//将"LEDSwitch":与"}"的之间的值存储到Data数组中
    // 			strncpy(Data, p+len+2, q-p-len-2);  // strncpy(目标数组, 源数组, 复制的长度) 
    // 			// strncpy(Data, p+len+2, 1);  // strncpy(目标数组, 源数组, 复制的长度) 
    // 			//清空OLED第四行显示
    // 			// OLED_ShowString(4,1, "                 ");
    // 			// //将Data数组中的数据字符在OLED上显示
    // 			// OLED_ShowString(4,1, Data);
    // 			// Serial2_SendString(Data); 
    // 			// Serial2_SendString("\r\n");
    
    // 			//将Data数组中的数据转化为整数
    // 			*value = atoi(Data);  
    // 			//清楚所有的字符数组
    // 			memset(String, 0, sizeof(String));
    // 			memset(Data, 0, sizeof(Data));
    // 			return 1;
    // 			// memset(Data, 0, sizeof(Data));
    // 		}
    // 		return 0;		
    // 	// }
    // }
    
    /********************
     * 函数名称:ESP8266_Received
     * 函数功能:接收云端消息 并解析数据格式
     * 输入参数:u8 *hour, u8 *minute, u8 *second 时分秒 u8 *temp_set, u8 *humi_set 温度湿度阈值
     * 输出参数:无
     * 调用示例:ESP8266_Received(&hour, &minute, &temp_set, &humi_set);
    *********************/
    u8 ESP8266_Received(u8 *hour_set, u8 *minute_set, u8 *temp_set, u8 *humi_set, u8 *hour_set2, u8 *minute_set2)
    // void ESP8266_Received(u8 *hour_set, u8 *minute_set, u8 *second_set)
    {
    
    	char String[250];
    	memset(String, 0, sizeof(String));    //清空String数组
    	Serial_ReceiveString(String);
    	// Serial2_SendString(String);             //发送串口2给电脑看指令情况
    	//解析出属性名对应的值  例如解析出LEDSwitch的值 {"LEDSwitch":0},
    	//查找PRO在String中的位置 例如查找字符串 "LEDSwitch" 在String中的位置, 返回指向第一次出现的位置的指针 即字符串第一个字符引号的位置
    	char *params_start = strstr(String, "\"params\":{");
    
        if (params_start != NULL) {
    
    		char *hour_start = strstr(params_start, "\"hour_set\":");
            if (hour_start != NULL) {
                int hour;
                sscanf(hour_start, "\"hour_set\":%d", &hour);
    			*hour_set = hour;
            }
    
            char *minute_start = strstr(params_start, "\"minute_set\":");
            if (minute_start != NULL) {
                int minute;
                sscanf(minute_start, "\"minute_set\":%d", &minute);
    			*minute_set = minute;
            }
    
    		char *temp_set_start = strstr(params_start, "\"temp_set\":");
            if (temp_set_start != NULL) {
                int temp;
                sscanf(temp_set_start, "\"temp_set\":%d", &temp);
    			*temp_set = temp;
            }
    
    		char *humi_set_start = strstr(params_start, "\"humi_set\":");
            if (humi_set_start != NULL) {
                int humi;
                sscanf(humi_set_start, "\"humi_set\":%d", &humi);
    			*humi_set = humi;
            }
    
    		char *hour_start2 = strstr(params_start, "\"hour_set2\":");
            if (hour_start2 != NULL) {
                int hour2;
                sscanf(hour_start2, "\"hour_set2\":%d", &hour2);
    			*hour_set2 = hour2;
            }
    
            char *minute_start2 = strstr(params_start, "\"minute_set2\":");
            if (minute_start2 != NULL) {
                int minute2;
                sscanf(minute_start2, "\"minute_set2\":%d", &minute2);
    			*minute_set2 = minute2;
            }
    
    		return 1;
        }
    	return 0;
    
    }
    
    

    五、传感器数据读取

    部分代码参考链接 江协科技STM32入门教程-2023版 细致讲解 中文字幕

    1. DHT11(单总线)

    1. DHT11.h
    #ifndef __DHT11_H
    #define __DHT11_H
    
    // 定义DHT11的数据引脚
    #define DHT11_RCC       RCC_APB2Periph_GPIOB
    #define DHT11_IO        GPIOB
    #define DHT11_PIN       GPIO_Pin_15
    
    // 函数声明
    u8 DHT11_Init(void);       // 初始化DHT11
    u8 DHT11_Read_Data(float *temp, float *humi);   // 读取DHT11数据
    // u8 DHT11_Read_Data(u8 *temp, u8 *humi);   // 读取DHT11数据
    
    #endif
    
    1. DHT11.c
    #include "stm32f10x.h"                 // Device header
    #include "Delay.h"                     // 时间延时函数
    #include "DHT11.h"                     // DHT11头文件
    #include <string.h>
    
    
    static void GPIO_SETOUT(void);          // 定义一个静态函数GPIO_SETOUT 作用是设置为输出模式
    static void DHT11_Rst(void);            // 定义一个静态函数DHT11_Rst 作用是主机发送开始信号
    static void GPIO_SETIN(void);           // 定义一个静态函数GPIO_SETIN 作用是设置为输入模式
    static u8 DHT11_Check(void);            // 定义一个静态函数DHT11_Check 作用是检测DHT11的存在
    
    
    /******************
     * 函数名称:DHT11_SETOUT
     * 函数功能:设置为输出模式
     * 输入参数:无
     * 输出参数:无
     * 调用示例:DHT11_SETOUT();
    *******************/
    static void GPIO_SETOUT(void)
    {
        GPIO_InitTypeDef GPIO_InitStructure;   // 定义一个GPIO_InitTypeDef类型的结构体变量GPIO_InitStructure
        GPIO_InitStructure.GPIO_Pin = DHT11_PIN;     // 设置DHT11的IO口
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  // 设置为推挽输出
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置IO口速度为10MHz
        GPIO_Init(DHT11_IO, &GPIO_InitStructure);         // 初始化DHT11的IO口
    }
    
    /******************
     * 函数名称:DHT11_SETIN
     * 函数功能:设置为输入模式
     * 输入参数:无
     * 输出参数:无
     * 调用示例:DHT11_SETIN();
    *******************/
    static void GPIO_SETIN(void)
    {
        GPIO_InitTypeDef GPIO_InitStructure;   // 定义一个GPIO_InitTypeDef类型的结构体变量GPIO_InitStructure
        GPIO_InitStructure.GPIO_Pin = DHT11_PIN;     // 设置DHT11的IO口
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;     // 设置为浮空输入
        GPIO_Init(DHT11_IO, &GPIO_InitStructure);                 // 初始化DHT11的IO口
    }
    
    /******************
     * 函数名称:DHT11_Rst
     * 函数功能:主机发送开始信号
     * 输入参数:无
     * 输出参数:无
     * 调用示例:DHT11_Rst();
     * ****************/
    static void DHT11_Rst(void)   // static 说明作用域在DHT11.c文件内
    {
        GPIO_SETOUT();             // 设置为输出模式
        // 主机拉低电平20ms
        GPIO_ResetBits(DHT11_IO, DHT11_PIN);  // 主机拉低DHT11的IO口
        Delay_ms(20);              // 延时20ms
        // 主机拉高电平30us
        GPIO_SetBits(DHT11_IO, DHT11_PIN);    // 主机拉高DHT11的IO口
        Delay_us(30);              // 延时30us
    }
    
    /******************
     * 函数名称:DHT11_Check
     * 函数功能:检测DHT11的存在
     * 输入参数:无
     * 输出参数:u8 返回值1表示存在,0表示不存在
     * 调用示例:DHT11_Check();
    ******************/
    static u8 DHT11_Check(void)
    {
        u8 retry = 0;  // 定义一个重试次数
        GPIO_SETIN();  // 设置为输入模式
        // 首先DHT11拉低电平80us 
        // 这里检测如果一直是高电平且超过100us 判定为没有响应
        while ((GPIO_ReadInputDataBit(DHT11_IO, DHT11_PIN) == 1) && retry < 100)  // 当DHT11的IO口为高电平时 且 重试次数小于100
        {
            retry++;          // 重试次数加1
            Delay_us(1);      // 延时1us
        }
    
        if (retry >= 100)     // 如果重试次数大于等于100
        {
            return 0;         // 返回0
        }
        
        else
        {
            retry = 0;        // 重试次数清零
        }
        // 接着DHT11拉高电平80us 
        // 这里检测如果一直是低电平且超过100us 判定为没有响应
        while ((GPIO_ReadInputDataBit(DHT11_IO, DHT11_PIN) == 0) && retry < 100)  // 当DHT11的IO口为低电平时 且 重试次数小于100
        {
            retry++;      // 重试次数加1
            Delay_us(1);  // 延时1us
        }
    
        if (retry >= 100) // 如果重试次数大于等于100
        {
            return 0;     // 返回0
        }
        
        return 1;  // 返回1
    }
    
    /******************
     * 函数名称:DHT11_Init
     * 函数功能:初始化DHT11
     * 输入参数:无
     * 输出参数:u8 返回值1表示初始化成功,0表示初始化失败
     * 调用示例:DHT11_Init();
    *******************/
    u8 DHT11_Init(void)
    {
        RCC_APB2PeriphClockCmd(DHT11_RCC, ENABLE);  // 使能DHT11的时钟
    
        DHT11_Rst();               // 主机发送开始信号
    
        return DHT11_Check();      // 返回检测结果
    }
    
    /******************
     * 函数名称:static u8 DHT11_ReadBit(void)
     * 函数功能:读取一个位
     * 输入参数:无
     * 输出参数:u8 返回值1表示读取到1,0表示读取到0
     * 调用示例:DHT11_ReadBit();
    ******************/
    static u8 DHT11_ReadBit(void)
    {
        u8 retry = 0;          // 定义一个重试次数
    
        // DHT11拉低电平50us 
        // 这里检测如果一直是高电平且超过100us 退出循环
        while ((GPIO_ReadInputDataBit(DHT11_IO, DHT11_PIN) == 1) && retry < 100)  // 当DHT11的IO口为高电平时 且 重试次数小于100
        {
            retry++;           // 重试次数加1
            Delay_us(1);      // 延时1us
        }
    
        retry = 0;          // 重试次数清零
    
        // 接着DHT11拉高电平26-28us或者70us 
        // 这里检测如果一直是低电平且超过100us 退出循环
        while ((GPIO_ReadInputDataBit(DHT11_IO, DHT11_PIN) == 0) && retry < 100)  // 当DHT11的IO口为低电平时 且 重试次数小于100
        {
            retry++;      // 重试次数加1
            Delay_us(1);  // 延时1us
        }
    
        // 0信号为26-28us,1信号则为70us 相当于高电平的第40us时候的判断电平所处状态
        Delay_us(40);    // 延时40us
        if ((GPIO_ReadInputDataBit(DHT11_IO, DHT11_PIN) == 1))  // 如果DHT11的IO口为高电平
        {
            return 1;  // 返回1
        }
        else
        {
            return 0;  // 返回0
        }
    }
    
    /******************
     * 函数名称:static u8 DHT11_ReadByte(void)
     * 函数功能:读取一个字节
     * 输入参数:无
     * 输出参数:u8 返回值为读取到的字节
     * 调用示例:DHT11_ReadByte();
    *******************/
    static u8 DHT11_ReadByte(void)
    {
        u8 i, dat = 0;  // 定义一个变量i和dat
    
        for (i = 0; i < 8; i++)  // 循环8次
        {
            dat <<= 1;           // dat左移1位
            dat |= DHT11_ReadBit();  // dat或上读取到的位
     }
        return dat;  // 返回dat
    }
    
    /******************
     * 函数名称:DHT11_Read_Data
     * 函数功能:读取DHT11的数据
     * 输入参数:float *temp 温度指针,float *humi 湿度指针
     * 输出参数:u8 返回值1表示读取成功,0表示读取失败
     * 调用示例:DHT11_ReadData(&temp,&humi);如何调用
    *******************/
    
    u8 DHT11_Read_Data(float *temp, float *humi)
    {
        u8 buf[5];  // 定义一个长度为5的数组buf
        memset(buf, 0, sizeof(buf));
        u8 i;       // 定义一个变量i
    
        DHT11_Rst();  // 主机发送开始信号
        if (DHT11_Check() == 1)  // 如果检测DHT11存在
        {
            for (i = 0; i < 5; i++)  // 循环5次
            {
                buf[i] = DHT11_ReadByte();  // 读取一个字节
            }
    
            if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])  // 如果前4个字节的和等于第5个字节 说明数据正确
            {
                *humi = buf[0] + buf[1] / 10.0;  // 湿度等于第一个字节加上第二个字节除以10 
                *temp = buf[2] + buf[3] / 10.0;  // 温度等于第三个字节加上第四个字节除以10
                // *humi = buf[0];				//将湿度值放入指针humi
                // *temp = buf[2];				//将温度值放入指针temp
                memset(buf, 0, sizeof(buf));      // 清空buf
                return 1;  // 返回1
            }
        }
        else
        {
            memset(buf, 0, sizeof(buf));  // 清空buf
            return 0;  // 返回0
        }
    }
    
    

    2. 光敏传感器(电平输出)

    1. Light.h
    #ifndef __LIGHT_H
    #define __LIGHT_H
    
    #include "stm32f10x.h"                  // Device header
    
    void Light_Init(void);
    u8 Light_GetNum(void);
    
    #endif
    
    
    1. Light.c
    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "Light.h"
    
    void Light_Init(void)
    {
    	//使用GPIOA的第0引脚
    	GPIO_InitTypeDef GPIO_InitStructure;
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);	
    }
    
    u8 Light_GetNum(void)
    {
    	uint8_t State = 0;
    	if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0)
    	{
    		State = 1;
    	}
    	if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 1)
    	{
    		State = 0;
    	}
    	return State;
    }
    

    3. AS-PRO语音模块(电平读取)

    1. ALARM.h
    #ifndef __ALARM_H
    #define __ALARM_H
    
    void Alarm_Init(void);
    void Alarm1_ON(void);
    void Alarm1_OFF(void);
    void Alarm1_Turn(void);
    void Alarm2_ON(void);
    void Alarm2_OFF(void);
    void Alarm2_Turn(void);
    
    #endif
    
    
    1. ALARM.c
    #include "stm32f10x.h"                  // Device header
    
    void Alarm_Init(void)
    {
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;            
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	GPIO_ResetBits(GPIOA, GPIO_Pin_2|GPIO_Pin_3);
    }
    
    void Alarm1_ON(void)
    {
    	GPIO_SetBits(GPIOA, GPIO_Pin_2);
    	
    }
    
    void Alarm1_OFF(void)
    {
    	GPIO_ResetBits(GPIOA, GPIO_Pin_2);// 低电平触发
    }
    
    void Alarm1_Turn(void)
    {
    	if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2) == 0)
    	{
    		GPIO_SetBits(GPIOA, GPIO_Pin_2);
    	}
    	else
    	{
    		GPIO_ResetBits(GPIOA, GPIO_Pin_2);
    	}
    }
    
    void Alarm2_ON(void)
    {
    	GPIO_SetBits(GPIOA, GPIO_Pin_3);
    	
    }
    
    void Alarm2_OFF(void)
    {
    	GPIO_ResetBits(GPIOA, GPIO_Pin_3);
    }
    
    void Alarm2_Turn(void)
    {
    	if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) == 0)
    	{
    		GPIO_SetBits(GPIOA, GPIO_Pin_3);
    	}
    	else
    	{
    		GPIO_ResetBits(GPIOA, GPIO_Pin_3);
    	}
    }
    

    4. OLED屏幕(IIC)

    1. OLED.h
    #ifndef __OLED_H
    #define __OLED_H
    
    void OLED_Init(void);
    void OLED_Clear(void);
    void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
    void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
    void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
    void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
    void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
    void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
    
    #endif
    
    
    1. OLED.c
    #include "stm32f10x.h"
    #include "OLED_Font.h"
    
    /*引脚配置*/
    #define OLED_W_SCL(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
    #define OLED_W_SDA(x)		GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
    
    /*引脚初始化*/
    void OLED_I2C_Init(void)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
     	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
     	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	
    	OLED_W_SCL(1);
    	OLED_W_SDA(1);
    }
    
    /**
      * @brief  I2C开始
      * @param  无
      * @retval 无
      */
    void OLED_I2C_Start(void)
    {
    	OLED_W_SDA(1);
    	OLED_W_SCL(1);
    	OLED_W_SDA(0);
    	OLED_W_SCL(0);
    }
    
    /**
      * @brief  I2C停止
      * @param  无
      * @retval 无
      */
    void OLED_I2C_Stop(void)
    {
    	OLED_W_SDA(0);
    	OLED_W_SCL(1);
    	OLED_W_SDA(1);
    }
    
    /**
      * @brief  I2C发送一个字节
      * @param  Byte 要发送的一个字节
      * @retval 无
      */
    void OLED_I2C_SendByte(uint8_t Byte)
    {
    	uint8_t i;
    	for (i = 0; i < 8; i++)
    	{
    		OLED_W_SDA(Byte & (0x80 >> i));
    		OLED_W_SCL(1);
    		OLED_W_SCL(0);
    	}
    	OLED_W_SCL(1);	//额外的一个时钟,不处理应答信号
    	OLED_W_SCL(0);
    }
    
    /**
      * @brief  OLED写命令
      * @param  Command 要写入的命令
      * @retval 无
      */
    void OLED_WriteCommand(uint8_t Command)
    {
    	OLED_I2C_Start();
    	OLED_I2C_SendByte(0x78);		//从机地址
    	OLED_I2C_SendByte(0x00);		//写命令
    	OLED_I2C_SendByte(Command); 
    	OLED_I2C_Stop();
    }
    
    /**
      * @brief  OLED写数据
      * @param  Data 要写入的数据
      * @retval 无
      */
    void OLED_WriteData(uint8_t Data)
    {
    	OLED_I2C_Start();
    	OLED_I2C_SendByte(0x78);		//从机地址
    	OLED_I2C_SendByte(0x40);		//写数据
    	OLED_I2C_SendByte(Data);
    	OLED_I2C_Stop();
    }
    
    /**
      * @brief  OLED设置光标位置
      * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
      * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
      * @retval 无
      */
    void OLED_SetCursor(uint8_t Y, uint8_t X)
    {
    	OLED_WriteCommand(0xB0 | Y);					//设置Y位置
    	OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
    	OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
    }
    
    /**
      * @brief  OLED清屏
      * @param  无
      * @retval 无
      */
    void OLED_Clear(void)
    {  
    	uint8_t i, j;
    	for (j = 0; j < 8; j++)
    	{
    		OLED_SetCursor(j, 0);
    		for(i = 0; i < 128; i++)
    		{
    			OLED_WriteData(0x00);
    		}
    	}
    }
    
    /**
      * @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]);		//显示下半部分内容
    	}
    }
    
    /**
      * @brief  OLED显示字符串
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  String 要显示的字符串,范围:ASCII可见字符
      * @retval 无
      */
    void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
    {
    	uint8_t i;
    	for (i = 0; String[i] != '\0'; i++)
    	{
    		OLED_ShowChar(Line, Column + i, String[i]);
    	}
    }
    
    /**
      * @brief  OLED次方函数
      * @retval 返回值等于X的Y次方
      */
    uint32_t OLED_Pow(uint32_t X, uint32_t Y)
    {
    	uint32_t Result = 1;
    	while (Y--)
    	{
    		Result *= X;
    	}
    	return Result;
    }
    
    /**
      * @brief  OLED显示数字(十进制,正数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~4294967295
      * @param  Length 要显示数字的长度,范围:1~10
      * @retval 无
      */
    void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	for (i = 0; i < Length; i++)							
    	{
    		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
    	}
    }
    
    /**
      * @brief  OLED显示数字(十进制,带符号数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:-2147483648~2147483647
      * @param  Length 要显示数字的长度,范围:1~10
      * @retval 无
      */
    void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	uint32_t Number1;
    	if (Number >= 0)
    	{
    		OLED_ShowChar(Line, Column, '+');
    		Number1 = Number;
    	}
    	else
    	{
    		OLED_ShowChar(Line, Column, '-');
    		Number1 = -Number;
    	}
    	for (i = 0; i < Length; i++)							
    	{
    		OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
    	}
    }
    
    /**
      * @brief  OLED显示数字(十六进制,正数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
      * @param  Length 要显示数字的长度,范围:1~8
      * @retval 无
      */
    void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
    	uint8_t i, SingleNumber;
    	for (i = 0; i < Length; i++)							
    	{
    		SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
    		if (SingleNumber < 10)
    		{
    			OLED_ShowChar(Line, Column + i, SingleNumber + '0');
    		}
    		else
    		{
    			OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
    		}
    	}
    }
    
    /**
      * @brief  OLED显示数字(二进制,正数)
      * @param  Line 起始行位置,范围:1~4
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
      * @param  Length 要显示数字的长度,范围:1~16
      * @retval 无
      */
    void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	for (i = 0; i < Length; i++)							
    	{
    		OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
    	}
    }
    
    /**
      * @brief  OLED初始化
      * @param  无
      * @retval 无
      */
    void OLED_Init(void)
    {
    	uint32_t i, j;
    	
    	for (i = 0; i < 1000; i++)			//上电延时
    	{
    		for (j = 0; j < 1000; j++);
    	}
    	
    	OLED_I2C_Init();			//端口初始化
    	
    	OLED_WriteCommand(0xAE);	//关闭显示
    	
    	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
    	OLED_WriteCommand(0x80);
    	
    	OLED_WriteCommand(0xA8);	//设置多路复用率
    	OLED_WriteCommand(0x3F);
    	
    	OLED_WriteCommand(0xD3);	//设置显示偏移
    	OLED_WriteCommand(0x00);
    	
    	OLED_WriteCommand(0x40);	//设置显示开始行
    	
    	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
    	
    	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置
    
    	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
    	OLED_WriteCommand(0x12);
    	
    	OLED_WriteCommand(0x81);	//设置对比度控制
    	OLED_WriteCommand(0xCF);
    
    	OLED_WriteCommand(0xD9);	//设置预充电周期
    	OLED_WriteCommand(0xF1);
    
    	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
    	OLED_WriteCommand(0x30);
    
    	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭
    
    	OLED_WriteCommand(0xA6);	//设置正常/倒转显示
    
    	OLED_WriteCommand(0x8D);	//设置充电泵
    	OLED_WriteCommand(0x14);
    
    	OLED_WriteCommand(0xAF);	//开启显示
    		
    	OLED_Clear();				//OLED清屏
    }
    

    六、软件外设配置

    1. 串口配置

    1. Serial.h
    #ifndef __SERIAL_H
    #define __SERIAL_H
    
    #include <stdio.h>
    
    void Serial_Init(void);
    void Serial2_Init(void);
    void Serial_SendByte(uint8_t Byte);
    void Serial2_SendByte(uint8_t Byte);
    void Serial_SendArray(uint8_t *Array, uint16_t Length);
    void Serial2_SendArray(uint8_t *Array, uint16_t Length);
    void Serial_SendString(char *String);
    void Serial2_SendString(char *String);
    void Serial_SendNumber(uint32_t Number, uint8_t Length);
    void Serial2_SendNumber(uint32_t Number, uint8_t Length);
    void Serial_Printf(char *format, ...);
    void Serial2_Printf(char *format, ...);
    
    void Serial_ReceiveString(char *String);
    
    uint8_t Serial_GetRxFlag(void);
    uint8_t Serial_GetRxData(void);
    
    
    #endif
    
    
    1. Serial.c
    #include "stm32f10x.h"                  // Device header
    #include <stdio.h>
    #include <stdarg.h>
    #include "timer.h"
    
    uint8_t Serial_RxData;
    uint8_t Serial_RxFlag;
    
    
    void Serial_Init(void)
    {
    	//开启USART1时钟 在APB2总线上
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    	//开启GPIOA时钟 在APB2总线上
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	
    	//配置A9为复用推挽输出 作为TX
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	//配置A10为浮空输入 作为RX
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//浮空输入
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	//配置USART1参数
    	USART_InitTypeDef USART_InitStructure;
    	USART_InitStructure.USART_BaudRate = 115200;
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不使用流控
    	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //配置 USART 为同时支持发送和接收数据
    	USART_InitStructure.USART_Parity = USART_Parity_No;             //不进行数据奇偶校验
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;          //一个停止位
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b;     //不用校验所以选择8位
    	USART_Init(USART1, &USART_InitStructure);                       //初始化USART1
    	
    	//使能USART1接收中断 (USART_IT_RXNE 表示接收寄存器非空中断)。
    	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    	
    	配置NVIC 配置中断优先级分组为Group 2。这是一种设置中断优先级的方式。
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    	
    	NVIC_InitTypeDef NVIC_InitStructure;                     
    	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;        //指定中断通道为USART1串口中断
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			 //启动外部通道
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//配置中断抢占优先级为1。
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;       //配置中断子优先级为1。
    	NVIC_Init(&NVIC_InitStructure);
    	
    	USART_Cmd(USART1, ENABLE);
    }
    
    // 串口2初始化
    void Serial2_Init(void)
    {
    	// Enable USART2 clock on APB1 bus
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    	// Enable GPIOA clock on APB2 bus
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    	// Configure GPIOA pins for USART2
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // Alternate function push-pull
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;       // TX pin
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // Input floating
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;     // RX pin
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    	// Configure USART2 parameters
    	USART_InitTypeDef USART_InitStructure;
    	USART_InitStructure.USART_BaudRate = 115200;
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // No flow control
    	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;                // Enable both transmission and reception
    	USART_InitStructure.USART_Parity = USART_Parity_No;                            // No parity check
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;                         // 1 stop bit
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b;                    // 8 data bits
    	USART_Init(USART2, &USART_InitStructure);                                      // Initialize USART2
    
    	// Enable USART2 receive interrupt (USART_IT_RXNE indicates non-empty receive register interrupt)
    	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
    
    	// Configure NVIC with interrupt priority group 2
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    	NVIC_InitTypeDef NVIC_InitStructure;
    	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;        // Specify USART2 interrupt channel
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;          // Enable external channel
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // Configure preemption priority as 1
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;        // Configure sub priority as 1
    	NVIC_Init(&NVIC_InitStructure);
    
    	USART_Cmd(USART2, ENABLE);
    }
    
    
    //串口1发送字
    void Serial_SendByte(uint8_t Byte) 
    {
    	USART_SendData(USART1, Byte);
    	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//1则清零完成
    	
    }
    
    //串口2发送字
    void Serial2_SendByte(uint8_t Byte) 
    {
    	USART_SendData(USART2, Byte);
    	while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    }
    
    //串口1发送数组
    void Serial_SendArray(uint8_t *Array, uint16_t Length)
    {
    	uint16_t i;
    	for(i = 0; i < Length; i++)
    	{
    		Serial_SendByte(Array[i]);
    	}
    }
    
    //串口2发送数组
    void Serial2_SendArray(uint8_t *Array, uint16_t Length)
    {
    	uint16_t i;
    	for(i = 0; i < Length; i++)
    	{
    		Serial2_SendByte(Array[i]);
    	}
    }
    
    //串口1发送字符串
    void Serial_SendString(char *String)
    {
    	uint8_t i;
    	for(i = 0; String[i] != '\0'; i++)//0->'\0'
    	{
    		Serial_SendByte(String[i]);
    	}
    }	
    
    //串口2发送字符串
    void Serial2_SendString(char *String)
    {
    	uint8_t i;
    	for(i = 0; String[i] != '\0'; i++)
    	{
    		Serial2_SendByte(String[i]);
    	}
    }
    
    //计算X的Y次方
    uint32_t Serial_Pow(uint32_t X, uint32_t Y)
    {
    	uint32_t Result = 1;
    	while(Y--)
    	{
    		Result *= X;
    	}
    	return Result;
    }
    
    //串口1发送数字字符
    void Serial_SendNumber(uint32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	for(i = 0; i<Length; i++)
    	{
    		Serial_SendByte(Number / Serial_Pow(10, Length-i-1)%10 + '0');
    	}
    }
    
    //串口2发送数字字符
    void Serial2_SendNumber(uint32_t Number, uint8_t Length)
    {
    	uint8_t i;
    	for(i = 0; i<Length; i++)
    	{
    		Serial2_SendByte(Number / Serial_Pow(10, Length-i-1)%10 + '0');
    	}
    }
    
    //重定向printf到串口
    int fputc(int ch, FILE *f) 
    {
    	Serial_SendByte(ch);
    	return ch;
    }
    
    //串口1打印
    void Serial_Printf(char *format, ...)
    {
    	char String[100];
    	va_list arg;
    	va_start(arg, format);
    	vsprintf(String, format, arg);
    	va_end(arg);
    	Serial_SendString(String);
    }
    
    void Serial2_Printf(char *format, ...)
    {
    	char String[100];
    	va_list arg;
    	va_start(arg, format);
    	vsprintf(String, format, arg);
    	va_end(arg);
    	Serial2_SendString(String);
    }
    
    //用于检查串口接收标志位是否被设置。
    //如果 Serial_RxFlag 为1(已经接收到数据),它将清零标志位并返回1;
    //否则,返回0。
    uint8_t Serial_GetRxFlag(void)
    {
    	if(Serial_RxFlag==1)
    	{
    		Serial_RxFlag=0;
    		return 1;
    	}
    	return 0;
    }
    
    //用于获取串口接收到的数据。字节将被存储在 Serial_RxData 中。
    uint8_t Serial_GetRxData(void)
    {
    	return Serial_RxData;
    }
    
    //一个串口接收字符串的函数,收到\r\n符号就停止接收,并返回接收的字符串。
    //串口接收字符串
    u8 time_out_rx = 0;
    
    void Serial_ReceiveString(char *String)
    {
    	uint8_t i = 0;
    
    	//启动定时器1
    	TIM_Cmd(TIM1, ENABLE);
    	//清除定时器1的更新中断标志位	
    	TIM_ClearFlag(TIM1, TIM_FLAG_Update);
    
    	while(1)
    	{
    		
    		while(Serial_GetRxFlag() == 0 && time_out_rx == 0);          //等待接收到数据
    		if(time_out_rx == 1)                                        //如果串口接收超时
    		{
    			time_out_rx = 0;                                       //清除串口接收超时标志位
    			TIM_Cmd(TIM1, DISABLE);                                //关闭定时器1
    			break;                                              //返回0
    		}
    		String[i] = Serial_GetRxData();          //将接收到的数据存储在String中
    		i++;
    		if(String[i-1] == '\n' && String[i-2] == '\r')
    		{
    			String[i] = '\0';
    			time_out_rx = 0;                                       //清除串口接收超时标志位
    			TIM_Cmd(TIM1, DISABLE);                                //关闭定时器1
    			break;
    		}
    	}
    }
    //如何调用这个函数?
    // char String[100];
    // Serial_ReceiveString(String);
    
    
    //串口1中断处理函数,运行机制是串口接收到数据时,会触发接收中断
    //当USART1串口接收到数据时,会触发接收中断。
    void USART1_IRQHandler(void)
    {
    	if(USART_GetFlagStatus(USART1, USART_IT_RXNE) == SET)//检查接收中断标志位 收到数据 标志位为1
    	{
    		//如果接收到数据,它将数据存储在 Serial_RxData 中
    		Serial_RxData = USART_ReceiveData(USART1);//数据转存
    		//Serial_RxFlag 设置为1,表示已经接收到数据
    		Serial_RxFlag = 1;
    		//清除接收中断标志位。这一步是可选的,因为读取数据后,标志位会自动清零。
    		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    	}
    }
    
    // //定时器1中断服务函数 用于串口接收超时
    void TIM1_UP_IRQHandler(void)
    {
    	if (TIM_GetITStatus(TIM1, TIM_IT_Update) == SET)          //检查TIM1的更新中断是否发生
    	{
    		
    		time_out_rx = 1;                              //设置串口接收标志位 1s超时
    		//清除TIM1的更新中断标志位
    		TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
    	}
    }
    

    2. 定时器配置

    1. Timer.h
    #ifndef __TIMER_H
    #define __TIMER_H
    
    void Timer1_Init(void);
    void Timer2_Init(void);
    
    #endif
    
    
    1. Timer.c
    #include "stm32f10x.h"                  // Device header
    
    //TIM2
    
    void Timer2_Init(void)
    {
    	//启用了TIM2定时器的时钟 TIM2是连接到APB1总线上的一个定时器。
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    	
    	//配置TIM2的时钟源为内部时钟 这意味着TIM2的时钟将由微控制器的内部时钟源提供。
    	TIM_InternalClockConfig(TIM2);
    	
    	//定时器TIM2初始化
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//配置时钟分频为1 即不分频。
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = 10000-1; //10kHZ除以10000等于1s计时(计数9999次溢出实现定时1s)
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;  //对72M进行7200分频,得到10kHZ (1/10000s计数一次)
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //参数用于高级计数器,这里设置为0
    	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
    	
    	//用于清除TIM2的更新中断标志位 以确保在初始化后不会立即进入中断。
    	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    	//启用TIM2的更新中断 当TIM2的计数器溢出时,将产生更新事件并触发中断。
    	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    	
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置中断优先级分组为Group 2。
    	
    	NVIC_InitTypeDef NVIC_InitStructure;
    	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;  //指定中断通道为TIM2的通道。
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  //启用中断通道。
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //配置中断抢占优先级为2
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;         //配置中断子优先级为1
    	NVIC_Init(&NVIC_InitStructure);
    	
    	TIM_Cmd(TIM2, ENABLE);
    }   
    
    
    //TIM1
    void Timer1_Init(void)
    {
    	//启用了TIM1定时器的时钟 TIM1是连接到APB2总线上的一个定时器。
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
    	
    	//配置TIM1的时钟源为内部时钟 这意味着TIM1的时钟将由微控制器的内部时钟源提供。
    	TIM_InternalClockConfig(TIM1);
    	
    	//定时器TIM1初始化
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//配置时钟分频为1 即不分频。
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = 15000-1; //10kHZ除以10000等于1s计时(计数9999次溢出实现定时1s)
    	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;  //对72M进行7200分频,得到10kHZ (1/10000s计数一次)
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //参数用于高级计数器,这里设置为0
    	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
    	
    	//用于清除TIM1的更新中断标志位 以确保在初始化后不会立即进入中断。
    	TIM_ClearFlag(TIM1, TIM_FLAG_Update);
    	//启用TIM1的更新中断 当TIM2的计数器溢出时,将产生更新事件并触发中断。
    	TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
    	
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置中断优先级分组为Group 2。
    	
    	NVIC_InitTypeDef NVIC_InitStructure;
    	NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;  //指定中断通道为TIM1的通道。
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  //启用中断通道。
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  //配置中断抢占优先级为2
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;         //配置中断子优先级为1
    	NVIC_Init(&NVIC_InitStructure);
    	
    	TIM_Cmd(TIM1, DISABLE);
    }   
    
    
    //void TIM2_IRQHandler(void)
    //{
    //	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    //	{
    //		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    //	}	
    //}
    
    

    3. 延时函数配置

    1. Delay.h
    #ifndef __DELAY_H
    #define __DELAY_H
    
    void Delay_us(uint32_t us);
    void Delay_ms(uint32_t ms);
    void Delay_s(uint32_t s);
    
    #endif
    
    
    1. Delay.c
    #include "stm32f10x.h"
    
    /**
      * @brief  微秒级延时
      * @param  xus 延时时长,范围:0~233015
      * @retval 无
      */
    void Delay_us(uint32_t xus)
    {
    	SysTick->LOAD = 72 * xus;				//设置定时器重装值
    	SysTick->VAL = 0x00;					//清空当前计数值
    	SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器
    	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0
    	SysTick->CTRL = 0x00000004;				//关闭定时器
    }
    
    /**
      * @brief  毫秒级延时
      * @param  xms 延时时长,范围:0~4294967295
      * @retval 无
      */
    void Delay_ms(uint32_t xms)
    {
    	while(xms--)
    	{
    		Delay_us(1000);
    	}
    }
     
    /**
      * @brief  秒级延时
      * @param  xs 延时时长,范围:0~4294967295
      * @retval 无
      */
    void Delay_s(uint32_t xs)
    {
    	while(xs--)
    	{
    		Delay_ms(1000);
    	}
    } 
    
    

    八、主函数编写

    1. main.c
    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Serial.h"
    #include "Esp8266.h"
    #include "DHT11.h"
    #include "Timer.h"
    #include "ALARM.h"
    #include "Light.h"
    
    u8 temp_humi_send_flag = 0;         //温湿度数据发送标志位
    u8 count_flag = 0;                  //计数标志位
    
    u8 hour = 0;                        //时钟
    u8 minute = 0;                      //分钟
    u8 second = 0;                      //秒钟
    
    
    
    int main(void)
    {
    	OLED_Init();                       // OLED初始化
    
    	Timer1_Init();                     // 定时器1初始化
    
    	Serial_Init();                     // 串口1初始化
    	// Serial2_Init();                    // 串口2初始化
    
    	Alarm_Init();                      // 语音初始化
    	Light_Init();                      // 光照传感器初始化
    	DHT11_Init();                      // DHT11初始化
    
    	OLED_ShowNum(1, 14, 0,  1);
    	OLED_ShowString(1, 15, "/2");
    
    	ESP8266_Init();                    // ESP8266初始化
    	
    	ESP8266_Time_Get(&hour, &minute, &second); //获取ESP8266的时间
    
    	OLED_ShowNum(1, 14, 1,  1);
    	OLED_ShowString(1, 15, "/2");
    
    	Delay_s(1);
    
    	ESP8266_Init();                            // ESP8266初始化 需初始化两次 否则时间不正确
    
    	OLED_ShowNum(1, 14, 2,  1);
    	OLED_ShowString(1, 15, "/2");
    
    	ESP8266_Time_Get(&hour, &minute, &second); //获取ESP8266的时间
    
    	
    
    
    
    	Timer2_Init();                     // 定时器2初始化
    	
    	OLED_Clear();                      // OLED清屏
    
    	// OLED_ShowString(1,1,"DHT11 Test");
    
    	float temp, humi;                  //温湿度数据
    
    	u8 light_state = 0;                  //光照强度
    
    	u8 temp_set = 32;                  //温度报警阈值
    	u8 humi_set = 78;                  //湿度报警阈值
    
    	u8 hour_set = 0;                   //时钟报警阈值1
    	u8 minute_set = 0;                 //时钟报警阈值1
    	u8 second_set = 0;                 //时钟报警阈值1
    
    	u8 hour_set2 = 0;                   //时钟报警阈值1
    	u8 minute_set2 = 0;                 //时钟报警阈值1
    	u8 second_set2 = 0;                 //时钟报警阈值1
    	
    	u8 temp_humi_flag = 0;             //温湿度报警标志位
    	u8 time_flag = 0;                  //时钟报警标志位
    
    	u8 s_d_flag = 0;
    
    	u8 time_get_flag = 0;
    
    	while(1)
    	{	
    
    		// 如果count_flag被设置,说明已经过了1s,此时将second加1
    		if(count_flag == 1)
    		{		
    			time_get_flag = time_get_flag + 1;
    
    			if (time_get_flag == 600)                 //如果time_get_flag等于600,说明已经过了10分钟,此时重新获取时间网络校准
    			{
    				//关闭定时器
    				TIM_Cmd(TIM2, DISABLE);
    				//重新获取时间
    				ESP8266_Time_Get(&hour, &minute, &second);
    				//打开定时器
    				TIM_Cmd(TIM2, ENABLE);	
    				time_get_flag = 0;
    			}
    			
    			second = second + 1;
    			if(second >= 60)       	 //如果second大于等于60,说明已经过了1分钟,此时将minute加1
    			{
    				second = 0;
    				minute++;
    			}
    			if(minute >= 60)         //如果minute大于等于60,说明已经过了1小时,此时将hour加1
    			{
    				minute = 0;
    				hour++;
    			}
    			if(hour >= 24)           //如果hour大于等于24,说明已经过了1天,此时将hour置0
    			{
    				hour = 0;
    			}
    			// OLED显示时间
    			OLED_ShowString(1,1,"Time: ");
    			OLED_ShowNum(1,7,hour, 2);
    			OLED_ShowString(1,9,":");
    			OLED_ShowNum(1,10,minute, 2);
    			OLED_ShowString(1,12,":");
    			OLED_ShowNum(1,13,second, 2);
    			// 标志位清零
    			count_flag = 0;
    		}
    		
    		// 如果temp_humi_send_flag被设置,说明已经过了1s,此时读取温湿度数据
    		if(temp_humi_send_flag == 1)
    		{   
    			// 读取温湿度数据
    			if(DHT11_Read_Data(&temp, &humi) == 1)
    			{
    				static char temp_str[5];                       //温度字符串
    				static char humi_str[5];                       //湿度字符串
    				sprintf(temp_str, "Temp: %.1f C", temp);       //温度数据转化为字符串
    				OLED_ShowString(2,1,temp_str);                 //在OLED上显示温度数据
    				sprintf(humi_str, "Humi: %.1f %%", humi);      //湿度数据转化为字符串
    				OLED_ShowString(3,1,humi_str);                 //在OLED上显示湿度数据
    
    				// 在后面显示温湿度阈值
    				OLED_ShowNum(2,14, temp_set, 2);
    				OLED_ShowNum(3,14, humi_set, 2);					
    			}
    			// 如果DHT11读取数据失败
    			else
    			{
    				OLED_ShowString(1,1,"DHT11 Error");             //在OLED上显示DHT11错误
    			}
    
    			light_state = Light_GetNum();                             //读取光照强度
    
    			if (light_state == 1 )
    			{
    				time_flag = 0;                                          //说明打开过药箱
    			}
    
    			OLED_ShowNum(1, 16, light_state, 1);                      //在OLED上显示光照强度
    
    			if(s_d_flag == 0)
    			{
    				ESP8266_Send("humidity", humi, "hour_set", hour_set, "minute_set", minute_set,"temp_set", temp_set, "hour_set2", hour_set2, "minute_set2", minute_set2);	 //发送温湿度数据到阿里云
    				s_d_flag =1;
    			}
    			else
    			{
    				ESP8266_Send("temperature", temp, "switch", light_state, "humi_set",humi_set, "temp_humi_flag", temp_humi_flag, "time_flag", time_flag, "second_set", second_set);	 //发送温湿度数据到阿里云
    				s_d_flag = 0;
    			}
    				
    			
    			temp_humi_send_flag = 0;                            //标志位清零
    		}
    		
    		// 温度湿度报警功能
    		if(temp > temp_set || humi > humi_set)  // 如果温度或湿度大于阈值,蜂鸣警报   
    		{
    			// Serial2_SendString("T_alarm\r\n");  // 串口2发送温度报警信息
    			Alarm1_ON();                        // 蜂鸣警报
    			temp_humi_flag = 1;                      // 温度报警标志位
    		}
    		else
    		{
    			Alarm1_OFF();                       // 关闭蜂鸣警报
    			temp_humi_flag = 0;                      // 温度报警标志位
    		}
    
    
    		// 时钟报警功能
    		if((hour == hour_set && minute == minute_set && second < 15) || (hour == hour_set2 && minute == minute_set2 && second < 15)) // 如果时间等于设置时间,语音提醒
    		{
    			// Serial2_SendString("H_alarm\r\n");   // 串口2发送时钟语音提醒
    			Alarm2_ON();                         // 语音提醒
    			time_flag = 1;                       // 时钟报警标志位 只有打开药箱 才能消除
    		}
    		else
    		{
    			Alarm2_OFF();                        // 语音提醒
    		}
    
    
    		// 判断串口1中断标志位是否被设置, 远程开关功能
    		if(Serial_GetRxFlag() == 1)              
    		{
    
    			if(ESP8266_Received(&hour_set, &minute_set, &temp_set, &humi_set, &hour_set2, &minute_set2))    //接收阿里云发送的时间数据
    			{
    				ESP8266_Send("humidity", humi, "hour_set", hour_set, "minute_set", minute_set,"temp_set", temp_set, "hour_set2", hour_set2, "minute_set2", minute_set2);	 //发送温湿度数据到阿里云
    				s_d_flag =0;
    			}
    		}
    
    		// OLED显示设置的时间
    		OLED_ShowString(4,1,"Hint: ");
    		OLED_ShowNum(4,7,hour_set, 2);
    		OLED_ShowString(4,9,":");
    		OLED_ShowNum(4,10,minute_set, 2);
    		// OLED_ShowString(4,12,"|");
    		OLED_ShowNum(4,12,hour_set2, 2);
    		OLED_ShowString(4,14,":");
    		OLED_ShowNum(4,15,minute_set2, 2);
    
    	}
    
    }
    
    // TIM2中断服务函数
    void TIM2_IRQHandler(void)
    {
    	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)          //检查TIM2的更新中断是否发生
    	{
    		temp_humi_send_flag = 1;                              //设置温湿度数据发送标志位
    		
    		count_flag = 1;                                       //设置计数变量
    
    		// ESP8266_Send("humidity", humidity);                   //发送湿度数据       
    		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);           //清除TIM2的更新中断标志位
    	}	
    }
    

    九、语音模块配置

    主体思路就是 配置模块的A1\A0引脚为下拉输入模式,当检测到高电平(来自STM32 的信号)时,分别输出播放不同的音频.

    十、Web端制作

    具体步骤可参考我之前的文章
    链接: 基于树莓派和阿里云物联网平台的传感器读取及控制

    作者:Super_Chao_H

    物联沃分享整理
    物联沃-IOTWORD物联网 » 基于Stm32和Esp8266的智能药箱设计与阿里云物联网集成

    发表评论