【LPC54616的自学之路-3】串口

第二个教程当然就是串口啦【摸索的时间有点长呢】

串口

LPC54616的串口分为三个部分

  • 轮询
  • 中断
  • DMA

这和STM32差不多,也是三种模式,但是还是内部硬件实现有些差异的

  • NXP的串口/SPI/I2S这三种外设是统一叫做Flexcomm 接口来管理,也就是它是那种同一个引脚功能实现了三种功能的玩法
  • 这三种外设是共享同一个中断入口的FlexcommX中断,它有很多个Flexcomm中断
  • 初始化上需要先初始化Flexcomm接口,然后到具体的串口/SPI/I2S
  • 它的串口是内部有FIFO的那种设计,然后功能上没有STM32那么多玩法,后面会讲

让我们开始吧

FLEXCOMM简介

简介
框图

直接点就是这三种串行通信的接口是统一配置和管理的,对指定引脚配置即可切换不同的功能,然后到不同的外设.

基本配置

按这样的顺序

  1. GPIO时钟与外设初始化
  2. GPIO初始化
  3. FLEXCOMM时钟与外设初始化
  4. 配置FLEXCOMM为串口,配置串口的波特率,起始位,结束位等等基本参数
  5. 配置串口的中断【可选】
  6. 配置对应串口的DMA【可选】

以下代码按串口0 P1.5【RX】 P1.6【TX】举例

串口的基本初始化代码

对应步骤1,2,3,4

//设置波特率
static uint8_t USARTx_SetBaudRate(USART_Type *base, uint32_t baudrate_Bps,
		uint32_t srcClock_Hz) {
	uint32_t best_diff = (uint32_t) -1, best_osrval = 0xf, best_brgval =
			(uint32_t) -1;
	uint32_t osrval, brgval, diff, baudrate;

	/* If synchronous master mode is enabled, only configure the BRG value. */
	if ((base->CFG & USART_CFG_SYNCEN_MASK) != 0U) {
		if ((base->CFG & USART_CFG_SYNCMST_MASK) != 0U) {
			brgval = srcClock_Hz / baudrate_Bps;
			base->BRG = brgval - 1U;
		}
	} else {
		/*
		 * Smaller values of OSR can make the sampling position within a data bit less accurate and may
		 * potentially cause more noise errors or incorrect data.
		 */
		for (osrval = best_osrval; osrval >= 8U; osrval--) {
			brgval = (((srcClock_Hz * 10U) / ((osrval + 1U) * baudrate_Bps))
					- 5U) / 10U;
			if (brgval > 0xFFFFU) {
				continue;
			}
			baudrate = srcClock_Hz / ((osrval + 1U) * (brgval + 1U));
			diff = baudrate_Bps < baudrate ?
					baudrate - baudrate_Bps : baudrate_Bps - baudrate;
			if (diff < best_diff) {
				best_diff = diff;
				best_osrval = osrval;
				best_brgval = brgval;
			}
		}

		/* value over range */
		if (best_brgval > 0xFFFFU) {
			return 1;
		}

		base->OSR = best_osrval;
		base->BRG = best_brgval;
	}

	return 0;
}

//串口0初始化
void USART0_Base_Init(void) {
	/*
	GPIO初始化*/
	//开启IOCON时钟
	//P105
	SYSCON->AHBCLKCTRLSET[0] |= 1 << 13;

	//P98
	//复位外设P100
	SYSCON->PRESETCTRLSET[0] |= 1 << 13;
	//回读值是不是1
	while ((SYSCON->PRESETCTRL[0] & (1 << 13)) == 0) {
		;
	}

	//结束复位外设
	SYSCON->PRESETCTRLCLR[0] |= 1 << 13;
	//回读值是不是0
	while ((SYSCON->PRESETCTRL[0] & (1 << 13)) == (1 << 13)) {
		;
	}

	//使用FLEXCOMM0
	//P1_5是RX
	//P1_6是TX
	//这两个引脚都是Type-D类型的P190
	//P194看引脚功能分配
	//串口都是
	//功能1
	//数字模式引脚
	//其余默认
	IOCON->PIO[1][5] |= (1 << IOCON_PIO_FUNC_SHIFT)
			| (1 << IOCON_PIO_DIGIMODE_SHIFT);
	IOCON->PIO[1][6] |= (1 << IOCON_PIO_FUNC_SHIFT)
			| (1 << IOCON_PIO_DIGIMODE_SHIFT);

	//开启FLEXCOMM时钟
	SYSCON->AHBCLKCTRLSET[1] |= 1 << 11;
	//复位FLEXCOMM外设
	SYSCON->PRESETCTRLSET[1] = 1 << 11;
	//等待开始复位
	while ((SYSCON->PRESETCTRL[1] & (1 << 11)) == 0) {
		;
	}
	//结束复位
	//复位FLEXCOMM外设
	SYSCON->PRESETCTRLCLR[1] = 1 << 11;
	//等待复位结束
	while ((SYSCON->PRESETCTRL[1] & (1 << 11)) == (1 << 11)) {
		;
	}
	//配置串口
	FLEXCOMM0->PSELID = (uint32_t) 1 << 0;
	//配置串口
	//FIFO功能
	USART0->FIFOCFG |= 1 << USART_FIFOCFG_ENABLETX_SHIFT;	//使能发送
	USART0->FIFOCFG |= 1 << USART_FIFOCFG_ENABLERX_SHIFT;	//使能接收
	USART0->FIFOCFG |= 1 << USART_FIFOCFG_EMPTYTX_SHIFT;	//清空发FIFO
	USART0->FIFOCFG |= 1 << USART_FIFOCFG_EMPTYRX_SHIFT;	//清空收FIFO

	USART0->FIFOTRIG |= 1 << USART_FIFOTRIG_TXLVLENA_SHIFT;	//开启发送FIFO触发
	USART0->FIFOTRIG |= 1 << USART_FIFOTRIG_RXLVLENA_SHIFT;	//开启接收FIFO触发

	USART0->FIFOTRIG &= ~USART_FIFOTRIG_TXLVL_MASK;	//发送触发门限为0
	USART0->FIFOTRIG |= 0 << USART_FIFOTRIG_TXLVL_SHIFT;

	USART0->FIFOTRIG &= ~USART_FIFOTRIG_RXLVL_MASK;	//接收触发门限为0
	USART0->FIFOTRIG |= 0 << USART_FIFOTRIG_RXLVL_SHIFT;

	//波特率,数据长度,奇偶校验等等
	USART0->CFG |= 1 << USART_CFG_ENABLE_SHIFT;	//EN
	USART0->CFG |= 1 << USART_CFG_DATALEN_SHIFT;	//8 Bits
	USART0->CFG |= 0 << USART_CFG_PARITYSEL_SHIFT;	//无校验
	USART0->CFG |= 0 << USART_CFG_STOPLEN_SHIFT;	//1 Stop
	USART0->CFG |= 0 << USART_CFG_CLKPOL_SHIFT;

	//波特率配置【我这里试过配置了1M,921600都是能够通信的,串口的时钟是】
	uint8_t res = USARTx_SetBaudRate(USART0, 1000000, 12000000);
	if (res != 0) {
		return;
	}
}

串口简介

在这里插入图片描述

在这里插入图片描述

54616的串口是内部有FIFO的设计,串口和FIFO的配置/状态/中断是分在不同的寄存器中的,使用的时候需要注意
54616的串口的中断功能相比STM32比要弱一些吧

只支持下面这些中断

有发送线空闲,居然没有接收线空闲中断【在STM32上可以做的串口DMA+空闲中断实现不定长数据传输就很难做了】

在这里插入图片描述
在这里插入图片描述
其他的寄存器就自己看手册吧,这里不过多描述,按流程配置就可以

轮询

基本上和STM32的玩法类似,就是循环填发送寄存器/取接收寄存器的数据,判断发送满/发送线空闲,接收缓冲非空/接收故障

