Chinaunix首页 | 论坛 | 博客
  • 博客访问: 605537
  • 博文数量: 165
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1554
  • 用 户 组: 普通用户
  • 注册时间: 2013-10-23 22:57
个人简介

我本仁慈,奈何苍天不许

文章分类

全部博文(165)

文章存档

2018年(1)

2016年(33)

2015年(5)

2014年(34)

2013年(92)

分类: 嵌入式

2016-07-21 14:13:21

原文地址:nrf51822裸机教程-UART 作者:ifndef

Uart硬件模块通常都有内置的硬件接收buff,比如51822的硬件uart模块图如下



因为通常接收到uart数据时都会做一些处理。比如保存到数据,或者对数据做一些判断之类的。

如果uart的波特率设置的很快,mcu的处理速度又不是很快或者是处理的过程比较耗时,那么当uart串口连续过来很多数据时,你在处理第一个数据时,后续的数据就可能丢失。所以通常uart模块都会内置一个很小的硬件接收buff51822就内置了一个6字节的硬件接收buff。这样就能起到缓存作用。


Uart一般有两种工作方式  带流控和不带流控的。


带流控的工作方式 就是多了RTS(表示可以是否可以接收)CTS(表示是否可以发送)引脚。 

RTS引脚作为输出,由uart硬件模块自动控制。通常就是与上面所说的硬件Buff协调自动工作。比如在硬件buff已经填充了2个字节还剩下4个字节的时候RTS引脚就输出高电平的deactivate 信号(51822的工作方式就是这样),当buff中的数据都被读出后回复有效信号(低电平)。


CTS作为输入由外部输入。 当CTS有效时(低电平)模块可以发送,当为无效时,模块自动暂停发送,并在CTS恢复有效时继续发送。


那么将两个uart模块的rtscts交叉相接。 如果发送方发送太快,当接收方的接收硬件buff已经存了两个字节后,接收方自动无效rts信号,表示不能接收了。 因为接收方rts与发送方cts相接。 所以发送方的cts也编程无效信号,于是发送方自动停止发送。


这样就保证了接收方不会接收溢出。流量控制也就是体现在这里。


这里说的是51822的,至于为什么收到两个字节还剩4个字节的buff就设置rts无效信号,表示自己不收了,手册中uart部分有详细的说明。

不同的mcu中的uart特性都会有一些各自的特性。不过原理都是跟上面一样的。


关于硬件流控不熟悉的可能不好理解,建议百度多看看相关说明。看看手册中的发送时序也有助于理解。


 但是针对使用来说,不理解他的工作方式也不会有问题。只要将两个uart模块的cts,rts交叉相接,使能Uart的流控就可以了。因为是硬件流控,上面所说的过程都是硬件自动进行的。


 

不带流控的工作方式  就只需要tx rx,电源,地就行了。现代mcu工作频率一般都比较高,处理速度也比较快。所以不带流控的工作方式基本能满足很多很多应用了。


后面的教程代码也是使用不带流控的工作方式(我的板子上usb转串口芯片没接rts,cts)


首先看一下寄存器的介绍:


ENABLE: 使能uart模块

PSELRXD: 选择GPIO作为RX引脚

PSELTXD: 选择GPIO作为TX引脚


RXD:接收寄存器。从改寄存器中获取uart接收到的数据

TXD: 将需要发送的数据填入该寄存器。

BAUDRATE: 设置波特率。只支持固定的几个波特率。具体查看手册。


CONFIG:用来使能校验(偶校验)和流控。



下面介绍uart轮训方式和中断方式具体操作。



新建工程选择自己板子使用的芯片型号:

教程中为了更直接的理解模块的使用。不使用sdk中提供的库函数,而直接操作寄存器来实现。

所以运行时环境勾选下必要的CMSIS下的COREDevice下的Startup。因为用了gpio的函数 勾选一下nRF_Drivers下的nrf_gpio 就可以了。



然后配置jlink的设置(我的板子使用的是jlinksw方式下载程序)




创建main.c文件,然后添加到工程中




下面介绍main.c代码细节。



使用轮训方式工作


#include "nrf51.h"

#include "nrf_gpio.h"


#define RX_PIN       (11)

#define TX_PIN       (9)





void uart_init(void){

    //设置引脚输入输出方向

    nrf_gpio_cfg_input(RX_PIN, NRF_GPIO_PIN_NOPULL);

    nrf_gpio_cfg_output(TX_PIN);

   

   

    //设置输入输出引脚。 流控引脚置位无效值

    NRF_UART0->PSELRXD = RX_PIN;      

    NRF_UART0->PSELTXD = TX_PIN;

    NRF_UART0->PSELRTS = 0XFFFFFFFF;

    NRF_UART0->PSELCTS = 0XFFFFFFFF;

   

    NRF_UART0->BAUDRATE = 0x00275000;      //9600波特率

    NRF_UART0->CONFIG = 0;             //不使用流控,不使用校验

   

    //清零一下事件

    NRF_UART0->EVENTS_RXDRDY = 0;

    NRF_UART0->EVENTS_TXDRDY = 0;  

   

    NRF_UART0->ENABLE = 4;          //开启uart

    NRF_UART0->TASKS_STARTRX = 1;   //使能接收

    NRF_UART0->TASKS_STARTTX = 1;   //使能发送

   

}


