目录
CANopenNode基础代码移植
使用cubemx新建工程,设置RCC,设置SYS。激活CAN,配置CAN的波特率500k(这个波特率需要和电机驱动器的波特率对应,并不一定是500k)。
配置定时器6,时间为1ms,根据公式计算得到参数PSC取72-1,ARR取1000-1
配置板子上的两个LED
配置串口,方便调试
把CANopenNode的代码复制到项目文件夹中,注意一共两个文件
在keil里添加文件
设置头文件路径,在设置CANopenNode的时候只需要添加下面一个头文件路径就行
CANopenNode_STM32项目的代码就添加到这一级
main函数,记得引入头文件
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "CO_app_STM32.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
CANopenNodeSTM32 canOpenNode;
/* USER CODE END PV */
/**
* @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_CAN_Init();
MX_TIM6_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
canOpenNode.CANHandle = &hcan;
canOpenNode.HWInitFunction = MX_CAN_Init;
canOpenNode.timerHandle = &htim6;
canOpenNode.desiredNodeID = 1;
canOpenNode.baudrate = 500;
canopen_app_init(&canOpenNode);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, !canOpenNode.outStatusLEDRed);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, !canOpenNode.outStatusLEDGreen);
canopen_app_process();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
定时器回调函数编写
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim==canOpenNode.timerHandle)
{
canopen_app_interrupt();
}
}
/* USER CODE END 4 */
串口进行printf重定向
在usart.h中引入stdio.h
在usart.c中进行重定向
/* USER CODE BEGIN 1 */
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};
FILE __stdout;
int fputc(int ch, FILE *f)
{
while ((USART1->SR & 0X40) == 0); /* 等待上一个字符发送完成 */
USART1->DR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
/* USER CODE END 1 */
如果提示这个报错,勾选使用MicroLIB
下载到单片机,运行
打开调试助手,如果出现下面的提示,原因是堆空间不够
在CUBEMX中修改Heap Size,由0x200,修改为0x2000
生成项目,下载到单片机,出现以下提示,说明代码正常运行
通过CAN分析仪接收到板子的上线报文说明移植成功
电机驱动代码编写
首先确保电机支持CANopen通讯。
本次使用的电机符合CIA402协议,发送的SDO报文格式如下
第一部分是COB-ID,长度1个字节,电机的node-ID默认为0x01,所以报文ID一直是601。
第二部分是CMD,长度1个字节,其实就是数据部分的长度,
0x2F: 一个字节长度 // 1 byte data length
0x2B: 两个字节长度 // 2 byte data length
0x27: 三个字节长度 // 3 byte data length
0x23: 四个字节长度 // 4 byte data length
0x22: 未指定字节长度
第三部分是电机的对象字典(也就是节点的OD)中的索引,长度2个字节。索引发送的是小段模式,比如这一部分为0X40 0X60,实际操作的索引地址为0X6040。
第四部分是电机的对象字典中的子索引,长度1个字节
第五部分是要写入的数据,数据发送的时候是小端模式,低字节数据先发送,长度由第二部分决定。
新建一个demo_motor.c
写入以下代码
#include "CO_app_STM32.h"
#include "301/CO_SDOclient.h"
#include "GPIO.h"
#include "usart.h"
/*
*********************************************************************************************************
* 宏定义和变量
*********************************************************************************************************
*/
#define CANopenSlaveID 0x01
uint8_t g_ucTempBuf[20];//存放SDO报文的数据部分
extern CO_t* CO;
/*
*********************************************************************************************************
* 本地函数声明
*********************************************************************************************************
*/
CO_SDO_abortCode_t write_SDO(CO_SDOclient_t *SDO_C, uint8_t nodeId,
uint16_t index, uint8_t subIndex,
uint8_t *data, size_t dataSize);
void motorAccelerate(void);
void motorDecelerate(void);
/*
*********************************************************************************************************
* 函 数 名: DemoMotor
* 功能说明: 根据按键不同进行响应
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void DemoMotor(void)
{
uint8_t key=key_scan(0);//得到键值
if(key==3)
{
}
else if(key==0)
{
printf("key0按下,电机启动\r\n");
motorAccelerate();
}
else if(key==1)
{
printf("key1按下,电机停止\r\n");
motorDecelerate();
}
else if(key==2)
{
printf("key is key2\r\n");
}
else
{
printf("key error\r\n");
}
}
/*
*********************************************************************************************************
* 函 数 名: motorAccelerate
* 功能说明: 电机在PV模式下开始运行
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void motorAccelerate(void)
{
//初始化伺服状态机
g_ucTempBuf[0]=0x00;
g_ucTempBuf[1]=0x00;
write_SDO(CO->SDOclient, CANopenSlaveID, 0x6040, 0x00, g_ucTempBuf, 2);
//设置细分参数4000 pulse/r
g_ucTempBuf[0]=0xA0;
g_ucTempBuf[1]=0x0F;
g_ucTempBuf[2]=0x00;
g_ucTempBuf[3]=0x00;
write_SDO(CO->SDOclient, CANopenSlaveID, 0x2001, 0x00, g_ucTempBuf, 4);
//设置电流3000mA
g_ucTempBuf[0]=0xB8;
g_ucTempBuf[1]=0x0B;
write_SDO(CO->SDOclient, CANopenSlaveID, 0x2000, 0x00, g_ucTempBuf, 2);
//设置加速时间2000pulse/^2
g_ucTempBuf[0]=0xD0;
g_ucTempBuf[1]=0x07;
g_ucTempBuf[2]=0x00;
g_ucTempBuf[3]=0x00;
write_SDO(CO->SDOclient, CANopenSlaveID, 0x6083, 0x00, g_ucTempBuf, 4);
//设置减速时间2000pulse/^2
g_ucTempBuf[0]=0xD0;
g_ucTempBuf[1]=0x07;
g_ucTempBuf[2]=0x00;
g_ucTempBuf[3]=0x00;
write_SDO(CO->SDOclient, CANopenSlaveID, 0x6084, 0x00, g_ucTempBuf, 4);
//设置目标速度8000pulse/^2
g_ucTempBuf[0]=0x40;
g_ucTempBuf[1]=0x1F;
g_ucTempBuf[2]=0x01;
g_ucTempBuf[3]=0x00;
write_SDO(CO->SDOclient, CANopenSlaveID, 0x60FF, 0x00, g_ucTempBuf, 4);
//切换工作模式PV
g_ucTempBuf[0]=0x03;
write_SDO(CO->SDOclient, CANopenSlaveID, 0x6060, 0x00, g_ucTempBuf, 1);
//切换驱动器状态机
g_ucTempBuf[0]=0x06;
g_ucTempBuf[1]=0x00;
write_SDO(CO->SDOclient, CANopenSlaveID, 0x6040, 0x00, g_ucTempBuf, 2);
//切换驱动器状态机
g_ucTempBuf[0]=0x07;
g_ucTempBuf[1]=0x00;
write_SDO(CO->SDOclient, CANopenSlaveID, 0x6040, 0x00, g_ucTempBuf, 2);
//开始运行
g_ucTempBuf[0]=0x0F;
g_ucTempBuf[1]=0x00;
write_SDO(CO->SDOclient, CANopenSlaveID, 0x6040, 0x00, g_ucTempBuf, 2);
}
/*
*********************************************************************************************************
* 函 数 名: motorDecelerate
* 功能说明: 电机停止
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void motorDecelerate(void)
{
//减速停止
g_ucTempBuf[0]=0x00;
g_ucTempBuf[1]=0x00;
g_ucTempBuf[2]=0x00;
g_ucTempBuf[3]=0x00;
write_SDO(CO->SDOclient, CANopenSlaveID, 0x60FF, 0x00, g_ucTempBuf, 4);
}
CO_SDO_abortCode_t write_SDO(CO_SDOclient_t *SDO_C, uint8_t nodeId,
uint16_t index, uint8_t subIndex,
uint8_t *data, size_t dataSize)
{
CO_SDO_return_t SDO_ret;
bool_t bufferPartial = false;
// setup client (this can be skipped, if remote device is the same)
SDO_ret = CO_SDOclient_setup(SDO_C,
CO_CAN_ID_SDO_CLI + nodeId,
CO_CAN_ID_SDO_SRV + nodeId,
nodeId);
if (SDO_ret != CO_SDO_RT_ok_communicationEnd) {
return -1;
}
// initiate download
SDO_ret = CO_SDOclientDownloadInitiate(SDO_C, index, subIndex,
dataSize, 1000, false);
if (SDO_ret != CO_SDO_RT_ok_communicationEnd) {
return -1;
}
// fill data
size_t nWritten = CO_SDOclientDownloadBufWrite(SDO_C, data, dataSize);
if (nWritten < dataSize) {
bufferPartial = true;
// If SDO Fifo buffer is too small, data can be refilled in the loop.
}
//download data
do {
uint32_t timeDifference_us = 10000;
CO_SDO_abortCode_t abortCode = CO_SDO_AB_NONE;
SDO_ret = CO_SDOclientDownload(SDO_C,
timeDifference_us,
false,
bufferPartial,
&abortCode,
NULL, NULL);
if (SDO_ret < 0) {
return abortCode;
}
//bsp_DelayUS(timeDifference_us);
HAL_Delay(timeDifference_us/1000);
} while(SDO_ret > 0);
return CO_SDO_AB_NONE;
}
在CO_fifo.h中,把这行代码中括号中的0修改为1
在CO_SDOclient.h中,把这行代码中括号中的0修改为1
在CUBEMX配置按键GPIO
在gpio.h中声明按键扫描函数
在gpio.c中写按键扫描的代码
/*----------------------------------------------------------------------------*/
/* Configure GPIO */
/*----------------------------------------------------------------------------*/
/* USER CODE BEGIN 1 */
#define KEY0 HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)
#define KEY1 HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)
#define KEY2 HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2)
/* USER CODE END 1 */
/* USER CODE BEGIN 2 */
//mode 0 不支持连按
//mode 1 支持连按
uint8_t key_scan(uint8_t mode)
{
static uint8_t key_up=1;//按键按松开标志
if(mode)key_up=1; //支持连按
if(key_up&&(KEY0==0||KEY1==0||KEY2==0))
{
HAL_Delay(10);//去抖动
key_up=0;
if(KEY0==0)return 0;
else if(KEY1==0)return 1;
else if(KEY2==0)return 2;
}
else if(KEY0==1&&KEY1==1&&KEY2==1)
key_up=1;
return 3;// 无按键按下
}
/* USER CODE END 2 */
在main函数中声明demo函数为外部引用
在while循环中调用这个函数
把板子上的CAN模块和电机的CANopen通讯端口连接起来。上电之后,板子上按KEY0启动电机,按KEY1电机停止
可以配合调试助手,方便查看过程