发送
//串口0轮询发送
uint8_t USART0_Tx_Polling(const uint8_t *data, size_t length) {
	uint16_t cnt = UARTX_TRY_MAX_CNT;
	uint8_t ret = 0;
	for (; length > 0U; length--) {
		cnt = UARTX_TRY_MAX_CNT;
		//缓冲满判断
		while ((USART0->FIFOSTAT & USART_FIFOSTAT_TXNOTFULL_MASK) == 0) {
			;	//如果发送FIFO已经满了
			if (cnt > 0) {
				cnt--;
			} else {
				//超时
				ret = 0xff;
				return ret;
			}
		}
		//开始往FIFO码数据
		USART0->FIFOWR = *data;
		data++;
		cnt = UARTX_TRY_MAX_CNT;
		//发送状态空闲判断
		while ((USART0->STAT & USART_STAT_TXIDLE_MASK) == USART_STAT_TXIDLE_MASK) {
			;	//发送忙,等待
			if (cnt > 0) {
				cnt--;
			} else {
				//超时
				ret = 0xff;
				return ret;
			}
		}

	}
	return ret;
}
接收
//串口0轮询接收
uint8_t USART0_Rx_Polling(uint8_t *data, size_t length) {

	uint8_t ret = 0;
	uint32_t statusFlag = 0;
	for (; length > 0U; length--) {
		//缓冲非空判断
		if ((USART0->FIFOSTAT & USART_FIFOSTAT_RXNOTEMPTY_MASK) == 0) {
			ret = 0xfe;
			break;
		}
		//故障标志判断
		if ((USART0->FIFOSTAT & USART_FIFOSTAT_RXERR_MASK) != 0) {
			ret = 0xff;
			//清空RX FIFO
			USART0->FIFOCFG |= USART_FIFOCFG_EMPTYRX_MASK;
			//清除错误标志
			USART0->FIFOSTAT |= USART_FIFOSTAT_RXERR_MASK;
			break;
		}

		/* 读状态标志 */
		statusFlag = USART0->STAT;
		/* 清除所有标志 */
		USART0->STAT |= statusFlag;
		if ((statusFlag & USART_STAT_PARITYERRINT_MASK) != 0U) {
			ret = 0xff;
			break;
		}
		if ((statusFlag & USART_STAT_FRAMERRINT_MASK) != 0U) {
			ret = 0xff;
			break;
		}
		if ((statusFlag & USART_STAT_RXNOISEINT_MASK) != 0U) {
			ret = 0xff;
			break;
		}

		//开始往FIFO码数据
		if (ret == 0) {
			*data = (uint8_t) USART0->FIFORD;
			data++;
		}
	}
	return ret;
}

这里不过多描述COPY手册的原文,这里寄存器的操作都还不复杂

中断

编写中断函数,编写自己的处理函数【这里只讨论接收中断】

void FLEXCOMM0_IRQHandler(void) {
	if ((USART0->FIFOINTSTAT & (1 << USART_FIFOINTSTAT_RXLVL_SHIFT)) != 0) {
		//处理接收FIFO水印
		//写自己的用户处理函数
		//每接收到一个字节就会进来一次
	}

	if ((USART0->FIFOINTSTAT & (1 << USART_FIFOINTSTAT_RXERR_SHIFT)) != 0) {
		//处理接收FIFO错误
		//处理接收错误函数
		USART0->FIFOSTAT |= 1 << USART_FIFOSTAT_RXERR_SHIFT;
	}
}

在串口初始化的末尾加上中断使能的代码

//开启接收FIFO水印中断和接收错误中断
	USART0->FIFOINTENSET = USART_FIFOINTENSET_RXLVL_MASK|USART_FIFOINTENSET_RXERR_MASK;
	//开中断
	NVIC_EnableIRQ(FLEXCOMM0_IRQn);
DMA

先简单介绍下54616的DMA系统
在这里插入图片描述

它是只有一个DMA0
按通道区分的,支持优先级,事件触发
它没有具体的模式这种概念,不像STM32那样有【单次,循环,双缓冲】,而是使用一种叫DMA描述符的东西【见下方描述】
在这里插入图片描述
描述符就三个信息,源内存的结尾地址,目的内存的结尾地址,下一个描述符的地址【可以为空】
芯片维护了一个DMA通道描述符的表,这个表需要自己定义,并且要512字节对齐
描述符与描述符表格的数据结构如下【参考NXP自己的库函数】
对应STM32的三种模式,NXP这款芯片的DMA的玩法就很灵活了

  • 单次传输
  • 乒乓传输
  • 交替传输
  • 链式传输
    以上为官方供参考的四种传输模式的实现
    应该还可以自己设计出更复杂的传输方式
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
//描述符结构体的定义
typedef struct _dma_descriptor
{
    volatile uint32_t xfercfg; /*!< Transfer configuration */
    void *srcEndAddr;          /*!< Last source address of DMA transfer */
    void *dstEndAddr;          /*!< Last destination address of DMA transfer */
    void *linkToNextDesc;      /*!< Address of next DMA descriptor in chain */
} dma_descriptor_t;

static dma_descriptor_t my_s_dma_descriptor_table0[(30)] __attribute__((aligned((512))));
//按照要求需要定义一个DMA描述符的表,512对齐

static dma_descriptor_t *s_dma_descriptor_table[] =
		{ my_s_dma_descriptor_table0 };
DMA的配置流程
  1. 配置DMA时钟
  2. 配置DMA外设
  3. 配置需要的DMA通道
  4. 开启DMA中断
void MY_DMA0_Init(DMA_Type *base) {
	//开启DMA时钟
	SYSCON->AHBCLKCTRLSET[0] = 1 << 20;
	//开始复位外设
	SYSCON->PRESETCTRLSET[0] = 1 << 20;
	//等待外设复位开始
	while (0u == (SYSCON->PRESETCTRL[0] & (1 << 20))) {

	}
	//结束复位外设
	SYSCON->PRESETCTRLCLR[0] = 1 << 20;
	//等待外设复位结束
	while ((1 << 20) == (SYSCON->PRESETCTRL[0] & (1 << 20))) {

	}
	//关联描述符
	//P263,注意阅读SRAMBASE的描述
	base->SRAMBASE = (uint32_t) s_dma_descriptor_table[0];
	//启动DMA外设
	base->CTRL |= DMA_CTRL_ENABLE_MASK;

	//使能收发DMA通道
	//P249
	//P264
	//FLEXCOM0 RX CH0
	base->COMMON[0].ENABLESET |= 1 << 0;
	//FLEXCOM0 TX CH1
	base->COMMON[0].ENABLESET |= 1 << 1;

	//开启DMA全局中断
	NVIC_EnableIRQ(DMA0_IRQn);//【可选】
	//开启两个通道的中断【可选】
	base->COMMON[0].INTENSET |= 1 << 0;
	base->COMMON[0].INTENSET |= 1 << 1;
}
DMA收发接口的实现

因为54616没有像STM32那样的空闲中断,它的DMA也没有像STM32那样的DMA传输成功的数据量的寄存器,感觉要实现空闲中断+串口DMA不定长传输会有很大的困难
虽然芯片有接收空闲状态,但是它没有对应的中断,这个就很不方便了
另外它的接收与发送是FIFO模式的,DMA传输成功以后,FIFO的当前数据量都读取完了被清空了,没有办法读取出本次成功传输的数据量来
目前暂时没有太好的办法,下面的代码也只是做到了定长收发
对整个流程不清楚的可以读一读官方写的例子,移植到自己的板子上,看看调用路径,对一对手册,多来几次就可以写出寄存器版本的代码了

DMA发的基本流程
  1. 开启串口DMA发
  2. 配置对应通道描述符【内存到外设】
  3. 判断上一次传输是否完成
  4. 使能对应通道DMA
  5. 触发传输
uint8_t UARTx_DMA_TX(USART_Type *base, uint8_t *pTX, uint16_t len) {
	uint8_t ret = 0;
	//开启串口的DMA发送功能
	base->FIFOCFG |= USART_FIFOCFG_DMATX_MASK;
	//内存到外设
	//获取通道0的描述符地址
	dma_descriptor_t *descriptor =
			(dma_descriptor_t*) &s_dma_descriptor_table[0][1];
//	 //判断上一次的传输是否完成
//	 //P264
	if (DMA0->COMMON[0].ACTIVE & (1 << 1)) {
		//仍在进行中
		ret = 0xff;
	}
	if (ret == 0) {
		//外设请求使能
		DMA0->CHANNEL[1].CFG |= DMA_CHANNEL_CFG_PERIPHREQEN_MASK;
		//创建描述符
		descriptor->srcEndAddr = (uint32_t*) (pTX + (len - 1));    //注意这里是源地址的结束
		descriptor->dstEndAddr = (uint32_t*) &base->FIFOWR;

		//P271
		descriptor->xfercfg |= 1 << 0;    //配置有效需要自行开启!!!
		//不开重装
		descriptor->xfercfg |= 0 << DMA_CHANNEL_XFERCFG_RELOAD_SHIFT;
		//不开软件触发
		descriptor->xfercfg |= 0 << DMA_CHANNEL_XFERCFG_SWTRIG_SHIFT;
		//不清触发标志
		descriptor->xfercfg |= 0 << DMA_CHANNEL_XFERCFG_CLRTRIG_SHIFT;
		//设置中断A
		descriptor->xfercfg |= 1 << DMA_CHANNEL_XFERCFG_SETINTA_SHIFT;
		//不设置中断B
		descriptor->xfercfg |= 0 << DMA_CHANNEL_XFERCFG_SETINTB_SHIFT;
		//传输宽度8位
		descriptor->xfercfg |= 0 << DMA_CHANNEL_XFERCFG_WIDTH_SHIFT;
		//源地址递增1字节
		descriptor->xfercfg |= 1 << DMA_CHANNEL_XFERCFG_SRCINC_SHIFT;
		//目的地址不递增
		descriptor->xfercfg |= 0 << DMA_CHANNEL_XFERCFG_DSTINC_SHIFT;
		//传输数量
		descriptor->xfercfg |= (len - 1) << DMA_CHANNEL_XFERCFG_XFERCOUNT_SHIFT;
		descriptor->linkToNextDesc = (void*) NULL;		 //单次传输
		DMA0->CHANNEL[1].XFERCFG = descriptor->xfercfg;
		//使能DMA通道
		DMA0->COMMON[0].ENABLESET |= 1 << 1;
		//如果没有开启硬件触发,手动软件触发
		if ((DMA0->CHANNEL[1].CFG & DMA_CHANNEL_CFG_HWTRIGEN_MASK) == 0) {
			//手动软件触发
			DMA0->CHANNEL[1].XFERCFG |= DMA_CHANNEL_XFERCFG_SWTRIG_MASK;
		}
	}

	return ret;

}
DMA收的基本流程
  1. 开启串口DMA收
  2. 配置对应通道描述符【外设到内存】
  3. 判断上一次传输是否完成
  4. 使能对应通道DMA
  5. 触发传输
//串口DMA接收
uint8_t UARTx_DMA_RX(USART_Type *base, uint8_t *pRX, uint16_t len) {
	uint8_t ret = 0;
	//开启串口的DMA接收功能
	base->FIFOCFG |= USART_FIFOCFG_DMARX_MASK;
	//外设到内存
	//获取通道0的描述符地址
	dma_descriptor_t *descriptor =
			(dma_descriptor_t*) &s_dma_descriptor_table[0][0];
//	 //判断上一次的传输是否完成
//	 //P264
	if (DMA0->COMMON[0].ACTIVE & (1 << 0)) {
		//仍在进行中
		ret = 0xff;
	}
	if (ret == 0) {
		//外设请求使能
		DMA0->CHANNEL[0].CFG |= DMA_CHANNEL_CFG_PERIPHREQEN_MASK;
		//创建描述符
		descriptor->srcEndAddr = (uint32_t*) &base->FIFORD;
		descriptor->dstEndAddr = (uint32_t*) (pRX + (len - 1));    //注意这里是源地址的结束

		//P271
		descriptor->xfercfg |= 1 << 0;    //配置有效需要自行开启!!!
		//不开重装
		descriptor->xfercfg |= 0 << DMA_CHANNEL_XFERCFG_RELOAD_SHIFT;
		//不开软件触发
		descriptor->xfercfg |= 0 << DMA_CHANNEL_XFERCFG_SWTRIG_SHIFT;
		//不清触发标志
		descriptor->xfercfg |= 0 << DMA_CHANNEL_XFERCFG_CLRTRIG_SHIFT;
		//设置中断A
		descriptor->xfercfg |= 1 << DMA_CHANNEL_XFERCFG_SETINTA_SHIFT;
		//不设置中断B
		descriptor->xfercfg |= 0 << DMA_CHANNEL_XFERCFG_SETINTB_SHIFT;
		//传输宽度8位
		descriptor->xfercfg |= 0 << DMA_CHANNEL_XFERCFG_WIDTH_SHIFT;
		//源地址不递增
		descriptor->xfercfg |= 0 << DMA_CHANNEL_XFERCFG_SRCINC_SHIFT;
		//目的地址递增1字节
		descriptor->xfercfg |= 1 << DMA_CHANNEL_XFERCFG_DSTINC_SHIFT;
		//传输数量
		descriptor->xfercfg |= (len - 1) << DMA_CHANNEL_XFERCFG_XFERCOUNT_SHIFT;
		descriptor->linkToNextDesc = (void*) NULL;		 //单次传输
		DMA0->CHANNEL[0].XFERCFG = descriptor->xfercfg;
		//使能DMA通道
		DMA0->COMMON[0].ENABLESET |= 1 << 0;
		//如果没有开启硬件触发,手动软件触发
		if ((DMA0->CHANNEL[0].CFG & DMA_CHANNEL_CFG_HWTRIGEN_MASK) == 0) {
			//手动软件触发
			DMA0->CHANNEL[0].XFERCFG |= DMA_CHANNEL_XFERCFG_SWTRIG_MASK;
		}
	}

	return ret;

}
DMA中断

中断里面需要处理传输完成和传输错误
此外他们的每一个DMA通道可以支持配置中断A和中断B,两次触发中断,我们只用A就好

extern uint8_t dma_rx_str[8];//这是我自己定义的接收缓存
//串口0的DMA中断处理函数
static void USART0_DMA_IRQ_Process(void) {
	//发送完成处理
	//INTA的处理
	if ((DMA0->COMMON[0].INTA & (1 << 1)) != 0) {
		//清除INTA标志
		DMA0->COMMON[0].INTA |= (1 << 1);
		//关闭串口DMA发送
		USART0->FIFOCFG &= ~(USART_FIFOCFG_DMATX_MASK);
		//用户函数可以写在这里
	}
	//INTB的处理
	//INTA的处理
	if ((DMA0->COMMON[0].INTB & (1 << 1)) != 0) {
		//清除INTA标志
		DMA0->COMMON[0].INTB |= (1 << 1);
		//关闭串口DMA发送
		USART0->FIFOCFG &= ~(USART_FIFOCFG_DMATX_MASK);
		//用户函数可以写在这里
	}

	//接收完成处理
	//INTA的处理
	if ((DMA0->COMMON[0].INTA & (1 << 0)) != 0) {
		//清除INTA标志
		DMA0->COMMON[0].INTA |= (1 << 0);
//		//关闭串口DMA接收
		USART0->FIFOCFG &= ~(USART_FIFOCFG_DMARX_MASK);
		//用户函数可以写在这里
	}
	//INTB的处理
	//INTA的处理
	if ((DMA0->COMMON[0].INTB & (1 << 0)) != 0) {
		//清除INTA标志
		DMA0->COMMON[0].INTB |= (1 << 0);
		//关闭串口DMA接收
		USART0->FIFOCFG &= ~(USART_FIFOCFG_DMARX_MASK);
		//用户函数可以写在这里
	}

	//错误处理
	if ((DMA0->COMMON[0].ERRINT & ((1 << 1))) != 0) {
		DMA0->COMMON[0].ERRINT |= ((1 << 1));
		//用户函数可以写在这里
	}

	if ((DMA0->COMMON[0].ERRINT & ((1 << 0))) != 0) {
		DMA0->COMMON[0].ERRINT |= ((1 << 0));
		//用户函数可以写在这里
	}

}

void DMA0_IRQHandler(void) {
	//串口0
	USART0_DMA_IRQ_Process();
}

大概内容就这么多吧
不打算给出完整工程,这种需要自己多实践才行
用这种冷的不行的芯片只能多看看官方例子和啃手册,做实验了
不像STM32的教程烂大街的多
ENJOY~

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值