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

一、串口通信/协议

(一)串口通信简介

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

七一建党节|热烈庆祝中国共产党成立103周年!

时光荏苒&#xff0c;岁月如梭。 在这热情似火的夏日&#xff0c; 我们迎来了中国共产党成立103周年的重要时刻。 这是一个值得全体中华儿女共同铭记和庆祝的日子&#xff0c; 也是激励我们不断前进的重要时刻。 103年&#xff0c; 风雨兼程&#xff0c;砥砺前行。 从嘉兴…

【电路笔记】-A类放大器

A类放大器 文章目录 A类放大器1、A类放大器概述2、A类放大器基本通用发射极配置3、变压器耦合配置4、总结在 放大器类型简介的文章中,我们介绍了不同类别的放大器。 在本文中,我们将更详细地介绍A类放大器。 在介绍不同的A类放大器配置前,首先的是要记住放大器类别的选择标…

MySQL的简介和安装目录

今日总结到此结束&#xff0c;拜拜&#xff01;

内容分发网络(CDN)学习记录

目录 静态内容动态内容CDN工作原理CDN缓存 CDN关键技术1.内容路由功能2.内容分发技术&#xff1a;内容分发技术主要是PUSH和PULL3.内容存储技术4.内容管理技术 全局负载均衡基于DNS的GSLB基于HTTP重定向的GSLB基于IP欺骗的GSLB服务器群选择策略 静态内容 静态内容是不会因用户…

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

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

数据链路层分析

文章目录 前言一、数据链路层概述二、终端之间的通信三、帧格式1.Ethernet_II型2.IEEE 802.3 四、MTU分析五、数据帧的传输1.MAC地址2.单播3.广播4.组播5.数据帧的收发 前言 网络中传输数据需要定义并遵循一些标准&#xff0c;以太网是根据IEEE802.3标准来管理和控制数据帧的&…

值得收藏!盘点那些适合普通人方便又好用的AIGC工具!(下)

【导读】接上一篇文章&#xff0c;盘点国内外适合普通人能够轻松上手的AIGC工具&#xff08;上&#xff09;。今天又为大家整理了一些好用又方便的AI设计工具、AI办公工具、AI编程工具、AI指令工具和AI检测工具&#xff0c;如果有没更新到的工具也欢迎大家评论区交流。 一 、A…

理性决策的艺术:从购房到择偶的数学智慧;37% 规则,做出最佳决策的秘诀;用数学模型解决人生难题

在面对人生重大决策时&#xff0c;如购房或择偶&#xff0c;我们常常感到迷茫和困惑。然而&#xff0c;如果我们能够将这些看似复杂的问题简化为数学模型&#xff0c;我们就能以更加理性和系统的方式做出决策。 37%规则 1950年代&#xff0c;当时几位数学家开始研究这样一个问…

获取onnx模型输入输出结构信息的3种方式:ONNX、onnxruntime、netron

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

二、基础—常用数据结构:列表、元祖、集合、字典、函数等(爬虫及数据可视化)

二、基础—常用数据结构&#xff1a;列表、元祖、集合、字典、函数等&#xff08;爬虫及数据可视化&#xff09; 1&#xff0c;字符串2&#xff0c;最常用的是列表&#xff08;重点掌握&#xff09;3&#xff0c;元组4&#xff0c;字典&#xff08;重要&#xff09;5&#xff0…

51-1 内网信息收集 - 内网资源探测

导语 在内网渗透过程中,通常需要利用各种技术来探测内网资源,为后续的横向渗透做准备。发现内网存活的主机及其详细信息可以帮助确定攻击方向和潜在的漏洞。 一、基于 ICMP 发现存活主机 ICMP(Internet Control Message Protocol,因特网控制消息协议)是 TCP/IP 协议簇的…

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

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

springcloud-gateway 网关组件中文文档

Spring Cloud网关 Greenwich SR5 该项目提供了一个基于Spring生态系统的API网关&#xff0c;其中包括&#xff1a;Spring 5&#xff0c;Spring Boot 2和项目Reactor。Spring Cloud网关的目的是提供一种简单而有效的方法来路由到API&#xff0c;并向它们提供跨领域的关注&#x…

7.1作业

初始化 /******rcc章节初始化********/ |//1.使能gpiob组控制器 |RCC->MP_AHB4ENSETR |(0X1<<1); |//2.使能gpiog组控制器 |RCC-&…

数据结构 - C/C++ - 链表

目录 结构特性 内存布局 结构样式 结构拓展 单链表 结构定义 节点关联 插入节点 删除节点 常见操作 双链表 环链表 结构容器 结构设计 结构特性 线性结构的存储方式 顺序存储 - 数组 链式存储 - 链表 线性结构的链式存储是通过任意的存储单元来存储线性…

制氢厂氢气泄漏安全监测:氢气传感器守护“氢”安全

随着全球能源结构的转型和清洁能源的需求日益增长&#xff0c;氢能作为一种高效、清洁的能源载体&#xff0c;受到了广泛关注。制氢厂作为氢能产业的重要组成部分&#xff0c;其安全问题也日益凸显。在制氢过程中&#xff0c;氢气泄漏是潜在的安全隐患之一&#xff0c;因此&…

Python容器 之 字符串--下标和切片

1.下标&#xff08;索引&#xff09; 一次获取容器中的一个数据 1, 下标(索引), 是数据在容器(字符串, 列表, 元组)中的位置, 编号 2, 一般来说,使用的是正数下标, 从 0 开始 3, 作用: 可以通过下标来获取具体位置的数据. 4, 语法&#xff1a; 容器[下标] 5, Python 中是支持…

猫冻干可以天天喂吗?喂冻干前要了解的必入主食冻干榜单

近年来&#xff0c;冻干猫粮因其高品质而备受喜爱&#xff0c;吸引了无数猫主人的目光&#xff0c;对于像我这样的养猫达人来说&#xff0c;早已尝试并认可了冻干喂养。然而&#xff0c;对于初入养猫行列的新手们来说&#xff0c;可能会有疑问&#xff1a;什么是冻干猫粮&#…

通过容器启动QAnything知识库问答系统

QAnything (Question and Answer based on Anything) 是致力于支持任意格式文件或数据库的本地知识库问答系统&#xff0c;可断网安装使用。目前已支持格式&#xff1a;PDF(pdf)&#xff0c;Word(docx)&#xff0c;PPT(pptx)&#xff0c;XLS(xlsx)&#xff0c;Markdown(md)&…

操作配置文件保存方式(上位机)

上位机:(Supervisor Control) 指的是用于监视和控制其他设备或者系统的计算机&#xff0c;在工业自动化和过程控制领域 上位机典型就是一台PC或者服务器&#xff0c;用于语各种下位机进行通信的&#xff0c;收集数据&#xff0c;并且根据收集的数据发送一些数据。 典型的设备…