ESP32

在这里插入图片描述

在这里插入图片描述


控制两个不同周期的LED灯闪烁

思路:
在loop中循环检测运行时长(loop执行时间非常短)
定义的int类型是占了4字节所以是232 (-2,147,483,648 到2,147,483,647)
unsigned int 是0 到4,294,967,295
1d=24h=1,440m=86,400s
1h=60m=3600s
1m=60s

mills()
功能:获取本程序已经进行多少毫秒
返回值:本程序已经运行的时间(毫秒)
micros()返回的微秒

#include <Arduino.h>
int ledPin1 = 12;
int ledPin2 = 13;
int led1Status = 0;
int led2Status = 0;
unsigned int prev1Time = 0;
unsigned int prev2Time = 0;
// put function declarations here:

void setup() {
  // put your setup code here, to run once:
  pinMode(ledPin1,OUTPUT);
  pinMode(ledPin2,OUTPUT);
  digitalWrite(ledPin1,HIGH);
  digitalWrite(ledPin2,HIGH);
  led1Status = HIGH;
  led2Status = HIGH;
  prev1Time = millis();
  prev2Time = millis();
}

void loop() {
  // put your main code here, to run repeatedly:
  unsigned int now1 = millis();
  if(now1 -prev1Time > 3000)
  {
    int status1 = led1Status == HIGH ? LOW : HIGH;
    digitalWrite(ledPin1,status1);
    led1Status = status1;
    prev1Time = now1;
  }
  unsigned int now2 = millis();
  if(now2 -prev2Time > 2000)
  {
    int status2 = led2Status == HIGH ? LOW : HIGH;
    digitalWrite(ledPin2,status2);
    led2Status = status2;
    prev2Time = now2;
  }
}
// put function definitions here:

使用RBD库函数读取按键状态

#include <Arduino.h>
#include<RBD_Timer.h>
#include<RBD_Button.h>
int pin = 2;
int Button = 5;
int ledStstus =0 ;
RBD::Button button(Button,INPUT_PULLUP);
//创建一个消除按键抖动的对象

// put function declarations here:


void setup() {
  // put your setup code here, to run once:
  pinMode(pin,OUTPUT);
  button.setDebounceTimeout(20);
  digitalWrite(pin,HIGH);
  ledStstus = HIGH;
  }

void loop() {
  // put your main code here, to run repeatedly:

  if(button.onPressed())
  {
    ledStstus = !ledStstus;
    digitalWrite(pin,ledStstus);
  }
}

// put function definitions here:

LEDC/PWM/led呼吸灯(有单独的硬件电路)

ledc是指led pwm控制器,实际上是pwm信号产生器

ledc通道绑定gpio控制led亮度

PWM分辨率:讲一个周期的PWM分成若干份
最大分辨率公式:
f l o o r ( l o g 2 ?? M H z P W M 频率 ) floor(log _{2} \frac{??MHz}{PWM频率}) floorlog2PWM频率??MHz

其中floor是向下取整
频率先设置,然后精度(分辨率)根据公式算出,也向下取整

int led=0;//设置通道
int gpio=4;//设置要绑定的gpio管脚
ledcSetup(ch0,5000,12);//设置通道0的频率和分辨率
ledcAttachPin(gpio4,ch0);
ledcWrite(ch0,pow(2,11));

精度(分辨率)设置的是12(212)一个周期共分成4096个格子
pow(2,11) 高电平占用2048个格子
所以占空比是50%

led灯呼吸灯效果(ledc/pwm实现)

思路
在这里插入图片描述

  • 每秒固定调整占空比50次
  • 假设T为呼吸周期,则光从关闭到最亮经过的时间就是T/2.
  • 半个周期就要进行50*T/2次调整占空比
  • count表示的是占空比为100%时等分的格子
  • step是每次调整要加上的步进值(增量)
    s t e p = c o u n t 50 ∗ T 2 = 2 ∗ c o u n t 50 ∗ T step = \frac{count}{50*\frac{T}{2}}=\frac{2*count}{50*T} step=502Tcount=50T2count
  • 占空比超过100%时就要以step值递减到0

使用阻塞代码(使用delay)

