http://blog.csdn.net/DFSAE/article/details/78715815
这里没有具体的设计,只有大概的设计思路。
整个软件分为5个部分,包括电机控制,摄像头控制,串口转发控制这三部分底层和socket网络通信,协议的打包及解析这里通信部分。
大概存在3个线程:socket通信线程,电机控制线程和连接超时线程(这个本来想用定时器的,但是没找到定时器的依赖库= =)。
程序主要在socket线程的监听上位机发送下来的数据中度过。当收到数据后进行解析。然后执行响应的动作。应用软件最核心的部分就在这里了。另外这里的电机操作正常情况是需要分开来的,后面解释为什么,如果做一起就说明比较懒了。
这个线程里面处理上位机发下来的电机控制信号,实现对电机的输出。这就是操作分离开来的部分,后面有详细解释。
其实就是一个定时器的功能,保证在一定时间没有接收上位机数据(连接断开)后,重新进行socket监听,以便进行新的连接。正常情况下,上位机应该隔一段时间就发下连接数据,以免断开连接。
*注:另外,这里IP地址暂时先定死,由前面的配置的ip为准:192.168.55.1,默认端口号为1234。两个摄像头的端口号分别为8090和8070。
自顶向下开始分析实现。
注:这里要现在用命令行建一个car_server的文件夹,里面放个car_server.log的日志文件。
在main函数里开启了3个线程:
err = pthread_create(&socket_th_id, NULL, socketListen_thread, NULL);
err = pthread_create(&moter_th_id, NULL, moterControl_thread, NULL);
err = pthread_create(&timer_th_id, NULL, timer_thread, NULL);
socket监听线程中做的事情:判断是否掉线?如果掉线,开启socket监听,等待再次连接。其中car_recvAnalysisCmd函数在没有收到数据时其实是阻塞的。
/*************************************************
* 函数名: socketListen_thread
* 说明:scoket监听线程。
* 输入参数:
* 输出参数:
**************************************************/
void * socketListen_thread(void * arg)
{
system("echo start socket listen thread! >> /car_server/car_server.log");
printf("start socket listen thread!\n");
while (1)
{
//如果处于未连接状态,进行socket监听连接
if (get_carLinkStatus(car) == Car_Link_Disconnet)
{
system("echo remote link! >> /car_server/car_server.log");
car_waitRemoteLink(car);
}
car_recvAnalysisCmd(car);//接受数据
usleep(2000);
}
return (void *)0;
}
电机控制线程,20ms执行一次控制。(目前采用直接控制的方式了)
/*************************************************
* 函数名: moterControl_thread
* 说明:电机控制线程。
* 输入参数:
* 输出参数:
**************************************************/
void * moterControl_thread(void * arg)
{
system("echo start moter control thread! >> /car_server/car_server.log");
printf("start moter control thread!\n");
while (1)
{
//电机控制20ms一次
usleep(20000);
}
}
该线程是用来判断连接超时的。
/*************************************************
* 函数名: timer_thread
* 说明:电机控制线程。
* 输入参数:
* 输出参数:
**************************************************/
void * timer_thread(void * arg)
{
system("echo start timer thread! >> /car_server/car_server.log");
printf("start timer thread!\n");
while (1)
{
car_linkTimeoutDece(car);
//判断失去连接,清空电机参数列表,并停车
sleep(1);
}
}
socket基础操作参考:http://c.biancheng.net/cpp/html/3030.html
socket.h里定义了一堆宏,来定义连接的IP和端口。默认数据端口为1234。
/*! 本机服务器IP及通信端口号 */
#define SERVER_IP "192.168.55.1"
#define SERVER_DATA_PORT 1234
#define SERVER_CAMERA_PORT1 8070
#define SERVER_CAMERA_PORT2 8090
socket部分封装了基础的socket通信,比如连接,断开,接收数据,发送数据等,具体代码如下:
/*************************************************
* 函数名:socket_send
* 说明:发送socket数据
* 输入参数:sInfo
* dataBuff 发送缓冲
* size 发送长度
* 输出参数:错误号
**************************************************/
SocketErrNo socket_send(const SocketInfo * const sInfo,
const unsigned char *dataBuff, int size)
{
if (write(sInfo->clnt_sock, dataBuff, size) == -1)
{
socket_err_deal(Send_Err);
return Send_Err;
}
return No_Err;
}
/*************************************************
* 函数名:socket_recv
* 说明:接收socket数据
* 输入参数:sInfo
* dataBuff 接收数据缓冲
* size 接受长度
* 输出参数:错误号
**************************************************/
SocketErrNo socket_recv(const SocketInfo * const sInfo,
unsigned char *dataBuff, int size, int *ret_size)
{
if ((*ret_size = read(sInfo->clnt_sock, dataBuff, size)) == -1)
{
socket_err_deal(Recv_Err);
return Recv_Err;
}
return No_Err;
}
/*************************************************
* 函数名:socket_close
* 说明:关闭socket
* 输入参数:sInfo
* 输出参数:错误号
**************************************************/
SocketErrNo socket_close(const SocketInfo * const sInfo)
{
//关闭套接字
close(sInfo->clnt_sock);
close(sInfo->serv_sock);
return No_Err;
}
/*************************************************
* 函数名:socket_listen
* 说明:关闭socket
* 输入参数:sInfo
* 输出参数:错误号
**************************************************/
void socket_listen(SocketInfo * const sInfo)
{
socklen_t clnt_addr_size;
struct sockaddr_in clnt_addr;
#ifdef SOCKET_ERROR_PRINT
static unsigned int link_t = 0;
#endif
//进入监听状态,等待用户发起请求
listen(sInfo->serv_sock, 20);
//接收客户端请求
clnt_addr_size = sizeof(clnt_addr);
sInfo->clnt_sock = accept(sInfo->serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
#ifdef SOCKET_ERROR_PRINT
link_t++;
printf("link successful, link time: %d\n", link_t);
#endif
}
/*************************************************
* 函数名:socket_init
* 说明:初始化socket端口
* 该函数中有阻塞部分,需要开线程实现
* 输入参数:sInfo 被设socket
* ip 设置IP
* port 设置端口
* protocol 设置协议种类
* 输出参数:错误号
**************************************************/
SocketErrNo socket_init(SocketInfo * const sInfo, const char *ip,
int port, ProtocolType protocol)
{
struct sockaddr_in serv_addr;
int protoType = Socket_TCP;
SocketErrNo error_no;
//判断参数是否正常
if (port <= 0)
{
error_no = Port_Err;
goto errDeal;
}
switch (protocol)
{
case Socket_TCP:
protoType = IPPROTO_TCP; break;
case Socket_UDP:
protoType = IPPROTO_UDP; break;
case Socket_IP:
protoType = IPPROTO_IP; break;
default:
error_no = Protocol_Err; goto errDeal;
}
//初始化
sInfo->proto = protocol;
sInfo->port = port;
memset(sInfo->ip, 0, 16);
memcpy(sInfo->ip, ip, strlen(ip));
//创建套接字
sInfo->serv_sock = socket(AF_INET, SOCK_STREAM, protoType);
//将套接字和IP、端口绑定
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr(sInfo->ip); //具体的IP地址
serv_addr.sin_port = htons(sInfo->port); //端口
bind(sInfo->serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
socket_listen(sInfo);
return No_Err;
errDeal:
socket_err_deal(error_no);
return error_no;
}
这里有个car_recvAnalysisCmd(car_ctnl.c文件内)函数,其实可以认为是Car提供的一个方法。
该方法完成了数据的接收(阻塞式的),在调用commData_unpack函数实现对协议的解析。
/*************************************************
* 函数名: car_recvAnalysisCmd
* 说明:接受并解析协议
* 输入参数:
* 输出参数:命令号
**************************************************/
void car_recvAnalysisCmd(Car* carInfo)
{
int recv_len = 0;
if (socket_recv(&carInfo->sock, recv_buff, RECV_BUFF_SIZE, &recv_len) != No_Err) //阻塞接收
{
#ifdef CAR_INfO_DEBUG_PRINT
printf("socket unlink!\n");
#endif
}
if (recv_len > 0)
{
#ifdef CAR_INfO_DEBUG_PRINT
char i = 0;
printf("thread:\nrecv len %d, data:", recv_len);
for ( ; i < recv_len; i++)
printf("%02x ", recv_buff[i]);
printf("\n");
#endif
car_resetLinkTime(&carInfo_ctnl);
commData_unpack(recv_buff, recv_len);
recv_len = 0;
}
}
commData_unpack(pack.c文件内)函数这部分是实现对数据的拆包,然后回调各个模块的协议处理函数。
这里需要解释一下。这里中间有个while处理纯属多余(阴影啊,呵呵,算了不改了,其实完全可以简写,就一帧的数据就好了)。
这个函数里把中间有效的数据分离出来,传递到cmdData_analysis函数中。
/*************************************************
* 函数名: commData_unpack
* 说明:数据拆包并调用分析处理
* 输入参数:data 源数据缓冲
* size 数据长度
* 输出参数:是否存在有效数据
*************************************************/
int commData_unpack(const unsigned char *data, int size)
{
const unsigned char *p = data;
int len = 0;
int frame_num = 0;
int i = 0;
while (1)
{
if (size < FREAM_MIN_LEN) //检查最小长度
return frame_num;
if (*p++ == FREAM_HEAD)
{
len = ((*p)<<8) + *(p+1);
#ifdef PACK_PRINT
printf("pack len is %d\n", len);
#endif
if (len <= (size-2)) //数据长度合法判断 FREAM_HEAD+FREAM_TAIL
{
#ifdef DATA_CHECK
if ((*(p+len) == FREAM_TAIL) && checkout(*(p+2), len-2))//长度判断和校验
#else
if (*(p+len) == FREAM_TAIL)//长度判断
#endif
{
cmdData_analysis((p+2), len-2);
frame_num++;
size -= (len+1); //len+FREAM_TAIL
}
}
size -= 3; //FREAM_HEAD+len
len = 0;
}
else
size--;
if (0 == size)
return frame_num;
}
}
cmdData_analysis函数(packCmd_port.h中)。实现回调的映射关系:cmd_anylsTab本身是一个函数指针数组,中间放着处理回调函数,可以自定义添加,但他们需要实现一个接口:int (*Cmd_Analy)(const unsigned char *data, int size)。
然后后面会根据对应的标识找到数组中对应的函数指针,然后进行回调。
接着就是各个模块对通信协议的处理了。
typedef int (*Cmd_Analy)(const unsigned char *data, int size); //实现接口
/*! 命令列表 */
typedef enum Cnt_Cmd{
Link_Cmd = 0,
Unlink_Cmd,
TestLink_Cmd,
Camera_Cmd,
Moter_Cmd,
Max_Cmd
}Cnt_Cmd;
/*! 协议对应命令值列表 */
int cmdList[Max_Cmd]={
0x01,
0x02,
0x03,
0x20,
0x10
};
/*! 命令处理表 */
Cmd_Analy cmd_anylsTab[Max_Cmd] = {
&car_sw_cmdCntl,
&car_sw_cmdCntl,
&car_link_cmdCntl,
&camera_cmdCntl,
&moter_cmdCntl,
};
/*************************************************
* 函数名:cmdData_analysis
* 说明:命令数据解析
* 输入参数:data 数据
* size 数据长度
* 输出参数:
**************************************************/
void cmdData_analysis(const unsigned char *data, int size)
{
int i;
#ifdef PACK_ANALYSIS_PRINT
printf("cmd code: %d\n", data[0]);
#endif
for (i = 0; i < (int)Max_Cmd; i++)
{
if (cmdList[i] == data[0])
break;
}
if (i == (int)Max_Cmd)
{
#ifdef PACK_ANALYSIS_PRINT
printf("not exist current cmd protocol!\n");
#endif
return;
}
cmd_anylsTab[i](data, size); //回调
}
这里的设计很烂(当时太随便写了)。 我好像把发送套到具体类中了。所以我打算简单用个信号量分离一下。
这部分的接口是这样的,各个模块的内部数据则由每个模块内部自己打包,自己的数据打包完后,调用commData_pack(pack.c文件内)负责对协议框架的数据打包,完成打包后置位信号量,而由外层负责发送。(结果外层忘记给发送数据留位置了,所以最后socket发送部分都省了)
/*************************************************
* 函数名: commData_pack
* 说明:数据打包函数
* 外部保证缓冲区有足够长度
* 输入参数:data 发送数据缓冲
* size 数据长度,包括标识
* 输出参数:原数据+校验码的总字节数
**************************************************/
int commData_pack(unsigned char *data, int size)
{
int i = 0;
//往后移动3个字节(长度,帧头)
for (i = size - 1; i >= 0; i--)
{
data[size+3] = data[size];
}
data[0] = 0X7F;
data[1] = size+2 / 256;
data[2] = size+2 % 256;
data[size+3] = 0x65;
return 1;
}
电机操作其实可以在解析出电机控制的协议后直接控制,但这样会存在一个问题——如果上位机发下来两次控制的电机参数差异过大。会瞬间改变PWM或者方向,容易造成硬件损伤。所以,最好这里加一个改变缓冲。如果上位机那边做控制突变的控制也可以避免这种情况。最好是MT7688上也做一层保护。可以用记录对比上一次控制值的方式进行控制。
这样就需要我们从数据解析到控制这里进行解耦,具体方式如下:
{创建一个电机参数列表。当解析到电机控制数据后把控制数据扔到里面,如果要做突变控制就可以在这里增加。然后单独开一条线程,也就是前面的电机控制线程。这个线程里检测电机参数列表是否有新的数据,一旦有新的数据就取出最早的一组电机参数进行控制,然后再经过休眠20ms后控制下一组。}
然而还是让上位机做范围控制吧。这里就做最简单的。
这里用一个ctnl_mode来表示当前控制方式(但放入电机参数列表中的时候都会转化为驱动的四个参数值)。sw表示总开关,只有远程开启总开关状态下才会打开。dir_sta表示运动方向,和开车类似:前进,停车,后退档。
/*! 电机状态 */
typedef struct Moter_Info
{
Moter_SW sw; //开关
Moter_Dir dir_sta; //方向状态
unsigned char angle; //方向角度,档位和速度控制模式下有效
unsigned char ctnl_mode; //控制方式 0PWM控制 1速度档位控制 2具体速度值控制
unsigned char speedLevel; //速度档位
unsigned char leftMoterDuty; // 左电机pwm PWM控制模式下有用
unsigned char rightMoterDuty; // 右电机pwm PWM控制模式下有用
int speed; //未用到
}Moter_Info;
解析到数据后的操作离不开这两/三步:
(1).读取协议中的参数,转化为4个电机参数。
(2).读取上次的值,使用算法计算出要推入参数列表的参数集(需要额外变量,目前不做)
(3).把转化后的参数推入参数列表中,交给电机控制线程处理
按这个角度说最好是需要把得到参数和写入参数两部分分离。但是。。还是按最简单的来吧。就混在一起好了。所以就变成了2步:
(1).读取参数值
(2).直接写入
然后就直接把他放在电机命令的解析函数里了
/*************************************************
* 函数名: moter_cmdCntl
* 说明:电机协议命令解析控制
* 该函数只能由上层解析控制。
* 输入参数:data 接收数据
* size 数据大小
* 输出参数:命令号
**************************************************/
int moter_cmdCntl(const unsigned char *data, int size)
{
unsigned char sendbuf[200] = {0};
if (data[0] != 0x10)
return 0;
#ifdef MOTER_DEBUG_PRINT
printf("moter command number: %d\n", data[1]);
#endif
switch (data[1])
{
case 0x10: //开关,方向控制
if (data[2] == 0x00) //stop
{
moter_dirCtnl(&moter_ctnl, Moter_Stop);
moter_setSpeedLevel(&moter_ctnl, 0);
}
else if (data[2] == 0x01) //forward
{
moter_dirCtnl(&moter_ctnl, Moter_Forward);
moter_setSpeedLevel(&moter_ctnl, 0);
}
else if (data[2] == 0x02) //back
{
moter_dirCtnl(&moter_ctnl, Moter_Backward);
moter_setSpeedLevel(&moter_ctnl, 0);
}
moter_change(&moter_ctnl);
break;
case 0x11: //方向速度档位控制
if (data[3] <= 0x06) //&& (data[3] >= 0x00))
{
moter_setDirct(&moter_ctnl, 1);
moter_setSpeedLevel(&moter_ctnl, data[3]); //档位
moter_setMoveAngle(&moter_ctnl, data[2]); //方向
moter_change(&moter_ctnl);
}
break;
case 0x12: break;//方向速度控制(没功能)
case 0x14: //左右电机单独PWM控制
moter_setPWM(&moter_ctnl, data[3], data[5]);
moter_setDirct(&moter_ctnl, 0);
break;
case 0x15: //速度/档位及方向状态查询
break;
default:
#ifdef MOTER_DEBUG_PRINT
printf("command number error\n");
#endif
return 0;
}
return data[1];
}
除去速度需要做闭环控制留空,其他做了实现。
/*************************************************
* 函数名: moter_init
* 说明:电机状态初始化,在正式控制电机前需要做的
* 输入参数:moInfo
* 输出参数:
**************************************************/
static void moter_init(Moter_Info *moInfo)
/*************************************************
* 函数名: moter_change
* 说明:改变电机运动
* 输入参数:moInfo
* 输出参数:
**************************************************/
static void moter_change(Moter_Info *moInfo)
{
char buf[4] = {0,0,0,0};
IS_MOTER_CNTL_CLOSE;
if (moInfo->dir_sta==Moter_Stop)
{//停止输出
buf[0] = buf[1] = buf[2] = buf[3] = 0;
write(pwm_fd, buf, sizeof(buf));
}
else
{//根据速度档位和方向控制电机
switch (moInfo->ctnl_mode)
{
case 0: //0 PWM控制
{
buf[0] = buf[2] = ((moInfo->dir_sta==Moter_Backward) ? 2 : 1);
buf[1] = moInfo->leftMoterDuty;
buf[3] = moInfo->rightMoterDuty;
write(pwm_fd, buf, sizeof(buf));
break;
}
case 1: //1 速度档位控制
{
moter_speedCtnl(moInfo);
break;
}
case 2: //2 具体速度值控制
{
break;
}
default:
#ifdef MOTER_DEBUG_PRINT
printf("error control mode\n");
#endif
break;
}
}
}
/*************************************************
* 函数名: moter_speedCtnl
* 说明:改变电机速度控制,转化为写入驱动的电机参数
* 输入参数:moInfo
* 输出参数:
**************************************************/
static void moter_speedCtnl(Moter_Info *moInfo)
/*************************************************
* 函数名: moter_setPWM
* 说明:设置电机占空比,最直接的控制方式,
* 输入参数:moInfo
* level 速度档位
* 输出参数:
**************************************************/
static void moter_setPWM(Moter_Info *moInfo, unsigned char l_pwm, unsigned char r_pwm)
{
IS_MOTER_CNTL_CLOSE;
if ((l_pwm > 100) || (r_pwm > 100))
{
#ifdef MOTER_DEBUG_PRINT
printf("moter's setting pwm is out of range\n");
#endif
}
moInfo->leftMoterDuty = l_pwm;
moInfo->rightMoterDuty = r_pwm;
}
/*************************************************
* 函数名: moter_chSpeedLevl
* 说明:设置电机速度档位
* 输入参数:moInfo
* level 速度档位
* 输出参数:
**************************************************/
static void moter_setSpeedLevel(Moter_Info *moInfo, unsigned char level)
{
IS_MOTER_CNTL_CLOSE;
if (level > 6)
return;
if (moInfo->dir_sta == Moter_Stop)
{
#ifdef MOTER_DEBUG_PRINT
printf("car is stop\n");
#endif
return;
}
//改变占空比
moInfo->speedLevel = level;
#ifdef MOTER_DEBUG_PRINT
printf("set current speed level: %d\n", moInfo->speedLevel);
#endif
}
/*************************************************
* 函数名: moter_setMoveAngle
* 说明:设置方向角度
* 输入参数:方向状态, 角度
* 输出参数:
**************************************************/
static void moter_setMoveAngle(Moter_Info *moInfo, unsigned char ang)
{
IS_MOTER_CNTL_CLOSE;
if (ang > 180)
{
#ifdef MOTER_DEBUG_PRINT
printf("moter's angle setting value is out of value\n");
#endif
return;
}
moInfo->angle = ang;
#ifdef MOTER_DEBUG_PRINT
printf("moter's angle setting value: %d\n", ang);
#endif
}
/*************************************************
* 函数名: moter_dirCtnl
* 说明:电机开关控制
* 输入参数:moInfo
* dir
* 输出参数:
**************************************************/
void moter_dirCtnl(Moter_Info *moInfo, Moter_Dir dir)
{
IS_MOTER_CNTL_CLOSE;
if (dir == Moter_Forward)
{
moInfo->dir_sta = Moter_Forward;
#ifdef MOTER_DEBUG_PRINT
printf("set car forward\n");
#endif
}
else if (dir == Moter_Backward)
{
moInfo->dir_sta = Moter_Backward;
#ifdef MOTER_DEBUG_PRINT
printf("set car backward\n");
#endif
}
else if (dir == Moter_Stop)
{
moInfo->dir_sta = Moter_Stop;
#ifdef MOTER_DEBUG_PRINT
printf("set car off\n");
#endif
}
}
/*************************************************
* 函数名: set_moterSwitch
* 说明:电机状态复位
* 输入参数:Moter_Info
* 输出参数:
*************************************************/
void set_moterSwitch(Moter_Info *moInfo, Moter_SW sw)
{
moter_init(moInfo);
moter_change(moInfo); //开或关都要恢复到初始
moInfo->sw = sw;
#ifdef MOTER_DEBUG_PRINT
if (moInfo->sw != Moter_SW_Off)
printf("moter control switch is open\n");
else
printf("moter control switch is close\n");
#endif
}
摄像头方面,是直接控制1个或2个(看开几个摄像头)摄像头进程来控制。
/*************************************************
* 函数名: start_cameraProc1
* 说明:开启摄像头1进程
* 输入参数:
* 输出参数: 是否成功
*************************************************/
static int start_cameraProc1(Camera_Info *cameraInfo)
{
if (cameraInfo->camera_pid1 != 0)
{
#ifdef CAMERA_DEBUG_PRINT
PRINT("camera process 1 been existed!\n");
#endif
return 0;
}
char port_str[40] = "output_http.so -p ";
char * const camera1_argv[] = {"mjpg_streamer", "-i", "input_uvc.so -d /dev/video0", "-o", port_str, NULL};
int2str((port_str+strlen(port_str)), cameraInfo->camera1_port);
cameraInfo->camera_pid1 = getpid();
if (cameraInfo->camera_pid1 == 0)
{
#ifdef CAMERA_DEBUG_PRINT
PRINT("start camera process 1 error!\n");
#endif
return 0;
}
else
{
#ifdef CAMERA_DEBUG_PRINT
PRINT("start camera process 1!\n");
PRINT("port info is: %s!\n", port_str);
PRINT("camera1 process pid is %d\n", cameraInfo->camera_pid1);
#endif
execv("/usr/bin/mjpg_streamer", camera1_argv);
return 1;
}
}
/*************************************************
* 函数名: kill_cameraProc
* 说明:关闭摄像头进程
* 输入参数:
* 输出参数: 是否成功
**************************************************/
static int kill_cameraProc(Camera_Info *cameraInfo)
{
char str[50] = {0};
if ((cameraInfo->camera_pid1 == 0) && (cameraInfo->camera_pid2 == 0))
{
#ifdef CAMERA_DEBUG_PRINT
PRINT("Kill camera process error!\n");
#endif
return 0;
}
else
{
if (cameraInfo->camera_pid1 != 0)
{
sprintf(str, "kill -9 %d", cameraInfo->camera_pid1);
system(str); //删除进程1
ameraInfo->camera_pid1 = 0;
}
if (cameraInfo->camera_pid2 != 0)
{
memset(str, 0, 50);
sprintf(str, "kill -9 %d", cameraInfo->camera_pid2);
system(str); //删除进程2
cameraInfo->camera_pid2 = 0;
}
return 1;
}
}
摄像头协议处理过程如下,这里的通信应答其实都是成功的。
/*************************************************
* 函数名: camera_cmdCntl
* 说明:摄像头协议命令解析控制
* 该函数只能由上层解析控制。
* 输入参数: data 接收数据
* size 数据大小
* 输出参数:命令号
**************************************************/
int camera_cmdCntl(const unsigned char *data, int size)
{
unsigned char sendbuf[200] = {0};
#ifdef CAMERA_DEBUG_PRINT
PRINT("camera command number: %d\n", data[0]);
#endif
if (get_cameraSwitch(&camera_obj) == Camera_SW_Off)
{
#ifdef CAMERA_DEBUG_PRINT
PRINT("camera switch is closed\n");
#endif
return data[1];
}
switch (data[1])
{
case 0x01://开关控制
{
if (data[2] == 0x0f || data[2] == 0x01)
{
//开启摄像头app
if (camera_obj.camera_pid1 != 0)
return data[1];
camera_obj.camera_pid1 = fork();
switch(camera_obj.camera_pid1)
{
case -1:exit(1);break;
case 0: start_cameraProc1(&camera_obj);exit(0);camera_cmdAns(sendbuf,data[2], 1);
default: break;
}
}
if (data[2] == 0x0f || data[2] == 0x02)
{
if (camera_obj.camera_pid2 != 0)
return data[1];
camera_obj.camera_pid2 = fork();
switch(camera_obj.camera_pid2)
{
case -1:exit(1);break;
case 0: start_cameraProc2(&camera_obj);exit(0);camera_cmdAns(sendbuf,data[2], 1);
default: break;
}
}
if (data[2] == 0x00)
{
#ifdef CAMERA_DEBUG_PRINT
PRINT("camera close\n");
#endif
kill_cameraProc(&camera_obj);//关闭摄像头
camera_cmdAns(sendbuf,data[2], 1);
}
break;
}
case 0x02: //设置端口
{
int t_port1 = data[2]*256+data[3];
int t_port2 = data[3]*256+data[4];
if ((t_port1 > 1000) && (t_port2 > 1000))
{
camera_obj.camera1_port = t_port1;
camera_obj.camera2_port = t_port2;
#ifdef CAMERA_DEBUG_PRINT
PRINT("camera port 1:%d\n", camera_obj.camera1_port);
PRINT("camera port 2:%d\n", camera_obj.camera2_port);
#endif
camera_cmdAns(sendbuf,data[2], 1);
//关闭原来的端口重新开启
}
else
{
#ifdef CAMERA_DEBUG_PRINT
PRINT("camera port para is bad");
#endif
camera_cmdAns(sendbuf,data[2], 0);
}
break;
}
default:
#ifdef CAMERA_DEBUG_PRINT
PRINT("camera command error!\n");
#endif
break;
}
return data[1];
}
串口使用的是ttyS2的驱动,波特率为9600。程序里只需要向对文件一样对串口驱动正常操作即可。
#define UART_DEV "/dev/ttyS2"
/*************************************************
* 函数名: BSP_uart_init
* 说明:串口初始化
* 输入参数:moInfo
* 输出参数:
**************************************************/
int BSP_uart_init(Uart_Info *uartInfo)
{
static char first_init_flg = 0;
if (!first_init_flg)
{
#ifdef UART_PRINT
printf("Initialize the uart device\n");
#endif
//打开PWM驱动
uart_fd = open(UART_DEV, O_RDWR);
if (uart_fd < 0)
{
#ifdef UART_PRINT
printf("open the uart driver failed!\n");
#endif
return -1;
}
else
{
#ifdef UART_PRINT
printf("open the uart driver success!\n");
#endif
}
uart_init(uartInfo);
}
else
{
#ifdef UART_PRINT
printf("uart device had been initialized!\n");
#endif
}
return 0;
}
/*************************************************
* 函数名: get_uartObj
* 说明:获得串口
* 输入参数:
* 输出参数:
**************************************************/
Uart_Info *get_uartObj(void)
{
return &uartObj;
}
/*************************************************
* 函数名: write2Uart
* 说明:写串口
* 输入参数:
* 输出参数:
**************************************************/
void write2Uart(Uart_Info *uartInfo, char *pdata, unsigned int size)
{
int readRet = write(uart_fd, pdata, size);
#ifdef UART_PRINT
if (readRet == -1)
{
printf("uart write error!\n");
}
else
{
int i = 0;
printf("uart write data:\n");
for (i = 0 ; i < size; i++)
printf("%02x ", pdata[i]);
}
#endif
}
/*************************************************
* 函数名: write2Uart
* 说明:读串口
* 输入参数:
* 输出参数:实际读到的值
**************************************************/
int readFromUart(Uart_Info *uartInfo, char *pdata, unsigned int readSize)
{
int l_read = 0;
l_read = read(uart_fd, pdata, readSize);
#ifdef UART_PRINT
if (l_read == -1)
{
printf("uart read error!\n");
}
else
{
int i = 0;
printf("uart read data:\n");
for (i = 0 ; i < l_read; i++)
printf("%02x ", pdata[i]);
}
#endif
return l_read;
}
和源目录同级的makefile解析和驱动的是一样的,详情见驱动部分。
因为这里用了pthread的库,所以这里顶级Makefile需要额外加点东西。
参考:http://blog.csdn.net/shenwanjiang111/article/details/52367499
需要把这部分加上:DEPENDS:加上 +libpthread, TARGET_LDFLAGS:加上-lpthread
define Package/car_server
SECTION:=utils
CATEGORY:=Utilities
TITLE:=Frame buffer device testing tool
DEPENDS:=+libncurses +libpthread
endef
....
TARGET_LDFLAGS:= -lpthread
另一个Makefile如下:
all: car_server
OBJS = car_server.o socket.o pack.o camera.o moter_cntl.o car_cntl.o uart.o
CC = gcc
CCFLAGS = -Wall -c -o
socket.o:socket.c socket.h msg_output.h
$(CC) -c socket.c -o socket.o
uart.o:uart.c uart.h msg_output.h
$(CC) -c uart.c -o uart.o
pack.o:pack.c pack.h camera.h car_cntl.h moter_cntl.h packCmd_port.h msg_output.h
$(CC) -c pack.c -o pack.o
camera.o:camera.c camera.h msg_output.h
$(CC) -c camera.c -o camera.o
car_cntl.o:car_cntl.c car_cntl.h moter_cntl.h camera.h socket.h uart.h msg_output.h
$(CC) -c car_cntl.c -o car_cntl.o
moter_cntl.o:moter_cntl.c moter_cntl.h msg_output.h
$(CC) -c moter_cntl.c -o moter_cntl.o
car_server.o:car_server.c socket.h pack.h car_cntl.h msg_output.h
$(CC) $(CCFLAGS) $@ $< $(LDFLAGS)
car_server: $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
clean:
rm -f rbcfg *.o
这个和普通的Makefile差不多,照着最简单的仿写的。
编译运行的过程和第4篇驱动测试的那里是一样的,详见驱动测试部分。
启动脚本的方式大概参考了这两篇文章:
http://blog.csdn.net/jk110333/article/details/39529459
http://www.jianshu.com/p/20881a8b6e02
#!/bin/sh /etc/rc.common
# /init.d/car
START=100
start()
{
car_server &
}
stop()
{
car_server -s
}
restart()
{
car_server &
}
ln -s /etc/init.d/car /etc/rc.d/S100car
chmod -R 777 /etc/init.d/car #设置权限,否则无法激活开机启动,提示权限不足
/etc/init.d/car enable #激活开机启动
/etc/init.d/car start #运行start函数启动程序
这两步和上一步的效果差不多,都会在/etc/rc.d下生成链接。
重启后应用被成功自启动,只需要连接上WIFI就可以使用,不需要串口开启。
但是还是出现了点问题:上电后应用程序socket不到上位机。但这时如果再开启重新这个应用,上位机还是可以正常控制。初步判定可能是和应用软件启动和wifi相关的服务启动的顺序有关。
所以想了一个办法:软件启动后采用延时,推迟开启socket监听的时间的方式。然后就可以顺利监听了。