树莓派SPI通信主机从机代码实现与详解
SPI(Serial Peripheral Interface)是由Motorola提出的一种高速、全双工、同步串行通信协议,广泛应用于嵌入式系统中,如传感器、显示屏、存储器等外设的连接。其核心结构由主设备(Master)与从设备(Slave)构成,通过四根信号线完成数据传输:MOSI(主发从收)、MISO(从发主收)、SCLK(时钟信号)、CS(片选信号)。SPI通信具有无需地址、速率高、接口简
简介:本文围绕树莓派上的SPI通信展开,详细介绍了基于BCM2835芯片的SPI主机与从机代码实现过程。SPI是一种高速同步串行通信接口,常用于连接微控制器与传感器、LCD、ADC等外设。文章讲解了SPI通信的基本原理、GPIO引脚配置、时钟设置、数据传输流程等内容,并通过C语言结合bcm2835库进行编程实践,涵盖初始化、数据收发、资源释放等关键步骤。压缩包中包含完整的代码与文档,适合嵌入式开发者和树莓派爱好者进行学习与实操。 
1. SPI通信协议简介
SPI(Serial Peripheral Interface)是由Motorola提出的一种 高速、全双工、同步串行通信协议 ,广泛应用于嵌入式系统中,如传感器、显示屏、存储器等外设的连接。其核心结构由 主设备(Master) 与 从设备(Slave) 构成,通过四根信号线完成数据传输:MOSI(主发从收)、MISO(从发主收)、SCLK(时钟信号)、CS(片选信号)。
SPI通信具有 无需地址、速率高、接口简单 等优点,常用于短距离、高速的数据交互场景。相较于I2C和UART,SPI在速度和灵活性方面更具优势,但其 缺乏标准协议规范 ,需要主从设备之间约定好通信模式(Mode 0~3)与时序参数。
在现代嵌入式系统中,如树莓派、STM32、ESP32等平台均内置SPI控制器,支持快速连接多种外设模块,为物联网、工业控制、智能硬件等领域提供了坚实的基础。
2. 树莓派BCM2835芯片SPI接口概述
2.1 BCM2835芯片的SPI模块
树莓派的第一代型号基于Broadcom BCM2835芯片设计,该芯片集成了多个外设接口,其中SPI(Serial Peripheral Interface)是嵌入式系统中常用的高速同步串行通信接口之一。BCM2835芯片内嵌一个SPI控制器,支持主模式(Master Mode)下的数据传输。该模块为树莓派与外部SPI设备(如传感器、ADC、LCD屏等)之间的通信提供了硬件基础。
2.1.1 SPI控制器的基本结构
BCM2835的SPI控制器由一组寄存器组成,用于控制通信过程的各个方面。其基本结构如下:
| 寄存器名称 | 地址偏移 | 功能描述 |
|---|---|---|
| CS (Control and Status) | 0x00 | 控制SPI模式、启动传输、设置片选信号等 |
| FIFO (Data FIFO) | 0x04 | 用于数据发送与接收的FIFO缓冲区 |
| CLK (Clock Divider) | 0x08 | 设置SPI时钟分频,控制通信速率 |
| DLEN (Data Length) | 0x0C | 设置每次传输的数据长度(字节数) |
| LTOH (Last Transfer Over Head) | 0x10 | 设置最后一次传输的延迟时间 |
| DC (DMA Control) | 0x14 | 控制DMA操作(可选) |
控制器通过这些寄存器完成SPI通信的初始化、数据传输、时钟配置等操作。SPI控制器支持DMA模式,能够实现高速数据传输,减少CPU的负担。
2.1.2 主要寄存器与功能说明
以下是对几个关键寄存器的详细说明:
- CS寄存器 :
该寄存器控制SPI的启动、传输模式、片选信号、中断使能等。例如: CS[2]:设置SPI为DMA使能(DMA Enable)。CS[3]:设置SPI为传输完成中断使能(Interrupt Enable)。CS[4]:设置传输完成后清除FIFO缓冲区(Clear FIFO)。-
CS[7:5]:控制片选信号(Chip Select)。 -
CLK寄存器 :
该寄存器用于设置SPI的时钟频率,通过设置分频值控制SCLK(Serial Clock)的速率。BCM2835的系统时钟为250MHz,因此SPI的SCLK频率可通过公式计算:SCLK = Core Clock / (divider) -
DLEN寄存器 :
设置每次传输的数据长度(以字节为单位)。例如,若设置为0x01,则一次传输1个字节;若为0x10,则为16个字节。 -
FIFO寄存器 :
数据通过FIFO寄存器进行发送和接收。写入FIFO寄存器将数据放入发送缓冲区,读取FIFO寄存器则从接收缓冲区获取数据。
示例代码:SPI寄存器初始化
以下是一个使用 mmap 直接操作寄存器的SPI初始化代码片段:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define BCM2835_PERI_BASE 0x20000000
#define BCM2835_SPI0_BASE (BCM2835_PERI_BASE + 0x204000)
volatile unsigned int *spi0;
int main() {
int mem_fd;
void *spi_map;
// 打开/dev/mem设备
if ((mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
printf("Can't open /dev/mem\n");
exit(-1);
}
// 映射SPI寄存器地址
spi_map = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, BCM2835_SPI0_BASE);
if (spi_map == MAP_FAILED) {
printf("mmap error\n");
close(mem_fd);
exit(-1);
}
spi0 = (volatile unsigned int *)spi_map;
// 设置SPI控制寄存器
spi0[0] = 0x00; // 清除CS寄存器
spi0[0] = (1 << 2); // 启用DMA
spi0[0] |= (1 << 4); // 清除FIFO
spi0[0] |= (1 << 6); // 设置CS0为低电平使能
spi0[0] |= (1 << 7); // 启动SPI传输
// 设置SCLK频率为1MHz(系统时钟250MHz,分频值为250)
spi0[2] = 250;
// 设置数据长度为1字节
spi0[3] = 1;
// 写入FIFO缓冲区
spi0[1] = 0x55; // 发送数据
// 等待传输完成
while (!(spi0[0] & (1 << 16)));
// 关闭映射
munmap(spi_map, 4096);
close(mem_fd);
return 0;
}
代码逻辑分析 :
- 内存映射 :使用
mmap()将BCM2835的SPI寄存器地址映射到用户空间,便于直接访问硬件寄存器。 - 寄存器配置 :
-spi0[0](CS寄存器):清零后设置DMA、FIFO清除、片选信号和启动SPI。
-spi0[2](CLK寄存器):设置SCLK频率为1MHz,即系统时钟250MHz除以250。
-spi0[3](DLEN寄存器):设置单次传输1字节。 - 数据发送 :将数据写入FIFO寄存器(
spi0[1])。 - 等待传输完成 :通过轮询CS寄存器的第16位判断传输是否完成。
该代码展示了如何直接操作BCM2835芯片的SPI寄存器,实现数据发送功能。适用于需要精细控制SPI通信的嵌入式应用场景。
2.2 SPI接口的硬件资源分布
2.2.1 树莓派上的SPI引脚位置
在树莓派的40引脚GPIO排针上,SPI接口由以下引脚组成:
| 信号线 | 引脚编号(物理) | GPIO编号 | 功能说明 |
|---|---|---|---|
| SCLK | 23 | GPIO11 | 串行时钟 |
| MOSI | 19 | GPIO10 | 主输出从输入 |
| MISO | 21 | GPIO9 | 主输入从输出 |
| CE0 | 24 | GPIO8 | 片选信号0 |
| CE1 | 26 | GPIO7 | 片选信号1 |
这些引脚默认配置为SPI模式,但也可通过GPIO复用设置进行更改。树莓派提供了两个片选信号CE0和CE1,允许同时连接两个SPI从设备。
2.2.2 硬件SPI与软件SPI的区别
| 特性 | 硬件SPI | 软件SPI |
|---|---|---|
| 实现方式 | 利用芯片内置SPI控制器 | 使用GPIO模拟SPI时序 |
| 速度 | 高速,可达32MHz | 低速,受CPU影响大 |
| CPU占用 | 极低 | 高,频繁中断处理 |
| 可靠性 | 高,稳定 | 低,容易受干扰 |
| 多路复用 | 支持多个从设备 | 需要手动管理时序 |
硬件SPI依赖BCM2835的SPI控制器,通信速度快、稳定性高,适合需要高速数据传输的场景,如ADC采集、LCD驱动等。而软件SPI通过GPIO模拟SPI时序,适用于调试或低速设备连接。
SPI通信流程图(Mermaid格式)
graph TD
A[开始SPI通信] --> B{是否使用硬件SPI?}
B -->|是| C[配置SPI寄存器]
B -->|否| D[使用GPIO模拟时序]
C --> E[设置SCLK频率]
C --> F[设置片选信号]
C --> G[设置数据长度]
D --> H[设置GPIO为输出]
D --> I[模拟SCLK时序]
D --> J[模拟MOSI数据发送]
J --> K[等待MISO数据接收]
E --> L[写入FIFO缓冲区]
F --> L
G --> L
L --> M[启动SPI传输]
M --> N{是否DMA传输?}
N -->|是| O[启用DMA通道]
N -->|否| P[轮询FIFO状态]
O --> Q[传输完成]
P --> Q
该流程图展示了SPI通信从初始化到数据传输的全过程,涵盖了硬件与软件SPI的不同路径。通过流程图可以清晰理解SPI通信在不同实现方式下的控制逻辑。
2.3 SPI通信在嵌入式系统中的作用
2.3.1 数据传输的实时性与可靠性
SPI协议因其同步时钟机制和全双工通信能力,具备良好的实时性和高数据传输可靠性。在嵌入式系统中,SPI常用于以下场景:
- 传感器数据采集 :如温度、湿度、加速度传感器等,需要高速采集和处理。
- LCD显示控制 :图形LCD或OLED屏幕通过SPI接口与主控芯片通信,实现图像快速刷新。
- 存储器读写 :如SPI Flash、EEPROM等非易失性存储器的数据读写操作。
- 音频传输 :某些音频编解码器(如WM8731)通过SPI进行控制与数据传输。
SPI的高速特性使其在需要大量数据交换的场景中表现优异。例如,ADC芯片通过SPI可以实现每秒数十万次的采样,满足实时监测和处理的需求。
2.3.2 与I2C、UART协议的对比分析
| 特性 | SPI | I2C | UART |
|---|---|---|---|
| 通信方式 | 同步、全双工 | 同步、半双工 | 异步、全双工 |
| 引脚数量 | 4(SCLK、MOSI、MISO、CS) | 2(SDA、SCL) | 2(TXD、RXD) |
| 通信速率 | 高,可达几十MHz | 低至高速模式(400kHz~5MHz) | 中等,通常为115200bps |
| 主从架构 | 单主多从 | 单主/多主多从 | 点对点 |
| 数据格式 | 无固定格式,可定制 | 7/10位地址 + 数据 | 起始位 + 数据位 + 停止位 |
| 适用场景 | 高速数据传输、图形显示、存储器访问 | 传感器、EEPROM等低速设备 | 串口调试、模块间通信 |
SPI协议在速度和灵活性方面优于I2C和UART,适用于高速、大数据量的通信需求。I2C则在引脚数量和多设备支持方面更优,适合连接多个低速外设。UART则适用于简单的点对点通信,常用于调试和模块间数据交换。
示例代码:SPI与I2C通信速率对比
以下是一个SPI与I2C通信速率对比的伪代码示例:
import spidev
import smbus
import time
# SPI初始化
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000 # 1MHz
# I2C初始化
bus = smbus.SMBus(1)
i2c_address = 0x50
# 发送1000个字节测试SPI
start_time = time.time()
for _ in range(1000):
spi.xfer([0x55])
end_time = time.time()
print(f"SPI传输1000字节耗时:{end_time - start_time:.6f}秒")
# 发送1000个字节测试I2C
start_time = time.time()
for _ in range(1000):
bus.write_byte(i2c_address, 0x55)
end_time = time.time()
print(f"I2C传输1000字节耗时:{end_time - start_time:.6f}秒")
逻辑分析 :
- SPI测试 :使用
spidev库设置SPI速率为1MHz,循环发送1000个字节,记录耗时。 - I2C测试 :使用
smbus库连接I2C设备,循环写入1000个字节,记录耗时。 - 结果对比 :SPI通信速率显著高于I2C,适用于高速数据传输场景。
该示例验证了SPI在数据传输效率上的优势,适合在需要快速响应和大量数据交换的嵌入式系统中使用。
本章系统地介绍了BCM2835芯片的SPI模块、硬件资源分布及其在嵌入式系统中的应用。通过对寄存器结构、引脚定义、通信方式及协议对比的深入分析,读者能够全面掌握树莓派SPI通信的基础知识,为后续章节的编程实践打下坚实基础。
3. GPIO引脚配置为SPI模式
树莓派作为一款广受欢迎的嵌入式开发平台,其丰富的GPIO资源为开发者提供了极大的灵活性。在实际项目中,SPI通信常用于连接高速外设,如ADC、DAC、传感器、LCD等。要使用树莓派的SPI功能,首先需要将特定的GPIO引脚配置为SPI模式。本章将深入讲解GPIO引脚的基础知识、SPI模式的配置流程以及常用配置工具的使用方法。
3.1 树莓派GPIO引脚基础知识
树莓派的GPIO引脚不仅可以作为通用输入输出使用,还可以通过复用功能实现特定的外设接口,如SPI、I2C和UART。掌握这些引脚的功能和配置方法,是进行SPI通信的前提。
3.1.1 引脚编号与功能定义
树莓派的GPIO引脚编号存在多种表示方式,主要包括:
- 物理引脚编号(BOARD) :按照物理排列编号,如1~40。
- BCM编号(Broadcom SOC Channel) :基于SoC芯片定义的编号方式,如GPIO2、GPIO3等。
- ** WiringPi编号**:WiringPi库使用的编号方式。
以下为树莓派3B+中与SPI相关的引脚编号对照表:
| 物理引脚编号 | BCM编号 | 功能说明 |
|---|---|---|
| 19 | GPIO10 | MOSI(主出从入) |
| 21 | GPIO9 | MISO(主入从出) |
| 23 | GPIO11 | SCLK(时钟) |
| 24 | GPIO8 | CE0(片选0) |
| 26 | GPIO7 | CE1(片选1) |
提示 :默认情况下,SPI0的MISO、MOSI、SCLK、CE0和CE1对应上述引脚。SPI1则使用不同的引脚(如GPIO16、GPIO17、GPIO18、GPIO19、GPIO20),但通常SPI0已能满足大多数需求。
3.1.2 多功能复用配置方法
每个GPIO引脚都有多种复用模式(Function Select),SPI通信需要将相关引脚设置为 ALT0 模式。具体设置方式如下:
- 访问GPIO寄存器 :树莓派的GPIO功能选择由 GPFSELx 寄存器控制。
- 计算寄存器偏移量 :每个引脚在GPFSEL寄存器中占用3位,例如GPIO10对应GPFSEL1的第30~32位。
- 设置ALT0模式 :将对应的3位设置为
100(即二进制值4)表示ALT0模式。
例如,将GPIO10设置为MOSI功能(ALT0)的代码如下:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#define BCM2835_PERI_BASE 0xFE000000
#define GPIO_BASE (BCM2835_PERI_BASE + 0x200000) // GPIO registers
#define BLOCK_SIZE (4*1024)
volatile unsigned *gpio;
void setup_io() {
int mem_fd;
void *gpio_map;
if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC)) < 0) {
printf("Can't open /dev/mem\n");
exit(-1);
}
gpio_map = mmap(
NULL, // 地址由系统分配
BLOCK_SIZE, // 映射4K字节
PROT_READ|PROT_WRITE, // 可读写
MAP_SHARED, // 共享映射
mem_fd, // 文件描述符
GPIO_BASE // GPIO寄存器起始地址
);
close(mem_fd);
if (gpio_map == MAP_FAILED) {
printf("mmap error %p\n", gpio_map);
exit(-1);
}
gpio = (volatile unsigned *)gpio_map;
}
void set_gpio_alt(int pin, int alt) {
int reg = pin / 10;
int shift = (pin % 10) * 3;
gpio[reg] = (gpio[reg] & ~(7 << shift)) | (alt << shift);
}
int main() {
setup_io();
set_gpio_alt(10, 4); // 设置GPIO10为ALT0
return 0;
}
逐行解释:
setup_io():打开/dev/mem,将GPIO寄存器地址映射到用户空间,以便直接访问硬件寄存器。set_gpio_alt(pin, alt):根据引脚号和功能模式设置寄存器中的对应位。main():调用函数将GPIO10设置为ALT0,即SPI0的MOSI功能。
注意 :此方式为底层寄存器操作,适合理解硬件原理,但实际开发建议使用更高层的库如
wiringPi或bcm2835简化配置。
3.2 SPI模式的配置流程
配置树莓派的SPI接口需要明确指定哪些引脚用于SPI通信,并启用相应的SPI控制器。这一过程可以通过设备树(Device Tree)机制进行配置。
3.2.1 设定MISO、MOSI、SCLK和CE引脚
SPI通信需要以下基本引脚:
- MISO (Master In Slave Out):主机接收数据。
- MOSI (Master Out Slave In):主机发送数据。
- SCLK (Serial Clock):同步时钟。
- CE (Chip Enable):片选信号,控制从机是否响应。
在树莓派中,这些引脚默认已配置为SPI功能,但有时可能被其他功能占用,需要手动确认。
3.2.2 使用设备树配置SPI接口
设备树(Device Tree)是一种描述硬件配置的机制。树莓派使用 .dts (设备树源文件)来定义硬件接口。我们可以通过修改设备树启用或配置SPI接口。
操作步骤如下:
- 查看当前设备树配置 :
ls /boot/overlays | grep spi
输出可能包括:
spi0-2cs.dtbo
spi0-hw-cs.dtbo
spi1-1cs.dtbo
- 编辑
/boot/config.txt文件 :
sudo nano /boot/config.txt
添加以下行以启用SPI0接口并使用硬件片选(CE0):
dtoverlay=spi0-hw-cs
- 重启树莓派 :
sudo reboot
- 验证SPI设备节点 :
ls /dev/spi*
输出应包含:
/dev/spidev0.0 /dev/spidev0.1
表示SPI0已启用,支持两个从设备(CE0和CE1)。
说明 :
spidev0.0对应CE0,spidev0.1对应CE1。
3.3 配置工具与命令行操作
除了手动修改设备树文件,树莓派还提供了图形化和命令行工具来配置SPI接口,简化操作流程。
3.3.1 raspi-config工具的使用
raspi-config 是树莓派官方提供的配置工具,支持图形化界面操作。
操作步骤:
- 打开终端,输入以下命令:
sudo raspi-config
-
使用方向键选择
Interfacing Options→SPI。 -
选择
Yes启用SPI接口。 -
按照提示完成操作,最后选择
Finish并重启系统。
优势 :简单直观,适合新手快速配置SPI接口。
3.3.2 手动修改/boot/config.txt文件
通过编辑 /boot/config.txt 文件,可以手动控制设备树加载选项。
配置示例:
# 启用SPI0并使用硬件片选
dtoverlay=spi0-hw-cs
# 启用SPI1(若需要)
dtoverlay=spi1-1cs
参数说明:
dtoverlay=spi0-hw-cs:启用SPI0接口,并使用硬件片选(CE0和CE1)。dtoverlay=spi1-1cs:启用SPI1接口,使用一个片选(CE0)。
验证配置是否生效:
dmesg | grep spi
输出应包含类似以下信息:
[ 5.678901] spi spi0: controller initialized
[ 5.678912] spidev spi0.0: SPI device registered
表示SPI控制器和设备节点已成功初始化。
总结与扩展
本章从GPIO引脚的基础知识入手,详细介绍了树莓派上SPI引脚的编号方式和复用配置方法。随后讲解了如何通过设备树机制启用SPI接口,并给出了具体的代码示例和配置命令。最后,介绍了使用 raspi-config 工具和手动修改 /boot/config.txt 的两种方式,帮助开发者灵活配置SPI通信环境。
在下一章中,我们将深入探讨SPI通信的主从架构、时序控制和数据格式等内容,进一步夯实SPI编程的理论基础。
4. SPI主机与从机通信原理
SPI(Serial Peripheral Interface)通信协议的核心特性之一是其 主从架构 ,即由一个 主机 (Master)控制整个通信过程,而一个或多个 从机 (Slave)响应主机的请求并交换数据。本章将深入剖析SPI主从通信的工作机制,包括角色定义、时序控制、数据格式、握手机制及常见问题处理,帮助开发者在树莓派等嵌入式平台上高效实现SPI通信。
4.1 SPI通信的主从架构
SPI是一种典型的 单主多从 通信协议,主机负责生成时钟信号(SCLK)并发起通信,从机则通过片选信号(CS)被选中后参与数据交换。
4.1.1 主机与从机的角色定义
- 主机(Master) :
- 控制时钟信号(SCLK)的频率和极性;
- 启动和结束数据传输;
- 管理片选信号(CS),选择通信的从设备;
- 提供MOSI(Master Out Slave In)数据输出;
-
接收来自从机的MISO(Master In Slave Out)数据。
-
从机(Slave) :
- 仅在CS信号有效时响应主机;
- 根据SCLK同步接收MOSI上的数据;
- 在SCLK的控制下将数据通过MISO发送给主机;
- 通常不主动发起通信,只能被动响应。
4.1.2 片选信号(CS)的作用
片选信号(Chip Select)是SPI通信中用于 选择当前通信的从设备 的控制信号。每个从设备都有一个独立的CS引脚。主机通过将某个从机的CS拉低(低电平有效)来激活该设备,其他未被选中的从设备则保持静默。
CS信号的关键作用包括:
| 功能 | 描述 |
|---|---|
| 设备选择 | 多从设备共享MISO、MOSI、SCLK时,CS用于指定目标 |
| 功耗控制 | 非选中状态可降低从设备功耗 |
| 通信同步 | 有些从设备要求CS下降沿作为通信启动信号 |
💡 例如,在树莓派上使用SPI0接口时,CE0(GPIO8)和CE1(GPIO7)是两个片选引脚,支持连接两个SPI从设备。
4.2 通信时序与数据格式
SPI通信的时序由 时钟极性(CPOL) 和 时钟相位(CPHA) 共同决定,这两者定义了数据采样和发送的时机。此外,数据帧结构与传输顺序也影响通信的正确性。
4.2.1 时钟极性与相位(CPOL/CPHA)
SPI通信的四种标准模式由CPOL(Clock Polarity)和CPHA(Clock Phase)组合而成:
| 模式 | CPOL | CPHA | 数据采样边沿 | 数据变化边沿 |
|---|---|---|---|---|
| Mode 0 | 0 | 0 | 上升沿 | 下降沿 |
| Mode 1 | 0 | 1 | 下降沿 | 上升沿 |
| Mode 2 | 1 | 0 | 下降沿 | 上升沿 |
| Mode 3 | 1 | 1 | 上升沿 | 下降沿 |
💡 主机与从机必须设置相同的CPOL和CPHA模式,否则会导致数据采样错误。
示例:Mode 0 的时序图(使用mermaid流程图表示)
sequenceDiagram
主机->>从机: SCLK=0 (CPOL=0)
主机->>从机: 数据在上升沿采样 (CPHA=0)
主机->>从机: 数据在下降沿变化
4.2.2 数据帧结构与传输顺序
SPI通信的数据帧通常为 8位或16位 ,但也可以是其他长度,具体取决于从设备的设计。数据传输顺序分为两种:
- MSB First (高位先传):默认模式,高位先发送;
- LSB First (低位先传):某些设备要求低位先传。
示例:发送数据0xA5(二进制:10100101)
- MSB First :依次发送 1, 0, 1, 0, 0, 1, 0, 1
- LSB First :依次发送 1, 0, 1, 0, 0, 1, 0, 1(顺序反转)
💡 树莓派的BCM2835 SPI控制器支持配置数据传输顺序,可通过寄存器设置
SPI0_TXH的LSBFE位来控制。
4.3 SPI通信的握手机制
SPI通信虽然没有类似I2C的应答机制,但其通过 同步时钟控制 实现高效的数据交换。主机控制SCLK,从机根据时钟同步发送和接收数据。
4.3.1 同步启动与数据交换过程
SPI通信的典型数据交换过程如下:
sequenceDiagram
主机->>从机: 拉低CS,启动通信
主机->>从机: 输出SCLK,开始传输
主机->>从机: MOSI输出第一个bit
从机-->>主机: MISO输出第一个bit
... 重复发送其余bit ...
主机->>从机: 传输完成后拉高CS,结束通信
💡 每次传输一个bit时,主从设备同时交换数据,即“发送一个bit,接收一个bit”,因此SPI是 全双工 通信协议。
4.3.2 常见通信错误与处理策略
在实际应用中,SPI通信可能遇到以下问题:
| 错误类型 | 原因 | 解决策略 |
|---|---|---|
| 数据错位 | CPOL/CPHA配置不一致 | 检查并统一通信模式 |
| 无响应 | 从机未上电或CS未正确拉低 | 检查电源与片选引脚 |
| 数据错误 | 时钟频率过高导致采样失败 | 降低SCLK频率 |
| 无法初始化 | 设备树未启用SPI | 修改 /boot/config.txt 启用SPI |
| 数据不一致 | 缓冲区未清空或读写顺序错误 | 清空缓冲区,确保读写同步 |
示例代码:SPI通信失败的排查
以下是一个使用 bcm2835 库进行SPI通信的代码片段:
#include <bcm2835.h>
#include <stdio.h>
int main(int argc, char **argv)
{
// 初始化BCM2835库
if (!bcm2835_init()) {
printf("BCM2835初始化失败\n");
return 1;
}
// 设置SPI0为主机模式
bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST); // 高位先发
bcm2835_spi_setDataMode(BCM2835_SPI_MODE0); // 模式0
bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_64); // 时钟分频
// 使能CE0片选
bcm2835_spi_chipSelect(BCM2835_SPI_CS0);
// 发送数据
uint8_t tx_data = 0xA5;
uint8_t rx_data = bcm2835_spi_transfer(tx_data);
printf("发送数据: 0x%02X, 接收数据: 0x%02X\n", tx_data, rx_data);
// 关闭BCM2835库
bcm2835_close();
return 0;
}
代码逐行解析:
bcm2835_init():初始化SPI控制器;bcm2835_spi_setBitOrder():设置数据发送顺序;bcm2835_spi_setDataMode():设置通信模式;bcm2835_spi_setClockDivider():设置SCLK频率(系统时钟/64);bcm2835_spi_chipSelect():选择使用CE0片选;bcm2835_spi_transfer():发送一个字节并返回接收到的字节;bcm2835_close():释放资源。
💡 若接收到的数据为0x00或随机值,说明通信失败,应检查硬件连接、电源、时钟配置和片选信号。
小结(非总结,而是内容延伸)
SPI通信虽然结构简单、速率高,但其依赖于 精确的时序配置 和 硬件连接正确性 。在实际开发中,建议使用逻辑分析仪(如Saleae Logic)捕获SPI信号,辅助调试通信问题。下一章将详细介绍如何使用 bcm2835 库进行SPI编程,包括初始化、数据收发、参数配置等实用技巧。
5. 使用bcm2835库进行SPI编程
在嵌入式开发中,高效的硬件通信离不开优秀的库支持。 bcm2835 是一个专为树莓派 BCM2835 系列芯片设计的 C 语言库,它封装了底层寄存器操作,提供了便捷的 API 接口,特别适用于 GPIO、SPI、I2C 等外设的访问。本章将围绕 bcm2835 库的使用,深入讲解如何通过该库进行 SPI 编程,包括库的安装配置、SPI 接口初始化、参数设置、以及数据的发送与接收实现,为后续实战项目打下坚实基础。
5.1 bcm2835库的安装与配置
5.1.1 安装依赖与编译步骤
在使用 bcm2835 库之前,首先需要在树莓派系统中完成安装和配置。该库依赖于标准的编译工具链和 Linux 内核支持。
安装依赖
sudo apt-get update
sudo apt-get install build-essential libtool autoconf automake
下载与编译
wget http://www.airspaybar.com/bcm2835/files/bcm2835-1.71.tar.gz
tar zxvf bcm2835-1.71.tar.gz
cd bcm2835-1.71
./configure
make
sudo make check
sudo make install
逻辑分析 :
-wget:从官网下载源码包;
-tar zxvf:解压.tar.gz格式压缩包;
-./configure:生成 Makefile 配置文件;
-make:编译源码;
-sudo make check:执行测试验证是否编译成功;
-sudo make install:将编译好的库文件安装到系统目录中。
参数说明:
- 版本兼容性 :确保树莓派操作系统(如 Raspbian)与库版本兼容;
- 权限问题 :需要
sudo权限进行安装,因为会写入系统/usr/local/lib等目录; - 交叉编译 :若用于嵌入式交叉编译环境,需指定
--host参数。
5.1.2 库函数的基本使用方法
安装完成后,即可在 C 程序中调用 bcm2835 提供的 API。以下是基本使用步骤:
示例代码:初始化库并打印版本信息
#include <bcm2835.h>
#include <stdio.h>
int main(int argc, char **argv)
{
if (!bcm2835_init())
{
printf("BCM2835 初始化失败。\n");
return 1;
}
printf("BCM2835 库版本: %s\n", bcm2835_version());
bcm2835_close();
return 0;
}
逻辑分析 :
-bcm2835_init():初始化库,映射寄存器;
-bcm2835_version():获取当前库版本信息;
-bcm2835_close():释放资源,结束调用。
编译命令:
gcc -o test_bcm2835 test_bcm2835.c -lbcm2835
表格:常用库函数概览
| 函数名 | 功能说明 |
|---|---|
bcm2835_init() |
初始化库,映射寄存器 |
bcm2835_close() |
释放资源,结束库调用 |
bcm2835_spi_begin() |
启动 SPI 模块 |
bcm2835_spi_setClockDivider() |
设置 SPI 时钟分频 |
bcm2835_spi_transfer() |
发送并接收一个字节数据 |
5.2 初始化与配置SPI接口
5.2.1 初始化SPI通信模块
使用 bcm2835 库进行 SPI 通信前,必须先启用 SPI 模块并进行基本配置。
示例代码:初始化 SPI 模块
#include <bcm2835.h>
#include <stdio.h>
int main(int argc, char **argv)
{
if (!bcm2835_init())
{
printf("BCM2835 初始化失败。\n");
return 1;
}
if (!bcm2835_spi_begin())
{
printf("SPI 初始化失败。\n");
bcm2835_close();
return 1;
}
printf("SPI 初始化成功。\n");
bcm2835_spi_end();
bcm2835_close();
return 0;
}
逻辑分析 :
-bcm2835_spi_begin():启用 SPI 控制器并配置为默认模式(Mode 0);
-bcm2835_spi_end():关闭 SPI 模块;
- 该函数内部会自动配置 SPI 的引脚为复用模式(MISO、MOSI、SCLK)。
5.2.2 设置通信参数(模式、速率)
SPI 通信具有四种标准模式(Mode 0~3),由 CPOL 和 CPHA 决定。此外,还需要设置通信速率(时钟频率)。
示例代码:设置 SPI 模式与速率
#include <bcm2835.h>
#include <stdio.h>
int main(int argc, char **argv)
{
if (!bcm2835_init())
{
printf("BCM2835 初始化失败。\n");
return 1;
}
if (!bcm2835_spi_begin())
{
printf("SPI 初始化失败。\n");
bcm2835_close();
return 1;
}
// 设置 SPI 模式为 Mode 1 (CPOL=0, CPHA=1)
bcm2835_spi_setDataMode(BCM2835_SPI_MODE1);
// 设置 SPI 时钟频率为 1MHz
bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_256);
printf("SPI 模式设置为 Mode 1,频率设置为 1MHz。\n");
bcm2835_spi_end();
bcm2835_close();
return 0;
}
逻辑分析 :
-bcm2835_spi_setDataMode():设置 SPI 通信模式;
-BCM2835_SPI_MODE0 ~ BCM2835_SPI_MODE3:对应四种标准模式;
-bcm2835_spi_setClockDivider():设置时钟分频值,决定 SPI 速率;
- 分频值选择:
-BCM2835_SPI_CLOCK_DIVIDER_65536= 65536 → 最慢;
-BCM2835_SPI_CLOCK_DIVIDER_2= 2 → 最快(约为 125 MHz);
- 常用值为 256(1 MHz)或 64(约 3.9 MHz)。
表格:SPI 模式与 CPOL/CPHA 对应关系
| 模式编号 | CPOL | CPHA | 说明 |
|---|---|---|---|
| Mode 0 | 0 | 0 | 时钟空闲为低电平,上升沿采样 |
| Mode 1 | 0 | 1 | 时钟空闲为低电平,下降沿采样 |
| Mode 2 | 1 | 0 | 时钟空闲为高电平,下降沿采样 |
| Mode 3 | 1 | 1 | 时钟空闲为高电平,上升沿采样 |
5.3 数据发送与接收函数
5.3.1 单字节与多字节数据传输
SPI 是一种全双工通信协议,每次发送一个字节数据的同时也会接收一个字节数据。 bcm2835 提供了 bcm2835_spi_transfer() 函数用于单字节传输,也支持多字节传输函数。
示例代码:单字节发送与接收
#include <bcm2835.h>
#include <stdio.h>
int main(int argc, char **argv)
{
if (!bcm2835_init())
{
printf("BCM2835 初始化失败。\n");
return 1;
}
if (!bcm2835_spi_begin())
{
printf("SPI 初始化失败。\n");
bcm2835_close();
return 1;
}
uint8_t tx_data = 0xA5;
uint8_t rx_data = bcm2835_spi_transfer(tx_data);
printf("发送数据: 0x%02X,接收数据: 0x%02X\n", tx_data, rx_data);
bcm2835_spi_end();
bcm2835_close();
return 0;
}
逻辑分析 :
-bcm2835_spi_transfer():发送一字节并返回接收的一字节;
- 常用于与 SPI 从设备进行交互,如读取传感器数据、发送控制指令等。
示例代码:多字节发送与接收
#include <bcm2835.h>
#include <stdio.h>
#define BUF_SIZE 4
int main(int argc, char **argv)
{
if (!bcm2835_init())
{
printf("BCM2835 初始化失败。\n");
return 1;
}
if (!bcm2835_spi_begin())
{
printf("SPI 初始化失败。\n");
bcm2835_close();
return 1;
}
uint8_t tx_buffer[BUF_SIZE] = {0x01, 0x02, 0x03, 0x04};
uint8_t rx_buffer[BUF_SIZE];
bcm2835_spi_transfernb(tx_buffer, rx_buffer, BUF_SIZE);
for (int i = 0; i < BUF_SIZE; i++)
{
printf("发送[%d]: 0x%02X, 接收[%d]: 0x%02X\n", i, tx_buffer[i], i, rx_buffer[i]);
}
bcm2835_spi_end();
bcm2835_close();
return 0;
}
逻辑分析 :
-bcm2835_spi_transfernb():发送和接收指定长度的缓冲区数据;
-tx_buffer:待发送数据;
-rx_buffer:接收数据;
- 适用于连续数据块传输,如图像数据、ADC 转换结果等。
5.3.2 接收缓冲区与状态检查
在实际开发中,应加入状态检查机制,确保数据完整性和通信稳定性。
示例代码:带状态检查的 SPI 通信
#include <bcm2835.h>
#include <stdio.h>
int main(int argc, char **argv)
{
if (!bcm2835_init())
{
printf("BCM2835 初始化失败。\n");
return 1;
}
if (!bcm2835_spi_begin())
{
printf("SPI 初始化失败。\n");
bcm2835_close();
return 1;
}
uint8_t tx_data = 0xA5;
uint8_t rx_data = bcm2835_spi_transfer(tx_data);
if (rx_data != 0xFF) // 假设 0xFF 表示无效数据
{
printf("有效接收数据: 0x%02X\n", rx_data);
}
else
{
printf("接收到无效数据,可能通信失败。\n");
}
bcm2835_spi_end();
bcm2835_close();
return 0;
}
逻辑分析 :
- 通过判断接收值是否为无效值(如 0xFF),可以识别通信是否正常;
- 在实际项目中,建议使用 CRC 校验、重传机制等方式增强数据可靠性。
Mermaid 流程图:SPI 通信状态检查流程
graph TD
A[开始SPI通信] --> B[发送数据]
B --> C[接收数据]
C --> D{接收数据是否有效?}
D -- 是 --> E[处理有效数据]
D -- 否 --> F[记录错误或重传]
E --> G[结束通信]
F --> G
总结
本章围绕 bcm2835 库的 SPI 编程进行了系统讲解,从库的安装、SPI 模块初始化,到通信参数设置,再到数据发送与接收的实现,层层递进地展示了树莓派平台下 SPI 编程的核心流程。下一章将继续深入 SPI 通信的初始化设置,包括模式选择与时钟频率配置,帮助开发者掌握完整的 SPI 通信控制能力。
6. SPI初始化设置(模式、时钟频率)
在树莓派上进行SPI通信之前,必须对SPI模块进行初始化设置,包括选择合适的通信模式和设定时钟频率。本章将详细介绍SPI通信模式的选择、时钟频率的配置机制以及完整的初始化流程,并通过代码示例说明如何使用bcm2835库进行相关设置。
6.1 SPI通信模式的选择
SPI协议定义了四种标准通信模式,它们由时钟极性(CPOL)与时钟相位(CPHA)两个参数决定。不同的从设备对模式的要求不同,因此主机在初始化SPI接口时必须正确设置该参数以确保与从机的兼容性。
6.1.1 四种标准SPI模式(Mode 0~3)
| 模式编号 | CPOL | CPHA | 时钟空闲状态 | 数据采样边沿 |
|---|---|---|---|---|
| Mode 0 | 0 | 0 | 低电平 | 上升沿 |
| Mode 1 | 0 | 1 | 低电平 | 下降沿 |
| Mode 2 | 1 | 0 | 高电平 | 下降沿 |
| Mode 3 | 1 | 1 | 高电平 | 上升沿 |
- CPOL(Clock Polarity) :决定SPI时钟在空闲状态时的电平。
- CPHA(Clock Phase) :决定数据在哪个时钟边沿被采样。
选择正确的模式是确保SPI通信正常进行的关键。例如,某些ADC芯片可能仅支持Mode 0,而某些EEPROM芯片可能使用Mode 3。因此,在开发过程中应查阅从设备的数据手册以确认其支持的SPI模式。
6.1.2 模式匹配与从机兼容性
SPI模式的匹配原则如下:
- 主机与从机的CPOL和CPHA设置必须一致 ,否则会导致数据采样错误。
- 若从机支持多种模式,可以选择一个主机支持的模式进行通信。
- 在多从机系统中,若不同从机支持的模式不一致,需在切换从机时重新配置SPI模式。
在实际开发中,建议优先选择Mode 0,因为这是大多数设备默认支持的模式。
graph TD
A[开始初始化SPI] --> B[读取从机数据手册]
B --> C{是否支持多种SPI模式?}
C -->|是| D[选择主机支持的模式]
C -->|否| E[设置为主机与从机共同支持的模式]
D --> F[配置CPOL与CPHA寄存器]
E --> F
F --> G[验证通信是否正常]
G -->|正常| H[初始化完成]
G -->|异常| I[重新选择模式并测试]
6.2 时钟频率设置
SPI通信的时钟频率决定了数据传输的速度,是影响通信效率的重要参数。树莓派的SPI控制器支持通过分频机制来设置不同的时钟频率。
6.2.1 时钟分频机制
BCM2835芯片的SPI控制器使用主频(默认为250MHz)作为时钟源,并通过分频器来生成所需的SPI时钟频率。分频系数为偶数,范围为32~65536。分频公式如下:
SPI Clock = Core Clock / (Divider)
其中,Core Clock 通常为 250MHz(可通过设备树修改)。
例如:
- 分频值为 64 → SPI Clock = 250MHz / 64 ≈ 3.90625 MHz
- 分频值为 128 → SPI Clock = 250MHz / 128 ≈ 1.953125 MHz
分频值的设置由 SPI0_CLK 寄存器控制,该寄存器的地址为 0x202040 。
6.2.2 设置合适的通信速率
选择SPI通信速率时,需考虑以下因素:
- 从设备的最大支持速率 :例如,某些LCD模块仅支持最高10MHz的时钟频率。
- 通信距离与噪声干扰 :高频信号更容易受到干扰,长距离通信应适当降低频率。
- 系统负载与实时性要求 :高速通信可能增加CPU负载,影响系统稳定性。
通常建议从低速开始测试,逐步提高频率以达到最佳性能。
// 使用 bcm2835 库设置 SPI 时钟频率
#include <bcm2835.h>
int main(int argc, char **argv)
{
if (!bcm2835_init())
return 1;
bcm2835_spi_begin();
bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST); // 数据高位先发
bcm2835_spi_setDataMode(BCM2835_SPI_MODE0); // 设置为 Mode 0
bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_64); // 设置时钟分频为64(约3.9MHz)
// 后续发送或接收数据操作
uint8_t data = 0x55;
uint8_t result = bcm2835_spi_transfer(data);
printf("Received: 0x%02X\n", result);
bcm2835_spi_end();
bcm2835_close();
return 0;
}
代码逐行解析:
bcm2835_init():初始化bcm2835库。bcm2835_spi_begin():启用SPI接口。bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST):设置数据高位先发。bcm2835_spi_setDataMode(BCM2835_SPI_MODE0):设置SPI通信模式为Mode 0。bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_64):设置时钟分频值为64。bcm2835_spi_transfer(data):发送数据并接收返回值。bcm2835_spi_end()和bcm2835_close():结束SPI通信并释放资源。
6.3 初始化流程示例
6.3.1 配置寄存器与调用API函数
在树莓派中,SPI模块的初始化可以通过直接操作寄存器实现,也可以使用bcm2835库封装的API函数简化开发流程。
使用寄存器配置SPI(底层方式):
volatile unsigned int* spi0_base = (unsigned int*)BCM2835_SPI0_BASE;
// 设置SPI控制寄存器(CS)
spi0_base[BCM2835_SPI0_CS / 4] = BCM2835_SPI0_CS_CLEAR_RX | BCM2835_SPI0_CS_CLEAR_TX;
// 设置SPI时钟分频
spi0_base[BCM2835_SPI0_CLK / 4] = 64; // 250MHz / 64 = ~3.9MHz
// 启用SPI
spi0_base[BCM2835_SPI0_CS / 4] |= BCM2835_SPI0_CS_TA;
使用bcm2835库配置SPI(推荐方式):
if (!bcm2835_init()) {
fprintf(stderr, "Failed to initialize BCM2835 library\n");
return -1;
}
bcm2835_spi_begin();
bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST);
bcm2835_spi_setDataMode(BCM2835_SPI_MODE0);
bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_64);
6.3.2 实际代码片段与参数说明
参数说明:
- BCM2835_SPI_BIT_ORDER_MSBFIRST :高位先发(MSB First)。
- BCM2835_SPI_MODE0 :SPI模式0(CPOL=0, CPHA=0)。
- BCM2835_SPI_CLOCK_DIVIDER_64 :设置时钟分频为64。
流程说明:
- 初始化bcm2835库;
- 开启SPI接口;
- 设置数据传输顺序;
- 设置SPI通信模式;
- 设置SPI时钟频率;
- 发送/接收数据;
- 结束SPI通信。
graph TD
A[初始化bcm2835库] --> B[开启SPI接口]
B --> C[设置数据传输顺序]
C --> D[设置SPI通信模式]
D --> E[设置SPI时钟频率]
E --> F[发送或接收数据]
F --> G[结束SPI通信]
G --> H[释放资源]
本章详细讲解了SPI通信模式的选择与时钟频率的配置方法,并通过实际代码示例展示了如何使用bcm2835库进行初始化设置。下一章将围绕SPI数据的发送与接收实现展开,介绍如何在主机与从机之间高效传输数据。
7. SPI数据发送与接收实现
7.1 主机发送数据流程
在SPI通信中,主机负责发起通信并控制时钟信号。主机发送数据的过程通常包括数据准备、缓冲区设置、调用发送函数以及结果判断等步骤。
数据准备与缓冲区设置
在发送数据之前,主机需要将待发送的数据组织成缓冲区。通常使用数组或动态内存分配来存储数据。
// 示例:定义发送缓冲区
#define BUF_SIZE 10
uint8_t tx_buffer[BUF_SIZE] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A};
发送函数的调用与结果判断
使用 bcm2835 库时,可以调用 bcm2835_spi_transfer 函数进行数据发送和接收(SPI是全双工的)。
#include <bcm2835.h>
int main(int argc, char **argv)
{
// 初始化BCM2835库
if (!bcm2835_init())
{
return 1;
}
// 设置SPI模式为Mode 0,时钟频率为1MHz
bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST); // MSB First
bcm2835_spi_setDataMode(BCM2835_SPI_MODE0); // Mode 0
bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_256); // 250MHz / 256 ≈ 976.5625kHz
// 发送数据
uint8_t tx_data[] = {0x01, 0x02, 0x03, 0x04};
uint8_t rx_data[4]; // 接收缓冲区
bcm2835_spi_transfern(tx_data, sizeof(tx_data));
// 打印接收到的数据(用于全双工测试)
for (int i = 0; i < sizeof(tx_data); i++)
{
printf("Received byte %d: 0x%02X\n", i, rx_data[i]);
}
// 关闭BCM2835库
bcm2835_close();
return 0;
}
tx_data[]:发送的数据数组。rx_data[]:接收的数据缓冲区,SPI通信是全双工的,所以发送时也会接收到数据。bcm2835_spi_transfern():发送并接收数据,适用于连续传输多个字节。
注意 :发送过程中应确保SPI接口已正确初始化,并且时钟频率与从机设备匹配。
7.2 从机接收数据处理
在SPI通信中,从机设备通常需要对主机发送的数据进行接收和处理。从机处理包括中断响应、数据解析和错误处理等。
从机端的中断响应机制
从机可以通过硬件中断或轮询方式接收数据。对于树莓派而言,通常作为SPI主机,但从机设备(如外部MCU)可使用中断机制响应主机通信。
以下为伪代码示例:
void spi_slave_isr()
{
uint8_t data = read_spi_register(SPI_DATA_REG); // 读取接收到的数据
process_data(data); // 处理数据
}
read_spi_register():读取SPI数据寄存器。process_data():根据协议解析并执行操作。
数据校验与错误处理
从机端应对接收的数据进行校验,例如CRC校验、长度校验等。常见的错误处理策略包括:
| 错误类型 | 处理方法 |
|---|---|
| 数据校验失败 | 返回错误码,请求重传 |
| 接收缓冲区溢出 | 清除缓冲区,重新初始化SPI接口 |
| 通信超时 | 启动看门狗,复位SPI模块 |
在从机设计中,建议使用状态机机制处理不同阶段的通信流程,以增强健壮性。
7.3 实战项目:SPI驱动外设
本节将通过三个典型SPI外设的连接与通信示例,展示SPI在实际项目中的应用。
7.3.1 连接LCD显示模块
以常见的ST7735 LCD模块为例,其使用SPI接口进行数据通信。连接方式如下:
| 树莓派引脚 | LCD模块引脚 | 功能 |
|---|---|---|
| GPIO10 | MOSI | 数据发送 |
| GPIO11 | SCLK | 时钟信号 |
| GPIO8 | CE0 | 片选信号 |
| GPIO24 | DC | 数据/命令选择 |
| GPIO25 | RST | 复位信号 |
初始化代码片段(使用 bcm2835 库):
// 设置DC和RST引脚为输出
bcm2835_gpio_fsel(24, BCM2835_GPIO_FSEL_OUTP);
bcm2835_gpio_fsel(25, BCM2835_GPIO_FSEL_OUTP);
// 发送初始化命令
send_command(0x01); // 软件复位
delay(150);
send_command(0x11); // 退出睡眠模式
7.3.2 控制ADC采集数据
以 MCP3208 12位ADC芯片为例,它通过SPI接口将模拟信号转换为数字信号。读取通道0的电压值:
uint16_t read_adc(int channel)
{
uint8_t tx[] = {0x06 | ((channel & 0x07) >> 2), ((channel & 0x01) << 7), 0x00};
uint8_t rx[3] = {0};
bcm2835_spi_transfernb(tx, rx, 3);
return ((rx[1] << 8) & 0x300) | rx[2];
}
channel:指定ADC通道(0~7)。tx[]:构造SPI请求数据帧。rx[]:返回的12位ADC值。
7.3.3 EEPROM读写操作实现
以 Microchip 的 25AA020A EEPROM 为例,支持SPI接口,读写流程如下:
写入数据流程:
void eeprom_write_byte(uint8_t address, uint8_t data)
{
uint8_t tx[3] = {0x02, address, data}; // 写命令 + 地址 + 数据
bcm2835_spi_transfern(tx, 3);
delay(5); // 等待写入完成
}
读取数据流程:
uint8_t eeprom_read_byte(uint8_t address)
{
uint8_t tx[3] = {0x03, address, 0x00}; // 读命令 + 地址 + dummy byte
uint8_t rx[3] = {0};
bcm2835_spi_transfern(tx, rx, 3);
return rx[2];
}
0x02:写入命令。0x03:读取命令。address:EEPROM地址。data:写入的数据。
通过上述实战项目,开发者可以掌握如何利用SPI协议驱动各类外设,实现嵌入式系统的数据采集、显示与存储功能。
简介:本文围绕树莓派上的SPI通信展开,详细介绍了基于BCM2835芯片的SPI主机与从机代码实现过程。SPI是一种高速同步串行通信接口,常用于连接微控制器与传感器、LCD、ADC等外设。文章讲解了SPI通信的基本原理、GPIO引脚配置、时钟设置、数据传输流程等内容,并通过C语言结合bcm2835库进行编程实践,涵盖初始化、数据收发、资源释放等关键步骤。压缩包中包含完整的代码与文档,适合嵌入式开发者和树莓派爱好者进行学习与实操。
更多推荐




所有评论(0)