先根据我之前写过的一篇笔记,[通过CAN上位机和DCH软件控制欧诺克伺服电机-CSDN博客](https://blog.csdn.net/m0_67622648/article/details/157762897?spm=1001.2014.3001.5501)理解怎么才能让伺服电机动起来,其实就是通过CAN接口给驱动器发送命令。

​ 随便拿块有CAN接口的开发板就行,F1、F4、F7均可以,先实现CAN的环回测试,让你的设备没问题的前提下,就可以开始用开发板来控制伺服电机。

通过CAN接口给欧诺克伺服驱动器发送命令控制电机转动

使用到的设备

H743芯片的核心板(主机)、CAN分析仪(用来测试的从机)、一套欧诺克设备包括欧诺克的伺服驱动器和伺服电机(实际应用的从机)

用STM32CubeMX创建工程

串口

设置好串口参数,用来打印调试信息,只需要串口发送,不需要开串口中断接收(根据你的需求)

在这里插入图片描述

CAN

将给FDCAN外设提供时钟的PLL输出为20MHz(如果不是20MHz,后续计算CAN波特率的时候就要注意替换掉CAN的时钟频率为你设置的数值)

在这里插入图片描述

选择正常模式,这里数据段的波特率是设置为2MHz的,不是所有芯片都有数据段波特率这个的,F1和F4系列就只有经典CAN只能使用单一波特率,而FDCAN允许数据段使用更高的波特率。如果用的是经典CAN,就无需理会这里。

在这里插入图片描述

仲裁段波特率,很明显现在仲裁段的波特率是500Kbps,经典CAN只需要设置波特率为500Kbps即可,这里怎么算出来的,看我写的另一篇笔记STM32的CAN波特率参数设置(FDCAN的仲裁段波特率)-CSDN博客
在这里插入图片描述
在这里插入图片描述

按键

设置了三个按键作为发送命令的控制方,也就是按键按下就发送对应的命令给从机,这里你也可以用其他方法来替代,例如用串口也可以

在这里插入图片描述

添加代码到工程里

先将CubeMX生成的代码进行代码编译,没问题就添加需要的代码。

can.c

FDCAN1_Filter_Config函数对CAN的Rx过滤器进行了配置,设置为标准ID,使用掩码模式等等,并开启了CAN;

FDCAN1_Send_Msg是发送函数,传入的参数是接收方的ID号、发送数据的长度和发送的内容(数组形式发送);

从机会自动向主机返回一些自身的状态信息,利用FDCAN1_Receive_Msg接收。

FDCAN_RxHeaderTypeDef FDCAN_RxHeader;

/*
 * 函数名:FDCAN1_Filter_Config
 * 描述  :CAN的过滤器 配置
 * 输入  :无
 * 输出  : 无
 * 调用  :内部调用
 */
void FDCAN1_Filter_Config(void)
{
	FDCAN_FilterTypeDef FDCAN1_RXFilter={0};
	
	/* 配置RX滤波器 */
	FDCAN1_RXFilter.IdType		= FDCAN_STANDARD_ID;                  /*标准ID:匹配 11 位标准标识符(范围 0x000 ~ 0x7FF);扩展ID:匹配 29 位扩展标识符(范围 0x0000000 ~ 0x1FFFFFFF)*/
	FDCAN1_RXFilter.FilterIndex	= 0;                                  /*指定要配置的过滤器编号,FDCAN_STANDARD_ID:0 ~ 127;FDCAN_EXTENDED_ID:0 ~ 63*/              
	FDCAN1_RXFilter.FilterType 	= FDCAN_FILTER_MASK;                  /*FDCAN_FILTER_MASK:掩码模式(最常用);FDCAN_FILTER_RANGE:范围模式;FDCAN_EXT_FILTER_RANGE_NO_EIDM:扩展范围模式(仅对扩展 ID 有效,忽略 EID 部分位)*/
	FDCAN1_RXFilter.FilterConfig= FDCAN_FILTER_TO_RXFIFO0;            /*指定过滤器筛选后的消息去向,FDCAN_FILTER_TO_RXFIFO0:匹配的消息存入接收FIFO0;FDCAN_FILTER_TO_RXFIFO1:匹配的消息存入接收FIFO1;FDCAN_FILTER_TO_RXBUFFER:匹配的消息存入指定的接收缓冲区(需配合 RxBufferIndex);FDCAN_FILTER_DISABLE:禁用该过滤器(不筛选任何消息)*/  
	FDCAN1_RXFilter.FilterID1 	= 0x0000;                             /*过滤器的第一个标识符,具体含义取决于 FilterType,标准 ID:0x000 ~ 0x7FF,扩展 ID:0x0000000 ~ 0x1FFFFFFF*/
	FDCAN1_RXFilter.FilterID2	= 0x0000;                             /*如果FDCAN配置为传统模式的话,这里是32位掩码;过滤器的第二个标识符(掩码或范围上限),具体含义取决于 FilterType*/
	/*
		FDCAN1_RXFilter.RxBufferIndex = ;		指定匹配的消息存入哪个接收缓冲区(仅当 FilterConfig=FDCAN_FILTER_TO_RXBUFFER 时有效),仅当 FilterConfig=FDCAN_FILTER_TO_RXBUFFER 时有效,其他情况下会被忽略
		FDCAN1_RXFilter.IsCalibrationMsg = ;	指定过滤器是否用于筛选 “校准消息”
		*/
	if(HAL_FDCAN_ConfigFilter(&hfdcan1,&FDCAN1_RXFilter) == HAL_OK)
	{
		printf("FDCAN1的滤波器初始成功\r\n");
	}
	else
	{
		printf("FDCAN1的滤波器初始失败\r\n");
	}
	
	/* 调用滤波全局设置 */ 
	HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, FDCAN_REJECT, ENABLE, ENABLE); 
	
	/* 设置Rx FIFO0的wartermark为1 */
	HAL_FDCAN_ConfigFifoWatermark(&hfdcan1, FDCAN_CFG_RX_FIFO0, 1);

	/* 激活RX FIFO0的watermark通知中断,位开启Tx Buffer中断*/
	HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_WATERMARK, 0);
	
	/* 启动FDCAN */	
	if(HAL_FDCAN_Start(&hfdcan1) == HAL_OK)
	{
		printf("FDCAN1初始化成功");
	}
	else
	{
		printf("FDCAN1初始化失败");
	}
}

/**
  * 函数功能: can发送一组数据(固定格式:标准帧,数据帧)
  * 输入参数: id: 发送ID
  *           len: 数据长度(可设置为FDCAN_DLC_BYTES_2~FDCAN_DLC_BYTES_8)	
  *           msg: 数据指针,最大为8个字节
  * 返 回 值: 返回值:0,成功; 1,失败
  * 说    明: 支持动态设置ID和数据长度
  */
uint8_t FDCAN1_Send_Msg(uint32_t id, uint32_t len, uint8_t* msg)
{
    /* 临时创建发送头结构体,避免修改全局变量影响其他发送 */
    FDCAN_TxHeaderTypeDef txHeader;
    
    /* 配置发送参数 */
    txHeader.Identifier = id;                           /* 设置发送ID */
    txHeader.IdType = FDCAN_STANDARD_ID;                /* 标准ID */
    txHeader.TxFrameType = FDCAN_DATA_FRAME;            /* 数据帧 */
    txHeader.DataLength = len;                           /* 发送数据长度 */
    txHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;    /* 设置错误状态指示 */
    txHeader.BitRateSwitch = FDCAN_BRS_OFF;             /* 关闭可变波特率 */
    txHeader.FDFormat = FDCAN_CLASSIC_CAN;               /* 传统的CAN */
    txHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;    /* 用于发送事件FIFO控制, 无发送事件 */
    txHeader.MessageMarker = 0;                           /* 用于复制到TX EVENT FIFO的消息Maker来识别消息状态 */
    
    if(HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &txHeader, msg) == HAL_OK)
    {
        printf("ID:0x%03X, 长度:%d, 数据发送成功\r\n", id, len);
        return 0;
    }
    
    printf("ID:0x%03X, 长度:%d, 数据发送失败\r\n", id, len);
    return 1;
}

/**
  * 函数功能: can口接收数据查询
  * 输入参数: buf:数据缓存区
  * 返 回 值: 返回值:0,无数据被收到;
  * 说    明: 无
  */
uint8_t FDCAN1_Receive_Msg(uint8_t *buf)
{	
	if(HAL_FDCAN_GetRxMessage(&hfdcan1,FDCAN_RX_FIFO0,&FDCAN_RxHeader,buf)!=HAL_OK)	
		return 0;	
	return FDCAN_RxHeader.DataLength>>16;		/* 接收数据 */
}

把以上代码放入can.c文件

在这里插入图片描述

can.h

extern FDCAN_RxHeaderTypeDef FDCAN_RxHeader;

void FDCAN1_Filter_Config(void);
uint8_t FDCAN1_Send_Msg(uint32_t id, uint32_t len, uint8_t* msg);
uint8_t FDCAN1_Receive_Msg(uint8_t *buf);

串口发送(重定义printf函数)

/*重定义printf函数*/
int fputc(int ch,FILE *f)
{
	HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xffff);
	return ch;
}

按键

定义引脚

#define KEY1_PORT	GPIOI
#define KEY2_PORT	GPIOC
#define KEY3_PORT	GPIOH

#define KEY1_PIN		GPIO_PIN_8
#define KEY2_PIN		GPIO_PIN_13
#define KEY3_PIN		GPIO_PIN_4

定义变量

uint8_t KeyNum = 0;

​ 这里是CubeMX那要开启TIM2的内部时钟,设置定时时间为1ms(上面忘记截图了,懒得截了),怎么计算定时器定时时间看另一篇笔记STM32定时器时间的计算_32定时器时间计算-CSDN博客

​ 这里是用定时器来达到非阻塞式按键检测,每20ms进行一次按键检测

uint8_t Key_GetNum(void)
{
	/* 读后清零 */
	uint8_t Temp = 0;
	if(KeyNum)
	{
		Temp = KeyNum;	/* 把键值给中间变量,然后把KeyNum清零 */
		KeyNum = 0;
		return Temp;
	}
	return 0;
}

/* 获取按键按下状态 */
uint8_t Key_GetState(void)
{
	if (HAL_GPIO_ReadPin(KEY1_PORT, KEY1_PIN) == 0)
	{
		return 1;		/* 非阻塞式判断按键1按下则返回1 */
	}
	if (HAL_GPIO_ReadPin(KEY2_PORT, KEY2_PIN) == 0)
	{
		return 2;		/* 非阻塞式判断按键2按下则返回2 */
	}
	if (HAL_GPIO_ReadPin(KEY3_PORT, KEY3_PIN) == 0)
	{
		return 3;		/* 非阻塞式判断按键3按下则返回3 */
	}
	return 0;			/* 没有按下则返回0 */
}

/* 每隔1ms被调用一次 */
void Key_Tick(void)
{
	static uint8_t Count = 0;
	static uint8_t Current_State  = 0;	/* 本次的按键状态 */
	static uint8_t Previous_State = 0;	/* 上一次的按键状态 */
	
	Count++;					/* 计数值增加 */
	if(Count >= 20)	/* 每20ms进入一次 */
	{
		Count = 0;
		/* 获取本次的按键状态和上一次的按键状态 */
		Previous_State = Current_State;	/* 第一次进来的时候,Current_State默认是0,故Previous_State没有获取状态,第二次进来的时候,Previous_State获取到Current_State的第一次状态 */
		Current_State = Key_GetState();	/* 第一次进来的时候,Previous_State没有获取状态,但是Current_State获取到第一次状态,第二次进来的时候,Previous_State获取到Current_State的第一次状态,而Current_State获取第二次状态 */
		/* 如果Current_State=0,Previous_State=1,则表示有一次按键按下松手的瞬间 */
		if(Current_State==0 && Previous_State!=0)
		{
			KeyNum = Previous_State;	/* K1按下松手瞬间,KeyNum=1;K2按下松手瞬间,KeyNum=2; */
		}
	}
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim ->Instance == TIM2)
	{
		Key_Tick();	/* 每隔1ms调用一次 */
	}
}

用阻塞式也可以。

main.c

宏定义和要发送的数据

#define slave_addr	0x601		/* 从机地址 */

uint8_t FDCAN_Rxflag = 0;

/* 发送的数据 */
uint8_t slave_addrbuf[2]={0x01,0x01};	/* 设置从机地址为0x01 */
uint8_t speed_modebuf[8]={0x2F,0x60,0x60,0x00,0x03,0x00,0x00,0x00};	/* 设置伺服驱动器为速度模式 */
uint8_t enable_buf[8]={0x2B,0x40,0x60,0x00,0x0F,0x00,0x00,0x00};	/* 使能伺服驱动器 */
uint8_t ccr_buf[8]={0x23,0xFF,0x60,0x00,0x0A,0x8B,0x02,0x00};		/* 设置驱动器正转速度100rmp */
uint8_t cr_buf[8]={0x23,0xFF,0x60,0x00,0x96,0x91,0xE6,0xFF};		/* 设置驱动器反转速度-100rmp */
uint8_t stop_buf[8]={0x2B,0x40,0x60,0x00,0x00,0x00,0x00,0x00};		/* 伺服驱动器停止 */

/* 假设接收 8 字节数据 */
uint8_t rxbuf[8];

主函数

