一 、Python基础知识精简版
- # 用井字符开头的是单行注释
- """
- 多行字符串用三个引号包裹,也常被用来做多行注释
- """
1.1 原始数据类型和运算符
- #除法会自动转换成浮点数
35 / 5 # => 7.0
5 / 3 # => 1.6666666666666667
- # 整数除法的结果都是向下取整 //为取整
5 // 3 # => 1
5.0 // 3.0 # => 1.0 # 浮点数也可以
-5 // 3 # => -2
-5.0 // 3.0 # => -2.0
- # 浮点数的运算结果也是浮点数
3 * 2.0 # => 6.0
- # x 的 y 次方
2**4 # => 16
- # 用 not 取非
not True # => False
not False # => True
- # 逻辑运算符,注意 and 和 or 都是小写
True and False # => False
False or True # => True
- # 整数也可以当作布尔值
0 and 2 # => 0
-5 or 0 # => -5
0 == False # => True
2 == True # => False
1 == True # => True
- # 大小比较可以连起来!
1 < 10 # => True
1 > 10 # => False
1 < 2 < 3 # => True
2 < 3 < 2 # => False
- # 字符串用单引双引都可以
"这是个字符串"
'这也是个字符串'
- # 用加号连接字符串
"Hello " + "world!" # => "Hello world!"
- # 字符串可以被当作字符列表
"This is a string"[0] # => 'T'
- # 用.format 来格式化字符串
"{} can be {}".format("strings", "interpolated")
- # 可以重复参数以节省时间
"{0} be nimble, {0} be quick, {0} jump over the {1}".format("Jack", "candle stick")
# => "Jack be nimble, Jack be quick, Jack jump over the candle stick"
- # 如果不想数参数,可以用关键字
"{name} wants to eat {food}".format(name="Bob", food="lasagna")
# => "Bob wants to eat lasagna"
- # 如果你的 Python3 程序也要在 Python2.5 以下环境运行,也可以用老式的格式化语法(不常用)
"%s can be %s the %s way" % ("strings", "interpolated", "old")
- # None 是一个对象
None # => None
- # 当与 None 进行比较时不要用 ==,要用 is。is 是用来比较两个变量是否指向同一个对象。
"etc" is None # => False
None is None # => True
- # None,0,空字符串,空列表,空字典都算是 False # 所有其他值都是 True
bool(0) # => False
bool("") # => False
bool([]) # => False
bool({}) # => False
1.2 变量和集合
- # print 是内置的打印函数
print("I'm Python. Nice to meet you!")
- # 在给变量赋值前不用提前声明
- # 传统的变量命名是小写,用下划线分隔单词
some_var = 5
some_var # => 5
- # 访问未赋值的变量会抛出异常
- # 用列表(list)储存序列
li = []
- # 创建列表时也可以同时赋给元素
other_li = [4, 5, 6]
- # 用 append 在列表最后追加元素
li.append(1) # li 现在是[1]
li.append(2) # li 现在是[1, 2]
li.append(4) # li 现在是[1, 2, 4]
li.append(3) # li 现在是[1, 2, 4, 3]
- # 用 pop 从列表尾部删除
li.pop() # => 3 且 li 现在是[1, 2, 4]
# 把 3 再放回去 li.append(3) # li 变回[1, 2, 4, 3]
- # 列表存取跟数组一样
li[0] # => 1
# 取出最后一个元素
li[-1] # => 3
# 越界存取会造成 IndexError
li[4] # 抛出 IndexError
- # 列表有切割语法
li[1:3] # => [2, 4]
# 取尾
li[2:] # => [4, 3]
# 取头
li[:3] # => [1, 2, 4]
# 隔一个取一个
li[::2] # =>[1, 4]
# 倒排列表
li[::-1] # => [3, 4, 2, 1]
# 可以用三个参数的任何组合来构建切割
# li[始:终:步伐]
- # 用 del 删除任何一个元素
del li[2] # li is now [1, 2, 3]
- # 列表可以相加
# 注意:li 和 other_li 的值都不变
li + other_li # => [1, 2, 3, 4, 5, 6]
- # 用 extend 拼接列表
li.extend(other_li) # li 现在是[1, 2, 3, 4, 5, 6]
- # 用 in 测试列表是否包含值
1 in li # => True
- # 用 len 取列表长度
len(li) # => 6
- # 元组是不可改变的序列
tup = (1, 2, 3)
tup[0] # => 1
tup[0] = 3 # 抛出 TypeError
- # 列表允许的操作元组大都可以
len(tup) # => 3
tup + (4, 5, 6) # => (1, 2, 3, 4, 5, 6)
tup[:2] # => (1, 2)
2 in tup # => True
- # 可以把元组合列表解包,赋值给变量
a, b, c = (1, 2, 3) # 现在 a 是 1,b 是 2,c 是 3
- # 元组周围的括号是可以省略的
d, e, f = 4, 5, 6
- # 交换两个变量的值就这么简单
e, d = d, e # 现在 d 是 5,e 是 4
- # 用字典表达映射关系
empty_dict = {}
- # 初始化的字典
filled_dict = {"one": 1, "two": 2, "three": 3}
- # 用[]取值
filled_dict["one"] # => 1
- # 用 keys 获得所有的键。
- # 因为 keys 返回一个可迭代对象,所以在这里把结果包在 list 里。我们下面会详细介 绍可迭代。
- # 注意:字典键的顺序是不定的,你得到的结果可能和以下不同。
list(filled_dict.keys()) # => ["three", "two", "one"]
- # 用 in 测试一个字典是否包含一个键
"one" in filled_dict # => True
1 in filled_dict # => False
- # 访问不存在的键会导致 KeyError
filled_dict["four"] # KeyError
- # 用 get 来避免 KeyError
filled_dict.get("one") # => 1
filled_dict.get("four") # => None
- # 当键不存在的时候 get 方法可以返回默认值
filled_dict.get("one", 4) # => 1
filled_dict.get("four", 4) # => 4
- # setdefault 方法只有当键不存在的时候插入新值
filled_dict.setdefault("five", 5) # filled_dict["five"]设为 5
filled_dict.setdefault("five", 6) # filled_dict["five"]还是 5
- # 字典赋值
filled_dict.update({"four":4}) # => {"one": 1, "two": 2, "three": 3, "four": 4}
filled_dict["four"] = 4 # 另一种赋值方法
- # 用 del 删除
del filled_dict["one"] # 从 filled_dict 中把 one 删除
- # 用 set 表达集合
empty_set = set()
# 初始化一个集合,语法跟字典相似。
some_set = {1, 1, 2, 2, 3, 4} # some_set 现在是{1, 2, 3, 4}
# 可以把集合赋值于变量
filled_set = some_set
- # 为集合添加元素
filled_set.add(5) # filled_set 现在是{1, 2, 3, 4, 5}
- # & 取交集
other_set = {3, 4, 5, 6}
filled_set & other_set # => {3, 4, 5}
- # | 取并集
filled_set | other_set # => {1, 2, 3, 4, 5, 6}
- # - 取补集
{1, 2, 3, 4} - {2, 3, 5} # => {1, 4}
- # in 测试集合是否包含元素
2 in filled_set # => True
10 in filled_set # => False
1.3 流程控制和迭代器
####################################################
## 3. 流程控制和迭代器
####################################################
# 先随便定义一个变量
some_var = 5
# 这是个 if 语句。注意缩进在 Python 里是有意义的
# 印出"some_var 比 10 小"
if some_var > 10:
print("some_var 比 10 大")
elif some_var < 10: # elif 句是可选的
print("some_var 比 10 小")
else: # else 也是可选的
print("some_var 就是 10")
"""
用 for 循环语句遍历列表
打印:
dog is a mammal
cat is a mammal
mouse is a mammal
"""
for animal in ["dog", "cat", "mouse"]:
print("{} is a mammal".format(animal))
"""
"range(number)"返回数字列表从 0 到给的数字
打印:
0
1
2
3
"""
for i in range(4):
print(i)
"""
while 循环直到条件不满足
打印:
0
1
2
3
"""
x = 0
while x < 4:
print(x)
x += 1 # x = x + 1 的简写
# 用 try/except 块处理异常状况
try:
# 用 raise 抛出异常
raise IndexError("This is an index error")
except IndexError as e:
pass # pass 是无操作,但是应该在这里处理错误
except (TypeError, NameError):
pass # 可以同时处理不同类的错误
else: # else 语句是可选的,必须在所有的 except 之后
print("All good!") # 只有当 try 运行完没有错误的时候这句才会运行
# Python 提供一个叫做可迭代(iterable)的基本抽象。一个可迭代对象是可以被当作序列
# 的对象。比如说上面 range 返回的对象就是可迭代的。
filled_dict = {"one": 1, "two": 2, "three": 3}
our_iterable = filled_dict.keys()
print(our_iterable) # => dict_keys(['one', 'two', 'three']),是一个实现可
#迭代接口的对象
# 可迭代对象可以遍历
for i in our_iterable:
print(i) # 打印 one, two, three
# 但是不可以随机访问
our_iterable[1] # 抛出 TypeError
# 可迭代对象知道怎么生成迭代器
our_iterator = iter(our_iterable)
# 迭代器是一个可以记住遍历的位置的对象
# 用__next__可以取得下一个元素
our_iterator.__next__() # => "one"
# 再一次调取__next__时会记得位置
our_iterator.__next__() # => "two"
our_iterator.__next__() # => "three"
# 当迭代器所有元素都取出后,会抛出 StopIteration
our_iterator.__next__() # 抛出 StopIteration
# 可以用 list 一次取出迭代器所有的元素
list(filled_dict.keys()) # => Returns ["one", "two", "three"]
1.4 函数
####################################################
## 4. 函数
####################################################
# 用 def 定义新函数
def add(x, y):
print("x is {} and y is {}".format(x, y))
return x + y # 用 return 语句返回
# 调用函数
add(5, 6) # => 印出"x is 5 and y is 6"并且返回 11
# 也可以用关键字参数来调用函数
add(y=6, x=5) # 关键字参数可以用任何顺序
# 我们可以定义一个可变参数函数
def varargs(*args):
return args
varargs(1, 2, 3) # => (1, 2, 3)
# 我们也可以定义一个关键字可变参数函数
def keyword_args(**kwargs):
return kwargs
# 我们来看看结果是什么:
keyword_args(big="foot", loch="ness") # => {"big": "foot", "loch": "ness"}
# 这两种可变参数可以混着用
def all_the_args(*args, **kwargs):
print(args)
print(kwargs)
"""
all_the_args(1, 2, a=3, b=4) prints:
(1, 2)
{"a": 3, "b": 4}
"""
# 调用可变参数函数时可以做跟上面相反的,用*展开序列,用**展开字典。
args = (1, 2, 3, 4)
kwargs = {"a": 3, "b": 4}
all_the_args(*args) # 相当于 foo(1, 2, 3, 4)
all_the_args(**kwargs) # 相当于 foo(a=3, b=4)
all_the_args(*args, **kwargs) # 相当于 foo(1, 2, 3, 4, a=3, b=4)
# 函数作用域
x = 5
def setX(num):
# 局部作用域的 x 和全局域的 x 是不同的
x = num # => 43
print (x) # => 43
def setGlobalX(num):
global x
print (x) # => 5
x = num # 现在全局域的 x 被赋值
print (x) # => 6
setX(43)
setGlobalX(6)
# 函数在 Python 是一等公民
def create_adder(x):
def adder(y):
return x + y
return adder
add_10 = create_adder(10)
add_10(3) # => 13
# 也有匿名函数
(lambda x: x > 2)(3) # => True
# 内置的高阶函数
map(add_10, [1, 2, 3]) # => [11, 12, 13]
filter(lambda x: x > 5, [3, 4, 5, 6, 7]) # => [6, 7]
# 用列表推导式可以简化映射和过滤。列表推导式的返回值是另一个列表。
[add_10(i) for i in [1, 2, 3]] # => [11, 12, 13]
[x for x in [3, 4, 5, 6, 7] if x > 5] # => [6, 7]
1.5 类
####################################################
## 5. 类
####################################################
# 定义一个继承 object 的类
class Human(object):
# 类属性,被所有此类的实例共用。
species = "H. sapiens"
# 构造方法,当实例被初始化时被调用。注意名字前后的双下划线,这是表明这个属
# 性或方法对 Python 有特殊意义,但是允许用户自行定义。你自己取名时不应该用这
# 种格式。
def __init__(self, name):
# Assign the argument to the instance's name attribute
self.name = name
# 实例方法,第一个参数总是 self,就是这个实例对象
def say(self, msg):
return "{name}: {message}".format(name=self.name, message=msg)
# 类方法,被所有此类的实例共用。第一个参数是这个类对象。
@classmethod
def get_species(cls):
return cls.species
# 静态方法。调用时没有实例或类的绑定。
@staticmethod
def grunt():
return "*grunt*"
# 构造一个实例
i = Human(name="Ian")
print(i.say("hi")) # 印出 "Ian: hi"
j = Human("Joel")
print(j.say("hello")) # 印出 "Joel: hello"
# 调用一个类方法
i.get_species() # => "H. sapiens"
# 改一个共用的类属性
Human.species = "H. neanderthalensis"
i.get_species() # => "H. neanderthalensis"
j.get_species() # => "H. neanderthalensis"
# 调用静态方法
Human.grunt() # => "*grunt*"
一个简单的类示例:
class Car:
"""
一个简单的汽车类,包含汽车的基本属性和行为。
"""
def __init__(self, brand, model, year):
"""
初始化汽车的属性。
参数:
brand (str): 汽车品牌。
model (str): 汽车型号。
year (int): 汽车生产的年份。
"""
self.brand = brand
self.model = model
self.year = year
def display_info(self):
"""
显示汽车的详细信息。
"""
return f"{self.year} {self.brand} {self.model}"
def honk(self):
"""
汽车按喇叭。
"""
return "Beep Beep!"
# 创建Car类的实例
my_car = Car("Toyota", "Corolla", 2021)
# 调用方法显示汽车信息
print(my_car.display_info())
# 调用方法模拟汽车按喇叭
print(my_car.honk())
1.6 模块
####################################################
## 6. 模块
####################################################
# 用 import 导入模块
import math
print(math.sqrt(16)) # => 4.0
# 也可以从模块中导入个别值
from math import ceil, floor
print(ceil(3.7)) # => 4.0
print(floor(3.7)) # => 3.0
# 可以导入一个模块中所有值
# 警告:不建议这么做
from math import *
# 如此缩写模块名字
import math as m
math.sqrt(16) == m.sqrt(16) # => True
# Python 模块其实就是普通的 Python 文件。你可以自己写,然后导入,
# 模块的名字就是文件的名字。
# 你可以这样列出一个模块里所有的值
import math
dir(math)
一个简单的模块示例:
# 假设我们有一个名为 greetings.py 的模块文件,它定义了一个函数来打印问候语:
# greetings.py
def greet(name):
"""
打印问候语。
参数:
name (str): 要问候的名字。
"""
print(f"Hello, {name}! Welcome to the world of Python modules.")
现在,我们可以在另一个Python脚本中导入并使用这个模块:
# main.py
import greetings
# 使用导入的模块中的函数
greetings.greet("Alice")
#它会打印出 "Hello, Alice! Welcome to the world of Python modules."。
1.7 高级用法
- 列表推导式(List Comprehensions): 列表推导式提供了一种优雅的方式来创建列表,基于现有的列表或任何可迭代对象。
squares = [x**2 for x in range(10)]
- 生成器(Generators): 生成器是一种特殊的迭代器,用于惰性地生成值。它们在内存使用上比列表更高效,特别是处理大量数据时。
def count_up_to(max):
count = 1
while count <= max:
yield count
count += 1
for number in count_up_to(5):
print(number)
- 装饰器(Decorators): 装饰器是一种设计模式,用于修改或增强函数、方法或类的行为,而不必修改其实际代码。
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
- 上下文管理器(Context Managers): 上下文管理器允许你以一种可读性强、易于编写和维护的方式处理资源,如文件操作。
with open('file.txt', 'r') as file:
data = file.read()
- 类和继承(Classes and Inheritance): Python支持面向对象编程,可以创建类和子类,实现代码的复用和扩展。
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
my_dog = Dog()
print(my_dog.speak())
- 异常处理(Exception Handling): Python提供了一套完整的异常处理机制,允许你捕获并处理程序中的错误。
try:
x = int(input("Please enter a number: "))
y = 1 / x
except ZeroDivisionError:
print("Division by zero is not allowed.")
except ValueError:
print("Invalid input, please enter a number.")
- 函数注解(Function Annotations): 函数注解允许你指定函数参数和返回值的类型,这有助于代码的自我文档化和静态类型检查。
def greet(name: str, age: int) -> str:
return f"Hello, {name}! You are {age} years old."
- 多线程和多进程(Multithreading and Multiprocessing): Python允许你创建多线程和多进程来并行执行任务,提高程序的效率。
import threading
def print_numbers():
for i in range(1000000):
print(i, end='')
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_numbers)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
- 属性装饰器(Property Decorators): 属性装饰器允许你为类属性创建 getter、setter 和 deleter 方法,使得属性访问更加灵活。
class Person:
def __init__(self, age):
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("Age cannot be negative")
self._age = value
person = Person(30)
print(person.age)
person.age = 40 # 使用 setter
- 元类(Metaclasses): 元类是类的类,它允许你在创建类时进行控制和定制。
class Meta(type):
def __new__(cls, name, bases, dct):
print(f"Creating class {name}")
return super().__new__(cls, name, bases, dct)
class MyClass(metaclass=Meta):
pass
二 、实验
2.1 点亮一个LED灯
构造函数
led=machine.Pin(id,mode,pull)
构建 led 对象。id:引脚编号;mode:输入输出方式;pull:上下拉电阻配置。
使用方法
led.value([x]) 引脚电平值。输出状态:x=0 表示低电平,x=1 表示高电平;输入状态:无须 参数,返回当前引脚值。
led.on()
使引脚输出高电平“1”。
led.off()
使引脚输出低电平“0”。
更详细内容,请查看 micropython 库文档:https://docs.01studio.cc/
from machine import Pin #导入 Pin 模块
led=Pin(16,Pin.OUT) #构建 led 对象,GPIO2,输出
led.value(1) #点亮 LED,也可以使用 led.on()
#串口不一定一样
2.2 按键
pyWiFi-ESP32-S3 开发板上有 2 个按键,RST 和 KEY,RST 顾名思义是复位用 的,所以真正自带可以用的就只有 1 个按键 KEY。
构造函数
KEY=machine.Pin(id,mode,pull)
构建按键对象。id:引脚编号;mode:输入输出方式;pull:上下拉电阻配置。
使用方法
KEY.value() 引脚电平值。输入状态:无须参数,返回当前引脚值 0 或者 1。
更详细内容,请查看 micropython 库文档:https://docs.01studio.cc/
按键被按下时候可能会发生抖动,有可能造成误判,因此我们 需要使用延时函数来进行消抖。
常用的方法就是当检测按键值为 0 时,延时一段时间,大约 10ms,再判断 按键引脚值仍然是 0,是的话说明按键被按下。延时使用 time 模块,使用方法如 下:
import time
time.sleep(1) # 睡眠 1 秒
time.sleep_ms(500) # 睡眠 500 毫秒
time.sleep_us(10) # 睡眠 10 微妙
start = time.ticks_ms() # 获取毫秒计时器开始值
delta = time.ticks_diff(time.ticks_ms(), start) # 计算从上电开始到当前时间的差值
实验参考代码:
from machine import Pin
import time
LED=Pin(46,Pin.OUT) #构建 LED 对象,开始熄灭
KEY=Pin(0,Pin.IN,Pin.PULL_UP) #构建 KEY 对象
state=0 #LED 引脚状态
while True:
if KEY.value()==0: #按键被按下
time.sleep_ms(10) #消除抖动
if KEY.value()==0: #确认按键被按下
state=not state #使用 not 语句而非~语句 ~语句为取反,在此很容易出错
LED.value(state) #LED 状态翻转
print('KEY')
while not KEY.value(): #检测按键是否松开
pass
2.3 外部中断
利用中断方式来检查按键 KEY 状态,被按键被按下(产生外部中断)后使 LED 的亮灭状态翻转。
构造函数
KEY=machine.Pin(id,mode,pull)
构建按键对象。id:引脚编号;mode:输入输出方式;pull:上下拉电阻配置。
使用方法
KEY.irq(handler,trigger)
配置中断模式。
handler:中断执行的回调函数;
trigger: 触发中断的方式,共 4 种,分别是 Pin.IRQ_FALLING(下降沿触发)、 Pin.IRQ_RISING(上升沿触发)、Pin.IRQ_LOW_LEVEL(低电平触发)、 Pin.IRQ_HIGH_LEVEL(高电平触发)。
更详细内容,请查看 micropython 库文档:https://docs.01studio.cc/
from machine import Pin
import time
LED=Pin(46,Pin.OUT) #构建 LED 对象,开始熄灭
KEY=Pin(0,Pin.IN,Pin.PULL_UP) #构建 KEY 对象
state=0 #LED 引脚状态
#LED 状态翻转函数
def fun(KEY):
global state
time.sleep_ms(10) #消除抖动
if KEY.value()==0: #确认按键被按下
state = not state
LED.value(state)
KEY.irq(fun,Pin.IRQ_FALLING) #定义中断,下降沿触发
2.4 定时器
通过定时器让 LED 周期性每秒闪烁 1 次。
构造函数
tim=machine.Timer(id)
构建定时器对象。
【id】ESP32-S3 有 2 路硬件定时器,id=0~1,也可以定义成-1,即 RTOS 虚拟定时器
使用方法
tim.init(period,mode,callback)
定时器初始化。
period:单位为 ms;
mode:2 种工作模式,Timer.ONE_SHOT(执行一次)、Timer.PERIODIC(周期 性);callback:定时器中断后的回调函数。
更详细内容,请查看 micropython 库文档:https://docs.01studio.cc/
from machine import Pin,Timer
led=Pin(46,Pin.OUT)
Counter = 0
Fun_Num = 0
def fun(tim):
global Counter
Counter = Counter + 1
print(Counter)
led.value(Counter%2)
#使用定时器 1
tim = Timer(1)
tim.init(period=1000, mode=Timer.PERIODIC,callback=fun) #周期为 1000ms
2.5 12C总线(OLED显示屏)
学习使用 MicroPython 的 I2C 总线通讯编程和 OLED 显示屏的使用。
什么是 I2C?
I2C 是用于设备之间通信的双线协议,在物理层面,它由 2 条线组成:SCL 和 SDA,分别是时钟线和数据线。也就是说不通设备间通过这两根线就可以进行通 信。
我们从 pyWiFi-ESP32-S3 和 pyBase 相结合的原理图可以看到 GPIO40—Y6— SCL, GPIO42—Y8—SDA 的连接关系。ESP32-WROOM上具有I2C功能的常见引脚 GPIO4(SDA)和GPIO5(SCL)。
本实验将使用 MicroPython 的 Machine 模块来定义 Pin 口和 I2C 初始化。具体如下:
构造函数
i2c = machine.I2C(scl,sda)
构建 I2C 对象。scl:时钟引脚;sda:数据引脚。
使用方法
i2c.scan()
扫描 I2C 总线的设备。返回地址,如:0x3c;
i2c.readfrom(addr,nbytes)
从指定地址读数据。addr:指定设备地址;nbytes:读取字节数;
i2c.write(buf)
写数据。buf:数据内容;
更详细内容,请查看 micropython 库文档:https://docs.01studio.cc/
定义好 I2C 后,还需要驱动一下 OLED。这里我们已经写好了 OLED 的库函 数,在 ssd1306.py 文件里面。开发者只需要拷贝到 pyBoard 文件系统里面,然后 在 main.py 里面调用函数即可。人生苦短,我们学会调用函数即可,也就是注重 顶层的应用,想深入的小伙伴也可以自行研究 ssd1306.py 文件代码。OLED 显示 屏对象介绍如下:
构造函数
oled = SSD1306_I2C(width, height, i2c, addr)
构 OLED 显示屏对象。width:屏幕宽像素;height: 屏幕高像素;i2c:定义好的 I2C 对象; addr:显示屏设备地址。
使用方法
oled.text(string,x,y) 将 string 字符写在指定为位置。string:字符;x:横坐标;y:纵坐标。 oled.show()
执行显示。
oled.fill(RGB)
清屏。RGB:0 表示黑色,1 表示白色。
from machine import SoftI2C,Pin #从 machine 模块导入 I2C、Pin 子模块
from ssd1306 import SSD1306_I2C #从 ssd1306 模块中导入 SSD1306_I2C 子模块
i2c = SoftI2C(sda=Pin(42), scl=Pin(40)) #I2C 初始化:sda-->42, scl -->40
#OLED 显示屏初始化:128*64 分辨率,OLED 的 I2C 地址是 0x3c
oled = SSD1306_I2C(128, 64, i2c, addr=0x3c)
oled.text("Hello World!", 0, 0) #写入第 1 行内容
oled.text("MicroPython", 0, 20) #写入第 2 行内容
oled.text("By 01Studio", 0, 50) #写入第 3 行内容
oled.show() #OLED 执行显示
2.6 RTC实时时钟
学习 RTC 编程和制作电子时钟,使用 OLED 显示。
实验的原理是读取 RTC 数据,然后通过 OLED 显示。毫无疑问,强大的 MicroPython 已经集成了内置时钟函数模块。位于 machine 的 RTC 模块中,具体 介绍如下:
构造函数
rtc=machine.RTC()
构建 RTC 对象。
使用方法
rtc.datetime((2022, 4, 1, 4, 0, 0, 0, 0))
设置日期和时间。按顺序分别是:(年,月,日,星期,时,分,秒,微秒), 其中星期使用 0-6 表示周一至周日。
rtc.datetime()
获取当前日期和时间。
更详细内容,请查看 micropython 库文档:https://docs.01studio.cc/
# 导入相关模块
from machine import Pin, SoftI2C, RTC,Timer
from ssd1306 import SSD1306_I2C
# 定义星期和时间(时分秒)显示字符列表 0-6
week = ['Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat', 'Sun']
time_list = ['', '', '']
# 初始化所有相关对象
i2c = SoftI2C(sda=Pin(42), scl=Pin(40)) #OLED 屏 I2C 初始化
oled = SSD1306_I2C(128, 64, i2c, addr=0x3c)
rtc = RTC()
# 首次上电配置时间,按顺序分别是:年,月,日,星期,时,分,秒,次秒级;这里做了
# 一个简单的判断,检查到当前年份不对就修改当前时间,开发者可以根据自己实际情况来
# 修改。
if rtc.datetime()[0] != 2022:
rtc.datetime((2022, 4, 1, 4, 0, 0, 0, 0))
def RTC_Run(tim):
datetime = rtc.datetime() # 获取当前时间
oled.fill(0) # 清屏显示黑色背景
oled.text('SKY', 0, 0) # 首行显示 01Studio
oled.text('RTC Clock', 0, 15) # 次行显示实验名称
# 显示日期,字符串可以直接用“+”来连接
oled.text(str(datetime[0]) + '-' + str(datetime[1]) + '-' +
str(datetime[2]) + ' ' + week[datetime[3]], 0, 40)
# 显示时间需要判断时、分、秒的值否小于 10,如果小于 10,则在显示前面补“0”以达
# 到较佳的显示效果
for i in range(4, 7):
if datetime[i] < 10:
time_list[i - 4] = "0"
else:
time_list[i - 4] = ""
# 显示时间
oled.text(time_list[0] + str(datetime[4]) + ':' + time_list[1] +
str(datetime[5]) + ':' + time_list[2] + str(datetime[6]), 0, 55)
oled.show()
#开启 RTOS 定时器
tim = Timer(0)
tim.init(period=300, mode=Timer.PERIODIC, callback=RTC_Run) #周期 300ms
由于实验要用到 OLED 显示屏,所以同样别忘了将示例代码该实验文件夹下 的 ssd1306.py 文件复制到 pyWiFi-ESP32-S3 的文件系统里面。
2.7 ADC(电位器)
通过编程调用 MicroPython 的内置 ADC 函数,实现测量输入电压,并显示到 屏幕上。
pyBase 开发底板的 X7 引脚连接到了电位器,通过电位器的调节可以使得 X7 引脚上的电压变化范围实现从 0-3.3V。
构造函数
adc=machine.ADC(Pin(id))
构建 ADC 对象。
【id】目前仅支持 ESP32-S3 的 ADC1,共 10 个通道:
GPIO1: ADC1_0
GPIO2: ADC1_1
GPIO3: ADC1_2
GPIO4: ADC1_3
GPIO5: ADC1_4
GPIO6: ADC1_5
GPIO7: ADC1_6
GPIO8: ADC1_7
GPIO9: ADC1_8
GPIO10: ADC1_9
使用方法
adc.read()
获取 ADC 值。测量精度是 12 位,返回 0- 4095(默认表示 0-1V)。
adc.atten(attenuation)
配置衰减器。配置衰减器能增加电压测量范围,但是以精度为代价的。
attenuation:衰减设置
ADC.ATTN_0DB: 0dB 衰减, 最大输入电压为 1.00v - 这是默认配置; ADC.ATTN_2_5DB: 2.5dB 衰减, 最大输入电压约为 1.34v;
ADC.ATTN_6DB:6dB 衰减, 最大输入电压约为 2.00v;
ADC.ATTN_11DB:11dB 衰减, 最大输入电压约为 3.3v。
更详细内容,请查看 micropython 库文档:https://docs.01studio.cc/
#导入相关模块
from machine import Pin,SoftI2C,ADC,Timer
from ssd1306 import SSD1306_I2C
#初始化相关模块
i2c = SoftI2C(sda=Pin(42), scl=Pin(40)) #I2C 初始化
oled = SSD1306_I2C(128, 64, i2c, addr=0x3c)
adc = ADC(Pin(7)) #引脚 7 跟 pyBase 的电位器相连接
adc.atten(ADC.ATTN_11DB) #开启衰减器,测量量程增大到 3.3V
def ADC_Test(tim):
oled.fill(0) # 清屏显示黑色背景
oled.text('01Studio', 0, 0) # 首行显示 01Studio
oled.text('ADC', 0, 15) # 次行显示实验名称
#获取 ADC 数值
oled.text(str(adc.read()),0,40)
oled.text('(4095)',60,40)
#计算电压值,获得的数据 0-4095 相当于 0-3.3V,('%.2f'%)表示保留 2 位小数
oled.text(str('%.2f'%(adc.read()/4095*3.3)),0,55)
oled.text('V',40,55)
oled.show()
#开启定时器
tim = Timer(1)
tim.init(period=300, mode=Timer.PERIODIC, callback=ADC_Test) #周期 300ms
ESP32-WROOM中用35引脚代替pyWiFi-ESP32-S3中的7,可以使用相应功能,但是板子出错。
2.8 PWM(无源蜂鸣器)
通过不同频率的 PWM 信号输出,驱动无源蜂鸣器发出不同频率的声音。
构造函数
pwm=machine.PWM(machine.Pin(id),freq,duty)
构建 PWM 对象。id:引脚编号;freq:频率值;duty:占空比;配置完后PWM自动生效。
使用方法
pwm.freq(freq)
设置频率。freq:频率值在 1-1000 之间,freq 为空时表示获取当前频率值。
pwm.duty(duty)
设置占空比。duty:占空比在 0-1023 之间,duty 为空时表示获取当前占空比值。
pwm.deinit()
关闭 PWM。
更详细内容,请查看 micropython 库文档:https://docs.01studio.cc/
PWM 可以通过 ESP32-S3 所有 GPIO 引脚输出. 所有通道都有 1 个特定的频率,从 0 到 40M 之间(单位是 Hz)。占空比的值为 0 至 1023 之间。在本实验中我们用到引脚 4。
from machine import Pin, PWM
import time
Beep = PWM(Pin(15), duty=512) # 在同一语句下创建和配置 PWM,占空比 50%
#蜂鸣器发出频率 200Hz 响声
Beep.freq(200)
time.sleep(1)
#蜂鸣器发出频率 400Hz 响声
Beep.freq(400)
time.sleep(1)
#蜂鸣器发出频率 600Hz 响声
Beep.freq(600)
time.sleep(1)
#蜂鸣器发出频率 800Hz 响声
Beep.freq(800)
time.sleep(1)
#蜂鸣器发出频率 1000Hz 响声
Beep.freq(1000)
time.sleep(1)
#停止
Beep.deinit()
2.9 UART(串口通信)
编程实现串口收发数据。
pyWiFi-ESP32-S3 开发套件,需要使用 USB 转 TTL 工具。
构造函数
uart=machine.UART(id,baudrate,tx=None,rx=None bits=8, parity=None, stop=1,…)
创建 UART 对象。
【id】0-1
【baudrate】波特率,常用 115200、9600
【tx】自定义 IO
【rx】自定义 IO
【bits】数据位
【parity】校验;默认 None, 0(偶校验),1(奇校验)
【stop】停止位,默认 1
…
特别说明: ESP32-S3 的 UART 引脚映射到其它 IO 来使用,用户可以通过构造 函数时候指定如 tx=8,rx=9 的方式来改变串口引脚,实现更灵活的应用。
使用方法
uart.deinit()
关闭串口
uart.any()
返回等待读取的字节数据,0 表示没有
uart.read([nbytes])
【nbytes】读取字节数
UART.readline()
读行
UART.write(buf)
【buf】串口 TX 写数据
更详细内容,请查看 micropython 库文档:https://docs.01studio.cc/
#导入串口模块
from machine import UART
uart=UART(2,115200,rx=9,tx=8) #设置串口号 1 和波特率
uart.write('Hello 01Studio!')#发送一条数据
while True:
#判断有无收到信息
if uart.any():
text=uart.read(128) #接收 128 个字符
print(text) #通过 REPL 打印串口 3 接收的数据
我们按照上述方式将 USB 转 TTL 的 TX 接到开发板的 RX(9),USB 转 TTL 的 RX 接到开发板的 TX(8)。GND 接一起,3.3V 可以选择接或不接。
2.10 Socket通信
通过 Socket 编程实现 pyWiFi-ESP32-S3 与电脑服务器助手建立连接,相互收发数据。
构造函数
s=usocket.socekt(af=AF_INET, type=SOCK_STREAM,proto=IPPROTO_TCP)
构建 usocket 对象。
af: AF_INET→IPV4,AF_INET6 → IPV6;
type: SCOK_STREAM→TCP,SOCK_DGRAM→UDP;
proto: IPPROTO_TCP→TCP 协议,IPPROTO_UDP→UDP 协议。
(如果要构建 TCP 连接,可以使用默认参数配置,即不输入任何参数。)
使用方法
addr=usocket.getaddrinfo('www.01studio.org', 80)[0][-1]
获取 Socket 通信格式地址。返回:('47.91.208.161',80)
s.connect(address)
创建连接。address:地址格式为 IP+端口。例:('192.168.1.115',10000)
s.send(bytes)
发送。bytes:发送内容格式为字节
s.recv(bufsize)
接收数据。bufsize:单次最大接收字节个数。
s.bind(address)
绑定,用于服务器角色
s.listen([backlog])
监听,用于服务器角色。backlog:允许连接个数,必须大于 0。
s.accept
() 接受连接,用于服务器角色。
更详细内容,请查看 micropython 库文档:https://docs.01studio.cc/
#导入相关模块
import network,usocket,time
from machine import SoftI2C,Pin,Timer
from ssd1306 import SSD1306_I2C
#初始化相关模块
i2c = SoftI2C(sda=Pin(42), scl=Pin(40))
oled = SSD1306_I2C(128, 64, i2c, addr=0x3c)
#WIFI 连接函数
def WIFI_Connect():
WIFI_LED=Pin(46, Pin.OUT) #初始化 WIFI 指示灯
wlan = network.WLAN(network.STA_IF) #STA 模式
wlan.active(True) #激活接口
start_time=time.time() #记录时间做超时判断
if not wlan.isconnected():
print('Connecting to network...')
wlan.connect('01Studio', '88888888') #输入 WIFI 账号密码
while not wlan.isconnected():
#LED 闪烁提示
WIFI_LED.value(1)
time.sleep_ms(300)
WIFI_LED.value(0)
time.sleep_ms(300)
#超时判断,15 秒没连接成功判定为超时
if time.time()-start_time > 15 :
print('WIFI Connected Timeout!')
break
if wlan.isconnected():
#LED 点亮
WIFI_LED.value(1)
#串口打印信息
print('network information:', wlan.ifconfig())
#OLED 数据显示
oled.fill(0) #清屏背景黑色
oled.text('IP/Subnet/GW:',0,0)
oled.text(wlan.ifconfig()[0], 0, 20)
oled.text(wlan.ifconfig()[1],0,38)
oled.text(wlan.ifconfig()[2],0,56)
oled.show()
return True
else:
return False
#判断 WIFI 是否连接成功
if WIFI_Connect():
#创建 socket 连接 TCP 类似,连接成功后发送“Hello 01Studio!”给服务器。
s=usocket.socket()
addr=('192.168.1.115',10000) #服务器 IP 和端口
s.connect(addr)
s.send('Hello 01Studio!')
while True:
text=s.recv(128) #单次最多接收 128 字节
if text == '':
pass
else: #打印接收到的信息为字节,可以通过 decode('utf-8')转成字符串
print(text)
s.send('I got:'+text.decode('utf-8'))
time.sleep_ms(300)
在时候服务器已经在监听状态!用户需要根据自己的实际情况自己输入 WIFI 信息和服务器 IP 地址+端口。即修改上面的代码以下部分内容。(服务器 IP 和端 口可以在网络调试助手找到。) WiFi 网络信息: wlan.connect('01Studio', '88888888') #输入 WIFI 账号密码 服务器信息: addr=('192.168.1.115',10000) #服务器 IP 和端口
2.11 MQTT通信
通过编程实现 pyWiFi-ESP32-S3 实现 MQTT 协议信息的发布和订阅(接收)。
总结下来 MQTT 有如下特性/优势:
➢ 异步消息协议
➢ 面向长连接
➢ 双向数据传输
➢ 协议轻量级
➢ 被动数据获取
从上图可以看到,MQTT 通信的角色有两个,分别是服务器和客户端。服务器只负责中转数据,不做存储;客户端可以是信息发送者或订阅者,也可以同时是两者。
构造函数
client=simple. MQTTClient (client_id, server, port)
构建 MQTT 客户端对象。
client_id: 客户端 ID,具有唯一性;
server: 服务器地址,可以是 IP 或者网址;
port:服务器端口。(默认是 1883,服务器通常采用的端口,也可以自定义。)
使用方法
client.connect()
连接到服务器。
client.publish(TOPIC,message)
发布。TOPIC:主题编号;message: 信息内容,例:'Hello 01Studio!' client.subscribe(TOPIC)
订阅。TOPIC:主题编号。
client.set_callback(callback)
设置回调函数。callback:订阅后如果接收到信息,就执行相名称的回调函数。 client.check_msg()
检查订阅信息。如收到信息就执行设置过的回调函数 callback。
由于客户端分为发布者和订阅者角色,因此为了方便大家更好理解,本实验分开两个案例来编程,分别为发布者和订阅者。再结合 MQTT 网络调试助手来测试。
发布者(publish)参考代码:
import network,time
from simple import MQTTClient #导入 MQTT 板块
from machine import SoftI2C,Pin,Timer
from ssd1306 import SSD1306_I2C
#初始化相关模块
i2c = SoftI2C(sda=Pin(42), scl=Pin(40))
oled = SSD1306_I2C(128, 64, i2c, addr=0x3c)
#WIFI 连接函数
def WIFI_Connect():
WIFI_LED=Pin(46, Pin.OUT) #初始化 WIFI 指示灯
wlan = network.WLAN(network.STA_IF) #STA 模式
wlan.active(True) #激活接口
start_time=time.time() #记录时间做超时判断
if not wlan.isconnected():
print('connecting to network...')
wlan.connect('01Studio', '88888888') #输入 WIFI 账号密码
while not wlan.isconnected():
#LED 闪烁提示
WIFI_LED.value(1)
time.sleep_ms(300)
WIFI_LED.value(0)
time.sleep_ms(300)
#超时判断,15 秒没连接成功判定为超时
if time.time()-start_time > 15 :
print('WIFI Connected Timeout!')
break
if wlan.isconnected():
#LED 点亮
WIFI_LED.value(1)
#串口打印信息
print('network information:', wlan.ifconfig())
#OLED 数据显示(如果没接 OLED,请将下面代码屏蔽)
oled.fill(0) #清屏背景黑色
oled.text('IP/Subnet/GW:',0,0)
oled.text(wlan.ifconfig()[0], 0, 20)
oled.text(wlan.ifconfig()[1],0,38)
oled.text(wlan.ifconfig()[2],0,56)
oled.show()
return True
else:
return False
#发布数据任务
def MQTT_Send(tim):
client.publish(TOPIC, 'Hello 01Studio!')
#执行 WIFI 连接函数并判断是否已经连接成功
if WIFI_Connect():
SERVER = 'mq.tongxinmao.com'
PORT = 18830
CLIENT_ID = 'pyWiFi-ESP32-S3' # 客户端 ID
TOPIC = '/public/01Studio/1' # TOPIC 名称
client = MQTTClient(CLIENT_ID, SERVER, PORT)
client.connect()
#开启 RTOS 定时器,编号为-1,周期 1000ms,执行 socket 通信接收任务
tim = Timer(-1)
tim.init(period=1000, mode=Timer.PERIODIC,callback=MQTT_Send)
订阅者(subscribe)参考代码:
import network,time
from simple import MQTTClient #导入 MQTT 板块
from machine import SoftI2C,Pin,Timer
from ssd1306 import SSD1306_I2C
#初始化相关模块
i2c = SoftI2C(sda=Pin(42), scl=Pin(40))
oled = SSD1306_I2C(128, 64, i2c, addr=0x3c)
#WIFI 连接函数
def WIFI_Connect():
WIFI_LED=Pin(46, Pin.OUT) #初始化 WIFI 指示灯
wlan = network.WLAN(network.STA_IF) #STA 模式
wlan.active(True) #激活接口
start_time=time.time() #记录时间做超时判断
if not wlan.isconnected():
print('connecting to network...')
wlan.connect('01Studio', '88888888') #输入 WIFI 账号密码
while not wlan.isconnected():
#LED 闪烁提示
WIFI_LED.value(1)
time.sleep_ms(300)
WIFI_LED.value(0)
time.sleep_ms(300)
#超时判断,15 秒没连接成功判定为超时
if time.time()-start_time > 15 :
print('WIFI Connected Timeout!')
break
if wlan.isconnected():
#LED 点亮
WIFI_LED.value(1)
#串口打印信息
print('network information:', wlan.ifconfig())
#OLED 数据显示(如果没接 OLED,请将下面代码屏蔽)
oled.fill(0) #清屏背景黑色
oled.text('IP/Subnet/GW:',0,0)
oled.text(wlan.ifconfig()[0], 0, 20)
oled.text(wlan.ifconfig()[1],0,38)
oled.text(wlan.ifconfig()[2],0,56)
oled.show()
return True
else:
return False
#设置 MQTT 回调函数,有信息时候执行
def MQTT_callback(topic, msg):
print('topic: {}'.format(topic))
print('msg: {}'.format(msg))
#接收数据任务
def MQTT_Rev(tim):
client.check_msg()
#执行 WIFI 连接函数并判断是否已经连接成功
if WIFI_Connect():
SERVER = 'mq.tongxinmao.com'
PORT = 18830
CLIENT_ID = 'pyWiFi-ESP32-S3' # 客户端 ID
TOPIC = '/public/01Studio/1' # TOPIC 名称
client = MQTTClient(CLIENT_ID, SERVER, PORT) #建立客户端对象
client.set_callback(MQTT_callback) #配置回调函数
client.connect()
client.subscribe(TOPIC) #订阅主题
#开启 RTOS 定时器,编号为 1,周期 300ms,执行 socket 通信接收任务
tim = Timer(1)
tim.init(period=300, mode=Timer.PERIODIC,callback=MQTT_Rev)
为了方便测试,我们可以使用 MQTT 网络助手进行调试。这里推荐一个在线 MQTT 网络调试助手:http://www.tongxinmao.com/txm/webmqtt.php#collapseOne。