CAN总线协议编程实例
1. can.h
#ifndef __CAN_H
#define __CAN_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* CAN 引脚 定义 */
#define CAN_RX_GPIO_PORT GPIOA
#define CAN_RX_GPIO_PIN GPIO_PIN_11
#define CAN_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define CAN_TX_GPIO_PORT GPIOA
#define CAN_TX_GPIO_PIN GPIO_PIN_12
#define CAN_TX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
/******************************************************************************************/
/* CAN接收RX0中断使能 */
#define CAN_RX0_INT_ENABLE 0 /* 0,不使能; 1,使能; */
/* 函数声明 */
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 can_init(uint32_t tsjw,uint32_t tbs2,uint32_t tbs1,uint16_t brp,uint32_t mode); /* CAN初始化 */
#endif
2. can.c
#include "./BSP/CAN/can.h"
#include "./BSP/LED/led.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
CAN_HandleTypeDef g_canx_handler; /* CANx句柄 */
CAN_TxHeaderTypeDef g_canx_txheader; /* 发送参数句柄 */
CAN_RxHeaderTypeDef g_canx_rxheader; /* 接收参数句柄 */
/**
* @brief CAN初始化
* @param tsjw : 重新同步跳跃时间单元.范围: 1~3;
* @param tbs2 : 时间段2的时间单元.范围: 1~8;
* @param tbs1 : 时间段1的时间单元.范围: 1~16;
* @param brp : 波特率分频器.范围: 1~1024;
* @note 以上4个参数, 在函数内部会减1, 所以, 任何一个参数都不能等于0
* CAN挂在APB1上面, 其输入时钟频率为 Fpclk1 = PCLK1 = 36Mhz
* tq = brp * tpclk1;
* 波特率 = Fpclk1 / ((tbs1 + tbs2 + 1) * brp);
* 我们设置 can_init(1, 8, 9, 4, 1), 则CAN波特率为:
* 36M / ((8 + 9 + 1) * 4) = 500Kbps
*
* @param mode : CAN_MODE_NORMAL, 普通模式;
CAN_MODE_LOOPBACK,回环模式;
* @retval 0, 初始化成功; 其他, 初始化失败;
*/
uint8_t can_init(uint32_t tsjw, uint32_t tbs2, uint32_t tbs1, uint16_t brp, uint32_t mode)
{
g_canx_handler.Instance = CAN1;
g_canx_handler.Init.Prescaler = brp; /* 分频系数*/
g_canx_handler.Init.Mode = mode; /* 模式设置 */
g_canx_handler.Init.SyncJumpWidth = tsjw; /* 重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ */
g_canx_handler.Init.TimeSeg1 = tbs1; /* tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ */
g_canx_handler.Init.TimeSeg2 = tbs2; /* tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ */
g_canx_handler.Init.TimeTriggeredMode = DISABLE; /* 非时间触发通信模式 */
g_canx_handler.Init.AutoBusOff = DISABLE; /* 软件自动离线管理 */
g_canx_handler.Init.AutoWakeUp = DISABLE; /* 睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位) */
g_canx_handler.Init.AutoRetransmission = ENABLE; /* 禁止报文自动传送 */
g_canx_handler.Init.ReceiveFifoLocked = DISABLE; /* 报文不锁定,新的覆盖旧的 */
g_canx_handler.Init.TransmitFifoPriority = DISABLE; /* 优先级由报文标识符决定 */
if (HAL_CAN_Init(&g_canx_handler) != HAL_OK)
{
return 1;
}
#if CAN_RX0_INT_ENABLE
/* 使用中断接收 */
__HAL_CAN_ENABLE_IT(&g_canx_handler, CAN_IT_RX_FIFO0_MSG_PENDING); /* FIFO0消息挂号中断允许 */
HAL_NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn); /* 使能CAN中断 */
HAL_NVIC_SetPriority(USB_LP_CAN1_RX0_IRQn, 1, 0); /* 抢占优先级1,子优先级0 */
#endif
CAN_FilterTypeDef sFilterConfig;
/*配置CAN过滤器*/
sFilterConfig.FilterBank = 0; /* 过滤器0 */
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000; /* 32位ID */
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000; /* 32位MASK */
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; /* 过滤器0关联到FIFO0 */
sFilterConfig.FilterActivation = CAN_FILTER_ENABLE; /* 激活滤波器0 */
sFilterConfig.SlaveStartFilterBank = 14; //F103ZET6只有14组过滤器组
//初始化时设置为掩码模式且收到的数据全部接收,在后面的接收中通过软件筛选报文
/* 过滤器配置 */
if (HAL_CAN_ConfigFilter(&g_canx_handler, &sFilterConfig) != HAL_OK)
{
return 2;
}
/* 启动CAN外围设备 */
if (HAL_CAN_Start(&g_canx_handler) != HAL_OK)
{
return 3;
}
return 0;
}
/**
* @brief CAN底层驱动,引脚配置,时钟配置,中断配置
此函数会被HAL_CAN_Init()调用
* @param hcan:CAN句柄
* @retval 无
*/
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
{
if (CAN1 == hcan->Instance)
{
CAN_RX_GPIO_CLK_ENABLE(); /* CAN_RX脚时钟使能 */
CAN_TX_GPIO_CLK_ENABLE(); /* CAN_TX脚时钟使能 */
__HAL_RCC_CAN1_CLK_ENABLE(); /* 使能CAN1时钟 */
GPIO_InitTypeDef gpio_initure;
gpio_initure.Pin = CAN_TX_GPIO_PIN;
gpio_initure.Mode = GPIO_MODE_AF_PP;
gpio_initure.Pull = GPIO_PULLUP;
gpio_initure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(CAN_TX_GPIO_PORT, &gpio_initure); /* CAN_TX脚 模式设置 */
gpio_initure.Pin = CAN_RX_GPIO_PIN;
gpio_initure.Mode = GPIO_MODE_AF_INPUT;
HAL_GPIO_Init(CAN_RX_GPIO_PORT, &gpio_initure); /* CAN_RX脚 必须设置成输入模式 */
}
}
#if CAN_RX0_INT_ENABLE /* 使能RX0中断 */
/**
* @brief CAN RX0 中断服务函数
* @note 处理CAN FIFO0的接收中断
* @param 无
* @retval 无
*/
void USB_LP_CAN1_RX0_IRQHandler(void)
{
uint8_t rxbuf[8];
uint32_t id;
can_receive_msg(id, rxbuf);
printf("id:%d\r\n", g_canx_rxheader.StdId);
printf("ide:%d\r\n", g_canx_rxheader.IDE);
printf("rtr:%d\r\n", g_canx_rxheader.RTR);
printf("len:%d\r\n", g_canx_rxheader.DLC);
printf("rxbuf[0]:%d\r\n", rxbuf[0]);
printf("rxbuf[1]:%d\r\n", rxbuf[1]);
printf("rxbuf[2]:%d\r\n", rxbuf[2]);
printf("rxbuf[3]:%d\r\n", rxbuf[3]);
printf("rxbuf[4]:%d\r\n", rxbuf[4]);
printf("rxbuf[5]:%d\r\n", rxbuf[5]);
printf("rxbuf[6]:%d\r\n", rxbuf[6]);
printf("rxbuf[7]:%d\r\n", rxbuf[7]);
}
#endif
/**
* @brief CAN 发送一组数据
* @note 发送格式固定为: 标准ID, 数据帧
* @param id : 标准ID(11位)
* @retval 发送状态 0, 成功; 1, 失败;
*/
uint8_t can_send_msg(uint32_t id, uint8_t *msg, uint8_t len)
{
uint32_t TxMailbox = CAN_TX_MAILBOX0;
g_canx_txheader.StdId = id; /* 标准标识符 */
g_canx_txheader.ExtId = id; /* 扩展标识符(29位) */
g_canx_txheader.IDE = CAN_ID_STD; /* 使用标准帧 */
g_canx_txheader.RTR = CAN_RTR_DATA; /* 数据帧 */
g_canx_txheader.DLC = len;
if (HAL_CAN_AddTxMessage(&g_canx_handler, &g_canx_txheader, msg, &TxMailbox) != HAL_OK) /* 发送消息 */
{
return 1;
}
while (HAL_CAN_GetTxMailboxesFreeLevel(&g_canx_handler) != 3); /* 等待发送完成,所有邮箱为空 */
return 0;
}
/**
* @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(&g_canx_handler, CAN_RX_FIFO0) == 0) /* 没有接收到数据 */
{
return 0;
}
if (HAL_CAN_GetRxMessage(&g_canx_handler, 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;//返回的是接收数据个数
}
//我们对于过滤器的配置是不过滤任何报文 ID,也就是说可以接收全部
//报文。但是我们可以编写接收函数时,使用软件的方式过滤报文 ID,通过形参来跟接收到的报
//文 ID 进行匹配。
2.1 注意点
关于can_init函数参数的设置,
* @param tsjw : 重新同步跳跃时间单元.范围: 1~3;
* @param tbs2 : 时间段2的时间单元.范围: 1~8;
* @param tbs1 : 时间段1的时间单元.范围: 1~16;
* @param brp : 波特率分频器.范围: 1~1024;
* @note 以上4个参数, 在函数内部会减1, 所以, 任何一个参数都不能等于0, 参数减1后的值是TS2[2:0]/TS1[3:0]/BRP[3:0]寄存器的值,由下图该寄存器参与波特率计算时还要+1,故init函数的参数直接填你要设置的TS2/TS1/BPR实际长度即可(如下面的计算)。
* CAN挂在APB1上面, 其输入时钟频率为 Fpclk1 = PCLK1 = 36Mhz
* tq = brp * tpclk1;
* 波特率 = Fpclk1 / ((tbs1 + tbs2 + 1) * brp);
* 我们设置 can_init(1, 8, 9, 4, 1), 则CAN波特率为:
* 36M / ((8 + 9 + 1) * 4) = 500Kbps
3. main.c
//本程序是用CAN总线协议发送一组数据到另一个开发板或自己接收自己发送的数据
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/CAN/can.h"
int main(void)
{
uint8_t key;
uint8_t i = 0, t = 0;
uint8_t cnt = 0;
uint8_t canbuf[8];
uint8_t rxlen = 0;
uint8_t res;
uint8_t mode = 1; /* CAN工作模式: 0,普通模式; 1,环回模式 */
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
usmart_dev.init(72); /* 初始化USMART */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
key_init(); /* 初始化按键 */
can_init(CAN_SJW_1TQ, CAN_BS2_8TQ, CAN_BS1_9TQ, 4, CAN_MODE_LOOPBACK); /* CAN初始化, 环回模式, 波特率500Kbps */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "CAN TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "LoopBack Mode", RED);
lcd_show_string(30, 130, 200, 16, 16, "KEY0:Send KEK_UP:Mode", RED); /* 显示提示信息 */
lcd_show_string(30, 150, 200, 16, 16, "Count:", RED); /* 显示当前计数值 */
lcd_show_string(30, 170, 200, 16, 16, "Send Data:", RED); /* 提示发送的数据 */
lcd_show_string(30, 230, 200, 16, 16, "Receive Data:", RED); /* 提示接收到的数据 */
while (1)
{
key = key_scan(0);
if (key == KEY0_PRES) /* KEY0按下,发送一次数据 */
{
for (i = 0; i < 8; i++)
{
canbuf[i] = cnt + i; /* 填充发送缓冲区 */
if (i < 4)
{
lcd_show_xnum(30 + i * 32, 190, canbuf[i], 3, 16, 0X80, BLUE); /* 显示数据 */
}
else
{
lcd_show_xnum(30 + (i - 4) * 32, 210, canbuf[i], 3, 16, 0X80, BLUE); /* 显示数据 */
}
}
res = can_send_msg(0X12, canbuf, 8); /* ID = 0X12, 发送8个字节 */
if (res)
{
lcd_show_string(30 + 80, 170, 200, 16, 16, "Failed", BLUE); /* 提示发送失败 */
}
else
{
lcd_show_string(30 + 80, 170, 200, 16, 16, "OK ", BLUE); /* 提示发送成功 */
}
}
else if (key == WKUP_PRES) /* WK_UP按下,改变CAN的工作模式 */
{
mode = !mode;
if (mode == 0) /* 普通模式,需要2个开发板 */
{
can_init(CAN_SJW_1TQ, CAN_BS2_8TQ, CAN_BS1_9TQ, 4, CAN_MODE_NORMAL); /* CAN普通模式初始化, 普通模式, 波特率500Kbps */
lcd_show_string(30, 110, 200, 16, 16, "Nnormal Mode ", RED);
}
else /* 回环模式,一个开发板就可以测试了. */
{
can_init(CAN_SJW_1TQ, CAN_BS2_8TQ, CAN_BS1_9TQ, 4, CAN_MODE_LOOPBACK); /* CAN普通模式初始化, 回环模式, 波特率500Kbps */
lcd_show_string(30, 110, 200, 16, 16, "LoopBack Mode", RED);
}
}
rxlen = can_receive_msg(0X12, canbuf); /* CAN ID = 0X12, 接收数据查询 */
if (rxlen) /* 接收到有数据 */
{
lcd_fill(30, 270, 130, 310, WHITE); /* 清除之前的显示 */
for (i = 0; i < rxlen; i++)
{
if (i < 4)
{
lcd_show_xnum(30 + i * 32, 250, canbuf[i], 3, 16, 0X80, BLUE); /* 显示数据 */
}
else
{
lcd_show_xnum(30 + (i - 4) * 32, 270, canbuf[i], 3, 16, 0X80, BLUE); /* 显示数据 */
}
}
}
t++;
delay_ms(10);
if (t == 20)
{
LED0_TOGGLE(); /* 提示系统正在运行 */
t = 0;
cnt++;
lcd_show_xnum(30 + 48, 150, cnt, 3, 16, 0X80, BLUE); /* 显示数据 */
}
}
}