nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

CRC и полудуплексный UART - часть 2

В первой части мы чуть доработали приёмопередатчик, чтобы он "дружил" с модулем CRC, а также написали сам этот модуль, проверили всё это на передачу - работает.

Теперь надо посмотреть, как оно будет работать на приём. Задача модуля: "самостоятельно" посчитать CRC от входящего сообщения, затем получить на вход переданный CRC (в конце сообщения) и убедиться, что они совпадают, в противном случае выдать ошибку.

Ещё разок покажем результаты симуляции с прошлого раза, там видна и работа приёмника:
TXwithDataBitEarly.png


Сигналы RXisDataBit = 1 (то есть, вывод isDataBit второго модуля приёмопередатчика, который мы сейчас включили на приём) поступают за 2 такта до того, как бит данных "защёлкивается" в сдвиговый регистр, т.е сначала RXisDataBit = 1, затем на положительном фронте следующего такта бит поступает в регистр, и только к началу следующего такта он появляется на выходе этого регистра.

Но мы предлагаем пустить на вход CRC непосредственно rxd - данные с выхода приёмника RS485. Выходит, они будут "защёлкиваться" одним тактом ранее в CRC, чем в сдвиговый регистр - не вижу ничего страшного. Каждый бит при скорости 921 600 бод и тактовой частоте 25 МГц будет передаваться 27 тактов, а мы берём два "из середины".

Похоже, ничего менять не надо, должно заработать.

Приведём код приёмопередатчика, так как в прошлый раз он немного менялся:
`include "math.v"

module HalfDuplexUART(	input clk, input [7:0] D, input startTX, input isCRC, input CRC_bit,
			output [7:0] Q, output HasOutput, output FrameError, output LineBreak,
			output txd, output reg RW = 1'b0, output isDataBit,
			inout rxd,
			output [3:0] DebugState);

	assign rxd = RW? 1'b1 : 1'bz;

	parameter CLKfreq = 25_000_000;
	parameter BAUDrate = 921_600;

	localparam Quotient = ((CLKfreq + BAUDrate/2) / BAUDrate) - 1;
	localparam DividerBits = `CLOG2(Quotient);
	localparam Limit = Quotient - 1;
	localparam LimitDiv2 = (CLKfreq + BAUDrate/4) / (BAUDrate * 2) - 1;

	localparam sIdle 	=	4'b0000;
	localparam sStart	=	4'b0110;
	localparam sB1		=	4'b0111;
	localparam sB2		=	4'b1000;
	localparam sB3		=	4'b1001;
	localparam sB4		=	4'b1010;
	localparam sB5		=	4'b1011;
	localparam sB6		=	4'b1100;
	localparam sB7		=	4'b1101;
	localparam sB8		=	4'b1110;
	localparam sStop	= 	4'b1111;

	wire [3:0] State;
	wire isStopState;
	wire isIdle = (~State[3]) & (~State[2]); //shortcut as not all states are used
	wire isStart = (~State[3]) & State[2] & (~State[0]);	//small shortcut...
	
	wire [DividerBits-1:0] FD; //Frequency Divider
	
	wire comb_ce = (FD & Limit) == Limit;
	reg ce = 1'b0;
	always @(posedge clk)
		ce <= comb_ce;
	assign isDataBit = (State[3] | State[0])&(~isStopState)&comb_ce;	
	
	always @(posedge clk)
		RW <= (startTX & isIdle)? 1'b1 : (isStopState & ce)? 1'b0 : RW;
	
	wire ZeroFreqDivider = isIdle & rxd | ce;
	wire HalfFreqDivider = isIdle & ~rxd;
	
	lpm_counter Divider (
			.clock (clk),
			.sset (HalfFreqDivider),
			.sclr (ZeroFreqDivider),
			.Q (FD) );
  defparam
    Divider.lpm_direction = "UP",
    Divider.lpm_port_updown = "PORT_UNUSED",
    Divider.lpm_type = "LPM_COUNTER",
    Divider.lpm_width = DividerBits,
    Divider.lpm_svalue = LimitDiv2;   
	

	lpm_counter StateMachine (
				.clock (clk),
				.cnt_en (ce),
				.sset (isIdle & ((~rxd) | startTX)),
				.sclr (isStart & rxd & (~RW)),
				.q (State),
				.cout (isStopState) );
	defparam
		StateMachine.lpm_direction = "UP",
		StateMachine.lpm_port_updown = "PORT_UNUSED",
		StateMachine.lpm_type = "LPM_COUNTER",
		StateMachine.lpm_width = 4,
		StateMachine.lpm_svalue = sStart;


	assign DebugState = State;
	
	
	reg [8:0] SR = 9'b1_1111_1111;
							
	always @(posedge clk) if (ce | startTX & isIdle) begin
		SR[8] <= (startTX & isIdle)? D[7] : rxd;
		SR[7:1] <= (startTX & isIdle)? D[6:0] : SR[8:2];
		//SR[0] <= (startTX & isIdle)? 1'b0 : (isCRC & (State[3:1] != 3'b111))? CRC_bit : SR[1];
		SR[0] <= (startTX & isIdle)? 1'b0 : (isCRC &~& State[3:1])? CRC_bit : SR[1];
	end
		
	assign Q = SR[8:1];
	assign txd = SR[0];
	
	assign HasOutput = isStopState & ce & rxd & ~RW; //т.е приняты уже все данные, а стоповый бит ожидаемо высокий.
	//у нас как раз есть 1 такт, чтобы занести данные из Q, т.к. на следующий такт они уже пропадут!
	
	//assign FrameError = isStopState & ce & (~rxd) & ~RW;
	//assign LineBreak = FrameError & (Q == 0) & ~RW;	
	
endmodule


И подсоединим модуль CRC на приёмной стороне в нашей тестовой схеме. Т.е верхние два модуля - это как бы передатчик, а нижние два - приёмник, хотя сами модули одинаковые:


И смотрим, как оно работает. Передача первого байта:


CRC на стороне приёмника работает "синхронно", разве что с небольшим опережением, как это ни странно. Он хватает следующий бит на середине периода его передачи (чтобы подальше от всяких заваленных фронтов), а на стороне передатчика только на окончании периода, потому так удобнее :)

RX_CRC_error = 0, так и должно быть.

Передача второго байта:


По-прежнему полная синхронность, ошибки не показывает. Ну, пока CRC только считается, RX_CRC_error непрерывно сбрасывается в 0.

Теперь передача первого байта CRC, и на стороне приёмника перевели его в состояние CRC_en = 0, это значит простой сдвиговый регистр, но в то же время будет проверяться совпадение каждого бита:


Опять всё синхронно, ошибки не показывает.

И второй байт:


В итоге CRC сбросился в ноль, ошибки так и не появилось. Всё верно.

Теперь нужно всё-таки смоделировать ошибку. Для этого "заземлю" в передатчике вход isCRC, чтобы он всегда передавал данные, поступающие на вход D, ну и скормлю ему неправильный CRC, отличающийся одним битом. А ну-ка:


Вместо значения 0x7F, мы отправили 0x7A, а поскольку передача в UART ведётся "младшим битом вперёд", уже первый бит не совпал - и "высветилась" ошибка RX_crc_error = 1. При этом основные регистры (16-битное значение crc_out) всё равно сбросились в ноль, что позволяет такой штуке делать CRC от всего сообщения, включая заголовок, если нужно. К следующему сообщению модуль CRC вновь готов к работе!

Теперь давайте, наконец, объединим приёмопередатчик и модуль CRC в один модуль, который будет корректно работать и на приём, и на передачу:


Всё-таки вывел наружу два разных входа: transmitCRC управляет передатчиком, заставляя вместо данных из D передать текущие 8 бит из CRC. computeCRC разрешает вычисление CRC, в противном случае модуль работает как сдвиговый регистр, заодно проверяя совпадение заталкиваемых ему на вход битов с теми, что он в данный момент выталкивает.

Разделение нужно, поскольку по нынешнему протоколу мы берём CRC от СЛОВ ДАННЫХ, но не от ЗАГОЛОВКА. Поэтому при передаче заголовка мы подадим transmitCRC = 0 (нужно передать именно данные по D, а не CRC неизвестно откуда взявшийся) и computeCRC = 0 (за время передачи заголовка как раз всё обнулится). Затем, при передаче слов данных, подаём transmitCRC = 0 и computeCRC = 1. И наконец, когда настаёт черёд передать CRC, ставим transmitCRC=1 и computeCRC=0. Как видно, три различных состояния, для которых заведомо нужны 2 бита, одним не обойдёшься никак.

И FrameError (хотя само выражение закомментировано) вывел наружу. Всё-таки, корректно было бы при возникновении ошибки в приёме байта (не пойми откуда взявшийся нолик на месте стопового бита) будет логично прервать получение сообщения - уже и без CRC понятно, что запороли.

Такая связка синтезируется в 54 ЛЭ (при тактовой частоте 25 МГц и скорости передачи 921600 бод), максимально допустимая частота 100 МГц.

Проверим, что всё правильно соединили, для чего превратим эту связку в схемотехнический символ и поставим две штуки (проверить обмен между ними):


Запускаем:


Да, 4 байта приняты правильные (включая 2 байта CRC), ошибку CRC не даёт.

И теперь то же самое, но вместо последнего байта CRC - произвольный байт:


Ага, выскакивает ошибка CRC, замечательно!


Неплохо: работу CRC на приёме проверили, всё хорошо. И совмещённый модуль из CRC и приёмопередатчика тоже нарисовали, аж на 54 ЛЭ, он тоже работает.

Осталось совсем немного по информационному обмену: расширить этот приёмопередатчик UART до 16 бит, а потом соединить с "протокольным контроллером МКО".
Tags: ПЛИС, работа, странные девайсы
Subscribe

Recent Posts from This Journal

  • Тестируем atan1 на QuatCore

    Пора уже перебираться на "железо" потихоньку. Решил начать с самого первого алгоритма, поскольку он уже был написан на ассемблере. В программу внёс…

  • Формулы приведения, что б их... (и atan на ТРЁХ умножениях)

    Формулу арктангенса на 4 умножениях ещё немножко оптимизировал с помощью алгоритма Ремеза: Ошибка уменьшилась с 4,9 до 4,65 угловой секунды, и…

  • Алгоритм Ремеза в экселе

    Вот и до него руки дошли, причина станет ясна в следующем посте. Изучать чужие библиотеки было лениво (в том же BOOSTе сам чёрт ногу сломит), писать…

  • atan на ЧЕТЫРЁХ умножениях

    Мишка такой человек — ему обязательно надо, чтоб от всего была польза. Когда у него бывают лишние деньги, он идёт в магазин и покупает какую-нибудь…

  • Ай да Пафнутий Львович!

    Решил ещё немного поковыряться со своим арктангенсом. Хотел применить алгоритм Ремеза, но начал с узлов Чебышёва. И для начала со своего "линейного…

  • atan(y/x) на двух умножениях!

    Чего-то никак меня не отпустит эта тема, всё кажется, что есть очень простой и эффективный метод, надо только его найти! Сейчас вот такое…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments