嵌入式学习 day62 SPI子系统、adxl345驱动、驱动回顾
一、SPI
1、特性:
2、spi组成:
SCLK:时钟信号线。与I2C类似的,所有设备都都需要时钟信号线进行同步,该信号由主机负责提供;
MOSI:主出从入,相当于主机的数据发送线;
MISO:主入从出,相当于主机的数据接收线;
CS:片选信号,与I2C不同,SPI设备没有所谓的从机地址,连接在总线上的设备通过CS片选信号选择。在同一时刻,总线上只有一个外设被选择,CS信号线通常是低电平有效的。
注:需要注意的是,对于MOSI和MISO来说不是必须存在的,可以只有MOSI或者只有MISO。当然这样的话就只能实现单工通信方式了,这样的SPI成为三线SPI。至于到底采用三线方式还是四线方式取决于外设本身。例如,OLCD只需要主机发送数据而不需要从机发送数据,就可以使用三线SPI的连接方式。
3、时序:
(1)时钟极性(Clock Polarity 简称CPOL):规定了时钟信号线在空闲时的状态,CPOL=0表示时钟信号线在空闲时为低电平;CPOL=1表示时钟信号线在空闲是为高电平;
(2)时钟相位(Clock Phase简称CPHA):与I2C不同,SPI的数据接收方并不是在时钟高或者低电平时采样的,而是在时钟信号处于沿时,至于是在上升沿还是下降沿取决于CPHA。CPHA=0表示是在时钟信号变化的第一个沿时采样,CPHA=1表示是在时钟信号变化的第二个沿时采样。
注:这样的排列组合总共能够组合出四种方式,具体使用哪种方式是由外设厂家规定的
4、spi结构
5、spi框架流程
与i2c类似
6、设备树
(1)imx6ull.dtsi
(2) arch/arm/boot/dts/imx6ull-alientek-emmc.dts
二、adxl345
1、概念:
ADXL345 是由 ADI(亚德诺半导体,Analog Devices) 推出的一款低成本、低功耗、高分辨率的 三轴 MEMS(微机电系统)加速度传感器,广泛用于消费电子、可穿戴设备、工业监测、汽车电子等领域,核心功能是检测物体在 X、Y、Z 三个正交方向的线性加速度(包括重力加速度),并输出数字信号供处理器解析。
2、连接方式:
3、时序:
(1)写时序
(2)读时序
注:MB:是单个寄存器操作,还是多个寄存器操作
4、寄存器
5、驱动编写
(1)配置设备树
vim arch/arm/boot/dts/imx6ull-alientek-emmc.dts
(2)驱动层编写
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/miscdevice.h>
#include <linux/spi/spi.h>/* ADXL345 寄存器定义 */
#define ADXL345_DEVID 0x00 // 设备ID寄存器
#define ADXL345_DEVID_VAL 0xE5 // 预期设备ID值
#define ADXL345_DATA_FORMAT 0x31 // 数据格式寄存器
#define ADXL345_POWER_CTL 0x2D // 电源控制寄存器
#define ADXL345_DATAX0 0x32 // X轴数据起始地址static ssize_t adxl345_read(struct file *fp, char __user *puser, size_t n, loff_t *off);
static int adxl345_probe(struct spi_device *spi);
static int adxl345_remove(struct spi_device *spi);
static struct spi_device *adxl345_spi;static struct of_device_id adxl345_matchtable[] = {{.compatible = "pute,puteadxl345"},{},
};static struct spi_device_id adxl345_table[] = {{.name = "puteadxl345"},{},
};static struct file_operations fops = {.owner = THIS_MODULE,.read = adxl345_read,
};static struct miscdevice miscdevice = {.minor = MISC_DYNAMIC_MINOR,.name = "misc_adxl345",.fops = &fops,
};static struct spi_driver adxl345_driver = {.driver = {.owner = THIS_MODULE,.name = "puteadxl345",.of_match_table = adxl345_matchtable,},.id_table = adxl345_table,.probe = adxl345_probe,.remove = adxl345_remove,
};/* 读取三轴加速度的主函数 */
int adxl345_read_xyz(struct spi_device *spi, int *x, int *y, int *z)
{int ret;u8 tx_buf[7] = {0}; // 发送缓冲区:命令+6字节数据u8 rx_buf[7] = {0}; // 接收缓冲区struct spi_transfer tr = {.tx_buf = tx_buf,.rx_buf = rx_buf,.len = 7, // 1字节命令 + 6字节数据.delay_usecs = 20,};struct spi_message msg;/* 构造读取命令 (0x80: 读标志 | 0x40: 多字节模式) */tx_buf[0] = ADXL345_DATAX0 | 0x80 | 0x40; // 连续读取6字节数据spi_message_init(&msg);spi_message_add_tail(&tr, &msg);/* 执行SPI传输 */ret = spi_sync(spi, &msg);if (ret < 0) {pr_err("SPI transfer failed: %d\n", ret);return ret;}/* 解析原始数据 (小端模式: LSB在前) */*x = (s16)((rx_buf[2] << 8) | rx_buf[1]); *y = (s16)((rx_buf[4] << 8) | rx_buf[3]);*z = (s16)((rx_buf[6] << 8) | rx_buf[5]);return 0;
}/* 初始化ADXL345传感器 */
int adxl345_init(struct spi_device *spi)
{int ret;u8 tx[2], rx[2];/* 验证设备ID */tx[0] = ADXL345_DEVID | 0x80; // 读命令ret = spi_write_then_read(spi, tx, 1, rx, 1);if (ret < 0 || rx[0] != ADXL345_DEVID_VAL) {pr_info("Device ID check failed (got 0x%02X, expected 0x%02X)\n", rx[0], ADXL345_DEVID_VAL);return -ENODEV;}/* 配置数据格式 (±16g, 全分辨率) */tx[0] = ADXL345_DATA_FORMAT; // 寄存器地址tx[1] = 0x0B; // 0x0B = 0b1011 (全分辨率 + ±16g)[6](@ref)ret = spi_write_then_read(spi, tx, 2, NULL, 0);if (ret < 0) return ret;/* 启动测量模式 */tx[0] = ADXL345_POWER_CTL;tx[1] = 0x08; // 0x08 = 测量模式[6](@ref)return spi_write_then_read(spi, tx, 2, NULL, 0);
}static ssize_t adxl345_read(struct file *fp, char __user *puser, size_t n, loff_t *off)
{int ret = 0;int buf[3] = {0};if (adxl345_init(adxl345_spi) != 0) {pr_info("ADXL345 initialization failed\n");return -EIO;}/* 读取加速度数据 */if (adxl345_read_xyz(adxl345_spi, &buf[0], &buf[1], &buf[2]) != 0) {pr_info("read adxl345 failed\n");}ret = copy_to_user(puser, buf, sizeof(buf));if (ret)pr_info("copy_to_user failed\n");return sizeof(buf);
}static int adxl345_probe(struct spi_device *spi)
{int ret = 0;adxl345_spi = spi;ret = misc_register(&miscdevice);if (ret)pr_info("misc register failed\n");pr_info("adxl345_spi->bits_per_word = %d\n", adxl345_spi->bits_per_word);pr_info("adxl345_spi->mode = %#x\n", adxl345_spi->mode);pr_info("adxl345 probe ok\n");return 0;
}static int adxl345_remove(struct spi_device *spi)
{int ret = 0;ret = misc_deregister(&miscdevice);if (ret)pr_info("misc deregister failed\n");pr_info("adxl345 remove ok\n");return 0;
}static int __init adxl345_drv_init(void)
{int ret = 0;ret = spi_register_driver(&adxl345_driver);if (ret)pr_info("spi_register_driver failed");return 0;
}static void __exit adxl345_drv_exit(void)
{spi_unregister_driver(&adxl345_driver);return;
}module_init(adxl345_drv_init);
module_exit(adxl345_drv_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("haiersen");
(3)应用层编写
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/ioctl.h>
#include <signal.h>
#include "../adxl345_lcd_fb/framebuff.h"int main(void)
{int fd = 0;int data[3] = {0};char buf[100] = {0};fb_t *pfb = NULL;pfb = framebuff_init("/dev/fb0");if (NULL == pfb){printf("framebuff_init failed\n");return -1;}fd = open("/dev/misc_adxl345", O_RDWR);if (-1 == fd){perror("fail to open");return -1;}clear_screen(pfb);while (1){read(fd, data, sizeof(data));sprintf(buf,"x:%05d, y:%05d, z:%05d", data[0], data[1], data[2]);draw_string(pfb,100,200, buf,0,0,0,255, 255, 255);fflush(stdout);usleep(10000);}close(fd);return 0;
}
三、驱动学习回顾
四、framebuffer
framebuffer:帧缓存的技术,通过申请显存空间,将显存空间写入每个像素点的RGB信息来完成对屏幕像素点颜色的控制
1、找到裸机自带的驱动代码
(1)在arch/arm/boot/dts/imx6ull.dtsi找到compatible匹配选项
(2)使用find . -type f -print0 | xargs -0 grep -l "函数名"找到驱动所在位置
(3)查看驱动代码
2、设备树
3、应用层操作framebuffer设备的方法
(1)打开设备
(2)ioct获得设备信息(分辨率、RGB888、RGB565)
(3)申请显存空间
(4)向显存空间写入数据,对应屏幕显示颜色信息
(5)封装一些功能函数接口
4、应用层代码编写
(1)main.c
#include "framebuff.h"
#include <stdio.h>int main(void)
{ fb_t *pfb = NULL;pfb = framebuff_init("/dev/fb0");if (NULL == pfb){printf("framebuff_init failed\n");return -1;}clear_screen(pfb);
// draw_string(pfb, 200, 300, "Hello World!", 255, 255, 255, 0, 0, 0);
// draw_hanzi(pfb, 200, 300, "溫", 255, 255, 255, 0, 0, 0);draw_hanzi_string(pfb, 200, 300, "温度", 255, 255, 255, 0, 0, 0);draw_hanzi_string(pfb, 250, 300, "湿度", 255, 255, 255, 0, 0, 0);framebuff_deinit(pfb);return 0;
}
(2)framebuffer.h
#ifndef __FRAMEBUFF_H__
#define __FRAMEBUFF_H__#include <linux/fb.h>/* 單個像素點類型 */
typedef struct __rgb__
{unsigned char B; //藍色unsigned char G; //綠色unsigned char R; //紅色unsigned char Reserved;
}rgb_t;/* 屏幕操作句柄 */
typedef struct __fb__
{struct fb_var_screeninfo vscreeninfo;//屏幕參數rgb_t *prgb; //顯存空間首地址int fd; //屏幕文件描述符
}fb_t;/* 漢字結構 */
typedef struct __hanzi__
{char str[4]; //漢字的字符串unsigned char zimo[32]; //漢字字模
}hanzi_t;extern unsigned char fontdata_8x16[4096];
extern hanzi_t ghanzi[50];extern fb_t *framebuff_init(const char *pdevname);
extern void framebuff_deinit(fb_t *pfb);
extern int draw_pixel(fb_t *pfb, int y, int x, unsigned char r, unsigned char g, unsigned b);
extern int draw_hor_line(fb_t *pfb, int y, int x, int width, unsigned char r, unsigned char g, unsigned char b);
extern int draw_ver_line(fb_t *pfb, int y, int x, int height, unsigned char r, unsigned char g, unsigned char b);
extern int draw_dict(fb_t *pfb, int y, int x, int width, int height, unsigned char r, unsigned char g, unsigned char b);
extern int fill_dict(fb_t *pfb, int y, int x, int width, int height, unsigned char r, unsigned char g, unsigned char b);
extern void clear_screen(fb_t *pfb);
extern void draw_ascii(fb_t *pfb, int y, int x, unsigned char ch,unsigned char fr, unsigned char fg, unsigned char fb,unsigned char br, unsigned char bg, unsigned char bb);
extern void draw_string(fb_t *pfb, int y, int x, char *pstr,unsigned char fr, unsigned char fg, unsigned char fb,unsigned char br, unsigned char bg, unsigned char bb);
extern void draw_hanzi(fb_t *pfb, int y, int x, char *pstr, unsigned char fr, unsigned char fg, unsigned char fb,unsigned char br, unsigned char bg, unsigned char bb);
extern void draw_hanzi_string(fb_t *pfb, int y, int x, char *pstr, unsigned char fr, unsigned char fg, unsigned char fb,unsigned char br, unsigned char bg, unsigned char bb);
#endif
(3)framebuffer.c
#include "framebuff.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <linux/fb.h>
#include <stdio.h>
#include <sys/mman.h>/* 打開屏幕設備,返回具有屏幕信息的句柄 */
fb_t *framebuff_init(const char *pdevname)
{int ret = 0;fb_t *pfb = NULL;pfb = malloc(sizeof(fb_t));if (NULL == pfb){return NULL;}pfb->fd = open(pdevname, O_RDWR);if (-1 == pfb->fd){return NULL;}ret = ioctl(pfb->fd, FBIOGET_VSCREENINFO, &pfb->vscreeninfo);if (ret) {return NULL;}printf("width:%d, height:%d\n", pfb->vscreeninfo.xres_virtual, pfb->vscreeninfo.yres_virtual);if (32 == pfb->vscreeninfo.bits_per_pixel) {printf("format RGB888!\n");} else if (16 == pfb->vscreeninfo.bits_per_pixel) {printf("format RGB565\n");}pfb->prgb = mmap(NULL, pfb->vscreeninfo.xres_virtual * pfb->vscreeninfo.yres_virtual * pfb->vscreeninfo.bits_per_pixel / 8,PROT_READ | PROT_WRITE, MAP_SHARED, pfb->fd, 0);if (NULL == pfb->prgb) {return NULL;}return pfb;
}/* 銷毀屏幕信息的句柄 */
void framebuff_deinit(fb_t *pfb)
{if (pfb != NULL){free(pfb);}return;
}/* 繪制一個像素點 */
int draw_pixel(fb_t *pfb, int y, int x, unsigned char r, unsigned char g, unsigned b)
{(pfb->prgb + y * pfb->vscreeninfo.xres_virtual + x)->R = r;(pfb->prgb + y * pfb->vscreeninfo.xres_virtual + x)->G = g;(pfb->prgb + y * pfb->vscreeninfo.xres_virtual + x)->B = b;return 0;
}/* 繪制橫線 */
int draw_hor_line(fb_t *pfb, int y, int x, int width, unsigned char r, unsigned char g, unsigned char b)
{int i = 0;for(i = 0; i < width; i++){draw_pixel(pfb, y, x+i, r, g, b);}return 0;
}/* 繪制豎線 */
int draw_ver_line(fb_t *pfb, int y, int x, int height, unsigned char r, unsigned char g, unsigned char b)
{int i = 0;for(i = 0; i < height; i++){draw_pixel(pfb, y+i, x, r, g, b);}return 0;
}/* 繪制矩形 */
int draw_dict(fb_t *pfb, int y, int x, int width, int height, unsigned char r, unsigned char g, unsigned char b)
{draw_hor_line(pfb, y, x, width, r, g, b);draw_hor_line(pfb, y+height, x, width, r, g, b);draw_ver_line(pfb, y, x, height, r, g, b);draw_ver_line(pfb, y, x+width, height, r, g, b);return 0;
}/* 繪制實心矩形 */
int fill_dict(fb_t *pfb, int y, int x, int width, int height, unsigned char r, unsigned char g, unsigned char b)
{int i = 0;for (i = 0; i < height; i++){draw_hor_line(pfb, y+i, x, width, r, g, b);}return 0;
}/* 清屏 */
void clear_screen(fb_t *pfb)
{fill_dict(pfb, 0, 0, pfb->vscreeninfo.xres_virtual, pfb->vscreeninfo.yres_virtual, 0, 0, 0);return;
}/* 繪制ASCII碼字符 */
void draw_ascii(fb_t *pfb, int y, int x, unsigned char ch,unsigned char fr, unsigned char fg, unsigned char fb,unsigned char br, unsigned char bg, unsigned char bb)
{int j = 0;int i = 0;unsigned char flag = 0x80;for (j = 0; j < 16; j++){for (flag = 0x80, i = 0; i < 8; i++, flag >>= 1){if (fontdata_8x16[ch*16+j] & flag) {draw_pixel(pfb, y+j, x+i, fr, fg, fb);}else {draw_pixel(pfb, y+j, x+i, br, bg, bb);}}}return;
}/* 繪制字符串 */
void draw_string(fb_t *pfb, int y, int x, char *pstr,unsigned char fr, unsigned char fg, unsigned char fb,unsigned char br, unsigned char bg, unsigned char bb)
{int i = 0;while (*pstr != '\0'){draw_ascii(pfb, y, x+i*8, *pstr, fr, fg, fb, br, bg, bb);pstr++;i++;}return;
}/* 顯示中文 */
void draw_hanzi(fb_t *pfb, int y, int x, char *pstr, unsigned char fr, unsigned char fg, unsigned char fb,unsigned char br, unsigned char bg, unsigned char bb)
{unsigned char zimo[32] = {0};int i = 0;int j = 0;unsigned char flag = 0x80;int n = 0;for (n = 0; n < sizeof(ghanzi) / sizeof(ghanzi[0]); n++){if (0 == strncmp(ghanzi[n].str, pstr, 3)){break;}}if (n == (sizeof(ghanzi) / sizeof(ghanzi[0]))){return;}for (j = 0; j < 16; j++){for (flag = 0x80, i = 0; i < 8; i++, flag >>= 1){if (ghanzi[n].zimo[2*j] & flag) {draw_pixel(pfb, y+j, x+i, fr, fg, fb);}else {draw_pixel(pfb, y+j, x+i, br, bg, bb);}}for (flag = 0x80, i = 8; i < 16; i++, flag >>= 1){if (ghanzi[n].zimo[2*j+1] & flag) {draw_pixel(pfb, y+j, x+i, fr, fg, fb);}else {draw_pixel(pfb, y+j, x+i, br, bg, bb);}}}return;
}/* 繪制漢字字符串 */
void draw_hanzi_string(fb_t *pfb, int y, int x, char *pstr, unsigned char fr, unsigned char fg, unsigned char fb,unsigned char br, unsigned char bg, unsigned char bb)
{int i = 0;while (*pstr != '\0'){draw_hanzi(pfb, y, x+i*16, pstr, fr, fg, fb, br, bg, bb);pstr += 3;i++;}return;
}
(4)font_8x16.c(acsii)
(5)font_16x16.c(汉字)
5、应用