前言:
本次项目采用的ESP32芯片作为主控,使用MicroPython和thonny来开发的智慧系统。MCU连接阿里云通过阿里云的数据流转功能将连接MCU的DHT11,超声波等传感器所测得的数据通过MQTT协议上传至阿里云平台,而后在云平台上通过数据流转功能将得到的数据通过MQTT协议下发至微信小程序上而后显示,通过小程序可远程开关板载LED灯以及WS2812B炫彩灯。
一、ESP32
上海乐鑫的ESP32微控制器是一款集成有2.4GHz Wi-Fi和蓝牙4.0双模的物联网芯片方案,采用超低功耗的40纳米工艺代工。片上集成有天线开关、射频巴伦、功率放大器、接收低噪声放大器、滤波器、电源管理模块等功能,仅需要20余个外围元件,就可以适配大量的物联网场景。
除此之外,ESP32系列的工作温度范围达在-40°C ~ +125°C 之间,并且内部集成的自校准电路可以实现动态电压调整,以消除外部电路缺陷,适应外部条件的变化。从而在产品量产时,不需要使用到昂贵的专用Wi-Fi测量仪器。
该芯片即可以基于内嵌的Xtensa 32-bit MCU作为独立的片上系统使用,也可以作为其它MCU主控方案的从设备,通过 SPI/SDIO 或者 I2C、UART等串行总线协议提供Wi-Fi与蓝牙通信功能。

二、DHT11温湿度传感器
1、具有抗冲击性及电气性能优良
2、 完全标定
3、 优异的长期稳定性
4、响应迅速、恢复时间快、抗干扰能力强
5、数字输出,单总线通讯

图二、DHT11数字温度传感器

图三、DHT11数字温度传感器原理与
在该项目当中,DHT11的选型为三针默认NC引脚悬空,输出口接ESP32的GPIO16

对于MicroPython来说,使用该模块来检测环境的温湿度相较于c语言来说变得异常简单,因为它自带了DHT11的驱动函数,并且封装好了只需要调用即可。上述代码dht库中就包含了DHT11的测量函数measure(),只需对其引用使用一个变量来存储函数的和返回值便可得到对应的温湿度信息,最后将其封装成一个函数,供其它工程进行调用。
下方链接可供读者快速了解关于MicroPyhton在ESP32上的一下语法应用。
Quick reference for the ESP32 — MicroPython latest documentation (86x.net)
单总线通讯:DHT11通过单总线通讯应用与DHT11与MCU之间的通讯上,通过该方式来向MCU发送测得的温湿度信息。

图三、DHT11单总线传输顺序数据图

图四、DHT11单总线传送数据表

图五、单总线数据计算示例
步骤一:
DHT11上电后(DHT11上电后要等待1S以越过不稳定状态在此期间不能发送任何指令),测 试环境温湿度数据,并记录数据,同时DHT11的DATA数据线由上拉电阻拉高一直保持高电平; 此时DHT11的DATA引脚处于输入状态,时刻检测外部信号。
步骤二:
MCU初始电平状态为高,我们可以选择I/O口默认初始电平为高的I/O口也就是有着上拉电阻的IO口,这样它的起始电位则为高电平,而后MCU的I/O口输出低电平且低电平时间不低于18ms,而后MCU释放总线,总线电平返回为高,MCU的I/O口此时设置为输入模式,等待DHT11反应回复应答信号。

图六、主机发送起始信号

图七、从机响应信号
步骤三:
DHT11的输出引脚DATA检测到外部信号有低电平,等待外部信号低电平结束,延迟后的DHT11输出引脚为输出状态,输出83us的低电平信号作为应答信号,而后再输出87us左右的高电平通知MCU接收接下来的数据
步骤四:
由DHT11的DATA引脚输出40位的数据,MCU根据I/O电平的变化接收40位数据,位数据 “0”的格式为:54微秒的低电平和23-27微秒的高电平,位数据“1”的格式为:54微秒的低 电平加68-74微秒的高电平。

图八、输出数据0传输格式

图九、输出数据1传输格式
结束信号:
最后一bit传输完成之后,从机拉低总线50us,随后总线由上拉电阻将总线拉高进入空闲状态,等待下次的数据传输。

图十:完整通讯规程
对应STM32(C语言)代码块:
void DHT11_OUT() //将引脚设置为输出模式
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
void DHT11_INPUT() //设置为输入模式
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_Instructure;
GPIO_Instructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_Instructure.GPIO_Pin=GPIO_Pin_0;
GPIO_Instructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Instructure);
}
void DHT11_Rest() //复位函数
{
DHT11_OUT();
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //拉低
Delay_ms(20); //至少拉低18ms
GPIO_SetBits(GPIOA,GPIO_Pin_0); //拉高
Delay_us(25); //拉高20-40us
}
void DHT11_ACK()
{
DHT11_INPUT() //引脚设置为输入模式
}
uint16_t DHT11_Check()
{
uint16_t count=0;
DHT11_INPUT();
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==1 && count<100) //等待输入电平为0跳出循环
{
Delay_us(1);
count++;
}
if(count>=100)
{
return 1;
}
else
count= 0; //检测到低电平 此时将计数值重新至0,进入下一个高电平检测
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==0 && count<100) //等待输入电平为高跳出循环
{
Delay_us(1);
count++;
}
if(count>=100)
{
return 1;
}
return 0; //检测到高电平 传感器正常开始传送数据
}
uint16_t DHT11_ReadBit()
{
uint16_t count=0;
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==1 && count<=100)
{
count++;
Delay_us(1);
}
count=0;
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==0 && count<=100)//读取高电平
{
count++;
Delay_us(1);
}
Delay_us(40);
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==1) //说明该为读取的数据为1
return 1;
else
return 0; //不是1说明数据为0
}
uint16_t DHT11_ReadByte()
{
uint16_t date;
for(uint16_t i=0;i<=8;i++)
{
date<<=1;
date|=DHT11_ReadBit(); //数据读取 高位在前,低位在后,读取完向左移1位,将提取的数据为逐步提至最高位
}
return date;
}
void Readdate(uint16_t *temp,uint16_t *hum)
{
uint16_t buf[5];
DHT11_Rest();
if(DHT11_Check()==0)
{
for(uint16_t i=0;i<5;i++)
{
buf[i]=DHT11_ReadByte();
}
if(buf[0]+buf[1]+buf[2]+buf[3]==buf[4])
{
*temp=buf[0];
*hum=buf[2];
}
}
}
三、WS2812B(RGB彩灯)

