IMX6ULL与STM32F103的CAN通信实现

  • 硬件连接
  • 驱动层实现
    • IMX6ULL
    • STM32F103ZET6
  • 应用层实现
    • IMX6ULL
    • STM32F103ZET6
  • 结果

在上一篇博文中,我们利用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 // CAN的RX引脚复用UART3_RTSMX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX    0x1b020 // CAN的TX引脚复用UART3_CTS>;
};
// CAN1控制器
flexcan1: can@02090000 {compatible = "fsl,imx6ul-flexcan", "fsl,imx6q-flexcan"; // 匹配NXP原厂CAN驱动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>; // 指定CAN收发器电压为3.3Vstatus = "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 // 使用CAN1
#define CAN_CLK           RCC_APB1Periph_CAN1 // CAN1的时钟
#define CAN_RX_IRQ        USB_LP_CAN1_RX0_IRQn // CAN接收中断号
#define CAN_RX_IRQHandler USB_LP_CAN1_RX0_IRQHandler // CAN接收中断服务函数#define CAN_RX_PIN        GPIO_Pin_11 // Rx:PA11
#define CAN_TX_PIN        GPIO_Pin_12 // Tx:PA12
#define CAN_TX_GPIO_PORT  GPIOA
#define CAN_RX_GPIO_PORT  GPIOA
#define CAN_TX_GPIO_CLK   RCC_APB2Periph_GPIOA // GPIOA的时钟
#define CAN_RX_GPIO_CLK   RCC_APB2Periph_GPIOAtypedef enum{STANDARD = 0,EXTENDED
} CAN_ID_TYPE; // 报文ID类型typedef enum{DATA = 0,REMOTE
} CAN_DATA_TYPE; // 帧类型/* GPIO配置 */
static void CAN_GPIO_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(CAN_TX_GPIO_CLK|CAN_RX_GPIO_CLK, ENABLE); // 使能Rx和Tx对应GPIO的时钟GPIO_InitStructure.GPIO_Pin = CAN_TX_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // Tx配置为复用推挽输出         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; // Rx配置为上拉输入GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(CAN_RX_GPIO_PORT, &GPIO_InitStructure);
}/* NVIC配置 */
static void CAN_NVIC_Config(void)
{NVIC_InitTypeDef NVIC_InitStructure;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 设置中断优先级分组1NVIC_InitStructure.NVIC_IRQChannel = CAN_RX_IRQ; // CAN接收中断号NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
}/* CAN控制器配置 */
static void CAN_Mode_Config(void)
{CAN_InitTypeDef CAN_InitStructure;RCC_APB1PeriphClockCmd(CAN_CLK, ENABLE); // 使能CAN时钟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; // 禁止FIFO锁定模式    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; // 位段1CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq; // 位段2   CAN_InitStructure.CAN_Prescaler = 8; // 时钟分频CAN_Init(CANx, &CAN_InitStructure);
}/* CAN过滤器配置 */
static void CAN_Filter_Config(void)
{CAN_FilterInitTypeDef CAN_FilterInitStructure;u16 FilterId = 0x123; // 可接收的报文IDu16 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; // 关联FIFO0            CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;         CAN_FilterInit(&CAN_FilterInitStructure);CAN_ITConfig(CANx, CAN_IT_FMP0, ENABLE); // 使能FIFO0的消息挂起中断
}/* CAN配置 */
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);
}/* CAN发送消息 */
u8 CAN_SendTxMsg(CAN_TypeDef *CANxx, CanTxMsg *TxMessage)
{u8 mailbox = 3;if((mailbox = CAN_Transmit(CANxx, TxMessage)) != CAN_TxStatus_NoMailBox) // 调用库函数CAN_Transmit实现发送{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检查发送状态是否为完成。

应用层实现

我们设计一个非常简单的双机交互流程,如下:

#mermaid-svg-8O4ATX7Vj2oRp9ak {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-8O4ATX7Vj2oRp9ak .error-icon{fill:#552222;}#mermaid-svg-8O4ATX7Vj2oRp9ak .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8O4ATX7Vj2oRp9ak .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-8O4ATX7Vj2oRp9ak .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8O4ATX7Vj2oRp9ak .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8O4ATX7Vj2oRp9ak .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8O4ATX7Vj2oRp9ak .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8O4ATX7Vj2oRp9ak .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8O4ATX7Vj2oRp9ak .marker.cross{stroke:#333333;}#mermaid-svg-8O4ATX7Vj2oRp9ak svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8O4ATX7Vj2oRp9ak .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8O4ATX7Vj2oRp9ak text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-8O4ATX7Vj2oRp9ak .actor-line{stroke:grey;}#mermaid-svg-8O4ATX7Vj2oRp9ak .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-8O4ATX7Vj2oRp9ak .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-8O4ATX7Vj2oRp9ak #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-8O4ATX7Vj2oRp9ak .sequenceNumber{fill:white;}#mermaid-svg-8O4ATX7Vj2oRp9ak #sequencenumber{fill:#333;}#mermaid-svg-8O4ATX7Vj2oRp9ak #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-8O4ATX7Vj2oRp9ak .messageText{fill:#333;stroke:#333;}#mermaid-svg-8O4ATX7Vj2oRp9ak .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8O4ATX7Vj2oRp9ak .labelText,#mermaid-svg-8O4ATX7Vj2oRp9ak .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-8O4ATX7Vj2oRp9ak .loopText,#mermaid-svg-8O4ATX7Vj2oRp9ak .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-8O4ATX7Vj2oRp9ak .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-8O4ATX7Vj2oRp9ak .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-8O4ATX7Vj2oRp9ak .noteText,#mermaid-svg-8O4ATX7Vj2oRp9ak .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-8O4ATX7Vj2oRp9ak .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8O4ATX7Vj2oRp9ak .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8O4ATX7Vj2oRp9ak .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8O4ATX7Vj2oRp9ak .actorPopupMenu{position:absolute;}#mermaid-svg-8O4ATX7Vj2oRp9ak .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-8O4ATX7Vj2oRp9ak .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8O4ATX7Vj2oRp9ak .actor-man circle,#mermaid-svg-8O4ATX7Vj2oRp9ak line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-8O4ATX7Vj2oRp9ak :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 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; // ID掩码setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, &filters, sizeof(filters)); // 设置接收过滤sendframe.can_id = 0x123; // 帧ID为0x123(标准帧)sendframe.can_dlc = 8; // 一次发送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);elseprintf("标准帧 <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");//usleep(5000);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();//printf("CAN_Config end\n");threadCreate();//printf("threadCreate end\n");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配置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;//printf("send from mailbox:%d\r\n", ret);flag = 0; // 等待下一次消息到来}}
}

结果

两边编译成功后烧写到各自板子中,启动IMX6侧的应用进程,可以看到STM32侧打印的串口消息和IMX6侧进程打印的接收消息如下。可以看出STM32成功接收到了ID为0x123的标准数据帧,每次接收8字节0x01到0x08,IMX6ULL侧成功接收到了ID为0x456的标准数据帧,每次接收8字节:0x78、0x89、0x9a、0xab、0xbc、0xcd、0xde和0xef。

IMX6ULL与STM32F103的CAN通信实现相关推荐

  1. STM32F103单片机modbus通信示例

    前两天在研究STM32F103单片机的串口空闲中断时,突然想起来Modbus通信非常适合用空闲中断来处理.先看看Modbus RTU模式下的通信规范. 可以看到Modbus RTU通信模式下,数据的开 ...

  2. 对于威纶通MT8071ip与正点原子stm32f103的modbus通信,如何接线和配置

    1.威纶通与正点原子stm32f103的接线关系:威纶通的针脚1接stm32f103开发板的RS485的B端口,针脚2接RS485的A端口. 2.如果是威纶通接usb转串口485的接口,那就是针脚1接 ...

  3. NBIOT专栏之BC28与STM32F103单片机串口通信连接阿里云

    系列文章目录 创思通信BC28核心板+STM32F系列单片机开发记录博客 1.测试BC28模块 2.串口助手MQTT连接阿里云物联网平台接发数据 前言 前面记录了BC28使用串口调试助手连接阿里云的过 ...

  4. STM32F103单片机串口通信带奇偶校验位

    在调试Modubus协议时需要用到串口的奇偶校验位,但是平时用串口时很少用到奇偶校验位,网上搜相关的历程也很少,将自己写的串口奇偶校验代码分享上来方便以后查阅. 如果用库函数的话,添加奇偶检验位就很简 ...

  5. STM32F103移植uCOS-III

    本文主要叙述使用STM32F103C8T6移植uCOSIII操作系统,构建3个任务,以1s和3s洲际对LED进行点亮-熄灭控制,以2s周期通过串口发送"hello uc/OS! 欢迎来到RT ...

  6. 单片机串口实现字符串命令解析

    通常情况下串口通信用的大多数都是用十六进制数据来传输指令,比如最常见的modbus的通信,如读保持寄存器指令:01 03 00 00 00 01 84 0A,这种十六进制的指令在这里就不讨论了.想要详 ...

  7. STM32F103ZE驱动PMW3901光流模块

    本文将会简单的介绍如何使用STM32F103ZE驱动PMW3901光流模块,使用标准库. 所用材料如下 一块 STM32F103最小系统板以及一个 PMW3901光流模块 通过查阅PMW3901的数据 ...

  8. 电路设计100个小技巧

    100个电路设计小技巧 非常感谢Robert Feranec在谷歌上分享的硬件设计技巧,因为国内的你很可能访问不了,所以我将这些小技巧整理下来,并且称之为"硬件设计黄金守则100条" ...

  9. 结合原理图关于STM32后期例程的更新说明

    十一以来工作突然忙碌起来不夸张地说连续几周从周一干到周天,因为赶到项目交付,所以博客更新变慢,感谢粉丝和订阅朋友们支持,但这段时间笔者依然在准备,依然在学习,工科生读文学书少,但不由地想起了那句名言: ...

最新文章

  1. Lesson 16.2 图像的基本操作
  2. 2018/3/18 noip模拟赛 20分
  3. 经典C语言程序100例之十七
  4. 【转载】solr教程,值得刚接触搜索开发人员一看
  5. HDU4417 Super Mario 主席树
  6. Spring注释,我从来没有机会使用第1部分:@primary
  7. kadane算法_使用KADANE的算法求最大子阵列和
  8. 惹毛了老婆后,老王居然本能地想按Ctrl+Z...
  9. 【AI视野·今日NLP 自然语言处理论文速览 第十八期】Fri, 2 Jul 2021
  10. Java记录 -45- List的toString方法
  11. RMAN CATALOG命令手动注册磁带库中的备份片
  12. java-通过JDBC操作数据库
  13. No project is easy, am I right?
  14. 小米无线网卡linux驱动下载,小米随身WiFi,Linux下AP热点驱动(开源)
  15. windows下面刷新dns
  16. MathType输入花体字
  17. codeforces 1293 A. ConneR and the A.R.C. Markland-N(思维)
  18. Excel编程环境搭建
  19. 2021最新qq域名检测接口
  20. 【Git配置技巧】01. 配置文件git config介绍

热门文章

  1. android 带刻度的滑动条_Android实现滑动刻度尺效果
  2. Avaya CEO:愿意与思科、微软共享客户
  3. php狼人杀,《狼人杀专业术语》 一秒变成狼圈人!
  4. mt8788 android 9.0 通过按键来打开APK
  5. 使用教育网邮箱学生验证Microsoft Imagine 微软开发者 获取window server 2016正版密钥教程
  6. 临时起搏器测试----概念梳理
  7. 限制input框只能输入0~100的正整数
  8. python标记_python关于信息标记的三种形式
  9. 通过ssh隧道连接mysql
  10. eclipse 启动 tomcat htt://localhost:8080 不能访问