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.
相关推荐
Drifter_yh1 小时前
【黑马点评】Redisson 分布式锁核心原理剖析
java·数据库·redis·分布式·spring·缓存
鸽鸽程序猿1 小时前
【Redis】zset 类型介绍
数据库·redis·缓存
z玉无心1 小时前
Redis
数据库·redis·oracle
予枫的编程笔记1 小时前
【Redis核心原理篇2】Redis 单线程模型:为什么单线程还能这么快?
数据库·redis·缓存
fengxin_rou1 小时前
一文吃透 Redis 压缩列表、listpack 及哈希表扩容与并发查询
数据库·redis·散列表
袁袁袁袁满1 小时前
Linux云服务器如何判断系统是否发生过异常断电?
linux·运维·服务器
winfreedoms1 小时前
Puppypi——hiwonder-toolbox中配置文件解析
网络·智能路由器
一只鹿鹿鹿1 小时前
智慧水利一体化建设方案
大数据·运维·开发语言·数据库·物联网
学不完的1 小时前
haproxy
linux·运维·https·负载均衡·haproxy
LCG元2 小时前
STM32MP1边缘网关:Linux系统下Modbus转MQTT协议转换实战
linux·stm32·嵌入式硬件