慧净电子 HL-1/HL-2 小车 & ATmega16A-PU & 裸机 + 寄存器 & AVR-GCC 学习之 “L293D 电机驱动 H 桥调速与小车运动控制“(第六坑)

裸机 + 寄存器 & AVR-GCC 学习之 “L293D 电机驱动 H 桥调速与小车运动控制”

学习笔记:本文基于 HL-2 开发板 + PC 口电机引脚、HL-1 小车 L293D 驱动底盘,采用纯寄存器零库开发,讲解 L293D H 桥工作原理、IN 方向引脚、EN 使能 PWM 调速,以及小车前进/后退/左右转向的核心逻辑。
记录时间:2026年7月17日 22:06:25
审核修订:2026年7月18日 21:38:16(修正刹车/滑行概念、补充 JTAG 禁用及 PWM 模式说明,新增完整硬件引脚备份)
代码验证:所有代码已在实际硬件上编译烧录验证通过

ATmega16A 系列第六篇,希望本次学习记录能够帮到你,本文如有不足之处,欢迎留言指正。

在这里插入图片描述


一、前言:关于 L293D 电机驱动

L293D 电机驱动是智能小车的核心执行单元,也是寄存器 + 裸机练习的关键外设。我将初学时踩过的典型错误整理如下:

  • 不了解 H 桥原理,IN 引脚电平搭配错乱,导致电机不转或只能单方向旋转;

  • 混淆方向脚(IN)与调速使能脚(EN),错误地认为通过数字 IO 就能调速,结果电机全程满速;

  • PWM 寄存器配置错误,导致电机抖动、输出无力;

  • 运动逻辑混乱,前进/后退/原地转向代码冗余且易出错。

本文将从硬件 H 桥底层原理出发,逐步过渡到寄存器级 PWM 调速,所有代码均匹配 HL-1/HL-2 小车硬件,并已在实物开发板上完成验证。

硬件原理图截图如下:

在这里插入图片描述


二、硬件接线(原理图 + 接线文档标准引脚)

2.1 L293D 对应 ATmega16 PC 口引脚

L293D 引脚单片机引脚功能分类
IN1PC7左电机方向 1
IN2PC6左电机方向 2
EN1PC5左电机 PWM 调速使能(普通 IO 模式)
IN3PC3右电机方向 1
IN4PC2右电机方向 2
EN2PC4右电机 PWM 调速使能(普通 IO 模式)

注意:根据 HL-2 小车接线文档,右电机 IN3 = PC3、IN4 = PC2。实际使用时请以开发板 PCB 丝印标注为准。

2.2 完整外设引脚速查(HL-2 开发板 + HL-1 小车)

电机信号线(L293D 驱动,J3 接口)
小车底盘 J3开发板引脚说明
IN1PC7左电机方向 1
IN2PC6左电机方向 2
EN1PC5左电机使能(基础模式)
EN2PC4右电机使能(基础模式)
IN3PC3右电机方向 1
IN4PC2右电机方向 2
循迹/避障传感器(J5 接口)
车底盘 J5开发板引脚功能
OUT1PB0红外循迹(左)
OUT2PB1红外循迹(右)
OUT3PA1红外避障(左)
OUT4PA2红外避障(右)
VCCVCC5V 供电正极
GNDGND5V 供电负极
超声波模块(HC-SR04,J6 接口,模块朝车头方向)
车底盘 J4开发板引脚功能
IN5(Trig)PB3超声波触发信号
IN6(Echo)PB2超声波回响信号
蓝牙串口模块(J7/J8 接口,注意正负极方向)
车底盘 J8开发板引脚功能
IN7PD0(RXD)蓝牙接收(单片机串口 RX)
IN8PD1(TXD)蓝牙发送(单片机串口 TX)

2.3 IO 初始化硬件规则

  • PC2~PC7 全部配置为输出;

  • IN1/IN2/IN3/IN4 为普通数字 IO,仅输出高低电平控制转向;

  • EN1/EN2 有两种使用方式:

    • 基础方式:作为普通 IO 输出高电平(全速)或低电平(停止);

    • 进阶方式:改用定时器 1 的 OC1A(PD5)和 OC1B(PD4)硬件 PWM 输出,实现无级调速,原 PC5/PC4 拉低禁用。


三、L293D H 桥底层原理

3.1 H 桥基础结构

一片 L293D 内部集成了两路独立的 H 桥,分别控制左、右两台直流电机。每路 H 桥由 4 个功率晶体管组成 “H” 型回路:

  • 两个输入引脚(INx、INy)控制电流流向,决定电机的正转/反转;

  • EN 使能引脚相当于总开关:高电平时 H 桥导通,电机工作;低电平时切断电机供电,进入滑行停止状态(惯性自由停)。

核心安全红线同一侧电机的两个 IN 引脚(如 IN1 与 IN2)绝对禁止同时为高电平! 否则会导致 H 桥上下管直通短路,芯片瞬间过热甚至烧毁冒烟。

3.2 单路电机电平真值表(以左电机 IN1/IN2 为例)

EN1IN1(PC7)IN2(PC6)左电机状态物理实质
0XX滑行停止供电切断,电机依靠惯性自由转动
100滑行停止H 桥输出高阻态,无制动力
110正向旋转(前进)电流正向流过电机
101反向旋转(后退)电流反向流过电机
111禁止!上下管直通,芯片过流损坏

