S32K144是恩智浦半导体推出的一款高性能微控制器(MCU),主要针对汽车和高可靠性的工业应用。在汽车工业应用中使用CAN总线必不可少。
需要注意的是在ISO 11898(High Speed,速度在5kbps-1Mbps)中,隐形电平电压差在0附件。显性电平电压差在2V左右
在ISO 11519(Low Speed,速度在5kbsp-125kbps)中,隐形电平电压差小于0V,显性电平电压差大于0V。
CAN总线与RS485的比较:
1,速度与距离:CAN与RS485以1Mbit/S的高速率传输的距离都不超过100M,可谓高速上的距离差不多。但是在低速时CAN以5Kbit/S时,距离可达10KM,而485再低的速率也只能到1219米左右(都无中继)。可见CAN在长距离的传输上拥有绝对的优势。
2,总线利用率:RS485是单主从结构,就是一个总线上只能有一台主机,通讯都由它发起的,它没有下命令,下面的节点不能发送,而且要发完即答,受到答复后,主机才向下一个节点询问,这样是为了防止多个节点向总线发送数据,而造成数据错乱。而CAN-bus是多主从结构,每个节点都有CAN控制器,多个节点发送时,以发送的ID号自动进行仲裁,这样就可以实现总线数据不错乱,而且一个节点发完,另一个节点可以探测到总线空闲,而马上发送,这样省去了主机的询问,提高了总线利用率,增强了快速性。所以在汽车等实性要求高的系统,都是用CAN总线,或者其他类似的总线。
3,错误检测机制,RS485只规定了物理层,而没有数据链路层,所以它对错误是无法识别的,除非一些短路等物理错误。这样容易造成一个节点破坏了,拼命向总线发数据(一直发1),这样造成整个总线瘫痪。所以RS485一旦坏一个节点,这个总线网络都挂。而CAN总线有CAN控制器,可以对总线任何错误进行检测,如果自身错误超过128个,就自动闭锁。保护总线。如果检测到其他节点错误或者自身错误,都会向总线发送错误帧,来提示其他节点,这个数据是错误的。大家小心。这样CAN总线一旦有一个节点CPU程序跑飞了,它的控制器自动闭锁。保护总线。所以在安全性要求高的网路,CAN是很强的。
4,价格与培训成本:CAN器件的价格大约是485的2倍这样,485的通讯从软件上是很方便的,只要懂串行通讯,就可以编程,而CAN需要底层工程师了解CAN复杂的层,编写上位机软件也要了解CAN的协议。可谓培训成本较高。
5,CAN总线通过CAN控制器接口芯片82C250的两个输出端CANH和CANL与物理总线相连,而CANH端的状态只能是高电平或悬浮状态,CANL端只能是低电平或悬浮状态。这就保证不会出现象在RS-485网络中,当系统有错误,出现多节点同时向总线发送数据时,导致总线呈现短路,从而损坏某些节点的现象。而且CAN节点在错误严重的情况下具有自动关闭输出功能,以使总线上其他节点的操作不受影响,从而保证不会出现象在网络中,因个别节点出现问题,使得总线处于“死锁”状态。
6,CAN具有完善的通信协议,可由CAN控制器芯片及其接口芯片来实现,从而大大降低了系统的开发难度,缩短了开发周期,这些是只仅仅有电气协议的RS-485所无法比拟的。
本次示例使用的芯片为S32K14,环境为S32 Design Stduio for ARM,使用的CAN端口号为CAN1,测试方法为回环测试
目录
1.创建一个基本工程
这里就不细说
2.初始化用到的引脚
用到的引脚有:
UART1:TX->PTC7 RX->PTC6
CAN1: TX->PTA13 RX->PTA12
构建项目
配置串口1 波特率115200
配置CAN为CAN1,波特率为500K,回环模式
设置完成后构建项目
3.创建一个delay.c/h
delay.c
static uint32_t fac_us;
/*延迟函数致敬原子哥*/
void SysTick_Init(void) //systick 走 core_clk 48mhz 重装载寄存器 FFFFFF 为16 777 215hz 16.77mhz/48mhz 约等于333毫秒
{
S32_SysTick->RVR = 0xFFFFFFul; //重装载寄存器
S32_SysTick->CVR = 0ul; //当前计数
S32_SysTick->CSR = 0u; //控制寄存器
}
/* Enable SysTick counter and interrupt */
void SysTick_Enable(void)
{
S32_SysTick->CSR = S32_SysTick_CSR_TICKINT(1u) | S32_SysTick_CSR_ENABLE(1);
}
/* Disable SysTick */
void SysTick_Disable(void)
{
S32_SysTick->CSR = 0ul;
}
static uint32_t fac_us;
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told,tnow,tcnt=0;
uint32_t reload=S32_SysTick->RVR; //LOAD的值
ticks=nus*fac_us; //需要的节拍数
told= S32_SysTick->CVR ; //刚进入时的计数器值
while(1)
{
tnow= S32_SysTick->CVR ;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
};
}
void delay_ms(uint32_t nms)
{
uint32_t i;
for(i=0;i<nms;i++) delay_us(1000);
}
int delay_init(void)
{
uint32_t frequency;
CLOCK_SYS_GetFreq(CORE_CLOCK, &frequency);
fac_us = frequency / 1000000;// 得到的频率是mhz 如果想知道1us多少个节拍,需要除 1 000 000
SysTick_Init(); //systick 走 core_clk 48mhz 重装载寄存器 FFFFFF 为16 777 215hz 16.77mhz/48mhz 0.333秒 约等于333毫秒
SysTick_Enable();
return fac_us;
}
delay.h
#ifndef DELAY_H_
#define DELAY_H_
void delay_ms(uint32_t nms);
void delay_us(uint32_t nus);
int delay_init(void);
#endif /* DELAY_H_ */
4.设置串口输出
#include <stdio.h>
#include <string.h>
#include "stdarg.h"
#include <stdint.h>
#include <stdbool.h>
char USART1_TX_BUF[200];
void u1_printf(char* fmt,...)
{
uint32_t bytesRemaining;
va_list ap;
va_start(ap,fmt);
vsprintf((char*)USART1_TX_BUF,fmt,ap);
va_end(ap);
LPUART_DRV_SendData(INST_LPUART1, (uint8_t *)USART1_TX_BUF, strlen(USART1_TX_BUF)); //发送
while (LPUART_DRV_GetTransmitStatus(INST_LPUART1, &bytesRemaining)!= STATUS_SUCCESS) {}
}
接下来就可以使用u1_printf来输出了
int main(void)
{
/* Write your local variable definition here */
/*** Processor Expert internal initialization. DON'T REMOVE THIS CODE!!! ***/
#ifdef PEX_RTOS_INIT
PEX_RTOS_INIT(); /* Initialization of the selected RTOS. Macro is defined by the RTOS component. */
#endif
/*** End of Processor Expert internal initialization. ***/
CLOCK_SYS_Init(g_clockManConfigsArr, CLOCK_MANAGER_CONFIG_CNT,g_clockManCallbacksArr, CLOCK_MANAGER_CALLBACK_CNT);
CLOCK_SYS_UpdateConfiguration(0U, CLOCK_MANAGER_POLICY_AGREEMENT);
delay_init();//初始化delay函数
PINS_DRV_Init(NUM_OF_CONFIGURED_PINS, g_pin_mux_InitConfigArr);
LPUART_DRV_Init(INST_LPUART1, &lpuart1_State, &lpuart1_InitConfig0);
/* Write your code here */
/* For example: for(;;) { } */
u1_printf("S32 begin\r\n");
while(1){
}
/*** Don't write any code pass this line, or it will be deleted during code generation. ***/
/*** RTOS startup code. Macro PEX_RTOS_START is defined by the RTOS component. DON'T MODIFY THIS CODE!!! ***/
#ifdef PEX_RTOS_START
PEX_RTOS_START(); /* Startup of the selected RTOS. Macro is defined by the RTOS component. */
#endif
/*** End of RTOS startup code. ***/
/*** Processor Expert end of main routine. DON'T MODIFY THIS CODE!!! ***/
for(;;) {
if(exit_code != 0) {
break;
}
}
return exit_code;
/*** Processor Expert end of main routine. DON'T WRITE CODE BELOW!!! ***/
} /*** End of main routine. DO NOT MODIFY THIS TEXT!!! ***/
无误
4.设置CAN输入&输出
初始化
#define CAN_MSG_MASK_ALL 0x07FF //全bit匹配 只接收接收邮箱里有的ID
#define CAN_MSG_MASK_NULL 0x0 //不匹配 接收所有消息
can_message_t recvMsg_CAN1; //接收邮箱结构体
can_message_t trasiMsg_CAN1 ; //发送邮箱结构体
uint32_t IRQ_CAN1_RX; //CAN1收到信息标志位
#define CAN1_TX_MAILBOX1 0x1 //CAN1发送邮箱标识
#define CAN1_RX_MAILBOX1 0x2 //CAN1接收邮箱标识 最大为32
#define CAN1_RX_MAILBOX1_ID 0x55 //CAN1识别的ID
#define CAN1_TX_MAILBOX1_ID 0x55 //CAN1发送的ID
void CAN1_Init(void){
CAN_Init(&can_pal1_instance, &can_pal1_Config0);
can_buff_config_t Rx_buffCfg = {
.enableFD = false,//表示是否启用灵活数据速率CAN FD
.enableBRS = false,//表示是否在CAN FD帧内启用比特率切换
.fdPadding = 0U,//表示当数据长度码指定的负载大小大于实际数据长度时,用于填充的值。这有助于保持数据帧的一致性
.idType = CAN_MSG_ID_STD,//标准帧
.isRemote = false,//表示该帧是否为远程请求帧
};
can_buff_config_t Tx_buffCfg = {
.enableFD = false,
.enableBRS = false,
.fdPadding = 0U,
.idType = CAN_MSG_ID_STD,
.isRemote = false
};
CAN_ConfigRxBuff(&can_pal1_instance, CAN1_RX_MAILBOX1, &Rx_buffCfg, CAN1_RX_MAILBOX1_ID);//注册一个接收的邮箱 接收的ID 0x55
CAN_ConfigTxBuff(&can_pal1_instance, CAN1_TX_MAILBOX1, &Tx_buffCfg); //发送邮箱配置载入
CAN_SetRxFilter(&can_pal1_instance,CAN_MSG_ID_STD,CAN1_RX_MAILBOX1,CAN_MSG_MASK_ALL);//标准帧 全bit匹配
CAN_InstallEventCallback(&can_pal1_instance,&CAN1_Callback_Func,(void*)0); //注册CAN1回调函数
CAN_Receive(&can_pal1_instance, CAN1_RX_MAILBOX1, &recvMsg_CAN1); //CAN1接收邮箱CAN1_RX_MAILBOX1开启接收
}
初始化CAN1,主要设置了CAN1的收发邮箱以及开启了中断
接收
刚刚在初始化里开启了中断,现在接收只需要在中断里置位标志位后再main函数处理即可,刚刚注册的回调函数为CAN1_Callback_Func()
//CAN1回调函数
void CAN1_Callback_Func (uint32_t instance,can_event_t event,uint32_t buffIdx,void *flexcanState)
{
(void)flexcanState;
(void)instance;
(void)buffIdx;
if(event == CAN_EVENT_RX_COMPLETE){//接收完成 事件
if(recvMsg_CAN1.id == CAN1_RX_MAILBOX1_ID) {
IRQ_CAN1_RX =CAN1_RX_MAILBOX1_ID;
CAN_Receive(&can_pal1_instance, CAN1_RX_MAILBOX1, &recvMsg_CAN1);//接收报文并重新注册回调函数
}
}
}
在main函数里 处理数据
//CAN1处理消息
void CAN1_Read(void){
u1_printf("CAN1 邮箱消息: ID:0x%x \r\n",recvMsg_CAN1.id);
for(uint8_t i=0;i<recvMsg_CAN1.length;i++){
u1_printf("0x%x ",recvMsg_CAN1.data[i]);
}
u1_printf("\r\n");//依次打印出标准帧的数据
}
main里:
if(IRQ_CAN1_RX == CAN1_RX_MAILBOX1_ID){
CAN1_Read();
}
发送
//CAN发送数据数组 ID
void CAN1_Send(uint8_t *dat,uint32_t ID){
can_message_t trasiMsg_CAN1;
trasiMsg_CAN1.cs = 0U;
trasiMsg_CAN1.id = ID;
for(uint8_t i=0;i<8;i++){
trasiMsg_CAN1.data[i]=dat[i];
}
trasiMsg_CAN1.length = 8;
CAN_Send(&can_pal1_instance, CAN1_TX_MAILBOX1, &trasiMsg_CAN1);
u1_printf("CAN1 Send ID:0x%x \r\n",trasiMsg_CAN1.id);
for(int i=0; i<trasiMsg_CAN1.length;i++)
{
u1_printf("0x%x ",trasiMsg_CAN1.data[i]);
}
u1_printf("\r\n");
}
在main函数中使用一个按键触发,也可以用别的条件触发
if((PINS_DRV_ReadPins(PTC)>>12)&0x01){
while((PINS_DRV_ReadPins(PTC)>>12)&0x01);
CAN1_Send(candat,CAN1_TX_MAILBOX1_ID);//使用CAN1_TX_MAILBOX1_ID发送数组candat
}
在main再加一句 使得在接收到数据之后可以处理
if(IRQ_CAN1_RX==CAN1_RX_MAILBOX1_ID){
CAN1_Read();
IRQ_CAN1_RX=0;
}
整体main函数
int main(void)
{
/* Write your local variable definition here */
/*** Processor Expert internal initialization. DON'T REMOVE THIS CODE!!! ***/
#ifdef PEX_RTOS_INIT
PEX_RTOS_INIT(); /* Initialization of the selected RTOS. Macro is defined by the RTOS component. */
#endif
/*** End of Processor Expert internal initialization. ***/
CLOCK_SYS_Init(g_clockManConfigsArr, CLOCK_MANAGER_CONFIG_CNT,g_clockManCallbacksArr, CLOCK_MANAGER_CALLBACK_CNT);
CLOCK_SYS_UpdateConfiguration(0U, CLOCK_MANAGER_POLICY_AGREEMENT);
delay_init();//初始化delay函数
PINS_DRV_Init(NUM_OF_CONFIGURED_PINS, g_pin_mux_InitConfigArr);
LPUART_DRV_Init(INST_LPUART1, &lpuart1_State, &lpuart1_InitConfig0);
CAN1_Init();
/* Write your code here */
/* For example: for(;;) { } */
u1_printf("S32 begin\r\n");
while(1){
if(IRQ_CAN1_RX==CAN1_RX_MAILBOX1_ID){
CAN1_Read();
IRQ_CAN1_RX=0;
}
if((PINS_DRV_ReadPins(PTC)>>12)&0x01){
CAN1_Send(candat,CAN1_TX_MAILBOX1_ID);
while((PINS_DRV_ReadPins(PTC)>>12)&0x01);
}
delay_ms(100);
}
/*** Don't write any code pass this line, or it will be deleted during code generation. ***/
/*** RTOS startup code. Macro PEX_RTOS_START is defined by the RTOS component. DON'T MODIFY THIS CODE!!! ***/
#ifdef PEX_RTOS_START
PEX_RTOS_START(); /* Startup of the selected RTOS. Macro is defined by the RTOS component. */
#endif
/*** End of RTOS startup code. ***/
/*** Processor Expert end of main routine. DON'T MODIFY THIS CODE!!! ***/
for(;;) {
if(exit_code != 0) {
break;
}
}
return exit_code;
/*** Processor Expert end of main routine. DON'T WRITE CODE BELOW!!! ***/
} /*** End of main routine. DO NOT MODIFY THIS TEXT!!! ***/
整体CAN函数
#define CAN_MSG_MASK_ALL 0x07FF //全bit匹配 只接收接收邮箱里有的ID
#define CAN_MSG_MASK_NULL 0x0 //不匹配 接收所有消息
can_message_t recvMsg_CAN1; //接收邮箱结构体
uint32_t IRQ_CAN1_RX; //CAN1收到信息标志位
#define CAN1_TX_MAILBOX1 0x0 //CAN1发送邮箱标识
#define CAN1_RX_MAILBOX1 0x1 //CAN1接收邮箱标识 最大为32
#define CAN1_RX_MAILBOX1_ID 0x55 //CAN1识别的ID 为11位标识
#define CAN1_TX_MAILBOX1_ID 0x55 //CAN1发送的ID 为11位标识
//CAN1回调函数
void CAN1_Callback_Func (uint32_t instance,can_event_t event,uint32_t buffIdx,void *flexcanState)
{
(void)flexcanState;
(void)instance;
(void)buffIdx;
if(event == CAN_EVENT_RX_COMPLETE){//接收完成 事件
if(recvMsg_CAN1.id == CAN1_RX_MAILBOX1_ID) {
IRQ_CAN1_RX =CAN1_RX_MAILBOX1_ID;
CAN_Receive(&can_pal1_instance, CAN1_RX_MAILBOX1, &recvMsg_CAN1);//接收报文并重新注册回调函数
}
}
}
void CAN1_Init(void){
CAN_Init(&can_pal1_instance, &can_pal1_Config0);
can_buff_config_t Rx_buffCfg = {
.enableFD = false,//表示是否启用灵活数据速率CAN FD
.enableBRS = false,//表示是否在CAN FD帧内启用比特率切换
.fdPadding = 0U,//表示当数据长度码指定的负载大小大于实际数据长度时,用于填充的值。这有助于保持数据帧的一致性
.idType = CAN_MSG_ID_STD,//标准帧
.isRemote = false,//表示该帧是否为远程请求帧
};
can_buff_config_t Tx_buffCfg = {
.enableFD = false,
.enableBRS = false,
.fdPadding = 0U,
.idType = CAN_MSG_ID_STD,
.isRemote = false
};
CAN_ConfigRxBuff(&can_pal1_instance, CAN1_RX_MAILBOX1, &Rx_buffCfg, CAN1_RX_MAILBOX1_ID);//注册一个接收的邮箱 接收的ID 0x55
CAN_ConfigTxBuff(&can_pal1_instance, CAN1_TX_MAILBOX1, &Tx_buffCfg); //发送邮箱配置载入
CAN_SetRxFilter(&can_pal1_instance,CAN_MSG_ID_STD,CAN1_RX_MAILBOX1,CAN_MSG_MASK_ALL);//标准帧 全bit匹配
CAN_InstallEventCallback(&can_pal1_instance,&CAN1_Callback_Func,(void*)0); //注册CAN1回调函数
CAN_Receive(&can_pal1_instance, CAN1_RX_MAILBOX1, &recvMsg_CAN1); //CAN1接收邮箱CAN1_RX_MAILBOX1开启接收
}
//CAN1处理消息
void CAN1_Read(void){
u1_printf("CAN1 邮箱消息: ID:0x%x \r\n",recvMsg_CAN1.id);
for(uint8_t i=0;i<recvMsg_CAN1.length;i++){
u1_printf("0x%x ",recvMsg_CAN1.data[i]);
}
u1_printf("\r\n");//依次打印出标准帧的数据
}
uint8_t candat[8]={1,2,3,4,5,6,7,8};
void CAN1_Send(uint8_t *dat,uint32_t ID){
can_message_t trasiMsg_CAN1;
trasiMsg_CAN1.cs = 0U;
trasiMsg_CAN1.id = ID;
for(uint8_t i=0;i<8;i++){
trasiMsg_CAN1.data[i]=dat[i];
}
trasiMsg_CAN1.length = 8;
CAN_Send(&can_pal1_instance, CAN1_TX_MAILBOX1, &trasiMsg_CAN1);
u1_printf("CAN1 Send ID:0x%x \r\n",trasiMsg_CAN1.id);
for(int i=0; i<trasiMsg_CAN1.length;i++)
{
u1_printf("0x%x ",trasiMsg_CAN1.data[i]);
}
u1_printf("\r\n");
}
现象
开机为
按下按键,松开后
至此CAN回环测试成功
参考文章: