CAN总线与UART中断驱动接收的综合解析
1. CAN总线软件相关内容
1.1 CAN过滤器配置
当掩码
0x001
应用于一个ID时,如果ID为奇数,则匹配
0x001
。但无论ID经过
0x001
掩码处理后是什么,都不会匹配给定值
0x010
,这是禁用第二个未使用过滤器的一种方式。配置后,消息总是奇数或偶数,并会进入CAN外设FIFO接收队列之一。此外,指定过滤器还有其他可能,如使用32位值来比较扩展ID值。
1.2 CAN接收中断
每个CAN FIFO都有自己的ISR,这允许为每个FIFO队列分配不同的中断优先级。在示例中,两个FIFO处理方式相同,相关中断服务例程代码如下:
// Listing 19-2. The CAN Receive Interlude ISRs
0058: void
0059: usb_lp_can_rx0_isr(void) {
0060: can_rx_isr(0,CAN_RF0R(CAN1)&3);
0061: }
0067: void
0068: can_rx1_isr(void) {
0069: can_rx_isr(1,CAN_RF1R(CAN1)&3);
0070: }
CAN_RF0R()
和
CAN_RF1R()
宏用于确定FIFO队列的长度。通用的CAN接收ISR代码如下:
// Listing 19-3. The Common CAN Receive ISR
0029: static void
0030: can_rx_isr(uint8_t fifo,unsigned msgcount) {
0031: struct s_canmsg cmsg;
0032: bool xmsgidf, rtrf;
0033:
0034: while ( msgcount-- > 0 ) {
0035: can_receive(
0036: CAN1,
0037: fifo, // FIFO # 1
0038: true, // Release
0039: &cmsg.msgid,
0040: &xmsgidf, // true if msgid is extended
0041: &rtrf, // true if requested transmission
0042: (uint8_t *)&cmsg.fmi, // Matched filter index
0043: &cmsg.length, // Returned length
0044: cmsg.data,
0045: NULL); // Unused timestamp
0046: cmsg.xmsgidf = xmsgidf;
0047: cmsg.rtrf = rtrf;
0048: cmsg.fifo = fifo;
0049: // If the queue is full, the message is lost
0050: xQueueSendToBackFromISR(canrxq,&cmsg,NULL);
0051: }
0052: }
代码的一般流程如下:
1. 接收消息(第35 - 45行)。
2. 将消息排队到FreeRTOS队列
canrxq
(第50行)。
3. 重复上述步骤直到没有更多消息(第34行)。
1.3 消息结构
相关消息结构定义如下:
// Listing 19-4. Message Structures Found in canmsgs.h
0020: struct s_canmsg {
0021: uint32_t msgid; // Message ID
0022: uint32_t fmi; // Filter index
0023: uint8_t length; // Data length
0024: uint8_t data[8]; // Received data
0025: uint8_t xmsgidf : 1; // Extended message flag
0026: uint8_t rtrf : 1; // RTR flag
0027: uint8_t fifo : 1; // RX Fifo 0 or 1
0028: };
0029:
0030: enum MsgID {
0031: ID_LeftEn = 100, // Left signals on/off (s_lamp_en)
0032: ID_RightEn, // Right signals on/off (s_lamp_en)
0033: ID_ParkEn, // Parking lights on/off (s_lamp_en)
0034: ID_BrakeEn, // Brake lights on/off (s_lamp_en)
0035: ID_Flash, // Inverts signal bulb flash
0036: ID_Temp, // Temperature
0037: ID_HeartBeat = 200, // Heartbeat signal (s_lamp_status)
0038: ID_HeartBeat2 // Rear unit heartbeat
0039: };
0040:
0041: struct s_lamp_en {
0042: uint8_t enable : 1; // 1==on, 0==off
0043: uint8_t reserved : 1;
0044: };
0045:
0046: struct s_temp100 {
0047: int celciusx100; // Degrees Celcius x 100
0048: };
0049:
0050: struct s_lamp_status {
0051: uint8_t left : 1; // Left signal on
0052: uint8_t right : 1; // Right signal on
0053: uint8_t park : 1; // Parking lights on
0054: uint8_t brake : 1; // Brake lines on
0055: uint8_t flash : 1; // True for signal flash
0056: uint8_t reserved : 4;
0057: };
消息通过消息ID指示,不同的消息ID代表不同的消息类型。例如,
ID_LeftEn
、
ID_RightEn
、
ID_ParkEn
和
ID_BrakeEn
表示灯控制消息,
ID_Temp
用于请求和接收温度。
消息接收后,先将其存储在
s_canmsg
结构体中,部分成员需要先在局部变量中接收再复制到结构体中。最后,使用
xQueueSendToBackFromISR()
将结构体复制到队列。
1.4 应用接收与消息处理
ISR将数据消息
s_canmsg
排队后,需要有任务从队列中取出消息。在
canmsgs.c
模块中有相关任务定义:
0076: static void
0077: can_rx_task(void *arg __attribute((unused))) {
0078: struct s_canmsg cmsg;
0079:
0080: for (;;) {
0081: if ( xQueueReceive(canrxq,&cmsg,portMAX_DELAY) == pdPASS )
0082: can_recv(&cmsg);
0083: }
0084: }
当
can_recv()
函数被调用时,应用程序会处理接收到的CAN消息。相关代码如下:
// Listing 19-5. Processing a Received CAN Message in the Application (from rear.c)
0119: void
0120: can_recv(struct s_canmsg *msg) {
0121: union u_msg {
0122: struct s_lamp_en lamp;
0123: } *msgp = (union u_msg *)msg->data;
0124: struct s_temp100 temp_msg;
0125:
0126: gpio_toggle(GPIO_PORT_LED,GPIO_LED);
0127:
0128: if ( !msg->rtrf ) {
0129: // Received commands:
0130: switch ( msg->msgid ) {
0131: case ID_LeftEn:
0132: case ID_RightEn:
0133: case ID_ParkEn:
0134: case ID_BrakeEn:
0135: case ID_Flash:
0136: lamp_enable((enum MsgID)msg->msgid,msgp->lamp.enable);
0137: break;
0138: default:
0139: break;
0140: }
0141: } else {
0142: // Requests:
0143: switch ( msg->msgid ) {
0144: case ID_Temp:
0145: temp_msg.celciusx100 = degrees_C100();
0146: can_xmit(ID_Temp,false,false,sizeof temp_msg,&temp_msg);
0147: break;
0148: default:
0149: break;
0150: }
0151: }
0152: }
根据
rtrf
标志的值,该函数处理不同类型的消息。如果
rtrf
为
false
,处理接收到的命令;如果为
true
,处理请求。
1.5 发送CAN消息
发送CAN消息可以调用
libopencm3
例程
can_transmit()
,相关代码如下:
0018: void
0019: can_xmit(uint32_t id,bool ext,bool rtr,uint8_t length,void *data) {
0020:
0021: while ( can_transmit(CAN1,id,ext,rtr,length,(uint8_t*)data) == -1 )
0022: taskYIELD();
0023: }
如果发送失败(返回 -1),调用
taskYIELD()
共享CPU时间。需要注意的是,
can_transmit()
成功返回并不意味着消息已发送,可能由于总线繁忙或消息优先级低而延迟发送。
2. UART中断驱动接收相关内容
2.1 背景
STM32 USART外设包含一个数据寄存器,用于保存最后接收到的8或9位字。如果在接收到下一个字之前CPU未读取该数据,会发生溢出,数据会被覆盖。在低波特率下,CPU通常能及时接收数据,但在高波特率或软件延迟时,数据可能丢失。
解决方案是让外设数据加载到保持寄存器时触发CPU中断,调用中断服务例程(ISR)读取数据并存储到内存,ISR返回后恢复原代码执行。
2.2 中断服务例程(ISR)的限制
-
异步调用
:ISR可能在关键函数(如
malloc())执行过程中被调用,因此应避免调用非递归函数。 - 快速返回 :ISR应尽快返回,避免影响其他中断和系统,且不能阻塞执行。
- 栈使用 :ISR使用当前运行任务的栈,应使用最小栈空间,所有任务需预留足够额外栈空间。
- 全局变量访问 :ISR访问全局变量时需谨慎,避免关键部分,可使用FreeRTOS队列解决共享数据访问问题。
2.3 程序设计
演示程序的
main()
函数进行所有设置并启动FreeRTOS内核调度器:
// Listing 20-1. The main() Setup Function
0205: int
0206: main(void) {
0207:
0208: rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]);
0209:
0210: init_clock();
0211: init_gpio();
0212: init_usart();
0213:
0214: xTaskCreate(main_task,"MAIN",100,NULL,configMAX_PRIORITIES-1,NULL);
0215: xTaskCreate(tx_task,"UARTTX",100,NULL,configMAX_PRIORITIES-1,NULL);
0216: vTaskStartScheduler();
0217:
0218: for (;;); // Don't go past here
0219: return 0;
0220: }
其中,
rcc_clock_setup_pll()
建立CPU时钟,
init_clock()
初始化其他时钟,
init_gpio()
初始化GPIO端口,
init_usart()
初始化USART外设。创建主任务和发送任务后启动调度器。
2.4 时钟、GPIO和UART初始化
- 时钟初始化 :
// Listing 20-2. Clock Initialization
0053: static void
0054: init_clock(void) {
0055:
0056: // Clock for GPIO port A: GPIO_USART1_TX
0057: rcc_periph_clock_enable(RCC_GPIOA);
0058: rcc_periph_clock_enable(RCC_GPIOC);
0059: rcc_periph_clock_enable(RCC_USART1);
0060: }
初始化GPIOA、GPIOC和USART1的时钟。
-
GPIO初始化
:
// Listing 20-3. GPIO Initialization
0118: static void
0119: init_gpio(void) {
0120:
0121: // gpio for LED
0122: gpio_set_mode(
0123: GPIOC,
0124: GPIO_MODE_OUTPUT_2_MHZ,
0125: GPIO_CNF_OUTPUT_PUSHPULL,
0126: GPIO13);
0127: gpio_set(GPIOC,GPIO13); // LED off
0128: }
将GPIOC的GPIO13配置为输出并初始化为高电平,关闭LED。
-
UART初始化
:
// Listing 20-4. UART Initialization
0026: static QueueHandle_t uart_txq, // TX queue
0027: uart_rxq; // RX queue
0085: static void
0086: init_usart(void) {
0087:
0088: // Create queues
0089: uart_txq = xQueueCreate(256,sizeof(char));
0090: uart_rxq = xQueueCreate(256,sizeof(char));
0091:
0092: // Enable Interrupt controller
0093: nvic_enable_irq(NVIC_USART1_IRQ);
0094:
0095: gpio_set_mode(GPIOA,
0096: GPIO_MODE_OUTPUT_50_MHZ,
0097: GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,
0098: GPIO_USART1_TX);
0099: gpio_set_mode(GPIOA,
0100: GPIO_MODE_INPUT,
0101: GPIO_CNF_INPUT_FLOAT,
0102: GPIO_USART1_RX);
0103:
0104: usart_set_baudrate(USART1,115200);
0105: usart_set_databits(USART1,8);
0106: usart_set_stopbits(USART1,USART_STOPBITS_1);
0107: usart_set_mode(USART1,USART_MODE_TX_RX);
0108: usart_set_parity(USART1,USART_PARITY_NONE);
0109:
0110: usart_enable_rx_interrupt(USART1);
0111: usart_enable(USART1);
0112: }
创建发送和接收队列,启用中断控制器,配置GPIO端口,设置波特率等UART参数,启用接收中断并启动UART外设。
2.5 中断服务例程
libopencm3
库要求UART中断服务例程名为
usart1_isr()
:
// Listing 20-5. Interrupt Service Routine
0068: static char ch;
0069:
0070: void
0071: usart1_isr(void) {
0072: while ( usart_get_flag(USART1,USART_SR_RXNE) ) {
0073: // Data received
0074: ch = usart_recv(USART1);
0075:
0076: // Push to queue
0077: xQueueSendFromISR(uart_rxq,&ch,NULL);
0078: }
0079: }
检查是否有数据接收,若有则读取数据并推送到
uart_rxq
队列。在高波特率下,可能在处理一个字节时又接收到新字节,使用
while
循环立即处理。
综上所述,CAN总线和UART中断驱动接收在嵌入式系统中都有重要应用,通过合理配置和编程,可以实现高效、稳定的数据通信。
CAN总线与UART中断驱动接收的综合解析(续)
3. 技术要点总结与对比
3.1 CAN总线技术要点
-
过滤器配置
:通过掩码
0x001可对ID进行匹配过滤,还可使用32位值比较扩展ID值。 -
接收中断
:每个CAN FIFO有独立的ISR,可分配不同中断优先级。接收消息后,先存储在
s_canmsg结构体,再通过xQueueSendToBackFromISR()存入队列。 -
消息处理
:根据消息ID和
rtrf标志处理不同类型消息,如灯控制消息和温度请求消息。 -
消息发送
:调用
can_transmit()发送消息,发送失败时调用taskYIELD()共享CPU时间。
3.2 UART中断驱动接收技术要点
- 背景问题 :STM32 USART外设数据寄存器可能因溢出导致数据丢失,尤其在高波特率或软件延迟时。
- ISR限制 :异步调用,需避免非递归函数;快速返回,不能阻塞执行;合理使用栈空间,预留足够额外栈空间;谨慎访问全局变量,可使用FreeRTOS队列解决共享数据访问问题。
-
程序设计
:通过
main()函数进行初始化和任务创建,启动FreeRTOS内核调度器。 - 初始化步骤 :包括时钟、GPIO和UART初始化,创建发送和接收队列,启用中断控制器,配置相关参数。
-
中断服务例程
:
usart1_isr()检查数据接收情况,将接收到的数据推送到uart_rxq队列。
3.3 两者对比
| 技术类型 | 数据处理方式 | 应用场景 | 中断处理特点 |
|---|---|---|---|
| CAN总线 | 基于消息ID和标志处理不同类型消息,使用队列存储消息 | 适用于汽车电子、工业控制等多节点通信场景 | 每个FIFO有独立ISR,可分配不同优先级 |
| UART中断驱动接收 | 接收到数据后立即推送到队列,通过队列进行数据传递 | 适用于串行通信,如与外部设备的数据交互 | 单个中断服务例程处理数据接收 |
4. 实际应用中的注意事项
4.1 CAN总线应用注意事项
- 消息优先级 :消息ID越低,优先级越高,在仲裁中更易获胜。在设计系统时,需根据实际需求合理分配消息ID。
-
发送延迟
:
can_transmit()成功返回不代表消息已发送,可能因总线繁忙或消息优先级低而延迟。可通过设置重试机制或调整消息优先级来解决。 - 队列管理 :当队列满时,消息可能丢失。可根据实际情况调整队列大小,或在队列满时采取相应的处理策略。
4.2 UART中断驱动接收应用注意事项
- 波特率选择 :根据实际通信需求选择合适的波特率,高波特率可能导致数据丢失,需确保系统有足够的处理能力。
- ISR优化 :ISR应尽量简洁,避免复杂操作,以减少对系统的影响。可将一些处理任务放到主任务中执行。
- 队列溢出处理 :在高波特率下,队列可能会满。可通过动态调整队列大小或在队列满时采取丢弃旧数据等策略。
5. 操作流程总结
5.1 CAN总线操作流程
graph TD;
A[初始化CAN外设] --> B[配置过滤器];
B --> C[启用CAN接收中断];
C --> D[等待消息接收];
D --> E{是否有消息};
E -- 是 --> F[接收消息到s_canmsg结构体];
F --> G[将消息存入队列];
G --> D;
E -- 否 --> D;
H[需要发送消息] --> I[调用can_transmit()发送消息];
I --> J{发送是否成功};
J -- 否 --> K[调用taskYIELD()];
K --> I;
J -- 是 --> L[继续其他操作];
5.2 UART中断驱动接收操作流程
graph TD;
A[初始化UART外设] --> B[创建发送和接收队列];
B --> C[启用UART接收中断];
C --> D[等待数据接收];
D --> E{是否有数据接收};
E -- 是 --> F[读取数据];
F --> G[将数据推送到接收队列];
G --> D;
E -- 否 --> D;
H[需要发送数据] --> I[将数据存入发送队列];
I --> J[通过UART发送数据];
6. 总结与展望
CAN总线和UART中断驱动接收在嵌入式系统中各有优势,CAN总线适用于多节点通信,UART中断驱动接收适用于串行通信。通过合理配置和编程,可以实现高效、稳定的数据通信。
未来,随着嵌入式系统的不断发展,对数据通信的要求也越来越高。CAN总线可能会在自动驾驶、智能交通等领域得到更广泛的应用,而UART中断驱动接收可能会在物联网设备、传感器数据采集等方面发挥更大的作用。同时,对ISR的优化和队列管理等技术也将不断发展,以提高系统的性能和可靠性。
希望通过本文的介绍,能帮助读者更好地理解CAN总线和UART中断驱动接收的原理和应用,在实际开发中能够灵活运用这些技术,实现高效、稳定的数据通信。
超级会员免费看
82

被折叠的 条评论
为什么被折叠?



