点亮第一个 LED

其连接到 pyAI-K210 的外部 IO 引脚如下(可以看开发板原理图),LED 蓝灯对应的外部 IO 为 IO12,从电路可以看到当 IO12 为低电平时,蓝灯被点亮。

K210 为外部 IO 和内部 IO,其片上外设(比如 GPIO、I2C 等)对应的引脚是可以任意设置的,而传统大部分 MCU 片上外设和引脚对应关系已经固定了, 只有部分引脚可以复用, 相比之下 K210 自由度更大。

因此我们在编程使用 GPIO 的时候需要注册一下硬件 IO 和 K210 内部 IO 的对应关系。注册方式使用 fpioa_manager:简称 fm,该模块用于注册芯片内部功能和引脚,帮助用户管理内部功能和引脚。

构造函数

1
fm.register(pin,function,force=False)

【pin】芯片外部 IO

【function】芯片功能

【force】=True 则强制注册,清除之前的注册记录;

例:fm.register(12, fm.fpioa.GPIO0,force=True)

表示将外部 IO12 注册到内部 GPIO0

更多有关引脚和功能注册信息请看官方文档:

https://wiki.sipeed.com/soft/maixpy/zh/api_reference/Maix/gpio.html

注册成功后我们就可以通过 GPIO 对象模块来控制外部 IO,从而控制 LED。

GPIO 对象说明如下:

1
GPIO(ID,MODE,PULL,VALUE)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GPIO 对象。

【ID】内部 GPIO 编号;

【MODE】GPIO 模式;

GPIO.IN :输入模式

GPIO.OUT :输出模式

【PULL】

GPIO.PULL_UP :上拉

GPIO.PULL_DOWN :下拉

GPIO.PULL_NONE :无

【value】GPIO 初始化电平

1:高电平

0:低电平
1
GPIO.value([value])

【value】GPIO 输出电平值;

1:高电平

0:低电平

方式 1 是:import Maix,然后通过 Maix.GPIO 来操作;

方式 2 是:from Maix import GPIO,意思是直接从 Maix 中引入 GPIO 模块,然后直接通过 GPIO 来操作。显然方式 2 会显得更直观和方便,本实验也是使用

方式 2 来编程。代码编写流程如下:

1
2
3
4
5
6
7
8
9
10
'''  
实验名称:点亮 LED_B 蓝灯
实验目的:学习 led 点亮。
'''
from Maix import GPIO
from fpioa_manager import fm
#将蓝灯引脚 IO12 配置到 GPIO0,K210 引脚支持任意配置
fm.register(12, fm.fpioa.GPIO0,force=True)
LED_B = GPIO(GPIO.GPIO0, GPIO.OUT) #构建 LED 对象
LED_B.value(0) #点亮 LED

关于python中from和import

Python的from和import用法 - keena_jiao - 博客园 (cnblogs.com)

流水灯

1
2
utime()
#时间模块

使用方法

1
2
3
4
5
6
utime.sleep(seconds)
秒级颜色。seconds:延时秒数
utime.sleep_ms(ms)
毫秒级延时。ms:延时毫秒数。
utime.sleep_us(us)
微秒级延时。us:延时微秒数。

*更多用法请阅读 MaixPy 官方文档:

https://wiki.sipeed.com/soft/maixpy/zh/api_reference/standard/utime.html

知道了延时函数的使用方法后,我们可以简单的梳理一下流程,首先导入LED 和 utime 模块,程序开始先让 RGB LED 灭掉,开启循环,依次点亮每个 LED,延时 1 秒,关闭 LED。流程如下:

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
'''
实验名称:流水灯
版本:v1.0
日期:2019.12
作者:01Studio
实验目的:让 RGB 灯循环闪烁。
'''
from Maix import GPIO
from fpioa_manager import fm
import utime
#将将 LED 外部 IO 注册到内部 GPIO,K210 引脚支持任意配置
fm.register(12, fm.fpioa.GPIO0)
fm.register(13, fm.fpioa.GPIO1)
fm.register(14, fm.fpioa.GPIO2)
LED_B = GPIO(GPIO.GPIO0, GPIO.OUT,value=1) #构建 LED 对象
LED_G = GPIO(GPIO.GPIO1, GPIO.OUT,value=1) #构建 LED 对象
LED_R = GPIO(GPIO.GPIO2, GPIO.OUT,value=1) #构建 LED 对象
while True:

#蓝灯亮 1 秒
LED_B.value(0) #点亮 LED
utime.sleep(1)
LED_B.value(1) #关闭 LED

#绿灯亮 1 秒
LED_G.value(0) #点亮 LED
utime.sleep(1)
LED_G.value(1) #关闭 LED
#红灯亮 1 秒
LED_R.value(0) #点亮 LED
utime.sleep(1)
LED_R.value(1) #关闭 LED

上述代码没错是完整地按照编程思路来编写,但可以见到有很多格式相似的地方,这显得代码非常冗余。我们可以通过 for 函数来编写程序,由于是对 3 个LED 的操作,因此我们可以用 for i in range(0,3): 语句来修改,参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
'''
实验名称:流水灯
版本:v1.0
日期:2019.12
作者:01Studio
实验目的:让 RGB 灯循环闪烁。
'''
from Maix import GPIO
from fpioa_manager import fm
import utime
#将将 LED 外部 IO 注册到内部 GPIO,K210 引脚支持任意配置
fm.register(12, fm.fpioa.GPIO0)
fm.register(13, fm.fpioa.GPIO1)
fm.register(14, fm.fpioa.GPIO2)
#构建 LED 对象,并初始化输出高电平,关闭 LED
LED_B = GPIO(GPIO.GPIO0, GPIO.OUT,value=1)
LED_G = GPIO(GPIO.GPIO1, GPIO.OUT,value=1)
LED_R = GPIO(GPIO.GPIO2, GPIO.OUT,value=1)
#定义数组方便循环语句调用
LED=[LED_B, LED_G, LED_R]
while True:
for i in range(0,3):
LED[i].value(0) #点亮 LED
utime.sleep(1)
LED[i].value(1) #关闭 LED

按键

从原理图可以看到,按键 KEY 的一端连接到 K210 的外部 IO16,另一端连接到 GND。所以按键在没按下时候输入高电平(1),按下时候输入低电平(0)。和 LED 一样,按键的输入检测也是用到 GPIO 对象模块,具体如下:

构造函数

1
GPIO(ID,MODE,PULL,VALUE)

GPIO 对象。

【ID】内部 GPIO 编号;

【MODE】GPIO 模式;

GPIO.IN :输入模式

GPIO.OUT :输出模式

【PULL】

GPIO.PULL_UP :上拉

GPIO.PULL_DOWN :下拉

GPIO.PULL_NONE :无

【value】GPIO 初始化电平

1:高电平

0:低电平

使用

1
GPIO.value([value])

【value】GPIO 输出电平值;

1:高电平

0:低电平

*输入模式时候参数为空,表示获取当前 IO 输入电平值。

GPIO 对象使用非常简单,我们将按键即外部“IO16”引脚配置成输入,实现当检测到按键被按下时候点亮 LED 蓝灯,松开时关闭 LED 蓝灯来做指示。代码编写流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from Maix import GPIO
from fpioa_manager import fm
#注册 IO,蓝灯-->IO12,KEY-->IO16
fm.register(12, fm.fpioa.GPIO0)
fm.register(16, fm.fpioa.GPIO1)
#初始化 IO
LED_B = GPIO(GPIO.GPIO0, GPIO.OUT)
KEY = GPIO(GPIO.GPIO1, GPIO.IN)
while True:
if KEY.value()==0: #按键被按下接地
LED_B.value(0) #点亮 LED_B,蓝灯
else:
LED_B.value(1) #熄灭 LED

外部中断

前面我们在做普通的按键(GPIO)时候,虽然能实现 IO 口输入输出功能,但代码是一直在检测 IO 输入口的变化,因此效率不高,特别是在一些特定的场合,比如某个按键,可能 1 天才按下一次去执行相关功能,这样我们就浪费大量时间来实时检测按键的情况。

为了解决这样的问题,我们引入外部中断概念,顾名思义,就是当按键被按下(产生中断)时,我们才去执行相关功能。这大大节省了 CPU 的资源,因此中断的在实际项目的应用非常普遍。

1
GPIO.irq(CALLBACK_FUNC,TRIGGER_CONDITION)

配置中断。

【CALLBACK_FUNC】中断执行的回调函数;

【TRIGGER_CONDITION】中断触发方式;

GPIO.IRQ_RISING:上升沿触发

GPIO.IRQ_FALLING:下降沿沿触发

GPIO.IRQ_BOTH:都触发

1
GPIO.disirq()

关闭中断。

我们先来了解一下上升沿和下降沿的概念,由于按键 KEY 引脚是通过按键接到 GND,也就是我们所说的低电平“0”,所以当按键被按下再松开时,引脚先获得下降沿,再获得上升沿,如下图所示:

按键被按下时候可能会发生抖动,抖动如下图,有可能造成误判,因此我们

需要使用延时函数来进行消抖:

我们可以选择下降沿方式触发外部中断,也就是当按键被按下的时候立即产

生中断。

需要注意的是 K210 只有高速 GPIO 才有外部中断,GPIO 常量表如下:

K210 - GPIO
普通 GPIO
GPIO0 - GPIO7
高速 GPIO
GPIOHS0 – GPIOHS31

编程思路中断跟 GPIO 按键章节类似,在初始化中断后,当系统检测到外部

中断时候,执行 LED 状态反转的代码即可。流程图如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from Maix import GPIO
from fpioa_manager import fm
import utime

#注册 IO,注意高速 GPIO 口才有中断
fm.register(12, fm.fpioa.GPIO0)
fm.register(16, fm.fpioa.GPIOHS0)
#构建 lED 和 KEY 对象

LED_B=GPIO(GPIO.GPIO0,GPIO.OUT,value=1)
KEY=GPIO(GPIO.GPIOHS0, GPIO.IN, GPIO.PULL_UP)

#LED 状态表示
state = 1

#中断回调函数
def fun(KEY):
global state
utime.sleep_ms(10) #消除抖动
if KEY.value()==0: #确认按键被按下
state = not state
LED_B.value(state)

#开启中断,下降沿触发
KEY.irq(fun, GPIO.IRQ_FALLING)

定时器

构造函数

1
2
machine.Timer(id,channel,mode=Timer.MODE_ONE_SHOT,period=1000,unit=Timer.UNIT_MS, callback=None, arg=None, start=True,
priority=1, div=0)

定时器对象 Timer 对象在 machine 模块下。

【id】定时器编号, [Timer.TIMER0~TIMER2] 定时器 0-2;

【channel】Timer 通道,[Timer.CHANNEL0~Timer.CHANNEL3]

【mode】定时器模式

MODE_ONE_SHOT: 一次性

MODE_PERIODIC: 周期性

MODE_PWM

【period】定时器为周期性模块时每个周期时间值

【unit】周期的单位

Timer.UNIT_S:秒

Timer.UNIT_MS:毫秒

Timer.UNIT_US:微妙

Timer.UNIT_NS:纳秒

【callback】定时器中断执行的回调函数;注意:回调函数是在中断中调用

的,所以在回调函数中请不要占用太长时间以及做动态内存分配开关中断等

动作。

【arg】回调函数第 2 个参数

【start】是否在构建对象后立即开始定时器,

=True: 立即开始;

=False: 不立即开始,需要调用 start()来开启。

【priority】硬件中断优先级,在 K210 中,取值范围是[1,7],值越小优先级越高

【div】硬件分频器。

使用方法

Timer.callback(fun)
定义回调函数。
Timer.period([value])
配置周期。
Timer.start()
启动定时器。
Timer.stop()
停止定时器。
Timer.deinit()
注销定时器。
*更多用法请阅读 MaixPy 官方文档:https://wiki.sipeed.com/soft/maixpy/zh/api_reference/machine/timer.html

定时器到了预设指定时间后,也会产生中断,因此跟外部中断的编程方式类

似,代码编程流程图如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Maix import GPIO
from fpioa_manager import fm
from machine import Timer

#注册 IO 和构建 LED 对象
fm.register(12, fm.fpioa.GPIO0)
LED_B = GPIO(GPIO.GPIO0, GPIO.OUT)

#计数变量
Counter=0

#定时器回调函数
def fun(tim):
global Counter
Counter = Counter + 1
print(Counter)
LED_B.value(Counter%2)#LED 循环亮灭。

#定时器 0 初始化,周期 1 秒
tim = Timer(Timer.TIMER0, Timer.CHANNEL0, mode=Timer.MODE_PERIODIC,
period=1000, callback=fun)

 

 

PWM

蜂鸣器分有源蜂鸣器和无源蜂鸣器,有源蜂鸣器的使用方式非常简单,只需

要接上电源,蜂鸣器就发声,断开电源就停止发声。而本实验用到的无源蜂鸣器,是需要给定指定的频率,才能发声的,而且可以通过改变频率来改变蜂鸣器的发声音色,以此来判定 pyAI-K210 的 PWM 输出频率是在变化的。

pyBase 开发底板上的无源蜂鸣器连接到引脚 X5。如下图所示:

而 pyAI-K210 并没有引脚直接连接到 pyBase 的 X5(主要避免影响 IO 复用。)而 IO15 连接到 pyBase 开发底板的 X6 引脚,因此我们可以用跳线帽或者跳线来连接 pyBase 的 X5 和 X6 引脚。相当于将无源蜂鸣器接到 pyAI-K210 的外部 IO15引脚。

构造函数

1
machine.PWM(tim, freq, duty, pin, enable=True)

PWM 对象在 machine 模块下。

【tim】K210 的 PWM 依赖于定时器来产生波形

【freq】PWM 频率

【duty】PWM 占空比

【pin】PWM 输出引脚

【enable】是否在构建对象后立即产生波形,默认 True。

使用方法

PWM.freq(freq)
设置频率。不传参数返回当前频率值。
PWM.duty(duty)
设置占空比。不传参数返回当前占空比值。[0-100]表示占空比百分比107
PWM.enable()
使能 PWM 输出。
PWM.disable()
暂停 PWM 输出。
PWM.deinit()
注销 PWM。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from machine import Timer,PWM
import time
#PWM 通过定时器配置,接到 IO15 引脚
tim = Timer(Timer.TIMER0, Timer.CHANNEL0, mode=Timer.MODE_PWM)
beep = PWM(tim, freq=1, duty=50, pin=15)
#循环发出不同频率响声。
while True:
beep.freq(200)
time.sleep(1)
beep.freq(400)
time.sleep(1)
beep.freq(600)
time.sleep(1)
beep.freq(800)
time.sleep(1)
beep.freq(1000)
time.sleep(1)

有条件的朋友可以使用示波器测量 pyAI-K210 的 IO15 引脚或 pyBase 的 X5 引脚,观察信号波形的变化:

 

I2C 总线(OLED显示屏)

我们先来看看开发板的原理图,也就是MicroPython 上的 OLED 接口是如何连线的。下图是 pyBase 开发底板的原理图。

我们再来看看 pyAI-K210 转接板的原理图接口部分。

结合以上可以得知 pyBase 底板连接到 OLED 的对应关系是 Y6→SCL 和Y8→SDA。对应 pyAI-K210 的关系是:IO27→Y6→SCL,IO28→Y8→SDA。本例程将使用 MicroPython 的 Machine 模块的 I2C 来定义 Pin 口和 I2C 初始化。具体如下:

 

构造函数

1
2
machine.I2C(id, mode=I2C.MODE_MASTER, scl=None, sda=None,
freq=400000, timeout=1000, addr=0, addr_size=7)

构建 I2C 对象。

【id】I2C ID,[ I2C.I2C0~I2C.I2C2 ]

【scl】时钟引脚;直接传引脚编号;

【sda】数据引脚; 直接传引脚编号;

【freq】通信频率,即速度;

【timeout】参数保留,设置无效;

【addr】从机地址;

【addr_size】地址长度, 支持 7 位寻址和 10 位寻址, 取值 7 或者 10。

使用方法
i2c.scan()
扫描 I2C 总线的设备。返回地址,如:0x3c;
i2c.readfrom(addr,len)
从指定地址读数据。addr:指定设备地址;len:读取字节数;
i2c.writeto(addr,buf)
写数据。addr:从机地址;buf:数据内容;
i2c.deinit()
注销 I2C。
*其它更多用法请阅读官方文档:https://wiki.sipeed.com/soft/maixpy/zh/api_reference/machine/i2c.h

定义好 I2C 后,还需要驱动一下 OLED。这里我们已经写好了 OLED 的库函数,在 ssd1306k.py 文件里面。开发者只需要将改 py 文件拷贝到 pyAI-K210 文件系统里面,然后在 main.py 里面调用函数即可。人生苦短,我们学会调用函数即可,也就是注重顶层的应用,想深入的小伙伴也可以自行研究 ssd1306k.py 文件代码。OLED 显示屏对象介绍如下:

构造函数

1
oled = SSD1306_I2C(i2c, addr)

构 OLED 显示屏对象。默认分辨率 128*64;

【i2c】定义好的 I2C 对象;

【addr】显示屏设备地址。

使用方法

oled.fill([value])

清屏

value=0x00 (黑屏);value=0xFF(白屏)

oled.text(string,x,y)

将 string 字符写在指定为位置。

【string】:字符;

【x】横坐标;[0-127];

【y】纵坐标。[0-7] 共 8 行

学习了 I2C、OLED 对象用法后我们通过编程流程图来理顺一下思路:

1
2
3
4
5
6
7
8
9
10
11
12
from machine import I2C
from ssd1306k import SSD1306

#定义 I2C 接口和 OLED 对象
i2c = I2C(I2C.I2C0, mode=I2C.MODE_MASTER,scl=27, sda=28)
oled = SSD1306(i2c, addr=0x3c)
#清屏,0x00(白屏),0xff(黑屏)
oled.fill(0)
#显示字符。参数格式为(str,x,y),其中 x 范围是 0-127,y 范围是 0-7(共 8 行)
oled.text("Hello World!", 0, 0) #写入第 0 行内容
oled.text("MicroPython", 0, 2) #写入第 2 行内容
oled.text("By 01Studio", 0, 5) #写入第 5 行内容

 

 

UART(串口通信)

串口是非常常用的通信接口,有很多工控产品、无线透传模块都是使用串口来收发指令和传输数据,这样用户就可以在无须考虑底层实现原理的前提下将各类串口功能模块灵活应用起来。

 

K210 一共有 3 个串口,每个串口可以自由映射引脚。 例:

1
2
3
# IO6→RX1,IO7→TX1
fm.register(6, fm.fpioa.UART1_RX, force=True)
fm.register(7, fm.fpioa.UART1_TX, force=True)

 

构造函数

1
machine.UART(uart,baudrate,bits,parity,stop,timeout, read_buf_len)

创建 UART 对象。

【uart】串口编号。[UART.UART1~UART3]

【baudrate】波特率,常用 115200、9600

【bits】数据位,默认 8

【parity】校验;默认 None, 0(偶校验),1(奇校验)

【stop】停止位,默认 1

【timeout】串口接收超时时间

【read_buf_len】串口接收缓冲大小。

使用方法

UART.read(num)

读取串口缓冲数据

【num】读取字节数

UART.readline(num)

读取串口缓冲数据的行

【num】行数

UART.write(buf)

串口发送数据

【buf】需要发送的数据

UART.deinit()

注销串口

 

我们可以用一个USB转TTL工具,配合电脑上位机串口助手来跟MicroPython

开发板模拟通信。

注意要使用 3.3V 电平的 USB 转串口 TTL 工具,本实验我们使用 pyBase 的外

接串口引脚,也就是 Y9(TX)和 Y10(RX),接线示意图如下:

从 pyAI-K210 原理图可以看到外部 IO6→Y9→RX ,IO7→Y10→TX。

在本实验中我们可以先初始化串口,然后给串口发去一条信息,这样 PC 机的串口助手就会在接收区显示出来,然后进入循环,当检测到有数据可以接收时候就将数据接收并打印,并通过 REPL 打印显示。代码编写流程图如下:

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
from machine import UART,Timer
from fpioa_manager import fm
#映射串口引脚
fm.register(6, fm.fpioa.UART1_RX, force=True)
fm.register(7, fm.fpioa.UART1_TX, force=True)
#初始化串口
uart = UART(UART.UART1, 115200, read_buf_len=4096)
uart.write('Hello 01Studio!')
while True:
text=uart.read() #读取数据
if text: #如果读取到了数据
print(text.decode('utf-8')) #REPL 打印
uart.write('I got'+text.decode('utf-8')) #数据回传

实验结果

这时候打开电脑的设备管理器,能看到 2 个 COM。写着 CH340 的是串口工具,另外一个则是 pyAI-K210 的 REPL。如果 CH340 驱动没安装,则需要手动安装,驱动在:配套资料包→开发工具→windows→串口终端→CH340 文件夹下。

本实验要用到串口助手,打开配套资料包→开发工具→windows→串口终端工具下的【UartAssist.exe】软件。

将串口工具配置成 COM14(根据自己的串口号调整)。波特率 115200。运行程序,可以看到一开始串口助手收到 pyAI-K210 上电发来的信息“Hello 01Studio!”。我们在串口助手的发送端输入“http://www.01studio.org”, 点击发送,可以看到pyAI-K210 在接收到该信息后在 REPL 里面打印了出来。如下图所示:

 

 

thread(线程)

我们看到前面的编程都是一个循环来完成,但当我们需要分时完成不同任务时候,线程编程就派上用场了。这有点像 RTOS(实时操作系统),今天我们就来学习一下如何通过 MicroPython 编程实现多线程。

 

pyAI-K210 的 MicroPython 固件已经集成了_thread 线程模块。我们直接调用即可。该模块衍生于 python3,属于低级线程,详情可以看官网介绍:https://docs.python.org/3.5/library/_thread.html#module-thread

编程流程如下:

1
2
3
4
5
6
7
8
9
10
11
import _thread #导入线程模块
import time
#线程函数
def func(name):
while True:
print("hello {}".format(name))
time.sleep(1)
_thread.start_new_thread(func,("1",)) #开启线程 1,参数必须是元组
_thread.start_new_thread(func,("2",)) #开启线程 2,参数必须是元组
while True:
pass