На прошлом уроке мы научились работать с шиной SPI только на передачу данных, а сегодня мы уже попытаемся что-нибудь по данной шине принять от подключаемого устройства.
Как нельзя лучше для этой цели подойдёт микросхема FLASH-памяти семейства W25Q. Есть много других вариантов работы с шиной SPI на приём, но с данной микросхемой мы уже работали с применением контроллера STM32 и поэтому я считаю, что лучше идти уже по изученному, чем сталкиваться с кучей проблем.
Повторю некоторые характеристики данной микросхемы FLASH-памяти.
Потребляемая мощность и температурный диапазон:
- Напряжение питания 2.7…3.6 В
- Типичный потребляемый ток: 4 мА (активный режим), <1 мкА (в режиме снижения мощности)
- Рабочий температурный диапазон -40°C…+85°C.
Гибкая архитектура с секторами размером 4 кбайт:
- Посекторное стирание (размер каждого сектора 4 кбайт)
- Блочное стирание (32кбайт и 64 кбайт)
- Программирование от 1 до 256 байт
- До 100 тыс. циклов стирания/записи
- 20-летнее хранение данных
Максимальная частота работы микросхемы:
- 104 МГц в режиме SPI
- 208/416 МГц — Dual / Quad SPI
Есть ещё много различных возможностей, но это самые основные.
Также микросхема существует в различных корпусах, но в большинстве случаев распространён корпус SMD SO8.
Распиновка микросхемы следующая
Назначение каждого вывода:
Номер вывода | Название | Ввод/вывод (I/O) | Назначение |
1 | /CS | I | Выбор чипа |
2 | DO (IO1) | I/O | Вывод данных (Ввод/вывод данных #1 для режимов S-SPI и D-SPI) |
3 | /WP (IO2) | I/O | Вход защиты записи (Ввод/вывод данных #2 для режима Q-SPI) |
4 | GND | Общий провод | |
5 | DI(IO0 ) | I/O | Ввод данных (Ввод/вывод данных #0 для режимов S-SPI и D-SPI) |
6 | CLK | I | Ввод тактовых импульсов |
7 | /HOLD or /RESET (IO3 ) | I/O | Ввод/вывод данных #3 для режима Q-SPI |
8 | VCC | Напряжение питания |
Согласно распиновке схема у нас получится вот такая
Следует отметить, что микросхема W25Q устроена так же как FLASH-память у stm32, то есть память у неё разбита на страницы по 256 байт, страницы объединены в секторы по 4096 байт, а секторы в блоки по 65536 байт. Организацию памяти микросхемы можно посмотреть на схеме
Перед тем как что-то записать, нужно стереть сектор (4096 байт) или блок (65536 байт). Можно стереть несколько секторов или блоков, или весь чип полностью. Во время стирания ячейки заполняются значениями 0xFF.
Каким образом организован процесс стирания, а также чтения и записи микросхемы, мы разберём в дальнейших занятиях, а сегодня наша задача — подключить микросхему к контроллеру, настроить проект для организации кода работы контроллера с микросхемой и в качестве испытания считать некоторые данные из неё.
Поэтому мы пока не будем забивать себе голову изучением всех регистров и их битов данной микросхемы W25Q, так как их там очень много, а будем их изучать по мере их использования.
Я буду использовать микросхему 25Q32FVSIG в корпусе SMD SO8, в которой 32 мегабита памяти, которую я припаял на переходник SO8-DIP, а также распаял штырьевые линейки для удобства подключения к контроллеру, получилась вот такая табуреточка
Подключим нашу микросхему к отладочной плате, также для большего контроля за передачей данных подключим логический анализатор и в результате получим получим вот такую схему
Проект урока был сделан из проекта прошлого урока с именем и назван был SPI_25Q_INFO.
Прежде, чем открыть наш проект в среде программирования, в файл Kconfig.projbuild каталога main мы добавим ещё один пункт для ножки MISO
1 2 3 4 5 6 7 8 |
MOSI pin config PIN_NUM_MISO int "MISO pin" range 0 48 default 19 help MISO pin |
Файлы max7219.h и max7219.c переименуем соответственно в w25q_spi.h и w25q_spi.c, очистим их и пока они будут такого содержания:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#ifndef MAIN_W25Q_SPI_H_ #define MAIN_W25Q_SPI_H_ //================================================== #include <string.h> #include <stdio.h> #include <stdint.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include <driver/gpio.h> #include <driver/spi_master.h> #include "esp_log.h" //================================================== #endif /* MAIN_W25Q_SPI_H_ */ |
1 2 3 |
#include "w25q_spi.h" //================================================== #define CLOCK_SPEED_HZ (1000000) // 1 MHz |
Мы пока частоту большую не будем использовать, чтобы не думать, что у нас ошибки из за слишком большой скорости.
В файле CMakeLists.txt также внесём соответствующее исправление
set(COMPONENT_SRCS "main.c w25q_spi.c")
Откроем проект в Espressif IDE и в файле main.h вместо max7219.h подключим файл w25q_spi.h
#include "w25q_spi.h"
В функции app_main удалим локальную переменную
volatile long cnt;
В заполнении полей структуры инициализации модуля SPI вместо -1 добавим ножку MISO
.miso_io_num = CONFIG_PIN_NUM_MISO,
В самом начале тела функции зададим направление для ножек GPIO
1 2 3 4 |
esp_err_t ret; gpio_reset_pin( CONFIG_PIN_NUM_CS ); gpio_set_direction( CONFIG_PIN_NUM_CS, GPIO_MODE_OUTPUT ); gpio_set_level( CONFIG_PIN_NUM_CS, 0 ); |
В третьем параметре функции инициализации модуля зададим автоматическое определения канала DMA
ret = spi_bus_initialize(HSPI_HOST, &cfg, SPI_DMA_CH_AUTO);
Вот это всё удалим
max7219_t dev;
Init_7219(&dev, HSPI_HOST, CONFIG_PIN_NUM_CS);
Number_7219(&dev, 12345678);
vTaskDelay(2000 / portTICK_PERIOD_MS);
В бесконечном цикле счётчик нам также не нужен, так что код, предназначенный для него, тоже удалим
Clear_7219(&dev);
Number_7219(&dev, cnt);
cnt++;
if(cnt>99999999) cnt = 0;
Теперь у нас проект должен собраться.
В файле w25q_spi.h объявим структуру для нашего устройства
1 2 3 4 5 6 7 8 |
#include "esp_log.h" //================================================== typedef struct { spi_device_interface_config_t spi_cfg; spi_device_handle_t spi_dev; } w25q_t; //================================================== |
А в файле w25q_spi.c добавим функцию инициализации микросхемы
1 2 3 4 5 6 |
//================================================== void w25_ini (w25q_t *dev, spi_host_device_t host, gpio_num_t cs_pin) { } //================================================== |
Объявим на данную функцию прототип в заголовочном файле и вызовем её в функции app_main файла main.c, объявив перед этим делом переменную структуры
1 2 3 |
ESP_LOGI(TAG, "spi bus initialize: %d", ret); w25q_t dev; w25_ini(&dev, HSPI_HOST, CONFIG_PIN_NUM_CS); |
Вернёмся в функцию инициализации w25_ini в файл w25q_spi.c, объявим там переменную для отслеживания кода ошибки, также объявим переменную структуры устройства и заполним её поля
1 2 3 4 5 6 7 8 9 |
void w25_ini (w25q_t *dev, spi_host_device_t host, gpio_num_t cs_pin) { esp_err_t ret; spi_device_interface_config_t devcfg; memset( &devcfg, 0, sizeof( spi_device_interface_config_t ) ); devcfg.clock_speed_hz = CLOCK_SPEED_HZ; devcfg.spics_io_num = cs_pin; devcfg.queue_size = 7; devcfg.mode = 0; |
Также добавим глобальный символьный массив для информации в логах
1 2 3 4 |
#define CLOCK_SPEED_HZ (1000000) // 1 MHz //================================================== static const char *TAG = "w25q_spi"; //================================================== |
Вернёмся в функцию w25_ini, объявим указатель для переменной типа структуры устройства и вызовем функцию инициализации нашего устройства, проверив затем код ошибки с помощью лога
1 2 3 4 |
devcfg.mode = 0; spi_device_handle_t handle; ret = spi_bus_add_device( HSPI_HOST, &devcfg, &handle); ESP_LOGI(TAG, "spi_bus_add_device=%d",ret); |
Соберём код, прошьём контроллер и убедимся, что обе наши функции инициализации отработали без ошибок
Присвоим указатель полю нашей структуры
1 2 |
ESP_LOGI(TAG, "spi_bus_add_device=%d",ret); dev->spi_dev = handle; |
В файле w25q_spi.h добавим несколько макросов с адресами регистров микросхемы
1 2 3 4 5 6 7 |
#include "esp_log.h" //================================================== #define W25_ENABLE_RESET 0x66 #define W25_RESET 0x99 #define W25_READ 0x03 #define W25_GET_JEDEC_ID 0x9f //================================================== |
Вернёмся в файл w25q_spi.c и выше функции w25_ini добавим функцию перезагрузки микросхемы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//================================================== void W25_Reset (w25q_t *dev) { spi_transaction_t SPITransaction; uint8_t data[2]; data[0] = W25_ENABLE_RESET; data[1] = W25_RESET; memset( &SPITransaction, 0, sizeof( spi_transaction_t ) ); SPITransaction.length = 2 * 8; SPITransaction.tx_buffer = data; SPITransaction.rx_buffer = data; spi_device_transmit( dev->spi_dev, &SPITransaction ); } //================================================== |
Здесь всё просто, мы посылаем в шину код разрешения перезагрузки и код самой перезагрузки.
В функции w25_ini вызовем нашу функцию, добавив задержку до и после вызова функции.
1 2 3 4 5 |
dev->spi_dev = handle; vTaskDelay(100 / portTICK_PERIOD_MS); W25_Reset(dev); vTaskDelay(100 / portTICK_PERIOD_MS); |
Соберём код, прошьём контроллер и посмотрим, передались ли данные
Всё передалось.
Выше функции w25_ini добавим функцию чтения идентификатора нашей микросхемы
1 2 3 4 5 6 7 8 9 10 11 12 13 |
uint32_t W25_Read_ID(w25q_t *dev) { spi_transaction_t SPITransaction; uint8_t data[4]; data[0] = W25_GET_JEDEC_ID; memset( &SPITransaction, 0, sizeof( spi_transaction_t ) ); SPITransaction.length = 4 * 8; SPITransaction.tx_buffer = data; SPITransaction.rx_buffer = data; spi_device_transmit(dev->spi_dev, &SPITransaction ); return (data[1] << 16) | (data[2] << 8) | data[3]; } //================================================== |
Здесь практически тот же самый код, так как данные, прочитанные из шины, пишутся в тот же массив, указатель на который мы присваиваем переменной структуры. поэтому мы просто забираем эти данные и возвращаем.
Вызовем нашу функцию в w25_ini и отобразим наш идентификатор в терминале
1 2 3 4 |
vTaskDelay(100 / portTICK_PERIOD_MS); uint32_t id = W25_Read_ID(dev); ESP_LOGI(TAG, "ID:0x%X",id); |
Соберём код, прошьём контроллер и посмотрим результат
В заголовочном файле w25q_spi.h объявим структуру для информации о микросхеме согласно считанного идентификатора
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
} w25q_t; //================================================== typedef struct { uint16_t PageSize; uint32_t PageCount; uint32_t SectorSize; uint32_t SectorCount; uint32_t BlockSize; uint32_t BlockCount; uint32_t NumKB; uint8_t SR1; uint8_t SR2; uint8_t SR3; }w25_info_t; //================================================== |
Вернёмся в файл w25q_spi.c и в функции w25_ini объявим переменную типа данной структуры
1 2 |
esp_err_t ret; w25_info_t w25_info; |
Ниже определим тип микросхемы и выведем о ней информацию в терминал
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
ESP_LOGI(TAG, "ID:0x%X",id); id &= 0x0000ffff; switch(id) { case 0x401A: w25_info.BlockCount=1024; printf("w25qxx Chip: w25q512\r\n"); break; case 0x4019: w25_info.BlockCount=512; printf("w25qxx Chip: w25q256\r\n"); break; case 0x4018: w25_info.BlockCount=256; printf("w25qxx Chip: w25q128\r\n"); break; case 0x4017: w25_info.BlockCount=128; printf("w25qxx Chip: w25q64\r\n"); break; case 0x4016: w25_info.BlockCount=64; printf("w25qxx Chip: w25q32\r\n"); break; case 0x4015: w25_info.BlockCount=32; printf("w25qxx Chip: w25q16\r\n"); break; case 0x4014: w25_info.BlockCount=16; printf("w25qxx Chip: w25q80\r\n"); break; case 0x4013: w25_info.BlockCount=8; printf("w25qxx Chip: w25q40\r\n"); break; case 0x4012: w25_info.BlockCount=4; printf("w25qxx Chip: w25q20\r\n"); break; case 0x4011: w25_info.BlockCount=2; printf("w25qxx Chip: w25q10\r\n"); break; default: printf("w25qxx Unknown ID\r\n"); return; } w25_info.PageSize=256; w25_info.SectorSize=0x1000; w25_info.SectorCount=w25_info.BlockCount*16; w25_info.PageCount=(w25_info.SectorCount*w25_info.SectorSize)/w25_info.PageSize; w25_info.BlockSize=w25_info.SectorSize*16; w25_info.NumKB=(w25_info.SectorCount*w25_info.SectorSize)/1024; printf("Page Size: %d Bytes\r\n",(unsigned int)w25_info.PageSize); printf("Page Count: %u\r\n",(unsigned int)w25_info.PageCount); printf("Sector Size: %u Bytes\r\n",(unsigned int)w25_info.SectorSize); printf("Sector Count: %u\r\n",(unsigned int)w25_info.SectorCount); printf("Block Size: %u Bytes\r\n",(unsigned int)w25_info.BlockSize); printf("Block Count: %u\r\n",(unsigned int)w25_info.BlockCount); printf("Capacity: %u KB\r\n",(unsigned int)w25_info.NumKB); |
Соберём код, прошьём контроллер и посмотрим результат
Также посмотрим в программе логического анализа, как происходит процесс чтения
Всё хорошо, без пропусков.
Теперь прибавим немного скорость
#define CLOCK_SPEED_HZ (10000000) // 10 MHz
Посмотрим результат
Всё также отлично обменивается.
Итак, на данном уроке мы получили информацию из микросхемы W25Q. Также мы научились работать с модулем SPI на чтение.
Спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Логический анализатор 16 каналов можно приобрести здесь
Микросхему FLASH-памяти W25Q32FVSSIG SOP8 (10 штук) можно приобрести здесь W25Q32FVSSIG
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий