最近搞了个petoi的机器狗,是基于opencat这个框架做的,这里来读一读opencat的代码,顺便入一下嵌入式开发的门
顺便体验一下gpt,看看在目前大模型的帮助下自己入门一个新领域需要多长时间。
这里的代码仓库是opencat的esp32版本:https://github.com/PetoiCamp/OpenCatEsp32
板子

电源目前还看不太懂,就不看了,这里就是看看一些功能性的模块
- ESP32,SoC,有WiFi,蓝牙,16M的flash
- CP2102 UART to USB,是用来做串口通信的
- MPU6050,是陀螺仪,用来获知当前的动作状态,通过I2C和ESP32连接
- EEPROM,持久化存储,通过I2C和ESP32连接,有64k的空间
- 12个PWM舵机接口:一般是3个接口,电源,地(电源负极),控制信号,用来控制舵机的转角
- 还有一些其他的,暂时不关注
代码
然后看看代码,ESP32支持使用Arduino IDE开发,代码读起来也比较简单。和普遍的C程序类似,Arduino的程序预定义了setup()和loop()两个函数
setup()函数:setup()函数只在整个程序的开始时调用一次。它用于初始化变量、配置引脚模式、启动外围设备、初始化串行通信等。- 在
setup()函数中,你会设置你的Arduino板需要的所有初始配置。一旦setup()函数完成执行,Arduino将永远不会再次进入这个函数。
loop()函数:loop()函数在setup()函数之后被调用,并且会不断地重复执行,直到Arduino板断电或重置。- 在
loop()函数中,你编写需要重复执行的任务,比如读取传感器数据、处理输入、控制输出设备等。 loop()函数的执行频率取决于在其中执行的代码的复杂性和Arduino板的处理能力。理论上,loop()函数的执行速度尽可能快,但实际上,它受到代码执行时间和板载硬件的限制。
这里的loop就是每执行完一轮就立刻执行下一轮,所以loop的逻辑要足够简单才能执行的够快
setup

Arduino中有一些预定义的库,这个Serial就是一个,负责进行串口通信,一般是debug用。
这里的具体含义就是初始化波特率为115200,即115200 bit/s,然后把缓冲区中的数据都清空,再调用initRobot
initRobot中,先Wire.begin(),这里表示的是初始化I2C总线。
I2C(Inter-Integrated Circuit)是一种用于连接低速外围设备的串行通信协议,常用于传感器、EEPROM、RTC(实时时钟)等设备的通信。
Wire库是Arduino IDE中内置的库,它提供了与I2C设备通信的接口。Wire.begin()函数需要在setup()函数中调用,以启动I2C总线,并使Arduino作为I2C设备进行通信准备。
然后打了一些debug日志,如Start等等
printToAllPorts(),这里会把当前板子的名字,这里是"bitte"发给所有的端口。比如发送蓝牙等,具体细节回头再看

* 读取eeprom中的数据
* Wire.beginTransmission()是开启I2C传输会话,DEVICE_ADDRESS是对应的EEPROM设备的I2C地址
* Wire.Wire()写入eeaddress的高8位和低8位。MSB和LSB代表Most Significant Byte和Lease Significant Byte
* Wire.endTransmission(),停止会话,这里就是把上面的地址发给了EEPROM
* Wire.requestFrom(),向EEPROM发送读取请求,请求一个byte的数据
* Wire.read()读取数据

