【单片机与嵌入式】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速度,赋值为50MHzGPIO_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);                                      //延时500msGPIO_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);                                      //延时500msGPIO_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);                                      //延时500msGPIO_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;    //设置结果初值为1while (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;                    //定义可变参数列表数据类型的变量argva_start(arg, format);          //从format开始,接收参数列表到arg变量vsprintf(String, format, arg);  //使用vsprintf打印格式化字符串和参数列表到字符数组中va_end(arg);                    //结束变量argSerial_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);              //串口发送一个字节数据0x41uint8_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;//设置波特率为9600USART_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.初始化串口1USART_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.mzph.cn/diannao/38867.shtml

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

相关文章

入选顶会ICML,清华AIR等联合发布蛋白质语言模型ESM-AA,超越传统SOTA

作为细胞内无数生化反应的驱动力&#xff0c;蛋白质在细胞微观世界中扮演着建筑师和工程师的角色&#xff0c;不仅催化着生命活动&#xff0c;更是构筑、维系生物体形态与功能的基础构件。正是蛋白质之间的互动、协同作用&#xff0c;支撑起了生命的宏伟蓝图。 然而&#xff0…

Ubuntu DNS服务配置 深度解析

测试方法 resolvectl status dig alidns.com 修改实践 直接用接口配置&#xff0c;没用 /etc/resolv.conf&#xff0c;有效 /etc/netplan/01-network-manager-all.yaml,无效 /etc/systemd/resolved.conf&#xff0c;见link&#xff0c;为全局配置 [Resolve] DNS1.1.1.1 Fa…

Adobe Premiere 视频编辑软件下载安装,pr全系列分享 轻松编辑视频

Adobe Premiere&#xff0c;自其诞生之日起&#xff0c;便以其卓越的性能和出色的表现&#xff0c;稳坐视频编辑领域的王者宝座&#xff0c;赢得了无数专业编辑人员与广大爱好者的青睐。这款强大的视频编辑软件&#xff0c;凭借其丰富的功能和灵活的操作性&#xff0c;为用户提…

2024年道路运输安全员(企业管理人员)备考题库资料。

46.危险货物道路运输随车携带的单据&#xff0c;下列选项不属于的是&#xff08;&#xff09;。 A.道路运输危险货物安全卡 B.运单或者电子运单 C.道路危险货物运输从业资格证 D.车辆检测报告 答案&#xff1a;D 47.危险货物运输驾驶人员在24小时内实际驾驶车辆时间累计不…

ROS2在rviz2中实时显示轨迹和点

本文是将《ROS在rviz中实时显示轨迹和点》博客中rviz轨迹显示转为ROS2环境中的rviz2显示。 ros2的工作空间创建这里就不展示了。 包的创建 ros2 pkg create --build-type ament_cmake showpath --dependencies rclcpp nav_msgs geometry_msgs tf2_geometry_msgsshowpath.cpp…

【漏洞复现】和丰多媒体信息发布系统 QH.aspx 任意文件上传漏洞

0x01 产品简介 和丰多媒体信息发布系统也称数字标牌&#xff08;Digital Signage&#xff09;&#xff0c;是指通过大屏幕终端显示设备&#xff0c;发布商业、财经和娱乐信息的多媒体专业视听系统&#xff0c;常被称为除纸张媒体、电台、电视、互联网之外的“第五媒体”。该系…

1-4.时间序列数据建模流程范例

文章最前&#xff1a; 我是Octopus&#xff0c;这个名字来源于我的中文名–章鱼&#xff1b;我热爱编程、热爱算法、热爱开源。所有源码在我的个人github &#xff1b;这博客是记录我学习的点点滴滴&#xff0c;如果您对 Python、Java、AI、算法有兴趣&#xff0c;可以关注我的…

信息学奥赛初赛天天练-41-CSP-J2021基础题-n个数取最大、树的边数、递归、递推、深度优先搜索应用

PDF文档公众号回复关键字:20240701 2021 CSP-J 选择题 单项选择题&#xff08;共15题&#xff0c;每题2分&#xff0c;共计30分&#xff1a;每题有且仅有一个正确选项&#xff09; 4.以比较作为基本运算&#xff0c;在N个数中找出最大数&#xff0c;最坏情况下所需要的最少比…

我在中东做MCN,月赚10万美金

