2.50 OpenMV4 智能摄像头
资料
https://book.openmv.cc/python-background.html
在 OpenMV4 中,使用到颜色识别,就避免不了需要对应颜色的阈值,通过阈值去查找到指定的颜色。
颜色阈值的设置
这里以红色阈值设置为案例。
- 使用 OpenMV IDE 连接摄像头
打开 OpenMV IDE,连接上 OpenMV 摄像头。
连接成功示意图
- 开启 OpenMV4 采集颜色 案例是识别红色。我使用电脑上的画图软件,画出一个矩形。
然后使用摄像头采集图像数据到 IDE。
- 打开阈值编辑器
在弹出的窗口中,选择帧缓冲区。
- 获取颜色阈值 通过调整滑块,将我们需要识别的颜色调整为白色,其他颜色调整为黑色。完成之后下方的 LAB 阈值就是颜色阈值了
案例一:串口自定义格式通信
使用 OpenMV4 采集最大色块的中心位置,通过串口自定义格式 发送给梁山派。
接线
使用梁山派上的串口 1(TX=PA2 RX=PA3) 连接 OpenMV4 的串口 3(TX=P5 RX=P4)。
下方连接图中的 OpenMV4 是 Plus 型号,但是与普通版通用。
OpenMV4 | 梁山派 | 接线图 |
---|
OpenMV4 代码
内容讲解:查找图像中最大白色色块,通过串口 3(P4 P5)输出数据格式 "[%d,%d]" 给梁山派的串口。
import sensor, image, time #导入传感器类、图像类、时间类
from pyb import UART #导入串口类
uart = UART(3, 115200) #实例一个串口3,波特率为115200 (TX=P4 RX=P5)
sensor.reset() # 复位和初始化摄像头传感器
sensor.set_pixformat(sensor.RGB565) # 设置像素格式为RGB565(或GRAYSCALE)
sensor.set_framesize(sensor.QVGA) # 设置帧大小为QVGA (320x240)
sensor.skip_frames(time = 2000) # 等待设置生效
clock = time.clock() # 创建一个时钟对象来跟踪图像帧率
#白色 颜色阈值
white_threshold = (71, 100, -128, 127, -128, 127)
# 查找最大色块函数
def find_max(blobs):
max_size=0
# 把blobs每个元素代入变量blob
for blob in blobs:
# 如果当前色块的像素量 跟之前比 是最多的
if blob.pixels() > max_size:
# 记录这个最多像素量的色块
max_blob=blob
# 更新最大像素量
max_size = blob.pixels()
return max_blob
uart.write("Hello World!\r") # 通过串口3输出固定字符串 “Hello World!”
# 主循环
while(True):
clock.tick() # 更新FPS时钟
img = sensor.snapshot() # 创建一个对象,拍一张照片并返回图像数据给img对象
print(clock.fps()) # IDE调试输出帧率
# 注意:当连接到IDE时,OpenMV Cam的运行速度约为一半。
# 一旦断开连接,FPS应该会增加。
# 在img对象中根据 传入的颜色阈值(white_threshold) 查找色块
blobs = img.find_blobs( [ white_threshold ] )
# 如果色块数量不为0
if blobs:
# 找到最大色块
max_blob=find_max(blobs)
# 在img图像中 对查找到的色块 画一个矩形
# blobs.rect() 色块的矩形参数
# draw_rectangle 画一个矩形框
img.draw_rectangle(max_blob.rect())
# 在图像上绘制一个十字。 cx代表中心x点 cy表示中心y点
img.draw_cross(max_blob.cx(), max_blob.cy())
# 整理字符串 将最大色块的 中心xy点 按照%d格式,格式化字符串
output_str="[%d,%d]" % ( max_blob.cx(), max_blob.cy() )
# 在IDE中输出字符串
print( 'Maximum color block position : ' + output_str + '\r\n' )
# 通过串口3输出字符串
uart.write( 'Maximum color block position : ' + output_str + '\r\n' )
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
OpenMV4 代码文件
梁山派代码
bsp_openmv4.c
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年07月27日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "bsp_openmv4.h"
#include "bsp_usart.h"
#include "stdio.h"
#include "string.h"
uint8_t openmv4_recv_buff[USART_RECEIVE_LENGTH]; // 接收缓冲区
uint16_t openmv4_recv_length = 0; // 接收数据长度
uint8_t openmv4_recv_complete_flag = 0; // 接收数据完成标志位
/******************************************************************
* 函 数 名 称:OpenMV4_usart_gpio_config
* 函 数 说 明:OpenMV4串口配置GPIO
* 函 数 形 参:band_rate:波特率
* 函 数 返 回:无
* 作 者:LC
* 备 注:LC
******************************************************************/
void OpenMV4_usart_gpio_config(uint32_t band_rate)
{
/* 开启时钟 */
rcu_periph_clock_enable(BSP_OPENMV4_TX_RCU); // 开启串口时钟
rcu_periph_clock_enable(BSP_OPENMV4_RX_RCU); // 开启端口时钟
rcu_periph_clock_enable(BSP_OPENMV4_RCU); // 开启端口时钟
/* 配置GPIO复用功能 */
gpio_af_set(BSP_OPENMV4_TX_PORT, BSP_OPENMV4_AF, BSP_OPENMV4_TX_PIN);
gpio_af_set(BSP_OPENMV4_RX_PORT, BSP_OPENMV4_AF, BSP_OPENMV4_RX_PIN);
/* 配置GPIO的模式 */
/* 配置TX为复用模式 上拉模式 */
gpio_mode_set(BSP_OPENMV4_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_OPENMV4_TX_PIN);
/* 配置RX为复用模式 上拉模式 */
gpio_mode_set(BSP_OPENMV4_RX_PORT, GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_OPENMV4_RX_PIN);
/* 配置TX为推挽输出 50MHZ */
gpio_output_options_set(BSP_OPENMV4_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_OPENMV4_TX_PIN);
/* 配置RX为推挽输出 50MHZ */
gpio_output_options_set(BSP_OPENMV4_RX_PORT,GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, BSP_OPENMV4_RX_PIN);
/* 配置串口的参数 */
usart_deinit(BSP_OPENMV4_USART); // 复位串口
usart_baudrate_set(BSP_OPENMV4_USART,band_rate); // 设置波特率
usart_parity_config(BSP_OPENMV4_USART,USART_PM_NONE); // 没有校验位
usart_word_length_set(BSP_OPENMV4_USART,USART_WL_8BIT); // 8位数据位
usart_stop_bit_set(BSP_OPENMV4_USART,USART_STB_1BIT); // 1位停止位
/* 使能串口 */
usart_enable(BSP_OPENMV4_USART); // 使能串口
usart_transmit_config(BSP_OPENMV4_USART,USART_TRANSMIT_ENABLE); // 使能串口发送
usart_receive_config(BSP_OPENMV4_USART,USART_RECEIVE_ENABLE); // 使能串口接收
/* 中断配置 */
nvic_irq_enable(BSP_OPENMV4_USART_IRQ, 1, 1); // 配置中断优先级
usart_interrupt_enable(BSP_OPENMV4_USART,USART_INT_RBNE); // 读数据缓冲区非空中断和溢出错误中断
usart_interrupt_enable(BSP_OPENMV4_USART,USART_INT_IDLE); // 空闲检测中断
}
//
/******************************************************************
* 函 数 名 称:Openmv4DataAnalysis
* 函 数 说 明:解析OpenMV4 发送过来的自定义格式数据 [%d,%d] 解析
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void Openmv4DataAnalysis(void)
{
char temp[200] = {0};
char *buff = temp;
int i = 0;
//没有接收到数据 或者 数据没有接收完成 则不进行处理
if( openmv4_recv_complete_flag == 0 ) return;
//串口0输出接收到的数据(调试)
printf( "%s\r\n", openmv4_recv_buff );
//清除接收完成标志位,等待下一次接收
openmv4_recv_complete_flag = 0;
//找到格式的头 '['
if( strstr((const char*)openmv4_recv_buff, "[" ) != NULL )
{
buff = strstr((const char*)openmv4_recv_buff, "[" );
}
//找到结尾
while( buff[i] != ']' )
{
i++;
}
//从buff2里复制长度为i的字符串到buff1
strncpy(buff, buff, i);
//strncpy函数不会补零,需手动补上
buff[i+1] = '\0';
printf("buff = %s\r\n", buff );
//清除数据
memset(temp,0,200);
}
/************************************************
函数名称 : BSP_OPENMV4_USART_IRQHandler
功 能 : 专门OpenMV4串口接收中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : LC
*************************************************/
void BSP_OPENMV4_USART_IRQHandler(void)
{
if(usart_interrupt_flag_get(BSP_OPENMV4_USART,USART_INT_FLAG_RBNE) == SET) // 接收缓冲区不为空
{
// 把接收到的数据放到缓冲区中
openmv4_recv_buff[openmv4_recv_length++] = usart_data_receive(BSP_OPENMV4_USART);
}
if(usart_interrupt_flag_get(BSP_OPENMV4_USART,USART_INT_FLAG_IDLE) == SET) // 检测到帧中断
{
usart_data_receive(BSP_OPENMV4_USART); // 必须要读,读出来的值不能要
openmv4_recv_buff[openmv4_recv_length] = '\0'; // 数据接收完毕,数组结束标志
openmv4_recv_length = 0; // 接收长度清零
openmv4_recv_complete_flag = 1; // 接收完成
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
bsp_openmv4.h
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年07月27日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#ifndef _BSP_OPENMV4_H_
#define _BSP_OPENMV4_H_
#include "gd32f4xx.h"
#define BSP_OPENMV4_TX_RCU RCU_GPIOA // 串口TX的端口时钟
#define BSP_OPENMV4_RX_RCU RCU_GPIOA // 串口RX的端口时钟
#define BSP_OPENMV4_RCU RCU_USART1 // 串口1的时钟
#define BSP_OPENMV4_TX_PORT GPIOA // 串口TX的端口
#define BSP_OPENMV4_RX_PORT GPIOA // 串口RX的端口
#define BSP_OPENMV4_AF GPIO_AF_7 // 串口1的复用功能
#define BSP_OPENMV4_TX_PIN GPIO_PIN_2 // 串口TX的引脚
#define BSP_OPENMV4_RX_PIN GPIO_PIN_3 // 串口RX的引脚
#define BSP_OPENMV4_USART USART1 // 串口1
#define BSP_OPENMV4_USART_IRQ USART1_IRQn // 串口1中断
#define BSP_OPENMV4_USART_IRQHandler USART1_IRQHandler // 串口1中断服务函数
/* 串口缓冲区的数据长度 */
#define USART_RECEIVE_LENGTH 4096
void OpenMV4_usart_gpio_config(uint32_t band_rate);
void Openmv4DataAnalysis(void);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
案例验证
将梁山派和 OpenMV4 接好线。在梁山派代码的 main.c 中编写以下代码。
main.c
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年07月27日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "bsp_led.h"
#include "sys.h"
#include "bsp_usart.h"
#include "bsp_key.h"
#include "bsp_basic_timer.h"
#include "bsp_pwm.h"
#include "stdlib.h"
#include "string.h"
#include "bsp_openmv4.h"
/************************************************
函数名称 : main
功 能 : 主函数
参 数 : 无
返 回 值 : 无
作 者 : LC
*************************************************/
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化
led_gpio_config(); // led初始化
key_gpio_config(); // key初始化
usart_gpio_config(115200); // 串口0初始化(调试)
OpenMV4_usart_gpio_config(115200); // PENMV4串口初始化
printf("start\r\n");
while(1) {
//解析OPENMV4发送过来的数据
Openmv4DataAnalysis();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
验证说明:串口调试助手连接的是梁山派的串口 0,它输出的是解析 OPENMV4 后的数据。
梁山派工程代码文件
案例二:任意颜色线循迹
接线
使用梁山派上的串口 1(TX=PA2 RX=PA3) 连接 OpenMV4 的串口 3(TX=P5 RX=P4)。
下方连接图中的 OpenMV4 是 Plus 型号,但是与普通版通用。
OpenMV4 | 梁山派 | 接线图 |
---|
OpenMV4 代码
代码说明:实现任意颜色的寻线,需要知道线的颜色和位置。线的颜色识别通过色块查找的方式;而位置的判断,通过识别出线的颜色之后,在识别到的区域中心画一个十字型符号‘+’,并记录该色块中心位置命名为 x_location。
如何识别线的颜色?通过寻找色块函数 **find_blobs() **进行查找
将 x_location 与图像的 X 轴中心作比较,就可以得到线的位置 Line_position。其中图像的 X 轴中心命名为 centre
当 x_location > centre 时,Line_position 设置为正数,x_location 越大,则 Line_position 越大。
当 x_location < centre 时,Line_position 设置为负数,x_location 越小,则 Line_position 越小。
当 x_location = centre 时,Line_position 设置为 0。
如何实现循迹?
将 OpenMV4 放置在中间位置。
当 Line_position 为负数,说明识别到线在图像的左侧。我们需要控制小车左转。
当 Line_position 为正数,说明识别到线在图像的右侧。我们需要控制小车右转。
当 Line_position 为 0 , 说明识别到线在图像的中间。我们往前直行。
需要注意的是,线的颜色不可以和背景色相同,否则该案例失效。
import sensor, image, time, math, lcd#调用声明
from pyb import UART
import json
#修改这里的颜色阈值可以做到任意颜色识别循迹
#颜色阈值修改可以参考: ”颜色阈值选择工具“ https://book.openmv.cc/image/blob.html
GRAYSCALE_THRESHOLD =(0, 42, 22, -41, 27, -23)
# 识别颜色的矩形区域
Identification_region = [0, 50, 160, 20]
# 色块的X轴位置
x_location = 0
# 图像的X轴中心
centre = 0
# 线的位置
Line_position = 0
# 摄像头配置
sensor.reset() # 初始化摄像头传感器
sensor.set_pixformat(sensor.RGB565) # 设置相机的像素模式为16 bits/像素(即显示彩色)
sensor.set_framesize(sensor.QQVGA2) # 128x160 分辨率的相机传感器
sensor.skip_frames(30) # 跳过30帧,让相机图像在改变相机设置后稳定下来
lcd.init() # LCD初始化
uart = UART(3,115200) #实例一个串口3,波特率为115200 (TX=P4 RX=P5)
uart.init(115200,bits=8,parity=None,stop=1) #配置串口的波特率、数据位、校验位、停止位
while(True):
img = sensor.snapshot() # 拍一张照片并返回图像
#设置图像的X轴中心
centre = (img.width() / 2)
# 寻找 颜色阈值【GRAYSCALE_THRESHOLD】设置的颜色
blobs = img.find_blobs([GRAYSCALE_THRESHOLD], roi=Identification_region, merge=True)
# roi 是感兴趣区域的矩形元组(x,y,w,h)。如果指定[10,10,20,20],则在以(10,10)为起点的位置,以长宽都为20的大小查找色块
# 如果roi未指定,ROI即整个图像的图像矩形。 操作范围仅限于 roi 区域内的像素。
# merge 若为True,则合并所有没有被过滤掉的色块,这些色块的边界矩形互相交错重叠。
#识别到色块
if blobs:
most_pixels = 0 #最大色块的像素
largest_blob = 0 #最大色块
for i in range(len(blobs)):
#目标区域找到的颜色块(线段块)可能不止一个,找到最大的一个,作为本区域内的目标直线
if blobs[i].pixels() > most_pixels:
most_pixels = blobs[i].pixels() #记录最大色块的像素
largest_blob = i #记录最大色块
# 在查找到的最大色块处画矩形
img.draw_rectangle(blobs[largest_blob].rect())
#在起始点(0,0)到结束点(30,30)画一个矩形
#img.draw_rectangle((0,0,30, 30))
# 在最大色块的中心画一个十字
#.cx()表示中心点的x轴, .cy()表示中心点的y轴
img.draw_cross(blobs[largest_blob].cx(),blobs[largest_blob].cy())
#记录中心点X轴位置
x_location = blobs[largest_blob].cx()
#未识别到色块
else:
lcd.display(img.draw_string(2, 24, "!NONE!", color = (0, 0, 255), scale = 2, mono_space = False))
x_location = centre
#在图像的正中间画一个红色的十字,方便确定屏幕中间的位置
img.draw_cross(int(centre), int(img.height()/2), color = (255, 0, 0), size = 10 )
#在识别的区域内画蓝色矩形
img.draw_rectangle(Identification_region,color=(0, 0, 255))
#计算色块距离屏幕中间的距离, (img.width() / 2)后,色块中心点在屏幕左边则是负值 色块中心点在屏幕右边则是正值
Line_position="[%d]"%( x_location - centre )
# LCD显示图像,并在图像的(2,1)位置显示字符串output_str的内容,字符串颜色为蓝色,字体大小为2,字符间距不固定
lcd.display(img.draw_string(2, 1, "%s"%Line_position, color = (0, 0, 255), scale = 2, mono_space = False))
# 串口3发送数据
uart.write(Line_position+'\r\n')
# IDE调试输出,以下内容可以删除
#小于-10输出左转
#大于 10输出右转
#在-10到10范围内则输出前进
num = x_location - centre
if (num <= 10 and num >= -10):
print("前进")
elif num > 10:
print("右转")
elif num < 10:
print("左转")
#输出
print(Line_position)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
案例验证
案例代码文件
案例三:矩形识别与中心判断
OpenMV4 代码
import sensor, image, time, lcd
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA2)#像素128x160
sensor.skip_frames(time = 2000)
clock = time.clock()
# 查找最大矩形块函数
def find_max(rects):
rect_max=0
# 把blobs每个元素代入变量rect
for rect in rects:
# 如果当前矩形的宽 跟之前比 是最大的
if rect.w() > rect_max:
global max_rect
# 记录这个最多大的矩形
max_rect = rect
# 更新最大矩形宽度
rect_max = rect.w()
#返回这个最大矩形元素
return max_rect
while(True):
#记录帧率
clock.tick()
#拍照
img = sensor.snapshot()
#查找到最大矩形块,查找范围x=0,y=20,w=128,h=120
rects = find_max(img.find_rects(roi=(0,20,128,120)))
#将查找到的最大矩形画一个绿色的矩形框
img.draw_rectangle(rects.x(),rects.y(),rects.w(),rects.h(),color=(0,255,0))
#计算矩形中心
#中心X定位:矩形像素宽度 / 2 + x轴起点位置
#中心Y定位:矩形像素高度 / 2 + y轴起点位置
x_central = (rects.w() / 2) + rects.x()
y_central = (rects.h() / 2) + rects.y()
#显示识别到的最大矩形中心的X,Y位置
img.draw_string(2,1,"x=%d y=%d"%(x_central,y_central))
#在查找到的矩形中心画一个蓝色的十字
img.draw_cross(int(x_central), int(y_central), color = (0, 0, 255), size = 5 )
#在查找区域画一个红色方框
#区域越小,帧数越高
img.draw_rectangle(0,20,128,120,color=(255,0,0))
#IDE输出帧率
print(clock.fps())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
案例验证
代码文件
案例四:任意角度矩形识别与边角定位
OpenMV4 代码
import sensor, image, time
# 初始化摄像头
sensor.reset()
sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式为RGB565格式
sensor.set_framesize(sensor.QQVGA) # 设置图像大小为160*120
sensor.set_auto_whitebal(True) # 设置自动白平衡
sensor.set_brightness(3000) # 设置亮度为3000
sensor.skip_frames(time = 20) # 跳过帧
clock = time.clock()
while(True):
clock.tick()
img = sensor.snapshot()
#去除鱼眼畸变
img.lens_corr(1.8)
# -----矩形框部分-----
# 在图像中寻找矩形
for r in img.find_rects(threshold = 10000):
# 判断矩形边长是否符合要求
if r.w() > 20 and r.h() > 20:
# 在屏幕上框出矩形
img.draw_rectangle(r.rect(), color = (255, 0, 0), scale = 4)
# 获取矩形角点位置
corner = r.corners()
# 在屏幕上圈出矩形角点
img.draw_circle(corner[0][0], corner[0][1], 5, color = (255, 0, 0), thickness = 2, fill = False)
img.draw_circle(corner[1][0], corner[1][1], 5, color = (0, 255, 0), thickness = 2, fill = False)
img.draw_circle(corner[2][0], corner[2][1], 5, color = (0, 0, 255), thickness = 2, fill = False)
img.draw_circle(corner[3][0], corner[3][1], 5, color = (255, 255, 0), thickness = 2, fill = False)
# 打印四个角点坐标, 角点1的数组是corner[0], 坐标就是(corner[0][0],corner[0][1])
# 角点检测输出的角点排序每次不一定一致,矩形左上的角点有可能是corner0,1,2,3其中一个
left_down_dot ="LD=[%d,%d]"%(corner[0][0],corner[0][1]) #红色点
right_down_dot ="RD=[%d,%d]"%(corner[1][0],corner[1][1]) #绿色点
right_up_dot ="RU=[%d,%d]"%(corner[2][0],corner[2][1]) #蓝色点
left_up_dot ="LU=[%d,%d]"%(corner[3][0],corner[3][1]) #黄色点
print(left_up_dot + "\n" + left_down_dot + "\n" + right_up_dot + "\n" + right_down_dot)
# 打印帧率
print(clock.fps())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
案例验证
代码文件
案例五 激光识别与定位
将识别到的绿色激光以黑色方框圈出,并在 IDE 中输出激光 XY 轴位置。
OpenMV4 代码
import sensor, image, time
# 初始化摄像头
sensor.reset()
sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式为RGB565格式
sensor.set_framesize(sensor.QQVGA) # 设置图像大小为160*120
sensor.set_auto_whitebal(True) # 设置自动白平衡
sensor.set_brightness(3000) # 设置亮度为3000
sensor.skip_frames(time = 20) # 跳过帧
clock = time.clock()
while(True):
clock.tick()
img = sensor.snapshot()
#去除鱼眼畸变
img.lens_corr(1.8)
# -----跟踪激光部分-----
# 设置绿色激光颜色阈值
G_td = [(98, 81, -48, 41, 14, 72)]
# 根据阈值找到色块
for b in img.find_blobs(G_td,pixels_threshold=2, area_threshold=15, merge=True,invert = 0):
# 在识别到的激光处画一个黑色矩形
img.draw_rectangle(b.rect(), color = (0, 0, 0), scale = 1, thickness = 2)
# 打印激光色块的中心位置
# 使用b.x()获取色块矩形左上角X坐标
# 使用b.y()获取色块矩形左上角Y坐标
# 使用b.w()获取色块矩形宽度
# 使用b.h()获取色块矩形高度
# 矩形中心坐标为(x + w/2,y + h/2)
print("[%d,%d]"%( (b.x() + b.w()/2), (b.y() + b.h()/2 ) ))
break
# 打印帧率
print(clock.fps())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
案例验证
如识别不出绿色,请按照颜色阈值的设置章节,配置你的绿色阈值。
识别代码文件
案例六:舵机二维云台控制与矩形坐标定位
说明:本案例以追踪一个坐标点为例。当 OpenMV4 识别到矩形坐标后,将矩形坐标的一个点与 OpenMV4 的图像中心作比较,获得距离图像中心的差值。控制思想与案例二:任意颜色线循迹一致。将坐标点与图像中心的差值发送给梁山派,由梁山派根据差值控制云台。
- 当坐标点在图像中心点的右边时,差值为负,梁山派控制云台右转;
- 当坐标点在图像中心点的左边时,差值为正,梁山派控制云台左转;
以上为 X 轴控制的思想,Y 轴同理。
接线
使用梁山派上的串口 1(TX=PA2 RX=PA3) 连接 OpenMV4 的串口 3(TX=P5 RX=P4)。 舵机云台的 X 轴左右转动的舵机信号线接入 PA5;Y 轴上下转动的舵机信号线接入 PA1;
下方连接图中的 OpenMV4 是 Plus 型号,但是与普通版通用。
OpenMV4 | 梁山派 | 二维云台 X 轴舵机 | 二维云台 Y 轴舵机 | 接线图 |
---|
OpenMV4 代码
说明:OpenMV4 的代码以案例四为例进行修改。本案例只识别一个角点,其余角点的定位,请查看案例四:任意角度矩形识别与边角定位。
import sensor, image, time
from pyb import UART #导入串口类
uart = UART(3, 256000) #实例一个串口3,波特率为256000 (TX=P4 RX=P5)
# 初始化摄像头
sensor.reset()
sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式为RGB565格式
sensor.set_framesize(sensor.QQVGA) # 设置图像大小为160*120
sensor.set_auto_whitebal(True) # 设置自动白平衡
sensor.set_brightness(3000) # 设置亮度为3000
sensor.skip_frames(time = 20) # 跳过帧
clock = time.clock()
while(True):
clock.tick()
img = sensor.snapshot()
#去除鱼眼畸变
img.lens_corr(1.8)
# -----矩形框部分-----
# 在图像中寻找矩形
for r in img.find_rects(threshold = 10000):
# 判断矩形边长是否符合要求
if r.w() > 20 and r.h() > 20:
# 在屏幕上框出矩形
img.draw_rectangle(r.rect(), color = (255, 0, 0), scale = 4)
# 获取矩形4个角点的位置
corner = r.corners()
# 在图像中心画一个十字
img.draw_cross(int(img.width()/2),int(img.height()/2),color=(255,0,0))
# 计算X轴与图像中心的差值 ( 差值 = 图像中心坐标 - 识别到的坐标 )
x = (img.width() / 2) - corner[3][0]
# 计算Y轴与图像中心的差值 ( 差值 = 图像中心坐标 - 识别到的坐标 )
y = (img.height() / 2) - corner[3][1]
# 将XY轴的差值 整理为字符串
left_up_dot = "LU[%d,%d]"%(x,y)
# 通过串口发送给梁山派
uart.write(left_up_dot)
# 在屏幕上以黄色圈出矩形左上的角点
img.draw_circle(corner[3][0], corner[3][1], 5, color = (255, 255, 0), thickness = 2, fill = False)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
梁山派 OpenMV4 解析代码
说明:初始化两个 PWM 用于控制云台舵机。需要注意的是如何解析 OpenMV4 发送过来的 XY 轴的差值。OpenMV4 的差值以 [X,Y] 的格式发送给梁山派。
X 轴的差值解析:
例如:buff=[-12,32],进入 X 轴解析函数后,先将 buff 的数据保存到 temp 里。
这是为了保护 buff 里的数据不被修改,因为我们后面还要通过 buff 解析 Y 轴数据
c1
char temp_str[200]={0}; char *temp = temp_str; int i = 0; sprintf(temp, "%s", buff);
接着通过让temp的下标不断自增,找到","的下标位置i,将temp下标位置i之前的内容,复制到temp。我们带入参数模拟。
原来的参数如下。
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|------|---|---|---|---|----|---|---|---|
| temp | [ | - | 1 | 2 | , | 3 | 2 | ] |
通过以下代码找到了“,”的下标位4。
```c
i=0;
while( temp[i] != ',' )
{
i++;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
再通过以下代码,将“,”之前的内容重新保存到 temp。
//从temp2里复制长度为i的字符串到temp1
strncpy(temp, temp, i);
2
Temp 的内容修改内容如下:
下标 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
temp | [ | - | 1 | 2 | , |
通过将 i 下标内容修改为字符串结束符‘\0’,Temp 的内容又修改如下:
下标 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
temp | [ | - | 1 | 2 | \0 |
通过 atoi 函数将 temp 的字符串数据转为整形数据,但是 atoi 函数要求只转换与数字相关的字符串,我们的 temp 里面还有一个“[”,因此传入的字符串数据从下标 1 的地址进行转换。
//保存数据
openmv_data_x = atoi(&temp[1]);
2
最终 X 轴变量 openmv_data_x 为 -12.
以下是完整的 X 轴的字符串解析函数。
//解析X轴数据
void get_openmv4_data_x(char* buff)
{
char temp_str[200]={0};
char *temp = temp_str;
int i = 0;
sprintf(temp, "%s", buff);
//找到结尾
i=0;
while( temp[i] != ',' )
{
i++;
}
//从temp2里复制长度为i的字符串到temp1
strncpy(temp, temp, i);
//strncpy函数不会补零,需手动补上
temp[i] = '\0';
printf("temp = %s\r\n",temp);
//保存数据
openmv_data_x = atoi(&temp[1]);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Y 轴与 X 轴同理,不过需要进行一些修改,这里不再进行阐述。完整的 Y 轴的字符串解析函数如下:
//解析Y轴数据
void get_openmv4_data_y(char* buff)
{
char temp_str[200]={0};
char *temp = temp_str;
int i = 0;
sprintf(temp, "%s", buff);
//过滤‘,’号之前的数据
i=0;
while( temp[i] != ',' )
{
i++;
}
//过滤X轴数据,因为我们现在在解析Y轴
temp = temp+i;
//找到结尾
i=0;
while( temp[i] != ']' )
{
i++;
}
//从temp2里复制长度为i的字符串到temp1
strncpy(temp, temp+1, i);
//strncpy函数不会补零,需手动补上
temp[i] = '\0';
//保存数据
openmv_data_y = atoi(temp);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
以下为梁山派接收 OpenMV4 的数据并进行解析的 bsp_openmv4.c 与.h。
bsp_openmv4.c
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "bsp_openmv4.h"
#include "bsp_usart.h"
#include "stdio.h"
#include "string.h"
uint8_t openmv4_recv_buff[USART_RECEIVE_LENGTH]; // 接收缓冲区
uint16_t openmv4_recv_length = 0; // 接收数据长度
uint8_t openmv4_recv_complete_flag = 0; // 接收数据完成标志位
int openmv_data_x = 0;
int openmv_data_y = 0;
/******************************************************************
* 函 数 名 称:OpenMV4_usart_gpio_config
* 函 数 说 明:OpenMV4串口配置GPIO
* 函 数 形 参:band_rate:波特率
* 函 数 返 回:无
* 作 者:LC
* 备 注:LC
******************************************************************/
void OpenMV4_usart_gpio_config(uint32_t band_rate)
{
/* 开启时钟 */
rcu_periph_clock_enable(BSP_OPENMV4_TX_RCU); // 开启串口时钟
rcu_periph_clock_enable(BSP_OPENMV4_RX_RCU); // 开启端口时钟
rcu_periph_clock_enable(BSP_OPENMV4_RCU); // 开启端口时钟
/* 配置GPIO复用功能 */
gpio_af_set(BSP_OPENMV4_TX_PORT, BSP_OPENMV4_AF, BSP_OPENMV4_TX_PIN);
gpio_af_set(BSP_OPENMV4_RX_PORT, BSP_OPENMV4_AF, BSP_OPENMV4_RX_PIN);
/* 配置GPIO的模式 */
/* 配置TX为复用模式 上拉模式 */
gpio_mode_set(BSP_OPENMV4_TX_PORT,GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_OPENMV4_TX_PIN);
/* 配置RX为复用模式 上拉模式 */
gpio_mode_set(BSP_OPENMV4_RX_PORT, GPIO_MODE_AF,GPIO_PUPD_PULLUP,BSP_OPENMV4_RX_PIN);
/* 配置TX为推挽输出 50MHZ */
gpio_output_options_set(BSP_OPENMV4_TX_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_OPENMV4_TX_PIN);
/* 配置RX为推挽输出 50MHZ */
gpio_output_options_set(BSP_OPENMV4_RX_PORT,GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, BSP_OPENMV4_RX_PIN);
/* 配置串口的参数 */
usart_deinit(BSP_OPENMV4_USART); // 复位串口
usart_baudrate_set(BSP_OPENMV4_USART,band_rate); // 设置波特率
usart_parity_config(BSP_OPENMV4_USART,USART_PM_NONE); // 没有校验位
usart_word_length_set(BSP_OPENMV4_USART,USART_WL_8BIT); // 8位数据位
usart_stop_bit_set(BSP_OPENMV4_USART,USART_STB_1BIT); // 1位停止位
/* 使能串口 */
usart_enable(BSP_OPENMV4_USART); // 使能串口
usart_transmit_config(BSP_OPENMV4_USART,USART_TRANSMIT_ENABLE); // 使能串口发送
usart_receive_config(BSP_OPENMV4_USART,USART_RECEIVE_ENABLE); // 使能串口接收
/* 中断配置 */
nvic_irq_enable(BSP_OPENMV4_USART_IRQ, 1, 1); // 配置中断优先级
usart_interrupt_enable(BSP_OPENMV4_USART,USART_INT_RBNE); // 读数据缓冲区非空中断和溢出错误中断
usart_interrupt_enable(BSP_OPENMV4_USART,USART_INT_IDLE); // 空闲检测中断
}
//解析X轴数据
void get_openmv4_data_x(char* buff)
{
char temp_str[200]={0};
char *temp = temp_str;
int i = 0;
sprintf(temp, "%s", buff);
//找到结尾
i=0;
while( temp[i] != ',' )
{
i++;
}
//从temp2里复制长度为i的字符串到temp1
strncpy(temp, temp, i);
//strncpy函数不会补零,需手动补上
temp[i] = '\0';
printf("temp = %s\r\n",temp);
//保存数据
openmv_data_x = atoi(&temp[1]);
}
//解析Y轴数据
void get_openmv4_data_y(char* buff)
{
char temp_str[200]={0};
char *temp = temp_str;
int i = 0;
sprintf(temp, "%s", buff);
//过滤‘,’号之前的数据
i=0;
while( temp[i] != ',' )
{
i++;
}
temp = temp+i;
//找到结尾
i=0;
while( temp[i] != ']' )
{
i++;
}
//从temp2里复制长度为i的字符串到temp1
strncpy(temp, temp+1, i);
//strncpy函数不会补零,需手动补上
temp[i] = '\0';
//保存数据
openmv_data_y = atoi(temp);
}
/******************************************************************
* 函 数 名 称:Openmv4DataAnalysis
* 函 数 说 明:解析OpenMV4 发送过来的自定义格式数据 以[%d,%d] 解析
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void Openmv4DataAnalysis(void)
{
char temp[200] = {0};
char *buff = temp;
int i = 0;
//没有接收到数据 或者 数据没有接收完成 则不进行处理
if( openmv4_recv_complete_flag == 0 ) return;
//清除接收完成标志位,等待下一次接收
openmv4_recv_complete_flag = 0;
//找到格式的头 '['
if( strstr((const char*)openmv4_recv_buff, "[" ) != NULL )
{
buff = strstr((const char*)openmv4_recv_buff, "[" );
}
//找到结尾
while( buff[i] != ']' )
{
i++;
}
//从buff2里复制长度为i的字符串到buff1
strncpy(buff, buff, i);
//strncpy函数不会补零,需手动补上
buff[i+1] = '\0';
//保存X轴数据
get_openmv4_data_x(buff);
//保存Y轴数据
get_openmv4_data_y(buff);
//清除数据
memset(temp,0,200);
}
//获取x位置
int get_data_x(void)
{
return openmv_data_x;
}
//获取y位置
int get_data_y(void)
{
return openmv_data_y;
}
/************************************************
函数名称 : BSP_OPENMV4_USART_IRQHandler
功 能 : 专门OpenMV4串口接收中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : LC
*************************************************/
void BSP_OPENMV4_USART_IRQHandler(void)
{
if(usart_interrupt_flag_get(BSP_OPENMV4_USART,USART_INT_FLAG_RBNE) == SET) // 接收缓冲区不为空
{
// 把接收到的数据放到缓冲区中
openmv4_recv_buff[openmv4_recv_length++] = usart_data_receive(BSP_OPENMV4_USART);
}
if(usart_interrupt_flag_get(BSP_OPENMV4_USART,USART_INT_FLAG_IDLE) == SET) // 检测到帧中断
{
usart_data_receive(BSP_OPENMV4_USART); // 必须要读,读出来的值不能要
openmv4_recv_buff[openmv4_recv_length] = '\0'; // 数据接收完毕,数组结束标志
openmv4_recv_length = 0; // 接收长度清零
openmv4_recv_complete_flag = 1; // 接收完成
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
bsp_openmv4.h
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#ifndef _BSP_OPENMV4_H_
#define _BSP_OPENMV4_H_
#include "gd32f4xx.h"
#define BSP_OPENMV4_TX_RCU RCU_GPIOA // 串口TX的端口时钟
#define BSP_OPENMV4_RX_RCU RCU_GPIOA // 串口RX的端口时钟
#define BSP_OPENMV4_RCU RCU_USART1 // 串口1的时钟
#define BSP_OPENMV4_TX_PORT GPIOA // 串口TX的端口
#define BSP_OPENMV4_RX_PORT GPIOA // 串口RX的端口
#define BSP_OPENMV4_AF GPIO_AF_7 // 串口1的复用功能
#define BSP_OPENMV4_TX_PIN GPIO_PIN_2 // 串口TX的引脚
#define BSP_OPENMV4_RX_PIN GPIO_PIN_3 // 串口RX的引脚
#define BSP_OPENMV4_USART USART1 // 串口1
#define BSP_OPENMV4_USART_IRQ USART1_IRQn // 串口1中断
#define BSP_OPENMV4_USART_IRQHandler USART1_IRQHandler // 串口1中断服务函数
/* 串口缓冲区的数据长度 */
#define USART_RECEIVE_LENGTH 4096
void OpenMV4_usart_gpio_config(uint32_t band_rate);
void Openmv4DataAnalysis(void);
int get_data_x(void);
int get_data_y(void);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
梁山派云台控制代码
云台控制代码,在工程中命名为 bsp_sg90.c 与 bsp_sg90.h。需要注意但是定时器的周期尽量设置大一些,这样在控制舵机旋转时的精度也会高一些。
bsp_sg90.c
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LC
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "bsp_sg90.h"
#include "systick.h"
unsigned int Servo_Angle = 0;//舵机角度
/******************************************************************
* 函 数 名 称:SG90_Init
* 函 数 说 明:PWM配置
* 函 数 形 参: pre定时器时钟预分频值 per周期
* 函 数 返 回:无
* 作 者:LC
* 备 注:PWM频率=200 000 000 /( (pre+1) * (per+1) )
******************************************************************/
void SG90_Init(void)
{
timer_parameter_struct timere_initpara={0}; // 定义定时器结构体
timer_oc_parameter_struct timer_ocintpara={0}; //定时器比较输出结构体
//定时器时钟
rcu_periph_clock_enable(RCU_SIG_TIMER); // 开启定时器时钟
//引脚时钟
rcu_periph_clock_enable(RCU_SIG);
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); // 配置定时器时钟
/* 配置AIN1*/
gpio_mode_set(PORT_SIG,GPIO_MODE_AF,GPIO_PUPD_NONE,GPIO_SIG);
gpio_output_options_set(PORT_SIG,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_SIG);
gpio_af_set(PORT_SIG,AF_SIG,GPIO_SIG);
gpio_mode_set(PORT_SIG,GPIO_MODE_AF,GPIO_PUPD_NONE,GPIO_PIN_1);
gpio_output_options_set(PORT_SIG,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_1);
gpio_af_set(PORT_SIG,AF_SIG,GPIO_PIN_1);
// 复位定时器
timer_deinit(BSP_SIG_TIMER);
/* 配置定时器参数 */
// 频率f =系统时钟 / ( (prescaler+1) * (period+1) )
// 频率f = 200,000,000/ (20000 * 200) = 50hz
// 周期T = 1/f = 50 = 0.02S = 20ms
timere_initpara.prescaler = 200-1; // 时钟预分频值
timere_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边缘对齐
timere_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timere_initpara.period = 20000-1; // 周期
timere_initpara.clockdivision = TIMER_CKDIV_DIV1; // 分频因子
timere_initpara.repetitioncounter = 0; // 重复计数器 0-255
timer_init(BSP_SIG_TIMER,&timere_initpara); // 初始化定时器
/* 配置输出结构体 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; // 有效电平的极性
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; // 配置比较输出模式状态 也就是使能PWM输出到端口
timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH; // 通道互补输出极性为高电平
timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE; // 通道互补输出状态失能
timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW; // 信道输出的空闲状态为低
timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;// 信道互补输出的空闲状态为低
/* 配置定时器输出功能 */
timer_channel_output_config(BSP_SIG_TIMER,BSP_SIG_CHANNEL,&timer_ocintpara);
timer_channel_output_config(BSP_SIG_TIMER,TIMER_CH_1,&timer_ocintpara);
/* 配置占空比 范围 0 ~ (per-1) */
// t = 0.5ms——————-舵机会转动 0 °
// t = 1.0ms——————-舵机会转动 45°
// t = 1.5ms——————-舵机会转动 90°
// t = 2.0ms——————-舵机会转动 135°
// t = 2.5ms——————-舵机会转动180°
// 1.我该如何计算括号里的数,从而实现我想要的功能?
// 此处以90度为例。
// 答:PWM周期为20ms,所以占空比就应该为1.5ms/20ms = 7.5% = 0.075,
// 所以 TIMx 捕获比较 1 寄存器值就为period-period*7.5% = 20000-20000*0.075=18500
timer_channel_output_pulse_value_config(BSP_SIG_TIMER,BSP_SIG_CHANNEL,18500);
timer_channel_output_pulse_value_config(BSP_SIG_TIMER,TIMER_CH_1,18500);
// 配置定时器通道输出比较模式
timer_channel_output_mode_config(BSP_SIG_TIMER,BSP_SIG_CHANNEL,TIMER_OC_MODE_PWM1);
timer_channel_output_mode_config(BSP_SIG_TIMER,TIMER_CH_1,TIMER_OC_MODE_PWM1);
// 配置定时器通道输出影子寄存器
timer_channel_output_shadow_config(BSP_SIG_TIMER,BSP_SIG_CHANNEL,TIMER_OC_SHADOW_DISABLE);
timer_channel_output_shadow_config(BSP_SIG_TIMER,TIMER_CH_1,TIMER_OC_SHADOW_DISABLE);
/* 只有高级定时器使用 */
timer_auto_reload_shadow_enable(BSP_SIG_TIMER);
timer_primary_output_config(BSP_SIG_TIMER, ENABLE);
/* 使能定时器 */
timer_enable(BSP_SIG_TIMER);
}
/******************************************************************
* 函 数 名 称:Set_Servo_Angle
* 函 数 说 明:设置角度
* 函 数 形 参:angle=要设置的角度,范围0-180
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void Set_Servo_Angle(unsigned int angle)
{
unsigned int ServoAngle = 0;
//设置的角度超过180度
if( angle > 180 )
{
return ;
}
//保存设置的角度
Servo_Angle = angle;
//换算角度
//高电平时间=0.5ms+(角度/180°)×2ms
ServoAngle = (int)(200*(1-((0.5+angle/90.0)/20.0)));
timer_channel_output_pulse_value_config(BSP_SIG_TIMER,BSP_SIG_CHANNEL,ServoAngle);
}
//设置SG90为参数范围内(0~180°)任意度数
/*
* countPeriod:pWM一个周期计数值,这里为20000
* CycleTime:一个周期多少ms,这里为20
*
//角度转化为数值
//compare_value=0.5*countPeriod/CycleTime+angle*countPeriod/CycleTime/90;
//compare_value=0.5*200/20+angle*200/20/90;
*/
/******************************************************************
* 函 数 名 称:读取当前角度
* 函 数 说 明:Get_Servo_Angle
* 函 数 形 参:无
* 函 数 返 回:当前角度
* 作 者:LC
* 备 注:使用前必须确保之前使用过
void Set_Servo_Angle(unsigned int angle)
函数设置过角度
******************************************************************/
unsigned int Get_Servo_Angle(void)
{
return Servo_Angle;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
bsp_sg90.h
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LC
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#ifndef _BSP_SG90_H
#define _BSP_SG90_H
#include "gd32f4xx.h"
#include "systick.h"
//目前只宏定义了X轴舵机的
#define RCU_SIG RCU_GPIOA
#define PORT_SIG GPIOA
#define GPIO_SIG GPIO_PIN_5
#define AF_SIG GPIO_AF_1
#define RCU_SIG_TIMER RCU_TIMER1
#define BSP_SIG_TIMER TIMER1 // 定时器
#define BSP_SIG_CHANNEL TIMER_CH_0 // 定时器通道
void SG90_Init(void);
void Set_Servo_Angle(unsigned int angle);
unsigned int Get_Servo_Angle(void);
void TIM3_PWM_Init_AllChannel(void);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
案例验证
在 main.c 文件中,编写如下:
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LC
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "bsp_usart.h"
#include "bsp_sg90.h"
#include "bsp_openmv4.h"
//简易PID控制调试,仅提供PID的基础控制
//PID的调试方法请参考:https://lceda001.feishu.cn/wiki/EzsUwfxqQiUyDQkaZLjcDyo5nWb?from=from_copylink
float Kp = 3.5,Ki=0.009,Kd =0.01;
float error = 0,P = 0,I = 0,D = 0,PID_value = 0;
float decide = 0;
float previous_error = 0,previous_I = 0;
static int initial_motor_angle = 18500;
//PID计算
void calc_pid(void)
{
P=error;
I=I+error;
D=error-previous_error;
PID_value=(Kp*P)+(Ki*I)+(Kd*D);
previous_error=error;
}
//舵机限幅,不能超过180度,不能小于0度
void motor_cortrol(void)
{
int motor_angle = initial_motor_angle-PID_value;
if(motor_angle <= 17500)
{
motor_angle = 17500;
}
if(motor_angle >= 19500)
{
motor_angle = 19500;
}
timer_channel_output_pulse_value_config(BSP_SIG_TIMER,BSP_SIG_CHANNEL,motor_angle);
}
//X轴舵机控制测试
//传入参数: channel=PWM输出通道 x=x轴差值
void x_test(uint16_t channel, int x)
{
static uint16_t i = 18500;//初始角度90度
if( x >= 0 )
{
timer_channel_output_pulse_value_config(BSP_SIG_TIMER,channel,i--);
}
else
{
timer_channel_output_pulse_value_config(BSP_SIG_TIMER,channel,i++);
}
}
//Y轴舵机控制测试
//传入参数: channel=PWM输出通道 y=y轴差值
void y_test(uint16_t channel, int y)
{
static uint16_t i = 18500;//初始角度90度
if( y >= 0 )
{
timer_channel_output_pulse_value_config(BSP_SIG_TIMER,channel,i++);
}
else
{
timer_channel_output_pulse_value_config(BSP_SIG_TIMER,channel,i--);
}
}
/******************************************************************
* 函 数 名 称:
* 函 数 说 明:
* 函 数 形 参:
* 函 数 返 回:
* 作 者:LC
* 备 注:x轴电机接入PA5 y轴电机接入PA1
******************************************************************/
int main(void)
{
int i = 18500;//初始90度
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化
usart_gpio_config(256000U);// 串口0初始化
OpenMV4_usart_gpio_config(256000);//
SG90_Init();
while(1)
{
//OpenMV4数据解析
Openmv4DataAnalysis();
//X轴控制测试
x_test(BSP_SIG_CHANNEL,get_data_x());
//Y轴控制测试
y_test(TIMER_CH_1, get_data_y());
// printf("x=%d y=%d\r\n",get_data_x(), get_data_y());
// error=get_data_x();
// calc_pid();
// motor_cortrol();
delay_1ms(5);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
以下视频中摄像头的动作均由云台控制。