这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » ATOMS3R-CAMAICHATBOT+借助AI完成OLED驱动开发

共1条 1/1 1 跳转至

ATOMS3R-CAMAICHATBOT+借助AI完成OLED驱动开发

助工
2026-05-25 07:03:14     打赏

AtomS3R-CAM AI Chatbot 套件由控制器与语音底座两大核心部分组成;

控制器部分采用AtomS3R-CAM (集成0.3MP GC0308摄像头、9轴IMU、IR发射管等);

image.png

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 ,适用于物联网设备监控、教育开发工具等嵌入式场景。

电路原理图如下:

image.png

image.png

管脚映射图如下:

image.png 

这里只用主控板和OLED进行接线,搭建好硬件接线然后分析电路决定使用IIC驱动OLED使用了GPIO39GPIO38作为时钟线和数据线,提出要求使用0X3C作为地址驱动IIC协议的OLED,这个地址也是使用了逻辑分析仪和积木程序测试出来的,如下是逻辑分析仪波形:

image.png 

接着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),因为不同固件的字体高度可能略有差异。

image.png 

OLED显示和硬件接线图:

fc57f03a0480983fc26fba2048d2e6d5.jpg


 



共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]