更新了ds18x20的分辨率设置方法。
最终的工程文件跳转:最终文件
满足下面的需求:
需求:需要做一个温湿度测试仪。
一、使用DHT11检测温度值、湿度值;
二、使用DS18B20检测温度值;
竖着拿单片机时(如图1)在OLED上显示DHT11湿度值、温度值、DS18B20温度值三者的曲线(设计的界面应包括坐标轴、单位,屏幕上应实时显示当前三者分别的测量值);
三、横着拿单片机时(如图2)在OLED上显示DHT11湿度值、温度值、DS18B20温度值三者的柱状图(设计的界面应包括坐标轴、单位,屏幕上应实时显示当前三者分别的测量值);
四、以上“横着”、“竖着”屏幕显示模式切换应该是自动的,不是手动按按钮控制的(提醒使用板载的重力加速度传感器);
五、可以通过按钮控制DHT11湿度值、温度值、DS18B20温度值三者的报警阈值,检测到的数据超过阈值则蜂鸣器报警,对应的LED闪烁(或在屏幕上显示是哪个测量值超过阈值了);
六、报警阈值存储到板载EEPROM芯片中,实现掉电保存。
可移动硬盘中的有几个重要的文件 boot.py、main.py、pybcdc.inf。如果板子正常启动,上电先会运行 boot.py,然后再配置 USB,最后运行 main.py。其中 pybcdc.inf 就是上面所说的要安装的驱动。
import machine
import pyb
pyb.country('US') # ISO 3166-1 Alpha-2 code, eg US, GB, DE, AU
pyb.main('main.py')
#这个是指定运行一下main.py的文件
main函数导入的包
import pyb, _thread,time,onewire,ds18x20,dht #引用 pyb,线程,时间,一线通讯,18b20
from pyb import Pin#引用引脚
from ssd1306 import SSD1306#引用 SS1306
from machine import Pin#引用引脚
from pyb import Accel,Timer,I2C,Pin, ExtInt,Switch
前置部件的学习
OLED的使用
OLED的使用可以调用SSD1306.py这个文件创建对象来实现。
下面是ssd1306.py的代码,可以添加和修改函数。可以之后再来看。
import pyb
import font
# Constants
DISPLAYOFF = 0xAE
SETCONTRAST = 0x81
DISPLAYALLON_RESUME = 0xA4
DISPLAYALLON = 0xA5
NORMALDISPLAY = 0xA6
INVERTDISPLAY = 0xA7
DISPLAYON = 0xAF
SETDISPLAYOFFSET = 0xD3
SETCOMPINS = 0xDA
SETVCOMDETECT = 0xDB
SETDISPLAYCLOCKDIV = 0xD5
SETPRECHARGE = 0xD9
SETMULTIPLEX = 0xA8
SETLOWCOLUMN = 0x00
SETHIGHCOLUMN = 0x10
SETSTARTLINE = 0x40
MEMORYMODE = 0x20
COLUMNADDR = 0x21
PAGEADDR = 0x22
COMSCANINC = 0xC0
COMSCANDEC = 0xC8
SEGREMAP = 0xA0
CHARGEPUMP = 0x8D
EXTERNALVCC = 0x10
SWITCHCAPVCC = 0x20
SETPAGEADDR = 0xB0
SETCOLADDR_LOW = 0x00
SETCOLADDR_HIGH = 0x10
ACTIVATE_SCROLL = 0x2F
DEACTIVATE_SCROLL = 0x2E
SET_VERTICAL_SCROLL_AREA = 0xA3
RIGHT_HORIZONTAL_SCROLL = 0x26
LEFT_HORIZONTAL_SCROLL = 0x27
VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29
VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A
# I2C devices are accessed through a Device ID. This is a 7-bit
# value but is sometimes expressed left-shifted by 1 as an 8-bit value.
# A pin on SSD1306 allows it to respond to ID 0x3C or 0x3D. The board
# I bought from ebay used a 0-ohm resistor to select between "0x78"
# (0x3c << 1) or "0x7a" (0x3d << 1). The default was set to "0x78"
DEVID = 0x3c
# I2C communication here is either <DEVID> <CTL_CMD> <command byte>
# or <DEVID> <CTL_DAT> <display buffer bytes> <> <> <> <>...
# These two values encode the Co (Continuation) bit as b7 and the
# D/C# (Data/Command Selection) bit as b6.
CTL_CMD = 0x80
CTL_DAT = 0x40
class SSD1306(object):
def __init__(self, pinout, height=32, external_vcc=True, i2c_devid=DEVID):
# 设置基本参数
self.external_vcc = external_vcc
self.height = 32 if height == 32 else 64
self.pages = int(self.height / 8)
self.columns = 128
self.i2c = pyb.I2C(1)
self.i2c.init(pyb.I2C.MASTER, baudrate=400000) # 400kHz
self.devid = i2c_devid
self.offset = 1
self.cbuffer = bytearray(2)
self.cbuffer[0] = CTL_CMD
def clear(self):
self.buffer = bytearray(self.offset + self.pages * self.columns)
if self.offset == 1:
self.buffer[0] = CTL_DAT
def write_command(self, command_byte):
self.cbuffer[1] = command_byte
self.i2c.send(self.cbuffer, addr=self.devid, timeout=5000)
def invert_display(self, invert):
self.write_command(INVERTDISPLAY if invert else NORMALDISPLAY)
def display(self):
self.write_command(COLUMNADDR)
self.write_command(0)
self.write_command(self.columns - 1)
self.write_command(PAGEADDR)
self.write_command(0)
self.write_command(self.pages - 1)
if self.offset == 1:
self.i2c.send(self.buffer, addr=self.devid, timeout=5000)
else:
self.dc.high()
self.spi.send(self.buffer)
def set_pixel(self, x, y, state):
index = x + (int(y / 8) * self.columns)
if state:
self.buffer[self.offset + index] |= (1 << (y & 7))
else:
self.buffer[self.offset + index] &= ~(1 << (y & 7))
def init_display(self):
chargepump = 0x10 if self.external_vcc else 0x14
precharge = 0x22 if self.external_vcc else 0xf1
multiplex = 0x1f if self.height == 32 else 0x3f
compins = 0x02 if self.height == 32 else 0x12
contrast = 0xff # 0x8f if self.height == 32 else (0x9f if self.external_vcc else 0x9f)
data = [DISPLAYOFF,
SETDISPLAYCLOCKDIV, 0x80,
SETMULTIPLEX, multiplex,
SETDISPLAYOFFSET, 0x00,
SETSTARTLINE | 0x00,
CHARGEPUMP, chargepump,
MEMORYMODE, 0x00,
SEGREMAP | 0x10,
COMSCANDEC,
SETCOMPINS, compins,
SETCONTRAST, contrast,
SETPRECHARGE, precharge,
SETVCOMDETECT, 0x40,
DISPLAYALLON_RESUME,
NORMALDISPLAY,
DISPLAYON]
for item in data:
self.write_command(item)
self.clear()
self.display()
def poweron(self):
if self.offset == 1:
pyb.delay(10)
else:
self.res.high()
pyb.delay(1)
self.res.low()
pyb.delay(10)
self.res.high()
pyb.delay(10)
def poweroff(self):
self.write_command(DISPLAYOFF)
def contrast(self, contrast):
self.write_command(SETCONTRAST)
self.write_command(contrast)
def draw_text(self, x, y, string, size=1, space=1):
def pixel_x(char_number, char_column, point_row):
char_offset = x + char_number * size * font.cols + space * char_number
pixel_offset = char_offset + char_column * size + point_row
return self.columns - pixel_offset
def pixel_y(char_row, point_column):
char_offset = y + char_row * size
return char_offset + point_column
def pixel_mask(char, char_column, char_row):
char_index_offset = ord(char) * font.cols #按照一行是一个
return font.bytes[char_index_offset + char_column] >> char_row & 0x1
pixels = (
(pixel_x(char_number, char_column, point_row),
pixel_y(char_row, point_column),
pixel_mask(char, char_column, char_row))
for char_number, char in enumerate(string)
for char_column in range(font.cols)
for char_row in range(font.rows)
for point_column in range(size)
for point_row in range(1, size + 1))
for pixel in pixels:
self.set_pixel(*pixel)
这个函数库实现了对SSD1306 OLED显示屏的控制,特别是通过I2C接口与显示屏进行通信的功能。SSD1306是一种用于小尺寸显示屏的常见驱动芯片,广泛用于嵌入式系统、微控制器项目和显示简单文本或图形的应用中。该库的核心功能包括初始化显示屏、清屏、设置像素、显示文本和图像,以及控制显示屏的对比度、开启/关闭电源和反转显示内容。
以下是该库的一些主要功能:
- 显示初始化 (init_display): 设置显示屏的参数,如对比度、预充电周期、复用率、扫描方向等,并清空屏幕后显示。
- 清除屏幕 (clear): 清除显示缓冲区内容,方便后续绘制新的内容。
- 设置像素 (set_pixel): 根据指定的坐标设置单个像素的亮灭状态,允许自定义绘制图像或其他内容。
- 显示内容 (display): 将缓冲区中的内容发送到OLED显示屏进行显示。
- 反转显示 (invert_display): 可以反转屏幕显示,使得亮的像素变暗,暗的像素变亮。
- 调节对比度 (contrast): 调整显示屏的亮度,对比度越高显示越亮。
- 绘制文本 (draw_text): 可以在指定的坐标位置显示文本,支持不同的字体大小和间距。
- 开关屏幕 (poweron/poweroff): 控制屏幕的电源状态,节省电量。
- 通过I2C发送命令或数据: 利用
write_command
方法发送命令控制显示屏的操作,或者发送缓冲区数据实现显示效果。
总结来说,这个库主要用于嵌入式系统中的SSD1306 OLED显示屏控制,适合显示文本、简单的图形、和执行基本的显示屏管理操作。
写字体的小demo
从实际的显示出发。一个OLED的尺寸是64*128.
从 display.draw_text(x,y,“text”)入手。
x[0,128]y[0,64]
实际上,一个字符的高度大概是8个单位长度。可以装下21个字符,长度的占比是6个单位长度。安装上下比较美观的,行分隔比较大的,可以选择12个单位的间距。
display = SSD1306(pinout={'sda': 'PB7', 'scl': 'PB6'}, height=64, external_vcc=False)
display.poweron()#开启 OLED 电源
display.init_display()#初始化显示
display.draw_text(1,1,'YD-pyboard')#显示位置,显示内容,写入显存
display.draw_text(1,13,'www.vcc-gnd.com') #显示位置,显示内容
display.draw_text(1,25,'flyfish') #显示位置,显示内容
display.draw_text(1,37,'1234567890-=,.<>/;') #显示位置,显示内容
display.draw_text(1,49,'abcdefghijklmnopqrstu') #显示位置,显示内容
display.draw_text(1,56, 'YD-pyboard')#y最低可显示的位置。
说明,因为底层的python解释器,设置,无法进行text[::-1]这样的字符反转操作。
这个功能可以设置屏幕的亮(白屏)和暗(黑屏)
display.invert_display(True)
display.invert_display(False) # 禁用反转显示(恢复正常显示)
我们要绘制坐标轴 ,还有预警这些,需要写一个画线的函数,和画原点的函数。
画线的函数我们分为画直线的轴,还有画曲线的。在实现这个之后,我们认为线应该可以移动,坐标 轴和曲线在buff内可以重绘。
命令控制语句
两个参数
CTL_CMD = 0x80
CTL_DAT = 0x40
这两个命令是SSD1306 OLED控制器芯片的命令集。
CTL_CMD
(控制命令)和CTL_DAT
(数据命令)是SSD1306
OLED控制器芯片的命令集。它们用于控制OLED的显示模式、设置时钟、调整对比度等操作。
CTL_CMD
是一个字节,用于表示要发送的命令。CTL_DAT
是一个字节,用于表示要发送的数据。在向SSD1306
OLED控制器发送命令时,需要先发送CTL_CMD
,然后发送相应的数据。例如,要设置SSD1306
OLED的显示模式,需要先发送CTL_CMD
,然后发送一个表示显示模式的字节。不同的显示模式对应不同的字节值。总之,
CTL_CMD
和CTL_DAT
是SSD1306
OLED控制器芯片的命令集,用于控制OLED的显示模式、设置时钟、调整对比度等操作。
先了解代码的类的设置
def __init__(self, pinout, height=32, external_vcc=True, i2c_devid=DEVID):
self.external_vcc = external_vcc
self.height = 32 if height == 32 else 64
self.pages = int(self.height / 8)
self.columns = 128
self.i2c = pyb.I2C(1)
self.i2c.init(pyb.I2C.MASTER, baudrate=400000) # 400kHz
self.devid = i2c_devid
self.offset = 1#设置偏移量(offset)为1。偏移量用于指定缓冲区(cbuffer)中数据的起始位置。
self.cbuffer = bytearray(2)#创建一个长度为2的bytearray对象(cbuffer),用于存储控制命令(CTL_CMD)和后续的数据。
self.cbuffer[0] = CTL_CMD#将控制命令(CTL_CMD)写入cbuffer的第一个字节(索引为0)。
这段代码是用于初始化一个名为SSD1306
的类,该类用于控制一个OLED(电容式显示屏)模块。以下是代码的详细解释:
-
__init__
方法:这是一个类的构造函数,用于初始化类的属性和执行一些必要的操作。 -
pinout
:一个表示OLED模块引脚配置的参数。 -
height
:一个表示OLED模块像素高度的参数,默认为32。 -
external_vcc
:一个表示是否使用外部VCC(电源)的参数,默认为True。 -
i2c_devid
:一个表示I2C设备ID的参数,默认为DEVID
。 -
self.external_vcc
:将external_vcc
参数赋值给self.external_vcc
属性。 -
self.height
:将height
参数赋值给self.height
属性。如果height
不是32,则将其设置为64。 -
self.pages
:计算OLED模块的页数(即像素高度除以8)。 -
self.columns
:将columns
参数赋值给self.columns
属性。 -
self.i2c
:创建一个名为self.i2c
的pyb.I2C
对象,用于与OLED模块进行I2C通信。 -
self.i2c.init(pyb.I2C.MASTER, baudrate=400000)
:初始化self.i2c
对象,设置为主模式,波特率为400kHz。 -
self.devid
:将i2c_devid
参数赋值给self.devid
属性。 -
cbuff
:是控制缓冲区的意思
总之,这段代码的主要目的是初始化一个SSD1306
类的实例,该实例用于控制一个OLED模块。
使用bytearray作为显示缓冲区
每8行像素组成一个页(page),一页有8*128个像素。
显示数据按列优先方式组织。
清空缓冲区
def clear(self):
self.buffer = bytearray(self.offset + self.pages * self.columns)
if self.offset == 1:
self.buffer[0] = CTL_DAT#写数据标识
写命令的标识
def write_command(self, command_byte):
self.cbuffer[1] = command_byte
self.i2c.send(self.cbuffer, addr=self.devid, timeout=5000)
向一个设备(可能是传感器或执行器)发送命令的函数。函数的实现原理是通过I2C(Inter-IntegratedCircuit)通信协议将命令字节写入设备。
函数的输入参数是一个命令字节(command_byte),用于指定要执行的命令。函数首先将命令字节写入一个内部缓冲区(cbuffer)的第二个字节(索引为1),然后使用i2c库的send方法将缓冲区数据发送给设备。
设备地址(devid)和发送超时时间(timeout)是函数的附加参数,用于指定要通信的设备地址和发送数据时的超时时间。
下面给出命令注释
#define DISPLAYOFF 0xAE // 关闭显示器(Display Off)
#define SETCONTRAST 0x81 // 设置显示对比度(Set Contrast)
#define DISPLAYALLON_RESUME 0xA4 // 关闭像素关闭显示(显示内容恢复)(Display All On Resume)
#define DISPLAYALLON 0xA5 // 点亮所有像素(Display All On)
#define NORMALDISPLAY 0xA6 // 正常显示模式(Normal Display)
#define INVERTDISPLAY 0xA7 // 反转显示(Invert Display)
#define DISPLAYON 0xAF // 打开显示器(Display On)
#define SETDISPLAYOFFSET 0xD3 // 设置显示偏移(Set Display Offset)
#define SETCOMPINS 0xDA // 设置COM引脚硬件配置(Set COM Pins Hardware Configuration)
#define SETVCOMDETECT 0xDB // 设置VCOMH电平(Set VCOMH Deselect Level)
#define SETDISPLAYCLOCKDIV 0xD5 // 设置显示时钟分频(Set Display Clock Divide Ratio)
#define SETPRECHARGE 0xD9 // 设置预充电周期(Set Precharge Period)
#define SETMULTIPLEX 0xA8 // 设置多路复用率(Set Multiplex Ratio)
#define SETLOWCOLUMN 0x00 // 设置低列地址(Set Low Column Address)
#define SETHIGHCOLUMN 0x10 // 设置高列地址(Set High Column Address)
#define SETSTARTLINE 0x40 // 设置显示起始行(Set Display Start Line)
#define MEMORYMODE 0x20 // 设置内存地址模式(Set Memory Addressing Mode)
#define COLUMNADDR 0x21 // 设置列地址范围(Set Column Address)
#define PAGEADDR 0x22 // 设置页地址范围(Set Page Address)
#define COMSCANINC 0xC0 // 正常扫描COM(Scan COM from 0 to N-1)
#define COMSCANDEC 0xC8 // 反向扫描COM(Scan COM from N-1 to 0)
#define SEGREMAP 0xA0 // 正常段重映射(Set Segment Re-map)
#define CHARGEPUMP 0x8D // 设置电荷泵(Enable Charge Pump Regulator)
#define EXTERNALVCC 0x10 // 使用外部VCC电源(Use External VCC)
#define SWITCHCAPVCC 0x20 // 使用内部开关电容VCC电源(Use Internal Switch Cap VCC)
#define SETPAGEADDR 0xB0 // 设置页地址(Set Page Start Address)
#define SETCOLADDR_LOW 0x00 // 设置低列地址(Set Low Column Address)
#define SETCOLADDR_HIGH 0x10 // 设置高列地址(Set High Column Address)
#define ACTIVATE_SCROLL 0x2F // 启动滚动(Activate Scroll)
#define DEACTIVATE_SCROLL 0x2E // 停止滚动(Deactivate Scroll)
#define SET_VERTICAL_SCROLL_AREA 0xA3 // 设置垂直滚动区域(Set Vertical Scroll Area)
#define RIGHT_HORIZONTAL_SCROLL 0x26 // 向右水平滚动(Right Horizontal Scroll)
#define LEFT_HORIZONTAL_SCROLL 0x27 // 向左水平滚动(Left Horizontal Scroll)
#define VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29 // 垂直加右水平滚动(Vertical and Right Horizontal Scroll)
#define VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A // 垂直加左水平滚动(Vertical and Left Horizontal Scroll)
翻转和正常模式
def invert_display(self, invert):
self.write_command(INVERTDISPLAY if invert else NORMALDISPLAY)
显示(重要功能)
def display(self):
self.write_command(COLUMNADDR)#设置列地址范围
#接着,发送了两个参数,分别是0和self.columns - 1,用于设置列地址的范围。
self.write_command(0)
self.write_command(self.columns - 1)
self.write_command(PAGEADDR)#设置页地址范围
self.write_command(0)
self.write_command(self.pages - 1)
if self.offset == 1:
self.i2c.send(self.buffer, addr=self.devid, timeout=5000)
else:
self.dc.high()
self.spi.send(self.buffer)
更多OLED的使用
参考下面的文章OLED绘制文本
传感器的数据读取
传感器的初始化设置
import pyb, _thread,time,onewire,ds18x20,dht #引用 pyb,线程,时间,一线通讯,18b20
from pyb import Pin#引用引脚
from ssd1306 import SSD1306#引用 SS1306
from machine import Pin#引用引脚
display = SSD1306(pinout={'sda': 'PB7','scl': 'PB6'},height=64,external_vcc=False)#OLED 初始化
ow=onewire.OneWire(Pin('PB0'))#初始化 onewire
ds=ds18x20.DS18X20(ow)#初始化 18B20
dht = dht.DHT11(Pin("PB1"))#dht11 初始化
加速度传感器
记录加速度计
from pyb import Accel
accel = Accel()
print(accel.x(), accel.y(), accel.z(),accel.tilt())
#经过过滤的输出,前四次样本的采样和。
accel.filtered_xyz()
设备倾斜角的算法
在 MicroPython 的 pyb.Accel
类中,tilt()
方法用于计算设备的倾斜角度,通常是相对于地面的倾斜程度。倾斜角度可以用来判断设备相对于重力的方向。
tilt()
方法的计算
tilt()
方法通常会根据加速度计的 x、y 和 z 分量计算出设备的倾斜角度。以下是一个基本的思路:
-
获取加速度值:首先从加速度计中读取 x、y 和 z 的值。
-
计算倾斜角度:可以使用反正切函数(
atan2
)来计算相对于水平面的倾斜角度。公式如下:- 计算横向和纵向倾斜角度:
- 横向倾斜(Roll):
roll = atan2(y, z)
- 纵向倾斜(Pitch):
pitch = atan2(x, z)
- 横向倾斜(Roll):
- 根据需要将这些角度转换为度数(degrees)。
- 计算横向和纵向倾斜角度:
示例计算代码
下面是一个简单的示例代码,展示如何计算倾斜角度:
from pyb import Accel
import math
accel = Accel()
# 获取加速度值
x = accel.x()
y = accel.y()
z = accel.z()
# 计算倾斜角度
roll = math.atan2(y, z) * (180 / math.pi) # 转换为度
pitch = math.atan2(x, z) * (180 / math.pi) # 转换为度
print("Roll:", roll, "degrees")
print("Pitch:", pitch, "degrees")
上述是可能的计算方式之一。
def recordacc(sec):
time.sleep(sec)
# 打开文件以写入数据,无法写入文件,没有权限。
with open('accel_data.txt', 'w') as file:
for _ in range(100): # 记录100次数据,可以根据需要调整
x = accel.x() # 获取 x 轴加速度
y = accel.y() # 获取 y 轴加速度
z = accel.z() # 获取 z 轴加速度
tilt = accel.tilt() # 获取倾斜角度
print(accel.x(), accel.y(), accel.z(),accel.tilt())
file.write("{x}, {y}, {z}, {tilt}/n")
# 等待一段时间,以便获取新的数据
time.sleep(0.1) # 每0.1秒记录一次
因为,这个MicroPython可能无法访问文件系统,所以无法进行文件的读写。
取一些采集数据,观察一下静止的不同姿态的数据是否不同。
from pyb import Accel
accel = Accel()
for i in range(100):
print(accel.x(), accel.y(), accel.z(),accel.tilt())
#经过过滤的输出,前三次样本的采样和。
if(i%4==0)
print(accel.filtered_xyz()/4)
time.sleep(0.1) # 每0.1秒记录一次
我们直接运行不考虑过程的话。
平放在桌面上
1 -1 -21 26
(5, -5, -84)按照数据除以4来统计的话。就是(0,0,20)左右是平放的状态
直立起来的加速度传感器的数据状态。
-18 -3 -4 26
-20 -3 -4 26
-22 -3 0 26
-21 -2 -1 26
-20 -1 -2 26
-22 -3 -2 26
对于需要的那个状态的加速度传感器的数值是(0,20,0)左右设置检查的函数
abs(y - 20) < 4
温度测量器材的区别
DHT11 和 DS18B20 都是常用的温度传感器,但它们在性能和精度方面有所区别:
-
DHT11:
- 温度测量范围:0°C 到 50°C。
- 温度精度:±2°C。
- 湿度测量:支持,测量范围为 20% 到 90% RH,精度为 ±5% RH。
- 特点:DHT11 是一个低成本、低精度的传感器,适合一些对精度要求不高的环境监测应用。
-
DS18B20:
- 温度测量范围:-55°C 到 125°C。
- 温度精度:±0.5°C 在 -10°C 到 +85°C 的范围内,其他范围可能稍有降低。
- 湿度测量:不支持,仅用于温度测量。
- 特点:DS18B20 具有更宽的测量范围和更高的精度,适用于对温度监测精度要求较高的场景。
区别:
- 测量范围:DS18B20 的温度测量范围更宽,尤其适合低温和高温环境,而 DHT11 的测量范围相对有限。
- 精度:DS18B20 的温度测量精度更高,适用于需要较高温度精确度的场景。
- 功能:DHT11 可以同时测量温度和湿度,而 DS18B20 仅测量温度。
所以,如果你关注的是精度或需要监测极端温度,DS18B20 更合适;如果你还需要监测湿度且对温度精度要求不高,DHT11 是更经济的选择。
温湿度传感器(DHT11)
精度相对较低,响应时间较长,最小采样间隔为1s。受内部恢复时间的影响,不能频繁读取数据。
因此,下面采样的time.sleep时间是1s
dht = dht.DHT11(Pin("PB1"))
dht.measure()
dht.temperature()
dht.humidity()
#使用try except结构
对于DH11这个传感器
DS18X20检测:
类文件
from micropython import const
_CONVERT = const(0x44)
_RD_SCRATCH = const(0xbe)
_WR_SCRATCH = const(0x4e)
class DS18X20:
def __init__(self, onewire):
self.ow = onewire
self.buf = bytearray(9)
def scan(self):
return [rom for rom in self.ow.scan() if rom[0] in (0x10, 0x22, 0x28)]
def convert_temp(self):
self.ow.reset(True)
self.ow.writebyte(self.ow.SKIP_ROM)
self.ow.writebyte(_CONVERT)
def read_scratch(self, rom):
self.ow.reset(True)
self.ow.select_rom(rom)
self.ow.writebyte(_RD_SCRATCH)
self.ow.readinto(self.buf)
#循环冗余码的检查
if self.ow.crc8(self.buf):
raise Exception('CRC error')
return self.buf
def write_scratch(self, rom, buf):
self.ow.reset(True)
self.ow.select_rom(rom)
self.ow.writebyte(_WR_SCRATCH)
self.ow.write(buf)
def read_temp(self, rom):
buf = self.read_scratch(rom)
if rom[0] == 0x10:
if buf[1]:
t = buf[0] >> 1 | 0x80
t = -((~t + 1) & 0xff)
else:
t = buf[0] >> 1
return t - 0.25 + (buf[7] - buf[6]) / buf[7]
else:
t = buf[1] << 8 | buf[0]
if t & 0x8000: # sign bit set
t = -((t ^ 0xffff) + 1)
return t / 16
说明:
单线通信(OneWire)协议与传感器进行通信。
链接的GPIO端口是PB0
查看一个GPIO端口的使用情况
from machine import Pin
# 创建 Pin 对象
pin = Pin('PB0', Pin.IN) # 查询 GPIO0 的状态(输入模式)
# 读取引脚值
print(pin.value()) # 打印当前引脚的高低电平状态
工作流程
初始化 OneWire 和 DS18X20 设备:
from machine import Pin
import ds18x20
ow=onewire.OneWire(Pin('PB0'))
ds=ds18x20.DS18X20(ow)
扫描设备:
# 扫描 OneWire 总线,找到所有支持的设备(ROM 码)
roms = ds.scan()
print("Found devices:", roms)
开始温度转换:
# 开始温度转换
ds.convert_temp()
# 通常需要等待一段时间让转换完成
import time
time.sleep_ms(750) # 通常 DS18B20 需要 750ms
读取温度数据
# 读取每个设备的温度
for rom in roms:
temp = ds.read_temp(rom)
print("Temperature:", temp, "°C")
查看测试的输出:
>>> ds=ds18x20.DS18X20(ow)# 18B20
>>> roms = ds.scan()
>>> print("Found devices:", roms)
Found devices: [bytearray(b'(\xcbpt\x1e \x01\x93')]
如果有多个设备的时候
Found devices: [bytearray(b'(\xcbpt\x1e \x01\x93'), bytearray(b'(\x8b\xd7\xf1\x04\x00\x03\x1a')]
上述的几个设备初始化,只有ds18x20是属于onewire传送数据,需要使用rom来访问。
Ds18x20的进一步学习
可自行搜索文档
一个文档
ds18B20核心是直接温度-数字测量。温度转换可由用户,自定义9,10,11,12,从0.5,0.25,0.125,0.0625的分辨率。上电默认是12位转换精度。上电后工作在低功耗闲置状态下,主设备必须向DS18B20发送温度转换命令[44h]才能开始温度转换。转换后,温度转换的值会保存在暂存存储器的温度寄存器中,并恢复闲置状态。
温度寄存器的格式:
温度/数据对应关系:
上电复位时是+85C.
接下来研究一下存储器:
TH Register (Threshold High):高位温度触发点。(只读)
TL Register (Threshold Low):低位温度触发点。(只读)
温度报警寄存器
Temperature TH和TL:温度的高字节还有低字节。
Configuration Register (Config Reg):配置寄存器,包含分辨率和其他配置信息。
Temperature LSB (T_LSB):温度的低字节。
Reserved:保留字节,通常为 0xFF。
Reserved:保留字节,通常为 0xFF。
Reserved:保留字节,通常为 0xFF。
CRC:循环冗余校验码,用于验证数据的完整性。
使用写暂存寄存器命令[4Eh]才能将数据写入Byte2,3,4中,必须从Byte2中的最低位开始。
可以通过[BEh](读取命令)来检查是否修改了。
配置寄存器修改分辨率
可以看到有效的数据转换间隔是750ms.如果我们希望减少数据转化的时间,加快访问效率,就需要设置位00.降低分辨率
roms = ds. Scan()#此处只有一个rom
rom = rom[0]
#因为可以写入三个数据,所以要构造三字节的数据
#可以设置新的TH和TL
#也可以设置为原来的
scratch = ds.read_scratch(rom)
TH = scratchpad[2]
TL = scratchpad[3]
# 设置分辨率和其他配置
TH = 0x50 # 高位温度触发点,例如40.0°C
TL = 0x05 # 低位温度触发点,例如2.5°C
RESOLUTION_12_BIT = 0b01111111 # 12位分辨率
RESOLUTION_9_BIT =0b00011111 #0x1f
# 构建要写入的字节序列
buf = bytearray([TH, TL, RESOLUTION_9_BIT ])
ds.write_scratch(rom,buf)
#同理,可以通过读取来确定是否修改成功了
config_register = scratchpad[4]
#resolution_bits = (config_register >> 5) & 0b1
#resolution_bits = (config_register >> 6) & 0b1
线程设置和切换
_thread
库是 Python 中的一个低级线程模块,用于创建和操作线程。它是 threading
模块的底层实现。虽然功能相对简单,但可以用来启动线程、执行并发操作等。
_thread
模块中的主要函数:
-
_thread.start_new_thread(function, args[, kwargs])
启动一个新的线程并执行指定的函数。function
:在线程中运行的函数。args
:传递给函数的参数,必须是元组。kwargs
:可选的关键字参数(在某些实现中不一定支持)。
示例:
import _thread import time def print_numbers(thread_name, delay): for i in range(5): time.sleep(delay) print(f"{thread_name}: {i}") # 创建两个线程 _thread.start_new_thread(print_numbers, ("Thread-1", 1)) _thread.start_new_thread(print_numbers, ("Thread-2", 2)) # 主线程需要延迟,否则子线程可能不会有机会运行 time.sleep(10)
-
_thread.exit()
当前线程退出执行,不抛出异常。可以在任何地方调用来安全地退出线程。示例:
import _thread def some_task(): print("Thread starting") _thread.exit() # 退出线程 print("This will not be printed") _thread.start_new_thread(some_task, ())
-
_thread.allocate_lock()
分配一个新的锁对象,用于线程同步。锁有两个状态:locked
(锁定)和unlocked
(未锁定)。
相关的锁对象方法:lock.acquire(blocking=True)
:获取锁。如果blocking
为True
,线程会阻塞直到锁被释放。如果为False
,则尝试获取锁,若锁已经被占用,返回False
。lock.release()
:释放锁。
示例:
import _thread import time lock = _thread.allocate_lock() def thread_task(name): with lock: # 获取锁 print(f"{name} acquired lock") time.sleep(2) print(f"{name} released lock") # 创建两个线程,它们会争夺同一个锁 _thread.start_new_thread(thread_task, ("Thread-1",)) _thread.start_new_thread(thread_task, ("Thread-2",)) time.sleep(5) # 等待子线程完成
-
_thread.get_ident()
返回当前线程的标识符(一个整数)。每个线程有一个唯一的标识符,可以用来区分线程。示例:
import _thread def print_thread_id(): print(f"Thread ID: {_thread.get_ident()}") _thread.start_new_thread(print_thread_id, ())
-
_thread.exit_thread()
当前线程退出(和_thread.exit()
功能相同)。这是一个较老的函数,现在通常推荐使用_thread.exit()
代替。 -
_thread.interrupt_main()
中断主线程(发送一个KeyboardInterrupt
到主线程)。这可以用来从子线程中中断主线程的执行。示例:
import _thread import time def interrupt_main_thread(): time.sleep(2) print("Interrupting main thread") _thread.interrupt_main() _thread.start_new_thread(interrupt_main_thread, ()) try: while True: print("Main thread running...") time.sleep(1) except KeyboardInterrupt: print("Main thread interrupted")
-
_thread.stack_size([size])
获取或设置新线程的栈大小(单位:字节)。如果传递size
,则设置新线程的栈大小,并返回旧的栈大小;如果不传递参数,则返回当前的栈大小。不同平台对栈大小有不同的限制。示例:
import _thread # 获取当前的线程栈大小 current_stack_size = _thread.stack_size() print(f"Current stack size: {current_stack_size}")
总结
- 创建线程:
_thread.start_new_thread()
- 退出线程:
_thread.exit()
或exit_thread()
- 同步锁:
_thread.allocate_lock()
- 获取线程 ID:
_thread.get_ident()
- 中断主线程:
_thread.interrupt_main()
尽管 _thread
提供了基本的线程管理功能,但在实际应用中,推荐使用 threading
模块,因为它提供了更高级的功能(如守护线程、线程池、条件变量等),并且更安全易用。
定时器 Timer
from pyb import Timer#引入定时器
tim = Timer(1, freq=1000)#使用定时器 1,频率是 1000
tim.counter() # 获取当前计数值
tim.freq(0.5) # 0.5 Hz
tim.callback(lambda t: pyb.LED(1).toggle())#设置中断函数
有定时器,可以达到数据的采集的统一,因为我们可以使用中断去取数据,来达到不同频率的仪器,同步采集。
使用全局的数据变量来记录。
另外设置显示函数。
第一个需求(DHT11检测温度值和湿度值):
OLED显示数值
from ssd1306 import SSD1306#引用 SSD1306
import _thread,time,dht#引用线程,时间,dht 库
from machine import Pin#引用 PIN 库
display = SSD1306(pinout={'sda': 'PB7','scl':'PB6'},height=64,external_vcc=False)#显示OLED 初始化
dht = dht.DHT11(Pin("PB1"))#dht11 初始化
def funcA(sec):#线程 A
time.sleep(sec)#延时
try:#异常处理
display.poweron()#OLED 上电
display.init_display()#OLED 初始化
display.draw_text(1,1,'DHT11Tem')#OLED 显示内容 1
display.draw_text(1,13,'DHT11Hum')#OLED 显示内容 2
while True:
try:
dht.measure()#DHT 数据获取
print("temperature:",dht.temperature())#向 REPL 打印温度
print("humidity:",dht.humidity())#向 REPL 打印湿度
display.draw_text(55,1,str(round(dht.temperature(),1)))#OLED 显示温度
display.draw_text(55,13,str(round(dht.humidity(),1)))#OLED 显示湿度
except Exception as ex:#异常处理
print("DHT11 lose")#打印就是异常
display.display()#OLED 显示
time.sleep(1)#延时
except Exception as ex:#异常处理
print('Unexpected error: {0}'.format(ex))#异常处理函数
if __name__ == '__main__':#程序入口
_thread.start_new_thread(funcA, (1,))#线程 A
第一次实现的代码,后期,会把代码和布局进行整合。
从DHT11读取数据绘制曲线的代码
# 从左下角开始了
def draw_curve_from_sensor( start_x=90, step=1):
"""
从传感器数据中读取加速度值并绘制曲线。当 x 减少到 0 时,动态更新显示器。
:param start_x: 曲线起始的 x 坐标,默认 90.给前面留下数值的空间
:param step: 每次 y 坐标的增量
"""
# dht = dht.DHT11(Pin("PB1"))#dht11 初始化
# accel = Accel() # 初始化加速度传感器
x = start_x
# prev_y = 32 # 初始 y 值,OLED 显示屏中间(假设屏幕高度为64)
while x >= 0:
# 从传感器读取 y 数据
# accel_y = accel.y()
try:
dht.measure()#DHT 数据获取
display.draw_axes()
# 将加速度传感器的 y 值映射到显示屏的范围(0 到 height)
# mapped_y = int((accel_y + 10) * (self.height / 20)) # 将加速度值范围 [-10, 10] 映射到显示屏高度
# # 清除上一帧的像素
# self.clear()
tem1 = dht.temperature()
hum = dht.humidity()
mapped_tem1 = 54-int((tem1 - 5) / (50) * 54)
mapped_hum = 54-int((hum - 20) / (100-20) * 54)
# 绘制当前传感器值的点
display.set_pixel(x, mapped_tem1, 1)
display.set_pixel(x, mapped_hum, 1)
# 动态更新显示器
display.display()
# 控制绘制速度
pyb.delay(10)
# 更新 x 坐标和 y 值
x -= step
# 如果 x 坐标到达 0,重置为 98 以形成循环效果
if x < 0:
x = start_x
display.clear()
#在每次更新函数之后:重新设置坐标的内容
display.draw_axes()
display.draw_text(1,13,'Tem')#OLED 温度
display.draw_text(1,25,'Hum')#OLED 湿度
display.draw_text(1,37,'tem')# 精细的温度
# prev_y = mapped_y
except Exception as ex:#异常处理
print("DHT11 lose")#打印就是异常
display.display()
把前四个需求的处理部分进行类组织
我们可以把这个需求实现的东西,用一个类来进行组织。
先贴一个ai生成的搞笑的类图
AI画图太抽象了,按照功能来划分。
设置的全局变量
global update_flag_up # 加更新按钮
global update_flag_down# 减更新按钮
global update_mode # 模式切换
global mode_num # 修改数据的模式
global tem1_d # 第一个传感器的温度限制
global tem2_d # 第二个传感器的温度限制
global hum_d # 第一个传感器的湿度限制
global display_mode # 显示模式
传感器的初始化
传感器的初始化和数据存储空间的初始化。
ow=onewire.OneWire(Pin('PB0'))#初始化 onewire
ds=ds18x20.DS18X20(ow)#初始化 18B20
dht = dht.DHT11(Pin("PB1"))#dht11 初始化
accel = Accel()
i2c = I2C(1, I2C.MASTER, baudrate=100000)
default=30
b_default=default.to_bytes(1, 'big')
i2c.mem_write(b_default, 80, 0x10)#b'\x1e' 两种输入初值的方式
#睡眠一段时间,使设备正常工作
time.sleep_ms(50)
i2c.mem_write(b_default, 80, 0x20)
time.sleep_ms(50)
i2c.mem_write(b_default, 80, 0x30)
#蜂鸣器的初始化
buzzer = Pin('PA1')
tim = Timer(2, freq=1000)
ch = tim.channel(2, Timer.PWM, pin=buzzer)
#ch.pulse_width_percent(0)
传感器限制的初始化
def init_threshold(default):
global tem1_d
global tem2_d
global hum_d
if default != None:
b_default=default.to_bytes(1, 'big')
i2c.mem_write(b_default, 80, 0x10)#b'\x1e' 两种输入初值的方式
#睡眠一段时间,使设备正常工作
time.sleep_ms(50)
i2c.mem_write(b_default, 80, 0x20)
time.sleep_ms(50)
i2c.mem_write(b_default, 80, 0x30)
else:
# 读取EEPROM数据
data = i2c.mem_read(1, 80, 0x10)
tem1_d = int.from_bytes(data, 'big')
data = i2c.mem_read(1, 80, 0x20)
tem2_d = int.from_bytes(data, 'big')
data = i2c.mem_read(1, 80, 0x30)
hum_d = int.from_bytes(data, 'big')
print("tem1_d:", tem1_d)
print("tem2_d:", tem2_d)
print("hum_d:", hum_d)
架构设计
按照程序的功能和结构进行划分:
- 数据读取函数:用于读取加速度传感器和其他三个传感器的数据。
- 状态判断函数:用于判断是否切换显示模式,以及是否触发报警。
- 显示和报警函数:用于显示和报警信息
数据读取函数
def read_dht(self):
try:
self.dht.measure()
temperature = self.dht.temperature()
humidity = self.dht.humidity()
return temperature, humidity
except Exception as ex:
print("DHT11 error:", ex)
return None, None
def read_ds18b20(self):
try:
roms = self.ds.scan()
self.ds.convert_temp()
for rom in roms:
temperature = self.ds.read_temp(rom)
return temperature
except Exception as ex:
print("DS18B20 error:", ex)
return None
def read_accel(self):
try:
x, y, z = self.accel.x(), self.accel.y(), self.accel.z()
tilt = self.accel.tilt()
return x, y, z, tilt
except Exception as ex:
print("Accel error:", ex)
return None, None, None, None
状态判断函数
这些函数判断是否需要切换显示模式和是否触发报警。
# 判断是否需要切换显示模式
def check_mode(x, y, z):
#只对这个(0,20,0)进行判断
if abs(y - 20) < 4 :
# 判断是否触发报警
def check_alarm(tem1, tem2, hum):
#global tem1_d
#global tem2_d
#global hum_d
#不需要修改,此处只是说明是全局的限制值
# 初始化标志位
flag = 0
# 检查每个条件,将对应位置为 1
if tem1 > tem1_d:
flag |= 0b100 # 设置第一位为 1
if tem2 > tem2_d:
flag |= 0b010 # 设置第二位为 1
if hum > hum_d:
flag |= 0b001 # 设置第三位为 1
return flag # 返回二进制标志
显示和报警函数
负责显示模式和报警。
# 显示模式
def display_mode(mode, x, y, z, temperature, humidity, temp_ds18b20):
if mode == "default":
print(f"Displaying default mode: X={x}, Y={y}, Z={z}")
else:
print(f"Displaying alternate mode with additional info")
# 其他显示内容,如温湿度、DS18B20 温度
print(f"Temp: {temperature}, Humidity: {humidity}, DS18B20 Temp: {temp_ds18b20}")
def buzzer_alert(num):
ch.pulse_width_percent(num)
# 报警
def process_flag(flag):
# 根据 flag 的每一位来控制相应的 LED
if flag & 0b100: # 检查第一位,是否需要亮 LED1
pyb.LED(1).on()
if flag & 0b010: # 检查第二位,是否需要亮 LED2
pyb.LED(2).on()
if flag & 0b001: # 检查第三位,是否需要亮 LED3
pyb.LED(3).on()
# 统一延时2秒
buzzer_alert(60)
time.sleep(2)
buzzer_alert(0)
pyb.LED(1).off()
pyb.LED(2).off()
pyb.LED(3).off()
定时器回调函数
# 定时器回调函数
def timer_callback(timer):
# 读取数据
x, y, z = read_accel_data()
temperature, humidity, temp_ds18b20 = read_other_sensors()
# 判断模式和报警
mode = check_mode(x, y, z)
alarm_triggered = check_alarm(temperature, humidity, temp_ds18b20)
# 显示模式
display_mode(mode, x, y, z, temperature, humidity, temp_ds18b20)
# 报警
if alarm_triggered:
alarm()
# 初始化定时器,设置为 0.5 Hz
tim = Timer(1, freq=0.5)
tim.callback(timer_callback)
进一步优化
# 回调 1:快速读取传感器数据并存储在全局变量中
def sensor_data_callback(timer):
global sensor_data
sensor_data = read_all_sensors() # 尽量确保这个函数快速执行
# 存储数据后立即返回
# 回调 2:状态检查和报警
def check_status_callback(timer):
global sensor_data, mode
x, y, z, temperature, humidity, temp_ds18b20 = sensor_data
mode = check_mode(x, y, z)
if check_alarm(temperature, humidity, temp_ds18b20):
alarm()
# 仅做判断逻辑,避免阻塞
# 主线程:显示模式处理(不在定时器中)
while True:
x, y, z, temperature, humidity, temp_ds18b20 = sensor_data
display_mode(mode, x, y, z, temperature, humidity, temp_ds18b20)
time.sleep(1) # 显示内容的更新周期可以比数据采集周期慢
第五个需求:按键控制阈值
有三个按键。
一个USR按键和两个KEY1,KEY2。
第三个电路图的原理
读取usr按键
from pyb import Switch
sw =Switch()
sw.value()
按下按键可以使用回调函数
from pyb import Switch
sw = Switch()
sw.callback(lambda: pyb.LED(1).toggle())
#里面就是一个自定义函数,修改LED1的电位
KEY1(S1),KEY2(S2)如何使用输入功能和中断输入功能
S1对应的是PB9,S2对应的是PB8
from pyb import Pin #引用 PIN 库函数
p_in = Pin('PB9', Pin.IN, Pin.PULL_UP)#设置输入上拉输入状态
p_in.value()#询问引脚的状态
Pin:这是一个类,用于表示微控制器上的一个引脚。
‘PB9’:这是引脚的名称,表示具体的引脚编号。不同的微控制器有不同的引脚命名规则,PB9可能表示B组的第9个引脚。
Pin.IN:这是引脚的一个属性,设置引脚为输入模式。
Pin.PULL_UP:这是引脚的另一个属性,启用内部上拉电阻。上拉电阻的作用是在引脚悬空时提供一个默认的高电平状态。
编写中断程序
from pyb import Pin, ExtInt#应引用 Pin 库,和外部中断库
callback = lambda e: print("PB9 is passed")#编写中断处理程序
ext = ExtInt(Pin('PB9'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback)#初始化输入
from pyb import Pin, ExtInt
def callback(line):
print("PB9 is passed")
ext = ExtInt(Pin('PB9'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback)
Pin用于控制引脚,ExtInt用于处理外部中断。
ext = ExtInt(Pin(‘PB9’), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback):
Pin(‘PB9’):指定使用PB9引脚。
ExtInt.IRQ_RISING:设置中断类型为上升沿触发,即当引脚从低电平变为高电平时触发中断。
Pin.PULL_NONE:不启用内部上拉或下拉电阻。
callback:指定中断触发时调用的回调函数。
也就是说点击之后触发。
eeprom存储
使用24C08来进行读写.
24c08 是一个非遗失 eeprom 存储器器件,采用的 IIC 总线技术。 24c08 在许多试验中都有出现。 24c02 的应用,主要在存储一些掉电后还要保存数据的场合,在上次运行时,保存的数据,在下一次运行时还能够调出。
PB6 连接 SCL,PB7 连接 SDA,这两个通讯线都实际进行了上拉电阻,大部分 I2C 都需要进行上拉处理。
import pyb
from pyb import Pin,Timer,I2C
from ssd1306 import SSD1306
import _thread,time
from machine import Pin
display = SSD1306(pinout={'sda': 'PB7','scl': 'PB6'},height=64,external_vcc=False)
def funcA(sec):
time.sleep(sec)
#这里1表示I2C总线的编号,I2C.MASTER表示设备作为主设备,baudrate=100000设置通信的波特率。
i2c = I2C(1, I2C.MASTER, baudrate=100000)
print("scan I2C",i2c.scan())#[60, 76, 80, 81, 82, 83],扫描到的设备
time.sleep_ms(50)
print("write 24C08 yd")
i2c.mem_write('yd', 80, 0x10) # 写入数据,写入设备,写入起始地址
time.sleep_ms(50)
print("read 24C08",i2c.mem_read(2, 80, 0x10))#读取两个字节,读取设备
time.sleep_ms(50)
ONtimes=i2c.mem_read(1, 80, 0x14)#读取一个特定的地址(0x14),这里假设这个地址存储了一个计数器的值,并将其转换为整数
x = int.from_bytes(ONtimes,'big')#'big'和'little'是大端和小端存储。有from_bytes,还有to_bytes,x.to_bytes(length, 'big').
print("ONtimes",x)
x=x+1
#重新写入
b = x.to_bytes(1,'big')
i2c.mem_write( b, 80, 0x14)
try:
display.poweron()
display.init_display()
display.draw_text(1,1,'24C08 TEST')
display.draw_text(1,13,'24C08 times')
display.draw_text(105,13,str(x-1))
while True:
display.display()
time.sleep_ms(30)
except Exception as ex:
print('Unexpected error: {0}'.format(ex))
if __name__ == '__main__':
_thread.start_new_thread(funcA, (1,))
这些地址通常是7位的,但在某些情况下,它们可能被扩展为8位。在MicroPython中,地址通常以7位的形式表示,最左边的位是读/写位,用于指示接下来的操作是读操作还是写操作。
关于存储的说明:
24C08 是一款由 Microchip Technology 生产的 EEPROM(电可擦除可编程只读存储器),其存储容量为 8K bits,即 1024 字节。
存储容量:8Kbit(即1024字节)。
页大小:8字节。
地址范围:从0x00到0x3FF。
0x00-0x3ff有1024个地址,每位存一个字节。
页地址是整页的,0x00第一页,0x10第二页。
在这个代码中,我们访问非整页地址会受限。
ONtimes=i2c.mem_read(1, 80, 0x14)
说明:本来打算用中断来设置内存的数据,但是中断时,会有堆栈保存,修改内存,这种耗时和修改不被允许。
给出的方案是根据修改标志,设置循环的线程来检查标志,在里面执行修改操作.
from pyb import Accel,Timer,I2C,Switch
#记住在开头声明全局,并进行赋值
global update_flag_up
global update_flag_down
global update_mode
global mode_num
update_flag_up=False
update_flag_down=False
update_mode=False
mode_num = 0#0,1,2
#三个值代表温度DH1,温度XD2,湿度
default=30
b_default=default.to_bytes(1, 'big')
i2c.mem_write(b_default, 80, 0x10)#b'\x1e' 两种输入初值的方式
i2c.mem_write(b_default, 80, 0x20)
i2c.mem_write(b_default, 80, 0x30)
def callback1(line):
global update_flag_up
update_flag_up = True
def callback2(line):
global update_flag_down
update_flag_down = True
def callback3(line):
global update_mode
update_mode = True
def detect_sign3(sec):
time.sleep(sec)
global update_mode
global mode_num
while True:
if update_mode:
# 重置标志
update_mode = False
mode_num = (mode_num + 1)%3
print("mode:", mode_num)
def detect_sign1(sec):
time.sleep(sec)
global update_flag_up
while True:
if update_flag_up:
# 重置标志
update_flag_up = False
time.sleep_ms(50)
# 读取EEPROM中的数据
if 0 ==mode_num:
addr=0x10
if 1 ==mode_num:
addr=0x20
if 2 ==mode_num:
addr=0x30
data = i2c.mem_read(1, 80,addr)
x = int.from_bytes(data, 'big')
# 数据加一
x = x + 1
# 将新的数据写回EEPROM
b = x.to_bytes(1, 'big')
i2c.mem_write(b, 80, addr)
time.sleep_ms(50)
# 打印新的数据
print("Updated value:", x)
# 避免CPU占用过高,添加短暂的延时
time.sleep_ms(10)
def detect_sign2(sec):
time.sleep(sec)
global update_flag_down
while True:
if update_flag_down:
# 重置标志
update_flag_down = False
time.sleep_ms(50)
# 读取EEPROM中的数据
if 0 ==mode_num:
addr=0x10
if 1 ==mode_num:
addr=0x20
if 2 ==mode_num:
addr=0x30
data = i2c.mem_read(1, 80,addr)
x = int.from_bytes(data, 'big')
x = x -1
# 将新的数据写回EEPROM
b = x.to_bytes(1, 'big')
i2c.mem_write(b, 80, addr)
time.sleep_ms(50)
# 打印新的数据
print("Updated value:", x)
# 避免CPU占用过高,添加短暂的延时
time.sleep_ms(10)
def detect_sign3(sec):
time.sleep(sec)
global update_mode
global mode_num
while True:
if update_mode:
# 重置标志
update_mode = False
mode_num = (mode_num + 1)%3
print("mode:", mode_num)
#主函数调用代码
ext = ExtInt(Pin('PB9'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback1)
ext = ExtInt(Pin('PB8'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback2)
sw = Switch()
sw.callback(callback3)
_thread.start_new_thread(detect_sign1, (1,))
_thread.start_new_thread(detect_sign2, (1,))
_thread.start_new_thread(detect_sign3, (1,))
上述的代码会有一些冗余
使用这个代码来进行替换detect_sign1和2函数
def detect_sign(sec):
time.sleep(sec)
global update_flag_up, update_flag_down
global tem1_d, tem2_d, hum_d
while True:
if update_flag_up or update_flag_down:
time.sleep_ms(200)
# 确定当前模式对应的地址和变量
mode_map = {
0: (0x10, 'tem1_d'),
1: (0x20, 'tem2_d'),
2: (0x30, 'hum_d')
}
if mode_num in mode_map:
addr, var_name = mode_map[mode_num]
# 根据标志决定是加一还是减一
delta = 1 if update_flag_up else -1
# 更新对应的全局变量
if var_name == 'tem1_d':
tem1_d += delta
elif var_name == 'tem2_d':
tem2_d += delta
else: # var_name == 'hum_d'
hum_d += delta
# 读取EEPROM数据
data = i2c.mem_read(1, 80, addr)
x = int.from_bytes(data, 'big') + delta
# 写回EEPROM
b = x.to_bytes(1, 'big')
i2c.mem_write(b, 80, addr)
print("Updated value:", x)
# 重置标志
update_flag_up = False
update_flag_down = False
time.sleep_ms(10)
蜂鸣器警报和LED
让三个LED,根据模式进行报警
from pyb import LED
#因为模式设置是0,1,2
#我们希望是2,3,4对应报警亮灯
led = LED()
while True:
led.toggle()
pyb.delay(500)
led.toggle()
pyb.delay(500)
#设置蜂鸣器
buzzer = Pin('PA1') # X1 has TIM2, CH1,
tim = Timer(2, freq=1000) #2 是代表是定时器 2,freq=1000 是设置为 1kHz
ch = tim.channel(2, Timer.PWM, pin=buzzer)#1 代表是通道 2,模式为 PWM,pin=p 引脚上述设置的语句
ch.pulse_width_percent(0)#占空比为 50%