【单片机与嵌入式】stm32串口通信入门

news2024/11/15 10:19:31

一、串口通信/协议

(一)串口通信简介

串口通信是一种通过串行传输方式在电子设备之间进行数据交换的通信方式。它通常涉及两条线(一条用于发送数据,一条用于接收数据),适用于各种设备,从微控制器到计算机等。串口通信的关键特点包括:

1.串行传输:数据位按照顺序一个接一个地传输,与并行传输相比,节省了引脚和线缆。
2.异步或同步传输:串口通信可以是异步的(通过单独的时钟信号进行数据同步)或同步的(通过时钟信号直接同步数据传输)。
3.通信速率:串口通信的速率通常以波特率(bps,每秒传输的位数)来衡量,典型的速率有9600、19200、38400、115200等。
4.简单性和广泛应用:串口通信协议相对简单,因此在许多嵌入式系统、传感器和计算机外围设备中广泛使用。
5.标准和协议:常见的串口标准包括RS-232、RS-485等,每种标准有特定的电气特性和通信协议。
6.通信模式:通信可以是单向的(如仅发送或仅接收),也可以是双向的(可以发送和接收数据)。

(二)通信模式(数据传输方式)

1、串行通信与并行通信

串行通信和并行通信是两种数据传输方式,它们有着明显的区别:

串行通信:

  定义:串行通信是一种逐位地传输数据的方式,数据位按照顺序一个接一个地传输
  传输方式:使用单条线路传输数据,一般包括发送线(TX)和接收线(RX)。
  优点:相比并行通信,串行通信可以减少线路和引脚数量,降低成本和复杂度。
  典型应用:常见于长距离通信和嵌入式系统,如串口通信(RS-232、RS-485)、USB等。


并行通信:

  定义:并行通信是同时传输多个数据位的方式,每个数据位使用一个独立的信号线路
  传输方式:通常使用多个并列的线路,每个线路传输一个数据位,同时进行。
  优点:传输速度快,适合于需要高速数据传输的应用。
  缺点:需要更多的线路和引脚,因此成本较高且布线复杂。
  典型应用:在计算机内部的数据总线(如PCI总线)、内存访问等地方常见并行通信。

可以将两种数据传输方式理解为“串联”与“并联”。

2、单工、半双工、全双工通信

单工、半双工和全双工通信是描述数据在通信中传输方向和能力的术语.单工通信适合于单向传输、无需反馈的应用;半双工通信适合于双向通信但不能同时进行;全双工通信适合需要同时双向传输数据的场合。

单工通信:

  定义:单工通信是指数据只能在一个方向上传输的通信方式。发送方只能发送数据,接收方只能接收数据,不能同时发送和接收。
  特点:通信是单向的,类似于单行道,信息只能在一个方向上流动。
  应用场景:常见于广播和一些简单的传感器网络中,如无线电广播、键盘到计算机的数据传输等。

半双工通信:

  定义:半双工通信允许数据在两个方向上传输,但不能同时进行。在某一时刻,通信设备要么发送数据,要么接收数据。
  特点:通信设备在发送数据时不能同时接收,反之亦然。类似于单行道,但是可以在两个方向上切换流量。
  应用场景:广泛应用于无线电对讲机、对讲电话以及以太网的半双工模式。


全双工通信:

  定义:全双工通信允许数据在两个方向上同时进行传输,发送和接收可以同时进行。
  特点:通信设备可以同时发送和接收数据,就像双车道道路一样,流量可以在两个方向上同时流动。
  应用场景:常见于电话通信、以太网等需要同时双向传输数据的场合,如互联网视频会议、数据中心的服务器通信等。

(三)串口协议和RS-232标准介绍

1、串口协议

串口协议通常指的是一种通过串行通信进行数据传输的通信协议。串口协议是通过串行通信传输数据的一种约定和规范,通常包括以下几个方面:

(1)数据格式:定义了数据位(通常为7或8位)、校验位(可选)、停止位(通常为1或2位)等格式。这些位组合在一起形成一个完整的数据帧。
(2)波特率:指数据传输速率,单位为波特(bps)。常见的波特率有9600、19200、38400、115200等。
(3)通信协议:指定了数据如何被解释、传输和接收。常见的协议包括ASCII码、Modbus、SPI等,具体的选择取决于应用的需求和设备的兼容性。
(4)硬件接口:定义了物理连接和电气特性,如信号电平、线路配置(如RS-232、RS-485)、引脚定义等。

