若该文为原创文章,转载请注明原文出处。
本文基于正点原子的K210开发板学习,当是记录自己的学习过程和心得。
K210整体比如简单,结合AI基本一天就可以使用开发。
教程参考正点原子教程或
一、K210是什么?
K210全称为堪智K210,是嘉楠科技自主研发的一款采用RISC-V处理器架构,具备视听一体、自主IP内核与可编程能力强三大特点,支持机器视觉与机器听觉多模态识别,可广泛应用于智能家居、智能园区、智能能耗和智能农场等场景。堪智K210使用台积电超低功耗的28纳米先进制程,具有双核64位处理器,总算力可达1TOPS,内置多种硬件加速单元(KPU、FPU、FFT等),并且拥有较好的功耗性能、稳定性与可靠性。

二、开发环境搭建
K210官方裸机版本SDK并不支持MicroPython,需要下载CanMV支持的固件才可以使用MicroPython来使用。
K210模块出厂已经烧录好出厂固件,可以连接CanMV IDE来使用MicroPython语法编程。不需要每次烧录出厂固件。只有在固件升级或者烧录了其他固件的情况下才需要重新烧录出厂固件。
出厂固件只提供bin烧录文件,不提供源码。如果需要学习K210的MicroPython底层固件源码,可以前往官方CanMV项目
为了方便简单开发,所以使用CanMV固件直接开发
1、CanMV 固件烧写
正点原子提供了固件:
CanMV 固件位置:A 盘 -> 软件资料 -> 软件 -> CanMV 固件
2、下载软件

按操作烧写,CanMV固件
3、CanMV IDE安装
软件自行下载安装
1)使用指南
点击左下角的连接按钮。

选择对应的COM端。

点击OK连接设备后,连接成功会显示下面的图标。

4、运行测试
在进行本小节内容时,需要先完成IDE导入例程,也可以直接运行示例中的部分程序。
先连接设备,如果使用的是出厂固件的话, K210的屏幕会显示下图的内容。

打开CanMV IDE并连接到K210。
点击 文件 -> 文档文件夹 -> 例程 -> 01-Basic -> helloworld.py 或者点击 文件 -> 示例 -> Basics -> helloworld.py。

点击左下角绿色运行按钮。

就可以看到K210先显示红色的画面,然后屏幕会显示相机捕捉到的内容,如下图所示。

点击左下角的串行终端,还可以看到K210传回来的信息,可以看到下图显示一些参数,左下角还有个红色的 X 这个是取消运行程序, 取消之后K210就会执行默认的开机程序。

5、传输模型文件到TF卡
模型文件通常包含了神经网络架构的参数和权重信息,是神经网络训练过程的结果。对于K210而言,模型可以用于人脸检测、口罩识别、人脸识别、物体识别等各种机器学习任务。由于K210芯片资源有限,所以训练好的模型需要传输至TF卡储存,模型文件的后缀为.kmodel。
传输模型文件至TF卡的方式有两种,方式一是使用读卡器,将TF卡插入读卡器,读卡器插入电脑USB口,下载学习资料中的模型文件压缩包并解压,将KPU文件夹复制到H:盘的根目录。

方式一可以一次导入所有模型文件,省去每次导入模型文件的操作,以不同路径来区分加载模型文件。所有模型文件都放到KPU文件夹下,请不要修改KPU以及KPU目录以下的文件名称,否则默认例程程序会出现找不到模型文件的问题。
方式二是通过CanMV IDE来导入模型文件,无需借助读卡器,这里以导入landmark68.kmodel为例,将K210模块通过microUSB数据线连接到电脑USB口。然后点击CanMV IDE左下角连接按钮,连接成功会将图标变为已连接状态,同时灰色播放键变为绿色。

打开工具->保存文件到CanMV Cam。

点击打开,然后选择要传入的模型文件,传入内存卡。
注意:这里写入的文件优先写入到内存卡,保存时请删掉文件路径,带路径保存容易报错。

运行代码时,以具体保存的模型路径为准,如按照本教程方式一操作,则使用默认代码,无需修改。

如按照本教程的方式二操作,需要将程序中的多余路径删掉,即仅保留前缀/sd/。

三、MaixHub训练部署
参考:k210------Maixhub 云端模型训练_maixhub训练模型-CSDN博客
1、训练模型


根据规则,每类标签不少于50张,把图片上传后,一张张手动标注

标注好后,创建训练任务,参数基本不用变,选择对部署平台nncase,确定等待训练完成

训练结果

之后把模型下载下来部署测试

