本例程在TM4C123GH6PM单片机上,模拟STM32的DMA+IDLE中断,实现串口的接收功能,已在EK-TM4C123GXL评估板上验证通过。
原理如下:
1、串口0通过DMA的pingpang模式接收,DMA的transfer size为1字节,即每个字节都会触发DMA中断。
2、串口使能DMA时,无法正常使用ReceiveTimeOut中断,所以本例程使用Timer0来实现“接收超时中断”,也即STM32的IDLE中断。
3、串口发送也是通过DMA,DMA的transfer size也是为1字节。
#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_uart.h"
#include "driverlib/gpio.h"
#include "driverlib/interrupt.h"
#include "driverlib/pin_map.h"
#include "driverlib/rom.h"
#include "driverlib/sysctl.h"
#include "driverlib/uart.h"
#include "driverlib/udma.h"
#include "driverlib/timer.h"
#include "string.h"
//*****************************************************************************
// The error routine that is called if the driver library encounters an error.
//*****************************************************************************
#ifdef DEBUG
void
__error__(char *pcFilename, uint32_t ui32Line){}
#endif
#if defined(ewarm)
#pragma data_alignment=1024
uint8_t ui8ControlTable[1024];
#elif defined(ccs)
#pragma DATA_ALIGN(ui8ControlTable, 1024)
uint8_t ui8ControlTable[1024];
#else
uint8_t ui8ControlTable[1024] __attribute__ ((aligned(1024)));
#endif
#define UART_RXBUF_SIZE 64
#define UART_TXBUF_SIZE 64
#define DMA_TRANS_SIZE 1
static char g_ui8RxBuf[UART_RXBUF_SIZE];
static char g_ui8TxBuf[UART_TXBUF_SIZE];
static uint32_t g_ui32RxPriCnt = 0;
static uint32_t g_ui32RxAltCnt = 0;
static uint8_t g_ui8RxDoneFlag = 0;
static uint8_t g_ui8TxDoneFlag = 0;
static uint8_t g_ui8TxBeginFlag = 0;
static uint32_t g_ui32uDMAErrCount = 0;
static uint32_t g_ui32TimerCount = 0;
void uDMAErrorHandler(void)
{
uint32_t ui32Status;
ui32Status = ROM_uDMAErrorStatusGet();
if(ui32Status)
{
ROM_uDMAErrorStatusClear();
g_ui32uDMAErrCount++;
}
}
void UARTIntHandler(void)
{
uint32_t ui32Status;
uint32_t ui32Mode;
ui32Status = ROM_UARTIntStatus(UART0_BASE, true);
ROM_UARTIntClear(UART0_BASE, ui32Status);
ui32Mode = ROM_uDMAChannelModeGet(UDMA_CHANNEL_UART0RX | UDMA_PRI_SELECT);
if(ui32Mode == UDMA_MODE_STOP)
{
g_ui32RxPriCnt++;
ROM_uDMAChannelTransferSet(UDMA_CHANNEL_UART0RX | UDMA_PRI_SELECT,
UDMA_MODE_PINGPONG,
(void *)(UART0_BASE + UART_O_DR),
g_ui8RxBuf + 2*g_ui32RxPriCnt, DMA_TRANS_SIZE);
// buffer只有64字节,这里限制一下,防止溢出
if (g_ui32RxPriCnt > 30) {
g_ui32RxPriCnt = 30;
}
}
ui32Mode = ROM_uDMAChannelModeGet(UDMA_CHANNEL_UART0RX | UDMA_ALT_SELECT);
if(ui32Mode == UDMA_MODE_STOP) {
g_ui32RxAltCnt++;
ROM_uDMAChannelTransferSet(UDMA_CHANNEL_UART0RX | UDMA_ALT_SELECT,
UDMA_MODE_PINGPONG,
(void *)(UART0_BASE + UART_O_DR),
g_ui8RxBuf+1 + 2*g_ui32RxAltCnt, DMA_TRANS_SIZE);
if (g_ui32RxAltCnt > 30) {
g_ui32RxAltCnt = 30;
}
}
if( (ui32Status&UART_INT_TX) == UART_INT_TX ){
ROM_uDMAChannelDisable(UDMA_CH9_UART0TX);
ROM_UARTDMADisable(UART0_BASE, UART_DMA_TX);
ROM_UARTIntDisable(UART0_BASE, UART_INT_TX);
g_ui8TxDoneFlag = 1;
return;
}
// 重新开始计时
ROM_TimerLoadSet(TIMER0_BASE, TIMER_A, 1400);
ROM_TimerEnable(TIMER0_BASE, TIMER_A);
}
// 用作接收超时中断
void Timer0IntHandler(void)
{
ROM_TimerDisable(TIMER0_BASE, TIMER_A);
ROM_TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
ROM_IntMasterDisable();
g_ui8RxDoneFlag = 1;
g_ui32TimerCount++;
ROM_IntMasterEnable();
}
void UARTDMAInit(){
ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
ROM_uDMAEnable();
ROM_uDMAControlBaseSet(ui8ControlTable);
ROM_GPIOPinConfigure(GPIO_PA0_U0RX);
ROM_GPIOPinConfigure(GPIO_PA1_U0TX);
ROM_GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
ROM_UARTConfigSetExpClk(UART0_BASE, ROM_SysCtlClockGet(), 115200,
(UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE |
UART_CONFIG_PAR_NONE));
// 1. 不使用FIFO,即FIFO深度为1字节。
// ROM_UARTFIFODisable(UART0_BASE);
// 2. RX使用14个字节的FIFO(不存在UART_FIFO_TX8_8的设置项,最高只能设置14字节)
// 由于DMA_TRANS_SIZE为1字节,所以正常是用不上多余的FIFO的;
// 但是特殊情况下,如果传输速度超过DMA中断的处理能力,
// FIFO就可以多缓存14个字节,一定程度上能避免数据丢失。
ROM_UARTFIFOLevelSet(UART0_BASE, UART_FIFO_TX7_8, UART_FIFO_RX7_8);
ROM_UARTFIFOEnable(UART0_BASE);
ROM_UARTEnable(UART0_BASE);
ROM_UARTDMAEnable(UART0_BASE, UART_DMA_RX | UART_DMA_TX);
ROM_IntMasterEnable();
ROM_IntEnable(INT_UART0);
// 使用DMA时,RT中断无法正常使用,所以用Timer0代替RT中断
// ROM_UARTIntEnable(UART0_BASE, UART_INT_RT);
// DMA_RX配置
ROM_uDMAChannelAssign(UDMA_CH8_UART0RX);
ROM_uDMAChannelAttributeDisable(UDMA_CHANNEL_UART0RX,
UDMA_ATTR_ALTSELECT |
UDMA_ATTR_HIGH_PRIORITY |
UDMA_ATTR_REQMASK);
ROM_uDMAChannelControlSet(UDMA_CHANNEL_UART0RX | UDMA_PRI_SELECT,
UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 |
UDMA_ARB_1);
ROM_uDMAChannelControlSet(UDMA_CHANNEL_UART0RX | UDMA_ALT_SELECT,
UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 |
UDMA_ARB_1);
ROM_uDMAChannelTransferSet(UDMA_CHANNEL_UART0RX | UDMA_PRI_SELECT,
UDMA_MODE_PINGPONG,
(void *)(UART0_BASE + UART_O_DR),
g_ui8RxBuf, DMA_TRANS_SIZE);
ROM_uDMAChannelTransferSet(UDMA_CHANNEL_UART0RX | UDMA_ALT_SELECT,
UDMA_MODE_PINGPONG,
(void *)(UART0_BASE + UART_O_DR),
g_ui8RxBuf+1, DMA_TRANS_SIZE);
ROM_uDMAChannelAttributeEnable(UDMA_CHANNEL_UART0RX, UDMA_ATTR_HIGH_PRIORITY);
ROM_uDMAChannelEnable(UDMA_CH8_UART0RX);
}
void TimerInit(){
ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
ROM_TimerConfigure(TIMER0_BASE, TIMER_CFG_ONE_SHOT);
// 16MHz,115200bps,大约1byte的时间,超时就会中断
ROM_TimerLoadSet(TIMER0_BASE, TIMER_A, 1400);
ROM_IntEnable(INT_TIMER0A);
ROM_TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
}
void UARTDMATx(const char *pui8Buffer, uint32_t ui32Count){
// 发送结束中断,EndOfTransfer
ROM_UARTTxIntModeSet(UART0_BASE, UART_TXINT_MODE_EOT);
ROM_UARTIntEnable(UART0_BASE, UART_INT_TX);
ROM_UARTDMAEnable(UART0_BASE, UART_DMA_TX);
ROM_uDMAChannelAssign(UDMA_CH9_UART0TX);
ROM_uDMAChannelAttributeDisable(UDMA_CHANNEL_UART0TX,
UDMA_ATTR_ALTSELECT |
UDMA_ATTR_HIGH_PRIORITY |
UDMA_ATTR_REQMASK);
ROM_uDMAChannelControlSet(UDMA_CHANNEL_UART0TX | UDMA_PRI_SELECT,
UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE |
UDMA_ARB_1);
ROM_uDMAChannelTransferSet(UDMA_CHANNEL_UART0TX | UDMA_PRI_SELECT,
UDMA_MODE_BASIC,
(void *)pui8Buffer,
(void *)(UART0_BASE + UART_O_DR), ui32Count);
ROM_uDMAChannelAttributeEnable(UDMA_CHANNEL_UART0TX, UDMA_ATTR_HIGH_PRIORITY);
ROM_uDMAChannelEnable(UDMA_CH9_UART0TX);
}
void UARTCmdDeal(){
if (g_ui8RxDoneFlag == 0 || g_ui8TxBeginFlag == 1) return;
int8_t i8frameLen = (g_ui32RxAltCnt + g_ui32RxPriCnt) < UART_RXBUF_SIZE ?
(g_ui32RxAltCnt + g_ui32RxPriCnt) : UART_RXBUF_SIZE;
memset(g_ui8TxBuf, 0, UART_TXBUF_SIZE);
strncpy(g_ui8TxBuf, g_ui8RxBuf, i8frameLen);
UARTDMATx(g_ui8TxBuf, strlen(g_ui8TxBuf));
g_ui8TxBeginFlag = 1;
// 等待发送结束,这里最好改成非阻塞的等待方式
while (g_ui8TxDoneFlag == 0);
}
void UARTDMARestore(){
if(g_ui8TxDoneFlag == 0) return;
g_ui8TxBeginFlag = 0;
g_ui8TxDoneFlag = 0;
g_ui8RxDoneFlag = 0;
g_ui32RxPriCnt = 0;
g_ui32RxAltCnt = 0;
ROM_uDMAChannelTransferSet(UDMA_CHANNEL_UART0RX | UDMA_PRI_SELECT,
UDMA_MODE_PINGPONG,
(void *)(UART0_BASE + UART_O_DR),
g_ui8RxBuf, DMA_TRANS_SIZE);
ROM_uDMAChannelTransferSet(UDMA_CHANNEL_UART0RX | UDMA_ALT_SELECT,
UDMA_MODE_PINGPONG,
(void *)(UART0_BASE + UART_O_DR),
g_ui8RxBuf+1, DMA_TRANS_SIZE);
// 如果收到的指令是奇数个字节,则alternate control structure会被选中,
// 那么接下来的指令就会从g_ui8RxBuf+1的位置开始放置。
// 因此需要禁止UDMA_ATTR_ALTSELECT,让primary control structure被选中。
ROM_uDMAChannelAttributeDisable(UDMA_CHANNEL_UART0RX,
UDMA_ATTR_ALTSELECT);
}
int main(void)
{
// 外部16MHz的晶振
ROM_SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN |
SYSCTL_XTAL_16MHZ);
// 使用内部16MHz的时钟源,经过倍频后,是80MHz,也单片机支持的最高频率
// 使用这个频率的话,Timer0的中断计数要改为6944,大约1byte的时长
// ROM_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_OSC_INT);
UARTDMAInit();
TimerInit();
while(1)
{
UARTCmdDeal();
UARTDMARestore();
}
}
串口调试助手截图如下:
截图中红色下划线标注响应内容。
由截图可见,本例程可以在1ms内完成响应,而且没有任何丢包。
|