2、RS-232标准

RS-232是最早的一种串行通信标准,定义了数据传输的电气特性和信号架构。它具有以下特点:

(1)电气特性:RS-232标准规定了发送和接收设备之间的电平范围。典型的RS-232电平为正负12V,用于表示逻辑1和逻辑0。
(2)连接方式:RS-232通常使用DB-9或DB-25连接器,分别具有9个或25个引脚,用于连接串口设备。
(3)信号线:RS-232定义了多条信号线,包括发送数据(TX)、接收数据(RX)、数据就绪(DSR)、数据载波检测(DCD)、请求发送(RTS)和清除发送(CTS)等。
(4)应用范围:RS-232广泛应用于计算机和外围设备之间的通信,如调制解调器、终端设备、打印机等。

尽管RS-232标准已经存在多年,但随着技术的进步,如USB和以太网的普及,RS-232在某些领域的使用逐渐减少。然而,它仍然是许多传统设备中不可或缺的通信接口之一,且在工业控制、测量设备等领域仍然广泛使用。

3、RS232电平与TTL电平的区别

RS-232和TTL电平在电气特性、应用场景和连接方式上有很大的不同。RS-232适用于长距离和抗干扰要求较高的通信环境,而TTL电平适合于数字电路和低功耗设备之间的短距离通信。选择合适的电平标准取决于具体的应用需求,包括通信距离、抗干扰能力、功耗和设备兼容性等因素。

RS-232电平

(1)RS-232标准定义了发送和接收设备之间的电平范围,典型的RS-232电平为正负12V。逻辑1通常对应于负电平(-3V 至 -15V之间),逻辑0对应于正电平(+3V 至 +15V之间)。这种电平范围使得RS-232在长距离传输和抗干扰能力方面表现优秀。

(2)RS-232通常用于需要较长传输距离(最长可达50英尺)和较高抗干扰能力的应用,如计算机串口、调制解调器、终端设备等。它适合于工业环境和长距离通信需求,但其电平范围较广,需要较多的电气和电子元件支持。

(3)RS-232通常使用DB-9DB-25连接器,这些连接器包含多个引脚,用于传输数据及控制信号。

TTL电平

(1)TTL(Transistor-Transistor Logic)电平是指通常在逻辑电路中使用的电平标准,典型的TTL电平是0V到5V。逻辑1通常为高电平(约3.3V至5V),逻辑0为低电平(约0V至0.8V),这种电平适合数字电路和集成电路之间的直接通信。

(2)TTL电平通常用于短距离通信和数字电路之间的通信,如微控制器与传感器之间的串行通信、逻辑电路板之间的通信等。由于其电平范围较窄,适合于低功耗应用和简化的通信接口

(3)TTL电平通常使用简单的单针或双针连接器,如用于Arduino等开发板的数字输入输出引脚。

(四)CH340(串口)

CH340模块是一种USB转串口芯片,能够将USB接口转换为异步串口通信接口,支持RS-232和TTL电平标准,适用于单片机开发、嵌入式系统及消费电子产品中,提供稳定的数据传输和低功耗解决方案。

USB/TTL转232

CH340是一个USB总线的转接芯片,实现USB转串口、USB转IrDA红外或者USB转打印口。为了增加串口通讯的远距离传输及抗干扰能力,RS-232标准使用-15V表示逻辑1,+15V 表示逻辑0。常常会使用MH340芯片对USB/TTL与RS- 232电平的信号进行转换。

二、标准库开发

为了解决不同芯片厂商生产的基于Cortex内核的微处理器在软件上的兼容问题,ARM公司与众多芯片和软件厂商共同制定了CMSIS标准(Cortex Microcontroller Software Interface Standard,Cortex微控制器软件接口标准),意在将所有Cortex芯片厂商产品的软件接口标准化。

固件库:

安装步骤:

img

导入后列表:

三、标准库点灯

(一)配置GPIO函数:

//1.打开APB2时钟(不懂为什么第一步是这个的可以参考我上一篇博客)
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
//2.GPIO引脚设置
    GPIO_InitTypeDef GPIO_InitStructure;
    //GPIO结构体定义
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    //设置GPIO功能模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    //设置作用GPIO引脚
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    //配置GPIO速度
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    //如果你设置的引脚是PB9,()内分别为GPIOB,&你定义的结构体
    
//3.GPIO输出电平设置
    GPIO_ResetBits(GPIOB, GPIO_Pin_9);
    //低电平,点亮LED灯(因为我们的LED灯正极接电源侧,负极接引脚PB9,要使LED亮需要使PB9输出低电平导通)
    GPIO_SetBits(GPIOB, GPIO_Pin_9);
    //高电平,LED灯不亮

接线图:

(二)完整点灯代码:

​
#include "stm32f10x.h"                  // Device header
#include "Delay.h"     
​
int main(void)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //开启GPIOA的时钟
                                                            //使用各个外设前必须开启时钟,否则对外设的操作无效
    
    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;                    //定义结构体变量
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;        //GPIO模式,赋值为推挽输出模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;               //GPIO引脚,赋值为第0号引脚
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       //GPIO速度,赋值为50MHz
    
    GPIO_Init(GPIOA, &GPIO_InitStructure);                  //将赋值后的构体变量传递给GPIO_Init函数
                                                            //函数内部会自动根据结构体的参数配置相应寄存器
                                                            //实现GPIOA的初始化
    
    /*主循环,循环体内的代码会一直循环执行*/
    while (1)
    {
        /*设置PA0引脚的高低电平,实现LED闪烁,下面展示3种方法*/
        
        /*方法1:GPIO_ResetBits设置低电平,GPIO_SetBits设置高电平*/
        GPIO_ResetBits(GPIOA, GPIO_Pin_0);                  //将PA0引脚设置为低电平
        Delay_ms(500);                                      //延时500ms
        GPIO_SetBits(GPIOA, GPIO_Pin_0);                    //将PA0引脚设置为高电平
        Delay_ms(500);                                      //延时500ms
        
        /*方法2:GPIO_WriteBit设置低/高电平,由Bit_RESET/Bit_SET指定*/
        GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);        //将PA0引脚设置为低电平
        Delay_ms(500);                                      //延时500ms
        GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);          //将PA0引脚设置为高电平
        Delay_ms(500);                                      //延时500ms
        
        /*方法3:GPIO_WriteBit设置低/高电平,由数据0/1指定,数据需要强转为BitAction类型*/
        GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);     //将PA0引脚设置为低电平
        Delay_ms(500);                                      //延时500ms
        GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);     //将PA0引脚设置为高电平
        Delay_ms(500);                                      //延时500ms
    }
}


​

四、标准库串口通信实现

LED GPIO 初始化:

#include "stm32f10x.h"
#include "OLED_Font.h"
​
/*引脚配置*/
#define OLED_W_SCL(x)       GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x)       GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
​
/*引脚初始化*/
void OLED_I2C_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    OLED_W_SCL(1);
    OLED_W_SDA(1);
}
​
/**
  * @brief  I2C开始
  * @param  无
  * @retval 无
  */
void OLED_I2C_Start(void)
{
    OLED_W_SDA(1);
    OLED_W_SCL(1);
    OLED_W_SDA(0);
    OLED_W_SCL(0);
}
​
/**
  * @brief  I2C停止
  * @param  无
  * @retval 无
  */
void OLED_I2C_Stop(void)
{
    OLED_W_SDA(0);
    OLED_W_SCL(1);
    OLED_W_SDA(1);
}
​
/**
  * @brief  I2C发送一个字节
  * @param  Byte 要发送的一个字节
  * @retval 无
  */