图片&#xff5c;Photo by Ben Koorengevel on Unsplash ©自象限原创 作者丨程心 在迪拜购物中心和世界最高建筑哈利法塔旁的主街上&#xff0c;徐晋已经“蹲”了三个小时&#xff0c;每当遇到穿着时髦的年轻男女&#xff0c;他都会上前询问&#xff0c;有没有意愿成为…

C语言部分复习笔记

1. 指针和数组 数组指针 和 指针数组 int* p1[10]; // 指针数组int (*p2)[10]; // 数组指针 因为 [] 的优先级比 * 高&#xff0c;p先和 [] 结合说明p是一个数组&#xff0c;p先和*结合说明p是一个指针 括号保证p先和*结合&#xff0c;说明p是一个指针变量&#xff0c;然后指…

Web2Code :网页理解和代码生成能力的评估框架

多模态大型语言模型&#xff08;MLLMs&#xff09;在过去几年中取得了爆炸性的增长。利用大型语言模型&#xff08;LLMs&#xff09;中丰富的常识知识&#xff0c;MLLMs在处理和推理各种模态&#xff08;如图像、视频和音频&#xff09;方面表现出色&#xff0c;涵盖了识别、推…

VuePress介绍

从本文开始&#xff0c;动手搭建自己的博客&#xff01;希望读者能跟着一起动手&#xff0c;这样才能真正掌握。 ‍ VuePress 是什么 VuePress 是由 Vue 作者带领团队开发的&#xff0c;非常火&#xff0c;使用的人很多&#xff1b;Vue 框架官网也是用了 VuePress 搭建的。即…

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…

php 通过vendor文件 生成还原最新的composer.json

起因&#xff1a;因为历史原因&#xff0c;在本项目中composer.json基本算废了&#xff0c;没法直接使用composer管理扩展&#xff0c;今天尝试修复一下composer.json。 历史文件&#xff0c;可以看出来已经很久没有维护了&#xff0c;我们主要是恢复require的信息 {"na…

基于CNN的股票预测方法【卷积神经网络】

基于机器学习方法的股票预测系列文章目录 一、基于强化学习DQN的股票预测【股票交易】 二、基于CNN的股票预测方法【卷积神经网络】 文章目录 基于机器学习方法的股票预测系列文章目录一、CNN建模原理二、模型搭建三、模型参数的选择&#xff08;1&#xff09;探究window_size…

下代iPhone或回归可拆卸电池,苹果这操作把我看傻了

刚度过一个愉快的周末&#xff0c;苹果又双叒叕摊上事儿了。 iPhone13 系列被曝扎堆电池鼓包了。 早在去年&#xff0c;就有 iPhone13 和 iPhone14 用户反馈过类似的问题&#xff0c;表示在手机仅仅使用了一年多的时间就出现了电池鼓包的情况&#xff0c;而且还把屏幕给撑起来了…

舞会无领导:一种树形动态规划的视角

没有上司的舞会 Ural 大学有 &#x1d441; 名职员&#xff0c;编号为1∼&#x1d441;。 他们的关系就像一棵以校长为根的树&#xff0c;父节点就是子节点的直接上司。 每个职员有一个快乐指数&#xff0c;用整数 &#x1d43b;&#x1d456; 给出&#xff0c;其中1≤&…

校园卡手机卡怎么注销?

校园手机卡的注销流程可以根据不同的运营商和具体情况有所不同&#xff0c;但一般来说&#xff0c;以下是注销校园手机卡的几种常见方式&#xff0c;我将以分点的方式详细解释&#xff1a; 一、线上注销&#xff08;通过手机APP或官方网站&#xff09; 下载并打开对应运营商的…

当年很多跑到美加澳写代码的人现在又移回香港?什么原因?

当年很多跑到美加澳写代码的人现在又移回香港&#xff1f;什么原因&#xff1f; 近年来&#xff0c;确实有部分曾经移民到美国、加拿大、澳大利亚等地的香港居民选择移回香港。这一现象与多种因素相关&#xff0c;主要可以归结为以下几点&#xff1a; 疫情后的环境变化&#…

【STM32】温湿度采集与OLED显示

一、任务要求 1. 学习I2C总线通信协议&#xff0c;使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集&#xff0c;并将采集的温度-湿度值通过串口输出。 任务要求&#xff1a; 1&#xff09;解释什么是“软件I2C”和“硬件I2C”&#xff1f;&#xff08;阅读野火配…