IMX6ULL与STM32F103的CAN通信实现
在上一篇博文中,我们利用USBCAN设备及其上位机软件,测试了一块以IMX6ULL为核心芯片的开发板的CAN通信收发功能,了解了在Linux应用层基于套接字实现CAN网络应用的基本方法。本次我们将USBCAN替换为一块以STM32F103ZET6为核心芯片的开发板,尝试实现双机之间的CAN通信基本功能。
硬件连接
在硬件上,两个板子各自均只留出1路CAN接口,只需将两个CAN接口的CAN_H与CAN_L对应连接起来即可。由于CAN信号是两根线的差分信号,故不需要再连接其它。两板的CAN收发器均采用经典的TJA1050,器件连接的原理图如下。用线连好后双机的CAN通信连接示意图如下。芯片内部集成的CAN控制器与外接的CAN收发器交换CAN报文数据,收发器是控制器与物理总线的中间媒介,负责在控制器的逻辑电平和总线的差分信号间做转换。而CAN控制器的具体行为由相关寄存器的状态决定,需要我们编写程序来控制。两个收发器之间的CAN_H和CAN_L两条线的两段之间,需要各自接一个120Ω的电阻,由原理图可见板载已经接入了该电阻,无需额外接入。两个板子连接的实物图如下。
驱动层实现
这里所说的驱动层,是从Linux系统体系的角度而言的。Linux具有明显的内核与应用层之分,对底层硬件的驱动程序是内核的组成部分,在应用层控制硬件工作的流程中充当了一种“中间件”的作用,对上提供相关接口供应用层调用,对下通过操作寄存器等真正操控硬件的行为。但对于STM32裸机的开发方式来说,程序并不需要明确划分哪些部分属于app、哪些属于driver,所有的代码是可以放在一起的。我们本次的开发基于ST官方提供的标准库,所以这里把基于标准库封装开发所得的用于实现STM32的CAN外设基本功能的接口,称为STM32的CAN“驱动层”。
IMX6ULL
为驱动IMX6ULL上的CAN外设,首先要找到描述该设备硬件信息的设备树,其中外设引脚信息和控制器描述信息是必不可少的。我们在内核源码/arch/arm/boot/dts/目录下找到有关该CAN外设的设备树代码如下:
pinctrl_flexcan1: flexcan1grp {
fsl, pins = <
MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020
MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020
> ;
} ;
flexcan1: can@02090000 {
compatible = "fsl,imx6ul-flexcan" , "fsl,imx6q-flexcan" ;
reg = < 0x02090000 0x4000 > ;
interrupts = < GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH> ;
clocks = < & clks IMX6UL_CLK_CAN1_IPG> ,
< & clks IMX6UL_CLK_CAN1_SERIAL> ;
clock- names = "ipg" , "per" ;
stop- mode = < & gpr 0x10 1 0x10 17 > ;
status = "disabled" ;
} ;
上面CAN控制器的status为disabled,表示不可用,需要改成okay才能正常使用。一般我们不会直接去修改原控制器的默认设置,而是采用“追加”的方式更改属性值,如下为对flexcan1硬件信息的追加补充:
& flexcan1 {
pinctrl- names = "default" ;
pinctrl- 0 = < & pinctrl_flexcan1> ;
xceiver- supply = < & reg_can_3v3> ;
status = "okay" ;
} ;
这样设备树的配置基本就完成了,然后需要使能IMX6ULL的flexcan驱动。一般而言,NXP在官方提供的内核里已经集成了多种类型外设的驱动,如果仅仅使用基础通信等基本功能而不涉及复杂需求的实现,那么大多数情况下直接使能内核自带驱动后就可以了。在内核源码顶层目录下执行内核配置make menuconfig,依次进入Networking support、CAN bus subsystem support、CAN Device Driver,找到Support for Freescale FLEXCAN based chips选项,编译类型选成编译为内核一部分即built-in,如下: 保存设置后CAN驱动的编译选项就存在了编译配置文件中,再编译内核就能使上面的设备树配置和驱动配置生效了,将生成的Image和dtb文件载入IMX6ULL开发板,即可看到系统中的CAN设备,如下。在/sys/class/net/目录下的can0即为我们配置的外设,查看其控制器设备树的status可见值为okay,和我们设置的一样。
STM32F103ZET6
要使用STM32F103的CAN外设,我们需要考虑这些方面:①引脚配置,要考虑CAN_RX和CAN_TX所在引脚号、GPIO的时钟使能、GPIO参数如何配置等;②CAN控制器配置,包括CAN外设时钟使能、CAN工作模式、波特率、接收FIFO属性等;③CAN过滤器配置,主要决定CAN的接收策略;④CAN外设的接收和发送接口;⑤在需要利用CAN中断的场合,还应该配置NVIC和CAN中断类型。后面和IMX6ULL交互时需要用到CAN接收中断,所以我们本次需要配置中断。基于ST提供的标准库,可以将上述几方面开发成自己的接口,即为所谓的CAN“驱动层”。我们开发的接口主要代码如下:
# define CANx CAN1
# define CAN_CLK RCC_APB1Periph_CAN1
# define CAN_RX_IRQ USB_LP_CAN1_RX0_IRQn
# define CAN_RX_IRQHandler USB_LP_CAN1_RX0_IRQHandler
# define CAN_RX_PIN GPIO_Pin_11
# define CAN_TX_PIN GPIO_Pin_12
# define CAN_TX_GPIO_PORT GPIOA
# define CAN_RX_GPIO_PORT GPIOA
# define CAN_TX_GPIO_CLK RCC_APB2Periph_GPIOA
# define CAN_RX_GPIO_CLK RCC_APB2Periph_GPIOA
typedef enum {
STANDARD = 0 ,
EXTENDED
} CAN_ID_TYPE;
typedef enum {
DATA = 0 ,
REMOTE
} CAN_DATA_TYPE;
static void CAN_GPIO_Config ( void )
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd ( CAN_TX_GPIO_CLK| CAN_RX_GPIO_CLK, ENABLE) ;
GPIO_InitStructure. GPIO_Pin = CAN_TX_PIN;
GPIO_InitStructure. GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure. GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init ( CAN_TX_GPIO_PORT, & GPIO_InitStructure) ;
GPIO_InitStructure. GPIO_Pin = CAN_RX_PIN;
GPIO_InitStructure. GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure. GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init ( CAN_RX_GPIO_PORT, & GPIO_InitStructure) ;
}
static void CAN_NVIC_Config ( void )
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig ( NVIC_PriorityGroup_1) ;
NVIC_InitStructure. NVIC_IRQChannel = CAN_RX_IRQ;
NVIC_InitStructure. NVIC_IRQChannelPreemptionPriority = 0 ;
NVIC_InitStructure. NVIC_IRQChannelSubPriority = 0 ;
NVIC_InitStructure. NVIC_IRQChannelCmd = ENABLE;
NVIC_Init ( & NVIC_InitStructure) ;
}
static void CAN_Mode_Config ( void )
{
CAN_InitTypeDef CAN_InitStructure;
RCC_APB1PeriphClockCmd ( CAN_CLK, ENABLE) ;
CAN_DeInit ( CANx) ;
CAN_StructInit ( & CAN_InitStructure) ;
CAN_InitStructure. CAN_TTCM = DISABLE;
CAN_InitStructure. CAN_ABOM = ENABLE;
CAN_InitStructure. CAN_AWUM = ENABLE;
CAN_InitStructure. CAN_NART = DISABLE;
CAN_InitStructure. CAN_RFLM = DISABLE;
CAN_InitStructure. CAN_TXFP = DISABLE;
CAN_InitStructure. CAN_Mode = CAN_Mode_Normal;
CAN_InitStructure. CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure. CAN_BS1 = CAN_BS1_5tq;
CAN_InitStructure. CAN_BS2 = CAN_BS2_3tq;
CAN_InitStructure. CAN_Prescaler = 8 ;
CAN_Init ( CANx, & CAN_InitStructure) ;
}
static void CAN_Filter_Config ( void )
{
CAN_FilterInitTypeDef CAN_FilterInitStructure;
u16 FilterId = 0x123 ;
u16 FilterMask = 0xFFFF ;
CAN_FilterInitStructure. CAN_FilterNumber = 0 ;
CAN_FilterInitStructure. CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure. CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInitStructure. CAN_FilterIdHigh = FilterId<< 5 ;
CAN_FilterInitStructure. CAN_FilterIdLow = ( u16) ( CAN_ID_STD| CAN_RTR_DATA) ;
CAN_FilterInitStructure. CAN_FilterMaskIdHigh = FilterMask;
CAN_FilterInitStructure. CAN_FilterMaskIdLow = FilterMask;
CAN_FilterInitStructure. CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
CAN_FilterInitStructure. CAN_FilterActivation = ENABLE;
CAN_FilterInit ( & CAN_FilterInitStructure) ;
CAN_ITConfig ( CANx, CAN_IT_FMP0, ENABLE) ;
}
void CAN_Config ( void )
{
CAN_GPIO_Config ( ) ;
CAN_NVIC_Config ( ) ;
CAN_Mode_Config ( ) ;
CAN_Filter_Config ( ) ;
}
void CAN_InitRxMessage ( CanRxMsg * RxMessage, CAN_ID_TYPE idtype, CAN_DATA_TYPE datatype, u8 dlc, u8 fmi)
{
RxMessage-> StdId = 0 ;
RxMessage-> ExtId = 0 ;
if ( idtype == STANDARD)
RxMessage-> IDE = CAN_ID_STD;
else if ( idtype == EXTENDED)
RxMessage-> IDE = CAN_ID_EXT;
if ( datatype == DATA)
RxMessage-> RTR = CAN_RTR_DATA;
else if ( datatype == REMOTE)
RxMessage-> RTR = CAN_RTR_REMOTE;
RxMessage-> DLC = dlc;
memset ( RxMessage-> Data, 0x00 , 8 ) ;
RxMessage-> FMI = fmi;
}
void CAN_SetTxMessage ( CanTxMsg * TxMessage, u32 id, CAN_ID_TYPE idtype, CAN_DATA_TYPE datatype, u8 dlc, unsigned char * data)
{
TxMessage-> StdId = id;
if ( idtype == STANDARD)
TxMessage-> IDE = CAN_ID_STD;
else if ( idtype == EXTENDED)
TxMessage-> IDE = CAN_ID_EXT;
if ( datatype == DATA)
TxMessage-> RTR = CAN_RTR_DATA;
else if ( datatype == REMOTE)
TxMessage-> RTR = CAN_RTR_REMOTE;
TxMessage-> DLC = dlc;
memcpy ( TxMessage-> Data, data, dlc) ;
}
u8 CAN_SendTxMsg ( CAN_TypeDef * CANxx, CanTxMsg * TxMessage)
{
u8 mailbox = 3 ;
if ( ( mailbox = CAN_Transmit ( CANxx, TxMessage) ) != CAN_TxStatus_NoMailBox)
{
while ( ( CAN_TransmitStatus ( CANxx, mailbox) ) != CAN_TxStatus_Ok) ;
return 0 ;
}
else
{
printf ( "CAN_SendTxMsg error and no mail box\r\n" ) ;
return 1 ;
}
}
上面代码中,CAN接收中断号定义在文件stm32f10x.h中的枚举类型IRQn_Type,该类型规定了STM32F10x的所有中断号。CAN接收中断服务函数的名称定义在启动文件start_up_stm32f10x_hd.s中。在CAN控制器配置函数中,设定了禁止FIFO锁定模式,意味着FIFO溢出时新接收的报文会覆盖旧的报文而不会被丢弃。由时间参数的设定可以得出,CAN波特率即为36MHz/(8*(1+5+3))=500kbps。在CAN过滤器的配置中,采用了32位掩码模式,根据ID为0x123和掩码为0xFFFF可知,这里设定了仅接受ID为0x123的报文,并且为标准ID和数据帧。使能了FIFO0的消息挂起中断,即该邮箱中接收到消息就进入CAN接收服务函数。在CAN_SendTxMsg接口中,调用了库函数CAN_Transmit进行发送,该函数返回有效的邮箱号或表示无可用邮箱的标志。利用库函数CAN_TransmitStatus检查发送状态是否为完成。
应用层实现
我们设计一个非常简单的双机交互流程,如下:
IMX6
STM32
send 0x123
send 0x456
send 0x123
send 0x456
......
IMX6
STM32
IMX6这边首先发送ID为x0123的CAN报文,STM32接收到后回复ID为0x456的CAN报文,IMX6收到后继续发送0x123,然后重复这个过程。
IMX6ULL
关于IMX6ULL的CAN通信应用层的基本知识,可参考之前的博文《imx6ull开发板的CAN通信》,这里不详细说明了。我们设置2个线程分别处理发送和接收任务,发送线程在收到报文后延时2秒发送,两个线程之间使用简单的标志位同步。应用层代码如下:
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <unistd.h>
# include <sys/ioctl.h>
# include <sys/socket.h>
# include <linux/can.h>
# include <linux/can/raw.h>
# include <net/if.h>
# include <pthread.h>
struct can_frame receiveframe = { 0 } ;
struct can_frame sendframe = { 0 } ;
struct can_filter filters[ 3 ] ;
int sockfd;
int flag = 1 ;
void CAN_Config ( void ) {
struct ifreq ifr = { 0 } ;
struct sockaddr_can can_addr = { 0 } ;
int i;
int ret;
if ( ( sockfd = socket ( PF_CAN, SOCK_RAW, CAN_RAW) ) < 0 ) {
perror ( "socket error" ) ;
exit ( EXIT_FAILURE) ;
}
strcpy ( ifr. ifr_name, "can0" ) ;
if ( ( ioctl ( sockfd, SIOCGIFINDEX, & ifr) ) != 0 ) {
perror ( "ioctl error" ) ;
exit ( EXIT_FAILURE) ;
}
can_addr. can_family = AF_CAN;
can_addr. can_ifindex = ifr. ifr_ifindex;
if ( ( ret = bind ( sockfd, ( struct sockaddr * ) & can_addr, sizeof ( can_addr) ) ) < 0 ) {
perror ( "bind error" ) ;
close ( sockfd) ;
exit ( EXIT_FAILURE) ;
}
filters[ 0 ] . can_id = 0x123 ;
filters[ 1 ] . can_id = 0x234 ;
filters[ 2 ] . can_id = 0x456 ;
for ( i = 0 ; i < 3 ; i++ )
filters[ i] . can_mask = 0x7FF ;
setsockopt ( sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, & filters, sizeof ( filters) ) ;
sendframe. can_id = 0x123 ;
sendframe. can_dlc = 8 ;
sendframe. data[ 0 ] = 0x01 ;
sendframe. data[ 1 ] = 0x02 ;
sendframe. data[ 2 ] = 0x03 ;
sendframe. data[ 3 ] = 0x04 ;
sendframe. data[ 4 ] = 0x05 ;
sendframe. data[ 5 ] = 0x06 ;
sendframe. data[ 6 ] = 0x07 ;
sendframe. data[ 7 ] = 0x08 ;
}
void * canReceive ( void * arg) {
while ( 1 ) {
int i = 0 ;
if ( ( read ( sockfd, & receiveframe, sizeof ( struct can_frame ) ) ) < 0 ) {
perror ( "read error" ) ;
break ;
}
if ( receiveframe. can_id & CAN_ERR_FLAG) {
printf ( "Error frame!\n" ) ;
break ;
}
printf ( "Received CAN data from STM32F103ZET6\n" ) ;
if ( receiveframe. can_id & CAN_EFF_FLAG)
printf ( "扩展帧 <0x%08x> \n" , receiveframe. can_id & CAN_EFF_MASK) ;
else
printf ( "标准帧 <0x%03x> \n" , receiveframe. can_id & CAN_SFF_MASK) ;
if ( receiveframe. can_id & CAN_RTR_FLAG) {
printf ( "remote request\n" ) ;
continue ;
}
printf ( "can_dlc: [%d] \ndata: " , receiveframe. can_dlc) ;
for ( i; i < receiveframe. can_dlc; i++ )
printf ( "%02x " , receiveframe. data[ i] ) ;
printf ( "\n\n" ) ;
flag = 1 ;
}
}
void * canSend ( void * arg) {
while ( 1 ) {
sleep ( 2 ) ;
if ( flag == 1 ) {
if ( ( write ( sockfd, & sendframe, sizeof ( sendframe) ) ) != sizeof ( sendframe) ) {
perror ( "write error" ) ;
break ;
}
flag = 0 ;
}
}
}
void threadCreate ( void ) {
pthread_t t1;
pthread_t t2;
int ret;
if ( ret = pthread_create ( & t1, NULL , canReceive, NULL ) ) {
printf ( "canReceive thread create fail\r\n" ) ;
return ;
}
pthread_detach ( t1) ;
if ( ret = pthread_create ( & t2, NULL , canSend, NULL ) ) {
printf ( "canSend thread create fail\r\n" ) ;
return ;
}
pthread_detach ( t2) ;
}
int main ( void ) {
CAN_Config ( ) ;
threadCreate ( ) ;
while ( 1 ) {
sleep ( 10000 ) ;
}
return 0 ;
}
STM32F103ZET6
STM32侧的接收是在CAN接收中断服务函数中完成的,在文件stm32f10x_it.c中添加如下代码:
extern CanRxMsg RxMessage;
extern u8 flag;
void CAN_RX_IRQHandler ( void )
{
CAN_Receive ( CANx, CAN_FIFO0, & RxMessage) ;
flag = 1 ;
}
有报文到来则FIFO0的消息挂起中断触发,调用库函数CAN_Receive实现接收,并且通过简单的标志位通知主函数消息收到。接收缓冲区和通知标志均在主函数中声明。主函数循环检测该标志,如检测到被置1则通过串口把收到的数据打印出来,并立即发送报文给IMX6ULL。主函数的主要代码如下:
CanTxMsg TxMessage;
CanRxMsg RxMessage;
unsigned char senddata[ ] = { 0x78 , 0x89 , 0x9A , 0xAB , 0xBC , 0xCD , 0xDE , 0xEF } ;
u8 flag = 0 ;
int main ( void ) {
uartInit ( 115200 ) ;
CAN_Config ( ) ;
CAN_SetTxMessage ( & TxMessage, 0x456 , STANDARD, DATA, 8 , senddata) ;
CAN_InitRxMessage ( & RxMessage, STANDARD, DATA, 8 , 0 ) ;
while ( 1 ) {
if ( flag == 1 )
{
u8 i = 0 ;
printf ( "[Received CAN data from IMX6ULL] ID[0x%X] IDE[0x%X] RTR[0x%X] DLC[0x%X]\r\n" ,
RxMessage. StdId, RxMessage. IDE, RxMessage. RTR, RxMessage. DLC) ;
for ( i; i < RxMessage. DLC; i++ )
printf ( "data[%d]:0x%X " , i, RxMessage. Data[ i] ) ;
printf ( "\r\n" ) ;
printf ( "\n" ) ;
if ( CAN_SendTxMsg ( CANx, & TxMessage) )
break ;
flag = 0 ;
}
}
}
结果
两边编译成功后烧写到各自板子中,启动IMX6侧的应用进程,可以看到STM32侧打印的串口消息和IMX6侧进程打印的接收消息如下。可以看出STM32成功接收到了ID为0x123的标准数据帧,每次接收8字节0x01到0x08,IMX6ULL侧成功接收到了ID为0x456的标准数据帧,每次接收8字节:0x78、0x89、0x9a、0xab、0xbc、0xcd、0xde和0xef。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)