void OLED_I2C_SendByte(uint8_t Byte)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        OLED_W_SDA(Byte & (0x80 >> i));
        OLED_W_SCL(1);
        OLED_W_SCL(0);
    }
    OLED_W_SCL(1);  //额外的一个时钟,不处理应答信号
    OLED_W_SCL(0);
}
​
/**
  * @brief  OLED写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void OLED_WriteCommand(uint8_t Command)
{
    OLED_I2C_Start();
    OLED_I2C_SendByte(0x78);        //从机地址
    OLED_I2C_SendByte(0x00);        //写命令
    OLED_I2C_SendByte(Command); 
    OLED_I2C_Stop();
}
​
/**
  * @brief  OLED写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void OLED_WriteData(uint8_t Data)
{
    OLED_I2C_Start();
    OLED_I2C_SendByte(0x78);        //从机地址
    OLED_I2C_SendByte(0x40);        //写数据
    OLED_I2C_SendByte(Data);
    OLED_I2C_Stop();
}
​
/**
  * @brief  OLED设置光标位置
  * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
  * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
  * @retval 无
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
    OLED_WriteCommand(0xB0 | Y);                    //设置Y位置
    OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));    //设置X位置高4位
    OLED_WriteCommand(0x00 | (X & 0x0F));           //设置X位置低4位
}
​
/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
    uint8_t i, j;
    for (j = 0; j < 8; j++)
    {
        OLED_SetCursor(j, 0);
        for(i = 0; i < 128; i++)
        {
            OLED_WriteData(0x00);
        }
    }
}
​
/**
  * @brief  OLED显示一个字符
  * @param  Line 行位置,范围:1~4
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的一个字符,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{       
    uint8_t i;
    OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);       //设置光标位置在上半部分
    for (i = 0; i < 8; i++)
    {
        OLED_WriteData(OLED_F8x16[Char - ' '][i]);          //显示上半部分内容
    }
    OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);   //设置光标位置在下半部分
    for (i = 0; i < 8; i++)
    {
        OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);      //显示下半部分内容
    }
}
​
/**
  * @brief  OLED显示字符串
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串,范围:ASCII可见字符
  * @retval 无
  */
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
    uint8_t i;
    for (i = 0; String[i] != '\0'; i++)
    {
        OLED_ShowChar(Line, Column + i, String[i]);
    }
}
​
/**
  * @brief  OLED次方函数
  * @retval 返回值等于X的Y次方
  */
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
    uint32_t Result = 1;
    while (Y--)
    {
        Result *= X;
    }
    return Result;
}
​
/**
  * @brief  OLED显示数字(十进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~4294967295
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
    uint8_t i;
    for (i = 0; i < Length; i++)                            
    {
        OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
    }
}
​
/**
  * @brief  OLED显示数字(十进制,带符号数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-2147483648~2147483647
  * @param  Length 要显示数字的长度,范围:1~10
  * @retval 无
  */
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
    uint8_t i;
    uint32_t Number1;
    if (Number >= 0)
    {
        OLED_ShowChar(Line, Column, '+');
        Number1 = Number;
    }
    else
    {
        OLED_ShowChar(Line, Column, '-');
        Number1 = -Number;
    }
    for (i = 0; i < Length; i++)                            
    {
        OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
    }
}
​
/**
  * @brief  OLED显示数字(十六进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFFFFFF
  * @param  Length 要显示数字的长度,范围:1~8
  * @retval 无
  */
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
    uint8_t i, SingleNumber;
    for (i = 0; i < Length; i++)                            
    {
        SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
        if (SingleNumber < 10)
        {
            OLED_ShowChar(Line, Column + i, SingleNumber + '0');
        }
        else
        {
            OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
        }
    }
}
​
/**
  * @brief  OLED显示数字(二进制,正数)
  * @param  Line 起始行位置,范围:1~4
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
    uint8_t i;
    for (i = 0; i < Length; i++)                            
    {
        OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
    }
}
​
/**
  * @brief  OLED初始化
  * @param  无
  * @retval 无
  */
void OLED_Init(void)
{
    uint32_t i, j;
    
    for (i = 0; i < 1000; i++)          //上电延时
    {
        for (j = 0; j < 1000; j++);
    }
    
    OLED_I2C_Init();            //端口初始化
    
    OLED_WriteCommand(0xAE);    //关闭显示
    
    OLED_WriteCommand(0xD5);    //设置显示时钟分频比/振荡器频率
    OLED_WriteCommand(0x80);
    
    OLED_WriteCommand(0xA8);    //设置多路复用率
    OLED_WriteCommand(0x3F);
    
    OLED_WriteCommand(0xD3);    //设置显示偏移
    OLED_WriteCommand(0x00);
    
    OLED_WriteCommand(0x40);    //设置显示开始行
    
    OLED_WriteCommand(0xA1);    //设置左右方向,0xA1正常 0xA0左右反置
    
    OLED_WriteCommand(0xC8);    //设置上下方向,0xC8正常 0xC0上下反置
​
    OLED_WriteCommand(0xDA);    //设置COM引脚硬件配置
    OLED_WriteCommand(0x12);
    
    OLED_WriteCommand(0x81);    //设置对比度控制
    OLED_WriteCommand(0xCF);
​
    OLED_WriteCommand(0xD9);    //设置预充电周期
    OLED_WriteCommand(0xF1);
​
    OLED_WriteCommand(0xDB);    //设置VCOMH取消选择级别
    OLED_WriteCommand(0x30);
​
    OLED_WriteCommand(0xA4);    //设置整个显示打开/关闭
​
    OLED_WriteCommand(0xA6);    //设置正常/倒转显示
​
    OLED_WriteCommand(0x8D);    //设置充电泵
    OLED_WriteCommand(0x14);
​
    OLED_WriteCommand(0xAF);    //开启显示
        
    OLED_Clear();               //OLED清屏
}

