SPI apk->jni->ko的整体框架及部分代码介绍
SPI, 属于 串行外围设备接口, 是 Motorola 公司推出的一种同步串行接口技术. SPI 总线在物理上是通过接在外围设备微控制器(PICmicro) 上面的微处理控制单元 (MCU) 上叫作同步串行端口(Synchronous Serial Port) 的模块(Module)来实现的, 它允许 MCU 以全双工的同步串行方式, 与各种外围设备进行高速数据通信.
通常情况下,
我们只需要对上图所描述的四个管脚(pin) 进行编程即可控制整个 SPI 设备之间的数据通信:
SCK, Serial Clock, 主要的作用是 Master 设备往 Slave 设备传输时钟信号, 控制数据交换的时机以及速率;
CS, 用于 Master 设备片选 Slave 设备, 使被选中的 Slave 设备能够被 Master 设备所访问;
MOSI, 在 Master 上面也被称为 Tx-Channel, 作为数据的出口, 主要用于 SPI 主设备发送给SPI从设备的接口;
MISO, 在 Master 上面也被称为 Rx-Channel, 作为数据的入口, 主要用于SPI 主设备接收数据;
SPI 通信协议描述
总线工作方式有四种:
对应的CPOL和CPHA分别是时钟极性和时钟相位,我接下来采用的是模式3;
如何判断CPOL和CPHA
(2)如何判断CPHA:而数据采样时刻对应着的SCLK的电平,是第一个边沿还是第二个边沿,对应着CPHA为0还是1。


