前言
最近想要做一个项目是涉及用国产MCU--CW32配合K210控制舵机实现跟踪物体的目的,我想要实现一个功能就是识别到目标并且把目标的坐标信息通过串口传输给单片机,单片机控制舵机进行控制,那么视觉方面目前我认为最好的选择就是使用k210了,它不仅成本低,性能好,而且基于MicroPython的开发极易上手,单片机选用的是武汉芯源半导体公司的国产芯片CW32.
什么是CW32
CW32是武汉芯源半导体推出的基于Cortex-M0+内核的32位微控制器:CW32系列MCU,该系列MCU在多项指标上大幅领先于其他品牌同类产品,可以满足通用、低成本、超低功耗以及高性能等不同应用市场的MCU需求。
什么是k210:
K210 是嘉楠(Cannaan)科技的一款集成机器视觉与机器听觉能力的系统级芯片 (RSIC-V CPU)。使用台积电 (TSMC) 超低功耗的 28 纳米先进制程,具有双核 64 位处理器,拥有较好的功耗性能,稳定性与可靠性。该方案力求零门槛开发,可在最短时效部署于用户的产品中,赋予产品人工智能(AI)。 可以说是集性能强劲与高性价比于一身。
一、CW32根据K210串口传回来的数据输出PWM控制舵机进行跟踪
CW32F030x6/x8 是基于 eFlash 的单芯片微控制器,集成了主频高达 64MHz 的 ARM® Cortex®-M0+ 内核、高速嵌入式存储器(多至 64K 字节 FLASH 和多至 8K 字节 SRAM)以及一系列全面的增强型外设和 I/O 口。
所有型号都提供全套的通信接口(三路 UART、两路 SPI 和两路 I2C)、12 位高速 ADC、七组通用和基本定时器以及一组高级控制 PWM 定时器。
CW32F030x6/x8 可以在 -40° C 到 105° C 的温度范围内工作,供电电压宽达 1.65V ~ 5.5V。支持 Sleep 和DeepSleep 两种低功耗工作模式。
通用异步收发器 (UART) 支持异步全双工、同步半双工和单线半双工模式,支持硬件数据流控和多机通信;可编程数据帧结构;可以通过小数波特率发生器提供宽范围的波特率选择。
UART 控制器工作在双时钟域下,允许在深度休眠模式下进行数据的接收,接收完成中断可以唤醒 MCU 回到运行模式
本例程中选用的串口1
详细代码如下
1.配置系统时钟
void RCC_Configuration(void)
{
// 1. HSE 使能&校准,外部晶振频率=8MHz
RCC_HSE_Enable(RCC_HSE_MODE_OSC,8000000, RCC_HSE_DRIVER_NORMAL, RCC_HSE_FLT_CLOSE);
// 2. 设置 HCLK&PCLK的 分频系数
RCC_HCLKPRS_Config(RCC_HCLK_DIV1);
RCC_PCLKPRS_Config(RCC_PCLK_DIV1);
// 3. 使能 PLL,6倍频至 48MHz
RCC_PLL_Enable(RCC_PLLSOURCE_HSEOSC, 8000000, 6);
RCC_PLL_OUT();
// 4. 设置 Flash读等待周期:48M≥时钟源HCLK>24M,为 2cycle;时钟源HCLK>48M,为 3cycle
__RCC_FLASH_CLK_ENABLE();
FLASH_SetLatency(FLASH_Latency_2);
// 5. 切换时钟至 PLL
RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL);
RCC_SystemCoreClockUpdate(48000000); //48000000
}
2.初始化IO口
void GPIO_Configuration(void)
{
// 使能 GPIOB时钟:因为使用了 PB06(M4电机)& PB07(M3电机)引脚
__RCC_GPIOB_CLK_ENABLE();
// 使能 GPIOA时钟:因为使用了 PA08(M1电机)& PA11(M2电机)引脚
__RCC_GPIOA_CLK_ENABLE();
// 配置 计时器:GTIM3_CH4(PA12引脚)
PA12_AFx_GTIM3CH4();
PA12_DIGTAL_ENABLE(); // 使能 数字方式
PA12_DIR_OUTPUT(); // 输出
PA12_PUSHPULL_ENABLE(); // 模式:推挽输出
PA12_SPEED_HIGH(); // 高速
// 声明 GPIO端口初始化配置
GPIO_InitTypeDef GPIO_InitStructure;
// 中断方式:选择 不使用
GPIO_InitStructure.IT = GPIO_IT_NONE;
// 模式:推挽输出
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
// 配置 计时器:ATIM_CH2A(PB06引脚)
PB06_AFx_ATIMCH2A();
// 初始化 PB06(Y)引脚
GPIO_Init(CW_GPIOB, &GPIO_InitStructure);
// 选择引脚:PB07(X)引脚
GPIO_InitStructure.Pins = GPIO_PIN_7;
// 配置 计时器:ATIM_CH3A(PB07引脚)
PB07_AFx_ATIMCH3A();
// 初始化 PB07 引脚
GPIO_Init(CW_GPIOB, &GPIO_InitStructure);
}
3.初始化串口以及串口接收函数
void UART_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APBPeriphClk_Enable2(RCC_APB2_PERIPH_UART1, ENABLE);
RCC_AHBPeriphClk_Enable( RCC_AHB_PERIPH_GPIOA, ENABLE);
PA08_AFx_UART1TXD();
PA09_AFx_UART1RXD();
GPIO_InitStructure.Pins = GPIO_PIN_8; //PA8
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.Pins = GPIO_PIN_9; //PA9
GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
GPIO_Init(CW_GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_Over = USART_Over_16;
USART_InitStructure.USART_Source = USART_Source_PCLK;
USART_InitStructure.USART_UclkFreq = 64000000;
USART_InitStructure.USART_StartBit = USART_StartBit_FE;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(CW_UART1, &USART_InitStructure);
//使能UARTx RC中断
USART_ITConfig(CW_UART1, USART_IT_RC, ENABLE);
//优先级,无优先级分组
NVIC_SetPriority(UART1_IRQn, 0);
//UARTx中断使能
NVIC_EnableIRQ(UART1_IRQn);
}
void UART1_IRQHandler(void)
{
/* USER CODE BEGIN */
static int i=0; //注意不同数组不要用同一个变量!
if(USART_GetITStatus(CW_UART1, USART_IT_RC) != RESET)
{
USART_ClearITPendingBit(CW_UART1, USART_IT_RC);
k210_data[i++] =USART_ReceiveData_8bit(CW_UART1); //接收数据
if(k210_data[0]!=0xa3) i=0; //判断第一个帧头
if((i==2)&&(k210_data[1]!=0xb3)) i=0; //判断第二个帧头
if(i==7) //代表一组数据传输完毕
{ PA00_SETHIGH();
i = 0;
if( k210_data_test(k210_data) ) //判断数据合理性
{
/****************************************************
更新数据
****************************************************/
//【1】x误差:
k210_error_x = k210_data[2];
//【2】x标志位:
x_flag = k210_data[3];
//【3】x误差:
k210_error_y = k210_data[4];
//【4】x标志位:
y_flag = k210_data[5];
lenth = k210_data[6];
// Data_Handle();
OLED_Show();
}
}
/* USER CODE END */
}
}
5.X轴与Y轴的误差通过PID算法得到的值输出PWM控制舵机
int PID_xCalc(int error_x)
{
float Pout_x;
// float Iout;
float Dout_x;
// float DelEk;
float out_x;
//判断时间......
if(pid.C1ms>(pid.T_x)/*|| (Circle_Flag==4)|| Circle_Flag==2*/)
{
pid.Pv_x = error_x;
pid.Ek_x=pid.Pv_x-pid.Sv_x; //P
Pout_x=pid.Kp_x*pid.Ek_x;
Dout_x=pid.Kd_x*(pid.Ek_x-pid.Ek_1_x);
out_x = Pout_x+Dout_x;
pid.OUT_x=out_x;
if(pid.OUT_x>pid.pwmcycle_x) {pid.OUT_x=pid.pwmcycle_x; }
if(pid.OUT_x<-pid.pwmcycle_x) {pid.OUT_x=-pid.pwmcycle_x; }
pid.Ek_1_x=pid.Ek_x;
pid.C1ms=0;
return pid.OUT_x;
}
}
int PID_yCalc(int error_y)
{
float Pout_y;
// float Iout;
float Dout_y;
// float DelEk;
float out_y;
//判断时间......
if(pid.C2ms>(pid.T_y)/*|| (Circle_Flag==4)|| Circle_Flag==2*/)
{
pid.Pv_y = error_y;
pid.Ek_y=pid.Pv_y-pid.Sv_y; //P
Pout_y=pid.Kp_y*pid.Ek_y;
Dout_y=pid.Kd_y*(pid.Ek_y-pid.Ek_1_y);
out_y = Pout_y+Dout_y;
pid.OUT_y=out_y;
//判断并修正输出结果
if(pid.OUT_y>pid.pwmcycle_y) {pid.OUT_y=pid.pwmcycle_y; }
if(pid.OUT_y<-pid.pwmcycle_y) {pid.OUT_y=-pid.pwmcycle_y; }
pid.Ek_1_y=pid.Ek_y;
pid.C2ms=0;
return pid.OUT_y;
}
}
二、K210获取物体x轴y轴的位置通过串口把数据传给单片机
导入模块
from machine import Timer,PWM
import sensor, image, lcd
from fpioa_manager import fm
from Maix import GPIO
from machine import UART
配置串口
fm.register(10, fm.fpioa.UART1_TX, force=True)
fm.register(11, fm.fpioa.UART1_RX, force=True)
usart1 = UART(UART.UART1, 115200)
usart1.init(115200, bits=8, parity=None, stop=1) #初始化串口
设置颜色阈值
class Color_Property():
cx = 0 # 色块 x轴 中心坐标
cy = 0 # 色块 y轴 中心坐标
flag = 0 # 色块标志位 1 找到 0 未找到
color = 0 # 色块颜色标志位 例如 你可以用 1 来表示 黑色
density = 0 # 色块密度比 反映色块锁定程度 值越大 锁定程度越好
pixels_max = 0 # 色块像素最大值
led_flag = 0 # LED标志位 方便调试用
color_threshold = (0, 0, 0, 0, 0, 0) # 色块颜色阈值
color_roi = (0,0,320,240) # 色块寻找区域(感兴趣区域)
color_x_stride = 1 # 色块 x轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
color_y_stride = 1 # 色块 y轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
color_pixels_threshold = 100 # 色块 像素个数阈值 例如调节此参数为100 则可以滤除色块像素小于100的色块
color_area_threshold = 100 # 色块 被框面积阈值 例如调节此参数为100 则可以滤除色块被框面积小于100的色块
color_merge = True # 是否合并寻找到的色块 True 则合并 False 则不合并
color_margin = 1 # 色块合并间距 例如调节此参数为1 若上面选择True合并色块 且被找到的色块有多个 相距1像素 则会将这些色块合并
# 红色
red = Color_Property()
red.color_threshold = (15, 35, -37, -13, 0, 39)
#red.color_roi = (0,0,320,240)
red.color_roi = (0,110,320,20)
red.color_x_stride = 1
red.color_y_stride = 1
#red.color_pixels_threshold = 100
#red.color_area_threshold = 100
red.color_pixels_threshold = 10
red.color_area_threshold = 10
red.color_merge = True
red.color_margin = 1
初始化参数配置
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.run(1)
#lcd初始化
lcd.init()
def find_max(blobs):
max_size=0
for blob in blobs:
if blob[2]*blob[3] > max_size:
max_blob=blob
max_size = blob[2]*blob[3]
return max_blob
获取误差
def get_target_err():
img=sensor.snapshot()
blobs = img.find_blobs([red.color_threshold])
if blobs:
max_blob = find_max(blobs)
cx_error = max_blob.cx()-img.width()/2
cy_error = max_blob.cy()-img.height()/2
img.draw_rectangle(max_blob.rect()) # rect
img.draw_cross(max_blob.cx(), max_blob.cy()) # cx, cy
lcd.display(img)
# 在这个范围【-5,5】,则认为在中心
if abs(cx_error) < 10:
cx_error = 0
if abs(cy_error) < 10:
cy_error = 0
img = img.draw_cross(int(cx_error), int(cy_error))
return (cx_error,cy_error)
else:
img = img.draw_cross(160, 120)
lcd.display(img)
return (0, 0) # 没找到 就返回在中央坐标
#串口发送数据函数:
def usart_send(x, y):
global Turn
#【1】设置标志位:
if x>=0:
x_flag = 1
if x<0 :
x_flag = 0;
if y>=0:
y_flag = 1
if y<0 :
y_flag = 0;
#【2】处理数据:
x_err = int(x)
y_err = int(y)
#print(x_err,y_err)
#【3】串口发送:
data = bytearray([0xa3,0xb3, x_err,x_flag ,y_err , y_flag, 0xc3]) #帧头 + 帧头 + 循迹值 + 帧尾
usart1.write(data)
while(True):
cx_error,cy_error=get_target_err()
usart_send(cx_error,cy_error)