DAY66 SPI Driver for ADXL345 Accelerometer

SPI Driver for ADXL345 Accelerometer

1. Core Principles of SPI Protocol

SPI is a full-duplex, synchronous, master-slave communication bus. To drive peripherals, first master the physical layer wiring and timing rules.

1.1 Physical Layer: Four-Wire Connection Logic

Each wire in the standard SPI four-wire system has a clear function. The specific connections between the IMX6ULL (master device) and ADXL345 (slave device) are as follows:

Signal Line Function Description IMX6ULL Pin (ECSPI3) ADXL345 Pin
SCLK Serial clock, generated by master UART2_RX_DATA SCLK
MOSI Master Out Slave In UART2_CTS_B MOSI
MISO Master In Slave Out UART2_RTS_B MISO
CS/SS Slave Select (active low, GPIO emulated) UART2_TX_DATA (GPIO1_IO20) CS

Note: The IMX6ULL's ECSPI3 has a built-in hardware CS, but this article uses GPIO to emulate CS for greater flexibility and to help beginners understand the core logic of "chip select."

1.2 Timing Rules: CPOL and CPHA Determine Communication Mode

SPI timing is defined by two key parameters: CPOL (Clock Polarity) and CPHA (Clock Phase) , which combine to form four communication modes. ADXL345 recommends Mode 3 (CPOL=1, CPHA=1).

Parameter Meaning Mode 3 Config
CPOL Clock idle level: 0=low idle, 1=high idle 1
CPHA Data sampling edge: 0=first clock edge, 1=second clock edge 1

Timing logic for Mode 3:

  • SCLK remains high when idle;
  • The master sends data on the falling edge of SCLK, and the slave samples data on the rising edge (second edge).

2. IMX6ULL ECSPI Controller: From Pins to Register Configuration

The IMX6ULL's SPI controller is called ECSPI (Enhanced Configurable SPI). Using ECSPI3 as an example, we complete the underlying hardware initialization.

2.1 Pin Multiplexing Configuration

IMX6ULL pins are multifunctional. The corresponding pins must first be multiplexed as ECSPI3 functions, and electrical characteristics (pull-up, speed, etc.) must be configured.

c 复制代码
#include "imx6ull.h"  

// Pin multiplexing configuration function (low-level register encapsulation, no modification needed)  
void IOMUXC_SetPinMux(unsigned int mux_register, unsigned int mux_mode);  
void IOMUXC_SetPinConfig(unsigned int config_register, unsigned int config_value);  

// ECSPI3 pin initialization  
void spi3_pin_init(void)  
{  
    // 1. MISO: UART2_RTS_B multiplexed as ECSPI3_MISO  
    IOMUXC_SetPinMux(IOMUXC_UART2_RTS_B_ECSPI3_MISO, 0);  
    IOMUXC_SetPinConfig(IOMUXC_UART2_RTS_B_ECSPI3_MISO, 0x10B1); // Pull-up, 100MHz speed  

    // 2. MOSI: UART2_CTS_B multiplexed as ECSPI3_MOSI  
    IOMUXC_SetPinMux(IOMUXC_UART2_CTS_B_ECSPI3_MOSI, 0);  
    IOMUXC_SetPinConfig(IOMUXC_UART2_CTS_B_ECSPI3_MOSI, 0x10B1);  

    // 3. SCLK: UART2_RX_DATA multiplexed as ECSPI3_SCLK  
    IOMUXC_SetPinMux(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK, 0);  
    IOMUXC_SetPinConfig(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK, 0x10B1);  

    // 4. CS: UART2_TX_DATA multiplexed as GPIO1_IO20  
    IOMUXC_SetPinMux(IOMUXC_UART2_TX_DATA_GPIO1_IO20, 0);  
    IOMUXC_SetPinConfig(IOMUXC_UART2_TX_DATA_GPIO1_IO20, 0x10B1);  

    // GPIO1_IO20 set as output, initial high (ADXL345 not selected)  
    GPIO1->GDIR |= (1 << 20);  
    GPIO1->DR |= (1 << 20);  
}  

2.2 ECSPI3 Core Register Configuration

The IMX6ULL's ECSPI controller core configuration is in CONREG (Control Register) and CONFIGREG (Configuration Register). The focus is on matching ADXL345's Mode 3 timing while setting master mode, data length, and clock division.