uint8_t get_uart_data(void){

    uint8_t temp;

    //轮训等待直到收到数据

    while(NRF_UART0->EVENTS_RXDRDY  == 0 ){

       ;

    }

    temp = NRF_UART0->RXD;

    NRF_UART0->EVENTS_RXDRDY = 0;

   

    return temp;

}


void send_uart_byte(uint8_t data){

    uint8_t temp = data;


    NRF_UART0->EVENTS_TXDRDY = 0;

    NRF_UART0->TXD = temp;

    //轮训等待直到数据发送完毕

    while(NRF_UART0->EVENTS_TXDRDY == 0){

       ;

    }

}


int main(void){

    uint8_t data;

    uart_init();

    //死循环等待输入,电脑串口输入 0-9字符后板子会发送回去在电脑串口上    //显示

    while( 1 ){

       data = get_uart_data();

       if ( data <= '9' && data >= '0' ){

           send_uart_byte(data);

       }

    }

    return 0;

}





中断方式工作:


使用中断方式,需要设置使能事件触发中断

INTENSET:用来设置使能在 rx,tx完成事件发生时触发中断。


这里写的例子比较简单,也不方便使用。仅仅只是为了演示一下uart中断接收和发送。

例子中接收中断收到20字节数据就会置位一个 标志变量,main循环中检查这个标量被置位后就会启动发送。Main中只是发送了一个字节。因为每个字节发送完成后都会产生发送完成事件并进入中断。所以后续的19个字节 会在中断中依次发送。


如果需要在自己的工程中使用uart的中断发送和接收。应该参考nordic sdk中的app_uart_fifo.c中的实现, 使用一个状态机和两个缓冲buff来控制uart的接收和发送。


void uart_init(void){

   

    nrf_gpio_cfg_input(RX_PIN, NRF_GPIO_PIN_NOPULL);

    nrf_gpio_cfg_output(TX_PIN);

   

   

    //设置输入输出引脚。 流控引脚置位无效值

    NRF_UART0->PSELRXD = RX_PIN;      

    NRF_UART0->PSELTXD = TX_PIN;

    NRF_UART0->PSELRTS = 0XFFFFFFFF;

    NRF_UART0->PSELCTS = 0XFFFFFFFF;

   

    NRF_UART0->BAUDRATE = 0x00275000;      //9600波特率

    NRF_UART0->CONFIG = 0;             //不适用流控,不使用校验

   

    NRF_UART0->INTENSET = ( 1<<2 ) | ( 1<<7 );    //使能 RXDRDYTXDRDY事件触发中断

   

    //清零一下事件

    NRF_UART0->EVENTS_RXDRDY = 0;

    NRF_UART0->EVENTS_TXDRDY = 0;  

   

    NRF_UART0->ENABLE = 4;          //开启uart

    NRF_UART0->TASKS_STARTRX = 1;   //使能接收

    NRF_UART0->TASKS_STARTTX = 1;   //使能发送

   

    //开启MCUuart中断

    NVIC_SetPriority(UART0_IRQn, 1);

    NVIC_ClearPendingIRQ(UART0_IRQn);

    NVIC_EnableIRQ(UART0_IRQn);

   

}


#define BUFF_SIZE (30)

uint8_t data[BUFF_SIZE];

uint8_t receive_index = 0;

uint8_t send_index = 0;

uint8_t flag = 0;


int main(){

      

    uart_init();

   

    while(1){

       if ( flag ==1 ){

           flag = 0;  //重置flag

           NRF_UART0->TXD = data[send_index++];   //这里只发送一个字节,剩余19的字节通过中断发送

          

       }

    }

    return 0;

}


void UART0_IRQHandler(void){

   if (NRF_UART0->EVENTS_RXDRDY != 0)

   {

       /// 注意清0事件标志

       NRF_UART0->EVENTS_RXDRDY = 0;

       data[receive_index++] = (uint8_t)NRF_UART0->RXD;

   if(receive_index == 20){

           receive_index = 0;

           flag = 1;

       }

   }



   if (NRF_UART0->EVENTS_TXDRDY != 0)

   {

       // 注意清0事件标志 

       NRF_UART0->EVENTS_TXDRDY = 0;         

       if(send_index != 0){     //只要没发送完就继续发送

           NRF_UART0->TXD = data[send_index++];

           if(send_index == 20){

              send_index = 0;          //发送完了,重置0

           }

       }

   }

}


阅读(5446) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~