1.DMA发送配置
1.DMA发送配置
if (uiDmaX == DMA1) {
rcu_periph_clock_enable(RCU_DMA1);
} else if (uiDmaX == DMA0) {
rcu_periph_clock_enable(RCU_DMA0);
}
dma_single_data_parameter_struct DmaInitStruct;
dma_deinit(uiDmaX, iDmaTxChan);
DmaInitStruct.direction = DMA_MEMORY_TO_PERIPH; /* 数据传输方向为内存到外设*/
DmaInitStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; /* 内存自增 */
DmaInitStruct.memory0_addr = (UINT32)pUartDmaChan->SIOCHAN_pcTxbuffer1;
DmaInitStruct.periph_memory_width = DMA_MEMORY_WIDTH_8BIT; /* 存储和外设的传输宽度 */
DmaInitStruct.number = 0; /* 传输的过程会重新配置 */
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, iDmaTxChan, &DmaInitStruct);
dma_channel_subperipheral_select(uiDmaX, iDmaTxChan, uiDmaTxPeri); /* 外设选择 */
dma_circulation_disable(uiDmaX, iDmaTxChan); /* 非循环模式 ,必须设置 */
if (pUartDmaChan->SIOCHAN_pcTxbuffer2 != 0) { /* 双BUFF时,设置第二个BUFF的地址*/
uiDmaM1Addr = (UINT32)pUartDmaChan->SIOCHAN_pcTxbuffer2;
DMA_CHM1ADDR(uiDmaX, iDmaTxChan) = uiDmaM1Addr; /* 设置存储器1基地址 */
} else {
UART_DEBUG("malloc tx buff2 faild!\r\n");
}
dma_channel_disable(uiDmaX, iDmaTxChan); /* disable DMA channel */
dma_interrupt_enable(uiDmaX, iDmaTxChan, DMA_CHXCTL_FTFIE); /* full transfer finish interrupt enable*/
DMA发送传输的方向为:内存(DMA buff)------>外设(串口);
初始化设置失能dma发送:因为发送开启的是发送空中断,如果一开始就开启中断, 串口发送BUFF中的数据为空,串口会不断的进入中断;
初始化使能DMA完成中断:只有在DMA发送完成之后才会触发中断;
memory_inc(内存自增)设置:设置为自增使能,内存指的是DMA TxBuff,要发送的数据搬运到TxBuff时,TxBuff的内存地址自动增加,不需要用户手动一个一个地址的增加。
periph_inc(外设地址自增)设置:设置为不自增,因为外设地址是串口的DR;
循环模式:dma内存中数据满了之后,会从头开始存储数据到DMA buff。由于是DMA 发送,如果开启循环模式,会导致DMA TxBuff中的数据还没来得及传到外设,就会被新的数据覆盖(DMA会不断的搬运数据),导致数据丢失;
2.串口写成字符设备–write函数的实现
我将串口实现为一个字符设备,因为我们操作系统中的tty设备架构使数据传输速度达不到3M的波特率;
自实现一个write函数;从APP发送数据buff, 到DMA搬运到内存,再发送到外设流程如下:
细节实现:为实现对WrRingBuff的操作只能是读和写其中一种, 需要自旋锁或者二进制信号量
write中使用自旋锁:自旋锁可以屏蔽中断,直接屏蔽uart中断;
二进制信号量:往WrRingBuff放数据前先阻塞,第一次放数据的时候不阻塞。有两种情况会释放信号量:①从WrRingBuff取完数据②上一次往WrRingBuff放数据时,WrRingBuff不满,这一次不阻塞直接放;
发送中断服务函数里面不能使用互斥锁,因为互斥锁在获取不到锁的情况下会睡眠,而中断是不能睡眠的;
下面是write函数的实现
static ssize_t __sioWrite (PLW_FD_ENTRY pFdEntry,
PCHAR pcData,
size_t stLen)
{
__PUART_SIODMACHAN pUartDmaChan = (__PUART_SIODMACHAN)pFdEntry->FDENTRY_pdevhdrHdr;
UINT32 uiBase = pUartDmaChan->SIOCHAN_pBase;
INT iWriteLen = 0;
REGISTER ssize_t sstNbStart = 0;
while (stLen > 0) {
API_SemaphoreBPend(pUartDmaChan->SIOCHAN_hWrSyncSemB, LW_OPTION_WAIT_INFINITE);
LW_SPIN_LOCK_IRQ(&_G_sioSpinlock, &iSioreg);
iWriteLen = _rngBufPut(pUartDmaChan->SIOCHAN_vxRingIdWrBuf,
pcData,
(INT)stLen);
if (!_rngIsFull(pUartDmaChan->SIOCHAN_vxRingIdWrBuf)) { /* Ring buff 没有满 */
LW_SPIN_UNLOCK_IRQ(&_G_sioSpinlock, iSioreg);
API_SemaphoreBPost(pUartDmaChan->SIOCHAN_hWrSyncSemB); /* 释放发送信号量 */
} else {
LW_SPIN_UNLOCK_IRQ(&_G_sioSpinlock, iSioreg);
}
usart_interrupt_enable(uiBase, USART_INTEN_TBEIE); /* 开启串口发送空中断 */
sstNbStart += iWriteLen;
stLen -= (size_t)iWriteLen;
pcData += iWriteLen;
}
return (sstNbStart);
}
3.串口发送中断服务函数的实现
中断服务函数的实现流程放到上图了;
串口DMA发送是从DMA TxBuff —>外设(串口的DR);这个搬运过程是使能DMA后,DMA主动实现的;
在DMA搬运数据到串口之前,DMA TxBuff中需要有数据,数据是从WrRingBuff中读取的,我这里使用
_rngBufGet(pUartDmaChan->SIOCHAN_vxRingIdWrBuf,
pUartDmaChan->SIOCHAN_pcTxbuffer1,
usTxBuffSize);
将数据放到DMA 的 Txbuff1;具体的实现可以自行定义,比如可以使用复制的方式;
第二步修改DMA的配置:因为使用的是双BUFF,所以需要指定DMA TxBuff用于传输,当然是哪个BUFF中有数,指定哪个BUFF了;配置DMA要传输的个数,即往DMA buff中放了多少个数,配置为多少;
第三步:开启DMA传输,即使能DMA
dma_channel_enable(uiDmaX, iDmaTxChan);
释放二进制信号量,可以往WrRingBuff中写数据;
if ((RESET != usart_interrupt_flag_get(uiBase, USART_INT_TBEIE))
&& (RESET != usart_flag_get(uiBase, USART_FLAG_TBE))) {
bIsUsedBuff1 = pUartDmaChan->SIOCHAN_bTxUsedBuff1;
if (bIsUsedBuff1) { /* 双buff选择 */
uiWriteBytes = _rngBufGet(pUartDmaChan->SIOCHAN_vxRingIdWrBuf,
pUartDmaChan->SIOCHAN_pcTxbuffer1,
usTxBuffSize);
} else {
uiWriteBytes = _rngBufGet(pUartDmaChan->SIOCHAN_vxRingIdWrBuf,
pUartDmaChan->SIOCHAN_pcTxbuffer2,
usTxBuffSize);
}
pUartDmaChan->SIOCHAN_usTxNextSent = uiWriteBytes;
if (pUartDmaChan->SIOCHAN_usTxNextSent != 0) { /* DMA BUFF中的数据量大于0 */
dma_channel_disable(uiDmaX, iDmaTxChan);
DMA_CHCNT(uiDmaX, iDmaTxChan) = pUartDmaChan->SIOCHAN_usTxNextSent;
pUartDmaChan->SIOCHAN_usTxNextSent = 0; /* 设置要传输长度为0 */
if (bIsUsedBuff1) {
dmaSwitchBuffer(uiDmaX, iDmaTxChan, DMA_MEMORY_0); /* 指定要传输的DMA buff */
} else {
dmaSwitchBuffer(uiDmaX, iDmaTxChan, DMA_MEMORY_1);
}
pUartDmaChan->SIOCHAN_bTxUsedBuff1 = !(bIsUsedBuff1); /* 切换DMA tx buff */
dma_channel_enable(uiDmaX, iDmaTxChan);
API_SemaphoreBPost(pUartDmaChan->SIOCHAN_hWrSyncSemB); /* ring buff中的数取完了 */
}
}
DMA传输完后中断服务函数:
对DMA传输完成的说明:
发送的数据方向为存储器到外设,DMA默认为传输控制器,从上图看CNT的值减为0则传输完成;CNT的值是每次开启DMA传输之前设置,每通过DMA传输一个字节,CNT的值就会减1;传输完成,则状态寄存器的传输完成标志位就会置1;输入和输出完成标志位使用的是同一个;
if((RESET != dma_interrupt_flag_get(uiDmaX, iDmaTxChan, DMA_INTF_FTFIF))) {
dma_interrupt_flag_clear(uiDmaX, iDmaTxChan, DMA_INTC_FTFIFC);
while(usart_flag_get(uiBase, USART_FLAG_TC) == RESET); /* 防止数据没有传完丢失 */
usart_flag_clear(uiBase, USART_FLAG_TC);
usart_interrupt_disable(uiBase, USART_INTEN_TBEIE); /* 关闭在startup开启的中断 */
}
这里重点要注意的是等待串口完成标志,因为在测试的时候发现,DMA传输数据的过程中最后一个数据会丢。数据从DMA传到串口数据寄存器,然后再发送到移位寄存器
数据发送到移位寄存器后,就会将TBE置1,所以数据会被覆盖;
失能串口发送空中断,是为了防止一直重复进入中断服务函数,导致APP调用write后不起作用;