寒假在家一起练——esp32—s2
1.项目介绍: 通过IO扩展板上的按键和旋转编码器控制并实现菜单功能
IO扩展板上的1个按键和旋转编码器的3个输入端口是通过R-2R电阻网络的方式连接在一起,生成一个模拟电压量。按下任何一个按键都会改变这个模拟电压量的值。IO扩展板上的LCD屏幕为128*128分辨率的1.44寸彩色屏幕,通过SPI总线进行访问
要求:本任务需要通过ESP32核心板的ADC监测IO板模拟输出管脚的变化,判断哪一个按键或编码器的旋转发生了变化,进而控制1.44寸LCD屏幕的菜单显示,要求实现主菜单和至少二级菜单。
2.设计思路
2.1基本设计思路
MCU通过ADC检测R-2R电路输出电压的变化,进而判断是某种方式引起的变化(按键,旋转编码器),通过确定某种按键以及编码器变化,再根据他们不同的组合,再结合LCD屏幕进而生成二级菜单;
2.2详细设计思路(基于micropython)
1:确定所需的硬件
LCD屏幕,板载ADC,定时器0,旋转编码器,按键,RGB灯
2:初始化所需的硬件
1.1:初始化ADC
初始化ADC通道0(根据原理图编码器电路输出选择对应ADC通道)
不同的衰减倍数对应不同的检测电压范围。
ADC的默认满量程电压为1.1V。要读取更高
的电压(最高为引脚最大电压,通常为3.3V)
则需要将该ADC通道的信号衰减设置为> 0dB。
0dB衰减(ADC_ATTEN_0db)表示参考电压为1.1V
2.5dB衰减(ADC_ATTEN_2_5db)表示参考电压为1.5V
6dB衰减(ADC_ATTEN_6db)表示参考电压为2.2V
11dB衰减(ADC_ATTEN_11db)表示参考电压为3.9V
adc0=ADC(Pin(pin_cfg.A_OUT))
adc0.atten(ADC.ATTN_11DB)
1.2:初始化定时器0
'''
初始化定时器0
'''
time0=Timer(0)
'''
开启定时器 10ms进入一次回调函数
'''
time0.init(period=10,callback=Timer_adc_read_t)
1.3:初始化RGB灯
RGB_LED_R=Pin(pin_cfg.RGB_LED_RED,Pin.OUT)
RGB_LED_B=Pin(pin_cfg.RGB_LED_BLUE,Pin.OUT)
RGB_LED_G=Pin(pin_cfg.RGB_LED_GREEN,Pin.OUT)
1.4:初始化LCD(SPI协议)
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是美国摩托罗拉公司(Motorola)最先推出的一种同步串行传输规范,也是一种单片机外设芯片串行扩展接口,是一种高速、全双工、同步通信总线,所以可以在同一时间发送和接收数据,SPI没有定义速度限制,通常能达到甚至超过10M/bps。SPI有主、从两种模式,通常由一个主模块和一个或多个从模块组成(SPI不支持多主机),主模块选择一个从模块进行同步通信,从而完成数据的交换。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起,当存在多个从设备时,通过各自的片选信号进行管理。SPI通信原理很简单,需要至少4根线,单向传输时3根线,它们是MISO(主设备数据输入)、MOSI(主设备数据输出)、SCLK(时钟)和CS/SS(片选):
接下来是软件驱动部分
spi = SPI(2, baudrate=40000000, polarity=0, phase=0,sck=Pin(pin_cfg.LCD_SCL),mosi=Pin(pin_cfg.LCD_SDA))
lcd=TFT(spi,pin_cfg.LCD_DC, pin_cfg.LCD_RES, pin_cfg.LCD_CS)
lcd.initb3()
lcd.rgb(False)
lcd.rotation(2)
3:定时器回调函数读取ADC并判决按键及旋转编码器变化
def Timer_adc_read_t(timer):
global adc0_value_1
global adc0_value_2
'''
esp32最高分辨率是13位,该句输出的是ADC输出的原始值0~8191
'''
adc0_value_1=adc0.read()
'''
该句输出的是ADC输出的原始值扩放到0~65535范围的值
'''
adc0_value_2=adc0.read_u16()
panduan_which_key()
def panduan_which_key():
'''
在这里声明全局变量的意义就是为了防止重名
保证他不是一个新的变量,他是原来的全局变量
'''
global which_key
global adc0_value_2
'''
按键及旋钮采样率高10ms左右采集一次
'''
'''
把储存按键次数当前值的变量内的值赋给储存按键次数过去值的变量
'''
geshu_past[0]=geshu_now[0]
geshu_past[1]=geshu_now[1]
geshu_past[2]=geshu_now[2]
geshu_past[3]=geshu_now[3]
'''
各个按键被按下所满足的条件,并记录满足按键条件的次数
'''
if adc0_value_2>=17000 and adc0_value_2<=20000:
jilu_geshu[0]+=1
elif adc0_value_2>=43050 and adc0_value_2<=50000:
jilu_geshu[1]+=1
elif adc0_value_2>=51000 and adc0_value_2<=54400:
jilu_geshu[2]+=1
elif adc0_value_2>=56500 and adc0_value_2<=56950:
jilu_geshu[3]+=1
'''
把按键次数当前值赋给储存按键次数当前值的变量
'''
geshu_now[0]=jilu_geshu[0]
geshu_now[1]=jilu_geshu[1]
geshu_now[2]=jilu_geshu[2]
geshu_now[3]=jilu_geshu[3]
'''
根据记录满足按键的次数(类似软件延时)
+
geshu_past[0]==geshu_now[0]松开按键操作
来判定一次按键的完成
'''
if jilu_geshu[0]>=4 and geshu_past[0]==geshu_now[0]:
jilu_geshu[0]=0
which_key=1
geshu_now[0]=0
geshu_past[0]=0
elif jilu_geshu[3]>=2 and geshu_past[3]==geshu_now[3]:
jilu_geshu[3]=0
which_key=4
geshu_now[3]=0
geshu_past[3]=0
elif jilu_geshu[1]>=4 and geshu_past[1]==geshu_now[1]:
jilu_geshu[1]=0
which_key=2
geshu_now[1]=0
geshu_past[1]=0
elif jilu_geshu[2]>=2 and geshu_past[2]==geshu_now[2]:
jilu_geshu[2]=0
which_key=3
geshu_now[2]=0
geshu_past[2]=0
例:jilu_geshu[0]>=4类似51 32里判断按键时的延时消抖,标志着按键
geshu_past[0] 记录 过去一次定时器扫描 满足 当按下K1时所对应的ADC值范围条件的次数
geshu_now[0] 记录 当前定时器扫描 满足 当按下K2时所对应的ADC值范围条件的次数
geshu_past[0]==geshu_now[0](除去未按键状态)代表着按键松开了jilu_geshu[0]不会再增加
同理geshu_past[0]和geshu_now[0]都不会增加
如果(判断按键+(geshu_past[0]==geshu_now[0]))就代表一次按键有按键有松开,判定一次按键完毕。
4:根据确定的按键及旋转编码器变化的不同组合来控制LCD显示不同的GUI界面并实现不同功能
1.1:主GUI界面
'''
主界面操作函数 K2控制圆形移动,K1控制确定
'''
def main_GUI_set():
global main_GUI_flag
global RED_LED_GUI_flag
global BLUE_LED_GUI_flag
global GREEN_LED_GUI_flag
global VALUE_ADC_GUI_flag
global k1_anjian_cishu
global k2_anjian_cishu
global main_GUI_continue_reflesh_flag
if main_GUI_flag==1:
if main_GUI_continue_reflesh_flag==1:
main_GUI_continue_reflesh_flag=0
main_GUI_regular_show()
elif k1_anjian_cishu==0 and k2_anjian_cishu!=0:
main_GUI_fillcircle_move()
elif k2_anjian_cishu%4==0 and k1_anjian_cishu==1:
RED_LED_GUI_flag=1
elif k2_anjian_cishu%4==1 and k1_anjian_cishu==1:
BLUE_LED_GUI_flag=1
elif k2_anjian_cishu%4==2 and k1_anjian_cishu==1:
GREEN_LED_GUI_flag=1
elif k2_anjian_cishu%4==3 and k1_anjian_cishu==1:
VALUE_ADC_GUI_flag=1
对应实物
其中不同的GUI界面所对应不同的状态标志位main_GUI_flag=1标志着当前界面在主界面
(总共五个GUI界面)
刚上电
GREEN_LED_GUI_flag=0
VALUE_ADC_GUI_flag=0
BLUE_LED_GUI_flag=0
RED_LED_GUI_flag=0
main_GUI_flag=1
main_GUI_continue_reflesh_flag是主GUI界面所对应的是否持续更新标志位如:lcd当前界面在主界面,有些东西显示一次就行,不用一直刷新,一直刷新,会严重影响程序速率所以有五个GUI界面,就有五个是否持续更新标志位。
刚上电
GREEN_LED_GUI_continue_reflesh_flag=1
VALUE_ADC_GUI_continue_reflesh_flag=1
BLUE_LED_GUI_continue_reflesh_flag=1
RED_LED_GUI_continue_reflesh_flag=1
main_GUI_continue_reflesh_flag=1
k2_anjian_cishu,k2_anjian_cishu记录按键的次数,K2控制圆形移动,K1控制确定,进而判断进入了哪个GUI以及选择了什么功能
1.2:红灯GUI界面
'''
红灯GUI界面操作函数 K2控制圆形移动,K1控制确定
'''
def RED_LED_GUI_set():
global main_GUI_flag
global RED_LED_GUI_flag
global k1_anjian_cishu
global k2_anjian_cishu
global main_GUI_continue_reflesh_flag
global RED_LED_GUI_continue_reflesh_flag
if RED_LED_GUI_flag==1:
main_GUI_flag=0
if RED_LED_GUI_continue_reflesh_flag==1:
RED_LED_GUI_regular_show()
RED_LED_GUI_continue_reflesh_flag=0
k1_anjian_cishu=0
k2_anjian_cishu=0
RED_LED_GUI_fillcircle_move()
if k2_anjian_cishu%3==0 and k1_anjian_cishu==1:
LED_RED_ON()
lcd.text((100,2), "ON ", TFT.RED, sysfont, 1.1)
lcd.text((100,22),"OFF", TFT.BLACK, sysfont, 1.1)
k1_anjian_cishu=0
elif k2_anjian_cishu%3==1 and k1_anjian_cishu==1:
LED_RED_OFF()
lcd.text((100,2), "ON ", TFT.BLACK, sysfont, 1.1)
lcd.text((100,22),"OFF", TFT.RED, sysfont, 1.1)
k1_anjian_cishu=0
elif k2_anjian_cishu%3==2 and k1_anjian_cishu==1:
k1_anjian_cishu=0
k2_anjian_cishu=0
main_GUI_flag=1
RED_LED_GUI_flag=0
main_GUI_continue_reflesh_flag=1
RED_LED_GUI_continue_reflesh_flag=1
对应实物
1.3:蓝灯GUI界面
'''
蓝灯GUI界面操作函数 K2控制圆形移动,K1控制确定
'''
def BLUE_LED_GUI_set():
global main_GUI_flag
global BLUE_LED_GUI_flag
global k1_anjian_cishu
global k2_anjian_cishu
global main_GUI_continue_reflesh_flag
global BLUE_LED_GUI_continue_reflesh_flag
if BLUE_LED_GUI_flag==1:
main_GUI_flag=0
if BLUE_LED_GUI_continue_reflesh_flag==1:
BLUE_LED_GUI_regular_show()
BLUE_LED_GUI_continue_reflesh_flag=0
k1_anjian_cishu=0
k2_anjian_cishu=0
BLUE_LED_GUI_fillcircle_move()
if k2_anjian_cishu%3==0 and k1_anjian_cishu==1:
LED_BLUE_ON()
lcd.text((100,2), "ON ", TFT.BLUE, sysfont, 1.1)
lcd.text((100,22),"OFF", TFT.BLACK, sysfont, 1.1)
k1_anjian_cishu=0
elif k2_anjian_cishu%3==1 and k1_anjian_cishu==1:
LED_BLUE_OFF()
lcd.text((100,2), "ON ", TFT.BLACK, sysfont, 1.1)
lcd.text((100,22),"OFF", TFT.BLUE, sysfont, 1.1)
k1_anjian_cishu=0
elif k2_anjian_cishu%3==2 and k1_anjian_cishu==1:
k1_anjian_cishu=0
k2_anjian_cishu=1
main_GUI_flag=1
BLUE_LED_GUI_flag=0
main_GUI_continue_reflesh_flag=1
BLUE_LED_GUI_continue_reflesh_flag=1
对应实物
1.4:绿灯GUI界面
'''
绿灯GUI界面操作函数 K2控制圆形移动,K1控制确定
'''
def GREEN_LED_GUI_set():
global main_GUI_flag
global GREEN_LED_GUI_flag
global k1_anjian_cishu
global k2_anjian_cishu
global main_GUI_continue_reflesh_flag
global GREEN_LED_GUI_continue_reflesh_flag
if GREEN_LED_GUI_flag==1:
main_GUI_flag=0
if GREEN_LED_GUI_continue_reflesh_flag==1:
GREEN_LED_GUI_regular_show()
GREEN_LED_GUI_continue_reflesh_flag=0
k1_anjian_cishu=0
k2_anjian_cishu=0
GREEN_LED_GUI_fillcircle_move()
if k2_anjian_cishu%3==0 and k1_anjian_cishu==1:
LED_GREEN_ON()
lcd.text((100,2), "ON ", TFT.GREEN, sysfont, 1.1)
lcd.text((100,22),"OFF", TFT.BLACK, sysfont, 1.1)
k1_anjian_cishu=0
elif k2_anjian_cishu%3==1 and k1_anjian_cishu==1:
LED_GREEN_OFF()
lcd.text((100,2), "ON ", TFT.BLACK, sysfont, 1.1)
lcd.text((100,22),"OFF", TFT.GREEN, sysfont, 1.1)
k1_anjian_cishu=0
elif k2_anjian_cishu%3==2 and k1_anjian_cishu==1:
k1_anjian_cishu=0
k2_anjian_cishu=2
main_GUI_flag=1
GREEN_LED_GUI_flag=0
main_GUI_continue_reflesh_flag=1
GREEN_LED_GUI_continue_reflesh_flag=1
对应实物
1.5:ADC值GUI界面
'''
ADC值GUI界面操作函数
'''
def VALUE_ADC_GUI_set():
global k1_anjian_cishu
global k2_anjian_cishu
global kL_anjian_cishu
global kR_anjian_cishu
global main_GUI_flag
global VALUE_ADC_GUI_flag
global VALUE_ADC_GUI_continue_reflesh_flag
if VALUE_ADC_GUI_flag==1:
main_GUI_flag=0
if VALUE_ADC_GUI_continue_reflesh_flag==1:
VALUE_ADC_GUI_regular_show()
VALUE_ADC_GUI_continue_reflesh_flag=0
k1_anjian_cishu=0
k2_anjian_cishu=0
kL_anjian_cishu=0
kR_anjian_cishu=0
VALUE_ADC_GUI_fillcircle_move()
origin_adc_set()
expand_adc_set()
voltage_v_set()
VALUE_ADC_GUI_EXIT()
对应实物
由于origin adc
expand adc
voltage(V)
这三个功能位并不像红绿蓝灯GUI界面下灯开启一样简单,绿蓝灯GUI界面下灯开启后就没必要再次开启刷新了,但这三个功能位开启后还需要一直刷新数据,所以要设定标志位让他们刷新。
origin_adc_on_flag=1 标志着LCD显示esp32_s2 13位ADC读取到的原始数字量
expand_adc_on_flag=1 标志着LCD显示esp32_s2 13位ADC读取到的数字量扩放到16位的数字量
voltage_on_flag=1 标志着LCD显示esp32_s2 13位ADC读取到的数字量转化为的电压值
初始化:
origin_adc_on_flag=0
expand_adc_on_flag=0
voltage_on_flag=0
1.6:选择GUI界面
'''
选择哪一个GUI界面,默认开始是在主界面
'''
def which_GUI():
main_GUI_set()
RED_LED_GUI_set()
BLUE_LED_GUI_set()
GREEN_LED_GUI_set()
VALUE_ADC_GUI_set()
3设计思路框图
4.软件流程框图
5.硬件介绍
本次项目核心板
ESP32-S2 WiFi模块是物联网、可穿戴电子设备和智能家居等应用场景的理想选择,另搭配输入控制、输出显示以及传感器感知和控制的套件,使其功能更加完善。
该模块板载了:
- ESP32-S2-MINI-1模组
- 这是一款4 GHz WiFi 模组
- 内置 ESP32S2 系列芯片,Xtensa® 单核 32 位 LX7 微处理器
- 内置芯片叠封 4 MB flash,可叠封 2 MB PSRAM
- 37 个 GPIO,具有丰富的外设
- 板载 PCB 天线
配套的ESP32 S2 开发板除了ESP32wifi模组之外还集成了USB TYPE -C接口,两个按键,一个电源指示灯,一个用户LED灯,2排10pin的排针,将重要IO引出。使用USB供电或通过排针3.3V供电。
本次项目扩展版
- 按键、旋转编码器输入 - 以模拟信号的方式
- 双电位计控制输入 - 以数字信号的方式
- RGB三色LED显示
- 1.44寸128*128 LCD,SPI总线访问
- MMA7660三轴姿态传感器
- 电阻加热
- 温度传感器
- 与ESP32-S2核心模块的接口
6:功能实现
成功采集到编码器以及按键的变化,并实现了二级菜单功能的实现
五个GUI界面(完整演示在B站视频)
7:主函数代码(都封装在各自函数中了)
while True:
which_GUI()
which_key_show()
8:遇到的主要难题
1:怎样识别到一次按键或旋转一次编码器的完成?
识别到还是很容易的,但还要加上松键操作这个我大致想了一天,最后采用以下方法解决了,效果还是不错的。
'''
例:
geshu_past[0] 记录 过去一次定时器扫描 满足 当按下K1时所对应的ADC值范围条件的次数
geshu_now[0] 记录 当前定时器扫描 满足 当按下K2时所对应的ADC值范围条件的次数
geshu_past[0]==geshu_now[0](除去未按键状态)代表着按键松开了jilu_geshu[0]不会再增加
同理geshu_past[0]和geshu_now[0]都不会增加
如果(判断按键+(geshu_past[0]==geshu_now[0]))就代表一次按键有按键有松开,判定一次按键完毕。
'''
geshu_past=[0,0,0,0]
geshu_now =[0,0,0,0]
2:大量标志位的设置
这个是在一步步的调试中解决的。
3:我刚开始是打算用两个按键实现这个二级菜单,做前面几个GUI时还行基本都实现了,但最后一个时,就是有问题,最后检查发现圈起来的地方判断重复了,然后想了好多方法让他们避免冲突,但发现完全避免不了(个人的想法哈)
没办法只好换个判断采用了编码器的旋转,效果还可以。
9:未来的计划或建议
1:未来的计划
我计划在嵌入式行业深究,本科毕业前熟悉 ARM、STM32 系列等嵌入式开发,熟悉常用接口硬件设计与驱动调试,包括 SPI,I2C、PCIe、USB、UART 等常见总线协议硬件原理设计、调试手段、驱动开发,能够对系统与设备问题进行分析定位熟练掌握自动化设备控制流程、深入了解硬件接口,精通硬件通信协议。我打算32就跑跑嵌入式实时操作系统,例如 freertos,rtthread 等,实现快速开发;
2:建议
望自己不要计较其他得失,学会技术才是关键,确定了这个方向就要坚定的走下去,多跟大佬交流,感受自己的差距。
10:最后的最后
感谢硬禾学堂举办的活动,真的挺好,很能鼓励像我这样的小白,望硬禾学堂越办越好,我在这里由衷的谢谢你们了。