图11、ws2812b实物图
图12、原理图
数据传送方式为D1,D2.D3,D4代表按顺序排列的WS2812B灯珠。从图中可以看出D1介绍到第一个24bit的数据后将后三个的数据传输给下一个灯珠,下一个灯珠取完自身所要接收的灯珠后将而后的数据往下传递。

数据的发送由高位到低位,每8bit位代表WS2812B灯珠对该颜色的发光强度,所以三原色绿红蓝的十六进制编码分别为:0xff0000,0x00ff00,0x0000ff。

MicroPython代码 ,通过元组来存储不同颜色的色值分量,达到改变颜色交替呈现不同的颜色的效果。
from machine import Pin, PWM
from neopixel import NeoPixel
import utime
# Pin configuration
pin = Pin(15, Pin.OUT)
np = NeoPixel(pin, 4)
ws2812bshow_flag = 0
# Define colors
red = (255, 0, 0)
blue = (0, 0, 255)
green = (0, 255, 0)
white = (255, 255, 255)
orange = (255, 165, 0)
purple = (128, 0, 128)
cyan = (0, 255, 255)
yellow = (255, 255, 0)
pink = (255, 192, 203)
indigo = (75, 0, 130)
light_blue = (173, 216, 230)
dark_green = (0, 100, 0)
light_green = (144, 238, 144)
gold = (255, 215, 0)
salmon = (250, 128, 114)
violet = (238, 130, 238)
turquoise = (64, 224, 208)
beige = (245, 222, 179)
coral = (255, 127, 80)
mint = (189, 252, 201)
chocolate = (210, 105, 30) # Color for breathing effect
# Function to show colors in a forward direction
def show(color, num):
for i in range(num, 4):
np[i] = color
np.write()
utime.sleep(0.05)
# Function to show colors in a reverse direction
def show1(color, num):
for i in range(num, 4)[::-1]:
np[i] = color
np.write()
utime.sleep(0.05)
# Function to display a sequence of colors
def ws2812b_show():
for i in range(1):
show(red, i)
show1(blue, i)
show(green, i)
show1(white, i)
show(red, i)
show1(blue, i)
show(white, i)
show1(chocolate, i)
# Function to display another sequence of colors
def ws28b12b_show2():
for i in range(1):
show1(orange, i)
show(purple, i)
show1(yellow, i)
show(pink, i)
show1(indigo, i)
show(light_blue, i)
show1(dark_green, i)
# Function to display a third sequence of colors
def ws2812b_show3():
for i in range(1):
show1(light_green, i)
show(gold, i)
show1(salmon, i)
show(violet, i)
show1(turquoise, i)
show(beige, i)
show1(coral, i)
show(mint, i)
# Function to create a breathing effect for NeoPixels
def breathing_led(duration=1, steps=50):
for step in range(steps):
# Calculate brightness level (0-255)
brightness = int((step / steps) * 255) # Fade in
for i in range(30): # Set all pixels to the calculated brightness
np[i] = (chocolate[0] * brightness // 255,
chocolate[1] * brightness // 255,
chocolate[2] * brightness // 255)
np.write() # Update the NeoPixel strip
utime.sleep(duration / steps) # Wait time based on duration and steps
for step in range(steps):
# Calculate brightness level (255-0)
brightness = int(((steps - step) / steps) * 255) # Fade out
for i in range(30): # Set all pixels to the calculated brightness
np[i] = (chocolate[0] * brightness // 255,
chocolate[1] * brightness // 255,
chocolate[2] * brightness // 255)
np.write() # Update the NeoPixel strip
utime.sleep(duration / steps) # Wait time based on duration and steps
def turn_off_neopixels():
for i in range(30): # 假设你的NeoPixel条带有30个LED
np[i] = (0, 0, 0) # 将每个LED设置为黑色
np.write() # 更新NeoPixel条带以显示关闭状态
使用stm32采用PWM+DMA的方法来驱动WS2812B灯珠,使用DMA将所要显示的颜色直接传输到TIM2的CCR2寄存器,无需经过CPU,大大提高了数据传输的效率且节省了CPU资源。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#define TH0 30
#define TH1 60
#define DATA_SIZE 24
uint16_t LED_Value[400];
//PWM+DMA初始化
void PWM_ws2812b_Init(uint16_t arr)
{
//开启对应外设的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
//结构体变量
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef Time_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
DMA_InitTypeDef DMA_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
Time_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分割
Time_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //计数方式
Time_TimeBaseStructure.TIM_Period=arr; //计数值
Time_TimeBaseStructure.TIM_Prescaler=0; //分频
TIM_TimeBaseInit(TIM2,&Time_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCIdleState=TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
TIM_OCInitStructure.TIM_OCMode=TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High; //输出极行为高
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
/*DMA初始化*/
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(TIM2->CCR2);//设置DMA目的地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)LED_Value;//设置DMA源地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//方向:从内存SendBuff到内存
ReceiveBuff
DMA_InitStructure.DMA_BufferSize = 24; //一次传输大小DMA_BufferSize=SendBuffSize
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //ReceiveBuff地址不增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //SendBuff地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //ReceiveBuff数据单位,16bit
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //SENDBUFF_SIZE数据单位,16bit
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //DMA模式:正常模式(传输一次)
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级:中
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //内存到内存的传输
DMA_Init(DMA1_Channel7, &DMA_InitStructure); //配置DMA1的7通道(不同定时器的通道不一样)
DMA_Cmd(DMA1_Channel7, DISABLE); //失能DMA1的7通道,因为一旦使能就开始传输
}
void Rest()
{
TIM_Cmd(TIM2,DISABLE);
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
Delay_ms(1);
}
void ws2812b_writ(uint16_t num,uint32_t GRB_data)
{
uint8_t i,j;
for(i=0;i<num;i++)
{
for(j=0;j<24;j++)
{
LED_Value[j]=((GRB_data<<j)&0x800000)? TH1:TH0;
}
}
}
void PWM_WS2812B_Show(uint16_t num)
{
DMA_SetCurrDataCounter(DMA1_Channel7, num*DATA_SIZE);
DMA_Cmd(DMA1_Channel7, ENABLE);
TIM_Cmd(TIM2, ENABLE);
while(DMA_GetFlagStatus(DMA1_FLAG_TC7) != SET);
DMA_Cmd(DMA1_Channel7, DISABLE);
DMA_ClearFlag(DMA1_FLAG_TC7);
TIM_Cmd(TIM2, DISABLE);
}
void WS2812_Green(uint16_t num)
{
ws2812b_writ(num,0xff0000);
PWM_WS2812B_Show(num);
}
void WS2812_Red(uint16_t num)
{
ws2812b_writ(num,0x00ff00);
PWM_WS2812B_Show(num);
}
void WS2812_Blue(uint16_t num)
{
ws2812b_writ(num,0x0000ff);
PWM_WS2812B_Show(num);
}
四、超声波传感器

图13、超声波传感器实物图
VCC:电源线,连接单片机的5V(VCC5)
GND:地线,连接单片机的接地(GND)
TRIG:触发控制信号输入
ECHO:回响信号输出

图14、原理图
该模块采用I/O口TRIG口触发测距,给与TRIG口一个至少15us的高电平,该模块自动发送8个40khz的方波,自动检测是否有超声波信号返回,有信号返回时,通过ECHO脚输出一个高电平,高电平持续的时间就是超声波从发射到接收的时间。
距离=(高电平时间*声速(320m/s))/2;

使用定时器进行定时,将于echo引脚MCU引脚设置为下拉输入模式,将该引脚与外部中断想映射,通过外部中断的上升沿触发模式,触发中断服务函数。当该引脚检测到高电平时,定时器开始计数,计数到高电平结束关闭定时器,获得高电平的持续时间,从而计算超声波测得的距离。
import machine, time
from machine import Pin
__version__ = '0.2.0'
__author__ = 'Roberto Sánchez'
__license__ = "Apache License 2.0. https://www.apache.org/licenses/LICENSE-2.0"
class HCSR04:
"""
Driver to use the untrasonic sensor HC-SR04.
The sensor range is between 2cm and 4m.
The timeouts received listening to echo pin are converted to OSError('Out of range')
"""
# echo_timeout_us is based in chip range limit (400cm)
def __init__(self, trigger_pin, echo_pin, echo_timeout_us=500*2*30):
"""
trigger_pin: Output pin to send pulses
echo_pin: Readonly pin to measure the distance. The pin should be protected with 1k resistor
echo_timeout_us: Timeout in microseconds to listen to echo pin.
By default is based in sensor limit range (4m)
"""
self.echo_timeout_us = echo_timeout_us
# Init trigger pin (out)
self.trigger = Pin(trigger_pin, mode=Pin.OUT, pull=None)
self.trigger.value(0)
# Init echo pin (in)
self.echo = Pin(echo_pin, mode=Pin.IN, pull=None)
def _send_pulse_and_wait(self):
"""
Send the pulse to trigger and listen on echo pin.
We use the method `machine.time_pulse_us()` to get the microseconds until the echo is received.
"""
self.trigger.value(0) # Stabilize the sensor
time.sleep_us(5)
self.trigger.value(1)
# Send a 10us pulse.
time.sleep_us(10)
self.trigger.value(0)
try:
pulse_time = machine.time_pulse_us(self.echo, 1, self.echo_timeout_us)
return pulse_time
except OSError as ex:
if ex.args[0] == 110: # 110 = ETIMEDOUT
raise OSError('Out of range')
raise ex
def distance_mm(self):
"""
Get the distance in milimeters without floating point operations.
"""
pulse_time = self._send_pulse_and_wait()
# To calculate the distance we get the pulse_time and divide it by 2
# (the pulse walk the distance twice) and by 29.1 becasue
# the sound speed on air (343.2 m/s), that It's equivalent to
# 0.34320 mm/us that is 1mm each 2.91us
# pulse_time // 2 // 2.91 -> pulse_time // 5.82 -> pulse_time * 100 // 582
mm = pulse_time * 100 // 582
return mm
def distance_cm(self):
"""
Get the distance in centimeters with floating point operations.
It returns a float
"""
pulse_time = self._send_pulse_and_wait()
# To calculate the distance we get the pulse_time and divide it by 2
# (the pulse walk the distance twice) and by 29.1 becasue
# the sound speed on air (343.2 m/s), that It's equivalent to
# 0.034320 cm/us that is 1cm each 29.1us
cms = (pulse_time / 2) / 29.1
return cms
//引用上放的工程
from hcsr04 import HCSR04
from time import sleep
import ssd1306
sensor = HCSR04(trigger_pin=33, echo_pin=27) # 定义超声波模块Tring控制管脚及超声波模块Echo控制管脚
dangerflag=0
# 距离显示函数
def hcsr04OLED():
ds = sensor.distance_cm() # 获取超声波计算距离 ,也可以调用sensor.distance_mm() 得到mm值
return ds
STM32代码:大概思路
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
uint8_t flag;
void Timer_Init(uint32_t arr,uint32_t prc)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //开启时钟
TIM_TimeBaseInitTypeDef TimeBaseInitStructure;
TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TimeBaseInitStructure.TIM_Period=arr;
TimeBaseInitStructure.TIM_Prescaler=prc;
TIM_TimeBaseInit(TIM3,&TimeBaseInitStructure);
//更新中断 事件触发中断
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
TIM_ITConfig(TIM3,TIM_IT_Trigger,ENABLE);
//中断优先级管理
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3; //相应优先级
NVIC_Init(&NVIC_InitStructure);
}
void TIM3_IRQHandler(void) //定时中断服务函数
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)!=RESET)//状态返回值为1 说明产生中断
{
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除标志位
}
}
void hcsr04_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//trig引脚配置
GPIO_InitTypeDef GPIOInitStructure;
GPIOInitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //推挽输出
GPIOInitStructure.GPIO_Pin=GPIO_Pin_0;
GPIOInitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIOInitStructure);
//ehco引脚配置
GPIOInitStructure.GPIO_Mode=GPIO_Mode_IPD; //下拉输入
GPIOInitStructure.GPIO_Pin=GPIO_Pin_1;
GPIOInitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIOInitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource1); //外部中断引脚映射
//外部中断配置
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line0;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStructure);
//中断优先级
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=EXTI9_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
NVIC_Init(&NVIC_InitStructure);
}
void EXTI9_5_IRQnHandler()
{
if(EXTI_GetITStatus(EXTI_Line0))
{
flag++;
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
float Getdistance()
{
float distance;
if(flag==1)
{
TIM_SetCounter(TIM3,0);
TIM_Cmd(TIM3,ENABLE);
while(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_1));
TIM_Cmd(TIM3,DISABLE);
distance=(TIM_GetCounter(TIM3)*340)/200.0;
flag=0;
}
return distance;
}
void hcsr04_Start()
{
GPIO_SetBits(GPIOA,GPIO_Pin_0);
Delay_us(20); //大于10us
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
}
int main()
{
float getdistance;
OLED_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Timer_Init(10000-1,7200-1); //72MHZ/(7200-1+1)/(10000-1+1) =1000ms=1s
hcsr04_Init();
while(1)
{
hcsr04_Start();
getdistance=Getdistance();
OLED_ShowNum(1,1,getdistance,5);
}
}
五、光敏传感器
光敏电阻器是利用半导体的光电导效应制成的一种电阻值随入射光的强弱而改变的电阻器,又称为光电导探测器。 入射光强,电阻减小,入射光弱,电阻增大。常用的制作材料为硫化镉,另外还有硒、硫化铝、硫化铅和硫化铋等材料。这些制作材料具有在特定波长的光照射下,其阻值迅速减小的特性。一般用于光的测量、光的控制和光电转换(将光的变化转换为电的变化)。

图15、光敏传感器实物图

图十六、原理图
本次采用的是三针光敏传感器,无DO引脚(模块在环境光线亮度达不到设定阈值时,模块在环境光线亮度达不到设定阈值时DO端输出高电平,当外界环境光线亮度超过设定阈值时,DO端输出低电平;3、DO输出端可以与单片机直接相连,通过单片机来检测高低电平,由此来检测环境的光线亮度改变;)。GPIO34作为光敏传感器AO的接入引脚,通过ADC转换读取该引脚的电压值转换成ad值,进而改变PWM输出的占空比,从而改变LED发光的强弱。
# 说明:光敏电阻传感器实验
#####################################################
from machine import Pin,ADC,PWM
from time import sleep
ESP32TEST_photo = 34 # ADC6复用管脚为GP34
light_led=3
# 初始化工作
def ESP32TEST_setup():
global photo_ADC,light_led
photo_ADC = ADC(Pin(ESP32TEST_photo)) # ADC6复用管脚为GP34
photo_ADC.atten(ADC.ATTN_11DB) # 11dB 衰减, 最大输入电压约3.6v
light_led = PWM(Pin(light_led), freq=1000, duty=0) # led灯
def light_PWM(value):
light_led.duty(value)
# 循环函数
def ESP32TEST_loop():
ESP32TEST_status = 1 # 状态值
# 无限循环
while True:
try:
advalue=photo_ADC.read()
light_PWM(advalue-3095)
print ('Photoresistor Value: ', photo_ADC.read()) # 读取ADC6的值16-bits,获取光敏模拟量值
print(advalue-3095)
sleep(1) # 延时200ms
except Exception as e: # 捕获所有异常
print("发生错误:{e}") # 打印错误信息
break # 退出循环
# 程序入口
def photosenstive():
ESP32TEST_setup() # 地址设置
ESP32TEST_loop() # 调用无限循环
STM32代码:大概思路将传感器的AO引脚连接MCU的AD口,通过ADC得到不同光强的不同ad值,采用定时器输出比较功能输出PWM波,选择合适的AD值来作为输出给LED的占空比。以下是定时器部分以及AD部分的初始化代码,需要注意的是在开始进行ad转换时需要软件触发AD转换一次(ADC_SoftwareStartConvCmd(ADC1, ENABLE); );
#include "stm32f10x.h" // Device header
/**
* 函 数:PWM初始化
* 参 数:无
* 返 回 值:无
*/
void PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO重映射*/
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,重映射必须先开启AFIO的时钟
//GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
//GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //将JTAG引脚失能,作为普通GPIO引脚使用
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC1Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道1
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare); //设置CCR1的值
}
void NewAD_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置AD时钟
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ContinuousConvMode=ENABLE; //连续转换使能
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; //对其方式选择又对齐
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发;
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent; //单通道
ADC_InitStructure.ADC_NbrOfChannel=1; //通道数为1
ADC_InitStructure.ADC_ScanConvMode=DISABLE; //扫描模式 单通道Disbale 多通道ENABLE
ADC_Init(ADC1,&ADC_InitStructure);
/*规则组通道配置*/
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,配置为通道0
/*ADC校准*/
ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准 重置校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1); //开始校准
while (ADC_GetCalibrationStatus(ADC1) == SET);
}
uint16_t NewAD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位,即等待AD转换结束
return ADC_GetConversionValue(ADC1); //读数据寄存器,得到AD转换的结果
}
六、OLED显示屏

OLED即有机发光管(Organic Light-Emitting Diode,OLED)。OLED显示技术具有自发光、广视角、几乎无穷高的对比度、较低功耗、极高反应速度、可用于绕曲性面板、使用温度范围广、构造及制程简单等有点,被认为是下一代的平面显示屏新兴应用技术。OLED显示和传统的LCD显示不同,其可以自发光,所以不需要背光灯,这使得OLED显示屏相对于LCD显示屏尺寸更薄,同时显示效果更优。 常用的OLED屏幕有蓝色、黄色、白色等几种。屏的大小为0.96寸,像素点为 128*64,所以我们称为0.96oled屏或者12864。该项目采用的是4针0.96寸OLED显示屏,作为出传感器检测数据的显示,该模块使用IIC通信协议与MCU通信, 其中IIC协议两引脚接线情况为:SCL->PIN14,SDAA->PIN13;
1、IIC详解:
IIC总线具有两根线,一根时钟线SCL用于保证主从机之间的时钟同步,一跟数据线SDA用作数据传输。IIC是同步,半双工的通信协议,主从模式支持一主多从和多主多从的方式。

从设备的SCL线和SDA线都挂载在主设备的SCL线与SDA线上,因为主机对SCL线有绝对的掌控权所以主设备的输出模式可以设置为推挽输出,其它的从设备的SCL线的输出模式皆为开漏输出。对于SDA线来说,若主从设备的SDA线的输出模式均设为推挽,当主设备输出高电平,从设备输出低电平时,则SDA总线上就会产生短路的现象,,因次SDA线的输出模式不能为推挽输出。当SDA的输出模式为开漏输出模式,外接一个4.7k的上拉电阻为弱上拉模式,当从设备输出高电平时,上管导通,因为是开漏输出从设备没有输出高电平的能力,此时引脚处于浮空状态,此时从机便转为输入状态。当从机输出低电平时,下管导通实现强下拉,总线电平为低电平。
当设备需要输出时,可以通过是否输出低电平使得总线电平变化;当引脚需要输入时,可以输出高电平,相当于断开引脚,观察总线电平变化。所以输入之前可以输出高电平,不需要切换输入模式。
实现线与的功能:只要有一个设备输出低电平,这个总线就是低电平(强下拉:直接接地,落弱下拉:接下拉电阻),只有所有设备输出高电平(因为高电平相当于都断开引脚,上拉电阻将总线电平拉高),总线才处于高电平 。
2、IIC时序图 :
起始条件和终止条件又主机对SCL与SDA总线进行控制,在SCL为高电平时,主机拉低SDA作为起始条件,此时从机会对自身产生复位,等待主机的选取。当主机松开对SCL线的控制,因为有上拉电阻的原因SCL线回弹至高电平后,主机也松开对SDA线的拉低控制,SDA回弹至高电平,至此总线回到平静状态,通讯终止。 
主机在发送一个字节时在SCL低电平期间,主机将数据从高到低依次防止SDA总线上,发送高电平在松开SDA,SDA自动回弹至高电平,主机松开SCL线,SCL位于高电平,此时从机在SCL总线处于高电平期间读取SDA总线上的高电平,主机发送低电平则拉低SDA总线,主机松开SCL线,从机在SCL总线高电平期间读取SDA总线的低电平,依次循环上述过程八次,则完成主机给从机一个字节的发送。

主机接收字节需在SCL低电平期间,同时放手对SDA线的控制,交予从机。从机将数据放置在SDA总线上,仍是从高到低的顺序,从机输出高电平则释放SDA线,输出低电平则拉低SDA线,数据放置完毕后,总线释放SCL线,主机在SCL处于高电平期间读取SDA线上的高低电平,重复上述步骤八次形成一个字节。
从机发送,主机应答则为发送应答:主机在接收完一个字节之后,在下一个时钟从机释放SDA线,此时由主机来控制SDA线高低电平,拉低则代表数据0,回弹则为高电平。数据0代表应答,主机将SDA线拉低。数据1代表非应答,主机则不对SDA线进行操作。应答之后从机可以继续给主机发送消息,主机照单全收,如果主机发送的是非应答,此时从机则不能在向主机发送数据,放开对SDA的控制交予主机。
主机发送,从机应答则为接收应答:主机发送一个字节后,在下一个时钟主机释放SDA线,来判断从机是否应答,在SCL为高电平期间,主机读取SDA线的高低电平状态,若读取SDA线为高电平则说明无从机应答,若SDA线读取状态为低电平则说明有从机应答,主机可以继续发送消息。

•指定地址写
•对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
在主机发送起始条件之后,主机在SCL处于电平期间向SDA线的写入电平状态,此时SDA线由主机控制,主机向SDA线写入想要写入数据的设备地址,从机地址为7为最后一位为读写位,此时最后一位为0则说明该次主机执行为写,前七位则为从机的设备地址。写入一个字节完毕后紧跟着主机发送应答,对于设备地址从机接收到该信号后会拉低SDA总线,表示收到主机的选取。而后主机开始发送需要写入从机设备的寄存器地址仍是八位,等待从机的应答,从机应答之后,主机开始向对应设备的对应寄存器地址写入一个字节的数据,而后再次等待从机的应答,而后停止写入,发送停止信号。

