MT7688双摄像头双电机驱动小车(6)应用软件

关于这篇主要是运行在MT7688上的应用程序的实现。

这部分程序很早之前写的,后来没想到要放出来,所以一开始也没什么设计可言,就是想到哪里写哪里。最后为了能拿的出手,代码改了一部分,还有一部分因为懒,就都没改了,所以算一个四不像体。如果要改很多地方要做重构,电脑配置和时间有限就跳过这一步。
多亏叽叽叽老司机提醒,终于不输出只输出到终端了,有的以日志形式输出。这样即使后台运行也可以看到状态了。但是转化太麻烦就省略了= =

主目录:

http://blog.csdn.net/DFSAE/article/details/78715815


本篇目录

一.设计

二.实现

1.线程实现
2.socket通信
3.通信协议的打包和解析
4.电机控制
5.摄像头控制
6.串口转发控制

三.makefile解析

四.编译,运行

五.设置为上电启动


一.设计

这里没有具体的设计,只有大概的设计思路。
整个软件分为5个部分,包括电机控制,摄像头控制,串口转发控制这三部分底层和socket网络通信,协议的打包及解析这里通信部分。
大概存在3个线程:socket通信线程,电机控制线程和连接超时线程(这个本来想用定时器的,但是没找到定时器的依赖库= =)。

1>.socket通信线程

程序主要在socket线程的监听上位机发送下来的数据中度过。当收到数据后进行解析。然后执行响应的动作。应用软件最核心的部分就在这里了。另外这里的电机操作正常情况是需要分开来的,后面解释为什么,如果做一起就说明比较懒了。

2>.电机控制线程

这个线程里面处理上位机发下来的电机控制信号,实现对电机的输出。这就是操作分离开来的部分,后面有详细解释。

3>.连接超时线程

其实就是一个定时器的功能,保证在一定时间没有接收上位机数据(连接断开)后,重新进行socket监听,以便进行新的连接。正常情况下,上位机应该隔一段时间就发下连接数据,以免断开连接。

*注:另外,这里IP地址暂时先定死,由前面的配置的ip为准:192.168.55.1,默认端口号为1234。两个摄像头的端口号分别为8090和8070。

二.实现

自顶向下开始分析实现。
注:这里要现在用命令行建一个car_server的文件夹,里面放个car_server.log的日志文件。

1.线程实现

在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);   
    }
} 

2.socket通信

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;
}

2.通信协议的打包和解析

1>.数据解析

这里有个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);            //回调
}
2>.数据发送

这里的设计很烂(当时太随便写了)。 我好像把发送套到具体类中了。所以我打算简单用个信号量分离一下。
这部分的接口是这样的,各个模块的内部数据则由每个模块内部自己打包,自己的数据打包完后,调用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;

}

3.电机控制

电机操作其实可以在解析出电机控制的协议后直接控制,但这样会存在一个问题——如果上位机发下来两次控制的电机参数差异过大。会瞬间改变PWM或者方向,容易造成硬件损伤。所以,最好这里加一个改变缓冲。如果上位机那边做控制突变的控制也可以避免这种情况。最好是MT7688上也做一层保护。可以用记录对比上一次控制值的方式进行控制。
这样就需要我们从数据解析到控制这里进行解耦,具体方式如下:
{创建一个电机参数列表。当解析到电机控制数据后把控制数据扔到里面,如果要做突变控制就可以在这里增加。然后单独开一条线程,也就是前面的电机控制线程。这个线程里检测电机参数列表是否有新的数据,一旦有新的数据就取出最早的一组电机参数进行控制,然后再经过休眠20ms后控制下一组。}
然而还是让上位机做范围控制吧。这里就做最简单的。

1>.如果要麻烦的那一种,就需要列表的数据结构,以4个字节为一组。这里实现就省略了。
2>.因为电机的操作方式比较多(协议上就有三种,速度控制的还没做),所以要加数据结构控制一下。

这里用一个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;
3>.分解电机操作

解析到数据后的操作离不开这两/三步:
(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];
}
4>.实现电机基础功能

除去速度需要做闭环控制留空,其他做了实现。

/*************************************************
* 函数名: 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
}

5.摄像头控制

摄像头方面,是直接控制1个或2个(看开几个摄像头)摄像头进程来控制。

1>.下面两个函数实现了基本对摄像头的开或关。
/*************************************************
* 函数名: 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;
    }   
}
2>.协议处理

摄像头协议处理过程如下,这里的通信应答其实都是成功的。

/*************************************************
* 函数名: 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];
}

6.串口转发控制

串口使用的是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解析

和源目录同级的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

1>.在/etc/init.d/目录下写了名为car的脚本:
#!/bin/sh  /etc/rc.common
# /init.d/car
START=100
start()
{
        car_server &
}

stop()
{
        car_server  -s
}

restart()
{
        car_server &
}
2>.然后执行了连接命令,使它在/etc/rc.d下产生脚本连接,开机时会自动运行

ln -s /etc/init.d/car /etc/rc.d/S100car

3>.再执行下面命令

chmod -R 777 /etc/init.d/car #设置权限,否则无法激活开机启动,提示权限不足
/etc/init.d/car enable #激活开机启动
/etc/init.d/car start #运行start函数启动程序
这两步和上一步的效果差不多,都会在/etc/rc.d下生成链接。

4>.重启

重启后应用被成功自启动,只需要连接上WIFI就可以使用,不需要串口开启。

但是还是出现了点问题:上电后应用程序socket不到上位机。但这时如果再开启重新这个应用,上位机还是可以正常控制。初步判定可能是和应用软件启动和wifi相关的服务启动的顺序有关。

所以想了一个办法:软件启动后采用延时,推迟开启socket监听的时间的方式。然后就可以顺利监听了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值