单片机通过CAN发指令控制欧诺克伺服电机
先根据我之前写过的一篇笔记,[通过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速度正转。

更多推荐



所有评论(0)