[开源硬件] 自制简易电子秤(第三篇——串口DMA)

[复制链接]
3262|1
 楼主| Andrew55 发表于 2018-7-25 15:38 | 显示全部楼层 |阅读模式
很多事情,乍一看非常的简单,但是当真正实际动手做的时候,才发现会有各种需要解决的问题。而且每个功能需求实现起来其实并不难,难的是把这么多功能集成起来,并且系统能够稳定的运行。


所以我也很佩服那些能在裸机上真正做出一个完整系统并稳定运行的大佬。
小弟这里只是发表一下个人的一点学习体会,可能会有错误的地方。欢迎各位大佬批评指正交流!

今天想写一下在做这个电子秤的时候遇到的串口收发数据的问题。

关于电子秤的显示屏我使用的是HMI智能串口屏。这个屏幕使用起来很简单,显示效果还是不错的,并且支持触控操作
使用商家提供的上位机能够快速定制自己喜欢的显示界面 。
下面是我自己制作的界面,有点简陋:


这个串口屏的原理是当你按下显示屏上面的某一个按钮时,串口屏会通过串口以字符串的方式向接收机发送预先定义好的指令




由于用户按下按钮的随机性,所以有时会出现串口收发冲突的问题,导致程序卡死,或者主控接收不到数据。
也因为数据一直处于发送状态,如果采用中断收发的话,太占用系统资源。
所以我采用中断方式发送,DMA方式接收。避免了收发的冲突


串口DMA配置:

  1. /*---------------------------------
  2. ** 名称:USART_RX_DMA_Config
  3. ** 功能:配置串口DMA接收
  4. ** 入口:无
  5. ** 出口:无
  6. ** 说明:串口1的RX接在DMA的通道5上面
  7. ----------------------------------*/
  8. static void USART_RX_DMA_Config(void)
  9. {
  10.         DMA_InitTypeDef DMA_InitStructure;
  11.         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);    //使能DMA传输
  12.        
  13.         DMA_DeInit(DMA1_Channel5); //将DMA的通道5寄存器重设为缺省值
  14.        
  15.         DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;         //DMA外设基地址
  16.         DMA_InitStructure.DMA_MemoryBaseAddr = (u32)USART_RX_Buf;                 //DMA内存基地址
  17.         DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                                 //数据传输方向,外设读取到内存
  18.         DMA_InitStructure.DMA_BufferSize = DMA_Max_Receive_Len;                                        //DMA通道的DMA缓存的大小
  19.         DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
  20.         DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                         //内存地址寄存器递增
  21.         DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
  22.         DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
  23.         DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                                 //工作在正常模式
  24.         DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //DMA通道 x拥有中优先级
  25.         DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
  26.         DMA_Init(DMA1_Channel5, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Rx_DMA 所标识的寄存器
  27.        
  28.         //先使能DMA,在开启串口,防止上电时第一次无法传输数据。
  29.         DMA_Cmd(DMA1_Channel5, ENABLE);                                 //使能USART1 TX DMA1 所指示的通道
  30.         USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);                 //使能串口1的DMA发送
  31.         USART_Cmd(USART1, ENABLE); //使能串口1
  32.        
  33.         DMA_Start();//开始一次DMA传输
  34. }
写到这里又想到DMA通道的优先级问题。
因为我这个系统里面还有一个DMA通道用于了电压采集ADC数据的传输。
当同时出现两个DMA通道的时候,就需要考虑优先级的配置了。
这也是导致之前我数据一直无法接收到的问题。因为之前我串口的DMA通道优先级低于ADC的DMA优先级,导致DMA一直无法搬运串口缓冲区的的数据。
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //DMA通道 x拥有中优先级
我把串口的DMA的优先级设到了最高。

这样我就可以在主循环里面循环检测下面这个函数的返回值,就可以知道串口DMA的缓冲区有没有数据,从而判断用户有没有按下按钮:
  1. /*----------------------------------
  2. ** 名称:USART_DMA_Receive
  3. ** 功能:检测DMA缓冲区是否接收到有效数据
  4. ** 入口:无
  5. ** 出口:无
  6. ** 说明:在主函数中循环检测DMA缓冲区是否有数据
  7. ----------------------------------*/
  8. unsigned char USART_DMA_Receive(void)
  9. {               
  10.         if(USART_RX_Buf[0])
  11.         {
  12.                 //计算接收到数据的长度
  13.                 DMA_Receive_LEN = DMA_Max_Receive_Len - DMA_GetCurrDataCounter(DMA1_Channel5);
  14.                
  15.                 //接收的数据拷贝到缓冲区
  16.                 memcpy(CmdRx_Buffer,USART_RX_Buf,DMA_Receive_LEN);
  17.                 CmdRx_Buffer[DMA_Receive_LEN] = '\0';
  18.                
  19.                 //清空缓冲,并开始下一次的接收
  20.                 memset(USART_RX_Buf,0,DMA_Max_Receive_Len);
  21.                 DMA_Start();
  22.                 return 1;
  23.         }
  24.         else
  25.                 return 0;
  26. }
这样即避免了系统一直进入串口接收中断,导致其他的一些时序通信被打断的问题,也提高了系统的实时性。
只需要在主循环中执行如下语句即可:
  1.   //查看DMA缓冲区有无数据
  2.                 if(USART_DMA_Receive() == 1)
  3.                         HMI_Receive();
如果接收到数据的话,才会进入对指令的判断函数。
其中HMI_Receive();函数就是一系列的switch   case  进行按钮对应的功能切换。

经过一番调试,最终实现了这种双向的串口收发功能。
当用户在触摸屏上按下对应的功能按钮时,系统进入相应的函数执行。

简单的说两个功能的实现:
1、例如电子秤的去皮按钮:
就是让当前称盘上的重量清零,那么我读取当前重量的ADC,并且作为以后称重的基础重量就可以了。
关键在于当前重量一定要读的精确,以后的重量数据才能精确。读取5次取平均值。
  1. /*---------------------------------
  2. ** 名称:remove_empty_weight
  3. ** 功能:去皮重功能,其实就是重新更新一下 empty_coe 参数
  4. ** 入口:无
  5. ** 出口:无
  6. ** 说明:题目要求去皮重量不超过100g
  7. ----------------------------------*/
  8. void remove_empty_weight(void)
  9. {
  10.         unsigned char remove_filter_num =  2;
  11.         unsigned long int temp = 0;
  12.        
  13.         if(now_weight < 100)
  14.         {
  15.                 while(remove_filter_num--)
  16.                 {
  17.                         temp += CS1237_Read_18bit_ADC();
  18.                 }       
  19.                 temp = temp / 2;
  20.                 empty_coe = Weight_Coe_100g * temp;
  21.         }
  22.         //去皮重量超过100g,给出警告
  23.         else
  24.                 Beep_Warning(2,50);
  25. }


2、查看明细功能。
其实就是在之前将每一次金额数据保存到数组里,当需要查看明细的时候,依次将这些金额数据发送到屏幕上面即可,在发送的同时计算一下总金额:
  1.   //查看明细p
  2.                 case 0x70:
  3.                         money_buf[7] = 0;
  4.                         Beep_Warning(1,100);
  5.                         for(i = 0; i<5; i++)
  6.                         {
  7.                                 sprintf(buf,"t%d.txt="%.1f"", i+9, money_buf[i]);        //发送之前各个商品的价格
  8.                                 HMI_Send_String(buf);
  9.                                 money_buf[7]+= money_buf[i];
  10.                         }       
  11.                         sprintf(buf,"t6.txt="%.1f"",money_buf[7]);        //合计总价并发送
  12.                         HMI_Send_String(buf);
  13.                         break;


今天就更新这么多吧。
明天说一下系统低功耗的设计实现。欢迎大家继续关注。
因为作者水平有限,以上难免有错误的地方。欢迎各位朋友共同讨论学习。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
小小电子爱好者 发表于 2018-7-25 16:09 | 显示全部楼层
来个沙发    赞赞赞
您需要登录后才可以回帖 登录 | 注册

本版积分规则

9

主题

20

帖子

4

粉丝
快速回复 在线客服 返回列表 返回顶部