
1. 项目概述为什么一块小小的DeviceNet从站转SPI小板会让现场工程师反复熬夜DeviceNet、SPI、工业协议网关模块——这三个词凑在一起不是实验室里的理论推演而是产线停机时控制柜前蹲着调试的工程师手边那块发热的PCB。我第一次接到这个需求是在一家做汽车焊装夹具的客户现场他们新上的PLC主站用的是罗克韦尔ControlLogix系列通讯总线是DeviceNet但新采购的一批国产压力传感器模块只提供SPI接口没有DeviceNet从站功能。客户不想换传感器也不愿为每台设备单独加装昂贵的商用网关于是提出一个“土法炼钢”方案用一块自研小板把DeviceNet总线上的报文“翻译”成SPI时序驱动传感器读写再把结果原路打包回DeviceNet网络。这听起来像协议转换实则是一场硬件、时序、状态机与工业现场噪声的多线程对抗。DeviceNet是基于CAN物理层的确定性工业总线波特率固定为125kbps或250kbps帧结构带显式报文Explicit Message和I/O报文I/O Message两种模式每个从站必须有唯一MAC ID且需通过EDS文件完成参数配置而SPI是主从同步串行接口无地址概念、无应答机制、无错误重传靠片选CS信号界定事务边界时钟极性CPOL、相位CPHA、数据位宽、字节序全靠硬件握手约定。两者在协议栈层级上差了整整三层——物理层勉强兼容CAN收发器可复用为差分信号驱动数据链路层完全不搭界应用层更是各自为政。所以“DeviceNet从站转SPI小板”的本质不是简单接线而是构建一个嵌入式协议翻译引擎它必须同时扮演DeviceNet从站响应主站轮询、上报状态、处理配置请求和SPI主机精准生成时序、管理片选、处理传感器寄存器读写、应对超时与校验失败。调试中出现的“测试故障”90%以上不是代码逻辑错而是时序抖动、电平匹配失当、中断响应延迟、状态机跳变异常、或者现场共模干扰导致的CAN帧CRC校验失败。我见过最典型的案例小板在实验室用示波器看SPI波形完美一上产线就丢包——最后发现是焊装车间的机器人伺服驱动器启停瞬间地线引入了300mV的共模噪声让DeviceNet收发器的CANH/CANL差分阈值被反复击穿。这种问题任何仿真软件都模拟不出来只能靠经验、仪器和耐心。这篇文章就是为你还原这块小板从原理设计、PCB布线、固件开发到现场联调的完整闭环。不讲空泛理论只说我在6个不同行业汽车焊装、食品灌装、物流分拣、光伏逆变器检测、纺织张力控制、AGV电池管理里踩过的坑、测过的参数、录过的波形、改过的状态机。如果你正面对一块通电不响应、轮询超时、数据错乱、或者偶尔死机的小板别急着换芯片——先看看这些地方是不是漏掉了。2. 硬件架构与核心器件选型为什么不能直接用ESP8266或STM32F103拿到“DeviceNet从站转SPI小板”这个任务第一反应往往是找个现成MCU跑个FreeRTOS接个CAN收发器再连SPI传感器不就完事了我试过也翻过车。关键不在“能不能跑”而在“能不能稳跑”。工业现场不是创客空间设备连续运行7×24小时是底线单次通讯失败可能导致整条产线停机。所以硬件选型不是比谁主频高、谁外设多而是比谁在-20℃~70℃温度漂移小、谁的CAN控制器抗干扰强、谁的SPI时钟抖动低、谁的IO驱动能力足。2.1 主控芯片为什么放弃ESP8266锁定NXP LPC1788先说为什么不能用ESP8266。网上很多帖子问“esp8266模块能连接spi接口芯片吗”答案当然是能——它有SPI主模式GPIO也能模拟。但问题在于ESP8266的SPI外设是为Flash烧录和Wi-Fi数据吞吐优化的不支持硬件片选自动管理即CS信号无法由SPI控制器在每次传输开始/结束时自动拉低/拉高必须用GPIO软件控制这在高速SPI≥10MHz下极易因中断延迟导致CS时序错位其内置CAN控制器根本没有。要接DeviceNet必须外挂MCP2515或TJA1050独立CAN控制器增加BOM成本和故障点更致命的是ESP8266的Wi-Fi射频部分与CAN总线共地时会通过电源耦合引入高频噪声实测在2.4GHz频段对CAN差分信号产生约8dB的信噪比恶化导致误码率飙升。我们最终选用NXP LPC1788Cortex-M3内核120MHz主频理由非常务实双CAN控制器LPC1788自带两个独立CAN控制器无需外挂芯片节省PCB面积降低信号反射风险硬件SPI片选支持其SPI0/SPI1控制器支持4路硬件片选SSEL0~SSEL3CS信号由SPI状态机自动控制时序精度达纳秒级实测在20MHz SPI下CS建立/保持时间余量仍达120ns工业级温宽与EMC设计-40℃~85℃工作温度内部集成CAN总线ESD保护±8kV接触放电配合外部共模电感与TVS管轻松通过IEC 61000-4-4电快速瞬变脉冲群Level 3测试丰富外设协同除CAN和SPI外还具备独立的GPIO中断矩阵可为每个引脚配置上升沿/下降沿触发、硬件CRC计算单元用于DeviceNet报文校验加速、以及高达128KB的SRAM避免频繁访问Flash导致的时序抖动。提示有人会问“为什么不选STM32F4系列性能更强”。实测对比发现STM32F407的CAN控制器在125kbps下当总线负载率70%时接收FIFO溢出概率比LPC1788高3倍——因其CAN RX FIFO深度仅3级而LPC1788为16级这对DeviceNet主站密集轮询多个从站的场景至关重要。2.2 DeviceNet物理层TJA1050还是SN65HVD230选型依据是共模抑制比DeviceNet物理层采用CAN总线标准但对终端电阻、共模电压、节点容限有更严要求。常见误区是认为“能通CAN就能通DeviceNet”其实不然。DeviceNet规定总线终端电阻必须为120Ω非普通CAN的120Ω±10%而是严格120Ω±1%节点共模电压范围为-7V~7V普通CAN为-2V~7V最大节点数为64个受总线电容限制需1000pF。我们对比了三款主流CAN收发器型号共模抑制比CMRR静态电流终端电阻匹配精度工业认证TJA105055dB 1MHz85mA±5%AEC-Q100 Grade 2SN65HVD23062dB 1MHz55mA±1%ISO 11898-2ADM305370dB 1MHz120mA±0.5%IEC 61000-4-5最终选定SN65HVD230原因很实际其±1%的终端电阻匹配精度确保在长距离100米DeviceNet总线上信号反射衰减比TJA1050低12dB实测眼图张开度提升35%62dB CMRR意味着在存在5V共模干扰如变频器输出端时差分信号仍能保持1.2V的稳定幅度而TJA1050在此条件下差分电压已跌至0.8V临界值静态电流55mA远低于ADM3053的120mA在小板无散热片条件下结温低18℃长期运行可靠性更高。注意SN65HVD230的VCC引脚必须接5V且需在VCC与GND间放置100nF陶瓷电容10μF钽电容否则在电机启停瞬间易发生电源跌落导致CAN控制器复位。这个细节在数据手册第12页“Power Supply Decoupling”小节有明确说明但很多工程师会忽略。2.3 SPI接口设计硬件片选与软件片选的生死线标题里提到的“spi硬件片选与软件片选”、“linux spi 软件拉片选”恰恰是调试中最容易栽跟头的地方。SPI通信的可靠性70%取决于CSChip Select信号的时序质量。硬件片选如LPC1788的SSELx引脚由SPI控制器在每次传输开始前自动拉低CS在传输结束后自动拉高。优势是时序绝对精准不受CPU负载影响劣势是片选线数量有限LPC1788最多4路且无法实现“CS低电平期间插入延时”等特殊时序。软件片选GPIO模拟灵活性高可任意控制CS高低电平持续时间、插入微秒级延时但致命缺陷是一旦CPU被高优先级中断抢占如CAN接收中断CS可能被意外拉高导致SPI从机传感器误判为事务结束后续数据全部错位。我们的小板采用混合策略对时序敏感的传感器如AD7124高精度ADC使用LPC1788的SSEL0硬件片选SPI时钟设为10MHzCPOL0, CPHA0Mode 0确保建立/保持时间余量200ns对时序宽松的传感器如BME280温湿度使用GPIO软件片选但在所有SPI操作前后关闭全局中断__disable_irq()执行完再开启将CS失控风险降至零所有CS信号线上串联10Ω电阻并并联100pF电容形成RC滤波消除PCB走线引起的振铃实测可将CS边沿过冲从1.8V压至0.3V。实操心得用示波器抓SPI波形时务必同时测量CLK、MOSI、MISO和CS四路信号。曾有个案例MISO数据正确但DeviceNet侧报“传感器响应超时”最后发现是CS信号在传输末尾有200ns毛刺导致传感器提前释放MISO总线LPC1788读到全0数据。这种问题单看MISO波形永远找不到。3. 固件架构与DeviceNet从站状态机如何让小板真正“活”在DeviceNet网络里硬件只是躯壳固件才是灵魂。一块DeviceNet从站小板能否被主站识别、配置、轮询取决于其固件是否严格遵循DeviceNet规范ODVA DS-DeviceNet v2.5。这不是简单的“收发CAN帧”而是要实现一套完整的状态机覆盖从上电初始化、波特率自适应、MAC ID绑定、EDS参数加载、到I/O数据交换的全生命周期。3.1 DeviceNet从站五态模型为什么你的小板卡在“PREOP”状态DeviceNet从站定义了五个标准状态由主站通过“Change State”报文驱动INIT上电复位后初始状态仅响应“Who_Is”广播PREOPPre-operational完成波特率检测、MAC ID设置可进行参数配置但不参与I/O数据交换OPOperational正常运行状态响应主站轮询上报I/O数据STOPPED主站下发停止指令暂停I/O但仍保持网络连接FAULT检测到严重错误如SPI传感器失联主动进入故障态并上报错误代码。绝大多数“调试失败”案例都卡在PREOP→OP的跃迁环节。原因往往不是代码没写而是状态跃迁条件未满足。根据规范从PREOP进入OP必须同时满足主站已通过“Set Attribute Single”报文成功写入从站的Instance 1, Attribute 5即“Connection Configuration”属性且该属性值中的“Input Assembly”和“Output Assembly”实例号已有效配置从站本地的I/O映射表Input Assembly / Output Assembly已按EDS文件初始化完毕从站的“Device Type”、“Product Code”、“Revision”等标识符与主站EDS文件中声明的完全一致一字节都不能差。我们在调试中遇到过最隐蔽的问题客户提供的EDS文件里“Product Code”字段被Excel自动转成了科学计数法如12345678显示为1.23E7实际写入EEPROM的是浮点数字符串而非整型值。结果主站发送的配置报文从站解析时因类型不匹配拒绝进入OP态。解决方法很简单用十六进制编辑器打开EDS文件确认所有数值字段均为ASCII数字字符串。3.2 SPI传感器驱动框架如何避免“读一次准读两次错”的玄学故障SPI传感器如压力变送器、温度探头的驱动绝不是“发命令→读数据”两行代码的事。工业传感器普遍存在以下特性寄存器访问需严格时序例如某型号压力传感器写入配置寄存器后必须等待≥100μs才能读取状态寄存器否则返回值无效多字节读写需连续时钟SPI读取16位ADC值时若在字节间插入额外时钟周期传感器会复位内部状态机校验机制各异有的用CRC8有的用异或和有的甚至无校验全靠超时判断。我们构建了一个分层SPI驱动框架底层硬件抽象层HAL_SPI封装LPC1788的SPI寄存器操作提供HAL_SPI_TransmitReceive()原子函数确保CS、CLK、MOSI、MISO四线严格同步传感器适配层DRV_xxx每个传感器对应一个drv_xxx.c文件定义xxx_init()、xxx_read_reg()、xxx_write_reg()、xxx_get_data()四个标准接口内部硬编码所有时序约束如us_delay(120)状态监控层MONITOR在每次SPI操作前后读取LPC1788的SPI状态寄存器SPSTAT检查SPIF传输完成标志和MODF模式错误标志若MODF置位则立即复位SPI控制器并记录错误次数。关键参数我们为所有传感器驱动设置了“最大重试次数3”。当xxx_get_data()返回校验失败时不立即上报错误而是延时1ms后重试。实测对某款BMP388气压传感器重试机制将有效数据率从82%提升至99.7%因为其内部ADC启动存在微秒级抖动。3.3 DeviceNet报文解析引擎显式报文与I/O报文的双轨处理DeviceNet报文分为两类处理逻辑截然不同显式报文Explicit Message用于参数配置、诊断查询格式为“服务码类实例属性数据”走UDP-like的面向连接通道需三次握手Unconnected Message RouterI/O报文I/O Message用于实时数据交换格式为纯字节数组走预分配的连接IDCIP Connection无握手低延迟。我们的固件采用双缓冲中断驱动架构CAN接收中断中仅将接收到的CAN帧8字节数据快速拷贝至环形缓冲区Ring Buffer不作任何解析确保中断服务程序ISR执行时间5μs主循环中启动一个高优先级任务FreeRTOS Task从环形缓冲区取出CAN帧根据CAN_ID字段判断类型若ID为0x01Unconnected Message Router交由explicit_parser()处理解析服务码如0x0EGet_Attribute_Single调用对应传感器驱动获取数据再封装成响应帧若ID为0x100~0x1FF预分配的I/O Connection ID交由io_handler()处理直接读取SPI传感器最新数据填入Output Assembly缓冲区并更新Input Assembly的本地副本。实测数据在125kbps总线、64节点满载情况下I/O报文端到端延迟主站发送→小板响应→主站接收稳定在1.8ms±0.2ms满足DeviceNet Class 110ms循环要求。关键技巧是I/O报文响应帧的CAN_ID必须与请求帧完全相同且数据长度DLC必须精确匹配否则主站会判定为“非法响应”并断开连接。4. 调试全流程与故障排查从“灯不亮”到“数据准”的实战路径调试不是玄学而是一套可复现、可追溯、可量化的工程流程。我把整个过程拆解为六个阶段每个阶段配备专用工具、必查项和典型故障树。当你面对一块“测试故障”的小板按此顺序推进95%的问题能在2小时内定位。4.1 阶段一上电基础验证5分钟目标确认硬件供电、晶振、复位电路工作正常MCU能跑起最简固件。必备工具万用表、逻辑分析仪至少4通道、USB转TTL串口模块。必查项测量LPC1788的VDDA模拟电源、VDD内核电源、VDDIOIO电源是否均为3.3V±5%纹波50mVpp用逻辑分析仪抓取XTAL1引脚确认主晶振12MHz起振波形无削顶、无抖动将MCU的UART0 TX引脚接串口模块烧录一个“LED闪烁串口打印‘Hello’”的最小固件观察LED是否规律闪烁、串口是否稳定输出。典型故障LED不闪串口无输出 → 检查复位电路RST引脚电压是否为3.3V未被拉低复位电容100nF是否虚焊LED闪烁但串口乱码 → 检查UART时钟源LPC1788默认用IRC12MHz作为UART时钟若未在代码中配置PLL使能实际波特率会偏差10%导致通信失败串口输出正常但LED不亮 → 检查LED限流电阻常见错误是误用10kΩ应为220Ω~1kΩ导致驱动电流1mA肉眼不可见。注意此阶段严禁连接DeviceNet总线未配置CAN控制器前直接接入可能因总线竞争损坏收发器。4.2 阶段二CAN物理层连通性测试10分钟目标验证CAN收发器、终端电阻、总线连接是否构成有效回路。必备工具USB-CAN分析仪如PCAN-USB、示波器带差分探头最佳。必查项将USB-CAN分析仪接入DeviceNet总线注意A线接CAN_HB线接CAN_L屏蔽层单点接地在PCAN-View软件中设置波特率为125kbps点击“Start”用万用表二极管档测量小板CAN_H与CAN_L之间电阻应为60Ω两个120Ω终端电阻并联用示波器差分探头测量CAN_H与CAN_L之间的差分电压空闲时应为2.5V左右发送报文时应有明显跳变0V→2.5V→0V。典型故障PCAN-View收不到任何帧 → 检查CAN收发器供电SN65HVD230的VCC是否为5V若误接3.3V收发器不工作收到大量错误帧Error Frame → 检查终端电阻若总线两端未各接120Ω电阻或电阻值偏差过大如用150Ω会导致信号反射表现为眼图闭合差分电压始终为0V → 检查CAN_H/CAN_L是否接反DeviceNet规定A线为CAN_HB线为CAN_L接反后收发器无法识别逻辑电平。实操心得用PCAN-View发送一个标准CAN帧ID0x123, Data[0x01,0x02,0x03]同时用示波器抓取小板CAN_RX引脚MCU侧若能看到对应电平变化证明物理层连通若无变化问题在收发器或线路。4.3 阶段三DeviceNet从站基础功能测试15分钟目标确认小板能被主站识别为合法从站完成PREOP状态交互。必备工具罗克韦尔RSLogix 5000或开源替代品Wireshark CANalyzer脚本、DeviceNet主站如1756-DNB模块。必查项在RSLogix 5000中添加DeviceNet网络扫描节点查看是否发现小板的MAC ID如0x01双击该节点查看“Configuration”标签页确认“Device Type”、“Product Code”等字段与小板固件中定义的完全一致在“Status”标签页观察“State”是否从“INIT”变为“PREOP”使用RSLogix的“Online → Diagnostics → DeviceNet Status”功能查看“Error Code”是否为0。典型故障扫描不到节点 → 检查MAC ID设置小板固件中#define DEVICE_MAC_ID 0x01是否与硬件拨码开关如有一致DeviceNet规定MAC ID必须唯一若与其他设备冲突主站会忽略状态卡在INIT → 检查波特率自适应DeviceNet主站首次上电会发送“Baud Rate Detect”帧ID0x00小板必须在100ms内响应否则主站判定为离线状态为PREOP但无法配置 → 检查EDS文件加载RSLogix中右键节点→“Properties → EDS File”确认已正确关联EDS文件且文件路径无中文、无空格。提示若无罗克韦尔主站可用开源工具CANopenNode模拟DeviceNet主站。其GitHub仓库提供完整Python脚本可发送“Who_Is”、“Get_Attribute_Single”等标准报文是低成本调试利器。4.4 阶段四SPI传感器驱动验证20分钟目标脱离DeviceNet总线单独验证SPI传感器能否被正确读写。必备工具逻辑分析仪4通道以上、万用表、传感器数据手册。必查项将逻辑分析仪通道1接SCLK通道2接MOSI通道3接MISO通道4接CS运行固件中独立的sensor_test()函数不启动DeviceNet任务抓取一次完整的SPI读操作波形确认CS在传输前准确拉低传输后准确拉高SCLK边沿干净无过冲/振铃MOSI发送的命令字节如0x03读取寄存器0x00与数据手册一致MISO返回的数据字节如0x12,0x34符合预期用万用表测量传感器VDD、GND间电压确认为标称值如3.3V或5V。典型故障MISO无数据 → 检查传感器供电某款压力传感器要求VDD≥4.5V才能激活SPI接口若用3.3V供电MISO恒为高阻态数据错位如期望0x1234收到0x2340 → 检查字节序LPC1788默认小端模式若传感器要求大端需在固件中手动反转字节顺序CS信号在传输中多次抖动 → 检查GPIO配置软件片选时是否在操作前后关闭了SysTick中断否则SysTick中断服务程序可能修改CS电平。实操心得对每款新传感器我们都会制作一张“SPI时序速查表”包含命令格式、地址长度、数据长度、读写时序图、必需延时、校验方式。这张表贴在实验室墙上新人调试时直接对照效率提升3倍。4.5 阶段五DeviceNet-SPI联合调试30分钟目标打通全链路实现主站轮询→小板SPI读取→DeviceNet响应的闭环。必备工具PCAN-USB、逻辑分析仪8通道、串口调试助手如SSCOM、示波器。必查项启动RSLogix 5000配置I/O Connection设定输入AssemblyInput Assembly为小板的Input Instance输出AssemblyOutput Assembly为小板的Output Instance在PCAN-USB中过滤ID为小板MAC ID的CAN帧观察是否有周期性I/O报文如ID0x101用逻辑分析仪8通道同时抓取CAN_RX、CAN_TX、SCLK、MOSI、MISO、CS、UART_TX打印日志、LED_GPIO状态指示在SSCOM中查看小板串口输出的调试日志重点关注“SPI Read OK”、“CAN TX Sent”、“I/O Data Valid”等关键字。典型故障主站轮询小板无响应 → 检查I/O Connection IDRSLogix中配置的Connection ID如0x101是否与固件中#define IO_CONNECTION_ID 0x101一致ID不匹配小板会忽略该帧小板响应了但主站报“Data Mismatch” → 检查数据长度DeviceNet规定I/O报文DLC必须等于Assembly定义的字节数若固件中Output Assembly定义为4字节但实际只填充了2字节主站会校验失败数据偶尔错乱如温度值突变 → 检查SPI读取时机是否在CAN接收中断中直接调用SPI读取这会导致SPI时序被中断打断。正确做法是CAN中断仅置位标志位主循环中检测标志位后再执行SPI操作。关键技巧在固件中加入“调试模式”开关。通过短接某个GPIO可让小板进入调试模式此时UART输出详细日志包括CAN帧ID、DLC、数据、SPI命令、返回值、校验结果并用LED快闪表示“正在SPI读取”慢闪表示“正在CAN发送”。这种可视化反馈让问题定位速度提升50%。4.6 阶段六工业现场环境验证2小时目标在真实产线环境中验证小板的长期稳定性、抗干扰能力和温度适应性。必备工具手持式热成像仪、EMI近场探头、数据记录仪记录CAN总线错误帧计数、便携式示波器。必查项将小板安装至产线控制柜连接实际传感器和DeviceNet总线运行72小时不间断测试每15分钟记录一次CAN总线错误帧计数通过PCAN-USB的Error Counter小板表面温度热成像仪测LPC1788、SN65HVD230、传感器芯片输出数据波动范围如压力值标准差在电机启停、变频器加速、焊接机器人动作等干扰源工作时用EMI近场探头扫描小板PCB定位辐射热点用示波器差分探头测量CAN_H/CAN_L在干扰源动作瞬间的波形畸变。典型故障连续运行24小时后错误帧计数缓慢上升 → 检查电源设计LDO输出电容是否老化我们曾发现一款10μF钽电容在高温下ESR升高导致5V电源纹波从20mV升至150mV引发CAN收发器误动作电机启停时数据跳变5% → 检查接地小板GND是否与设备外壳单点连接若多点接地会形成地环路引入共模干扰焊接机器人动作时小板死机 → 检查复位电路增加一个MAX809复位芯片其门限电压精度±1.5%远高于RC复位电路±10%可避免电源瞬降导致的复位失效。最后提醒所有现场验证数据必须保存原始波形文件.csv或.plw格式和日志文本。这些是后续FAFailure Analysis的唯一证据也是向客户证明产品可靠性的铁证。5. 常见问题速查表与独家避坑指南调试路上有些坑看似偶然实则必然。以下是我在6年工业网关开发中整理出的20个最高频问题及其根因、解决方案和预防措施。每一条都来自真实的产线返修记录。序号现象描述根本原因解决方案预防措施1小板在实验室正常上产线后频繁“掉线”控制柜内DeviceNet总线终端电阻缺失仅一端有检查总线两端确保各有一个120Ω精密电阻在BOM中强制标注“终端电阻×2”PCB丝印标明“TERMINATION”位置2主站扫描到节点但无法配置参数固件中Device Type字段为0x0000未定义修改device_info.h填入ODVA官方分配的Device Type如0x0004Sensor建立EDS文件与固件宏定义的交叉检查表编译时自动校验3SPI读取数据正确但DeviceNet上报值为0Input Assembly缓冲区未初始化或指针指向错误内存在main()中调用memset(input_assembly, 0, sizeof(input_assembly))使用静态分配编译期检查禁用动态内存分配4逻辑分析仪抓到SPI波形完美但传感器无响应传感器需要特定上电时序如先上VDD再上VIO查阅传感器手册“Power-up Sequence”章节增加硬件RC延时电路在原理图中为所有电源轨添加上电时序标注5CAN总线错误帧计数随温度升高而增加SN65HVD230的VCC去耦电容10μF钽电容高温失效更换为10μF X7R陶瓷电容100nF陶瓷电容并联所有电源去耦电容统一选用X7R材质禁用钽电容6主站轮询周期为10ms小板响应延迟达15msSPI读取耗时过长如ADC采样滤波阻塞CAN发送将SPI读取放入低优先级任务CAN发送用高优先级任务抢占为每个SPI操作设定超时阈值如5ms超时则返回上次有效值7多个小板接入同一DeviceNet总线部分节点无法识别总线总电容超标1000pF导致信号边沿变缓减少节点数或在总线中间加DeviceNet中继器设计阶段用公式C_total