一般做蓝牙的程序员都知道,如果说使用nordic的蓝牙芯片并且当前设备做主机的话,是用Nordic的官方例程一般都是下面这个ble_app_uart_c的例程
这个例程使用的串口是uart+easydma进行数据接收和发送,这个例程在RAM区域开辟了2块存储接收数据和发送数据的RX BUFFER 和TX BUFFER那么easydma的任务就是负责将数据从UART的RX FIFO拿数据到RAM区域和从RAM区域搬移需要发送的数据到TX输出端口,我了解这些是基于nrf52840的数据手册以及这位大佬的讲述NRF52832 UARTE使用DMA不定长接收-CSDN博客,大佬讲述的非常清晰,解答了我很多的困惑,并让我清晰的认识到了官方提供的串口代码的缺陷,nordic提供的官方串口例程,出现的一个明显的现象就是每次都只会去传输1个字节的数据,在当前字节传输完成之后,会去产生中断事件并且在中断里面继续开始传输下一个字节的数据,我通过逻辑分析仪捕获的波形,字节到字节之间的间隔差不多8us
在115200bit/s的情况下,A1到A2是传输整个字节的时间103.54us,D1~D2是包括停止位在内的时间间隔为17.27us,如果说数据量大,那么每个字节传输的间隔完全无法确定,我测试的是达到了170us有时候甚至更多,而且如果你对数据还要进行处理再输出的话,那么极有可能出现TX BUFFER溢出的现象,也就是会报NRF_NO_MEM的错误,如果说你的应用场景需要该串口输出速率高一点,那么非常建议采用上面大佬的方案:自己写驱动,不用这个串口例程,我测试了当使用自己写的串口驱动的时候,每1包输出255的话,传输1个字节的书简大约是87us,这个速度是相对来讲比较理想的,并且传输也是相对稳定的,除了搬移的时候到开始发送这段期间内需要几百us,其他都是比较理想的;
下面是我参考的上面那位大佬之后写的串口驱动
//引用的C库头文件
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
//Log需要引用的头文件
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include "nrf_pwr_mgmt.h"
#include "nrf_drv_clock.h"
#include "app_timer.h"
#define UARTE_RXD 8 //UARTE对应串口的RXD
#define UARTE_TXD 6 //UARTE对应串口的TXD
#define UARTE_BAUD_115200 0X01D7E000 //配置波特率为115200
#define UARTE_TX_BUFF_SIZE 1024 //定义TX缓冲区长度
#define UARTE_RX_BUFF_SIZE 1024 //定义RX缓冲区长度
#define ENABLE_UART_EVENTS_RXDRDY (NRF_UARTE0->INTEN |= 0X00000004) //启动接收字节中断
#define DISABLE_UART_EVENTS_RXDRDY (NRF_UARTE0->INTEN &= 0XFFFFFFFB) //关闭接收字节中断
#define UARTE_TIMER_INTERVAL APP_TIMER_TICKS(0.095) //95Us 一个字节接收的时间最大值
APP_TIMER_DEF (m_uarte_timer_id);
struct
{
unsigned char txBuf[UARTE_TX_BUFF_SIZE];
unsigned short int txBufLength;
unsigned char rxBuf[UARTE_RX_BUFF_SIZE];
unsigned short int rxBufLength;
bool rxTimeOutFlag; //接收超时标志位
bool rxEndFlag ; //接收结束标志位
}uarteStruct;
接下来是初始化带DMA的串口
static void uarte_init (void)
{
NRF_UART0->PSELRXD = UARTE_RXD; //配置串口对应引脚
NRF_UART0->PSELTXD = UARTE_TXD;
NRF_UARTE0->BAUDRATE = UARTE_BAUD_115200; //配置波特率
NRF_UARTE0->CONFIG = 0x00;//无校验位 无流控 1个停止位
NRF_UARTE0->RXD.PTR = (uint32_t)(uarteStruct.rxBuf); //配置DMA接收缓冲区
NRF_UARTE0->RXD.MAXCNT = 255; //配置DMA接收长度
NRF_UARTE0->ENABLE = 0x08;
NRF_UARTE0->INTEN = 0X00000114; //0001 0001 0100
NVIC->ISER[UARTE0_UART0_IRQn>>5] |= 0x01<<(UARTE0_UART0_IRQn&0x1f); //使能UARTE中断
NRF_UARTE0->TASKS_STARTRX = 1; //使能UARTE字节接收任务 用来接收第一个字节的数据
uartE_APP_Timer_Init(); //初始化UARTE定时器
}
定时器部分,上面第一块代码已经声明了该定时器实例
static void uarte_appTimerStart (void) //启动app定时器
{
ret_code_t err_code = app_timer_start(m_uarte_timer_id,UARTE_TIMER_INTERVAL,NULL);
APP_ERROR_CHECK(err_code);//检查返回值
}
static void uarte_appTimerStop (void) //停止APP定时器
{
ret_code_t err_code = app_timer_stop(m_uarte_timer_id);
APP_ERROR_CHECK(err_code);//检查返回值
}
//定时器中断函数
static void uarte_timer_handler (void *p_context)
{
UNUSED_PARAMETER(p_context);
if(NRF_UARTE0->EVENTS_RXDRDY)
{
NRF_UARTE0->EVENTS_RXDRDY = 0;
}
else
{
uarteStruct.rxTimeOutFlag = true; //接收超时
NRF_UARTE0->TASKS_STOPRX = 1; //停止接收 进入(EVENTS_ENDRX)中断
}
}
static void lfclk_config(void)
{
//初始化时钟模块,设置低频时钟源
ret_code_t err_code = nrf_drv_clock_init();
APP_ERROR_CHECK(err_code);
//请求低频时钟,输入参数为NULL表示低频时钟启动后不产生事件
nrf_drv_clock_lfclk_request(NULL);
}
static void uartE_APP_Timer_Init (void) //初始化esp软件定时器
{
//初始化APP定时器模块
ret_code_t err_code;//
//加入创建用户定时任务的代码,创建用户定时任务
err_code = app_timer_create(&m_uarte_timer_id,APP_TIMER_MODE_REPEATED,uarte_timer_handler);
APP_ERROR_CHECK(err_code);//检查返回值
lfclk_config();
}
如果是在Nordic官方例程里面的例程的话,那么在重写串口中断函数的时候,需要非常的注意,因为官方例程里面都是已经定义过的,自己定义的话需要将官方定义的那个注释掉,不然会报错的
void UARTE0_UART0_IRQHandler(void)
{
if(NRF_UARTE0->EVENTS_ENDRX)
{
NRF_UARTE0->EVENTS_ENDRX = 0;
NRF_UARTE0->RXD.PTR +=NRF_UARTE0->RXD.AMOUNT;
uarteStruct.rxBufLength +=NRF_UARTE0->RXD.AMOUNT;
if(uarteStruct.rxBufLength>=UARTE_RX_BUFF_SIZE || uarteStruct.rxTimeOutFlag == true) //接收停止
{
NRF_LOG_INFO("receive data end.")
uarte_appTimerStop (); //停止APP定时器
NRF_UARTE0->RXD.PTR = (uint32_t)(uarteStruct.rxBuf);
uarteStruct.rxEndFlag = true; //置位接收结束标志位
NRF_UARTE0->EVENTS_RXDRDY = 0; //清除接收字节中断标志位
ENABLE_UART_EVENTS_RXDRDY; //开启字节接收中断
}
else
{
if(uarteStruct.rxBufLength+255>=UARTE_RX_BUFF_SIZE)
{
NRF_UARTE0->RXD.MAXCNT = UARTE_RX_BUFF_SIZE-uarteStruct.rxBufLength;
}
else NRF_UARTE0->RXD.MAXCNT = 255;
}
NRF_UARTE0->TASKS_STARTRX = 1; //启动接收任务
}
else if(NRF_UARTE0->EVENTS_RXDRDY)
{
NRF_LOG_INFO("receive frist data.")
DISABLE_UART_EVENTS_RXDRDY; //禁止接收中断
NRF_UARTE0->EVENTS_RXDRDY = 0;
uarteStruct.rxEndFlag = false; //清除接收结束标志位
uarteStruct.rxTimeOutFlag = false; //清除超时接收标志位
uarteStruct.rxBufLength = 0; //接收长度清0
uarte_appTimerStart (); //启动APP定时器
}
if(NRF_UARTE0->EVENTS_ENDTX) //DMA发送结束中断
{
NRF_UARTE0->EVENTS_ENDTX = 0;
uarteStruct.txBufLength -= NRF_UARTE0->TXD.AMOUNT; //获取剩余的发送长度
if(uarteStruct.txBufLength)
{
NRF_UARTE0->TXD.PTR+=NRF_UARTE0->TXD.AMOUNT;
if(uarteStruct.txBufLength<255)NRF_UARTE0->TXD.MAXCNT = uarteStruct.txBufLength;
else NRF_UARTE0->TXD.MAXCNT = 255;
NRF_UARTE0->TASKS_STARTTX = 1;
}
}
}
最重要的发送函数
static void uartE_SendDmaStart (unsigned char *buff,unsigned short int length)
{
NRF_UARTE0->TXD.PTR = (uint32_t)buff;
uarteStruct.txBufLength = length;
if(length>=255)NRF_UARTE0->TXD.MAXCNT = 255;
else NRF_UARTE0->TXD.MAXCNT = length;
NRF_UARTE0->TASKS_STARTTX = 1;
}
如果说你只是想在官方的其他情况正常使用app_uart_put(byte)这样的带fifo的串口又想加快发送速度的话,你可以在发送的时候直接将DMA的TX.PTR的指针指向你要发送的存放数据的数组并且改写上面的发送函数就可以了
static void uartE_SendDmaStart (unsigned char *buff,unsigned short int length)
{
NRF_UARTE0->TXD.PTR = (uint32_t)buff;//DMA需要搬运的数据指针指向需要发送的数组
NRF_UARTE0->TXD.MAXCNT = length;//发送的长度
NRF_UARTE0->TASKS_STARTTX = 1;//启动发送
}
按照上面这个改写,正常的在DMA里面的数据发送完成后在下面中断里面查看寄存器EVENTS_ENDTX的值,就可以判断是否发送完成
//可以查看NRF_UARTE_EVENT_ENDTX对应寄存器EVENTS_ENDTX值,会在DMA发送完成后产生
void nrfx_uarte_0_irq_handler(void)
{
uarte_irq_handler(NRF_UARTE0, &m_cb[NRFX_UARTE0_INST_IDX]);
}
主函数:
int main(void)
{
unsigned short int i = 0;
ret_code_t err_code = app_timer_init(); //该函数只能调用一次
APP_ERROR_CHECK(err_code);
log_init(); //初始化log程序模块
NRF_LOG_INFO("UARTE DMA TEST.")
uartE_DMA_Init (); //UARTE_DMA初始化程序
while(true)
{
//处理挂起的LOG和运行电源管理
if (NRF_LOG_PROCESS() == false )
{
//运行电源管理,该函数需要放到主循环里面执行
nrf_pwr_mgmt_run();
if(uarteStruct.rxEndFlag == true) //接收结束
{
uarteStruct.rxEndFlag = false;
NRF_LOG_INFO("receive length:%d.",uarteStruct.rxBufLength)
NRF_LOG_HEXDUMP_INFO(uarteStruct.rxBuf,uarteStruct.rxBufLength);
for(i=0;i<uarteStruct.rxBufLength;i++)
{
uarteStruct.txBuf[i] = uarteStruct.rxBuf[i];
}
uarteStruct.txBufLength = uarteStruct.rxBufLength;
uartE_SendDmaStart(uarteStruct.txBuf,uarteStruct.txBufLength);
}
}
NRF_LOG_FLUSH();
__WFE();
}
}