波特率配置:

img

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>
​
/**
  * 函    数:串口初始化
  * 参    数:无
  * 返 回 值:无
  */
void Serial_Init(void)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);  //开启USART1的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //开启GPIOA的时钟
    
    /*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;
    GPIO_Init(GPIOA, &GPIO_InitStructure);                  //将PA9引脚初始化为复用推挽输出
    
    /*USART初始化*/
    USART_InitTypeDef USART_InitStructure;                  //定义结构体变量
    USART_InitStructure.USART_BaudRate = 9600;              //波特率
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
    USART_InitStructure.USART_Mode = USART_Mode_Tx;         //模式,选择为发送模式
    USART_InitStructure.USART_Parity = USART_Parity_No;     //奇偶校验,不需要
    USART_InitStructure.USART_StopBits = USART_StopBits_1;  //停止位,选择1位
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     //字长,选择8位
    USART_Init(USART1, &USART_InitStructure);               //将结构体变量交给USART_Init,配置USART1
    
    /*USART使能*/
    USART_Cmd(USART1, ENABLE);                              //使能USART1,串口开始运行
}
​
/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
    USART_SendData(USART1, Byte);       //将字节数据写入数据寄存器,写入后USART自动生成时序波形
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);   //等待发送完成
    /*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
​
/**
  * 函    数:串口发送一个数组
  * 参    数:Array 要发送数组的首地址
  * 参    数:Length 要发送数组的长度
  * 返 回 值:无
  */
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
    uint16_t i;
    for (i = 0; i < Length; i ++)       //遍历数组
    {
        Serial_SendByte(Array[i]);      //依次调用Serial_SendByte发送每个字节数据
    }
}
​
/**
  * 函    数:串口发送一个字符串
  * 参    数:String 要发送字符串的首地址
  * 返 回 值:无
  */
void Serial_SendString(char *String)
{
    uint8_t i;
    for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
    {
        Serial_SendByte(String[i]);     //依次调用Serial_SendByte发送每个字节数据
    }
}
​
/**
  * 函    数:次方函数(内部使用)
  * 返 回 值:返回值等于X的Y次方
  */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
    uint32_t Result = 1;    //设置结果初值为1
    while (Y --)            //执行Y次
    {
        Result *= X;        //将X累乘到结果
    }
    return Result;
}
​
/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
    uint8_t i;
    for (i = 0; i < Length; i ++)       //根据数字长度遍历数字的每一位
    {
        Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');    //依次调用Serial_SendByte发送每位数字
    }
}
​
/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)
{
    Serial_SendByte(ch);            //将printf的底层重定向到自己的发送字节函数
    return ch;
}
​
/**
  * 函    数:自己封装的prinf函数
  * 参    数:format 格式化字符串
  * 参    数:... 可变的参数列表
  * 返 回 值:无
  */
void Serial_Printf(char *format, ...)
{
    char String[100];               //定义字符数组
    va_list arg;                    //定义可变参数列表数据类型的变量arg
    va_start(arg, format);          //从format开始,接收参数列表到arg变量
    vsprintf(String, format, arg);  //使用vsprintf打印格式化字符串和参数列表到字符数组中
    va_end(arg);                    //结束变量arg
    Serial_SendString(String);      //串口发送字符数组(字符串)
}

主函数代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
​
int main(void)
{
    /*模块初始化*/
    OLED_Init();                        //OLED初始化
    
    Serial_Init();                      //串口初始化
    
    /*串口基本函数*/
    Serial_SendByte(0x41);              //串口发送一个字节数据0x41
    
    uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45};   //定义数组
    Serial_SendArray(MyArray, 4);       //串口发送一个数组
    
    //Serial_SendString("hello windows!");      //串口发送字符串
    
    Serial_SendNumber(111, 3);          //串口发送数字
    
    /*下述3种方法可实现printf的效果*/
    
    /*方法1:直接重定向printf,但printf函数只有一个,此方法不能在多处使用*/
    printf("\r\nNum2=%d", 222);         //串口发送printf打印的格式化字符串
                                        //需要重定向fputc函数,并在工程选项里勾选Use MicroLIB
    
    /*方法2:使用sprintf打印到字符数组,再用串口发送字符数组,此方法打印到字符数组,之后想怎么处理都可以,可在多处使用*/
    char String[100];                   //定义字符数组
    sprintf(String, "\r\nNum3=%d", 333);//使用sprintf,把格式化字符串打印到字符数组
    Serial_SendString(String);          //串口发送字符数组(字符串)
    
    /*方法3:将sprintf函数封装起来,实现专用的printf,此方法就是把方法2封装起来,更加简洁实用,可在多处使用*/
    Serial_Printf("\r\nNum4=%d", 444);  //串口打印字符串,使用自己封装的函数实现printf的效果
    Serial_Printf("\r\n");
    
    while (1)
    {
        Serial_SendString("hello windows");
        Serial_Printf("\r\n");
    }
}

结果演示:

STM32以查询方式接收上位机(win10)串口发来的数据,如果接收到“Y”则点亮链接到stm32上的一个LED灯;接收到“N”则熄灭LED灯。

代码演示:

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
​
void USART_Config(void)
{
//1.开启GPIOA和USART1的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
    
//2.结构体定义
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    
//3.USART设置RX/TX
    
  //USART1_TX,默认情况下复用PA9引脚
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
    GPIO_Init(GPIOA, &GPIO_InitStructure); 
    
  //USART1_RX,默认情况下复用PA10引脚
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure); 
    
//4.USART1参数配置
    
    USART_InitStructure.USART_BaudRate = 9600;
  //设置波特率为9600
    
    USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位占8位
    USART_InitStructure.USART_StopBits = USART_StopBits_1; //1位停止位
    USART_InitStructure.USART_Parity = USART_Parity_No; //无校验
    USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
​
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitTypeDef NVIC_InitStructure;
    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);
    
//5.初始化串口1
    USART_Cmd(USART1, ENABLE); //使能串口1
​
}
​
//重定向c 库函数printf 到串口,重定向后可使用printf 函数
int fputc(int ch, FILE *f)
{
  /* 发送一个字节数据到串口 */
  USART_SendData(USART1, (uint8_t) ch);
    /* 等待发送完毕 */
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    
    return (ch);
}
​
///重定向c 库函数scanf 到串口,重写向后可使用scanf、getchar 等函数
int fgetc(FILE *f)
{
    /* 等待串口输入数据 */
    while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
    return (int)USART_ReceiveData(USART1);
}
​
int main(void)
{
    USART_Config();
    //1.开启GPIOB时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
//2.结构体定义
    GPIO_InitTypeDef GPIO_InitStructure;
    
//3.GPIO配置  
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
    GPIO_Init(GPIOA, &GPIO_InitStructure); 
    
//4.初始化灯
    GPIO_SetBits(GPIOA,GPIO_Pin_4);
    
    char ch;
  while(1)
  {
        printf("请输入指令:Y亮灯,N灭灯!");
        ch=getchar();
        printf("接收到字符:%c\n",ch);
        switch(ch)
        {
            case 'N':
                GPIO_SetBits(GPIOA,GPIO_Pin_4);
            break;
            case 'Y':
                GPIO_ResetBits(GPIOA,GPIO_Pin_4);
            break;
            default:
                break;
        }
            // 等待一段时间,以便在串口调试工具中可以看到消息之间的间隔  
    for (uint32_t i = 0; i < 10000000; i++);    
    }                                       
}
#include "stm32f10x.h" // Device header
#include "Serial.h"
//操作IO口的三个步骤
//1、使用RCC开启GPIO时钟
//2、使用GPIO_Init函数初始化GPIO
//3、使用输出或输入函数控制GPIO口
uint8_t KeyNum;
uint8_t RxData;
int main()
{
    OLED_Init();
    Serial_Init();
    GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);//将A6口初始化为电平
