目录
- 一、SPI驱动程序框架
- 二、怎么编写SPI设备驱动程序
- 1. 编写设备树
- 2. 注册spi_driver
- 3. 怎么发起SPI传输
- 3.1 接口函数
- 3.2 函数解析
- 三、示例1:编写SPI_DAC模块驱动程序
- 1. 要做什么事情
- 2. 硬件
- 2.1 原理图
- 2.2 连接
- 3. 编写设备树
- 4. 编写驱动程序
- 5. 编写app层操作程序
- 四、示例1:编写SPI_OLED模块驱动程序
- 1. 硬件
- 1.1 原理图
- 1.2 连接
- 2. 编写设备树
- 3. 编写驱动程序
- 3. 编写app层操作程序
参考资料:
- 内核头文件:
include\linux\spi\spi.h
- 内核文档:
Documentation\spi\spidev
一、SPI驱动程序框架
在之前的文章《SPI驱动学习二(驱动框架)》中,我们已经讲解过该部分内容,这里我们再回顾下,如下图所示:
SPI Master(或者说控制器) 通过platform总线设备驱动模型进行实现,SPI Device通过SPI driver驱动模型来实现。
二、怎么编写SPI设备驱动程序
1. 编写设备树
-
查看原理图,确定这个设备链接在哪个SPI控制器下
-
在设备树里,找到SPI控制器的节点
-
在这个节点下,创建子节点,用来表示SPI设备
-
示例如下:
&ecspi1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ecspi1>; fsl,spi-num-chipselects = <2>; cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>; status = "okay"; dac: dac { compatible = "100ask,dac"; reg = <0>; spi-max-frequency = <10000000>; }; };
2. 注册spi_driver
SPI设备的设备树节点,会被转换为一个spi_device结构体。我们需要编写一个spi_driver来支持它。
示例如下:
static const struct of_device_id dac_of_match[] = {
{.compatible = "100ask,dac"},
{}
};
static struct spi_driver dac_driver = {
.driver = {
.name = "dac",
.of_match_table = dac_of_match,
},
.probe = dac_probe,
.remove = dac_remove,
//.id_table = dac_spi_ids,
};
3. 怎么发起SPI传输
3.1 接口函数
接口函数都在这个内核文件里:include\linux\spi\spi.h
-
简易函数
/** * SPI同步写 * @spi: 写哪个设备 * @buf: 数据buffer * @len: 长度 * 这个函数可以休眠 * * 返回值: 0-成功, 负数-失败码 */ static inline int spi_write(struct spi_device *spi, const void *buf, size_t len); /** * SPI同步读 * @spi: 读哪个设备 * @buf: 数据buffer * @len: 长度 * 这个函数可以休眠 * * 返回值: 0-成功, 负数-失败码 */ static inline int spi_read(struct spi_device *spi, void *buf, size_t len); /** * spi_write_then_read : 先写再读, 这是一个同步函数 * @spi: 读写哪个设备 * @txbuf: 发送buffer * @n_tx: 发送多少字节 * @rxbuf: 接收buffer * @n_rx: 接收多少字节 * 这个函数可以休眠 * * 这个函数执行的是半双工的操作: 先发送txbuf中的数据,在读数据,读到的数据存入rxbuf * * 这个函数用来传输少量数据(建议不要操作32字节), 它的效率不高 * 如果想进行高效的SPI传输,请使用spi_{async,sync}(这些函数使用DMA buffer) * * 返回值: 0-成功, 负数-失败码 */ extern int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx); /** * spi_w8r8 - 同步函数,先写8位数据,再读8位数据 * @spi: 读写哪个设备 * @cmd: 要写的数据 * 这个函数可以休眠 * * * 返回值: 成功的话返回一个8位数据(unsigned), 负数表示失败码 */ static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd); /** * spi_w8r16 - 同步函数,先写8位数据,再读16位数据 * @spi: 读写哪个设备 * @cmd: 要写的数据 * 这个函数可以休眠 * * 读到的16位数据: * 低地址对应读到的第1个字节(MSB),高地址对应读到的第2个字节(LSB) * 这是一个big-endian的数据 * * 返回值: 成功的话返回一个16位数据(unsigned), 负数表示失败码 */ static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd); /** * spi_w8r16be - 同步函数,先写8位数据,再读16位数据, * 读到的16位数据被当做big-endian,然后转换为CPU使用的字节序 * @spi: 读写哪个设备 * @cmd: 要写的数据 * 这个函数可以休眠 * * 这个函数跟spi_w8r16类似,差别在于它读到16位数据后,会把它转换为"native endianness" * * 返回值: 成功的话返回一个16位数据(unsigned, 被转换为本地字节序), 负数表示失败码 */ static inline ssize_t spi_w8r16be(struct spi_device *spi, u8 cmd);
-
复杂的函数
/** * spi_async - 异步SPI传输函数,简单地说就是这个函数即刻返回,它返回后SPI传输不一定已经完成 * @spi: 读写哪个设备 * @message: 用来描述数据传输,里面含有完成时的回调函数(completion callback) * 上下文: 任意上下文都可以使用,中断中也可以使用 * * 这个函数不会休眠,它可以在中断上下文使用(无法休眠的上下文),也可以在任务上下文使用(可以休眠的上下文) * * 完成SPI传输后,回调函数被调用,它是在"无法休眠的上下文"中被调用的,所以回调函数里不能有休眠操作。 * 在回调函数被调用前message->statuss是未定义的值,没有意义。 * 当回调函数被调用时,就可以根据message->status判断结果: 0-成功,负数表示失败码 * 当回调函数执行完后,驱动程序要认为message等结构体已经被释放,不能再使用它们。 * * 在传输过程中一旦发生错误,整个message传输都会中止,对spi设备的片选被取消。 * * 返回值: 0-成功(只是表示启动的异步传输,并不表示已经传输成功), 负数-失败码 */ extern int spi_async(struct spi_device *spi, struct spi_message *message); /** * spi_sync - 同步的、阻塞的SPI传输函数,简单地说就是这个函数返回时,SPI传输要么成功要么失败 * @spi: 读写哪个设备 * @message: 用来描述数据传输,里面含有完成时的回调函数(completion callback) * 上下文: 能休眠的上下文才可以使用这个函数 * * 这个函数的message参数中,使用的buffer是DMA buffer * * 返回值: 0-成功, 负数-失败码 */ extern int spi_sync(struct spi_device *spi, struct spi_message *message); /** * spi_sync_transfer - 同步的SPI传输函数 * @spi: 读写哪个设备 * @xfers: spi_transfers数组,用来描述传输 * @num_xfers: 数组项个数 * 上下文: 能休眠的上下文才可以使用这个函数 * * 返回值: 0-成功, 负数-失败码 */ static inline int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers);
3.2 函数解析
在SPI子系统中,用spi_transfer结构体描述一个传输,用spi_message管理多个传输。
SPI传输时,发出N个字节,就可以同时得到N个字节。
- 即使只想读N个字节,也必须发出N个字节:可以发出0xff
- 即使只想发出N个字节,也会读到N个字节:可以忽略读到的数据。
spi_transfer结构体如下图所示:
- tx_buf:不是NULL的话,要发送的数据保存在里面
- rx_buf:不是NULL的话,表示读到的数据不要丢弃,保存进rx_buf里
可以构造多个spi_transfer结构体,把它们放入一个spi_message里面。
spi_message结构体如下图所示:
SPI传输示例:
三、示例1:编写SPI_DAC模块驱动程序
参考资料: DAC芯片手册TLC5615.pdf
1. 要做什么事情
- 查看原理图,编写设备树
- 编写驱动程序,注册一个spidrv
- 编写测试程序
2. 硬件
2.1 原理图
2.2 连接
3. 编写设备树
确认SPI时钟最大频率:
T = 25 + 25 = 50ns
F = 20000000 = 20MHz
设备树如下:
dac: dac {
compatible = "100ask,dac";
reg = <0>;
spi-max-frequency = <20000000>;
};
DAC模块接在这个插座上,那么要在设备树里spi1的节点下创建子节点。如下:
&ecspi1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi1>;
fsl,spi-num-chipselects = <2>;
cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;
status = "okay";
dac: dac {
compatible = "100ask,dac";
reg = <0>;
spi-max-frequency = <20000000>;
};
};
将该部分内容添加到主控板的设备树文件中。
4. 编写驱动程序
上一篇文章《SPI驱动学习四(通过SPI操作外设模块)》中,我们基于spidev编写过DAC的应用程序,可以参考它:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <linux/uaccess.h>
#define SPI_IOC_WR 123
/*-------------------------------------------------------------------------*/
static struct spi_device *dac;
static int major;
static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int val;
int err;
unsigned char tx_buf[2];
unsigned char rx_buf[2];
struct spi_message msg;
struct spi_transfer xfer[1];
int status;
memset(&xfer[0], 0, sizeof(xfer));
/* copy_from_user */
err = copy_from_user(&val, (const void __user *)arg, sizeof(int));
printk("spidev_ioctl get val from user: %d\n", val);
/* 发起SPI传输: */
/* 1. 把val修改为正确的格式 */
val <<= 2; /* bit0,bit1 = 0b00 */
val &= 0xFFC; /* 只保留10bit */
tx_buf[1] = val & 0xff;
tx_buf[0] = (val>>8) & 0xff;
/* 2. 发起SPI传输同时写\读 */
/* 2.1 构造transfer
* 2.2 加入message
* 2.3 调用spi_sync
*/
xfer[0].tx_buf = tx_buf;
xfer[0].rx_buf = rx_buf;
xfer[0].len = 2;
spi_message_init(&msg);
spi_message_add_tail(&xfer[0], &msg);
status = spi_sync(dac, &msg);
/* 3. 修改读到的数据的格式 */
val = (rx_buf[0] << 8) | (rx_buf[1]);
val >>= 2;
/* copy_to_user */
err = copy_to_user((void __user *)arg, &val, sizeof(int));
return 0;
}
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
/* REVISIT switch to aio primitives, so that userspace
* gets more complete API coverage. It'll simplify things
* too, except for the locking.
*/
.unlocked_ioctl = spidev_ioctl,
};
/*-------------------------------------------------------------------------*/
/* The main reason to have this class is to make mdev/udev create the
* /dev/spidevB.C character device nodes exposing our userspace API.
* It also simplifies memory management.
*/
static struct class *spidev_class;
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "100ask,dac" },
{},
};
/*-------------------------------------------------------------------------*/
static int spidev_probe(struct spi_device *spi)
{
/* 1. 记录spi_device */
dac = spi;
/* 2. 注册字符设备 */
major = register_chrdev(0, "100ask_dac", &spidev_fops);
spidev_class = class_create(THIS_MODULE, "100ask_dac");
device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_dac");
return 0;
}
static int spidev_remove(struct spi_device *spi)
{
/* 反注册字符设备 */
device_destroy(spidev_class, MKDEV(major, 0));
class_destroy(spidev_class);
unregister_chrdev(major, "100ask_dac");
return 0;
}
static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "100ask_spi_dac_drv",
.of_match_table = of_match_ptr(spidev_dt_ids),
},
.probe = spidev_probe,
.remove = spidev_remove,
/* NOTE: suspend/resume methods are not necessary here.
* We don't do anything except pass the requests to/from
* the underlying controller. The refrigerator handles
* most issues; the controller driver handles the rest.
*/
};
/*-------------------------------------------------------------------------*/
static int __init spidev_adc_init(void)
{
int status;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
status = spi_register_driver(&spidev_spi_driver);
if (status < 0) {
}
return status;
}
module_init(spidev_adc_init);
static void __exit spidev_adc_exit(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_adc_exit);
MODULE_LICENSE("GPL");
将该驱动编译进内核或者编译为ko文件,即可使用,该操作方法属于基本操作,此处不再赘述!
5. 编写app层操作程序
/* 参考: 内核源码根目录下tools\spi\spidev_fdx.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#define SPI_IOC_WR 123
/* dac_test /dev/100ask_dac <val> */
int main(int argc, char **argv)
{
int fd;
unsigned int val;
int status;
unsigned char tx_buf[2];
unsigned char rx_buf[2];
if (argc != 3)
{
printf("Usage: %s /dev/100ask_dac <val>\n", argv[0]);
return 0;
}
fd = open(argv[1], O_RDWR);
if (fd < 0) {
printf("can not open %s\n", argv[1]);
return 1;
}
val = strtoul(argv[2], NULL, 0);
status = ioctl(fd, SPI_IOC_WR, &val);
if (status < 0) {
printf("SPI_IOC_WR\n");
return -1;
}
/* 打印 */
printf("Pre val = %d\n", val);
return 0;
}
四、示例1:编写SPI_OLED模块驱动程序
1. 硬件
1.1 原理图
1.2 连接
把OLED模块接到扩展板的SPI_A插座上,如下:
2. 编写设备树
DC引脚(决定传输数据还是命令)使用GPIO4_20,也需在设备树里指定。设备树如下:
&ecspi1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi1>;
fsl,spi-num-chipselects = <2>;
cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;
status = "okay";
oled: oled {
compatible = "100ask,oled";
reg = <0>;
spi-max-frequency = <10000000>;
dc-gpios = <&gpio4 20 GPIO_ACTIVE_HIGH>;
};
};
3. 编写驱动程序
//spi_oled_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <linux/uaccess.h>
#include <linux/gpio/consumer.h>
#define OLED_IOC_INIT 123
#define OLED_IOC_SET_POS 124
//为0 表示命令,为1表示数据
#define OLED_CMD 0
#define OLED_DATA 1
/*-------------------------------------------------------------------------*/
static struct spi_device *oled;
static int major;
static struct gpio_desc *dc_gpio;
static void dc_pin_init(void)
{
gpiod_direction_output(dc_gpio, 1);
}
static void oled_set_dc_pin(int val)
{
gpiod_set_value(dc_gpio, val);
}
static void spi_write_datas(const unsigned char *buf, int len)
{
spi_write(oled, buf, len);
}
/**********************************************************************
* 函数名称: oled_write_cmd
* 功能描述: oled向特定地址写入数据或者命令
* 输入参数:@uc_data :要写入的数据
@uc_cmd:为1则表示写入数据,为0表示写入命令
* 输出参数:无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/03/04 V1.0 芯晓 创建
***********************************************************************/
static void oled_write_cmd_data(unsigned char uc_data,unsigned char uc_cmd)
{
if(uc_cmd==0)
{
oled_set_dc_pin(0);
}
else
{
oled_set_dc_pin(1);//拉高,表示写入数据
}
spi_write_datas(&uc_data, 1);//写入
}
/**********************************************************************
* 函数名称: oled_init
* 功能描述: oled_init的初始化,包括SPI控制器得初始化
* 输入参数:无
* 输出参数: 初始化的结果
* 返 回 值: 成功则返回0,否则返回-1
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/03/15 V1.0 芯晓 创建
***********************************************************************/
static int oled_init(void)
{
oled_write_cmd_data(0xae,OLED_CMD);//关闭显示
oled_write_cmd_data(0x00,OLED_CMD);//设置 lower column address
oled_write_cmd_data(0x10,OLED_CMD);//设置 higher column address
oled_write_cmd_data(0x40,OLED_CMD);//设置 display start line
oled_write_cmd_data(0xB0,OLED_CMD);//设置page address
oled_write_cmd_data(0x81,OLED_CMD);// contract control
oled_write_cmd_data(0x66,OLED_CMD);//128
oled_write_cmd_data(0xa1,OLED_CMD);//设置 segment remap
oled_write_cmd_data(0xa6,OLED_CMD);//normal /reverse
oled_write_cmd_data(0xa8,OLED_CMD);//multiple ratio
oled_write_cmd_data(0x3f,OLED_CMD);//duty = 1/64
oled_write_cmd_data(0xc8,OLED_CMD);//com scan direction
oled_write_cmd_data(0xd3,OLED_CMD);//set displat offset
oled_write_cmd_data(0x00,OLED_CMD);//
oled_write_cmd_data(0xd5,OLED_CMD);//set osc division
oled_write_cmd_data(0x80,OLED_CMD);//
oled_write_cmd_data(0xd9,OLED_CMD);//ser pre-charge period
oled_write_cmd_data(0x1f,OLED_CMD);//
oled_write_cmd_data(0xda,OLED_CMD);//set com pins
oled_write_cmd_data(0x12,OLED_CMD);//
oled_write_cmd_data(0xdb,OLED_CMD);//set vcomh
oled_write_cmd_data(0x30,OLED_CMD);//
oled_write_cmd_data(0x8d,OLED_CMD);//set charge pump disable
oled_write_cmd_data(0x14,OLED_CMD);//
oled_write_cmd_data(0xaf,OLED_CMD);//set dispkay on
return 0;
}
//坐标设置
/**********************************************************************
* 函数名称: OLED_DIsp_Set_Pos
* 功能描述:设置要显示的位置
* 输入参数:@ x :要显示的column address
@y :要显示的page address
* 输出参数: 无
* 返 回 值:
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/03/15 V1.0 芯晓 创建
***********************************************************************/
static void OLED_DIsp_Set_Pos(int x, int y)
{ oled_write_cmd_data(0xb0+y,OLED_CMD);
oled_write_cmd_data((x&0x0f),OLED_CMD);
oled_write_cmd_data(((x&0xf0)>>4)|0x10,OLED_CMD);
}
static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int x, y;
/* 根据cmd操作硬件 */
switch (cmd)
{
case OLED_IOC_INIT: /* init */
{
dc_pin_init();
oled_init();
break;
}
case OLED_IOC_SET_POS: /* set pos */
{
x = arg & 0xff;
y = (arg >> 8) & 0xff;
OLED_DIsp_Set_Pos(x, y);
break;
}
}
return 0;
}
static ssize_t
spidev_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
char *ker_buf;
int err;
ker_buf = kmalloc(count, GFP_KERNEL);
err = copy_from_user(ker_buf, buf, count);
oled_set_dc_pin(1);//拉高,表示写入数据
spi_write_datas(ker_buf, count);
kfree(ker_buf);
return count;
}
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
/* REVISIT switch to aio primitives, so that userspace
* gets more complete API coverage. It'll simplify things
* too, except for the locking.
*/
.write = spidev_write,
.unlocked_ioctl = spidev_ioctl,
};
/*-------------------------------------------------------------------------*/
/* The main reason to have this class is to make mdev/udev create the
* /dev/spidevB.C character device nodes exposing our userspace API.
* It also simplifies memory management.
*/
static struct class *spidev_class;
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "100ask,oled" },
{},
};
/*-------------------------------------------------------------------------*/
static int spidev_probe(struct spi_device *spi)
{
/* 1. 记录spi_device */
oled = spi;
/* 2. 注册字符设备 */
major = register_chrdev(0, "100ask_oled", &spidev_fops);
spidev_class = class_create(THIS_MODULE, "100ask_oled");
device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_oled");
/* 3. 获得GPIO引脚 */
dc_gpio = gpiod_get(&spi->dev, "dc", 0);
return 0;
}
static int spidev_remove(struct spi_device *spi)
{
gpiod_put(dc_gpio);
/* 反注册字符设备 */
device_destroy(spidev_class, MKDEV(major, 0));
class_destroy(spidev_class);
unregister_chrdev(major, "100ask_oled");
return 0;
}
static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "100ask_spi_oled_drv",
.of_match_table = of_match_ptr(spidev_dt_ids),
},
.probe = spidev_probe,
.remove = spidev_remove,
/* NOTE: suspend/resume methods are not necessary here.
* We don't do anything except pass the requests to/from
* the underlying controller. The refrigerator handles
* most issues; the controller driver handles the rest.
*/
};
/*-------------------------------------------------------------------------*/
static int __init spidev_oled_init(void)
{
int status;
status = spi_register_driver(&spidev_spi_driver);
if (status < 0) {
}
return status;
}
module_init(spidev_oled_init);
static void __exit spidev_oled_exit(void)
{
spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_oled_exit);
MODULE_LICENSE("GPL");
3. 编写app层操作程序
//font.h文件,字符信息对应的像素数组
#ifndef _FONT_H_
#define _FONT_H_
const unsigned char oled_asc2_8x16[95][16]=
{
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},// 0
{0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00},//!1
{0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//"2
{0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00},//#3
{0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00},//$4
{0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00},//%5
{0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10},//&6
{0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//'7
{0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00},//(8
{0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00},//)9
{0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00},//*10
{0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00},//+11
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00},//,12
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01},//-13
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00},//.14
{0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00},///15
{0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00},//016
{0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//117
{0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00},//218
{0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00},//319
{0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00},//420
{0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00},//521
{0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00},//622
{0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00},//723
{0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00},//824
{0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00},//925
{0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00},//:26
{0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00},//;27
{0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00},//<28
{0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00},//=29
{0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00},//>30
{0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00},//?31
{0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00},//@32
{0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20},//A33
{0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00},//B34
{0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00},//C35
{0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00},//D36
{0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00},//E37
{0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00},//F38
{0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00},//G39
{0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20},//H40
{0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//I41
{0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00},//J42
{0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00},//K43
{0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00},//L44
{0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00},//M45
{0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00},//N46
{0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00},//O47
{0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00},//P48
{0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00},//Q49
{0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20},//R50
{0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00},//S51
{0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00},//T52
{0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00},//U53
{0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00},//V54
{0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00},//W55
{0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20},//X56
{0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00},//Y57
{0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00},//Z58
{0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00},//[59
{0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00},//\60
{0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00},//]61
{0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//^62
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},//_63
{0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//`64
{0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20},//a65
{0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00},//b66
{0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00},//c67
{0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20},//d68
{0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00},//e69
{0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//f70
{0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00},//g71
{0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20},//h72
{0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//i73
{0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00},//j74
{0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00},//k75
{0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},//l76
{0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F},//m77
{0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20},//n78
{0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00},//o79
{0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00},//p80
{0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80},//q81
{0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00},//r82
{0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00},//s83
{0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00},//t84
{0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20},//u85
{0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00},//v86
{0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00},//w87
{0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00},//x88
{0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00},//y89
{0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00},//z90
{0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40},//{91
{0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00},//|92
{0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00},//}93
{0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//~94
};
const unsigned char hz_1616[][32]={
{0x02,0x00,0x02,0x00,0xE2,0xFF,0x22,0x42,0x22,0x42,0x32,0x42,0x2A,0x42,0x26,0x42,0x22,0x42,0x22,0x42,0x22,0x42,0x22,0x42,0xE2,0xFF,0x02,0x00,0x02,0x00,0x00,0x00},/*"百",0*/
{0x00,0x00,0xF8,0xFF,0x01,0x00,0x02,0x00,0x00,0x00,0xE2,0x1F,0x22,0x08,0x22,0x08,0x22,0x08,0xE2,0x1F,0x02,0x00,0x02,0x40,0x02,0x80,0xFE,0x7F,0x00,0x00,0x00,0x00},/*"问",1*/
{0x00,0x00,0xFE,0xFF,0x02,0x10,0x22,0x08,0x42,0x06,0x82,0x01,0x72,0x0E,0x02,0x10,0x22,0x08,0x42,0x06,0x82,0x01,0x72,0x4E,0x02,0x80,0xFE,0x7F,0x00,0x00,0x00,0x00},/*"网",2*/
};
#endif
//spi_oled.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include "font.h"
#define OLED_IOC_INIT 123
#define OLED_IOC_SET_POS 124
//为0 表示命令,为1表示数据
#define OLED_CMD 0
#define OLED_DATA 1
static int fd_spidev;
static int dc_pin_num;
void oled_write_cmd_data(unsigned char uc_data,unsigned char uc_cmd);
int oled_init(void);
int oled_fill_data(unsigned char fill_Data);
void OLED_DIsp_Clear(void);
void OLED_DIsp_All(void);
void OLED_DIsp_Set_Pos(int x, int y);
void OLED_DIsp_Char(int x, int y, unsigned char c);
void OLED_DIsp_String(int x, int y, char *str);
void OLED_DIsp_CHinese(unsigned char x,unsigned char y,unsigned char no);
void OLED_DIsp_Test();
void OLED_DIsp_Set_Pos(int x, int y);
void oled_write_datas(const unsigned char *buf, int len)
{
write(fd_spidev, buf, len);
}
/**********************************************************************
* 函数名称: OLED_DIsp_Clear
* 功能描述: 整个屏幕显示数据清0
* 输入参数:无
* 输出参数: 无
* 返 回 值:
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/03/15 V1.0 芯晓 创建
***********************************************************************/
void OLED_DIsp_Clear(void)
{
unsigned char x, y;
char buf[128];
memset(buf, 0, 128);
for (y = 0; y < 8; y++)
{
OLED_DIsp_Set_Pos(0, y);
oled_write_datas(buf, 128);
}
}
/**********************************************************************
* 函数名称: OLED_DIsp_All
* 功能描述: 整个屏幕显示全部点亮,可以用于检查坏点
* 输入参数:无
* 输出参数:无
* 返 回 值:
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/03/15 V1.0 芯晓 创建
***********************************************************************/
void OLED_DIsp_All(void)
{
unsigned char x, y;
char buf[128];
memset(buf, 0xff, 128);
for (y = 0; y < 8; y++)
{
OLED_DIsp_Set_Pos(0, y);
oled_write_datas(buf, 128);
}
}
//坐标设置
/**********************************************************************
* 函数名称: OLED_DIsp_Set_Pos
* 功能描述:设置要显示的位置
* 输入参数:@ x :要显示的column address
@y :要显示的page address
* 输出参数: 无
* 返 回 值:
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/03/15 V1.0 芯晓 创建
***********************************************************************/
void OLED_DIsp_Set_Pos(int x, int y)
{
ioctl(fd_spidev, OLED_IOC_SET_POS, x | (y << 8));
}
/**********************************************************************
* 函数名称: OLED_DIsp_Char
* 功能描述:在某个位置显示字符 1-9
* 输入参数:@ x :要显示的column address
@y :要显示的page address
@c :要显示的字符的ascii码
* 输出参数: 无
* 返 回 值:
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/03/15 V1.0 芯晓 创建
***********************************************************************/
void OLED_DIsp_Char(int x, int y, unsigned char c)
{
int i = 0;
/* 得到字模 */
const unsigned char *dots = oled_asc2_8x16[c - ' '];
/* 发给OLED */
OLED_DIsp_Set_Pos(x, y);
/* 发出8字节数据 */
//for (i = 0; i < 8; i++)
// oled_write_cmd_data(dots[i], OLED_DATA);
oled_write_datas(&dots[0], 8);
OLED_DIsp_Set_Pos(x, y+1);
/* 发出8字节数据 */
//for (i = 0; i < 8; i++)
//oled_write_cmd_data(dots[i+8], OLED_DATA);
oled_write_datas(&dots[8], 8);
}
/**********************************************************************
* 函数名称: OLED_DIsp_String
* 功能描述: 在指定位置显示字符串
* 输入参数:@ x :要显示的column address
@y :要显示的page address
@str :要显示的字符串
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/03/15 V1.0 芯晓 创建
***********************************************************************/
void OLED_DIsp_String(int x, int y, char *str)
{
unsigned char j=0;
while (str[j])
{
OLED_DIsp_Char(x, y, str[j]);//显示单个字符
x += 8;
if(x > 127)
{
x = 0;
y += 2;
}//移动显示位置
j++;
}
}
/**********************************************************************
* 函数名称: OLED_DIsp_CHinese
* 功能描述:在指定位置显示汉字
* 输入参数:@ x :要显示的column address
@y :要显示的page address
@chr :要显示的汉字,三个汉字“百问网”中选择一个
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/03/15 V1.0 芯晓 创建
***********************************************************************/
void OLED_DIsp_CHinese(unsigned char x,unsigned char y,unsigned char no)
{
unsigned char t,adder=0;
OLED_DIsp_Set_Pos(x,y);
for(t=0;t<16;t++)
{//显示上半截字符
oled_write_datas(&hz_1616[no][t*2], 1);
adder+=1;
}
OLED_DIsp_Set_Pos(x,y+1);
for(t=0;t<16;t++)
{//显示下半截字符
oled_write_datas(&hz_1616[no][t*2+1], 1);
adder+=1;
}
}
/**********************************************************************
* 函数名称: OLED_DIsp_Test
* 功能描述: 整个屏幕显示测试
* 输入参数:无
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/03/15 V1.0 芯晓 创建
***********************************************************************/
void OLED_DIsp_Test(void)
{
int i;
OLED_DIsp_String(0, 0, "wiki.100ask.net");
OLED_DIsp_String(0, 2, "book.100ask.net");
OLED_DIsp_String(0, 4, "bbs.100ask.net");
for(i = 0; i < 3; i++)
{ //显示汉字 百问网
OLED_DIsp_CHinese(32+i*16, 6, i);
}
}
/* spi_oled /dev/100ask_oled */
int main(int argc, char **argv)
{
if (argc != 2)
{
printf("Usage: %s /dev/100ask_oled\n", argv[0]);
return -1;
}
fd_spidev = open(argv[1], O_RDWR);
if (fd_spidev < 0) {
printf("open %s err\n", argv[1]);
return -1;
}
ioctl(fd_spidev, OLED_IOC_INIT);
OLED_DIsp_Clear();
OLED_DIsp_Test();
return 0;
}
本文章参考了韦东山老师驱动大全部分笔记,其余内容为自己整理总结而来。水平有限,欢迎各位在评论区指导交流!!!😁😁😁