完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
扫一扫,分享给好友
如果CSDN和知乎无法看到图片,欢迎来到我的博客我的博客
有哪个大佬教下我知乎的markdowm外链图片显示问题~感激不尽 1.背景 项目想要的效果 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hK4lGQT4-1611042352755)(https://raw.githubusercontent.com/casojie/blog_image/master/solo_blog/16110189338073.png)] 项目需求,在stm32F072中,有四个USB CDC设备,四个Usart串口,现将它们命名为Usart1-Usart4,CDC1-CDC4,它们之间一一对应,在串口x收的数据需转发至CDCx,CDCx收到的数据需要抓发到对应的Usartx,如CDC1收到的数据,需要使用USART1发送出去 CDC1<------转发------>USART1 CDC2<------转发------>USART2 CDC3<------转发------>USART3 CDC4<------转发------>USART4 承接上一篇malloc队列在stm32的问题:stm32 malloc队列缓存 malloc在stm32不可靠,运行一段时间后,返回来非法地址 malloc在USB和USART接收中断中使用,大数据时会导致丢失后半部分数据 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JMzJQCfm-1611042352765)(https://raw.githubusercontent.com/casojie/blog_image/master/solo_blog/16110195154716.png)] 上诉问题也可能是我其他代码和设置导致的,既然动态的malloc不行,那就换静态的 2.静态链表 2.1 什么是静态链表? 说白了,就是用大数组做的队列,事先申请一块大的内存空间(如:a[100][2]),之后想办法串联起来,我弄个图就懂了。如下图的数组: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j3Ke9E2s-1611042352771)(https://raw.githubusercontent.com/casojie/blog_image/master/solo_blog/16110217364958.png)] a[0]的next为1,那么a[a[0].next]==a[1],这样的话是不是相当于下图: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fP2hpuz0-1611042352777)(https://raw.githubusercontent.com/casojie/blog_image/master/solo_blog/16110224213453.png)] 下图只画了五个节点,上面是十个节点 2.2 静态链表优缺点 动态和静态链表的优缺点都结合了 静态优点:分配“新空间”快速,比malloc要快上不少,其本质是从数组里找出空闲的元素,不是实际意义上的分配真实内存空间 缺点:数组的局限性,其限制了它最大大小,如上图(最多只能有10个节点) 其实本质上是可以将静态和动态链表结合的,当静态链表长度不够时,调用真正意义上的malloc动态分配新的大空间,不过这就是后话了. 3.项目中的静态链表队列 回到项目,代码的逻辑结构如下:USBCDC/USART的中断函数里取出接收的数据,写入各种缓存区,在使用USART/USBCDC发送函数发出 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BAD5hQRc-1611042352785)(https://raw.githubusercontent.com/casojie/blog_image/master/solo_blog/16110195154716.png)] 4路USBCDC,分别为CDC1-CDC4,4路的USART1-USART4 为每个CDCx---USARTx都维护一个队列,都维护一个R,W指针。R:出队端,W:入队端 维护一个空闲节点队列,方便维护和分配 3.1 USBCDCx数据转USARTx 3.1.1 所需的变量 则声明定义一下变量: typedef struct U***_Usart_Inode{ //节点 u_char string[64];//数据域 u_int8_t next; //后续节点 u_int8_t string_len;//当前节点string使用的长度 }UChain_Inode; typedef struct CHAIN_TABLE{//存储R ,W指针,表示一个队列 uint8_t Write_Point; uint8_t Read_Point; }Chain_Table; typedef struct U***_Usart_Buff{ UChain_Inode inode[70]; //声明 Chain_Table U***_Usart_Ready_Point;//空闲节点队列指针 }U***_usart_buff; //--------------------------------------------------- U***_usart_buff U***_Buff;//USB转USART缓存队列 Chain_Table U***x_buff_Point[4];//四路CDCx四个链表 变量的意义都在注释中,只是封了一些结构体,原理一样的 3.1.2 使用前的初始化 void USB_Usart_Buff_init() { uint8_t pos=0; /* Index next * 0 1 * 1 2 * 2 3 * 3 4 * */ U***_Buff.U***_Usart_Ready_Point.Read_Point=0;//R指0 for(pos=0;pos<70;pos++) //将空闲节点串起来 { U***_Buff.inode[pos].next=pos+1; U***_Buff.inode[pos].string_len=0; } //空闲节点next赋值一个无效值 U***_Buff.inode[70-1].next=0xFF; //最后一个节点无后续节点,则next初始化为0xFF U***_Buff.U***_Usart_Ready_Point.Write_Point=70-1;//R指向69最后一个数据 for(pos=0;pos<4;pos++)//分配至少四个 { U***x_buff_Point[pos].Write_Point=U***x_buff_Point[pos].Read_Point=malloc_U***(1); //只有一个节点的情况 U***_Buff.inode[U***x_buff_Point[pos].Write_Point].next=0xFF;//W指针无效 U***_Buff.inode[U***x_buff_Point[pos].Write_Point].string_len=0; } } 将空闲队列建立起来,初始时,所有的节点都没有使用,空闲队列应包含所有节点 初始化USBx转USARTx链表,开始时都只分配一个节点 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BHRODbFm-1611042352787)(https://raw.githubusercontent.com/casojie/blog_image/master/solo_blog/16110245651830.png)] 上图就是初始化后的结果,R0为空闲链表的R,W0为空闲链表的W,R1,W1链表USBCDC1,R2,W2=CDC2,etc R0因为分配了四个节点给四个链表,则R0移动到[4],R1,W1指向[0],其他类似,当Rx=Wx时,表示队列为空(我知道这会带来一些问题) 3.1.3 调用使用他们 封装假malloc和free uint8_t malloc_U***(uint8_t num)//从空闲队列中出队一个空闲节点 { uint8_t pos=0; uint8_t R=(U***_Buff.U***_Usart_Ready_Point.Read_Point); uint8_t W=(U***_Buff.U***_Usart_Ready_Point.Write_Point); uint8_t R_return=R; for(pos=0;pos if(R!=W) { R=U***_Buff.inode[R].next; U***_Buff.inode[R].string_len=0; } else break; } if(pos!=num)//节点数量不足 { return 0xFF; } else //分配成功 { U***_Buff.U***_Usart_Ready_Point.Read_Point=R; return R_return; } } void free_U***(uint8_t index)//释放一个节点//入队空闲队列 { num1--; uint8_t R=(U***_Buff.U***_Usart_Ready_Point.Read_Point); uint8_t W=(U***_Buff.U***_Usart_Ready_Point.Write_Point); U***_Buff.inode[W].next=index; W=U***_Buff.inode[W].next; U***_Buff.inode[W].next=0xFF; U***_Buff.inode[W].string_len=0; U***_Buff.U***_Usart_Ready_Point.Write_Point=W; } USBCDC中断函数接收数据 static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len,uint8_t epnum) { /* USER CODE BEGIN 6 */ uint8_t USART_Index=epnum-CDC_OUT_EP-1;//判断来自哪个CDC uint8_t *W=&(U***x_buff_Point[USART_Index].Write_Point);//CDC对应链表W uint8_t pos=0; for(pos=0;pos<(*Len);pos++) //拷贝到来的数据,一包大小最大64,USB2.0最大包长度 U***_Buff.inode[*W].string[pos]=Buf[pos]; U***_Buff.inode[*W].string_len=pos;//长度 U***_Buff.inode[*W].next=malloc_U***(1);//分配一个节点 if(U***_Buff.inode[*W].next!=0xFF) //申请新节点success { *W=U***_Buff.inode[*W].next; U***_Buff.inode[*W].next=0xFF; U***_Buff.inode[*W].string_len=0; } else //fail malloc { } USBD_CDC_SetRxBuffer(&hU***DeviceFS_CDC, &Buf[0]); USBD_CDC_ReceivePacket(&hU***DeviceFS_CDC,epnum); return (USBD_OK); /* USER CODE END 6 */ } 发送处理队列内的内容 void Update_USB_RECV() { uint8_t pos=0; uint8_t* R=NULL; uint8_t old_string=0xFF; for(pos=0;pos<4;pos++)//遍历四个CDCx队列,看看哪个有数据 { R=&(U***x_buff_Point[pos].Read_Point); if(U***_Buff.inode[*R].string_len!=0&& USART_info[pos].huart.gState==HAL_UART_STATE_READY) { // printf("USB: %dn",U***_Buff.inode[*R].string_len); HAL_UART_Transmit(&(USART_info[pos].huart),U***_Buff.inode[*R].string,U***_Buff.inode[*R].string_len,0xFFFF); U***_Buff.inode[*R].string_len=0; if(U***_Buff.inode[*R].next!=0xFF)//判断是否有后续节点 { old_string=*R; *R=U***_Buff.inode[*R].next; free_U***(old_string); } } } } 3.1.4 代码细节分析 其实上面的代码是有一个不好的地方,空闲队列判断是空的条件是R0=W0,这意味着,空闲队列至少会占用一个节点,这就会浪费一个节点的资源。后面我会再分析为什么要浪费这个节点空间(省去了很多事) 3.2 USARTx转USBCDCx 3.2.1 代码 这部分要复杂一些,原因来自USARTx的接收中断,如接收字符串"0123456789",则if(__HAL_UART_GET_IT(huart,UART_IT_RXNE)!=RESET)分支运行10次,一个字符一次,if(__HAL_UART_GET_IT(huart,UART_IT_IDLE)!=RESET)运行一次,最后一个字符接收后产生,标记传输已完成 变量的声明和mallo和free与上面基本一样 //USARTx数据接收函数 void USART_RXIN_IDLE_Recever(UART_HandleTypeDef *huart,uint8_t index) { static uint8_t buff_num=0; uint16_t save_full_cache=0xFF+1; uint8_t *W=&(Usartx_buff_Point[index].Write_Point); uint8_t *string_len=&(Usart_Buff.inode[*W].string_len); if(__HAL_UART_GET_IT(huart,UART_IT_RXNE)!=RESET) //接收的数据,一字符一次 { if(*string_len<64) { Usart_Buff.inode[*W].string[(*string_len)++]=huart->Instance->RDR; } else save_full_cache=huart->Instance->RDR; // SET_BIT(USART_MAP[index]->RQR, USART_RQR_RXFRQ);//清空标志位 if(*string_len>=64) //当前节点已满 { Usart_Buff.inode[*W].next=malloc_Usart(1); if(Usart_Buff.inode[*W].next!=0xFF) { *W=Usart_Buff.inode[*W].next; Usart_Buff.inode[*W].next=0xFF; Usart_Buff.inode[*W].string_len=0; if(save_full_cache!=0xFF+1) //中断1字符cache,A已>64,但W仍然指向A,下一字符来时,懂了吧 Usart_Buff.inode[*W].string[Usart_Buff.inode[*W].string_len++]=save_full_cache; } else { //申请失败 } } } if(__HAL_UART_GET_IT(huart,UART_IT_IDLE)!=RESET) //一次传输完成标识 { __HAL_UART_CLEAR_IT(huart,UART_CLEAR_IDLEF); //clear flag of RXINE if(*string_len!=0&&*string_len<64)//防止无数据时的IDLE中断,和防止malloc和上面多重复一次的情况 { *string_len+=LEN_OFFSET; //标记该节点数据完成 Usart_Buff.inode[*W].next=malloc_Usart(1); if(Usart_Buff.inode[*W].next!=0xFF) //申请成功 { *W=Usart_Buff.inode[*W].next; Usart_Buff.inode[*W].next=0xFF; Usart_Buff.inode[*W].string_len=0; } else { //申请失败,无需做任何事 } } } } //USBCDC发送 void Update_USART_RECV() { uint8_t pos=0; uint8_t current_point=0xFF; uint8_t string_len=0; u_char *string=NULL; for(pos=0;pos<4;pos++) { current_point=Usartx_buff_Point[pos].Read_Point; if(current_point!=0xFF&& Usart_Buff.inode[current_point].string_len>=64&& ((USBD_CDC_HandleTypeDef *)(hU***DeviceFS_CDC.pClassData))->TxState==0) { string=Usart_Buff.inode[current_point].string; string_len=Usart_Buff.inode[current_point].string_len; CDC_Transmit_FS(string, string_len%LEN_OFFSET, CDC_IN_EP+1+pos); } } } //USB回调判断数据是否成功发送 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.1264n"); } else { hcdc->TxState = 0U; //从这开始,其他的不用看 //完成的标志,//free节点 if(epnum>=CDC_OUT_EP1&&epnum<=CDC_OUT_EP4) { uint8_t Usart_Index=epnum-2; uint8_t last_inode=Usartx_buff_Point[Usart_Index].Read_Point; if(last_inode!=0xFF&&Usart_Buff.inode[last_inode].next!=0xFF) //有后续节点 { Usartx_buff_Point[Usart_Index].Read_Point=Usart_Buff.inode[last_inode].next; // Usart_Buff.inode[last_inode].next= Usartx_buff_Point[Usart_Index].Read_Point; free_Usart(last_inode); } else { Usart_Buff.inode[last_inode].string_len=0; } } //这里 } return USBD_OK; } else { return USBD_FAIL; } } 3.2.2 代码细节分析 道理和原理都是一样的,有几种情况需要考虑 当空闲队列用完时:W无法偏移到新节点 USARTx接收中断如何标记本次数据传输完成:让string_len偏移+65标记,则发送端只要判断string_len>=64即可发送,发送时%65即可 4.为什么要浪费一个节点空间? 这当时是一个bug,之前的代码是不会浪费这个节点空间的,判断空闲队列为空时,R0=W0=0xFF,这里先说这个链表队列的最基本的原则 mallo函数只能改动R指针,Free只能改动W指针,不能同时改动:先上bug代码 uint8_t malloc_U***(uint8_t num) { uint8_t pos=0; uint8_t R=(U***_Buff.U***_Usart_Ready_Point.Read_Point); uint8_t W=(U***_Buff.U***_Usart_Ready_Point.Write_Point); uint8_t R_return=R; for(pos=0;pos if(R!=W) //有 { R=U***_Buff.inode[R].next; U***_Buff.inode[R].string_len=0; } else if(R==W&&R!=0xFF) //仅剩一个空闲 { W=0xFF; //再无空闲节点 R=0xFF; } else //无空闲节点R==W&&R==0xFF { break; } } if(pos!=num)//节点数量不足 { return 0xFF; } else //分配成功 { U***_Buff.U***_Usart_Ready_Point.Read_Point=R; U***_Buff.U***_Usart_Ready_Point.Write_Point=W; return R_return; } } void free_U***(uint8_t index)//释放一个节点 { uint8_t R=(U***_Buff.U***_Usart_Ready_Point.Read_Point); uint8_t W=(U***_Buff.U***_Usart_Ready_Point.Write_Point); if(W==0xFF) //没有空闲节点的情况 { R=W=index; U***_Buff.inode[W].next=0xFF; U***_Buff.inode[W].string_len=0; } else //有空闲节点的情况,往下走 { U***_Buff.inode[W].next=index; W=U***_Buff.inode[W].next; U***_Buff.inode[W].next=0xFF; U***_Buff.inode[W].string_len=0; } U***_Buff.U***_Usart_Ready_Point.Read_Point=R; U***_Buff.U***_Usart_Ready_Point.Write_Point=W; } 现考虑一种情况:此时空闲队列里有一个节点[5],现进入free_U***()函数准备释放[6](空闲队列加入[6]),进入函数后复制一份R0,W0为RF,WF,接着进入else分支,WF下移到[6],还没走出else分支,这时malloc_USB()由于中断函数内,得意插队执行,但此时R0和W0均没有改变仍然指向[5],把[5]分配出去,R0=W0=0xFF,分配完成。接着中断恢复继续指向free_U***(),执行最后下面两句话,又对R0,W0赋值。照成空闲链表队列断裂.我也考虑过全部的Rx,Wx都换成指针,都仔细分析仍然有问题。而设定mlloc只操作R指针,Free只操作W指针,就像在R,W之间插入了一张隔板,避免了他们冲突,R指针就一直只能等于W或者后面 U***_Buff.U***_Usart_Ready_Point.Read_Point=R; U***_Buff.U***_Usart_Ready_Point.Write_Point=W; [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ovs1C9Uu-1611042352790)(https://raw.githubusercontent.com/casojie/blog_image/master/solo_blog/16110400841799.png)] 5.后续 问题还远远没有结束,由于有两个空闲队列,一共浪费了128B的空间,这在单片机上仅有的16K内存来说还是有点不应该,或许有其他更好的办法,希望大佬给给意义,之所以设计缓冲区,是因为USBCDC与USART的速率严重不对等,最后的办法还是对USBCDC进行速度控制,但是不会也没找到相关资料。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1198 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1211 浏览 1 评论
620 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
465 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1138 浏览 2 评论
1675浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
335浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
331浏览 3评论
325浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
301浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-8-31 20:14 , Processed in 0.713256 second(s), Total 75, Slave 58 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191