第十一次培训:接着学习程序,并整理数据传输的流程

这篇博客主要介绍了STM32程序学习,包括协议结构体定义、数据包格式和处理流程。讲解了如何接收和发送数据,中断的禁止和使能,以及中断处理例程的限制。同时,提到了串口空闲检测中断在数据接收完成判断中的应用。
摘要由CSDN通过智能技术生成

代码在电脑上的位置

程序相关

可以参考:第十次培训任务:C语言在实际中的应用以及补充

protocol_v1.0.1的程序学习及STM32数据串口的数据发送和接收学习的笔记
在整个protocol_v1.0.1最开始是对协议中的数据进行宏定义和结构体定义:
#define GPRS_PRO_SEQ_LEN 2 宏定义消息流水号
#define GPRS_PRO_DEV_ID_LEN 7 设备ID的长度
#define GPRS_PRO_CMD_LEN 2 功能ID的长度
#define GPRS_PRO_DEVICE_ID 7 设备ID
#define GPRS_PRO_BASE_LEN 17 基础数据的长度(除了数据段以外的所有长度之和)

//协议结构体定义
typedef struct
{
  u16 seq_num;
  u16 len;
  u8 dev_id[GPRS_PRO_DEVICE_ID];

//:GPRS 协议数据包主要用于终端与服务器网关之间的数据通信约定,设备ID
  u16 command_id;
  u8 *pro_data;
}gprs_pro_cmd_t;


static const gprs_pro_menu_t Gprs_protocol_menu[] =        //static const 类型  函数名
{
  {0x8000, gprs_pro_ack_ServerCommonlyAck},
  {0x8002, gprs_pro_ack_TeConnect},
  {0x8004,gprs_pro_ack_TimeSynch},
  {0xFFFF, NULL}
};


static const gprs_pro_menu_t Gprs_protocol_menu[] =
{
  {0x8000, gprs_pro_ack_ServerCommonlyAck},              //对应命令ID(0x8000/2/4)执行函数
  {0x8002, gprs_pro_ack_TeConnect},
  {0x8004,gprs_pro_ack_TimeSynch},
  {0xFFFF, NULL}
};


Wifi.begin()的参数为字符串数组或指针变量,在Arduino里,String是一个类,不是字符串变量类型。如果定义的SSIDName是一个对象。当然不能被Wifi.begin()接受了。

对象和类是什么:通俗的例子,找对象。首先告诉编译器你想要对象,于是编译器就给你个对象的集合(类)让你选,你根据想要的属性(类的属性,需要定义)去定义。例如:LED类中的LED()就好比你挑对象之前,先告诉编译器你对对象有啥要求,即用来初始化对象的属性。然后你创建方法。

LED led(13);   //类创建完之后,对象你就大约有∞多个了,这就好比男(LED,类名),XX(led,对象),XX岁((13),初始化对象的属性;所以这句话就是说,我要创建个led对象,它属于LED类(具有LED类的所有属性和方法);然后这个对象引脚号为13;


if (strcmp(rece_data, "@@") == 0)
    {
      data = pro_protocol_analysis((u8*)&rece_data,tempstr.length)
    }

描述

C 库函数 int strcmp(const char *str1, const char *str2)str1 所指向的字符串和 str2 所指向的字符串进行比较。

声明

下面是 strcmp() 函数的声明。

int strcmp(const char *str1, const char *str2)

参数

  • str1 -- 要进行比较的第一个字符串。
  • str2 -- 要进行比较的第二个字符串。

返回值

该函数返回值如下:

  • 如果返回值小于 0,则表示 str1 小于 str2。
  • 如果返回值大于 0,则表示 str1 大于 str2。
  • 如果返回值等于 0,则表示 str1 等于 str2。

数据包格式说明 消息头: 固定两个字符“@@”,HEX 表示为 40h 40h;这一步是判断数据里面有没有消息头。


  //event:
  event.size = (17 +com->len);    
  event.buff = malloc(event.size);
  if(NULL == event.buff)
  {
    printf("Pro package can't malloc!\r\n");
    return;
  }

为event分配空间。初始化配置

描述

C 库函数 void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。

声明

下面是 malloc() 函数的声明。

void *malloc(size_t size)

参数

  • size -- 内存块的大小,以字节为单位。

返回值

该函数返回一个指针 ,指向已分配大小的内存。如果请求失败,则返回 NULL。


  //event:
  event.size = (17 +com->len);    
  event.buff = malloc(event.size);
  if(NULL == event.buff)
  {
    printf("Pro package can't malloc!\r\n");
    return;
  }
  com_para = event.buff; //
  
  //head:
  com_para[0] = 0x40;
  com_para[1] = 0x40;
  com_para += 2;

 分配空间,buff的分配情况用com_para表示

//head: 
  com_para[0] = 0x40;           //0x40=40H,是二进制的0100 0000
  com_para[1] = 0x40;
  com_para += 2;


  //len:
  crc16 = com->len +GPRS_PRO_SEQ_LEN +GPRS_PRO_DEV_ID_LEN +GPRS_PRO_CMD_LEN;
  memcpy(com_para, (u8*)&crc16, 2); 
  buff_overturn(com_para,2);   //待定处理
  com_para += 2;

对信息进行crc16校验

描述

C 库函数 void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个字节到存储区 str1

声明

下面是 memcpy() 函数的声明。

void *memcpy(void *str1, const void *str2, size_t n)

参数

  • str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
  • str2 -- 指向要复制的数据源,类型强制转换为 void* 指针。
  • n -- 要被复制的字节数。

返回值

该函数返回一个指向目标存储区 str1 的指针。


  //send:
  wifi.Send(event.buff,event.size);  //待定处理
  free(event.buff,event.size);

执行函数后释放内存

描述

C 库函数 void free(void *ptr) 释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。

声明

下面是 free() 函数的声明。

void free(void *ptr)

参数

  • ptr -- 指针指向一个要释放内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果传递的参数是一个空指针,则不会执行任何动作。

返回值

该函数不返回任何值。


流程相关

一般流程:定义GPIO、串口、中断结构体。

#include "stm32f4xx.h"
#include "usart.h"


void My_USART1_Init(void)
{

     //GPIO结构体定义
    GPIO_InitTypeDef  GPIO_InitStructure;

     //串口结构体定义
    USART_InitTypeDef USART_InitStructure;

     //中断结构体定义
    NVIC_InitTypeDef NVIC_InitStructure;
    //使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//使能GPIOA时钟

    //引脚映射

    GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);

    //端口初始化

    //GPIOA9端口初始化结构体参数设置

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;

    //GPIOA9端口初始化
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //GPIOA10端口初始化结构体参数设置

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;

    //GPIOA10端口初始化
    GPIO_Init(GPIOA, &GPIO_InitStructure);

     //串口初始化结构体参数设置

    USART_InitStructure.USART_BaudRate=115200;
    USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
    USART_InitStructure.USART_Parity=USART_Parity_No;
    USART_InitStructure.USART_StopBits=USART_StopBits_1;
    USART_InitStructure.USART_WordLength=USART_WordLength_8b;

    //串口初始化
    USART_Init(USART1,&USART_InitStructure);

    //串口使能
    USART_Cmd(USART1 ,ENABLE);
    //中断使能
    USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//接收非空使能

    //中断初始化结构体参数设置
    NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;

    //中断初始化
    NVIC_Init(&NVIC_InitStructure);
}

void USART1_IRQHandler(void)
{
    u8 res;

    //判断串口1
    if(USART_GetITStatus(USART1,USART_IT_RXNE)){

        //从串口1是否发生接收中断
        res=USART_ReceiveData(USART1);

        //通过串口1发送数据
        USART_SendData(USART1,res);
    }

}
int main(void)
{

    //中断分组使能
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    //串口初始化
    My_USART1_Init();
    while(1);
}

中断的禁止和使能以及处理例程

中断使能的使用

中断处理器例程即中断处理函数,其作用是根据被服务的中断的含义进行适当的操作,通常是读、写数据。中断处理例程与普通C函数没有什么差别,但是中断处理装置有以下限制:

中断处理装置不能与用户空间传递数据,因为它不在进程上下文执行;

中断处理程序也不能做任何可能休眠的事情,例如,调用wait_event(),除了使用GFP_ATOMIC之外的任何东西来分配内存,或者锁住一个信号量;

中断处理例程不能调用schedule()。

接受数据:

参考文章:1.STM32 串口接收不定长数据 STM32 USART空闲检测中断

2.STM32串口发送数据和接收数据方式总结

多个串口发送,代码模板

串口发送数据:

       1. 串口发送数据最直接的方式就是标准调用库函数 。 void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
第一个参数是发送的串口号,第二个参数是要发送的数据了。但是用过的朋友应该觉得不好用,一次只能发送单个字符,所以我们有必要根据这个函数加以扩展。

void Send_data(u8 *s)
{
	while(*s!='\0')
	{ 
		while(USART_GetFlagStatus(USART1,USART_FLAG_TC )==RESET);	
		USART_SendData(USART1,*s);
		s++;
	}
}

这里通过循环调用USART_SendData来一 一发送我们的字符串。 

while(USART_GetFlagStatus(USART1,USART_FLAG_TC )==RESET);

这句话有必要加,他是用于检查串口是否发送完成的标志,如果不加这句话会发生数据丢失的情况。这个函数只能用于串口1发送。多个串口发送那么就还需要改进这个程序:

void Send_data(USART_TypeDef * USARTx,u8 *s)
{
	while(*s!='\0')
	{ 
		while(USART_GetFlagStatus(USARTx,USART_FLAG_TC )==RESET);	
		USART_SendData(USARTx,*s);
		s++;
	}
}

 这样就可实现任意的串口发送。但有一点,在使用实时操作系统的时候(如UCOS,Freertos等),需考虑函数重入的问题。当然也可以简单的实现把该函数复制一下,然后修改串口号也可以避免该问题。然而这个函数不能像printf那样传递多个参数,所以还可以在改进,最终程序如下:

void USART_printf ( USART_TypeDef * USARTx, char * Data, ... )
{
	const char *s;
	int d;   
	char buf[16];
	
	va_list ap;
	va_start(ap, Data);
 
	while ( * Data != 0 )     // 判断是否到达字符串结束符
	{				                          
		if ( * Data == 0x5c )  //'\'
		{									  
			switch ( *++Data )//*++Data= *(Data++),Data指向下一个字节
			{
				case 'r':							          //回车符
				USART_SendData(USARTx, 0x0d);//0x0d就是回车符
				Data ++;
				break;
 
				case 'n':							          //换行符
				USART_SendData(USARTx, 0x0a);//0x0a就是换行符	
				Data ++;
				break;
 
				default:
				Data ++;
				break;
			}			 
		}
		
		else if ( * Data == '%')
		{									  //
			switch ( *++Data )
			{				
				case 's':										  //字符串
				s = va_arg(ap, const char *);
				
				for ( ; *s; s++) 
				{
					USART_SendData(USARTx,*s);
					while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );
				}
				
				Data++;
				
				break;
 
				case 'd':			
					//十进制
				d = va_arg(ap, int);
				
				itoa(d, buf, 10);
//itoa (表示 integer to alphanumeric)是把整型数转换成字符串的一个函数。
//char* itoa(int value,char*string,int radix);//value: 要转换的整数,string: 转换后的字符
串,radix: 转换进制数,如2,8,10,16 进制等。
				
				for (s = buf; *s; s++) 
				{
					USART_SendData(USARTx,*s);
					while( USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET );
				}
				
				Data++;
				
				break;
				
				default:
				Data++;
				
				break;
				
			}		 
		}
		
		else USART_SendData(USARTx, *Data++);
		
		while ( USART_GetFlagStatus ( USARTx, USART_FLAG_TXE ) == RESET );
		
	}
}

解释:

*++Data相当于*(data++),并且data指向下一个字节

 

 

判断这一包数据接收完成:

1.在接收数据时启动一个定时器,在指定时间间隔内没有接收到新数据,认为数据接收完成;2.在数据中加入帧头、帧尾,通过在程序中判断是否接收到帧尾来确定数据接收完毕。这两种方法的缺点为,需要主程序来判断和处理,对主程序造成不小压力。

STM32单片机空闲检测中断可以很好的解决这个问题。他的工作原理为:

当STM32的串口接收完一包数据后,会产生一个空闲中断。这个中断在串口其他任何状态都不产生,只会在接收完一包数据后才会产生,一包数据可以是1个字节或者多个字节。因此,我们可以在这个空闲中断函数中,设置一个接收完成标志位。那么,我们只需要在主程序中检测这个标志位就知道数据是否接收完成了。具体应该怎么操作呢?其他不表,直接上代码:

  下面是主程序和串口中断函数

先来看第30-36行的中断函数内容,首先是把接收到的字节存到rx_buff中,并且数据长度rx_cnt++,接着调用库函数清除接收中断标志位,属于常规的数据接收操作。

不同的是第38-46行:

判断是不是产生了串口空闲中断(USART_IT_IDLE),然后就是置位接收完成标志位rx_done = 1,并且清除空闲中断标志位。

注意事项:

调用库函数USART_ClearITPendingBit(DEBUG_USARTx, USART_IT_IDLE);是不会清除空闲中断标志位的。应该采用42-43两条语句实现,否则会一直进入中断函数。

temp = USART1->SR; //先读SR,再读DR才能完成idle中断的清零,否则一直进入中断。
temp = USART1->DR;

接收的模板:

		if(USART_RX_STA&0x8000)   // 判断标志位,已经有数据接收到,可以读取出来。
		{				 	   
			printf("USART_RX_STA02=%d\r\n",USART_RX_STA);
			len=USART_RX_STA&0x3fff;//接收的数据长度
			printf("len=%d\r\n",len);
			
			printf("\r\n你发送的消息:\r\n");
			if(USART_RX_BUF[0]=='e')  LED1=!LED1;  //判断接收的消息
			
			for(t=0;t<len;t++)
			{
				//USART_SendData(USART1, USART_RX_BUF[t]);         //向串口1 发送数据
				USART_SendData(USART1, 5465);   
				while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//表示发送完成
			}
			printf("\r\n\r\n");//换行
			USART_RX_STA=0;
		}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值