[STM32F1] 基于STM32CUBEMX STM32F103ZET6 CAN 程序设计

[复制链接]
280|0
Zhiniaocun 发表于 2025-10-10 11:19 | 显示全部楼层 |阅读模式
一、CAN总线概述:
CAN(Controller Area Network)总线是一种广泛应用于汽车、工业控制等领域的串行通信协议,由德国博世公司于1986年推出。其设计初衷是解决汽车电子系统中复杂线束的问题,现已成为国际标准(ISO 11898)。
二、核心特点
1… 高可靠性:采用差分信号传输(CAN_H和CAN_L),如NXP的JTA1050(5V供电)通信,CAN_H与CAN_L电压均为2.5V表示隐性逻辑1,CAN_H=3.5V同时CAN_L=1.5V,相差2V即为显性逻辑0,抗干扰能力强。
2. 多主通信:节点无主从之分,任何节点均可主动发送数据。
3.冲突仲裁:基于优先级的非破坏性仲裁机制(标识符决定优先级)。
4. 实时性:短帧结构(最大8字节数据)和高速率(可达1 Mbps)适合实时控制。

三、帧类型

1数据帧:传输实际数据,包含标识符、控制段、数据段和CRC校验。
2. 远程帧:请求其他节点发送特定数据,无数据段。
3.错误帧:节点检测错误时主动发送,强制其他节点重发。
4.过载帧:用于增加帧间隔延迟,处理繁忙状态。

四、应用场景
1.汽车电子:ECU(发动机控制单元)、传感器、仪表盘通信。
2.工业自动化:PLC、电机控制器、机器人通信。
3. 医疗设备:医疗仪器间的数据同步。
五、物理层标准
4. 高速CAN(ISO 11898-2):速率1 Mbps,终端电阻120Ω,用于实时性要求高的场景。
2.低速容错CAN(ISO 11898-3):速率125 kbps,抗干扰更强,适用于恶劣环境。
六、协议栈组成
5. 物理层:定义电气特性(如RS-485电平)。
6. 数据链路层:处理帧结构、错误检测、仲裁等。
7. 应用层协议:如CANopen、J1939(汽车专用)。
七、 CAN硬件配置
1.查看STM32F103ZET6手册:确定要用的引脚。

1859168e87b2cf316a.png

八、基于STM32CUBE MX进行代码生成:

配置debug为SW,否则无法仿真调试。

5997468e87b25d5a92.png

2.配置时钟:外部时钟

1991268e87b1d8bd37.png

6985968e87b1878c97.png

由图可知APB1外设时钟为36MHZ,即CAN总线时钟。
3.配置CAN总线参数

1498268e87b0def5d2.png

本例为1MHZ通信,所以分频为3 、BS1=8、BS2=3。波特率 = APB1 时钟 / (分频系数 * (1 + BS1 + BS2))=36MHZ/(3*(1+8+3))=1MHZ 通信频率。
点击generate code生成代码。
九、代码编写
1.配置CAN的GPIO管脚:
gpio.c
#include "gpio.h"

void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11|GPIO_PIN_12, GPIO_PIN_RESET);

  /*Configure GPIO pins : PA11 PA12 */
  GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}



2.代码can.c

#include "can.h"
CAN_HandleTypeDef hcan;
/* 使能CAN接收中断 */
void CAN_Enable_Interrupt(void)//配置CAN_RX1接收通道
{
  // 使能FIFO0消息 pending 中断(接收到数据时触发)
  if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
  {
    Error_Handler();
  }
  // 配置NVIC(中断优先级)
  HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 0, 0); // 优先级可根据需求调整
  HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn);
}
void MX_CAN_Init(void)
{
// ***************************************************************
// BaudRate = 1 / NominalBitTime
// NominalBitTime = 1tq + tBS1 + tBS2
// tq = (BRP[9:0] + 1) x tPCLK
// tPCLK = CAN's clock = APB1's clock
// 1Mbps 速率下,采用点的位置在6tq位置处,BS1=5, BS2=2
// 500kbps 速率下,采用点的位置在8tq位置处,BS1=7, BS2=3
// 250kbps 速率下,采用点的位置在14tq位置处,BS1=13, BS2=2
// 125k, 100k, 50k, 20k, 10k 的采用点位置与 250K 相同
// ****************************************************************

  //波特率 = APB1 时钟 / (分频系数 * (1 + BS1 + BS2))=36MHZ/(3*(1+8+3))=1MHZ 与电机通信
  hcan.Instance = CAN1;
  hcan.Init.Prescaler = 3;  // 预分频器
  hcan.Init.Mode = CAN_MODE_NORMAL;  // 正常模式(收发使能)
  hcan.Init.SyncJumpWidth = CAN_SJW_1TQ; // 同步跳转宽度:1个时间量子
  hcan.Init.TimeSeg1 = CAN_BS1_8TQ; // 时间段1:8个时间量子
  hcan.Init.TimeSeg2 = CAN_BS2_3TQ; // 时间段2:3个时间量子
  hcan.Init.TimeTriggeredMode = DISABLE; // 禁用时间触发模式
  hcan.Init.AutoBusOff = DISABLE;  // 自动退出总线关闭模式(可选)
  hcan.Init.AutoWakeUp = DISABLE;
  hcan.Init.AutoRetransmission = DISABLE; // 自动重发(建议使能)
  hcan.Init.ReceiveFifoLocked = DISABLE;  // 接收FIFO不锁定
  hcan.Init.TransmitFifoPriority = DISABLE;// 发送FIFO优先级不使能
  if (HAL_CAN_Init(&hcan) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN CAN_Init 2 过滤器配置:若需接收特定 ID,
        需修改FilterId和FilterMask(掩码为 1 的位表示必须匹配)*/
   // 配置CAN过滤器   
        CAN_FilterTypeDef can_filter_init;   
        can_filter_init.FilterBank = 0;   // 过滤器组0(共14组,CAN1用0-13)
        can_filter_init.FilterMode = CAN_FILTERMODE_IDMASK;  // 掩码模式
        can_filter_init.FilterScale = CAN_FILTERSCALE_32BIT; // 32位过滤器
        /* 接收所有标准ID消息(掩码全0表示不过滤) */
        can_filter_init.FilterIdHigh = 0x0000;   // 过滤器ID高位  32位ID高16位
        can_filter_init.FilterIdLow = 0x0000;    // 过滤器ID低位  32位ID低16位
        can_filter_init.FilterMaskIdHigh = 0x0000;// 过滤器掩码高位  32位掩码高16位
        can_filter_init.FilterMaskIdLow = 0x0000; // 过滤器掩码低位  32位掩码低16位
        //在初始化过程中,需要配置过滤器以决定使用哪个 FIFO 缓冲区。 此处为CAN_RX0 关联CAN1_RX0_IRQ中断向量
  can_filter_init.FilterFIFOAssignment = CAN_RX_FIFO0;   // 关联到接收FIFO0  匹配消息存入FIFO0
  can_filter_init.FilterActivation = ENABLE;   // 启用过滤器
  can_filter_init.SlaveStartFilterBank = 14;      // 从机过滤器起始组 (单CAN时无用)
        if (HAL_CAN_ConfigFilter(&hcan, &can_filter_init) != HAL_OK)   
                {   
                Error_Handler();   
                }
  // 启动CAN   
        if (HAL_CAN_Start(&hcan) != HAL_OK)   
                {   
                 Error_Handler();   
                }      
}
void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(canHandle->Instance==CAN1)
  {
    __HAL_RCC_CAN1_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();
    /**CAN GPIO Configuration
    PD0     ------> CAN_RX
    PD1     ------> CAN_TX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    GPIO_InitStruct.Pin = GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    __HAL_AFIO_REMAP_CAN1_3();

    HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn);
  }
}
void HAL_CAN_MspDeInit(CAN_HandleTypeDef* canHandle)
{
  if(canHandle->Instance==CAN1)
  {
    __HAL_RCC_CAN1_CLK_DISABLE();
    /**CAN GPIO Configuration
    PD0     ------> CAN_RX
    PD1     ------> CAN_TX
    */
    HAL_GPIO_DeInit(GPIOD, GPIO_PIN_0|GPIO_PIN_1);
    HAL_NVIC_DisableIRQ(CAN1_RX1_IRQn);
   // 禁用中断   
                HAL_NVIC_DisableIRQ(CAN1_RX0_IRQn);
  }
}
/* USER CODE BEGIN 1 */
//MX_CAN_GPIO_Init();   // 初始化CAN GPIO   
//MX_CAN_Init();        // 初始化CAN外设   
//CAN_Filter_Config();  // 配置过滤器      
//HAL_CAN_Start(&hcan); // 启动CAN外设
/* CAN接收消息(轮询方式)
   参数:id - 存储接收的标准ID
        data - 存储接收的数据(最大8字节)
        len - 存储数据长度(1-8)
   返回:HAL_OK - 接收成功;其他 - 接收失败
*/
HAL_StatusTypeDef CAN_Receive_Message(uint32_t *id, uint8_t *data, uint8_t *len)
{
  CAN_RxHeaderTypeDef RxHeader;
  uint32_t fifo_level;
  // 检查FIFO0是否有数据(也可检查FIFO1,根据过滤器配置)
  fifo_level = HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0);
  if (fifo_level == 0)
  {
    return HAL_ERROR; // FIFO为空,无数据
  }
  // 读取FIFO0中的数据
  if (HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, data) != HAL_OK)
  {
    return HAL_ERROR; // 读取失败
  }
  // 仅处理标准数据帧(过滤远程帧和扩展帧)
  if (RxHeader.IDE != CAN_ID_STD || RxHeader.RTR != CAN_RTR_DATA)
  {
    return HAL_ERROR;
  }
  // 提取有效信息
  *id = RxHeader.StdId;    // 标准ID(11位)
  *len = RxHeader.DLC;     // 数据长度(1-8)
  return HAL_OK;
}



