项目背景:
成人本科的论文选题是用golang做一个简易的嵌入式POS机应用, 支持扫zfb/wx的在线支付二维码, 所以用c封装了几个函数给golang使用. 那这里面又涉及到了另一个问题, 如何使用arm版golang.
在我前面的文章里有一篇如何去编译arm版golang, 但是就这个项目而言, 我忽略了一个问题: golang调用c代码的时候, 需要指定gcc, 而我所指定的gcc是amd64架构, 就算直接copy到arm板子上也不能用, 还需要编译arm版gcc, 这就很麻烦. 还好, 我们同样可以通过指定交叉编译器去交叉编译golang的代码, 这只需要通过一些简单的设置就能成功.
环境说明:
执行环境: Linux ubuntu 5.0.0-32-generic. 即我编译(执行go build)golang代码的环境.
目标环境: arm.
设置参数:
首先, 在编译之前, 要确保ubuntu下有golang环境(能执行go命令), 我的golang版本是:
go version go1.13.10 linux/amd64
golang的环境变量设置跟我前面如何编译arm版golang文章里设置的环境变量差不多, 如下:
export GO111MODULE=on
export GOPROXY=https://goproxy.io
export GOARM=7
export GOARCH=arm
export GOOS=linux
export CGO_ENABLED=1
export CC_FOR_TARGET=/opt/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin/arm-linux-gnueabihf-gcc
export CXX_FOR_TARGET=/opt/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin/arm-linux-gnueabihf-g++
export GOROOT=/usr/local/go
export GOPATH=/usr/local/gopath
主要是开启cgo和指定交叉编译器.
代码展示:
首先, 目录结构如下:
其中目录cdep/包含了scanDevice.h/scanDevice.c文件, 主要是指定波特率/数据位等参数, scandevice.go对外提供了一个包.
scanDevice.h代码如下:
#ifndef SCANDEVICE_H
#define SCANDEVICE_H
#include <sys/types.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdbool.h>
#define MSG_SIZE_MIN 20
#define MSG_SIZE_MAX 1024
typedef struct SerialInfo
{
unsigned char databit;
unsigned char stopbit;
speed_t rate;
}SerialInfo;
int32_t SetGetFD(int32_t rate, const char *file);
bool Read(int32_t fd, void *msg, int32_t msgSize);
void CloseFD(int32_t fd);
int32_t openSerail(const char *file);
int32_t setTermios(int32_t fd, const SerialInfo *serialInfo);
speed_t getBaudRate(const int32_t rate);
void setDataBit(unsigned char bit, struct termios *setting);
void setParity( unsigned char *str, struct termios *setting);
void setStopBit(unsigned char bit, struct termios *setting);
#endif
scanDevice.c代码如下:
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include "scanDevice.h"
// 设置获取FD
int32_t SetGetFD(int32_t rate, const char *file)
{
int32_t fd = 0;
if ((fd = openSerail(file)) == -1)
{
return -1;
}
SerialInfo serialInfo = {'8', '1', getBaudRate(rate)};
setTermios(fd, (const SerialInfo *)&serialInfo);
return fd;
}
// 从FD读数据
bool Read(int32_t fd, void *msg, int32_t msgSize)
{
if (msgSize < MSG_SIZE_MIN || msgSize > MSG_SIZE_MAX)
{
return false;
}
int32_t offset = 0;
while(true)
{
int32_t ret = read(fd, (unsigned char *)msg + offset, msgSize);
if (ret == -1) // 读取失败, 比如关闭FD
{
return false;
}
if (strstr((unsigned char *)msg, "\r\n") != NULL) // 此扫码枪会在数据的结尾增加\r\n, 我以此为判断条件
{
return true;
}
offset += ret;
}
}
// 关闭FD
void CloseFD(int32_t fd)
{
close(fd);
}
int32_t openSerail(const char *file)
{
return open(file, O_RDWR|O_NOCTTY);
}
int32_t setTermios(int32_t fd, const SerialInfo *serialInfo)
{
struct termios setting;
tcgetattr(fd, &setting);
//设置波特率
cfsetispeed(&setting, serialInfo->rate);
cfsetospeed(&setting, serialInfo->rate);
cfmakeraw(&setting);
setDataBit(serialInfo->databit, &setting);
setParity("none", &setting);
setStopBit(serialInfo->stopbit, &setting);
tcflush(fd, TCIFLUSH);
setting.c_cc[VTIME] = 0;
setting.c_cc[VMIN] = 1;
tcsetattr(fd, TCSANOW, &setting);
return 0;
}
// getBaudRate 获取波特率
speed_t getBaudRate(const int32_t rate)
{
switch (rate)
{
case 4800:
return B4800;
case 9600:
return B9600;
case 19200:
return B19200;
case 38400:
return B38400;
case 57600:
return B57600;
case 115200:
return B115200;
default:
return B115200;
}
}
// setDataBit 设置数据位
void setDataBit(unsigned char bit, struct termios *setting)
{
switch (bit)
{
case '8':
setting->c_cflag |= CS8;
break;
case '7':
setting->c_cflag |= CS7;
break;
case '6':
setting->c_cflag |= CS6;
break;
case '5':
setting->c_cflag |= CS5;
break;
default:
setting->c_cflag |= CS8;
break;
}
}
// setParity 设置parity
void setParity( unsigned char *str, struct termios *setting)
{
if (strcmp("odd",(char *)str) == 0)
{
setting->c_cflag |= (PARODD | PARENB);
setting->c_iflag |= INPCK;
}
else if (strcmp("even",(char *)str) == 0)
{
setting->c_cflag |= PARENB;
setting->c_cflag &= ~PARODD;
setting->c_iflag |= INPCK;
}
else // 默认, 可以填写str为"none"
{
setting->c_cflag &= ~PARENB;
setting->c_iflag &= ~INPCK;
}
}
// setStopBit 设置停止位
void setStopBit(unsigned char bit, struct termios *setting)
{
switch (bit)
{
case '1':
setting->c_cflag &= ~CSTOPB;
break;
case '2':
setting->c_cflag |= CSTOPB;
break;
default:
setting->c_cflag &= ~CSTOPB;
break;
}
}
scandevice.go代码如下:
package cdep
/*
#include <stdlib.h>
#include "scanDevice.h"
#cgo CFLAGS: -I./
*/
import "C"
import (
"reflect"
"unsafe"
)
// SetGetFD ..
func SetGetFD(rate int32, file string) int32 {
filePtr := C.CString(file)
defer C.free(unsafe.Pointer(filePtr))
return int32(C.SetGetFD(C.int(rate), filePtr))
}
// ReadFD ..
func ReadFD(fd int32, msg []byte, msgSize int32) bool {
return bool(C.Read(C.int(fd), unsafe.Pointer(reflect.ValueOf(msg).Pointer()), C.int(msgSize)))
}
// CloseFD ..
func CloseFD(fd int32) {
C.CloseFD(C.int(fd))
}
这部分golang代码, 有一点需要注意: 在调用C.CString()函数的时候, 会调用c的malloc函数, 记得用C.free释放.
scan.go代码主要是调用cdep的包, 如下:
package armscan
import (
"log"
"armscan/cdep"
)
// MacroMsgSize 宏定义
const MacroMsgSize = 1024
// Scan ..
func Scan() {
file := "/dev/ttymxc6"
fd := cdep.SetGetFD(9600, file)
if fd == -1 {
log.Println("SetGetFD failed.")
return
}
log.Println("SetGetFD success.")
defer cdep.CloseFD(fd)
for {
var msg = make([]byte, MacroMsgSize)
if !cdep.ReadFD(fd, msg, int32(MacroMsgSize)) {
log.Fatal("err")
}
log.Println(string(msg))
}
}
main.go就一行代码, 调用包armscan里的Scan()函数, 这里不再展示.
测试展示:
在支付宝上打开了KFC会员卡, 扫码结果跟实际的卡号一致, 如下:
结束.
c代码里的read函数可能需要根据你的需求来更改, 让它更趋于完善.