nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Мучаем 5576ХС4Т - часть 'h25 - передаём показания АЦП на компьютер

Часть 0 - покупаем, паяем, ставим драйвера и софт
Часть 1 - что это вообще за зверь?
Часть 2 - наша первая схема!
Часть 3 - кнопочки и лампочки
Часть 4 - делитель частоты
Часть 5 - подавление дребезга кнопки
Часть 6 - заканчиваем кнопочки и лампочки
Часть 7 - счетчики и жаба
Часть 8 - передатчик UART
Часть 9 - Hello, wolf!
Часть 'hA - приёмник UART
Часть 'hB - UART и жаба
Часть 'hC - полудуплексный UART.
Часть 'hD - МКО (МКИО, Mil-Std 1553) для бедных, введение.
Часть 'hE - приёмопередатчик МКО "из подручных материалов" (в процессе)
Часть 'hF - модуль передатчика МКО
Часть 'h10 - передатчик сообщений МКО
Часть 'h20 - работа с АЦП ADC124s051
Часть 'h21 - преобразование двоичного кода в двоично-десятичный (BCD)
Часть 'h22 - Bin2Bcd с последовательной выдачей данных
Часть 'h23 - перемножитель беззнаковых чисел с округлением
Часть 'h24 - перемножитель беззнаковых чисел, реализация
Часть 'h25 - передаём показания АЦП на компьютер
Часть 'h26 - работа над ошибками (быстрый UART)
Часть 'h27 - PNG и коды коррекции ошибок CRC32
Часть 'h28 - передатчик изображения PNG
Часть 'h29 - принимаем с ПЛИС изображение PNG
Часть 'h2A - ZLIB и коды коррекции ошибок Adler32
Часть 'h2B - ускоряем Adler32
Часть 'h2C - формирователь потока Zlib
Часть 'h2D - передаём сгенерированное PNG-изображение
Часть 'h2E - делим отрезок на равные части
Часть 'h2F - знаковые умножители, тысячи их!
Часть 'h30 - вычислитель множества Мандельброта
Часть 'h31 - ускоренные сумматоры
Часть 'h32 - ускоренные счётчики (делаем часы)
Часть 'h33 - ускоряем ВСЁ
Часть 'h34 - ускоренные перемножители
Часть 'h35 - умножители совсем просто
Часть 'h36 - уравновешенный четверичный умножитель


Соединим вместе модули, которые мы писали ранее, чтобы в кои-то веки получить "законченную конструкцию". К 4 входам АЦП подключены через делители напряжения 4 шины питания: +1,8 (ядро ПЛИС), +3,3 (периферия ПЛИС, питание Ethernet, генератора тактовой частоты, RS485 и пр.), +5 (питание виртуального COM-порта CP2102, он же преобр. USB-UART, а также селектор синхроимпульсов LM1881), +12 (питание аналоговой камеры с подсветкой). АЦП последовательно оцифровывает эти значения (в каждый момент он может оцифровывать только с одного из входов), затем они подаются на умножитель, чтобы из абстрактных "попугаев" превратиться в целое число милливольт либо сотен микровольт.



С выхода умножителя данные идут на преобразователь в десятичный код, ещё немножко доработанный, чтобы мы могли "попросить" его поставить десятичную точку (или запятую) там, где мы хотим. Десятичный код в форме ASCII сохраняется в нужном месте в памяти, прямо посередине заранее заготовленной строки. И наконец, когда все 4 значения обновятся, мы запускаем передачу строки по UART.



Также мы в кои-то веки обозначили выводы, которыми мы не пользовались ранее (Ethernet, RS485, и недавно появившееся на моей макетке выходы селектора синхроимпульсов и скоростного, до 100 млн. выборок в секунду, АЦП), чтобы ничего не пожечь из-за неправильной конфигурации ножек (они по умолчанию настраиваются как выход с нулевым логическим уровнем). Как показала практика, прямо-таки "сгорания" не происходит, но энергопотребление повышается из-за того, что некоторые ножки "тянут в разные стороны"!


Схема получилась довольно крупная, но простая, мы здесь "играем в лего", объединяя ранее спроектированные модули.

Сверху у нас стоит делитель частоты, из 80 МГц дающий импульсы длительностью 1 такт (ce, clock enable) и частотой 16 МГц. Такие импульсы нужны для функционирования нашего "контроллера SPI" для общения с АЦП. Далее эти 16 МГц делятся ещё дальше, до 4 Гц - для запуска АЦП и счётчика каналов.

Эти делители основаны на CustomCounter, который мы делали чуть ранее.

ChanCounter мы вытащили из "библиотечных элементов" - это счётчик (megafunctions - arithmetic - lpm_counter), для которого мы задали разрядность 2 бита, счёт вверх, простой двоичный, с выходом разрешения счёта и выходом переноса. Он считает от нуля до трёх, на трёх появляется единица на выходе cout, а по следующему импульсу он снова сбрасывается в ноль. С его помощью мы выбираем, какой канал оцифровывать, а также, по окончании цикла опроса мы запускаем передатчик строки по UART.

Далее стоит наш контроллер SPI конкретно для микросхемы ADC124s051, он соединён с АЦП по 4 проводам. 4 раза в секунду мы запускаем оцифровку, а сразу по её окончанию запускаем умножитель.

Модуль ADCConstants - это "самопальная" таблица констант: на что домножить результат оцифровки, чтобы представить его в "удобоваримом виде", где должна стоять десятичная точка (запятая), и куда мы должны положить строку, результат преобразования двоичного кода в десятичный. Он совсем простой:
module ADCconstants (input [1:0] Chan, output [15:0] Mult, output [6:0] DigitAddr, output [1:0] DecimalPos);

assign Mult =  	(Chan == 2'b00)? 16'd13848 : //12 V
		(Chan == 2'b01)? 16'd55298 : //5 V
		(Chan == 2'b10)? 16'd30695 : //1,8 V
				 16'd36541;  //3,3 V
							 
assign DigitAddr = (Chan == 2'b00)? 7'h46 : //12 V
		   (Chan == 2'b01)? 7'h33 : //5 V
		   (Chan == 2'b10)? 7'h0D : //1,8 V
		  		    7'h21;  //3,3 V
									 
assign DecimalPos = (Chan == 2'b00)? 1'd1 : 1'd0;
endmodule


Константы по сути показывают, какое напряжение на шине вводит АЦП "в насыщение". Я старался так подобрать резисторы, чтобы вариация "+10%" от номинала ещё помещалась в диапазон работы, но не более того, чтобы не делить сигнал слишком сильно. Как видно, мы можем мерять напряжение по шине "12 вольт" от 0 до 13,848 вольта, по шине "5 вольт" - от 0 до 5,5298 вольта, и так далее. (Вообще, не совсем так, поскольку сейчас мы 12-битное значение дополняем 4 младшими нулями до 16-битного, поэтому правильнее эти константы помножить на 0xFFF0, а затем поделить на 0x10000 - вот это и будет максимальное значение).

Дальше у нас модуль умножителя 16-битных беззнаковых чисел, с округлением банкира - как только завершена очередная оцифровка, и на входе C уже дожидается нужная константа, мы запускаем умножение.

Как только умножение окончено, запускается преобразование 16-битного двоичного числа по сути в строку с десятичным представлением этого числа. Его выход 8-битный - он выдаёт по одному символу за раз, от самого старшего разряда к самому младшему. В отличие от нашей прошлой реализации, мы добавили новую "фичу" - выдачу десятичной точки (запятой) там, где мы попросим. Вот код этого нового модуля:
//Possible values for DecimalPointPos:
//00 - d,dddd
//01 - dd,ddd
//10 - ddd,dd (these 3 should be enough for volts, amperes, ohms etc. (as we can use milli, micro, kilo, etc)
//11 - ddddd 

module Bin2Bcd16withFixedPoint (input clk,
                                input [15:0] D,
                                input start,
                                input [1:0] DecimalPointPos,
                                output [7:0] char,
                                output DigitReady,
                                output [2:0] DigitPosition,
                                output reg Finished = 1'b0);
                                
parameter DecimalPointChar = ",";
//first, state machine
localparam sIdle  =       5'b00000;
localparam sStart =       5'b00001;
localparam sDigit0Ready = 5'b10000; //senior, tens of thousands
localparam sShift1_1    = 5'b10001;
localparam sShift1_2    = 5'b10010; //nice place for outputting decimal point, if needed
localparam sShift1_3    = 5'b10011;
localparam sDigit1Ready = 5'b10100; //thousands
localparam sShift2_1    = 5'b10101;
localparam sShift2_2    = 5'b10110;
localparam sShift2_3    = 5'b10111;
localparam sDigit2Ready = 5'b11000; //hundreds
localparam sShift3_1    = 5'b11001;
localparam sShift3_2    = 5'b11010;
localparam sShift3_3    = 5'b11011;
localparam sDigit3Ready = 5'b11100; //tens
localparam sShift4_1    = 5'b11101;
localparam sShift4_2    = 5'b11110;
localparam sShift4_3    = 5'b11111; //Digit3 will be ready exactly when we're back to Idle!

wire [4:0] State;

wire IsIdle = (State == sIdle); //no shortcuts, all states are used!
wire TC; //Terminal Count
always @(posedge clk)
	Finished <= TC;

wire DoCorrection = ~State[4]; //we do correction on conversion steps, but not when showing results
assign DigitPosition = {Finished, State[3:2]};

wire ShowingDP = (DecimalPointPos == State[3:2]) & (DecimalPointPos != 2'b11) & State[4] & State[1] & (~State[0]);

assign DigitReady = Finished | (State[4] & (~State[1]) & (~State[0])) | ShowingDP;

lpm_counter	counter (
				.clock (clk),
				.cnt_en (~IsIdle | start),
				.q (State),
				.cout (TC) );
	defparam
		counter.lpm_direction = "UP",
		counter.lpm_port_updown = "PORT_UNUSED",
		counter.lpm_type = "LPM_COUNTER",
		counter.lpm_width = 5;

//now our datapath
wire [19:0] Q;
reg [10:0] SR = 1'b0; //shift register

reg [3:0] senior = 1'b0; //[19:16]
assign char = ShowingDP? DecimalPointChar : {4'h3, senior};


wire B0toB1;
SeqBinToBcdLeastSignBlock Block0 (
	.clk (clk),
	.sclr (start),
	.D (D[15]),
	.DoCorrection (DoCorrection),
	.bIn (SR[10]),
    .Q (Q[3:0]),
    .bOut (B0toB1));

wire B1toB2;
SeqBinToBcdAtomicBlock Block1 (
	.clk (clk),
	.sclr (start),
	.DoCorrection (DoCorrection),
	.bIn (B0toB1),
    .Q (Q[7:4]),
    .bOut (B1toB2));

wire B2toB3;
SeqBinToBcdAtomicBlock Block2 (
	.clk (clk),
	.sclr (start),
	.DoCorrection (DoCorrection),
	.bIn (B1toB2),
    .Q (Q[11:8]),
    .bOut (B2toB3));
    
wire B3toB4;
SeqBinToBcdAtomicBlock Block3 (
	.clk (clk),
	.sclr (start),
	.DoCorrection (DoCorrection),
	.bIn (B2toB3),
    .Q (Q[15:12]),
    .bOut (B3toB4));

always @(posedge clk) begin
	senior <= start? D[3:0] : {senior[2:0], B3toB4};
	SR     <= start? D[14:4]: {SR[9:0], senior[3]};
end

endmodule


Мы не стали усердствовать и делать возможность ставить запятую в любом месте - либо не ставим вообще (если имеем дело с целым числом), либо она ставится после первого, второго либо третьего разряда. Поскольку "ходовые" приставки СИ идут через тысячу (киловольты-вольты-милливольты-микровольты), этого вполне должно хватить! Нам пока хватает :)

Также мы видим, что можем указать в виде параметра, использовать ли точку или запятую. Мы пока выбрали запятую - это же всё-таки 5576ХС4Т, а не какая-нибудь вам Flex10k!

К сожалению, выход номера десятичного разряда для нас немножко бесполезен - он не учитывает десятичную запятую (и это правильно, пожалуй), поэтому мы вводим ещё один счётчик, чтобы помещать результаты преобразования в последовательные ячейки памяти. Этот счётчик мы тоже взяли "библиотечный" - разрядность 7 бит (именно такая у нас адресация памяти - 128x8), счёт вверх, вход параллельной загрузки и разрешения счёта. Когда только стартует преобразование двоичного кода в десятичный, мы "заблаговременно" загружаем начальный адрес в памяти, куда мы хотим запихнуть данное число. Затем, как только будет приходить каждый из разрядов (или запятая) - счётчик будет увеличиваться на единичку, а в память будет заноситься только что поступивший байт.

Наконец, разберём нижнюю строку. Она начинается с элемента and2 (тоже библиотечный, primitives - logic), с помощью которого мы формируем импульс запуск передатчика строки - он происходит каждый раз после 4 оцифровок "в ряд".

UARTStringControl мы написали в ноябре (hello, wolf), а передатчик UART - и того раньше.

Остаётся память - она у нас в кои-то веки двухпортовая, причём с "защелкиванием" как входов, так и выходов. Поскольку схема немножко "разжирела", то при попытке оставить выход "комбинаторным" я заполучил Critical Warning, что время распространения сигнала превышено - схема не может устойчиво работать на 80 МГц. Добавление ещё одной "защёлки" спасло ситуацию и никак не повлияло на работу передатчика, поскольку он изначально был на такое рассчитан.

Память мы инициализируем следующей строкой:
xШина 1,8 В: x,xxxx
Шина 3,3 В: x,xxxx
Шина 5 В: x,xxxx
Шина 12 В: xx,xxx

n

Его мы преобразуем в .hex (подробности в hello, wolf), и затем уже в hex-редакторе в Quartus'е переправляем самый первый байт на ноль, равно как и самый последний. Иксы - это как раз то место, куда запихнутся нужные нам значения.

И ещё у нас слева напихано всевозможных входов и выходов:
- SA2 .. SA6 - кнопки. Они "притянуты" на +3,3 вольта, и если не обозначить эти ножки как "входы", ПЛИС подаст на них лог. 0, что приведёт к "ужасающему" току 1,6 мА.

- CompSync, VertSync, BurstSync, OddEvenSync - выходы селектора синхроимпульсов (их я сам припаял, на исходной отладочной плате ничего такого не было). Тоже негоже выходы соединять с выходами, так что обозначили, что для ПЛИС это входы!

- uUART_RXD, RS485_RXD - входы приёмопередатчиков, причём довольно-таки мощные и подключённые "напрямую" - вот они вполне могли мне "выжечь" выходы ПЛИС (они ещё и принимают значение лог. 1, когда сигнал отсутствует, тогда как ПЛИС тянет в лог. 0), но по счастью не выжгли. Тут, если их правильно не сконфигурировать, мы могли потерять десятки миллиампер.

- ETH_CLK_global, ETH_CLKout, ETH_nInt - выходы микросхемы контроллера Ethernet. Та же история
- ETH_nCS, FastAdcDisable - выходы "выбора чипа" для Ethernet (единица означает - чип не выбран) и для скоростного АЦП. И на тот, и на другой подаём единицу, чтобы немножко снизить потребление. При этом все прочие ножки (8 бит с АЦП, MISO с Ethernet-контроллера) со стороны этих чипов переходят в Z-состояние, поэтому мы делаем правильно, "притягивая" их на землю. (Последний есть только у меня, на исходной отладочной плате его не было).

Весь этот агрегат в целом синтезируется в 307 логических элементов, или в 3% от общего количества на ПЛИС 5576ХС4Т. Пока ещё можно жить спокойно.

Показания довольно сильно "гуляют", поскольку они никоим образом не фильтруются. Можно было бы рассмотреть вопрос цифрового фильтра, чтобы они "успокоились", и тут уже начинает "мерещится" процессор...
Tags: ПЛИС, математика, работа, странные девайсы
Subscribe

  • Так есть ли толк в ковариационной матрице?

    Задался этим вопросом применительно к своему прибору чуть более 2 недель назад. Рыл носом землю с попеременным успехом ( раз, два, три, четыре),…

  • Big Data, чтоб их ... (4)

    Наконец-то стряхнул пыль с компьютерной модели сближения, добавил в неё код, чтобы мы могли определить интересующие нас точки, и выписать…

  • Потёмкинская деревня - 2

    В ноябре 2020 года нужно было сделать скриншот несуществующей программы рабочего места под несуществующий прибор, чтобы добавить его в документацию.…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 3 comments