AtomS3R-CAM AI Chatbot 套件由控制器与语音底座两大核心部分组成;
控制器部分采用AtomS3R-CAM (集成0.3MP GC0308摄像头、9轴IMU、IR发射管等);

AtomS3R-CAM 是一款集成了摄像头的迷你物联网可编程控制器,内部集成了 ESP32-S3-PICO-1-N8R8 主控,集成 Wi-Fi 功能、8 MB 片上 Flash 和 8 MB PSRAM。它具有 5V 转 3.3V 的电源管理电路,并内置了三轴 BMM150 地磁传感器和六轴 BMI270 姿态传感器。此外,产品还搭载了 0.3MP 的 GC0308 摄像头,支持图像采集,并具有红外发射控制功能。摄像头前配备了保护玻璃,用于防尘并提高图像质量。与之前的产品相比,AtomS3R-CAM 的 3D 天线 经过增强,提供了更好的性能和更高的稳定性。该产品出厂配备了 UVC (USB Video Class) 功能固件,作为免驱 USB 摄像头使用,用户无需安装驱动即可通过 USB 接口直接使用摄像头。产品还包括 USB Type-C 接口用于供电和固件下载,并带有一个 HY2.0-4P 扩展端口。底部设计了六个 GPIO 和电源引脚,便于扩展。产品尺寸仅为 24.0 x 24.0 x 13.5mm ,适用于物联网设备监控、教育开发工具等嵌入式场景。
电路原理图如下:


管脚映射图如下:
这里只用主控板和OLED进行接线,搭建好硬件接线然后分析电路决定使用IIC驱动OLED使用了GPIO39和GPIO38作为时钟线和数据线,提出要求使用0X3C作为地址驱动IIC协议的OLED,这个地址也是使用了逻辑分析仪和积木程序测试出来的,如下是逻辑分析仪波形:
接着AI生成了一段代码,之前的有些分析和代码不能正常显示,主要原因是IIC接线和地址相关问题,就略过了,
这段代码(也是AI代写)已经接近最后结果了:
>>>
MicroPython v1.27.0-dirty on 2026-04-22; M5STACK AtomS3R with ESP32-S3-PICO-1
Type "help()" for more information.
>>> import gc
>>> gc.collect()
>>>
paste mode; Ctrl-C to cancel, Ctrl-D to finish
=== import machine
=== import time
=== from hardware import Pin, I2C
=== # 1. 定义引脚
=== # 注意:ESP32-S3 的某些引脚默认可能是输入或其他功能,需确保配置正确
=== scl_pin = Pin(39, mode=Pin.OUT, pull=Pin.PULL_UP)
=== sda_pin = Pin(38, mode=Pin.OUT, pull=Pin.PULL_UP)
=== # 2. 创建 I2C 对象
=== # 如果之前有实例,先尝试去初始化以防冲突
=== try:
=== i2c = I2C(0)
=== i2c.deinit()
=== except:
=== pass
=== # 重新初始化,频率降低到 50kHz 以提高稳定性进行调试
=== i2c = I2C(0, scl=scl_pin, sda=sda_pin, freq=50000)
=== print("Starting I2C Scan on SCL:39, SDA:38...")
=== time.sleep_ms(100) # 等待硬件稳定
=== # 3. 扫描所有地址
=== devices = i2c.scan()
=== if devices:
=== print(f"Success! Found devices: {[hex(d) for d in devices]}")
===
=== # 假设找到的第一个设备是 OLED (通常是 0x3C 或 0x3D)
=== # oled_addr = devices[0]
=== oled_addr = 0x3C
=== # --- 简单的点亮测试 ---
=== def send_cmd(cmd):
=== i2c.writeto_mem(oled_addr, 0x00, bytes([cmd]))
===
=== def send_data(data_bytes):
=== i2c.writeto_mem(oled_addr, 0x40, data_bytes)
=== # 初始化序列 (SSD1306)
=== init_seq = [
=== 0xAE, # Display OFF
=== 0xD5, 0x80, # Clock Div
=== 0xA8, 0x3F, # Multiplex
=== 0xD3, 0x00, # Offset
=== 0x40, # Start Line
=== 0x8D, 0x14, # Charge Pump ENABLE (关键)
=== 0x20, 0x00, # Memory Mode
=== 0xA1, # Segment Remap
=== 0xC8, # COM Scan Dec
=== 0xDA, 0x12, # COM Pins
=== 0x81, 0xCF, # Contrast
=== 0xA4, # Resume to RAM
=== 0xA6, # Normal Color
=== 0xAF # Display ON
=== ]
===
=== for i in range(0, len(init_seq), 2 if i < len(init_seq)-1 and init_seq[i] in [0xD5, 0xA8, 0xD3, 0x8D, 0x20, 0xDA, 0x81] else 1):
=== # 简单处理变长命令,这里为了稳健,逐个发送单字节命令或数据
=== pass
===
=== # 更稳健的发送方式:
=== for cmd in [0xAE, 0xD5, 0x80, 0xA8, 0x3F, 0xD3, 0x00, 0x40, 0x8D, 0x14, 0x20, 0x00, 0xA1, 0xC8, 0xDA, 0x12, 0x81, 0xCF, 0xA4, 0xA6, 0xAF]:
=== send_cmd(cmd)
===
=== # 清屏并填充全亮 (测试屏幕是否物理损坏)
=== buffer = bytearray(1024)
=== # 让屏幕全亮
=== for i in range(1024):
=== buffer[i] = 0xFF
===
=== send_cmd(0x21) # Set Column Address
=== send_cmd(0x00)
=== send_cmd(0x7F)
=== send_cmd(0x22) # Set Page Address
=== send_cmd(0x00)
=== send_cmd(0x07)
===
=== send_data(buffer)
=== print("Screen should be fully WHITE now.")
===
=== else:
=== print("Failed. No I2C devices found.")
=== print("Check:")
=== print("1. Is OLED VCC connected to 3.3V or 5V?")
=== print("2. Is GND connected?")
=== print("3. Are SDA/SCL swapped? (Try swapping 38 and 39)")
=== print("4. Is the OLED module working? (Test with Arduino if possible)")
===
===
===
Starting I2C Scan on SCL:39, SDA:38...
Success! Found devices: ['0x3c']
Traceback (most recent call last):
File "<stdin>", line 51, in <module>
NameError: name 'i' isn't defined
(以下是文心一言给出的意见)
这个错误 NameError: name 'i' isn't defined 是因为你在列表推导式或 for 循环的条件判断中引用了变量 i,但该变量在当前的上下文中尚未定义或不可见。
具体来说,问题出在这一行:
for i in range(0, len(init_seq), 2 if i < len(init_seq)-1 and init_seq[i] in [0xD5, 0xA8, 0xD3, 0x8D, 0x20, 0xDA, 0x81] else 1):
在 Python 中,range() 函数的参数是在循环开始前计算的。此时,循环变量 i 还没有被赋值,因此在计算步长(step)时引用 i 会导致 NameError。此外,这种动态步长的逻辑在 range 中也是不支持的。
解决方案
既然你已经在后面提供了一个“更稳健的发送方式”(直接列出所有命令字节),建议直接删除那段报错的复杂循环代码,只保留后面那个简单的 for cmd in [...] 循环。
以下是修正后的完整代码,你可以直接复制粘贴到 REPL 的 paste mode (Ctrl+E) 中运行:
import machine
import time
from machine import Pin, I2C # 注意:通常是 from machine import,而不是 hardware
# 1. 定义引脚
# M5Stack AtomS3R 的默认 I2C 引脚可能不同,这里强制使用 GPIO 38 (SDA) 和 39 (SCL)
scl_pin = Pin(39, mode=Pin.OUT, pull=Pin.PULL_UP)
sda_pin = Pin(38, mode=Pin.OUT, pull=Pin.PULL_UP)
# 2. 创建 I2C 对象
try:
i2c = I2C(0)
i2c.deinit()
except:
pass
# 重新初始化,频率降低到 50kHz 以提高稳定性
i2c = I2C(0, scl=scl_pin, sda=sda_pin, freq=50000)
print("Starting I2C Scan on SCL:39, SDA:38...")
time.sleep_ms(100)
# 3. 扫描所有地址
devices = i2c.scan()
if devices:
print(f"Success! Found devices: {[hex(d) for d in devices]}")
oled_addr = 0x3C # 通常 SSD1306 的地址是 0x3C 或 0x3D
# --- 定义发送函数 ---
def send_cmd(cmd):
# writeto_mem 的第一个参数是地址,第二个是内存地址/寄存器地址,第三个是数据
# 对于 SSD1306,控制字节通常通过 I2C 写入时的第一个字节来区分命令/数据
# 但 MicroPython 的 writeto_mem 会自动处理控制字节如果驱动支持,或者我们需要用 writeto
# 这里使用更底层的 writeto 确保兼容性,或者继续使用 writeto_mem 如果硬件抽象层支持
# 注意:很多 MicroPython 端口对 SSD1306 使用 writeto_mem 时,memaddr 参数可能被忽略或用于控制字节
# 更通用的方法是:
i2c.writeto(oled_addr, bytes([0x00, cmd])) # 0x00 表示接下来是命令
def send_data(data_bytes):
# 0x40 表示接下来是数据
# 由于 writeto 有长度限制,大数据块可能需要分片,但 1024 字节通常在允许范围内
i2c.writeto(oled_addr, bytes([0x40]) + data_bytes)
# --- 初始化序列 (SSD1306) ---
# 直接使用扁平化的命令列表,避免复杂的步长逻辑
init_cmds = [
0xAE, # Display OFF
0xD5, 0x80, # Clock Div
0xA8, 0x3F, # Multiplex
0xD3, 0x00, # Offset
0x40, # Start Line
0x8D, 0x14, # Charge Pump ENABLE (关键)
0x20, 0x00, # Memory Mode
0xA1, # Segment Remap
0xC8, # COM Scan Dec
0xDA, 0x12, # COM Pins
0x81, 0xCF, # Contrast
0xA4, # Resume to RAM
0xA6, # Normal Color
0xAF # Display ON
]
print("Sending initialization commands...")
for cmd in init_cmds:
send_cmd(cmd)
# --- 清屏并填充全亮 ---
print("Filling screen with white...")
buffer = bytearray(1024)
for i in range(1024):
buffer[i] = 0xFF
# 设置列地址范围 (0-127)
send_cmd(0x21)
send_cmd(0x00)
send_cmd(0x7F)
# 设置页地址范围 (0-7)
send_cmd(0x22)
send_cmd(0x00)
send_cmd(0x07)
# 发送数据
send_data(buffer)
print("Screen should be fully WHITE now.")
else:
print("Failed. No I2C devices found.")
print("Check:")
print("1. Is OLED VCC connected to 3.3V?")
print("2. Is GND connected?")
print("3. Are SDA/SCL swapped? (Try swapping 38 and 39)")
主要修改说明:
删除了错误的循环:移除了导致 NameError 的 for i in range(...) 复杂逻辑。
简化了命令发送:直接使用 init_cmds 列表遍历发送,这与原代码中注释掉的“更稳健的发送方式”一致。
修正了导入语句:将 from hardware import Pin, I2C 改为标准的 from machine import Pin, I2C。虽然在某些特定固件中 hardware 可能是别名,但 machine 是 MicroPython 的标准库。如果报错 No module named 'machine',再改回 hardware。
修正了底层写入方法:
原代码使用 i2c.writeto_mem(oled_addr, 0x00, bytes([cmd]))。在某些 ESP32 实现中,writeto_mem 可能不会正确发送 SSD1306 所需的 Control Byte (0x00 为命令, 0x40 为数据)。
新代码改用 i2c.writeto(oled_addr, bytes([0x00, cmd])),这是更通用且可靠的方式,明确发送了控制字节。
如果屏幕仍然不亮:
检查接线:AtomS3R 的 GPIO 38 和 39 是否确实连接到了 OLED 的 SDA 和 SCL?有些 OLED 模块需要上拉电阻,但 AtomS3 内部已启用 PULL_UP,通常没问题。
电压问题:确保 OLED 是 3.3V 版本。如果是 5V 版本,可能需要电平转换或连接到 5V 引脚(如果 OLED 支持 5V)。
地址确认:虽然扫描到 0x3c,但有些屏幕可能需要 0x3d。如果 0x3c 不行,尝试修改 oled_addr = 0x3D。
复位引脚:部分 OLED 模块有一个 RES/RST 引脚,需要拉高或连接到 MCU 的复位引脚。如果模块上有 RST 引脚,确保它被正确连接(通常接 3.3V 或通过电容复位)。
(OLED能显示亮屏,接着是我提出的问题)
能运行了 能否在此基础上 输出 hollow
文心一言:
既然屏幕已经点亮,我们现在可以在显存(Buffer)中绘制字符 "hollow" 并显示出来。
由于 MicroPython 的 framebuf 模块通常包含在标准固件中,我们可以利用它来轻松绘制文本。如果不确定是否包含 framebuf,下面的代码使用了最基础的逐字节绘制法(不依赖 framebuf 的字体渲染),通过一个简单的 5x7 点阵字模来实现,确保在任何基础环境下都能运行。
请将以下代码复制并粘贴到 REPL 中运行:
python
import machine
import time
from machine import Pin, I2C
# --- 1. 初始化 I2C 和 OLED (复用之前的配置) ---
scl_pin = Pin(39, mode=Pin.OUT, pull=Pin.PULL_UP)
sda_pin = Pin(38, mode=Pin.OUT, pull=Pin.PULL_UP)
try:
i2c = I2C(0)
i2c.deinit()
except:
pass
i2c = I2C(0, scl=scl_pin, sda=sda_pin, freq=400000) # 提高频率以加快刷新
oled_addr = 0x3C
def send_cmd(cmd):
i2c.writeto(oled_addr, bytes([0x00, cmd]))
def send_data(data_bytes):
i2c.writeto(oled_addr, bytes([0x40]) + data_bytes)
def init_oled():
init_cmds = [
0xAE, 0xD5, 0x80, 0xA8, 0x3F, 0xD3, 0x00, 0x40,
0x8D, 0x14, 0x20, 0x00, 0xA1, 0xC8, 0xDA, 0x12,
0x81, 0xCF, 0xA4, 0xA6, 0xAF
]
for cmd in init_cmds:
send_cmd(cmd)
init_oled()
# --- 2. 定义简单的 5x7 字体字模 (仅包含 hollow 所需的字符) ---
# 格式: 每个字符 5 字节,每字节代表一列的 8 位像素 (低位在上)
font_5x7 = {
'h': [0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08], # 简化版 h,实际需调整
# 为了更美观,使用标准的 5x7 点阵数据
}
# 标准 5x7 ASCII 字模子集 (h, o, l, w)
# 数据源: 常见 LCD 字库
char_map = {
'h': [0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08], # 占位,下面替换为真实数据
'o': [0x00, 0x00, 0x1C, 0x22, 0x22, 0x22, 0x1C],
'l': [0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08],
'w': [0x00, 0x00, 0x22, 0x22, 0x22, 0x2A, 0x14],
}
# 修正 'h' 和 'l' 的标准点阵
char_map['h'] = [0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08] # 临时简单竖线,如需完美请替换
# 更准确的 'h': [0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08] -> 实际上 h 是 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 (左竖) + 右边弯曲?
# 让我们用一个更通用的简单字模生成函数或者直接硬编码 "hollow" 的图像数据会更稳定。
# --- 替代方案:直接绘制 "hollow" 的像素数据 ---
# 屏幕分辨率 128x64。我们将文字放在中间。
# 使用一个 128x8 的缓冲区 (1页高,128宽) 来演示,或者全屏缓冲。
# 这里我们使用 framebuf 如果可用,否则手动绘制。
# 尝试导入 framebuf
try:
import framebuf
HAS_FRAMEBUF = True
except ImportError:
HAS_FRAMEBUF = False
if HAS_FRAMEBUF:
# 创建帧缓冲区
buffer = bytearray(128 * 64 // 8)
fb = framebuf.FrameBuffer(buffer, 128, 64, framebuf.MONO_VLSB)
# 清屏
fb.fill(0)
# 绘制文字 "hollow"
# x, y, color, font_size (可选)
# 默认字体较小,我们把它画在中间
text = "hollow"
# 估算宽度: 6个字符 * ~6像素 = 36像素。居中: (128-36)/2 ≈ 46
fb.text(text, 40, 28, 1)
# 将缓冲区发送到 OLED
send_cmd(0x21) # 设置列地址
send_cmd(0)
send_cmd(127)
send_cmd(0x22) # 设置页地址
send_cmd(0)
send_cmd(7)
send_data(buffer)
print("Displayed 'hollow' using framebuf.")
else:
# 如果没有 framebuf,我们手动绘制一个简单的 "HOLLOW" 块状字或点阵
# 这里为了简单,我们绘制一个居中的矩形框表示 "Hollow" 的概念,或者逐像素画
# 鉴于没有 framebuf 画字很复杂,我们绘制一个空心矩形 (Hollow Rectangle) 来呼应 "hollow"
buffer = bytearray(1024)
# 绘制一个空心矩形 (Hollow Box)
# 范围: x: 20-100, y: 10-50
# 页地址计算: y//8
def set_pixel(buf, x, y, color=1):
if 0 <= x < 128 and 0 <= y < 64:
index = x + (y // 8) * 128
if color:
buf[index] |= (1 << (y % 8))
else:
buf[index] &= ~(1 << (y % 8))
# 画横线
for x in range(20, 101):
set_pixel(buffer, x, 10) # 顶
set_pixel(buffer, x, 50) # 底
# 画竖线
for y in range(10, 51):
set_pixel(buffer, 20, y) # 左
set_pixel(buffer, 100, y) # 右
# 发送数据
send_cmd(0x21)
send_cmd(0)
send_cmd(127)
send_cmd(0x22)
send_cmd(0)
send_cmd(7)
send_data(buffer)
print("Displayed a Hollow Box (framebuf not available).")
代码说明:
自动检测 framebuf:
大多数 ESP32 MicroPython 固件都包含 framebuf 模块。如果存在,代码会使用 fb.text("hollow", ...) 直接在屏幕中央绘制字符串 "hollow"。这是最理想的效果。
备用方案(无 framebuf):
如果固件精简导致没有 framebuf,代码会自动切换到一个“备用模式”,在屏幕中央绘制一个空心矩形(Hollow Rectangle),以视觉方式回应 "hollow" 这个词,避免因缺少字体库而报错。
刷新逻辑:
使用了全屏刷新(发送 1024 字节),确保显示稳定。
预期结果:
屏幕上会清晰地显示单词 "hollow"。
如果显示乱码,请尝试调整 fb.text 的 Y 坐标(例如改为 20 或 30),因为不同固件的字体高度可能略有差异。
OLED显示和硬件接线图:

我要赚赏金
