本文拿我当初做了一个共享设备为例,最开始用的硬件是stm32f1+sim800(2g),这两个应该是国内做共享设备最普遍的组合了,因为据说联通2G快淘汰了,如果想用4g的sim7600,但是这也不影响AT指令使用,大家也可以用NBIOT(SIM7000)或者移远的.

1,stm32f1串口使用(中断+DMA接受 与 DMA发送)
stm32的串口大家可能都会用,但是还是有很多小白不会使用接受不定长数据,接受的最好方式就是(中断+DMA),配置就是基础的GPIO口配置,串口配置,DMA配置 这里不做多说,网上有太多DEMO。
接受中断最好用空闲中断(USART_IT_IDLE),中断内部先DMA失能,然后再DMA使能,目的是为了获得发来数据及数据长度。 以串口3为例(因为本人串口1用来debug,串口2用来传数据帧了)。
建立 串口接受缓冲区(全局变量) u8 USART3_RX_BUF[USART3_MAX_RECV_LEN];

//刚才提到的配置
UART_DMA_Config(DMA1_Channel3,(u32)&USART3->DR,(u32)USART3_RX_BUF,DMA_DIR_PeripheralSRC);
if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET)
{
DataLen=USART3_MAX_RECV_LEN-DMA_GetCurrDataCounter(DMA1_Channel3);
/*
DataLen就是接受数据的长度,USART3_RX_BUF就是接受的缓冲区
*/
DMA1_Channel3->CNDTR = USART3_MAX_RECV_LEN;//充填
}

在中断函数末尾,别忘了USART3->SR与USART3->DR还有各种清楚故障标志位的。

2,数据接受处理(循坏队列)
AT指令最重要的就是提取有效并且最准确的数据,但是接受到的数据很有可能是不及时的,粘包的,快速的(还没处理完就发过来了),甚至我还遇见过一个指令在另一条指令的中间的奇葩事件,粘包和快速数据,粘包和快速数据的处理方法是采用循环队列,首先我选择了使用中断判断后进入两个不同的缓冲区,然后程序执行过程中轮询取结果即可。为什么取两个呢,因为一个用作与AT模块的AT指令通信,一个用作接受发送方的数据缓存。
(1)判断用if(strstr((char *)USART3_RX_BUF,"+IPD"))即可,“+IPD”的意思就是这个指令接下来的数据是发送方发的数据而并非是模块的简单AT交互,因为发送方(本项目是服务器),那么有这个“+IPD”的就是服务器数据,保留在新的缓存区,没有“+IPD”的继续放在USART3_RX_BUF里即可。
(2)与simcom模块AT的交互指令分为两种,第一种是及时接受(也就是你发送它就马上接受),第二种是等一会儿才能接受,第一种用硬延时即可,第二种需要定时器中断加上标志位,发送此指令,设立相应标志位,程序顺次执行其他命令,如果计时结束还没有在USART3_RX_BUF中发现此结果,采取重发机制。
(3)服务器发来的数据是最重要的,也就是"+IPD"数据,因为简单的模块AT交互指令没有接收到“OK”,继续发送即可,但是如果服务器数据没有接收到会影响很多,因为相当于控制失效,是个很严重的后果,所以确保服务器数据能及时发送并且解析成功,我采用了循环队列,判断是服务器数据入列EnQueue(ATtemp ,&ATline);,需要解析服务器数据时出列DeQueue(&ATtemp ,&ATline);。
附(C Primer Plus 队列代码)

#include "Ringqueue.h"
#include "malloc.h"
static void CopyToNode(Item item, Node *pn);
static void CopyToItem(Node *pn, Item *pi);
void InitializeQueue(Queue *pq)
{pq->front = pq->rear = NULL;pq->items = 0;
}
bool QueueIsFull(const Queue *pq)
{return pq->items == MAXQUEUE;
}
bool QueueIsEmpty(const Queue *pq)
{return pq->items ==0;
}int QueueItemCount(const Queue *pq)
{return  pq->items;
}   bool EnQueue(Item item, Queue *pq)
{Node *pnew;if(QueueIsFull(pq))return false;pnew = (Node*)mymalloc(sizeof(Node));CopyToNode(item, pnew);pnew->next = NULL;if (QueueIsEmpty(pq))pq->front = pnew;       /*项位于队列的首端*/elsepq->rear->next=pnew;    /*项位于队列的尾端*/pq->rear = pnew;            /*记录队列尾端的位置*/pq->items++;                /*队列项数加1*/return true;}
/*出队列应该判断是否粘包*/
bool DeQueue(Item *pitem, Queue *pq)
{Node* pt;if(QueueIsEmpty(pq))return false;CopyToItem(pq->front, pitem); // *pitem=pq->front->itempt = pq->front;pq->front =pq->front->next;myfree(pt);pq->items--;if(pq->items == 0)pq->rear = NULL;return true;}
void EmptyTheQueue(Queue *pq)
{Item dummy;while(!QueueIsEmpty(pq))DeQueue(&dummy, pq);}static void CopyToNode(Item item, Node *pn)
{pn->item = item;
//    memcpy(pn->item.item_buf,item.item_buf,200);
//    pn->item.item_sendlen= item.item_sendlen;}
static void CopyToItem(Node *pn, Item *pi)
{*pi =pn->item;
}

这个是利用链表方式组成循环队列,因为服务器粘包最多为两包,所以当判断为粘包时,将粘包的前包进行处理,后包截取到另一个数据帧即可,利用标志位先判断是否发生粘包,下一次处理数据时先判断粘包标志位,如果发生粘包就先处理上一次粘包截取后的后包。(因为这段代码涉及公司业务,就不粘贴出来了)。
还有一种方式是利用大数组来组成循环队列,这个数组方式其实更适合管理服务器发来的数据,只要根据帧头及帧的数据长度即可。

3,发送AT指令方法
通常AT指令的发送函数都是三个形参,AT命令(u8 *cmd),AT应答(u8 *ack),等待时间(u16 waittime)
类似u8 SIM800_Send_Cmd(u8 *cmd,u8 *ack,u16 waittime);其实还可以设置两个AT应答ack1 和ack2,判断的话用接受到的缓冲区是否同时含有这两个字符串,这个也不难在下面demo改改就可以。

u8 SIM800_Send_Cmd(u8 *cmd,u8 *ack,u16 waittime)
{u8 ret = CMD_ACK_NONE; //放在下面还是这里合适???//dev.msg_recv &= ~MSG_DEV_ACK;    if(ack!=NULL){//新的一次发送开始,需要把之前recv 的ack 状态清除掉//dev.msg_recv = 0;dev.msg_expect |= MSG_DEV_ACK;memset(dev.atcmd_ack, 0, sizeof(dev.atcmd_ack));strcpy(dev.atcmd_ack, (char *)ack);}    //Clear_Usart3();     //放下面还是放在这里合适if((u32)cmd <= 0XFF){while((USART3->SR&0X40)==0);//等待上一次数据发送完成  USART3->DR = (u32)cmd;}else {u3_printf("%s\r\n",cmd);//发送命令}//Clear_Usart3(); //放上面还是放在这里合适if(ack&&waittime)      //需要等待应答{while(waittime!=0)    //等待倒计时{ delay_ms(10);  //if(dev.msg_recv & MSG_DEV_RESET)if(dev.need_reset != ERR_NONE){ret = CMD_ACK_DISCONN;break;}//IDLE 是指串口同时收到"SEND OK" + "正确的服务器回文",在//定时器处理中已经将设备状态转换为IDLE 状态//else if((dev.msg_recv & MSG_DEV_ACK) && ((dev.status == CMD_IDLE) || (dev.status == CMD_OPEN_DEVICE)))else if(dev.msg_recv & MSG_DEV_ACK){ret = CMD_ACK_OK;dev.msg_recv &= ~MSG_DEV_ACK;break;}             waittime--; }}else   //不需要等待应答,这里暂时不添加相关的处理代码{;}return ret;
} void u3_printf(char* fmt,...)
{  u16 i,j; va_list ap; va_start(ap,fmt);memset(USART3_TX_BUF, 0, USART3_MAX_SEND_LEN); vsprintf((char*)USART3_TX_BUF,fmt,ap);va_end(ap);i=strlen((const char*)USART3_TX_BUF);     //此次发送数据的长度BSP_Printf("S: %s\r\n", USART3_TX_BUF);for(j=0;j<i;j++)                          //循环发送数据{while(USART_GetFlagStatus(USART3,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕   USART_SendData(USART3,USART3_TX_BUF[j]); }
}

建立个USART3_TX_BUF[]即可,剩下的就是把 USART3_RX_BUF接收到的数据,经过处理再用USART3_TX_BUF发出去即可。处理逻辑时,我用的是状态机,状态机可以自行百度一下。因为这种物联设备都需要OTA固件升级,stm32正好也带IAP功能,当时就用http协议下载程序调试成功了,还有mqtt功能,这些以后有空补上吧。邮箱:1543323154@qq.com

AT指令(嵌入式+物联网)编程心得C语言相关推荐

  1. c语言嵌入式系统编程软件,C语言嵌入式系统编程软件设计研究论文

    C语言嵌入式系统编程软件设计研究论文 摘要:近年来,C语言编程在嵌入式系统越来越受到广大技术人员的青睐.介绍了C语言系统软件的编程思路,阐述了嵌入式系统编程软件架构的基本知识,包括模块划分.分层架构. ...

  2. c语言编程心得,C语言编程心得

    记录这些是为了日后自己想查阅以前经验的方便,同时若能给其他网友带来一些帮助,就更好了~ C语言,自己经常遇到的问题: 1.段错误 段错误一般是由于访问了不存在的地址造成的,具体的原因有文件路径不存在, ...

  3. C语言嵌入式系统编程修炼(经典中的经典)

    C语言嵌入式系统编程修炼      http://blog.chinaunix.net/u/25764/showart_326589.html转载自这里,真是太经典了. C语言嵌入式系统编程修炼   ...

  4. 浅谈C语言嵌入式系统编程注意事项

    C语言嵌入式系统编程注意事项之背景篇   本文的讨论主要围绕以通用处理器为中心的协议处理模块进行,因为它更多地牵涉到具体的C语言编程技巧 不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上 ...

  5. C语言在嵌入式系统编程时的注意事项

    C语言是一门通用计算机编程语言,应用广泛.C语言的设计目标是提供一种能以简易的方式编译.处理低级存储器.产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言. 尽管C语言提供了许多低级处理的功 ...

  6. C语言嵌入式系统编程

    不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力.无疑,汇编语言具备这样的特质.但是,归因于汇编语言开发过程的复杂性,它并不是嵌入式系统开发 ...

  7. C语言嵌入式系统编程注意事项

    不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力.无疑, 汇编语言 具备这样的特质.但是,归因于汇编语言开发过程的复杂性,它并不是 嵌入式系 ...

  8. 使用Keil语言的嵌入式C编程教程(上)

    使用Keil语言的嵌入式C编程教程(上) Embedded C Programming Tutorial with Keil Language Embedded System 嵌入式系统是指以单片机为 ...

  9. 编写书籍《C语言嵌入式系统编程修炼之道》序言

    序言        目前,嵌入式系统已经无处不在,遍布于世界的每一个角落.智能家电.手机.PDA.汽车.通信电台等几乎所有的电力.电器与电子产品都包含一个或多个嵌入式系统.有人的地方就有江湖,有电的地 ...

最新文章

  1. 英文谚语:Take that with a grain of salt
  2. Perfect Permutation
  3. C语言求最大公约数欧几里得Euclid算法(附完整源码)
  4. 为什么写Java程序需要接口
  5. 10行代码AC——1016 部分A+B (15分)
  6. renameto 阻塞_打造简化版文件下载器
  7. mq集群要建传输队列吗_面试官:消息队列这些我必问!
  8. ViewState笔记
  9. win7下用docker部署的基于openvino的yolov5算法(一)docker与win文件夹共享
  10. 阿里、美团、滴滴产品经理共述:产品经理的“乐”与“伤”
  11. 机器学习实现计算不规则图形面积_【名师课堂】苏教数学五年级上2.11校园绿地面积...
  12. 小知识--windows下的电脑关机注销等命令
  13. php 漏洞扫描,10个最佳PHP代码安全扫描程序来查找漏洞
  14. 红帆参加HiMSS 2010(2010亚太区卫生信息大会)
  15. java实现excel导出功能
  16. ArcGIS基础知识之shape文件的构成
  17. 云场景实践研究第81期:大搜车
  18. Listary - 文件浏览与搜索增强的超级神器
  19. JDBC编程(JDBC的使用)
  20. 谁是中国手机市场第一重要吗?

热门文章

  1. 【C4AI-2022】基于飞桨的跨平台智慧农业遥感监测平台
  2. 会员系统小程序开发制作功能使用方法
  3. 【Proteus仿真】555振荡电路+CD4017流水灯(频率可调)
  4. springboot整合swagger2,附带源码
  5. oracle删除字段速度,oracle删除字段
  6. Excel多个空白单元统一复制上方单元内容的操作
  7. undefined symbol: _ZN6caffe26detail36_typeMetaDataInstance_preallocated_7E
  8. jquery 添加transform样式
  9. 实验五 数码管扫描显示电路
  10. Linux 下值得使用的 5 个屏幕录像软件 [zt]