ESP32 for Arduino 学习笔记教程:点阵屏与 millis() 应用

一、课程目标

  1. 掌握 ESP32 引脚模式配置,实现硬件控制
  2. 学会使用 millis() 函数实现非阻塞延时,替代 delay()
  3. 理解点阵屏驱动原理,实现图案动态切换显示
  4. 掌握移位寄存器控制方法(shiftOut 函数应用 )

二、核心知识点回顾

1. 引脚模式配置

  • 函数pinMode(引脚编号, 模式)
    • 模式包含 INPUT(输入)、OUTPUT(输出)等
    • 例:pinMode(13, OUTPUT); 配置引脚 13 为输出模式,用于控制点阵屏锁存信号

2. millis() 函数

  • 功能:获取程序启动后经过的毫秒数,实现非阻塞延时
  • 优势:相比 delay(),不会阻塞程序执行,可同时处理多个任务
  • 用法unsigned long currentTime = millis(); 结合逻辑判断实现延时,如:
if (millis() - oldTime >= 1000) {
  // 执行延时 1 秒后的操作
  oldTime = millis(); // 更新时间标记
}

3. 点阵屏驱动原理

  • 通过移位寄存器(如 74HC595)控制:
    • 行数据(Rowdata):控制点亮哪一行
    • 列数据(COLdata):控制该行哪些点点亮
  • 核心函数 shiftOut(数据引脚, 时钟引脚, 数据顺序, 数据):将数据逐位移位输出到寄存器

三、代码实战与解析

完整代码(带详细注释)

// 点阵屏控制引脚定义
const int LPin = 13, CPin = 14, DPin = 15;  
// LPin: 锁存引脚 | CPin: 时钟引脚 | DPin: 数据引脚
byte Rowdata, COLdata;  // 存储行、列数据

// 图案定义(8x8 点阵)
// 小爱心图案
byte small[8] = {0x00, 0x00, 0x00, 0x14, 0x3E, 0x1C, 0x08, 0x00}; 
// 大爱心图案
byte big[8] = {0x00, 0x66, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18};   

// 全局变量(用于 millis() 非阻塞延时)
// 记录上一次状态切换的时间
long long oldTime = 0; 
// 控制图案显示状态(小爱心/大爱心)
boolean ledState = false; 

void setup() {
  // 配置控制引脚为输出模式
  pinMode(LPin, OUTPUT);  
  pinMode(CPin, OUTPUT);  
  pinMode(DPin, OUTPUT);  

  // 初始状态设置
  // 锁存引脚低电平,允许数据输入
  digitalWrite(LPin, LOW); 
  // 时钟引脚初始化为低
  digitalWrite(CPin, LOW); 

  // 初始化串口通信(波特率 115200),用于调试输出
  Serial.begin(115200); 
}

void loop() {
  // 非阻塞延时:每隔 1000 毫秒(1 秒)切换状态
  if (millis() - oldTime >= 1000) { 
    oldTime = millis();  // 更新时间标记
    ledState = !ledState;  // 切换显示状态(小爱心 ↔ 大爱心)
    Serial.println(ledState);  // 串口打印状态(调试用)
  }

  // 根据状态显示对应图案
  if (ledState == false) {
    displayImages(small);  // 显示小爱心
  } else {
    displayImages(big);  // 显示大爱心
  }
}

// 点阵屏显示函数:逐行输出图案数据
void displayImages(byte imgs[]) { 
  for (int row = 0; row < 8; row++) {  // 遍历 8 行
    // 生成行数据:每次只点亮一行(通过位运算 0b1 << row 实现)
    Rowdata = 0b1 << row; 
    // 列数据:对图案数据取反(根据硬件电路决定是否需要取反)
    COLdata = ~imgs[row]; 
    matrixDisplay();  // 输出行、列数据到点阵屏
    delay(1);  // 短暂延时,保证显示稳定
  }
}

// 矩阵显示核心函数:通过移位寄存器输出数据
void matrixDisplay() { 
  // 先发送列数据到移位寄存器
  shiftOut(DPin, CPin, MSBFIRST, COLdata); 
  // 再发送行数据到移位寄存器
  shiftOut(DPin, CPin, MSBFIRST, Rowdata); 
  // 锁存引脚置高,将移位寄存器数据更新到点阵屏(点亮对应点)
  digitalWrite(LPin, LOW); 
  // 锁存引脚置低,准备下一次数据传输
  digitalWrite(LPin, HIGH); 
}

