Введение
Есть мнение, что интерфейс 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). Микросхема поддерживает множество команд, но из всего разнообразия мы будем использовать следующие:
Подготовительный этап. Функции для работы со 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.
Список радиоэлементовОбозначение
Тип
Номинал
Количество
ПримечаниеМагазинМой блокнот
МК STM32STM32F042F61
SPI FLASHAT45DB161D1
LDOXC6206P332MR1
Конденсатор 06031мкФ2
Конденсатор 06030.1 мкФ3
Конденсатор 060322 пФ2
Резистор 060310 кОм2
Резистор 0603220 Ом1
Кварцевый резонатор8 МГц1
Светодиод 08051
Добавить все
Скачать список элементов (PDF)
Прикрепленные файлы:
- usb.lay (36 Кб)
- at45db161d.ZIP (1 Кб)