Так что уж лучше MicroSD для начала по SPI, там хоть 25 МБит/с - всяко побольше. Пока что припаял гнездо для карточки, тоже из Линовских запасов ардуиновскую платку "Wireless SD shield" и мучительно соображаю, как реализовать инициализацию и передачу данных.
Алгоритм инициализации, особенно поддерживающий карточки SDHC (свыше 2 ГБ) довольно муторный: подать 74 такта на SCK при CS=1, потом CMD0, потом CMD8 и т.д., обязательно дожидаясь, когда карта соизволит ответить.
Чтобы это отладить за разумное время, нужно результат каждого этапа передавать по UART. До сих пор у нас было одно устройство ввода-вывода, то это был UART, потом LCD, теперь будет всё вместе. Можно было бы им просто присвоить разные адреса, может в итоге так и будет, но тогда нужны отдельные процедуры для разных устройств ввода-вывода. А ведь LCD и UART до сих пор "приводились в действие" одним и тем же print, и потенциально - одной и той же процедурой преобразования 16-битного числа в десятичную форму "с фиксированной запятой".
Так что сейчас решил ввести один адрес для вывода: "OUT", один для ввода: "IN", и ещё адрес для выбора устройства, с которым общаемся по шине в данный момент: "SIO" (Select I/O). Вроде бы получилось:

Заодно чуть улучшил компилятор, чтобы он не настаивал на вызове процедур через стек :) В итоге, сейчас весь код программы, которая инициализирует ЖК-экран, добавляет туда 2 новых символа, выводит 3 строки, а потом отправляет 2 строки по UART, занимает 14 слов, или 28 байт :) Полный размер схемы составил 509 ЛЭ - это процессор, контроллеры UART и LCD, и ещё таймер, мигающий светодиодиком раз в секунду, почему-то успокаивает он меня.
Приведём изменившийся фрагмент QuatCore:

У нас появился новый модуль QuatCoreIOselector. Именно он "висит" на шинах DestAddr и SrcAddr, тогда как модули UART и LCD теперь запускаются от него, сами они адреса больше не проверяют.
Вот код данного модуля:
module QuatCoreIOselector (input clk, input [7:0] DestAddr, input [7:0] SrcAddr, input [15:0] DataBus, output UARTtxEN, output LCD_EN, output SDtxEN, output UARTrxEN, output SDrxEN); wire isSelection = (~DestAddr[7])&DestAddr[6]; wire isIO_out = (~DestAddr[7])&(~DestAddr[6]); wire isIO_in = (SrcAddr[7:4] == 4'b1001); reg [1:0] sel = 2'b0; always @(posedge clk) if (isSelection) sel <= DataBus[1:0]; assign UARTtxEN = (sel==2'b00) & isIO_out; assign LCD_EN = (sel==2'b01) & isIO_out; assign SDtxEN = sel[1] & isIO_out; assign UARTrxEN = (sel==2'b00) & isIO_in; assign SDrxEN = sel[1] & isIO_in; endmodule
На шине DestAddr этот модуль откликается на 2 команды. OUT (00xx_xxxx, или 0x00..0x3F) - вывод информации через выбранное устройство. SIO (01xx_xxxx, оно же 0x40..0x7F) - задать текущее устройство ввода-вывода.
Чтобы запомнить, какое устройство выбрано, внутри находится 2-битный регистр sel, и по команде SIO в него "защёлкиваются" младшие два бита из шины данных. 00 соответствует UART, 01 - LCD, 1x - SD.
На шине SrcAddr модуль откликается на команду IN (1001_xxxx) - ввод информации через выбранное устройство. Если шина DestAddr была "наполовину пуста" за счёт отсутствия модуля Imm (immediate), то в SrcAddr пришлось немножко потеснить АЛУ. Ему были отведены команды 100x_xxx (32 штуки), но реально задействовалось всего 2 бита, чтобы выбрать между Acc, UAC и C, так что половину диапазона мы с чистой совестью откусили. Теперь 1000_xxxx принадлежит АЛУ, а 1001_xxxx - на I/O.
Но пока устройства ввода у нас не реализованы, да и контроллер SD ещё не готов.
Покажем теперь код очередной тестовой программы:
;то же самое, но дополнительно выдаём Good News, everyone на компьютер %include "QuatCoreConsts.inc" %include "LCD_code_page_1.inc" .rodata ;адр 0 InitLCD dw 0x330 ;установить разрядность интерфейса (одиночная посылка, команда, 0011) Init1 dw 0x330 ;установить разрядность Init2 dw 0x330 ;установить разрядность Init3 dw 0x320 ;установить разрядность 4 бита Init4 dw 0x12A ;установка параметров (двойная посылка, команда, 0010 1010 = 4 бита, страница 1) Init5 dw 0x10C ;включение дисплея (двойная посылка, команда, 0000 1100 = включить, курсора нет, ничего не мигает) Init6 dw 0x101 ;очистка дисплея (двойная посылка, команда, 0000 0001) Init7 dw 0x106 ;установка режима ввода данных (двойная посылка, команда, 0000 0110 = курсор вправо, дисплей не двигается) Init8 dw 0x140 ;выбор области CGRAM, установка на нулевом адресе Init9 dw 0x00E ;нулевой символ, 0-я строка InitA dw 0x00A ;1-я строка InitB dw 0x00A ;2-я строка InitC dw 0x00B ;3-я строка InitD dw 0x00B ;4-я строка InitE dw 0x00A ;5-я строка InitF dw 0x00A ;6-я строка Init10 dw 0x00E ;7-я строка (для курсора вообще, но мы его не исп) Init11 dw 0x00E ;1-й символ, 0-я строка Init12 dw 0x00A ;1-я строка Init13 dw 0x00A ;2-я строка Init14 dw 0x01A ;3-я строка Init15 dw 0x01A ;4-я Init16 dw 0x00A ;5-я Init17 dw 0x00A ;6-я Init18 dw 0x00E ;7-я Init19 dw 0x8180 ;выбор области DDRAM,нулевая позиция (адр. в памяти 0x19 = 25) Row0 dw 0,1,'-','з','н','а','к',' ','ч','е','т','ы','р','ё','х' ;(адр. 40) Row1 dw 0x1C0,'Г','а','м','и','л','ь','т','о','н',' ',' ','Р','о','д','р','и','г','е','с' ;(адр. 60 в конце) Row2 dw 0x194,'Б','р','а','н','е','ц',' ',' ','Ш','м','ы','г','л','е','в','с','к','и',f'й' ;(адр. 80 в конце) ORG -64 UartStr dw 'G','o','o','d',' ','n','e','w','s',',',' ','e','v','e','r','y','o','n','e','!',13,10 UartStr1 dw 'м','ы',' ','р','а','б','о','т','а','е','м',' ','и',' ','с',' ','U','A','R','T',',',' ','и',' ','с',' ','Ж','К',f'!' ;для интересу, сейчас без стека. Адрес возврата сохраним в регистр C .code main proc SIO LCD SP InitLCD C CALL(print) SP Row0 C CALL(print) SIO UART SP UartStr C CALL(print) @@endless: JMP @@endless main endp ;изменяет только SP (после вызова он указывает сразу ПОСЛЕ последнего символа строки) ;адрес возврата ищет в рег. C print proc @@start: OUT [SP] Acc [SP++] SUB 0 JGE @@start JMP C print endp
В самом начале мы видим новый заголовочный файл, QuatCoreConsts.inc. Вот его содержание:
;константы для аргументов QuatCore ;можно было бы их в сам компилятор "зашить", но пущай так .data ;костыль нашего компилятора - он ищет EQU только в сегменте данных ;устройства ввода-вывода. Выбор производится командой SIO UART EQU 0 LCD EQU 1 SD EQU 2 ;константы, которыми можно инициализировать аккумулятор, командой ZAcc MinusThreeHalf EQU 0 MinusOne EQU 1 ThreeHalf EQU 2 RoundZero EQU 3
Дальше идут уже знакомые "строки" для инициализации ЖК-экрана, для показа сообщения, а потом мы видим директиву:
ORG -64
т.е последующие строки размещаются по адресу "-64". Проблема в том, что строка UartStr попадала на адрес 0x51, а такое значение присвоить регистру "непосредственно" нельзя. Можно только первые 64 и последние 64 значения в памяти, так выходит, поскольку для Imm-значений происходит расширение знака.
Директива ORG -64 заставляет компилятор запихать данные за 64 слова до окончания области памяти, чтобы к ним был простой доступ. Вот что значит, память забили!
Место под стек мы не зарезервировали вообще, т.к решили на этот раз обойтись без него. По сути, вся память здесь могла быть ROM - и всё бы отработало как надо.
В коде программы видим, что решили для разнообразия использовать SP как аргумент - указатель на строку, которую надо вывести :) Адрес возврата хранить в регистре "C" (он заведомо 16-битный, в него всё влезет!).
Теперь кроме "макроса" / директивы CALL, стоящей на месте DestAddr, у нас появилась CALL(...), стоящая на SrcAddr. Эта директива просит занести метку в таблицу вызовов функций, а вместо CALL(...) подставить одну из команд Call0..CallF, осуществляющие переход по соответствующим адресам с выводом адреса возврата на шину данных.
Регистр PC сейчас занимает 4 бита :) Но программа всё же делает что-то осмысленное.
Дальше на очереди - модуль SPI для SD-карточки и её инициализация...