Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
OPENMV
RA8-M85-vision-board
【24嵌入式设计大赛】基于RT-Thread VisionBoard开发版的视觉循迹小车
发布于 2024-09-08 21:49:10 浏览:528
订阅该版
[tocm] # 基于RT\_Thread Vision Board的视觉循迹小车 #### 简介 关于循迹小车的项目可谓是已经很俗套了,那为什么这里我还要再使用RT\_Thread Vision Board来实现一下这个视觉循迹小车的项目呢?最主要的原因就是性价比了,之前在公司做项目,一直用的都是某瞳科技的产品,价格就离谱,有的时候赶上电赛,就更离谱了。期间也想过使用ESP32或者其他单片机自己DIY一个OpenMV玩玩,但是一方面是硬件限制,一方面是性能限制,迟迟没有进展。看到RT\_Thread推出的这个开发板,就突然想用他来试试,看能否平替之前项目中的OpenMV板子。测试结果,简直完美,性能可谓是有过之而无不及。不仅价格合适,而且自带WIFI可以直接实现远程图传,也不用再额外购买其他模块了。在调循迹功能时,直接在电脑上远程查看摄像头的实时信息,别提有多爽了。 #### 硬件环境 为了方便初学者上手,在设计时所有设备均才用模块化设计。 ##### 1. Vision Board Vision Board搭载全球首颗 480 MHz Arm Cortex-M85芯片,拥有Helium和TrustZone技术的加持。SDK包里集成了OpenMV机器视觉例程,配合MicroPython 解释器,使其可以流畅地开发机器视觉应用 ![postcard.png](https://oss-club.rt-thread.org/uploads/20240908/3af22f5a5c13f8d5185ebb4efc2674e7.png.webp) #### 2. STM32F103VCT6最小系统板 longlongago 之前从咸鱼收的个二手板子,这次正好给用上了,大家可以随便找个板子代替,这里做的时候只用到了一个串口和Vision Board通信,然后用到了一个定时器的4个输出通道,只要满足在这两个需求即可。当然也可以直接使用Vision Board来控制。这里主要是想后边在用这个板子搞点别的控制玩玩。 ##### 3. L298N电机驱动模块 L298N是SGS公司生产的一款通用的电机驱动模块。其内部包含4路逻辑驱动电路,有两个H桥的高电压大电流全桥驱动器,接收TTL逻辑电平信号,一个模块可同时驱动两个直流电机工作,具有反馈检测和过热自断功能。利用L298N驱动电机时,主控芯片只需通过I/O口输出控制电平即可实现对电机转向的控制,编程简单,稳定性好 ![L298N.jpeg](https://oss-club.rt-thread.org/uploads/20240908/5fce1d3a47121a1d94e0036b029bb816.jpeg.webp) ##### 4. 履带车体(带电机)直接淘宝 车体的话是直接在淘宝买的一个履带式底盘,也是这次项目中花费最大的一笔了,希望可以报销。[小车底盘链接](https://detail.tmall.com/item.htm?abbucket=7&id=646813642257&ns=1&pisk=f69sLQGnOP4_RWiKcrnFOweCMDBXhEMz5osvqneaDOBOkBtklNomsORCleLCWRoissQXSOCD_Z7NlttDPDurUY-MjtXKz4kzNkSvSTjOXsU49vINs2oU8Y-MjZq_k0RKUSwzKwPTMZLAp6IcXtEODRnC9isPHRCYBWUd-wQAHsCO99IVcRIAXGnQ9gjzkGQODMCdcgaOHtLAvJh1hJsx1gKsvnAPxYkHrhQQFHvCXfbSEwexLpI6AOtOi-evdG16hqNZIv5wM3fkQHHbKO-W99I6kY3Op3dB419sPR7DSK12L6ZY6H9f5_pOOu2RIHjvksOnP8S1QB1WCCm0JhLP5QBM0ugwA_OCZMCLc5Bwa3JcwdH_rw5lcUsW5PsPy877ucFb9Y2fAaoIASVmOdOce1s5571OxGgrADagi1IhAaoIA7FN6Mj6fDiQZr1..&priceTId=214782f017252874616175444e2d07&skuId=4831190102917&spm=a21n57.1.item.22.cab8523c5mZzmx&utparam=%7B%22aplus_abtest%22:%2277685ea85ddfd9577edbdafc97c637b8%22%7D&xxc=taobaoSearch) ![底盘.png](https://oss-club.rt-thread.org/uploads/20240908/d2f9948deaf72e0f40a0f61f106957ae.png.webp) ##### 5. 锂电池 锂电池淘宝链接[淘宝链接](https://item.taobao.com/item.htm?abbucket=7&id=632184698346&ns=1&pisk=fHZELeT17MIEOKFmmvnrQhXEPSmKxDffYuGSE82odXcnO8Do45V5pXwl97rrs8UCp41pUpEag3tC9wek40ilGssfcJBK20fX33hWUBHxUpjWxXcGOdmPfssfcR7np0wGGTHTDAkjh3mo-4misfHrEvc3ZVxiefxnEHDksOltE0Do-4mijYM-EeD3ZFciUYRkqYYHIfDxh0cuZHcGduqebXuh3HnhFcRXeqG0KX2QElAqJeei_uZ8jvyZrJ4Jq3qZL2rPaesdf2auHqEQIg-tA-zijvPlm6mgnPyj8k52AbFL44Zs7LY3QulEok0wHnmLBYVozPC2cvoQ7qrUJTdgKSGUoDeW36wZuPuxIVvy-Dabhu30xMRx1qHzgxPeTgPkwAV7IuB3EUunBAlfQOzb8-gN8DwLLUL-SOHZGtW2yU3nBAlfCoLJyVVoQj6FH&priceTId=214782f017252877128195142e2d07&skuId=4504138772505&spm=a21n57.1.item.56.cab8523c5mZzmx&utparam=%7B%22aplus_abtest%22:%22da640e1ed51bee29dcf91cf1218e826e%22%7D) ![电池.png](https://oss-club.rt-thread.org/uploads/20240908/6154f003ff0b394c8f005e30e244fd95.png.webp) ##### 6. 杜邦线若干 #### 硬件链接说明 ![实物图.jpg](https://oss-club.rt-thread.org/uploads/20240908/f0be9ea64e420eee510d6a3d0ce757c8.jpg.webp) 小车整体上采用12V锂电池供电,通过L298N模块将12V转换为5V然后给VisionBoard和STM32最小系统板供电。然后电机的控制线接到L298N的输出信号上,L298N的输入信号由单片机的定时器4的通道1234上,对应单片机的PD12、PD13、PD14、PD15四个引脚。Vision Board的P801引脚和P802引脚连接到单片机的串口2的PA2和PA3引脚,用于实时接收识别到的角度信息。 #### 软件部分 ##### 1. Vision Board部分 Vision Board采用官方提供的SDK中的openMV固件,采用keil编译烧录,官方已经做的很完善了,所以在这里我就没有再去做什么改动直接使用历程烧录然后执行,重点是烧录完后OpenMVIDE里的代码。首先我们要实现设备的联网功能,我们需要自己创建一个热点,这里我用的是我电脑的热点。然后在代码里配置好自己的热点信息。然后运行代码,第一次运行时会报error,那是因为我们此时设置的IP地址并不是我们模块的地址,这个地址会在openMVIDE下方的串口终端里打印出来,我们需要根据这个ID来修改我们的代码中的参数。 ![IP地址设置.png](https://oss-club.rt-thread.org/uploads/20240908/1f86af3f5e56feb44ccdde5dd97b39c9.png) 修改好参数之后,接下来我们就可以通过网页来试试查看摄像头的内容内容,需要注意的是电脑需要和我们的开发板链接的是同一个网。然后输入IP和端口号(http://192.168.137.201:8080/)。显示的角度信息会通过串口实时传递给我们的STM32开发板,用于调整小车姿态。 ![网页端效果.png](https://oss-club.rt-thread.org/uploads/20240908/e39dd8b9244f6d5dd1afd2f7249c48ae.png) ``` import network import socket import sensor, image, time, math, lcd from machine import UART from struct import pack uart = UART(2, 115200) # 追踪黑线。使用 [(128, 255)] 追踪白线. GRAYSCALE_THRESHOLD = [(0, 64)] #采样图像为QVGA 320*240,列表把roi把图像分成3个矩形,越靠近的摄像头视野(通常为图像下方)的矩形权重越大。 ROIS = [ # [ROI, weight] (80, 200, 150, 40, 0.7), # 不同的结构,参数需要调整。 (40, 100, 150, 40, 0.3), (40, 0, 150, 40, 0.1) ] # 计算以上3个矩形的权值【weight】的和,和不需要一定为1. weight_sum = 0 for r in ROIS: weight_sum += r[4] # r[4] 为矩形权重值. # 摄像头配置 sensor.reset() # 初始化. sensor.set_pixformat(sensor.GRAYSCALE) # 使用灰度图像. sensor.set_framesize(sensor.QVGA) # 使用QVGA 320*240或QQVGA 160*120. sensor.set_vflip(1) #摄像头后置模式 sensor.skip_frames(time = 2000) # 等待摄像头稳定. SSID = "cccc" # 需要根据自己的wifi热点名称修改 KEY = "12345678" # 填写自己的wifi热点密码 HOST = "192.168.137.201" # IP地址会在openMVIDE的串口终端中打印提示 PORT = 8080 # 自定义接口 wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(SSID, KEY) while not wlan.isconnected(): print('Trying to connect to "{:s}"...'.format(SSID)) time.sleep_ms(1000) # We should have a valid IP now via DHCP print("WiFi Connected ", wlan.ifconfig()) # Create server socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # Bind and listen s.bind([HOST, PORT]) s.listen(5) s.setblocking(True) def start_streaming(s): print("Waiting for connections..") client, addr = s.accept() # set client socket timeout to 5s client.settimeout(5.0) print("Connected to " + addr[0] + ":" + str(addr[1])) # Read request from client data = client.recv(1024) # Should parse client request here # Send multipart header client.sendall( "HTTP/1.1 200 OK\r\n" "Server: OpenMV\r\n" "Content-Type: multipart/x-mixed-replace;boundary=openmv\r\n" "Cache-Control: no-cache\r\n" "Pragma: no-cache\r\n\r\n" ) # FPS clock clock = time.clock() # Start streaming images # NOTE: Disable IDE preview to increase streaming FPS. while True: clock.tick() # Track elapsed milliseconds between snapshots(). frame = sensor.snapshot() centroid_sum = 0 for r in ROIS: blobs = frame.find_blobs(GRAYSCALE_THRESHOLD, roi=r[0:4], merge=True) # r[0:4] 是上面定义的roi元组. if blobs: # Find the blob with the most pixels. largest_blob = max(blobs, key=lambda b: b.pixels()) # Draw a rect around the blob. frame.draw_rectangle(largest_blob.rect()) frame.draw_cross(largest_blob.cx(), largest_blob.cy()) centroid_sum += largest_blob.cx() * r[4] # r[4] 是每个roi的权重值. center_pos = (centroid_sum / weight_sum) # 确定直线的中心. # 将直线中心位置转换成角度,便于机器人处理. deflection_angle = 0 # 使用反正切函数计算直线中心偏离角度。可以自行画图理解 #权重X坐标落在图像左半部分记作正偏,落在右边部分记为负偏,所以计算结果加负号。 deflection_angle = -math.atan((center_pos-160)/120) #采用图像为QVGA 320*240时候使用 # 将偏离值转换成偏离角度. deflection_angle = math.degrees(deflection_angle) # 计算偏离角度后可以控制机器人进行调整. print("Turn Angle: %f\r\n" % deflection_angle) Uart_Angle = pack('f',deflection_angle) uart.write(Uart_Angle+"XX") time.sleep_ms(50) frame.draw_string(0,0,"Angle:%d" % deflection_angle,scale=3) cframe = frame.to_jpeg(quality=35, copy=True) header = ( "\r\n--openmv\r\n" "Content-Type: image/jpeg\r\n" "Content-Length:" + str(cframe.size()) + "\r\n\r\n" ) client.sendall(header) client.sendall(cframe) while True: try: start_streaming(s) except OSError as e: print("socket error: ", e) #sys.print_exception(e) ``` ##### 2. STM32控制部分 在STM32上,开发时采用RT_Thread Studio来进行软件功能的设计。创建工程步骤参考官方文档即可,已经很详细了,这里不再过多赘述。这里主要来说一下小车底盘控制部分和跟openMV串口通信部分的设计。 ###### 串口通信代码 代码的整体思路就是使用串口2的接收回调函数来给出信号量,然后在一个线程里去对信号量进行获取,获取成功就认为是收到数据了,然后读取串口数据,接收超时,就认为是openMV已经给我们发了一个完整的一帧数据。 ``` #define SAMPLE_UART_NAME "uart2" /* 用于接收消息的信号量 */ static struct rt_semaphore rx_sem; static rt_device_t serial; struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 初始化配置参数 */ /* 接收数据回调函数 */ static rt_err_t uart_input(rt_device_t dev, rt_size_t size) { /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */ rt_sem_release(&rx_sem); return RT_EOK; } static void serial_thread_entry(void *parameter) { rt_err_t ret; uint8_t recv_buff[6] = {0}; uint8_t recv_cnt=0; float angle = 0; while (1) { ret = rt_sem_take(&rx_sem, 50); if (ret == RT_EOK) { rt_device_read(serial, -1, &recv_buff[recv_cnt], 1); rt_kprintf("RECV[%d]:%x\r\n",recv_cnt,recv_buff[recv_cnt]); recv_cnt++; }else { rt_kprintf("Recv_Over\r\n"); if((recv_cnt == 6) && recv_buff[4]==0x58 &&recv_buff[5]==0x58 ){ recv_cnt =0; rt_memcpy(&angle,&recv_buff,4); rt_kprintf("接收到角度信息:%f\r\n",angle); Car_Control(angle); angle = 0; } } } } static int OpenMV_Uart_Init(void) { rt_err_t ret; /* 查找系统中的串口设备 */ serial = rt_device_find(SAMPLE_UART_NAME); if (!serial) { rt_kprintf("find %s failed!\n", SAMPLE_UART_NAME); return RT_ERROR; } /* step2:修改串口配置参数 */ config.baud_rate = BAUD_RATE_115200; //修改波特率为 9600 config.data_bits = DATA_BITS_8; //数据位 8 config.stop_bits = STOP_BITS_1; //停止位 1 config.bufsz = 128; //修改缓冲区 buff size 为 128 config.parity = PARITY_NONE; //无奇偶校验位 /* step3:控制串口设备。通过控制接口传入命令控制字,与控制参数 */ rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config); /* 初始化信号量 */ rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO); /* 以中断接收及轮询发送模式打开串口设备 */ rt_device_open(serial, RT_DEVICE_FLAG_INT_RX); /* 设置接收回调函数 */ rt_device_set_rx_indicate(serial, uart_input); /* 创建 serial 线程 */ rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 100); /* 创建成功则启动线程 */ if (thread != RT_NULL) { rt_thread_startup(thread); rt_kprintf("Uart2 Init OK\n", SAMPLE_UART_NAME); } else { ret = RT_ERROR; } return ret; } INIT_APP_EXPORT(OpenMV_Uart_Init); ``` ###### 电机控制代码 对于电机的控制主要就是使用RT-Thread中的PWM设备驱动了,这里基本流程和官方说的一致,但是有一个问题就是,前期在按照官方文档配置时,PWM配置好了但是并没有博兴输出,参考了社区里的配置教程发现,生成的工程里需要我们手动添加上PWM的初始化。在电机控制上,这里就简单的根据vision Board回传过来的角度信息实时控制小车两侧的速度实现巡线,具体参数需要根据实际情况调整。这里由于场地限制,只测试了一下小车是否会根据角度不同二发生速度和方向的转变。具体可以看演示视频。 ![PWM修改.png](https://oss-club.rt-thread.org/uploads/20240908/809d74b4e5490ad23921be02ba801cba.png) ``` #define PWM4_DEV_NAME "pwm4" /* PWM设备名称 */ #define PWM4_DEV_CHANNEL1 1 /* PWM通道 */ #define PWM4_DEV_CHANNEL2 2 /* PWM通道 */ #define PWM4_DEV_CHANNEL3 3 /* PWM通道 */ #define PWM4_DEV_CHANNEL4 4 /* PWM通道 */ struct rt_device_pwm *pwm4_dev; /* PWM设备句柄 */ static int PWM_Motor_INIT(void) { /* 查找设备 */ pwm4_dev = (struct rt_device_pwm *)rt_device_find(PWM4_DEV_NAME); if (pwm4_dev == RT_NULL) { rt_kprintf("pwm sample run failed! can't find %s device!\n", PWM4_DEV_NAME); return RT_ERROR; } /* 设置PWM周期和脉冲宽度默认值 */ rt_pwm_set(pwm4_dev, PWM4_DEV_CHANNEL1, 1000000, 0); rt_pwm_set(pwm4_dev, PWM4_DEV_CHANNEL2, 1000000, 0); rt_pwm_set(pwm4_dev, PWM4_DEV_CHANNEL3, 1000000, 0); rt_pwm_set(pwm4_dev, PWM4_DEV_CHANNEL4, 1000000, 0); /* 使能设备 */ rt_pwm_enable(pwm4_dev, PWM4_DEV_CHANNEL1); rt_pwm_enable(pwm4_dev, PWM4_DEV_CHANNEL2); rt_pwm_enable(pwm4_dev, PWM4_DEV_CHANNEL3); rt_pwm_enable(pwm4_dev, PWM4_DEV_CHANNEL4); return 0; } /* 自动初始化PWM输出接口 */ INIT_APP_EXPORT(PWM_Motor_INIT); //右轮控制,从后往前看 void MotorRight_Control(uint8_t dir,uint32_t speed) { if (dir == 1) { rt_pwm_set(pwm4_dev, PWM4_DEV_CHANNEL1, 1000000, speed); rt_pwm_set(pwm4_dev, PWM4_DEV_CHANNEL2, 1000000, 0); } else { rt_pwm_set(pwm4_dev, PWM4_DEV_CHANNEL1, 1000000, 0); rt_pwm_set(pwm4_dev, PWM4_DEV_CHANNEL2, 1000000, speed); } } void MotorLeft_Control(uint8_t dir,uint32_t speed) { if (dir == 1) { rt_pwm_set(pwm4_dev, PWM4_DEV_CHANNEL3, 1000000, speed); rt_pwm_set(pwm4_dev, PWM4_DEV_CHANNEL4, 1000000, 0); } else { rt_pwm_set(pwm4_dev, PWM4_DEV_CHANNEL3, 1000000, 0); rt_pwm_set(pwm4_dev, PWM4_DEV_CHANNEL4, 1000000, speed); } } /* * angle openMV回传实时数据信息,大于0 右转,小于0 左转 * */ void Car_Control(float angle) { if (angle > 0) { if(angle <3) { MotorRight_Control(1,400000); MotorLeft_Control(1,400000); }else if (angle<10) { MotorRight_Control(1,400000); MotorLeft_Control(1,600000); }else if(angle<30){ MotorRight_Control(1,300000); MotorLeft_Control(1,700000); }else { MotorRight_Control(1,0); MotorLeft_Control(1,0); } }else { if(angle > -3) { MotorRight_Control(1,400000); MotorLeft_Control(1,400000); }else if (angle > -10) { MotorRight_Control(1,600000); MotorLeft_Control(1,400000); }else if(angle > -30){ MotorRight_Control(1,700000); MotorLeft_Control(1,300000); }else { MotorRight_Control(1,0); MotorLeft_Control(1,0); } } } ``` #### 项目功能演示 [哔站视频链接](https://www.bilibili.com/video/BV1gZpqeuE3F/) #### 写在最后 本次设计只是为了验证RT-Thread 推出的Vision Board开发版是否可以替代某瞳科技的openMV开发版,验证结果很完美,比之前买的400+的功能还要完善,还可以拓展屏幕,具备WIFI功能,后续也考虑用作低成本图传。NICE!!!.也希望国产操作系统RT_Thread 越做越好,毕竟国人写文档还是复合咱们自己的习惯,方便上手。同时也感谢RTT电子设计大赛群中各位大佬的技术答疑,十分的专业。
3
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
cymcc
这家伙很懒,什么也没写!
文章
1
回答
0
被采纳
0
关注TA
发私信
相关文章
1
OPENMV软件包怎么使用
2
OPENMV STM32H7 编译失败
3
openMV+micropy与RT1064的一个尝试
4
Vision Board连接不上OpenMV IDE
5
Vision Board使用openmv无法下载脚本
6
在visonboard开发中尝试在openmv中加载个人训练的YOLOv5模型,报错,超出内存
7
Vision Board 的openmv如何部署socket 模块
8
Vison Board 如何在openmv 、main.py脚本中实现和其它线程通信?
9
Vision Board 兩個專案編譯問題
10
Vision-Board例程编译后不通过
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
ulog
C++_cpp
at_device
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
a1012112796
13
个答案
2
次被采纳
张世争
9
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
本月文章贡献
程序员阿伟
8
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
5
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部