3.主程序main.c

#include "main.h"
#include "can.h"
#include "gpio.h"
/* USER CODE END PV */
CAN_RxHeaderTypeDef RxHeader;
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
int main(void)
{

  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_CAN_Init();
  /* USER CODE BEGIN 2 */
  uint8_t MOTO_DISABLE[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC};
  uint32_t tx_mailbox;
// 准备发送数据     
                CAN_TxHeaderTypeDef tx_header;     
                tx_header.StdId = 0x101;       // 标准ID   电机控制 ID 号  
                tx_header.ExtId = 0x00;        // 扩展ID未使用     
                tx_header.RTR = CAN_RTR_DATA;  // 数据帧     
                tx_header.IDE = CAN_ID_STD;    // 标准帧     
                tx_header.DLC = 8;             // 数据长度     
                tx_header.TransmitGlobalTime = DISABLE;
  while (1)
  {
                //发送数据   
  if(HAL_CAN_AddTxMessage(&hcan, &tx_header, MOTO_DISABLE, &tx_mailbox) != HAL_OK)   
    {      
      Error_Handler();     
    }
        // 等待发送完成     
  while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 3) {}
        HAL_Delay(1000);       
        }
}
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */



通过CAN转USB工具进行测试:

5922968e87aba5610c.png

5.编写主程序CAN接收代码:(采用轮循的方式接收,不设置中断):
#include "main.h"
#include "can.h"
#include "gpio.h"

uint8_t  data[8]; //接收缓存数据
uint8_t  FIFO_test[2];
uint32_t global_rx_id;//接收ID号
uint8_t  global_rx_len;//接收数据长度
uint8_t  global_rx_flag = 0;//接收标志位
uint32_t fifo_level;//有数据标志位

CAN_RxHeaderTypeDef RxHeader;
void SystemClock_Config(void);
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_CAN_Init();
  while (1)
  {
// 轮询接收数据                    
/******************CAN通信接收数据处理程序开始*******************************/         
// 检查FIFO0是否有数据(也可检查FIFO1,根据过滤器配置)  
// 本工程初始CAN用的是FIFO的0缓冲区  收到数据其
fifo_level = HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0); //FIFO有数据不为0  执行完其返回标志自动清零
if (fifo_level == 0)  // FIFO为空,无数据   
        __nop();
         else
         {
                FIFO_test[0]++;
                 // 读取FIFO0中的数据(如同循环检测按键  看数据变化)   
    if (HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, data) == HAL_OK)//成功接收为 HAL_OK
          { }
     //仅处理标准数据帧(过滤远程帧和扩展帧)   
          if (RxHeader.IDE != CAN_ID_STD || RxHeader.RTR != CAN_RTR_DATA)   
                 {     
                  return HAL_ERROR;   
                 }
                 // 提取有效信息   
                 global_rx_id = RxHeader.StdId;    // 标准ID(11位)   
                 global_rx_len = RxHeader.DLC;     // 数据长度(1-8)
          }
         // 检查FIFO1是否有数据
         fifo_level = HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO1);   
   if (fifo_level == 0)  // FIFO为空,无数据   
   __nop();
         else
         FIFO_test[1]++;
/******************CAN通信接收数据处理程序结束*******************************/                        
         HAL_Delay(50);
  }
}
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */



6.启用CAN通信工具与仿真测试:OK

3566368e87a9fba202.png

982768e87a95f2528.png

————————————————
版权声明:本文为CSDN博主「兴园电子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36598085/article/details/151152958

您需要登录后才可以回帖 登录 | 注册

本版积分规则

71

主题

286

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部