int main(void)
{

  /* USER CODE BEGIN 1 */
	
	uint8_t keynum = 0;

  /* USER CODE END 1 */

  /* MPU Configuration--------------------------------------------------------*/
  MPU_Config();

  /* 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_USART1_UART_Init();
  MX_TIM2_Init();
  MX_FDCAN1_Init();
  /* USER CODE BEGIN 2 */
	
	printf("欧诺克伺服电机控制......\r\n");
	
	/*配置过滤器*/
	FDCAN1_Filter_Config();
		
	/* 开启定时器2的中断 */
	HAL_TIM_Base_Start_IT(&htim2);
	
	printf("向ID=0x000发送从机地址设置命令\r\n");				
	/* 向ID=0x000发送从机地址设置命令 */
	FDCAN1_Send_Msg(0x000, FDCAN_DLC_BYTES_2, slave_addrbuf);
	HAL_Delay(1000);
	
	printf("设置从机为速度模式\r\n");				
	/* 向ID=slave_addr发送速度模式设置命令 */
	FDCAN1_Send_Msg(slave_addr, FDCAN_DLC_BYTES_8, speed_modebuf);				
	HAL_Delay(1000);
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
				
		keynum = Key_GetNum();
		if(keynum == 1)
		{
			printf("使能伺服驱动器\r\n");
			FDCAN1_Send_Msg(slave_addr, FDCAN_DLC_BYTES_8, enable_buf);				
		}
		if(keynum == 2)
		{
			printf("使伺服驱动器100rpm正转\r\n");
			FDCAN1_Send_Msg(slave_addr, FDCAN_DLC_BYTES_8, ccr_buf);
		}
		if(keynum == 3)
		{
			printf("让伺服驱动器停止工作\r\n");
			FDCAN1_Send_Msg(slave_addr, FDCAN_DLC_BYTES_8, stop_buf);
		}
		
		HAL_Delay(100);
		/* 中断接收 */
		if(FDCAN_Rxflag == 1)
		{
			FDCAN_Rxflag = 0;	/* 清除中断标志位 */
			printf("接收到数据\r\n");
			if(FDCAN_RxHeader.IdType == FDCAN_STANDARD_ID)
			{
				printf("Std ID is 0x%X\r\n",FDCAN_RxHeader.Identifier);
			}
			if(FDCAN_RxHeader.IdType == FDCAN_EXTENDED_ID)
			{
				printf("Ext ID is 0x%X\r\n",FDCAN_RxHeader.Identifier);
			}
			/* 打印接收的数据 */
			for(uint8_t i=0;i<8;i++)
			{									    
				printf("canbuf[%d]=0x%X\r\n",i,rxbuf[i]);
 			}
		}
		
    /* USER CODE END WHILE */

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

CAN接收回调函数

/** 
* 函数功能: CAN 接收回调函数 
* 输入参数: RxFifo0ITs:返回标志位 
* 返 回 值: 无 
* 说 明: 无 
*/ 
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
	if(hfdcan == &hfdcan1)
	{
		/* 1. 判断具体是哪种 FIFO0 中断	*/
		if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_WATERMARK) != RESET)
		{
			/* 2. 从 FIFO0 中读取消息	*/
			HAL_FDCAN_GetRxMessage(&hfdcan1, FDCAN_RX_FIFO0, &FDCAN1_RxHeader, rxbuf);
			
			/* 3. 重新激活中断通知
			它告诉 FDCAN 外设:“这次中断我已经处理完了,请你在下次 FIFO0 再次达到水位线时,继续通知我(再次触发中断)。
			HAL_FDCAN_ActivateNotification 是一个 “一次性” 的设置。一旦中断被触发并进入回调函数,相应的中断通知就会被自动禁用。
			如果你不在这里重新调用它,那么下一次即使 FIFO 里有新数据,中断也不会再发生了。
			*/
			HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_WATERMARK , 0);
		
			FDCAN_Rxflag = 1;
		}
	}
}

用CAN分析仪测试

将CAN分析仪的CAN_H、CAN_L分别连接开发板CAN接口的CAN_H、CAN_L,记得打开CAN分析仪的120Ω终端电阻。

在这里插入图片描述

打开CAN分析仪的专业工具,设置参数让CAN分析仪正常工作,选择CAN通道,设置波特率为500kbps(因为之前配置的就是500kbps)

在这里插入图片描述

复位开发板之后,就可以在串口助手和分析仪软件上看到调试信息

在这里插入图片描述

按下按键,开发板(主机)向CAN分析仪(调试用的从机)发送数据

在这里插入图片描述

让伺服电机转动

上面的CAN分析仪测试没问题之后,也就是说代码是没问题,可以正常向驱动器发送指令,将连接CAN分析仪的线对应接到驱动器上即可,给驱动器上电,然后按下按键,开发板(主机)就会给驱动器(从机)发送命令,使得伺服电机以100RPM速度正转。

在这里插入图片描述

打开CAN分析仪的专业工具,设置参数让CAN分析仪正常工作,选择CAN通道,设置波特率为500kbps(因为之前配置的就是500kbps)

在这里插入图片描述

复位开发板之后,就可以在串口助手和分析仪软件上看到调试信息

在这里插入图片描述

按下按键,开发板(主机)向CAN分析仪(调试用的从机)发送数据

在这里插入图片描述

让伺服电机转动

上面的CAN分析仪测试没问题之后,也就是说代码是没问题,可以正常向驱动器发送指令,将连接CAN分析仪的线对应接到驱动器上即可,给驱动器上电,然后按下按键,开发板(主机)就会给驱动器(从机)发送命令,使得伺服电机以100RPM速度正转。

在这里插入图片描述

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