前段时间一直在做超声波的调试,很多模拟量的问题没有解决这段时间降模拟量和通信做了调整,目前完成了Modbus-RTU的数据通信以及超声波测距的工作,整体的项目进展目前已经基本完成了,所以这里来和大家做一下汇报,分享一下自己的成果,首先从整体的项目规划以及框图开始说起,可能会比较繁琐一点,但是我还是觉得尽量详细的介绍我们现在的东西,也许会对大家带来一些启发。
1 项目介绍
AGV在现在物流运输和分拣中起到了重要作用,通常情况下是由上级电脑完成系统路径的规划,然后车辆按照分配路线将物流包裹配送到对应的区域内,从而完成大量包裹的分拣的工作,节约了很多人力成本,也避免了由于人为操作失误造成物流停滞、延迟的负面影响。因此AGV在物流分拣的重要性可想而知。在AGV的整体路径规划中,AGV的智能性也将起到至关重要的作用,其中最主要的一点就是安全距离检测,可以判断对于障碍物的判断,以及车辆急停、车辆会根据障碍物距离做出相应的减速、停止、转向等动作。
2 硬件方案
整体的硬件方案是STM32F103CBT6作为主控,通过分时复用完成多多个超声波探头的然后通过RS485或者CAN总线完成数据的传输,通常情况下,RS485和CAN总线在实时性上是有一些距离的。目前的话只做了RS485这块,支持Modbus_RTU的协议。整体还配备了温度采集传感器,可以通过温度来进行声音测距的温度补偿,计算出当前温度的声速,从而对测量距离进行修正。
2.1 硬件原理图
整体的原理图如上图,采用八个国内的超声波测距芯片,然后使用DCDC,完成12V到3.3V的电压转换,通过两个8通道的选择开关完成对每个传感器的数据选择和读取,通过TTL串口实现数的采集。软件内配置状态机,可以正常的完成采集。
3 软件流程图
3.1 状态机切换机制
状态机是在控制最常用的一种机制,可以避免阻塞,完成程序的流畅运行,同样避免了不必要的程序运行占用资源。目前我使用的状态机一共设置了几个状态,每个状态有对应状态的工作任务。
目前一共分为:空闲状态、命令发送状态、回复等待状态、超时状态、异常状态。
3.2 代码展示
3.2.1 状态机代码
void get_Data(struct CSB_Get *CSB_Struct)
{
int i=0;
unsigned int Status=CSB_Struct->Stttus;
unsigned int Data=0;
if(CSB_Struct->Channel_Count>CSB_Struct->Channel_MAX)
{
CSB_Struct->Channel_Count=0;/*-- 如果当前通道计数大于 最大通道--那么这次就作废---*/
return ;
}
else
{
switch(Status)
{
/*--------*/
case Status_Ideal:/*--数据采集之前的数据初始化工作 ---*/
{
for(i=0;i<CSB_Struct->Channel_MAX;i++)
{
CSB_Struct->CSB_Data_Stable[i]=CSB_Struct->CSB_Curent_Data[i];
SysTemInfo.Sys_Data.CSB_Data[i]=CSB_Struct->CSB_Curent_Data[i]/1000;
}
select(CSB_Struct->Channel_Count); /*-- 选择发送通道 --*/
CSB_Struct->TimeOut_Flag=0; /*-- 超时标志清零--*/
CSB_Struct->UART_GetData_Flag=0; /*-- 串口接收数据标志清零--*/
CSB_Struct->Uart_GetData_Count=0; /*-- 串口接收数量计数清零 ---*/
CSB_Struct->CSB_Curent_Data[CSB_Struct->Channel_Count]=0;/*当前通道长度清零--*/
CSB_Struct->TimeStart_Flag=1;
CSB_Struct->TimeOutCount=0;
CSB_Struct->Uart_Data[0]=0;
CSB_Struct->Uart_Data[1]=0;
CSB_Struct->Uart_Data[2]=0;
CSB_Struct->Stttus=Status_SendCND;
}break;
case Status_SendCND:
{
select(CSB_Struct->Channel_Count); /*-- 选择发送通道 --*/
Send_Cmd(); /*-- 发送超声波的数据采集命令 --*/
timer_enable(TIMER1); /*-- 开启定时器 -------*/
CSB_Struct->Uart_Data[2]=0;
CSB_Struct->Stttus=Status_WaitReply;
}break;
case Status_WaitReply :
{
if(CSB_Struct->UART_GetData_Flag==1)
{
Data =(Data|(CSB_Struct->Uart_Data[0]<<16) |(CSB_Struct->Uart_Data[1]<<8)|(CSB_Struct->Uart_Data[2]));
CSB_Struct->CSB_Curent_Data[CSB_Struct->Channel_Count]=Data;
CSB_Struct->Channel_Count++;
CSB_Struct->Stttus=Status_Ideal;
}
/*-- 如果超时 --*/
if(CSB_Struct->TimeOut_Flag==1)
{
CSB_Struct->Stttus=Status_TimeOut;
}
}break;
case Status_TimeOut: /*-- 直接跳转到空闲模式,继续下一个通道的测量 ----*/
{
CSB_Struct->Channel_Count++;
CSB_Struct->TimeStart_Flag=0;
CSB_Struct->TimeOutCount=0;
timer_disable(TIMER1); /*-- 开启定时器 -------*/
CSB_Struct->Stttus=Status_Ideal;
}break;
/*--------*/
default:
{
CSB_Struct->TimeOut_Flag=0; /*-- 超时标志清零--*/
CSB_Struct->UART_GetData_Flag=0; /*-- 串口接收数据标志清零--*/
CSB_Struct->Uart_GetData_Count=0; /*-- 串口接收数量计数清零 ---*/
CSB_Struct->Uart_Data[0]=0;
CSB_Struct->Uart_Data[1]=0;
CSB_Struct->Uart_Data[2]=0;
CSB_Struct->Channel_Count=0;
CSB_Struct->TimeStart_Flag=0;
timer_disable(TIMER1); /*-- 开启定时器 -------*/
}
}
}
}
3.2.2 通信代码展示 这里仅展示部分代码
case ReadMultRegister : //--功能码:03 -- 读多个寄存器----
{
MB->ProtocalStr.MB_DataNum=MB->MB_RxData[MB_DataNum]<<8|MB->MB_RxData[MB_DataNum+1];
MB->ProtocalStr.CRCData=MB->MB_RxData[MB->MB_RxData_Len-1]<<8|MB->MB_RxData[MB->MB_RxData_Len-2];//--填入CRC数值--
//--进入这里,说明地址和功能码都满足了,现在需要做的事判断输出数量是否在规定范围内--
//--IO数量的计算应当从起始地址+数量来计算--如果起始地址加上数量之后大于IO的点数,
//--此时应当报错处理--
DataLimit=MB->ProtocalStr.MB_StartAddr + MB->ProtocalStr.MB_DataNum;//--起始地址+读取数量---
if((DataLimit>=1)&&(DataLimit<MaxiunReadREG_Data))
{
MB->MB_TxData[TX_MB_Addr]=MB->MB_RxData[MB_Addr];//--填充站号---
MB->MB_TxData[TX_MB_FunCode]=MB->MB_RxData[MB_FunCode];//--填充功能码--
//--读取线圈的函数----unsigned short ReadMultReg_03(unsigned char *Buffer,unsigned short StartAddr,unsigned short Length)
RTN_DataLen=ReadMultReg_03(&MB->MB_TxData[TX_MB_DataBase],MB->ProtocalStr.MB_StartAddr,MB->ProtocalStr.MB_DataNum);
//--填充字节数--
MB->MB_TxData[TX_MB_TxNum]=(unsigned char )(RTN_DataLen&0xff); //--填充字节数--
//--开始填充数据--
//计算CRC---校验和----校验是按照从包头开始 -- 一直到数据结束---
CRC_Data=usMBCRC16( MB->MB_TxData, RTN_DataLen+3 );//--计算CRC的数值
RTN_DataLen=RTN_DataLen+5; //--这里是包含了CRC校验的----站号 +功能码 +数量+CRCData*2=5
MB->MB_TxData_Len=RTN_DataLen;//--传入带发送的字节长度--
MB->MB_TxData[RTN_DataLen-2]=(CRC_Data&0xff); //--CRC-H
MB->MB_TxData[RTN_DataLen-1]=((CRC_Data>>8)&0xff); //--CRC-L
// 完成数据包的组包
//-- 发送标志----
MB->SendFlag=1;
}
else/*--说明--超出范围了---应当返回异常码------ --*/
{
MB->MB_TxData[MB_Addr]=(0x80+ MB->MB_RxData[MB_FunCode]);//--填充站号--
MB->MB_TxData[MB_FunCode]=MB_ERR_Output_Outof_RangeNum;//--填充功能码--错误码--
MB->MB_TxData_Len=2;
MB->SendFlag=1;
}
}
break ;
//--
case ReadinputRegister : //--功能码:04 -- 读输入寄存器----
{
MB->ProtocalStr.MB_DataNum=MB->MB_RxData[MB_DataNum]<<8|MB->MB_RxData[MB_DataNum+1];
MB->ProtocalStr.CRCData=MB->MB_RxData[MB->MB_RxData_Len-1]<<8|MB->MB_RxData[MB->MB_RxData_Len-2];//--填入CRC数值--
DataLimit=MB->ProtocalStr.MB_StartAddr + MB->ProtocalStr.MB_DataNum;//--起始地址+读取数量---
if((DataLimit>=1)&&(DataLimit<MaxiunReadREG_Data))
{
MB->MB_TxData[TX_MB_Addr]=MB->MB_RxData[MB_Addr];//--填充站号---
MB->MB_TxData[TX_MB_FunCode]=MB->MB_RxData[MB_FunCode];//--填充功能码--
//--读取线圈的函数----unsigned short ReadMultReg_03(unsigned char *Buffer,unsigned short StartAddr,unsigned short Length)
RTN_DataLen=ReadInputReg_04(&MB->MB_TxData[TX_MB_DataBase],MB->ProtocalStr.MB_StartAddr,MB->ProtocalStr.MB_DataNum);
//--填充字节数--
MB->MB_TxData[TX_MB_TxNum]=(unsigned char )(RTN_DataLen&0xff); //--填充字节数--
//--开始填充数据--
//计算CRC---校验和----校验是按照从包头开始 -- 一直到数据结束---
CRC_Data=usMBCRC16( MB->MB_TxData, RTN_DataLen+3 );//--计算CRC的数值
RTN_DataLen=RTN_DataLen+5; //--这里是包含了CRC校验的----站号 +功能码 +数量+CRCData*2=5
MB->MB_TxData_Len=RTN_DataLen;//--传入带发送的字节长度--
MB->MB_TxData[RTN_DataLen-2]=(CRC_Data&0xff); //--CRC-H
MB->MB_TxData[RTN_DataLen-1]=((CRC_Data>>8)&0xff); //--CRC-L
// 完成数据包的组包
//-- 发送标志----
MB->SendFlag=1;
}
else/*--说明--超出范围了---应当返回异常码------ --*/
{
MB->MB_TxData[MB_Addr]=(0x80+ MB->MB_RxData[MB_FunCode]);//--填充站号--
MB->MB_TxData[MB_FunCode]=MB_ERR_Output_Outof_RangeNum;//--填充功能码--错误码--
MB->MB_TxData_Len=2;
MB->SendFlag=1;
}
}
break ;
//----
case WriteSingelRegister : //--功能码:06 -- 写单个寄存器----
{
MB->ProtocalStr.MB_DataNum=1;
//--站号-功能码-起始地址 - 数量 字节计数-- 寄存器数值--
DataLimit=MB->ProtocalStr.MB_StartAddr+MB->ProtocalStr.MB_DataNum;
//--进入这里,说明地址和功能码都满足了,现在需要做的事判断输出数量是否在规定范围内--
if((MB->ProtocalStr.MB_DataNum>=1)&&(MB->ProtocalStr.MB_DataNum<=0x07D0))
{
//--站号-功能码-寄存器地址- 寄存器值
for(i=0;i<6;i++)
MB->MB_TxData[i]=MB->MB_RxData[i];
//--写多个寄存器的函数----unsigned short WriteMultRegister_16(unsigned char *Buffer,unsigned short StartAddr,unsigned short Length)
RTN_DataLen=WriteSingelReg_06(&MB->MB_RxData[4],MB->ProtocalStr.MB_StartAddr,MB->ProtocalStr.MB_DataNum);
//--填充字节数--
// MB->MB_TxData[TX_MB_TxNum]=(unsigned char )(RTN_DataLen&0xff); //--填充字节数--
//--开始填充数据--
//计算CRC---校验和----校验是按照从包头开始 -- 一直到数据结束---
CRC_Data=usMBCRC16( MB->MB_TxData, RTN_DataLen+6 );//--计算CRC的数值
RTN_DataLen=RTN_DataLen+8; //--这里是包含了CRC校验的----站号 +功能码 +数量+CRCData*2=5
MB->MB_TxData_Len=RTN_DataLen;//--传入带发送的字节长度--
MB->MB_TxData[RTN_DataLen-2]=(CRC_Data&0xff); //--CRC-H
MB->MB_TxData[RTN_DataLen-1]=((CRC_Data>>8)&0xff); //--CRC-L
// 完成数据包的组包
//-- 发送标志----
MB->SendFlag=1;
}
else/*--说明--超出范围了---应当返回异常码------ --*/
{
MB->MB_TxData[MB_Addr]=(0x80+ MB->MB_RxData[MB_FunCode]);//--填充站号--
MB->MB_TxData[MB_FunCode]=MB_ERR_Output_Outof_RangeNum;//--填充功能码--错误码--
MB->MB_TxData_Len=2;
MB->SendFlag=1;
}
}
break ;
4 实物测试图
由于电路没有处理好,所以目前可以稳定测试的距离在40cm以内,下面是Modbus-RTU 的测试界面。