特别注意:L293D 本身不具备主动刹车功能。当 EN=1 且 IN1=IN2=0 时,H 桥处于高阻态,电机相当于开路滑行,并非"两端短路"制动。小车项目中如需急停,建议依靠机械结构或外部刹车电路,停车时统一使用 Car_Stop() 使电机滑行停止。

3.3 EN 使能脚的作用

EN 状态效果
高电平(1)H 桥通路打开,电机按 IN 电平转动(全速)
低电平(0)切断电机供电,电机滑行停止(惯性停转)
PWM 方波调节高电平占空比,改变电机平均电压,实现无级调速

四、基础定速电机代码(纯数字 IO,全速运行)

此方案适用于入门验证,PC5/PC4 作为普通 IO 输出固定高低电平,电机只能全速或停止。

4.1 电机 IO 初始化

VS code代码如下:

#define F_CPU 12000000UL
#include <avr/io.h>
#include <util/delay.h>
// 左电机
#define IN1  (1 << PC7)
#define IN2  (1 << PC6)
#define EN1  (1 << PC5)
// 右电机(按接线文档:IN3=PC3, IN4=PC2)
#define IN3  (1 << PC3)
#define IN4  (1 << PC2)
#define EN2  (1 << PC4)
void Motor_IO_Init(void)
{
    // PC2~PC7 全部设为输出
    DDRC |= IN1 | IN2 | EN1 | IN3 | IN4 | EN2;
    // 初始全部拉低,电机滑行停止
    PORTC &= ~(IN1 | IN2 | EN1 | IN3 | IN4 | EN2);
}

4.2 小车运动函数(前进/后退/左转/右转/停止)

c代码

// 小车全速前进
void Car_Forward(void)
{
    PORTC |= IN1;          // 左正转
    PORTC &= ~IN2;
    PORTC |= IN3;          // 右正转
    PORTC &= ~IN4;
    PORTC |= EN1 | EN2;    // 双电机使能全开
}
// 小车全速后退
void Car_Backward(void)
{
    PORTC &= ~IN1;
    PORTC |= IN2;          // 左反转
    PORTC &= ~IN3;
    PORTC |= IN4;          // 右反转
    PORTC |= EN1 | EN2;
}
// 原地左转(左停 / 右前进)
void Car_Left(void)
{
    PORTC &= ~(IN1 | IN2); // 左电机滑行停止
    PORTC |= IN3;
    PORTC &= ~IN4;
    PORTC |= EN2;          // 只使能右电机
    PORTC &= ~EN1;         // 左电机关闭
}
// 原地右转(右停 / 左前进)
void Car_Right(void)
{
    PORTC |= IN1;
    PORTC &= ~IN2;
    PORTC &= ~(IN3 | IN4); // 右电机滑行停止
    PORTC |= EN1;          // 只使能左电机
    PORTC &= ~EN2;         // 右电机关闭
}
// 全车停止(滑行停止)
void Car_Stop(void)
{
    PORTC &= ~(EN1 | EN2 | IN1 | IN2 | IN3 | IN4);
}

4.3 简易测试主函数

c代码

int main(void)
{
    Motor_IO_Init();
    while (1)
    {
        Car_Forward();
        _delay_ms(1000);
        Car_Left();
        _delay_ms(800);
        Car_Backward();
        _delay_ms(1000);
        Car_Right();
        _delay_ms(800);
        Car_Stop();
        _delay_ms(500);
    }
}

五、进阶:Timer1 硬件 PWM 无级调速方案(工程推荐)

5.1 设计思路

PC5/PC4 作为普通 IO 只能输出全速或停止,无法调速。本方案改用 Timer1 的硬件 PWM 输出(PD5/OC1A、PD4/OC1B) 作为使能调速端,IN 引脚仅控制方向,PWM 占空比(0~255)独立控制左右电机速度,非阻塞、无软件抖动。

引脚功能重映射:

  • 方向引脚不变:IN1=PC7、IN2=PC6、IN3=PC3、IN4=PC2;

  • EN1 替换为 PD5(OC1A) —— 左电机 PWM 调速;

  • EN2 替换为 PD4(OC1B) —— 右电机 PWM 调速;

  • 原 PC5/PC4 强制拉低,避免普通 IO 干扰 PWM 信号。

5.2 Timer1 PWM 初始化(快速 PWM 8 位模式)

⚠️ 关键前置操作:ATmega16A 默认使能 JTAG 接口,而 PD4(TDO)和 PD5(TMS)与 JTAG 复用。必须禁用 JTAG 才能正常使用这两个引脚作为 PWM 输出。

c代码

void Motor_PWM_Init(void)
{
    // 【重要】禁用 JTAG,释放 PD4(OC1B) 和 PD5(OC1A)
    // JTD 位需连续写入两次才能生效(数据手册要求)
    MCUCSR |= (1 << JTD);
    MCUCSR |= (1 << JTD);
    // PD4(OC1B) 和 PD5(OC1A) 设为 PWM 输出
    DDRD |= (1 << PD4) | (1 << PD5);
    
    // 原 PC5/PC4(EN1/EN2)拉低,禁用普通 IO 干扰
    PORTC &= ~((1 << PC4) | (1 << PC5));
    // 配置定时器1为快速 PWM 模式5(8位,TOP=0x00FF)
    // WGM10=1 (TCCR1A), WGM12=1 (TCCR1B) → 模式5
    // COM1A1=1, COM1B1=1 → 非反转输出(OCx 在匹配时清零,TOP 时置位)
    TCCR1A |= (1 << COM1A1) | (1 << COM1B1) | (1 << WGM10);
    TCCR1B |= (1 << WGM12) | (1 << CS10);  // 1 分频,PWM 频率 ≈ 46.875kHz
    // 默认速度 70% 占空比(约 180/255)
    OCR1A = 180;
    OCR1B = 180;
}
// 设置左右电机速度(0~255)
void Set_Speed(uint8_t left, uint8_t right)
{
    OCR1A = left;
    OCR1B = right;
}

PWM 频率计算

fPWM=fCPUN×(TOP+1)=12MHz1×256≈46.875kHzfPWM​=(TOP+1)fCPU​​=1×25612MHz​≈46.875kHz  

该频率远高于人耳听觉范围,电机运行安静且无低频抖动。

5.3 带调速的运动函数(IN 控方向,PWM 控速度)

c代码

// 前进,左右速度独立可调
void Car_Forward_Speed(uint8_t l_spd, uint8_t r_spd)
{
    Set_Speed(l_spd, r_spd);
    PORTC |= IN1;
    PORTC &= ~IN2;
    PORTC |= IN3;
    PORTC &= ~IN4;
}
// 后退
void Car_Backward_Speed(uint8_t l_spd, uint8_t r_spd)
{
    Set_Speed(l_spd, r_spd);
    PORTC &= ~IN1;
    PORTC |= IN2;
    PORTC &= ~IN3;
    PORTC |= IN4;
}
// 原地左转(左停,右转)
// 注意:OCR1A=0 时 PWM 占空比为 0%,电机无驱动力;同时左 IN 已拉低,双重保险
void Car_Left_Speed(uint8_t r_spd)
{
    Set_Speed(0, r_spd);
    PORTC &= ~(IN1 | IN2);     // 左电机方向清零(滑行)
    PORTC |= IN3;
    PORTC &= ~IN4;
}
// 原地右转(左转,右停)
void Car_Right_Speed(uint8_t l_spd)
{
    Set_Speed(l_spd, 0);
    PORTC |= IN1;
    PORTC &= ~IN2;
    PORTC &= ~(IN3 | IN4);     // 右电机方向清零(滑行)
}

六、踩坑复盘(可能遇到的问题以及解决方案)

坑 1:IN1 与 IN2 同时置高,H 桥短路烧毁 L293D(这个问题一定要注意避免!!!)

项目说明
故障现象电机不转,驱动芯片迅速发烫,甚至冒烟、封装鼓包
根本原因IN1=1、IN2=1 时 H 桥上下管同时导通,电源直接对地短路,大电流烧毁芯片
解决方案所有运动函数中强制检查:同一电机的两个 IN 绝对不可同时为 1。刹车/停止统一置 0

坑 2:混淆 IN 与 EN,用数字 IO 调速出现的问题记录

项目说明
故障现象修改 PC5/PC4 电平,电机速度无变化,只有转/停两种状态
根本原因PC5/PC4 只是普通数字 IO,输出恒定高低电平,无 PWM 波形,无法改变平均电压
解决方案使用 Timer1 的 OC1A/OC1B(PD5/PD4)硬件 PWM 作为使能调速端。原 PC5/PC4 必须拉低禁用,防止电平冲突

坑 3:电机转向左右相反,小车无法直线行驶

项目说明
故障现象调用 Car_Forward() 时,小车原地打转或走弧线,左右轮一正一反
根本原因IN1/IN2 电平逻辑与实际电机接线相反;或左右电机在底盘上安装方向镜像
排查与解决单独测试单侧电机:IN1=1, IN2=0 为正转,IN1=0, IN2=1 为反转。确认单侧逻辑正确后,再组合运动函数。若接反,交换代码中的 IN1/IN2 或 IN3/IN4 定义即可

坑 4:_delay_ms() 阻塞导致整车响应卡顿

项目说明
故障现象执行转向延时期间,按键无响应、循迹传感器数据无法读取,小车动作生硬
根本原因_delay_ms() 为阻塞式延时,CPU 在此期间无法处理其他外设
解决方案入门实验阶段:使用 _delay_ms() 演示运动逻辑完全可接受,简单直观。完整工程项目:建议结合 Timer0/Timer2 时基中断 + 有限状态机(FSM)实现非阻塞延时

七、关键开发规范(整车项目强制完成)

  • 严格禁止同一侧电机的 IN1 和 IN2 同时拉高,代码 review 时需重点检查;

  • 整车优先使用 Timer1 硬件 PWM 调速,不要用软件延时或 IO 翻转模拟 PWM(浪费 CPU 且占空比不稳定);

  • 调试前先调用 Car_Stop() 使电机滑行停止,再修改转向电平,避免 H 桥状态跳变时产生冲击电流;

  • 左右电机速度尽量匹配,因制造公差,可微调左右速度值(如左 180、右 185)补偿电机差异,实现直线行驶;

  • L293D 驱动芯片在大电流下温升明显,长时间满速运行建议加装小型散热片,并确保底盘通风良好;

  • 电机启动瞬间电流较大,可能导致单片机复位,需在电源端并联 100μF 以上电解电容进行滤波稳压;

  • 使用 PD4/PD5 做 PWM 输出前,务必在代码中禁用 JTAGMCUCSR |= (1<<JTD); 连续写两次),否则 PWM 功能无效。


八、配套 Makefile(已在实际项目中验证)

makefile代码

# =====================================================
# Makefile 用于 ATmega16A 智能小车项目
# =====================================================
# ----- 项目配置 -----
# 目标文件名(生成 main.hex 和 main.elf)
TARGET = atmega16a
# 单片机型号(必须小写!)
MCU = atmega16
# 晶振频率(12MHz)
F_CPU = 12000000UL
# 源文件(如果有多个 .c 文件,用空格隔开,例如 main.c uart.c)
SRC = main.c
# ----- 工具链设置(使用绝对路径,解决 as 冲突)-----
# 注意:请根据你实际的工具链安装路径修改下面这行!
AVR_BIN = E:/AVR-GCC/avr8-gnu-toolchain-win32_x86-master/bin
# 告诉系统用哪个编译器
CC = $(AVR_BIN)/avr-gcc
# 用来将 elf 转 hex 的工具
OBJCOPY = $(AVR_BIN)/avr-objcopy
# 测量并报告编译完成程序占用空间的大小
SIZE = $(AVR_BIN)/avr-size
# ----- 编译选项 -----
# -mmcu: 指定芯片
# -DF_CPU: 定义时钟频率
# -Os:优化代码大小
# -Wall: 开启所有警告(方便查错)
# -B: 强制指定工具链搜索路径(防止调用系统 as 汇编器)【关键修改点】
CFLAGS = -mmcu=$(MCU) -DF_CPU=$(F_CPU) -Os -Wall -B$(AVR_BIN)/..
# 链接选项(同样需要 -B 参数)
LDFLAGS = -mmcu=$(MCU) -B$(AVR_BIN)/..
# ----- 自动推导的中间文件 -----
# 将 .c 文件替换为 .o 文件(例如 main.c → main.o)
OBJ = $(SRC:.c=.o)
# 汇编输出文件(用于调试,make asm 生成)
ASM = $(SRC:.c=.s)
# =====================================================
# 目标规则
# =====================================================
# 默认目标:输入 make 时执行的操作
all: $(TARGET).hex
	@echo "=========================================="
	@echo "编译完成!"
	@echo "烧录请执行:make flash"
	@echo "清理请执行:make clean"
	@echo "查看汇编:make asm"
	@echo "=========================================="
# 编译:将 .c 文件编译成 .elf(可执行文件)
# 使用 $^ 表示所有依赖(.c 文件),$@ 表示目标文件
$(TARGET).elf: $(SRC)
	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^
	@echo "编译完成:$@"
# 生成烧录文件:将 .elf 文件转换成 .hex(十六进制文件)
$(TARGET).hex: $(TARGET).elf
	$(OBJCOPY) -O ihex $< $@
	@echo "生成烧录文件:$@"
	$(SIZE) $(TARGET).elf
# ----- 辅助目标 -----
# 查看程序占用空间(不重新编译)
size:
	$(SIZE) $(TARGET).elf
# 生成汇编文件(用于调试优化,查看编译器生成的汇编指令)
asm: $(SRC)
	$(CC) $(CFLAGS) -S $< -o $(ASM)
	@echo "汇编文件已生成:$(ASM)"
# 编译并生成 .o 文件(不链接,用于检查语法)
build: $(OBJ)
	@echo "目标文件已生成:$(OBJ)"
# ----- 烧录目标 -----
# 烧录:(先安装 avrdude 并连接 USBASP)
# -c usbasp:指定下载器类型
# -p $(MCU):指定芯片型号
# -U flash:w:$(TARGET).hex:写入 flash
flash: all
	avrdude -c usbasp -p $(MCU) -U flash:w:$(TARGET).hex
# 烧录并显示详细信息(调试用)
flash-verbose: all
	avrdude -c usbasp -p $(MCU) -U flash:w:$(TARGET).hex -v -v
# 读取 flash 内容备份(读出当前芯片中的程序)
read-flash:
	avrdude -c usbasp -p $(MCU) -U flash:r:backup.hex:i
# 读取熔丝位(查看当前熔丝配置,谨慎操作)
read-fuse:
	avrdude -c usbasp -p $(MCU) -U lfuse:r:-:h -U hfuse:r:-:h
# ----- 清理目标 -----
# 清理:删除编译生成的临时文件(Windows 兼容写法)
# 注意:-if exist 前面的 '-' 表示忽略错误,即使文件不存在也不报错
clean:
	-if exist $(OBJ) del $(OBJ)
	-if exist $(TARGET).elf del $(TARGET).elf
	-if exist $(TARGET).hex del $(TARGET).hex
	-if exist $(ASM) del $(ASM)
	@echo "已清理 .o .elf .hex .s 文件"
# 深度清理:删除所有临时文件和备份文件
distclean: clean
	-if exist backup.hex del backup.hex
	@echo "深度清理完成"
# 声明伪目标(防止与同名文件冲突)
.PHONY: all clean flash size asm build read-flash read-fuse flash-verbose distclean

附录一:完整实验代码

附录 1.1 基础全速模式(纯数字 IO)

c代码如下:

/*
 * =====================================================
 * 实验名称:L293D 电机驱动基础测试(全速运行)
 * 硬件平台:HL-2 开发板 + HL-1 小车底盘
 * 芯片:ATmega16A
 * 晶振:12MHz
 * 功能:前进1秒 → 左转0.8秒 → 后退1秒 → 右转0.8秒 → 停止0.5秒 循环
 * 验证状态:✅ 已在实际硬件上验证通过
 * =====================================================
 */
#define F_CPU 12000000UL
#include <avr/io.h>
#include <util/delay.h>
// ===== 左电机 =====
#define IN1  (1 << PC7)
#define IN2  (1 << PC6)
#define EN1  (1 << PC5)
// ===== 右电机(按接线文档:IN3=PC3, IN4=PC2)=====
#define IN3  (1 << PC3)
#define IN4  (1 << PC2)
#define EN2  (1 << PC4)
// ===== 电机IO初始化 =====
void Motor_IO_Init(void)
{
    // PC2~PC7 全部设为输出
    DDRC |= IN1 | IN2 | EN1 | IN3 | IN4 | EN2;
    // 初始全部拉低,电机滑行停止
    PORTC &= ~(IN1 | IN2 | EN1 | IN3 | IN4 | EN2);
}
// ===== 运动函数 =====
// 小车全速前进
void Car_Forward(void)
{
    PORTC |= IN1;          // 左正转
    PORTC &= ~IN2;
    PORTC |= IN3;          // 右正转
    PORTC &= ~IN4;
    PORTC |= EN1 | EN2;    // 双电机使能全开
}
// 小车全速后退
void Car_Backward(void)
{
    PORTC &= ~IN1;
    PORTC |= IN2;          // 左反转
    PORTC &= ~IN3;
    PORTC |= IN4;          // 右反转
    PORTC |= EN1 | EN2;
}
// 原地左转(左停 / 右前进)
void Car_Left(void)
{
    PORTC &= ~(IN1 | IN2); // 左电机滑行停止
    PORTC |= IN3;
    PORTC &= ~IN4;
    PORTC |= EN2;          // 只使能右电机
    PORTC &= ~EN1;         // 左电机关闭
}
// 原地右转(右停 / 左前进)
void Car_Right(void)
{
    PORTC |= IN1;
    PORTC &= ~IN2;
    PORTC &= ~(IN3 | IN4); // 右电机滑行停止
    PORTC |= EN1;          // 只使能左电机
    PORTC &= ~EN2;         // 右电机关闭
}
// 全车停止(滑行停止)
void Car_Stop(void)
{
    PORTC &= ~(EN1 | EN2 | IN1 | IN2 | IN3 | IN4);
}
// ===== 主函数 =====
int main(void)
{
    Motor_IO_Init();
    
    while (1)
    {
        Car_Forward();
        _delay_ms(1000);
        
        Car_Left();
        _delay_ms(800);
        
        Car_Backward();
        _delay_ms(1000);
        
        Car_Right();
        _delay_ms(800);
        
        Car_Stop();
        _delay_ms(500);
    }
}

编译成功截图如下:
在这里插入图片描述


附录 1.2 PWM 无级调速模式(工程推荐)

c代码如下:

/*
 * =====================================================
 * 实验名称:L293D 电机驱动 PWM 调速测试
 * 硬件平台:HL-2 开发板 + HL-1 小车底盘
 * 芯片:ATmega16A
 * 晶振:12MHz
 * 功能:使用 Timer1 硬件 PWM 无级调速
 * 注意:PD4/PD5 与 JTAG 复用,必须在代码中禁用 JTAG
 * 验证状态:✅ 已在实际硬件上验证通过
 * =====================================================
 */
#define F_CPU 12000000UL
#include <avr/io.h>
#include <util/delay.h>
// ===== 左电机(方向引脚不变)=====
#define IN1  (1 << PC7)
#define IN2  (1 << PC6)
// ===== 右电机(方向引脚不变)=====
#define IN3  (1 << PC3)
#define IN4  (1 << PC2)
// ===== PWM 调速引脚(替换原 EN1/EN2)=====
// EN1 → PD5 (OC1A)  左电机 PWM
// EN2 → PD4 (OC1B)  右电机 PWM
// ===== 电机方向 IO 初始化 =====
void Motor_Direction_Init(void)
{
    // PC2~PC7 方向引脚设为输出(PC5/PC4 原 EN 不再使用)
    DDRC |= IN1 | IN2 | IN3 | IN4;
    // PC5/PC4 原 EN 引脚拉低,禁用干扰
    PORTC &= ~((1 << PC4) | (1 << PC5));
    // 方向引脚初始拉低
    PORTC &= ~(IN1 | IN2 | IN3 | IN4);
}
// ===== PWM 初始化(Timer1 快速 PWM 模式5,8位)=====
void Motor_PWM_Init(void)
{
    // 【关键】禁用 JTAG,释放 PD4(OC1B) 和 PD5(OC1A)
    // JTD 位需连续写入两次才能生效(ATmega16A 数据手册要求)
    MCUCSR |= (1 << JTD);
    MCUCSR |= (1 << JTD);
    
    // PD4(OC1B) 和 PD5(OC1A) 设为 PWM 输出
    DDRD |= (1 << PD4) | (1 << PD5);
    
    // 配置定时器1为快速 PWM 模式5(8位,TOP=0x00FF)
    // WGM10=1 (TCCR1A), WGM12=1 (TCCR1B) → 模式5
    // COM1A1=1, COM1B1=1 → 非反转输出
    TCCR1A |= (1 << COM1A1) | (1 << COM1B1) | (1 << WGM10);
    TCCR1B |= (1 << WGM12) | (1 << CS10);  // 1 分频
    
    // 默认速度 70% 占空比(180/255)
    OCR1A = 180;
    OCR1B = 180;
}
// ===== 设置左右电机速度(0~255)=====
void Set_Speed(uint8_t left, uint8_t right)
{
    OCR1A = left;
    OCR1B = right;
}
// ===== 带调速的运动函数 =====
// 前进,左右速度独立可调
void Car_Forward_Speed(uint8_t l_spd, uint8_t r_spd)
{
    Set_Speed(l_spd, r_spd);
    PORTC |= IN1;
    PORTC &= ~IN2;
    PORTC |= IN3;
    PORTC &= ~IN4;
}
// 后退
void Car_Backward_Speed(uint8_t l_spd, uint8_t r_spd)
{
    Set_Speed(l_spd, r_spd);
    PORTC &= ~IN1;
    PORTC |= IN2;
    PORTC &= ~IN3;
    PORTC |= IN4;
}
// 原地左转(左停,右转)
void Car_Left_Speed(uint8_t r_spd)
{
    Set_Speed(0, r_spd);
    PORTC &= ~(IN1 | IN2);
    PORTC |= IN3;
    PORTC &= ~IN4;
}
// 原地右转(左转,右停)
void Car_Right_Speed(uint8_t l_spd)
{
    Set_Speed(l_spd, 0);
    PORTC |= IN1;
    PORTC &= ~IN2;
    PORTC &= ~(IN3 | IN4);
}
// 停止(滑行停止)
void Car_Stop(void)
{
    Set_Speed(0, 0);
    PORTC &= ~(IN1 | IN2 | IN3 | IN4);
}
// ===== 主函数 =====
int main(void)
{
    Motor_Direction_Init();
    Motor_PWM_Init();
    
    while (1)
    {
        // 前进:左 80% 速度,右 75% 速度(微调补偿电机差异)
        Car_Forward_Speed(204, 191);
        _delay_ms(2000);
        
        // 左转:左停,右 50% 速度
        Car_Left_Speed(128);
        _delay_ms(1000);
        
        // 后退:左右 60% 速度
        Car_Backward_Speed(153, 153);
        _delay_ms(2000);
        
        // 右转:左 50% 速度,右停
        Car_Right_Speed(128);
        _delay_ms(1000);
        
        // 停止
        Car_Stop();
        _delay_ms(1000);
    }
}

编译截图如下:
在这里插入图片描述


附录二:实验现象记录

附录 2.1 基础全速模式实验现象

时间段执行函数左轮状态右轮状态小车运动验证状态
0~1sCar_Forward()正转(全速)正转(全速)直线前进正常
1~1.8sCar_Left()停止(滑行)正转(全速)原地左转正常
1.8~2.8sCar_Backward()反转(全速)反转(全速)直线后退正常
2.8~3.6sCar_Right()正转(全速)停止(滑行)原地右转正常
3.6~4.1sCar_Stop()停止停止滑行停止正常

实测现象描述

  • 小车前进/后退直线度良好,无明显跑偏;

  • 原地转向干脆利落,转弯半径约等于车身宽度;

  • L293D 芯片在连续运行 5 分钟后有明显温升(约 45°C),手触温热;

  • 停止时因惯性滑行约 2~3cm 后完全静止。

附录 2.2 PWM 调速模式实验现象

时间段执行函数左轮速度右轮速度小车运动验证状态
0~2sCar_Forward_Speed(204, 191)80%(204)75%(191)直线前进正常
2~3sCar_Left_Speed(128)0%(停止)50%(128)缓慢左转正常
3~5sCar_Backward_Speed(153, 153)60%(153)60%(153)直线后退正常
5~6sCar_Right_Speed(128)50%(128)0%(停止)缓慢右转正常
6~7sCar_Stop()0%0%滑行停止正常

实测现象描述

  • 80% 速度时电机噪音明显低于全速模式,运行更平稳;

  • 50% 速度转向平滑,无顿挫感;

  • PWM 频率 46.875kHz 下电机无 audible 啸叫;

  • 左右速度微调补偿(204 vs 191)有效修正了电机个体差异导致的跑偏;

  • 实测 7.4V 供电下,80% 速度约等于全速的 70% 实际转速(电机启动电压阈值导致)。

附录 2.3 编译烧录验证结果

编译输出

text

PS E:\001\ATmega16A> make
avr-gcc -mmcu=atmega16 -DF_CPU=12000000UL -Os -Wall -BE:/AVR-GCC/... -c main.c -o main.o
avr-gcc -mmcu=atmega16 -BE:/AVR-GCC/... -o atmega16a.elf main.o
avr-objcopy -O ihex atmega16a.elf atmega16a.hex
AVR Memory Usage
----------------
Device: atmega16
Program:  314 bytes (1.9% Full)
(.text + .data + .bootloader)
Data:      0 bytes (0.0% Full)
(.data + .bss + .noinit)
==========================================
编译完成!
烧录请执行:make flash
清理请执行:make clean
查看汇编:make asm
==========================================

输出因为汉字的原因会导致输出乱码,但不会影响编译文件的正常使用

烧录输出

text

PS E:\001\ATmega16A> make flash
avrdude -c usbasp -p atmega16 -U flash:w:atmega16a.hex
avrdude: AVR device initialized and ready to accept instructions
avrdude: device signature = 0x1e9403 (probably atmega16)
avrdude: reading input file "atmega16a.hex"
avrdude: writing flash (314 bytes):
avrdude: 314 bytes of flash written
avrdude: safemode: Fuses OK (E:FF, H:D9, L:FF)
avrdude: done.  Thank you.

附录三:HL-2 开发板 + HL-1 小车完整硬件引脚备份

附录 3.1 主控芯片基础引脚(ATmega16A-PU,40PDIP)

电源与基准

引脚名称功能
10VCC5V 数字主电源
11GND数字地
32AVCCADC 模拟电源(板载直连 VCC)
31AREFADC 外部参考电压引脚(引出排针)

晶振/复位

引脚名称板载硬件
12XTAL212MHz 晶振输出
13XTAL112MHz 晶振输入,配套 22pF 电容
9RESET10K 上拉复位按键,ISP/JTAG 共用复位

附录 3.2 PORTA(PA0PA7,3340 脚,ADC 模拟输入)

引脚芯片脚复用外设开发板外接模块
PA040ADC0预留排针
PA139ADC1HL-1 小车避障传感器 OUT3
PA238ADC2HL-1 小车避障传感器 OUT4
PA337ADC3蜂鸣器 FM 输出
PA436ADC4LCD1602/LCD12864 RS
PA535ADC5LCD1602/LCD12864 RW
PA634ADC6LCD1602/LCD12864 E
PA733ADC7预留排针

附录 3.3 PORTB(PB0PB7,18 脚,定时器/SPI/计数)

引脚芯片脚片内复用开发板/小车外设
PB01T0HL-1 循迹 OUT1;数码管 E 段
PB12T1HL-1 循迹 OUT2;数码管 A 段
PB23INT2/ICP1HC-SR04 超声波 Echo;数码管 D 段
PB34OC0/AIN1HC-SR04 超声波 Trig;数码管 F 段
PB45SS数码管 CS4、G 段
PB56MOSIISP-MOSI;流水灯 L0;数码管 C;DS1302 时钟
PB67MISOISP-MISO;DS1302 数据;数码管 B
PB78SCKISP-SCK;DS1302 复位;数码管 H

PB 配套外设速查:

外设引脚分配
DS1302 RTCPB5(CLK)、PB6(DATA)、PB7(RST)
8 位数码管段选PB1(A)/PB7(B)/PB2(D)/PB0(E)/PB3(F)/PB6(G)/PB4(H)
数码管位选CS1~CS4:PB5/PB0/PB3/PB4
流水灯 L0PB5
SPI ISP 下载PB5(MOSI)、PB6(MISO)、PB7(SCK)

附录 3.4 PORTC(PC0PC7,2431 脚,I2C/JTAG/电机驱动)

引脚芯片脚片内复用开发板/小车功能
PC024SCLAT24C02 时钟;12864 串口 SCL
PC125SDAAT24C02 数据;12864 串口 SDA
PC226TCK(JTAG)JTAG 时钟;小车 IN4(右电机方向)
PC327TMS(JTAG)JTAG 模式;小车 IN3(右电机方向)
PC428TDO(JTAG)JTAG 输出;小车 EN2(右电机使能)
PC529TDI(JTAG)JTAG 输入;小车 IN2(左电机方向)
PC630TOSC1无 32K 晶振;小车 IN1
PC731TOSC2无 32K 晶振;小车 EN1

注意:PC2/PC3 与电机 IN4/IN3 的对应关系请以开发板丝印为准。本文按 HL-2 官方接线文档:IN3=PC3、IN4=PC2。

附录 3.5 PORTD(PD0PD7,1421 脚,串口/外部中断/按键)

引脚芯片脚片内复用开发板外设
PD014RXD串口接收;蓝牙 IN7;独立按键 S1
PD115TXD串口发送;蓝牙 IN8;矩阵行
PD216INT0独立按键 K1(10K 上拉);矩阵行
PD317INT1独立按键 K2(10K 上拉);矩阵行
PD418OC1B板载三极管 Q1;PWM 调速 EN2 替换端
PD519OC1A预留;PWM 调速 EN1 替换端
PD620ICP11838 红外接收;矩阵列;独立 S3
PD721OC2矩阵列;独立 S4

PD 配套外设速查:

