文章目录
了解CAN通讯协议以及CAN 协议及标准规格 。本实验是基于STM32F103开发的CAN通信,来一起研究下STM32数据手册 中CAN的特色。
准备好了吗?开始我的show time。
主控:STM32F103ZET6
CAN收发器:TJA1040T

软件开发使用虚拟机 + VScode + STM32Cube 开发STM32,在虚拟机中直接完成编译下载。
该部分可参考:软件开发环境构建
本实验基于CubeMX详解构建基本框架 进行开发。
(1)时钟配置
由于CAN在APB1时钟线上,APB1时钟配置36M

(2)配置CAN参数

假j把CAN的时钟配置为500KHz
基本模式配置
根据自己需要进行配置,这里实验都不需要,直接disable关掉

自动重发数据:若使能,数据出错了可以重新发送数据
接收FIFO锁定模式:若使能,FIFO数据不可以重叠,更替
发送FIFO优先级:若关闭,就按照邮箱的优先级来发送数据;若使能,就按照自己设定的优先级发送。
CAN工作模式配置

模式选择:正常模式、静默模式、环回模式、环回静默模式。
实验选用正常模式、环回模式测试CAN通信。
中断模式配置
在NVIC Settings选项卡中将CAN接收中断使能打开

设置中断优先级

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相互通信背景:用另一块STM32开发板上的CAN通信与本实验中的板子CAN(打印信息有DWB)通信。
实验板子CAN发送(左图),STM32开发板CAN接收(右图)。两个板子CANH对应相连;CANL对应相连。

实验板子CAN接收(左图),STM32开发板CAN发送(右图)。两个板子CANH对应相连;CANL对应相连。

整体波形:

开始帧(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位)

结束帧

我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此
我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
CSV.open(name,"r").eachdo|row|putsrowend我得到以下错误:CSV::MalformedCSVErrorUnquotedfieldsdonotallow\ror\n文件名是一个.txt制表符分隔文件。我是专门做的。我有一个.csv文件,我转到excel,并将文件保存为.txt制表符分隔的文件。所以它是制表符分隔的。CSV.open不应该能够读取制表符分隔的文件吗? 最佳答案 尝试像这样指定字段分隔符:CSV.open("name","r",{:col_sep=>"\t"}).eachdo|row|
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt