当前位置: 首页 > news >正文

Day59 SPI驱动与ADXL345传感器应用及Linux系统移植基础

day59 SPI驱动与ADXL345传感器应用及Linux系统移植基础

本日内容涵盖SPI总线协议的底层配置、ADXL345加速度计的驱动实现,以及Linux内核驱动开发前的系统移植基础知识。我们将从裸机驱动的SPI初始化开始,逐步封装读写函数,最终在板卡上读取传感器数据并显示。随后,我们将过渡到Linux内核驱动领域,详细解析系统启动的三个核心阶段:Bootloader、Kernel和Rootfs,并介绍相关的硬件存储概念。


一、SPI总线底层驱动实现

1.1 硬件引脚配置与片选控制

首先,我们需要将i.MX6ULL芯片的UART2引脚复用为ECSPI3的信号线,并初始化片选引脚(GPIO1_IO20)为高电平,以确保设备处于非选中状态。

// spi.c
#include "../imx6ull/MCIMX6Y2.h"
#include "../imx6ull/core_ca7.h"
#include "../imx6ull/fsl_iomuxc.h"
#include "spi.h"void spi_init()
{// 将UART2的TX/RX/CTS/RTS引脚复用为ECSPI3的SS/SCLK/MOSI/MISOIOMUXC_SetPinMux(IOMUXC_UART2_TX_DATA_GPIO1_IO20, 1);  // SS (片选)IOMUXC_SetPinMux(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK, 1);  // SCLK (时钟)IOMUXC_SetPinMux(IOMUXC_UART2_CTS_B_ECSPI3_MOSI, 1);   // MOSI (主出从入)IOMUXC_SetPinMux(IOMUXC_UART2_RTS_B_ECSPI3_MISO, 1);   // MISO (主入从出)// 配置引脚电气属性 (100K下拉, 100MHz, 40Ohm驱动能力)IOMUXC_SetPinConfig(IOMUXC_UART2_TX_DATA_GPIO1_IO20, 0x10B0);IOMUXC_SetPinConfig(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK, 0x10B0);IOMUXC_SetPinConfig(IOMUXC_UART2_CTS_B_ECSPI3_MOSI, 0x10B0);IOMUXC_SetPinConfig(IOMUXC_UART2_RTS_B_ECSPI3_MISO, 0x10B0);// 将GPIO1_IO20配置为输出模式,并设置为高电平(片选无效)GPIO1->GDIR |= (1 << 20);      // 设置为输出方向GPIO1->DR |= (1 << 20);        // 输出高电平,取消片选
}

功能讲解: 此函数完成了SPI通信所需的硬件初始化。它通过IOMUXC_SetPinMux将物理引脚的功能从UART切换到SPI,再通过IOMUXC_SetPinConfig设置引脚的电气特性。最后,将片选引脚(GPIO1_IO20)配置为输出并置高,确保在初始化完成前SPI设备不会被意外选中。


1.2 SPI寄存器配置详解

接下来,我们配置ECSPI3控制器的核心寄存器,包括控制寄存器(CONREG)和配置寄存器(CONFIGREG),以设定通信参数。

// spi.c (续)
void spi_init()
{// ... (前面的引脚配置代码)// 配置ECSPI3控制寄存器 (CONREG)ECSPI3->CONREG = (0x7 << 20) |  // 突发长度: 8位 (7+1=8)(14 << 12) |  // 分频器: 2^14 (用于计算时钟频率)(2 << 8) |    // 时钟分频: 2^2 = 4(1 << 4) |    // 通道模式: 主机模式(1 << 3) |    // 自动发送使能: 写入TXDATA后自动发送(1 << 0);     // SPI使能: 开启SPI模块// 配置ECSPI3配置寄存器 (CONFIGREG)ECSPI3->CONFIGREG = (1 << 20) | // 时钟极性 (CPOL): 1 (空闲时钟为高)(1 << 4) |   // 片选控制: 低电平有效(1 << 0);    // 相位 (CPHA): 1 (数据在时钟第二个边沿采样)
}

功能讲解:

  • CONREG: 设定了SPI的核心工作模式。
    • (0x7 << 20): 设置突发长度为8位,即每次传输一个字节。
    • (14 << 12) | (2 << 8): 计算SPI时钟频率。系统时钟为60MHz,经过(2^2)分频后再经(2^14)分频,最终得到约1.46MHz的SPI时钟,满足ADXL345最大5MHz的要求。
    • (1 << 4): 设置为Master模式。
    • (1 << 3): 启用自动发送功能,向TXDATA写入数据后会自动触发传输。
    • (1 << 0): 使能SPI模块。
  • CONFIGREG: 设定了SPI的电气特性。
    • (1 << 20): 设置CPOL=1,即时钟空闲时为高电平。
    • (1 << 4): 设置片选信号为低电平有效。
    • (1 << 0): 设置CPHA=1,即数据在时钟的第二个边沿采样。这与ADXL345的规格书要求一致。

1.3 SPI读写函数封装

根据SPI“全双工”特性(发送的同时接收),我们分别封装了写入和读取函数,并最终提供一个通用的传输函数。

// spi.c (续)
void spi_write(unsigned char data)
{unsigned char invalid_data = 0;// 等待发送缓冲区为空while(!(ECSPI3->STATREG & (1 << 0)));// 发送数据ECSPI3->TXDATA = data;// 等待接收缓冲区有数据 (此时接收到的是无效数据)while(!(ECSPI3->STATREG & (1 << 3)));// 读取并丢弃接收到的无效数据invalid_data = ECSPI3->RXDATA;
}unsigned char spi_read(void)
{unsigned char data = 0;// 等待发送缓冲区为空while(!(ECSPI3->STATREG & (1 << 0)));// 发送一个虚拟字节 (0xFF),以产生时钟来读取数据ECSPI3->TXDATA = 0xff;// 等待接收缓冲区有数据while(!(ECSPI3->STATREG & (1 << 3)));// 读取并返回接收到的有效数据data = ECSPI3->RXDATA;return data;
}unsigned char spi_transfer(unsigned char data)
{unsigned char r_data = 0;// 等待发送缓冲区为空while(!(ECSPI3->STATREG & (1 << 0)));// 发送数据ECSPI3->TXDATA = data;// 等待接收缓冲区有数据while(!(ECSPI3->STATREG & (1 << 3)));// 读取接收到的数据r_data = ECSPI3->RXDATA;return r_data;   
}

功能讲解:

  • spi_write: 用于向SPI设备写入一个字节。它先等待发送缓冲区空闲,然后写入数据。由于SPI是全双工的,写入的同时也会收到一个字节,这个字节对写操作而言是无效的,因此需要将其读出并丢弃。
  • spi_read: 用于从SPI设备读取一个字节。为了产生时钟信号以获取数据,它必须先向总线发送一个虚拟字节(通常为0xFF)。设备会在时钟作用下将数据放在MISO线上,程序再读取该数据。
  • spi_transfer: 这是一个通用的读写函数。它执行一次完整的SPI事务:发送一个字节,并同时接收一个字节。这个函数可以用于任何需要进行单字节交换的场景。

二、ADXL345加速度计驱动实现

2.1 ADXL345寄存器访问协议

ADXL345使用SPI协议进行通信。其读写命令格式如下:

  • 写入: 0x00 + 寄存器地址 (最高位为0表示写操作)
  • 读取: 0x80 + 寄存器地址 (最高位为1表示读操作)
// adxl345.h
#ifndef __ADXL345_H
#define __ADXL345_Htypedef struct __adxl345_g
{unsigned short x;unsigned short y;unsigned short z;
}adxl345_g_t;void adxl345_write(unsigned char reg, unsigned char data);
unsigned char adxl345_read(unsigned char reg);
void adxl345_init(void);
void adxl345_get_data(adxl345_g_t * data);#endif

功能讲解: 头文件定义了一个结构体adxl345_g_t用于存储XYZ三轴的加速度值,并声明了四个对外接口函数,供应用层调用。


2.2 驱动函数实现

// adxl345.c
#include "../imx6ull/MCIMX6Y2.h"
#include "../imx6ull/core_ca7.h"
#include "../imx6ull/fsl_iomuxc.h"
#include "adxl345.h"
#include "spi.h"void adxl345_write(unsigned char reg, unsigned char data)
{// 拉低片选,选中ADXL345GPIO1->DR &= ~(1 << 20);// 发送寄存器地址spi_transfer(reg);// 发送要写入的数据spi_transfer(data);// 拉高片选,取消选中GPIO1->DR |= (1 << 20);
}unsigned char adxl345_read(unsigned char reg)
{unsigned char data = 0;// 拉低片选,选中ADXL345GPIO1->DR &= ~(1 << 20);// 发送带读标志的寄存器地址spi_transfer(reg | 0x80);// 发送虚拟字节,读取数据data = spi_transfer(0xff);// 拉高片选,取消选中GPIO1->DR |= (1 << 20);return data;
}void adxl345_init(void)
{// 设置电源控制寄存器 (0x2D): 使能测量模式adxl345_write(0x2D, 0x08);// 设置数据格式寄存器 (0x31): 全分辨率模式,±16g量程adxl345_write(0x31, 0x08);// 设置数据速率寄存器 (0x2C): 设置为200Hzadxl345_write(0x2C, 0x0B);
}void adxl345_get_data(adxl345_g_t * data)
{// 读取X轴数据 (低8位)data->x = adxl345_read(0x32);// 读取X轴数据 (高8位),并左移8位与低8位合并data->x |= (adxl345_read(0x33) << 8);// 读取Y轴数据 (低8位)data->y = adxl345_read(0x34);// 读取Y轴数据 (高8位),并左移8位与低8位合并data->y |= (adxl345_read(0x35) << 8);// 读取Z轴数据 (低8位)data->z = adxl345_read(0x36);// 读取Z轴数据 (高8位),并左移8位与低8位合并data->z |= (adxl345_read(0x37) << 8);
}

功能讲解:

  • adxl345_write: 实现了对ADXL345寄存器的写入操作。它首先拉低片选信号,然后通过spi_transfer发送寄存器地址和数据,最后拉高片选信号结束通信。
  • adxl345_read: 实现了对ADXL345寄存器的读取操作。它拉低片选,发送带读标志的寄存器地址,再发送虚拟字节0xFF以获取数据,最后拉高片选。
  • adxl345_init: 初始化ADXL345。它配置了三个关键寄存器:
    • 0x2D: 设置为0x08,启动测量模式。
    • 0x31: 设置为0x08,选择全分辨率模式和±16g量程。
    • 0x2C: 设置为0x0B,将数据输出速率设为200Hz。
  • adxl345_get_data: 读取XYZ三轴的加速度原始数据。由于每个轴的数据是16位,存储在两个连续的寄存器中,因此需要分别读取高低8位并合并。

2.3 应用层测试代码

// main.c (节选)
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "core_ca7.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "interrupt.h"
#include "clock.h"
#include "epit.h"
#include "gpt.h"
#include "uart.h"
#include "stdio.h"
#include "i2c.h"
#include "string.h"
#include "adc.h"
#include "lcd.h"
#include "framebuffer.h"
#include "pwm.h"
#include "touchscreen.h"
#include "adxl345.h"
#include "spi.h"int main(void)
{system_interrupt_init();clock_init();led_init();beep_init();gpt1_init();uart1_init();i2c_init(I2C1);i2c_init(I2C2);pwm1_init();lcd_init();clear_screen();spi_init(); // 初始化SPIunsigned char dev_id = 0;    char buf[10] = {0};// 读取设备ID,验证通信是否正常dev_id = adxl345_read(0);sprintf(buf, "devid %02x", dev_id);lcd_show_string(100, 100, 200, 200, 32, buf, 0xff0000);adxl345_init(); // 初始化ADXL345while(1){unsigned char str[40] = {0};adxl345_g_t data;// 获取当前XYZ三轴数据adxl345_get_data(&data);// 格式化为字符串sprintf(str, "%d  %d  %d   ", data.x, data.y, data.z);// 在LCD上显示lcd_show_string(100, 200, 200, 200, 32, str, 0xff0000);}return 0;
}