* i2cDetect,扫描I2C网络,并打印出所有连接的I2C的设备的地址
* I2C的地址范围是1~126,,这里是扫描所有的端口
* Wire.beginTransmission(), Wire.endTransmission(),如果返回err为0,说明这个设备有响应,则打印对应的地址
* 如果错误码为4,说明传输超时,设备没有响应(可能是有设备,但是设备坏了什么的),会打印Unknown error
- 然后是i2cEepromSetup,这里应该是读取现有的数据,来看程序是否是第一次初始化
- 先读一下birth mark,看看是否已经初始化了。如果是新的板子的话,会执行初始化的函数
- 这里会把sortware version持久化进去
- 写入sound state和buzzer volume
- 将moduleActivatedQ数据中的内容写入到eeprom中,这里moduleActivatedQ还不清楚是什么含义
- playMelody,这里会播个音乐
- 生成一个新的id,作为robot的名字
- 这里会询问用户,是否做一些数据的初始化,校准的offset什么的
- 如果不是第一次执行的话,这里会判断一下代码的版本,如果有升级的话,这里会做resetAsNewBoard,重新初始化
- 读取moduleActivatedQ数组
- 这里有一个小疑问是,如果我更改了代码的版本的话,他是否会初始化EEPROM呢?
- 然后是一些其他设备的初始化:
- imuSetup,陀螺仪的初始化,inertial measurement unit,即惯性测量单元
- bleSetup,bluetooth low energy
- blueSspSetup,bluetooth secure simple pairing
- servoSetup,初始化伺服系统
- 伺服系统的介绍:
- 伺服系统(Servo System)是一种用于精确控制位置、速度和力的系统。它广泛应用于机器人、自动化设备、数控机床、遥控模型等领域。伺服系统通常包括一个伺服驱动器(Servo Driver)和一个伺服电机(Servo Motor)。
- 伺服驱动器:伺服驱动器是伺服系统的控制部分,它接收来自控制器的信号,并将其转换为伺服电机可以理解的电信号。伺服驱动器通常具有电流、速度和位置控制功能,可以精确控制伺服电机的运动。
- 伺服电机:伺服电机是伺服系统的执行部分,它将电信号转换为机械运动。伺服电机通常具有高扭矩、高精度、快速响应的特点,可以快速准确地执行指令。
- 伺服系统的工作原理通常包括以下步骤:
- 指令输入:控制器发送一个指令给伺服驱动器,指示伺服电机需要达到的位置、速度或力。
- 信号处理:伺服驱动器接收到指令后,进行信号处理,生成一个可以驱动伺服电机的电信号。
- 电机驱动:伺服驱动器将处理后的电信号发送给伺服电机,伺服电机根据电信号驱动机械部件运动。
- 反馈调整:伺服系统通常包括一个反馈机制,如编码器或霍尔效应传感器,用于检测伺服电机的实际位置或速度。反馈信号发送回伺服驱动器,驱动器根据反馈信号调整电信号,以实现精确控制。
- 伺服系统(Servo System)是一种用于精确控制位置、速度和力的系统。它广泛应用于机器人、自动化设备、数控机床、遥控模型等领域。伺服系统通常包括一个伺服驱动器(Servo Driver)和一个伺服电机(Servo Motor)。
- 先读取EEPROM中的校准值到servoCalib中
- allocateTimer,还不清楚是干什么的
- attachAllESPServos
- 遍历所有的PWM,一共12个,然后这里的DOF是8(DOF是自由度的意思,degrees of freedom。具体计算还不是很清楚,这里他一共有8个关节,可能就是8个DOF的意思)
- 获取servoModule的类型,这个计算方式很诡异,对于前8个是regular,然后后面4个是knee
- 代码提前定义了ServoModel,分别是servoG41, servoP1S和servoP1L,定义了伺服系统的angel range, frequency, minPulse, maxPulse
- 等下看看具体都代表什么
- Servo.attach(PWM_pin, modelObj),给对应的伺服系统添加pin,以及model obj。
- zeroPosition,记录对应关节的offset
- calibratedZeroPosition,修正关节的offset
- 伺服系统的介绍:
- 如果定义了VOLTAGE的话,这里会检测,如果是电量过低,会死循环,不会继续推进了
-
QA,quality assurance program,应该是一些测试之类的玩意,保证硬件正常
-
写入birth mark,标记板子初始化正确。让我联想到一些原子初始化相关的事情。如果初始化到一半,写入内存失败怎么半?感觉应该记录一个initializing的状态,如果是初始化未完成,会放弃读取EEPROM中的所有数据。或者单纯在没有初始化的时候就不读取EEPROM
-
initModuleManager(),下来再看看
Servo
伺服系统可以通过脉冲宽度调整(Pulse Width Modulation, PWM)和角度调整来实现精确控制,这主要是因为伺服电机的设计和控制方式。伺服电机通常包括一个旋转编码器,它可以测量电机轴的旋转角度。这种设计使得伺服系统能够通过两种不同的方式进行控制:
1. 脉冲宽度调整(PWM):
- 伺服系统使用PWM信号来控制电机的转速。PWM信号的频率和占空比决定了电机的转速和方向。
- 频率(F)是指PWM信号的周期数,而占空比(D)是指每个周期中信号为高电平的时间与整个周期的比例。
- 通过改变PWM信号的频率和占空比,可以精确控制电机的转速和方向。
2. 角度调整:
- 伺服电机通常配备了旋转编码器,它可以测量电机的旋转角度。
- 控制器通过发送一系列的脉冲信号来控制电机的旋转角度。每个脉冲信号都会导致电机旋转一定角度,这个角度通常由控制器内部的精确计数器来测量。
- 通过发送特定数量的脉冲信号,可以精确控制电机的旋转角度。例如,发送100个脉冲信号可能会导致电机旋转90度。
- 看一看Servo的逻辑,这个貌似是一个开源的库,ESP32的Servo library
- attach,传入GPIO的引脚号,以及ServoModel,即上面说的angleRange什么的
- 这里就是记录一些参数,然后调用pwm.attachPin(),每一个Servo都有一个ESP32PWM的成员
- write(),设置Servo的角度,单位是度。这里的语意是:
- 如果传入的value是0~180,则认为是degree
- 如果传入的value是min~max,这里直接设置的是500 ~ 2500,则会认为是microseconds。这里是设置脉冲宽度
- writeMicroseconds(),以脉冲宽度写入Servo
- read(),读取上次设置的脉冲宽度,以angle来读
- readMicroseconds,读取上次写入的脉冲宽度,以microsecond来读
这里主要的逻辑都在ESP32PWM中,用来发送PWM信号。问了下GPT,发送ESP32中发送PWM信号有几种:
* 可以直接用Arduino的analogWrite
* 使用GPIO直接发送PWM信号
* ledc库
* 使用定时器生成PWM信号
* 外部PWM发生器
ledcWrite()本来用来控制led灯带的亮度,原理是通过发送PWM信号,来控制电流的通断时间,从而控制电流的大小,来调节亮度。
ledcWrite()的参数是接受一个通道号和占空比,ledcWrite将占空比转化为PWM信号发送给对应的LED灯带通道
https://docs.petoi.com/v/chinese/li-cheng/8.-pwm
ledc:
* ledcSetup,设置通道的频率,resolution。ESP32中有16个通道,8个高速通道和8个低速通道。这里的精度有上限,如果频率很高,那么占空比就不能很细致。大概感觉就是他的timer是有精度上限的。设置timer生成信号的频率就可以得到PWM信号了。
* ledcAttachPin,设置ledc的输出引脚,就是把对应的通道的PWM信号发给那个设备来控制
* ledcWrite,选择通道,设置他的占空比进行输出
先写到这里吧,感觉舵机这块需要单独看看他的module test
文章评论