引言
STM32(基于ARM Cortex-M内核的微控制器)和树莓派(基于ARM Cortex-A内核的单板计算机)是嵌入式系统中两种非常流行的硬件平台。STM32擅长实时控制、低功耗和精确的硬件接口,而树莓派则擅长运行复杂的操作系统、处理大量数据和网络通信。将两者结合,可以构建功能强大的混合系统,例如:STM32负责实时传感器数据采集和电机控制,树莓派负责数据处理、用户界面和云端通信。
本文将详细介绍STM32与树莓派之间的多种通信方法,涵盖从硬件连接到软件编程的完整流程。我们将重点讨论以下几种主流通信方式:
- UART(串口通信):最简单、最常用的异步通信方式。
- I2C(内部集成电路总线):两线制同步通信,适合连接多个从设备。
- SPI(串行外设接口):高速同步通信,适合与高速外设(如显示屏、SD卡)通信。
- USB(通用串行总线):利用树莓派的USB主机功能与STM32的USB设备功能通信。
- 以太网(通过STM32的以太网MAC或外接模块):实现网络通信。
- CAN(控制器局域网络):常用于汽车和工业环境的可靠通信。
我们将以STM32F4系列(如STM32F407VGT6)和树莓派4B为例进行说明,但原理同样适用于其他型号。
1. UART(串口通信)
UART是最简单、最直接的通信方式,只需要连接TX、RX和GND三根线即可实现双向通信。
1.1 硬件连接
注意:STM32和树莓派的逻辑电平都是3.3V,因此可以直接连接,无需电平转换。
- STM32端:选择一个UART外设,例如USART2。其引脚通常为:
PA2(USART2_TX)PA3(USART2_RX)
- 树莓派端:使用其板载的UART引脚(GPIO14/15)或通过USB转串口模块连接。
连接方式:
- STM32
PA2(TX) -> 树莓派GPIO15(RX) 或 USB转串口模块的RX - STM32
PA3(RX) -> 树莓派GPIO14(TX) 或 USB转串口模块的TX - STM32
GND-> 树莓派GND或 USB转串口模块的GND
重要提示:树莓派的板载UART默认可能被用于蓝牙或系统控制,需要在/boot/config.txt中配置以启用标准UART:
# 在树莓派终端中编辑配置文件
sudo nano /boot/config.txt
# 在文件末尾添加以下行
dtoverlay=pi3-disable-bt
enable_uart=1
# 保存并重启
sudo reboot
重启后,树莓派的UART设备通常为/dev/ttyAMA0。
1.2 软件编程
1.2.1 STM32端编程(使用HAL库)
使用STM32CubeMX生成初始化代码,配置USART2为115200波特率,8位数据,无校验,1停止位。
接收中断方式(推荐):
// main.c
#include "main.h"
#include <stdio.h>
#include <string.h>
UART_HandleTypeDef huart2;
uint8_t rx_buffer[100];
uint8_t rx_index = 0;
uint8_t rx_data;
// 重定向printf到USART2
int __io_putchar(int ch) {
HAL_UART_Transmit(&huart2, (uint8_t*)&ch, 1, HAL_MAX_DELAY);
return ch;
}
// 接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
if (rx_data != '\n' && rx_index < 99) {
rx_buffer[rx_index++] = rx_data;
} else {
rx_buffer[rx_index] = '\0'; // 字符串结束符
printf("Received: %s\n", rx_buffer); // 打印接收到的数据
rx_index = 0;
}
HAL_UART_Receive_IT(&huart2, &rx_data, 1); // 重新启动中断接收
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
// 启动中断接收
HAL_UART_Receive_IT(&huart2, &rx_data, 1);
while (1) {
// 主循环可以执行其他任务
HAL_Delay(1000);
printf("STM32 is running...\n"); // 每秒发送一条消息
}
}
1.2.2 树莓派端编程(Python)
使用Python的pyserial库与STM32通信。
# install: pip install pyserial
import serial
import time
# 配置串口参数
ser = serial.Serial(
port='/dev/ttyAMA0', # 对于板载UART
baudrate=115200,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=1 # 读取超时时间(秒)
)
if ser.is_open:
print("串口已打开")
else:
print("串口打开失败")
exit()
try:
while True:
# 发送数据到STM32
message = "Hello from Raspberry Pi!\n"
ser.write(message.encode('utf-8'))
print(f"Sent: {message.strip()}")
# 读取STM32的响应
if ser.in_waiting > 0:
response = ser.readline().decode('utf-8').strip()
print(f"Received: {response}")
time.sleep(1) # 每秒发送一次
except KeyboardInterrupt:
print("\n程序被用户中断")
finally:
ser.close()
print("串口已关闭")
运行结果:
- STM32会每秒发送”STM32 is running…“,并打印接收到的树莓派消息。
- 树莓派会每秒发送”Hello from Raspberry Pi!“,并打印接收到的STM32消息。
2. I2C通信
I2C使用两根线(SDA和SCL)实现多设备通信,STM32可以作为主设备或从设备,树莓派通常作为主设备。
2.1 硬件连接
- STM32端:选择I2C外设,例如I2C1。其引脚通常为:
PB6(I2C1_SCL)PB7(I2C1_SDA)
- 树莓派端:使用其板载I2C引脚(GPIO2/3)。
- 连接方式:
- STM32
PB6(SCL) -> 树莓派GPIO3(SCL) - STM32
PB7(SDA) -> 树莓派GPIO2(SDA) - STM32
GND-> 树莓派GND
- STM32
- 上拉电阻:I2C总线需要上拉电阻(通常4.7kΩ)连接到3.3V。STM32和树莓派的I2C引脚通常有内部上拉,但为了稳定性,建议外接上拉电阻。
树莓派配置:
- 启用I2C:
sudo raspi-config->Interface Options->I2C->Yes。 - 安装I2C工具:
sudo apt install i2c-tools - 检测设备:
sudo i2cdetect -y 1(树莓派4B的I2C总线为1)
2.2 软件编程
2.2.1 STM32端编程(作为从设备)
STM32配置为I2C从设备,地址设为0x30(7位地址)。
// main.c
#include "main.h"
#include <stdio.h>
I2C_HandleTypeDef hi2c1;
uint8_t rx_buffer[100];
uint8_t tx_buffer[] = "STM32 I2C Response";
// I2C从设备地址
#define I2C_SLAVE_ADDR 0x30
// I2C从设备接收回调
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) {
if (hi2c->Instance == I2C1) {
printf("I2C Received: %s\n", rx_buffer);
// 可以在这里处理接收到的数据
}
}
// I2C从设备传输完成回调
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c) {
if (hi2c->Instance == I2C1) {
printf("I2C Transmission Complete\n");
}
}
// I2C地址匹配回调
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode) {
if (hi2c->Instance == I2C1) {
if (TransferDirection == I2C_DIRECTION_TRANSMIT) {
// 主设备要写数据到从设备
HAL_I2C_Slave_Receive_IT(hi2c, rx_buffer, sizeof(rx_buffer));
} else {
// 主设备要从从设备读数据
HAL_I2C_Slave_Transmit_IT(hi2c, tx_buffer, sizeof(tx_buffer));
}
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
// 启动I2C从设备监听
HAL_I2C_EnableListen_IT(&hi2c1);
while (1) {
// 主循环
HAL_Delay(1000);
}
}
2.2.2 树莓派端编程(作为主设备)
使用Python的smbus2库与STM32通信。
# install: pip install smbus2
import smbus2
import time
# I2C总线号(树莓派4B为1)
BUS = 1
# STM32从设备地址(7位)
DEVICE_ADDR = 0x30
try:
# 创建I2C总线对象
bus = smbus2.SMBus(BUS)
print(f"I2C总线已打开,从设备地址: 0x{DEVICE_ADDR:02X}")
while True:
# 向STM32发送数据
message = "Hello from Raspberry Pi via I2C"
# 将字符串转换为字节列表
data = [ord(c) for c in message]
bus.write_i2c_block_data(DEVICE_ADDR, 0, data)
print(f"Sent: {message}")
# 从STM32读取数据
# 读取16个字节
received = bus.read_i2c_block_data(DEVICE_ADDR, 0, 16)
# 将字节列表转换为字符串
response = ''.join(chr(b) for b in received)
print(f"Received: {response}")
time.sleep(1)
except Exception as e:
print(f"Error: {e}")
finally:
bus.close()
print("I2C总线已关闭")
运行结果:
- STM32作为从设备,监听I2C总线。当树莓派发送数据时,STM32会打印接收到的数据。
- 树莓派作为主设备,每秒向STM32发送数据并读取STM32的响应。
3. SPI通信
SPI是一种高速、全双工的同步通信协议,使用4根线(MOSI, MISO, SCLK, CS)。
3.1 硬件连接
- STM32端:选择SPI外设,例如SPI1。其引脚通常为:
PA5(SPI1_SCK)PA6(SPI1_MISO)PA7(SPI1_MOSI)PA4(SPI1_CS) 或其他GPIO作为片选
- 树莓派端:使用其板载SPI引脚(GPIO11/10/9/8)。
- 连接方式:
- STM32
PA5(SCK) -> 树莓派GPIO11(SCLK) - STM32
PA6(MISO) -> 树莓派GPIO9(MISO) - STM32
PA7(MOSI) -> 树莓派GPIO10(MOSI) - STM32
PA4(CS) -> 树莓派GPIO8(CE0) 或其他GPIO - STM32
GND-> 树莓派GND
- STM32
树莓派配置:
- 启用SPI:
sudo raspi-config->Interface Options->SPI->Yes。 - 安装SPI工具:
sudo apt install python3-spidev - 检查SPI设备:
ls /dev/spidev*
3.2 软件编程
3.2.1 STM32端编程(作为从设备)
STM32配置为SPI从设备,使用中断方式接收和发送数据。
// main.c
#include "main.h"
#include <stdio.h>
SPI_HandleTypeDef hspi1;
uint8_t rx_buffer[100];
uint8_t tx_buffer[] = "STM32 SPI Response";
// SPI从设备接收回调
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {
if (hspi->Instance == SPI1) {
printf("SPI Received: %s\n", rx_buffer);
// 可以在这里处理接收到的数据
}
}
// SPI从设备传输完成回调
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {
if (hspi->Instance == SPI1) {
printf("SPI Transmission Complete\n");
}
}
// SPI从设备接收和传输完成回调(全双工)
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
if (hspi->Instance == SPI1) {
printf("SPI Full-Duplex Complete, Received: %s\n", rx_buffer);
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SPI1_Init();
// 启动SPI从设备接收(全双工模式)
HAL_SPI_TransmitReceive_IT(&hspi1, tx_buffer, rx_buffer, sizeof(tx_buffer));
while (1) {
// 主循环
HAL_Delay(1000);
}
}
3.2.2 树莓派端编程(作为主设备)
使用Python的spidev库与STM32通信。
# install: pip install spidev
import spidev
import time
# 配置SPI参数
spi = spidev.SpiDev()
spi.open(0, 0) # 总线0,设备0(对应CE0)
spi.max_speed_hz = 1000000 # 1MHz
spi.mode = 0 # SPI模式0(CPOL=0, CPHA=0)
try:
while True:
# 发送数据到STM32
message = "Hello from Raspberry Pi via SPI"
# 将字符串转换为字节列表
data = [ord(c) for c in message]
# 发送并接收数据(全双工)
received = spi.xfer2(data) # xfer2会同时发送和接收
# 将接收到的字节列表转换为字符串
response = ''.join(chr(b) for b in received)
print(f"Sent: {message}")
print(f"Received: {response}")
time.sleep(1)
except Exception as e:
print(f"Error: {e}")
finally:
spi.close()
print("SPI已关闭")
运行结果:
- STM32作为从设备,通过SPI接收数据并发送响应。
- 树莓派作为主设备,每秒发送数据并读取STM32的响应。
4. USB通信
STM32可以配置为USB设备(如CDC虚拟串口、HID设备等),树莓派作为USB主机与之通信。
4.1 硬件连接
- STM32端:使用STM32的USB引脚(通常为PA11/PA12,对应USB_D-/USB_D+)。
- 树莓派端:使用树莓派的USB-A端口。
- 连接方式:
- STM32
PA11(USB_D-) -> USB线的D- - STM32
PA12(USB_D+) -> USB线的D+ - STM32
GND-> USB线的GND - STM32
5V(如果需要供电)-> USB线的5V(注意:STM32的5V引脚通常来自USB,如果使用外部电源,请确保共地)
- STM32
注意:STM32的USB引脚需要连接到USB连接器的D+和D-。对于开发板,通常有USB接口。如果使用裸芯片,需要外接USB连接器。
4.2 软件编程
4.2.1 STM32端编程(USB CDC虚拟串口)
使用STM32CubeMX生成USB CDC代码。
// main.c
#include "main.h"
#include "usbd_cdc_if.h" // USB CDC接口
// USB CDC接收缓冲区
uint8_t usb_rx_buffer[100];
uint32_t usb_rx_length = 0;
// USB CDC接收回调函数(在usbd_cdc_if.c中定义)
// 这里我们假设在usbd_cdc_if.c中已经定义了回调函数,我们只需要在main.c中实现
// 实际上,回调函数通常在usbd_cdc_if.c中定义,但为了示例,我们在这里模拟
// 在usbd_cdc_if.c中,CDC_Receive_FS函数会被调用
// 我们可以在main.c中定义一个全局变量来存储接收到的数据
extern uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];
extern uint32_t UserRxBufferFSLength;
// 在main.c中,我们可以定期检查USB接收状态
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USB_DEVICE_Init();
// 启动USB设备
// USB CDC设备会自动运行
while (1) {
// 检查是否有新的USB数据
if (UserRxBufferFSLength > 0) {
// 处理接收到的数据
printf("USB CDC Received: %s\n", UserRxBufferFS);
// 清空接收缓冲区
UserRxBufferFSLength = 0;
}
// 发送数据到树莓派
uint8_t message[] = "STM32 USB CDC Response\n";
CDC_Transmit_FS(message, sizeof(message));
HAL_Delay(1000);
}
}
注意:在STM32CubeMX中,需要配置USB为CDC模式,并生成代码。在usbd_cdc_if.c中,CDC_Receive_FS函数会将接收到的数据复制到UserRxBufferFS,并设置UserRxBufferFSLength。我们需要在main.c中定期检查这个长度。
4.2.2 树莓派端编程
树莓派会自动识别STM32的USB CDC设备为一个虚拟串口(如/dev/ttyACM0)。
import serial
import time
# 配置串口参数(与UART类似,但设备不同)
ser = serial.Serial(
port='/dev/ttyACM0', # USB CDC设备通常为ttyACM0或ttyUSB0
baudrate=115200,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=1
)
if ser.is_open:
print("USB CDC串口已打开")
else:
print("USB CDC串口打开失败")
exit()
try:
while True:
# 发送数据到STM32
message = "Hello from Raspberry Pi via USB CDC\n"
ser.write(message.encode('utf-8'))
print(f"Sent: {message.strip()}")
# 读取STM32的响应
if ser.in_waiting > 0:
response = ser.readline().decode('utf-8').strip()
print(f"Received: {response}")
time.sleep(1)
except KeyboardInterrupt:
print("\n程序被用户中断")
finally:
ser.close()
print("USB CDC串口已关闭")
运行结果:
- STM32作为USB CDC设备,通过USB线与树莓派通信。
- 树莓派将STM32识别为虚拟串口,使用串口通信库进行通信。
5. 以太网通信
STM32可以通过内置的以太网MAC(如STM32F407)或外接以太网模块(如W5500)与树莓派进行网络通信。
5.1 硬件连接
5.1.1 STM32内置以太网MAC
- STM32端:使用STM32F407的以太网MAC引脚(通常为RMII接口):
PA1(ETH_REF_CLK)PA2(ETH_MDIO)PA7(ETH_CRS_DV)PB11(ETH_TX_EN)PB12(ETH_TXD0)PB13(ETH_TXD1)PC1(ETH_MDC)PC4(ETH_RXD0)PC5(ETH_RXD1)
- 树莓派端:使用其以太网端口。
- 连接方式:需要通过以太网变压器和RJ45接口连接到网络。通常使用一个以太网模块(如LAN8720)连接到STM32,然后通过网线连接到路由器,树莓派也连接到同一个路由器。
5.1.2 使用外接以太网模块(如W5500)
- STM32端:使用SPI接口连接W5500模块。
- 连接方式:STM32的SPI引脚连接到W5500的SPI引脚,W5500的以太网接口通过RJ45连接到网络。
5.2 软件编程
5.2.1 STM32端编程(使用LwIP协议栈)
使用STM32CubeMX配置以太网MAC和LwIP协议栈。
// main.c
#include "main.h"
#include "lwip.h"
#include "lwip/udp.h"
#include "lwip/tcp.h"
#include "lwip/netif.h"
#include "lwip/init.h"
#include "lwip/sockets.h"
// UDP服务器配置
#define UDP_PORT 5000
struct udp_pcb *udp_server;
// UDP接收回调
void udp_recv_callback(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) {
if (p != NULL) {
// 打印接收到的数据
printf("UDP Received: %s\n", (char*)p->payload);
// 发送响应
char response[] = "STM32 UDP Response";
struct pbuf *resp_p = pbuf_alloc(PBUF_TRANSPORT, strlen(response), PBUF_RAM);
if (resp_p != NULL) {
memcpy(resp_p->payload, response, strlen(response));
udp_sendto(pcb, resp_p, addr, port);
pbuf_free(resp_p);
}
pbuf_free(p);
}
}
// TCP服务器配置
#define TCP_PORT 8080
struct tcp_pcb *tcp_server;
// TCP接收回调
err_t tcp_recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) {
if (p != NULL) {
// 打印接收到的数据
printf("TCP Received: %s\n", (char*)p->payload);
// 发送响应
char response[] = "STM32 TCP Response";
tcp_write(tpcb, response, strlen(response), TCP_WRITE_FLAG_COPY);
tcp_output(tpcb);
pbuf_free(p);
}
return ERR_OK;
}
// TCP连接回调
err_t tcp_accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err) {
// 设置接收回调
tcp_recv(newpcb, tcp_recv_callback);
return ERR_OK;
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ETH_Init();
MX_LWIP_Init();
// 初始化UDP服务器
udp_server = udp_new();
if (udp_server != NULL) {
udp_bind(udp_server, IP_ADDR_ANY, UDP_PORT);
udp_recv(udp_server, udp_recv_callback, NULL);
printf("UDP Server started on port %d\n", UDP_PORT);
}
// 初始化TCP服务器
tcp_server = tcp_new();
if (tcp_server != NULL) {
tcp_bind(tcp_server, IP_ADDR_ANY, TCP_PORT);
tcp_server = tcp_listen(tcp_server);
tcp_accept(tcp_server, tcp_accept_callback);
printf("TCP Server started on port %d\n", TCP_PORT);
}
while (1) {
// 处理LwIP协议栈
MX_LWIP_Process();
HAL_Delay(10);
}
}
5.2.2 树莓派端编程(UDP/TCP客户端)
import socket
import time
# UDP客户端
def udp_client():
# 创建UDP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = ('192.168.1.100', 5000) # STM32的IP地址和UDP端口
try:
while True:
message = "Hello from Raspberry Pi via UDP"
sock.sendto(message.encode('utf-8'), server_address)
print(f"Sent UDP: {message}")
# 设置超时
sock.settimeout(1.0)
try:
data, addr = sock.recvfrom(1024)
print(f"Received UDP: {data.decode('utf-8')}")
except socket.timeout:
print("UDP timeout")
time.sleep(1)
finally:
sock.close()
# TCP客户端
def tcp_client():
# 创建TCP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('192.168.1.100', 8080) # STM32的IP地址和TCP端口
try:
sock.connect(server_address)
print("TCP connected")
while True:
message = "Hello from Raspberry Pi via TCP"
sock.sendall(message.encode('utf-8'))
print(f"Sent TCP: {message}")
# 接收响应
data = sock.recv(1024)
print(f"Received TCP: {data.decode('utf-8')}")
time.sleep(1)
except Exception as e:
print(f"TCP Error: {e}")
finally:
sock.close()
# 选择一种通信方式
if __name__ == "__main__":
# 运行UDP客户端
udp_client()
# 或者运行TCP客户端
# tcp_client()
运行结果:
- STM32作为UDP/TCP服务器,监听指定端口。
- 树莓派作为客户端,向STM32发送数据并接收响应。
6. CAN通信
CAN总线常用于汽车和工业环境,具有高可靠性和实时性。STM32通常有内置CAN控制器,树莓派需要外接CAN模块(如MCP2515)。
6.1 硬件连接
- STM32端:选择CAN外设,例如CAN1。其引脚通常为:
PA11(CAN1_RX)PA12(CAN1_TX)
- 树莓派端:使用外接CAN模块(如MCP2515),通过SPI连接到树莓派。
- 连接方式:
- STM32
PA11(CAN_RX) -> CAN总线的CAN_H和CAN_L(通过CAN收发器,如TJA1050) - STM32
PA12(CAN_TX) -> CAN总线的CAN_H和CAN_L(通过CAN收发器) - 树莓派的CAN模块连接到同一个CAN总线。
- CAN总线两端需要连接120Ω终端电阻。
- STM32
6.2 软件编程
6.2.1 STM32端编程(CAN过滤器和中断)
// main.c
#include "main.h"
#include <stdio.h>
CAN_HandleTypeDef hcan1;
CAN_RxHeaderTypeDef rx_header;
uint8_t rx_data[8];
CAN_TxHeaderTypeDef tx_header;
uint8_t tx_data[8];
// CAN接收中断回调
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
if (hcan->Instance == CAN1) {
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data) == HAL_OK) {
printf("CAN Received: ID=0x%03X, Data=", rx_header.StdId);
for (int i = 0; i < rx_header.DLC; i++) {
printf("%02X ", rx_data[i]);
}
printf("\n");
// 发送响应(回显)
tx_header.StdId = 0x123; // 响应ID
tx_header.DLC = rx_header.DLC;
tx_header.IDE = CAN_ID_STD;
tx_header.RTR = CAN_RTR_DATA;
memcpy(tx_data, rx_data, rx_header.DLC);
HAL_CAN_AddTxMessage(&hcan1, &tx_header, tx_data, NULL);
}
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_CAN1_Init();
// 配置CAN过滤器(接收所有ID)
CAN_FilterTypeDef filter;
filter.FilterIdHigh = 0x0000;
filter.FilterIdLow = 0x0000;
filter.FilterMaskIdHigh = 0x0000;
filter.FilterMaskIdLow = 0x0000;
filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;
filter.FilterBank = 0;
filter.FilterMode = CAN_FILTERMODE_IDMASK;
filter.FilterScale = CAN_FILTERSCALE_32BIT;
filter.FilterActivation = CAN_FILTER_ENABLE;
HAL_CAN_ConfigFilter(&hcan1, &filter);
// 启动CAN
HAL_CAN_Start(&hcan1);
// 启用CAN接收中断
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
// 发送初始消息
tx_header.StdId = 0x123;
tx_header.DLC = 8;
tx_header.IDE = CAN_ID_STD;
tx_header.RTR = CAN_RTR_DATA;
for (int i = 0; i < 8; i++) {
tx_data[i] = i;
}
HAL_CAN_AddTxMessage(&hcan1, &tx_header, tx_data, NULL);
while (1) {
// 主循环
HAL_Delay(1000);
}
}
6.2.2 树莓派端编程(使用python-can库)
# install: pip install python-can
import can
import time
# 配置CAN接口(假设使用MCP2515模块)
# 需要先安装socketcan或使用python-can的特定后端
# 这里假设使用socketcan,需要先配置树莓派的CAN接口
# 例如:sudo ip link set can0 up type can bitrate 500000
try:
# 创建CAN总线对象
bus = can.interface.Bus(channel='can0', bustype='socketcan')
print("CAN总线已打开")
# 创建消息
msg = can.Message(arbitration_id=0x123, data=[0, 1, 2, 3, 4, 5, 6, 7], is_extended_id=False)
while True:
# 发送消息
bus.send(msg)
print(f"Sent CAN: ID=0x{msg.arbitration_id:03X}, Data={msg.data}")
# 接收消息(非阻塞)
try:
received = bus.recv(timeout=1.0)
if received:
print(f"Received CAN: ID=0x{received.arbitration_id:03X}, Data={received.data}")
except can.CanError as e:
print(f"CAN Error: {e}")
time.sleep(1)
except Exception as e:
print(f"Error: {e}")
finally:
bus.shutdown()
print("CAN总线已关闭")
运行结果:
- STM32作为CAN节点,发送和接收CAN消息。
- 树莓派通过外接CAN模块与STM32通信。
总结
本文详细介绍了STM32与树莓派之间的多种通信方法,包括UART、I2C、SPI、USB、以太网和CAN。每种方法都有其适用场景:
- UART:简单、直接,适合低速、点对点通信。
- I2C:适合多设备、中低速通信,硬件连接简单。
- SPI:高速、全双工,适合与高速外设通信。
- USB:利用树莓派的USB主机功能,适合需要虚拟串口或HID设备的场景。
- 以太网:适合网络通信,可以实现远程控制和数据传输。
- CAN:高可靠性,适合汽车和工业环境。
在实际项目中,可以根据具体需求选择合适的通信方式。例如,如果只需要简单的数据交换,UART是最简单的选择;如果需要连接多个传感器,I2C可能更合适;如果需要高速数据传输,SPI或以太网是更好的选择。
希望本文能帮助你成功实现STM32与树莓派之间的通信。如有任何问题,欢迎进一步讨论。