//GPIO_SetBits(GPIOA,GPIO_Pin_6);另一种初始化函数
    while(1)
    {
        if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)//若接收寄存器数据转到RDR中,则RXNE标志位置一,标志位自动清零
        {
        RxData=USART_ReceiveData(USART1);
            if(RxData=='Y')
            {GPIO_ResetBits(GPIOA,GPIO_Pin_6);}
            if(RxData=='N')
            {GPIO_SetBits(GPIOA,GPIO_Pin_6);}
        }
    }
}

五、Keil的仿真逻辑分析仪观察时序波形

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1883691.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

万字长文|下一代系统内存数据加速接口SDXI解读

本文内容分为5章节&#xff0c;总计10535字&#xff0c;内容较多&#xff0c;建议先收藏&#xff01; 1.SDXI技术产生的背景 2.SDXI相比DMA的优势 3.SDXI实现原理与架构 3.1 描述符环原理解读 3.2 上下文管理介绍 3.3 AKey与RKey解读 3.4 错误日志和状态管理 3.5 跨Function访…

javascript 常见设计模式

什么是设计模式? 在软件开发中&#xff0c;设计模式是解决特定问题的经验总结和可复用的解决方案。设计模式可以提高代码的复用性、可维护性和可读性&#xff0c;是提高开发效率的重要手段。 单例模式 1.概念 单例模式 &#xff08;Singleton Pattern&#xff09;&#xf…

c++ 智能指针实战分析

一.智能指针的设计思路 智能指针是类模板&#xff0c;再栈上创建智能指针对象。把普通指针交给智能指针对象。智能指针对象过期时&#xff0c;调用析构函数释放普通指针的内存。 智能指针的类型 auto_ptr是C98的标准&#xff0c;c17已经弃用。unique_ptr、shared_ptr和weak_…

动手学深度学习(Pytorch版)代码实践 -计算机视觉-41目标检测数据集

41目标检测数据集 import os import pandas as pd import torch import torchvision import matplotlib.pylab as plt from d2l import torch as d2l# 数据集下载链接 # http://d2l-data.s3-accelerate.amazonaws.com/banana-detection.zip# 读取数据集 #save def read_data_b…

Mustango——音乐领域知识生成模型探索

Mustango&#xff1a;利用领域知识的音乐生成模型 论文地址&#xff1a;https://arxiv.org/pdf/2311.08355.pdf 源码地址&#xff1a;https://github.com/amaai-lab/mustango 论文题为**“**利用音乐领域知识开发文本到音乐模型’Mustango’”。它利用音乐领域的知识从文本指…

计算机毕业设计Python深度学习美食推荐系统 美食可视化 美食数据分析大屏 美食爬虫 美团爬虫 机器学习 大数据毕业设计 Django Vue.js

Python美食推荐系统开题报告 一、项目背景与意义 随着互联网和移动技术的飞速发展&#xff0c;人们的生活方式发生了巨大变化&#xff0c;尤其是餐饮行业。在线美食平台如雨后春笋般涌现&#xff0c;为用户提供了丰富的美食选择。然而&#xff0c;如何在海量的餐饮信息中快速…

python 笔试面试八股(自用版~)

1 解释型和编译型语言的区别 解释是翻译一句执行一句&#xff0c;更灵活&#xff0c;eg&#xff1a;python; 解释成机器能理解的指令&#xff0c;而不是二进制码 编译是整个源程序编译成机器可以直接执行的二进制可运行的程序&#xff0c;再运行这个程序 比如c 2 简述下 Pyth…

2.2章节python的变量和常量

在Python中&#xff0c;变量和常量有一些基本的概念和用法&#xff0c;但需要注意的是&#xff0c;Python本身并没有内置的“常量”类型。然而&#xff0c;程序员通常会遵循一种约定&#xff0c;即使用全部大写的变量名来表示常量。 一、变量 在Python中&#xff0c;变量是一…

对不起,AI大模型不是风口

“我们正处在全新起点&#xff0c;这是一个以大模型为核心的人工智能新时代&#xff0c;大模型改变了人工智能&#xff0c;大模型即将改变世界。”——5月26日&#xff0c;百度创始人、董事长兼CEO李彦宏先生在2023中关村论坛发表了《大模型改变世界》演讲。 李彦宏指出&#…

4PCS点云配准算法实现

4PCS点云配准算法的C实现如下&#xff1a; #include <iostream> #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/common/common.h> #include <pcl/common/distances.h> #include <pcl/common/transforms.h> #in…

PostgreSQL介绍与安装

一、PostgreSQL数据库介绍 1、什么是数据库&#xff1f; 数据库&#xff08;Database&#xff09;是按照数据结构来组织、存储和管理数据的仓库。每个数据库都有一个或多个不同的 API 用于创建&#xff0c;访问&#xff0c;管理&#xff0c;搜索和复制所保存的数据。 我们也…

JAVA医院绩效考核管理系统源码:系统优势、系统目的、系统原则 (自主研发 功能完善 可直接上项目)

JAVA医院绩效考核管理系统源码&#xff1a;系统优势、系统目的、系统原则 &#xff08;自主研发 功能完善 可直接上项目&#xff09; 医院绩效考核系统优势 1.实现科室负责人单独考核 对科室负责人可以进行单独考核、奖金发放。 2. 科室奖金支持发放到个人 支持奖金二次分配&…

同一个excel表格,为什么在有的电脑上会显示#NAME?

一、哪些情况会产生#NAME?的报错 1.公式名称拼写错误 比如求和函数SUM&#xff0c;如果写成SUN就会提示#NAME&#xff1f;报错。 2.公式中的文本值未添加双引号 如下图&#xff1a; VLOOKUP(丙,A:B,2,0) 公式的计算结果会返回错误值#NAME?&#xff0c;这是因为公式中文本…

GraphPad Prism生物医学数据分析软件下载安装 GraphPad Prism轻松绘制各种图表

Prism软件作为一款功能强大的生物医学数据分析与可视化工具&#xff0c;其绘图功能尤为突出。该软件不仅支持绘制基础的图表类型&#xff0c;如直观明了的柱状图、展示数据分布的散点图&#xff0c;以及描绘变化趋势的曲线图&#xff0c;更能应对复杂的数据呈现需求&#xff0c…

7-1作业

1.实验目的&#xff1a;完成字符收发 led.h #ifndef __GPIO_H__ #define __GPIO_H__#include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_uart.h"//RCC,GPIO,UART初始化 void init();//字符数据发送 void set_tt…

【2024 插件开发大赛】为 ONLYOFFICE 开发插件,赢取万元奖金!

我们发布了 2024 插件开发大赛&#xff1a;为 ONLYOFFICE 开发适合中国用户的插件&#xff0c;赢取税前5500 – 10000元的结项奖金与证书&#xff01;阅读本文了解详情。 关于 ONLYOFFICE ONLYOFFICE 是一个国际开源项目&#xff0c;由领先的 IT 公司 Ascensio System SIA 开发…

论坛万能粘贴手(可将任意文件转为文本)

该软件可将任意文件转为文本。 还原为原文件的方法&#xff1a;将得到的文本粘贴到记事本&#xff0c;另存为UUE格式&#xff0c;再用压缩软件如winrar解压即可得到原文件。建议用于小软件。 下载地址&#xff1a;https://download.csdn.net/download/wgxds/89505015 使用演示…

算法基础入门 - 2.栈、队列、链表

文章目录 算法基础入门第二章 栈、队列、链表2.1 队列2.2 栈2.3 纸牌游戏2.4 链表如何建立链表?1.我们需要一个头指针(head)指向链表的初始。链表还没建立时头指针head为空2.建立第一个结点3.设置刚创建的这个结点的数据域(左半)和指针域(右半)4.设置头指针,头指针可方便…

构建高效的数字风控系统:应对现代网络威胁的策略与实践

文章目录 构建高效的数字风控系统&#xff1a;应对现代网络威胁的策略与实践1. 数字风控基本概念1.1 数字风控&#xff08;数字化风控&#xff09;1.2 数字风控的原理1.3 常见应用场景 2. 数字风控的必要性3. 构建高效的数字风控系统3.1 顶层设计与规划3.2 数据基础建设3.3 风险…

代码随想录第38天|动态规划

1049. 最后一块石头的重量 II 参考 备注: 当物体容量也等同于价值时, 01背包问题的含义则是利用好最大的背包容量sum/2, 使得结果尽可能的接近或者小于 sum/2 等价: 尽可能的平分成相同的两堆, 其差则为结果, 比如 (abc)-d, (ac)-(bd) , 最终的结果是一堆减去另外一堆的和, 问…