动态内存申请:就是我需要用的时候,再开辟一段内存。比如在程序中需要把数据uint8_t a[1,2,3], 复制给另一个数组,那就要创建创建另一个数组,需要3个字节内存, 那就临时申请3个字节,使用完后又把这3个字节释放。
**1、节点:**队列里的一个数据块,类似FreeRTOS的任务控制块。数据结构如下:
2、队列:N个节点连接起来
如果我们要往串口发送数据。
1、printf(“this is a FreeRTOS system”)
2、printf(“now start ADC”)
3、printf(“start to display on LCD”)
常规的发送完1之后,即使我们用DMA发送,MCU也必须等1发送完以后再执行2这句,等待2发送完以后再执行3这句。但是我们用队列就不一样了,MCU把1写到节点1,把2写到节点2,把3写到节点3,执行这这步很快。实际数据呢还没有发送出去,只是把它放到队列里等待发送了。我们可以定时比如1ms检查一次串口是否空闲了,则把一个节点通过DMA的方式给串口发送。
3、用实际程序说明:
1)、定义数据结构,串口DMA就从这个数据 与 串口数据寄存器间 搬运数据
struct COM_DATA_ST
{
uint8_t SendBuff[200];
uint8_t ReceBuff[200];
uint8_t Send_Complete_Flag;
uint8_t Bus_Idle_Count;
};
struct node
{
uint8_t Length; //有效数据
uint8_t *Data; //数据指针
struct node *pNext; //节点指针
};
定义一个串口数据和三个队列头节点。如果只发送,只要一个头节点就可以了。
//定义三个链表头结点,头结点不存数据
struct node Rece_Quene;
struct node Send_Quene;
struct node Send_Ack_Quene;
2)、串口初始化和DMA初始化
用STM32CubeMX生成串口,DMA程序。但初始化里作一下修改,生成的MDA程序没有初始化DMA发送的数据长度、数据地址,所以我们增加这部分,然后使能DMA。
/* USART1_TX Init */
hdma_usart1_tx.Instance = DMA1_Channel4;
hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_tx.Init.Mode = DMA_NORMAL;
hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/*******************增加以下部分***********************************/
hdma_usart1_tx.Instance->CNDTR=0;
hdma_usart1_tx.Instance->CPAR = USART1_DR_Base;
hdma_usart1_tx.Instance->CMAR = (uint32_t)Com_Data.SendBuff;
SET_BIT(huart->Instance->CR3, USART_CR3_DMAT);//使能DMA发送
/******************************************************/
接收部分,我们让它接收固定长度200个字节,然后开串口空闲中断,当串口接收了N个字节后,发现没有收到数据了,会产生空闲中断。中断我们就可以把数据取出来了。
/******************************************************/
hdma_usart1_rx.Instance->CNDTR=RECEBUFF_SIZE;
hdma_usart1_rx.Instance->CPAR = USART1_DR_Base;
hdma_usart1_rx.Instance->CMAR = (uint32_t)Com_Data.ReceBuff;
__HAL_DMA_ENABLE(&hdma_usart1_rx);//enable DMA
SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);//使能DMA接收
/******************************************************/
最后还要设置一下串口空闲中断,和打开串口 总中断
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);//设置串口空闲中断
HAL_NVIC_SetPriority(USART1_IRQn, 3, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
在空闲中断中,我们调用函数把接收到的数据入到 接收队列,然后开始新的200个数据接收
void USART1_IRQHandler(void)
{
usart1_rev_idle_callback();
HAL_UART_IRQHandler(&huart1);
}
void usart1_rev_idle_callback(void)
{
uint8_t data_length;
__HAL_DMA_DISABLE(&hdma_usart1_rx);//disable DMA
data_length=RECEBUFF_SIZE - (uint8_t)__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//取得接收长度,RECEBUFF_SIZE被定义为200,启动DMA接收的时候指定接收200字节
//当发生空闲中断时,表示接收完成,200减未接收的个数,等到已接收个数
InsertNode(&Rece_Quene,Com_Data.ReceBuff,data_length);//把DMA接收到的数据从Com_Data.ReceBuff复制到Rece_Quene连表中
hdma_usart1_rx.Instance->CNDTR = RECEBUFF_SIZE;//重新设置DMA接收200字节
__HAL_DMA_ENABLE(&hdma_usart1_rx);//enable DMA
}
并且HAL_UART_IRQHandler(&huart1);中增加清除中断标志
//如果是空闲中断
isrflags = __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE);
cr1its= __HAL_UART_GET_IT_SOURCE(huart, UART_IT_IDLE);
if((isrflags != RESET) && (cr1its != RESET))
{
__HAL_UART_CLEAR_IDLEFLAG(huart);//清除标志
isrflags=huart->Instance->SR;//读SR可以实现清除状态寄存器
isrflags=huart->Instance->DR;
}
然后接收就完成了,其它函数可以去接收队列里拿数据来处理。
3)、发送数据。
需要发送数据的地方,调用uart_send(&a[0],sizeof(a));把数据入到发送队列里。
void uart_send(uint8_t *ptr,uint8_t length)
{
InsertNode(&Send_Quene,ptr,length);
}
然后定时检查发送队列里如果有数据,就取数据发送。
写完这笔记发现还有优化的地方,未完待续…
为什么要用FreeRTOS,因为用main…while(1) 发现申请内存p1 =(struct node*)malloc(sizeof(struct node)); 失败,还不知道为什么
经过测试,接收串口有时候会漏接数据。