nRF51822 UART学习

nRF51822 UART引脚配置

UART引脚配置

和STM32不同,nRF51822没有固定的串口引脚RX和TX,与UART相关的信号RXD、CTS(Clear To Send)、RTS(Request To Send)和TXD可根据PSELRXD、PSELCTS、PSELRTS和PSELTXD寄存器的独立配置映射到相应的物理引脚。
nRF51822中与UART相关的寄存器如下图所示。


可见,为了使用nRF51822的UART,首先必须配置相应的引脚为UART的信号引脚,所幸的是,Nordic Semiconductor官方的SDK中已经给出了配置管脚映射的函数:simple_uart_config();该函数的源程序如下所示。
void simple_uart_config(  uint8_t rts_pin_number,
                          uint8_t txd_pin_number,
                          uint8_t cts_pin_number,
                          uint8_t rxd_pin_number,
                          bool    hwfc)
{
/** @snippet [Configure UART RX and TX pin] */
  nrf_gpio_cfg_output(txd_pin_number);
  nrf_gpio_cfg_input(rxd_pin_number, NRF_GPIO_PIN_NOPULL);  

  NRF_UART0->PSELTXD = txd_pin_number;
  NRF_UART0->PSELRXD = rxd_pin_number;
/** @snippet [Configure UART RX and TX pin] */
  if (hwfc)
  {
    nrf_gpio_cfg_output(rts_pin_number);
    nrf_gpio_cfg_input(cts_pin_number, NRF_GPIO_PIN_NOPULL);
    NRF_UART0->PSELCTS = cts_pin_number;
    NRF_UART0->PSELRTS = rts_pin_number;
    NRF_UART0->CONFIG  = (UART_CONFIG_HWFC_Enabled << UART_CONFIG_HWFC_Pos);
  }

  NRF_UART0->BAUDRATE         = (UART_BAUDRATE_BAUDRATE_Baud38400 << UART_BAUDRATE_BAUDRATE_Pos);
  NRF_UART0->ENABLE           = (UART_ENABLE_ENABLE_Enabled << UART_ENABLE_ENABLE_Pos);
  NRF_UART0->TASKS_STARTTX    = 1;
  NRF_UART0->TASKS_STARTRX    = 1;
  NRF_UART0->EVENTS_RXDRDY    = 0;
}
下面对这段程序进行说明,程序的参数为RTS(Request To Send,请求发送)引脚号、TXD(数据发送信号线)引脚号、CTS(Clear To Send,发送允许)引脚号和RXD(数据接收信号线)引脚号。
UART引脚 名称 作用 方向 开发板中对应的引脚号
RTS Request To Send nRF51822向外部发出的请求发送信号,外部设备接到此请求信号后,开始发送数据,nRF51822通过RXD信号线接收数据(低电平有效) 输出  
TXD     输出 P0.09
CTS Clear To Send 外部向nRF51822发送的发送允许信号,nRF51822接收到此信号后,开始发送数据,nRF51822通过TXD信号线开始发送数据(低电平有效) 输入  
RXD     输入 P0.11

RTS (Require ToSend,发送请求)输出信号,用于指示本设备准备好可接收数据,低电平有效,低电平说明本设备可以接收数据。

CTS (Clear ToSend,发送允许)输入信号,用于判断是否可以向对方发送数据,低电平有效,低电平说明本设备可以向对方发送数据。

此处有人将CTS翻译为发送允许,我感觉的确比翻译为清除发送好。因为CTS是对方的RTS控制己方的CTS是否允许发送的功能。

源程序中在进行引脚映射的时候,将TXD引脚设置为输出模式,将RXD引脚设置为无上拉的输入模式。然后将对应的引脚号写入对应的PSELTXD和PSELRXD寄存器。

源程序中参数hwfc表示硬件流控制(Hardware Flow Control),当为真时则使能硬件流控制。当使能硬件流控制时,和上面一样,先将RTS和CTS引脚设置为对应的输入输出模式,然后再将对应的引脚号写入对应的寄存器。

波特率设置

通过写波特率寄存器BAUDRATE来设置对应的波特率。由程序可知,通过将UART_BAUDRATE_BAUDRATE_Baud38400左移UART_BAUDRATE_BAUDRATE_Pos位然后写入BAUDRATE寄存器来实现波特率的设置。在好多情况下,nRF51822都是通过这样的方式来实现寄存器的写入,而且一般情况下寄存器都按照这样的规则,显示NRF_XX(XX表示某一设备),然后用箭头操作符指向相应的寄存器,当为任务寄存器时,为TASKS_XX(XX表示某一任务寄存器);当为事件寄存器时,为EVENTS_XX(XX表示某一事件寄存器)。
/* Register: UART_BAUDRATE */
/* Description: UART Baudrate. */

/* Bits 31..0 : UART baudrate. */
#define UART_BAUDRATE_BAUDRATE_Pos (0UL) /*!< Position of BAUDRATE field. */
#define UART_BAUDRATE_BAUDRATE_Msk (0xFFFFFFFFUL << UART_BAUDRATE_BAUDRATE_Pos) /*!< Bit mask of BAUDRATE field. */
#define UART_BAUDRATE_BAUDRATE_Baud1200 (0x0004F000UL) /*!< 1200 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud2400 (0x0009D000UL) /*!< 2400 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud4800 (0x0013B000UL) /*!< 4800 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud9600 (0x00275000UL) /*!< 9600 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud14400 (0x003B0000UL) /*!< 14400 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud19200 (0x004EA000UL) /*!< 19200 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud28800 (0x0075F000UL) /*!< 28800 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud38400 (0x009D5000UL) /*!< 38400 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud57600 (0x00EBF000UL) /*!< 57600 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud76800 (0x013A9000UL) /*!< 76800 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud115200 (0x01D7E000UL) /*!< 115200 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud230400 (0x03AFB000UL) /*!< 230400 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud250000 (0x04000000UL) /*!< 250000 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud460800 (0x075F7000UL) /*!< 460800 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud921600 (0x0EBEDFA4UL) /*!< 921600 baud. */
#define UART_BAUDRATE_BAUDRATE_Baud1M (0x10000000UL) /*!< 1M baud. */
在好多情况下,nRF51822都是通过这样的方式来实现寄存器的写入,而且一般情况下寄存器都按照这样的规则,显示NRF_XX(XX表示某一设备),然后用箭头操作符指向相应的寄存器,当为任务寄存器时,为TASKS_XX(XX表示某一任务寄存器);当为事件寄存器时,为EVENTS_XX(XX表示某一事件寄存器)。其实在关于nRF51822外设的declaration中就可以看到nRF51822有哪些外设资源,例如有多少个TIMER,多少个UART。现将nRF51 SDK中的头文件nrf51.h中关于外设基地址的宏定义复制如下,可以看到有TIMER0、TIMER1、TIEMR2三个基地址,则就知到nRF51822有三个定时/计数器。
/* ================================================================================ */
/* ================             Peripheral declaration             ================ */
/* ================================================================================ */

#define NRF_POWER                       ((NRF_POWER_Type          *) NRF_POWER_BASE)
#define NRF_CLOCK                       ((NRF_CLOCK_Type          *) NRF_CLOCK_BASE)
#define NRF_MPU                         ((NRF_MPU_Type            *) NRF_MPU_BASE)
#define NRF_PU                          ((NRF_PU_Type             *) NRF_PU_BASE)
#define NRF_AMLI                        ((NRF_AMLI_Type           *) NRF_AMLI_BASE)
#define NRF_RADIO                       ((NRF_RADIO_Type          *) NRF_RADIO_BASE)
#define NRF_UART0                       ((NRF_UART_Type           *) NRF_UART0_BASE)
#define NRF_SPI0                        ((NRF_SPI_Type            *) NRF_SPI0_BASE)
#define NRF_TWI0                        ((NRF_TWI_Type            *) NRF_TWI0_BASE)
#define NRF_SPI1                        ((NRF_SPI_Type            *) NRF_SPI1_BASE)
#define NRF_TWI1                        ((NRF_TWI_Type            *) NRF_TWI1_BASE)
#define NRF_SPIS1                       ((NRF_SPIS_Type           *) NRF_SPIS1_BASE)
#define NRF_GPIOTE                      ((NRF_GPIOTE_Type         *) NRF_GPIOTE_BASE)
#define NRF_ADC                         ((NRF_ADC_Type            *) NRF_ADC_BASE)
#define NRF_TIMER0                      ((NRF_TIMER_Type          *) NRF_TIMER0_BASE)
#define NRF_TIMER1                      ((NRF_TIMER_Type          *) NRF_TIMER1_BASE)
#define NRF_TIMER2                      ((NRF_TIMER_Type          *) NRF_TIMER2_BASE)
#define NRF_RTC0                        ((NRF_RTC_Type            *) NRF_RTC0_BASE)
#define NRF_TEMP                        ((NRF_TEMP_Type           *) NRF_TEMP_BASE)
#define NRF_RNG                         ((NRF_RNG_Type            *) NRF_RNG_BASE)
#define NRF_ECB                         ((NRF_ECB_Type            *) NRF_ECB_BASE)
#define NRF_AAR                         ((NRF_AAR_Type            *) NRF_AAR_BASE)
#define NRF_CCM                         ((NRF_CCM_Type            *) NRF_CCM_BASE)
#define NRF_WDT                         ((NRF_WDT_Type            *) NRF_WDT_BASE)
#define NRF_RTC1                        ((NRF_RTC_Type            *) NRF_RTC1_BASE)
#define NRF_QDEC                        ((NRF_QDEC_Type           *) NRF_QDEC_BASE)
#define NRF_LPCOMP                      ((NRF_LPCOMP_Type         *) NRF_LPCOMP_BASE)
#define NRF_COMP                        ((NRF_COMP_Type           *) NRF_COMP_BASE)
#define NRF_SWI                         ((NRF_SWI_Type            *) NRF_SWI_BASE)
#define NRF_NVMC                        ((NRF_NVMC_Type           *) NRF_NVMC_BASE)
#define NRF_PPI                         ((NRF_PPI_Type            *) NRF_PPI_BASE)
#define NRF_FICR                        ((NRF_FICR_Type           *) NRF_FICR_BASE)
#define NRF_UICR                        ((NRF_UICR_Type           *) NRF_UICR_BASE)
#define NRF_GPIO                        ((NRF_GPIO_Type           *) NRF_GPIO_BASE)

UART使能

与上边波特率设置类似,写使能寄存器ENABLE也是将UART_ENABLE_ENABLE_Enabled左移UART_ENABLE_ENABLE_Pos位来实现。

开启串口发送接收

当触发STARTTX任务时,UART传输时序启动。通过写TXD寄存器可以实现字节的发送。当一个字节成功发送的时候,UART会产生一个TXDRDY事件,于是新的字节也可以写到TXD寄存器。通过触发STOPTX任务,UART传输时序将会关闭。
当触发STARTRX任务时,UART接收时序启动。接收到的字节会被移动到CPU能够提取的RXD寄存器中。RXD寄存器时双缓冲的,当第一个字节被CPU从RXD寄存器中提取走的同时第二个字节可以被接收。每当一个新字节被移入RXD寄存器时UART会产生一个RXDRDY事件。当第一个字节被从RXD寄存器中提取走的时候,第二个字节会被从RXD-1移到RXD寄存器。通过触发STOPRX任务,UART接收时序将会关闭。
在程序最后,通过触发STARTTX任务和STARTRX任务,开启UART发送和接收。为了避免因为以前接收到数据而产生的RXDRDY事件导致接收到错误的数据,在最后也需要对EVENTS_RXDRDY清零。但是由于是在字节发送成功之后自动产生TXDRDY事件,对其清不清零并不影响将来的数据发送。

UART接收发送数据

接收数据

nRF51822 UART接收到的字符自动存储到双缓冲寄存器RXD中。每当一个新的字节被移入到RXD寄存器时,UART会产生一个RXDRDY事件,当第一个字节被CPU从RXD寄存器中提取走时第二个字节可以被接收。
nRF51822单字节接收函数源程序(官方SDK)如下所示,可以看到,函数返回的值为当前接收到的存放在RXD寄存器中的值。在自己编写程序的时候,可以通过循环检测串口,将这些值存入定义的数组或者flash中。
uint8_t simple_uart_get(void)
{
  while (NRF_UART0->EVENTS_RXDRDY != 1)
  {
    // Wait for RXD data to be received
  }
  
  NRF_UART0->EVENTS_RXDRDY = 0;
  return (uint8_t)NRF_UART0->RXD;
}


发送数据

nRF51822是通过TXD寄存器实现字节发送的,当一个字节发送成功的时候,UART会产生一个TXDRDY事件,于是新的字节也可以写到TXD寄存器。
因此,在进行nRF51822 UART发送数据时,我们通过写TXD寄存器(每次写一个字节,8位)来实现自动发送,发送完成之后,通过判断TXDRDY事件来确定发送成功。如果发送成功,对字符发送成功标志事件进行清零,以便下一次发送,然后继续进行下一字节的发送。
nRF51822单字节发送函数源程序(官方SDK)如下所示,在自己程序编写中,只需通过for循环,就可实现对字符数组的发送。当然,通过相应的指针可以实现字符串的发送。
void simple_uart_put(uint8_t cr)
{
  NRF_UART0->TXD = (uint8_t)cr;

  while (NRF_UART0->EVENTS_TXDRDY!=1)
  {
    // Wait for TXD data to be sent
  }

  NRF_UART0->EVENTS_TXDRDY=0;
}

nRF51822字符串发送函数源程序(官方SDK)如下所示。通过设置函数参数为一指针变量,可以实现对实参字符数组或者字符串的发送。
void simple_uart_putstring(const uint8_t *str)
{
  uint_fast8_t i = 0;
  uint8_t ch = str[i++];
  while (ch != '\0')
  {
    simple_uart_put(ch);
    ch = str[i++];
  }
}

接收超时

在官方给出的SDK中的simple_uart.c中还有这样一个函数,源程序如下所示:
bool simple_uart_get_with_timeout(int32_t timeout_ms, uint8_t *rx_data)
{
  bool ret = true;
  
  while (NRF_UART0->EVENTS_RXDRDY != 1)
  {
    if (timeout_ms-- >= 0)
    {
      // wait in 1ms chunk before checking for status
      nrf_delay_us(1000);
    }
    else
    {
      ret = false;
      break;
    }
  }  // Wait for RXD data to be received

  if (timeout_ms >= 0)
  {
    // clear the event and set rx_data with received byte
      NRF_UART0->EVENTS_RXDRDY = 0;
      *rx_data = (uint8_t)NRF_UART0->RXD;
  }

  return ret;
}
以下为该函数的说明
/** @brief Function for reading a character from UART with timeout on how long to wait for the byte to be received.
Execution is blocked until UART peripheral detects character has been received or until the timeout expires, which even occurs first
\return bool True, if byte is received before timeout, else returns False.
@param timeout_ms maximum time to wait for the data.
@param rx_data pointer to the memory where the received data is stored.
*/
bool simple_uart_get_with_timeout(int32_t timeout_ms, uint8_t *rx_data);

UART中断

UART中断使能

nRF51822 的UART并不需要过多的初始化,只需要对其进行使能就可以了。
按照以往介绍的技巧,使能相关的中断也就是要配置相关中断的INTENSET,首先要找到相关外设的基地址,在这里也就是NRF_UART0,然后通过箭头操作符指向相关的中断使能设置寄存器。中断使能语句如下所示:
NRF_UART0->INTENSET=UART_INTENSET_RXDRDY_Enabled<
在这里要注意一点,对相关外设的INTENSET寄存器配置并没有完成对中断的使能,还需要对中断向量控制器NVIC(Nested Vectored Interrupt Controller)中的中断使能寄存器ISER进行配置,庆幸的是,一般的嵌入式产品官方SDK中都有相应的库函数直接完成配置。
nRF51822完成中断使能寄存器配置的函数时NVIC_EnableIRQ(IRQn_Type IRQn);IRQn为一枚举变量,在官方SDK中又给出。个人感觉命名规则一般为XX_IRQn,XX为外设名。其在枚举定义中也给定了其在ISER中对应的bit。在NVIC_EnalbeIRQ(IRQn_Type IRQn)中也正是通过将1左移相应的位来实现ISER寄存器的配置的。在这里使能UART0中断语句如下所示:
NVIC_EnableIRQ(UART0_IRQn);

中断优先级设置

在nRF51822外部中断学习总结中,说过NVIC_SetPriority(IRQn_Type IRQn,uint32_t priority)这个函数是具体怎么实现的其功能的,在这里不再多说。只再次重复一下它的功能与作用。这个函数就是就是写中断编号为IRQn中断的中断优先级控制寄存器IP对应的位,将中断优先级priority写入对应的位。但是从_NVIC_PRIO_BITS的宏定义可以知道nRF51822只能设置4个优先级。
在这里设置优先级为1,设置中断优先级语句如下所示:
NVIC_SetPriority(UART0_IRQn,1);

中断处理函数

在配置完UART以及设置中断之后,接下来就是要编写对应的中断处理函数了,nRF51822的中断处理函数名称设置遵守如下规则,XX_IRQHandler,XX为中断名称。PS:其实这是自己观察发现的,但是事实上就是如此,nRF51822的中断服务函数的名称都是固定的,不能自己随便编写。因为编译的时候要将对应的服务程序放在固定的中断入口地址下 随便起的名字编译器不可能会认识,并且每一种不同型号的片子所对应的服务程序名不同。nRF51822的中断服务程序入口名可以在启动文件arm_startup_nrf51.s中查找到。通过观察发现,中断服务函数的名称和自己发现的是一致的。
这些入口函数名具体是在哪个文件中同地址对应起来的我还没有找到,但是你可以从芯片的启动文件中看到,比如中容量的片子就在startup_stm32f10x_md.s中可以看到所有可用的服务程序入口名,编写是照着里面的写就好了(当然服务程序的具体内容还是你自己写,放在stm32f10x_it.c里)。


在青风给出的nRF51822关于UART中断的教程中的中断处理函数中并没有像GPIOTE和RTC中断处理函数中那样先进行事件和中断使能判断,然后对事件进行清零,以便下一次事件的发生。此处没有搞明白青风的程序为什么会这样搞。


你可能感兴趣的:(nRF51822)