理想运行结果: 程序启动后,LCD屏幕上会先显示ADXL345的设备ID(应为0xE5)。随后,屏幕下方会实时刷新并显示XYZ三轴的原始加速度数据。当移动或倾斜开发板时,这些数值会发生相应变化。


三、Linux内核驱动开发前置知识:系统移植基础

3.1 Linux系统启动的三个核心阶段

一个完整的Linux系统启动过程分为三个主要阶段:

  1. Bootloader (引导程序): 一个小型的裸机程序,负责为内核启动准备环境并引导内核启动。
  2. Kernel (内核): 操作系统的核心,负责管理系统的硬件资源和软件进程。
  3. Rootfs (根文件系统): 一个按特定格式组织的文件集合,包含了系统运行所需的所有程序、库和配置文件。

这三个阶段按顺序执行,缺一不可。


3.2 Bootloader详解

Bootloader是一个一次性执行的裸机程序,它的主要任务是在内核启动前完成一系列初始化工作。

主要功能:

  • 初始化CPU: 设置CPU的工作模式。
  • 初始化异常向量表: 为后续的中断和异常处理做准备。
  • 关闭看门狗 (Watchdog): 防止在初始化过程中因超时而被复位。
  • 初始化时钟: 配置系统时钟源和分频器。
  • 初始化栈: 为C语言程序运行提供堆栈空间。
  • 初始化内存: 配置SDRAM控制器,使其能够正常工作。
  • 关闭缓存 (Cache): 关闭D-Cache(数据缓存),有时也关闭I-Cache(指令缓存),以确保直接访问硬件寄存器。
  • 关闭MMU: 在内核初始化之前,通常关闭内存管理单元。
  • 初始化外设: 如串口、网口等,用于调试或加载内核。
  • 集成协议: 如TFTP、NFS等,用于从网络加载内核。
  • 搬移内核: 将内核镜像从存储介质(如SD卡、eMMC)复制到内存中。
  • 向内核传参: 告诉内核根文件系统的位置、类型、控制台等信息。
  • 启动内核: 设置PC指针指向内核入口地址,将CPU控制权完全移交给内核。

关键点: Bootloader的任务完成后,其自身就不再运行,CPU的控制权彻底移交给了内核。


3.3 Kernel详解

内核是一个庞大而复杂的程序,它是操作系统的核心,负责管理系统的所有资源。

主要功能:

  • 文件管理: 管理磁盘上的所有文件和目录。
  • 进程管理: 创建、调度和销毁进程,并提供进程间通信机制。
  • 内存管理: 管理物理内存和虚拟内存,为进程分配和回收内存空间。
  • 网络管理: 提供网络协议栈,实现网络通信功能。
  • 设备管理: 管理各种硬件设备,为应用程序提供统一的访问接口。

内核启动的最后阶段是加载并挂载根文件系统,然后启动init进程,这是用户空间的第一个进程。


3.4 Rootfs详解

根文件系统是系统启动后加载的第一个文件系统,它是一个包含所有必要文件的集合。

包含内容:

  • 配置文件: 如/etc/fstab, /etc/inittab等。
  • 系统命令: 如ls, cp, mkdir等。
  • 服务程序: 如SSH服务器、Web服务器等后台服务。
  • 库文件: 如libc.so等动态链接库。
  • 用户程序: 用户安装的应用程序。
  • 普通文件: 文本文件、图片、音频文件等。

挂载 (Mount): “挂载”是指将一个文件系统连接到现有文件系统树中的某个目录(挂载点)的过程。例如,将位于/dev/mmcblk0p2的分区挂载到/目录下。


3.5 存储器类型概览

  • RAM (Random Access Memory): 随机存取存储器,访问速度快,但掉电后数据丢失。用于存放正在运行的程序和数据。
  • ROM (Read Only Memory): 只读存储器,掉电后数据不丢失,但访问速度慢。早期用于存放固件。
  • Flash: 闪存,结合了RAM和ROM的优点,速度快且掉电不丢失。现代嵌入式系统中广泛使用的存储介质,如SD卡、eMMC。
  • DDR3: 第三代双倍数据速率同步动态随机存取存储器,是本开发板上使用的内存类型,容量为512MB。
  • eMMC: 嵌入式多媒体卡,是一种集成了控制器的Flash存储设备,本开发板上容量为8GB。

3.6 系统烧写实践

我们使用Mfgtool2-eMMC-ddr512-SDCard.vbs工具将预先编译好的Linux系统镜像烧录到SD卡中。

步骤:

  1. 断开开发板电源。
  2. 将SD卡从开发板拔出。
  3. 使用USB Type-C线将开发板连接到电脑。
  4. 打开烧写工具Mfgtool2-eMMC-ddr512-SDCard.vbs
  5. 给开发板上电,等待工具识别到设备(显示为"Connected")。
  6. 将SD卡插入开发板的卡槽。
  7. 点击工具上的"Start"按钮,开始烧写。
  8. 等待两个进度条都走完,烧写完成。
  9. 拔掉USB线,将拨码开关拨至SD卡启动模式(通常是1和7位向上)。
  10. 上电启动,即可看到Linux系统成功运行。

此过程将完整的Linux系统(包括Bootloader, Kernel, Rootfs)部署到SD卡上,为后续的内核驱动开发做好了准备。

http://www.dtcms.com/a/467196.html

相关文章:

  • 做网站有限公司外贸企业有哪些公司
  • 设计师接私活的网站深圳装修设计培训
  • 数据密度与视觉层次:让信息既丰富又不乱
  • 银川市网站制作公司邢台网站建设基本流程
  • 浏览器解析HTML完整教程
  • dns加网站家具网站建设策划
  • 备案网站制作汽车租赁网站设计
  • 网站服务器设置地点elision豪华级创意企业wordpress
  • 济南网站建设(力推聚搜网络)海外网络推广
  • 合肥网站建设佳蓝网络网站建设培训深圳
  • 建设网站的申请信用卡吗网站头部导航样式
  • 网站建设及优化 赣icp公司网站建设合同电子版
  • 做阿里巴巴类似的网站天津西青区怎么样
  • 个人网站建设赚取流量费wordpress网站多层循环调用文章
  • Subversion(SVN)下载和安装教程(附安装包)
  • 网站建设推广注意什么网站建设学什么语音
  • 简单网站设计容城网站建设
  • Redis-字符串(String)类型
  • 网站建设分工个人网站建设方案策划书
  • 设计服务网站thinkphp网站开发教程
  • Python中的鸭子类型:理解动态类型的力量
  • c++左值与右值(自写)
  • 保定网站建设的过程ui设计包括什么
  • 网站和数据库工业园网站建设
  • NAS 上的轻量级 RSS 阅读器,支持多种提要,还能自动嗅探
  • 怎么进行网站维护怎么建自己的网址
  • NNDL 作业三
  • 【第三方网站代码登记测试_HTTP头语法代码详解】
  • 怎样建设网站卖农产品dede网站地图制作
  • 【双机位A卷】华为OD笔试之【模拟】双机位A-新学校选址【Py/Java/C++/C/JS/Go六种语言】【欧弟算法】全网注释最详细分类最全的华子OD真题题解