21 通用异步收发传输器(UART)

21.1 功能概述

UART全称Universal Asynchronous Receiver/Transmitter,即通用异步收发传输器件。 UART对接收的数据执行串并转换,对发送的数据执行并串转换。

特性:

  • 支持硬件流控
  • 可编程波特率发生器,最高波特率可达7000000bps
  • 独立的发送和接收 FIFO
  • 单个组合中断,包括接收(包括超时)、传输、调制解调器状态和错误状态中断,每个中断可屏蔽
  • 支持 DMA 方式

21.2 使用说明

21.2.1 设置波特率

使用 apUART_BaudRateSet 设置对应UART设备的波特率。

void apUART_BaudRateSet(
  UART_TypeDef* pBase,      //UART参数结构体&设备地址
  uint32_t ClockFrequency,  //时钟信号的频率
  uint32_t BaudRate         //波特率
  );

21.2.2 获取波特率

使用 apUART_BaudRateGet 获取对应UART设备的波特率。

uint32_t apUART_BaudRateGet (
  UART_TypeDef* pBase, 
  uint32_t ClockFrequency
  );

21.2.3 UART初始化

在使用UART之前,需要先通过 apUART_Initialize 对UART进行初始化。

void apUART_Initialize(
  UART_TypeDef* pBase, 
  UART_sStateStruct* UARTx,  //uart状态结构体
  uint32_t IntMask    //中断掩码
  );

初始化 UART 之前需要初始化如下所示的 UART 状态结构体:

typedef struct UART_xStateStruct
{
    // Line Control Register, UARTLCR_H
  UART_eWLEN      word_length;   // WLEN
  UART_ePARITY    parity;        // PEN, EPS, SPS
  uint8_t         fifo_enable;   // FEN
  uint8_t         two_stop_bits; // STP2
  // Control Register, UARTCR
  uint8_t         receive_en;        // RXE
  uint8_t         transmit_en;       // TXE
  uint8_t         UART_en;           // UARTEN
  uint8_t         cts_en;            //CTSEN
  uint8_t         rts_en;            //RTSEN
  // Interrupt FIFO Level Select Register, UARTIFLS
  uint8_t         rxfifo_waterlevel; // RXIFLSEL
  uint8_t         txfifo_waterlevel; // TXIFLSEL
  //UART_eFIFO_WATERLEVEL    rxfifo_waterlevel; // RXIFLSEL
  //UART_eFIFO_WATERLEVEL    txfifo_watchlevel; // TXIFLSEL

  // UART Clock Frequency
  uint32_t        ClockFrequency;
  uint32_t        BaudRate;

} UART_sStateStruct;

常用的中断掩码如下所示, IntMask 是它们的组合。

#define UART_INTBIT_RECEIVE            0x10  //receive interrupt
#define UART_INTBIT_TRANSMIT           0x20  //transmit interrupt

例如,配置并初始化串口,开启接收中断和发送中断,设置串口波特率为115200:

  • 首先,创建UART配置函数 config_uart ,在函数内初始化UART状态结构体,并配置必要的状态参数, 然后调用 apUART_Initialize 初始化串口。

      void config_uart(uint32_t freq, uint32_t baud)
      {
          UART_sStateStruct config;
    
          config.word_length       = UART_WLEN_8_BITS;
          config.parity            = UART_PARITY_NOT_CHECK;
          config.fifo_enable       = 1;
          config.two_stop_bits     = 0;
          config.receive_en        = 1;
          config.transmit_en       = 1;
          config.UART_en           = 1;
          config.cts_en            = 0;
          config.rts_en            = 0;
          config.rxfifo_waterlevel = 1;
          config.txfifo_waterlevel = 1;
          config.ClockFrequency    = freq;
          config.BaudRate          = baud;
    
          apUART_Initialize(PRINT_PORT, &config, UART_INTBIT_RECEIVE | UART_INTBIT_TRANSMIT);
      }

    使用时只需要如下所示调用 config_uart 函数即可。

      config_uart(OSC_CLK_FREQ, 115200); 

21.2.4 UART轮询模式

在轮询模式下,CPU通过检查线路状态寄存器中的位来检测事件:

  • 使用 apUART_Check_Rece_ERROR 查询接收产生的错误字。

      uint8_t apUART_Check_Rece_ERROR(
        UART_TypeDef* pBase
        );
  • apUART_Check_RXFIFO_EMPTY查询Rx FIFO是否为空。

      uint8_t apUART_Check_RXFIFO_EMPTY(
        UART_TypeDef* pBase
        );
  • 使用 apUART_Check_RXFIFO_FULL查询Rx FIFO是否已满。

      uint8_t apUART_Check_RXFIFO_FULL(
        UART_TypeDef* pBase
        );
  • 使用 apUART_Check_TXFIFO_EMPTY 查询Tx FIFO是否为空。

      uint8_t apUART_Check_TXFIFO_EMPTY(
        UART_TypeDef* pBase
        );
  • 使用 apUART_Check_TXFIFO_FULL 查询Tx FIFO是否已满。

      uint8_t apUART_Check_TXFIFO_FULL(
        UART_TypeDef* pBase
        ); 

21.2.5 UART中断使能/禁用

apUART_Enable_TRANSMIT_INT 使能发送中断,用 apUART_Disable_TRANSMIT_INT 禁用发送中断; 用 apUART_Enable_RECEIVE_INT 使能接收中断,用 apUART_Disable_RECEIVE_INT 禁用接收中断。

中断默认是禁用的,使能中断既能用上述 apUART_Enable_TRANSMIT_INTapUART_Enable_RECEIVE_INT 的方式,也可以通过 apUART_Initialize 初始化串口是设置参数 IntMask 的值使能相应的中断,详情请参考 UART初始化

21.2.6 处理中断状态

apUART_Get_ITStatus 获取某个UART上的中断触发状态,返回非 0 值表示该 UART 上产生了中断请求;用 apUART_Get_all_raw_int_stat 一次性获取所有 UART 的中断触发状态, 第 \(n\) 比特(第 0 比特为最低比特)对应 UART \(n\) 上的中断触发状态。

UART产生中断后,需要消除中断状态方可再次触发。用 apUART_Clr_RECEIVE_INT 消除某个 UART上接收中断的状态, 用 apUART_Clr_TX_INT 消除某个 UART上发送中断的状态。用 apUART_Clr_NonRx_INT 消除某个 UART上除接收以外的中断状态。

21.2.7 发送数据

使用 UART_SendData 发送数据。

void UART_SendData(
  UART_TypeDef* pBase, 
  uint8_t Data
  );

21.2.8 接收数据

使用 UART_ReceData 接收数据。

uint8_t UART_ReceData(
  UART_TypeDef* pBase
  );

21.2.9 DMA传输模式使能

使用 UART_DmaEnable使能UART的DMA工作模式,可以使用DMA完成对串口数据的收发,从而不占用 CPU 的资源。

void UART_DmaEnable(
  UART_TypeDef *pBase, 
  uint8_t tx_enable,   //发送使能(1)/禁用(0)
  uint8_t rx_enable,   //接收使能(1)/禁用(0)
  uint8_t dma_on_err
  );

21.3 示例代码

21.3.1 UART接收变长字节数据

  1. UART+FIFO方式
char dst[256];
int index = 0;
void setup_peripheral_uart()
{
    APB_UART0->FifoSelect = (0 << bsUART_TRANS_INT_LEVEL ) |
                    (0X10 << bsUART_RECV_INT_LEVEL  ) ;
    APB_UART0->IntMask = (1 << bsUART_RECEIVE_INTENAB) | (0 << bsUART_TRANSMIT_INTENAB) |
                        (1 << bsUART_TIMEOUT_INTENAB);
    APB_UART0->Control = 1 << bsUART_RECEIVE_ENABLE |
                1 << bsUART_TRANSMIT_ENABLE |
                1 << bsUART_ENABLE          |
                0 << bsUART_CTS_ENA         |
                0 << bsUART_RTS_ENA;
}

uint32_t uart_isr(void *user_data)
{
    uint32_t status;

    while(1)
    {
        status = apUART_Get_all_raw_int_stat(APB_UART0);

        if (status == 0)
            break;

        APB_UART0->IntClear = status;

        // rx int
        if (status & (1 << bsUART_RECEIVE_INTENAB))
        {
            while (apUART_Check_RXFIFO_EMPTY(APB_UART0) != 1)
            {
                char c = APB_UART0->DataRead;
                dst[index] = c;
                index++;
            }
        }

        // rx timeout_int
        if (status & (1 << bsUART_TIMEOUT_INTENAB))
        {
            while (apUART_Check_RXFIFO_EMPTY(APB_UART0) != 1)
            {
                char c = APB_UART0->DataRead;
                dst[index] = c;
                index++;
            }
        }
        printf("\ndst = %s\n",dst);
    }
    return 0;
}

void uart_peripherals_read_data()
{
    //注册uart0中断
    platform_set_irq_callback(PLATFORM_CB_IRQ_UART0, uart_isr, NULL);
    setup_peripheral_uart();
}
  1. UART+FIFO+DMA方式
#define bsDMA_INT_C_MASK         1    
#define bsDMA_DST_REQ_SEL        4
#define bsDMA_SRC_REQ_SEL        8
#define bsDMA_DST_ADDR_CTRL      12
#define bsDMA_SRC_ADDR_CTRL      14
#define bsDMA_DST_MODE           16
#define bsDMA_SRC_MODE           17
#define bsDMA_DST_WIDTH          18
#define bsDMA_SRC_WIDTH          21
#define bsDMA_SRC_BURST_SIZE     24

#define DMA_RX_CHANNEL_ID  1
#define DMA_TX_CHANNEL_ID  0

DMA_Descriptor test __attribute__((aligned (8)));
char dst[256];
char src[] = "Finished to receive a frame!\n";

void setup_peripheral_dma()
{
    printf("setup_peripheral_dma\n");
    SYSCTRL_ClearClkGate(SYSCTRL_ITEM_APB_DMA); 

    //配置DMA接收
    APB_DMA->Channels[1].Descriptor.Ctrl = ((uint32_t)0x0 << bsDMA_INT_C_MASK)
                                            | ((uint32_t)0x0 <<bsDMA_DST_REQ_SEL)
                                            | ((uint32_t)0x0 << bsDMA_SRC_REQ_SEL)
                                            | ((uint32_t)0x0 << bsDMA_DST_ADDR_CTRL)
                                            | ((uint32_t)0x2 << bsDMA_SRC_ADDR_CTRL)   //DMA_ADDRESS_FIXED
                                            | ((uint32_t)0x0 << bsDMA_DST_MODE)
                                            | ((uint32_t)0x1 << bsDMA_SRC_MODE)     //
                                            | ((uint32_t)0x0 << bsDMA_DST_WIDTH)
                                            | ((uint32_t)0x0 << bsDMA_SRC_WIDTH)
                                            | ((uint32_t)0x2 << bsDMA_SRC_BURST_SIZE); //4 transefers

    APB_DMA->Channels[1].Descriptor.SrcAddr = (uint32_t)&APB_UART0->DataRead;
    APB_DMA->Channels[1].Descriptor.DstAddr = (uint32_t)dst;
    APB_DMA->Channels[1].Descriptor.TranSize = 48;

    DMA_EnableChannel(1, &APB_DMA->Channels[1].Descriptor);
}

//添加UART通过DMA发送的配置
void UART_trigger_DmaSend(void)
{ 
    DMA_PrepareMem2Peripheral(  &test,
                                SYSCTRL_DMA_UART0_TX,
                                src, strlen(src),
                                DMA_ADDRESS_INC, 0);
    DMA_EnableChannel(DMA_TX_CHANNEL_ID, &test);
}

void setup_peripheral_uart()
{
    APB_UART0->FifoSelect = (0 << bsUART_TRANS_INT_LEVEL ) |
                    (0X7 << bsUART_RECV_INT_LEVEL  ) ;
    APB_UART0->IntMask = (0 << bsUART_RECEIVE_INTENAB) | (0 << bsUART_TRANSMIT_INTENAB) |
                        (1 << bsUART_TIMEOUT_INTENAB);
    APB_UART0->Control = 1 << bsUART_RECEIVE_ENABLE |
                1 << bsUART_TRANSMIT_ENABLE |
                1 << bsUART_ENABLE          |
                0 << bsUART_CTS_ENA         |
                0 << bsUART_RTS_ENA;
}

uint32_t uart_isr(void *user_data)
{
    uint32_t status;
    printf("@%x #%x #%x\n",APB_UART0->IntMask,APB_UART0->IntRaw,APB_UART0->Interrupt);

    while(1)
    {
        status = apUART_Get_all_raw_int_stat(APB_UART0);

        if (status == 0)
            break;

        APB_UART0->IntClear = status;

        // rx timeout_int
        if (status & (1 << bsUART_TIMEOUT_INTENAB))
        {
            while (apUART_Check_RXFIFO_EMPTY(APB_UART0) != 1)
            {
                char c = APB_UART0->DataRead;
                int index = APB_DMA->Channels[1].Descriptor.DstAddr - (uint32_t)dst;
                dst[index] = c;
                if (index == 255) 
                {
                    APB_DMA->Channels[1].Descriptor.DstAddr = (uint32_t)dst;
                    UART_trigger_DmaSend();
                }
                else
                    APB_DMA->Channels[1].Descriptor.DstAddr++;

                APB_DMA->Channels[1].Descriptor.TranSize = 48;
            }
            printf("\nlen=%d, dst = %s\n",APB_DMA->Channels[1].Descriptor.TranSize,dst);
        }
    }
    return 0;
}

void uart_peripherals_read_data()
{
    //注册uart0中断
    platform_set_irq_callback(PLATFORM_CB_IRQ_UART0, uart_isr, NULL);
    setup_peripheral_uart();
    setup_peripheral_dma();
}