Linux内核调用SPI平台级驱动_实现OLED的显示功能
Linux内核调用SPI驱动_实现OLED显示功能
0. 导语
进入Linux的世界,发现真的是无比的有趣,也发现搞Linux驱动从底层嵌入式搞起真的是很有益处。我们在单片机、DSP这些无操作系统的裸机中学习了这些最基本的驱动,然后用过GPIO时序去模拟、然后用那个芯片平台的外设去配置参数,到Linux的世界,对于底层的时序心中有数,做起来就容易很多。学习的过程就是不断的给自己出难题,然后去解决他,在未来工程里面遇到这个问题,就瞬间可以解决了,这就是经验的积累吧。
Linux驱动目录,包含了底层写好的SPI驱动,我们需要想办法调用人家写好的SPI驱动,就不需要写IO口模拟SPI时序了。在网络上,对于SPI应用级的驱动倒是很多,平台级驱动很少,而我们想把平台级驱动二次包装在我们的字符设备驱动中,对于用户,无需考虑SPI通信写协议还是写命令,只需要使用read和write函数写显示的内容就好了。
基于这样的想法,我们找了一个使用SPI协议的从器件来实现,我手里面有OLED设备,是支持SPI协议在OLED显示面板上显示字符的。所以搭建一个实验平台,做一个OLED的demo,未来所有的从SPI设备都遵循这个框架(而且我们在这个驱动中加入 了内核机制的驱动的自旋锁、互斥体的内核操作)。
实验平台如下:
- ARM板子: 友善之臂Nano-T3 (CortexA53架构, Samsung s5c6818)
- ARM的Linux系统:Ubuntu 16.04.2 LTS
- 编译调试Linux:Ubuntu 16.04.3 LTS amd64版本
- 编译器:arm-cortexa9-linux-gnueabihf-gcc (64位版本)
- 从设备:OLED (SPI模式)

1. 驱动架构模型

总体驱动架构模型如图所示,对于OLED驱动的表述,主要包含两个方面,一个是OLED这个传感器的抽象;一个是,misc字符驱动的注册,里面有read和write函数,供用户接口调用,(在read和write函数里面使用OLED设备表述里面的master控制oled的行为就好了,比如显示,清除,复位之类的)。
oled设备表述,为OLED设备的抽象,里面包含对硬件的描述和SPI的描述,还有对于写时序的时候使用自旋锁和互斥体对时序进行的保护,master为对oled设备的基本操作,包含复位,写字节等等。
在本博客中最重要的就是SPI平台驱动的使用,问题也非常的清晰,我们如何使用linux内核驱动里面写好的spi,参考Linux SPI API文档里面,那么复杂的结构体,哪些是在驱动中要使用,哪些是在应用级程序中使用的。网络上的资料大部分都是应用级的,没有讲述在字符驱动中二级注册spi驱动的,而我们对于OLED这样的SPI设备,则需要在驱动中调用,让用户无需关心任何SPI的调用。
在驱动模型中,master操作结构体里面,oled_write_byte这样的函数里面则需要调用系统级SPI,问题就非常明确,就在写byte的时候使用SPI。
那么我们就需要在注册完字符设备的时候,向内核注册spi,然后我们使用该SPI对OLED操作。
2. linux SPI驱动的注册
Linux Drivers目录具备一定的通用性也具备各个架构区别不同,在包含头文件的时候,要包含
- 通用性的linux spi文件
#include <linux/spi/spi.h> - mach级特性文件
#include <mach/slsi-spi.h>
同时也要关注:
- plat级的
device.c文件,里面包含了spi_board信息的模板,用这个可以省去了很多麻烦。
我们使用的oled_hw_t, 图上的结构(OLED->hw)的具体定义,里面定义了io口的编号和spi的各种机制,注意谁是指针,谁是实体。
struct oled_hw_t
{
unsigned int res_io_num;
unsigned int dc_io_num;
struct spi_transfer spi_trans;
struct spi_message spi_msg;
struct spi_driver *spi_drv;
struct spi_device *spi_dev;
struct spi_master *spi_master_bus;
};
我需要定义以下机制:
spi_driver
spi_driver会向内核申请总线处理的权限,当我们加载驱动的时候,在ARM机器的linux上的
/sys/bus/spi/drivers目录下会看到申请SPI驱动内核的名字。
static const struct spi_device_id oled_spi_id[] =
{
{“oledspi”, 1},
{},
};
static struct spi_driver sp6818_spi_driver =
{
.driver =
{
.name = "oled_spi",
.bus = &spi_bus_type,
.owner = THIS_MODULE,
},
.probe = oled_bus_spi_probe,
.remove = __devexit_p(oled_bus_spi_remove),
.suspend = oled_bus_spi_suspend,
.id_table = oled_spi_id,
};
MODULE_DEVICE_TABLE( spi, oled_spi_id );
按照spi_driver驱动的格式进行,补充好probe和remove,suspend函数,但是这里存在一个问题,当我们应该spi_register_driver的时候,正常应该执行probe函数里面的内容,但是这个不执行,怀疑是因为二级包装问题,我们的主调还是使用misc驱动的字符设备 __init标示在 misc的初始化函数上,而导致不进入spi_driver的probe函数。
spi_device
spi_device和spi_driver是成对出现的,在spi_driver注册完之后,则需要对spi_deivce进行配置,我们首先要声明一个spi_device,一会儿借助linux 的drivers 里面的platform级的deivce.c文件中的spi_board来注册我们的spi_device。
定义spi_device驱动,这里面的配置信息可以瞎填,我们使用spi_board中的配置信息会覆盖这些信息。
static struct spi_device sp6818_spi_device =
{
.mode = SPI_MODE_3,
.bits_per_word = 16,
.chip_select = SPI_CS_HIGH,
.max_speed_hz = 100000,
};
然后现在的工作就是如何spi_device和我们刚才spi_driver进行绑定了。
定义下面的信息:
static struct s3c64xx_spi_csinfo sp6818_csi =
{
.line = OLED_CS_IO,
.set_level = gpio_set_value,
.fb_delay = 0x2,
}; struct spi_board_info sp6818_board_info =
{
.modalias = "oled",
.platform_data = NULL,
.max_speed_hz = 10 * 1000 * 1000,
.bus_num = 0,
.chip_select = 2,
.mode = SPI_MODE_3,
.controller_data = &sp6818_csi,
};
这个模板就定义在platform级文件夹的device.c里面,我们按照模板的定义方式在我们的驱动文件里面也定义一个,在s3c64xx_spi_csinfo sp6818_csi中定义的是片选信号的IO口,这个IO口根据硬件原理图来的,然后定义spi_board_info结构体,这些都是为spi_device做准备的,spi的配置信息也由此写入。

按照这个顺序进行:程序就如同下面的参考,后面会给出完成程序。
static void oled_module_hw_init( OLED *self )
{
int ret,i;
struct spi_master *master;
struct spi_device *spi; self->hw.res_io_num = OLED_RES_IO;
self->hw.dc_io_num = OLED_DC_IO;
printk( DRV_NAME "\tregister spi driver...\n" );
self->hw.spi_drv = &sp6818_spi_driver;
ret = spi_register_driver( self->hw.spi_drv );
if ( ret < 0 ) {
printk( DRV_NAME "\terror: spi driver register failed" );
}
printk( DRV_NAME "\tmaster blind spi bus.\n" );
master = spi_busnum_to_master( 0 );
master->num_chipselect = 4;
if ( !master ) {
printk( DRV_NAME "\terror: master blind spi bus.\n" );
ret = -ENODEV;
return ret;
}
printk( DRV_NAME "\tnew spi device...\n" );
spi = spi_new_device( master, &sp6818_board_info );
if ( !spi ) {
printk( DRV_NAME "\terror: spi occupy.\n" );
return -EBUSY;
}
self->hw.spi_master_bus = master;
self->hw.spi_dev = spi;
printk( DRV_NAME "\thw init succussful...\n" );
}
到此,完成,spi的注册。
spi_device的注册里面,会在ARM上面的Linux的/sys/bus/spi/devices下面出现我们注册的device设备,如图:

spi0.2就是我们所注册的device设备,这个命名就和我们的spi_board_info有关系了,

如果,bus_num = 5, chip_select = 20, 那么注册的device就是spi5.20了。这里还有个坑,就是片选信号的数值大小和master里面的片选num的问题,linux的spi api要求,master的num-chipselect必须大于 spi_board_info里面chip_select的数值。你也看到上面初始化程序,为什么master->num_chipselect = 4; 这个语句了。
3. SPI 的使用
在驱动里面对于spi的使用就非常简单了。例如我们oled的write_byte函数:
static void oled_module_write_byte( OLED* self, \
unsigned int dat, \
enum data_type_t type)
{
int status;
unsigned int write_buffer[1];
if ( type == ENUM_WRITE_TYPE_CMD )
self->master->set_dc_low( self );
else
self->master->set_dc_high( self );
write_buffer[0] = dat;
write_buffer[1] = 0xFF;
status = spi_write( self->hw.spi_dev, write_buffer, 1 );
if ( status )
dev_err( &self->hw.spi_dev->dev, "%s error %d\n", __FUNCTION__, status );
}
使用spi_write函数就好了。

4. 结语
探索Linux SPI真是很费劲,这些花了好多时间,经历了无数次的实验,因为是驱动,经常在调试过程中出现暴栈、指针乱指,这些对于Linux内核都是毁灭性的错误,只能重启ARM Linux。光重启Linux就好几百次。不过总算是有成果,对于Linux驱动的学习还在进行,下次可能要实验I2C的平台驱动,找到规律和不同,再加上一些内核的操作,比如并发和IO等,在学习中成长。
源代码
Github地址:https://github.com/lifimlt/carlosdriver
见 oled.c oled.h 和oledfont.h三个文件
参考文献:
[1] Linux org, Serial Peripheral Interface (SPI),
[2] 郝过, Linux设备驱动模型SPI之二, 2016年2月28日
[3] invo-tronics , SPI Driver for Linux Based Embedded System, 2014年9月30日
[4] Linux学习之路, spi驱动框架全面分析,从master驱动到设备驱动, 2016年6月22日
Linux内核调用SPI平台级驱动_实现OLED的显示功能的更多相关文章
- Linux内核调用I2C驱动_驱动嵌套驱动方法
禁止转载!!!! Linux内核调用I2C驱动_以MPU6050为例 0. 导语 最近一段时间都在恶补数据结构和C++,加上导师的事情比较多,Linux内核驱动的学习进程总是被阻碍.不过,十一假期终于 ...
- Linux内核中SPI总线驱动分析
本文主要有两个大的模块:一个是SPI总线驱动的分析 (研究了具体实现的过程): 另一个是SPI总线驱动的编写(不用研究具体的实现过程). 1 SPI概述 SPI是英语Serial Peripheral ...
- Linux内核中SPI/I2c子系统剖析
Linux内核中,SPI和I2C两个子系统的软件架构是一致的,且Linux内核的驱动模型都以bus,driver,device三种抽象对象为基本元素构建起来.下文的分析将主要用这三种抽象对象的创建过程 ...
- linux内核移植X86平台的例子
bootloader支持启动多个Linux 内核安装(X86平台) 1. cparch/x86/boot/bzImage /boot/vmlinuz-$version 2. cp $initrd /b ...
- 4.9版本的linux内核中温度传感器adt7461的驱动源码在哪里
答:drivers/hwmon/lm90.c,这个文件中支持了好多芯片,内核配置项为CONFIG_SENSORS_LM90 Location: -> Device Drivers -> H ...
- 如何单独编译Linux内核源码中的驱动为可加载模块?
答: 分为两步: 1. 配置某个驱动为模块(如: CONFIG_RTC_XXX=m) 2. 指定路径并编译, 如编译drivers/rtc中的驱动 make SUBDIRS=drivers/rtc m ...
- Linux内核SPI支持概述
1. 什么是SPI? Serial Peripheral Interface是一种同步4线串口链路,用于连接传感器.内存和外设到微控制器.他是一种简单的事实标准,还不足以复杂到需要一份正式的规范.SP ...
- linux内核驱动——从helloworld开始
学习编程第一个都是学习hello world程序,学习内核驱动自然也不例外,我也是!本文整理了网上的一些资料以及加上自己的一些心得体会,希望对初学者有帮助,可别小看这个简单的hello world,本 ...
- Linux 内核USB 驱动
通用串行总线(USB)是一个在主机和许多外设之间的连接. 最初它被创建来替代许多慢速和不同的总线- 并口, 串口, 和键盘连接--有一个单个的所有设备都可以连接的总线类型.[45] USB 已经成长超 ...
随机推荐
- Best Time to Buy and Sell Stock II--疑惑
https://oj.leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/ 代码如下时能AC class Solution { publi ...
- UnicodeDecodeError: 'utf8' codec can't decode byte in position invalid start byte
在scrapy项目中,由于编码问题,下载的网页中中文都是utf-8编码,在Pipeline.py中方法process_item将结果保存到数据库中时,提示UnicodeDecodeError: 'ut ...
- Ngnix学习
- Excel使用SUMIF函数注意事项
sumif函数的公式使用方法如下: =sumif(查询匹配的区域,条件,汇总求和的区域) 条件可以是“>10”或"=10"这种格式.其中“查询匹配区域”和“汇总求和区域”需要 ...
- 【Leetcode】【Medium】Word Break
Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separa ...
- 【Leetcode】【Medium】Letter Combinations of a Phone Number
Given a digit string, return all possible letter combinations that the number could represent. A map ...
- February 18 2017 Week 7 Saturday
It is not easy to meet each other in such a big world. 世界这么大,能遇见不容易. Sometimes we choose to trust in ...
- PHP面向对象(OOP)编程入门教程链接
PHP官方学习OOP: http://php.net/manual/zh/oop5.intro.php 从其他博主学习:(以下链接来源: http://blog.snsgou.com/post-41. ...
- Sql去重一些技巧
下午的时候遇到点问题,Sql去重,简单的去重可以用 DISTINCT 关键字去重,不过,很多情况下用这个解决不了问题.重复的数据千变万化,例如:类似于qq.微信的最近联系人功能,读取这些数据肯定要和消 ...
- springmvc常用注解标签详解(转载)
1.@Controller 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ...