内容描述
网上很多讲CANopen协议的理论知识,但讲解如何实际运用的较少。本次项目是在F1上进行开发,也能快速移植用于其他类型芯片。贴出了网上较好的教程,并结合自己代码进行分享。
移植CANFestival
B站up主视频链接: 某位热心UP主的视频
此视频超级实用,还包括SDO PDO的配置和使用流程,适合入门观看
他还出了文档版 移植文档版
(此类教程较多,就不写了)
STM32配置
采用STM32F103ZET6开发板,使用CUBEMX配置生成项目
基础配置
(CUBEMX常用方法教程较多,此处不详讲,只贴出关键内容)
时钟配置:
请根据自己使用的板子配置时钟树
重要配置:
定时器配置:
CANFestival需要一个定时器来模拟多个软件定时器 注意此分频系数,F1为72M ,分频后为1M,即1us计数一次。此计数时间和CANFestival代码设置有关,若1us一次则需要修改CANFestival的timerscfg.h文件中的值 TIMEVAL_MAX 也需要修改为5000
CAN配置
根据通信需要设置波特率 此时波特率是500000bit/s 设置接收中断 设置GPIO,F1的板子默认这两个口,其他类型板子根据实际情况修改 生成项目 注意 生成的项目中有can.h文件,这两个文件和CANFestival里面的can.h文件重名,可能会导致编译错误,可通过修改文件名解决。
代码详解:
此代码为基础代码,只确保CAN和CANopen通信,其他开发未完善
代码链接:
代码链接
将代码移植到其他开发板上流程:
此代码以正点原子的F103ZET6开发板为基础移植和编写,若想用于其他开发板,比如F7,需按照以下流程 1、根据自己的开发板配置CUBEMX并生成项目。注意CANFestival库和项目生成的文件同名,我将项目中的can.h改为了F1can.h ,注意其他文件的头文件声明也需要统一修改,将两个头文件进行区分。 2、移植CANFestival库 也可直接复制我项目中的CANFestival文件夹。 3、编写关键函数 CAN 为检验CAN硬件是否可用,项目中包含了CAN的代码。此部分代码来自正点原子HAL库版本 主要三个函数:
(1)过滤器配置
void CAN_Config(void)
{
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.SlaveStartFilterBank = 14;
if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
{
while(1){}
}
if (HAL_CAN_Start(&hcan) != HAL_OK)
{
while(1){}
}
if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
{
while(1){}
}
TxHeader.StdId = 0x321;
TxHeader.ExtId = 0x01;
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.IDE = CAN_ID_STD;
TxHeader.DLC = 2;
TxHeader.TransmitGlobalTime = DISABLE;
}
(2)发送 如要运用请根据需要更改,本函数只是测试 数据和结构较乱。
uint8_t CAN1_Send_Msg(uint8_t* msg,uint8_t len)
{
uint8_t i=0;
uint32_t TxMailbox;
uint8_t message[8];
TxHeader.StdId=0X601;
TxHeader.IDE=CAN_ID_STD;
TxHeader.RTR=CAN_RTR_DATA;
TxHeader.DLC=len;
message[0] = 0x40;
message[1] = 0x28;
message[2] = 0x20;
message[3] = 0x00;
message[4] = 0x00;
message[5] = 0x00;
message[6] = 0x00;
message[7] = 0x00;
if(HAL_CAN_AddTxMessage(&hcan, &TxHeader, message, &TxMailbox) != HAL_OK)
{
return 1;
}
while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 3) {}
return 0;
}
(3)接收
此接收函数未进行检验,但接收中断可用。
u8 CAN1_Receive_Msg(u8 *buf)
{
u32 i;
u8 RxData[8];
if(HAL_CAN_GetRxFifoFillLevel(&CAN1_Handler, CAN_RX_FIFO0) != 1)
{
return 0xF1;
}
if(HAL_CAN_GetRxMessage(&CAN1_Handler, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
{
return 0xF2;
}
for(i=0;i<RxHeader.DLC;i++)
buf[i]=RxData[i];
return RxHeader.DLC;
}
使用方法: 在生成项目的主函数中添加CAN_Config(); 并通过CAN1_Send_Msg();函数发送数据。检验CAN功能是否正常。 (如果CAN无法进行通信,则难以进行CANOpen的开发)
CANOpen函数 CANFseitval移植时需要用户进行添加的函数都放在此文件中,方便移植。可在整理完CANFestival的文件加后直接添加此文件,完成整个移植。
#include "bsp_canopen.h"
#include "tim.h"
#include "bsp_can.h"
#include "F1can.h"
#include "can_driver.h"
#include "TestMaster.h"
extern CAN_TxHeaderTypeDef TxHeader;
extern CAN_RxHeaderTypeDef RxHeader;
void setTimer(TIMEVAL value)
{
TIM2->ARR = TIM2->CNT + value;
}
TIMEVAL getElapsedTime(void)
{
return TIM2->CNT;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == htim1.Instance)
{
TimeDispatch();
}
}
unsigned char canSend(CAN_PORT notused, Message *msg)
{
uint16_t time = 0;
uint32_t TxMailbox = msg->len;
TxHeader.StdId = msg->cob_id;
if(msg->rtr)
TxHeader.RTR = CAN_RTR_REMOTE;
else
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.IDE = CAN_ID_STD;
TxHeader.DLC = msg->len;
while(( HAL_CAN_AddTxMessage(&hcan,&TxHeader,msg->data,&TxMailbox)!=HAL_OK) || time > 200)
{
time ++;
return CAN_SEND_OK;
}
return CAN_SEND_ERR;
}
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef* hcan)
{
Message RxMSG ;
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxMSG.data);
RxMSG.cob_id = (uint16_t)(RxHeader.StdId);
if( RxHeader.RTR == CAN_RTR_REMOTE )
{
RxMSG.rtr = 1;
}
else
{
RxMSG.rtr = 0;
}
RxMSG.len = RxHeader.DLC;
canDispatch(&TestMaster_Data, &(RxMSG));
}
void init_rxmes(void)
{
TxHeader.StdId = 0x00;
TxHeader.ExtId = 0x00;
TxHeader.IDE = CAN_ID_STD;
TxHeader.DLC = 0;
}
s_BOARD MasterBoard = {"1", "1M"};
void InitNodes(CO_Data* d, UNS32 id)
{
if(strcmp(MasterBoard.baudrate, "none")){
setNodeId(&TestMaster_Data, 0x00);
setState(&TestMaster_Data, Initialisation);
setState(&TestMaster_Data, Operational);
masterSendNMTstateChange(&TestMaster_Data,0x01,NMT_Start_Node);
}
}
static TimerCallback_t init_callback;
void StartTimerLoop(TimerCallback_t _init_callback)
{
init_callback = _init_callback;
SetAlarm(NULL, 0, init_callback, 0, 0);
HAL_TIM_Base_Start_IT(&htim1);
}
void CANOpen_Init(void)
{
CAN_Config();
init_rxmes();
StartTimerLoop(&InitNodes);
HAL_CAN_ActivateNotification(&hcan,CAN_IT_RX_FIFO0_MSG_PENDING);
}
4、将CANOpen_Init(); 放入主函数中,完成CANOpen的移植。 如果是使用PDO,则需要配置字典,while里面可为空。CANOpen会自己根据定时器去字典里面查找需要发发送的命令。可以查看文章前面UP主链接里的教程。
如果是使用SDO,则需要通过writeNetworkDict(); readNetworkDict();函数进行读写。具体操作看链接 SDO读写指令 CANFestival SDO详解
SDO指令的组成和函数的编写请根据实际应用进行编写。
|