spi slave及master接口驱动及传输时序.docx
- 文档编号:15239423
- 上传时间:2023-07-02
- 格式:DOCX
- 页数:12
- 大小:19.36KB
spi slave及master接口驱动及传输时序.docx
《spi slave及master接口驱动及传输时序.docx》由会员分享,可在线阅读,更多相关《spi slave及master接口驱动及传输时序.docx(12页珍藏版)》请在冰点文库上搜索。
spislave及master接口驱动及传输时序
spislave及master接口驱动及传输时序
spislave驱动
spislave驱动在kernel中可以主要参考spidev.c,这是一个字符驱动,可以匹配kernel中的多个名称为“spidev”的spi设备,
分析这个文件,主要有以下几个重点:
1.如何编写多设备公用驱动
2.如何封装读写请求到spi框架层
3.spimessage请求如何分发到master
自spi_board_info或者spimaster注册后,两者就已经完成了匹配的工作,spislave驱动不关心任何匹配的细节,它只需要完成
与spislave的匹配,就可以通过slave进而找到master。
这里是通过
spi_register_driver(&spidev_spi_driver);注册进
kernel,而后spi框架进行namematch,再调用probe,完成关于设备的一些成员初始化操作。
下面针对上面的三个问题,进行分析这个驱动,
spi设备全局链及保护信号量:
staticLIST_HEAD(device_list);
staticDEFINE_MUTEX(device_list_lock);
相对与设备的驱动数据:
structspidev_data{
dev_tdevt;//设备号
spinlock_tspi_lock;//spi结构体的pin锁
structspi_device*spi;
structlist_headdevice_entry;//挂接到device_list
structmutexbuf_lock;//保护数据的lock
unsignedusers;//使用者
u8*buffer;//实际数据区,由open时进行动态分配,release时释放
};
spi中任何会由多个使用者访问的区域,都需要使用锁保护,如这里的users,个人觉得需要使用原子变量而不应该简单的使用整形。
在probe的时候,首先分配spidev_data,并初始化其
spi/device_entry/buf_lock/spi_lock,查找一个可用的bit用作次
设备号,创建设备spidevbusnum.cs,挂到全局链中,并将私有数据spidev_data放到dev->p->driver_data中。
open时,从inode中获取dev_t,然后对比整个链,找到目标数据spidev_data,放到file->private_data中,并分配缓存
读写时,直接从file中获取对应的spidev_data数据,然后通过spidevice来传递spi请求。
以上主要是数据如何传递的问题。
SPI读写请求的封装很简单,如下:
staticinlinessize_tspidev_sync_write(structspidev_data*spidev,size_tlen){
structspi_transfert={
.tx_buf=spidev->buffer,
.len=len,
};
structspi_messagem;
spi_message_init(&m);
spi_message_add_tail(&t,&m);
returnspidev_sync(spidev,&m);}staticinlinessize_tspidev_sync_read(structspidev_data*spidev,size_tlen){
structspi_transfert={
.rx_buf=spidev->buffer,
.len=len,
};
structspi_messagem;
spi_message_init(&m);
spi_message_add_tail(&t,&m);
returnspidev_sync(spidev,&m);}封装的同步函数:
staticssize_tspidev_sync(structspidev_data*spidev,structspi_message*message){DECLARE_COMPLETION_ONSTACK(done);
intstatus;
message->context=
spin_lock_irq(&spidev->spi_lock);
if(spidev->spi==NULL)
status=-ESHUTDOWN;
else
status=spi_async(spidev->spi,message);
spin_unlock_irq(&spidev->spi_lock);
if(status==0){
status=message->status;
if(status==0)
status=message->actual_length;}returnstatus;}只需要调用spi_async就可以完成数据读取/写入的操作。
这个函数在内部真正做了什么?
如何分发/回调?
我们走一遍代码:
首先master内部有两个锁:
spinlock_tbus_lock_spinlock;【用于异步】spi_async
structmutexbus_lock_mutex;【用于同步】spi_sync
对于不同的场景,需要对master进行不同类型的加锁,
异步:
spin_lock_irqsave(&master->bus_lock_spinlock,flags);
ret=__spi_async(spi,message);{message->spi=spi;
message->status=-EINPROGRESS;
returnmaster->transfer(spi,message);}spin_unlock_irqrestore(&master->bus_lock_spinlock,flags);
同步:
message->context=
if(!
bus_locked)
mutex_lock(&master->bus_lock_mutex);
status=spi_async_locked(spi,message);
if(!
bus_locked)
mutex_unlock(&master->bus_lock_mutex);
if(status==0){
status=message->status;}这里即在kernel内部完成了同步的工作,不需要像spidev那样需要自己等待完成量,使用的是bus_lock_mutex
内部与异步的调用方式一致:
spin_lock_irqsave(&master->bus_lock_spinlock,flags);
ret=__spi_async(spi,message);
spin_unlock_irqrestore(&master->bus_lock_spinlock,flags);
从这里可以看出,同步与异步没有本质差别,只是多了一个完成量的操作而已。
最终调用的函数为:
master->transfer(spi,message);
这个函数将在spimaster中分析。
在spislave侧需要熟悉传输的参数的每个域的功能,才能很好的完成工作
structspi_transfer{
constvoid*tx_buf;//非dma发送地址
void*rx_buf;//非dma读取地址
unsignedlen;//tx/rxbufffersize
dma_addr_ttx_dma;//若
spi_message.is_dma_mapped置位,为transfer的dmaaddress
dma_addr_trx_dma;//若
spi_message.is_dma_mapped置位,为read的dmaaddress
unsignedcs_change:
1;//传输完成后,修改cs信号
u8bits_per_word;//长度,优先覆盖spi_board_info的设置
(32)
u16delay_usecs;//传输后继续传输或者cs结束传输的中间时隙
u32speed_hz;//本次传输的速度,可以优先覆盖spi_board_info里的设置
structlist_headtransfer_list;//挂接到spi_message上的连接体};
structspi_message{
structlist_headtransfers;//transfer链
structspi_device*spi;//对应的spi设备
unsignedis_dma_mapped:
1;//是否启动dma功能
void*context;//回调参数
unsignedactual_length;//传输的真正长度
intstatus;//0,成功
structlist_headque;//driver使用
void*state;
};
每个域的使用方法,这里直接看起来并不明确,必须结合master的驱动。
---------------------------------------------------------------------------------------------------------
spimaster驱动
---------------------------------------------------------------------------------------------------------
SPI设备资源:
staticstructresources3c_spi0_resource[]={
[0]=DEFINE_RES_MEM(S3C24XX_PA_SPI,SZ_32),
[1]=DEFINE_RES_IRQ(IRQ_SPI0),
};
structplatform_devices3c_device_spi0={
.name="s3c2410-spi",
.id=0,
.num_resources=ARRAY_SIZE(s3c_spi0_resource),
.resource=s3c_spi0_resource,
.dev={
.dma_mask=&samsung_device_dma_mask,
.coherent_dma_mask=DMA_BIT_MASK
(32),}};
staticstructresources3c_spi1_resource[]={
[0]=DEFINE_RES_MEM(S3C24XX_PA_SPI1,SZ_32),
[1]=DEFINE_RES_IRQ(IRQ_SPI1),
};
structplatform_devices3c_device_spi1={
.name="s3c2410-spi",
.id=1,
.num_resources=ARRAY_SIZE(s3c_spi1_resource),
.resource=s3c_spi1_resource,
.dev={
.dma_mask=&samsung_device_dma_mask,
.coherent_dma_mask=DMA_BIT_MASK
(32),}};
在此之间,走过了一些弯路学了verilog/modelsim,在之前一直不明白的事情在逐渐的尝试中获得了新的认识,
硬件的ipcore的工作是由clock来驱动的,而不是软件意义上的过程,在同步时钟的上升/下降沿中进行数据处理,移位等
在SPI的ipcore设计中,主要有三个模块:
1.clockgenerate
2.datashift
3.registercontrol
首先通过AP过来的系统时钟及设备能够支持的最大时钟频率,计算出对应的最接近的分频系数,而模块1就是根据这个分频系数
来通过系统的源clock产生对应的目标clock。
【因为对于SPIIP不需要独立的精确的晶振】
生成了与slave同步的clock之后,输出到模块2,模块2负责具体的发送数据功能。
具体的采数发数时序见第三节。
而registercontrol则负责所有的可配置接口,如:
分频系数,支持位宽,FIFO深度,支持的片选数,以及相应的MSB/LSB
设置选项。
在SPI的协议中,最大的误区在于master与slave之间的私有协议:
SPImaster的本生设计中并不支持具体的传输协议,而是简单的提供了一个传输数据的通路,而协议则是由实现的slave端,以及
slave端驱动来决定的。
下面来总结这个数据发送与接收的具体过程:
读指定地址:
read(addr,&value,len);
1.配置相关读取操作的寄存器
2.slave驱动封装协议CMD【描述base+addr+len+flags】
3.拉下slave对应在master上的cs【低电平有效】
4.发送指定位宽cmd到slave端【由master来驱动,而slave只需要发送数据到对应fifo并启动发送即可】
5.slave端接收到指定的cmd,在MISO线上回应对应的数据
6.slave驱动从master的fifo中等待数据,当master读取到对应的线上数据,并放于FIFO中
7.读取到数据,拉高CS
/*makesureleniswordunits*/
intgps_spi_read_bytes_test3(u32len,u32addr,u32*data,boolsys,u32base){
u32read_cmd[2];
local_spi_init();
read_cmd[0]=SPI_READ_CMD(len,addr>>2+base);
spi_assert_function
(0);
local_spi_write((u8*)read_cmd,4);
local_spi_read((u8*)data,4);
spi_assert_function
(1);
return0;}写指定地址:
write(addr,&value,len);
intgps_spi_write_bytes_test2(u32len,u32addr,u32data){
u32write_cmd[2],one_read;
local_spi_init();
write_cmd[0]=SPI_WRITE_SYS_CMD(len*1,addr>>2);
write_cmd[1]=data;
spi_assert_function
(0);
local_spi_write((u8*)write_cmd,8);
spi_assert_function
(1);
return0;}一些读写的实现细节:
intlocal_spi_read(u8*buf,u32size){
/*waitfordata*/
while(reg->sts2&(0x1<<5)){
if(time_out++>READ_TIME_OUT){
gotoread_exit;}}
*p=(u32)reg->txd;
p++;
/*iftxfifoisnotfull*/
while(reg->sts2&(0x1<<6));
writel(*p,SPRD_SPI1_BASE);
p++;
/*bitlenis4*/
以上为master驱动需要实现传输的一些流程
---------------------------------------------------------------------------------------------------------
spi传输时序问题
---------------------------------------------------------------------------------------------------------
主要关注两个参数:
CPOL:
Clock初始电平(0:
低电平,1:
高电平)
CPHA:
采样位置(0:
第一个跳变边沿,1:
第二个跳变边沿):
上升沿采样,下降沿发送:
下降沿采样,上升沿发送:
下降沿采样,上升沿发送:
上升沿采样,下降沿发送
这里需要注意,在双工状态下,不可同沿采样及发送,必须错开。
采样的基本原理如下:
master与slave保持时钟同步,master每输出一个时钟,master输出一个数据(MOSI线),slave便响应一个数据(MISO线),在示波器上
可以看到三条线的数据交互过程。
setuptime与holdtime【假设上升沿采样】
建立时间:
是指在时钟信号上升沿到来以前,数据稳定不变的时间,如果建立时间不够,数据将不能在这个时钟上升沿被打入触发器;
保持时间:
是指在时钟信号上升沿到来以后,数据稳定不变的时间,如果保持时间不够,数据同样不能被打入触发器。
首先当第一个数据发送时master的寄存器首先锁住这个值,当第一个上升沿到来时,master获取数据,slave收到并Hold住固定时间,
同理如slave的数据接收.
相关问题
---------------------------------------------------------------------------------------------------------
一个由时钟倍频引起的SPI同步思考:
always@(posedgeclk_inorposedgerst)
begin
if(rst)
else
begin
else
end
end
//clk_outisassertedeveryotherhalfperiod
always@(posedgeclk_inorposedgerst)
begin
if(rst)
clk_out<=#Tp1'b0;
else
clk_out;
end
当divider为3时,计算的过程为:
||||||||||||
->2->1->0->3->2->1->0->3
->2->1->0->3
三线模式SPI:
用一个data线,来代替MOSI/MISO两根线,使用分时复用
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- spi slave及master接口驱动及传输时序 slave master 接口 驱动 传输 时序