看完上面的spi基础现在贴我驱动的部分代码,首先我们要知道在驱动中是先去注册一个设备,通过spi -- spi_board_info和
spi message这两者
static struct spi_board_info pg6317_spi_board_devs[] __initdata =
{
[0] =
{
.modalias = DRVNAME_PG6137,
.max_speed_hz = 8*1000*1000, //8* 1000*1000, // 25MHz
.bus_num = 0,
.chip_select = 0,
.mode = SPI_MODE_3, //选择模式我模块是用的模式3,cpol=1,cpha=1;
.controller_data = &spi_conf_mt65xx,
},
};
#define GPIO_SPI_CS_PIN (GPIO65 | 0x80000000)
#define GPIO_SPI_SCK_PIN (GPIO66 | 0x80000000)
#define GPIO_SPI_MISO_PIN (GPIO67 | 0x80000000)
#define GPIO_SPI_MOSI_PIN (GPIO68 | 0x80000000)
void pg6137_hw_init(void)
{
mt_set_gpio_mode(GPIO_SPI_CS_PIN, 0);
mt_set_gpio_dir(GPIO_SPI_CS_PIN,GPIO_DIR_OUT);
mt_set_gpio_out(GPIO_SPI_CS_PIN, GPIO_OUT_ONE);
mt_set_gpio_mode(GPIO_SPI_SCK_PIN, 1);
mt_set_gpio_dir(GPIO_SPI_SCK_PIN,GPIO_DIR_OUT);
mt_set_gpio_pull_enable(GPIO_SPI_SCK_PIN, GPIO_PULL_ENABLE);
mt_set_gpio_pull_select(GPIO_SPI_SCK_PIN, GPIO_PULL_DOWN);
mt_set_gpio_mode(GPIO_SPI_MISO_PIN, 1);
mt_set_gpio_dir(GPIO_SPI_MISO_PIN,GPIO_DIR_IN);
mt_set_gpio_pull_enable(GPIO_SPI_MISO_PIN, GPIO_PULL_ENABLE);
mt_set_gpio_pull_select(GPIO_SPI_MISO_PIN, GPIO_PULL_DOWN);
mt_set_gpio_mode(GPIO_SPI_MOSI_PIN, 1);
mt_set_gpio_dir(GPIO_SPI_MOSI_PIN,GPIO_DIR_OUT);
mt_set_gpio_pull_enable(GPIO_SPI_MOSI_PIN, GPIO_PULL_ENABLE);
mt_set_gpio_pull_select(GPIO_SPI_MOSI_PIN, GPIO_PULL_DOWN);
printk("pg6137_hw_init Set Gpio Ok\n");
}
上面这段代码是我初始化我的spi口,我是用的是开发板默认spi口,但是需要注意我把cs端置为了普通IO口来控制,因为我在调试的时候花了很长时间去调试,我的mosi能够正常发送(示波器测到波形完全正常),CLK和CS都是正常的可miso总是接收错误的数据后面改了很久才知道是片选默认的时间不够,因此我设置为普通引脚在我读和写的时候分别延时,最终数据接收正确。
static ssize_t beidou_pg6317_write(struct file *filp, const char *buff, size_t len, loff_t * off)
{
char spi_dat[50];
int i;
int state = -1;
copy_from_user(spi_dat , buff , len);
#if 0
printk("********************* \n");
for(i=0;i<50;i++)
{
spi_dat[i]=cpu_to_be32(spi_dat[i]);
printk("0x%x",spi_dat[i]);
}
#endif
for(i = 0; i < len ; i++)
printk("spi_dat[%d]=%x---",i,spi_dat[i]);
printk(" \n");
mt_set_gpio_mode(GPIO_SPI_CS_PIN, 0);
mt_set_gpio_dir(GPIO_SPI_CS_PIN,GPIO_DIR_OUT);
mt_set_gpio_out(GPIO_SPI_CS_PIN, GPIO_OUT_ZERO);
udelay(1000); //增加延时的时间;
state = spi_writebuf(spi_device , spi_dat , len);
mt_set_gpio_mode(GPIO_SPI_CS_PIN, 0);
mt_set_gpio_dir(GPIO_SPI_CS_PIN,GPIO_DIR_OUT);
mt_set_gpio_out(GPIO_SPI_CS_PIN, GPIO_OUT_ONE);
udelay(1000);
printk("beidou_pg6317_write state=%d\n",state);
printk("beidou_pg6317_write ok\n");
if(state >= 0)
return len;
return -1;
}
int spi_writebuf(struct spi_device *spi , u8 *tarnsfer_data , u8 len)
{
struct spi_transfer pg6317_spi_xfer;
struct spi_message pg6317_spi_msg;
spi_message_init(&pg6317_spi_msg);
pg6317_spi_xfer.tx_buf = tarnsfer_data;
pg6317_spi_xfer.rx_buf = tarnsfer_data;
pg6317_spi_xfer.len = len;
pg6317_spi_xfer.bits_per_word = 8;
spi_message_add_tail(&pg6317_spi_xfer, &pg6317_spi_msg);
printk("spi_writebuf ok\n");
return spi_sync(spi, &pg6317_spi_msg);
}
static ssize_t beidou_pg6317_read(struct file *filp, char *buffer, size_t length,loff_t * offset)
{
u32 spi_dat[50];
u8 i;
int state = -1;
mt_set_gpio_mode(GPIO_SPI_CS_PIN, 0);
mt_set_gpio_dir(GPIO_SPI_CS_PIN,GPIO_DIR_OUT);
mt_set_gpio_out(GPIO_SPI_CS_PIN, GPIO_OUT_ZERO);
udelay(1000);
state = spi_readbuf(spi_device , spi_dat , length);
printk("beidou_pg6317_read state=%d\n",state);
mt_set_gpio_mode(GPIO_SPI_CS_PIN, 0);
mt_set_gpio_dir(GPIO_SPI_CS_PIN,GPIO_DIR_OUT);
mt_set_gpio_out(GPIO_SPI_CS_PIN, GPIO_OUT_ONE);
printk("********************* \n");
for(i=0;i<50;i++)
{
spi_dat[i]=cpu_to_be32(spi_dat[i]);
}
printk("********************* \n");
copy_to_user(buffer , (char *)spi_dat , length);
printk("beidou_pg6317 read ok\n");
if(state >= 0)
return length;
return -1;
}
int spi_readbuf(struct spi_device *spi , u8 *tarnsfer_data , u8 receive_len)
{
int read_state = 0;
struct spi_transfer pg6317_spi_xfer;
struct spi_message pg6317_spi_msg;
spi_message_init(&pg6317_spi_msg);
pg6317_spi_xfer.tx_buf = tarnsfer_data;
pg6317_spi_xfer.rx_buf = tarnsfer_data;
pg6317_spi_xfer.len = receive_len;
pg6317_spi_xfer.bits_per_word = 8;
spi_message_add_tail(&pg6317_spi_xfer, &pg6317_spi_msg);
read_state = spi_sync(spi, &pg6317_spi_msg);
printk("spi_readbuf ok\n");
return read_state;
}
static int SPI_Init(struct spi_device *db)
{
struct mt_chip_conf* spi_par; // added 2015-12-3
spi_par = (struct mt_chip_conf *) db->controller_data;
spi_par->setuptime = 15;//20;//15;
spi_par->holdtime = 15;//20;//15;
spi_par->high_time = 100; //500; // 2 //10--6m 15--4m 20--3m 30--2m [ 60--1m 120--0.5m 300--0.2m]
spi_par->low_time = 100; //500; //2
spi_par->cs_idletime = 20; //25; //20
// spi_par->ulthgh_thrsh = 0;
// spi_par->sample_sel = 0;
// spi_par->cs_pol = 0;
spi_par->rx_mlsb = 1;
spi_par->tx_mlsb = 1;
spi_par->tx_endian = 0;
spi_par->rx_endian = 0;
spi_par->cpol = 1; //0;
spi_par->cpha = 1; // 0;
spi_par->com_mod = DMA_TRANSFER;
spi_par->pause = 1;
spi_par->finish_intr = 1;
spi_par->deassert = 0;
spi_setup(db);
}
spi_par为从设备变量spi_device,相关参数如下:
2. rx_mlsb & tx_mlsb:传输数据时,先从低bit发送还是高bit发送,1时代表先从低bit发送
3. tx_endian/rx_endian: 传输数据时,以大端模式传输或者小端模式传输,只对DMA传输有效,0时,代表小端模式传
输; fifo mode为小端模式。
4.com_mod: DMA或者fifo 传输,如下图设定即可
5. cpol:时钟极性选择
–CPOL=0,空闲电平为低电平,即高电平时输入数据有效
–CPOL=1时,空闲电平为高电平,即低电平时输入数据有效
6. cpha:时钟相位选择位
–CPHA=0,在奇次边沿采样,即每个周期的前沿采样,后沿输出
–CPHA=1,在偶次边沿采样,即每个周期的后沿采样,前沿输出
现在我们来看看jni层干了些什么?
JNIEXPORT jint JNICALL Java_com_keymantek_serialport_utils_TesamSPI_init(JNIEnv *env, jobject thiz)
{
int ret = -1;
int fd_state=0,i=0,read_long=0,write_count = 0;
fd_fps = open("/dev/pg6317_spi", O_RDWR);
LOGE("open fps add by ccy %d!\n",fd_fps);
if(fd_fps < 0)
{
LOGE("open error %d!\n",fd_fps);
return ret;
}
ret =0;
return ret;
}
首先是一个打开设备的函数
JNIEXPORT jint JNICALL Java_com_keymantek_serialport_utils_TesamSPI_deInit(JNIEnv *env, jobject thiz)
{
int ret;
LOGE("deInit is comming by ccy!\n");
LOGE("close fps add by ccy %d!\n",fd_fps);
close(fd_fps);
g_fd = -1;
fd_fps = -1;
if(fd_fps <=0)
{
ret = 0;
}
else{
ret = -1;
LOGE("close fps and sri failed!\n");
}
return ret;
}
close(fd)
JNIEXPORT jint JNICALL Java_com_keymantek_serialport_utils_TesamSPI_sendData
(JNIEnv *env, jobject obj,jbyteArray data,jint offset,jint length)
{
struct spi_info spi;
jint m;
jint j;
jint n;
jint l;
LOGE("ioctrll is enter by ccy!\n");
jint ver = 0;
jint ret = -1;
unsigned char ccy[length];
unsigned char array[length];
(*env)->GetByteArrayRegion(env,data,0,length,array);
LOGE("FPS1196 length is by ccy %d\n",length);
for(l=0;l<length;l++)
{
memcpy(&ccy[l],&array[offset+l],1);
}
for(n=0;n<length;n++)
{
LOGE("FPS1196 ccydata[%d] is by ccy %x\n",n,ccy[n]);
}
memcpy(spi.tx,ccy,length);//copy到spi.tx去我使用write写给我的设备;
spi.count = length;
spi.off = offset;
for(m = 0;m <length;m++)
{
LOGE("FPS1196 txdata[%d] is by ccy %x\n",m,spi.tx[m]);
}
if(write(fd_fps, spi.tx, spi.count) > 0)
{
LOGE("add by ccy1 for ioctl to spi IOCTL_FPS_WRITE%d!\n",fd_fps);
ret = length;
}
else{
LOGE("fps is close by ccy failed!\n" );
}
return ret;
}
写函数;
JNIEXPORT jint JNICALL Java_com_keymantek_serialport_utils_TesamSPI_recvData
(JNIEnv *env, jobject obj,jbyteArray data,jint offset)
{
struct spi_info spi;
jint m;
jint j;
jint n;
jint l;
int len;
LOGE("ioctrll is enter by ccy!\n");
jint ver = 0;
jint ret = -1;
unsigned char *pBuffer = (*env)->GetByteArrayElements(env,data,NULL);
jsize arrayLength = (*env)->GetArrayLength(env, data);
len = (int) arrayLength;
if(pBuffer == NULL)
{
LOGE("GetByteArrayElements failed add by ccy!");
return;
}
LOGE("Go read!");
if( read(fd_fps, spi.rx, len) > 0)
{
LOGE("add by ccy1 for ioctl to spi IOCTL_FPS_READ %d!\n",len);
ret=len;
}
else{
LOGE("fps is close by ccy failed!\n" );
}
//read(fd_fps, spi.rx, sizeof(spi.rx));
//ret=sizeof(spi.rx);
for(l=3;l<len;l++)
{
memcpy(&pBuffer[l-3+offset],&spi.rx[0+l],1);//由于我读回来模块前3个字节是乱的所以我在这里需要去掉
}
for(j = 0;j < len ; j++)
{
// LOGE("FPS1196_READ rxdata[%d] is by ccy %x\n",j,spi.rx[j]);
LOGE("FPS1196_READ pBuffer[%d] is by ccy %x\n",j,pBuffer[j]);
}
(*env)->ReleaseByteArrayElements(env,data,pBuffer,0);
LOGE("ccy is end11!!!\n");
LOGE("ccy is end!!!\n");
return ret;
}
读函数;最后就是apk调用我的open.write.read函数来进行读写操作了;
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.button1:
int a=tesamSPI.Open();
Log.d("ccy", "init()="+a);
int b=tesamSPI.Read( buf, 0, 8);
Log.d("ccy", "sendData()="+b);
int c=tesamSPI.Rece( rx,0);
Log.d("ccy", "Rece()="+c);
Log.d("ccy", "发送"+bytesToHexString(rx));
int d=tesamSPI.Close();
Log.d("ccy", "Close()="+d);
tv.setText("接收"+bytesToHexString(rx));
startBtn.setEnabled(false);
stopBtn.setEnabled(true);
break;
case R.id.button2:
//Log.e("ccy", "init");
tv.setText("");
startBtn.setEnabled(true);
stopBtn.setEnabled(false);
break;
default:
break;
}
}
差不多我的spi从底层到顶层的框架就是这样了
对了我是以字节形式输出的所以需要写一个字节转十六进制的代码这个百度即可!