上次我们学习完了基本的知识,这次我们来实现程序的编写。文章最后我会放上所有的程序,

一 接线图

这是我们的接线图。只需要把USB转串口换为蓝牙模块就可以了。

二 编写准备

1.编程准备

我们复制一下串口收发数据包的文件。

原来的程序是收到文本@LED_ON等就会打开。

我们先测试一下

在手机端输入@LED_ON灯就会打开,说明我们的蓝牙模块没有问题。

我们可以设置按键的自锁模式,按下发送@LED_ON开灯,松开发送@LED_OFF关灯。

我们首先需要一个对printf的重定向,让他把数据打印在串口。

我们先测试一下OLED等功能是否正常。

下载程序之后我们看见显示屏正常。

然后我们可以用这种办法显示一下变量。表示变量占据3个字符宽度,如果变量不满足3个字符宽度就高位补0。

可以看到屏幕上也是可以显示出来。

2.测试绘图功能

引用一个math.h的头文件。

定义一个全局变量。

之后再主循环中,不断刷新显示变量。

就可以看到正弦曲线已经显示出来了。

我们再来测试一下cos的波形。

可以看到同时显示了sinx和cosx两个波形。

3.修改包头包尾

我们直接让包头@变为我们的 [ 。包尾改用 ] 代表我们数据包结束。

之后把if里面的逻辑改写为结束的逻辑。

之后后面的else if直接删除就可以了。这就可以接收 [ 起始 ] 结束的数据包了。我们测试一下。

4.测试一下

if里面的逻辑先删除。现在的逻辑是将数据包的内容,显示再oled的最后一行。然后编译下载

我们可以看到单片机的OLED同步收到了数据。

三.程序编写

1.函数了解

我们需要用到三个函数,第一个是strtok,可以实现用指定字符分割字符串。

第二个是strcmp可以比对两个字符串是否相等。

第三个是atoi或者atof可以把字符串形式的数值转化为整型或者浮点型。

首先收到字符串后先调用strtok函数,这是string.h库里面的,我们还需要添加库。

strtok函数第一个参数给待分割的字符串,这里是我们接收字符的函数。

第二个参数指定用什么字符来分割字符串,我们要用逗号分割,这里注意需要加双引号。

分割后的返回值用char*接收。

这就是分割得到的第一个子串,如果我们再次调用就会得到第二个字串,第三个子串,但是我们只需要再第一次分割时候,传入待分割的字符串,后续分割时这个参数要个NULL空的,表示这是前面的后续分割,而不是开启一个新的分割。如果分割到结束,没有新的子串了,那么函数就会返回空指针。

2.测试一下

我们在发送区发送一个字符串,可以看到接受区收到了3个子串。

3.按键程序编写

我们可以判断一下,按键1的松开事件

之后我们加入打印测试一下,大家也可以自己加入自己的操作。

4.测试按键

我们先在小程序配置一下1这个按键。

按键2也是同理。

可以看到,按下按键2的瞬间,单片机显示没有问题。可以收到数据,在松开按键1的瞬间单片机也可以收到数据。

5.解析滑杆数据包

现在就可以测试了

滑动滑杆2就可以看到数值得变化。

但是现在都还是字符串数值,我们要变为真正的数值。

6.数值转换

因为atoi是strlib.h库里面的,我们还需要调用一下库。

后面我们就可以用%d和intvalue来显示了。这就是我们得滑杆得值。

转换值是小数得话就用这个。

7.测试一下

滑杆2测试,发现是小数,而且显示没有问题。

如果在程序中想要实时修改哪两个参数,就可以把这两个参数赋值给对应的参数就可以了。如果修改了滑杆得名称,就修改name比对的字符串就可以了。

8.摇杆程序编写

解析之后就得到了摇杆值,后续就可以利用这个值去操作遥控小车。

9.摇杆测试

经过测试可以看到我们得值在上面打印出来了。滑动摇杆,数值也会变化。

三.所有程序

1.main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
#include <string.h>
//使用转换函数得头文件
#include <stdlib.h>

//测试绘图
#include <math.h>

uint8_t a;
float x,y1,y2;

int main(void)
{	
    OLED_Init(); 
    Serial_Init();
    LED_Init();

    //显示字符
    printf("[display, 0, 0, hello word]");
    //显示变量,表示变量占据3个字符宽度,如果变量不满足3个字符宽度就高位补0
    printf("[display, 0, 0, a:%03d]", a);
    
    OLED_ShowString(1, 1,"TxPacket");//显示发送
    OLED_ShowString(1, 1,"RxPacket");//显示接受
    
      
    while(1)
    {
        
        //测试绘图
        x += 0.1;
        y1 = sin(x);
        y2 = cos(x);
        printf("[plot,%f,%f]", y1, y2);
        
        //标志位为1代表接收到了数据
        if(Serial_RxFlag == 1)
        {
            //先清除一下第四行,防止数据过长
            OLED_ShowString(4, 1, "                ");
            OLED_ShowString(4, 1, Serial_RxPacket);
            
            //三个字符串函数strtok分割字符串,strcmp字符串比角atoi和atof字符串数据转整型或浮点型
            //第一个参数给要分割的字符串  第二个参数指定用什么字符来分割字符串
            //                    我们要用逗号分割,这里注意需要加双引号。
            //分割后的返回值用char*接收
//            char* substr1 = strtok(Serial_RxPacket, ",");
//            char* substr2 = strtok(NULL, ",");
//            char* substr3 = strtok(NULL, ",");
            
            //第一个子串是数据包标识符,我们改革名字
            char* tag = strtok(Serial_RxPacket, ",");
            if(strcmp(tag,"key") == 0)//判断数据包是不是等于key,key就是按键数据包
            {
                //分割字符串
                char* name = strtok(NULL, ",");
                char* action = strtok(NULL, ",");
                
                //执行名称为1的按键松开事件
                if((strcmp(name, "1") == 0) && (strcmp(action, "up") == 0))
                {
                    printf("key, 1, up\r\n");
                }
                else if((strcmp(name, "2") == 0) && (strcmp(action, "down") == 0))
                {
                    printf("key, 1, down\r\n");
                }
            }
            
            //解析滑杆数据包
            else if(strcmp(tag, "slider") == 0)
            {
                //分割字符串
                char* name = strtok(NULL, ",");
                char* value = strtok(NULL, ",");
                
                //检查是不是收到了名称为1的滑杆数据包
                if(strcmp(name, "1") == 0)
                {
                    //现在得value是字符串,我们要转化为数值形式
                    uint8_t intvalue = atoi(value);
                    printf("slider, 1, %d\r\n", intvalue);
                }
                //检查是不是第二个
                else if(strcmp(name, "1") == 0)
                {
                    //如果转换值是小数,用这个
                    float floatvalue = atof(value);
                    printf("slider, 2, %.f\r\n", floatvalue);
                }
                
            }
            //解析摇杆数据包
            else if(strcmp(tag, "joystick") == 0)
            {
                //左摇杆横向值
                int8_t LH = atoi(strtok(NULL, ","));
                //左摇杆纵向值
                int8_t LV = atoi(strtok(NULL, ","));
                //右摇杆横向值
                int8_t RH = atoi(strtok(NULL, ","));
                //右摇杆纵向值
                int8_t RV = atoi(strtok(NULL, ","));
                
                //打印测试
                printf("joystick, %d, %d, %d, %d\r\n", LH, LV, RH, RV);
            }
            
//            //打印测试,看看是否分割完成
//            printf("%s\r\n", substr1);
//            printf("%s\r\n", substr2);
//            printf("%s\r\n", substr3);
            
            
            //标志位清0再程序结束之后
            Serial_RxFlag = 0;
        }
    }
}

2.serial.c

#include "stm32f10x.h"                  // Device header
#include "stdio.h"
#include "stdarg.h"

//这四个数据只存放用来发送和接受的载荷数据,包头,包尾就不存了。

char Serial_RxPacket[100];//接收缓存字符数组
uint8_t Serial_RxFlag;

void Serial_Init(void)
{
    //开启USART的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    
    //开启GPIO的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    //初始化GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    //这里就是把PA9设置为复用推挽输出供给USART的TX使用
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    //这里就是把PA9设置为复用推挽输出供给USART的TX使用
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    //初始化USART
    USART_InitTypeDef USART_InitStructcture;
    //波特率,直接写个参数init函数会自动计算。
    USART_InitStructcture.USART_BaudRate = 9600;
    //硬件流控制选择无
    USART_InitStructcture.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    //USART模式选择发送模式
    USART_InitStructcture.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    //校验位
    USART_InitStructcture.USART_Parity = USART_Parity_No;
    //停止位
    USART_InitStructcture.USART_StopBits = USART_StopBits_1;
    //字长
    USART_InitStructcture.USART_WordLength = USART_WordLength_8b;
    USART_Init(USART1, &USART_InitStructcture);
     
    //开启中断     选择谁触发中断      开启
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    //NVIC配置
    //先进性NVIC的分组,因为只用分一次  2位抢占,2位响应
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    //初始化NVIC
    NVIC_InitTypeDef NVIC_InitStructure;
    //中断通道
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    //中断使能,使能
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    //中断优先级设置 先都给个1
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
    
    //启动USART1串口
    USART_Cmd(USART1, ENABLE);
}

