STM32F103学习笔记之串口简单指令模式,便于调试
指令模式前提
1. SysTick定时器
2.串口中断
3.指令对应功能的配置
之前学习51单片机是在C语言中文网看到有50单片机的串口指令模式的教程,所以我在STM32上要做了一个,指令模式有个好处,就是之后方便调试各项功能。
前提
1. SysTick定时器
#include "def.h"
__IO uint32_t mTime;
/*函数名:Deley_Init
功 能:初始化SysTick定时器
备 注:调用函数 SysTick_Config(uint32_t ticks) (默认不分频)
该函数作用:1.初始化SysTick (uint32_t ticks为重装值)
2.打开SysTick
3.打开SysTick中断并设置中断优先级(最低)
4.返回一个值(0代表成功 1代表失败)
调用函数 SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
该函数作用:配置时钟来源(注意调用顺序 先调用SysTick_Config(uint32_t ticks)
再调用SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)) */
void Deley_Init(void){
SysTick_Config(9000);
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
}
/*函数名:Deley
功 能:延时函数
备 注:调用即可精准延时*/
void Deley(__IO uint32_t time){
mTime=time;
while(mTime > 0);
}
/*函数名:Deley_D
功 能:
备 注:在中断函数SysTick_Handler(void)中调用本函数,
SysTick_Handler(void) 函数在源文件stm32f10x_it中*/
void Deley_D(void){
if(mTime>0)
mTime--;
}
头文件:
#ifndef __DEF_H
#define __DEF_H
#include "stm32f10x.h"
void Deley_Init(void);
void Deley(__IO uint32_t time);
void Deley_D(void);
#endif
注意:定义mTime时一定要在前面加上 __IO 不然无法延时, __IO 的意思是告诉编辑器不要对这个变量进行优化,他在标准库里的定义 具体可以百度
#define __IO volatile !< defines 'read / write' permissions */
函数Deley_D(void)要在SysTick_Handler(void)中调用,源文件名:stm32f10x_it.c
如下:
**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
Deley_D(); //延时函数
Judge_ETB(); //判断是否接收完一帧数据
}
2.串口中断
#include "usart.h"
__IO uint8_t CocheData[64]; //临时数据缓存
__IO uint8_t count=0; //接收计数
__IO uint8_t TimeLag = 0; //数据帧判断时间
__IO uint8_t FLAG_USART1_IT=0; //串口1中断标志 表示接收到数据 0表示未中断,1表示发生中断
__IO uint8_t FLAG_FrameData = 0; //用来表示一帧数据接收完成
/*串口1初始化函数*/
void init_usart1() {
GPIO_InitTypeDef GPIOA_InitStructure; //定义GPIOA初始化结构体变量
USART_InitTypeDef USART_InitStructure; //定义串口初始化结构体变量
NVIC_InitTypeDef NVIC_InitStructure; //定义中断初始化结构体变量
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//ENABLE THE GPIOA 使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//ENABLE USART1 使能串口1时钟
GPIOA_InitStructure.GPIO_Pin = GPIO_Pin_9; //启用GPIOA Pin9引脚 串口发送引脚
GPIOA_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //工作模式 复用推挽输出
GPIOA_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //工作频率50MHz
GPIO_Init(GPIOA, &GPIOA_InitStructure); //初始化GPIOA
GPIOA_InitStructure.GPIO_Pin = GPIO_Pin_10; //启用GPIOA Pin10引脚 串口接收引脚
GPIOA_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //工作模式 悬空输入
GPIO_Init(GPIOA, &GPIOA_InitStructure); //初始化GPIO
USART_InitStructure.USART_BaudRate = 115200; //设置串口1的波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //设置数据长度
USART_InitStructure.USART_StopBits = USART_StopBits_1; //设置停止位为1位
USART_InitStructure.USART_Parity = USART_Parity_No; //设置奇偶校验为无校验
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //启用接收和传输模式
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //设置硬件流模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_Cmd(USART1, ENABLE); //使能串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //使能串口1中断
USART_ClearFlag(USART1, USART_IT_RXNE); //清除接收缓存非空
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //指定串口1的中断
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能串口1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //中断优先级
NVIC_Init(&NVIC_InitStructure);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); //中断优先级分组
}
/*发送单个字节*/
void USART1_SendChar(uint8_t dat){
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET){ //判断发送缓存区是否为空 TXE是发送缓存区清空标志
}
USART_SendData(USART1,dat);
}
/*发送多个字节*/
void USART1_SendMulti(uint8_t *dat,uint8_t len){
uint8_t i;
for(i=0;i<len;i++){ //发送个数
USART1_SendChar(*dat++);
}
}
/*发送字符串*/
void USART1_SendString(uint8_t *dat) {
while (*dat != '\0') { //遇到结束符停止发送
USART1_SendChar(*dat++);
}
}
/*串口1中断函数*/
void USART1_IRQHandler(void) {
if (USART_GetFlagStatus(USART1, USART_IT_RXNE)!=RESET) { //判断接收缓冲区是否非空
CocheData[count] = USART_ReceiveData(USART1); //把接收到的数据暂时存储到数据缓存数据
FLAG_USART1_IT = 1; //置位中断标志
}
count++; //统计接收的个数
}
/*判断是否接收完一帧数据 和判断数据流模式是否结束*/
void Judge_ETB() {
if (FLAG_USART1_IT) { //判断串口是否发生中断
FLAG_USART1_IT = 0; //发生中断,清除中断标志
TimeLag = 200; //重载判断数据帧结束时间
}
else if (TimeLag>0) { //没有发生中断开始计时30ms
TimeLag--;
if (TimeLag==0){ //30ms后串口没有发生中断,
FLAG_FrameData = 1; //置位数据帧标志,告诉后面的程序要开始处理了
}
}
这三个发送函数都比较好理解,就是调用标准库里的串口发送函数。
下面说下 数据帧。我们知道在串口助手输入一段字符后按一下发送,就会把这一段字符串连续的发送出去,直到结束。那么在段字符串就称为一帧数据。我们再发送一段字符串,就是第二帧数据,我们怎么去检测这些数据呢?这里可以看出第一段字符串发送出去之后隔了较长一段时间再发送第二段字符串,因为这段时间我们在输入第二段字符串的内容,就算是复制粘贴最快也要1到2s的时间。而串口发送一个字节所需要的时间非常短, 拿9600波特率来说,1s=1,000,000us/960=1041.6666大约1ms(8位数据位加上开始位和停止位共10位,所以串口发送一个字节是10位)可以看到1ms和1s的差距,所以判断数据帧的依据就是时间间隔,那时间间隔多久才合适呢首先要是比1ms大,再加上机器和单片机的处理时间等,但时间不能太长,要不然就会浪费时间,指令执行起来不顺畅,中和这些一般经验值值是30ms,因为我用串口下载数据到flash所以我设置的是200ms不知道为什么时间短了容易下载中断。大致思路有了接下来就是怎么实现了。因为一帧数据的长度是不固定的,所以我们每收到一个字节(每次中断)就要开始计时,如果在计时期间又收到数据就不是数据帧还没有结束,然后接收完这个字节数据重新计时3,直到接收完一个字节后计时到30ms期间没有任何数据过来,这就表示一帧数据接收完成,接下来就是处理这一帧数据。
3.指令处理
#include "command.h"
extern __IO uint8_t CocheData[64]; //串口临时数据缓存
extern __IO uint8_t count; //接收计数
extern __IO uint8_t FLAG_FrameData; //用来表示一帧数据接收完成 或是数据流接收完成
extern __IO uint8_t FLAG_FlowData; //数据流模式标识
/*把数据从数据缓冲区拷贝到存储区*/
char CopyCocheData(uint8_t *dat, uint16_t len) {
uint8_t i;
if(len>=count){ //比较接收长度;len 程序处理的字符串长度 count 串口实际接收的字符串长度
i = count;
count = 0;
for (char j = 0; j < i; j++)
dat[j] = CocheData[j]; //把数据从缓冲区拷贝出来以免被下一帧数据破坏,在闲置时期处理,释放缓冲区
return i; //返回接收到的数据长度
}
else{
i=len;
count = 0;
for(char j=0;j<len;j++)
dat[j] = CocheData[j];
return i;
}
}
/*指令比较函数,比较接收的指令是否与指令列表的指令一致*/
char CompareCommand(uint8_t *A, uint8_t *B, uint8_t len) {
while (len--) { //比较
if (*A++ != *B++) {
return 0;
}
}
return 1;
}
/*监视是否有指令到来*/
void MoniorCM() {
if (FLAG_FrameData) {
FLAG_FrameData = 0;
Command();
}
}
void Command() {
uchar i;
uint8_t len;
uint8_t cmd[64];
/*------------命令列表------------*/
uchar cmd1[] = "LED1 on";
uchar cmd2[] = "LED1 off";
uchar cmd3[] = "LED2 on";
uchar cmd4[] = "LED2 off";
uchar cmd5[] = "return";
uchar cmd6[] = "flash -";
uchar cmd7[] = "print ";
uchar warn[] = "There is no corresponding command\r\n"; //没有对应的命令警告语
uchar hint[] = " ->\r\n"; //命令执行成功标识符
/*------------命令长度汇总------------*/
uchar cmdLen[] = { sizeof(cmd1) - 1, sizeof(cmd2) - 1, sizeof(cmd3) - 1, sizeof(cmd4) - 1, sizeof(cmd5) - 1,
sizeof(cmd6) - 1 ,sizeof(cmd7)-1};
/*------------命令地址汇总------------*/
uchar *cmdPtr[] = { cmd1,cmd2, cmd3, cmd4, cmd5, cmd6 ,cmd7};
len = CopyCocheData(cmd, sizeof(cmd)); //把接收的数据拷贝出来
/*------------遍历命令列表------------*/
for (i = 0; i < sizeof(cmdLen); i++) {
if (len >= cmdLen[i]) { //首先比较接收到的数据帧长度是否大于命令长度
if (CompareCommand(cmd, cmdPtr[i], cmdLen[i])) {
break;
}
}
}
/*------------命令对应要执行的动作-----------*/
switch (i) {
case 0:
LED1_ON; //打开LED1
break;
case 1:
LED1_OFF; //关闭LLED1
break;
case 2:
LED2_ON; //打开LED2
break;
case 3:
LED2_OFF; //关闭LED2
LCD_print("aaaaaaa");
break;
case 4:
cmd[len] = '\0'; //在数据帧后面添加换行符
USART1_SendMulti(&cmd[cmdLen[4]+1],len-cmdLen[4]); //返回命令后面的字符串
break;
case 5: //flash操作
CMD_Flash(&cmd[cmdLen[5]]); //把命令后面的数据交给flash命令函数处理
break;
case 6:
cmd[len] = '\0'; //在数据帧后面添加结束符
LCD_print(&cmd[cmdLen[6]]); //在LCD上显示空格后面的字符串
break;
default:
USART1_SendString(warn); //发送没有对应的命令警告语
return; //没有对应的命令先发送警告语,然后结束此函数
}
cmd[len-1] = '\0'; //命令执行成功,在命令最后添加结束符
USART1_SendString(cmd); //返回执行的命令;然后再发送命令执行成功标识符
USART1_SendString(hint);
}
/*----------------------------分割线----------------------------*/
/*-----------------------以下为命令函数区-----------------------*/
/*------------问答模式返回32位无符号整型------------*/
uint32_t CMD_QAM(uint8_t *p){
uint32_t addr=0;
uint8_t data[4];
USART1_SendString(p);
while(FLAG_FrameData==0);
FLAG_FrameData = 0;
CopyCocheData(data,sizeof(data));
addr=data[0];
addr=(addr<<8)|data[1];
addr=(addr<<8)|data[2];
addr=(addr<<8)|data[3];
return addr;
}
/*------------问答模式返回字符串------------*/
char CMD_SQAM(uint8_t *data ,uint8_t *p){
uint8_t len=0;
USART1_SendString(p);
while(FLAG_FrameData==0);
FLAG_FrameData = 0;
len = CopyCocheData(data,64);
return len;
}
/*flash命令函数*/
void CMD_Flash(uint8_t *p) {
uint8_t data[64]={0}; //临时存储要处理的字符串数据
uint32_t addr=0; //flash要操作的地址 在有需要的时候赋值
uint32_t Idata=0; //临时存储需要处理的整型数据
if (*p == 'w') { //判断命令的参数是否为w 写数据
if(*++p == 's'){
addr=CMD_QAM("请输入24为地址:\r\n");
Idata=CMD_QAM("请输入要读取的个数:\r\n");
char len = CMD_SQAM(data,"请输入字符串(最多64个):");
//写入数据
FLASH_SWrite_Data(data,addr,len);
//发送数据
USART1_SendString("\r\n");
}
else{
addr=CMD_QAM("请输入24为地址:\r\n");
FLAG_FlowData = 1; //启动串口1的数据流模式
USART1_SendString("flash操作配置完成\r\n");
FLASH_ContReadData(addr);
FLAG_FlowData=0;
}
}
else if (*p == 'r') { //判断命令的参数是否为r 读数据
addr=CMD_QAM("请输入24为地址:\r\n");
Idata=CMD_QAM("请输入要读取的个数:\r\n");
//读出数据
FLASH_Read_Data(data,addr,Idata);
//发送数据
USART1_SendMulti(data,Idata);
USART1_SendString("\r\n");
}
else if (*p == 'i'){ //判断命令的参数是否为i 读取器件ID
FLASH_ReadID(data);
USART1_SendMulti(data,2);
USART1_SendString("\r\n");
}
else if (*p == 'f'){ //判断命令的参数是否为f 格式化flash
FLASH_Format();
}
else if (*p == 's'){ //判断命令的参数是否为s 扇区擦除
addr=CMD_QAM("请输入24为地址:\r\n");
FLASH_Sector_Erase(addr);
}
else if (*p == 'b'){ //判断命令的参数是否为b 块擦除
addr=CMD_QAM("请输入24为地址:\r\n");
FLASH_Block_Erase(addr);
}
else if (*p == 't'){
}
else { //没有找到参数
USART1_SendString("flash命令格式有问题,示范: flash -w 0x00 \r\n 参数: -w 向flash写数据\r\n -r 读取flash的数据\r\n");
}
}
头文件
#ifndef __COMMAND_H
#define __COMMAND_H
#include "stm32f10x.h"
#include "LED.h"
#include "usart.h"
#include "W25Q64Flash.h"
#include "text.h"
#define uchar unsigned char
char CopyCocheData(uint8_t *dat, uint16_t len); //把数据从数据缓冲区拷贝到指令执行区
char CompareCommand(uint8_t *A, uint8_t *B, uint8_t len); //指令比较函数,比较接收的指令是否与指令列表的指令一致
void MoniorCM(); //监视是否有指令到来
void Command(); //指令处理函数 处理对应指令的动作
uint32_t CMD_FlashParrm(uint8_t *p); //问答模式返回32位无符号整型
char CMD_SQAM(uint8_t *data ,uint8_t *p); //问答模式字符串 函数返回的是字符串长度
void CMD_Flash(uint8_t *p); //flash命令函数
#endif
代码有点多,乱的感觉。自己也是比较讨厌以后有时间优化
首先来看看主函数:
#include "stm32f10x.h"
#include "tft.h"
#include "def.h"
#include "usart.h"
#include "command.h"
#include "LED.h"
int main(void){
Deley_Init();
LCD_init();
init_usart1();
LED_Init();
W25Q64FlashInit();
InitPrint();
Deley(100);
while (1) {
MoniorCM();
}
}
先引用首先要的头文件,然后是各种初始化配置,再就是死循环,死循环一直在调用MoniorCM();函数,这个函数就是
/*监视是否有指令到来*/
void MoniorCM() {
if (FLAG_FrameData) {
FLAG_FrameData = 0; //清零数据帧接收完标志
Command(); //执行指令处理函数
}
}
先是判断是否接收完一帧数据,如果是就立马清零数据帧接收完标志,要随时准备接收下一帧数据,然后调用指令处理函数。也就是说要一直监视是否有数据接收完成。
下面看指令处理函数,拆开看
/*------------命令列表------------*/
uchar cmd1[] = "LED1 on";
uchar cmd2[] = "LED1 off";
uchar cmd3[] = "LED2 on";
uchar cmd4[] = "LED2 off";
uchar cmd5[] = "return";
uchar cmd6[] = "flash -";
uchar cmd7[] = "print ";
指令处理肯定要有指令了。指令部分我是参C语言中文网的
首先我们把接收到的字符串与指令列表里的字符串进行比较,如果有一样的字符串,说明有相应的指令,然后记下这条指令在列表里的编号,之后只要执行这个编号对应的语句即可。这里说一下把接收到的数据拷贝出来是便于接收下一帧数据。sizeof(cmd1) - 1是因为uchar存储的字符串是有结束符"\0"的。比较的时候先比较长度,只有长度大于或等于的情况下再逐个比较字符。执行动作就是把指令对应的功能先用函数集成好,只要提供一个接口函数放在这里。看起来就不会很乱。
4.指令对应功能的配置
这里只有GPIO的配置
#include "LED.h"
void LED_Init(void){
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_Init(GPIOE,&GPIO_InitStructure);
}
头文件
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h"
void LED_Init(void);
#define LED1_OFF GPIO_SetBits(GPIOB,GPIO_Pin_5)
#define LED1_ON GPIO_ResetBits(GPIOB,GPIO_Pin_5)
#define LED1_REV GPIO_WriteBit(GPIOB,GPIO_Pin_5, (BitAction)(1-(GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_5))))
#define LED2_OFF GPIO_SetBits(GPIOE,GPIO_Pin_5)
#define LED2_ON GPIO_ResetBits(GPIOE,GPIO_Pin_5)
#define LED2_REV GPIO_WriteBit(GPIOE,GPIO_Pin_5, (BitAction)(1-(GPIO_ReadOutputDataBit(GPIOE,GPIO_Pin_5))))
现在说下流程:SysTick中断负责处理数据是否接收完置位指令处理标志。串口中断负责处理数据接收到缓存区。主函数的死循环负责监视指令执行标志。
最后说一下 指令模式里的 问答模式返回32位无符号整型函数在输入时串口助手要勾选"16进制发送"并且一定要发送4个字节
源程序正在审核中
这只是我的笔记