Beagleboneblack linux4.14 使用SPI

  • BBB中设备树的加载:

       Cape分为两种,使用eeprom(电可擦可编程只读存储器)和不使用eeprom。

扩展板带有eeprom:插上后板子上电就能用,这是因为位于0x54-0x57地址的eeprom告诉了系统启动时该对哪几个overlay进行重载,从而开机后系统就识别出了硬件。

扩展板不带eeprom:通过修改uEnv.txt,让系统开机后就加载对应的dtbo文件,同样达到了这个目的。

老版本的linux内核含有插槽功能,可以再运行时动态加载设备树,但是由于异常太多,没有人提供维护,在4.14版本的内核之后被移除,只能通过uEnv.txt加载设备树。

  • GPIO编号规则:
       GPIOn_x = 32*n+x

       GPIO1_6 = 32*1+6=38

  • BBB查看当前引脚功能(BBB_Header_Table):
cat /sys/kernel/debug/pinctrl/44e10800.pinmux/pins

由于经常使用,就添加到环境变量:

/etc/profile:export PINS=/sys/kernel/debug/pinctrl/44e10800.pinmux/pins
sudo echo  export PINS=/sys/kernel/debug/pinctrl/44e10800.pinmux/pins  >> /etc/profile
source /etc/profile
cat $PINS|grep 8b8

 

 

 

44e108b8:表示引脚地址

00000008:表示引脚功能,可以看出设置为MODE0使能上下拉。

可以看一下引脚功能配置,例如IIC:

Beagleboneblack linux4.14 使用SPI_第1张图片

其中pinctrl-single:表示偏移地址,BBB_Header_Table中如下图所示:

Beagleboneblack linux4.14 使用SPI_第2张图片

其中pins:表示引脚的功能配置,包括“快速模式”、“输入模式”、“上拉下拉”、“使能上拉下拉”、“端口复用模式”,如下图所示:

Beagleboneblack linux4.14 使用SPI_第3张图片

查看bb_view卖家提供的系统查看SPI1:

Beagleboneblack linux4.14 使用SPI_第4张图片

00000027 <=> 0010 0111 <=>输入模式、MODE7

表示设备树SPI1的引脚配置的是通用GPIO口,也表示已经禁用了HDMI,因为Beaglebone Black使用的AM3359芯片上有两个SPI,但SPI1连接到了板子的HDMI芯片上,所以除非禁用HDMI,否则我们只能使用SPI0。顺便看了一下P9_17\P9_18,设置的是IIC模式。

 

  • 设置GPIO口的工作模式和启动设备树:

在BeagleBoneblack的/lib/firmware文件夹下已经存在了编译好的overlay .dtbo设备树文件,只需要将其加载就可以完成配置,其dts文件在/opt/source/bb.org-overlays/src/arm文件夹下:

Beagleboneblack linux4.14 使用SPI_第5张图片

Beagleboneblack linux4.14 使用SPI_第6张图片

关于设备树的格式规则这篇博客讲的挺好:

Linux设备树(2)——设备树格式和使用:

https://www.cnblogs.com/hellokitty2/p/10992949.html

 

linux4.14内核的设备树加载由于删除了solt,只能在/boot/uEnv.txt文件上修改,在uboot引导启动时加载dtbo文件:

Beagleboneblack linux4.14 使用SPI_第7张图片

至于Overide capes with eeprom 、Additional custom capes 、Custom cape三个地方的dtbo文件有什么区别我不知道,但是经过实验,放在Overide capes with eeprom和Custom cape会导致设备树加载异常,显示如下错误,望高人不吝指点。

Beagleboneblack linux4.14 使用SPI_第8张图片

修改uEnv.txt后,重新启动BeagleBone,通过串口查看uboot的启动引导信息,查看是否加载设备树:

Beagleboneblack linux4.14 使用SPI_第9张图片

加载正常,没有报错。

进入系统,查看引脚配置是否正确:

之前已经配置过环境变量,所以cat $PINS 便可以打印所有引脚的工作模式:

两个SPI引脚的工作模式配置正确。

接着使用内核自带的spi测试程序spidev_test.c来进行测试:

在用来编译BeagleBone内核的源码中,linux-4.14\tools\spi文件夹下存在该文件,将其拷贝到BeagleBone上,gcc编译,进行测试.

Beagleboneblack linux4.14 使用SPI_第10张图片

按照该博客所示,将MISO和MOSI引脚用杜邦线连接起来,在BeagleBone中则是将D0和D1连接起来,进行测试:

使用Beaglebone BlackSPI

https://www.cnblogs.com/dolphi/p/3662297.html

Beagleboneblack linux4.14 使用SPI_第11张图片

然而在debian9.5-linux4.14版本的测试程序结果却令人失望:

并没有打印出测试数据。

一开始怀疑设备树写的问题,怀疑驱动和设备树没有匹配上,怀疑硬件电路有问题、搞了两天都怀疑人生了,走了一大段弯路,不过也颇有其他收获。最后尝试完网上的各种方法,未果,遂从源码入,看看源码中到底写了什么?接下来就蠢哭自己了:

而这个verbose变量在参数解析时指定:

Beagleboneblack linux4.14 使用SPI_第12张图片

所以:

./spidev_test -D /dev/spidev1.0 -v

是有数据的。最后附上带有注释的spidev_test.c,有注释的不对的地方还请指出:

/*
 * SPI testing utility (using spidev driver)
 *
 * Copyright (c) 2007  MontaVista Software, Inc.
 * Copyright (c) 2007  Anton Vorontsov 
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License.
 *
 * Cross-compile with cross-gcc -I/path/to/cross-kernel/include
 */

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

static void pabort(const char *s)
{
// void perror(const char *str) 把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格。
	perror(s);
// 异常终止一个进程
	abort();
}

static const char *device = "/dev/spidev1.1";
static uint32_t mode;
static uint8_t bits = 8;
static char *input_file;
static char *output_file;
static uint32_t speed = 500000;
static uint16_t delay;
static int verbose;
static int transfer_size;
static int iterations;
static int interval = 5; /* interval in seconds for showing transfer rate */

uint8_t default_tx[] = {		//要发送的数据数组
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0x40, 0x00, 0x00, 0x00, 0x00, 0x95,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xF0, 0x0D,
};

uint8_t default_rx[ARRAY_SIZE(default_tx)] = {0, };		//接受的数据数组
char *input_tx;

//二进制用十六进制显示
static void hex_dump(const void *src, size_t length, size_t line_size,
		     char *prefix)
{
	int i = 0;
	const unsigned char *address = src;
	const unsigned char *line = address;
	unsigned char c;

	printf("%s | ", prefix);
	while (length-- > 0) {
		printf("%02X ", *address++);
		if (!(++i % line_size) || (length == 0 && i % line_size)) {
			if (length == 0) {
				while (i++ % line_size)
					printf("__ ");
			}
			printf(" | ");  /* right close */
			while (line < address) {
				c = *line++;
				printf("%c", (c < 33 || c == 255) ? 0x2E : c);
			}
			printf("\n");
			if (length > 0)
				printf("%s | ", prefix);
		}
	}
}

/*
 *  Unescape - process hexadecimal escape character
 *      converts shell input "\x23" -> 0x23
 */
static int unescape(char *_dst, char *_src, size_t len)
{
	int ret = 0;
	int match;
	char *src = _src;
	char *dst = _dst;
	unsigned int ch;

	while (*src) {
		if (*src == '\\' && *(src+1) == 'x') {
			match = sscanf(src + 2, "%2x", &ch);
			if (!match)
				pabort("malformed input string");

			src += 4;
			*dst++ = (unsigned char)ch;
		} else {
			*dst++ = *src++;
		}
		ret++;
	}
	return ret;
}

static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
{
	int ret;
	int out_fd;
	struct spi_ioc_transfer tr = {
		.tx_buf = (unsigned long)tx,
		.rx_buf = (unsigned long)rx,
		.len = len,
		.delay_usecs = delay,
		.speed_hz = speed,
		.bits_per_word = bits,
	};

	if (mode & SPI_TX_QUAD)
		tr.tx_nbits = 4;
	else if (mode & SPI_TX_DUAL)
		tr.tx_nbits = 2;
	if (mode & SPI_RX_QUAD)
		tr.rx_nbits = 4;
	else if (mode & SPI_RX_DUAL)
		tr.rx_nbits = 2;
	if (!(mode & SPI_LOOP)) {
		if (mode & (SPI_TX_QUAD | SPI_TX_DUAL))
			tr.rx_buf = 0;
		else if (mode & (SPI_RX_QUAD | SPI_RX_DUAL))
			tr.tx_buf = 0;
	}
//实现在SPI总线接收/发送数据操作,其中n的值可变
//SPI_IOC_MESSAGE(1)的1表示spi_ioc_transfer的数量
//ioctl默认操作,传输数据,传输n个数据包 
	ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
	if (ret < 1)
		pabort("can't send spi message");
//显示tx数据,将发送出去的数据显示为16进制
	if (verbose)
//hexdump主要用来查看“二进制”文件的十六进制编码。*注意:它能够查看任何文件,不限于与二进制文件。*
		hex_dump(tx, len, 32, "TX");

	if (output_file) {
		out_fd = open(output_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
		if (out_fd < 0)
			pabort("could not open output file");

		ret = write(out_fd, rx, len);
		if (ret != len)
			pabort("not all bytes written to output file");

		close(out_fd);
	}
//将接收到的数据转化为16进制
	if (verbose)
		hex_dump(rx, len, 32, "RX");
}

static void print_usage(const char *prog)		//参数错误则打印帮助信息
{
	printf("Usage: %s [-DsbdlHOLC3vpNR24SI]\n", prog);
	puts("  -D --device   device to use (default /dev/spidev1.1)\n"
	     "  -s --speed    max speed (Hz)\n"
	     "  -d --delay    delay (usec)\n"
	     "  -b --bpw      bits per word\n"
	     "  -i --input    input data from a file (e.g. \"test.bin\")\n"
	     "  -o --output   output data to a file (e.g. \"results.bin\")\n"
	     "  -l --loop     loopback\n"
	     "  -H --cpha     clock phase\n"
	     "  -O --cpol     clock polarity\n"
	     "  -L --lsb      least significant bit first\n"
	     "  -C --cs-high  chip select active high\n"
	     "  -3 --3wire    SI/SO signals shared\n"
	     "  -v --verbose  Verbose (show tx buffer)\n"
	     "  -p            Send data (e.g. \"1234\\xde\\xad\")\n"
	     "  -N --no-cs    no chip select\n"
	     "  -R --ready    slave pulls low to pause\n"
	     "  -2 --dual     dual transfer\n"
	     "  -4 --quad     quad transfer\n"
	     "  -S --size     transfer size\n"
	     "  -I --iter     iterations\n");
	exit(1);
}

static void parse_opts(int argc, char *argv[])	//解析传进来的参数
{
	while (1) {
		static const struct option lopts[] = {   	//参数命令表
/*
"device":name,表示选项名称
"1":has_arg,表示选项后是否携带参数,该参数有三个不同的值:
   a: no_argument(或者是0)时 ——参数后面不跟参数值,eg: --version,--help
   b: required_argument(或者是1)时 ——参数输入格式为:--参数 值 或者 --参数=值。eg:--dir=/home
   c: optional_argument(或者是2)时  ——参数输入格式只能为:--参数=值
"0":flag,这个参数有两个意思,空或者非空
   a:如果参数为空NULL,那么当选中某个长选项的时候,getopt_long将返回val值。
     eg,可执行程序 --help,getopt_long的返回值为h.             
   b:如果参数不为空,那么当选中某个长选项的时候,getopt_long将返回0,并且将flag指针参数指向val值。
     eg: 可执行程序 --http-proxy=127.0.0.1:80 那么getopt_long返回值为0,并且lopt值为1。
"D":val,表示指定函数找到该选项时的返回值,或者当flag非空时指定flag指向的数据的值val。

*/

			{ "device",  1, 0, 'D' },
			{ "speed",   1, 0, 's' },
			{ "delay",   1, 0, 'd' },
			{ "bpw",     1, 0, 'b' },
			{ "input",   1, 0, 'i' },
			{ "output",  1, 0, 'o' },
			{ "loop",    0, 0, 'l' },
			{ "cpha",    0, 0, 'H' },
			{ "cpol",    0, 0, 'O' },
			{ "lsb",     0, 0, 'L' },
			{ "cs-high", 0, 0, 'C' },
			{ "3wire",   0, 0, '3' },
			{ "no-cs",   0, 0, 'N' },
			{ "ready",   0, 0, 'R' },
			{ "dual",    0, 0, '2' },
			{ "verbose", 0, 0, 'v' },
			{ "quad",    0, 0, '4' },
			{ "size",    1, 0, 'S' },
			{ "iter",    1, 0, 'I' },
			{ NULL, 0, 0, 0 },
		};
		int c;
/*
*	int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex); 
*	1、argc和argv和main函数的两个参数一致。
*	2、longopts:表示长选项结构体
*	3、optstring: 表示短选项字符串。
*    形式如“a:b::cd:“,分别表示程序支持的命令行短选项有-a、-b、-c、-d,冒号含义如下:
*   (1)只有一个字符,不带冒号——只表示选项, 如-c 
*   (2)一个字符,后接一个冒号——表示选项后面带一个参数,如-a 100
*   (3)一个字符,后接两个冒号——表示选项后面带一个可选参数,即参数可有可无,如果带参数,则选项与参数之间不能有空格形式应该如-b200
*	4、如果longindex非空,它指向的变量将记录当前找到参数符合longopts里的第几个元素的描述,即是longopts的下标值
*	5、返回值:如果选项成功找到,返回选项字母;如果所有命令行选项都解析完毕,返回 -1;如果遇到选项字符不在 optstring 中,返回字符 '?';如果遇到丢失参数,那么返回值依赖于 optstring 中第一个字符,如果第一个字符是 ':' 则返回':',否则返回'?'并提示出错误信息。
*/
		c = getopt_long(argc, argv, "D:s:d:b:i:o:lHOLC3NR24p:vS:I:",
				lopts, NULL);

		if (c == -1)
			break;

		switch (c) {
		case 'D':		//设备名
			device = optarg;
			break;
		case 's':		//速率
		//将optarg所指的字符串转化为一个int型整数
			speed = atoi(optarg);
			break;
		case 'd':		//延时时间
			delay = atoi(optarg);
			break;
		case 'b':		//每字含多少位
			bits = atoi(optarg);
			break;
		case 'i':		//输入数据,input data from a file
			input_file = optarg;
			break;
		case 'o':		//输出数据,output data to a file
			output_file = optarg;
			break;
		case 'l':		//回送模式
			mode |= SPI_LOOP;
			break;
		case 'H':		//时钟相位
			mode |= SPI_CPHA;
			break;
		case 'O':		//时钟极性
			mode |= SPI_CPOL;
			break;
		case 'L':		//lsb最低有效位
			mode |= SPI_LSB_FIRST;
			break;
		case 'C':		//片选高电平
			mode |= SPI_CS_HIGH;
			break;
		case '3':		//3线传输模式
			mode |= SPI_3WIRE;
			break;
		case 'N':		//没片选
			mode |= SPI_NO_CS;
			break;
		case 'v':
			verbose = 1;
			break;
		case 'R':		//从机拉低电平停止数据传输
			mode |= SPI_READY;
			break;
		case 'p':		//Send data
			input_tx = optarg;
			break;
		case '2':
			mode |= SPI_TX_DUAL;
			break;
		case '4':
			mode |= SPI_TX_QUAD;
			break;
		case 'S':		//transfer size
			transfer_size = atoi(optarg);
			break;
		case 'I':		//iterations
			iterations = atoi(optarg);
			break;
		default:
			print_usage(argv[0]);
			break;
		}
	}
	if (mode & SPI_LOOP) {
		if (mode & SPI_TX_DUAL)
			mode |= SPI_RX_DUAL;
		if (mode & SPI_TX_QUAD)
			mode |= SPI_RX_QUAD;
	}
}

static void transfer_escaped_string(int fd, char *str)
{
	size_t size = strlen(str);
	uint8_t *tx;
	uint8_t *rx;

	tx = malloc(size);
	if (!tx)
		pabort("can't allocate tx buffer");

	rx = malloc(size);
	if (!rx)
		pabort("can't allocate rx buffer");

//格式转化,str是input file的指针
	size = unescape((char *)tx, str, size);
	transfer(fd, tx, rx, size);
	free(rx);
	free(tx);
}

static void transfer_file(int fd, char *filename)
{
	ssize_t bytes;
	struct stat sb;
	int tx_fd;
	uint8_t *tx;
	uint8_t *rx;
//stat():获取文件信息
/*
    dev_t     st_dev;     /* ID of device containing file 文件使用的设备号
    ino_t     st_ino;     /* inode number    索引节点号 
    mode_t    st_mode;    /* protection   文件对应的模式,文件,目录等
    nlink_t   st_nlink;   /* number of hard links    文件的硬连接数  
    uid_t     st_uid;     /* user ID of owner     所有者用户识别号
    gid_t     st_gid;     /* group ID of owner    组识别号  
    dev_t     st_rdev;    /* device ID (if special file)  设备文件的设备号
    off_t     st_size;    /* total size, in bytes  以字节为单位的文件容量   
    blksize_t st_blksize; /* blocksize for file system I/O 包含该文件的磁盘块的大小   
    blkcnt_t  st_blocks;  /* number of 512B blocks allocated  该文件所占的磁盘块  
    time_t    st_atime;   /* time of last access 最后一次访问该文件的时间   
    time_t    st_mtime;   /* time of last modification 最后一次修改该文件的时间   
    time_t    st_ctime;   /* time of last status change 最后一次改变该文件状态的时间   
*/
	if (stat(filename, &sb) == -1)
		pabort("can't stat input file");

	tx_fd = open(filename, O_RDONLY);
	if (tx_fd < 0)
		pabort("can't open input file");

	tx = malloc(sb.st_size);
	if (!tx)
		pabort("can't allocate tx buffer");

	rx = malloc(sb.st_size);
	if (!rx)
		pabort("can't allocate rx buffer");

	bytes = read(tx_fd, tx, sb.st_size);
	if (bytes != sb.st_size)
		pabort("failed to read input file");
	//传输数据
	transfer(fd, tx, rx, sb.st_size);
	free(rx);
	free(tx);
	close(tx_fd);
}

static uint64_t _read_count;
static uint64_t _write_count;

//显示传输速率
static void show_transfer_rate(void)
{
	static uint64_t prev_read_count, prev_write_count;
	double rx_rate, tx_rate;

	rx_rate = ((_read_count - prev_read_count) * 8) / (interval*1000.0);
	tx_rate = ((_write_count - prev_write_count) * 8) / (interval*1000.0);

	printf("rate: tx %.1fkbps, rx %.1fkbps\n", rx_rate, tx_rate);

	prev_read_count = _read_count;
	prev_write_count = _write_count;
}

static void transfer_buf(int fd, int len)
{
	uint8_t *tx;
	uint8_t *rx;
	int i;

	tx = malloc(len);
	if (!tx)
		pabort("can't allocate tx buffer");
	for (i = 0; i < len; i++)
		tx[i] = random();

	rx = malloc(len);
	if (!rx)
		pabort("can't allocate rx buffer");

	transfer(fd, tx, rx, len);

	_write_count += len;
	_read_count += len;

	if (mode & SPI_LOOP) {
		if (memcmp(tx, rx, len)) {
			fprintf(stderr, "transfer error !\n");
			hex_dump(tx, len, 32, "TX");
			hex_dump(rx, len, 32, "RX");
			exit(1);
		}
	}

	free(rx);
	free(tx);
}

int main(int argc, char *argv[])
{
	int ret = 0;
	int fd;

	parse_opts(argc, argv);   //解析传进来的参数
//open函数用来打开一个设备,把硬件当做文件来进行操作,返回的是一个整型变量,
//如果这个值等于-1,说明打开文件出现错误,如果为大于0的值,那么这个值代表的就是文件描述符。
//在使用SPI设备时,需要调用open()函数打开设备文件,获得文件描述符
	fd = open(device, O_RDWR);
	if (fd < 0)
		pabort("can't open device");

	/*
	 * spi mode
	 */
/*
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制
int ioctl(int fd, ind cmd, …); 
    其中fd是用户程序打开设备时使用open函数返回的文件标示符,cmd是用户程序对设备的控制命令,至于后面的省略号,
    那是一些补充参数,一般最多一个,这个参数的有无和cmd的意义相关。 

*/
	ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);
	if (ret == -1)
		pabort("can't set spi mode");

	ret = ioctl(fd, SPI_IOC_RD_MODE32, &mode);
	if (ret == -1)
		pabort("can't get spi mode");

	/*
	 * bits per word
	 */
	ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
	if (ret == -1)
		pabort("can't set bits per word");

	ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
	if (ret == -1)
		pabort("can't get bits per word");

	/*
	 * max speed hz
	 */
	ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
	if (ret == -1)
		pabort("can't set max speed hz");

	ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
	if (ret == -1)
		pabort("can't get max speed hz");

	printf("spi mode: 0x%x\n", mode);
	printf("bits per word: %d\n", bits);
	printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);

	//只能选择一种发送数据源
	if (input_tx && input_file)
		pabort("only one of -p and --input may be selected");

	if (input_tx)
		transfer_escaped_string(fd, input_tx);
	else if (input_file)
		transfer_file(fd, input_file);
	else if (transfer_size) {
		struct timespec last_stat;
//用于计算时间,有秒和纳秒两种精度
/*
a、CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变
b、CLOCK_MONOTONIC,从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
c、CLOCK_PROCESS_CPUTIME_ID,本进程到当前代码系统CPU花费的时间
d、CLOCK_THREAD_CPUTIME_ID,本线程到当前代码系统CPU花费的时间
*/
		clock_gettime(CLOCK_MONOTONIC, &last_stat);

		while (iterations-- > 0) {
			struct timespec current;

			transfer_buf(fd, transfer_size);

			clock_gettime(CLOCK_MONOTONIC, ¤t);
			if (current.tv_sec - last_stat.tv_sec > interval) {
				show_transfer_rate();
				last_stat = current;
			}
		}
		printf("total: tx %.1fKB, rx %.1fKB\n",
		       _write_count/1024.0, _read_count/1024.0);
	} else
//如果没有指定输出,也没有接受到数据,则发送默认数据
		transfer(fd, default_tx, default_rx, sizeof(default_tx));

	close(fd);

	return ret;
}

 

你可能感兴趣的:(BeagleBone)