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

tech2023-02-25  97

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

串口

LPC54616的串口分为三个部分

轮询中断DMA

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

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

让我们开始吧

FLEXCOMM简介

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

基本配置

按这样的顺序

GPIO时钟与外设初始化GPIO初始化FLEXCOMM时钟与外设初始化配置FLEXCOMM为串口,配置串口的波特率,起始位,结束位等等基本参数配置串口的中断【可选】配置对应串口的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的配置流程
配置DMA时钟配置DMA外设配置需要的DMA通道开启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发的基本流程
开启串口DMA发配置对应通道描述符【内存到外设】判断上一次传输是否完成使能对应通道DMA触发传输 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收的基本流程
开启串口DMA收配置对应通道描述符【外设到内存】判断上一次传输是否完成使能对应通道DMA触发传输 //串口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~

最新回复(0)