nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Мучаем 5576ХС4Т, часть 'hA - приёмник UART

Часть 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 - уравновешенный четверичный умножитель


Передавать всегда проще - нам сразу же даётся задание, что именно передать - и мы передаём, бездумно, бит за битом. И нет разницы, идёт ли речь о передаче по UART, по МКО (Mil-std 1553), о сохранении в файл или выводе на экран. Приём - всегда сложнее, потому что мы заранее не знаем, что нам ждать, и кроме совершенно корректных посылок или файлов мы можем встретиться с помехами, мусором, с повреждениями и пр., и на всё мы должны отреагировать корректно. Недаром болтливых много, а людей, способных выслушать - единицы, и так ценятся во всей вселенной электромонахи.

Сейчас мы реализуем простейший приёмник UART, который успешно справляется в тепличных условиях отладочной платы, когда передача производится на 5 см, отделяющих cp2102 (USB-UART конвертер) от ПЛИС. В более "шумных" условиях длинных линий, переотражений и наводок он может и подвести, поскольку использует всего одну выборку сигнала на каждый бит. Впрочем, есть смысл использовать именно его до победного конца - проверить хотя бы в "лабораторных условиях" хорошую передачу сигнала. Чересчур умные приёмники иногда скрывают проблемы в кабельных сетях, и всё замечательно работает, а потом от малейшего чиха вдруг прекращает. Поначалу задаёшься вопросом - почему оно не работает? Чуть позже - а как оно умудрялось работать раньше!??


Снова вспомним последовательность бит в потоке UART:



Поначалу у нас на линии сколько угодно долго держится логическая единица - признак того, что передатчик действительно включён (иначе был бы непрерывный ноль), но пока что ничего не передаёт.

Как только произойдёт переход с единицы на ноль - значит, началась передача. Отсчитываем ровно половину периода, от метки idle до метки start на рисунке, и должны снова увидеть ноль - значит, мы уловили не какой-то случайный импульс, наводку, а действительно начало передачи. В противном случае, возвращаемся в режим ожидания.

Затем отсчитываем ещё один целый период, к середине передачи первого бита данных. Что у нас на линии в этот момент - "проталкиваем" в сдвиговый регистр. Повторяем этим манером 8 раз, после чего остаётся проверить, что после 8 бит данных у нас действительно пришёл стоповый бит, то есть снова идёт единица. Если так - мы корректно приняли один байт данных, о чём должны известить соответствующим импульсом. Нет - делаем вид, что ничего и не было, лучше не получить никаких данных, чем получить ошибочные. Как вариант, можно, получив ноль на стоповом бите, выдать ошибку "frame error" (некорректный пакет), а если ещё и принятые данные оказались нулевыми - то впридачу выдавать признак break (обрыв на линии).

Наконец, возращаемся в исходное состояние и ждём прихода нового бита. Что важно заметить - счетчик обнуляется на каждой новой посылке, благодаря чему требования к совпадению скорости передачи не так уж высоки, допустимо отклонение плюс-минус 5%. Если предположить, что и передатчик, и приёмник отклоняются от номинального значения, и может так повезти, что один - в "плюс", а второй - в "минус", то надо иметь точность временнЫх интервалов около 2,5%. Легко с помощью кварца, сложнее с RC-цепочками (если компоненты сидят внутри микроконтроллера, к примеру, и имеют большой разброс и неизвестный температурный коэффициент), там уже нужно придумывать что-то более хитрое.

Покажем, как обычно реализуется простейший приёмник UART, с явно описанным конечным автоматом (взято с https://www.nandland.com/goboard/uart-go-board-project-part2.html, но похожие штуки можно встретить во многих местах):

/////////////////////////////////////////////////////////////////////
// File Downloaded from http://www.nandland.com
/////////////////////////////////////////////////////////////////////
// This file contains the UART Receiver.  This receiver is able to
// receive 8 bits of serial data, one start bit, one stop bit,
// and no parity bit.  When receive is complete o_rx_dv will be
// driven high for one clock cycle.
// 
// Set Parameter CLKS_PER_BIT as follows:
// CLKS_PER_BIT = (Frequency of i_Clock)/(Frequency of UART)
// Example: 25 MHz Clock, 115200 baud UART
// (25000000)/(115200) = 217
 
module UART_RX
  #(parameter CLKS_PER_BIT = 217)
  (
   input        i_Clock,
   input        i_RX_Serial,
   output       o_RX_DV,
   output [7:0] o_RX_Byte
   );
   
  parameter IDLE         = 3'b000;
  parameter RX_START_BIT = 3'b001;
  parameter RX_DATA_BITS = 3'b010;
  parameter RX_STOP_BIT  = 3'b011;
  parameter CLEANUP      = 3'b100;
  
  reg [7:0]     r_Clock_Count = 0;
  reg [2:0]     r_Bit_Index   = 0; //8 bits total
  reg [7:0]     r_RX_Byte     = 0;
  reg           r_RX_DV       = 0;
  reg [2:0]     r_SM_Main     = 0;
  
  
  // Purpose: Control RX state machine
  always @(posedge i_Clock)
  begin
      
    case (r_SM_Main)
      IDLE :
        begin
          r_RX_DV       <= 1'b0;
          r_Clock_Count <= 0;
          r_Bit_Index   <= 0;
          
          if (i_RX_Serial == 1'b0)          // Start bit detected
            r_SM_Main <= RX_START_BIT;
          else
            r_SM_Main <= IDLE;
        end
      
      // Check middle of start bit to make sure it's still low
      RX_START_BIT :
        begin
          if (r_Clock_Count == (CLKS_PER_BIT-1)/2)
          begin
            if (i_RX_Serial == 1'b0)
            begin
              r_Clock_Count <= 0;  // reset counter, found the middle
              r_SM_Main     <= RX_DATA_BITS;
            end
            else
              r_SM_Main <= IDLE;
          end
          else
          begin
            r_Clock_Count <= r_Clock_Count + 1;
            r_SM_Main     <= RX_START_BIT;
          end
        end // case: RX_START_BIT
      
      
      // Wait CLKS_PER_BIT-1 clock cycles to sample serial data
      RX_DATA_BITS :
        begin
          if (r_Clock_Count < CLKS_PER_BIT-1)
          begin
            r_Clock_Count <= r_Clock_Count + 1;
            r_SM_Main     <= RX_DATA_BITS;
          end
          else
          begin
            r_Clock_Count          <= 0;
            r_RX_Byte[r_Bit_Index] <= i_RX_Serial;
            
            // Check if we have received all bits
            if (r_Bit_Index < 7)
            begin
              r_Bit_Index <= r_Bit_Index + 1;
              r_SM_Main   <= RX_DATA_BITS;
            end
            else
            begin
              r_Bit_Index <= 0;
              r_SM_Main   <= RX_STOP_BIT;
            end
          end
        end // case: RX_DATA_BITS
      
      
      // Receive Stop bit.  Stop bit = 1
      RX_STOP_BIT :
        begin
          // Wait CLKS_PER_BIT-1 clock cycles for Stop bit to finish
          if (r_Clock_Count < CLKS_PER_BIT-1)
          begin
            r_Clock_Count <= r_Clock_Count + 1;
     	    r_SM_Main     <= RX_STOP_BIT;
          end
          else
          begin
       	    r_RX_DV       <= 1'b1;
            r_Clock_Count <= 0;
            r_SM_Main     <= CLEANUP;
          end
        end // case: RX_STOP_BIT
      
      
      // Stay here 1 clock
      CLEANUP :
        begin
          r_SM_Main <= IDLE;
          r_RX_DV   <= 1'b0;
        end
      
      
      default :
        r_SM_Main <= IDLE;
      
    endcase
  end    
  
  assign o_RX_DV   = r_RX_DV;
  assign o_RX_Byte = r_RX_Byte;
  
endmodule // UART_RX


Здесь приёмник ведёт себя более "мягко" - стоповый бит игнорируется полностью, просто отсчитываем ещё один период и выдаём ответ.

Синтез данного модуля даёт результат: нужно 69 логических ячеек (LE), честно говоря многовато.

Можно и лучше.

К сожалению, столь простой "коммандоаппарат", какой мы сделали в передатчике UART, здесь не получится. Если там мы ожидали запуска, после чего последовательно проходили по набору состояний, то здесь возможны "исключительные ситуации" в двух местах - на стартовом и на стоповом битах, поэтому логика становится чуточку сложнее.

Также сложнее реализация делителя частоты - он должен отсчитывать иногда пол-периода, а иногда - полный.

Вот наш код на чистом верилоге (без использования модулей, специфичных именно для Altera/Intel/ВЗПП-С):
`include "math.v"

module MyOwnUARTreceiver(input rxd,
			 input clk,
			 output reg [7:0] Q,
			 output HasOutput,
			 output FrameError,
			 output LineBreak);

  parameter CLKfreq = 80_000_000;
  parameter BAUDrate = 512_000; //debug

  localparam DividerBits = `CLOG2(CLKfreq / BAUDrate);
  localparam Limit = CLKfreq / BAUDrate - 1;
  localparam LimitDiv2 = CLKfreq / BAUDrate / 2 - 1;
	
  reg [DividerBits-1 : 0] FreqDivider;

  `define rxIdle  4'b1010
  `define rxStart 4'b0000
  `define rxB1	  4'b0001
  `define rxB2	  4'b0010
  `define rxB3	  4'b0011
  `define rxB4	  4'b0100
  `define rxB5	  4'b0101
  `define rxB6	  4'b0110
  `define rxB7	  4'b0111
  `define rxB8	  4'b1000
  `define rxStop  4'b1001
	
  reg [3:0] State = `rxIdle;
	
  wire isIdle = State[3] & State[1];
	
  wire ce = ((FreqDivider & Limit) == Limit);
	
  wire IsStart = (State == `rxStart);
	
  wire IsStop = State[3] & State[0];
	
  assign HasOutput = IsStop & ce & rxd;
  assign FrameError = IsStop & ce & (~rxd);
  assign LineBreak = FrameError & (Q == 0);
						
  always @(posedge clk) begin
    FreqDivider <= 	(isIdle & ~rxd)? LimitDiv2:
                        (isIdle | ce)?   1'b0:
					 FreqDivider + 1'b1;
	
    State <= 	(isIdle & ~rxd)?	`rxStart:
		ce?
		                 	(IsStart & rxd?	`rxIdle: State + 1'b1):
					State;		
		
    Q <= ce? {rxd, Q[7:1]} : Q;
end
	
endmodule



У нас два входа: clk (тактовая частота) и rxd - вход приёмника. Выходов чуть больше: 8-битный Q, куда попадает принятый байт, HasOutput, где ровно на один такт появляется единичка, признак, что мы приняли этот байт. FrameError - признак "неправильного пакета" и LineBreak - признак обрыва линии.

Давайте разбираться, что эта фигня делает. Первым делом - делитель частоты, образованный регистром FreqDivider и проводом ce (clock enable). Пока мы находимся в режиме ожидания (`rxIdle), а на линии стабильно держится единица, счетчик держится на нуле. Также он обнуляется, когда досчитывает до значения Limit, и соответственно, появляется единичка на проводе ce.

Если мы в режиме ожидания, но сигнал на приёмнике скакнул в ноль, мы присваиваем счетчику значение LimitDiv2, что позволит отсчитать ровно половину периода.

Наконец, в любом другом режиме (не в режиме ожидания), счётчик на каждом такте прибавляет единичку.

Чтобы чуточку упростить компаратор, проверяющий условие FreqDivider == Limit, мы применили хитрость, изложенную в части "счетчики и жаба".

Сдвиговый регистр здесь оказывается даже проще, чем в передатчике. Там нам нужно было придерживать его, пока идёт стартовый бит, чтобы не потерять полезные данные. Здесь же пускай сдвигает и на стартовом бите - ничего страшного, когда вслед за ним пойдут ещё 8 бит, они вытеснят стартовый бит. Останавливать его на стоповом бите мы тоже не стали - именно в момент поступления импульса ce в середине стопового бита, мы выдадим импульс HasOutput (если всё хорошо), и ровно в этот момент внешняя цепь должна "защёлкнуть" значение Q - уже на следующий такт оно будет неверным.

Здесь, описывая работу сдвигового регистра, мы применили фигурные скобки, {rxd, Q[7:1]}. Это означает, что в старший бит кладётся rxd, а в следующие биты, в сторону уменьшения - Q[7:1]. Говоря научным языком, фигурные скобки - это оператор конкатенации. Через запятую можно указывать сколько угодно отдельных выражений, они все будут идти друг за дружкой.

Посмотрим работу нашего конечного автомата. Наш хитрый план нумерации, когда rxStart - это все нули - попытка красиво "вписаться" в режим счетчика, где наиболее приоритетная операция (среди синхронных) - это сброс.

Если мы сидим в режиме ожидания rxIdle, то появление нолика в линии переводит нас в режим rxStart. Далее, все переходы делаются только по импульсу ce, то есть, когда будет отсчитан один период или пол-периода. К состоянию будет прибавляться единичка, то есть из rxStart мы будем переходить последовательно в rxB1, rxB2, ..., rxB8, rxStop и, наконец, своим ходом прибежим в rxIdle. Но если в стартовом бите мы вдруг примем единицу вместо нуля, то отправимся в rxIdle гораздо раньше.

Осталось изучить формирование выходных сигналов. Как видно, все они могут сработать лишь когда мы дойдём до стопового бита, во время импульса ce. HasOutput сработает, если на линии приёмника в этот момент находится единичка, как мы и ожидаем. Если вместо единички оказался нолик, мы выдаём FrameError. Если же при этом в сдвиговом регистре сидит нулевой байт (то есть, мы имеем последовательность из 10 нулей на линии, чего происходить никогда не должно), то для кучи мы выдадим признак LineBreak.

Запускаем синтез этого модуля и получаем: он требует 42 логических ячейки. Изучая сообщения компилятора, обнаруживаем, что он опять налажал и не нашёл в нашей схеме счетчиков - ни одного. Увы, Quartus II 9.0sp2 очень редко опознаёт счётчик с синхронным сбросом и синхронной загрузкой, особенно, если управляющая логика хоть сколько-нибудь сложна. Тем не менее, выигрыш перед реализацией "в лоб" по-прежнему есть, а ведь у нас ещё и появились выходы для "исключительных ситуаций".

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


Первым делом проверяем штатную работу:


Мы традиционно передавали число 42, и именно его мы получили во время единичного импульса HasOutput.

Теперь замедлим передатчик примерно на 1/8, выставив BaudRate = 7000000:



Мы не получаем сигнала HasOutput, вместо него - FrameError, и это совершенно оправданно. Как можно видеть по "осциллограммам", мы успеваем убежать вперёд передатчика на один такт, получая неверные данные. Поскольку последний переданный бит был нулевой, мы смогли заметить эту ошибку.

Наконец, посмотрим реакцию приёмника на разомкнутый вход:


Через каждые 10 периодов выскакивает FrameError и LineBreak.


В следующей части мы наконец-то прошьём приёмник и передатчик в ПЛИС, а также глянем, нельзя ли ужаться ещё сильнее, описав счетчики lpm_counter в явном виде?
Tags: ПЛИС, работа, странные девайсы
Subscribe

Recent Posts from This Journal

  • Нахождение двух самых отдалённых точек

    Пока компьютер долго и упорно мучал симуляцию, я пытался написать на ассемблере алгоритм захвата на ближней дистанции. А сейчас на этом коде можно…

  • Слишком общительный счётчик

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: RomWidth = 8 вместо 7, RamWidth = 9 вместо 8, и ещё EnableByteAccess=1, чтобы…

  • Балансируем конвейер QuatCore

    В пятницу у нас всё замечательно сработало на симуляции, первые 16 миллисекунд полёт нормальный. А вот прошить весь проект на ПЛИС и попробовать "в…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 2 comments