
Здравствуйте меня зовут Дмитрий сегодня мы продолжим исследование FPGA плат и напишем полноценный контроллер SD карт (правильней их называть SDHC, потому что карты формата SD давно не продаются). Причем мы создадим полноценный контроллер задействующий все возможности шины SD, а не жалкий SPI аналог.
Железо

Первый вопрос, как нам подключить SD карту к FPGA чипу? Конечно можно проявить чудеса владения паяльником, но я пошел по пути наименьшего сопротивления и просто купил на Али вот такую плату. Кстати светодиод на плате светится только когда вставлена карта. Все линии подтянуты к 3.3В, поэтому если мы ничего не делаем то на всех шинах единица.
Интерфейс

Кроме очевидных питания и земли, присутствует, тактовый сигнал CLK. Шина команд CMD. И четырехбитная шина данных. Кроме контактов самой SD карты присутствуют два контакта SD разъема. CD - chip detect на нем возникает 0 если вставить карту в разъем. WP - write protect на этом контакте будет ноль если активирован переключатель защиты от записи. Я кстати никогда не встречал микро SD карты с подобным переключателем, но контакт есть.
Архитектура модуля

Модуль состоит из 3-х основных модулей это:
M_SD_Card_Control - Это мозг всего модуля именно он определяет когда и что писать или читать. Сам модуль разбит на 4 подмодуля.
M_SD_Card_Init - Модуль инициализации SD карты, в нем зашита последовательность команд, необходимая для начала работ карты.
M_SD_Card_Read - Модуль чтения данных.
M_SD_Card_Write - Модуль записи данных.
M_SD_Card_Check_State - Модуль который проверяет состояние карты (о состояниях ниже). И если она не в том состоянии переключает в правильное.
M_SD_Card_DATA - Этот модуль заведет линиями данных.
M_SD_DATA_GET - Модуль который получает данные.
M_SD_DATA_SEND - Модуль отправляющий данные.
M_CRC16 - Модуль вычисляющий контрольную сумму. Данные защищены CRC16 контрольной суммой
M_SD_Card_CMD - Модуль отвечающий за линию команд.
M_SD_CMD_SEND - Модуль отправляющий команды.
M_SD_CMD_GET - Модуль принимающий отклики от SD карты на эти команды.
M_CRC7 - Модуль для вычисления контрольной суммы CRC - 7 которая используется для защиты команд.
Взглянув на эту структуру, кто-то может подумать что у меня маниакальное желание создавать отдельные модули. Но это не так, потому что в отличии от того-же C++ где мы можем делать функции хоть на 100 страниц и они будут работать. В Верилоге каждый оператор это отдельный элемент который реализуется на чипе, и если их очень много то они начнут создавать помехи друг для друга.
То есть это как электронная плата, вот вы создали конструкцию always считайте что поставили чип на плату. Например если в модуле два конечных автомата, это значит пора модуль поделить. А несколько конечных автоматов получится в любом случае, потому что принимаем данные мы по положительному фронту, а отсылаем по спаду. Вот уже получилось 2 конечных автомата сами собой. Я конечно хотел-бы упростить структуру но это невозможно.
Инициализация
Прежде чем работать с картой её нужно инициализировать. Инициализация должна проходить на частоте 400 килогерц.
Скрытый текст
always @(posedge clk_400k)
begin
if (rst)
begin
State_main_Init = S_IDLE;
Send_CMD_En <= 'd0;
Get_CMD_En <= 'd0;
Init_complite <= 1'b0;
Init_Fail <= 1'b0;
WaitMessageCounter <= 'd0;
end
else
begin
case (State_main_Init)
S_IDLE: if (Init_Enable) State_main_Init <= S_SEND_CMD0;
S_SEND_CMD0:
begin
if (!Send_CMD_Complite)
begin
CMD_ID <= 'd0;
Arg1 <= 'd0;
Arg2 <= 'd0;
Arg3 <= 'd0;
Arg4 <= 'd0;
Send_CMD_En <= 1'b1;
end
else
begin
Send_CMD_En <= 1'b0;
State_main_Init <= S_SEND_CMD8;
end
end
S_SEND_CMD8:
begin
if (!Send_CMD_Complite)
begin
CMD_ID <= 'd8;
Arg1 <= 'd0;
Arg2 <= 'd0;
Arg3 <= 8'b00000001;
Arg4 <= 8'b10101010;
Send_CMD_En <= 1'b1;
end
else
begin
Send_CMD_En <= 1'b0;
State_main_Init <= S_GET_RESP_CMD8;
Get_CMD_En <= 1'b1;
end
end
S_GET_RESP_CMD8:
begin
if(Get_CMD_Complite)
begin
Get_CMD_En <= 1'b0;
WaitMessageCounter <= 'd0;
if (Responce_R1_R3[15: 8] ^ Arg4) State_main_Init <= S_CMD8_RESPONSE_FAIL;
else
begin
State_main_Init <= S_SEND_CMD55;
end
end
else
begin
WaitMessageCounter <= WaitMessageCounter + 1'd1;
if (WaitMessageCounter == 'd255) State_main_Init <= S_FAIL;
end
end
S_SEND_CMD55:
begin
if (!Send_CMD_Complite)
begin
CMD_ID <= 'd55;
Arg1 <= 'd0;
Arg2 <= 'd0;
Arg3 <= 'd0;
Arg4 <= 'd0;
Send_CMD_En <= 1'b1;
end
else
begin
Send_CMD_En <= 1'b0;
State_main_Init <= S_GET_RESP_CMD55;
Get_CMD_En <= 1'b1;
end
end
S_GET_RESP_CMD55:
begin
if(Get_CMD_Complite)
begin
Get_CMD_En <= 1'b0;
WaitMessageCounter <= 'd0;
State_main_Init <= S_SEND_ACMD41;
end
else
begin
WaitMessageCounter <= WaitMessageCounter + 1'd1;
if (WaitMessageCounter == 'd255) State_main_Init <= S_RESP_TIME_OUT;
end
end
S_SEND_ACMD41:
begin
if (!Send_CMD_Complite)
begin
CMD_ID <= 'd41;
Arg1 <= 8'b01000000;
Arg2 <= 8'b00000010;
Arg3 <= 'd0;
Arg4 <= 'd0;
Send_CMD_En <= 1'b1;
end
else
begin
Send_CMD_En <= 1'b0;
State_main_Init <= S_GET_RESP_ACMD41;
Get_CMD_En <= 1'b1;
end
end
S_GET_RESP_ACMD41:
begin
if(Get_CMD_Complite)
begin
Get_CMD_En <= 1'b0;
WaitMessageCounter <= 'd0;
if (Responce_R1_R3[39]) State_main_Init <= S_SEND_CMD2;
else State_main_Init <= S_SEND_CMD55;
end
else
begin
WaitMessageCounter <= WaitMessageCounter + 1'd1;
if (WaitMessageCounter == 'd255) State_main_Init <= S_RESP_TIME_OUT;
end
end
S_SEND_CMD2:
begin
if (!Send_CMD_Complite)
begin
CMD_ID <= 'd2;
Arg1 <= 'd0;
Arg2 <= 'd0;
Arg3 <= 'd0;
Arg4 <= 'd0;
Send_CMD_En <= 1'b1;
end
else
begin
Send_CMD_En <= 1'b0;
State_main_Init <= S_GET_RESP_CMD2;
Get_CMD_En <= 1'b1;
end
end
S_GET_RESP_CMD2:
begin
if(Get_CMD_Complite)
begin
Get_CMD_En <= 1'b0;
WaitMessageCounter <= 'd0;
PNM[0]= Responce_R2[104:97];
PNM[1]= Responce_R2[96:89];
PNM[2]= Responce_R2[88:81];
PNM[3]= Responce_R2[80:73];
PNM[4]= Responce_R2[72:65];
State_main_Init <= S_SEND_CMD3;
end
else
begin
WaitMessageCounter <= WaitMessageCounter + 1'd1;
if (WaitMessageCounter == 'd255) State_main_Init <= S_RESP_TIME_OUT;
end
end
S_SEND_CMD3:
begin
if (!Send_CMD_Complite)
begin
CMD_ID <= 'd3;
Arg1 <= 'd0;
Arg2 <= 'd0;
Arg3 <= 'd0;
Arg4 <= 'd0;
Send_CMD_En <= 1'b1;
end
else
begin
Send_CMD_En <= 1'b0;
State_main_Init <= S_GET_RESP_CMD3;
Get_CMD_En <= 1'b1;
end
end
S_GET_RESP_CMD3:
begin
if(Get_CMD_Complite)
begin
Get_CMD_En <= 1'b0;
WaitMessageCounter <= 'd0;
RCA_Addr <= Responce_R1_R3[39:24];
State_main_Init <= S_SEND_CMD9;
end
else
begin
WaitMessageCounter <= WaitMessageCounter + 1'd1;
if (WaitMessageCounter == 'd255) State_main_Init <= S_RESP_TIME_OUT;
end
end
S_SEND_CMD9:
begin
if (!Send_CMD_Complite)
begin
CMD_ID <= 'd9;
{Arg1, Arg2} <= RCA_Addr;
Arg3 <= 'd0;
Arg4 <= 'd2;
Send_CMD_En <= 1'b1;
end
else
begin
Send_CMD_En <= 1'b0;
State_main_Init <= S_GET_RESP_CMD9;
Get_CMD_En <= 1'b1;
end
end
S_GET_RESP_CMD9:
begin
if(Get_CMD_Complite)
begin
Get_CMD_En <= 1'b0;
WaitMessageCounter <= 'd0;
State_main_Init <= S_SEND_CMD7;
DeviseSize <= Responce_R2[69:48];
end
else
begin
WaitMessageCounter <= WaitMessageCounter + 1'd1;
if (WaitMessageCounter == 'd255) State_main_Init <= S_RESP_TIME_OUT;
end
end
S_SEND_CMD7:
begin
if (!Send_CMD_Complite)
begin
CMD_ID <= 'd7;
{Arg1, Arg2} <= RCA_Addr;
Arg3 <= 'd0;
Arg4 <= 'd0;
Send_CMD_En <= 1'b1;
end
else
begin
Send_CMD_En <= 1'b0;
State_main_Init <= S_GET_RESP_CMD7;
Get_CMD_En <= 1'b1;
end
end
S_GET_RESP_CMD7:
begin
if(Get_CMD_Complite)
begin
Get_CMD_En <= 1'b0;
WaitMessageCounter <= 'd0;
State_main_Init <= S_SEND_CMD55_RCA;
end
else
begin
WaitMessageCounter <= WaitMessageCounter + 1'd1;
if (WaitMessageCounter == 'd255) State_main_Init <= S_RESP_TIME_OUT;
end
end
S_SEND_CMD55_RCA:
begin
if (!Send_CMD_Complite)
begin
CMD_ID <= 'd55;
{Arg1, Arg2} <= RCA_Addr;
Arg3 <= 'd0;
Arg4 <= 'd0;
Send_CMD_En <= 1'b1;
end
else
begin
Send_CMD_En <= 1'b0;
State_main_Init <= S_GET_RESP_CMD55_RCA;
Get_CMD_En <= 1'b1;
end
end
S_GET_RESP_CMD55_RCA:
begin
if(Get_CMD_Complite)
begin
Get_CMD_En <= 1'b0;
WaitMessageCounter <= 'd0;
State_main_Init <= S_SEND_ACMD6;
end
else
begin
WaitMessageCounter <= WaitMessageCounter + 1'd1;
if (WaitMessageCounter == 'd255) State_main_Init <= S_RESP_TIME_OUT;
end
end
S_SEND_ACMD6:
begin
if (!Send_CMD_Complite)
begin
CMD_ID <= 'd6;
Arg1 <= 'd0;
Arg2 <= 'd0;
Arg3 <= 'd0;
Arg4 <= 'd2;
Send_CMD_En <= 1'b1;
end
else
begin
Send_CMD_En <= 1'b0;
State_main_Init <= S_GET_RESP_ACMD6;
Get_CMD_En <= 1'b1;
end
end
S_GET_RESP_ACMD6:
begin
if(Get_CMD_Complite)
begin
Get_CMD_En <= 1'b0;
WaitMessageCounter <= 'd0;
if(Responce_R1_R3[23:8] == 16'b0000100100100000) State_main_Init <= S_INIT_PASSED;
else State_main_Init <= S_ACMD6_RESPONSE_FAIL;
end
else
begin
WaitMessageCounter <= WaitMessageCounter + 1'd1;
if (WaitMessageCounter == 'd255) State_main_Init <= S_RESP_TIME_OUT;
end
end
S_INIT_PASSED:
begin
if (Init_Enable) Init_complite <= 1'b1;
else
begin
State_main_Init <= S_IDLE;
Init_complite <= 1'b0;
$display("SD_CARD_INIT--> SD init complite");
end
end
S_RESP_TIME_OUT:
begin
$display("SD_CARD_INIT--> SD respond time Out");
State_main_Init <= S_FAIL;
end
S_CMD8_RESPONSE_FAIL:
begin
$display("SD_CARD_INIT--> CMD8 Respond fail");
State_main_Init <= S_FAIL;
end
S_ACMD6_RESPONSE_FAIL:
begin
$display("SD_CARD_INIT--> ACMD6 Respond fail");
State_main_Init <= S_FAIL;
end
S_FAIL:
begin
if (Init_Enable) Init_Fail <= 1'b1;
else
begin
State_main_Init <= S_IDLE;
Init_Fail <= 1'b0;
$display("SD_CARD_INIT--> SD init fail");
end
end
endcase
end
end
endmodule
Сначала посылаем команду CMD0 чтобы перевести карту в состояние сброса idle.
Потом посылаем CMD8 чтобы задать напряжение. Мы будем работать с напряжение 3.3В. Но более новые карты могут работать и с напряжением 1.8В. Кстати если мы так и не получим ответ на эту команду, то это значит что у нас карта первой версии. Но поскольку сейчас карты первой версии можно найти только в музее, то я просто прекращаю инициализацию в этом случае.
Потом надо перевести карту в состояние Ready командой ACMD41, но чтобы карта приняла команду с префиксом A ей нужно предварительно отослать команду CMD55. Кроме того нужно будет ожидать довольно-таки продолжительное время пока карта не перейдет в этот режим, бомбардируя её командами ACMD41 и проверяя бит 39 в отклике.
После этого командой CMD2 переводим карту в режим Ident.
В этом режиме если послать CMD3 то карта вышлет нам свой RCA адрес и перейдет в режим Stby. Теперь чтобы послать команду нашей карте мы должны указывать её RCA, иначе она нам не ответит. Дело в том что несколько SD карт могут работать с одной шиной данных, шина CMD конечно-же у всех должна быть своя ведь каждая карта посылает отклик не обращая внимания на другие, и чтобы можно было выбрать какую-то конкретную карту придумали RCA.
Теперь при помощи команды CMD9 можно узнать содержимое CSD регистра. В нем содержатся технические характеристики карты, но нас более всего интересует её размер. Мы получим 22-битное число. Дописав к которому ещё 10 единиц мы узнаем максимально возможный адрес который можно прочитать или записать.
Теперь командой CMD7 и RCA нашей карты мы её выбираем. В результате она переходит в состояние Tran и готова к работе. Кстати если выполнить команду CMD7 с RCA какой-то "чужой" карты, то наша карта перейдет в состояние Stby. А карта чей RCA мы передали перейдет в состояние Tran.
Осталось только переключить шину данных в 4 - битное состояние командой ACMD6. Все инициализация выполнена.
Передача команд
Команды передаются последовательно по шине CMD. Сначала мы передаем команду а потом ждем оклик от карты. И команда и отклик защищены контрольной суммой CRC-7. Поэтому мне пришлось разобраться как-же её считать вот видео где показана вычисление этой суммы.
Но на самом деле так CRC сумму никто не считает. Для этого используют метод быстрого вычисления CRC суммы по таблице. И я написал небольшую программу.
Скрытый текст
#include <windows.h>
#include <iostream>
#include <stdint.h>
#include <fstream>
#include <filesystem>
#include <bitset>
/* Table for CRC-7 (polynomial x^7 + x^3 + 1)
static const uint8_t CRCTable[256] =
{
0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f,
0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26,
0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e,
0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d,
0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45,
0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14,
0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c,
0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b,
0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13,
0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42,
0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a,
0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69,
0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21,
0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70,
0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38,
0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e,
0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36,
0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67,
0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f,
0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04,
0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55,
0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d,
0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a,
0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52,
0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03,
0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b,
0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28,
0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60,
0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31,
0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79
};
*/
uint8_t CRCPoly = 0b10001001; //x^7 + x^3 + 1
void CRC_table_Generate(uint8_t* CRCTable)
{
for (int i = 0; i < 256; ++i)
{
CRCTable[i] = (i & 0x80) ? i ^ CRCPoly : i;
for (int j = 1; j < 8; ++j)
{
CRCTable[i] = CRCTable[i] << 1;
if (CRCTable[i] & 0x80)
{
CRCTable[i] = CRCTable[i] ^ CRCPoly;
}
}
}
}
uint8_t CRC_table( const uint8_t * data, size_t length )
{
uint8_t crc = 0;
uint32_t index;
uint8_t CRCTable[256];
CRC_table_Generate(CRCTable);
if (data)
for (int i = 0; i < length; i++)
{
crc = crc << 1;
index = crc ^ data[i];
crc = CRCTable[index];
}
return crc;
}
int main()
{
uint8_t CRCTable[256];
uint8_t message [5] = {0b00010001,0,0,0b00001001,0};
std::cout<<"CRC = " << std::bitset<7>(CRC_table(message, 5))<< std::endl;
//Create mem_block.txt
std::ofstream fout("mem_block.txt");
CRC_table_Generate(CRCTable);
for (int i = 0; i < 256; i++)
{
fout << "mem[" << i << "] = 8'b" << std::bitset<8>(CRCTable[i]) << ";" << std::endl;
}
return 0;
}
Эта программа может вычислить CRC-7 сумму для данных в переменной message. А самое главное она распечатывает в файл mem_block.txt эту самую таблицу, и её уже можно вставить в наш модуль.
Передача данных

Данные передаются пакетами по 512 байт по 4 линиям поэтому обращаться напрямую к данным на карте нельзя, надо скопировать данные в буфер а потом к ним обращается.
Данные защищены контрольной суммой CRC-16. Причем для каждой лини вычисляется своя контрольная сумма. Также как и в случае с CRC-7 используется быстрый метод подсчета по таблицы. Я написал еще одну программу которая может вычислить CRC-16 и распечатать таблицу в файл mem_block.txt.
Скрытый текст
#include <windows.h>
#include <iostream>
#include <stdint.h>
#include <fstream>
#include <filesystem>
#include <bitset>
//x^16 + x^12 + x^5 + 1
/* const unsigned short Crc16Table[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
}; */
void CRC_table_Generate(uint16_t* CRCTable)
{
const uint16_t LengthCRC = 16;
const uint16_t polynomial = 0x1021;
for (int i = 0; i < 256; i++)
{
CRCTable[i] = i << 8;
for (int j = 0; j < 8; j++)
{
if (CRCTable[i] & 0x8000)
CRCTable[i] = (CRCTable[i] << 1) ^ polynomial;
else
CRCTable[i] = CRCTable[i] << 1;
}
}
}
uint16_t CRC_table( const uint8_t * data, size_t length )
{
uint16_t crc = 0;
uint16_t crc_tmp = 0;
uint32_t index = 0;
unsigned short Crc16Table[256];
CRC_table_Generate(Crc16Table);
for (int i = 0; i < length; i++)
{
crc_tmp = crc << 8;
crc = crc >> 8;
index = crc ^ data[i];
crc = Crc16Table[index] ^ crc_tmp;
}
return crc;
}
int main()
{
uint8_t message [512];
unsigned short CRCTable[256];
CRC_table_Generate(CRCTable);
for (int i = 0; i < 512; i++)
{
message[i] = 0xff;
}
std::cout<<"CRC = "<< std::hex << CRC_table(message, 512) << std::endl;
//mem_block.txt
std::ofstream fout("mem_block.txt");
CRC_table_Generate(CRCTable);
for (int i = 0; i < 256; i++)
{
fout << "mem[" << i << "] = 16'b" << std::bitset<16>(CRCTable[i]) << ";" << std::endl;
}
return 0;
}
Кстати после того как я написал эту программу. Я долго не мог понять почему мой алгоритм выдает 0x6995 для массива 512 байт 0xFF хотя должен выдавать 0x7FA1. Оказалось что при вычислении CRC16 обычно используют начальное значение 0xFFFF а для SD карт нужно использовать 0.
Чтение данных.
После инициализации можно переключиться на частоту 25 мегагерц. Причем если чтение на такой частоте проходит без проблем, то вот при записи даже на частоте 1 мегагерц, у меня зависала карта. Поэтому записывал данные на карту я с частотой 400 килогерц, только так удалось избежать зависаний.
Читать данные с карты можно двумя способами. Первый командой CMD17 мы читаем один блок адрес которого мы отправили вместе с командой. Или командой CMD18 в этом случае мы получим и блок данных адрес которого мы отправили и те блоки которые находятся за ним, чтение будет продолжатся пока не мы не отправим команду CMD12. Поэтому у модуля M_SD_Card есть переменная SD_SerialCount которая говорит сколько блоков мы хотим прочитать или записать кроме того на который указывает адрес. На самом деле когда я это испытывал у меня максимум удавалось прочитать 128 блоков (один адресуемый и 127 за ним) за раз, большее количество карта просто отказывалась передавать.
Поскольку мы используем 4 линии данных то разом мы получаем 4 байта в переменной SD_Out_Data то есть 32 бита. Причем первый байт находится, от нуля до седьмого бита, а четвертый от 31 до 25 бита. SD_Out_Data_Addr показывает адрес первого байта.
Запись данных
Данные приходится записывать на частоте 400 килогерц иначе карточка зависает.
Данные также можно записывать двумя способами, это командой CMD24 в этом случае будет записан только один блок 512байт или CMD25 которая будет записывать данные пока не придет команда CMD12 в случае с записью переменная SD_SerialCount работает также как и с чтением.
Данные нужно упаковывать по 4 байта в переменную SD_InPut_Data, а переменная SD_InPut_Data_Addr показывает адрес первого байта для чтения. То есть подготавливаем блок а модуль сам считает из него данные.
Тестирование

Естественно такой огромный модуль нельзя написать без тестов, я использую Icarus-Verilog. Все тесты можно найти в репозитории на GitHub. Я просто оставлю здесь картинку, могу сказать что мне стоило огромных усилий чтобы добиться такова результата.
Демонстрация
Чтобы не быть голословным я создал демонстрацию которая считывает картинку в формате .BMP (картинка обязательно должна иметь качество 24 бита на пиксель) и выводит её на экран.
Вы скажите а как записать картинку на SD карту? Если вы подумали что между делом я ещё и драйвер FAT-32 написал, то нет вы ошиблись. Картинку надо записывать на SD карту при помощи Win32DiskImager.
Для копирования данных с SD карты в SDRAM память используется модуль M_BMP_Copire. Я не буду его здесь подробно описывать, статья и так уже большая, я остановлюсь на одном моменте.
Это то что все строки в BMP имеют выравнивание кратное 4 байтам. Поэтому нельзя надеется что следующая строка начнется там где закончилась предыдущая. Поэтому смещение пикселя я нахожу таким образом:
assign PixelOffset = PixArrayOffset + (PixHeight - 1'b1 - V_count) *
StringLen + H_count * 'd3;
Как видите здесь я использую переменную StringLen которую я рассчитываю таким образом:
StringLen <= (PixWidth * 'd3) + ((PixWidth * 'd3) % 'd4);
Также если вам понравилась эта статья то возможно вам понравятся и другие статьи на эту тему:
tequier0
Чтобы конечный автомат был понятнее, предпочтительнее использовать двухблочную Moore FSM с выходными регистрами.
В одном блоке происходит переключение между стейтами согласно управляющим сигналам, во втором - логика стейтов, в т.ч. формирование управляющих сигналов для стейт машины.
Это обеспечивает более контролируемое поведение при синтезе, позволяет избегать латчей, ну и так проще контролировать состояния и наворачивать большое количество логики.
p.s. даже в случае одноблочной fsm best practices считается прописывать default state на случай сбоя/недостаточного покрытия тестами и более прогнозируемого поведения. Машина в таком случае хотя бы имеет шанс восстановиться.