1.DMA接收配置
1.direction :数据传输的方向是外设(串口)----->内存(DMA Buff);
2.memory_inc:内存自增,内存指DMA Buff,往Buff中搬移数据的时候,会依次往buff中搬运;
3.memory0_addr:DMA接收Buff地址;
4.periph_addr:外设地址为串口数据寄存器;
5.DMA_MEMORY_INCREASE_DISABLE:外设地址不增加
6.配置为循环模式:当DMA RxBuff存满数据时,从首地址开始存数据,不用在缓存区满的情况下手动切换缓存区,增加了程序运行效率。
7.dma_channel_enable:DMA接收使能,一开始就使能保证串口接收数据时开始的数据不会丢;
dma_single_data_parameter_struct DmaInitStruct;
dma_deinit(uiDmaX, iDmaRxChan);
DmaInitStruct.direction = DMA_PERIPH_TO_MEMORY; /* ***** */
DmaInitStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; /* 内存自增 */
DmaInitStruct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; /* 存储和外设的传输宽度 */
DmaInitStruct.number = usRxBuffSize;
DmaInitStruct.memory0_addr = (UINT32)pUartDmaChan->SIOCHAN_pcRxbuffer;
DmaInitStruct.periph_addr = (UINT32)&USART_DATA(iBase);
DmaInitStruct.periph_inc = DMA_MEMORY_INCREASE_DISABLE; /* 外设地址固定 */
DmaInitStruct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_single_data_mode_init(uiDmaX, iDmaRxChan, &DmaInitStruct);
dma_circulation_enable(uiDmaX, iDmaRxChan); /* 配置为循环模式 */
dma_channel_subperipheral_select(uiDmaX, iDmaRxChan, uiDmaRxPeri); /* 外设选择 */
dma_channel_enable(uiDmaX, iDmaRxChan); /* enable DMA channel */
2.DMA BUFF接收数据的过程
串口往DMA搬运数据是自动完成的:当检测到外设中有数据时,DMA就会先搬运数据到DMA RX Buff, 这个过程不消耗CPU,并且速度快;
另开启了一个线程,在线程中1ms检测一次DMA RxBuff里面的数据量,和上次的比较,就能得出此次需要传输的数据量,然后将数据全部复制到RxRingBuff,;因为DMA buff在配置时配置的是内存自增模式,所以搬运到DMA BUFF中的数据地址一直是增加的,且DMA BUFF是循环的;
为什么另外开启一个线程:因为等待信号的过程是一个死等的过程,如果不开启一个线程,那么其他的函数将不能执行;
2.1 1ms检测一次DMA BUFF中的数据量是怎么实现的:
方法一: 使用POSIX里面的高速定时器和信号通知机制
信号通知,定时通知一个线程,线程中会调用一个函数,函数中会接收信号。
timer_t timerId;
struct sigevent sigev;
struct timespec interval;
struct itimerspec tspec;
int ret;
if (uiChannl != 0 && uiChannl <= 7) {
sprintf(pcUartName, "t_uart%dcntGet", uiChannl);
if (!(bIsTimerCreate)) {
API_ThreadAttrBuild(&threadattr,
4 * LW_CFG_KB_SIZE,
140,
LW_OPTION_THREAD_STK_CHK,
(VOID *)pUartDmaChan);
pUartDmaChan->SIOCHAN_ulSioThreadId = API_ThreadCreate("t_dmaGetCnt", //创建一个线程,线程中会执行一个函数;
(PTHREAD_START_ROUTINE)__dmaGetCnt,
&threadattr, LW_NULL);
sigev.sigev_notify = SIGEV_THREAD_ID;
sigev.sigev_signo = SIGUSR2;
sigev.sigev_notify_thread_id = pUartDmaChan->SIOCHAN_ulSioThreadId;
ret = timer_create(CLOCK_MONOTONIC, &sigev, &timerId); /* 创建定时器,但是不开启 */
if (ret) {
fprintf(stderr, "timer create failed.\n");
return -1;
}
bIsTimerCreate = 1;
/*
* 设置定时时间
*/
interval.tv_sec = 0;
interval.tv_nsec = 1000000;
tspec.it_interval = interval; /* 定时间隔时间 */
tspec.it_value = interval;
ret = timer_settime(timerId, 0, &tspec, LW_NULL); /* 开启定时器 */
等待信号
struct timespec ts1, ts2;
sigset_t sigset;
INT iSig;
if (!(pUartDmaChan->SIOCHAN_bIsSigCreate)) { //有问题;创建一个timer,创建一个信号,
sigemptyset(&sigset);
sigaddset(&sigset, SIGUSR2);
sigprocmask(SIG_BLOCK, &sigset, NULL);
pUartDmaChan->SIOCHAN_bIsSigCreate = 1;
}
while(1) {
sigwait(&sigset, &iSig); /* 等待信号 */
if (iSig == SIGUSR2) {
/**********定时调用的代码 ******************************/
}
**方法二:**高速定时器 hstimerfd(); hstimerfd_settime()
创建定时器后,使用read()函数等待;
定时器创建后会生成一个描述符,read函数中就是等待这个描述符;
_G_itimerFd = hstimerfd_create(0); /* 创建定时器 */
if (_G_itimerFd == -1) {
fprintf(stderr, "error: %s.\n", strerror(errno));
return -1;
}
bIsTimerCreate = 1;
/*
* 我们以纳秒为单位
*/
ts.tv_sec = 0;
ts.tv_nsec = 1000000; /* 1ms定时调用接收线程 */
itspec.it_interval = ts;
itspec.it_value = ts;
iRet = hstimerfd_settime(_G_itimerFd, &itspec, LW_NULL);
if (iRet) {
fprintf(stderr, "error: %s.\n", strerror(errno));
return (PX_ERROR);
}
API_ThreadAttrBuild(&threadattr,
4 * LW_CFG_KB_SIZE,
249,
LW_OPTION_THREAD_STK_CHK,
NULL);
_G_ulSioThreadId = API_ThreadCreate("t_dmaGetCnt",
(PTHREAD_START_ROUTINE)__dmaGetCnt,
&threadattr, LW_NULL);
等待
UINT64 u64Overruns = 0;
INT i;
while(1) {
read(_G_itimerFd, &u64Overruns, sizeof(u64Overruns)); /* 等待定时器计时到时 */
/*************************要执行的代码*************************************/
}
方法三:使用tick中断,发送二进制信号量;
tick是系统的滴答时钟,1ms的时候系统tick中断函数中释放二进制信号量,所以tick的响应时间可以达到1ms;线程调用的函数中等待二进制信号量;
3.串口DMA Read函数的实现
Read函数的实现
读数据其实是从RxRingBuff中获取数据到APP中的buff,只需调用ringBuffGet()函数就可以。RxRingBuff中的数据上面已经提示怎么获取了;
实现RxRingBuff中有数据才从循环BUFF中读数据怎么实现?
答:可以使用二进制信号量;
一:进入read函数的时候先阻塞,当DMA BUFF中的数据全部搬运到RxRingBuff时,并且检测到上一次RxRingBuff的数据为空,释放二进制信号量;二、检测到RxRingBuff不为空,释放二进制信号量,APP调用read时会从非空的RxRingBuff中再次获取数据;
下面是read函数的实现
** 函数名称: __sioRead
** 功能描述: 串口读数据函数
** 输 入 : pFdEntry 打开的设备描述符
** pcData 接收缓存区地址
** usLen 接收数据长度
** 输 出 : NONE
** 返回值 : 实际写入的字节数
*********************************************************************************************************/
static ssize_t __sioRead (PLW_FD_ENTRY pFdEntry,
PCHAR pcData,
size_t stLen)
{
__PUART_SIODMACHAN pUartDmaChan = (__PUART_SIODMACHAN)pFdEntry->FDENTRY_pdevhdrHdr;
INT iNBytes;
if (pcData == LW_NULL) {
_ErrorHandle(EINVAL);
return (PX_ERROR);
}
if (stLen == 0) {
return (0);
}
API_SemaphoreBPend(pUartDmaChan->SIOCHAN_hRdSyncSemB, LW_OPTION_WAIT_INFINITE);
RDBUF_LOCK(pUartDmaChan); /* 读互斥信号量 */
iNBytes = _rngBufGet(pUartDmaChan->SIOCHAN_vxRingIdRdBuf,
pcData,
(INT)stLen);
if (!_rngIsEmpty(pUartDmaChan->SIOCHAN_vxRingIdRdBuf)) { /* ring buff数据未取完,下次读不等待直接取*/
RDBUF_UNLOCK(pUartDmaChan);
API_SemaphoreBPost(pUartDmaChan->SIOCHAN_hRdSyncSemB);
} else {
RDBUF_UNLOCK(pUartDmaChan);
}
return ((LONG)iNBytes);
}