Введение
Есть мнение, что интерфейс USB очень сложен, что если его использовать, то, только используя готовые библиотеки, а работать с ним на уровне регистров это извращение. В этом цикле статей, я хочу показать, что это не так, и мы создадим самую настоящую USB флешку, правда, всего на 2 мегабайта. Но ведь это не главное.
Для опытов нам понадобится плата с микроконтроллером STM32F0x2, разведенным разъемом USB и интерфейсом SPI, например как показано на рисунке 1:
Рисунок 1 - Схема электрическая принципиальная USB FLASH
Сразу оговорюсь, использование кварцевого резонатора не обязательно, на схеме и плате он предусмотрен, но прошивку мы напишем без его использования.
Рисунок 2 - Основная печатная плата
Рисунок 3 - Модульная плата с AT45DB161D
Фото флешки
Часть 1. Работа с USB FLASH AT45DB161D
Для начала разберемся с работой микросхемы SPI FLASH AT45DB161D.
AT45DB161D - микросхема Flash-памяти последовательного доступа объемом 16 МБит или 2 МБайта. Она поддерживает последовательный SPI-подобный интерфейс Atmel RapidS, работающий на скоростях до 66МГц. 16 мегабит памяти сгруппированы в 4096 страницы размером 512/528 байт. В дополнение к основной памяти, AT45DB161D содержит 2 SRAM буфера размером 512/528 байт. Они позволяют принимать данные во время записи в основную память, организовать запись непрерывного потока данных. Встроенная операция чтение-модификация-запись позволяет легко эмулировать EEPROM.
Рисунок 4 – Структурная схема AT45DB161D
Для максимальной гибкости в массиве памяти AT45DB161D реализованы три уровня разбиения: по секторам, по блокам и по страницам. Эти уровни, а также количество страниц в секторе и блоке изображены на рисунке 5. Все операции записи выполняются на страничном уровне, операции стирания на уровне всей микросхемы, сектора, блока или страницы.
Рисунок 5 – Архитектура памяти AT45DB161D
Микросхема управляется командами от ведущего процессора. Команда начинается спадающим фронтом сигнала CS, следом идут 8-битный код команды, номер буфера или адрес в массиве памяти. Подача тактового сигнала на SCK при активном CS приводит к чтению с вывода SI кода команды, номера буфера или адреса в массиве памяти. Коды команд, адреса и данные передаются старшим битом вперед (MSB). Микросхема поддерживает множество команд, но из всего разнообразия мы будем использовать следующие:
- Чтение регистра статуса (D7h);
- Непрерывное чтение массива (высокая скорость) (0Bh);
- Запись в буфер 1 (84h);
- Запись содержимого буфера 1 в страницу основной памяти с авто-стиранием (83h).
Подготовительный этап. Функции для работы со SPI.
Инициализация:
#define CS_HIGH GPIOB -> BSRR = GPIO_BSRR_BS_1 #define CS_LOW GPIOB -> BSRR = GPIO_BSRR_BR_1 void AT45DB161_Init(){ //Включаем тактирование SPI1, GPIOA, GPIOB RCC -> APB2ENR |= RCC_APB2ENR_SPI1EN; RCC -> AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN; //Настраиваем PA5, PA6, PA7 как AF GPIOA -> MODER |= GPIO_MODER_MODER5_1 | GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1; //High Speed GPIOA -> OSPEEDR |= GPIO_OSPEEDER_OSPEEDR5 | GPIO_OSPEEDER_OSPEEDR6 | GPIO_OSPEEDER_OSPEEDR7; //Настраиваем PB1 как GP OUTPUT GPIOB -> MODER |= GPIO_MODER_MODER1_0; //High Speed GPIOB -> OSPEEDR |= GPIO_OSPEEDER_OSPEEDR1; //CS = 1 CS_HIGH; /* SPI_CR1_SSM – программное управление выводом NSS (внутри микросхемы) SPI_CR1_SSI – активный уровень на NSS SPI_CR1_MSTR – режим SPI – мастер SPI_BR = 0 – частота SPI = fclk / 2 = 24МГц */ SPI1 -> CR1 = SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_MSTR; /* SPI_CR2_DS = 7 – режим 8 бит SPI_CR2_FRXTH – событие RXNE будет генерироваться, когда FIFO будет заполнено на ¼ - то есть когда придет 1 байт */ SPI1 -> CR2 = SPI_CR2_DS_0 | SPI_CR2_DS_1 | SPI_CR2_DS_2 | SPI_CR2_FRXTH; //SPI_CR1_SPE - включаем SPI SPI1 -> CR1 |= SPI_CR1_SPE; }
Функция отправки и приема байта:
//Регистр SPI_DR c 8-битным доступом #define SPI_DR8 *(uint8_t *)0x4001300C uint8_t SPI_SendByte (uint8_t byte){ //Ждем пока передающий буфер не пуст while(!(SPI1-> SR & SPI_SR_TXE)); //Передаем байт SPI_DR8 = byte; //Ждем пока приемный буфер пуст while(!(SPI1-> SR & SPI_SR_RXNE)); //Возвращаем принятое значение return SPI_DR8; }
Изначально микросхемы поставляются с размером страниц 528 байт. Программно можно установить размер 512 байт, но только один раз, после чего изменить его будет нельзя.
Для того, чтобы активировать режим страниц размером 512 байт, необходимо выполнить следующие шаги:
1. Запрограммировать регистр конфигурации:
- CS = 0;
- Послать в строгом порядке последовательность 3Dh + 2Ah + 80h + A6h;
- CS = 1;
Программирование должно закончится максимум за время tp (максимум 6 мс), в течении которого регистр статуса будет информировать о занятости микросхемы.
2. Сбросить и подать питание на микросхему, если сброс питания произойдет до завершения цикла программирования, то правильная запись не гарантируется;
3. Теперь запись страниц производится по 512 байт.
Чтобы выяснить установлен размер страницы в 512 байт или нет, необходимо проверить бит 0 в Регистре Статуса. Если он не установлен, команду можно послать повторно.
У меня микросхема была БУ, и в ней уже был установлен режим 512 байт, поэтому я эту последовательность не реализовывал.
1. Чтение регистра статуса (D7h)
Из регистра статуса можно узнать готовность/занятость микросхемы, результат сравнения страницы основной памяти с буфером, статус защиты сектора, емкость микросхемы.
Бит 7 RDY/BUSY – бит готовности/занятости микросхемы. Если бит равен 1, то микросхема не занята и готова к приему следующей команды. Если бит равен 0 – микросхема занята.
Бит 6 COMP – результат последней команды «Сравнение страницы основной памяти с буфером X». Если бит равен 0 – данные в буфере соответствуют данным в основной памяти. Если бит равен 1 – данные отличаются хотя бы 1 битом.
Биты 5..2 – объем микросхемы. Для микросхемы AT45DB161D – 1011.
Бит 1 – PROTECT – бит защиты секторов. Значение 1 соответствует включенной защите секторов, 0 – выключенной защите.
Бит 0 - PAGE SIZE – показывает размер страниц в основной памяти. Значение 1 соответствует размеру страниц в 512 байт, значение 0 – 528 байт.
Регистр статуса можно прочесть в любой момент времени, даже во время выполнения самотактируемых операций записи и стирания. Чтобы прочесть регистр статуса необходимо активировать сигнал CS, после чего загрузить код команды D7h. После загрузки кода команды, с последующими тактовыми импульсами, на выводе SO будет выдан 1-байтовый регистр статуса. После передачи одного байта регистра статуса, в случае активного CS и последующих тактовых импульсах, выдача повторится. Данные в регистре статуса постоянно обновляются, поэтому все повторные выдачи будут содержать новые данные.
Рисунок 7 – Диаграмма чтения регистра статуса
Напишем функцию чтения регистра статуса:
uint8_t AT45DB161_Read_Status(){ uint8_t temp; //CS = 0 CS_LOW; //Отправляем код команды SPI_SendByte(0xD7); //Читаем байт статуса temp = SPI_SendByte(0x00); //Ждем пока завершится работа SPI while(SPI1 -> SR & SPI_SR_BSY); //CS = 1 CS_HIGH; //Возвращаем байт статуса return temp; }
2. Непрерывное чтение массива (высокая скорость) (0Bh)
Эта команда применяется при последовательном чтении массива основной памяти последовательным интерфейсом с частотой тактового сигнала вплоть до максимальной, определяемой параметром fcar1.
Для чтения страниц стандартного размера (528 байт) необходимо сначала активировать сигнал CS, потом подать команду 0B, после которой следуют 3 адресных байта (формируют 24 бита адреса страницы и байта) и 1 произвольный. Первые 12 бит (PA11-PA0) 22-битной адресной последовательности задают номер читаемой страницы в массиве, а последние 10 бит(BA9-BA0) стартовый байт внутри страницы.
Рисунок 8 – Последовательность для команд чтения/записи страниц размером 528 байт
Для чтения страниц размером 512 байт необходимо подать команду 0Bh, после которой следуют 3 адресных байта (биты A20-A0) и 1 произвольный. Тактовые импульсы на выводе SCK, следующие после произвольного байта, приведут к выдаче данных на выводе SO.
Рисунок 9 – Последовательность для команд чтения/записи страниц размером 512 байт
Вывод CS должен оставаться в низком состоянии во время загрузки кода команды, адресных байт, произвольного байта и чтения данных. Если во время чтения будет достигнут конец страницы, микросхема продолжит чтение с начала следующей страницы без каких-либо задержек связанных с пересечением границ. Если будет прочтен последний байт массива памяти, чтение продолжиться с начала первой страницы, задержек так же не будет.
Переход вывода CS из низкого состояния в высокое завершит операцию чтения и переведет вывод SO в третье состояние. Максимальная частота SCK для непрерывного чтения определяется параметром fcar1. Команда непрерывного чтения не меняет содержимое внутренних SRAM-буферов.
Рисунок 10 – Диаграмма выполнения команды 0Bh
Напишем функцию чтения массива байт:
/* uint16_t page – номер читаемой страницы (0 – 4095) uint16_t addr – адрес байта внутри страницы (0 – 511) uint32_t length – количество читаемых байт uint8_t *out – указатель, куда ложить прочитанные байты */ void AT45DB161_Read_Data(uint16_t page, uint16_t addr, uint32_t length, uint8_t *out){ uint32_t i; uint8_t temp; //Ожидаем готовности микросхемы do { temp = AT45DB161_Read_Status(); } while (!(temp & 0x80)); //Исходя из размера страницы формируем адрес if (temp & 0x01){ //512 i = ((page << 9) | (addr & 0x1FF)); } else { //528 i = ((page << 10) | (addr & 0x3FF)) ; } //CS = 0 CS_LOW; //Отправляем код команды SPI_SendByte(0x0B); //Отправляем адрес SPI_SendByte(i >> 16); SPI_SendByte(i >> 8); SPI_SendByte(i); //Отправляем произвольный байт SPI_SendByte(0x00); //Читаем последующие байты в out[] for (i = 0; i < length; i++){ out[i] = SPI_SendByte(0xFF); } //Ждем завершения работы SPI while(SPI1 -> SR & SPI_SR_BSY); //CS = 1 CS_HIGH; }
3. Запись в буфер 1 (84h)
Данные, поступающие с входного сигнала SI, могут быть записаны либо в буфер 1, либо в буфер 2.
Для записи данных в стандартный буфер (размером 528 байт) необходимо подать однобайтовый код команды 84h для буфера 1 и 87h для буфера 2. После чего подать 3 адресных байта, состоящих из 14 произвольных бит и 10 бит адреса в буфере (BFA1-BFA0). Адресные биты задают первый байт в буфере в который будет выполнена запись.
Для записи данных в буфер размером 512 байт, необходимо подать команду 84h для буфера 1 и 87h для буфера 2. После команды подают 3 адресных байта, в которых 15 произвольных и 9 бит адреса в буфере (BFA9-BFA0). Адресные биты задают первый байт в буфере, в который будет выполнена запись.
Сразу после адресных байт, с последующими тактовыми импульсами, подают байты данных. Если запись дойдет до границы буфера, микросхема автоматически продолжит запись с его начала. Данные будут загружаться до тех пор, пока не произойдет переход сигнала CS из низкого уровня в высокий.
4. Запись содержимого буфера 1 в страницу основной памяти с авто-стиранием (83h)
Данные, находящиеся в буфере 1 или буфере 2, могут быть записаны в основную память микросхемы. Для этого необходимо подать команду 83h для буфера 1 и 86h для буфера 2.
При записи в страницы стандартного размера (528 байт), после команды подаются 3 адресных байта в которых 2 произвольных бита, 12 бит (PA11-PA0) задают адрес программируемой страницы в основной памяти , 10 произвольных бит.
Чтобы записать в страницы размером 512 байт необходимо подать команду 83h для буфера 1 и 86h для буфера 2. После команды подаются 3 адресных байта в которых 3 произвольных бита, 12 бит (A20-A9) задают адрес программируемой страницы в основной памяти, 9 произвольных бит.
Переход сигнала CS из низкого состояния в высокое сначала сотрёт страницу в основной памяти, после чего запишет данные из буфера в указанную страницу основной памяти. Операция стирания и записи страницы самотактируемая и выполняется за максимальное время tEP. В течение этого времени регистр статуса и вывод RDY/BUSY будут информировать о занятости микросхемы.
Напишем функцию записи страницы:
void AT45DB161_PageProgram(uint16_t page, uint8_t *data, uint16_t length){ uint16_t i; uint8_t temp; //Читаем регистр статуса, чтобы определить размер страницы temp = AT45DB161_Read_Status(); CS_LOW; //Пишем данные в буфер 1 с 0 адреса SPI_SendByte(0x84); SPI_SendByte(0x00); SPI_SendByte(0x00); SPI_SendByte(0x00); for (i = 0; i < length; i++){ SPI_SendByte(data[i]); } while(SPI1 -> SR & SPI_SR_BSY); CS_HIGH; CS_LOW; //Посылаем команду 83h SPI_SendByte(0x83); //Исходя из размера страницы формируем адрес if (temp & 0x01){ //512 SPI_SendByte((uint8_t)(page >> 7)); SPI_SendByte((uint8_t)((page & 0x7F) << 1)); SPI_SendByte(0x00); } else { //528 SPI_SendByte((uint8_t)(page >> 6)); SPI_SendByte((uint8_t)((page & 0x3F) << 2)); SPI_SendByte(0x00); } while(SPI1 -> SR & SPI_SR_BSY); CS_HIGH; //Ожидаем готовности микросхемы while (!(AT45DB161_Read_Status() & 0x80)); }
На этом все, теперь мы умеем работать с флеш-памятью и можем переходить к USB.
Список радиоэлементов
Обозначение | Тип | Номинал | Количество | Примечание | Магазин | Мой блокнот |
---|---|---|---|---|---|---|
МК STM32 | STM32F042F6 | 1 | Поиск в магазине Отрон | |||
SPI FLASH | AT45DB161D | 1 | Поиск в магазине Отрон | |||
LDO | XC6206P332MR | 1 | Поиск в магазине Отрон | |||
Конденсатор 0603 | 1мкФ | 2 | Поиск в магазине Отрон | |||
Конденсатор 0603 | 0.1 мкФ | 3 | Поиск в магазине Отрон | |||
Конденсатор 0603 | 22 пФ | 2 | Поиск в магазине Отрон | |||
Резистор 0603 | 10 кОм | 2 | Поиск в магазине Отрон | |||
Резистор 0603 | 220 Ом | 1 | Поиск в магазине Отрон | |||
Кварцевый резонатор | 8 МГц | 1 | Поиск в магазине Отрон | |||
Светодиод 0805 | 1 | Поиск в магазине Отрон | ||||
Скачать список элементов (PDF)
Прикрепленные файлы:
- usb.lay (36 Кб)
- at45db161d.ZIP (1 Кб)
Комментарии (15) | Я собрал (0) | Подписаться
Для добавления Вашей сборки необходима регистрация
[Автор]
[Автор]
Если речь о работе с usb, то мою библиотеку портировать не удастся, для этих целей там есть V-USB.
[Автор]
Ссылка
[Автор]
[Автор]
void AT45DB161_Init(). - Тут должна быть настройка портов SPI и настройка самого SPI, и настройка вывода CS.
uint8_t SPI_SendByte(uint8_t byte) - функция отправки и приема байта по SPI.
Дефайны CS_HIGH и CS_LOW - установить вывод CS в 1 и 0 соответственно.
Ну и смотря в каком компиляторе будете использовать, может понадобиться подключение заголовочных файлов с описанием регистров.