游乐游手机版
首页/AI教程/文章详情

STM32F105 CAN总线双机通信代码实现

时间:2026-06-09 15:48
基于STM32F105系列,采用标准外设库和CAN1实现双机通信,涵盖GPIO配置、中断设置、工作模式与波特率配置、过滤器设置,以及发送函数与接收中断服务函数。通过ID分配和过滤器配置实现双机配对,主函数中循环发送数据。

对于从事嵌入式开发的朋友来说,CAN总线通信的需求十分常见。本文以STM32F105系列微控制器为例,基于标准外设库,使用CAN1外设,提供一套完整的双机通信代码实现方案。整个流程涵盖初始化、数据发送、接收以及双机配对的关键配置,下面我们逐步展开。

基于STM32F105系列使用CAN总线实现双机通信代码

1. CAN总线初始化

初始化是CAN通信的基础环节,涉及GPIO配置、中断设置、工作模式与波特率设定以及过滤器配置,每一步都不可或缺。请看具体代码实现:

#include "stm32f10x.h"
#include "stm32f10x_can.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "misc.h"

// CAN初始化结构体
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
CanTxMsg TxMessage;
CanRxMsg RxMessage;

void CAN_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    // 使能GPIO和CAN时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);

    // 配置CAN引脚: PA11->RX, PA12->TX
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;   // TX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 推挽复用输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;   // RX
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // 上拉输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void CAN_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;

    // 配置CAN接收中断
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

void CAN_Mode_Config(void)
{
    CAN_DeInit(CAN1);
    CAN_StructInit(&CAN_InitStructure);

    // CAN工作模式配置
    CAN_InitStructure.CAN_TTCM = DISABLE;  // 禁止时间触发通信模式
    CAN_InitStructure.CAN_ABOM = DISABLE;  // 禁止自动离线管理
    CAN_InitStructure.CAN_AWUM = DISABLE;  // 禁止自动唤醒模式
    CAN_InitStructure.CAN_NART = DISABLE;  // 禁止自动重传
    CAN_InitStructure.CAN_RFLM = DISABLE;  // 禁止FIFO锁定
    CAN_InitStructure.CAN_TXFP = DISABLE;  // 禁止发送FIFO优先级

    // 配置波特率
    // CAN波特率 = APB1时钟 / ((CAN_BS1 + CAN_BS2 + 1) * CAN_Prescaler)
    // 假设APB1时钟为36MHz,波特率设为500kbps
    CAN_InitStructure.CAN_BS1 = CAN_BS1_9tq;   // 时间段1
    CAN_InitStructure.CAN_BS2 = CAN_BS2_4tq;   // 时间段2
    CAN_InitStructure.CAN_Prescaler = 6;         // 分频系数
    // 总时间片 = 1 + 9 + 4 = 14
    // 波特率 = 36MHz / (14 * 6) = 428.57kHz (接近500k)

    CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;  // 正常工作模式
    CAN_Init(CAN1, &CAN_InitStructure);

    // 配置CAN过滤器
    CAN_FilterInitStructure.CAN_FilterNumber = 0;     // 使用过滤器0
    CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;  // 掩码模式
    CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; // 32位模式
    CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;  // 要过滤的ID高位
    CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;   // 要过滤的ID低位
    CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;  // 掩码高位
    CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;   // 掩码低位
    CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;  // 关联到FIFO0
    CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;  // 启用过滤器
    CAN_FilterInit(&CAN_FilterInitStructure);

    // 使能CAN接收中断
    CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
}

2. CAN数据发送函数

发送部分的逻辑非常清晰:先构建发送帧结构体,填充ID、数据长度及具体内容,然后调用发送接口并等待传输完成。注意增加超时处理,避免程序陷入死等状态。

uint8_t CAN_Send_Msg(uint8_t* msg, uint8_t len, uint32_t id)
{
    uint8_t i = 0;
    uint8_t transmit_mailbox = 0;

    TxMessage.StdId = id;          // 标准ID
    TxMessage.ExtId = 0x00;        // 扩展ID
    TxMessage.IDE = CAN_Id_Standard;  // 标准帧
    TxMessage.RTR = CAN_RTR_Data;     // 数据帧
    TxMessage.DLC = len;              // 数据长度(最大8字节)

    // 填充数据
    for(i = 0; i < len; i++)
    {
        TxMessage.Data[i] = msg[i];
    }

    // 发送数据
    transmit_mailbox = CAN_Transmit(CAN1, &TxMessage);

    // 等待发送完成
    i = 0;
    while((CAN_TransmitStatus(CAN1, transmit_mailbox) != CANTXOK) && (i < 0xFF))
    {
        i++;
    }
    if(i >= 0xFF)
    {
        return 1;  // 发送失败
    }
    return 0;  // 发送成功
}

3. CAN数据接收中断服务函数

接收采用中断方式,效率更高。进入中断后先检查标志位,再读取FIFO中的数据存入缓冲区,记得清除中断标志。缓冲区内容可后续处理,例如转发至串口或触发其他控制逻辑。

void USB_LP_CAN1_RX0_IRQHandler(void)
{
    static uint8_t receive_buffer[8];

    if(CAN_GetITStatus(CAN1, CAN_IT_FMP0) != RESET)
    {
        // 读取接收到的数据
        CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);

        // 清除中断标志
        CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);

        // 处理接收到的数据
        if(RxMessage.IDE == CAN_Id_Standard)
        {
            uint8_t i;
            // 复制接收到的数据
            for(i = 0; i < RxMessage.DLC; i++)
            {
                receive_buffer[i] = RxMessage.Data[i];
            }
            // 在这里处理接收到的数据
            // 例如:将数据发送到串口或进行其他处理
            // Process_Received_Data(receive_buffer, RxMessage.DLC, RxMessage.StdId);
        }
    }
}

4. 主函数示例

主函数中演示了周期性发送的典型用法:先初始化系统时钟和CAN外设,然后在主循环中每隔1秒发送一帧数据。实际项目可根据需求调整发送频率或添加条件触发机制。

int main(void)
{
    uint8_t send_data[8] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
    uint32_t message_id = 0x123;  // CAN消息ID

    // 初始化系统时钟
    SystemInit();

    // 配置GPIO
    CAN_GPIO_Config();

    // 配置NVIC中断
    CAN_NVIC_Config();

    // 配置CAN模式
    CAN_Mode_Config();

    // 主循环
    while(1)
    {
        // 每隔1秒发送一次数据
        Delay(1000);  // 自定义延时函数

        // 发送CAN消息
        CAN_Send_Msg(send_data, 8, message_id);

        // 可以在这里添加其他处理逻辑
    }
}

5. 双机通信配置说明

要使两个设备正常通信,ID分配和过滤器设置是关键。以设备A作为发送方、设备B作为接收方为例:

设备A配置(发送设备):

// 设置设备A的ID为0x100
uint32_t device_a_id = 0x100;

设备B配置(接收设备):

// 配置过滤器只接收来自设备A的消息
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0100 << 5;  // ID高16位
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;         // ID低16位
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xFFFF;    // 掩码高位
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;     // 掩码低位

6. 完整的工程配置步骤

硬件连接:

  • CAN_H 连接 CAN_H
  • CAN_L 连接 CAN_L
  • 两个设备的GND相连
  • 在总线的两端接120Ω终端电阻

软件配置:

// 波特率计算
// 假设系统时钟为72MHz,APB1时钟为36MHz
// 500kbps配置:BS1=9, BS2=4, Prescaler=6
// 1Mbps配置:BS1=9, BS2=4, Prescaler=3

编译和下载:

  • 使用Keil MDK或STM32CubeIDE
  • 确保包含正确的头文件
  • 配置正确的系统时钟

注意事项

  • 终端电阻:必须在CAN总线的两端各接一个120Ω的终端电阻
  • 波特率:两个设备的波特率必须相同
  • 中断优先级:根据实际应用需求合理配置中断优先级
  • 错误处理:在实际应用中应添加错误检测和重发机制
  • ID冲突:确保每个设备的CAN ID唯一,避免冲突
来源:https://developer.aliyun.com/article/1740288
上一篇2026年阿里云618AI加速季智惠生产力活动攻略 下一篇C++在LibFuzzer模糊测试中的应用实践
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
AI幻觉变真功能实战教程:App Inventor 2视频录制拓展一周开发实录
AI教程 · 2026-07-01

AI幻觉变真功能实战教程:App Inventor 2视频录制拓展一周开发实录

先讲一个颇具戏剧性的开端。 这件事的开端颇显荒诞——有用户前来咨询,称AI Pro版的介绍中提到我们有一款“视频录制拓展”。团队全体成员都感到困惑,翻遍产品列表,发现根本不存在该组件。AI那种“一本正经胡说八道”的能力,这次确实让我们陷入尴尬。 按常理,此事到此便可结束——一句“抱歉,暂时没有这个拓

别再混淆OLAP和SQL-on-Hadoop两者查询本质不同
AI教程 · 2026-07-01

别再混淆OLAP和SQL-on-Hadoop两者查询本质不同

OLAP和SQL-on-Hadoop虽都使用SQL查询数据,但本质不同。SQL-on-Hadoop负责海量数据批量计算与ETL,查询速度秒级至分钟级;OLAP通过预聚合实现毫秒级多维分析,适合BI报表。两者在数据平台分工协作,前者是后厨加工,后者是前台快速服务。

GEO优化深度解析:AI偏好FAQ还是长文内容?
AI教程 · 2026-07-01

GEO优化深度解析:AI偏好FAQ还是长文内容?

在GEO优化中,AI对内容形式无统一偏好:FAQ在简单查询中引用率41%,长文在复杂查询中达58%。内容应基于用户意图选择形式,FAQ适配简单事实类问题,长文建立主题权威,两者互补而非替代。

架构师视角下程序员避免AI反噬的进阶之路
AI教程 · 2026-07-01

架构师视角下程序员避免AI反噬的进阶之路

AI时代程序员角色从执行者向指挥官转型,核心能力转向系统架构设计与问题分析。编码效率提升65%-80%,但安全漏洞率35%-70%。技能断层与K型分化加剧,系统架构师薪资上涨16%,AI指挥官等新岗位需求激增。

AI答案黑箱下技术人如何重构流量新秩序专访GEO优化师罗长才
AI教程 · 2026-07-01

AI答案黑箱下技术人如何重构流量新秩序专访GEO优化师罗长才

生成式引擎优化(GEO)从传统SEO的“被点击”转向“被引用”,基于RAG与向量检索的语义相似度计算重构流量秩序。面对AI引用不可追踪的“黑箱”困境,技术人需通过内容结构化、高频问题覆盖及多源覆盖提升被AI引用的概率,实现从排名竞争到知识网络节点价值的转变。