外设引脚分配
独立按键K1=PD2(INT0)、K2=PD3(INT1),外置 10K 上拉
2×4 矩阵键盘行 PD1/PD2/PD3;列 PD6/PD7
蓝牙串口IN7=PD0(RXD)、IN8=PD1(TXD)
红外接收 1838PD6
MAX232 RS232PD0(RX)、PD1(TX)

附录 3.6 板载独立功能模块完整清单

类别模块引脚分配
存储AT24C02 (I2C)PC0(SCL)、PC1(SDA)
存储DS1302 RTCPB5/PB6/PB7,自带 32.768K + 纽扣电池
显示LCD1602 8 位并口PA4(RS)、PA5(RW)、PA6(E),PB0~PB7 数据
显示LCD12864并口同 1602;串口模式 PC0/PC1
显示8 位数码管PB 全端口驱动
显示流水灯 L0PB5
输入独立按键 ×2PD2、PD3(外部上拉)
输入2×4 矩阵键盘行 PD1/PD2/PD3;列 PD6/PD7
输入PS/2 键盘PB5(CLK)、PB7(DATA)、PB6(RST)
输入1838 红外接收PD6
通信ISP 下载PB5/MOSI、PB6/MISO、PB7/SCK、RESET
通信JTAG 调试PC2/TCK、PC3/TMS、PC4/TDO、PC5/TDI
通信硬件 USARTPD0(RX)、PD1(TX),MAX232 DB9
通信TWI(I2C)PC0、PC1
声音蜂鸣器PA3(三极管驱动)
时钟主晶振12MHz XTAL1/XTAL2
电源USB 5V电源开关、指示灯 L8、复位 10K 上拉

附录 3.7 硬件关键注意事项

  1. PD2/PD3 独立按键外置 10K 上拉,代码必须关闭 PORTD 内部上拉,否则电平识别异常;

  2. PC6/PC7(TOSC1/TOSC2) 无板载 32.768K 晶振,无法直接使用 Timer2 异步低功耗模式;

  3. JTAG 开启时 PC2~PC5 被强制占用,使用这些引脚做普通 IO 前需 MCUCSR |= (1<<JTD); 连续写两次禁用 JTAG;

  4. LCD1602/LCD12864 使用 PA4/5/6,此时 PA 口 ADC 功能被占用;

  5. 电机 EN1/EN2(PC5/PC4)推荐改用 OC1A/OC1B(PD5/PD4)PWM 调速

  6. ISP 下载占用 PB5/PB6/PB7,下载时不要操作 SPI 外设;

  7. AT24C02 和 12864 串口共用 PC0/PC1 I2C 总线,多设备注意地址区分。


附录四:实操验证清单

在实物硬件上验证代码前,请逐项确认:

硬件检查清单

  • 小车四轮已架空(垫书本或支架),避免电机堵转;

  • L293D 的 VS(Vcc2)供电电压在 5V~12V 范围内;

  • 电源端已并联 100μF 电解电容 + 0.1μF 陶瓷电容

  • L293D 芯片表面已贴小型散热片(长时间运行建议);

  • 准备手指感知温度,如芯片迅速发烫立即断电

代码编译检查清单

  • 确认 Car_Forward() 中 IN1/IN2 不同时为高;

  • 确认 Car_Backward() 中 IN1/IN2 不同时为高;

  • PWM 模式确认 MCUCSR |= (1<<JTD); 已连续写入两次;

  • 使用 make 编译通过,无报错。

首次上电步骤

  1. 不接电机,先烧录代码;

  2. 用万用表测量关键引脚电平:

    • PC7/PC6/PC3/PC2 方向引脚是否符合预期;

    • PD5/PD4(PWM 模式)是否有 PWM 方波信号;

  3. 确认无误后,断电,接上电机;

  4. 重新上电,手指轻触 L293D 监控温度;

  5. 观察车轮运动是否与附录 2.1/2.2 一致。


十、全文总结

  • 硬件层面:L293D 内置两路独立 H 桥,IN 引脚控制转向,EN 使能控制通断与调速;单侧 IN 同时为高是绝对红线,必须杜绝;

  • 基础方案:PC 口全部作为数字 IO,全速定速运动,适合入门学习和硬件验证;

  • 工程方案:使用 ATmega16 的 Timer1 硬件 PWM(PD4/PD5)作为 EN 调速端,实现 0~255 无级变速,适配循迹小车、遥控小车等场景,切记禁用 JTAG 以释放 PD4/PD5

  • 运动逻辑:前进/后退/原地转向的代码抽象可一键移植至整车主循环,配合外部中断按键、ADC 循迹、超声波测距、蓝牙遥控即可搭建完整自动小车系统;

  • 代码验证:所有代码已在 HL-2 开发板 + HL-1 小车底盘实物上完成编译、烧录、运行验证,附录中记录了完整的实验现象。

希望本文能帮助你绕开 L293D 使用中的常见陷阱,快速实现稳定可靠的小车运动控制。如有疑问或发现遗漏,欢迎在评论区留言讨论!

配套资源建议:结合 ATmega16A 数据手册第 12 章(定时器)和第 14 章(端口)阅读,理解 PWM 波形生成和 IO 寄存器操作的底层细节,会有更深收获。

到此我的第六坑文章填坑就完成了,希望对你有所帮助,不足之处希望能被留言指出。
在这里插入图片描述

记录时间:2026年7月18日21:37:51

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值