什么是网站前台静态化,太原做网站设计,西安搬家公司哪家好,茂名网站建设托管STM32F407 系列文章 - CAN通讯#xff08;十一#xff09; 目录
前言
一、CAN
二、CAN驱动电路
三、CAN软件设计
1.CAN状态初始化
2.头文件相关定义
3.接收中断服务函数
4.用户层使用
1.用户层相关定义
2.发送数据
3.接收数据
1.查询方式处理
2.中断方式处理
3…STM32F407 系列文章 - CAN通讯十一 目录
前言
一、CAN
二、CAN驱动电路
三、CAN软件设计
1.CAN状态初始化
2.头文件相关定义
3.接收中断服务函数
4.用户层使用
1.用户层相关定义
2.发送数据
3.接收数据
1.查询方式处理
2.中断方式处理
3.总结
4.其它功能函数
总结 前言
一般STM32F407芯片都会自带2路CAN接口分别为CAN1和CAN2其通讯速度高达1Mb/s每个CAN总线发送端具备三个发送邮箱用来区别发送优先级接收端具备两个具有三级深度的接收 FIFO用来存储数据。一般从407芯片端口输出的CAN信号抗干扰性比较差不足以保证通讯的稳定性和可靠性这时我们就需要通过添加驱动电路可以增强信号的驱动能力确保信号在传输过程中不受干扰或衰减从而提高通讯的稳定性和可靠性。一般市场上所卖的板子都带这一功能的因此要实现CAN总线通讯功能需准备STM32F407开发板一块和CAN-Tool分析仪工具一个。 一、CAN
控制器局域网总线CANController Area Network是ISO国际标准化的串行通信协议总线使用双绞线来传输信号具有高性能、高可靠性和易于扩展的特点广泛应用于汽车、工业控制、机器人控制等领域是世界上应用最广泛的现场总线之一。CAN总线协议_百度百科 (baidu.com)
CAN通讯是一种多主机串行异步通信总线允许网络中的各个节点设备进行无中心控制的通信。每个节点都可以在总线上发送报文当多个节点同时发送报文时CAN总线会使用仲裁机制决定哪个报文优先发送优先级高的报文会先发送低优先级的报文则会停止发送。CAN总线的通信过程分为发送报文、仲裁机制、数据传输和错误检测与处理四个阶段。
要了解更为详细的CAN总线协议及其报文构成可参考CAN总线通信协议-CSDN博客讲的挺全面的。
二、CAN驱动电路
STM32单片机在进行CAN通讯时加驱动电路是为了增强信号传输能力、提供总线保护以及满足CAN总线物理层规范。这些措施有助于提高通讯的稳定性和可靠性确保单片机与CAN总线上的其他设备之间的正常通讯。下面提供一款国产驱动芯片SIT1050详细的芯片参数及引脚特性参考其datasheet。
SIT1050是一款应用于CAN协议控制器和物理总线之间的接口芯片可应用于卡车、公交、小汽车、工业控制等领域速率可达到1Mbps具有在总线与CAN协议控制器之间进行差分信号传输的能力设计电路原理如下。 三、CAN软件设计
CAN软件包括底层代码和用户层代码底层代码主要完成CAN状态的初始化工作主要涉及到对底层硬件引脚、时钟、中断的定义及调用用户层代码主要完成对CAN总线上数据消息的解析和处理。
关于底层代码的实现可以通过调用HAL官方库文件或者在可视化工具STM32CubeMX上配置然后一键画生成底层代码。不管那种方式其实都是在以CAN_TypeDef *结构完成CAN寄存器的配置在stm32f407xx.h文件上可查看。
1.CAN状态初始化
通过函数can1_init()完成主要为CAN1的设置包括波特率、工作模式设置、底层驱动配置 包括引脚配置、时钟配置、中断配置、过滤器设置、CAN总线外围设备、CAN中断使能设置等等该函数被main()函数调用。代码中有详细的介绍代码如下示例
CAN_HandleTypeDef hcan1; /* CAN1句柄 */
CAN_HandleTypeDef hcan2; /* CAN2句柄 */ /*** brief CAN初始化* note Prescaler : 重新同步跳跃时间单元.范围: 1~3;* TimeSeg2 : 时间段2的时间单元.范围: 1~8;* TimeSeg1 : 时间段1的时间单元.范围: 1~16;* Prescaler : 波特率分频器.范围: 1~1024;* 以上4个参数, 在函数内部会减1, 所以, 任何一个参数都不能等于0* CAN挂在APB1上面, 其输入时钟频率为 Fpclk1 PCLK1 42Mhz* tq Prescaler * tpclk1;* 波特率 Fpclk1 / ((TimeSeg1 TimeSeg2 1) * Prescaler);* 已知42M时钟和500Kbps要求, 根据波特率公式* 配置TimeSeg1 7, TimeSeg2 6 , 为Prescaler 6* 得出CAN波特率为: 42M / ((6 7 1) * 6) 500Kbps** param mode : CAN_MODE_NORMAL, 普通模式;CAN_MODE_LOOPBACK,回环模式;* retval 0, 初始化成功; 其他, 初始化失败;*/
uint8_t can1_init(uint32_t mode)
{/*** 1.完成CAN1的波特率和模式设置* 这里也可以通过外部选择波特率配置can1_init(uint32_t bps, uint32_t mode)* case 125:* case 500:* case 1000:***/hcan1.Instance CAN1;hcan1.Init.Prescaler 6; /* 分频系数(Fdiv)为Prescaler1 */hcan1.Init.Mode mode; /* 模式设置 */hcan1.Init.SyncJumpWidth CAN_SJW_1TQ; /* 重新同步跳跃宽度为SyncJumpWidth1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ */hcan1.Init.TimeSeg1 CAN_BS1_7TQ; /* 范围CAN_BS1_1TQ~CAN_BS1_16TQ */hcan1.Init.TimeSeg2 CAN_BS2_6TQ; /* 范围CAN_BS2_1TQ~CAN_BS2_8TQ */hcan1.Init.TimeTriggeredMode DISABLE; /* 非时间触发通信模式 */hcan1.Init.AutoBusOff ENABLE; /* 软件自动离线管理 */hcan1.Init.AutoWakeUp DISABLE; /* 睡眠模式通过软件唤醒(清除CAN-MCR的SLEEP位) */hcan1.Init.AutoRetransmission ENABLE; /* 禁止报文自动传送 */hcan1.Init.ReceiveFifoLocked DISABLE; /* 报文不锁定,新的覆盖旧的 */hcan1.Init.TransmitFifoPriority ENABLE; /* 优先级由报文标识符决定 */// 2.完成CAN1的底层驱动配置 包括引脚配置、时钟配置、中断配置if (HAL_CAN_Init(hcan1) ! HAL_OK){Error_Handler();return 1;}// 3.完成ID号为1#设备的过滤器设置CAN_Filter_Config(hcan1, 1);// 4.启动CAN1总线外围设备
#if CAN1_iSOpenif(HAL_CAN_Start(hcan1)!HAL_OK) {Error_Handler();return 1;}// 5.使能CAN1中断elseEnable_CAN1_Interrupts();
#endifreturn 0;
}上面can1_init()函数包含了HAL_CAN_Init()、CAN_Filter_Config()、HAL_CAN_Start()、Enable_CAN1_Interrupts()这4个函数分别完成如下功能
HAL_CAN_Init()此函数为HAL库函数主要是调用HAL_CAN_MspInit()函数以完成CAN1的底层驱动配置 包括引脚配置、时钟配置、中断配置。函数HAL_CAN_MspInit()同样为HAL库函数但是其被定义为若函数可以用来被重写的代码如下示例。
/*** brief CAN底层驱动引脚配置时钟配置中断配置此函数会被HAL_CAN_Init()调用* param hcan:CAN句柄* retval 无*/
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
{GPIO_InitTypeDef gpio_init_struct {0};if (CAN1 hcan-Instance){/* CAN1 clock enable */__HAL_RCC_CAN1_CLK_ENABLE(); /* 使能CAN1时钟 *//**CAN1 GPIO ConfigurationPA11 ------ CAN1_RXPA12 ------ CAN1_TX*/gpio_init_struct.Pin GPIO_PIN_11|GPIO_PIN_12;gpio_init_struct.Mode GPIO_MODE_AF_PP;gpio_init_struct.Pull GPIO_PULLUP;gpio_init_struct.Speed GPIO_SPEED_FREQ_HIGH;gpio_init_struct.Alternate GPIO_AF9_CAN1;HAL_GPIO_Init(GPIOA, gpio_init_struct); /* CAN1_RX和CAN1_TX脚 模式设置 *//* CAN1 interrupt Init */HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 1);HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 1, 1);HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn);}else if(CAN2 hcan-Instance){/* CAN2 clock enable */__HAL_RCC_CAN2_CLK_ENABLE(); /* 使能CAN2时钟 *//**CAN2 GPIO ConfigurationPB12 ------ CAN2_RXPB13 ------ CAN2_TX*/gpio_init_struct.Pin GPIO_PIN_12|GPIO_PIN_13;gpio_init_struct.Mode GPIO_MODE_AF_PP;gpio_init_struct.Pull GPIO_NOPULL;gpio_init_struct.Speed GPIO_SPEED_FREQ_HIGH;gpio_init_struct.Alternate GPIO_AF9_CAN2;HAL_GPIO_Init(GPIOB, gpio_init_struct); /* CAN2_RX和CAN2_TX脚 模式设置 *//* CAN2 interrupt Init */HAL_NVIC_SetPriority(CAN2_RX0_IRQn, 1, 1);HAL_NVIC_EnableIRQ(CAN2_RX0_IRQn);HAL_NVIC_SetPriority(CAN2_RX1_IRQn, 1, 1);HAL_NVIC_EnableIRQ(CAN2_RX1_IRQn);}
}
CAN_Filter_Config()此函数完成CAN设备的过滤器设置具体含义见代码中说明代码如下示例。
/*
过滤的基本说明
1 寄存器配置CAN_ID STID[10:3] STID[2:0]EXID[17:13] EXID[12:5] EXID[4:0]|IDE|RTR|0ID [31:24] [23:16] [15:8] [7:0]MASK [31:24] [23:16] [15:8] [7:0]STID 基本位11位 EXID扩展位18位 IDE扩展帧标识1位 RTR远程帧标识1位
2 掩码方式过滤的原则在掩码方式下掩码寄存器某位为1表示接收到的帧ID对应的位必须与标识符寄存器对应的位相同IDE为扩展帧CAN_ID_EXT4RTR为数据帧CAN_RTR_DATA0掩码为11|12|0 6
3 应用实现仅处理扩展帧和数据帧对标准帧、远程帧均不处理使用FIFO0接收发送至本控制器的CAN消息使用FIFO1接收其他控制器和单机的CAN消息ID1单机需要接收全部CAN消息并进行转发中间各单机仅接收并处理本机消息。
4 ID识别控制器接收消息的IDxx xxxxx xxxxxx YYYYYY xx xxxxxxxx掩码(0xFC003)|0x6
*/
void CAN_Filter_Config(CAN_HandleTypeDef *hcan, uint8_t deviceID)
{CAN_FilterTypeDef filter;uint32_t filterID (((uint32_t)deviceID10)3)|CAN_ID_EXT|CAN_RTR_DATA;uint32_t maskID (0xFC003)|(CAN_ID_EXT|CAN_RTR_DATA);//0x6;// 配置CAN过滤器filter.FilterMode CAN_FILTERMODE_IDMASK; //选择标识符掩码模式指示标识符的哪些位“必须匹配”filter.FilterScale CAN_FILTERSCALE_32BIT; //选择32位模式filter.FilterActivation CAN_FILTER_ENABLE; //激活筛选器filter.SlaveStartFilterBank 14;// FIFO0 筛选本机CAN消息filter.FilterBank 0; //FilterBank0 筛选器组编号共28个可配置且可调整的筛选器组filter.FilterIdHigh filterID16; //32位标识符filter.FilterIdLow filterID0xFFFF;filter.FilterMaskIdHigh maskID16; //32位掩码filter.FilterMaskIdLow maskID0xFFFF;filter.FilterFIFOAssignment CAN_FILTER_FIFO0;//FilterBank0 关联到FIFO0if (CAN1 hcan-Instance) //选择CAN1过滤器设置{if(HAL_CAN_ConfigFilter(hcan1, filter) ! HAL_OK)Error_Handler();}else if (CAN2 hcan-Instance) //选择CAN2过滤器设置{filter.FilterBank 14;if(HAL_CAN_ConfigFilter(hcan2, filter) ! HAL_OK)Error_Handler();}/*** 特殊设置 * FIFO1过滤器设置 仅ID1单机收其他单机的CAN消息***/if(DEVICEID deviceID ){filter.FilterBank 1; //过滤器编号filter.FilterIdHigh 0x0000; //32位IDfilter.FilterIdLow CAN_ID_EXT|CAN_RTR_DATA;filter.FilterMaskIdHigh 0x0000; //32位MASKfilter.FilterMaskIdLow CAN_ID_EXT|CAN_RTR_DATA;//0x6;filter.FilterFIFOAssignmentCAN_FILTER_FIFO1; //关联到FIFO1if (CAN1 hcan-Instance) //选择CAN1过滤器设置{if(HAL_CAN_ConfigFilter(hcan1, filter) ! HAL_OK)Error_Handler();}else if (CAN2 hcan-Instance) //选择CAN2过滤器设置{filter.FilterBank 15;if(HAL_CAN_ConfigFilter(hcan2, filter) ! HAL_OK)Error_Handler();}}/*** 特殊设置* 为适应标准帧增加过滤器***/filterID CAN_ID_STD|CAN_RTR_DATA; // ?(0x0580UL21)|filter.FilterBank 2; //过滤器编号filter.FilterIdHigh 0x0000; //32位IDfilter.FilterIdLow CAN_ID_STD|CAN_RTR_DATA;filter.FilterMaskIdHigh 0x0000; //32位MASKfilter.FilterMaskIdLow CAN_ID_STD|CAN_RTR_DATA;filter.FilterFIFOAssignment CAN_FILTER_FIFO0;//关联到FIFO0if (CAN1 hcan-Instance) //选择CAN1过滤器设置{if(HAL_CAN_ConfigFilter(hcan1, filter) ! HAL_OK)Error_Handler();}
}
HAL_CAN_Start()此函数主要完成开启CAN总线外围设备状态该函数为HAL库函数具体说明见官方说明此处不提供代码。Enable_CAN1_Interrupts()完成CAN接收中断使能以及FIFO和中断模式选择具体含义见代码中说明代码如下示例。
/*** brief 使能CAN1接收中断 以及FIFO和中断模式选择* note 根据STM官方手册F407每个CAN接收端具备两个具有三级深度的接收FIFO* 分别为FIFO0和FIFO1每个接收中断又可以选择不同的中断模式具体为* CAN_IT_RX_FIFO0_MSG_PENDING模式有消息就触发中断* CAN_IT_RX_FIFO0_FULL模式三级接收FIFO全满时触发中断* CAN_IT_RX_FIFO0_OVERRUN超出时就触发中断* retval 一般推荐pending模式中断*/
void Enable_CAN1_Interrupts()
{
#if RXFifo0_iSOpenHAL_CAN_ActivateNotification(hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
#endif#if RXFifo1_iSOpenHAL_CAN_ActivateNotification(hcan1, CAN_IT_RX_FIFO1_MSG_PENDING);
#endif
}
void Enable_CAN2_Interrupts()
{
#if RXFifo0_iSOpenHAL_CAN_ActivateNotification(hcan2, CAN_IT_RX_FIFO0_MSG_PENDING);
#endif#if RXFifo1_iSOpen HAL_CAN_ActivateNotification(hcan2, CAN_IT_RX_FIFO1_MSG_PENDING);
#endif
}
/*** brief 关闭CAN1接收中断* retval*/
void Disable_CAN1_Interrupts()
{
#if RXFifo0_iSOpenHAL_CAN_DeactivateNotification(hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
#endif#if RXFifo1_iSOpenHAL_CAN_DeactivateNotification(hcan1, CAN_IT_RX_FIFO1_MSG_PENDING);
#endif
}
void Disable_CAN2_Interrupts()
{
#if RXFifo0_iSOpenHAL_CAN_DeactivateNotification(hcan2, CAN_IT_RX_FIFO0_MSG_PENDING);
#endif#if RXFifo1_iSOpenHAL_CAN_DeactivateNotification(hcan2, CAN_IT_RX_FIFO1_MSG_PENDING);
#endif
}
该CAN状态初始化函数can1_init()一般在main()函数开始被调用代码如下示例。
int main(void)
{/* 略.....初始化设置代码 */can1_init(CAN_MODE_LOOPBACK); /* CAN1初始化, 环回模式, 波特率500Kbps */while (1) {/* CAN消息解析处理 */ }
}
2.头文件相关定义
上述函数相关头文件定义代码如下示例
#ifndef __CAN_H
#define __CAN_H#include ./SYSTEM/sys/sys.h
#include ./SYSTEM/delay/delay.hextern CAN_HandleTypeDef hcan1;
extern CAN_HandleTypeDef hcan2;
#define DEVICEID 1
#define CAN1_iSOpen 1
#define CAN2_iSOpen 0
#define RXFifo0_iSOpen 1
#define RXFifo1_iSOpen 1
#define isUserDefined 0
/******************************************************************************************/
/* CAN接收中断使能 */
#if isUserDefined
#define PRO_CAN1_RX0_IRQHandler CAN1_RX0_IRQHandler
#define PRO_CAN1_RX1_IRQHandler CAN1_RX1_IRQHandler
#define PRO_CAN2_RX0_IRQHandler CAN2_RX0_IRQHandler
#define PRO_CAN2_RX1_IRQHandler CAN2_RX1_IRQHandler
#else
void CAN1_RX0_IRQHandler(void); //ISR函数
void CAN1_RX1_IRQHandler(void); //ISR函数
void CAN2_RX0_IRQHandler(void); //ISR函数
void CAN2_RX1_IRQHandler(void); //ISR函数
#endif/* 函数声明 */
uint8_t can_receive_msg(uint32_t id, uint8_t *buf); /* CAN接收数据, 查询 */
uint8_t can_send_msg(uint32_t id, uint8_t *msg, uint8_t len); /* CAN发送数据 */
uint8_t can1_init(uint32_t mode); /* CAN初始化 */
void Error_Handler(void);
void Recv_CAN_Msgs(CAN_HandleTypeDef*, uint32_t);
void CAN_Filter_Config(CAN_HandleTypeDef*, uint8_t);
void Enable_CAN1_Interrupts(void);
void Enable_CAN2_Interrupts(void);
void Disable_CAN1_Interrupts(void);
void Disable_CAN2_Interrupts(void);
#endif
3.接收中断服务函数
处理中断服务例程Interrupt Service Routine, ISR函数它们由硬件中断触发自动执行。具体实现流程为当处理器的CAN1接口的RX0缓冲区接收到数据时硬件会触发一个中断CPU响应这个中断跳转到CAN1_RX0_IRQHandler这个中断服务例程的地址开始执行代码这个中断服务例程通常负责读取接收到的数据处理这些数据比如更新状态变量发送数据到其他模块等然后返回。
在startup_stm32f407xx.s文件该文件每个项目工程中都有主要作用为设置初始SP、设置初始处理器、设置带有异常ISR地址的向量表条目、调用main函数。上可以看到代码如下示例
; Vector Table Mapped to Address 0 at ResetAREA RESET, DATA, READONLY
__Vectors DCD __initial_sp ; Top of StackDCD Reset_Handler ; Reset HandlerDCD NMI_Handler ; NMI Handler; External Interrupts DCD CAN1_RX0_IRQHandler ; CAN1 RX0 DCD CAN1_RX1_IRQHandler ; CAN1 RX1 DCD CAN2_RX0_IRQHandler ; CAN2 RX0 DCD CAN2_RX1_IRQHandler ; CAN2 RX1
__Vectors_End
DCD CAN1_RX0_IRQHandler 这行代码是在汇编语言中使用的用于定义一个数据常量。在这个上下文中它定义了一个标签或者说是一个内存地址该标签指向 CAN1_RX0_IRQHandler 这个中断服务例程的地址。
这里介绍下ISR通常有以下特点
它们必须尽可能快地执行以减少中断延迟并避免阻塞其他中断的处理。它们通常访问特定的硬件寄存器来读取中断状态、清除中断标志并处理中断引起的事件。在多任务环境或实时操作系统RTOS中ISR可能负责设置标志或发送消息给任务以便在ISR之外处理更耗时的任务。在编写ISR时开发者需要确保它们遵循特定的硬件和编译器要求比如使用特定的中断向量表入口点名称在这个例子中是CAN1_RX0_IRQHandler以及可能需要在函数开始和结束时添加特定的汇编指令或内联代码来保存和恢复CPU寄存器状态。
在上面第二小节-头文件相关定义中可以看到对中断服务例程函数的定义或宏替换代码如下重写一下
#define isUserDefined 0#if isUserDefined
#define PRO_CAN1_RX0_IRQHandler CAN1_RX0_IRQHandler
#define PRO_CAN1_RX1_IRQHandler CAN1_RX1_IRQHandler
#define PRO_CAN2_RX0_IRQHandler CAN2_RX0_IRQHandler
#define PRO_CAN2_RX1_IRQHandler CAN2_RX1_IRQHandler
#else
void CAN1_RX0_IRQHandler(void); //ISR函数
void CAN1_RX1_IRQHandler(void); //ISR函数
void CAN2_RX0_IRQHandler(void); //ISR函数
void CAN2_RX1_IRQHandler(void); //ISR函数
#endif
相关函数的实现如下。这里需要说明一下如果你是通过STM32CubeMX生成的代码相应的ISR函数会在stm32f4xx_it.c文件此文件为所有异常处理程序和外围设备中断服务程序上实现。
/*** brief 弱函数 可被重写*/
__weak void Recv_CAN_Msgs(CAN_HandleTypeDef *hcan, uint32_t RxFifo)
{UNUSED(hcan);/* NOTE : This function Should not be modified, when the callback is needed,the Recv_CAN_Msgs could be implemented in the user file*/
}
/*** brief CAN_RX中断服务函数 引用中断处理函数* note 处理CAN FIFO0的接收中断* param 无* retval 无*/
void PRO_CAN1_RX0_IRQHandler()
{
#if isUserDefinedRecv_CAN_Msgs(hcan1, CAN_RX_FIFO0);
#elseHAL_CAN_IRQHandler(hcan1);
#endif
}
void PRO_CAN1_RX1_IRQHandler()
{
#if isUserDefinedRecv_CAN_Msgs(hcan1, CAN_RX_FIFO1);
#elseHAL_CAN_IRQHandler(hcan1);
#endif
}
void PRO_CAN2_RX0_IRQHandler()
{
#if isUserDefinedRecv_CAN_Msgs(hcan2, CAN_RX_FIFO0);
#elseHAL_CAN_IRQHandler(hcan2);
#endif
}
void PRO_CAN2_RX1_IRQHandler()
{
#if isUserDefinedRecv_CAN_Msgs(hcan2, CAN_RX_FIFO1);
#elseHAL_CAN_IRQHandler(hcan2);
#endif
}#if !isUserDefined
/*** brief 处理中断服务例程ISR函数 它们由硬件中断触发自动执行* note This function handles CAN1 CAN1 RX interrupts. */
void CAN1_RX0_IRQHandler(void)
{PRO_CAN1_RX0_IRQHandler();
}
void CAN1_RX1_IRQHandler(void)
{PRO_CAN1_RX1_IRQHandler();
}
void CAN2_RX0_IRQHandler(void)
{PRO_CAN2_RX0_IRQHandler();
}
void CAN2_RX1_IRQHandler(void)
{PRO_CAN2_RX1_IRQHandler();
}
#endifvoid 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 */
}
4.用户层使用
用户的使用主要为发送数据和接收数据两个动作这两个动作中均包含CAN数据的处理与解析过程。在讲解这两个动作前先完成其相关变量、功能函数、引用头文件的定义在头文件上完成。
1.用户层相关定义
发送数据和接收数据的相关函数头文件定义代码如下示例
#ifndef __CAN_USER_H
#define __CAN_USER_H#ifdef __cplusplus
extern C
{
#endif#include string.h
#include ./BSP/CAN/can.h
#include ./BSP/CAN/lib_array.h
extern Block_Circle_Array ProcCANArray;
extern Block_Circle_Array CANSendArray;
extern Block_Circle_Array g_PushCANArray;
typedef union _Union_Bit64 {uint64_t u64;int64_t i64;double f64;uint32_t u32[2];int32_t i32[2];float f32[2];uint16_t u16[4];int16_t i16[4];uint8_t b[8];
}UNION_BIT64;typedef struct
{CAN_EXT_ID ID; // 消息IDuint8_t Len; // 消息数据长度(字节)UNION_BIT64 Data; // 消息数据
}__attribute__((packed)) CAN_Message;
typedef enum
{STD_CAN_MSG 4,EXT_CAN_MSG 0
}CAN_Msg_Type;#define CAN_MSG_TYPE_FILTER 0x4
typedef enum
{CAN1_TIME 1,CAN2_TIME,UART1_TIME,UART2_TIME,UART3_TIME,IIC_TIME,NET_TIME
}DeviceComTimeIndex; // 设备通讯时间索引typedef enum
{CMD1 1,CMD2,CMD3,CMD4
}Info_Cmd;
typedef enum
{SRC1 1,SRC2,SRC3,SRC4
}Info_Src;
typedef enum
{DES1 1,DES2,DES3,DES4
}Info_Des;typedef enum
{ID1 0x1,ID2 0x2,ID3 0x3,ID4 0x4,UNKNOWN_ID 0x0 // 未知节点
}Device_ID;#define CombCMD(cmd, src, des) (((cmd) 12) | ((src) 6) | des) // 根据消息命令源节点和目标节点的ID组合成实际可辨别的命令
typedef enum
{ID1_BUS_CMD CombCMD(CMD1, SRC1, DES1),ID2_BUS_CMD CombCMD(CMD2, SRC2, DES2),ID3_BUS_CMD CombCMD(CMD3, SRC3, DES3),ID4_BUS_CMD CombCMD(CMD4, SRC4, DES4),
}CAN_Msg_Cmds;
void User_CAN_Init(void);
void Reset_CAN_Recv_Array(void);
uint32_t Create_Ext_CAN_Msg_ID(Bus_Pri_ID pri, uint8_t mid, Device_ID src, Device_ID des, CAN_Bus_ID bus, uint8_t index);
void Push_CAN_Send_Array(CAN_Bus_ID CANChannel, CAN_Message *msg);
void CAN_Data_Process(void);
void CAN_Msg_process(CAN_Message *msg);
#ifdef __cplusplus
}
#endif#endif2.发送数据
发送数据主要在Send_CAN_Msgs()函数上完成一般我们使用的环境比较复杂使用单机设备较多、或者数据量较大时为保证数据的及时处理和数据的完整性这时我们需要建立一个缓存块用来存放数据实现代码如下示例
//发送CAN消息
void Send_CAN_Msgs(void)
{CAN_TxHeaderTypeDef msgHead; /* 发送参数句柄 */CAN_Message msg;uint32_t txMailbox;
#if CAN1_iSOpenuint8_t CAN1FreeBoxs 0;uint8_t CAN1Msgs BlockCircleArray_GetBlockCount(CANSendArray); //缓存块if(CAN1Msgs 0) //有缓存的数据{CAN1FreeBoxs HAL_CAN_GetTxMailboxesFreeLevel(hcan1);if(0 CAN1FreeBoxs) // 有空邮箱{BlockCircleArray_Get(CANSendArray, (uint8_t*)msg);Set_CAN_TxHeader(msgHead, msg);if(HAL_OK HAL_CAN_AddTxMessage(hcan1, msgHead, msg.Data.b, txMailbox)){//发送成功在缓存块上删除该消息不成功时保留BlockCircleArray_Slide(CANSendArray); //Delay_us(10);}}}
#endif
//是否开放CAN2
#if CAN2_iSOpenuint8_t CAN2FreeBoxs 0;uint8_t CAN2Msgs BlockCircleArray_GetBlockCount(CANSendArray);if(CAN2Msgs 0){CAN2FreeBoxs HAL_CAN_GetTxMailboxesFreeLevel(hcan2);if(0 CAN2FreeBoxs){BlockCircleArray_Get(CANSendArray, (uint8_t*)msg);Set_CAN_TxHeader(msgHead, msg);if(HAL_OK HAL_CAN_AddTxMessage(hcan2, msgHead,msg.Data.b, txMailbox)){BlockCircleArray_Slide(CANSendArray);//Delay_us(5);}}}
#endif
//测试发送一组数据
#if 0
/*** brief 测试CAN 发送一组数据* note 发送格式固定为: 标准ID, 数据帧* param id : 标准ID(11位)* param msg : 数据指针* param len : 数据长度*/uint32_t id 0x11;uint8_t len 8;uint8_t msgs[len];uint16_t t 0;txMailbox CAN_TX_MAILBOX0;msgHead.StdId id; /* 标准标识符 */msgHead.ExtId id; /* 扩展标识符(29位) */msgHead.IDE CAN_ID_STD; /* 使用标准帧 */msgHead.RTR CAN_RTR_DATA; /* 数据帧 */msgHead.DLC len;if (HAL_CAN_AddTxMessage(hcan1, msgHead, msgs, txMailbox) ! HAL_OK) /* 发送消息 */{return;}while (HAL_CAN_GetTxMailboxesFreeLevel(hcan1) ! 3) /* 等待发送完成,所有邮箱为空 */{t;if (t 0xFFF) {HAL_CAN_AbortTxRequest(hcan1, txMailbox); /* 超时直接中止邮箱的发送请求 */return;}}
#endif
}
3.接收数据
CAN数据的接收一般有两种实现方式一种时通过中断接收并处理消息另一种是通过查询的方式实现。这里推荐采用中断方式实现这种方式高效、便捷工业上采取这种方式实现查询的方式适用于功能简单、且数据量少的情况使用。
1.查询方式处理
采用查询方式实现一般是在主函数的主循环中通过while(1)中不断地查询是否接收导数据因此此方式缺点就在这里需保证主循环中无其他重大耗时的功能实现代码如下示例。
/*** brief CAN 接收数据查询* note 接收数据格式固定为: 标准ID, 数据帧* param id : 要查询的 标准ID(11位)* param buf : 数据缓存区* retval 接收结果* arg 0 , 无数据被接收到;* arg 其他, 接收的数据长度*/
uint8_t can_receive_msg(uint32_t id, uint8_t *buf)
{if (HAL_CAN_GetRxFifoFillLevel(hcan1, CAN_RX_FIFO0) 0) /* 没有接收到数据 */{return 0;}CAN_RxHeaderTypeDef g_canx_rxheader; /* 接收参数句柄 */if (HAL_CAN_GetRxMessage(hcan1, CAN_RX_FIFO0, g_canx_rxheader, buf) ! HAL_OK) /* 读取数据 */{return 0;}if (g_canx_rxheader.StdId! id || g_canx_rxheader.IDE ! CAN_ID_STD || g_canx_rxheader.RTR ! CAN_RTR_DATA) /* 接收到的ID不对 / 不是标准帧 / 不是数据帧 */{return 0; }return g_canx_rxheader.DLC;
}
在main()函数实现如下。
int main(void)
{uint32_t id 0x12;uint8_t canbuf[8];uint8_t rxlen 0;/* 略.....初始化设置代码 */while (1) {rxlen can_receive_msg(id, canbuf); /* CANID0x12, 接收数据查询 */if (rxlen) /* 是否接收到有数据 */ {/* 解析处理 */ }}
}
2.中断方式处理
前面第三小节已经完成对CAN接收的中断服务函数定义这里继续在上面实现解析处理功能。在前面的HAL库函数HAL_CAN_IRQHandler()里面会调用HAL_CAN_RxFifo0MsgPendingCallback()函数该函数为若函数已被重写调用Recv_CAN_Msgs()和Pre_Process_CAN_Msgs()完成数据解析功能相关函数解释如下
HAL_CAN_RxFifo0MsgPendingCallback()此函数为HAL库函数但是其被定义为若函数可以用来被重写的。重写调用Recv_CAN_Msgs()函数以完成数据解析Recv_CAN_Msgs()关键数据接收处理函数完成CAN消息的帧头以及数据内容接收并判断来帧类型、帧来源等信息该函数也可以不通过被HAL_CAN_RxFifo0MsgPendingCallback()函数调用自行处理直接被中断服务例程ISR函数调用具体代码实现方式见第三小节-接收中断服务函数将宏define isUserDefined定义为1即可。Pre_Process_CAN_Msgs()消息预处理放入缓存队列。
代码如下示例。
// 消息预处理放入缓存队列
void Pre_Process_CAN_Msgs(CAN_Message* prcMsg)
{if(prcMsg ! NULL){if(Check_New_CAN_Msg(RecvIDList, g_1msTick, prcMsg-ID)) // 检查是否是新消息防止CAN1、CAN2上的重复消息{// 来自于SRC1的针对本机的控制命令待进一步处理信息if(prcMsg-ID.s.src SRC1)BlockCircleArray_Push(ProcCANArray, (uint8_t*)prcMsg);} }
}void Recv_CAN_Msgs(CAN_HandleTypeDef *hcan, uint32_t RxFifo)
{CAN_RxHeaderTypeDef rxHeader;CAN_Message rcvMsg {0};CAN_Msg_Type msgType;while(HAL_CAN_GetRxFifoFillLevel(hcan, RxFifo)){// 获得接收到的数据头和数据if (HAL_CAN_GetRxMessage(hcan, RxFifo, rxHeader, rcvMsg.Data.b) HAL_OK){if(rxHeader.IDE CAN_ID_EXT) // 扩展帧{rcvMsg.ID.id rxHeader.ExtId;msgType EXT_CAN_MSG;}else // 标准帧{rcvMsg.ID.id rxHeader.StdId;msgType STD_CAN_MSG;}rcvMsg.Len rxHeader.DLC;if(hcan hcan1) // 通过地址判断是CAN1地址区接受的数据还是CAN2{// 使用预留的数据区保存当前消息的接收总线通道号rcvMsg.ID.s.res CAN1_BUS | msgType;g_DeviceComTime[CAN1_TIME] g_1msTick;}else{rcvMsg.ID.s.res CAN2_BUS | msgType;g_DeviceComTime[CAN2_TIME] g_1msTick;}Pre_Process_CAN_Msgs(rcvMsg); }else{break;}}
}/*** brief 弱函数 已被重写* note pending callback 接收中断模式 * 处理CAN句柄上接收FIFO0的消息 该函数被HAL_CAN_IRQHandler()调用*/
// FIFO0收到的是本机信息
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{Recv_CAN_Msgs(hcan, CAN_RX_FIFO0);
}
// FIFO1收到的是其他单机信息
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
{Recv_CAN_Msgs(hcan, CAN_RX_FIFO1);
} 在main()函数实现如下。
int main(void)
{User_CAN_Init();/* 略.....初始化设置代码 */while (1) {/*** 其它功能 ***/CAN_Data_Process();}
}
3.总结
从上面对CAN数据接收的这两种实现方式可以看出不管是哪一种方式其底层都是查询调用can的FIFO缓存是否有数据不同的是一种在中断里面查看一种是在主循环里面查看。
4.其它功能函数
其它功能主要是完成一些参数变量的初始化设置和对CAN消息缓存块的解析处理代码如下示例。
#include ./BSP/CAN/can_user.h/*** 完成参数定义 ***/
void CAN_Arrays_Init(void)
{BlockCircleArray_Init(ProcCANArray, (uint8_t*)ProcCANMsgs, CAN_MSG_LEN, MAX_CAN_PROC_MSGS);BlockCircleArray_Init(CANSendArray, (uint8_t*)CANSendMsgs, CAN_MSG_LEN, MAX_CAN_SEND_MSGS);BlockCircleArray_Init(g_PushCANArray, (uint8_t*)PushCANMsgs, CAN_MSG_LEN, MAX_CAN_PUSH_MSGS);
}
void User_CAN_Init(void)
{CAN_Arrays_Init();
}
// 将待发送的测试信息压入CAN待发送缓存队列
void Push_CAN_Send_Array(CAN_Bus_ID CANChannel, CAN_Message* msg)
{
#if CAN1_iSOpenif(CANChannel CAN1_BUS msg ! NULL)BlockCircleArray_Push(CANSendArray, (uint8_t*)msg);
#endif
#if CAN2_iSOpenelse if(CANChannel CAN2_BUS msg ! NULL)BlockCircleArray_Push(CANSendArray, (uint8_t*)msg);
#endif
}
/* CAN总线网络异常处理尚未完成并验证
* 在系统初始化时设置了自动离线管理
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan)
{uint32_t err hcan-ErrorCode;// uint8_t status0;// uint32_t canTSR hcan-Instance-TSR;// uint32_t canABRQ CAN_TSR_ABRQ0; // 终止发送// uint32_t canTERR CAN_TSR_TERR0; // 发送失败// uint32_t canALST CAN_TSR_ALST0; // 仲裁失败switch(err){case HAL_CAN_ERROR_EWG: // EWG error break;case HAL_CAN_ERROR_EPV: // EPV error break;case HAL_CAN_ERROR_BOF: // BOF errorbreak;case HAL_CAN_ERROR_STF: // Stuff errorbreak;case HAL_CAN_ERROR_FOR: // Form errorbreak;case HAL_CAN_ERROR_ACK: // Acknowledgment errorbreak;case HAL_CAN_ERROR_BR: // Bit recessive break;case HAL_CAN_ERROR_BD: // LEC dominant break;case HAL_CAN_ERROR_CRC: // LEC transfer errorbreak;case HAL_CAN_ERROR_NONE: // No errorbreak;default:break;} hcan-ErrorCode HAL_CAN_ERROR_NONE;
}*/
void Set_CAN_TxHeader(CAN_TxHeaderTypeDef* header, CAN_Message msg)
{// if(msg.ID.s.res EXT_CAN_MSG)// 扩展帧// {header-ExtId msg.ID.id;header-StdId 0;header-IDE CAN_ID_EXT;// }// else // 标准帧// {// header-StdId msg.ID.id 0xFFFF;// header-ExtId 0;// header-IDE CAN_ID_STD;// }header-RTR CAN_RTR_DATA;header-DLC msg.Len; header-TransmitGlobalTime DISABLE;// 只能设置为disable
}
uint32_t Create_Ext_CAN_Msg_ID(Bus_Pri_ID pri, uint8_t mid, Device_ID src, Device_ID des, CAN_Bus_ID bus, uint8_t index)
{CAN_EXT_ID id;id.s.res EXT_CAN_MSG;id.s.pri pri;id.s.mid mid;id.s.src src;id.s.des des;id.s.bus bus;id.s.index index;return id.id;
}
// 单消息解析处理
void CAN_Msg_process(CAN_Message* msg)
{/*** 功能函数 ***/
}
// 多消息的处理
void CAN_Msgs_Process(void)
{/*** 设计思路在main函数调用CAN_Msg_process() ***/
}
5.主函数使用
上述完成对CAN接口底层代码和用户层代码的编写这里实现其功能被主函数main()使用代码如下示例。
/********************************************************************************* file : main.c* brief : Main program body*******************************************************************************/
#include ./SYSTEM/usart/usart.h
#include ./SYSTEM/delay/delay.h
#include ./BSP/LED/led.h
#include ./BSP/LCD/lcd.h
#include ./BSP/KEY/key.h
#include ./BSP/CAN/can_user.hint main(void)
{uint8_t key;uint8_t mode 1; /* CAN工作模式: 0,普通模式; 1,环回模式 */HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */delay_init(168); /* 延时初始化 */usart_init(115200); /* 串口初始化为115200 */led_init(); /* 初始化LED */lcd_init(); /* 初始化LCD */key_init(); /* 初始化按键 */can1_init(CAN_MODE_LOOPBACK); /* CAN初始化, 环回模式, 波特率500Kbps */User_CAN_Init();//lcd_show_string(30, 50, 200, 16, 16, STM32, RED);//lcd_show_string(30, 70, 200, 16, 16, CAN TEST, RED);while (1){/* 中断方式 包含发送数据和接收数据 */CAN_Data_Process();/* 查询方式 CAN ID 0x12, 接收数据查询uint8_t rxlen can_receive_msg(0x12, canbuf); if (rxlen) { // 接收到有数据for (uint8_t i 0; i rxlen; i) {// 处理数据 显示数据}}*/key key_scan(0); if (key KEY1_PRES) { /* KEY1_PRES按下, 改变CAN的工作模式 */mode !mode;/* CAN初始化, 普通(0)/回环(1)模式, 波特率500Kbps */can1_init(mode ? CAN_MODE_LOOPBACK : CAN_MODE_NORMAL);if (mode 0) /* 普通模式, 需要2个开发板 */printf(Normal Mode);else /* 回环模式,一个开发板就可以测试了. */printf(LoopBack Mode);Reset_CAN_Recv_Array();}delay_ms(1);}
} 总结
下面提供的代码基于STM32F407ZGT芯片编写可直接在原子开发板上运行也可运行在各工程项目上但需要注意各接口以及相应的引脚应和原子开发板上保持一致。
相应的代码链接单片机STM32F407-Case程序代码例程-CSDN文库 文章转载自: http://www.morning.qcbhb.cn.gov.cn.qcbhb.cn http://www.morning.bpwz.cn.gov.cn.bpwz.cn http://www.morning.ylljn.cn.gov.cn.ylljn.cn http://www.morning.hrjrt.cn.gov.cn.hrjrt.cn http://www.morning.xswrb.cn.gov.cn.xswrb.cn http://www.morning.bhqlj.cn.gov.cn.bhqlj.cn http://www.morning.gwmjy.cn.gov.cn.gwmjy.cn http://www.morning.jngdh.cn.gov.cn.jngdh.cn http://www.morning.jbmsp.cn.gov.cn.jbmsp.cn http://www.morning.hcsqznn.cn.gov.cn.hcsqznn.cn http://www.morning.tsflw.cn.gov.cn.tsflw.cn http://www.morning.hbtarq.com.gov.cn.hbtarq.com http://www.morning.jczjf.cn.gov.cn.jczjf.cn http://www.morning.tgbx.cn.gov.cn.tgbx.cn http://www.morning.pzlcd.cn.gov.cn.pzlcd.cn http://www.morning.nkjkh.cn.gov.cn.nkjkh.cn http://www.morning.ptlwt.cn.gov.cn.ptlwt.cn http://www.morning.24vy.com.gov.cn.24vy.com http://www.morning.xyjlh.cn.gov.cn.xyjlh.cn http://www.morning.wtcyz.cn.gov.cn.wtcyz.cn http://www.morning.ngznq.cn.gov.cn.ngznq.cn http://www.morning.kyfrl.cn.gov.cn.kyfrl.cn http://www.morning.qdrhf.cn.gov.cn.qdrhf.cn http://www.morning.npbgj.cn.gov.cn.npbgj.cn http://www.morning.mhmsn.cn.gov.cn.mhmsn.cn http://www.morning.bqpg.cn.gov.cn.bqpg.cn http://www.morning.smj78.cn.gov.cn.smj78.cn http://www.morning.tcxzn.cn.gov.cn.tcxzn.cn http://www.morning.swbhq.cn.gov.cn.swbhq.cn http://www.morning.wnkjb.cn.gov.cn.wnkjb.cn http://www.morning.mzqhb.cn.gov.cn.mzqhb.cn http://www.morning.rfwrn.cn.gov.cn.rfwrn.cn http://www.morning.mhpkz.cn.gov.cn.mhpkz.cn http://www.morning.slpcl.cn.gov.cn.slpcl.cn http://www.morning.mlcnh.cn.gov.cn.mlcnh.cn http://www.morning.gyylt.cn.gov.cn.gyylt.cn http://www.morning.tfsyk.cn.gov.cn.tfsyk.cn http://www.morning.ftrpvh.cn.gov.cn.ftrpvh.cn http://www.morning.rqckh.cn.gov.cn.rqckh.cn http://www.morning.yzzfl.cn.gov.cn.yzzfl.cn http://www.morning.fkyqm.cn.gov.cn.fkyqm.cn http://www.morning.sbyhj.cn.gov.cn.sbyhj.cn http://www.morning.mwhqd.cn.gov.cn.mwhqd.cn http://www.morning.mfzyn.cn.gov.cn.mfzyn.cn http://www.morning.wnnts.cn.gov.cn.wnnts.cn http://www.morning.fjfjm.cn.gov.cn.fjfjm.cn http://www.morning.rxkl.cn.gov.cn.rxkl.cn http://www.morning.rmyt.cn.gov.cn.rmyt.cn http://www.morning.rxlk.cn.gov.cn.rxlk.cn http://www.morning.zbmcz.cn.gov.cn.zbmcz.cn http://www.morning.zcncb.cn.gov.cn.zcncb.cn http://www.morning.wkmyt.cn.gov.cn.wkmyt.cn http://www.morning.pqyms.cn.gov.cn.pqyms.cn http://www.morning.qhrlb.cn.gov.cn.qhrlb.cn http://www.morning.thbqp.cn.gov.cn.thbqp.cn http://www.morning.qgghr.cn.gov.cn.qgghr.cn http://www.morning.jmbgl.cn.gov.cn.jmbgl.cn http://www.morning.bpmnz.cn.gov.cn.bpmnz.cn http://www.morning.xknsn.cn.gov.cn.xknsn.cn http://www.morning.msbmp.cn.gov.cn.msbmp.cn http://www.morning.qwwhs.cn.gov.cn.qwwhs.cn http://www.morning.dxtxk.cn.gov.cn.dxtxk.cn http://www.morning.rdmz.cn.gov.cn.rdmz.cn http://www.morning.cwlxs.cn.gov.cn.cwlxs.cn http://www.morning.syfty.cn.gov.cn.syfty.cn http://www.morning.ztcwp.cn.gov.cn.ztcwp.cn http://www.morning.youprogrammer.cn.gov.cn.youprogrammer.cn http://www.morning.jrqw.cn.gov.cn.jrqw.cn http://www.morning.bqdgr.cn.gov.cn.bqdgr.cn http://www.morning.gjlml.cn.gov.cn.gjlml.cn http://www.morning.zrpbf.cn.gov.cn.zrpbf.cn http://www.morning.txhls.cn.gov.cn.txhls.cn http://www.morning.ldcsw.cn.gov.cn.ldcsw.cn http://www.morning.paoers.com.gov.cn.paoers.com http://www.morning.ymjrg.cn.gov.cn.ymjrg.cn http://www.morning.zrgx.cn.gov.cn.zrgx.cn http://www.morning.swyr.cn.gov.cn.swyr.cn http://www.morning.yrsg.cn.gov.cn.yrsg.cn http://www.morning.khclr.cn.gov.cn.khclr.cn http://www.morning.qpqwd.cn.gov.cn.qpqwd.cn