随着城市化进程的加快,新能源汽车呼之欲出。在笔者所在的城市,力帆和长安等公司都先后推出了可供市民租用的电动汽车。在享受租车所带来的便利的同时,汽车的充电就成了一个难题,在城市道路和停车场安装充电桩成为解决这个问题的一个有效的手段。汽车充电时,需要有一个可用于停靠的车位,并且这个车位只能在汽车充电的时候使用,在其他时间都不能被占用。也就是说,每个充电的车位需要有一个车位锁,当有汽车充电的时候,车位锁打开,否则车位锁关闭。
本文用嵌入式开发板实现了对车位锁开关的控制,可供相关项目的开发人员参考。
一、硬件选型
在嵌入式开发板这块,我们选择了天嵌科技的TQ335X开发板,如下图所示:
在车位锁这块,我们选择了泊享网络的带RS485的车位锁,如下图所示:
二、电路连接
嵌入式开发板通过RS485方式和车位锁相连,RS485的电路原理图如下所示:
如上图所示,电路为TTL转RS485电路,通过跳线SW5接口相应配置,使之与串口2相连。
为了实现485通信功能,板载SW5的跳线选择方式如下图所示:
对于车位锁与开发板的连接,要将车位锁的红色连接线(+)与485的A端相连,将车位锁的黄色连接线(-)与485的B端相连。
三、软件开发环境
由于嵌入式开发板使用的是裁剪版的Linux(ubuntu),因此需要在办公电脑(windows系统)上安装对应的交叉编译环境。
首先,我们要安装Vmware虚拟机,笔者使用的虚拟机版本如下图所示:
接着,我们要将嵌入式系统配备的资料光盘中的裁剪版的Linux(ubuntu)系统安装到虚拟机上,以供编译程序时使用。笔者使用的ubuntu版本如下图所示:
在安装了ubuntu之后,还要将交叉编译器装载到系统中。交叉编译器的安装过程可以参看开发板使用手册,只要按照手册中的步骤(如下图所示)安装就可以了。
四、车位锁协议
我们使用程序通过向车位锁下发指令来控制其开关并获取其状态。车位锁的协议如下图所示:
具体而言,各命令的样例如下:
1.开锁命令:55 ADDR 01 01 CRC AA
接收成功返回 5A ADDR 02 01 01 CRC AA
接收失败返回 5B ADDR 03 01 01 00 CRC AA
2.闭锁命令:55 ADDR 01 02 CRC AA
接收成功返回 5A ADDR 02 02 01 CRC AA
接收失败返回 5B ADDR 03 02 01 00 CRC AA
3.读取锁状态:55 ADDR 01 06 CRC AA
执行成功返回 5A ADDR 02 06 STATUS CRC AA
接收失败返回 5B ADDR 03 06 01 00 CRC AA
注:STATUS 各状态为:00-闭锁状态,01-开锁状态,02-中间状态0~90°,03-中间状态90~180°,88-运动状态,10-当前开锁状态并且检测到上面无车。
五、程序编写
有了开发环境,有了协议,那么接下来我们就可以开始编写程序了。根据我们在第二部分的描述,程序要通过串口来连接RS485电路,因此,程序的大体流程为:初始化串口—>向串口发消息—>接收串口的响应消息—>关闭串口。
完整的C代码(LockCtrl.h和LockCtrl.c)如下所示:
LockCtrl.h代码内容:
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> //文件控制定义
#include <termios.h> //终端控制定义
#include <errno.h>
#define DEVICE "/dev/ttySAC2"
int init_serial(void); // 打开串口并初始化设置
int uart_send(int fd, char *data, int datalen); // 串口发送数据
int uart_recv(int fd, char *data, int datalen); // 串口接收数据
int OpenLock(void); // 开车位锁
int CloseLock(void); // 关车位锁
int GetLockStatus(void); // 获取车位锁 状态
LockCtrl.c代码内容:
#include "LockCtrl.h"
int serial_fd = 0;
// 打开串口并初始化设置
int init_serial(void)
{
serial_fd = open(DEVICE, O_RDWR | O_NOCTTY | O_NDELAY);
if (serial_fd < 0)
{
perror("open");
return -1;
}
//串口主要设置结构体termios <termios.h>
struct termios options;
/**1. tcgetattr函数用于获取与终端相关的参数。
* 参数fd为终端的文件描述符,返回的结果保存在termios结构体中
*/
tcgetattr(serial_fd, &options);
/*2. 修改所获得的参数*/
options.c_cflag |= (CLOCAL | CREAD); // 设置控制模式状态,本地连接,接收使能
options.c_cflag &= ~CSIZE; // 字符长度,设置数据位之前一定要屏掉这个位
options.c_cflag &= ~CRTSCTS; // 无硬件流控
options.c_cflag |= CS8; // 8位数据长度
options.c_cflag &= ~CSTOPB; // 1位停止位
options.c_iflag |= IGNPAR; // 无奇偶检验位
options.c_oflag = 0; // 输出模式
options.c_lflag = 0; // 不激活终端模式
cfsetospeed(&options, B9600); // 设置波特率
/**3. 设置新属性,TCSANOW:所有改变立即生效*/
tcflush(serial_fd, TCIFLUSH); // 溢出数据可以接收,但不读
tcsetattr(serial_fd, TCSANOW, &options);
return 0;
}
/**
*串口发送数据
*@fd:串口描述符
*@data:待发送数据
*@datalen:数据长度
*/
int uart_send(int fd, char *data, int datalen)
{
int len = 0;
len = write(fd, data, datalen); // 实际写入的长度
printf("uart_send:len=%d,datalen=%d\n", len, datalen);
if (len == datalen) // 发送成功
{
return len;
}
else
{
tcflush(fd, TCOFLUSH); // TCOFLUSH刷新写入的数据但不传送
return -1;
}
// return 0;
}
/**
*串口接收数据
*要求启动后,在pc端发送ascii文件
*/
int uart_recv(int fd, char *data, int datalen)
{
int len = 0, ret = 0;
fd_set fs_read;
struct timeval tv_timeout;
FD_ZERO(&fs_read);
FD_SET(fd, &fs_read);
tv_timeout.tv_sec = (10*20/115200+2);
tv_timeout.tv_usec = 0;
ret = select(fd+1, &fs_read, NULL, NULL, &tv_timeout);
printf("ret = %d\n", ret); //如果返回0,代表在描述符状态改变前已超过timeout时间,错误返回-1
if (FD_ISSET(fd, &fs_read))
{
len = read(fd, data, datalen);
printf("len = %d\n", len);
return len;
}
else
{
perror("select");
return -1;
}
// return 0;
}
/**
*开车位锁
*/
int OpenLock(void)
{
int iDataLen = 0;
int iRetVal = 0;
char szRcvBuf[20] = {0};
unsigned char szOpen[6] = {0x55,0x01,0x01,0x01,0x9A,0xAA}; // 55 01 01 01 9A AA
iRetVal = init_serial();
if (iRetVal != 0)
{
printf("OpenLock: exec init_serial failed!\n");
return -1;
}
iDataLen = uart_send(serial_fd, szOpen, sizeof(szOpen));
if (iDataLen == -1)
{
printf("OpenLock: exec uart_send failed!\n");
return -1;
}
iDataLen = uart_recv(serial_fd, szRcvBuf, sizeof(szRcvBuf));
if (iDataLen == -1)
{
printf("OpenLock: exec uart_recv failed!\n");
return -1;
}
printf("OpenLock: received len=%d\n", iDataLen);
if (szRcvBuf[0] == 0x5A)
{
printf("OpenLock: open lock success!\n");
}
else if (szRcvBuf[0] == 0x5B)
{
printf("OpenLock: open lock failed!\n");
}
else
{
printf("OpenLock: received data is wrong!\n");
}
close(serial_fd);
return 0;
}
/**
*关车位锁
*/
int CloseLock(void)
{
int iDataLen = 0;
int iRetVal = 0;
char szRcvBuf[20] = {0};
unsigned char szClose[6] = {0x55,0x01,0x01,0x02,0x78,0xAA}; // 55 01 01 02 78 AA
iRetVal = init_serial();
if (iRetVal != 0)
{
printf("CloseLock: exec init_serial failed!\n");
return -1;
}
iDataLen = uart_send(serial_fd, szClose, sizeof(szClose));
if (iDataLen == -1)
{
printf("CloseLock: exec uart_send failed!\n");
return -1;
}
iDataLen = uart_recv(serial_fd, szRcvBuf, sizeof(szRcvBuf));
if (iDataLen == -1)
{
printf("CloseLock: exec uart_recv failed!\n");
return -1;
}
printf("CloseLock: received len=%d\n", iDataLen);
if (szRcvBuf[0] == 0x5A)
{
printf("CloseLock: close lock success!\n");
}
else if (szRcvBuf[0] == 0x5B)
{
printf("CloseLock: close lock failed!\n");
}
else
{
printf("CloseLock: received data is wrong!\n");
}
close(serial_fd);
return 0;
}
/**
*获取车位锁 状态
*/
int GetLockStatus(void)
{
int iDataLen = 0;
int iRetVal = 0;
char szRcvBuf[20] = {0};
unsigned char szStatus[6] = {0x55,0x01,0x01,0x06,0x19,0xAA}; // 55 01 01 06 19 AA
iRetVal = init_serial();
if (iRetVal != 0)
{
printf("GetLockStatus: exec init_serial failed!\n");
return -1;
}
iDataLen = uart_send(serial_fd, szStatus, sizeof(szStatus));
if (iDataLen == -1)
{
printf("GetLockStatus: exec uart_send failed!\n");
return -1;
}
iDataLen = uart_recv(serial_fd, szRcvBuf, sizeof(szRcvBuf));
if (iDataLen == -1)
{
printf("GetLockStatus: exec uart_recv failed!\n");
return -1;
}
printf("GetLockStatus: received len=%d\n", iDataLen);
if (szRcvBuf[0] == 0x5A)
{
printf("GetLockStatus: get lock status success!\n");
if (szRcvBuf[4] == 0x00)
{
printf("Locked!\n");
}
else if (szRcvBuf[4] == 0x01)
{
printf("Opened!\n");
}
else if (szRcvBuf[4] == 0x02)
{
printf("Middle status(0~90)!\n");
}
else if (szRcvBuf[4] == 0x03)
{
printf("Middle status(90~180)!\n");
}
else if (szRcvBuf[4] == 0x88)
{
printf("Running status!\n");
}
else if (szRcvBuf[4] == 0x10)
{
printf("Opened and no car on it!\n");
}
}
else if (szRcvBuf[0] == 0x5B)
{
printf("GetLockStatus: Get lock status failed!\n");
}
else
{
printf("GetLockStatus: GetLockStatus: received data is wrong!\n");
}
close(serial_fd);
return 0;
}
// 主函数
int main(int argc, char **argv)
{
int iRetVal = 0;
// 开锁
iRetVal = OpenLock();
if (iRetVal != 0)
{
printf("Open lock failed!\n");
return -1;
}
// 闭锁
sleep(10);
iRetVal = CloseLock();
if (iRetVal != 0)
{
printf("Close lock failed!\n");
return -1;
}
// 开锁
sleep(10);
iRetVal = OpenLock();
if (iRetVal != 0)
{
printf("Open lock failed!\n");
return -1;
}
// 获取车位锁 状态
sleep(10);
iRetVal = GetLockStatus();
if (iRetVal != 0)
{
printf("Get lock status failed!\n");
return -1;
}
return 0;
}
六、程序编译及运行
将LockCtrl.c和LockCtrl.h文件拷贝到交叉编译环境上,并使用“arm-linux-gcc -g -o LockCtrl LockCtrl.c”命令编译程序,生成LockCtrl文件。
接着,通过FTP工具或“ftpget -u XXX -p XXX 10.10.10.10 LockCtrl”命令将LockCtrl文件上传到嵌入式开发板的特定目录下,修改权限之后执行“./LockCtrl”命令,可以看到程序正常运行,车位锁成功执行了开关操作。程序的输出如下:
[root@EmbedSky local]# ./LockCtrl
uart_send:len=6,datalen=6
ret = 1
len = 7
OpenLock: received len=7
OpenLock: open lock success!
uart_send:len=6,datalen=6
ret = 1
len = 7
CloseLock: received len=7
CloseLock: close lock success!
uart_send:len=6,datalen=6
ret = 1
len = 7
OpenLock: received len=7
OpenLock: open lock success!
uart_send:len=6,datalen=6
ret = 1
len = 7
GetLockStatus: received len=7
GetLockStatus: get lock status success!
Opened!
七、总结
本文介绍了通过嵌入式开发板控制车位锁开关的流程,并给出了完整的C代码实现。对于嵌入式开发,大家不仅要懂得如何编写程序,还要了解开发板的电路原理及接线方式,这对开发者提出了更高的要求。