项目背景:

    成人本科的论文选题是用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和指定交叉编译器.

代码展示:

    首先, 目录结构如下:

    

gocd arm架构镜像 arm golang_linux

    其中目录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会员卡, 扫码结果跟实际的卡号一致, 如下:

gocd arm架构镜像 arm golang_arm_02

结束.

    c代码里的read函数可能需要根据你的需求来更改, 让它更趋于完善.