使用ESP32也已经快一年了,有了很多体会和经验,最近开始写文章记录下心得和过程,这样的好处是方便自己查找资料和回顾,也许也能帮助到别人。也是善事一桩。
先记录一下今天的心得吧
今天要在esp32 wroom 模块上(此模块有4M FLASH,但没有PSRAM)中跑NES模拟器,硬件有ST7789的240X240 的彩屏,还有SD卡,通过sd卡上读取ROM文件,在彩屏上显示图像,然后通过I2s输出声音。还有I2C的扩展键盘,然后问题来了,彩屏和sd卡都是通过SPI总线连接的,常规都是通过乐鑫的官方库SPI.H 和sd.h 实现sd卡读写,但是此库默认使用的都是VSPI这个控制器。
知识点:
ESP32 共有 4 个 SPI 控制器 SPI0、SPI1、SPI2、SPI3,用于连接支持 SPI 协议的设备。SPI0 控制器作为 cache 访问外部存储单元接口使用,SPI1 作为主机使用,SPI2 和 SPI3 控制器既可作为主机使用又可作为从机使用。作主机使用时,每个 SPI 控制器可以使用多个片选信号 (CS0 ~ CS2) 来连接多个 SPI 从机设备。
SPI1 ~ SPI3 控制器共享两个 DMA 通道。
SPI0 和 SPI1 控制器通过一个仲裁器共用一组信号总线,这组带前缀 SPI 的信号总线由 D、Q、CS0 ~ CS2、CLK、WP 和 HD 信号组成,如表 25 所示。
相应地,控制器 SPI2 和 SPI3 分别使用带前缀 HSPI 和 VSPI 的信号总线。
这些信号总线包含的输入输出信号线可以经过 GPIO 交换矩阵和 IO_MUX 模块实现与芯片管脚的映射
虽然显示屏和sd卡是通过不同的引脚接在esp32的管脚上的,但软件上如果不作处理,将会造成冲突,因为两者都默认使用VSPI控制器。
实际测试,sd卡能读写时会造成彩屏无法显示,但如果将sd卡的代码删除,彩屏就可以显示
解决的问题是两者要使用不同的SPI,那如何才能让SD卡使用HSPI通迅呢?
先申明一个使用HSPI总线的SPI对象,再把此对象绑定到sd卡的begin中,如下:
SPIClass MySPI(HSPI);//申明一个叫MySPI的 使用HSPI的总线对象 用来读写SD卡
pinMode(SD_CS, OUTPUT);//SD卡CS脚
digitalWrite(SD_CS, HIGH);//低电平选中,高电平不选中
//自定义的SPI控制器的接线重新映射(就是实际的sd卡硬件接线)
MySPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
MySPI.setFrequency(1000000);//设置此SPI总线频率为1M 也可不用
//用MySPI这个SPI总线去操作SD卡
if( !SD.begin(SD_CS,MySPI) )
{
Serial.println("Card Mount Failed");//返回0失败
return;
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
通过这么一改,完美解决冲突,能读出sd卡上的文件内容显示在彩屏上了,并且也能播放sd卡上的音频文件通过esp32的内置DAC输出 25/26 脚 左右声道模拟音频输出,音质还行过的去,毕竟是8位的。
以下是读SD卡中WAV文件通过内置DAC输出的代码:
I2S_Init();
Serial.print("Play to :音乐");
File file = SD.open("/8.wav"); // 44100Hz, 16bit, stereo, linear PCM
//播放循环
while (file.readBytes(data, sizeof(data))) I2S_Write(data, sizeof(data));
file.close();
void I2S_Init()
{
//使用内置DAC的I2S的配置,注意因为内置DAC是8位的
i2s_config_t i2s_config =
{
.mode =(i2s_mode_t) ( I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN ),
.sample_rate =22050,
.bits_per_sample =(i2s_bits_per_sample_t) 16, /* the DAC module will only take the 8bits from MSB */
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_PCM_SHORT),
.intr_alloc_flags = 0, // default interrupt priority
.dma_buf_count = 16,
.dma_buf_len = 60,
.use_apll = false
};
i2s_driver_install((i2s_port_t)0, &i2s_config, 5, NULL); //install and start i2s driver
i2s_set_pin((i2s_port_t)0, NULL); //for internal DAC
// i2s_set_pin((i2s_port_t)0, &pin_config);//外部DAC 需要设置控制脚
//You can call i2s_set_dac_mode to set built-in DAC output mode.
//for internal DAC, this will enable both of the internal channels
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
// i2s_set_sample_rates((i2s_port_t)i2s_num, 22050); //set sample rates
// i2s_driver_uninstall(i2s_num); //stop & destroy i2s driver
}
void I2S_Write(char* data, int numData)
{
i2s_write_bytes(I2S_NUM_0, (const char *)data, numData, portMAX_DELAY);
}