一、在 STM32 上使用 LL 库(Low-Layer Library) 通过硬件 SPI 发送数据需要以下关键步骤:
1. 初始化 SPI 外设
使用 LL_SPI_Init()
配置 SPI 参数,例如模式、数据宽度、波特率等:
LL_SPI_InitTypeDef SPI_InitStruct = {0};
SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX; // 全双工模式
SPI_InitStruct.Mode = LL_SPI_MODE_MASTER; // 主模式
SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_8BIT; // 8 位数据
SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_LOW; // CPOL=0
SPI_InitStruct.ClockPhase = LL_SPI_PHASE_1EDGE; // CPHA=0
SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8; // 波特率分频
SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST; // 高位在前
LL_SPI_Enable(SPI1); //使能 SPI 外设
LL_SPI_Init(SPI1, &SPI_InitStruct);
2. 配置 GPIO 引脚
设置 SPI 相关引脚(SCK, MOSI, MISO, NSS)为复用功能模式:
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
// 配置 MOSI (PA7) 和 SCK (PA5)
GPIO_InitStruct.Pin = LL_GPIO_PIN_7 | LL_GPIO_PIN_5;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; // 复用模式
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
GPIO_InitStruct.Alternate = LL_GPIO_AF_5; // 根据芯片型号选择 AF
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
3. 使能 SPI 外设
LL_SPI_Enable(SPI1);
4. 发送数据
通过检查 TXE
(发送缓冲区空)标志,向数据寄存器写入数据:
// 等待发送缓冲区为空
while (!LL_SPI_IsActiveFlag_TXE(SPI1));
// 发送单字节数据
LL_SPI_TransmitData8(SPI1, data_byte);
// 可选:等待传输完成(避免过早关闭SPI)
while (LL_SPI_IsActiveFlag_BSY(SPI1));
5. 处理接收数据(全双工模式)
如果使用全双工模式,需读取接收的数据:
// 等待接收缓冲区非空
while (!LL_SPI_IsActiveFlag_RXNE(SPI1));
// 读取接收到的数据
uint8_t rx_data = LL_SPI_ReceiveData8(SPI1);
6. 关闭 SPI(可选)
传输完成后可关闭 SPI 以省电:
LL_SPI_Disable(SPI1);
关键点说明
1、时钟配置:确保 SPI 时钟已通过 LL_RCC_SetSPIClockSource()
正确配置。
2、NSS 引脚:硬件 NSS 可通过 LL_SPI_SetNSSMode()
配置为硬件管理,或手动控制 GPIO。
3、中断/DMA:可通过 LL_SPI_EnableIT_TXE()
启用中断,或使用 DMA 提高效率。
7.示例代码(发送单字节)
void SPI_SendByte(uint8_t data) {
// 等待发送缓冲区就绪
while (!LL_SPI_IsActiveFlag_TXE(SPI1));
// 写入数据
LL_SPI_TransmitData8(SPI1, data);
// 等待传输完成
while (LL_SPI_IsActiveFlag_BSY(SPI1));
}
8.示例代码(接收单字节数据)
在 SPI 主模式下,接收数据需要先发送一个虚拟字节(如 0xFF
)以触发时钟,从而读取从机返回的数据。
uint8_t SPI_ReceiveByte(void) {
uint8_t dummy_data = 0xFF; // 虚拟数据,用于生成时钟
uint8_t rx_data = 0;
// 1. 发送虚拟数据以生成时钟
while (!LL_SPI_IsActiveFlag_TXE(SPI1)); // 等待发送缓冲区就绪
LL_SPI_TransmitData8(SPI1, dummy_data); // 发送虚拟数据
// 2. 等待接收数据就绪
while (!LL_SPI_IsActiveFlag_RXNE(SPI1)); // 等待接收缓冲区非空
// 3. 读取接收到的数据
rx_data = LL_SPI_ReceiveData8(SPI1);
// 4. 等待传输完成(可选)
while (LL_SPI_IsActiveFlag_BSY(SPI1));
return rx_data;
}
9.同时发送和接收单字节(全双工)
在 SPI 全双工模式下,发送和接收是同步进行的。发送数据的同时,从机的响应数据也会被接收。
uint8_t SPI_TransmitReceiveByte(uint8_t tx_data) {
uint8_t rx_data = 0;
// 1. 等待发送缓冲区就绪
while (!LL_SPI_IsActiveFlag_TXE(SPI1));
// 2. 发送数据并触发接收
LL_SPI_TransmitData8(SPI1, tx_data);
// 3. 等待接收完成
while (!LL_SPI_IsActiveFlag_RXNE(SPI1));
// 4. 读取接收到的数据
rx_data = LL_SPI_ReceiveData8(SPI1);
// 5. 等待传输完成(避免过早关闭SPI)
while (LL_SPI_IsActiveFlag_BSY(SPI1));
return rx_data;
}
10、示例 :连续收发数据
如果需要发送/接收多个字节,可以循环调用上述函数:
void SPI_TransmitReceiveMultiple(uint8_t *tx_buf, uint8_t *rx_buf, uint32_t size) {
for (uint32_t i = 0; i < size; i++) {
rx_buf[i] = SPI_TransmitReceiveByte(tx_buf[i]);
}
}
11、关键注意事项
11.1. SPI 模式与时钟配置
CPOL 和 CPHA:确保与从机设备的时序匹配(例如 LL_SPI_POLARITY_LOW
+ LL_SPI_PHASE_1EDGE
对应模式0)。
波特率:通过 LL_SPI_BAUDRATEPRESCALER_xxx
设置合适的时钟分频,确保不超过从机支持的最大速率。
11.2. NSS(片选信号)管理
如果使用软件 NSS 控制(软件模拟片选),需配置:
LL_SPI_SetNSSMode(SPI1, LL_SPI_NSS_SOFT); // 软件控制片选
手动控制片选,需要在传输前拉低 GPIO,传输后拉高:
// 拉低片选(假设使用 PA4 作为 NSS)
LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_4);
SPI_TransmitReceiveByte(data); // 发送/接收
LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_4); // 拉高片选
11.3. 错误处理
检查 SPI 错误标志(如 OVR
溢出错误):
if (LL_SPI_IsActiveFlag_OVR(SPI1)) {
LL_SPI_ClearFlag_OVR(SPI1); // 清除溢出错误标志
}
通过以上示例,你可以实现 SPI 的单向接收、全双工通信及批量数据传输。实际应用中,若对性能要求较高,可进一步使用 中断 或 DMA 优化传输过程。