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.
相关推荐
_F_y5 小时前
MySQL视图
数据库·mysql
2301_790300965 小时前
Python单元测试(unittest)实战指南
jvm·数据库·python
云边云科技_云网融合5 小时前
AIoT智能物联网平台:架构解析与边缘应用新图景
大数据·网络·人工智能·安全
若风的雨5 小时前
WC (Write-Combining) 内存类型优化原理
linux
YMWM_5 小时前
不同局域网下登录ubuntu主机
linux·运维·ubuntu
若风的雨5 小时前
NCCL 怎么解决rdma 网卡本地send的时候需要对端recv要准备好的问题,或者mpi 怎么解决的?
网络
九章-5 小时前
一库平替,融合致胜:国产数据库的“统型”范式革命
数据库·融合数据库
zmjjdank1ng5 小时前
restart与reload的区别
linux·运维
哼?~5 小时前
进程替换与自主Shell
linux
浩浩测试一下6 小时前
DDOS 应急响应Linux防火墙 Iptable 使用方式方法
linux·网络·安全·web安全·网络安全·系统安全·ddos