设备地址 寄存器地址 发送数据
•当前地址读
•对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)
主机发送起始条件后,发送一个字节的数据,前七位为设备地址,后一位此时为1则主机执行任务读取。主机释放SDA线,若SDA线被拉低则说明从机应答,反之则无从机应答。因为寄存器存储数据为线性结构,一个指针指向一个地址,数据写入寄存器当中,指针自增指向下一个地址,因此当前地址读只能从寄存器存储数据结束的地址,读取地址无法改变因此该方式很少用,在读取完毕之后,总线发送非应答信号,此时从机不再向SDA写入数据供给主机读取数据,释放SDA线,主机释放停止信号,读取时序结束。

•指定地址读
•对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
因上方的当前地址读无法做到指定地址读,所以衍生出该方式,指定地址读,通过主机先写入寄存器地址后,不向该寄存器输入数据,因此指向该寄存器地址的指针不会产生变化,而此时主机再重复发送一个起始信号,将原本要写的时序转变为当前地址读的时序,因为写入并没有对该设备的指定地址写入数据,因此此时指针停留的地方就是我们写入寄存器地址,此时在用当前地址读的时序就能做到指定地址读的任务!

设备地址(0写) 寄存器地址 重复起始 设备地址(1读) 读取数据
3、软件模拟
Micropython:
from machine import Pin, I2C
i2c = I2C(0)
i2c = I2C(1, scl=Pin(5), sda=Pin(4), freq=400000)
STM32:使用GPIO0和GPIO1来模拟IIC时序。
void IIC_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
void W_SCL(uint8_t BitValue) //SCL时钟线写入电平
{
GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction)BitValue);
}
void W_SDA(uint8_t BitValue) //SDA数据线写入电平
{
GPIO_WriteBit(GPIOA,GPIO_Pin_1,(BitAction)BitValue);
}
uint8_t R_SDA( ) //读取SDA线的电平状态
{
uint8_t BitValue;
BitValue=GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1);
Delay_us(10);
return BitValue;
}
void IIC_start() //IIC起始信号
{
W_SCL(1);
W_SDA(1); //初始化将两线先至为高电平
W_SDA(0);
W_SCL(0);
}
void IIC_Stop() //IIC停止信号
{
W_SDA(0); //确保为低电平
W_SCL(1);
W_SDA(1);
}
void IIC_SendByte(uint8_t BitValue) //主机发送一个字节的数据
{
W_SCL(1);
for(uint8_t i=0;i<8;i++)
{
W_SDA(BitValue & (0x80>>i));
W_SCL(1); //从机在高电平时期读取
W_SCL(0);
}
}
uint8_t IIC_ReceiveByte() //主机接收一个字节的数据
{
uint8_t BitValue=0x00;
W_SDA(1);
for(uint8_t i=0;i<8;i++)
{
W_SCL(1);
if(R_SDA()==1)BitValue|=(0x80>>i);
W_SCL(0);
}
return BitValue;
}
void SendACK(uint8_t ACKBit) //主机发送给从机应答信号
{
W_SDA(ACKBit);
W_SCL(1);
W_SCL(0);
}
uint8_t ReceiveACK() // 主机接收从机的应答信号
{
uint8_t ACKBit;
W_SDA(1);
W_SCL(1);
ACKBit=R_SDA();
W_SCL(0);
return ACKBit;
}
STM32硬件IIC代码:

void Hardware_IIC()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10 | GPIO_Pin_11;;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Ack=I2C_Ack_Enable; //应答使能
I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit; //设备地址为7位
I2C_InitStructure.I2C_ClockSpeed=10000; //时钟速度 10khz
I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2; //时钟占空比,选择Tlow/Thigh = 2
I2C_InitStructure.I2C_Mode=I2C_Mode_I2C; //模式选为I2C模式
I2C_InitStructure.I2C_OwnAddress1=0x00; //设置自身设备地址 为0x00
I2C_Init(I2C2,&I2C_InitStructure);
I2C_Cmd(I2C2, ENABLE); //使能I2C2,开始运行
}
void W_Hard_IIC(uint8_t Address,uint8_t Data,uint8_t RegAddress)
{
I2C_GenerateSTART(I2C2,ENABLE); //起始条件
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);//等待EV5
I2C_Send7bitAddress(I2C2,Address,I2C_Direction_Transmitter); //发送设备地址
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)!=SUCCESS); //等待EV6
I2C_SendData(I2C2,RegAddress); //发送寄存器地址
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING)!=SUCCESS); //等待EV8
I2C_SendData(I2C2,Data); //发送数据
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED)!=SUCCESS); //等待EV8-2
I2C_GenerateSTOP(I2C2,ENABLE); //停止条件
}
uint8_t R_Hard_IIC(uint8_t Address,uint8_t RegAddress)
{
uint8_t Data;
I2C_GenerateSTART(I2C2,ENABLE); //起始条件
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS); //等待EV5
I2C_Send7bitAddress(I2C2,Address,I2C_Direction_Transmitter); //发送设备地址,方向为发送
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)!=SUCCESS); //等待EV6
I2C_SendData(I2C2,RegAddress); //发送寄存器地址
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED)!=SUCCESS); //等待EV8-2 最后一个发送的数据等待的事件
I2C_GenerateSTART(I2C2,ENABLE); //重复起始条件
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS); //等待EV5
I2C_Send7bitAddress(I2C2,Address,I2C_Direction_Receiver); //发送设备地址 ,方向为接收
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)!=SUCCESS); //等待ev6
I2C_AcknowledgeConfig(I2C2,DISABLE); //在接收最后一个字节之前提前将应答失能
I2C_GenerateSTOP(I2C2,ENABLE); //在接收最后一个字节之前提前申请停止条件
while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED)!=SUCCESS); //等待EV7
Data=I2C_ReceiveData(I2C2); //接收寄存器数据
I2C_GenerateSTART(I2C2,ENABLE); //将应答恢复为使能,为了不影响后续可能产生的读取多字节操作
return Data;
}
七、阿里云物联网云平台
阿里云物联网云平台(Alibaba Cloud IoT Platform)是阿里巴巴集团推出的一项云服务,旨在为物联网(IoT)设备提供全面的管理、连接和数据处理能力。该平台支持各种设备的接入、管理和数据分析,帮助企业实现智能化转型。
主要功能:
-
设备管理:
- 支持设备的注册、认证、管理和监控。
- 提供设备状态监控和远程控制功能。
-
数据处理:
- 实时数据采集和存储,支持大规模数据处理。
- 提供数据分析和可视化工具,帮助用户洞察数据价值。
-
安全性:
- 提供多层次的安全机制,包括设备身份认证、数据加密和访问控制。
- 确保数据在传输和存储过程中的安全性。
-
连接能力:
- 支持多种通信协议(如 MQTT、HTTP、CoAP 等),方便不同类型的设备接入。
- 提供稳定的网络连接,确保设备与云平台之间的实时通信。
-
应用开发:
- 提供丰富的 API 和 SDK,支持开发者快速构建物联网应用。
- 支持与其他阿里云服务(如大数据、人工智能等)的集成,扩展应用场景。
-
行业解决方案:
- 针对不同行业(如智能家居、工业互联网、智慧城市等)提供定制化解决方案。
- 帮助企业快速部署物联网应用,提升运营效率。
应用场景:
- 智能家居:通过物联网设备实现家庭自动化和远程控制。
- 工业自动化:监控和管理生产设备,提高生产效率和安全性。
- 智慧城市:通过传感器和数据分析提升城市管理和服务水平。
在准备ESP32连接阿里云的前题首先我们的MCU得具有上网的能力,我们可以通过自己的手机热点给MCU以提供连接Intnet的能力。对于使用Micropython来说我们可以调用network库,利用其中的API函数进行网络连接。
import network
import time
#建立WiFi连接
def wifi_conn():
ssid_c='xx' #要接入热点的SSID和密码
password = 'xxxxxx'
wlan = network.WLAN(network.STA_IF) # 创建STA模式
wlan.active(True) # 激活
# 检测是否已经连接WiFi
count=0 #尝试连接10次
if wlan.isconnected():
print('\033[31m当前esp32已连接WiFi,ip地址:%s \033[0m'%(wlan.ifconfig()[0]))
else:
# 搜索WiFi
print('搜索WiFi信号...')
ssid_all = wlan.scan() # 扫描附近的WiFi接入点,扫描的时候 定时器无法执行
get_ssid = 0
for ssid in ssid_all:
#print('%s\n'%str(bytes.decode(ssid[0])))
if ssid_c == bytes.decode(ssid[0]):
get_ssid = 1
if get_ssid == 1:
print('找到热点,准备连接。')
# 连接WiFi
try:
wlan.connect(ssid_c,password) # WiFi名,密码
print('正在连接...',end="")
while not wlan.isconnected() and count<10:
print('.',end="")
time.sleep(1)
count += 1
except:
print('WiFi连接异常,请检查配置')
sys.exit(-1)
else:
print('\033[31m 已连接,WiFi名:%s IP地址:%s \033[0m'%(ssid_c,wlan.ifconfig()[0]))
else:
print('找不到WiFi热点,请检查热点是否开启。')
若使用STM32来进行WiFi连接的话,因为该芯片没有集成WIFI模块,所以通常我们将会外接一个ESP8266模块进行网络的连接。
大佬的视频教程:【【新版OneNet云平台】STM32+ESP8266上传数据,简单易上手!】https://www.bilibili.com/video/BV1jS421A7Bk?vd_source=a6ff43fa7d7dafb91261e3bcfa491108
【ESP8266固件烧录详解】https://www.bilibili.com/video/BV1Mx4y1P72o?vd_source=a6ff43fa7d7dafb91261e3bcfa491108
WIFI模块ESP8266-01S-优快云博客 该博客有具体的使用方法,以及操作教程。


我用的是ESP8266-01s基本功能都大差不差,仅是有些地方有所区别,需要注意的是若想要对01s型号的模块烧录固件时需要将IO0引脚接地,不然会导致烧录失败,烧录完毕后即可拔出,而后使用将该引脚接在高电平上或者悬空。
MCU通过串口与该模块进行通信,通过AT指令来达到连接WIFI的目的。
串口通信:


下图中S代表的起始位,往后8个位均为数据位,P为停止位,数据校验方法有奇校验和偶校验。奇校验是指数据位中1的个数为偶数个则补一个1,反则补0。停止位为数据为结束后,将电平拉回高电平,便于下次的数据传输起始位拉低电平。
波特率发生器将传入的时钟信号转换得到所需的波特率信号(波特率是由USART_BRR波特率寄存器控制的,TR/Tx的波特率 = 串口时钟 / BRR的值。
一般我们先确定波特率,通过计算得到BRR的值,并将其赋给USART_BRR寄存器。如串口时钟72M,选用波特率115200,BRR的值为72M÷115200=625(16进制0x0271),BRR寄存器的值就为0x0271),发送控制器根据传入的波特率信号来控制数据的发送时序,当数据准备完毕后,CPU将数据写入发送数据寄存器TDR中,而后将写入的数据传入发送移位寄存器,最后通过TX引脚将数据传出。反之接收控制器是根据波特率信号来控制数据接收的时序,当数据从RX引脚写入至接收移位寄存器后,接收移位寄存器将数据传入接收数据寄存器RDR中,CPU可从该寄存器读取从RX引脚传入的数据。


STM32代码:USART1串口初始化以及简单的发送和接收函数的大概过程,将PA10作为接收引脚,P9作为输出引脚。输入引脚需要接收外部设备的高低电平信号,所以设置为浮空输入模式。输出引脚需要具有输出高低电平的能力于是只能使用推挽输出,复用是因为信号来自外设串口USART1。
#include "stm32f10x.h" // Device header
uint8_t Serial_RxData; //定义串口接收的数据变量
uint8_t Serial_RxFlag; //定义串口接收的标志位变量
void NewSerial_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitStructure.USART_BaudRate=9600; //波特率定义为9600
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流控制
USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //工作模式发送和接收都开
USART_InitStructure.USART_Parity=USART_Parity_No; //无校验位
USART_InitStructure.USART_StopBits=USART_StopBits_1; //一位停止位
USART_InitStructure.USART_WordLength=USART_WordLength_8b; //八个数据位
USART_Init(USART1,&USART_InitStructure);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //开启串口中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn; //选择配置NVIC的USART1线
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; //相应优先级
NVIC_Init(&NVIC_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //GPIO_Pin_9 设置位复用推挽输出 作为TX脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; // GPIO_Pin_10 设置为浮空输入 作为RX脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_Cmd(USART1,ENABLE); //使能USART1 开启串口1
}
void USART1_IRQnHandler(void)
{
if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET) //判断是否是USART1的接收事件触发的中断
{
Serial_RxData=USART_ReceiveData(USART1); //提取接收数据
Serial_RxFlag=1; //接收标志位
USART_ClearFlag(USART1,USART_IT_RXNE); //清除中断标志位
}
}
void NewSerial_SendByte(uint16_t Data)
{
USART_SendData(USART1,Data); //发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET); //等待发送完成 数据移位寄存器是否为空 空就说明发送完毕 返回SET
}
阿里云基本操作流程:







下图为设备三元组,用于绑定MCU绑定该设备所需的数据。

下图为MQTT连接参数

设备激活后即可通过下图来创建功能来接收MCU通过Jason格式上传的带有特定标识符的数据,云平台可通过特定的标识符来识别提取相关的数据



设备激活后,创建所需要的功能,发布后该功能即激活,若接收到以LEDSwitch为标识符的数据为0则表示关闭。为1则表示打开。


下图为查看MCU上发数据的主题类型和数据。

常见的一种物理型通信主题具有发布功能或订阅功能,通过自定义Topic类也可让一种主题同时具有发布和订阅的功能。MCU上传数据时至服务端MQTT所需的Topci类则为发布,接收云平台设备下发的数据至,MCU若需要该数据则需要通过MQTT服务端订阅该云平台设备,所需的Topic类的功能则为订阅。好比于抖音平台,抖音用户想要及时获取到自己喜欢的博主所发布的内容,则需要在抖音平台关注该博主,若该博主在抖音更新内容,抖音平台则会通知关注该博主的用户们。对于MQTT的讲解参考该文章:MQTT协议详解(完整版)-优快云博客


在了解Topic类后,我们可以通过Topic主题使得MCU于云平台相连,通过云平台可以实时查看MCU上传的各类传感器数据。但是在云平台查看数据会显得十分繁琐,于是我们可以通过云平台的数据流转功能,将数据下发至手机APP或者微信小程序,通过手机便可以实时查看。

红框内的topic主题可通过前面的自定义topic类添加




红框内的1002为数据目的ID号







自定义的报文均为发布主题。

# -*- coding: utf-8 -*-
# 说明:通过MQTT,连接阿里云物联网平台
# author: Clark
# 2023-9-15
#####################################################
from simple import MQTTClient
from machine import Pin
import network
import machine
import time
import json
sensor = HCSR04(trigger_pin=33, echo_pin=27) # 定义超声波模块Tring控制管脚及超声波模块Echo控制管脚
client = None
led = Pin(18, Pin.OUT, value=1) # ESP32的引脚设置
# 根据个人在阿里云物联网服务器上的信息进行替换
ProductKey = "k1dzmKOlajU"
DeviceName = "PZ12"
DeviceSecret = "dd57c299d2de990af3c4bb15301dfdcb"
LedStateFlag=0
LEDSwitchFlag=0
# 从阿里云物联网平台的 MQTT连接参数进行复制
ClientId = "k1dzmKOlajU.PZ12|securemode=2,signmethod=hmacsha256,timestamp=1725783353820|"
user_name = "PZ12&k1dzmKOlajU"
user_password = "12b329d67077a58ed05432a434fa0906231adb07b37dc11d76ce35d4f100dd0d"
mqttHostUrl = 'iot-06z00b0g1usm1zk.mqtt.iothub.aliyuncs.com'
port = 1883
topic_rep = '/sys/k1dzmKOlajU/PZ12/thing/event/property/post_reply' # 云端响应反馈信息主题
topic_pos = '/sys/k1dzmKOlajU/PZ12/thing/event/property/post' # 设备推送到云端消息的主题
topic_get = '/k1dzmKOlajU/PZ12/user/get' # 订阅 PZ12 主题
topic_pos1='/k1dzmKOlajU/PZ12/user/ESP32Duan' #设备发布
def ali_publish():
global client,sensor
global led,LedStateFlag,LEDSwitchFlag
temperature, humidity = DHT11.DHT11getMessage()
distance,dflag=hcsr04danger()
send_mseg = "{\"params\": {\"LedState\": %d, \"LEDSwitch\": %d,\"temp_c\": %d,\"humidity_RH\": %d,\"Distance\": %.2f,\"dangerflag\": %d}}" % (LedStateFlag,LEDSwitchFlag,temperature,humidity,distance,dflag) # 发布信息的字符串
client.publish(topic=topic_pos, msg=send_mseg, retain=True) # 设备发布主题下的相关信息
client.publish(topic=topic_pos1, msg=send_mseg, retain=True) # 设备发布主题下的相关信息
print("Published message: {send_mseg}") # 打印发送的消息
def sub_cb(topic, msg):
global led,LedStateFlag,LEDSwitchFlag
print("Received message on topic {topic}: {msg}") # 打印接收到的消息
msg = json.loads(msg.decode('utf-8')) # 确保消息已解码为字符串,然后转换为字典
print(msg)
如果JASON格式出错,则设备无法接收数据("{\"params\": {\"LedState\": %d, \"LEDSwitch\": %d,\"temp_c\": %d,\"humidity_RH\": %d,\"Distance\": %.2f,\"dangerflag\": %d}}" % (LedStateFlag,LEDSwitchFlag,temperature,humidity,distance,dflag) # 发布信息的字符串)
完整工程代码链接:百度网盘 请输入提取码
5046

被折叠的 条评论
为什么被折叠?



