👈 🏡 👉
文章目录
一、基础知识点
了解can通讯协议以及can 协议及标准规格 。本实验是基于stm32f103开发的can通信,来一起研究下stm32数据手册 中can的特色。
准备好了吗?开始我的show time。
二、开发环境
1、硬件开发准备
主控:stm32f103zet6
can收发器:tja1040t
2、软件开发准备
软件开发使用虚拟机 + vscode + stm32cube 开发stm32,在虚拟机中直接完成编译下载。
该部分可参考:
三、stm32cubemx相关配置
1、stm32cubemx基本配置
本实验基于cubemx详解构建基本框架 进行开发。
2、stm32cubemx can相关配置
(1)时钟配置
由于can在apb1时钟线上,apb1时钟配置36m
(2)配置can参数
- 开启主can配置
- 位时序配置
位时序顾名思义就是传输一个位的时序(如0或1)。位时序结构:同步段(sync_seg)、时间段1(bs1)、时间段2(bs2)
假j把can的时钟配置为500khz
(1)将系统时间36m进行4分频,则36m/4 = 9m
(2)位时序中同步段(sync_seg)固定1tq;stm32时间段1(bs1)包含两部分:传播时间段和相位缓冲时间段1,可以分配11tq;时间段2(bs2)包含相位缓冲时间段2,可以分配6tq。
这样1位由18个 tq 构成,则9m/18 = 500k
按照以上的配置可以实现can 500k通信。
-
基本模式配置
根据自己需要进行配置,这里实验都不需要,直接disable关掉
自动重发数据:若使能,数据出错了可以重新发送数据
接收fifo锁定模式:若使能,fifo数据不可以重叠,更替
发送fifo优先级:若关闭,就按照邮箱的优先级来发送数据;若使能,就按照自己设定的优先级发送。 -
can工作模式配置
模式选择:正常模式、静默模式、环回模式、环回静默模式。
实验选用正常模式、环回模式测试can通信。 -
中断模式配置
在nvic settings选项卡中将can接收中断使能打开
设置中断优先级
四、vscode代码讲解
1、构建一个can相关结构体
//定义结构体类型
typedef struct
{
uint32_t can_work_mode; // can 工作模式
uint8_t tx_buff[8]; // 发送缓存
uint8_t rx_buff[8]; // 接收缓存
void (*mycan_init)(void); // can 初始化
uint8_t (*mycan_send_message)(uint8_t *p_tx_buff, uint32_t *pmycan_mailbox_num); // 发送信息
void (*mycan_recevie_message)(uint8_t *p_rx_buff); // 接收信息
uint8_t rx_status_flag; // 接收标志位
} mycan_t;
2、定义can结构体
mycan_t mycan ={
can_mode_normal, // 正常接收发送模式
{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77},
{0},
mycan_init,
mycan_send_message,
mycan_recevie_message,
false // 默认没有接收到信息
};
3、初始化can
(1)can过滤器配置
filterbank:要配置的过滤器0(芯片一共14个,0-13)
filtermode:选用标识符屏蔽模式(可以接收一组id),若选择列表模式,只能接收一个特定的id
filterfifoassignment:将配置的过滤器0关联到fifo0
filteractivation:激活过滤器,若不激活接收不到任何数据
(2)使能接收挂起中断
在stm32cubemx里面是时钟了接收的总中断,这里使能的是总中断下的挂起中断
can接收中断包括:挂起中断(只要有信息就触发中断)、满中断(fifo都满了触发中断)、溢出中断(只有fifo都满后还接收到数据就会触发中断)
(3)启动can
void mycan_init(void)
{
can_filtertypedef mycan_filter;
// 配置过滤器
mycan_filter.filteridhigh = 0x34; // 过滤器需要过滤高id
mycan_filter.filteridlow = 0x00; // 过滤器需要过滤低id
mycan_filter.filtermaskidhigh = 0x00; // 过滤器掩码 '0'位不限制
mycan_filter.filtermaskidlow = 0x00; // 过滤器掩码 '0'位不限制
mycan_filter.filterfifoassignment = can_filter_fifo0; // 挂在过滤器fifo0
mycan_filter.filterbank = 0; // 过滤器0
mycan_filter.filtermode = can_filtermode_idmask; // id掩码模式
mycan_filter.filterscale = can_filterscale_16bit; // 16位过滤器
mycan_filter.filteractivation = can_filter_enable; // 激活过滤器
mycan_filter.slavestartfilterbank = 14;
// 配置过滤器
if (hal_can_configfilter(&hcan, &mycan_filter) != hal_ok)
{
printf("dwb --- can配置过滤器失败\n");
system.error_handler();
}
// 使能fifo接收到一个新报文中断
if(hal_can_activatenotification(&hcan, can_it_rx_fifo0_msg_pending) != hal_ok)
{
printf("dwb --- can使能接收挂起中断失败\n");
system.error_handler();
}
if(hal_can_start(&hcan) != hal_ok)
{
printf("dwb --- can开启失败\n");
system.error_handler();
}
printf("dwb --- can配置并开启成功!\n");
}
4、can发送
(1)can发送数据时序配置
定义发送时序参数,通过hal_can_addtxmessage函数发送数据到邮箱
(2)等待发送数据成功
延时1s时间,1s内反复通过hal_can_gettxmailboxesfreelevel函数检查空邮箱的个数。如果空邮箱个数等于3,则说明数据已经发送成功。
uint8_t mycan_send_message(uint8_t *p_tx_buff, uint32_t *pmycan_mailbox_num)
{
can_txheadertypedef mycan_txheader;
// 配置发送头
mycan_txheader.stdid = 0x34; // 发送设备标准id
mycan_txheader.extid = 0x00; // 扩展id
mycan_txheader.ide = can_id_std; // can标准id模式
mycan_txheader.rtr = can_rtr_data; // 数据帧
mycan_txheader.dlc = 8; // 传输长度8
mycan_txheader.transmitglobaltime = disable; // 时间戳 不使能
// 发送数据到邮箱并判断状态
if(hal_can_addtxmessage(&hcan, &mycan_txheader, p_tx_buff, pmycan_mailbox_num) != hal_ok)
{
printf("dwb --- 发送数据到邮箱失败\n");
return send_date_fail;
}
uint8_t rtc_seconds_t = myrtc.pmyrtc_current_time->seconds+1;
do
{
if(rtc_seconds_t == myrtc.pmyrtc_current_time->seconds)
{
printf("dwb --- 数据未发出 \n");
return send_date_fail;
}
} while (hal_can_gettxmailboxesfreelevel(&hcan) != 3);
printf("dwb --- 数据发送成功 \n\r");
return send_date_success;
}
5、主函数中调用发送接收函数
(1)调用结构体can发送函数成员进行数据发送
(2)通过rx_status_flag标识符判断是否接收到数据,后调用mycan_recevie_message接收
res = mycan.mycan_send_message(mycan.tx_buff, &mailbox_num);
printf("dwb --- mailbox_num = %ld\n\r", mailbox_num);
if(!res && true == mycan.rx_status_flag){
mycan.mycan_recevie_message(mycan.rx_buff);
mycan.rx_status_flag = false;
}
6、can接收中断函数
在初始化中can使能接收挂起中断。当有接收到数据就会调用中断函数
__weak void hal_can_rxfifo0msgpendingcallback(can_handletypedef *hcan)
这个函数是弱函数,直接重构就好了。
void hal_can_rxfifo0msgpendingcallback(can_handletypedef *hcan_t)
{
can_rxheadertypedef pmycan_tx_head;
// hal_statustypedef hal_can_getrxmessage(can_handletypedef *hcan, uint32_t rxfifo, can_rxheadertypedef *pheader, uint8_t adata[]);
if (hal_can_getrxmessage(&hcan, can_rx_fifo0, &pmycan_tx_head, mycan.rx_buff) == hal_ok)
mycan.rx_status_flag = true;
}
调用hal_can_getrxmessage函数接收数据,这里数据从can_rx_fifo0中读取。
为什么是fifo0呢?因为在初始化过滤器的时候将其关联到fifo0上。
解析接收的过程
中断初始化中,使能usb_lp_can1_rx0_irqn can接收中断
static void mx_nvic_init(void)
{
/* rtc_alarm_irqn interrupt configuration */
hal_nvic_setpriority(rtc_alarm_irqn, 1, 0);
hal_nvic_enableirq(rtc_alarm_irqn);
/* usb_lp_can1_rx0_irqn interrupt configuration */
hal_nvic_setpriority(usb_lp_can1_rx0_irqn, 2, 0);
hal_nvic_enableirq(usb_lp_can1_rx0_irqn); // can接收总中断使能
}
can初始化中使能接收挂起中断
// 使能fifo接收到一个新报文中断
if(hal_can_activatenotification(&hcan, can_it_rx_fifo0_msg_pending) != hal_ok)
{
printf("dwb --- can使能接收挂起中断失败\n");
system.error_handler();
}
can接收数据时,
(1)触发usb_lp_can1_rx0_irqhandler回调函数
void usb_lp_can1_rx0_irqhandler(void)
{
/* user code begin usb_lp_can1_rx0_irqn 0 */
/* user code end usb_lp_can1_rx0_irqn 0 */
hal_can_irqhandler(&hcan);
/* user code begin usb_lp_can1_rx0_irqn 1 */
/* user code end usb_lp_can1_rx0_irqn 1 */
}
(2)在hal_can_irqhandler函数中判断中断标志位为can_it_rx_fifo0_msg_pending(挂起中断,在初始化中使能挂起中断)
use_hal_can_register_callbacks宏定义为0,则调用hal_can_rxfifo0msgpendingcallback回调函数(这个函数是弱化函数,重构该函数之后就会调用重构函数)
void hal_can_irqhandler(can_handletypedef *hcan)
{
......
/* receive fifo 0 message pending interrupt management *********************/
if ((interrupts & can_it_rx_fifo0_msg_pending) != 0u)
{
/* check if message is still pending */
if ((hcan->instance->rf0r & can_rf0r_fmp0) != 0u)
{
/* receive fifo 0 message pending callback */
#if use_hal_can_register_callbacks == 1
/* call registered callback*/
hcan->rxfifo0msgpendingcallback(hcan);
#else
/* call weak (surcharged) callback */
hal_can_rxfifo0msgpendingcallback(hcan);
#endif /* use_hal_can_register_callbacks */
}
}
......
}
五、结果演示
can 内部回环测试
代码设置回环测试,can自发自收。
串口打印发送成功后接收到的数据内容以及发送邮箱号。
can 正常模式测试
代码模式配置为正常模式
两块板子can相互通信背景:用另一块stm32开发板上的can通信与本实验中的板子can(打印信息有dwb)通信。
实验板子can发送(左图),stm32开发板can接收(右图)。两个板子canh对应相连;canl对应相连。
实验板子can接收(左图),stm32开发板can发送(右图)。两个板子canh对应相连;canl对应相连。
使用adalm2000分析工具解析can时序
整体波形:
开始帧(1位)
右下角,传输1位的时间为1.998μs,和软件里配置的时间1999.99ns时间一致(500000hz)
设备id位(标准帧id 11位)
解析出来的配置为0x34与软件配置一致(00000110100)
注:由于位补充(在发送数据帧和遥控帧时, sof~crc 段间的数据,相同电平如果持续 5 位,在下一个位(第 6 个位)则要插入 1 位与前 5 位反型的电平)的原因,中间有插入一个补充位1(绿色1)
rtr(1位数据帧)、ide(1位标准id模式)、rb0(保留位)、数据长度码(8位)
由于连续5位0,则中间添加补充位1
数据(8个字节)
crc(校验位15位)、crc d(crc 界定符(用于分隔的位)1位)、ack(用来确认是否正常接收2位)
结束帧
发表评论