此案例需要搭配八路智能巡线模块(红外巡线模块)使用
USART:通用同步异步收发器。可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。这次实验用到的就是UART2。

在使用串口进行通信时,要求通信双方必须是“同频道”。“同频道” 指 相同的通信协议。 串口(USART)约定:通信时数据必须以“帧”的形式传递串口的一帧数据包括:起始位 + 数据位 + 校验位 + 停止位 其中: 1)起始位:固定是1个周期的低电平信号 2)数据位:可由通信双方自行约定是 5 ~ 9 bits 3)校验位:串口采用的是奇偶校验,可由通信双方自行约定 4)停止位:可选的 0.5 ~ 2 个周期的高电平 同时,为了同步通信双发的收发速度,还需要约定每秒钟传输的数据帧的数量,称为 波特率,典型波特率有9600、115200、57600 ……
(注:下图接线仅供位置参考,我们出厂有配备K230双头PH2.0 4Pin全黑线材,有防呆设计,不必担心接线问题)
| 八路智能巡线模块(红外巡线模块) | MSPM0G3507 |
|---|---|
| 5V | 5V |
| GND | GND |
| RX | TX0 |
| TX | RX0 |
| K230视觉模块 | MSPM0G3507 |
|---|---|
| 5V | 5V |
| GND | GND |
| RX | TX2 |
| TX | RX2 |



.png)
K230协议
当K230识别出路标之后,会通过串口发送一个数据帧,格式是“$09”+“标志内容ID,”+“#” 举例:“$091,#”
当单片机接收到上述数据帧时,会对每一位都进行判断。如果内容是“$09",继续接收,当收到“#”时,说明一包数据已经完成。解析K230传输的信息,再对标志内容ID跟特定指令比对,如果是对应的指令,小车就会做出对应的操作。
控制流程图
bsp_k230.c
xvoid data_deal_k230(uint8_t data) { // 处理K230设备发送的数据,按特定格式解析单字节数据 // Process data sent by K230 device, parse single-byte data according to specific format switch(data_step) { case 0: // 重置所有状态,确保起始干净 // Reset all states to ensure a clean start len = 0; memset(K230_data, 0, K230_DATA_MAX_LEN); if(data == '$') { // 检测到起始符'$',进入下一状态 // Detected start character '$', move to next state data_step++; } break; case 1: if(data == '0') { // 匹配到第二个特征字符'0',进入下一状态 // Matched second feature character '0', move to next state data_step++; } else { // 不匹配则重置状态 // Reset state if not matched data_step = 0; } break; case 2: if(data == '9') { // 匹配到第三个特征字符'9',进入下一状态 // Matched third feature character '9', move to next state data_step++; } else { // 不匹配则重置状态 // Reset state if not matched data_step = 0; } break; case 3: // 接收x的值(数字) // Receiving x value (numeric digits) if(data == ',') { // 结束接收x,进入校验结束符阶段 // End of x reception, enter end character verification phase if(len == 0 || len > K230_DATA_MAX_LEN) { // x为空或长度超限,无效数据 // x is empty or exceeds max length, invalid data data_step = 0; } else { // 数据长度有效,进入等待结束符状态 // Valid data length, enter end character waiting state data_step++; // 进入状态4等待'#' // Move to state 4 waiting for '#' } } else if(data >= '0' && data <= '9') { // 仅接收数字 // Only receive numeric digits if(len < K230_DATA_MAX_LEN) { // 限制最大长度,防止溢出 // Limit max length to prevent overflow K230_data[len++] = data; } else { // 超过最大长度,视为无效数据 // Exceeds max length, treat as invalid data data_step = 0; len = 0; memset(K230_data, 0, K230_DATA_MAX_LEN); } } else { // 收到非数字、非','的字符,无效数据 // Received non-numeric and non-',' character, invalid data data_step = 0; len = 0; memset(K230_data, 0, K230_DATA_MAX_LEN); } break; case 4: // 校验结束符'#' // Verify end character '#' if(data == '#') { // 完整匹配格式 // Complete format match deal_msg(len); // 处理接收到的有效消息 // Process received valid message } // 无论是否收到'#',都重置状态(避免残留) // Reset state regardless of whether '#' is received (to avoid residue) data_step = 0; len = 0; memset(K230_data, 0, K230_DATA_MAX_LEN); break; }}
void deal_msg(uint8_t length) { // 处理解析后的消息,转换数字并校验范围 // Process parsed message, convert digits and verify range data_id = 0; if(length == 1) { // 单字节数字转换 // Single-byte digit conversion data_id = K230_data[0] - '0'; } else if(length == 2) { // 双字节数字转换(十位+个位) // Two-byte digit conversion (tens place + units place) uint8_t data_H = K230_data[0] - '0'; uint8_t data_L = K230_data[1] - '0'; data_id = data_H * 10 + data_L; } // 校验data_id是否在1-11范围内 // Verify if data_id is within 1-11 range if(data_id >= 1 && data_id <= MAX_ID) { stop_flag = 0; // 有效数据,正常处理 // Valid data, normal processing } else { stop_flag = 1; // 无效数据,设置停止标志 // Invalid data, set stop flag }}main.py
x
# 如果检测到了目标(det_boxes 不为空列表)if det_boxes: # 遍历检测到的每一个目标 for det_boxe in det_boxes: # det_boxe 是一个列表或元组,里面包含了 (标签ID, 置信度, x1, y1, x2, y2) 等信息 x1, y1, x2, y2 = det_boxe[2], det_boxe[3], det_boxe[4], det_boxe[5] # 根据屏幕分辨率和AI模型输出分辨率的比例,计算出在屏幕上显示的目标框宽度和高度 # DISPLAY_WIDTH, DISPLAY_HEIGHT: 屏幕的实际分辨率 w = float(x2 - x1) * DISPLAY_WIDTH // OUT_RGB888P_WIDTH h = float(y2 - y1) * DISPLAY_HEIGHT // OUT_RGB888P_HEIGH
# 在OSD(On-Screen Display,屏幕显示)图层上绘制矩形框 # color_four[det_boxe[0]][1:] 表示根据目标的标签ID选择对应的颜色 osd_img.draw_rectangle( int(x1 * DISPLAY_WIDTH // OUT_RGB888P_WIDTH), int(y1 * DISPLAY_HEIGHT // OUT_RGB888P_HEIGH), int(w), int(h), color=color_four[det_boxe[0]][1:] # 根据标签ID获取颜色 ) # 根据标签ID获取对应的标签名称 label = labels[det_boxe[0]] # 将置信度(score)保留两位小数并转换为字符串 score_str = str(round(det_boxe[1], 2)) score = round(det_boxe[1], 2)
# 如果置信度大于0.7 if score > 0.7: # 在目标框上方绘制标签名称和置信度 osd_img.draw_string_advanced( int(x1 * DISPLAY_WIDTH // OUT_RGB888P_WIDTH), int(y1 * DISPLAY_HEIGHT // OUT_RGB888P_HEIGH) - 50, 32, label + " " + score_str, color=color_four[det_boxe[0]][1:] )
# 构造要通过UART发送的数据字符串 # 例如,如果检测到标签ID为0的目标,发送的字符串就是 "$090,#" send_data = "$" + "09" + str(det_boxe[0]) + ',' + "#" # 通过UART串口发送数据 uart.send(send_data) # 在控制台打印发送的数据,用于调试 print(send_data)
Display.show_image(osd_img, 0, 0, Display.LAYER_OSD3) # 手动进行垃圾回收,释放内存 gc.collect()
主要函数
data_deal_K230
| 函数原型 | void data_deal_K230(uint8_t data) |
|---|---|
| 功能描述 | 处理 K230 发来的数据,按固定格式(以 '$' 开头、"09" 为标识、数字为数据、',' 分隔、'#' 结尾)解析,有效数据通过 deal_msg 处理,无效数据则重置状态 |
| 输入参数 | data:从 K230 接收的单字节数据 |
| 返回值 | 无 |
deal_msg
| 函数原型 | void deal_msg(uint8_t length) |
|---|---|
| 功能描述 | 将解析到的数字数据(长度 1 或 2)转换为 ID 值,校验 ID 是否在 1-11 范围内,有效则置 stop_flag 为 0,否则置 1 |
| 输入参数 | length:解析到的数字数据长度 |
| 返回值 | 无 |
set_dataid
| 函数原型 | void set_dataid(K230_TYPE id) |
|---|---|
| 功能描述 | 设置 data_id 的值(用于清除等操作) |
| 输入参数 | id:要设置的 ID 值 |
| 返回值 | 无 |
get_dataid
| 函数原型 | uint8_t get_dataid(void) |
|---|---|
| 功能描述 | 获取当前的 data_id 值 |
| 输入参数 | 无 |
| 返回值 | uint8_t 类型,当前的 data_id |
Reverse_parking_no2
| 函数原型 | void Reverse_parking_no2(void) |
|---|---|
| 功能描述 | 控制小车倒入 2 号库:后退指定时间后停止,左转指定时间后停止,再后退指定时间 |
| 输入参数 | 无 |
| 返回值 | 无 |
Reverse_parking_no1
| 函数原型 | void Reverse_parking_no1(void) |
|---|---|
| 功能描述 | 控制小车倒入 1 号库:后退指定时间后停止,右转指定时间后停止,再后退指定时间后停车 |
| 输入参数 | 无 |
| 返回值 | 无 |
Road_sign_right
| 函数原型 | void Road_sign_right(void) |
|---|---|
| 功能描述 | 道路标识右转控制:根据传感器状态调整左右电机速度,实现右转或修正方向 |
| 输入参数 | 无 |
| 返回值 | 无 |
Road_sign_left
| 函数原型 | void Road_sign_left(void) |
|---|---|
| 功能描述 | 道路标识左转控制(左转优先巡线):根据传感器状态调整左右电机速度,实现左转或修正方向 |
| 输入参数 | 无 |
| 返回值 | 无 |
Road_sign_speedlimit
| 函数原型 | void Road_sign_speedlimit(void) |
|---|---|
| 功能描述 | 低速巡线控制:以限制速度为基础,根据传感器状态微调左右电机速度,实现转向或直行 |
| 输入参数 | 无 |
| 返回值 | 无 |
LineWalking_PWM
| 函数原型 | void LineWalking_PWM(void) |
|---|---|
| 功能描述 | 基于 PWM 的循线控制:获取红外传感器状态,根据不同检测情况调整左右电机 PWM 值,实现直行或转向 |
| 输入参数 | 无 |
| 返回值 | 无 |
LineCheck
| 函数原型 | int LineCheck(void) |
|---|---|
| 功能描述 | 检测小车当前位于黑线还是白线上:所有传感器未检测到黑线返回 WHITE,否则返回 BLACK |
| 输入参数 | 无 |
| 返回值 | int 类型,WHITE(白线)或 BLACK(黑线) |
此实验开始前首先需要将程序源码->K230文件中mp_deployment_source文件夹复制到CanMV/data目录下,路径如下,然后将main.py烧录到K230。

本教程地图所放置的路标如下图所示,下载程序成功后,把小车放在地图上,打开开关后,等待一会当K230的屏幕亮了之后,小车开始巡线。因电机机械误差、地面材质、电池电量等诸多因素,循迹,倒库等场景需根据实际场景进行调试
识别到限速标志,小车就会做不超过限速标志的速度巡线运动。
识别到解除限速标志,小车就会恢复到正常巡线运动
识别到左转/右转,小车会做优先左转/右转的巡线运动
识别到鸣笛,小车会停下鸣笛两声。然后恢复到正常巡线运动
识别红绿灯,红灯会停止,绿灯会继续往前走
识别1、2号库,小车识别1号库,会进入1号库;识别2号库,小车会倒进2号库
