一、CAN基础
差分信号:显性电平对应逻辑0,CAN_H和CAN_L差为2.5V;隐形电平对应逻辑1,CAN_H和CAN_L差为0V。
CAN总线的开始和结束都有一个120Ω的终端电阻。
数据帧:标准帧11位, 扩展帧29位。
其他的一些理论知识就不再赘述了,可以参考维基百科对于CAN的描述。
STM32F7xx的bxCAN主要特点:支持CAN2.0A和CAN2.0B,波特率高达1Mbps,支持时间触发,具有3个发送邮箱,2个接收邮箱,可变的过滤器组等。
二、几个重要的CAN函数
- HAL_StatusTypeDef HAL_CAN_Init(CAN_HandleTypeDef* hcan); // CAN初始化
- HAL_StatusTypeDef HAL_CAN_Transmit(CAN_HandleTypeDef *hcan, uint32_t Timeout); // CAN发送
- HAL_StatusTypeDef HAL_CAN_Receive(CAN_HandleTypeDef *hcan, uint8_t FIFONumber, uint32_t Timeout); // CAN接收
复制代码
三、几个重要的结构
- // CAN操作句柄 包含CAN基地址(CAN1/CAN2/CAN3) 初始化结构 发送接收结构体 其余三个是过程变量
- typedef struct
- {
- CAN_TypeDef *Instance; /*!< Register base address */
- CAN_InitTypeDef Init; /*!< CAN required parameters */
- CanTxMsgTypeDef* pTxMsg; /*!< Pointer to transmit structure */
- CanRxMsgTypeDef* pRxMsg; /*!< Pointer to reception structure */
- __IO HAL_CAN_StateTypeDef State; /*!< CAN communication state */
- HAL_LockTypeDef Lock; /*!< CAN locking object */
- __IO uint32_t ErrorCode; /*!< CAN Error code */
- }CAN_HandleTypeDef;
复制代码- // CAN配置结构体
- // 前5个参数来设置 CAN_BTR —— 波特率
- // 后6个参数用来设置 CAN_MCR —— 通信相关的控制位
- typedef struct
- {
- uint32_t Prescaler; /*!< Specifies the length of a time quantum.
- This parameter must be a number between Min_Data = 1 and Max_Data = 1024 */
- uint32_t Mode; /*!< Specifies the CAN operating mode.
- This parameter can be a value of @ref CAN_operating_mode */
- uint32_t SJW; /*!< Specifies the maximum number of time quanta
- the CAN hardware is allowed to lengthen or
- shorten a bit to perform resynchronization.
- This parameter can be a value of @ref CAN_synchronisation_jump_width */
- uint32_t BS1; /*!< Specifies the number of time quanta in Bit Segment 1.
- This parameter can be a value of @ref CAN_time_quantum_in_bit_segment_1 */
- uint32_t BS2; /*!< Specifies the number of time quanta in Bit Segment 2.
- This parameter can be a value of @ref CAN_time_quantum_in_bit_segment_2 */
- uint32_t TTCM; /*!< Enable or disable the time triggered communication mode.
- This parameter can be set to ENABLE or DISABLE. */
- uint32_t ABOM; /*!< Enable or disable the automatic bus-off management.
- This parameter can be set to ENABLE or DISABLE */
- uint32_t AWUM; /*!< Enable or disable the automatic wake-up mode.
- This parameter can be set to ENABLE or DISABLE */
- uint32_t NART; /*!< Enable or disable the non-automatic retransmission mode.
- This parameter can be set to ENABLE or DISABLE */
- uint32_t RFLM; /*!< Enable or disable the receive FIFO Locked mode.
- This parameter can be set to ENABLE or DISABLE */
- uint32_t TXFP; /*!< Enable or disable the transmit FIFO priority.
- This parameter can be set to ENABLE or DISABLE */
- }CAN_InitTypeDef;
复制代码- // 过滤器设置
- typedef struct
- {
- uint32_t FilterIdHigh; /*!< Specifies the filter identification number (MSBs for a 32-bit
- configuration, first one for a 16-bit configuration).
- This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
- uint32_t FilterIdLow; /*!< Specifies the filter identification number (LSBs for a 32-bit
- configuration, second one for a 16-bit configuration).
- This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
- uint32_t FilterMaskIdHigh; /*!< Specifies the filter mask number or identification number,
- according to the mode (MSBs for a 32-bit configuration,
- first one for a 16-bit configuration).
- This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
- uint32_t FilterMaskIdLow; /*!< Specifies the filter mask number or identification number,
- according to the mode (LSBs for a 32-bit configuration,
- second one for a 16-bit configuration).
- This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
- uint32_t FilterFIFOAssignment; /*!< Specifies the FIFO (0 or 1) which will be assigned to the filter.
- This parameter can be a value of @ref CAN_filter_FIFO */
- uint32_t FilterNumber; /*!< Specifies the filter which will be initialized.
- This parameter must be a number between Min_Data = 0 and Max_Data = 27 */
- uint32_t FilterMode; /*!< Specifies the filter mode to be initialized.
- This parameter can be a value of @ref CAN_filter_mode */
- uint32_t FilterScale; /*!< Specifies the filter scale.
- This parameter can be a value of @ref CAN_filter_scale */
- uint32_t FilterActivation; /*!< Enable or disable the filter.
- This parameter can be set to ENABLE or DISABLE. */
- uint32_t BankNumber; /*!< Select the start slave bank filter.
- This parameter must be a number between Min_Data = 0 and Max_Data = 28 */
- }CAN_FilterConfTypeDef;
复制代码- // 模式 我们使用普通模式
- #define CAN_MODE_NORMAL ((uint32_t)0x00000000U) /*!< Normal mode */
- #define CAN_MODE_LOOPBACK ((uint32_t)CAN_BTR_LBKM) /*!< Loopback mode */
- #define CAN_MODE_SILENT ((uint32_t)CAN_BTR_SILM) /*!< Silent mode */
- #define CAN_MODE_SILENT_LOOPBACK ((uint32_t)(CAN_BTR_LBKM | CAN_BTR_SILM)) /*!< Loopback combined with silent mode */
复制代码- // 标准帧 扩展帧
- #define CAN_ID_STD ((uint32_t)0x00000000U) /*!< Standard Id */
- #define CAN_ID_EXT ((uint32_t)0x00000004U) /*!< Extended Id */
复制代码- // 数据帧 远程帧
- #define CAN_RTR_DATA ((uint32_t)0x00000000U) /*!< Data frame */
- #define CAN_RTR_REMOTE ((uint32_t)0x00000002U) /*!< Remote frame */
复制代码- // CAN中断使能
- __HAL_CAN_ENABLE_IT(__HANDLE__, __INTERRUPT__)
复制代码- // 接收中断
- #define CAN_IT_FMP0 ((uint32_t)CAN_IER_FMPIE0) /*!< FIFO 0 message pending interrupt */
- #define CAN_IT_FF0 ((uint32_t)CAN_IER_FFIE0) /*!< FIFO 0 full interrupt */
- #define CAN_IT_FOV0 ((uint32_t)CAN_IER_FOVIE0) /*!< FIFO 0 overrun interrupt */
- #define CAN_IT_FMP1 ((uint32_t)CAN_IER_FMPIE1) /*!< FIFO 1 message pending interrupt */
- #define CAN_IT_FF1 ((uint32_t)CAN_IER_FFIE1) /*!< FIFO 1 full interrupt */
- #define CAN_IT_FOV1 ((uint32_t)CAN_IER_FOVIE1) /*!< FIFO 1 overrun interrupt */
复制代码
四、接口设计
与串口类似,使用中断接收。先封装单路CAN需要的几个小接口,再顶一个列表,最后使用统一的接口扫描这个列表。
- typedef enum
- {
- CAN_CHANNEL_NONE,
- CAN_CHANNEL_1,
- CAN_CHANNEL_2,
- CAN_CHANNEL_NUM
- } can_channel_t;
- #define CAN1_CHANNEL CAN1
- #define CAN1_PREEMPT_PRIO CAN1_RX_PRIORITY
- #define CAN1_RX_IRQ CAN1_RX0_IRQn
- #define CAN1_RX_IRQ_FUNC CAN1_RX0_IRQHandler
- #define CAN1_CLK_ENABLE() __HAL_RCC_CAN1_CLK_ENABLE()
- #define CAN1_TX_PORT GPIOA
- #define CAN1_TX_PIN GPIO_PIN_12
- #define CAN1_TX_AF GPIO_AF9_CAN1
- #define CAN1_TX_CONFIG() GPIOConfigExt(CAN1_TX_PORT, CAN1_TX_PIN, GPIO_MODE_AF_PP, GPIO_PULLUP, CAN1_TX_AF)
- #define CAN1_RX_PORT GPIOA
- #define CAN1_RX_PIN GPIO_PIN_11
- #define CAN1_RX_AF GPIO_AF9_CAN1
- #define CAN1_RX_CONFIG() GPIOConfigExt(CAN1_RX_PORT, CAN1_RX_PIN, GPIO_MODE_AF_PP, GPIO_PULLUP, CAN1_RX_AF)
- #define CAN2_CHANNEL CAN2
- #define CAN2_PREEMPT_PRIO CAN2_RX_PRIORITY
- #define CAN2_RX_IRQ CAN2_RX0_IRQn
- #define CAN2_RX_IRQ_FUNC CAN2_RX0_IRQHandler
- #define CAN2_CLK_ENABLE() __HAL_RCC_CAN2_CLK_ENABLE()
- #define CAN2_TX_PORT GPIOB
- #define CAN2_TX_PIN GPIO_PIN_6
- #define CAN2_TX_AF GPIO_AF9_CAN2
- #define CAN2_TX_CONFIG() GPIOConfigExt(CAN2_TX_PORT, CAN2_TX_PIN, GPIO_MODE_AF_PP, GPIO_PULLUP, CAN2_TX_AF)
- #define CAN2_RX_PORT GPIOB
- #define CAN2_RX_PIN GPIO_PIN_5
- #define CAN2_RX_AF GPIO_AF9_CAN2
- #define CAN2_RX_CONFIG() GPIOConfigExt(CAN2_RX_PORT, CAN2_RX_PIN, GPIO_MODE_AF_PP, GPIO_PULLUP, CAN2_RX_AF)
复制代码- <span style="background-color: rgb(255, 255, 255);">/</span>/ 抽象出一个CAN设备结构体
- typedef struct
- {
- CAN_HandleTypeDef handle; // CAN操作句柄
- CanTxMsgTypeDef tx; // CAN发送
- CanRxMsgTypeDef rx; // CAN接收
- can_queue_t recv; // 接收队列
- } can_dev_t;
复制代码- // 将每路CAN封装成几个小函数
- static can_dev_t can1_dev, can2_dev;
- static void can1_var_init(void)
- {
- can1_dev.recv = can1_queue_recv;
- CanQueueInit(&can1_dev.recv);
- }
- static void can1_gpio_init(void)
- {
- CAN1_TX_CONFIG();
- CAN1_RX_CONFIG();
- }
- // 波特率 = Fpclk1 / ((ts1+ts2+3) * brp) Fpclk1 = 54M
- static void can1_mode_init(void)
- {
- CAN1_CLK_ENABLE();
- can1_dev.handle.Instance = CAN1_CHANNEL;
- can1_dev.handle.pTxMsg = &can1_dev.tx;
- can1_dev.handle.pRxMsg = &can1_dev.rx;
- can1_dev.handle.Init.Prescaler = 6;
- can1_dev.handle.Init.Mode = CAN_MODE_NORMAL;
- can1_dev.handle.Init.SJW = CAN_SJW_1TQ; // 重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ
- can1_dev.handle.Init.BS1 = CAN_BS1_11TQ;// tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ
- can1_dev.handle.Init.BS2 = CAN_BS2_6TQ; // tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ
- can1_dev.handle.Init.TTCM = DISABLE; // 非时间触发通信模式
- can1_dev.handle.Init.ABOM = ENABLE; // 软件自动离线管理
- can1_dev.handle.Init.AWUM = DISABLE; // 睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
- can1_dev.handle.Init.NART = ENABLE; // 禁止报文自动传送
- can1_dev.handle.Init.RFLM = DISABLE; // 报文不锁定,新的覆盖旧的
- can1_dev.handle.Init.TXFP = DISABLE; // 优先级由报文标识符决定
- HAL_CAN_Init(&can1_dev.handle);
- }
- static void can1_filter_init(void)
- {
- CAN_FilterConfTypeDef filter;
- filter.FilterNumber = 0; // 过滤器0
- filter.FilterMode = CAN_FILTERMODE_IDMASK;
- filter.FilterScale = CAN_FILTERSCALE_32BIT;
- filter.FilterIdHigh = 0;
- filter.FilterIdLow = 0;
- filter.FilterMaskIdHigh = 0;
- filter.FilterMaskIdLow = 0;
- filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 过滤器0关联到FIFO0
- filter.FilterActivation = ENABLE; //激活滤波器0
- //filter.BankNumber=14;
- HAL_CAN_ConfigFilter(&can1_dev.handle, &filter);
- }
- static void can1_nvic_init(void)
- {
- __HAL_CAN_ENABLE_IT(&can1_dev.handle, CAN_IT_FMP0); //FIFO0消息挂号中断允许.
- HAL_NVIC_SetPriority(CAN1_RX_IRQ, CAN1_RX_PRIORITY, 0);
- HAL_NVIC_EnableIRQ(CAN1_RX_IRQ);
- }
- static void can2_var_init(void)
- {
- can2_dev.recv = can2_queue_recv;
- CanQueueInit(&can2_dev.recv);
- }
- static void can2_gpio_init(void)
- {
- CAN2_TX_CONFIG();
- CAN2_RX_CONFIG();
- }
- // 波特率 = Fpclk1 / ((ts1+ts2+3) * brp) Fpclk1 = 54M
- static void can2_mode_init(void)
- {
- CAN2_CLK_ENABLE();
- can2_dev.handle.Instance = CAN2_CHANNEL;
- can2_dev.handle.Init.Prescaler = 6;
- can2_dev.handle.Init.Mode = CAN_MODE_LOOPBACK;
- can2_dev.handle.Init.SJW = CAN_SJW_1TQ; // 重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ
- can2_dev.handle.Init.BS1 = CAN_BS1_11TQ;// tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ
- can2_dev.handle.Init.BS2 = CAN_BS2_6TQ; // tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ
- can2_dev.handle.Init.TTCM = DISABLE; // 非时间触发通信模式
- can2_dev.handle.Init.ABOM = ENABLE; // 软件自动离线管理
- can2_dev.handle.Init.AWUM = DISABLE; // 睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
- can2_dev.handle.Init.NART = ENABLE; // 禁止报文自动传送
- can2_dev.handle.Init.RFLM = DISABLE; // 报文不锁定,新的覆盖旧的
- can2_dev.handle.Init.TXFP = DISABLE; // 优先级由报文标识符决定
- HAL_CAN_Init(&can2_dev.handle);
- }
- static void can2_filter_init(void)
- {
- CAN_FilterConfTypeDef CAN_FilterInitStructure;
- CAN_FilterInitStructure.FilterNumber = 14;
- CAN_FilterInitStructure.FilterMode = CAN_FILTERMODE_IDMASK;
- CAN_FilterInitStructure.FilterScale = CAN_FILTERSCALE_32BIT;
- CAN_FilterInitStructure.FilterIdHigh = 0;
- CAN_FilterInitStructure.FilterIdLow = 0;
- CAN_FilterInitStructure.FilterMaskIdHigh = 0;
- CAN_FilterInitStructure.FilterMaskIdLow = 0;
- CAN_FilterInitStructure.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 过滤器0关联到FIFO0
- CAN_FilterInitStructure.FilterActivation = ENABLE; //激活滤波器0
- //CAN_FilterInitStructure.BankNumber=14;
- HAL_CAN_ConfigFilter(&can2_dev.handle, &CAN_FilterInitStructure);
- }
- static void can2_nvic_init(void)
- {
- __HAL_CAN_ENABLE_IT(&can2_dev.handle, CAN_IT_FMP0); //FIFO0消息挂号中断允许1
- HAL_NVIC_SetPriority(CAN2_RX_IRQ, CAN2_RX_PRIORITY, 0);
- HAL_NVIC_EnableIRQ(CAN2_RX_IRQ);
- }
复制代码- // 每路CAN都有的几个操作,使用列表包含,然后统一扫描处理
- typedef struct
- {
- uint8_t channel;
- can_dev_t *dev;
- void (* var_init_cb)(void);
- void (* gpio_init_cb)(void);
- void (* mode_init_cb)(void);
- void (* filter_init_cb)(void);
- void (* nvic_init_cb)(void);
- } can_config_t;
- static const can_config_t can_configs[] =
- {
- {CAN_CHANNEL_1, &can1_dev, can1_var_init, can1_gpio_init, can1_mode_init, can1_filter_init, can1_nvic_init},
- {CAN_CHANNEL_2, &can2_dev, can2_var_init, can2_gpio_init, can2_mode_init, can2_filter_init, can2_nvic_init},
- };
- static can_dev_t *can_dev_get(uint8_t channel)
- {
- uint8_t i;
- for(i = 0; i < ARRAY_SIZE(can_configs); ++i)
- {
- if(channel == can_configs<i>.channel)
- {
- return can_configs<i>.dev;
- }
- }
- return 0;
- }
- static void can_var_init(uint8_t channel)
- {
- uint8_t i;
- for(i = 0; i < ARRAY_SIZE(can_configs); ++i)
- {
- if(channel == can_configs<i>.channel)
- {
- can_configs<i>.var_init_cb();
- return;
- }
- }
- }
- static void can_gpio_init(uint8_t channel)
- {
- uint8_t i;
- for(i = 0; i < ARRAY_SIZE(can_configs); ++i)
- {
- if(channel == can_configs<i>.channel)
- {
- can_configs<i>.gpio_init_cb();
- return;
- }
- }
- }
- static void can_mode_init(uint8_t channel)
- {
- uint8_t i;
- for(i = 0; i < ARRAY_SIZE(can_configs); ++i)
- {
- if(channel == can_configs<i>.channel)
- {
- can_configs<i>.mode_init_cb();
- return;
- }
- }
- }
- static void can_filter_init(uint8_t channel)
- {
- uint8_t i;
- for(i = 0; i < ARRAY_SIZE(can_configs); ++i)
- {
- if(channel == can_configs<i>.channel)
- {
- can_configs<i>.filter_init_cb();
- return;
- }
- }
- }
- static void can_nvic_init(uint8_t channel)
- {
- uint8_t i;
- for(i = 0; i < ARRAY_SIZE(can_configs); ++i)
- {
- if(channel == can_configs<i>.channel)
- {
- can_configs<i>.nvic_init_cb();
- return;
- }
- }
- }</i></i></i></i></i></i></i></i></i></i></i></i>
复制代码- // 对外的接口 初始化 发送和接收
- void CanInit(uint8_t channel)
- {
- can_var_init(channel);
- can_gpio_init(channel);
- can_mode_init(channel);
- can_filter_init(channel);
- can_nvic_init(channel);
- }
- void CanSend(uint8_t channel, can_frame_t *frame)
- {
- can_dev_t *dev = can_dev_get(channel);
- if(dev == 0)
- {
- return;
- }
- dev->handle.pTxMsg->StdId = frame->StdId;
- dev->handle.pTxMsg->IDE = frame->IDE;
- dev->handle.pTxMsg->RTR = frame->RTR;
- dev->handle.pTxMsg->DLC = frame->DLC;
- memcpy(dev->handle.pTxMsg->Data, frame->Data, frame->DLC);
- HAL_CAN_Transmit(&dev->handle, 10);
- }
- uint8_t CanRecv(uint8_t channel, can_frame_t *frame)
- {
- can_dev_t *dev = can_dev_get(channel);
- if(dev == 0)
- {
- return 0;
- }
- return CanQueueRead(&can1_dev.recv, frame);
- }
复制代码- // CAN中断
- void CAN1_RX_IRQ_FUNC(void)
- {
- HAL_CAN_IRQHandler(&can1_dev.handle);
- }
- void CAN2_RX_IRQ_FUNC(void)
- {
- HAL_CAN_IRQHandler(&can2_dev.handle);
- }
- void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef *hcan)
- {
- if(hcan == (&can1_dev.handle))
- {
- //CAN_Receive_IT()函数会关闭FIFO0消息挂号中断,因此我们需要重新打开
- __HAL_CAN_ENABLE_IT(&can1_dev.handle, CAN_IT_FMP0);//重新开启FIF00消息挂号中断
- CanQueueWrite(&can1_dev.recv, can1_dev.handle.pRxMsg);
- }
- else if(hcan == (&can2_dev.handle))
- {
- //CAN_Receive_IT()函数会关闭FIFO0消息挂号中断,因此我们需要重新打开
- __HAL_CAN_ENABLE_IT(&can2_dev.handle, CAN_IT_FMP0);//重新开启FIF00消息挂号中断
- CanQueueWrite(&can2_dev.recv, can2_dev.handle.pRxMsg);
- }
- }
复制代码
之所以使用列表的形式,是为了方便增加和删除CAN通道和使对外的接口更统一,不会出现CAN1Init() CAN2Init(),个人习惯问题。
|