//发送数据函数
void Serial_SendByte(uint8_t Byte)
{
    //发送数据的函数
    USART_SendData(USART1, Byte);
    //等待发送完成
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

//发送数组函数  由于数组无法判断结束,需要加一个Length进来
void Serial_SendArray(uint8_t* Array, uint16_t Length)
{
    uint16_t i;
    for(i=0; i<Length; i++)
    {
        Serial_SendByte(Array[i]);
    }
}

//封装字符串  由于字符串自带一个结束标志位,所以就不需要进行长度判断了。
void Serial_SendString(char* String)
{
    uint8_t i;
    //字符串的本质就是一个带\0的数组。
    for(i=0; String[i] != 0;i++)
    {
         Serial_SendByte(String[i]);
    }
}

//次方函数
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
    uint32_t Result = 1;
    while(Y--)//循环Y次
    {
        Result *= X;//RESULT累乘y次的x,就是x的y次方
    }
    return  Result;
}

//封装显示数字
void  Serial_SendNum(uint32_t Number, uint8_t Length)
{
    //我们需要把数字的个位,十位,百位拆开,然后转换成对应的字符数据依次发送出去。
    uint8_t i;
    for(i=0; i<Length; i++)
    {
        //10的0次方是个位,所以要反过来。
        //假如是2位数,length为2,第一次循环length-i-1=2-0-1=1,就是发送十位,10的1次方。
        //第二次循环就是2-1-1=0,也就是10的0次方,个位。
        //最终要以字符的形式显示,所以我们要加一个偏移。
        Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + 0x30);
    }
}

//重写fputc函数
//int fputc(int ch, FILE* f)
//{
//    Serial_SendByte(ch);
//    return ch;
//}

//对sprintf函数进行封装   第一个参数用来接收格式化字符串
void Serial_printf(char* format, ...)
    //          第二个参数加三个.用于接收可变参数列表
{
    //定义输出的字符串
    char string[100];
    //va_list是类型名  arg是变量名
    va_list arg;//定义一个参数列表变量
    //从format开始接收参数表,放在arg里面
    va_start(arg, format);
    //之后sprinf打印位置是string
    //这里sprinf要改为 vsprintf因为sprintf只能接收直接写的参数
    //对于封装方式,只能用vsprintf
    vsprintf(string, format, arg);//格式化字符串是format,参数表是arg
    //释放参数表
    va_end(arg);
    //把string发送出去
    Serial_SendString(string);
}

//中断函数直接在启动文件复制
void USART1_IRQHandler(void)
{
    //首先要定一个标志当前状态的变量S,我们可以在中断里面定义一个静态变量。
    static uint8_t RxState = 0;
    static uint8_t pRxPacket = 0;//指示接收到哪一个数据了
    //先判断标志位
    if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
    {
        //获取一下RXData,读取寄存器里面的值
        uint8_t RxData = USART_ReceiveData(USART1);
        
        //根据RxState不同我们要进入不同的函数
        //进入等待包头程序
        if(RxState == 0)
        {
            //因为我们用@当包头
            if((RxData == '[') && (Serial_RxFlag == 0))//如果收到了包头
            {
                //为什么不读寄存器第一个数值?因为寄存器一次只能存储一个字节数据
                RxState = 1;//让状态变为1,可以进行下一步了,如果不等于,就不转运状态了
                pRxPacket = 0;//清除一下接收计数,为下次接收做准备
            }
        }
        
        //进入接受数据状态
        else if(RxState == 1)
        {
            //因为我们是不固定的长度,所以每次都要判断是不是为包尾
            if(RxData == ']')
            {
                RxState = 0;//是包尾的话就回到第一个状态。
                //还需要对字符数据加一个字符串结束标志位\0方便我们后续对字符串进行处理
                Serial_RxPacket[pRxPacket] = '\0';
                Serial_RxFlag = 1;//代表接收到一组完整的数据了,置一个标志位。
            }
            else//如果不是包尾才需要接收数据
            {
                //这里需要依次接受四个数组,存在RxPack数组里,所以需要一个变量用来记录接受几个数据
                //这里数组的第pRxPacket数是RxData,就是我们寄存器的值。
                //这就是每进入一次接收状态,数据就转运一次缓存数组,同时存位置++
                Serial_RxPacket[pRxPacket] = RxData;
                pRxPacket++;//移动到下一个位置
            }
        }
             
        //清除标志位,不影响函数
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

3.serial.h

#ifndef __SERIAL_
#define __SERIAL_

#include <stdio.h>

extern char Serial_RxPacket[];
extern uint8_t Serial_RxFlag;

void Serial_SendByte(uint8_t Byte);
void Serial_Init(void);
void Serial_SendArray(uint8_t* Array, uint16_t Length);
void Serial_SendString(char* String);
void  Serial_SendNum(uint32_t Number, uint8_t Length);
void Serial_printf(char* format, ...);
uint8_t Serial_GetFlag(void);

void Serial_SendPacket(void);

#endif

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