2、程序验证
下载的程序无法直接使用,需要修改点东西,比较头文件,还有自己添加了点功能
1、摄像头旋转180度
2、LCD显示180度
3、串口1输出识别信息
需要自己适配硬件
附程序源码
# generated by maixhub, tested on maixpy3 v0.4.8
# 修改:使用 maix.KPU 替代 KPU as kpu
import sensor, image, lcd, time
from maix import KPU
from machine import UART
import gc, sys
from fpioa_manager import fm
from board import board_info
input_size = (224, 224)
labels = ['Book', 'Umbrella', 'Cup', 'Watch', 'Clothes'] # 英文标签
# 中文标签(用于LCD显示)
labels_cn = ['书本', '雨伞', '水杯', '手表', '衣服']
# 颜色中文名
colors_cn = ['红色', '蓝色', '绿色', '黄色', '紫色', '橙色', '粉色', '青色']
anchors = [3.78, 4.61, 4.66, 4.16, 3.06, 4.91, 4.97, 4.69, 5.41, 4.31]
def lcd_show_except(e):
import uio
err_str = uio.StringIO()
sys.print_exception(e, err_str)
err_str = err_str.getvalue()
img = image.Image(size=input_size)
img.draw_string(0, 10, err_str, scale=1, color=(0xff,0x00,0x00))
lcd.display(img)
class Comm:
def __init__(self, uart):
self.uart = uart
self.last_msg = ""
self.last_objects = None
self.send_count = 0
self.state = "INIT" # INIT, DETECTING
self.has_object = False # 是否有物体
self.current_objects_set = set() # 当前检测到的物体集合
self.last_objects_set = set() # 上次检测到的物体集合
self.stable_start_time = 0 # 物体稳定开始时间
self.stable_objects_set = set() # 稳定的物体集合
self.stable_required_time = 3000 # 需要稳定3秒(毫秒)
self.last_output_time = 0 # 上次输出时间
self.output_interval = 1000 # 输出间隔1秒,避免频繁输出
def send_detect_result(self, objects, labels, colors_cn):
current_time = time.ticks_ms()
# 检查是否有物体
if objects and len(objects) > 0:
self.has_object = True
self.state = "DETECTING"
# 构建当前物体集合(用于比较)
current_set = set()
for obj in objects:
idx = obj.classid()
current_set.add((idx, colors_cn[idx % len(colors_cn)]))
self.current_objects_set = current_set
else:
self.has_object = False
self.state = "INIT"
self.current_objects_set = set()
self.stable_start_time = 0 # 重置稳定计时
self.stable_objects_set = set()
return
# 检查物体是否发生变化
objects_changed = False
if self.last_objects_set != self.current_objects_set:
objects_changed = True
print("Objects changed! Previous: {}, Current: {}".format(
self.last_objects_set, self.current_objects_set))
# 物体变化,重置稳定计时
self.stable_start_time = 0
self.stable_objects_set = set()
self.last_objects_set = self.current_objects_set.copy()
return
# 物体没有变化,检查是否已经稳定
if not objects_changed and self.has_object:
if self.stable_start_time == 0:
# 第一次检测到稳定物体,开始计时
self.stable_start_time = current_time
self.stable_objects_set = self.current_objects_set.copy()
print("Object stable, start counting...")
else:
# 检查是否已经稳定了3秒
stable_duration = time.ticks_diff(current_time, self.stable_start_time)
if stable_duration >= self.stable_required_time:
# 物体稳定3秒,可以输出
# 检查距离上次输出是否超过1秒(避免频繁输出)
if self.last_output_time == 0 or time.ticks_diff(current_time, self.last_output_time) >= 1000:
# 构建消息
msg = ""
color_en = ['Red', 'Blue', 'Green', 'Yellow', 'Pink', 'Orange', 'Purple', 'Cyan']
for obj in objects:
idx = obj.classid()
label_en = labels[idx]
color = color_en[idx % len(color_en)]
msg += "ITEM:{},COLOR:{}\n".format(label_en, color)
self.send_count += 1
print("[{}] Stable {}s, output: {}".format(self.send_count, stable_duration//1000, msg.strip()))
try:
self.uart.write(msg.encode())
print("UART sent: {}".format(msg.strip()))
except Exception as e:
print("UART send error:", e)
self.last_msg = msg
self.last_objects = objects.copy() if objects else None
self.last_output_time = current_time
# 重置稳定计时,重新开始计数(避免连续输出)
self.stable_start_time = current_time
self.stable_objects_set = self.current_objects_set.copy()
else:
# 距离上次输出不到1秒,跳过
pass
else:
# 还没到3秒,显示剩余时间
remaining = (self.stable_required_time - stable_duration) // 1000
if stable_duration % 1000 < 100: # 每秒打印一次
print("Stabilizing... {}/{}s".format(stable_duration//1000, self.stable_required_time//1000))
# 更新上次物体集合
self.last_objects_set = self.current_objects_set.copy()
def init_uart():
"""初始化UART1"""
try:
print("Configuring UART1 with board_info...")
fm.register(board_info.EX_UART1_TX, fm.fpioa.UART1_TX, force=True)
fm.register(board_info.EX_UART1_RX, fm.fpioa.UART1_RX, force=True)
uart = UART(UART.UART1, 115200, 8, 0, 0, timeout=1000, read_buf_len=1024)
print("UART1 configured successfully")
return uart
except Exception as e:
print("board_info configuration failed:", e)
try:
print("Configuring UART1 with fixed pins...")
fm.register(10, fm.fpioa.UART1_TX, force=True)
fm.register(11, fm.fpioa.UART1_RX, force=True)
uart = UART(UART.UART1, 115200, 8, 0, 0, timeout=1000, read_buf_len=1024)
print("UART1 configured successfully with fixed pins")
return uart
except Exception as e:
print("Fixed pins configuration failed:", e)
return None
class DummyUART:
def write(self, data):
print("Dummy UART write:", data)
def main(anchors, labels=None, model_addr="/sd/m.kmodel", sensor_window=input_size,
lcd_rotation=0, sensor_hmirror=False, sensor_vflip=False):
# 初始化传感器
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing(sensor_window)
sensor.set_hmirror(sensor_hmirror)
sensor.set_vflip(sensor_vflip)
sensor.run(1)
# 初始化LCD
lcd.init(type=1)
lcd.rotation(lcd_rotation)
lcd.clear(lcd.WHITE)
# 设置标签
if not labels:
try:
with open('labels.txt','r') as f:
exec(f.read())
except:
print("no labels.txt, use default labels")
labels = ['Book', 'Umbrella', 'Cup', 'Watch', 'Clothes']
if not labels:
print("no labels.txt")
img = image.Image(size=(320, 240))
img.draw_string(90, 110, "no labels.txt", color=(255, 0, 0), scale=2)
lcd.display(img)
return 1
# 显示启动画面
try:
img = image.Image("startup.jpg")
lcd.display(img)
except Exception:
img = image.Image(size=(320, 240))
img.draw_string(90, 110, "loading model...", color=(255, 255, 255), scale=2)
lcd.display(img)
# 初始化UART
uart = init_uart()
if uart is None:
print("WARNING: UART initialization failed, using dummy UART")
uart = DummyUART()
comm = Comm(uart)
print("System Start...")
print("Labels: {}".format(labels))
print("Stable required: {} seconds".format(comm.stable_required_time // 1000))
print("Output will only send after object stable for 3 seconds")
# 发送测试消息
try:
test_msg = "System Started!\n"
uart.write(test_msg.encode())
print("Test message sent")
except Exception as e:
print("Test message send failed:", e)
# 初始化task变量
task = None
try:
# 创建KPU对象
task = KPU()
# 加载模型
if isinstance(model_addr, int):
print("Load model from Flash: 0x{:X}".format(model_addr))
task.load_kmodel(model_addr)
else:
print("Load model from file: {}".format(model_addr))
task.load_kmodel(model_addr)
print("Model loaded successfully!")
# 使用默认输入尺寸
print("Using default input size: {}".format(sensor_window))
# 初始化YOLO2
layer_w = sensor_window[0] // 32
layer_h = sensor_window[1] // 32
print("layer_w={}, layer_h={}".format(layer_w, layer_h))
task.init_yolo2(anchors,
anchor_num=len(anchors)//2,
img_w=sensor_window[0],
img_h=sensor_window[1],
net_w=sensor_window[0],
net_h=sensor_window[1],
layer_w=layer_w,
layer_h=layer_h,
threshold=0.5,
nms_value=0.3,
classes=len(labels))
print("YOLO2 initialized successfully!")
# 颜色配置
display_colors = [
(255, 0, 0), # 亮红
(0, 255, 255), # 亮青
(0, 255, 0), # 亮绿
(0, 0, 255), # 亮蓝
(255, 255, 0), # 亮黄
(255, 0, 255), # 亮紫
(255, 128, 0), # 亮橙
(0, 255, 128) # 亮粉绿
]
label_colors = [
(255, 255, 255),
(255, 200, 200),
(200, 255, 200),
(200, 200, 255),
(255, 255, 200),
(255, 200, 255),
(255, 220, 200),
(200, 255, 220)
]
bg_color = (0, 0, 0)
frame_count = 0
# 主循环
while True:
img = sensor.snapshot()
t = time.ticks_ms()
frame_count += 1
# 运行检测
task.run_with_output(img)
objects = task.regionlayer_yolo2()
t = time.ticks_ms() - t
# 如果检测到物体,画框和标签
if objects:
for obj in objects:
x, y, w, h = obj[0], obj[1], obj[2], obj[3]
class_id = int(obj[4])
confidence = obj[5]
color = display_colors[class_id % len(display_colors)]
# 画粗边框
for i in range(3):
img.draw_rectangle(x+i, y+i, w-(2*i), h-(2*i), color=color, thickness=1)
# 显示标签
label_text = "{}:{:.2f}".format(labels[class_id], confidence)
text_x = x + 2
text_y = y + 2
text_color = label_colors[class_id % len(label_colors)]
img.draw_string(text_x, text_y, label_text,
scale=1.5, color=text_color,
background=bg_color)
class ObjectsWrapper:
def __init__(self, rect, value, classid):
self._rect = rect
self._value = value
self._classid = classid
def rect(self):
return self._rect
def value(self):
return self._value
def classid(self):
return self._classid
wrapped_objects = []
for obj in objects:
x, y, w, h = obj[0], obj[1], obj[2], obj[3]
class_id = int(obj[4])
confidence = obj[5]
wrapped_objects.append(ObjectsWrapper((x, y, w, h), confidence, class_id))
comm.send_detect_result(wrapped_objects, labels, colors_cn)
# 如果没有检测到物体,也要调用send_detect_result传递空列表
if not objects:
comm.send_detect_result([], labels, colors_cn)
# ========== LCD显示:只显示FPS ==========
fps_text = "{:.1f}FPS".format(1000/t if t > 0 else 0)
img.draw_string(2, 2, fps_text, scale=1.5, color=(0, 255, 255),
background=(0, 0, 0))
lcd.display(img)
gc.collect()
except Exception as e:
print("Error in main loop:", e)
import sys
sys.print_exception(e)
img = image.Image(size=(320, 240))
img.draw_string(10, 100, "Error: {}".format(str(e)[:30]), color=(255, 0, 0), scale=2)
lcd.display(img)
raise e
finally:
if task is not None:
try:
task.deinit()
print("Task deinitialized")
except Exception as e:
print("Task deinit error:", e)
if __name__ == "__main__":
try:
main(anchors=anchors, labels=labels,
model_addr="/sd/model-316242.kmodel",
lcd_rotation=2,
sensor_hmirror=True,
sensor_vflip=True)
except Exception as e:
sys.print_exception(e)
lcd_show_except(e)
finally:
gc.collect()
四、总结步骤
1:从MaixHub下载模型包
-
登录 MaixHub 网站,进入你已完成训练的项目。
-
在项目页面找到"部署 "或"Download "按钮,选择"手动部署"并下载。
-
下载的是一个压缩包,解压后你会得到几个关键文件:
-
xxx.kmodel: 这是核心的神经网络模型文件。 -
main.py: 这是MaixHub自动生成的示例运行代码。 -
(可能还有
labels.txt等文件)
-
2:准备SD卡
这是最稳妥、最推荐新手的方式。
-
将一张Micro SD卡格式化为
FAT32格式。 -
将解压得到的
xxx.kmodel和main.py这两个文件,直接复制到SD卡的根目录。 -
将SD卡插入K210开发板的卡槽。
注意 :SD卡根目录下只能 存放
main.py和.kmodel模型文件,不要有文件夹或其他无关文件,否则可能导致白屏或程序无法运行。
3:运行与调试
方法一:通过CanMV IDE运行(推荐先这样调试)
-
用USB线连接K210和电脑,打开CanMV IDE并连接开发板。
-
在IDE中打开SD卡上的
main.py文件。 -
点击"运行"按钮(绿色三角形),即可在IDE中看到运行结果和可能的报错信息。
方法二:上电自动运行
-
确保程序在IDE中调试通过。
-
断开USB连接或按复位键,程序就会自动运行了。
4、常见问题与解决方法
-
load error.../ 模型无法加载-
固件不匹配 :这是最常见的原因。MaixHub训练出的模型需要匹配特定版本的CanMV固件。请从MaixHub的模型下载页面查找其推荐的固件版本,并使用
kflash_gui等工具烧录该固件到你的K210。 -
SD卡问题 :检查SD卡是否格式化为
FAT32,以及文件是否在根目录。
-
-
LCD白屏 / 无反应
-
检查SD卡根目录下是否只有
main.py和.kmodel文件。 -
确保
main.py代码中,摄像头型号 (如sensor.reset()后的配置)和屏幕初始化 (lcd.init())与你硬件匹配。
-
-
运行后没有识别结果
-
检查模型参数 :检查
main.py中的labels(类别标签)、anchor(锚点,目标检测模型需要)等参数是否与你的模型匹配。 -
检查图像尺寸 :确认代码中对摄像头图像的处理(如
sensor.set_windowing或img.resize)与你模型训练时的输入尺寸一致。
-
5、总结
整个流程的核心就是"固件匹配 "和"文件放置正确"。只要确保这两点,从MaixHub下载的模型很快就能在你的K210上跑起来。
如有侵权,或需要完整代码,请及时联系博主。