代码在电脑上的位置
程序相关
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空闲检测中断
多个串口发送,代码模板
串口发送数据:
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;
}