#include <Arduino.h>
int gpio = 16;
int ch = 0;//leds通道
int duty = 0;
int count = 0;
int step = 0;
int breathTime = 3;//呼吸灯的周期,单位为s
// put function declarations here:


void setup() {
  // put your setup code here, to run once:
  ledcSetup(ch,1000,12);//建立ledc通道
  count = pow(2,12);//计算占空比为100时所占的格子数量
  step = 2 * count /(50 * breathTime);//计算一次增加多少占空比格子
  ledcAttachPin(gpio,ch);
 }

void loop() {
  // put your main code here, to run repeatedly:
  ledcWrite(ch,duty);
  duty += step;
  if(duty > count)
  {
    duty = count;
    step = -step;
  }
  else if(duty <0)
  {
    duty = 0;
    step = -step;
  }
  delay(20);
}

// put function definitions here:

不使用阻塞代码(使用millis)

int prevTime = 0;//记录上一次调整占空比的时间
……

void loop() {
  // put your main code here, to run repeatedly:
  int now = millis();
  if(now - prevTime >= 20)
  {
    ledcWrite(ch,duty);
    duty += step;
    if(duty > count)
  {
    duty = count;
    step = -step;
  }
  else if(duty <0)
  {
    duty = 0;
    step = -step;
  }
  }
prevTime = now;
}


AsyncTimer(定时器库)

AsynTimer使用基础 :

  • handle函数:所有操作都在此函数中完成,每一次loop时定时器都在handle中执行一次所有和定时器有关的操作。
  • t.handle();//要在loop中写一次
 #include <AsyncTimer.h>
 AsyncTimer timer;//定义一个定时器
 void setup() {
 
 }

void loop() {
timer.handle();//loop中必须写上这句
//这里开始处理其它事情

}

创建单次运行的定时任务

  • setTimeout(callbackFunction,delaylnMs)
  • 返回值:成功返回任务编号,失败返回0

回调函数:

  1. 可以使用普通的函数,形式为void func(){}
  2. 也可以使用lamda表达式,无参无返回值
  • 使用lamda [](){}
    • []参数:=传值,&传址
    • ()返回值
    • {}业务代码
  AsyncTimer t;
  t.setTimeout([](){
  	Serial.println("hello");
  	},2000);
  • 使用普通函数
  AsyncTimer t;
  void functionCall()
  {
  	Serial.println("hello");
  }
  t.setTimeout(functionCall,2000);

创建周期运行的定时任务

setInterval()和单次运行的定时任务类似

停止单个定时任务

cancel()

  • cancel an interval
AsyncTimer t;
unsigned short intervalId = t.setInterval([](){
	Serial.println("hello");
},2000);
//cancel the interval after 7 seconds:
t.setTimeout([=](){t.cancel(intervalId);
},7000);
  • cancel a timeout
AsyncTimer t;
//this timeout will never run 
unsigned short timeoutId = t.setTimeout([](){
	Serial.println("hello");
},3000);
//cancel the timeout before its executed
t.cancel(timeoutId);

停止多个定时任务

cancelAll(includeIntervals = true)
如果includeIntervals为true ,是取消所有的定时任务,包括周期性和一次性,如果是false,则只取消单次的定时任务

t.cancelAll();//括号内不填是true:取消所有的定时任务
t.cancelAll(false);//取消单次定时任务

I2C(Wire库)

两线的串行半双工通信方式
地址有7位和10位的
速度快于串口,慢于SPI。标准模式(100kbps)快速模式是(400kbps)

Wire库

  • 包含头文件
  • 库里面已经定义好两个I2C对象,可以直接拿来使用wire,wire1
    -默认的时钟频率是100khz

初始化

  • wire.begin();//成功返回true,失败返回false

重载:

  1. 作为主机加入时:
    1.1 无参数传入:即都使用默认sda:21,scl:22,频率100Khz
    1.2只传递sda,scl
    1.3都自定义3个参数
  2. 作为从机加入时
    2.1一个参数:指定从机地址
    2.2个参数:地址,sda,scl,频率都自定义

主机向从机发送数据(1)

  • beginTransmission(address);//开始传输
    • address是从机地址
  • uint8_t endTransmission(bool sendStop = true);//结束传输
    • 如果sendStop为true,则释放总线,false不释放总线
      • 返回值:
      • 0:成功
      • 1:数据过长,超出发送缓冲区
      • 2:在地址发送时接收到NACK信号
      • 3:在数据发送时接收到NACK信号
      • 4:其它错误
      • 5:超时

所有的写数据过程都要包含在两个函数之间

主机向从机发送数据(2)

size_t write(uint8_t);//只能传一个字节数据
size_t write(const uint8_t * ,size_t);//传一个Byte的数组

inline size_t write(const char * s)
{
return write((uint8_t*)s,strlen(s));
//返回值:成功传送的字节数
}

主机向从机发送数据使用write命令
第一个函数的重载版本,它的形参类型是uint8_t,是Byte类型,只能传递一个字节,如果用int long类型的数据传递进去,数据会被截断!(数据丢失)
如过传递多个字节,使用第二个函数重载

I2C设备地址的查找

原理:使用endTransmission函数返回0,说明该地址的设备链接在I2C总线上。枚举出设备地址

wire.begin();
for(int address = 0x01; address < 0x7f; address++)
{
	Wire.beginTransmission(address);
	auto error = Wire.endTransmission();
	if (error == 0)
	{
		Serial.printf("I2C device found at address 0x%02X\n", address);
		nDevices++;//有n个device
	}
{

主机向从机读取数据(1)

requestFrom(address,quantity,<stop>)

  • 请求后数据放在接收缓冲区

参数

  • address:从机地址
  • quantity:请求多少字节数量
  • stop:请求过后是否释放总线,true:释放(默认值)

返回值

  • 收到了从机发来的数据量大小(字节)

主机向从机读取数据(2)

int available(void);
判断接收缓冲区中有多少字节数据可读
int read(void);
接收一个字节的数据
size_t readBytes(uint8_t *buffer, size_t length)
一次读取多个字节
size t readBytesUntil(char terminator, uint8_t*buffer, size_t length)
一次最读取多个字节或碰到结束符terminator为止

从机注册相关的事件

void onReceive(void (*)(int));
注册接收到主机数据事件的回调函数,此回调函数在接收到主机发来的数据时触发
void onRequest(void(*)(void));
注册发送到主机数据事件的回调函数,此回调函数在主机向从机请求数据时触发

void setup()
{
	Wire.onReceive(onReceive);//注册接收事件
	Wire.onRequest(onRequest);//注册发送事件
	Wire.begin(27);//设置完成后将#27号从机加入到总线
}
  • 在onReceive的注册的回调函数中处理
void onReceive(int len)
{
	//有len字节数据可以接收
	while(wire.available())	
		{
		int val = Wire.read();
		//其它操作
		}
}
==============或者================
void onReceive(int len)
{
	//有len字节数据可以接收
	if(len == 0)
		return;
	byte* buf = new byte[len];
	Wire.readBytes(buf, len);
	//处理其它事情
	delete []buf;
}
  • 收到主机请求发送数据时,在onRequest中注册的回调函数就会被调用,此时就可以在此函数中向主机发送数据。
void onRequest()
{
char val ='a';
Wire.write(val);
}

ADC

void analogReadResolution(uint8_t bits);//设置ADC精度
void analogSetWidth(uint8_t bits);//设置位宽

void __analogReadResolution(uint8_t bits)
{
    if(!bits || bits > 16){
        return;
    }
    __analogReturnedWidth = bits;
#if CONFIG_IDF_TARGET_ESP32
    __analogSetWidth(bits);         // hadware from 9 to 12
#endif
}

设置衰减值

//所有的通道都设置
/*
 * Set the attenuation for all channels
 * Default is 11db
 * */
void analogSetAttenuation(adc_attenuation_t attenuation);
//设置指定的GPIO口
/*
 * Set the attenuation for particular pin
 * Default is 11db
 * */
void analogSetPinAttenuation(uint8_t pin, adc_attenuation_t attenuation);

typedef enum {
    ADC_0db,
    ADC_2_5db,
    ADC_6db,
    ADC_11db,
    ADC_ATTENDB_MAX
} adc_attenuation_t;

在这里插入图片描述

读取电压函数

/*
 * Get ADC value for pin,获取指定GPIO口的ADC值
 * */
uint16_t analogRead(uint8_t pin);

/*
 * Get MilliVolts value for pin,获取指定GPIO口的电压值,(毫伏)150-2450之间的精度可以使用
 * */
uint32_t analogReadMilliVolts(uint8_t pin);


中断

中断服务程序ISR (Interrupt Service Routines)

中断函数要求:
1.尽量短,减小执行时间
2.不要使用delay函数
3.不要使用serial、print等打印函数
4.和主程序共享的变量要加上volatile(多变,易失性)关键字
5.不要使用millis函数,它的值将不会增长
6.可以使用micros函数来获取程序运行时间

将中断附加到GPIO口
attachInterrupt(digitalPinToGPIONumber(pin), fcn, mode)
在这里插入图片描述
在这里插入图片描述
中断服务程序接收外界数据,共享给主程序,这时涉及到数据共享的问题

临界资源和临界区

  1. 临界资源:一次仅允许一个进程使用的共享资源(打印机)
  2. 临界区:每个进程中访问临界资源的那段程序称之为临界区

解决临界资源访问方法1:
在准备读取临界资源时暂时禁用中断,快速完成临界资源的访问后再开启中断
interrupts() //启用中断
noInterrupts()//禁用中断

ESP32有两颗CPU,禁用了这颗CPU的中断,另一颗CPU的线程还可能对临界资源访问,所以要对临界资源上锁,同时在处理临界资源时禁用中断.

自旋锁

解决临界资源访问方法2:
自旋锁加禁用中断
portMUX_INITIALIZER_UNLOCKED

typedef struct {
    uint32_t owner;
    uint32_t count;
} portMUX_TYPE;

portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
//在中断中使用的版本:
portENTER_CRITICAL_ISR(&mux);
···//原子操作,既上锁又禁用中断
···
portEXIT_CRITICAL_ISR(&mux);

//在主函数中使用的版本:
portENTER_CRITICAL(&mux);
···//原子操作,既上锁又禁用中断
···
portEXIT_CRITICAL(&mux);

二值信号量

在主函数中频繁使用portENTER_CRITICAL(&mux);会阻塞正常中断,所以引入二值信号量
(在中断中处理完成发送信号给主函数,主函数接收到信号后使用自旋锁后处理临界资源)

  • 信号量:semaphore是操作系统用来解决并发中的互斥和同步问题的一种方法
  • 信号量可以简单的理解为一个队列,我们只关注这个队列里元素的个数,也可以理解成一个整型全局变量,记录着信号个数
  • 二值信号量可以理解为一个队列,要么是满的(有信号),要么是空的(无信号),我们在任务中检查或等待信号的到来,再处理相关事宜
  • 二值信号量与全局变量相比,它可以保证原子性,可以等待信号(设置等待信号时间)等优点
  • 二值信号量常用于ISR和任务之间的同步

相关函数:

  1. 创建二值信号量
    xSemaphoreCreateBinary()
    成功返回信号量句柄,失败返回null
  2. 在普通任务中获取信号量
    xSemaphoreTake( xSemaphore, xBlockTime )
    xSemaphore:信号量句柄
    xBlockTime :等待的节拍数,0立即返回,portMAX_DELAY是一直等待,数字(ESP32默认1节拍是1ms)
    返回值:pdTRUE获取成功,pdFALSE获取失败
#if (configUSE_16_BIT_TICKS == 1)
typedef uint16_t TickType_t;
#define portMAX_DELAY (TickType_t)  0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY (TickType_t)  0xffffffffUL
#endif

在这里插入图片描述

  1. 在ISR中获取信号量
    xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )
  2. 在普通任务中释放信号量(也就是将信号量设置为有信号的状态)
    xSemaphoreGive( xSemaphore )
  3. 在ISR中释放信号量
    xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )
  4. 创建句柄
    SemaphoreHandle_t (加volatile修饰)

硬件定时器

精确定时
预分频器:ESP中的预分频器是16bit的,所以最大的分频数是65535
系统时钟通过预分频得到计数周期,给定时器使用
在这里插入图片描述

硬件定时器的使用流程:
初始化(确定时钟频率)->绑定ISR->设置触发ISR的计数值->启动定时器
1.void IRAM_ATTR onTimer(){······}//ISR中断服务函数
2.hw_timer_t * timer = NULL;//创建一个定时器的指针
3.timer = timerBegin(0,80,true);//初始化定时器,设置周期
4.timerAttachInterrupt(timer,onTimer,true);//将定时器与ISR绑定
5.timerAlarmWrite(tiemr,1000000,true);//在计数到1M时触发中断
6.timerAlarmEnable(timer);//开启定时器

函数功能,参数讲解

  1. 定时器初始化和结束
    1.1 定时器初始化
    hw_timer_t * timerBegin(uint8_t num, uint16_t divider, bool countUp);
    num:定时器编号,ESP32有4个可用定时器,编号0-3
    driver:分频数
    countUp: true为向上计数 , false为向下
    返回值:返回定时器指针,初始化失败返回null
    1.2定时器结束
    void timerEnd(hw_timer_t *timer);
  2. 定时器中断绑定与分离
    2.1定时器中断绑定
    void timerAttachInterrupt(hw_timer_t *timer, void (*fn)(void), bool edge);
    timer:定时器指针
    fn:ISR函数,函数原型为无参无返回
    edge:是否为边沿触发,true边沿,false电平
    2.2定时器中断分离
    void timerDetachInterrupt(hw_timer_t *timer);
  3. 定时器报警(中断)的设置(设置和获取参数)
    3.1 设定定时器计数器报警的值及周期模式
    void timerAlarmWrite(hw_timer_t *timer, uint64_t alarm_value, bool autoreload);
    alarm_value:计数器的值,计数到此值触发中断
    autoreload:是否重新加载定时器?true是 false一次性的
    3.1.1 设定定时器计数器报警的值
    void timerWrite(hw_timer_t *timer, uint64_t val);
    3.1.2设定定时器是否自动重新加载
    void timerSetAutoReload(hw_timer_t *timer, bool autoreload);
    3.2获取定时器是否重新加载
    bool timerGetAutoReload(hw_timer_t *timer);
    3.3获取定时器计数器报警的值,时间值
    uint64_t timerAlarmRead(hw_timer_t *timer);
    uint64_t timerAlarmReadMicros(hw_timer_t *timer);
    double timerAlarmReadSeconds(hw_timer_t *timer);
  4. 报警事件的启动与停止
    4.1 设置启动定时器报警
    void timerAlarmEnable(hw_timer_t *timer);
    4.2 设置禁用定时器报警
    void timerAlarmDisable(hw_timer_t *timer);
    4.3 获取定时器目前的报警状态是可用还是禁止
    bool timerAlarmEnabled(hw_timer_t *timer);
  5. 定时器计数器的运行控制
    5.1 计数器开始计数
    void timerStart(hw_timer_t *timer);

超声波测距模块(HC-SR04)

空气中的声速(20℃)是343m/s

距离 = 声速 ∗ 返回时间 2 距离=声速*\frac{返回时间}{2} 距离=声速2返回时间

pulseIn函数链接,此函数是阻塞式的
使用中断方式来测量距离
在这里插入图片描述
ESP32超声波测距代码查看链接,可编辑链接

舵机

扭矩
失速力矩(堵转力矩):电机轴被外力锁定的情况下,可连续输出力矩的最高值


SPI

  • 高速、全双工、同步的通信总线
    在这里插入图片描述
  • 强调控制权在主机
  • CS片选选出从机,下拉CS信号即为开始信号
  • 在SCK时钟线上升沿或下降沿时变换数据或者采集数据
  • 有4种工作模式:通过“时钟极性CPOL”和“时钟相位CPHA”来进行选择
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

ESP32API中都是以传输事务为基础的
片选线拉低到释放为一次传输事务,一次传输事务数据量最大是64字节,大于64字节要使用DMA

SPI主机库

首先引入头文件#include"SPI.h"
创建对象:SPIClass::SPIClass(uint8_t spi_bus)

SPIClass vspi(VSPI);
SPIClass hspi(HSPI);

另在源文件里定义了SPIClass SPI(VSPI);可以直接使用SPI对象

