5563浏览
查看: 5563|回复: 5

[教程] 从STM32开始的RoboMaster生活:进阶篇 VII [CAN]

[复制链接]
本文已经同步发布于作者部署的私人博客
为了更好的排版和观看体验
可以移步到
进阶篇 VII [CAN]

项目&教程仓库-STM32-RoboMaster-


1.0 什么是CAN?

1.1 背景 + 定义

Controller Area Network 控制器局域网络 是ISO国际标准化的串行通信协议。在汽车产业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种各样的电子控制系统被开发了出来。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应“减少线束的数量”、“通过多个LAN,进行大量数据的高速通信”的需要,1986 年德国电气商博世公司开发出面向汽车的CAN 通信协议。此后,CAN 通过ISO11898及ISO11519进行了标准化,在欧洲已是汽车网络的标准协议。

CAN 的高性能和可靠性已被认同,并被广泛地应用于工业自动化、船舶、医疗设备、工业设备等方面。现场总线是当今自动化领域技术发展的热点之一,被誉为自动化领域的计算机局域网。它的出现为分布式控制系统实现各节点之间实时、可靠的数据通信提供了强有力的技术支持。近年来,它具有的高可靠性和良好的错误检测能力受到重视,被广泛应用于汽车计算机控制系统和环境温度恶劣、电磁辐射强及振动大的工业环境。

1.2 意义 + 优势

  1. 多主控制
    • 在总线空闲时,所有的单元都可开始发送消息(多主控制)
    • 最先访问总线的单元可获得发送权(CSMA/CA力式)
    • 多个单元同时开始发送时,发送高优先级 ID 消息的单元可获得发送权
  2. 消息的发送
    • 在CAN协议中,所有的消息都以固定的格式发送。总线空闲时,所有与总线相连的单元都可开始发送新消息。两个以上的单元同时开始发送消息时,根据标识符(Identifier以下称为ID)决定优先级。ID并不是表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息ID的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息。仲裁失利的单元则立刻停止发送而进行接收工作。
  3. 系统的柔软性
    • 与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单元时,连接在总线上的其他单元的软硬件及应用层都不需要改变。
  4. 通信速度
    • 根据整个网络的规模,可设定适合的通信速度。在同一网络中,所有单元必须设定成统一的通信速度。即使有一个单元的通信速度与其它的不一样。此单元也会输出错误信号,妨碍整个网络的通信。不同网络间则可以有不同的通信速度。
  5. 远程数据请求
    • 可通过发送“遥控帧”,请求其他单元发送数据。
  6. 错误检测功能
    • 所有的单元都可以检测错误(错误检测功能)
    • 检测出错误的单元会立即同时通知其他所有单元(错误通知功能)
    • 正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能)
  7. 故障封闭
    • CAN可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。
  8. 连接
    • CAN总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加:提高通信速度,则可连接的单元数减少。

2.0 CAN原理

2.1  Differential signal 差分信号

ISO11898-2.jpg

从上图我们可以看到,与图中下部分的普通信号不同,并不是低电平表示0高电平表示1,而是当两根数据线在同一时刻,如果有电压差表示0无电压差表示1。

相对于单信号线传输的方式,使用差分信号传输具有如下优点:

  • 抗干扰能力强:当外界存在噪声干扰时,几乎会同时耦合到两条信号线上,而接收端只关心两个信号的差值,所以外界的共模噪声可以被完全抵消。
  • 有效抑制电磁干扰:同样的道理,由于两根信号的极性相反,他们对外辐射的电磁场可以相互抵消,耦合的越紧密,泄放到外界的电磁能量越少。
  • 时序定位精确:由于差分信号的开关变化是位于两个信号的交点,而不像普通单端信号依靠高低两个阈值电压判断,因而受工艺,温度的影响小,能降低时序上的误差,同时也更适合于低幅度信号的电路

由于差分信号线具有这些优点,所以在USB协议,485 协议,以太网协议,及 CAN 协议的物理层中,都使用了差分信号传输。

CAN node.png

虽然CAN是通过差分信号传输数据,但是MCU不是,所以需要CAN Transceiver来把MCU的普通信号转换成差分信号。STM32并不自己提供该功能,所以需要外接CAN Transceiver。

2.2 Time Quantum 时间量子

  • 数据的每一Bit位,都分为4段
    • 同步段 ( SS / Sync )
    • 传播时间段 ( PTS / Prop )
    • 相位缓冲段 1 ( PBS1 / Phase 1 )
    • 相位缓冲段 2 ( PBS2 / Phase 2 )

CAN_Bit_Timing2.jpg

  • 而这每一段又由称为Time Quantum 时间量子的最小时间单位组成

2.3 Data Frame 数据帧

在CAN标准中,分为标准格式和扩展格式,而帧也分为5种

  • 数据帧
  • 遥控帧
  • 错误帧
  • 过载帧
  • 间隔帧

我们这里主要解析一下数据帧

CAN-Bus-frame_in_base_format_without_stuffbits_1.jpg

每个数据帧总共包含7个段

  1. Start of Frame 帧起始:表示数据帧开始的段
  2. Arbitration Field 仲裁段:表示该帧优先级的段
  3. Control 控制段:表示数据的字节数及保留位的段
  4. Data 数据段:数据的内容,一帧可发送 0~8 个字节的数据
  5. CRC Field CRC段:检查帧的传输错误的段
  6. ACK Field ACK段:表示确认正常接收的段
  7. End of Frame 帧结束:表示数据帧结束的段

2.4 Arbitration 仲裁

arbitration.jpg

在CAN中,仲裁是为了解决当同一时刻有多个Node在试图发送信息的时候,让优先权最大的那个Node先发。在CAN中,1是隐性电平,0是显性电平,在ID中显性电平越靠前越多,也就是ID越小,优先权越大。在仲裁的时候,比如有上图中的3个Node同时发送数据,当他们同时发送帧ID的时候,CAN的特性之一就会起作用,隐性电平会被显性电平覆盖,也就是只要在同一时刻,CAN上既有Node发送隐性电平又有Node发送显性电平,那CAN上就只有显性电平,反过来说,只有当CAN上所有Node都只发送隐性电平的时候,CAN上才是隐性电平。这就是仲裁机制背后的物理机制。

然后接下来,我们继续谈谈仲裁的过程中到底发生了什么,继续上图的例子,在时刻5的时候,Node 2是隐性电平 ( 1 ) ,而其他Node是显性电平 ( 0 ) ,所以整个CAN上也是显性电平 ( 0 ) ,然后Node 2自己一对比自己的输入输出不同,很自觉得知道自己优先权比其他Node低,然后停止发送数据,转为监听模式,Node 1和Node 3继续仲裁。在时刻2的时候,Node 1是隐性电平 ( 1 ) ,而Node 3是显性电平 ( 0 ) ,所以Node 1也转为监听模式,最后当整个ID发送完,仲裁完成后,只有Node 3发现自己没事,就知道自己是优先权最大的那个,接下来就继续发送数据帧,而其他Node就暂停发送数据,一直监听Node 3的数据,直到Node 3发完数据,其他Node再继续仲裁下去,决定接下来的发送权。

3.0 STM32的CAN配置原理

3.1 图解CAN内部实现机构

Reference-Manual-1079.jpg

从图上我们可以看到,CAN 1与CAN 2为主从关系,CAN 1为Master ( 主 ) ,CAN 2为Slave ( 从 ) 。CAN 1和CAN 2有各自的3个发送邮箱,2个FIFO Buffer缓冲,6个接收邮箱。但是,28个Filter 过滤器却是共用的,我们可以规定,哪些Filter给那个CAN用,甚至可以在程序运行的时候调控。

Transmit Mailbox.png

Receive Mailbox.png

上面俩图是发送邮箱和接收邮箱的具体的流程和机制。具体的细节就实在太多了,本文毕竟不是参考手册,想更加深入的看官,可以去ST官方的Reference Manual查阅具体细节。

3.2 Filter 过滤器

Filter 过滤器就很简单了,就是过滤接收到的每一帧的ID,如果符合特定条件,那就将该帧放入接收FIFO中。

Filter 过滤器有两种模式:

  1. Identifier List Mode 标识符列表模式:它把要接收报文的 ID 列成一个表,要求报文 ID 与列表中的某一个标识符完全相同才可以接收,可以理解为白名单管理
  2. Mask Mode 掩码模式:它把可接收报文 ID 的某几位作为列表,这几位被称为掩码,可以把它理解成关键字搜索,只要掩码(关键字)相同,就符合要求,报文就会被保存到接收FIFO

CAN Mask.png

每种模式又有对应的16 Bit位,32 Bit位模式,具体的使用方法和区别,本文不深入讨论,这部分内容再出个完整的教程都足够了,所以这部分留给客官去看Reference Manuel来理解。

3.3 如何通过Bit rate 比特率来选择Time Quantum 时间量子

CAN for STM32.png

该图生成于CAN Bit Time Calculation,可以看出在iRM2018的时钟树配置下,42MHz的CAN总线,最推荐的是黄色的那条,是稳定和高速的交际范围,越往上越快,但是越不稳定,越往下越稳定,但是越慢。因为C620电调和M3508电机接收的比特率是1Mbps,所以我们只能选择最上面的那个最快的那组参数。

3.4 特殊功能选项

  • Automatic Bus-Off Management 自动离线管理:可以在出错时离线后适时自动恢复,不需要软件干预
  • Automatic Retransmission 自动重传:使用本功能时,会一直发送报文直到成功为止

3.5 RoboMaster Assistant

在用串口转接器后,可以在PWM接口连接上PC的RoboMaster Assitant来调试电机,重要的是可以调整电机的CAN数据发送频率,适当调低可以降低CPU的接收压力,把计算资源留给其他更加重要的功能。

4.0 CAN的用法

HAL_StatusTypeDef HAL_CAN_ActivateNotification(CAN_HandleTypeDef *hcan, uint32_t ActiveITs);
  • 参数
    • hcan:指向CAN配置结构体
    • ActiveITs:表明哪个中断会被启动,开启改中断的消息提示
  • 返回值
    • HAL_StatusTypeDef:如果开启成功,返回HAL_OK;如果失败,返回HAL_ERROR

HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *hcan);
  • 参数
    • hcan:指向CAN配置结构体
  • 返回值
    • HAL_StatusTypeDef:如果开启成功,返回HAL_OK;如果失败,返回HAL_ERROR

HAL_StatusTypeDef HAL_CAN_ConfigFilter(CAN_HandleTypeDef *hcan, CAN_FilterTypeDef *sFilterConfig);
  • 参数
    • hcan:指向CAN配置结构体
    • sFilterConfig:指向Filter过滤器配置结构体
  • 返回值
    • HAL_StatusTypeDef:如果开启成功,返回HAL_OK;如果失败,返回HAL_ERROR

HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox);
  • 参数
    • hcan:指向CAN配置结构体
    • pHeader:指向发送数据的配置结构体
    • aData[]:指向需要发送的数据
    • pTxMailbox:该函数会返回用于储存发送数据的发送邮箱编号到该变量
  • 返回值
    • HAL_StatusTypeDef:如果添加成功,返回HAL_OK;如果失败,返回HAL_ERROR

uint32_t HAL_CAN_IsTxMessagePending(CAN_HandleTypeDef *hcan, uint32_t TxMailboxes);
  • 参数
    • hcan:指向CAN配置结构体
    • TxMailboxes:指向发送数据的发送邮箱
  • 返回值
    • uint32_t:如果有发送数据正在等待发送,返回1;如果没有,返回0

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan);
  • 在写代码的时候,在main.c中创建HAL_CAN_RxFifo0MsgPendingCallback函数
  • 在该函数中填写在FIFO 0中已经没有正在等待接收的数据的时候开启中断,需要执行的代码

HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[]);
  • 参数
    • hcan:指向CAN配置结构体
    • RxFifo:指向负责接收的FIFO
    • pHeader:指向接收数据的配置结构体
    • aData[]:指向需要接收的数据
  • 返回值
    • HAL_StatusTypeDef:如果接收成功,返回HAL_OK;如果失败,返回HAL_ERROR

5.0 练习项目

5.1 项目简介

  • CAN电机控制:通过CAN控制电机,并且在按下按钮时通过CAN读取电机运行数据

5.2 项目代码

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>© Copyright (c) 2020 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef struct{
        uint8_t     motor_id;
        int16_t     angle;
        int16_t     current_get;
        int16_t     speed_rpm;
        uint8_t     temperature;
}   motor_3508_t;
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define CAN1_DEVICE_NUM     4
#define FIRST_GROUP_ID      0x200
#define MOTOR_SPEED_MAX     16384
#define CAN_DATA_SIZE       8
#define CAN1_RX_ID_START    0x201
#define MOTOR_ID            2
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
CAN_HandleTypeDef hcan1;

UART_HandleTypeDef huart7;

/* USER CODE BEGIN PV */
int16_t speedFlag=0;
uint8_t can1_rx_buffer[CAN1_DEVICE_NUM][CAN_DATA_SIZE];
motor_3508_t motorPrintTest={.motor_id=MOTOR_ID};
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_CAN1_Init(void);
static void MX_UART7_Init(void);
/* USER CODE BEGIN PFP */
void can_filter_enable(CAN_HandleTypeDef* hcan);
void can_filter_disable(CAN_HandleTypeDef* hcan);
void can_transmit(CAN_HandleTypeDef* hcan, uint16_t id, int16_t msg1, int16_t msg2, int16_t msg3, int16_t msg4);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_CAN1_Init();
  MX_UART7_Init();
  /* USER CODE BEGIN 2 */
  can_filter_disable(&hcan1);
  HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
  HAL_CAN_Start(&hcan1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
          while(speedFlag <= MOTOR_SPEED_MAX/12){
                  can_transmit(&hcan1,FIRST_GROUP_ID,speedFlag,speedFlag,speedFlag,speedFlag);
            speedFlag++;
                  HAL_Delay(1);
          }
          while(speedFlag >= -MOTOR_SPEED_MAX/12){
                  can_transmit(&hcan1,FIRST_GROUP_ID,speedFlag,speedFlag,speedFlag,speedFlag);
            speedFlag--;
            HAL_Delay(1);
          }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage 
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 6;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  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_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief CAN1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_CAN1_Init(void)
{

  /* USER CODE BEGIN CAN1_Init 0 */

  /* USER CODE END CAN1_Init 0 */

  /* USER CODE BEGIN CAN1_Init 1 */

  /* USER CODE END CAN1_Init 1 */
  hcan1.Instance = CAN1;
  hcan1.Init.Prescaler = 3;
  hcan1.Init.Mode = CAN_MODE_NORMAL;
  hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
  hcan1.Init.TimeSeg1 = CAN_BS1_11TQ;
  hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;
  hcan1.Init.TimeTriggeredMode = DISABLE;
  hcan1.Init.AutoBusOff = ENABLE;
  hcan1.Init.AutoWakeUp = DISABLE;
  hcan1.Init.AutoRetransmission = ENABLE;
  hcan1.Init.ReceiveFifoLocked = DISABLE;
  hcan1.Init.TransmitFifoPriority = DISABLE;
  if (HAL_CAN_Init(&hcan1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN CAN1_Init 2 */

  /* USER CODE END CAN1_Init 2 */

}

/**
  * @brief UART7 Initialization Function
  * @param None
  * @retval None
  */
static void MX_UART7_Init(void)
{

  /* USER CODE BEGIN UART7_Init 0 */

  /* USER CODE END UART7_Init 0 */

  /* USER CODE BEGIN UART7_Init 1 */

  /* USER CODE END UART7_Init 1 */
  huart7.Instance = UART7;
  huart7.Init.BaudRate = 115200;
  huart7.Init.WordLength = UART_WORDLENGTH_8B;
  huart7.Init.StopBits = UART_STOPBITS_1;
  huart7.Init.Parity = UART_PARITY_NONE;
  huart7.Init.Mode = UART_MODE_TX_RX;
  huart7.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart7.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart7) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN UART7_Init 2 */

  /* USER CODE END UART7_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOE_CLK_ENABLE();

  /*Configure GPIO pin : Button_Pin */
  GPIO_InitStruct.Pin = Button_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(Button_GPIO_Port, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI2_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(EXTI2_IRQn);

}

/* USER CODE BEGIN 4 */
void can_filter_enable(CAN_HandleTypeDef* hcan){
        CAN_FilterTypeDef CAN_FilterConfigStructure;

        CAN_FilterConfigStructure.FilterIdHigh = 0x0000;
        CAN_FilterConfigStructure.FilterIdLow = 0x0000;
        CAN_FilterConfigStructure.FilterMaskIdHigh = 0x0000;
        CAN_FilterConfigStructure.FilterMaskIdLow = 0x0000;
        CAN_FilterConfigStructure.FilterFIFOAssignment = CAN_FILTER_FIFO0;
        CAN_FilterConfigStructure.FilterMode = CAN_FILTERMODE_IDMASK;
        CAN_FilterConfigStructure.FilterScale = CAN_FILTERSCALE_32BIT;
        CAN_FilterConfigStructure.FilterActivation = ENABLE;
        CAN_FilterConfigStructure.SlaveStartFilterBank = 27;

        CAN_FilterConfigStructure.FilterBank = 0;

        HAL_CAN_ConfigFilter(hcan, &CAN_FilterConfigStructure);
}

void can_filter_disable(CAN_HandleTypeDef* hcan){
        CAN_FilterTypeDef CAN_FilterConfigStructure;

        CAN_FilterConfigStructure.FilterIdHigh = 0x0000;
        CAN_FilterConfigStructure.FilterIdLow = 0x0000;
        CAN_FilterConfigStructure.FilterMaskIdHigh = 0x0000;
        CAN_FilterConfigStructure.FilterMaskIdLow = 0x0000;
        CAN_FilterConfigStructure.FilterFIFOAssignment = CAN_FILTER_FIFO0;
        CAN_FilterConfigStructure.FilterMode = CAN_FILTERMODE_IDMASK;
        CAN_FilterConfigStructure.FilterScale = CAN_FILTERSCALE_32BIT;
        CAN_FilterConfigStructure.FilterActivation = DISABLE;
        CAN_FilterConfigStructure.SlaveStartFilterBank = 27;

        CAN_FilterConfigStructure.FilterBank = 0;

        HAL_CAN_ConfigFilter(hcan, &CAN_FilterConfigStructure);
}

void can_transmit(CAN_HandleTypeDef* hcan, uint16_t id, int16_t msg1, int16_t msg2, int16_t msg3, int16_t msg4){
        CAN_TxHeaderTypeDef tx_header;
        uint8_t             data[8];
        uint32_t            pTxMailbox;

        tx_header.StdId = id;
        tx_header.IDE   = CAN_ID_STD;
        tx_header.RTR   = CAN_RTR_DATA;
        tx_header.DLC   = CAN_DATA_SIZE;
        tx_header.TransmitGlobalTime = DISABLE;
        data[0] = msg1 >> 8;
        data[1] = msg1;
        data[2] = msg2 >> 8;
        data[3] = msg2;
        data[4] = msg3 >> 8;
        data[5] = msg3;
        data[6] = msg4 >> 8;
        data[7] = msg4;

        if (HAL_CAN_AddTxMessage(hcan, &tx_header, data, &pTxMailbox) == HAL_OK){
                while (HAL_CAN_IsTxMessagePending(hcan, pTxMailbox));
        }
}

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan){
        CAN_RxHeaderTypeDef rx_header;
        rx_header.StdId = (CAN_RI0R_STID & hcan->Instance->sFIFOMailBox[CAN_RX_FIFO0].RIR) >> CAN_TI0R_STID_Pos;
        uint8_t idx = rx_header.StdId - CAN1_RX_ID_START;
        HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, can1_rx_buffer[idx]);
}

void can1_read(uint8_t id, uint8_t buf[CAN_DATA_SIZE]){
        memcpy(buf, can1_rx_buffer[--id], CAN_DATA_SIZE);
}

void get_motor_data(motor_3508_t *motor, uint8_t buf[CAN_DATA_SIZE]){
        motor->angle        = (int16_t)(buf[0] << 8 | buf[1]);
        motor->speed_rpm    = (int16_t)(buf[2] << 8 | buf[3]);
        motor->current_get  = (int16_t)(buf[4] << 8 | buf[5]);
        motor->temperature  = (uint8_t)buf[6];
}

void print_motor_data(motor_3508_t *motor, char *msg){
        sprintf(msg, "======== 3508 at CAN bus ========\r\n"\
                     "ID           %d\r\n"\
                     "Angle        %d\r\n"\
                     "Current      %d\r\n"\
                     "Speed        %d\r\n"\
                     "Temperature  %u\r\n"\
                     "=================================\r\n\r\n"\
                     , motor->motor_id, motor->angle, motor->current_get, motor->speed_rpm, motor->temperature);
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
        if(GPIO_Pin == Button_Pin){
                can_filter_enable(&hcan1);
                uint8_t motorStatus[CAN_DATA_SIZE];
                char motorMsg[250];
                can1_read(MOTOR_ID,motorStatus);
                get_motor_data(&motorPrintTest, motorStatus);
                print_motor_data(&motorPrintTest, motorMsg);
                HAL_UART_Transmit(&huart7, (uint8_t*)motorMsg, strlen(motorMsg), HAL_MAX_DELAY);
                can_filter_disable(&hcan1);
        }
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* 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,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

5.3 效果展示




本文已经同步发布于作者部署的私人博客
为了更好的排版和观看体验
可以移步到
进阶篇 VII [CAN]


如果诸位看官喜欢的话,想让这个系列继续下去的话,就请在下方留言吧~

rzyzzxw  版主

发表于 2020-3-30 17:07:37

技术达人,炼金浪人
回复

使用道具 举报

炼金浪人  中级技匠
 楼主|

发表于 2020-3-30 22:17:25

rzyzzxw 发表于 2020-3-30 17:07
技术达人,炼金浪人

蟹蟹老师资瓷~
回复

使用道具 举报

gada888  版主

发表于 2020-5-30 16:47:25

硬核板子
回复

使用道具 举报

1780967186  学徒

发表于 2021-1-30 11:48:16

大神你好 我是初学者 请问您这套代码是可以控制四个电机电调的吗 其中有一段没有看懂可以麻烦解释一下吗
SatJanuary-202101307819..png
回复

使用道具 举报

1780967186  学徒

发表于 2021-1-30 11:51:29

1780967186 发表于 2021-1-30 11:48
大神你好 我是初学者 请问您这套代码是可以控制四个电机电调的吗 其中有一段没有看懂可以麻烦解释一下吗 ...

我的理解是函数里面的第二句是邮箱接收的过滤器与发出信号的ID进行匹配 然而我在Keil里面并没有观察到这些的具体数值,请问它是如何实现ID201-204的识别
回复

使用道具 举报

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

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail