今天折腾了一天的SPI设备的驱动加载,甚至动用了逻辑分析仪来查看spi总线的波形,主要包括两个SPI设备,at45db321d和 mcp2515,一个是串行的dataflash,一个是can总线设备芯片。前者对于我们来说非常重要,我们可以借助该设备对uboot和kernel 以及根文件系统进行更新。
预备知识:设备和驱动是如何匹配的?系统的热插拔是如何实现的?
首先一点,设备和驱动是严格区分的,设备是设备,驱动是驱动,设备通过struct device来定义,当然用户也可以将该结构体封装到自己定义的device 结构体中,例如,struct platform_device,这是我们采用platform_bus_type 总线的设备定义的结构体形式:
include/linux/platform_device.h文件中:
struct platform_device {
const char* name;
u32 id;
struct device dev;
u32 num_resources;
struct resource* resource;
};
只要是9260的外围模块,就像IIC硬件控制器,SPI硬件控制器,都被完全的定义成这种结构体的格式,这种结构体主要包含了硬件资源和名称,硬件资源分为寄存器和IRQ两种。platform_device通过向内核注册struct device dev这个结构体来告诉内核加载这个设备,
方法就是 device_register(&platform_device->dev)
内核不关心你使用的是platform_device还是spi_device,内核只关心你的struct device结构体,内核通过这个struct device结构体自然能够顺藤摸瓜找到你是platform_device还是spi_device,这就是linux最引以为傲的contian_of()大法。
驱动通过struct driver这个结构体来定义,与struct device一致,你也可以用自己的结构体去封装:例如,struct platform_driver。
include/linux/platform_device.h文件中:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state); int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
与device一致,应用程序通过driver_register(&platform_driver->driver)向内核中注册你当前的驱动,而内核不关心你封装成的结构,而内核搜索的方法
还是同样的contain_of大法。
系统如何将这两者匹配上的?而不会将iic的设备加载到spi的驱动上面?不会将这个iic设备的驱动加载到那个iic设备上,设备和驱动之间是如何联系的?总线,这就是总线的作用!
include/linux/device.h文件中有总线类型的定义。
struct bus_type {
const char * name; // bus name
struct bus_attribute* bus_attrs;
struct device_attribute* dev_attrs;
struct driver_attribute* drv_attrs;
int (*match)(struct device * dev, struct device_driver * drv);
int (*uevent)(struct device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
int (*probe)(struct device * dev);
int (*remove)(struct device * dev);
void (*shutdown)(struct device * dev);
int (*suspend)(struct device * dev, pm_message_t state);
int (*suspend_late)(struct device * dev, pm_message_t state);
int (*resume_early)(struct device * dev);
int (*resume)(struct device * dev);
};
这个总线设备中最重要的可能是match成员,由于我们一般很少去建立一个新的总线,所以我们很少涉及总线的编程,我们就只关注我们所关注的。
总线如何将两者关联起来,热插拔大家知道吧,当一个设备被通过device_register注册到内核中时,会导致一个热插拔事件产生,系统会遍历该总线(此总线是struct device{ bus_type *bus}注册时指明的bus)上的所有驱动程序,调用bus的match算法,来寻找与该设备相匹配的驱动程序,当一个驱动注册到内核的时候,处理过程与此相似,而一般的macth算法都比较简单,例如platform_bus的匹配算法就很简单,就是比较platform_device和platform_driver的name成员,如果匹配成功,就加载相应的设备或者驱动!这就完成了一个连接的过程。。。
那么这两种设备驱动中最重要的类型在linux中如何表现出来,那我们就有必要介绍一下从2.6开始实现的sys文件系统了
我们 int Oct if spi device
/sys/bus $ cat /etc/fstab
proc /proc proc defaults 0 0
devpts /dev/pts devpts defaults 0 0
tmpfs /dev/shm tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
/dev/mtdblock2 /mnt/flash2 yaffs defaults 0 0
加载这个文件系统对于我们分析设备模型是非常有好处的。
sys 文件夹下一般有如下的目录:
/sys $ ls -al
drwxr-xr-x 10 root root 0 Jan 1 1970 .
drwxrwxrwx 11 1000 tao 4096 May 22 06:56 ..
drwxr-xr-x 7 root root 0 Oct 27 14:09 block
drwxr-xr-x 8 root root 0 Jan 1 1970 bus
drwxr-xr-x 21 root root 0 Jan 1 1970 class
drwxr-xr-x 4 root root 0 Jan 1 1970 devices
drwxr-xr-x 2 root root 0 Jan 1 1970 firmware
drwxr-xr-x 2 root root 0 Jan 1 1970 fs
drwxr-xr-x 2 root root 0 Jan 1 1970 kernel
drwxr-xr-x 22 root root 0 Oct 27 14:10 module
block 是由于历史原因形成的block 设备的文件夹。我们关心的是bus 文件夹。 我们以spi 设备为例:spi 部分要包括两种设备,一种是platform_device ,一种是spi_device 。
在arch/arm/mach-at91/at91sam9260_device.c 文件中,定义的SPI 硬件控制模块设备,这我们不需要关心。
还有一种是spi_device ,定义在arch/arm/mach-at91/board-sam9260ek.c 文件中,这就是我们的 dataflash 和mcp2515设备, 所以如何设备加载成功的话,在bus 下面的每个目录里面,都存在devices 和drivers 两个文件夹,分别对应设备和文件。
/sys/bus/platform/devices $ ls -al
drwxr-xr-x 2 root root 0 Oct 27 16:01 .
drwxr-xr-x 4 root root 0 Jan 1 1970 ..
lrwxrwxrwx 1 root root 0 Oct 27 16:01 at91_i2c -> ../../../d evices/platform/at91_i2c
lrwxrwxrwx 1 root root 0 Oct 27 16:01 at91_nand -> ../../../ devices/platform/at91_nand
lrwxrwxrwx 1 root root 0 Oct 27 16:01 at91_ohci -> ../../../ devices/platform/at91_ohci
lrwxrwxrwx 1 root root 0 Oct 27 16:01 atmel_spi.0 -> ../.. /../devices/platform/atmel_spi.0
lrwxrwxrwx 1 root root 0 Oct 27 16:01 atmel_spi.1 -> ../.. /../devices/platform/atmel_spi.1
lrwxrwxrwx 1 root root 0 Oct 27 16:01 atmel_usart.0 -> ../.. /../devices/platform/atmel_usart.0
lrwxrwxrwx 1 root root 0 Oct 27 16:01 atmel_usart.1 -> ../.. /../devices/platform/atmel_usart.1
lrwxrwxrwx 1 root root 0 Oct 27 16:01 atmel_usart.2 -> ../.. /../devices/platform/atmel_usart.2
lrwxrwxrwx 1 root root 0 Oct 27 16:01 macb -> ../../../devic es/platform/macb
驱动
/sys/bus/platform/drivers/atmel_spi $ ls -al
drwxr-xr-x 2 root root 0 Jan 1 1970 .
drwxr-xr-x 8 root root 0 Jan 1 1970 ..
lrwxrwxrwx 1 root root 0 Oct 27 16:10 atmel_spi.0 -> ../.. /../../devices/platform/atmel_spi.0
lrwxrwxrwx 1 root root 0 Oct 27 16:10 atmel_spi.1 -> ../.. /../../devices/platform/atmel_spi.1
--w------- 1 root root 4096 Oct 27 16:10 bind
--w------- 1 root root 4096 Oct 27 16:10 unbind
如果出现上面的这个情况,说明你的设备(两路spi总线)和驱动都加载成功了,如果你的devices下面没有spi.0设备和spi.1设备的话,说明
board-sam9260ek.c文件中的这个函数出错:
static void __init ek_board_init(void)
{
/* Serial */
at91_add_device_serial();
/* USB Host */
at91_add_device_usbh(&ek_usbh_data);
/* USB Device */
at91_add_device_udc(&ek_udc_data);
/* SPI */
at91_add_device_spi(ek_spi_devices, ARRAY_SIZE(ek_spi_devices));
/* NAND */
at91_add_device_nand(&ek_nand_data);
/* Ethernet */
at91_add_device_eth(&ek_macb_data);
/* MMC */
at91_add_device_mmc(0, &ek_mmc_data);
/* I2C */
at91_add_device_i2c();
}
这里是设备注册的地方,我们还应该在下面这个目录下看到这两个文件。
/sys/bus/spi/devices $ ls -al
drwxr-xr-x 2 root root 0 Oct 27 14:09 .
drwxr-xr-x 4 root root 0 Jan 1 1970 ..
lrwxrwxrwx 1 root root 0 Oct 27 14:09 spi0.1 -> ../../../devices/platform/atmel_spi.0/spi0.1
lrwxrwxrwx 1 root root 0 Oct 27 14:09 spi1.0 -> ../../../devices/platform/atmel_spi.1/spi1.0
这两个链接说明我们的两个spi设备注册都被接受了,剩下来就是驱动的问题。有人看不懂这个sys文件系统的层次关系,其实这里比较好说明,就是spi0.1是atmel_spi.0设备的子设备嘛,很好理解的。
驱动:
platform_driver驱动:
/sys/bus/platform/drivers $ ls -al
drwxr-xr-x 8 root root 0 Jan 1 1970 .
drwxr-xr-x 4 root root 0 Jan 1 1970 ..
drwxr-xr-x 2 root root 0 Jan 1 1970 at91_i2c
drwxr-xr-x 2 root root 0 Jan 1 1970 at91_nand
drwxr-xr-x 2 root root 0 Jan 1 1970 at91_ohci
drwxr-xr-x 2 root root 0 Oct 27 16:10 atmel_spi
drwxr-xr-x 2 root root 0 Jan 1 1970 atmel_usart
drwxr-xr-x 2 root root 0 Jan 1 1970 macb
我们可以看到这个驱动只有一个atmel_spi,这个驱动是在哪加载的?
driver/spi/atmel_spi.c文件加载的。
spi_driver驱动:
/sys/bus/spi/drivers $ ls -al
drwxr-xr-x 4 root root 0 Oct 27 14:10 .
drwxr-xr-x 4 root root 0 Jan 1 1970 ..
drwxr-xr-x 2 root root 0 Oct 27 14:10 mcp2515
drwxr-xr-x 2 root root 0 Oct 27 14:09 mtd_dataflash
这是我们加载的两个驱动,说明驱动也加载正常了。
下面我们来说说我们遇到的问题吧。
在设备和驱动都加载正常之后,出现与dataflash设备通信不上的情况,驱动加载的时候,读取芯片的状态字读出是0xff,说明工作不正常,动用逻辑分析仪监控spi总线的通信,意外的发现,sck信号和cs信号正常,但是mosi无信号输出,开始觉得可能是spi总线适配器有问题,后来仔细观察原理图之后,发现dataflash和mmc/sd是使用同样的io口的,即pa0,pa1,pa2,而我的内核配置中打开了对mmc的支持,所以导致mosi不正常,所以可能9260的mmc与dataflash不能同时使用,但9263的可以。
解决办法:make menuconfig
Device Drivers--->MMC/SD card support,取消其支持,问题解决!
昨天其实还有一个问题可能大家没有注意到,没有解释清楚,其实是有问题的,我们的at91_add_device_spi函数如下:
static struct spi_board_info ek_spi_devices[] = {
#if !defined(CONFIG_MMC_AT91)
{ /* DataFlash chip */
.modalias = \"mtd_dataflash\",
.chip_select = 1,
.max_speed_hz = 15 * 1000 * 1000,
.bus_num = 0,
},
#if defined(CONFIG_MTD_AT91_DA TAFLASH_CARD)
{ /* DataFlash card */
.modalias = \"mtd_dataflash\",
.chip_select = 0,
.max_speed_hz = 15 * 1000 * 1000,
.bus_num = 0,
},
#endif
#endif
#if defined(CONFIG_SND_AT73C213) || defined(CONFIG_SND_AT73C213_MODULE) { /* AT73C213 DAC */
.modalias = \"at73c213\",
.chip_select = 0,
.max_speed_hz = 10 * 1000 * 1000,
.bus_num = 1,
},
#endif
/* spi can ,add by mrz */
#if defined(CONFIG_CAN_MCP2515_MODULE) ||defined(CONFIG_CAN_MCP2515) //defined(CONFIG_CAN_MCP2515)
{
.modalias = \"mcp2515\",
.chip_select = 0,
// .controller_data = A T91_PIN_PB3,
.irq = A T91_PIN_PC6, //A T91SAM9260_ID_IRQ0,
.platform_data = &mcp251x_data,
.max_speed_hz = 10 * 1000 * 1000,
.bus_num = 1,
.mode = 0,
},
/*
{
.modalias = \"mcp2515\",
.chip_select = 1,
// .controller_data = A T91_PIN_PC5,
.irq = A T91_PIN_PC7, //A T91SAM9260_ID_IRQ1,
.platform_data = &mcp251x_data,
.max_speed_hz = 10 * 1000 * 1000,
.bus_num = 1,
.mode = 0,
},
*/
#elif defined(CONFIG_CAN_MCP251X)
{
.modalias = \"mcp251x\",
.chip_select = 0,
// .controller_data = A T91_PIN_PB3,
.irq = A T91_PIN_PC6, //A T91SAM9260_ID_IRQ0,
.platform_data = &mcp251x_data,
.max_speed_hz = 10 * 1000 * 1000,
.bus_num = 1,
.mode = 0,
},
{
.modalias = \"mcp251x\",
.chip_select = 1,
// .controller_data = A T91_PIN_PC5,
.irq = A T91_PIN_PC7, //A T91SAM9260_ID_IRQ1,
.platform_data = &mcp251x_data,
.max_speed_hz = 10 * 1000 * 1000,
.bus_num = 1,
.mode = 0,
},
#endif
}
void __init at91_add_device_spi(struct spi_board_info *devices, int nr_devices)
{
int i;
unsigned long cs_pin;
short enable_spi0 = 0;
short enable_spi1 = 0;
/* Choose SPI chip-selects */
/*这里加载我们定义的spi_board_info结构体,也就是两个spi设备的信息,注意,他们这里没有使用spi_device结构体来做,而是使用一个板级信息体来完成。*/
for (i = 0; i < nr_devices; i++) {
/*该成员定义的就是cs引脚*/
if (devices.controller_data)
cs_pin = (unsigned long) devices.controller_data;
else if (devices.bus_num == 0)
cs_pin = spi0_standard_cs[devices.chip_select];
else
cs_pin = spi1_standard_cs[devices.chip_select];
/*根据需要加载的设备,确定需要打开哪几个SPI控制器,我们系统中有两个控制器,所以我们在以模块的方式加载驱动的时候,我们的设备必须在刚开始就被初始化!*/ if (devices.bus_num == 0)
enable_spi0 = 1;
else
enable_spi1 = 1;
/* enable chip-select pin */
/*将片选引脚设置为输出*/
at91_set_gpio_output(cs_pin, 1);
/* pass chip-select pin to driver */
devices.controller_data = (void *) cs_pin;
}
/*到此,循环执行完毕,向内核注册这些板级信息体*/
spi_register_board_info(devices, nr_devices);
/* Configure SPI bus(es) */
/*如果发现spi0上有设备注册,则打开spi0*/
if (enable_spi0) {
at91_set_A_periph(AT91_PIN_P A0, 0); /* SPI0_MISO */
at91_set_A_periph(AT91_PIN_P A1, 0); /* SPI0_MOSI */
at91_set_A_periph(AT91_PIN_P A2, 0); /* SPI1_SPCK */
at91_clock_associate(\"spi0_clk\", &at91sam9260_spi0_device.dev, \"spi_clk\");
platform_device_register(&at91sam9260_spi0_device);
}
/*spi0设备也是如此*/
if (enable_spi1) {
at91_set_A_periph(AT91_PIN_PB0, 0); /* SPI1_MISO */
at91_set_A_periph(AT91_PIN_PB1, 0); /* SPI1_MOSI */
at91_set_A_periph(AT91_PIN_PB2, 0); /* SPI1_SPCK */
at91_clock_associate(\"spi1_clk\", &at91sam9260_spi1_device.dev, \"spi_clk\");
platform_device_register(&at91sam9260_spi1_device);
}
}
从上面这个函数我们可以看出,这个函数就完成了两个功能:
1、向内核完成spi板级信息结构体的注册
2、注册了两个platform_device:spi0与spi1,这两个设备是spi总线控制器!
那么我们客户端spi_device设备的注册是如何完成的?不知道,呵呵
我今天仔细的看代码才发现玄机所在。
内核的注释很清晰的告诉我们,我们的spi设备是不允许热插拔!!这是由于spi设备驱动的框架不允许,我们的spi_device设备注册不是在板级初始化的时候完成的
在spi控制器的驱动加载的时候,也就是platform_driver:atmel_spi驱动加载的时候,
driver/spi/atmel_spi.c文件中:
static int __init atmel_spi_probe(struct platform_device *pdev)
{
struct resource *regs;
int irq;
struct clk *clk;
int ret;
struct spi_master *master;
struct atmel_spi *as;
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!regs)
return -ENXIO;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
clk = clk_get(&pdev->dev, \"spi_clk\");
if (IS_ERR(clk))
return PTR_ERR(clk);
/* setup spi core then atmel-specific driver state */
ret = -ENOMEM;
master = spi_alloc_master(&pdev->dev, sizeof *as);
if (!master)
goto out_free;
master->bus_num = pdev->id;
master->num_chipselect = 4;
master->setup = atmel_spi_setup;
master->transfer = atmel_spi_transfer;
master->cleanup = atmel_spi_cleanup;
platform_set_drvdata(pdev, master);
as = spi_master_get_devdata(master);
as->buffer = dma_alloc_coherent(&pdev->dev, BUFFER_SIZE, &as->buffer_dma, GFP_KERNEL);
if (!as->buffer)
goto out_free;
spin_lock_init(&as->lock);
INIT_LIST_HEAD(&as->queue);
as->pdev = pdev;
as->regs = ioremap(regs->start, (regs->end - regs->start) + 1); if (!as->regs)
goto out_free_buffer;
as->irq = irq;
as->clk = clk;
#ifdef CONFIG_ARCH_AT91
if (!cpu_is_at91rm9200())
as->new_1 = 1;
#endif
ret = request_irq(irq, atmel_spi_interrupt, 0,
pdev->dev.bus_id, master);
if (ret)
goto out_unmap_regs;
/* Initialize the hardware */
clk_enable(clk);
spi_writel(as, CR, SPI_BIT(SWRST));
spi_writel(as, MR, SPI_BIT(MSTR) | SPI_BIT(MODFDIS));
spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
spi_writel(as, CR, SPI_BIT(SPIEN));
/* go! */
dev_info(&pdev->dev, \"Atmel SPI Controller at 0x%08lx (irq %d)\\n\",
(unsigned long)regs->start, irq);
/*spi注册这个主控制器*/
ret = spi_register_master(master);
if (ret)
goto out_reset_hw;
return 0;
out_reset_hw:
spi_writel(as, CR, SPI_BIT(SWRST));
clk_disable(clk);
free_irq(irq, master);
out_unmap_regs:
iounmap(as->regs);
out_free_buffer:
dma_free_coherent(&pdev->dev, BUFFER_SIZE, as->buffer,
as->buffer_dma);
out_free:
clk_put(clk);
spi_master_put(master);
return ret;
}
而这个spi_register_master位于driver/spi/spi.c文件中,该函数调用了scan_boardinfo(master),扫描该spi master下面设备。该函数就存在于该文件下:该函数调用了spi_new_device(master, chip),这个chip就是一个spi_board_info结构体,这就是at91_add_device_spi第一个作用的用处:向内核的链表注册spi_board_info 结构体的用处所在。我们来看函数的调用过程:
atmel_spi_probe----->spi_register_master----->scan_boardinfo
---->spi_new_device
我们来看这个spi_new_device函数:
struct spi_device *spi_new_device(struct spi_master *master,
struct spi_board_info *chip)
{
struct spi_device *proxy;
struct device *dev = master->cdev.dev;
int status;
/* NOTE: caller did any chip->bus_num checks necessary */
if (!spi_master_get(master))
return NULL;
/*这里就是非常重要的spi_device结构体*/
proxy = kzalloc(sizeof *proxy, GFP_KERNEL);
if (!proxy) {
dev_err(dev, \"can't alloc dev for cs%d\\n\",
chip->chip_select);
}
/*这就是将我们的信息体中的数据转化为spi_device识别的数据*/
proxy->master = master;
proxy->chip_select = chip->chip_select;
proxy->max_speed_hz = chip->max_speed_hz;
proxy->mode = chip->mode;
proxy->irq = chip->irq;
proxy->modalias = chip->modalias;
snprintf(proxy->dev.bus_id, sizeof proxy->dev.bus_id,
\"%s.%u\", master->cdev.class_id,
chip->chip_select);
proxy->dev.parent = dev;
proxy->dev.bus = &spi_bus_type;
/*这里很重要,如果你的spi设备是dataflash的话,保存的就是你的分区表!!!所以我们要返回去修改我们的spi_boardinfo结构体*/
proxy->dev.platform_data = (void *) chip->platform_data;
/*片选信号*/
proxy->controller_data = chip->controller_data;
proxy->controller_state = NULL;
proxy->dev.release = spidev_release;
/* drivers may modify this default i/o setup */
status = master->setup(proxy);
if (status < 0) {
dev_dbg(dev, \"can't %s %s, status %d\\n\",
\"setup\", proxy->dev.bus_id, status);
goto fail;
}
/* driver core catches callers that misbehave by defining
* devices that already exist.
*/
/*看到这句话,大家放心了吧,大家也就知道怎么找到spi_driver驱动的。。。*/
status = device_register(&proxy->dev);
if (status < 0) {
dev_dbg(dev, \"can't %s %s, status %d\\n\",
\"add\", proxy->dev.bus_id, status);
goto fail;
}
dev_dbg(dev, \"registered child %s\\n\", proxy->dev.bus_id);
return proxy;
fail:
spi_master_put(master);
kfree(proxy);
return NULL;
}
下面我们要解决最后的一个问题,dataflash的分区的问题,看了这么多,大家应该知道怎么解决了吧!
我们看mtd_dataflash.c文件中驱动加载函数调用了下面这个函数来添加flash设备。。
static int __devinit
add_dataflash(struct spi_device *spi, char *name,
int nr_pages, int pagesize, int pageoffset)
{
struct dataflash *priv;
struct mtd_info *device;
/*这里就告诉我们要在spi_boardinfo结构体的platform_data成员指向一个我们需要的flash_platform_data 数据!*/
struct flash_platform_data *pdata = spi->dev.platform_data;
priv = kzalloc(sizeof *priv, GFP_KERNEL);
if (!priv)
return -ENOMEM;
init_MUTEX(&priv->lock);
priv->spi = spi;
priv->page_size = pagesize;
priv->page_offset = pageoffset;
/* name must be usable with cmdlinepart */
sprintf(priv->name, \"spi%d.%d-%s\",
spi->master->bus_num, spi->chip_select,
name);
device = &priv->mtd;
device->name = (pdata && pdata->name) ? pdata->name : priv->name; device->size = nr_pages * pagesize;
device->erasesize = pagesize;
device->writesize = pagesize;
device->owner = THIS_MODULE;
device->type = MTD_DA TAFLASH;
device->flags = MTD_WRITEABLE;
device->erase = dataflash_erase;
device->read = dataflash_read;
device->write = dataflash_write;
device->priv = priv;
dev_info(&spi->dev, \"%s (%d KBytes)\\n\", name, device->size/1024); dev_set_drvdata(&spi->dev, priv);
if (mtd_has_partitions()) {
struct mtd_partition *parts;
int nr_parts = 0;
/*我们这里没有定义该宏,所以不会在命令行传递分区表*/
#ifdef CONFIG_MTD_CMDLINE_PARTS
static const char *part_probes[] = { \"cmdlinepart\", NULL, };
nr_parts = parse_mtd_partitions(device, part_probes, &parts, 0);
#endif
if (nr_parts <= 0 && pdata && pdata->parts) {
parts = pdata->parts;
nr_parts = pdata->nr_parts;
}
if (nr_parts > 0) {
priv->partitioned = 1;
return add_mtd_partitions(device, parts, nr_parts);
}
} else if (pdata && pdata->nr_parts)
dev_warn(&spi->dev, \"ignoring %d default partitions on %s\\n\", pdata->nr_parts, device->name);
return add_mtd_device(device) == 1 ? -ENODEV : 0;
}
所以我们需要修改这个文件:
arch/arm/mach-at91/board-sam9260ek.c文件:
添加如下:
#if !defined(CONFIG_MMC_A T91)
#define SIZE_1PAGE 528
#define SIZE_1M (unsigned long)(1024*1024)
static struct mtd_partition ek_dataflash_partition[] = {
{
.name = \"U-boot ENV\",
.offset = 0,
.size = 64*SIZE_1PAGE,
},
{
.name = \"U-BOOT\",
.offset = 64*SIZE_1PAGE,
.size = 400*SIZE_1PAGE,
},
{
.name =\"Kernel\",
.offset=464*SIZE_1PAGE,
.size = 4000*SIZE_1PAGE,
},
{
.name =\"Root fs\",
.offset=4464*SIZE_1PAGE,
.size = (8192-4464)*SIZE_1PAGE,
},
};
struct flash_platform_data dataflash_atmel={
.name=\"AT45DB321\",
.parts=ek_dataflash_partition,
.nr_parts=ARRAY_SIZE(ek_dataflash_partition),
};
#endif
修改spi_boardinfo结构体:
static struct spi_board_info ek_spi_devices[] = {
#if !defined(CONFIG_MMC_A T91)
{ /* DataFlash chip */
.modalias = \"mtd_dataflash\",
.chip_select = 1,
.max_speed_hz = 15 * 1000 * 1000,
.bus_num = 0,
.platform_data=&dataflash_atmel,
},
添加platform_data结构成员。
这里我们建立mtd_partition结构体要注意,由于dataflash是以528字节每页的,其实,at45db321x芯片可以设置为512字节每页,这个操作是不可以逆转的,那个位是一个otp位,用过的人就应该知道,但是出厂的时候默认的528字节每页。
如果我们不是以528个字节为单位的话,内核将出警告,强制将分区加载为readonly格式。
到此,分区加载成功,dmesg输出如下信息:
<6>mtd_dataflash spi0.1: A T45DB321x (4224 KBytes)
<5>Creating 4 MTD partitions on \"AT45DB321\":
<5>0x00000000-0x00008400 : \"U-boot ENV\"
<5>0x00008400-0x0003bd00 : \"U-BOOT\"
<5>0x0003bd00-0x0023f700 : \"Kernel\"
<5>0x0023f700-0x00420000 : \"Root fs\"
linux简直太伟大了,使用得越多,就越能体会到其思想的伟大!灵活!
SPI驱动流程(S3C2440)
2007-12-27 22:50:25
简单的说就是写几个寄存器,其实非常简单的,呵呵
一、配置IO脚为SPI接口
我用的是(s3c2440)SPI1通道,所有的IO都在GPG脚上,故配置的是GPGCON,将对应的位置为SPI
然后是GPGUP寄存器
二、开始SPI寄存器的配置
1、SPPRE,设置传送频率,即SPICLK的频率
SPI_CLK=PCLK/2/(SPPRE+1)
根据S3C2440的资料,PCLK=48M,我要配置SPI_CLK为1M,
1=48/2/(SPPRE+1)=>SPPRE=23=>SPPRE=0x18
(个人理解,有错请指出,呵呵)
2、SPCON 寄存器
该寄存器有7 位,对应的控制包括:SPIMOD(6:5),SCK enable (4),master/slave (3),CPOL(2),CPHA(1),TAGD(0).具体怎么设置根据自己的需要设置就OK。
3、SPPIN 寄存器
就三个寄存器,都设置好后就可以写和接收数据啦
写之前记得要检查SPSTA的REDY位是否为1 .
NOTE:我的配置好IO之后,发现写SPCON寄存器无效,郁闷了两天终于找到原因,就CLKCON寄存器的SPI位没有
激活,所以写寄存器之前记得要检查CLKCON的状态
一点点经验,留个记号,呵呵
代码写的有点乱,测试的时候弄的,不好意思^_^
code :
/************************************************/
#ifndef MODULE
#define MODULE
#endif
#include
#include
#include
#include
#include
#include
/*---------- spi ---------------*/
/*----------------- addr ---------- note --------*/
#define GPG_CON 0x56000060 /* port G control */
#define GPG_DAT 0x56000064 /* port G data */
#define GPG_UP 0x56000068 /* pull up port G control */
#define SPCON_1 0x59000020 /*SPI1 control */
#define SPSTA_1 0x59000024 /* SPI1 status */
#define SPPIN_1 0x59000028 /* SPI1 pin control */
#define SPPRE_1 0X5900002C /* spi1 baud rate prescaler */
#define SPTDAT_1 0x59000030 /* spi1 Tx data */
#define SPRDAT_1 0x59000034 /* spi1 Rx data */
/* GPGCON */
#define nSS1 (1<<6 | 1<<7) /* GPG3 */
#define SPICLK1 (1<<14 | 1<<15) /* GPG7 */
#define SPIMOSI1 (1<<12 | 1<<13) /* GPG6 */
#define SPIMISO1 (1<<10 | 1<<11) /* GPG5 */
#define SPI_USE (nSS1 | SPICLK1 | SPIMOSI1 |SPIMISO1)
//static int spi_major;
#define spi_name "S3C2440_SPI"
#define spi_minor 1
#ifdef CONFIG_DEVFS_FS
static devfs_handle_t devfs_spi_dir,devfs_spi_raw;
#endif
MODULE_LICENSE("GPL");
#if 1
#define SPI_SPPRE1 (*(volatile unsigned long *)SPPRE1) /* spi1 baud rate prescaler */
#define SPI_GPGCON (*(volatile unsigned long *)GPGCON) /* port-G control */
#define SPI_SPCON1 (*(volatile unsigned long *)SPCON1) /* spi1 control */
#define SPI_SPSTA1 (*(volatile unsigned long *)SPSTA1) /* spi1 status */
#define SPI_SPPIN1 (*(volatile unsigned long *)SPPIN1) /* spi1 pin control */
#define SPI_SPTDA T1 (*(volatile unsigned long *)SPTDA T1) /* spi1 Tx data */ #define SPI_SPRDA T1 (*(volatile unsigned long *)SPRDAT1) /* spi1 Rx data */
#define SPTDA T1_READ (*(volatile unsigned long *)SPTDAT1) /* spi1 Tx data */
#define SPI_GPGDAT (*(volatile unsigned long *)GPGDAT) /* port-G data */
#define CLKCON (*(volatile unsigned long *)ADDR_CLKCON)
#endif
unsigned long GPGCON;
unsigned long GPGDAT;
unsigned long GPGUP;
unsigned long SPCON1;
unsigned long SPSTA1;
unsigned long SPPIN1;
unsigned long SPPRE1;
unsigned long SPTDA T1;
unsigned long SPRDA T1;
unsigned long ADDR_CLKCON;
static void get_sys_addr(void)
{
GPGCON =(unsigned long)ioremap(GPG_CON,4);
GPGDAT =(unsigned long)ioremap(GPG_DA T,4);
GPGUP =(unsigned long)ioremap(GPG_UP,4);
SPCON1 =(unsigned long)ioremap(SPCON_1,4);
SPSTA1 =(unsigned long)ioremap(SPSTA_1,4);
SPPIN1 =(unsigned long)ioremap(SPPIN_1,4);
SPPRE1 =(unsigned long)ioremap(SPPRE_1,4);
SPTDAT1=(unsigned long)ioremap(SPTDA T_1,4);
SPRDAT1=(unsigned long)ioremap(SPRDA T_1,4);
ADDR_CLKCON=(unsigned long )ioremap(0x4c00000c,4);
}
#if 1
static int spi_open(struct inode *inode,struct file *filp)
{
unsigned int port_state;
int i=0;
#if 1
/*=============== start init spi regiser ============*/
/* 时钟使能*/
printk("CLKCON: %x \n",(unsigned int )CLKCON);
if (!(CLKCON&(1<<18)))
CLKCON|=(1<<18);
printk("CLKCON: %x \n",(unsigned int )CLKCON);
/* 配置IO 脚为SPI */
port_state = readl(GPGCON);
printk("<1>only read :port_state= %x \n",port_state);
port_state &= ~SPI_USE;
port_state |= SPI_USE;
/* printk("<1>port_state= %x \n",port_state); */
SPI_GPGCON = port_state;
// writel(SPI_USE,GPGCON);
/* printk("<1> after write :readl(GPGCON)= %x \n",SPI_GPGCON); */
/* IO 脚使能*/
&n
关键字:SPI驱动流程(S3C2440)bsp; port_state=0x0;
port_state = readl(GPGUP);
/* printk("<1> GPGUP before write: %x \n",port_state); */
port_state &=~(1<<3 | 1<<5 | 1<<6 | 1<<7);
writel(port_state,GPGUP);
/* printk("<1>after write :GPGUP= %x \n",readl(GPGUP)); */
#endif
#if 1
port_state = readl(SPPRE1);
// writel(value,SPPRE1); /* if PCLK=50MHz PCLK/2/(value+1) =SPICLK */
SPI_SPPRE1=0x18; /* set Baud rate 1M .get it https://www.doczj.com/doc/9a9315362.html,.note: PCLE=48MHz ,SPICLK=48/2/(0x18+1)=1M */
/* set spi module */
/* printk("<1>before write :readl(SPCON1)=%x \n",readl(SPCON1)); */
port_state = readl(SPCON1);
port_state &= ~0x03f;
port_state |= (0<<0 | 0<<1 | 1<<2 | 1<<3 | 1<<4 | 0<<5 );
/* 0<<0 normal mode , 0<<1 format A,1<<2 active low ,1<<3 master
* 1<<4 SCK enable (master only), 0<<5 polling mode
*/
// writel(port_state,SPCON1);
SPI_SPCON1=(0<<0 | 0<<1 | 1<<2 | 1<<3 | 1<<4 | 0<<5 );
spiFLASH芯片WQ的单片机驱动代码 #include "w25q80.h" // 注:W25Q80由256 BYTE 组成一个PAGE,不可PGAE擦除,可以进行BYTE PROGRAM 或者PAGE PROGRAM // 由16 PAGE 组成一个SECTOR,可SECTOR擦除 // 由16 SECTOR组成一个BLOCK,可BLOCK 擦除 // 由16 BLOCK 组成一个FULL MEMEORY,可FULL MEMORY 擦除 // 所以,总容量是1M bytes // W25Q80主要命令字 #define READ_ARRAY 0x03 #define SECTOR_ERASE 0x20 #define BYTE_OR_PAGE_PROGRAM 0x02 #define WRITE_ENABLE 0x06 #define WRITE_DISABLE 0x04 #define READ_STATUS_REGISTER 0x05 #define Manufacturer_DeviceID 0x9F // 定义W25Q80的CS脚对应MCU的IO #define W25Q80_CS P1_2 // SPI硬件初始化 void Spi_Init(void) { PERCFG |= 0x02; // SPI1映射到P1口 P1SEL |= 0xE0; // P15~P17作复用功能(clk mosi miso) P1SEL &= ~0x04; // P12作GPIO P1DIR |= 0x04; // P12作输出 P1_2 = 1; // P12输出高电平 U1CSR &= ~0xA0; // SPI主方式 U1GCR &= ~0xC0; // CPOL=0 CPHA=0 U1GCR |= 0x20; // MSB U1BAUD = 0; // 波特率设为sysclk/8 U1GCR |= 0x11;
#include "spi.h" ////////////////////////////////////////////////////////////////////////////////// //本程序只供学习使用,未经作者许可,不得用于其它任何用途 ////////////////////////////////////////////////////////////////////////////////// //以下是SPI模块的初始化代码,配置成主机模式,访问SD Card/W25Q64/NRF24L01 //SPI口初始化 //这里针是对SPI2的初始化 void SPI2_Init(void) { RCC->APB2ENR|=1<<3; //PORTB时钟使能 RCC->APB1ENR|=1<<14; //SPI2时钟使能 //这里只针对SPI口初始化 GPIOB->CRH&=0X000FFFFF; GPIOB->CRH|=0XBBB00000; //PB13/14/15复用 GPIOB->ODR|=0X7<<13; //PB13/14/15上拉 SPI2->CR1|=0<<10; //全双工模式 SPI2->CR1|=1<<9; //软件nss管理 SPI2->CR1|=1<<8; SPI2->CR1|=1<<2; //SPI主机 SPI2->CR1|=0<<11; //8bit数据格式 SPI2->CR1|=1<<1; //空闲模式下SCK为1 CPOL=1 SPI2->CR1|=1<<0; //数据采样从第二个时间边沿开始,CPHA=1 //对SPI2属于APB1的外设.时钟频率最大为36M. SPI2->CR1|=3<<3; //Fsck=Fpclk1/256 SPI2->CR1|=0<<7; //MSBfirst SPI2->CR1|=1<<6; //SPI设备使能 SPI2_ReadWriteByte(0xff);//启动传输 } //SPI2速度设置函数 //SpeedSet:0~7 //SPI速度=fAPB1/2^(SpeedSet+1) //APB1时钟一般为36Mhz void SPI2_SetSpeed(u8 SpeedSet) { SpeedSet&=0X07; //限制范围 SPI2->CR1&=0XFFC7; SPI2->CR1|=SpeedSet<<3; //设置SPI2速度 SPI2->CR1|=1<<6; //SPI设备使能 } //SPI2 读写一个字节
关于使用STM32硬件SPI驱动NRF24L01+ 今天是大年初一总算有时间做点想做很久的事了,说到NRF2401可能很多电子爱好者都有用过或是软件模拟驱动又或是用单片机自带的硬件SPI来驱动,但不管是用哪种方法来驱动我想都在调试方面耗费了不少的时间(可能那些所谓的电子工程师不会出现这种情况吧!)网上的资料确实很多,但大多数都并没有经过发贴人认真测试过,有的只是理论上可以行的通但上机测试只能说是拿回来给他修改。本文作者也是经过无助的多少天才算是调试成功了(基于STM32硬件SPI,软件模拟的以前用51单片机已经调通了今天就不准备再拿来讲了,当然如果以后有朋友有需要也可以告诉我,我也可以重新写一篇关于51的驱动的只要有时间是没有问题的。)因为我用的是STM32F103C8T6的系统而且是刚接触不知道别的系统和我用的这个系统有多大的差别所以我不会整个代码全贴上来就完事了,我将就着重思路配合代码写出来,这样对于刚接触单片机的朋友会有很好的作用,但是还有一点请大家要原谅,可能会存在一些说的不好的地方,毕竟我没有经过正规渠道系统地学习过电子知识,对于前辈来说存在这样那样的问题不可避免的,在此也希望大家指教! 贴个图先:
NRF2401+的资料大家上网查一下,我输字的速度有点不好说!下面我来说一下整个调试流程吧 1.先把STM32串口一调通(因为我不知道STM32 I/O口不知可不可以像51那样并口输出数据,如果可以那就更方便啰)。 2.与NRF2401建立起通信(这个才是问题的关键);
3.利用读NRF2401的一个状态寄存器(STATUS)的值并通过串口发送到PC后通过51下载软件的串口助手显示出来(如果你用液晶来调试那你太有才了,你液晶和NRF2401存在牵连可能就会给寻找不成功的原因造成困难,而且还有不少硬件工作要做)在这说一下本文只调试发送程序,致于接收只改一个程序参数就行了。 我们先来调试STM32F103C8T6的串口1吧(也就是USART1)!它是STM32F103C8T6的片上外设之一,使用它时相对来说简单了不少。首先我要说一下我们要使用STM32的片上外设那么我们必须先对其进行初始化,实际上就是经过这段初始化代码让外设根据我们的要求来工作: void USART1_AllInit(void)//意思是USART1的所有初始化工作,我的英文不好所以可能涵数名可能也不怎么规范 { RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟,它是在APB2这条总线上的 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA时钟,它也是在APB2这条总线上的,因为USART1要用到GPIOA的端口所以也要初始化 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
AT89C52单片机驱动SD卡系统设计 本文详细阐述了用AT89C52单片机对SD卡进行操作的过程,提出了一种不带SD卡控制器,MCU读写SD卡的方法,实现了SD卡在电能监测及无功补偿数据采集系统中的用途。 长期以来,以Flash Memory为存储体的SD卡因具备体积小、功耗低、可擦写以及非易失性等特点而被广泛应用于消费类电子产品中。特别是近年来,随着价格不断下降且存储容量不断提高,它的应用范围日益增广。当数据采集系统需要长时间地采集、记录海量数据时,选择SD卡作为存储媒质是开发者们一个很好的选择。在电能监测以及无功补偿系统中,要连续记录大量的电压、电流、有功功率、无功功率以及时间等参数,当单片机采集到这些数据时可以利用SD作为存储媒质。本文主要介绍了SD卡在电能监测及无功补偿数据采集系统中的应用方案 设计方案 应用AT89C52读写SD卡有两点需要注意。首先,需要寻找一个实现AT89C52单片机与SD卡通讯的解决方案;其次,SD卡所能接受的逻辑电平与AT89C52提供的逻辑电平不匹配,需要解决电平匹配问题 通讯模式 SD卡有两个可选的通讯协议:SD模式和SPI模式。SD模式是SD卡标准的读写方式,但是在选用SD模式时,往往需要选择带有SD卡控制器接口的MCU,或者必须加入额外的SD卡控制单元以支持SD卡的读写。然而,AT89C52单片机没有集成SD卡控制器接口,若选用SD模式通讯就无形中增加了产品的硬件成本。在SD卡数据读写时间要求不是很严格的情况下,选用SPI模式可以说是一种最佳的解决方案。因为在SPI模式下,通过四条线就可以完成所有的数据交换,并且目前市场上很多MCU都集成有现成的SPI接口电路,采用SPI模式对SD卡进行读写操作可大大简化硬件电路的设计。 虽然AT89C52不带SD卡硬件控制器,也没有现成的SPI接口模块,但是可以用软件模拟出SPI总线时序。本文用SPI总线模式读写SD卡。 电平匹配 SD卡的逻辑电平相当于3.3V TTL电平标准,而控制芯片AT89C52的逻辑电平为5V CMOS电平标准。因此,它们之间不能直接相连,否则会有烧毁SD卡的可能。出于对安全工作的考虑,有必要解决电平匹配问题。 要解决这一问题,最根本的就是解决逻辑器件接口的电平兼容问题,原则主要有两条:一为输出电平器件输出高电平的最小电压值,应该大于接收电平器件识别为高电平的最低电压值;另一条为输出电平器件输出低电平的最大电压值,应该小于接收电平器件识别为低电平的最高电压值。 一般来说,通用的电平转换方案是采用类似SN74ALVC4245的专用电平转换芯片,这类芯片不仅可以用作升压和降压,而且允许两边电源不同步。但是,这个方案代价相对昂贵,而且一般的专用电平转换芯片都是同时转换8路、16路或者更多路数的电平,相对本系统仅仅需要转换3路来说是一种资源的浪费。 考虑到SD卡在SPI协议的工作模式下,通讯都是单向的,于是在单片机向
SPI总线在单片机系统中的实现 2007-04-28 10:56 来源:mcuzb //-----------------------函数声明,变量定义------------------------------------------#include
Linux下的SPI总线驱动(一)2013-04-12 15:08:46 分类:LINUX 版权所有,转载请说明转自一.SPI理论介绍 SPI总线全名,串行外围设备接口,是一种串行的主从接口,集成于很多微控制器内部。和I2C使用2根线相比,SPI总线使用4根线:MOSI (SPI 总线主机输出/ 从机输入)、MISO (SPI总线主机输入/从机输出)、SCLK(时钟信号,由主设备产生)、CS(从设备使能信号,由主设备控制)。由于SPI总线有专用的数据线用于数据的发送和接收,因此可以工作于全双工,当前市面上可以找到的SPI外围设备包括RF芯片、智能卡接口、E2PROM、RTC、触摸屏传感器、ADC。 SCLK信号线只由主设备控制,从设备不能控制信号线。同样,在一个基于SPI的设备中,至少有一个主控设备。这样传输的特点:这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCLK 时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。也就是说,主设备通过对SCLK时钟线的控制可以完成对通讯的控制。SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。不同的SPI 设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档。在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比I2C 系统要稍微复杂一些。 二.SPI驱动移植 我们下面将的驱动的移植是针对Mini2440的SPI驱动的移植 Step1:在Linux Source Code中修改arch/arm/mach-s3c2440/文件,加入头文件:#include
SPI 串行外设接口总线,最早由Motorola提出,出现在其M68系列单片机中,由于其简单实用,又不牵涉到专利问题,因此许多厂家的设备都支持该接口,广泛应用于外设控制领域。 SPI接口是一种事实标准,并没有标准协议,大部分厂家都是参照Motorola的SPI接口定义来设计的。但正因为没有确切的版本协议,不同家产品的SPI接口在技术上存在一定的差别,容易引起歧义,有的甚至无法直接互连(需要软件进行必要的修改)。 虽然SPI接口的内容非常简单,但本文仍将就其中的一些容易忽视的问题进行讨论。 SPI ( Serial Peripheral Interface ) SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。 时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB first);SPI 接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。 SPI接口信号线 SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。 设备选择线SS-(Slave select,或CS-)
SS-线用于选择激活某Slave设备,低有效,由Master驱动输出。只有当SS-信号线为低电平时,对应Slave设备的SPI接口才处于工作状态。 SCLK:同步时钟信号线, SCLK用来同步主从设备的数据传输,由Master驱动输出,Slave设备按SCK的步调接收或发送数据。 串行数据线: SPI接口数据线是单向的,共有两根数据线,分别承担Master到Slave、Slave到Master的数据传输;但是不同厂家的数据线命名有差别。 Motorola的经典命名是MOSI和MISO,这是站在信号线的角度来命名的。 MOSI:When master, out line; when slave, in line MISO:When master, in line; when slave, out line 比如MOSI,该线上数据一定是Master流向Slave的。因此在电路板上,Master的MOSI引脚应与Slave的MOSI引脚连接在一起。双方的MISO也应该连在一起,而不是一方的MOSI连接另一方的MISO。 不过,也有一些产家(比如Microchip)是按照类似SDI,SDO的方式来命名,这是站在器件的角度根据数据流向来定义的。 SDI:串行数据输入 SDO:串行数据输出 这种情况下,当Master与Slave连接时,就应该用一方的SDO连接另一个方的SDI。 由于SPI接口数据线是单向的,故电路设计时,数据线连接一定要正确,必然是一方的输出连接另一方的输入。 其实这个问题本来很简单的,但由于不同厂家产品的命名习惯可能不同,因此还需小心,以免低级出错。 数据传输的时序模式
文档编号: SPI驱动使用介绍 文档状态:[ ] 草稿[√] 正式发布[ ] 正在修改 版本0.01 校对版本日期2013-12-18 审核编制孙忠刚批准
版本变更记录 版本日期修改人变更理由/变更内容 孙忠刚初始版本 0.01 2013-12-18
1. SPI驱动提供的通讯方式 Spi提供两种通讯方式,对应的驱动接口分别为SyncTransmit和AsynTransmit。 a)同步通讯方式 当驱动接口Spi_SyncTransmit执行完毕,即完成SPI通讯。当SPI总线传输数据时,CPU处于轮询等待状态,直至SPI通讯结束CPU方继续向下执行。 b)异步通讯方式 当驱动接口Spi_AsynTransmit执行完毕,但SPI通讯不一定完成。CPU将待发送的数据写入SPI数据发送缓冲区,触发SPI通讯,CPU继续向下执行,而不等待 SPI通讯结束。 2. SPI驱动使用方法 a)基础知识介绍 在AutoSar标准中,与SPI通讯相关的三个术语:Channel、Job和Sequence。 1个Channel对应1个发送缓冲区和1接收缓冲区; 1个Job对应着1次SPI通讯发送的内容(既SPI 一次片选过程所传输的内容)。 1个Sequence 对应着1个SPI通讯序列(job序列)。多个Job可以分配给一个Sequence。 关于每个术语的详细解释,参考AutoSar标准。 SPI通讯是基于Sequence触发的,即使发送1个Job也要将该Job分配给1个队列,然后通过触发Sequence来实现Job的传输。 b)两种使用方法 方法1:对应1个SPI外设芯片,分配1个Job、1个Sequence。使用此种方法,触发一次Sequence,只能传输一个Job,当对外设发送多个Job时,需要多次触发Sequence,且在下一次触发Sequence时,必须确保上一次Sequence已经传输完毕,否则下一次Sequence传输会因为上一次Sequence传输占用SPI总线而失败。 方法2:对应1个SPI外设芯片,结合对外设芯片的控制方法,分配多个Job和多个Sequence,有目的分配Job到相应的Sequence中。在不同控制逻辑中,触发不同的Sequence,传输不同个数的Job序列。当对1个Sequence分配多Job时,触发此Sequence就可以完成多个Job的传输,SPI 驱动本身来保证Job序列的传输,不会产生下一个Job传输因为上一个Job占用SPI总线而失败的情况。
这是用SPI方式来驱动12864液晶的源程序,液晶屏的控制芯片为ST7565P经测试SPI的时钟可达到振荡频率的二分频。比用普通IO口模拟串行的方式快多了。。。 下面贴出源代码,有详细注释。。。。 本程序简单实用,可拿去作简单参考。。。。 编译环境:GCC+AVR STUDIO 单片机:ATMEGA 8515 晶振: 3.6864M #include
今天折腾了一天的SPI设备的驱动加载,甚至动用了逻辑分析仪来查看spi总线的波形,主要包括两个SPI设备,at45db321d和 mcp2515,一个是串行的dataflash,一个是can总线设备芯片。前者对于我们来说非常重要,我们可以借助该设备对uboot和kernel 以及根文件系统进行更新。 预备知识:设备和驱动是如何匹配的?系统的热插拔是如何实现的? 首先一点,设备和驱动是严格区分的,设备是设备,驱动是驱动,设备通过struct device来定义,当然用户也可以将该结构体封装到自己定义的device 结构体中,例如,struct platform_device,这是我们采用platform_bus_type 总线的设备定义的结构体形式: include/linux/platform_device.h文件中: struct platform_device { const char* name; u32 id; struct device dev; u32 num_resources; struct resource* resource; }; 只要是9260的外围模块,就像IIC硬件控制器,SPI硬件控制器,都被完全的定义成这种结构体的格式,这种结构体主要包含了硬件资源和名称,硬件资源分为寄存器和IRQ两种。platform_device通过向内核注册struct device dev这个结构体来告诉内核加载这个设备, 方法就是 device_register(&platform_device->dev) 内核不关心你使用的是platform_device还是spi_device,内核只关心你的struct device结构体,内核通过这个struct device结构体自然能够顺藤摸瓜找到你是platform_device还是spi_device,这就是linux最引以为傲的contian_of()大法。 驱动通过struct driver这个结构体来定义,与struct device一致,你也可以用自己的结构体去封装:例如,struct platform_driver。 include/linux/platform_device.h文件中: struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); int (*suspend_late)(struct platform_device *, pm_message_t state); int (*resume_early)(struct platform_device *); int (*resume)(struct platform_device *); struct device_driver driver; }; 与device一致,应用程序通过driver_register(&platform_driver->driver)向内核中注册你当前的驱动,而内核不关心你封装成的结构,而内核搜索的方法
浅析spi flash驱动及其程序 SPI Flash 首先它是个Flash,Flash是什么东西就不多说了(非易失性存储介质),分为NOR和NAND两种(NOR和NAND的区别本篇不做介绍)。SPI一种通信接口。那么严格的来说SPI Flash是一种使用SPI通信的Flash,即,可能指NOR也可能是NAND。但现在大部分情况默认下人们说的SPI Flash指的是SPI NorFlash。早期Norflash 的接口是parallel的形式,即把数据线和地址线并排与IC的管脚连接。但是后来发现不同容量的Norflash不能硬件上兼容(数据线和地址线的数量不一样),并且封装比较大,占用了较大的PCB板位置,所以后来逐渐被SPI(串行接口)Norflash所取代。同时不同容量的SPI Norflash管脚也兼容封装也更小。,至于现在很多人说起NOR flash直接都以SPI flash来代称。 NorFlash根据数据传输的位数可以分为并行(Parallel,即地址线和数据线直接和处理器相连)NorFlash和串行(SPI,即通过SPI接口和处理器相连)NorFlash;区别主要就是:1、SPI NorFlash每次传输一bit位的数据,parallel连接的NorFlash每次传输多个bit位的数据(有x8和x16bit两种);2、SPI NorFlash比parallel便宜,接口简单点,但速度慢。 NandFlash是地址数据线复用的方式,接口标准统一(x8bit和x16bit),所以不同容量再兼容性上基本没什么问题。但是目前对产品的需求越来越小型化以及成本要求也越来越高,所以SPI NandFlash渐渐成为主流,并且采用SPI NANDFlash方案,主控也可以不需要传统NAND控制器,只需要有SPI接口接口操作访问,从而降低成本。另外SPI NandFlash封装比传统的封装也小很多,故节省了PCB板的空间。怎么用说白了对于Flash就是读写擦,也就是实现flash的驱动。先简单了解下spi flash的物理连接。之前介绍SPI的时候说过,SPI接口目前的使用是多种方式(具体指的是物理连线有几种方式),Dual SPI、Qual SPI和标准的SPI接口(这种方式肯定不会出现在连接外设是SPI Flash上,这玩意没必要全双工),对于SPI Flash来说,主要就是Dual和Qual这两种方式。具体项目具体看了,理论上在CLK一定的情况下,线数越多访问速度也越快。我们项目采用的Dual SPI方式,即两线。移植需要更改sendrcv函数里面内容,宏定义内容 [cpp]view plain copy《code class=“language-cpp”》#define
GPIO模拟SPI通讯接口的驱动 一,某些时候我们会不得不使用GPIO来模拟SPI,I2C等通讯接口,如本例中,需要使用SPI接口发送9位的数据,如果使用linux内核提供的SPI子系统来做这个驱动是无法实现9位传输数据的。 二,用GPIO模拟SPI总的来说是比较简单,把相应的管脚配置成GPIO功能,再按需要配置管脚的输入输出方向,然后根据SPI总线的时序设定IO口的电平。 三,驱动代码如下,以备今后作参考: (linux-2.6.28 + TCC8900, 这个驱动是用来控制LCD的初始化的(型号为LW350AC9001)) #include
SPI总线 与IIC类似,SPI也是一种通信协议。今天我们就以WX25X16芯片为例来介绍SPI.首先我们来看下硬件连接。 从原理图可以看到该芯片需要单片机控制的管脚有4个,非别是CS,DO,DIO,CLK.其中CS是片选信号,只有将该位拉低才能选中该芯片。DO,DIO分别是输出和输入。CLK是时钟信号。 SPI通信的步骤如下所示:1)获取地址12)获取地址23)擦除扇区4)写入数据 好的,下面我们对每个步骤进行分析 (1)在对芯片操作前先要对端口及SPI外设进行相应的设置: /* 函数名:SPI_FLASH_Init(void) 功能:对端口和SPI初始化 输入:无 输出:无 调用:被主函数调用 */ void SPI_FLASH_Init(void) { SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; /* Enable SPI1 and GPIO clocks */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD,ENABL E); /*!< SPI_FLASH_SPI Periph clock enable */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); /*将PA5(CLK)配置成复用推挽输出 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); /*将PA6(DO)设置成浮空输入 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOA, &GPIO_InitStructure); /*将PA7(DIO)设为浮空输入*/
spi slave及master接口驱动及传输时序 spi slave驱动 spi slave驱动在kernel中可以主要参考spidev.c,这是一个字符驱动,可以匹配kernel 中的多个名称为“spidev”的spi设备, 分析这个文件,主要有以下几个重点: 1. 如何编写多设备公用驱动 2. 如何封装读写请求到spi框架层 3. spi message请求如何分发到master 自spi_board_info或者spi master注册后,两者就已经完成了匹配的工作,spi slave 驱动不关心任何匹配的细节,它只需要完成 与spi slave的匹配,就可以通过slave进而找到master。这里是通过 spi_register_driver(&spidev_spi_driver);注册进 kernel,而后spi框架进行name match,再调用probe,完成关于设备的一些成员初始化操作。 下面针对上面的三个问题,进行分析这个驱动, spi设备全局链及保护信号量: static LIST_HEAD(device_list); static DEFINE_MUTEX(device_list_lock); 相对与设备的驱动数据: struct spidev_data { dev_t devt; //设备号 spinlock_t spi_lock; //spi 结构体的pin锁 struct spi_device *spi; struct list_head device_entry;//挂接到device_list struct mutex buf_lock; //保护数据的lock unsigned users; //使用者 u8 *buffer; //实际数据区,由open时进行动态分配,release时释放 }; spi中任何会由多个使用者访问的区域,都需要使用锁保护,如这里的users,个人觉得需要使用原子变量而不应该简单的使用整形。 在probe的时候,首先分配spidev_data,并初始化其 spi/device_entry/buf_lock/spi_lock,查找一个可用的bit用作次 设备号,创建设备spidev busnum.cs,挂到全局链中,并将私有数据spidev_data放到dev->p->driver_data中。 open时,从inode中获取dev_t,然后对比整个链,找到目标数据spidev_data,放到file->private_data中,并分配缓存 读写时,直接从file中获取对应的spidev_data数据,然后通过spi device来传递spi 请求。