c 复制代码
// ECSPI3 initialization: Configure timing, mode, and data length  
void spi3_init(void)  
{  
    // 1. Initialize pins first  
    spi3_pin_init();  

    // 2. Reset CONREG register  
    ECSPI3->CONREG = 0;  

    /* CONREG parameter breakdown:  
     * 7<<20: BURST_LENGTH=7 → 8-bit data length (0~7 total 8 bits)  
     * 0x0E<<12: CLK_CTL=14 → Clock division (IPG_CLK=66MHz → 66/(14+1)=4.4MHz, compatible with ADXL345)  
     * 2<<8: CHIP_SELECT=2 → Select channel 0 (ECSPI3_CH0)  
     * 1<<4: MODE=1 → Master mode  
     * 1<<3: SCLK_CTL=1 → Clock controlled by CONREG's CLK_CTL  
     * 1<<0: ENABLE=1 → Enable ECSPI3  
     */  
    ECSPI3->CONREG |= (7 << 20) | (0x0E << 12) | (2 << 8) | (1 << 4) | (1 << 3) | (1 << 0);  

    // 3. Reset CONFIGREG register  
    ECSPI3->CONFIGREG = 0;  

    /* CONFIGREG parameter breakdown:  
     * 1<<20: SCLK_POL=1 → CPOL=1 (clock idle high)  
     * 1<<4: MOSI_POL=0 → MOSI pin polarity normal (no inversion)  
     * 1<<0: PHASE=1 → CPHA=1 (sample on second edge)  
     */  
    ECSPI3->CONFIGREG |= (1 << 20) | (1 << 4) | (1 << 0);  
}  

3. ADXL345 Driver Implementation: SPI Read/Write + Sensor Control

The core operation of ADXL345 is "register read/write"---writing configurations and reading data via SPI to/from sensor registers. First, implement the underlying SPI read/write, then encapsulate sensor-specific interfaces.

3.1 SPI Full-Duplex Read/Write Base Function

SPI is full-duplex communication: sending 1 byte while simultaneously receiving 1 byte. Thus, the core of the read/write function is "send data + wait for receive," using ECSPI's TX/RX FIFO for data transfer.

c 复制代码
// SPI3 channel 0 read/write function: Send 1 byte, receive 1 byte (full-duplex)  
unsigned int spi3_ch0_write_and_read(unsigned int data)  
{  
    // Wait for TX FIFO to be empty (ready for new data)  
    while((ECSPI3->STATREG & (1 << 0)) == 0);  
    // Write data to TXDATA  
    ECSPI3->TXDATA = data;  

    // Wait for RX FIFO to have data (data received)  
    while((ECSPI3->STATREG & (1 << 3)) == 0);  
    // Return received data  
    return ECSPI3->RXDATA;  
}  

3.2 ADXL345 Register Read/Write Encapsulation

ADXL345 register read/write follows fixed rules:

  • Read: Set the highest bit of the register address (reg_addr | 0x80), send the address followed by 0xFF (dummy data) to trigger the clock, then read the return value;
  • Write: Clear the highest bit of the register address, send the address followed by the data to be written.
c 复制代码
// ADXL345 read register: reg_addr=register address, returns register value  
unsigned char adxl345_read(unsigned char reg_addr)  
{  
    unsigned char ret = 0;  

    // 1. Pull CS low to select ADXL345  
    GPIO1->DR &= ~(1 << 20);  

    // 2. Send read command (highest bit of address set to 1)  
    spi3_ch0_write_and_read(reg_addr | 0x80);  
    // 3. Send dummy data 0xFF, read sensor return value  
    ret = spi3_ch0_write_and_read(0xFF);  

    // 4. Pull CS high to end communication  
    GPIO1->DR |= (1 << 20);  

    return ret;  
}  

// ADXL345 write register: reg_addr=register address, data=value to write  
void adxl345_write(unsigned char reg_addr, unsigned char data)  
{  
    // 1. Pull CS low to select ADXL345  
    GPIO1->DR &= ~(1 << 20);  

    // 2. Send write command (highest bit of address cleared)  
    spi3_ch0_write_and_read(reg_addr & ~0x80);  
    // 3. Send data to write  
    spi3_ch0_write_and_read(data);  

    // 4. Pull CS high to end communication  
    GPIO1->DR |= (1 << 20);  
}  

3.3 ADXL345 Initialization and Data Acquisition

The ADXL345 requires initialization (range configuration, entering measurement mode) before use, followed by reading triaxial acceleration data (16-bit, LSB first).

c 复制代码
// Define ADXL345 data structure: stores X/Y/Z triaxial acceleration values  
typedef struct {  
    short x; // X-axis acceleration  
    short y; // Y-axis acceleration  
    short z; // Z-axis acceleration  
} ADXL345_Data;  

// ADXL345 initialization: verify ID + configure range + enter measurement mode  
int adxl345_init(void)  
{  
    unsigned char dev_id;  
    
    // 1. Read device ID (DEVID register address 0x00, fixed value 0xE5)  
    dev_id = adxl345_read(0x00);  
    if(dev_id != 0xE5) {  
        return -1; // ID error, initialization failed  
    }  
    
    // 2. Configure DATA_FORMAT (0x31): ±16g range (0x0F)  
    adxl345_write(0x31, 0x0F);  
    
    // 3. Configure POWER_CTL (0x2D): 0x08 → enter measurement mode  
    adxl345_write(0x2D, 0x08);  
    
    return 0; // Initialization successful  
}  

