RT-thread 设备驱动组件之SPI设备
本文主要介绍RT-thread中的SPI设备驱动,涉及到的文件主要有:驱动框架文件(spi_dev.c,spi_core.c,spi.h),底层硬件驱动文件(spi_hard.c,spi_hard.h)。这里spi_hard.c和spi_hard.h是指利用MCU的硬件SPI接口,而不是通过GPIO口来模拟SPI时序。应用SPI设备驱动时,需要在rtconfig.h中宏定义#define RT_USING_SPI。
一、SPI设备驱动框架
先来看spi.h中的一些数据结构:
**
* SPI message structure
*/
struct rt_spi_message
{
const void *send_buf;
void *recv_buf;
rt_size_t length;
struct rt_spi_message *next; unsigned cs_take : ;
unsigned cs_release : ;
}; /**
* SPI configuration structure
*/
struct rt_spi_configuration
{
rt_uint8_t mode;
rt_uint8_t data_width;
rt_uint16_t reserved; rt_uint32_t max_hz;
}; struct rt_spi_ops;
struct rt_spi_bus
{
struct rt_device parent;
const struct rt_spi_ops *ops; struct rt_mutex lock;
struct rt_spi_device *owner;
}; /**
* SPI operators
*/
struct rt_spi_ops
{
rt_err_t (*configure)(struct rt_spi_device *device, struct rt_spi_configuration *configuration);
rt_uint32_t (*xfer)(struct rt_spi_device *device, struct rt_spi_message *message);
};
/**
* SPI Virtual BUS, one device must connected to a virtual BUS
*/
struct rt_spi_device
{
struct rt_device parent;
struct rt_spi_bus *bus; struct rt_spi_configuration config;
};
#define SPI_DEVICE(dev) ((struct rt_spi_device *)(dev))
spi_core.c,spi_dev.c这两个文件位于RTT\components\drivers\spi目录下,而spi.h头文件位于RTT\\components\drivers\include\drivers目录下。可在MKD工程的Drivers组下将上面两个源文件加进行,并将spi.h头文件所在目录添加到工程的include path下。
spi_core.c文件实现了spi的抽象操作,如注册spi总线(spi_bus),向SPI总线添加设备函数等。注: 这里将MCU的一路spi外设虚拟成spi总线,然后总线上可以挂很多spi设备(spi_device),一个spi_device有一个片选cs。spi总线和spi设备要在RTT中可以生效就必须先向RTT注册,因此就需要使用上面的注册SPI总线函数和向SPI总线中添加SPI设备。
spi_core.c还包含了配置SPI函数,发送和接收等通信函数,占用和释放SPI总线函数及选择SPI设备函数。这些函数都是抽象出来的,反映出SPI总线上的一些常规操作。真正执行这些操作的过程并不在spi_core.c源文件中,实际上,这些操作信息都是通过注册SPI总线和向总线添加SPI设备时这些操作集就已经"注册"下来了,真正操作时是通过注册信息内的操作函数去实现,也可以说是一种回调操作。spi_core.c中实现的函数主要有:rt_spi_bus_register(); rt_spi_bus_attach_device(); rt_spi_configure(); rt_spi_send_then_send(); rt_spi_send_then_recv(); rt_spi_transfer(); rt_spi_transfer_message(); rt_spi_take_bus(); rt_spi_release_bus(); rt_spi_take(); rt_spi_release()。
而spi_dev.c实现了SPI设备的一些抽象操作,比如读,写,打开,关闭,初始化等,当然当MCU操作SPI设备的时候,是需要通过SPI总线与SPI设备进行通信的,既然通信就必然会有SPI通信协议,但是通信协议并不在这里具体,spi_dev.c这里还只是SPI设备的抽象操作而已,它只是简单地调用spi_core.c源文件中的抽象通信而已,具体实现还是要靠上层通过SPI总线或SPI设备注册下来的信息而实现的。spi_device.c中实现的函数主要有:_spi_bus_device_read(); _spi_bus_device_write(); _spi_bus_device_control(); rt_spi_bus_device_init();_spidev_device_read();_spidev_device_write();_spidev_device_control();rt_spidev_device_init()。
在确保了spi_core.c,spi_dev.c和spi.h这三个源文件在MDK工程内之后,接着往下走。
二、底层硬件驱动
在spi_hard.c中实现configure和xfer函数(默认没有使用DMA):
static struct rt_spi_ops stm32_spi_ops =
{
configure,
xfer
};
然后,向RT-thread注册spi总线:
struct stm32_spi_bus
{
struct rt_spi_bus parent; SPI_TypeDef * SPI; #ifdef SPI_USE_DMA
DMA_Stream_TypeDef * DMA_Stream_TX;
uint32_t DMA_Channel_TX; DMA_Stream_TypeDef * DMA_Stream_RX;
uint32_t DMA_Channel_RX; uint32_t DMA_Channel_TX_FLAG_TC;
uint32_t DMA_Channel_RX_FLAG_TC;
#endif /* #ifdef SPI_USE_DMA */
}; struct stm32_spi_cs
{
GPIO_TypeDef * GPIOx;
uint16_t GPIO_Pin;
};
rt_err_t stm32_spi_register(SPI_TypeDef * SPI,
struct stm32_spi_bus * stm32_spi,
const char * spi_bus_name)
{
if(SPI == SPI1)
{
stm32_spi->SPI = SPI1;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//84MHZ #ifdef SPI_USE_DMA
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
/* DMA2_Stream0 DMA_Channel_3 : SPI1_RX ; DMA2_Stream2 DMA_Channel_3 : SPI1_RX */
stm32_spi->DMA_Stream_RX = DMA2_Stream0;
stm32_spi->DMA_Channel_RX = DMA_Channel_3;
stm32_spi->DMA_Channel_RX_FLAG_TC = DMA_FLAG_TCIF0;
/* DMA2_Stream3 DMA_Channel_3 : SPI1_TX ; DMA2_Stream5 DMA_Channel_3 : SPI1_TX */
stm32_spi->DMA_Stream_TX = DMA2_Stream3;
stm32_spi->DMA_Channel_TX = DMA_Channel_3;
stm32_spi->DMA_Channel_TX_FLAG_TC = DMA_FLAG_TCIF3;
#endif
}
else if(SPI == SPI2)
{
stm32_spi->SPI = SPI2;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//42MHZ #ifdef SPI_USE_DMA
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
/* DMA1_Stream3 DMA_Channel_0 : SPI2_RX */
stm32_spi->DMA_Stream_RX = DMA1_Stream3;
stm32_spi->DMA_Channel_RX = DMA_Channel_0;
stm32_spi->DMA_Channel_RX_FLAG_TC = DMA_FLAG_TCIF3;
/* DMA1_Stream4 DMA_Channel_0 : SPI2_TX */
stm32_spi->DMA_Stream_TX = DMA1_Stream4;
stm32_spi->DMA_Channel_TX = DMA_Channel_0;
stm32_spi->DMA_Channel_TX_FLAG_TC = DMA_FLAG_TCIF4;
#endif
}
else if(SPI == SPI3)
{
stm32_spi->SPI = SPI3;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);//42MHZ #ifdef SPI_USE_DMA
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
/* DMA1_Stream2 DMA_Channel_0 : SPI3_RX ; DMA1_Stream0 DMA_Channel_0 : SPI3_RX */
stm32_spi->DMA_Stream_RX = DMA1_Stream2;
stm32_spi->DMA_Channel_RX = DMA_Channel_0;
stm32_spi->DMA_Channel_RX_FLAG_TC = DMA_FLAG_TCIF2;
/* DMA1_Stream5 DMA_Channel_0 : SPI3_TX ; DMA1_Stream7 DMA_Channel_0 : SPI3_TX */
stm32_spi->DMA_Stream_TX = DMA1_Stream5;
stm32_spi->DMA_Channel_TX = DMA_Channel_0;
stm32_spi->DMA_Channel_TX_FLAG_TC = DMA_FLAG_TCIF5;
#endif
}
else
{
return RT_ENOSYS;
} return rt_spi_bus_register(&stm32_spi->parent, spi_bus_name, &stm32_spi_ops);
}
最后,进行spi硬件初始化,并挂载spi设备到已注册的spi总线。
int rt_hw_spi1_init(void)
{
/* register SPI bus */
static struct stm32_spi_bus stm32_spi; //it must be add static /* SPI1 configure */
{
GPIO_InitTypeDef GPIO_InitStructure; /* Enable GPIO Periph clock */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA , ENABLE); GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; /* Configure SPI1 pins */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
} /* SPI1 configuration */ /* register SPI1 to stm32_spi_bus */
stm32_spi_register(SPI1, &stm32_spi, "spi1"); /* attach spi10 */
{
static struct rt_spi_device rt_spi_device_10; //it must be add static
static struct stm32_spi_cs stm32_spi_cs_10; //it must be add static stm32_spi_cs_10.GPIOx = GPIOE;
stm32_spi_cs_10.GPIO_Pin = GPIO_Pin_3; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_SetBits(GPIOE, GPIO_Pin_3); rt_spi_bus_attach_device(&rt_spi_device_10, "spi10", "spi1", (void*)&stm32_spi_cs_10);//set spi_device->bus
/* config spi */
{
struct rt_spi_configuration cfg;
cfg.data_width = ;
cfg.mode = RT_SPI_MODE_3 | RT_SPI_MSB; /* SPI Compatible Modes 3 and SPI_FirstBit_MSB in lis302dl datasheet */ //APB2=168M/2=84M, SPI1 = 84/2,4,8,16,32 = 42M, 21M, 10.5M, 5.25M, 2.625M ...
cfg.max_hz = ; /* SPI_BaudRatePrescaler_16=84000000/16=5.25MHz. The max_hz of lis302dl is 10MHz in datasheet */
rt_spi_configure(&rt_spi_device_10, &cfg);
} /* config spi */
} /* attach spi10 */ return ;
}
INIT_BOARD_EXPORT(rt_hw_spi1_init);//rt_hw_spi1_init will be called in rt_components_board_init()
三、SPI设备初始化
这里以lis302dl三轴加速度计为例:
static rt_err_t lis302dl_init(const char * spi_device_name)
{
rt_uint8_t chip_id, ctrl, temp; spi_device = (struct rt_spi_device *)rt_device_find(spi_device_name);
if(spi_device == RT_NULL)
{
rt_kprintf("\nspi_device %s for lis302dl not found!\n", spi_device_name);
return -RT_ENOSYS;
} // /* If not use rt_device_write or rt_device_read, then it's no necessary to rt_device_open */
// /* oflag has no meaning for spi device , so set to RT_NULL */
// if(rt_device_open(&spi_device->parent, RT_NULL) != RT_EOK)
// {
// rt_kprintf("\nspi_device %s for lis302dl opened failed!\n", spi_device_name);
// return -RT_EEMPTY;
// } LIS302DL_InitTypeDef LIS302DL_InitStruct;
LIS302DL_FilterConfigTypeDef LIS302DL_FilterStruct;
/* Set configuration of LIS302DL*/
LIS302DL_InitStruct.Output_DataRate = LIS302DL_DATARATE_100;
LIS302DL_InitStruct.Power_Mode = LIS302DL_LOWPOWERMODE_ACTIVE;
LIS302DL_InitStruct.Full_Scale = LIS302DL_FULLSCALE_2_3;
LIS302DL_InitStruct.Self_Test = LIS302DL_SELFTEST_NORMAL;
LIS302DL_InitStruct.Axes_Enable = LIS302DL_XYZ_ENABLE;
LIS302DL_Init(&LIS302DL_InitStruct);
/* MEMS High Pass Filter configuration */
LIS302DL_FilterStruct.HighPassFilter_Data_Selection = LIS302DL_FILTEREDDATASELECTION_OUTPUTREGISTER;
LIS302DL_FilterStruct.HighPassFilter_Interrupt = LIS302DL_HIGHPASSFILTERINTERRUPT_1_2;
LIS302DL_FilterStruct.HighPassFilter_CutOff_Frequency = LIS302DL_HIGHPASSFILTER_LEVEL_1;
LIS302DL_FilterConfig(&LIS302DL_FilterStruct); /* not use internal high pass filter and INT2 */
ctrl=0x04;//enable INT1 Data ready interrupt; interrupt active high; pull-push;
LIS302DL_Write(&ctrl, LIS302DL_CTRL_REG3_ADDR, );
LIS302DL_Read(&temp, LIS302DL_CTRL_REG3_ADDR, );
if(temp == ctrl)
rt_kprintf("the LIS302DL_CTRL_REG3_ADDR(value 0x%02x) verify passed!\n", temp);
else
rt_kprintf("the LIS302DL_CTRL_REG3_ADDR(value 0x%02x) verify failed!\n", temp); /* Required delay for the MEMS Accelerometre: Turn-on time = 3/Output data Rate = 3/100 = 30ms in datasheet */
//rt_thread_delay(30);
extern void stm32_mdelay(rt_uint32_t ms);
stm32_mdelay(); /* power_mode is active */
LIS302DL_Read(&chip_id, LIS302DL_WHO_AM_I_ADDR, );
rt_kprintf("(chip_id of lis302dl is 0x%02x)", chip_id); return ;
} int rt_lis302dl_init(void)
{
rt_sem_init(&sem_lis302dl, "lis302dl", , RT_IPC_FLAG_FIFO); lis302dl_interrupt_int1(); lis302dl_init("spi10"); return ;
}
INIT_APP_EXPORT(rt_lis302dl_init);
注意事项:
1、若需要使用rt_device_read()或rt_device_write()函数,则必须先调用rt_device_open()打开spi设备,保证该设备的ref_count大于0。硬件初始化函数中不需要调用rt_device_open()打开spi总线,因为在rt_spi_bus_attach_device()函数中没有初始化bus->owner,从而会导致调用_spi_bus_device_read()或_spi_bus_device_write()时“RT_ASSERT(bus->owner != RT_NULL);”断言语句进入死循环。而 _spidev_device_read()或_spidev_device_write()中断言语句“RT_ASSERT(device->bus != RT_NULL);”正常通过。
2、在使用SPI设备驱动操作数字芯片的寄存器时,需谨慎使用rt_device_read()和rt_device_write()函数。因为根据spi读写时序,spi读写一次最少要连续操作2个字节数据(第一个为寄存器地址值,第二个为待读取或待写入的字节数据),并且在这2个字节数据之间CS信号不能拉高,而rt_device_read()和rt_device_write()函数仅操作一个字节后,cs信号拉高,导致字节数据不能正常读取或写入相应寄存器。所以,一般情况下在SPI工作在全双工模式时,读写数字芯片寄存器的函数中直接使用spi_core.c中的rt_spi_transfer()、rt_spi_send_then_recv()、rt_spi_send_then_send()三个函数,如下所示:
void LIS302DL_Write(rt_uint8_t* pBuffer, rt_uint8_t WriteAddr, rt_uint16_t NumByteToWrite)
{
/* Configure the MS bit:
- When 0, the address will remain unchanged in multiple read/write commands.
- When 1, the address will be auto incremented in multiple read/write commands.
*/
if(NumByteToWrite > 0x01)
{
WriteAddr |= (rt_uint8_t)MULTIPLEBYTE_CMD;
} /* the CS can't pull up between &WriteAddr and pBuffer */
//rt_device_write(&spi_device->parent, RT_NULL, &WriteAddr, 1);
//rt_device_write(&spi_device->parent, RT_NULL, pBuffer, NumByteToWrite);
rt_spi_send_then_send(spi_device, &WriteAddr, , pBuffer, NumByteToWrite);// transfer NumByteToWrite+1 bytes
} void LIS302DL_Read(rt_uint8_t* pBuffer, rt_uint8_t ReadAddr, rt_uint16_t NumByteToRead)
{
/* Configure the MS bit:
- When 0, the address will remain unchanged in multiple read/write commands.
- When 1, the address will be auto incremented in multiple read/write commands.
*/
if(NumByteToRead > 0x01)
{
ReadAddr |= (rt_uint8_t)(READWRITE_CMD | MULTIPLEBYTE_CMD);
}
else
{
ReadAddr |= (rt_uint8_t)READWRITE_CMD;
} /* the CS can't pull up between &WriteAddr and pBuffer */
//rt_device_write(&spi_device->parent, RT_NULL, &ReadAddr, 1);
//rt_device_read(&spi_device->parent, RT_NULL, pBuffer, NumByteToRead);
rt_spi_send_then_recv(spi_device, &ReadAddr, , pBuffer, NumByteToRead);// transfer NumByteToRead+1 bytes
}
3、对于可读取寄存器值的数字芯片,在写入字节数据后可通过读取相同寄存器,判断读出的值与写入的值是否一致,从而判断寄存器写操作是否正确,如下:
void LIS302DL_Init(LIS302DL_InitTypeDef *LIS302DL_InitStruct)
{
rt_uint8_t ctrl = 0x00; /* Configure MEMS: data rate, power mode, full scale, self test and axes */
ctrl = (rt_uint8_t) (LIS302DL_InitStruct->Output_DataRate | LIS302DL_InitStruct->Power_Mode | \
LIS302DL_InitStruct->Full_Scale | LIS302DL_InitStruct->Self_Test | \
LIS302DL_InitStruct->Axes_Enable); /* Write value to MEMS CTRL_REG1 regsister */
LIS302DL_Write(&ctrl, LIS302DL_CTRL_REG1_ADDR, ); rt_uint8_t temp = 0x00;
LIS302DL_Read(&temp, LIS302DL_CTRL_REG1_ADDR, );
if(temp == ctrl)
rt_kprintf("\nthe LIS302DL_CTRL_REG1_ADDR(value 0x%02x) verify passed!\n", temp);
else
rt_kprintf("\nthe LIS302DL_CTRL_REG1_ADDR(value 0x%02x) verify failed!\n", temp);
}
RT-thread 设备驱动组件之SPI设备的更多相关文章
- RT thread 设备驱动组件之USART设备
本文以stm32f4xx平台介绍串口驱动,主要目的是:1.RTT中如何编写中断处理程序:2.如何编写RTT设备驱动接口代码:3.了解串行设备的常见处理机制.所涉及的主要源码文件有:驱动框架文件(usa ...
- RT-thread 设备驱动组件之PIN设备
在RT-thread 2.0.0正式版中引入了pin设备作为杂类设备,其设备驱动文件pin.c在rt-thread-2.0.1\components\drivers\misc中,主要用于操作芯片GPI ...
- Linux设备驱动剖析之SPI(三)
572至574行,分配内存,注意对象的类型是struct spidev_data,看下它在drivers/spi/spidev.c中的定义: struct spidev_data { dev_t de ...
- 【Linux高级驱动】linux设备驱动模型之平台设备驱动机制
[1:引言: linux字符设备驱动的基本编程流程] 1.实现模块加载函数 a.申请主设备号 register_chrdev(major,name,file_operations); b.创 ...
- Linux 驱动框架---cdev字符设备驱动和misc杂项设备驱动
字符设备 Linux中设备常见分类是字符设备,块设备.网络设备,其中字符设备也是Linux驱动中最常用的设备类型.因此开发Linux设备驱动肯定是要先学习一下字符设备的抽象的.在内核中使用struct ...
- Ardupilot设备驱动 IIC、SPI、USART
设备代码层次结构 Ardupilot设备驱动代码的层次结构采用 前端实现 和 后端实现 分割,前端库主要供机器代码层调用,后端库主要供前端调用.这里前端可以理解为应用层,后端理解为驱动层,前端调用 ...
- Linux设备驱动剖析之SPI(四)
781行之前没什么好说的,直接看783行,将work投入到工作队列里,然后就返回,在这里就可以回答之前为什么是异步的问题.以后在某个合适的时间里CPU会执行这个work指定的函数,这里是s3c64xx ...
- Linux设备驱动剖析之SPI(二)
957至962行,一个SPI控制器用一个master来描述.这里使用SPI核心的spi_alloc_master函数请求分配master.它在drivers/spi/spi.c文件中定义: struc ...
- Linux设备驱动剖析之SPI(一)
写在前面 初次接触SPI是因为几年前玩单片机的时候,由于普通的51单片机没有SPI控制器,所以只好用IO口去模拟.最近一次接触SPI是大三时参加的校内选拔赛,当时需要用2440去控制nrf24L01, ...
随机推荐
- xshell5连接虚拟机的小问题处理
1.首先确保虚拟机是桥接状态,然后在虚拟机下用ifconfig查看ip地址(当然是默认你虚拟机下是linux) 2.确保虚拟机安装了ssh...安装openssh-server: 对应的sudo ap ...
- 广州Uber优步司机奖励政策(1月11日~1月17日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- 并发任务管理器AsyncTaskManager
//-------------------------------------------------------------------------- // // Copyright (c) BUS ...
- POM中常用依赖包
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven ...
- “腾讯WeTest助力《龙珠直播》盘点APP质量问题”
WeTest 导读 据调查数据表明,移动端用户在使用APP时如果遇到了闪退等兼容性问题,20%的用户会选择直接卸载. 2016年,被称为中国直播元年.随着各类直播平台的疯狂生长与扩散,直播产品在内容, ...
- 解决ssh_exchange_identification:read connection reset by peer 原因
服务器改了密码,试过密码多次后出现: ssh_exchange_identification: read: Connection reset by peer 可以通过ssh -v查看连接时详情 Ope ...
- JAVA基础学习之路(二)方法定义,重载,递归
一,方法的定义: package test; public class test1 { public static void main(String args[]) { int result = ad ...
- 数据库Mysql的学习(一)-启动和进入
数据库:按照数据结构来组织储存和管理数据的仓库. Mysql是关系型数据库管理系统 Mysql安装好之后... mysql的启动 1:通过控制面板里的”服务“找到mysql右键启动即可 2:开始菜单搜 ...
- [Clr via C#读书笔记]Cp4类型基础
Cp4类型基础 Object类型 Object是所有类型的基类,有Equals,GetHashCode,ToString,GetType四个公共方法,其中GetHashCode,ToString可以o ...
- UVa 10082 - WERTYU 解题报告 - C语言
1.题目大意: 输入一个错位的字符串(字母全为大写),输出原本想打出的句子. 2.思路: 如果将每个输入字符所对应的应输出字符一一使用if或者switch,则过于繁琐.因此考虑使用常量数组实现. 3. ...