作为一阶段最后的部分,该节课,承接前面五节课的所学的知识点,将通过所学的以及手中的零部件完成一个基础小车的设计。
6.1小车功能展示
要求:按键进行发车,同时小车定时器和裁判秒表开始计时。裁判向参赛选手传达小车动作指令(左转右转、亮起指定红绿灯),参赛选手使用串口发送指令控制车辆。裁判传达停止指令,小车和裁判计时停止,小车OLED 屏幕上显示时间,与裁判计时比对,多余或者少于的时间按照倍数扣分。最终按照得分排名。
功能分析:
1.定时器: 用Ticker库,写一个定时器,用于计时
2.Oled屏:用u8g2库,展示时间和相关变量
3.按键:启动定时器等
4.电机:驱动小车
5.串口:控制小车
定义的函数:
void time_go();
定时器中运行的函数,实现时间的流逝
void key();
按下不同按键所实现不同的功能
void oled_show();
定义oled屏展示的内容
void car_conteol();
根据标志位i_status的大小实现小车双电机的不同状态
void date_accept();
读取串口发送来的数据
6.2敲代码+连线
接线:(gnd与vcc省略)
oled
scl接g42,sad接g41,
按键
g16,g19,g9通过按键接gnd
TB6612FNG
vm接5v,ao1,ao2,bo1,bo2分别接两电机的正负,pwma接g12,pwmb与pwma相连,stby接正电压,ain1,ain2接g10,g11,bin1,bin2接g5,g6,gnd,vcc
详细代码讲解
一.计时的实现
利用Ticker库实现的,Ticker库是ESP32开发中的一个重要组成部分,它提供了一种简单的方式来定时执行函数。delay()函数会阻塞程序的执行,而Ticker可以在后台定时调用函数,从而实现非阻塞的定时任务。
attach(float interval, callbackFunction):开启一个定时器
detach():关闭定时器
delay():延时函数
使用Ticker
库的步骤包括:
- 包含
Ticker
库。 - 创建一个
Ticker
对象。
- 定义一个回调函数,该函数将在定时器触发时执行。
在我的代码里,回调函数就是time_go(),既定时器会定时运行回调函数,实现时间的流逝效果

- 使用
attach
方法启动定时器,并指定触发间隔和回调函数。
我将attach
方法放在按键里了,当按键1按下时,会启动定时器
timer.attach(0.01,time_go)
第一个参数 0.01:这是一个浮点数,表示定时器触发的间隔时间,单位是秒。
第二个参数 time_go:这是一个函数指针,指向你想要定时器每次触发时调用的函数。
- 使用
detach
方法停止定时器。(该方法将停止该定义对象中所有的定时器)
我将detach
方法也放在按键里了,当按键2按下时,会关闭该定时器
二.用u8g2库使用oled屏
由于oled屏,之前没讲过,这里简单介绍一下
(一)、OLED的介绍与使用

先简单介绍一下咱们的OLED屏幕,它的驱动芯片型号为SSD1306,是在我们日常嵌入式学习中非常普遍使用的显示屏幕,便于我们了解当前的工作状态或者信息。那么它呢,同样也是使用I2C通信协议。
介绍完OLED,讲一下U8G2库,它是嵌入式设备的单色图形库,我们直接使用当中的库函数就能达到显示的目的,并不需要弄明白其中的底层逻辑,大大提高了我们使用OLED屏幕的效率。
U8g2库支持很多OLED,LCD的控制芯片,就比如我们这次学习的SSD1306,具体支持列表可以点击查看:https://github.com/olikraus/u8g2/wiki/u8g2setupcpp
u8g2统一了这些芯片的操作,可以输出文字,图位,画图,功能很多。
了解以上内容后,我们对oled与u8g2有了一定的认识。接下来教你们如何添加U8G2库。
(二)、U8G2库安装
首先打开platformoio主页面,点击Libraries,在搜索框中输入U8G2,点击第一个

点击Add To Project,选择要添加到的工程,点击add。
等待一会后,出现lib_deps = olikraus/U8g2@^2.36.2就代表成功了
(三)、U8G2坐标
特别的:对u8g2的坐标系统是不一样的

左上角是坐标的原点,向右以及向下是x与y轴的正方向
(四)、U8G2类名
而u8g2针对很多不同的oled设定的不一样的初始化,这体现在u8g2类名上

下面是用u8g2库使用oled屏
就我的代码先对oled引脚进行定义
创建一个u8g2实例
- U8G2_SSD1306_128X64_NONAME_F_HW_I2C:
- U8G2: 这是u8g2库的标识符。
- SSD1306: 表示OLED显示屏的控制器型号,SSD1306是一个非常常见的OLED控制器。
- 128X64: 表示OLED显示屏的分辨率,这里是128像素宽和64像素高。
- NONAME: 这个参数表示OLED显示屏没有特定的名称或品牌。
- F: 表示使用了全帧缓冲区,这意味着所有的绘图操作首先在内存中完成,然后一次性刷新到显示屏上。
- HW_I2C: 表示使用硬件I2C接口,与软件I2C相比,硬件I2C通常更快且更稳定。
- u8g2:
- 这是实例化的对象名称,你可以通过这个对象名来调用u8g2库提供的各种函数来控制OLED显示屏。
- 四个变量:
- U8G2_R0:这个参数表示显示屏的旋转角度。
U8G2_R0
表示没有旋转,即0度。u8g2库还提供了其他旋转选项,如U8G2_R1
(90度旋转),U8G2_R2
(180度旋转),U8G2_R3
(270度旋转)。 - **reset=*/U8X8_PIN_NONE:
reset
: 这是OLED显示屏的复位引脚。在这里,U8X8_PIN_NONE
表示没有使用复位引脚。如果你的OLED模块有复位引脚,你需要在这里指定相应的GPIO引脚编号。 - **clock=*/BOARD_I2C_SCL:
clock
: 这是I2C接口的时钟线(SCL)引脚。BOARD_I2C_SCL
是一个宏定义,它指定了连接到OLED显示屏SCL引脚的ESP32 GPIO引脚编号。 - **data=*/BOARD_I2C_SDA:
data
: 这是I2C接口的数据线(SDA)引脚。BOARD_I2C_SDA
是一个宏定义,它指定了连接到OLED显示屏SDA引脚的ESP32 GPIO引脚编号。
- U8G2_R0:这个参数表示显示屏的旋转角度。
u8g2初始化
- u8g2.setBusClock(800000);这行代码设置了I2C总线的时钟频率。在这里,
800000
表示以赫兹为单位的时钟频率,即800 kHz(千赫兹)。I2C总线时钟频率决定了数据传输的速度。大多数I2C设备都有一个最大时钟频率的限制,所以在设置这个值时,你需要确保它不超过你的OLED显示屏或微控制器(如ESP32、Arduino等)支持的最大I2C时钟频率。u8g2.setBusClock()
: 这个函数是u8g2库提供的,用于配置I2C总线的时钟速度。800000
: 是一个整数值,表示800 kHz的时钟频率。这个值可以根据你的硬件和需求进行调整。
- u8g2.begin();这行代码初始化u8g2库,并准备OLED显示屏进行后续操作。调用
begin()
函数后,u8g2库会执行必要的初始化步骤,比如设置I2C通信,配置显示屏的控制器等。u8g2.begin()
: 这是一个初始化函数,它必须在任何显示操作之前调用。这个函数通常设置显示屏的初始状态,并准备它进行显示。
- u8g2.enableUTF8Print();这行代码启用UTF-8编码的字符串打印功能。UTF-8是一种字符编码,可以表示几乎所有的字符,包括各种语言的文字和符号。
u8g2.enableUTF8Print()
: 这个函数启用u8g2库的UTF-8打印功能。启用后,你可以使用u8g2库提供的打印函数(如u8g2.print()
)来显示UTF-8编码的字符串,这对于显示非ASCII字符(如中文、日文、韩文等)非常有用。
我对oled的使用都在定义的oled_show函数里
oled显示函数——oled_show()
oled_show
函数用于在OLED屏幕上显示小车的计时信息和当前状态。
- 清除缓冲区:
u8g2.clearBuffer();
调用此函数是为了清除OLED屏幕显示缓冲区中的内容,为新的显示内容做准备。 - 字符数组声明: 四个字符数组
str1
,str2
,str3
,str4
被声明并用于存储格式化后的时间字符串和状态信息。这些数组的大小为20个字符,足以存储格式化后的数字和时间分隔符。 - 字符串格式化: 使用
sprintf
函数将整数变量m
(分钟)、s
(秒)、ms
(毫秒)和i_status
(状态)格式化为字符串,并存储在之前声明的字符数组中。例如,sprintf(str1, "%d", m);
将分钟数格式化为字符串。 - 设置字体:
u8g2.setFont(u8g2_font_ncenB12_tr);
设置OLED显示的字体。这里使用了u8g2_font_ncenB12_tr
字体,它是一种12点大小的字体。 - 绘制字符串: 使用
u8g2.drawStr
函数在OLED屏幕上绘制字符串。u8g2.drawStr(0,39,str1);
表示在屏幕坐标(0,39)处绘制str1
字符串,即分钟数。类似的调用用于显示秒、毫秒和状态信息。 - 发送缓冲区数据:
u8g2.sendBuffer();
将之前写入缓冲区的显示内容发送到OLED屏幕上,使得字符串在屏幕上可见。
三.按键函数的介绍
按键引脚定义
按键引脚输入模式设置
按键处理函数
通过判断按键连接的引脚是否被置于低电平来判断按键是否按下,不同的按键按下,会执行不同的行为,而i_key是一个按键标志位,再我的代码中未有作用,不过可以在新加的内容里用,用这个标志位进行判断等等。
四.根据标志位改变电机的驱动
电机相关配置
电机初始化配置
car_conteol()函数控制小车
根据当前的状态值(i_status
)来控制小车的运动状态。
switch(i_status)
: 这个语句根据变量i_status
的值来选择执行不同的代码块。case 1
: 当i_status
等于 1 时,小车将直行。这通过设置电机控制引脚AIN1
和BIN1
为高电平(HIGH
),而AIN2
和BIN2
为低电平(LOW
)来实现。同时,通过ledcWrite(CHANNEL, 200);
设置PWM通道的占空比为200(在8位分辨率下,最大值为255,所以200大约是50%的占空比),以控制电机的速度。case 2
: 当i_status
等于 2 时,小车将右转。这通过将左侧电机的两个控制引脚都设置为低电平(LOW
),而右侧电机的前进控制引脚BIN1
设置为高电平,BIN2
设置为低电平来实现。case 3
: 当i_status
等于 3 时,小车将左转。代码中有一个错误,digitalWrite(BIN1, LOW);
被重复了,应该是digitalWrite(BIN2, LOW);
。这样左侧电机的前进控制引脚AIN1
设置为高电平,AIN2
设置为低电平,而右侧电机的两个控制引脚都设置为低电平。case 4
: 当i_status
等于 4 时,小车将停止。这通过将所有电机控制引脚设置为低电平来实现,并且通过ledcWrite(CHANNEL, 0);
停止PWM信号,从而停止电机转动。
五.串口的信息接收
date_accept
函数是用于从串口接收数据并根据接收到的数据设置状态的一个函数。
- 检查串口缓冲区:
if (Serial.available() > 0)
检查串口缓冲区是否有数据可读。Serial.available()
返回缓冲区中可读取的字节数。 - 读取数据: 如果缓冲区中有数据,
int incomingByte = Serial.read();
读取一个字节的数据,并将其存储在incomingByte
变量中。 - 数据解析与状态设置: 使用
switch
语句根据接收到的incomingByte
值设置i_status
变量的值。每个case
对应一个可能的输入值:case '0'
: 如果接收到的字节是 ‘0’,则将i_status
设置为 0。case '1'
: 如果接收到的字节是 ‘1’,则将i_status
设置为 1。case '2'
: 如果接收到的字节是 ‘2’,则将i_status
设置为 2。case '3'
: 如果接收到的字节是 ‘3’,则将i_status
设置为 3。case '4'
: 如果接收到的字节是 ‘4’,则将i_status
设置为 4。default
: 如果接收到的字节不是上述指定的字符之一,则不执行任何操作。
小结
以上就是本节课程的全部内容,基本上都是对之前知识点的回顾与分析,通过对所有之前学习的内容的结合,巩固知识:定时器与中断,电机驱动,串口通讯,i2c数据传输等。
本节课中,更多的是代码上的展示,需要大家对代码方面进行实践,才能学到很多