23、CAN总线与UART中断驱动接收的综合解析

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中断驱动接收的原理和应用,在实际开发中能够灵活运用这些技术,实现高效、稳定的数据通信。

内容概要:本文介绍了一套针对智能穿戴设备的跑步/骑行轨迹记录系统实战方案,旨在解决传统运动APP存在的定位漂移、数据断层和路径分析单一等问题。系统基于北斗+GPS双模定位、惯性测量单元(IMU)和海拔传感器,实现高精度轨迹采集,并通过卡尔曼滤波算法修正定位误差,在信号弱环境下利用惯性导航补位,确保轨迹连续性。系统支持跑步骑行两种场景的差异化功能,包括实时轨迹记录、多维度路径分析(如配速、坡度、能耗)、数据可视化(地图标注、曲线图、3D回放)、异常提醒及智能优化建议,并可通过蓝牙/Wi-Fi同步数据至手机APP,支持社交分享专业软件导出。技术架构涵盖硬件层、设备端手机端软件层以及云端数据存储,强调低功耗设计用户体验优化。经过实测验证,系统在定位精度、续航能力和场景识别准确率方面均达到预期指标,具备良好的实用性和扩展性。; 适合人群:具备一定嵌入式开发或移动应用开发经验,熟悉物联网、传感器融合数据可视化的技术人员,尤其是从事智能穿戴设备、运动健康类产品研发的工程师和产品经理;也适合高校相关专业学生作为项目实践参考。; 使用场景及目标:① 开发高精度运动轨迹记录功能,解决GPS漂移断点问题;② 实现跑步骑行场景下的差异化数据分析个性化反馈;③ 构建完整的“终端采集-手机展示-云端存储”系统闭环,支持社交互动商业拓展;④ 掌握低功耗优化、多源数据融合、动态功耗调节等关键技术在穿戴设备中的落地应用。; 阅读建议:此资源以真实项目为导向,不仅提供详细的技术实现路径,还包含硬件选型、测试验证商业扩展思路,建议读者结合自身开发环境,逐步实现各模块功能,重点关注定位优化算法、功耗控制策略跨平台数据同步机制的设计调优。
内容概要:《QTools_V4.6.1用户手册》详细介绍了一款专为AutoCAD及CASS设计的辅助插件,涵盖测绘、设计等多个领域,提供超过400项实用功能。主要包括拓扑检查(如碎线、碎面、短边、弧段、锐角等检查)、图形文字处理工具(如批量插图、文字对齐、编号、合并、替换等)、测绘专用工具(如断面、高程点、等高线、三角网处理)、以及图纸管理功能(如拆分、合并、解密、批量修改)等。插件支持云授权和加密锁两种激活方式,兼容AutoCAD 2004–2026及各版本CASS,并提供侧边栏、菜单栏、自定义命令等多种操作方式,同时具备自动更新性能检测功能。; 适合人群:从事测绘、地理信息、建筑设计等相关领域的技术人员,熟悉AutoCAD/CASS操作,具备一定工程制图经验的从业人员。; 使用场景及目标:①用于地形图、地籍图、宗地图等专业图纸的自动化处理质量检查;②提升CAD绘图效率,实现批量操作、数据提取、格式转换、拓扑修复等任务;③支持测绘项目中的断面绘制、高程分析、坐标展点、土方计算等核心流程;④解决图纸编辑受限、字体缺失、块无法分解等问题。; 阅读建议:建议结合实际项目操作手册中的功能命令,优先掌握常用快捷指令(如qq、tp、dm、gcd等),并利用“功能搜索”快速定位工具。使用前确保正确加载插件并完成授权,遇到问题可参考“常见问题”章节进行排查。定期关注更新内容以获取新功能和优化体验。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值