本帖最后由 电子DIY小家 于 2023-6-15 16:05 编辑
最近发现STC家的MCU开始支持DMA了。之前一直没时间用,这几天有空刚好看了下关于串口的这部分DMA.
可以看到这个串口部分的DMA一共就12个寄存器,可以说是简单到了极点,那就一起往下看看,当然了其实如标题类似的程序手册里也有,不过手册的相对简单,这边稍稍给他概括升级了一下下,顺便亲身体验一下这个DMA的使用,看看会不会踩坑。
首选贴上主函数的代码:
- void main(void)
- {
- u8 xdata dat[50];
-
- u16 i,j;
- COMx_InitDefine COMx_InitStructure; //结构定义
-
-
- //-----------------------------------------------系统初始化-----------------------------------------------
- WTST = 0; //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
- EAXFR = 1; //扩展寄存器(XFR)访问使能
- CKCON = 0; //提高访问XRAM速度
-
- P0M1 = 0x00; P0M0 = 0x00; //设置为准双向口
- P1M1 = 0x00; P1M0 = 0x00; //设置为准双向口
- P2M1 = 0x00; P2M0 = 0x00; //设置为准双向口
- P3M1 = 0x00; P3M0 = 0x00; //设置为准双向口
- P4M1 = 0x00; P4M0 = 0x00; //设置为准双向口
- P5M1 = 0x00; P5M0 = 0x00; //设置为准双向口
- P6M1 = 0x00; P6M0 = 0x00; //设置为准双向口
- P7M1 = 0x00; P7M0 = 0x00; //设置为准双向口
-
- //-----------------------------------------------串口初始化-----------------------------------------------
- COMx_InitStructure.UART_Mode = UART_8bit_BRTx; //模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
- COMx_InitStructure.UART_BRT_Use = BRT_Timer1; //选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
- COMx_InitStructure.UART_BaudRate = 115200UL; //波特率, 一般 110 ~ 115200
- COMx_InitStructure.UART_RxEnable = ENABLE; //接收允许, ENABLE或DISABLE
- COMx_InitStructure.BaudRateDouble = DISABLE; //波特率加倍, ENABLE或DISABLE
- COMx_InitStructure.Priority = Priority_1;
- UART_Configuration(UART1, &COMx_InitStructure); //初始化串口1
-
- Timer0_Init(); //定时器初始化,提供1ms系统时钟
- EA = 1; //允许总中断
- UART1_DMA_Receive(COM_RX_Lenth); //设置dma下的数据接收
-
- while (1)
- {
-
- P21 = 0;
-
- if( flag_1ms ) //1ms系统时间
- {
- flag_1ms = 0;
- if( COM1.RX_TimeIDLE>0 ) //模拟空闲中断的超时变量
- {
- COM1.RX_TimeIDLE--;
- if( COM1.RX_TimeIDLE==0 ) //空闲中断时间到达
- {
- P21 = 1;
- i=sprintf(dat,"接收:%d\t",(u16)UART1_DMA_ReceiveNum()); //计算接受到的数据长度
-
- for(j=0;j<UART1_DMA_ReceiveNum();j++) //将接收到的数据写入缓冲区
- dat[i++] = COM1.RX_Buff[j];
-
- UART1_DMA_FreightSend(dat,i); //打印字符串
- UART1_DMA_Receive(COM_RX_Lenth); //重新设置接收区
- }
- }
-
- if( COM1.RX_TimeOUT>0 ) //发送数据的超时
- {
- COM1.RX_TimeOUT--;
- if( COM1.RX_TimeOUT==0 )
- {
-
- }
- }
- }
-
- }
- }
-
复制代码
其次贴上子函数的一些代码
- COMx_Define xdata COM1;
-
- //========================================================================
- // 函数: void UART1_DMA_Interrupt (void) interrupt 50/51
- // 描述: UART1 DMA中断函数
- // 参数: none.
- // 返回: none.
- // 版本: VER1.0
- // 日期: 2021-5-8
- // 备注:
- //========================================================================
- void UART1_DMA_Interrupt(void) interrupt 13
- {
-
- if (DMA_UR1T_STA & 0x01) //发送完成
- {
- P21 = 1;
- DMA_UR1T_STA &= ~0x01;
- COM1.TX_OK = 1;
- }
- if (DMA_UR1T_STA & 0x04) //数据覆盖
- {
- P21 = 1;
- DMA_UR1T_STA &= ~0x04;
- }
-
- if (DMA_UR1R_STA & 0x01) //接收完成
- {
- P21 = 1;
- DMA_UR1R_STA &= ~0x01;
- COM1.RX_OK = 1;
- P20=!P20;
- }
- if (DMA_UR1R_STA & 0x02) //数据丢弃
- {
- P21 = 1;
- DMA_UR1R_STA &= ~0x02;
- }
- }
-
- void UART1_DMA_FreightSend(u8 *pData, u16 Size)
- {
- u16 i;
- if(Size == 0)
- return;
-
- for( i=0;i<Size;i++ )
- {
- COM1.TX_Buff[i] = pData[i];
- COM1.TX_Cnt++;
- }
-
- while(COM1.TX_OK==0);
- COM1.TX_OK = 0;
-
- Size -= 1;
- DMA_UR1T_AMTH = (u8)(Size >> 8); //设置传输总字节数(低8位):n+1
- DMA_UR1T_AMT = (u8)Size; //设置传输总字节数(高8位):n+1
- DMA_UR1T_TXAH = (u8)((u16)&COM1.TX_Buff >> 8);
- DMA_UR1T_TXAL = (u8)((u16)&COM1.TX_Buff);
- DMA_UR1T_CR = 0xc0; //bit7 1:使能 UART1_DMA, bit6 1:开始 UART1_DMA 自动发送
- COM1.RX_TimeOUT = RX_TimeOUT_Set1 ;
- }
-
- void UART1_DMA_Send(void)
- {
- u16 Size;
- if(COM1.TX_Cnt == 0)
- return;
-
- while(COM1.TX_OK==0);
- COM1.TX_OK = 0;
-
- Size = COM1.TX_Cnt-1;
- DMA_UR1T_AMTH = (u8)(Size >> 8); //设置传输总字节数(低8位):n+1
- DMA_UR1T_AMT = (u8)Size; //设置传输总字节数(高8位):n+1
- DMA_UR1T_TXAH = (u8)((u16)&COM1.TX_Buff >> 8);
- DMA_UR1T_TXAL = (u8)((u16)&COM1.TX_Buff);
- DMA_UR1T_CR = 0xc0; //bit7 1:使能 UART1_DMA, bit6 1:开始 UART1_DMA 自动发送
- COM1.RX_TimeOUT = RX_TimeOUT_Set1 ;
- }
-
- void UART1_DMA_Receive( u16 Size)
- {
- if(Size == 0) return;
- DMA_UR1R_CR = 0x00;
- COM1.RX_Cnt =Size;
- Size -= 1;
- DMA_UR1R_AMTH = (u8)(Size >> 8); //设置传输总字节数(低8位):n+1
- DMA_UR1R_AMT = (u8)Size; //设置传输总字节数(高8位):n+1
- DMA_UR1R_RXAH = (u8)((u16)&COM1.RX_Buff >> 8);
- DMA_UR1R_RXAL = (u8)((u16)&COM1.RX_Buff);
- DMA_UR1R_CR = 0xa1; //bit7 1:使能 UART1_DMA, bit5 1:开始 UART1_DMA 自动接收, bit0 1:清除 FIFO
- COM1.RX_Cnt_Num = 0;
-
- DMA_UR1R_DONEH = 0;
- DMA_UR1R_DONE = 0;
- }
-
- u16 UART1_DMA_ReceiveNum(void)
- {
- return ((u16)DMA_UR1R_DONEH<<8)|DMA_UR1R_DONE;
- //return (u8)DMA_UR1R_DONE;
- }
-
- void UART1_int (void) interrupt 4
- {
- P21 = 1;
- if(RI)
- {
- RI = 0;
- COM1.RX_TimeIDLE = RX_TimeIDLE_Set1;
- COM1.RX_Cnt_Num++;
- }
-
- if(TI)
- {
- TI = 0;
- }
- }
-
-
- //========================================================================
- // 函数: UART_Configuration
- // 描述: UART初始化程序.
- // 参数: UARTx: UART组号, COMx结构参数,请参考UART.h里的定义.
- // 返回: none.
- // 版本: V1.0, 2012-10-22
- //========================================================================
- u8 UART_Configuration(u8 UARTx, COMx_InitDefine *COMx)
- {
- #if defined( UART1 ) || defined( UART2 ) || defined( UART3 ) || defined( UART4 )
- u16 i;
- u32 j;
- #else
- UARTx = NULL;
- COMx = NULL;
- #endif
-
- #ifdef UART1
- if(UARTx == UART1)
- {
- //-------------------------------------------结构体参数初始化-------------------------------------------
- COM1.RX_TimeIDLE = 0;
- COM1.RX_TimeOUT = 0;
- COM1.RX_Cnt = 0;
- COM1.TX_Cnt = 0;
- COM1.TX_OK = 1;
- COM1.RX_OK = 0;
-
- for(i=0; i<COM_TX_Lenth; i++)
- COM1.TX_Buff[i] = 0;
- for(i=0; i<COM_RX_Lenth; i++)
- COM1.RX_Buff[i] = 0;
-
- //-------------------------------------------串口参数初始化-------------------------------------------
- SCON = (SCON & 0x3f) | COMx->UART_Mode; //模式设置
- if((COMx->UART_Mode == UART_9bit_BRTx) || (COMx->UART_Mode == UART_8bit_BRTx)) //可变波特率
- {
- j = (MAIN_Fosc / 4) / COMx->UART_BaudRate; //按1T计算
- if(j >= 65536UL) return FAIL; //错误
- j = 65536UL - j;
- if(COMx->UART_BRT_Use == BRT_Timer2)
- {
- T2R = 0; //Timer stop
- S1BRT = 1; //S1 BRT Use Timer2;
- T2_CT = 0; //Timer2 set As Timer
- T2x12 = 1; //Timer2 set as 1T mode
- T2H = (u8)(j>>8);
- T2L = (u8)j;
- T2R = 1; //Timer run enable
- }
- else
- {
- TR1 = 0;
- S1BRT = 0; //S1 BRT Use Timer1;
- T1_CT = 0; //Timer1 set As Timer
- TMOD &= ~0x30;//Timer1_16bitAutoReload;
- T1x12 = 1; //Timer1 set as 1T mode
- TH1 = (u8)(j>>8);
- TL1 = (u8)j;
- TR1 = 1;
- }
- }
- else if(COMx->UART_Mode == UART_ShiftRight)
- {
- if(COMx->BaudRateDouble == ENABLE) S1M0x6 = 1; //固定波特率SysClk/2
- else S1M0x6 = 0; //固定波特率SysClk/12
- }
- else if(COMx->UART_Mode == UART_9bit) //固定波特率SysClk*2^SMOD/64
- {
- if(COMx->BaudRateDouble == ENABLE) SMOD = 1; //固定波特率SysClk/32
- else SMOD = 0; //固定波特率SysClk/64
- }
- UART1_RxEnable(COMx->UART_RxEnable); //UART接收使能
-
- if(COMx->Priority <= Priority_3) UART1_Priority(COMx->Priority); else return FAIL;
- UART1_Interrupt(1);
-
- DMA_UR1T_CFG = 0x80; //bit7 1:Enable Interrupt
- DMA_UR1T_STA = 0x00;
- DMA_UR1T_AMT =0; //设置传输总字节数(低8位):n+1
- DMA_UR1T_AMTH = 0; //设置传输总字节数(高8位):n+1
- DMA_UR1T_TXAH = (u8)((u16)&COM1.TX_Buff >> 8);
- DMA_UR1T_TXAL = (u8)((u16)&COM1.TX_Buff);
- DMA_UR1T_CR = 0xc0; //bit7 1:使能 UART1_DMA, bit6 1:开始 UART1_DMA 自动发送
-
- DMA_UR1R_CFG = 0x80; //bit7 1:Enable Interrupt
- DMA_UR1R_STA = 0x00;
- DMA_UR1R_AMT = 0; //设置传输总字节数(低8位):n+1
- DMA_UR1R_AMTH = 0; //设置传输总字节数(高8位):n+1
- DMA_UR1R_RXAH = (u8)((u16)&COM1.RX_Buff >> 8);
- DMA_UR1R_RXAL = (u8)((u16)&COM1.RX_Buff);
- DMA_UR1R_CR = 0xa1; //bit7 1:使能 UART1_DMA, bit5 1:开始 UART1_DMA 自动接收, bit0 1:清除 FIFO
-
- return SUCCESS;
- }
- #endif
-
- return FAIL; //错误
- }
-
复制代码
- #ifndef __USART_H
- #define __USART_H
-
- #include "stc.h"
- //========================================================================
- // 定义声明
- //========================================================================
-
- #define UART1 1 //使用哪些串口就开对应的定义,不用的串口可屏蔽掉定义,节省资源
- #define UART2 2
- #define UART3 3
- #define UART4 4
-
- #define COM_TX_Lenth 290 //设置串口发送数据缓冲区大小
- #define COM_RX_Lenth 290 //设置串口接收数据缓冲区大小
-
-
- #define PRINTF_SELECT UART2 //选择 printf 函数所使用的串口,参数 UART1~UART4
- #define UART_ShiftRight 0 //同步移位输出
- #define UART_8bit_BRTx (1<<6) //8位数据,可变波特率
- #define UART_9bit (2<<6) //9位数据,固定波特率
- #define UART_9bit_BRTx (3<<6) //9位数据,可变波特率
-
-
- #define RX_TimeIDLE_Set1 5 //接收数据超时时间设置
- #define TimeOutSet2 5
- #define TimeOutSet3 5
- #define TimeOutSet4 5
-
- #define RX_TimeOUT_Set1 200;
-
-
- #define BRT_Timer1 1 //波特率发生器选择
- #define BRT_Timer2 2
- #define BRT_Timer3 3
- #define BRT_Timer4 4
-
- //========================================================================
- // UART设置
- //========================================================================
-
- #define UART1_RxEnable(n) (n==0?(REN = 0):(REN = 1)) /* UART1接收使能 */
- #define UART2_RxEnable(n) (n==0?(S2REN = 0):(S2REN = 1)) /* UART2接收使能 */
- #define UART3_RxEnable(n) (n==0?(S3REN = 0):(S3REN = 1)) /* UART3接收使能 */
- #define UART4_RxEnable(n) (n==0?(S4REN = 0):(S4REN = 1)) /* UART4接收使能 */
-
-
- #define CLR_TI2() S2TI = 0 /* 清除TI2 */
- #define CLR_RI2() S2RI = 0 /* 清除RI2 */
- #define CLR_TI3() S3TI = 0 /* 清除TI3 */
- #define CLR_RI3() S3RI = 0 /* 清除RI3 */
- #define CLR_TI4() S4TI = 0 /* 清除TI3 */
- #define CLR_RI4() S4RI = 0 /* 清除RI3 */
-
- #define S3_8bit() S3SM0 = 0 /* 串口3模式0,8位UART,波特率 = 定时器的溢出率 / 4 */
- #define S3_9bit() S3SM0 = 1 /* 串口3模式1,9位UART,波特率 = 定时器的溢出率 / 4 */
- #define S3_BRT_UseTimer3() S3ST3 = 1 /* BRT select Timer3 */
- #define S3_BRT_UseTimer2() S3ST3 = 0 /* BRT select Timer2 */
-
- #define S4_8bit() S4SM0 = 0 /* 串口4模式0,8位UART,波特率 = 定时器的溢出率 / 4 */
- #define S4_9bit() S4SM0 = 1 /* 串口4模式1,9位UART,波特率 = 定时器的溢出率 / 4 */
- #define S4_BRT_UseTimer4() S4ST4 = 1 /* BRT select Timer4 */
- #define S4_BRT_UseTimer2() S4ST4 = 0 /* BRT select Timer2 */
-
- //串口1中断优先级控制
- #define UART1_Priority(n) do{if(n == 0) PSH = 0, PS = 0; \
- if(n == 1) PSH = 0, PS = 1; \
- if(n == 2) PSH = 1, PS = 0; \
- if(n == 3) PSH = 1, PS = 1; \
- }while(0)
-
- #define UART1_Interrupt(n) (n==0?(ES = 0):(ES = 1)) /* UART1中断使能 */
- #define UART2_Interrupt(n) (n==0?(ES2 = 0):(ES2 = 1)) /* UART2中断使能 */
- #define UART3_Interrupt(n) (n==0?(ES3 = 0):(ES3 = 1)) /* UART3中断使能 */
- #define UART4_Interrupt(n) (n==0?(ES4 = 0):(ES4 = 1)) /* UART4中断使能 */
-
-
- //========================================================================
- // 变量声明
- //========================================================================
-
- typedef struct
- {
- u8 RX_TimeIDLE; //字节接收超时(模拟空闲中断)
- u8 RX_TimeOUT; //接收超时
-
- u8 RX_Buff[COM_RX_Lenth]; //接收数组
- u16 RX_Cnt; //接收数据个数
- u16 RX_Cnt_Num; //接收数据个数
- u8 TX_Buff[COM_TX_Lenth]; //发送数组
- u16 TX_Cnt; //发送数据个数
- u8 TX_OK; //发送完成 1
- u8 RX_OK; //发送完成 1
-
- } COMx_Define;
-
- typedef struct
- {
- u8 UART_Mode; //模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
- u8 UART_BRT_Use; //使用波特率, BRT_Timer1,BRT_Timer2,BRT_Timer3,BRT_Timer4
- u32 UART_BaudRate; //波特率, 一般 110 ~ 115200
- u8 Morecommunicate; //多机通讯允许, ENABLE,DISABLE
- u8 UART_RxEnable; //允许接收, ENABLE,DISABLE
- u8 BaudRateDouble; //波特率加倍, ENABLE,DISABLE
- u8 Priority; //优先级
- } COMx_InitDefine;
-
-
- u8 UART_Configuration(u8 UARTx, COMx_InitDefine *COMx);
-
- extern COMx_Define xdata COM1;
-
- void UART1_DMA_FreightSend(u8 *pData, u16 Size);
- void UART1_DMA_Send(void);
- void UART1_DMA_Receive( u16 Size);
- u16 UART1_DMA_ReceiveNum(void);
-
-
-
- #endif
复制代码
代码部分如上,整体用下来其实非常的简单,总的概括下几个注意事项:
1.串口DMA的读写的数据源地址必须在xram区域,否则无法正常读写;
2.接收不定长数据的时候,接收缓冲区没有接收满但是需要开启下一次接收之前,记得要先复位一下DMA_UR1R_CR寄存器,否则DMA_UR1R_DONE计数器不会清空
3.MDA发送的时候由于是缓冲区发送,为了避免发送过快数据丢失,记得在发送之前要判断上一次是否发送完成。
4.在使用接收之前一定要先开启DMA的数据缓冲区,否则无法收到数据
5.在接收不定长数据时,由于我的这个STC32G12K128暂时不带串口超时中断,所以用了串口数据接收中断做了超时校验(这里一定要开启接收中断),换别的型号的时候可以直接用硬件的超时中断。
6.由于串口的DMA中断用到的中断向量号非常大,可以使用插件或者内嵌汇编的中断跳转,具体查看手册“5.9 关于中断号大于31在Keil中编译出错的处理”相关内容
7.DMA_UR1T_AMT寄存器的实际读写的字节数为(AMT+1),即当AMT 设置为0时,读写1字节,当AMT 设置255时,读写256字节
可以看到整个代码比串口就加了十几行代码,但是效率提高了n多,下面看实测:
具体代码可以在附件中下载测试,等我有了STC32G8K64在来试试这个串口超时中断来替换这个软件超时  。
|