SPI主机通讯流程

  SPIClass * vspi = new SPIClass(VSPI);//创建对象
  vspi->begin();//SPI控制器绑定引脚,并占用总线(使用默认参数)
  pinMode(vspi->pinSS(),OUTPUT);//设置片选引脚为输出模式
  vspi->beginTransaction(SPISettings(SPI_CLK_DIV,MSBFIRST,SPI_MODE0));//设置SPI的传输参数
  digitalWrite(vspi->pinSS(),LOW);//片选
  vspi->transfer(data);
  digitalWrite(vspi->pinSS(),HIGH);
  vspi->endTransaction();
  vspi->end();
  delete vspi;

其中一些API参数说明

  1. 指定SPI相关引脚,并占用总线,不填为默认,速度最快
    void SPIClass::begin(int8_t sck, int8_t miso, int8_t mosi, int8_t ss)
  2. 释放总线
    void SPIClass::end()
  3. 设置SPI传输的相关参数,(时钟,极性,相位,比特顺序等)
    void SPIClass::beginTransaction(SPISettings settings)
  4. 结束本次传输,但不释放总线
    void SPIClass::endTransaction()
  5. SPI默认配置的时钟是1MHz,比特顺序是高位先行,使用MOD0的模式
class SPISettings
{
public:
    SPISettings() :_clock(1000000), _bitOrder(SPI_MSBFIRST), _dataMode(SPI_MODE0) {}
    SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) :_clock(clock), _bitOrder(bitOrder), _dataMode(dataMode) {}
    uint32_t _clock;
    uint8_t  _bitOrder;
    uint8_t  _dataMode;
};

SPI主机库数据传输方法

void transfer(void * data, uint32_t size);发送指定数量的字节数
下面的方法同时接收相同字节数的数据
uint8_t transfer(uint8_t data);发送一个字节
uint16_t transfer16(uint16_t data);2个字节
uint32_t transfer32(uint32_t data);4个字节

void transferBytes(const uint8_t * data, uint8_t * out, uint32_t size);
void transferBits(uint32_t data, uint32_t * out, uint8_t bits);

SPI从机库

需要包含第三方库#include"ESP32SPISlave.h"
一般不使用从机模式

ESP32使用DMA的方式进行SPI的传输


SD卡/TF卡模块

使用基于SPI协议的传输方式
Arduino中自带的一个读写SD卡的类:fs::SDFS不支持中文
使用第三方的SD库SdFat:SdFat库链接
下载后可放在Arduino第三方库文件夹中解压.
plantform中当前工程库存放路径:C:\Users\Xue_s\Documents\PlatformIO\Projects\SDcard\.pio\libdeps\airm2m_core_esp32c3\SdFat - Adafruit Fork
在SdFat-2.2.2\SdFat-2.2.2\src中的SdFatConfig.h修改配置USE_UTF8_LONG_NAMES(0改为1)

#ifndef USE_UTF8_LONG_NAMES
#define USE_UTF8_LONG_NAMES 0
#endif  // USE_UTF8_LONG_NAMES
------------------------------------
#ifndef USE_UTF8_LONG_NAMES
#define USE_UTF8_LONG_NAMES 1
#endif  // USE_UTF8_LONG_NAMES

将USE_UTF8_LONG_NAMES定义为1 ,这样字符串编码就使用utf8了,不然是ascii.

  • 功能
    – 支持UTF-8
    – 支持FAT16,FAT32,exFAT
  • 头文件
    – #include<SPI.h>
    – #include"SdFat.h"
  • 相关的类
    – SdFs 代表SD卡文件系统
    – FsFile 代表的是文件

DSPI(Dual SPI)


多任务

进程->线程(任务)
时间片轮转调度,任务优先级高的分配的时间长一些

ESP32多任务相关函数

  1. 首先包含头文件
    #include"freertos/FreeRTOS.h"
    #include"freertos/task.h"
  2. 创建任务
   BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pvTaskCode,
                                        const char * const pcName,
                                        const uint32_t usStackDepth,
                                        void * const pvParameters,
                                        UBaseType_t uxPriority,
                                        TaskHandle_t * const pvCreatedTask,
                                        const BaseType_t xCoreID);

pvTaskCode:指向任务输入函数的指针,任务函数原型:void task(void* param);
pcName:任务名称,最多16字节
usStackDepth:指定任务堆栈大小
pvParameters:用作所创建任务的参数的指针,在创建任务的时候可以向任务传递参数
uxPriority:任务优先级[0-25]级,数字越大,优先级越高
pvCreatedTask:指向任务句柄的指针,接收任务句柄
xCoreID:传入CPU核心,指明使用哪一颗CPU核心(ESP32有两个核心)

  1. 删除任务,如果在任务体中参数传入null结束任务
    void vTaskDelete( TaskHandle_t xTaskToDelete ) PRIVILEGED_FUNCTION;
  2. 获取任务优先级,本任务传入null
    UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask ) PRIVILEGED_FUNCTION;
  3. 获取本任务在哪个CPU上运行
    static inline BaseType_t IRAM_ATTR xPortGetCoreID(void)
    多任务及互斥量代码:可编辑链接,查看链接

互斥量(xSemaphoreHandle)

互斥量是一种特殊的二值信号量,它用于实现对临界资源的独占式处理(它不会屏蔽CPU的中断处理)
任意时刻的互斥量只有两种状态:开锁和闭锁.当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权.
xSemaphoreHandle//互斥锁,也算是一种信号量

使用:

  1. 创建一个互斥锁
    xSemaphoreHandle xMutex = xSemaphoreCreateMutex();
  2. 获取互斥锁(在普通任务中获取信号量)
    xSemaphoreTake( xSemaphore, xBlockTime )
    xSemaphore:信号量句柄
    xBlockTime :等待的节拍数,0是立即返回
    返回值:pdTRUE成功,pdFALSE失败
  3. 释放互斥锁
    xSemaphoreGive( xSemaphore )

U8g2操作OLED屏幕

支持芯片列表:链接
字体列表:链接
u8g2参考文档:链接
在这里插入图片描述

U8g2类名解析

  1. 第一部分:U8g2
  2. 第二部分:驱动芯片名
  3. 第三部分:显示大小
  4. 第四部分:OLED型号名
  5. 第五部分:
    5.1 数字壹:有一个的显存,使用firstPage()/nextPage()循环来更新屏幕,使用128字节的内存空间
    5.2 数字二:有两页的显存,可以使用所有的函数,使用256字节的内存空间
    5.3 字母F:完整的显示缓存,使用1024字节的内存空间
  6. 第六部分:OLED的通讯类型

常用的函数

  1. 创建对象
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2     //硬件I2C
(U8G2_R0, 
/* clock=*/ SCL, 
/* data=*/ SDA, 
/* reset=*/ U8X8_PIN_NONE); 
  1. 光标回到原点
    home()
  2. 光标设置在(x,y)处,设置的是文字左下角的点
    setCurser()
  3. 清除屏幕,清除缓冲区,光标回到左上角原点位置
    clear()
  4. 清除内存中数据缓冲区
    clearBuffer()
  5. 在屏幕上显示缓冲区的内容
    sendBuffer()
  6. 修改的内容处于firstPage和nextPage之间,每次都重新渲染所有内容
    firstPage(),nextPage()
  7. 获取指定字体里最高/宽字体的高/宽度
    u8g2.GetMaxCharHeight(),u8g2_GetMaxCharWidth()
  8. 透明模式/实显模式
    u8g2.SetDrawColor(0)
    0:透明模式(字是透明的,背景是亮的)
    1:实显模式(字是亮的,背景是透明的,默认)

刷屏方法:

//方法1:
u8g2.ClearBuffer();
//文字输出,图像输出
u8g2.SendBuffer();
//方法2:
u8g2.FirstPage();
  do{
    //文字输出,图像输出
  }while(u8g2.NextPage);

输出文本函数:

  1. 绘制文本字符,编码小于等于255.需提前设置字体,
    u8g2.DrawStr(x,y,"文本")
  2. 绘制编码为UTF-8的字符串,可以打印中文
    u8g2.drawUTF8(x,y,"字符")
  3. 绘制特殊文本图标,(需联合特殊字体)
    u8g2.DrawGlyph(x,y,"HEX")
  4. 打印文本字符,可以打印中文(需联合光标位置setCursor,setFont)支持变量
    u8g2.print()

输出图片

  1. 画图函数
    DrawXBM(&u8g2, x, y, w, h, bitmap);
    DrawXBMP(&u8g2, x, y, w, h, bitmap);
    DrawXBM和DrawXBMP区别在于DrawXBMP支持PROGMEM(但是实测都支持)
    PROGMEM是放在Flash中,不加放在RAM中

DAC及midi音乐

简谱

认识简谱链接
在这里插入图片描述
在这里插入图片描述

数字1-7对应do,re,mi,fa,sol,la,si

音的高低:
低音,中音,高音:分别对应数字下面加点,不加点,数字上面加点

音的长短

全音符:X—
二分音符:X-
四分音符:X
八分音符:X加1个下划线
十六分音符:X加2个下划线

调号:1=C,1=D,1=F

拍号:2/4,3/4,4/4
例:2/4是以四分音符为一拍,每小节唱两拍的时长

相关函数

  1. 输出电压
    dacWrite(pin, value)
    value:[0-255],对应的电压是0-3.3V
    V o l t a g e = v a l u e 255 ∗ 3.3 V Voltage=\frac{value}{255}*3.3V Voltage=255value3.3V
  2. 取消引脚的DAC功能
    dacDisable(pin)

可以使用ADC输出正弦波

f ( t ) = A s i n ( ω t + ψ ) + c f(t)=Asin(ωt+ψ)+c f(t)=Asin(ωt+ψ)+c
ω = 2 π f ω=2πf ω=2πf
其中: A是振幅,c是直流分量,要控制这两个值让f(t)的值域在[0,3.3]中
如: 让A为128,c为127.这样就可以输出主流分量为1.65V,振幅为1.65V,最小值为0,最大值为3.3v不失真的正弦波
对于t: 可以固定每隔固定的时间计算一次,如100us计算一次,每次将计算结果用dacWrite输出,就得到了正弦波信息
采样频率大于输出正弦波频率的两倍(内奎斯特定理)

ESP32使用DAC输出Midi音乐:可编辑链接,查看链接


I2S

I2S:Inter-IC Sound总线,又称集成电路内置音频总线,飞利浦公司为数字音频设备之间传输而制定的一种总线标准.
I2S是一种串行总线,传输的是PCM,DPM音频数据(未经压缩的音频采样数据裸流)
I2S特点:支持全双工/半双工.支持主从模式

PCM的参数:声道数,采样位数,采样频率
采样的数据量=采样频率x量化位数x声道数(bps)

底层API方式

首先包含头文件#include"driver/i2s.h"
通信的流程:
i2s_driver_install加载i2s的驱动
i2s_set_pin设置i2s使用的引脚
i2s_driver_uninstall卸载i2s驱动

ADC通道设置
esp_err_t i2s_set_adc_mode(adc_unit_t adc_unit, adc1_channel_t adc_channel);
设置衰减值
使能ADC测量功能
esp_err_t i2s_adc_enable(i2s_port_t i2s_num);

DAC通道设置
esp_err_t i2s_set_dac_mode(i2s_dac_mode_t dac_mode);

数据传输:读取
esp_err_t i2s_read(i2s_port_t i2s_num, void *dest, size_t size, size_t *bytes_read, TickType_t ticks_to_wait);
数据传输:发送
esp_err_t i2s_write(i2s_port_t i2s_num, const void *src, size_t size, size_t *bytes_written, TickType_t ticks_to_wait);

开始和结束
esp_err_t i2s_start(i2s_port_t i2s_num);
esp_err_t i2s_stop(i2s_port_t i2s_num);

字节序:大小端模式
大端模式:重要数据保存在内存的低地址中,和我们的阅读顺序一样(big endian)
小端模式:
X86计算机是小端模式

在这里插入图片描述

WAV

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值