只是做做小的测试实验,何必杀鸡用牛刀,本着一切从简的原则。先在Milk-v Duo开发安装了Tiny cc(小得很100KB)占不了SD卡多少空间。这样我们就在Milk-v Duo开发板上编写、编译实验程序。
Tiny cc安装很简单不在本文的介绍范围内,网上大把资料自己去查。
言归正传,文主要介绍的是利用Milk-v Duo开发板上linux自带的spi驱动来进行一个自发\自收小实验。废话说多了,我们开始吧,“向前走一二一”!
一、检查Milk-v Duo的SPI驱动
1、确认查看Milk-v Duo的SPI是否启用
需要确保SPI总线已经可用。有“spidev”这样的节点,可以查看SPI设备驱动是否存在
$ ls /dev/spi*
/dev/spidev0.0 /dev/spidev0.1
2、需要确保SPIDEV的驱动程序正确加载。可以使用下面的命令查看:
$ find /sys/devices/ -name spidev
二、如果SPI总线不可用,需开启Milk-v Dou的SPI设备
1、修改设备树文件
./home/lich/duo-buildroot-sdk/build/boards/cv180x/cv1800b_sophpi_duo_sd/dts_riscv/cv1800b_sophpi_duo_sd.dts
添加以下内容
&spi2 {
status = "okay";
cs-gpios = <&porta 18 0>;
spidev@0{
status = "okay";
};
};
2、修改配置文件 (kernel 内核配置)
./home/lich/duo-buildroot-sdk/build/boards/cv180x/cv1800b_sophpi_duo_sd/linux/cvitek_cv1800b_sophpi_duo_sd_defconfig
添加以下内容
CONFIG_SPI_MASTER=y
CONFIG_SPI_SPIDEV=y
CONFIG_SPI_DESIGNWARE=y
CONFIG_SPI_DW_MMIO=y
3、修改板子启动文件 Pinmux设置
初始化SPI设备和引脚 int board_init(void)
./home/lich/duo-buildroot-sdk/u-boot-2021.10/board/cvitek/cv180x/board.c
在uboot init中编辑int board_init(void)函数似乎相对容易。在u-boot-2021.10/board/cvitek/cv180x/board.c:230处取消注释
//pinmux_config(PINMUX_SPI2);
4、重新编译和打包系统,输入一下命令,编译SDK
source build/cvisetup.sh
defconfig cv1800b_sophpi_duo_sd
build_all
然后输入以下命令打包镜像
pack_sd_image
每次打包都会生成一个新的img,可以使用
生成的固件位置: install/soc_cv1800b_milkv_duo_sd/milkv-duo.img
rm -rf install/soc_cv1800b_sophpi_duo_sd/*.img*来进行删除
5、烧录镜像,放到板子上运行
然后查看spi设备是否已启动,可以看到spi设备在/dev目录出现了; 此时 SPI2 对应的为 /dev/spidev0.0
以上位主备工作,现在完事具备了,我们开始拉风吧
三、本次SPI编程要实现的结果:
用线短接 Milk-V Duo 10脚(PIO22--SPI2_SDO--MOSI) 11脚(PWR_GPIO21--SPI2_SDI--MISO),实现
Milk-V Duo 的 spi通信的自发自收。
SPI编程实现:
1、编程思路:
①、使能内核SPI驱动模块抽象出spi设备
②、利用open系统调用打开spi设备 "/dev/spidev0.0"
③、利用ioctl设置spi设备各项参数
④、进行读写操作
2、实际操作与代码部分:
①、使能内核SPI驱动
ls /dev/spidev
②Milk-V Duo自发自收代码
3、代码实现
/*********************************************************************************
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "SPISet.h"
int initSPI()
{
int spiFd;
spiFd=SPISetupMode (0, 500000, 0) ; //初始化SPI通道0,设置为最大速度500000hz,设置为默认模式 0
if(spiFd==-1)
{
printf("init spi failed!\n");
return -1;
}
return 0;
}
int main()
{
char tx_Data[10]={1,2,3,4,5,6,7,8,9,10}; //定义读写的数据
char rx_Data[10]={10,9,8,7,6,5,4,3,2,1};
int i=0;
initSPI(); //spi的初始化
while(1)
{
SPIDataRW(0,tx_Data,rx_Data,7); //向总线中写入7个数据
printf("read spi_rx_data is:\n");
for(i=0;i<10;i++)
{
printf("%d\n",rx_Data[i]);
}
printf("\n");
sleep(1);
}
return 0;
}
/*********************************************************************************
********************************************************************************/
#include <stdint.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include "SPISet.h"
static const char *spiDev0 = "/dev/spidev0.0" ;
static const char *spiDev1 = "/dev/spidev0.1" ;
static uint8_t spiBPW = 8 ;
static uint16_t spiDelay = 0 ;
static uint32_t spiSpeeds [2] ;
static int spiFds [2] ;
/* SPIDataRW: 通过SPI总线写入和读取数据,这也是一个全双工操作*/
int SPIDataRW (int channel, unsigned char *tx_data, unsigned char rx_data, int len)
{
int i = 0;
struct spi_ioc_transfer spi ; //属性消息的封装,和内核中spi_transfer基本一样
channel &= 1 ;
memset (&spi, 0, sizeof (spi)) ; //初始化内存单元
spi.tx_buf = (unsigned long)tx_data ;
spi.rx_buf = (unsigned long)rx_data ;
spi.len = len ;
spi.delay_usecs = spiDelay ;
spi.speed_hz = spiSpeeds [channel] ;
spi.bits_per_word = spiBPW ;
return ioctl (spiFds [channel], SPI_IOC_MESSAGE(1), &spi) ;
}
/ SPISetupMode: 打开SPI设备,并对模式等进行设置. /
int SPISetupMode (int channel, int speed, int mode)
{
int fd ;
if ((fd = open (channel == 0 ? spiDev0 : spiDev1, O_RDWR)) < 0)
{
printf("Unable to open SPI device: %s\n", strerror (errno)) ;
return -1;
}
spiSpeeds [channel] = speed ;
spiFds [channel] = fd ;
/ 设置spi的读写模式:
- Mode 0: CPOL=0, CPHA=0
- Mode 1: CPOL=0, CPHA=1
- Mode 2: CPOL=1, CPHA=0
- Mode 3: CPOL=1, CPHA=1
- 这里我们默认设置为模式0 /
if (ioctl (fd, SPI_IOC_WR_MODE, &mode) < 0)
{
printf("Can't set spi mode: %s\n", strerror (errno)) ;
return -1;
}
if (ioctl (fd, SPI_IOC_RD_MODE, &mode) < 0)
{ printf("Can't get spi mode: %s\n", strerror (errno)) ;
return -1;
}
/ spi的读写bit/word设置可写 ; 这里设置为8个位为一个字节 /
if (ioctl (fd, SPI_IOC_WR_BITS_PER_WORD, &spiBPW) < 0)
{
printf("Can't set bits per word: %s\n", strerror (errno)) ;
return -1;
}
if (ioctl (fd, SPI_IOC_RD_BITS_PER_WORD, &spiBPW) < 0)
{
printf("Can't get bits per word: %s\n", strerror (errno)) ;
return -1;
}
/ 设置spi读写速率 *************************************************/
if (ioctl (fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0)
{
printf("Can't set max speed hz: %s\n", strerror (errno));
return -1;
}
if (ioctl (fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0)
{
printf("Can't get max speed hz: %s\n", strerror (errno));
return -1;
}
return fd ;
}
/*********************************************************************************
********************************************************************************/
#ifdef __cplusplus
extern "C" {
#endif
int SPIDataRW (int channel, unsigned char *tx_data,unsigned char *rx_data, int len) ;
int SPISetupMode (int channel, int speed, int mode) ;
#ifdef __cplusplus
}
#endif
四、编译
tcc -c SPISet.c -o SPISet.o
tcc -c spi_own.c -o spi_own.o
tcc spi_own.o SPISet.o 生成 a.out
五、程序运行结果:
执行
./a.out
1、MISO与MOSI未短接
2、MISO与MOSI短接