一、微项目实现目标:
1,通过STM32USART模块,实现HEX类型的数据包传送与接受;
2,数据传递过程:数据打包---发送---数据解包--根据数据解包,进入做出相应操作
关键在于状态机的建立
二、微项目硬件配置需求:
stm32F103C8T6核心板一块
0.96寸OLED显示,用于显示计数
CH340一块,用于笔记本到串口的传输
上位机接受CH340端口数据
三、前置知识:
1,状态机的设计与建立
①0xff为包头,在检测到0xff且状态为S=0时刻,进入状态机。
ps:在检测到0xff且状态为S!=0时刻,不能判定为包头开始,防止在接受的实际数据段存在0xff导致数据错误的情况;
②在S=1状态下,开始接受数据,并开始计数,当数据包内容计数达到4时刻,切换状态为S=2;
③在S=2状态下,如果等待到包尾,则一个完整的数据包接受完成,此时上传标志位,并且S=0,等待下一轮数据;
四、代码逻辑分析:
1,配置USART初始化模块
①打开USART1时钟,打开GPIO时钟
②配置PA9和PA10参数,其中tx配置为复用推挽输出,rx配置为上拉输入模式
③配置USART参数,包括:模式、硬件流控制、传送数据字长、数据位、校验位、停止位
④开启USART中断(准备在中断中进行接受数据包处理,接受数据时刻,触发中断)
⑤配置中断优先级分组
⑥配置NVIC
⑦开启USART
2,发送数据模块,并且封装为发送为一个数组。
①主要需要关注的是,发送一个数据位,需要等待发送寄存器为空,才认为数据已经发送;
②封装数组发送
3,发送一个数据包,发送数据包很简单;
发送包头---发送数组数据---发送包尾
4,接受数据包相对就复杂的多,在中断函数中进行配置
①中断函数配置USART1_IRQHandler,中断服务函数的名称一定要与启动文件中的要保持一致;
5,状态机数据流,在中断服务函数中执行
五、代码示例:
1,配置USART初始化模块
void serial_init(void)
{
//开启GPIO、USART 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//配置GPIO端口,仅使用TX端口,PA9配置为复用推挽输出端口
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,& GPIO_InitStruct);
//开启RX通道,上拉输入
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,& GPIO_InitStruct);
//配置usart
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate=9600;
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;//开启RX和TX
USART_InitStruct.USART_Parity=USART_Parity_No ;
USART_InitStruct.USART_StopBits=USART_StopBits_1;
USART_InitStruct.USART_WordLength=USART_WordLength_8b;
USART_Init( USART1, &USART_InitStruct);
//开启USART中断
USART_ITConfig( USART1,USART_IT_RXNE, ENABLE);
//配置NVIC优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//配置NVIC
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel=USART1_IRQn ;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStruct);
//开启USART
USART_Cmd(USART1,ENABLE);
}
2,发送数据模块,并且封装为发送为一个数组
void senddata(uint8_t Byte)
{
USART_SendData(USART1, Byte);
while(USART_GetFlagStatus( USART1,USART_FLAG_TXE)==RESET);
}
//发送一个数组
void sendArray(uint8_t *arr,uint8_t size)
{
uint8_t i;
for(i=0;i<size;i++)
{
senddata(arr[i]);
}
}
3,发送一个数据包,发送数据包很简单;
发送包头---发送数组数据---发送包尾
//发送一个数据包,0xff为包头,0xfe为包尾
void sendpacket(void)
{
senddata(0xff);
sendArray(send_data,4);
senddata(0xfe);
}
4,接受数据包相对就复杂的多,在中断函数中进行配置
①中断函数配置USART1_IRQHandler,中断服务函数的名称一定要与启动文件中的要保持一致;
5,状态机数据流,在中断服务函数中执行
/接受的中断服务函数
void USART1_IRQHandler(void)
{
static uint8_t re_state=0;
static uint8_t pdata=0;
//静态变量只会初始化一次,与全局变量类似,区别在于静态变量只能在函数内访问
if(USART_GetITStatus(USART1, USART_IT_RXNE)==SET)
{
uint8_t re_data=USART_ReceiveData(USART1);
//状态执行流
if(re_state==0 && re_data==0xff)
{
re_state=1;
}
else if(re_state==1)
{
receive_data[pdata++]=re_data;
if(pdata>=4)
{
pdata=0;
re_state=2;
}
}
else if(re_state==2)
{
if(re_data==0xfe)
{
re_state=0;
receive_flag=1;
}
}
USART_ClearITPendingBit( USART1,USART_IT_RXNE );
}
}
在主函数中进行相应配置
#include "stm32f10x.h" // Device header
#include "delay.h"
#include "OLED.H"
#include "SERIAL.H"
#include <cstdio>
#include "key.H"
uint8_t keynum;
int main()
{
OLED_Init();
keyinit();
OLED_ShowString(1,1,"send_data:");
serial_init();
send_data[0]=0x01;
send_data[1]=0x02;
send_data[2]=0x03;
send_data[3]=0x04;
OLED_ShowString(3,1,"receive_data:");
while(1)
{
keynum=key_getnum();
if(keynum==1)
{
sendpacket();
OLED_ShowHexNum(2,1,send_data[0],2);
OLED_ShowHexNum(2,4,send_data[1],2);
OLED_ShowHexNum(2,7,send_data[2],2);
OLED_ShowHexNum(2,10,send_data[3],2);
send_data[0]++;
send_data[1]++;
send_data[2]++;
send_data[3]++;
}
if(receive_flag==1)
{
OLED_ShowHexNum(4,1,receive_data[0],2);
OLED_ShowHexNum(4,4,receive_data[1],2);
OLED_ShowHexNum(4,7,receive_data[2],2);
OLED_ShowHexNum(4,10,receive_data[3],2);
receive_flag=0;
}
}
}