// Read ADXL345 triaxial acceleration data  
ADXL345_Data adxl345_read_data(void)  
{  
    ADXL345_Data data;  
    
    // X-axis: low byte (0x32) + high byte (0x33)  
    data.x = adxl345_read(0x32);  
    data.x |= (short)(adxl345_read(0x33) << 8);  
    
    // Y-axis: low byte (0x34) + high byte (0x35)  
    data.y = adxl345_read(0x34);  
    data.y |= (short)(adxl345_read(0x35) << 8);  
    
    // Z-axis: low byte (0x36) + high byte (0x37)  
    data.z = adxl345_read(0x36);  
    data.z |= (short)(adxl345_read(0x37) << 8);  
    
    return data;  
}  

4. Main Function Test: Verify SPI Driver Functionality

Finally, write the main function to initialize hardware and loop through reading acceleration data, which can be printed via UART (requires prior UART initialization).

c 复制代码
int main(void)  
{  
    ADXL345_Data accel_data;  
    
    // 1. Initialize SPI3  
    spi3_init();  
    
    // 2. Initialize ADXL345  
    if(adxl345_init() != 0) {  
        // Print ID error via UART (requires uart_printf implementation)  
        uart_printf("ADXL345 init failed! ID error\r\n");  
        while(1);  
    }  
    uart_printf("ADXL345 init success!\r\n");  
    
    // 3. Loop to read triaxial data  
    while(1) {  
        accel_data = adxl345_read_data();  
        // Print X/Y/Z axis data  
        uart_printf("X: %d, Y: %d, Z: %d\r\n", accel_data.x, accel_data.y, accel_data.z);  
        // Simple delay  
        for(int i=0; i<1000000; i++);  
    }  
    
    return 0;  
}  

5. Practical Debugging: Common Issue Troubleshooting

Beginners most frequently encounter issues like "ID read as 0/255" or "data fluctuations." Follow these steps to troubleshoot:

5.1 Abnormal ID Reading (0/255)

  1. Hardware Wiring: Check if SCLK/MOSI/MISO/CS pins are reversed or poorly soldered.
  2. CS Level: Use a multimeter to measure GPIO1_IO20---ensure it pulls low during register reads and high afterward.
  3. SPI Mode: Confirm CONFIGREG's CPOL (1<<20) and CPHA (1<<0) are correctly configured (Mode 3).
  4. Clock Division: ECSPI3's clock must not exceed ADXL345's maximum rate (5MHz). This example uses 4.4MHz after division, meeting requirements.
  5. Logic Analyzer Capture : Observe whether MOSI sends 0x80 (read ID command) and MISO returns 0xE5.

5.2 Data Fluctuations

  1. Power Stability: ADXL345 requires 3.3V power supply---ensure minimal ripple.
  2. Grounding: Sensor GND must share ground with IMX6ULL to avoid interference.
  3. Range Configuration: Verify DATA_FORMAT register range matches data parsing (e.g., ±16g corresponds to 13mg/LSB resolution).

6. Summary

This article starts with SPI protocol principles, completes IMX6ULL ECSPI3 low-level configuration, encapsulates ADXL345 register read/write operations, and ultimately achieves triaxial acceleration data acquisition. Key takeaways:

  1. SPI's core lies in timing matching (CPOL/CPHA), which must align with the peripheral.
  2. IMX6ULL ECSPI configuration focuses on "master mode + data length + clock division."
  3. ADXL345's essence is "register read/write rules" (read address MSB=1, write address MSB=0).
  4. In bare-metal development, "FIFO waiting" and "CS level control" are critical SPI communication details.
相关推荐
CCIE-Yasuo8 分钟前
网络安全-学习数据库-Mysql-2
数据库·学习·mysql
这个DBA有点耶14 分钟前
内核级AI与多模融合:AI数据库在海外场景的落地要点
数据库·人工智能·架构
涛思数据(TDengine)18 分钟前
从工业本体到 AI 原生:TDengine 出席 2026 QCon 分享工业数据基础设施路径
大数据·数据库·人工智能·tdengine·工业
Jesse_EC25 分钟前
换乘次数最少的路径 SQL 查询详解与实战经验——从 WITH RECURSIVE 到 pgRouting
数据库·postgresql
工控丫丫31 分钟前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库
xiaoye-duck41 分钟前
《C++ 项目:手写大模型接入 SDK》DeepSeek接口设计(下):流式响应实现和测试
开发语言·网络·c++·语言模型
其实防守也摸鱼1 小时前
信创安全竞赛知识总结与题库
大数据·网络·人工智能·算法·安全·php
GeW1 小时前
国产化替代进入深水区之后Linux 与数据库为什么必须走向重建
linux
万联WANFLOW2 小时前
异地厂区视频监控远程运维:从组网到安全的实施要点
运维·网络·安全·企业组网·sdwan
这个DBA有点耶2 小时前
云数据库半夜卡顿,CPU才30%?深挖云盘IOPS突发限制与内核挂起机制
数据库·mysql·云原生