目录
一、引言
暑期实践的课题,题目和标题是一样的。
使用的是普中·天马F407板子,上位机使用MATLAB制作,都是在网上找的资料。
工程文件在主页里面有,不需要积分,可以直接下载。
工程文件在主页里面有,不需要积分,可以直接下载。
工程文件在主页里面有,不需要积分,可以直接下载。
1、设计要求
设计一套固体颜色自动检测以及显示的系统;
颜色算法可靠、识别速度不低于1s;
将颜色检测结果在上位机分类显示。
2、软硬件的使用
使用F407作为单片机,MATLAB作为上位机制作软件。
软件:
keil5 MDK和MATLAB 2023a.
硬件:
TCS3200颜色传感器,STM32407ZGT6,TFT3.5-ILI9488彩屏
二、Keil代码部分
1、定时器
使用的是定时器5,定时器用来记录每个滤波器发送过来的脉冲数,采用向上计数的方式。
//**********************************************//定时器(用来计数每个滤波器开启时发过来的脉冲数)
void TIM5_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;//设置GPIO为TIM的时钟输入引脚
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//设置定时器参数
NVIC_InitTypeDef NVIC_InitStructure;//设置中断优先级
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//打开PB端口时钟
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;//gpio模式设置为复用模式
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//设置为推挽
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不上下拉
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_TIM5);//设置GPIO复用为中断定时器输入
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);//使能TIM时钟
TIM_TimeBaseStructure.TIM_Period = 30000;//自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler =0; //定时器分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数模式
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);
TIM_ClearITPendingBit(TIM5,TIM_IT_Update);//清除溢出中断标志位
TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE );//使能定时器更新中断
TIM_TIxExternalClockConfig(TIM5,TIM_TIxExternalCLK1Source_TI1,TIM_ICPolarity_Falling,15);
TIM_SetCounter(TIM5,0);//计数器清零
TIM_Cmd(TIM5, ENABLE);//使能TIMx外设
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
2、TCS3200设置
通过对引脚输出不同的高低电平来达到开启不同的滤波器,滤波器开启的时间用延时来确定。
先开启不同的滤波器,通过控制Pin_2、Pin_3引脚的高低电平来控制,然后使用定时器来记录发送过来的方波数,检测200ms,后将定时器内数据存储到数组中,重置定时器。
//*************************************//TCS3200滤波过程(检测)开启不同的滤波器的过程
void TIM5_CAP(void)
{
GPIO_ResetBits(GPIOA,s2_PIN);
GPIO_ResetBits(GPIOA,s3_PIN); //红色滤波
TIM_SetCounter(TIM5,0);
delay_ms(200);
count=TIM_GetCounter(TIM5);
cnt[0] = count;
TIM_SetCounter(TIM5,0);
GPIO_SetBits(GPIOA,s2_PIN);
GPIO_SetBits(GPIOA,s3_PIN); //绿色滤波
TIM_SetCounter(TIM5,0);
delay_ms(200);
count=TIM_GetCounter(TIM5);
cnt[1] = count;
TIM_SetCounter(TIM5,0);
GPIO_ResetBits(GPIOA,s2_PIN);
GPIO_SetBits(GPIOA,s3_PIN); //蓝色滤波
TIM_SetCounter(TIM5,0);
delay_ms(200);
count=TIM_GetCounter(TIM5);
cnt[2] = count;
TIM_SetCounter(TIM5,0);
}
3、串口设置
通过串口输出检测到的数据,屏幕上显示RGB的值,同时串口接收上位机传输来的数据,串口先输入数字2,开启白平衡,输入数字1,为开启颜色检测。
白平衡和检测过后,会通过串口输出有三个变量的数组。R,G,B.
//***************************//串口1中断服务程序;利用串口收发数据,在串口处输入2,为白平衡,输入1,为开始检测
void USART1_IRQHandler(void)
{
unsigned char Res = 0;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
Res = USART_ReceiveData(USART1); //读取接收到的数据
//**********************************************燕颜色检测过程
if (Res=='1')
{
while(1)
{
TIM5_CAP();//开启滤波
LCD_Clear(WHITE);//刷新屏幕
LCD_ShowFontHZ(80,10,"颜色检测");
LCD_ShowString(10,40,tftlcd_data.width,tftlcd_data.height,24,"R:");
LCD_ShowString(10,70,tftlcd_data.width,tftlcd_data.height,24,"G:");
LCD_ShowString(10,100,tftlcd_data.width,tftlcd_data.height,24,"B:");
LCD_ShowNum(120, 40,(int)(cnt[0]*RGB[0]),3,24);
LCD_ShowNum(120, 70,(int)(cnt[1]*RGB[1]),3,24);
LCD_ShowNum(120, 100,(int)(cnt[2]*RGB[2]),3,24);
R = (int)(cnt[0]*RGB[0]);
G = (int)(cnt[1]*RGB[1]);
B = (int)(cnt[2]*RGB[2]);
if(R>255)
R=255;
if(G>255)
G=255;
if(B>255)
B=255;
printf("%d,%d,%d\r\n",R,G,B);
}
}
//*************************************************//白平衡
if (Res=='2')
{
TIM5_CAP();//开启滤波
//通过白平衡测试,计算得到白色物体RGB值255与1s内三色光脉冲数的RGB比例因子
RGB[0] = 255.0/ cnt[0] ; //红色光比例因子
RGB[1] = 255.0/ cnt[1] ; //绿色光比例因子
RGB[2] = 255.0/ cnt[2] ; //蓝色光比例因子
LCD_Clear(WHITE);//屏幕刷新
LCD_ShowFontHZ(80,10,"颜色检测");
LCD_ShowString(10,40,tftlcd_data.width,tftlcd_data.height,24,"R:");
LCD_ShowString(10,70,tftlcd_data.width,tftlcd_data.height,24,"G:");
LCD_ShowString(10,100,tftlcd_data.width,tftlcd_data.height,24,"B:");
LCD_ShowNum(100, 40, RGB[0],3,24);
LCD_ShowNum(100, 70,RGB[1],3,24);
LCD_ShowNum(100, 100,RGB[2],3,24);
LCD_ShowString(80,160,tftlcd_data.width,tftlcd_data.height,24,"White Blance");//白平衡结束
R = (int)(cnt[0]*RGB[0]);
G = (int)(cnt[1]*RGB[1]);
B = (int)(cnt[2]*RGB[2]);
if(R>255)
R=255;
if(G>255)
G=255;
if(B>255)
B=255;
printf("%.2f,%.2f,%.2f\r\n",RGB[0],RGB[1],RGB[2]);
}
}
}
三、上位机部分
上位机采用MATLAB App Design功能进行设计,主要实现接收单片机通过串口传输过来的数据,和向单片机通过串口发送指令。在运行上位机之前要将串口插上,不然会报错。
使用的是MATLAB 2023a
上位机面板:
上位机在编辑过程中主要使用各个部分的回调函数。
首先点击App,选择设计App,选择空白面板即可。
左侧选择按钮,放置在面板上
右侧更改按钮内容:
右键单击选择回调
搜索串口的回调部分内容:
% Code that executes after component creation
function startupFcn(app)
[app.COMS app.port] = get_com_ports();
app.DropDown.Items=app.COMS;
app.port_use=app.port(1);
app.s=serial(app.port_use,'BaudRate',115200,'Terminator','LF');
end
其中的get_com_ports()是用.m文件编写的串口搜索程序,建在同一个文件夹即可。我们在App中直接调用就可以获取到串口号。
.m内容:
function [COMS port] = get_com_ports()%获取串口号函数
command = 'wmic path win32_pnpentity get caption /format:list | find "COM"';
[status, cmdout] = system (command);
cmdout = strread(cmdout,'%s','delimiter','='); %按分隔符拆分字符串数组
if numel(cmdout)>0
j=1;
for i = 1:numel(cmdout) %numel 返回元素个数
if strcmp(cmdout{i}(1:7),'Caption')
COMS{j} = cmdout{i+1};
j=j+1;
end
end
COMS_split=cell2mat(COMS);
COMS_split=split(COMS_split,'(');%通过左括号将字符串分割
j=1;
for i = 1:numel(COMS_split) %numel 返回元素个数
if strcmp(COMS_split{i}(1:3),'COM')
port_temp{j} = COMS_split{i};
j=j+1;
end
end
port_temp=split(port_temp,')');%通过右括号将字符串分割
j=1;
for i = 1:(numel(port_temp)-1) %numel 返回元素个数
if strcmp(port_temp{i}(1:3),'COM')
port{j} = port_temp{i};
j=j+1;
end
end
elseif numel(cmdout)==0
COMS="没有搜索到串口";
port="null";
errordlg('没有搜索到任何可用端口');
end
end
串口选择是左侧下拉框器件。选择回调,在回调中输入检测电脑串口号的程序,让其能选择使用那个串口。
% Value changed function: DropDown
function DropDownValueChanged(app, event)
value = app.DropDown.Value;
for i=1:length(app.DropDown.Items) %遍历
if strcmp(value,app.DropDown.Items{i})
app.port_use=app.port(1);%获得端口
app.s=serial(app.port_use,'BaudRate',115200,'Terminator','LF');
end
end
end
打开串口按键,和前面的搜索串口按键一样设置,只是回调中内容不同。我这里的打开串口,并没有真正的打开,只是按下之后有一次反馈。如果在这里写入打开串口的程序,会出现,打开串口之后,LCD屏幕复位。真正的打开串口程序在后面会写到。
% Button pushed function: Button_2
function Button_2Pushed(app, event)
if app.lamp_flag==1%判断是否打开
app.Lamp.Color='g';
app.Button_2.Text="关闭串口";
app.lamp_flag=0;
else
app.Lamp.Color='r';
app.Button_2.Text="打开串口";
app.lamp_flag=1;
end
end
输出结果按键也是在左侧选择按钮即可,这里点击后才是打开串口,进行一次数据传输。
serial()这个函数就是打开串口的,后面是设置工作方式,波特率等。
打开一次后要直接关闭他,fclose()。否则下次使用串口,会提示串口被占用。
fgetl()是提取传输过来的数据,用split()分割。三个数据赋值,后进行颜色识别。
% Button pushed function: Button_4
function Button_4Pushed(app, event)
if app.lamp_flag==0;
app.s=serial('com5');
set(app.s,'BaudRate', 115200,'DataBits',8,'Parity','none');
app.s.DataTerminalReady='off';
fclose(instrfind);
fopen(app.s);
a=fgetl(app.s);
fclose(app.s);
a=a(1:length(a)-1)
a=split(a,',')
R=a(1,1)
G=a(2,1)
B=a(3,1)
app.Label_2.Text=R;
app.Label_3.Text=G;
app.Label_4.Text=B;
白平衡和开始检测也是左侧按钮,选择回调,点击后,先将以前开启的串口关闭,后再次开启串口使用fprintf()将数据发送到串口,单片机接收数据,执行操作
% Button pushed function: Button_5
function Button_5Pushed(app, event)
if app.lamp_flag==0;
app.s=serial('com5');
set(app.s,'BaudRate', 115200,'DataBits',8,'Parity','none');
app.s.DataTerminalReady='off';
fclose(instrfind);
fopen(app.s);
num=['2']
fprintf(app.s,num);%将数据发送至串口
value=app.s;
disp(value)
fclose(app.s);
end
end
% Button pushed function: Button_6
function Button_6Pushed(app, event)
if app.lamp_flag==0;
app.s=serial('com5');
set(app.s,'BaudRate', 115200,'DataBits',8,'Parity','none');
app.s.DataTerminalReady='off';
fclose(instrfind);
fopen(app.s);
num=['1']
fprintf(app.s,num);%将数据发送至串口
value=app.s;
disp(value)
fclose(app.s);
end
end
end
至于R,G,B值的显示,使用到的是左侧的文本,想要更改显示内容,只需要在按钮回调中写入
app.Label_2.Text=R;
app.Label_3.Text=G;
app.Label_4.Text=B;
就可以让RGB的数值显示,
中间区域的图像绘制,使用到的是左侧坐标区,
他的显示也是写在按钮回调中,
set(rectangle(app.UIAxes),'Edgecolor',[0,0,0],'LineWidth',0.8,'Facecolor',yanse);
四、参考资料
1.基于stm32f103的颜色识别,写的很好,有原理什么的,我主要参考的这个博客:https://blog.csdn.net/weixin_50950634/article/details/114645247?
2.上位机的制作主要是参考B站UP主的教程,教的比较好,全部都跟着做就能做好: