资讯动态

ATmega162双串口配置与实战避坑指南:从寄存器到中断

发布时间:2026/10/21 19:58:40 来源:尧图企业网站定制
市面上聊M162的人不多但这颗二十多年前的老AVR至今还在不少工业设备、通信板卡和教学实验室里默默干活。它最让人惦记的就是DIP40封装下集成了两个硬件串口这在当年的8位单片机里算是相当奢侈的配置。如果你正在维护一批老设备或者出于兴趣想研究AVR的串口机制这篇就把M162的双串口怎么配、怎么用、有哪些坑一次说清楚。1. 先认识一下M162双串口AVR里的“特殊工种”1.1 M162到底是一颗什么样的芯片M162的完整型号是ATmega162属于AVR经典系列中比较特殊的一档。它采用哈佛结构的8位RISC内核大多数指令单周期执行DIP40脚封装长得和同时代的ATmega16/32几乎一模一样。如果你手头有M162的数据手册翻到芯片框图那一页会看到它把大部分资源都往通信方向倾斜两个独立的USART串口支持全双工、各自独立的波特率发生器和收发中断16KB片上Flash程序存储器支持Bootloader分区1KB SRAM、512字节EEPROM4通道PWM、多个定时器以及SPI接口带JTAG接口既能调试也能编程。它和ATmega16最大的区别在于M162没有片内ADC省下的硅片面积被用来做第二路串口。很多第一次接触它的人会问“怎么连ADC都没有”答案很简单这颗芯片从设计之初就不是为了做传感器采集而生它的目标场景是工业通信、协议转换、多机互联这类需要不断收发数据的场合。我在实际使用中的体会是M162的定位有点像通信板上的“专职接线员”资源不算多但串口能力相当扎实。做数据采集、模拟量处理是它的短板可一旦涉及两个串口设备之间的桥接、透传、协议解析它反而是同时期AVR里最顺手的型号。1.2 为什么需要两个硬件串口很多新入行的朋友会问一个串口不够用吗软件模拟一个不就行了这个问题的答案放在M162的年代非常现实那时CPU主频普遍在8到16MHz之间用IO口模拟波特率115200的串口对定时器中断的实时性要求极高一个中断响应不及时帧就错位了。而工业现场常见的Modbus轮询、GPS报文解析、GPRS透传又往往需要同时和两个设备通信。我见过一个典型的现场应用一块控制板用USART0连接上位机接收配置指令和上报数据用USART1连接现场的传感器仪表按照Modbus协议周期采集参数。如果只有一路硬件串口就必须在上位机和仪表之间来回切换不仅代码复杂还会因为切换时序引入通信延迟。有了双串口两路各跑各的互不干扰开发难度和故障率都明显下降。另一个常见场景是串口桥接USART0接收的数据原样转发到USART1USART1收到的再转发给USART0。这样一个简单的双向透传盒子用M162写起来不到一百行代码而用单串口芯片做需要额外加一颗模拟串口的方案可靠性完全不在一个量级。1.3 它和ATmega16/32的那些爱恨情仇M162与ATmega16/32的管脚排列是兼容的很多情况下可以直接互相替换着玩。但替换之前必须清醒认识几件事ATmega16/32有8路10位ADCM162没有M162有两路USARTATmega16/32只有一路两者都有JTAG但引脚占用和熔丝位细节略有差异M162的Flash支持Boot区重映射这点和ATmega32类似比ATmega16灵活。当年不少工程师在选型时纠结要ADC就选M16要双串口就选M162。我自己的经验是如果项目涉及两个需要同时通信的串口设备哪怕需要外挂一个ADC芯片也值得选M162反过来如果只有一个串口、但需要大量模拟量采集M162并不合适。选型的关键不是看谁型号新而是看外设组合是否匹配你的通信拓扑。2. 双串口正确打开方式从寄存器到收发代码2.1 两个串口的寄存器地图M162的两个串口在寄存器层面是完全对称的任何一个会了另一个就是复制粘贴。USART0对应的寄存器是UCSR0A、UCSR0B、UCSR0C、UBRR0H、UBRR0L和UDR0USART1对应的就是UCSR1A、UCSR1B、UCSR1C、UBRR1H、UBRR1L和UDR1。每个串口的核心寄存器可以这样理解UBRR0H/UBRR0L16位波特率寄存器决定串口通信速率UCSR0B串口使能和中断开关包括接收使能RXEN0、发送使能TXEN0以及接收完成中断RXCIE0、发送完成中断TXCIE0、数据寄存器空中断UDRIE0UCSR0C帧格式配置设置数据位、停止位、校验位UCSR0A状态寄存器包含发送完成标志TXC0、接收完成标志RXC0、数据寄存器空标志UDRE0UDR0数据寄存器读它拿到接收字节写它发出发送字节。M162的串口是全双工模式也就是说发送和接收各自有独立的移位寄存器和缓冲同一时刻既能收又能发互不占用。这个特性在做透传时特别实用后面会看到代码里收发两条路径完全可以独立循环。2.2 波特率计算为什么选晶振这么讲究AVR的USART波特率生成器本质上是一个分频器异步正常模式下计算公式是UBRR F_CPU / (16 × BAUD) - 1其中F_CPU是系统时钟频率BAUD是目标波特率。计算出来的UBRR要取整放入寄存器但取整会带来实际波特率和目标波特率的偏差。举个例子系统时钟选7.3728MHz目标波特率9600代入公式UBRR 7372800 / (16 × 9600) - 1 48 - 1 47这个结果是整数实际波特率与目标完全一致误差为0。但如果系统时钟换成常见的8MHz同样跑9600UBRR 8000000 / (16 × 9600) - 1 52.08 - 1 51放进寄存器后实际波特率是8000000 / (16 × 52) ≈ 9615.38误差约0.16%。这个误差在波特率9600时问题不大但如果你跑到115200以上误差就会被放大导致通信不稳。所以老工程师选晶振时会优先挑能被波特率整除的频率比如7.3728MHz、11.0592MHz、14.7456MHz、18.432MHz这些频率就是为串口通信准备的。我多年的习惯是只要板子上以串口为主一律用14.7456MHz晶振这样9600、19200、38400、57600、115200都能算出接近整数的UBRR值省得后期在波特率误差上折腾。2.3 初始化代码让两个串口同时转起来用AVR-GCC写初始化函数非常直观。下面这段代码初始化USART0和USART1UART0跑9600UART1跑115200#define F_CPU 14745600UL void uart0_init(unsigned long baud) { unsigned int ubbr F_CPU / (16UL * baud) - 1; UBRR0H (unsigned char)(ubbr 8); UBRR0L (unsigned char)ubbr; UCSR0B (1 RXEN0) | (1 TXEN0); UCSR0C (1 UCSZ01) | (1 UCSZ00); // 8N1 } void uart1_init(unsigned long baud) { unsigned int ubbr F_CPU / (16UL * baud) - 1; UBRR1H (unsigned char)(ubbr 8); UBRR1L (unsigned char)ubbr; UCSR1B (1 RXEN1) | (1 TXEN1); UCSR1C (1 UCSZ01) | (1 UCSZ00); // 8N1 }这里UCSR0C配置成8位数据位、无校验、1位停止位也就是最常用的8N1格式。如果项目需要偶校验或2位停止位改UCSZ0/UPM0/USBS0这几个位就行但通信双方必须一致否则对端解析会全部错位。2.4 收发函数与轮询方式最简单的收发方式是轮询。发送一个字节时先等数据寄存器空标志UDRE0置位再把字节写入UDR0接收一个字节时等接收完成标志RXC0置位再从UDR0读出。void uart0_putchar(unsigned char ch) { while (!(UCSR0A (1 UDRE0))); UDR0 ch; } unsigned char uart0_getchar(void) { while (!(UCSR0A (1 RXC0))); return UDR0; }USART1的版本就是把寄存器和标志位改成1结尾的版本逻辑一模一样。发送字符串就循环调用putchar接收就轮询getchar并做协议解析。不过轮询方式有个很现实的限制如果在接收阻塞等待数据的同时又需要处理其他任务整个CPU就被卡住了。比如你在等USART0的数据此时USART1来了数据程序必须等USART0的while循环结束才能处理USART1。因此实际项目中双串口几乎总是配合中断或缓冲区来使用。2.5 中断方式双串口同时收发不丢数据更稳妥的做法是给每个串口配上接收环形缓冲区用中断往缓冲区里填数据主循环只管从缓冲区里取。这样两个串口的数据随时都会被“接住”不会因为主程序正忙着处理某一件事而丢失。AVR-GCC里USART0的接收完成中断向量是USART0_RXC_vectUSART1对应的是USART1_RXC_vect。示例思路如下#define RX_BUFFER_SIZE 128 volatile unsigned char rx0_buf[RX_BUFFER_SIZE]; volatile unsigned char rx0_head 0; volatile unsigned char rx0_tail 0; ISR(USART0_RXC_vect) { unsigned char data UDR0; unsigned char next (rx0_head 1) % RX_BUFFER_SIZE; if (next ! rx0_tail) { rx0_buf[rx0_head] data; rx0_head next; } }主循环里取数据时判断head和tail是否相等即可if (rx0_head ! rx0_tail) { unsigned char ch rx0_buf[rx0_tail]; rx0_tail (rx0_tail 1) % RX_BUFFER_SIZE; // 处理ch }这种环形缓冲区的结构很简单但能解决双串口开发中九成以上的丢数据问题。缓冲区大小建议至少128字节如果通信报文长或者波特率超过115200可以加大到256甚至512代价只是SRAM占用多一些。3. 除了双串口M162身上那些容易被忽略的能力3.1 JTAG接口不只是调试M162带JTAG接口这在当年的8位单片机里并不常见。JTAG四根线可以完成三件事在线调试、编程烧录、访问芯片内部资源。调试时你可以像用现代MCU的调试器一样在代码里设断点、单步执行、查看寄存器和变量。对串口通信这类时序敏感的程序单步跟踪非常有效比如排查为什么收到的数据总是少一个字节直接在接收中断里设断点就能看清。不过JTAG是有代价的。M162使用PF4到PF7这四个引脚作为JTAG信号线芯片默认JTAG功能开启如果你打算把这几个引脚当普通IO口用必须做两件事一是通过熔丝位把JTAGEN关掉二是在代码里把MCUCR寄存器中的JTD位写1禁用JTAG接口。这里有个细节JTD位比较特殊需要在一个时钟周期内连续写两次1才会生效代码上通常写两行紧跟的赋值语句。MCUCR (1 JTD); MCUCR (1 JTD);如果你不做这一步就算熔丝配置正确程序里对PF4到PF7的写操作也可能不生效测了半天IO电平纹丝不动最后才发现JTAG还占着引脚。这个问题我踩过不止一次。3.2 512字节EEPROM断电不丢数据的小仓库M162有512字节EEPROM适合保存设备地址、校准参数、通信配置这类掉电不能丢的信息。AVR-GCC提供了现成的头文件读写非常方便#include avr/eeprom.h unsigned char val; eeprom_write_byte((unsigned char*)0x10, 0xAB); val eeprom_read_byte((unsigned char*)0x10);这里面的坑在于EEPROM的写操作并不是瞬时完成的写入期间CPU会停在那里等待。如果此时恰好有串口中断到来数据就可能在中断里被延迟处理。所以在工业现场应用里如果要频繁写入EEPROM写之前最好先全局关中断写完再打开或者选用带磨损均衡的策略不要把某个固定地址写得太频繁。另一个需要注意的点是512字节虽然叫“小仓库”但对于存配置参数来说通常够用。我一般把0x00到0x0F留给版本号和设备标识0x10到0x1F存通信参数0x20之后存业务数据。提前规划好地址分配后面的维护会方便很多。3.3 引脚复用冲突提前规划的资源表M162的DIP40封装看起来引脚很多但当你把双串口、SPI、JTAG和外部中断都用上之后资源冲突会立刻显现。我习惯在项目开始时先画一张引脚占用表把所有外设的引脚需求列清楚。重点检查这几组冲突JTAG占用PF4到PF7一旦开启就要不了这4个IOUSART0和USART1的引脚是固定的不能像现代MCU那样任意映射SPI接口和部分IO复用如果板上有SPI器件要确认和串口、外部中断不打架外部中断引脚用来做按键唤醒、电平检测时要避免和调试接口共用。我在实际项目里养成的习惯是先用表格把所有引脚按“功能”“占用外设”“能否复用”三列整理出来再开始画原理图。设计阶段多花半小时后面焊接调试能省好几天的返工时间。4. 从零点亮一块M162最小系统搭建与烧录实录4.1 最小系统需要的物料M162的DIP40封装虽然看着笨重但对爱好者来说反而是好事——可以直接插面包板。搭建一个能跑串口的最小系统需要准备这些M162主控芯片一颗40脚DIP插座强烈建议先用插座再插芯片避免反复焊接损伤引脚14.7456MHz晶振一颗以及两颗20pF左右的负载电容10kΩ上拉电阻和100nF复位电容各一个5V稳压电源或者直接用USB供电的5V一个USB转TTL串口模块用于连接USART0如果还要测USART1准备第二个串口模块或者双串口模块。供电和时钟是这块板子最容易出问题的地方。M162在5V供电下最高可以跑到16MHz14.7456MHz没压力但如果你用3.3V供电主频最好控制在8MHz以下否则芯片可能不稳定甚至不工作。4.2 焊接和接线时的注意事项焊接DIP40座子本身不难但有几个细节值得提醒。晶振要尽量靠近芯片的XTAL1和XTAL2引脚两个负载电容的地端尽量短别绕大圈。复位引脚不要直接悬空接一个10kΩ电阻到VCC加一个100nF电容到地。很多初学者没接上拉电阻结果芯片上电后反复复位程序根本跑不起来。串口模块和M162之间的接线要注意交叉模块的TX接芯片的RXD模块的RX接芯片的TXD。我第一次做的时候直接把两边的TX接到一起发送端对发送端半天没反应后来才反应过来是接反了。这个低级错误在串口调试里非常常见。4.3 用ISP下载器烧录与熔丝位设置给M162烧录程序最常用的工具是USBASP加avrdude命令行。接线按照ISP标准MISO、MOSI、SCK、RESET、VCC、GND六根线接好后执行avrdude -c usbasp -p m162 -U flash:w:firmware.hex如果想顺便读一下当前熔丝位可以执行avrdude -c usbasp -p m162 -U lfuse:r:lfuse.txt:h avrdude -c usbasp -p m162 -U hfuse:r:hfuse.txt:h熔丝位是M162新手最容易翻车的地方。芯片默认使用内部1MHz RC振荡器如果你烧录的程序是按14.7456MHz写的串口波特率但芯片实际跑在1MHz串口收发必然乱码。所以拿到新芯片的第一步是先设置好外部晶振对应的熔丝位也就是把CKSEL位配置成外部晶振模式再烧录程序。如果你把SPIEN熔丝位关掉了JTAG也进不去那这块芯片就只能走高压并行编程恢复麻烦得多。我的建议是在没有把握的情况下只修改时钟相关的熔丝其他位保持默认。每次烧录前先用avrdude读一遍熔丝当前值确认无误再操作。4.4 通电后的第一步验证烧录成功后给板子上电先做最基础的串口回环验证。在代码里让USART0收到什么就原样发回什么unsigned char ch uart0_getchar(); uart0_putchar(ch);然后用电脑上的串口助手发送任意字符如果收到了相同的字符说明最小系统已经正常运转USART0通路没问题。接着把同样的代码套到USART1上分别验证两路串口。两路都通了整个最小系统就算真正点亮了。回环测试通过之后再进一步做双串口透传测试让USART0收到的数据转发到USART1USART1收到的转发到USART0。这一步能同时验证两路串口的双向通路也顺带检查了中断和缓冲区代码是否靠谱。5. M162实战避坑指南常见问题与排查5.1 常见问题速查表我整理了这些年用M162最容易遇到的一批问题全部是实际踩过的坑直接对照排查现象可能原因解决办法串口输出乱码波特率不匹配或晶振频率与熔丝位不一致用示波器/逻辑分析仪测串口引脚实际频率核对UBRR计算值和熔丝时钟源串口完全无反应TX/RX接反或未使能收发位检查交叉接线检查UCSR0B是否置位RXEN0/TXEN0PF4到PF7写不进数据JTAG功能占用引脚改熔丝关闭JTAGEN并在代码中连续两次写JTD置1上电后芯片发热、程序不跑供电接反或晶振未起振检查电源极性用万用表量晶振引脚电压向EEPROM写数据后程序卡死EEPROM写期间CPU被阻塞写之前关中断写完之后再开或减少写次数两个串口同时收发丢字节缓冲区太小或中断处理时间过长加大环形缓冲区中断里只做存取不做耗时操作使用256倍数波特率时误差大F_CPU不是波特率的整数倍更换晶振频率或启用U2X倍速模式降误差5.2 我踩过的几个典型坑第一个坑是波特率误差在长报文下被放大。当时一块板子用8MHz晶振跑19200波特率单字节测试怎么测都正常但一跑上百字节的Modbus报文就隔三差五丢帧。后来用逻辑分析仪抓波形发现每个字节的位宽和标准值差了那么一点点短报文看不出问题长报文累计误差就超出了对端设备的容忍范围。换成14.7456MHz晶振后问题彻底消失。第二个坑是JTAG引脚导致继电器误动作。当时把PF4接到一个继电器的控制端程序里明明写了输出低电平上电瞬间继电器却突然吸合了一下。排查了很久最后发现是JTAG接口在上电阶段对引脚的默认状态做了驱动导致不明不白的毛刺。从那以后凡是做继电器、电机这类输出负载我都会先禁用JTAG并且在硬件上加下拉电阻。第三个坑和烧录顺序有关。有一次急着测试一块板子的USART1把USART0的收发函数注释掉只保留了USART1的初始化结果从USART0下载程序的ISP通路也一起被注释没了。正确做法是不要把ISP所需的引脚初始化代码删掉保持它们在上电后立即可用否则烧录器连接不上又要折腾恢复熔丝。5.3 一条独家的排查思路如果两块M162板子之间的串口通信不稳定先别急着怀疑芯片把电脑、示波器、逻辑分析仪统统接到USART0上用串口助手发一帧固定数据看波形是否干净。波形没问题再怀疑对端设备波形毛刺多先查共地。串口通信的地线问题在双机互联时特别常见。两个设备各自用不同的电源适配器如果只连了信号线没连地线参考地不一致就会导致数据帧偶尔错位。我排查这种问题的最快办法是用一根导线把两块板子的GND短接再观察症状是否消失。如果消失了说明就是共地问题后期设计时把地线规划好就行。M162这颗芯片放到今天确实“复古”但它的双串口架构和清晰的寄存器设计反而适合用来彻底搞懂UART通信的底层机制。如果你手头正好有这块芯片或者在做老设备的维护不妨按上面的思路把两个串口都跑起来。这套东西的难点从来不在芯片本身而在于时钟、熔丝、引脚规划这些容易被忽略的细节上。一轮跑通之后你对AVR串口的理解会比看十篇教程都扎实。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