stm32 USB与USART串口数据互转----单纯malloc队列

1.背景

  • 项目需求,在stm32F072中,有四个USB CDC设备,四个Usart串口,现将它们命名为Usart1Usart4,CDC1CDC4,它们之间一一对应,在串口x收的数据需转发至CDCx,CDCx收到的数据需要抓发到对应的Usartx,如CDC1收到的数据,需要使用USART1发送出去
  • CDC1<------转发------>USART1
  • CDC2<------转发------>USART2
  • CDC3<------转发------>USART3
  • CDC4<------转发------>USART4
  • 此教程仅作为经验贴,勿实操

2.条件

  • 使用STM32的HAL库进行编写,在库中,USBCDCx的数据接收是来源于中断CDC_Receive_FS,发送是CDC_Transmit_FS,USART的接收是中断USART_RXIN_IDLE_Recever(自定义),发送HAL_UART_Transmit
  • 因为USART和USBCDC速度直接不匹配,USBCDC的速度要比USART快很多,因此需要一些缓冲区

2.1 所使用的接收发送函数

  • CDC_Receive_FS:该函数由库中断USBD_CDC_DataIn回调,次函数一次传输64B数据,如果USBCDC 本次传输>=64,如129,则该函数会调用三次,分别位64+64+1;从次函数copy接收的数据
  • CDC_Transmit_FS:直接调用,传入对应数据和大小即可
  • USART_RXIN_IDLE_Recever:该函数位自定义中断函数,但USART接收到一个字符时,传输完成时,进入该函数
  • HAL_UART_Transmit:直接调用,传入对应数据和大小即可

这些函数具体代码逻辑和执行过程会再写一篇文章

2.2 整体代码逻辑

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4yTsxeUM-1610629733744)(https://raw.githubusercontent.com/casojie/blog_image/master/solo_blog/stm32USBUSARTcache.png)]

3.malloc动态链表(USART–>USB部分)

  • 为节省本身仅有的16K空间,使用malloc链表,有四个USART串口,公维护四个链表,USART1~USART4各一条,存储在Usart_Recv_Buf_Piont[4][2]数组中中,如:USART1的链头(W),为Usart_Recv_Buf_Piont[0][1],USART1的链尾®,为Usart_Recv_Buf_Piont[0][0]:W是写入新数据的一头,R是读出数据的一头.

3.1 节点结构体和初始化

  • 设置每个节点有64B的数据空间,一个next32位地址指针,指向其后续节点。其结构体如下
typedef struct USART_Recv{
	u_char string[64];  //数据空间
	uint8_t string_len; //当前数据空间记录的长度
	uint32_t *next;     //后续节点地址
}Usart_Recv_Buf;
  • 初始化:对USART1~USART4维护的链表分配头节点。此时每条链的R,W都指向同一节点
void Usart_Buf_Init()
{
	uint8_t pos=0;
	for(pos=0;pos<4;pos++)
	{
		Usart_Recv_Buf_Piont[pos][0]=Usart_Recv_Buf_Piont[pos][1]=malloc_free_count(sizeof(Usart_Recv_Buf),0,NULL,0);
		Usart_Recv_Buf_Piont[pos][0]->next=NULL;
		Usart_Recv_Buf_Piont[pos][0]->string_len=0;
	}
}

3.2 数据接收

  • 在USART中USART_RXIN_IDLE_Recever中断函数负责接收数据,参数huart:USARTx的句柄,参数index:USARTx的序号,比如USART1接收到数据,huart为USART1的句柄(初始化时会创建),index为0,USART2的index就为1,3–2,数组下标。
  • 该函数会响应两个中断
  • UART_IT_RXNE:接收到一个字符时,该中断响应,需要我们把接收的字符copy存储起来,否则会丢失
  • UART_IT_IDLE: 总线空闲中断,当线路空闲时,表明传输的数据已经完成。
  • 举例:USARTx接收casojie字符串,UART_IT_RXNE响应6次,UART_IT_IDLE响应1次(传输完最后一个字符e后)
  • 代码主要逻辑:如接收84个字符。当前有个节点,且节点空间已使用的大小为0(string_len),新字符收到后(UART_IT_RXNE)将其存储至当前节点,string_len+1。直到string_len已经=64,表明该节点已无空间存储新字符,则malloc新节点,W指针下移至新节点,然后在新节点继续接收剩余的20个字符,20个字节接收完成后,新节点string_len==20<=64,但总传输已经完成UART_IT_IDLE响应,创建新节点,W向下移动。
  • 当前节点没用完64就继续用,用完就malloc,当总传输完成也malloc。
  • malloc_free_count函数只是对malloc,free封装,用于限制总节点数量而已,阅读代码时将其视为malloc,或free即可
void USART_RXIN_IDLE_Recever(UART_HandleTypeDef *huart,uint8_t index)
{
	if(Usart_Recv_Buf_Piont[index][1]!=NULL)
	{
		uint8_t *W_PLen=&(Usart_Recv_Buf_Piont[index][1]->string_len);	//当前节点已存储长度
		Usart_Recv_Buf **W_Point=&(Usart_Recv_Buf_Piont[index][1]);		//当前节点指针
		if(__HAL_UART_GET_IT(huart,UART_IT_RXNE)!=RESET)	//接收的数据
		{
			if(*W_PLen<64)	//当前节点还有空间
				(*W_Point)->string[(*W_PLen)++]=huart->Instance->RDR;
			else			//无空间数据丢弃
				SET_BIT(USART_MAP[index]->RQR, USART_RQR_RXFRQ);//清空标志位

			if((*W_PLen)==64)	//当前节点存储已满,需申请新节点
			{
				(*W_Point)->next=malloc_free_count(sizeof(Usart_Recv_Buf),0,NULL,0);
				if((*W_Point)->next>=0x2000000&&(*W_Point)->next<=0x20004000)	//申请节点成功
				{
					(*W_Point)=(*W_Point)->next;	//偏移
					(*W_Point)->string_len=0;	//初始化新节点长度
				}
				else	//申请节点失败
				{
					//无需做任何事
				}
			}
			jishu[0]++;
		}

		if(__HAL_UART_GET_IT(huart,UART_IT_IDLE)!=RESET)	//一次传输完成标识
		{
			__HAL_UART_CLEAR_IT(huart,UART_CLEAR_IDLEF);	//clear flag of RXINE
			if((*W_PLen)!=0&&(*W_PLen)<64)	//防止发送64字节整数倍时,RXNE,IDLE中断同时发送,重复malloc
			{
				(*W_PLen)+=LEN_OFFSET; //当无法malloc时,写指针无法下移,造成数据数据暂存无法发送,旧数据被新数据覆盖问题
				(*W_Point)->next = malloc_free_count(sizeof(Usart_Recv_Buf),0,NULL,0);
				if((*W_Point)->next>=0x2000000&&(*W_Point)->next<=0x20004000)	//申请节点成功
				{
					(*W_Point)=(*W_Point)->next;	//偏移
					(*W_Point)->string_len=0;	//初始化新节点长度
				}
				else	//申请节点失败
				{
					//无需做任何事
				}
			}
		}
	}
}

3.3 数据发送

  • 由于不能在中断里直接使用发送函数,因此在main函数的中的while里循环检测USART1~USART4的链表是否有数据,有则去发送
  • 代码主要逻辑:for循环检测四个链表的链尾(R)的长度是否>0,>0表明有数据,则去发送,当R==W&&R的string_len==0表示链表无数据
void Update_USART_RECV()
{
	static u_int32_t count=0;
	u_int i,j;
	u_char *string=NULL;
	uint8_t len=0;
	for(i=0;i<USART_MAP_NUM;i++)
	{
		if(Usart_Recv_Buf_Piont[i][0]!=NULL&& //防止malloc初始化时就已失败的情况
				Usart_Recv_Buf_Piont[i][0]->string_len>=64&&
				((USBD_CDC_HandleTypeDef *)(hUsbDeviceFS_CDC.pClassData))->TxState==0)
		{
			string=Usart_Recv_Buf_Piont[i][0]->string;
			len=Usart_Recv_Buf_Piont[i][0]->string_len;
			CDC_Transmit_FS(string, len%LEN_OFFSET, CDC_IN_EP+1+i);
			count++;
			//中断冲突问题
			printf("Update_USART_RECV:%d:328\n",count);
		}
	}
}
  • 如下代码中的if条件不能变为while,当串口累计数据较多,或者((USBD_CDC_HandleTypeDef *)(hUsbDeviceFS_CDC.pClassData))->TxState==0(USB的状态)条件不满足时,会在此模块花费过多时间而没有去执行其他模块,甚至卡死整个程序.
if(Usart_Recv_Buf_Piont[i][0]!=NULL&& //防止malloc初始化时就已失败的情况
				Usart_Recv_Buf_Piont[i][0]->string_len>=64&&
				((USBD_CDC_HandleTypeDef *)(hUsbDeviceFS_CDC.pClassData))->TxState==0)
  • 上诉代码并没有Free节点的相关操作,原因是STM32的USB的机制导致的.上诉代码仅仅是让数据写入了USB专属的硬件缓存(1k),并不是正真意义上的发送完成且成功,而Free的时机应该是接收到对方正常接收的信号才执行
  • 发送成功的标志是:(USBD_CDC_HandleTypeDef *)(hUsbDeviceFS_CDC.pClassData))->TxState==0:当使用CDC_Transmit_FS发送后,TxState会被置1,表示USB BUSY,当收到USB主机回复确认表示成功接收后,TxState会重新清0,表示USB 已处于就绪状态.
  • TxState=0由另一处的USB回调函数负责:参数epnum可用于识别哪个USARTx 的确认回馈,此时才可正真free节点
static uint8_t  USBD_CDC_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum)
{
  USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *)pdev->pClassData;
  PCD_HandleTypeDef *hpcd = pdev->pData;

  if (pdev->pClassData != NULL)
  {
    if ((pdev->ep_in[epnum].total_length > 0U) && ((pdev->ep_in[epnum].total_length % hpcd->IN_ep[epnum].maxpacket) == 0U))
    {
      /* Update the packet total length */
      pdev->ep_in[epnum].total_length = 0U;

      /* Send ZLP */
      USBD_LL_Transmit(pdev, epnum, NULL, 0U);
      // printf("USBD_CDC_DataIn.1264\n");
    }
    else
    {
      hcdc->TxState = 0U;
      //完成的标志
      if(epnum>=CDC_OUT_EP1&&epnum<=CDC_OUT_EP4)
      {
    	  // printf("USBD_CDC_DataIn:%d free.1278\n",epnum);
    	  Usart_Recv_Buf *old_send=Usart_Recv_Buf_Piont[epnum-2][0];
			if(old_send!=NULL&&old_send->next!=NULL)	//有后续节点,free并下移
			{
				Usart_Recv_Buf_Piont[epnum-2][0]=Usart_Recv_Buf_Piont[epnum-2][0]->next;
				malloc_free_count(0,0,old_send,1);

			}
			else if(old_send!=NULL&&old_send->next==NULL)	//当节点无法malloc后续节点时,不下移,只将接收长度置0,则此节点可继续使用
			{
				//i
				old_send->string_len=0;
			}
      }

    }
    return USBD_OK;
  }
  else
  {
    return USBD_FAIL;
  }
}

4. USB–>USART模块

  • 模块与上诉的基本一样,不在此赘述

5.实验效果

  • 运行一段时间后,进入void HardFault_Handler(void)异常中断,malloc在stm32时常返回错误的地址,并且不是NULL,坑爹!