代码分步解析

1. 引脚与图案定义

const int LPin = 13, CPin = 14, DPin = 15;  
byte Rowdata, COLdata;  
byte small[8] = {0x00, 0x00, 0x00, 0x14, 0x3E, 0x1C, 0x08, 0x00};  
byte big[8] = {0x00, 0x66, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18};  
  • 定义点阵屏控制引脚:LPin(锁存)、CPin(时钟)、DPin(数据)
  • 定义两个图案数组 small(小爱心)和 big(大爱心),每个数组存储 8 字节数据,对应 8x8 点阵的一行

2. setup() 函数:初始化配置

void setup() {
  pinMode(LPin, OUTPUT);  
  pinMode(CPin, OUTPUT);  
  pinMode(DPin, OUTPUT);  
  digitalWrite(LPin, LOW);  
  digitalWrite(CPin, LOW);  
  Serial.begin(115200); 
}
  • 配置引脚为输出模式,准备控制点阵屏
  • 初始化引脚电平:LPinCPin 置低,保证初始状态正确
  • 启动串口通信,波特率 115200,用于调试输出

3. loop() 函数:主逻辑循环

void loop() {
  if (millis() - oldTime >= 1000) { 
    oldTime = millis();  
    ledState = !ledState;  
    Serial.println(ledState);  
  }

  if (ledState == false) {
    displayImages(small);  
  } else {
    displayImages(big);  
  }
}
  • 非阻塞延时:通过 millis() - oldTime >= 1000 判断是否达到 1 秒,达到则切换 ledState 状态
  • 图案切换:根据 ledState 的值,调用 displayImages() 显示对应图案

4. displayImages() 函数:逐行处理图案数据

void displayImages(byte imgs[]) { 
  for (int row = 0; row < 8; row++) {  
    Rowdata = 0b1 << row;  
    COLdata = ~imgs[row];  
    matrixDisplay();  
    delay(1);  
  }
}
  • 遍历 8 行,逐行生成行数据(Rowdata)和列数据(COLdata
  • 调用 matrixDisplay() 输出数据到点阵屏,delay(1) 保证显示稳定

5. matrixDisplay() 函数:移位寄存器操作

void matrixDisplay() { 
  shiftOut(DPin, CPin, MSBFIRST, COLdata); 
  shiftOut(DPin, CPin, MSBFIRST, Rowdata); 
  digitalWrite(LPin, LOW);  
  digitalWrite(LPin, HIGH);  
}
  • shiftOut() 函数:按 MSBFIRST(高位先送)的顺序,通过 DPin(数据)和 CPin(时钟)将数据输出到移位寄存器
  • digitalWrite(LPin, LOW/HIGH):通过锁存引脚电平变化,将移位寄存器中的数据“锁存”到点阵屏,实现点亮操作

四、硬件连接说明

  1. 点阵屏与 ESP32 引脚对应
    • LPin(13 脚)→ 点阵屏锁存引脚
    • CPin(14 脚)→ 点阵屏时钟引脚
    • DPin(15 脚)→ 点阵屏数据引脚
  2. 移位寄存器(如 74HC595):若使用外部移位寄存器,需将 DPinCPin 连接到移位寄存器对应引脚,再由移位寄存器控制点阵屏

五、常见问题与解决

1. 图案显示异常(如乱码、缺行)

  • 检查
    • 图案数组 smallbig 的数据是否正确(可通过字模提取软件重新生成)
    • COLdata = ~imgs[row]; 中的取反操作是否必要(根据硬件电路调整,若不需要取反则删除 ~
  • 解决:重新校准图案数据,或调整取反逻辑

2. 延时不精准(millis() 失效)

  • 检查
    • 全局变量 oldTime 是否为 long long 类型(避免溢出)
    • 程序中是否有其他长时间阻塞操作(如 delay(1000) 会干扰 millis() 判断)
  • 解决:确保 oldTime 类型正确,用 millis() 替代所有 delay()

3. 串口无输出(Serial.println 失效)

  • 检查
    • setup() 中是否调用 Serial.begin(115200);
    • 串口监视器波特率是否为 115200
  • 解决:添加 Serial.begin(),并匹配串口监视器波特率

六、拓展练习

  1. 添加更多图案:用字模提取软件生成新图案(如笑脸、箭头),实现多图案循环切换
  2. 优化显示效果:通过 analogWrite() 实现点阵屏亮度调节
  3. 结合传感器:读取光线传感器数据,根据环境亮度自动切换图案显示频率

通过本节课,你已掌握 ESP32 点阵屏驱动、millis() 非阻塞延时的核心用法。继续拓展练习,可实现更复杂的交互效果!

2 条评论

  • @ 2025-7-23 19:22:25
    // LED矩阵控制引脚定义
    const int LPin = 13, CPin = 14, DPin = 15;  // LPin:锁存引脚, CPin:时钟引脚, DPin:数据引脚
    byte Rowdata, COLdata;                      // 行数据和列数据存储变量
    byte imgs[8] = {
      /*--  调入了一幅图像:这是您新建的图像  --*/
      /*--  宽度x高度=8x8  --*/
      0x10, 0x38, 0x7C, 0xFE, 0x38, 0x38, 0x38, 0x38
    };
    byte small[8] = {//小爱心
      /*--  调入了一幅图像:这是您新建的图像  --*/
      /*--  宽度x高度=8x8  --*/
      0x00, 0x00, 0x00, 0x14, 0x3E, 0x1C, 0x08, 0x00
    };
    byte big[8] = {//大爱心
      /*--  调入了一幅图像:这是您新建的图像  --*/
      /*--  宽度x高度=8x8  --*/
      0x00, 0x66, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18
    };
    void setup() {
      // 初始化控制引脚为输出模式
      pinMode(LPin, OUTPUT);
      pinMode(CPin, OUTPUT);
      pinMode(DPin, OUTPUT);
      // 初始状态设置
      digitalWrite(LPin, LOW);  // 锁存引脚低电平,允许数据输入
      digitalWrite(CPin, LOW);  // 时钟引脚初始化为低
      // 设置PWM分辨率为10位(0-1023),虽然此处未使用PWM功能
      // analogSetWidth(10);
      Serial.begin(115200);
    }
    
    long long oldTime = 0;//旧的时间
    boolean ledState = false;//小灯的状态
    
    void loop() {
      if (millis() - oldTime >= 1000) {
        oldTime = millis();//更新旧的时间
        ledState = !ledState;//把状态更新一下
        Serial.println(ledState);
        //digitalWrite(25, ledState);
      }
      if(ledState == false){
        displayImages(small);
      }else{
        displayImages(big);
      }
    }
    void displayImages(byte imges[]) {// 点阵显示函数:把图形数据输出到移位寄存器,控制点阵点亮
      for (int row = 0; row < 8; row++) { // 逐行处理
        Rowdata =  0b1 << row;
        COLdata = ~imgs[row];
        matrixDisplay();
        delay(1); // 短暂延时,让显示稳定
      }
    }
    // 矩阵显示函数 - 通过移位寄存器发送数据
    void matrixDisplay() {
      // 发送列数据(先发送)
      shiftOut(DPin, CPin, MSBFIRST, COLdata);
      // 发送行数据(后发送,级联寄存器)
      shiftOut(DPin, CPin, MSBFIRST, Rowdata);
      // 更新显示 - 上升沿锁存数据
      digitalWrite(LPin, LOW);  // 锁存引脚置高,将移位寄存器数据更新到输出
      digitalWrite(LPin, HIGH);   // 锁存引脚置低,准备下一次数据传输
    }
    
    • @ 2025-7-23 19:22:14
      const int LPin = 13,CPin = 14,DPin = 15;
      byte ROWdata,COLdata;
      int big_heart[8] = {0x00,0x66,0xFF,0xFF,0xFF,0x7E,0x3C,0x18};
      
      void setup() {
        pinMode(LPin,OUTPUT);
        pinMode(CPin,OUTPUT);
        pinMode(DPin,OUTPUT);
        digitalWrite(LPin,LOW);
        digitalWrite(CPin,LOW);
      }
      
      void loop() {
        for(int i=0;i<=7;i++) {
          ROWdata = 1 << i;
          COLdata = ~(big_heart[i]);
          matrixDisplay();
        }
      }
      
      void matrixDisplay() {
        shiftOut(DPin,CPin,MSBFIRST,COLdata);
        shiftOut(DPin,CPin,MSBFIRST,ROWdata);
        digitalWrite(LPin,HIGH);
        digitalWrite(LPin,LOW);
      }
      
      • 1