MIPSfpga и UART

    Прошло чуть больше месяца с тех пор, как я портировал open source модуль UART16550 на шину AHB-Lite. Писать об этом на тот момент было несколько не логично, так как еще не была опубликована статья про прерывания MIPSfpga.


    Если вы опытный разработчик, то для вас только одна полезная новость: UART16550 добавлен в состав системы MIPSfpga-plus, дальше можете не читать. А тем, кого интересует разобранный пример использования этого модуля — добро пожаловать под кат.


    image


    Введение


    Предполагается, что читатель:


    • знаком с предметной областью в объеме учебника Харрис-энд-Харрис [L1];
    • имеет доступ к исходным кодам MIPSfpga [L2] и mipsfpga-plus [L3];
    • имеет некоторый опыт программирования микроконтроллеров (любой архитектуры), включая использование UART. Освежить, что из себя представляет UART можно здесь [L4];

    Что такое UART16550


    История появления этой микросхемы неплохо описана в [L5], найти документацию на нее можно в google [L6], для нас важно другое:


    • она получила крайне широкое распространения;
    • работа с ней поддерживается ядром Linux;
    • для нее уже давно существует open source реализация на verilog [L7, L8, L9], но для шины Wishbone;
    • цена портирования этой реализации на используемую в MIPSfpga шину AHB-Lite — около 100 строчек кода [S1]:

    mfp_ahb_lite_uart16550
    module mfp_ahb_lite_uart16550(
        //ABB-Lite side
        input                   HCLK,
        input                   HRESETn,
        input       [ 31 : 0 ]  HADDR,
        input       [  2 : 0 ]  HBURST,
        input                   HMASTLOCK,  // ignored
        input       [  3 : 0 ]  HPROT,      // ignored
        input                   HSEL,
        input       [  2 : 0 ]  HSIZE,
        input       [  1 : 0 ]  HTRANS,
        input       [ 31 : 0 ]  HWDATA,
        input                   HWRITE,
        output  reg [ 31 : 0 ]  HRDATA,
        output                  HREADY,
        output                  HRESP,
        input                   SI_Endian,  // ignored
    
        //UART side
        input                   UART_SRX,   // UART serial input signal
        output                  UART_STX,   // UART serial output signal
        output                  UART_RTS,   // UART MODEM Request To Send
        input                   UART_CTS,   // UART MODEM Clear To Send
        output                  UART_DTR,   // UART MODEM Data Terminal Ready
        input                   UART_DSR,   // UART MODEM Data Set Ready
        input                   UART_RI,    // UART MODEM Ring Indicator
        input                   UART_DCD,   // UART MODEM Data Carrier Detect
    
        //UART internal
        output                  UART_BAUD,  // UART baudrate output
        output                  UART_INT    // UART interrupt
    );
    
        parameter   S_INIT      = 0,
                    S_IDLE      = 1,
                    S_READ      = 2,
                    S_WRITE     = 3;
    
        reg  [ 1:0 ]    State, Next;
    
        assign      HRESP  = 1'b0;
        assign      HREADY = (State ==  S_IDLE);
    
        always @ (posedge HCLK) begin
            if (~HRESETn)
                State <= S_INIT;
            else
                State <= Next;
        end
    
        reg  [ 2:0 ]    ADDR_old;
        wire [ 2:0 ]    ADDR = HADDR [ 4:2 ];
        wire [ 7:0 ]    ReadData;
    
        parameter       HTRANS_IDLE       = 2'b0;
        wire            NeedAction = HTRANS != HTRANS_IDLE && HSEL;
    
        always @ (*) begin
            //State change decision
            case(State)
                default     :   Next = S_IDLE;
                S_IDLE      :   Next = ~NeedAction  ? S_IDLE : (
                                        HWRITE      ? S_WRITE : S_READ );
            endcase
        end
    
        always @ (posedge HCLK) begin
            case(State)
                S_INIT      :   ;
                S_IDLE      :   if(HSEL) ADDR_old <= ADDR;
                S_READ      :   HRDATA <= { 24'b0, ReadData};
                S_WRITE     :   ;
            endcase
        end
    
        wire [ 7:0 ]    WriteData   = HWDATA [ 7:0 ];
        wire [ 2:0 ]    ActionAddr;
        wire            WriteAction;
        wire            ReadAction;
        reg  [ 10:0 ]   conf;
    
        assign { ReadAction, WriteAction, ActionAddr } = conf;
    
        always @ (*) begin
            //io
            case(State)
                default     :   conf = { 2'b00, 8'b0     };
                S_READ      :   conf = { 2'b10, ADDR     };
                S_WRITE     :   conf = { 2'b01, ADDR_old };
            endcase
        end
    
        // Registers
        uart_regs   regs(
            .clk            (   HCLK            ),
            .wb_rst_i       (   ~HRESETn        ),
            .wb_addr_i      (   ActionAddr      ),
            .wb_dat_i       (   WriteData       ),
            .wb_dat_o       (   ReadData        ),
            .wb_we_i        (   WriteAction     ),
            .wb_re_i        (   ReadAction      ),
            .modem_inputs   (   { UART_CTS, UART_DSR, UART_RI, UART_DCD }   ),
            .stx_pad_o      (   UART_STX        ),
            .srx_pad_i      (   UART_SRX        ),
            .rts_pad_o      (   UART_RTS        ),
            .dtr_pad_o      (   UART_DTR        ),
            .int_o          (   UART_INT        ),
            .baud_o         (   UART_BAUD       )
        );
    
    endmodule

    Основные особенности реализации


    • в качестве базового решения взят проект [L8], который, в свою очередь, основан на [L7, L9];
    • по сравнению с базовым решением из проекта исключен код, связанный с шиной Wishbone. Он заменен на соответствующий интерфейсный модуль для AHB-Lite (mfp_ahb_lite_uart16550) [S1];
    • весь заимствованный из базового проекта код размещен в каталоге uart16550 [S2];
    • модуль mfp_ahb_lite_uart16550 включен в состав системы на постоянной основе, опция MFP_USE_DUPLEX_UART [S3] в файле mfp_ahb_lite_matrix_config.vh определяет доступность сигналов UART_SRX и UART_STX;
    • для уже имеющихся сигналов UART_RX и UART_TX предусмотрен следующий порядок использования:
      UART_RX — вне зависимости от режима используется только для загрузки прошивки в память системы, как это и было ранее (модуль mfp_uart_receiver, к UART16550 не относится). Если опция MFP_USE_DUPLEX_UART не активна, то UART_TX используется для передачи данных от UART16550 наружу, прием в этом случае не доступен. Если опция MFP_USE_DUPLEX_UART активна, то UART_TX — не используются, для передачи данных из/в mfp_ahb_lite_uart16550 служат UART_SRX и UART_STX [S4];
    • вывод интерфейса управления модемом на уровень top модуля не производился. Соответствующие линии доступны в интерфейсе модуля ahb_lite_uart16550 и могут быть использованы при необходимости (UART_RTS, UART_CTS, UART_DTR, UART_DSR, UART_RI, UART_DCD) [S5];
    • сигнала прерывания (UART_INT) подключен к входу hw3 (сигнал SI_Int[3]) для режимов обратной совместимости и векторного. И к eic5 (сигнал EIC_input[5]) для контроллера внешних прерываний [S6];
    • при запуске в режиме симуляции линия UART_STX замкнута на UART_SRX [S7];
    • работоспособность полученного решения в режиме симуляции проверена с помощью Modelsim;
    • работоспособность на железе проверена на плате Terasic DE10-Lite [L10];
    • в качестве примера работы с UART16550 написаны две программы: 05_uart [S8] и 08_uart_irq [S9]: после сброса выполняется настройка uart (8n1, 115200), отправка приветствия после чего код каждого принятого символа отображается на LED и 7-сегментных индикаторах. При запуске на плате каждый принимаемый по uart символ отправляется обратно;
    • отладка разработанного модуля доступна "в отрыве" от кода mipsfpga+ в рамках проекта [L11];
    • для программного конфигурирования модуля следует использовать документацию базового проекта, [D1]. Рекомендую бегло ознакомиться с ней, чтобы лучше понимать приводимый ниже пример;
    • работа с шиной AHB-Lite детально описана в [D2];

    Пример


    Порядок запуска


    • проверить, что в файле mfp_ahb_lite_matrix_config.vh установлена следующая настройка [S3]:


      `define MFP_USE_DUPLEX_UART

    • перейти в каталог с программой: mipsfpga-plus/programs/05_uart [S8], либо 08_uart_irq [S9];
    • в файле main.c установить [S10]:


      #define RUNTYPE    SIMULATION

    • выполнить сборку программы и ее запуск в симуляторе:


      02_compile_and_link.bat
      05_generate_verilog_readmemh_file.bat
      06_simulate_with_modelsim.bat

    • после завершения работы скрипта симуляции нажать "нет", чтобы предотвратить закрытие симулятора;
    • то, как работает этот код "на железе", видно на заглавной gif-ке.

    Описание программы и конфигурации системы


    • так как пример 08_uart_irq предусматривает помимо настройки самого UART16550 еще и использование генерируемых им прерываний, то ниже рассматривается именно эта конфигурация;
    • директивы, обеспечивающие работу с регистрами контроллера вынесены в заголовочный файл uart16550.h [S11];
    • при запуске производится настройка UART16550 [S12]:


      void uartInit(uint16_t divisor)
      {
      // 8n1 uart mode
      MFP_UART_LCR = MFP_UART_LCR_8N1;
      // Divisor Latches access enable
      MFP_UART_LCR |= MFP_UART_LCR_LATCH;
      // Divisor LSB
      MFP_UART_DLL = divisor & 0xFF;
      // Divisor MSB
      MFP_UART_DLH = (divisor >> 8) & 0xff;
      // Divisor Latches access disable
      MFP_UART_LCR &= ~MFP_UART_LCR_LATCH;
      //enable Received Data available interrupt
      MFP_UART_IER = MFP_UART_IER_RDA;
      //set 4 byte Receiver FIFO Interrupt trigger level
      MFP_UART_FCR = MFP_UART_FCR_ITL4;
      }

    • настройка прерываний [S13] (детально разобрана в [L12]):


      void mipsInterruptInit(void)
      {
      // Status.BEV  0 - vector interrupt mode
      mips32_bicsr (SR_BEV);
      // Cause.IV,   1 - special int vector (0x200)
      // where 0x200 - base for others interrupts;
      mips32_biscr (CR_IV);
      // get IntCtl reg value
      uint32_t intCtl = mips32_getintctl();
      // set interrupt table vector spacing (0x20 in our case)
      // see exceptions.S for details
      mips32_setintctl(intCtl | INTCTL_VS_32);
      // interrupt enable, HW3 unmasked
      mips32_bissr (SR_IE | SR_HINT3); 
      }

    • обработка прерывания предполагает проверку того, что оно вызвано именно наличием данных во входящем FIFO [S14]:


      // uart interrupt handler
      void __attribute__ ((interrupt, keep_interrupts_masked)) __mips_isr_hw3 ()
      {
      // Receiver Data available interrupt handler
      if(MFP_UART_IIR & MFP_UART_IIR_RDA)
          uartReceive();
      }

    • после чего следует их чтение (до тех пор, пока FIFO не опустеет) и вывод [S15]:


      void uartReceive(void)
      {
      // is there something in receiver fifo?
      while (MFP_UART_LSR & MFP_UART_LSR_DR)
      {
          // data receive
          uint8_t data = MFP_UART_RXR;
          receivedDataOutput(data);
      
          #if   RUNTYPE == HARDWARE
          uartTransmit(data);
          #endif
      }
      }

    • в случае, если работа идет на "железе", то все полученные данные отправляются обратно [S16]:
      void uartTransmit(uint8_t data)
      {
      // waiting for transmitter fifo empty
      while (!(MFP_UART_LSR & MFP_UART_LSR_TFE)); 
      // data transmit
      MFP_UART_TXR = data;
      }
    • ниже представлен результат работы программы. Так как при настройке модуля мы выставили режим срабатывания прерывания после приема 4х символов, то передача на время прерывается для приема. Оставшиеся символы получены нами после еще одного такого же прерывания, но которое уже было вызвано не наличием 4х символов в очереди, а не пустой очередью и таймаутом (контроллер понял, что больше символов не будет и сообщил, что очередь не пустая);


      image


    • для программы 05_uart прием выполняется уже после завершения передачи, все это время принятые данные ждут в FIFO приемника:


      image



    Благодарности


    Автор выражает благодарность коллективу переводчиков учебника Дэвида Харриса и Сары Харрис «Цифровая схемотехника и архитектура компьютера», компании Imagination Technologies за академическую лицензию на современное процессорное ядро и персонально Юрию Панчулу YuriPanchul за его работу по популяризации MIPSfpga.


    Ссылки


    [L1] — Цифровая схемотехника и архитектура компьютера;
    [L2] — Как начать работать с MIPSfpga;
    [L3] — Проект MIPSfpga-plus на github;
    [L4] — Wikipedia: UART;
    [L5] — Wikipedia: UART16550;
    [L6] — Google: UART16550;
    [L7] — Проект freecores/uart16550;
    [L8] — Проект olofk/uart16550;
    [L9] — Проект opencores/UART 16550 core;
    [L10] -FPGA плата Terasic DE10-Lite;
    [L11] — Проект ahb_lite_uart16550;
    [L12] — MIPSfpga и прерывания;


    Документация


    [D1] — UART IP Core Specification;
    [D2] — MIPS32 microAptiv UP Processor Core AHB-Lite Interface;


    Изображения и таблицы


    [P1] — Работа примера на отладочной плате;
    [P2] — Работа в режиме обработки прерывания UART;
    [P3] — Работа путем периодического опроса регистра;


    Ссылки на исходный код


    [S1] — Модуль mfp_ahb_lite_uart16550;
    [S2] — Каталог mipsfpga-plus/uart16550;
    [S3] — Опция MFP_USE_DUPLEX_UART;
    [S4] — Сигналы UART_SRX и UART_STX;
    [S5] — Интерфейс управления модемом;
    [S6] — Подключение сигнала прерывания;
    [S7] — Сигналы UART в режиме симуляции;
    [S8] — Пример использования '05_uart';
    [S9] — Пример использования '08_uart_irq';
    [S10] — Настройка режима работы программы-примера;
    [S11] — Заголовочный файл uart16550.h;
    [S12] — Программная настройка UART16550;
    [S13] — Настройка прерываний;
    [S14] — Обработка прерывания UART;
    [S15] — Чтение полученных данных UART;
    [S16] — Обратная отправка принятых данных;

    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое