利用 CircuitPython 开发板简化基于 ARM® Cortex®-M0+ 的物联网嵌入式设计
投稿人:DigiKey 北美编辑
2017-11-07
很多嵌入式应用使用了高级 MCU,但它们只需基本的硬件控制功能,而无高级嵌入式设计的“硬实时”需求。开发人员和创客经常很容易陷到硬件设计、C/C++ 编程和实时操作系统的细节中。幸运的是,他们可以使用更简单的方法。
本文将介绍一种使用来自 Adafruit Industries 的微型开发板的较容易方法。该开发板结合了 Python 编程语言的嵌入式设计变体与基于 ARM Cortex-M0+ 处理器的高级 32 位 MCU。
高级 MCU 简化了设计
高级 MCU 通过将全套模拟和数字外设与功能强大的处理器内核集成在一起,从而帮助简化硬件设计。例如,Microchip Technology 的 ATSAMD21G18 MCU 将 ARM Cortex-M0+ 内核、256 KB 闪存、32 KB SRAM、高级控制子系统和大量外设全部集成在 10 x 10 mm 见方的扁平 (TQFP) 封装(图 1)中。
图 1:Microchip Technology 的 SAM D21 MCU 系列成员都基于超低功耗 ARM® Cortex®-M0+ 内核,提供全套功能块和外设,差别仅在于具体的存储器大小和外设通道数量。(图片来源:Microchip Technology)
除了 32 个 GPIO 之外,ATSAMD21G18 MCU 的外设集还包括多个高级串行通信 (SERCOM) 通道、波形输出通道、多通道 12 位模数转换器 (ADC)、模拟比较器、10 位数模转换器 (DAC)。
设计挑战
有了此类高级 MCU,开发人员无需花费时间查找和连接外部外设,但它们仍然对在系统设计中部署 MCU 的方式提出了严格要求。例如,在集成多种类型的电路时,ATSAMD21G18 MCU 的设计要通过相应的一组单独域来提供电源。因此,开发人员必须处理处理器内核 VDDCORE、内部稳压器 (VDDIN)、外设 (VDDIO) 和模拟模块 (VDDANA) 的多个电源和接地引脚(图 2)。
在设计过程中,开发人员必须遵守具体的建议,包括提供电源、接地以及选择和放置去耦电容器——这些对于经验丰富的开发人员极为平常,但对于新接触嵌入式 MCU 硬件设计的开发人员而言,却是潜在的陷阱。
图 2:Microchip Technology 的 ATSAMD21G18 MCU 使用多个功率域为不同的模拟和数字块供电,在为这些域供电时需要多加注意。(图片来源:Microchip Technology)
同样,这些器件的软件开发工作也是非常艰巨的。通常,新入门的嵌入式系统开发人员会发现他们埋头于从嵌入式开发资料了解 C/C++ 开发的相关细节,而这些资料更多地针对具有硬实时需求的应用。这些应用通常具有针对中断延迟和确定性响应的关键性时序要求。但是,很多面向物联网 (IoT) 的新兴传感器设计对数据采集或致动器工作的要求却要宽松得多,或者说这些要求很容易满足。
简化嵌入式开发
Adafruit 推出了一系列开发板,旨在帮助嵌入式开发人员消除这些硬件和软件设计障碍,为许多应用需求提供了特别有效的解决方案。Adafruit 的 Metro M0 Express 和 Feather M0 Express 都基于 ATSAMD21G18 MCU,提供的是完整的嵌入式系统,包括串行接口(USB、SPI、I2C 和 UART)、脉冲宽度调制 (PWM)、中断输入,以及多个模拟 IO 和 GPIO。这些开发板的差异仅在于尺寸和 GPIO 数量:2.8" x 2.1" x 0.28" 的 Metro M0 Express 提供 25 个 GPIO,而尺寸稍小 (2.0" x 0.9" x 0.28") 的 Feather M0 Express 则提供 20 个 GPIO。
SAM D21 MCU 系列使用了最高级的 MCU,提供的外设通道数远多于物理引脚,但提供的引脚映射功能可将外设功能分配给特定硬件引脚。因此,虽然尺寸小巧,但每个开发板都可使用共享引脚来提供 MCU 广泛外设的全部功能(图 3)。
图 3:Adafruit 利用引脚复用在微型 Feather M0 Express 开发板中提供大量 ATSAMD21G18 外设功能子集。(图片来源:Adafruit)
但是,对于开发人员而言,这些细节是透明的。Adafruit 在其开源软件包的特定模块中为每个开发板提供了特定配置(列表 1)。
STATIC const mp_rom_map_elem_t board_global_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_A0), MP_ROM_PTR(&pin_PA02) },
{ MP_ROM_QSTR(MP_QSTR_A1), MP_ROM_PTR(&pin_PB08) },
{ MP_ROM_QSTR(MP_QSTR_A2), MP_ROM_PTR(&pin_PB09) },
{ MP_ROM_QSTR(MP_QSTR_A3), MP_ROM_PTR(&pin_PA04) },
{ MP_ROM_QSTR(MP_QSTR_A4), MP_ROM_PTR(&pin_PA05) },
{ MP_ROM_QSTR(MP_QSTR_A5), MP_ROM_PTR(&pin_PB02) },
{ MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_PB11) },
{ MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_PB10) },
{ MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_PA12) },
{ MP_ROM_QSTR(MP_QSTR_D0), MP_ROM_PTR(&pin_PA11) },
{ MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_PA11) },
{ MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_PA10) },
{ MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_PA10) },
{ MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_PA22) },
{ MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_PA23) },
{ MP_ROM_QSTR(MP_QSTR_D5), MP_ROM_PTR(&pin_PA15) },
{ MP_ROM_QSTR(MP_QSTR_D6), MP_ROM_PTR(&pin_PA20) },
{ MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_PA07) },
{ MP_ROM_QSTR(MP_QSTR_D10), MP_ROM_PTR(&pin_PA18) },
{ MP_ROM_QSTR(MP_QSTR_D11), MP_ROM_PTR(&pin_PA16) },
{ MP_ROM_QSTR(MP_QSTR_D12), MP_ROM_PTR(&pin_PA19) },
{ MP_ROM_QSTR(MP_QSTR_D13), MP_ROM_PTR(&pin_PA17) },
{ MP_ROM_QSTR(MP_QSTR_NEOPIXEL), MP_ROM_PTR(&pin_PA06) },
};
MP_DEFINE_CONST_DICT(board_module_globals, board_global_dict_table);
列表 1:Adafruit 开源 CircuitPython 库摘录了硬件详细信息,其中包括使用开发板特定的引脚映射,例如此处显示的 Feather M0 Express 开发板映射。(代码来源:Adafruit)
开始开发时,用户可将开发板插入 USB 端口,并且将内置 USB 引导程序与 Arduino IDE 一起使用。为了进一步简化引入嵌入式软件设计,开发人员可以使用内置功能,轻松将 CircuitPython 加载到其电路板上,然后即可开始构建嵌入式应用。
利用 CircuitPython 简化开发
CircuitPython 旨在帮助加快嵌入式开发的学习速度,它的功能实际上源自 MicroPython,后者是与 Python 关系更直接的派生语言。凭借简单清晰的语法和大量的支持模块,Python 成为一种流行语言。但是,其代码占用空间过大,对嵌入式系统不实用。
MicroPython 砍掉了 Python 的一些比较繁琐的功能,简化的版本能够满足嵌入式系统的逻辑约束,同时又保留了语言的核心功能。在开发 CircuitPython 的过程中,Adafruit 更进一步,删除了被视为对嵌入式系统新手程序员不太必要的模块。
Adafruit 宣称 CircuitPython 的目标是提供一种非常适合培训的语言,让开发人员能够熟练掌握嵌入式设计,而无需纠缠于低级别开发细节。CircuitPython 从前代产品 Python 继承的最令人期待的特性之一是解释型特性,让开发人员能够通过交互方式探索外部模块的接口。例如,CircuitPython 的基本模块就是开发板模块——一个提供对相关开发板 I/O 引脚访问的开发板特定模块。开发人员能够从控制台启动 CircuitPython,导入该开发板模块并即时查看支持的引脚名称(列表 2)。
>>> import board
>>> dir(board)
['A0', 'SPEAKER', 'A1', 'A2', 'A3', 'A4', 'SCL', 'A5', 'SDA', 'A6', 'RX',
'A7', 'TX', 'LIGHT', 'A8', 'TEMPERATURE', 'A9', 'BUTTON_A', 'D4', 'BUTTON_B',
'D5', 'SLIDE_SWITCH', 'D7', 'NEOPIXEL', 'D8', 'D13', 'REMOTEIN', 'IR_RX',
'REMOTEOUT', 'IR_TX', 'IR_PROXIMITY', 'MICROPHONE_SCK', 'MICROPHONE_DO',
'ACCELEROMETER_INTERRUPT', 'ACCELEROMETER_SDA', 'ACCELEROMETER_SCL',
'SPEAKER_ENABLE', 'SCK', 'MOSI', 'MISO', 'FLASH_CS']
列表 2:在解析器控制台提示符处 (>>),程序员可以导入开发板
模块,并输入 dir(board)
,以查看该开发板特定模块中提供的引脚名称。(代码来源:Adafruit)
开发板模块提供与底层硬件的连接,同时提供一种简单方式来访问 Metro M0 Express 和 Feather M0 Express 开发板的引脚。例如,A0 模拟引脚被简单引用为 "board.A0"。另一方面,各个模块中驻留有特定硬件功能,例如:analogio 模块代表模拟;digitalio 模块代表数字;busio 模块代表 I2C、SPI 和 UART;pulseio 模块代表 PWM 和其他基于脉冲的协议等。因此,要在 CircuitPython 中读取 A0 模拟输入,只需导入相关模块,并读取相关器件实例的值(列表 3)。
import board
import analogio
def adc_to_voltage(val):
return val / 65535 * 3.3
adc = analogio.AnalogIn(board.A0)
pinA0voltage = adc_to_voltage(adc.value)
列表 3:与 Python 相同,CircuitPython 提供了很多高级别模块,开发人员可将它们导入自己的代码中;与 Python 不同,CircuitPython 还提供了一些模块,让程序员能够执行硬件级别的操作,例如读取值 (adc.value
) (在 ADC 输入引脚 (board.A0
) 处)。(代码来源:Adafruit)
开发人员可通过对模拟或数字 IO 引脚的直接访问,轻松地扩展硬件功能。例如,他们可以通过试验板将 LED 连接到开发板的 A0 连接(图 4),并且使用模拟模块让 LED 闪烁(列表 4),以详细研究模拟输出特性。
图 4:开发人员可以通过将试验板电路,例如具有限流电阻器的 LED,连接到 Metro M0 Express 板的 A0 模拟输出,即可调出 MCU 的 DAC,从而快速构建外部硬件原型。(图片来源:Adafruit)
import board
import analogio
led = analogio.AnalogOut(board.A0)
while True:
led.value = 65535 # max brightness
time.sleep(0.5) # stay on for 1/2 sec
led.value = 0 # off
time.sleep(0.5) # stay off for 1/2 sec
列表 4:对于图 4 所示的试验板电路,开发人员使用 CircuitPython analogio
模块,创建绑定到该板 A0 引脚的 Analogout
类实例 (led
),并修改其值属性,以便控制 LED 亮度。(代码来源:Adafruit)
大多数现代“智能”传感器和致动器都提供 I2C 或 SPI 接口,用于读取、写入和监视外围设备。虽然开发人员可将器件轻松连接到开发板的 SPI 或 I2C 接口,但软件接口可能需要额外的工作。
为了最大程度减少这类工作,Adafruit 为一些流行的器件(例如 Silicon Labs 的 SI7021 温度/湿度传感器)提供了 CircuitPython 模块。与模拟 I/O 模块相同,在定义了所需的 I2C 接口对象之后,SI7021 CircuitPython 模块允许程序员只需使用相应类对象的实例即可访问传感器(列表 5)。
import adafruit_si7021
from busio import I2C
from board import SCL, SDA
# create the I2C interface object
i2c = I2C(SCL, SDA)
# and use it to instantiate the sensor object
sensor = adafruit_si7021.SI7021(i2c)
# and perform the sensor measurements
current_temperature = sensor.temperature
current_relative_humidity = sensor.relative_humidity
列表 5:Adafruit 开源软件库提供了简化附加硬件功能访问的 CircuitPython 模块,例如使用 Silicon Labs 的 SI7021 传感器的温度和湿度测量。(代码来源:Adafruit)
Adafruit 板和 CircuitPython 开源库的组合虽然主要是作为一个学习平台,但也可用于创建相当先进的物联网设备和其他嵌入式设计。同时,开发人员需要认识到,诸如 MicroPython/CircuitPython 之类解释型语言,在满足硬实时需求的能力方面有很大的局限性。但是,对于许多嵌入式应用而言,这个学习平台可为扩展奠定坚实的基础。
为了增加硬件功能,开发者可在 Feather M0 Express 板上叠接可用的 Adafruit FeatherWing 子卡,甚至可以使用 FeatherWing Proto 原型板添加他们自己的电路。为了增加对 CircuitPython 中的额外硬件功能的支持,开发人员必须创建定制软件来添加所需的底层驱动程序。然而,通过将开放源码库与 Python 本身特性组合在一起,即使是这项工作也得到了最大程度的简化。
通过检查开源库,程序员可以研究用于实现硬件支持的关键设计模式。例如,Adafruit 的 SI7021 模块展示了相应的“Pythonic”类结构,包括构造函数和辅助函数(列表 6)。通过遵循这种方法,开发人员能够以最小的工作量来添加自己的硬件。
from micropython import const
import ustruct
import sys
from adafruit_bus_device.i2c_device import I2CDevice
HUMIDITY = const(0xf5)
TEMPERATURE = const(0xf3)
_RESET = const(0xfe)
_READ_USER1 = const(0xe7)
_USER1_VAL = const(0x3a)
def _crc(data):
crc = 0
for byte in data:
crc ^= byte
for i in range(8):
if crc & 0x80:
crc <<= 1
crc ^= 0x131
else:
crc <<= 1
return crc
class SI7021:
"""
A driver for the SI7021 temperature and humidity sensor.
"""
def __init__(self, i2c, address=0x40):
self.i2c_device = I2CDevice(i2c, address)
self.init()
self._measurement = 0
def init(self):
self.reset()
# Make sure the USER1 settings are correct.
while True:
# While restarting, the sensor doesn't respond to reads or writes.
try:
data = bytearray([_READ_USER1])
with self.i2c_device as i2c:
i2c.write(data, stop=False)
i2c.read_into(data)
value = data[0]
except OSError as e:
if e.args[0] not in ('I2C bus error', 19): # errno 19 ENODEV
raise
else:
break
if value != _USER1_VAL:
raise RuntimeError("bad USER1 register (%x!=%x)" % (
value, _USER1_VAL))
def _command(self, command):
with self.i2c_device as i2c:
i2c.write(ustruct.pack('B', command))
def _data(self):
data = bytearray(3)
data[0] = 0xff
while True:
# While busy, the sensor doesn't respond to reads.
try:
with self.i2c_device as i2c:
i2c.read_into(data)
except OSError as e:
if e.args[0] not in ('I2C bus error', 19): # errno 19 ENODEV
raise
else:
if data[0] != 0xff: # Check if read succeeded.
break
value, checksum = ustruct.unpack('>HB', data)
if checksum != _crc(data[:2]):
raise ValueError("CRC mismatch")
return value
def reset(self):
self._command(_RESET)
@property
def relative_humidity(self):
"""The measured relative humidity in percents."""
self.start_measurement(HUMIDITY)
value = self._data()
self._measurement = 0
return value * 125 / 65536 - 6
@property
def temperature(self):
"""The measured temperature in degrees Celcius."""
self.start_measurement(TEMPERATURE)
value = self._data()
self._measurement = 0
return value * 175.72 / 65536 - 46.85
def start_measurement(self, what):
"""
Starts a measurement.
Starts a measurement of either ``HUMIDITY`` or ``TEMPERATURE``
depending on the ``what`` argument.Returns immediately, and the
result of the measurement can be retrieved with the
``temperature`` and ``relative_humidity`` properties.This way it
will take much less time.
This can be useful if you want to start the measurement, but don't
want the call to block until the measurement is ready -- for instance,
when you are doing other things at the same time.
"""
if what not in (HUMIDITY, TEMPERATURE):
raise ValueError()
if not self._measurement:
self._command(what)
elif self._measurement != what:
raise RuntimeError("other measurement in progress")
self._measurement = what
列表 6:为了将自定义硬件添加到其 CircuitPython 应用中,开发人员可以使用像用于 SiLabs si7021 的 Adafruit CircuitPython 驱动程序这样的开源软件。该驱动程序展示了使用隐式 (__init__
) 和显式 (init
) 构造函数来设计传感器硬件类 (SI7021
),以及通过串行总线(本例中为 I2C 总线)来访问硬件本身的关键设计模式。(代码来源:Adafruit)
其他模块,特别是资源库的硬件抽象层 (HAL) 中的模块,提供了用于实现物理硬件访问的较低级别 C 语言服务和 hook。完成自定义模块后,开发人员可以利用分步说明,将自定义的 C 和 Python 代码添加到环境中,这些分步说明描述了 Python、MicroPython 和 CircuitPython 内置的特定 hook 的使用。在桌面或服务器 Python 环境中,增强过程在这一点即已结束,但在嵌入式环境中,则还需要额外的步骤,使用增强代码映像来更新开发板的固件。
Adafruit 为该开发板提供了内置的引导程序,可自动加载 USB Flashing Format (UF2) 映像。开发人员通过按下该开发板的 RESET 按钮两次来触发引导程序进程,这会导致在用户的主机文件系统中出现一个新的“boot”可移动驱动器。开发人员只需将 UF2 映像从主机系统拖放到代表开发板的可移动驱动器即可(图 5)。这与最初用于加载 CircuitPython 的过程相同。在这种情况下,开发人员只需拖放使用自定义代码构建的 UF2 映像。引导程序会自动执行,将新映像刷入该开发板。
图 5:Adafruit 通过为开发板提供引导程序简化了映像刷写,当通过按下开发板的 RESET 按钮启动时,导致 BOOT 可移动驱动器显示在文件系统中(本例中为 MAC OS),开发人员只需将新的 UF2 映像拖放至该驱动器上。(图片来源:Adafruit)
总结
对于希望获得嵌入式设计经验的开发人员来说,针对“硬”实时需求提供的工具和技术显得有些小题大做。同时,开发人员又希望可以随时使用能够提供广泛模拟和数字 IO 功能的高级 32 位 MCU。
Adafruit 的开源 CircuitPython 包则提供了一个更简单的开发环境,能够满足这些较简单的需求。通过将 CircuitPython 与 Adafruit 的 Metro M0 Express 或 Feather M0 Express 开发板结合在一起,新手开发人员可以快速获得嵌入式系统经验,而更有经验的开发人员则可以快速构建嵌入式应用原型。
CircuitPython 与 Adafruit 开发板一起为嵌入式应用开发提供了一个易于使用却功能强大的平台。
免责声明:各个作者和/或论坛参与者在本网站发表的观点、看法和意见不代表 DigiKey 的观点、看法和意见,也不代表 DigiKey 官方政策。