江协科技STM32学习- P27 实验-串口发送/串口接收

       🚀write in front🚀  
🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​ 

💬本系列哔哩哔哩江科大STM32的视频为主以及自己的总结梳理📚 

🚀Projeet source code🚀   

💾工程代码放在了本人的Gitee仓库:iPickCan (iPickCan) - Gitee.com

引用:

STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili

Keil5 MDK版 下载与安装教程(STM32单片机编程软件)_mdk528-CSDN博客

STM32之Keil5 MDK的安装与下载_keil5下载程序到单片机stm32-CSDN博客

0. 江协科技/江科大-STM32入门教程-各章节详细笔记-查阅传送门-STM32标准库开发_江协科技stm32笔记-CSDN博客

【STM32】江科大STM32学习笔记汇总(已完结)_stm32江科大笔记-CSDN博客

江科大STM32学习笔记(上)_stm32博客-CSDN博客

STM32学习笔记一(基于标准库学习)_电平输出推免-CSDN博客

STM32 MCU学习资源-CSDN博客

stm32学习笔记-作者: Vera工程师养成记

stem32江科大自学笔记-CSDN博客

术语:

英文缩写描述
GPIO:General Purpose Input Onuput通用输入输出
AFIO:Alternate Function Input Output复用输入输出
AO:Analog Output模拟输出
DO:Digital Output数字输出
内部时钟源 CK_INT:Clock Internal内部时钟源
外部时钟源 ETR:External Trigger 时钟源 External 触发
外部时钟源 ETR:External Trigger mode 1外部时钟源 External 触发 时钟模式1
外部时钟源 ETR:External Trigger mode 2外部时钟源 External 触发 时钟模式2
外部时钟源 ITRx:Internal Trigger inputs外部时钟源,ITRx (Internal trigger inputs)内部触发输入
外部时钟源 TIx:exTernal Input pin 外部时钟源 TIx (external input pin)外部输入引脚
CCR:Capture/Comapre Register捕获/比较寄存器
OC:Output Compare输出比较
IC:Input Capture输入捕获
TI1FP1:TI1 Filter Polarity 1Extern Input 1 Filter Polarity 1,外部输入1滤波极性1
TI1FP2:TI1 Filter Polarity 2Extern Input 1 Filter Polarity 2,外部输入1滤波极性2
DMA:Direct Memory Access直接存储器存取

正文:

0. 概述

从 2024/06/12 定下计划开始学习下江协科技STM32课程,接下来将会按照哔站上江协科技STM32的教学视频来学习入门STM32 开发,本文是视频教程 P2 STM32简介一讲的笔记。

1.🚢第一个代码-串口发送

接线图:

意:USB转串口的模块上面的跳线帽要插在VCC3V3这两个引脚上选择通信的TTL电平3.3V然后通信引脚TXD和RXD要接在STM32的PA9和PA10口,为什么是这两个口?查引脚定义表可得。

  • 🤠这里看到USART1的TX是PA9,RX是PA10,我们计划用USART1进行通信,所以就选这两个脚。
  • 🤠还要注意一个问题,TX和RX一定要交叉连接,比如这里PA9是STM32的TX,那么它要接的就是串口模块的RX接收,然后串口模块的TX发送要接在STM32的PA10,也就是RX接收。外。
  • 🤠然后两个设备之间要把负极接在一起,进行共地。

一般多个系统之间互联都要进行共地这样电平才能有高低的参考。就像两个人比身高一样,他俩必须要站在同一地平面上才能比较。如果一个人站在地球,一个人站在月球,那怎么知道谁高谁低?这就是共地的问题。

最后这个串口模块和STLINK或者USB串口都要插在电脑上,这样STM32和串口模块都有独立供电。所以这里通信的电源正极就不需要接了,直接三根线就行。

我们第一个代码只有STM32发送的部分,所以通讯线只有这个发送的有用,另一根线第一个代码没有用的,暂时可以不接。

在我们下一个串口发送加接收的代码,两根通信线就都需要接了。所以我们把这两根通信线一起都接上,这样两个代码的经线图是一模一样的。

连好线后,回到电脑端,此电脑-右键-属性-打开设备管理器,确保串口的驱动没问题,在这个端口目录下,可以看到有这个CH340的驱动,如果出现了COM号,并且前面图标没有感叹号,那就证明串口驱动没问题,否则的话需要安装一下串口模块的驱动(如需要安装串口驱动可以看江科大的第二节视频)。

初始化串口的步骤

初始化串口的步骤看这个基本结构图。

第一步开启时钟把需要用的USART和GPIO的时钟打开。

第二步,GPIO初始化把TX配置成复用输出,RX配置成输入。

第三步配置USART,直接使用一个结构体,就可以把这里所有的参数都配置好了。

第四步开关控制,如果只需要发送的功能,就直接开启USART初始化就结束了。如果需要接收的功能,可能还需要配置中断。那就在开启USART之前,再加上ITConfig和NVIC的代码就行了。

初始化完成之后,如果要发送数据,调用一个发送函数就行了。如果要接收数据,就调用接收的函数。如果要获取发送和接收的状态,就调用获取标志位的函数,这就是USART外设的使用思路。

USART的库函数

打开usart.h,拖到最后

这三个初始化函数大家应该都很熟悉了,不用说了

USART_ClockInit和USART_ClockStructInit

这两个函数是用来配置同步时钟输出的。包括时钟是不是要输出,时钟的极性、相位等参数,因为参数也比较多,所以也是用结构体这种方式来配置的,需要时钟输出的话可以了解一下这两个函数。

USART_Cmd 和 USART_ITConfig

这两个函数应该也很熟悉了。

USART_DMACmd

这个可以开启USART到DMA的触发通道,需要用DMA的话可以了解一下。

USART_SendData和USART_ReceiveData

这两个函数在我们发送和接收的时候会用到。SendData就是写DR寄存器,ReceiveData就是读DR寄存器。DR寄存器内部有四个寄存器,控制发送与接收,执行细节。我们上一小节已经分析过了,这里程序上就非常简单,写DR就是发送,读DR就是接收。至于怎么产生波形,怎么判断输入,软件一概不管。

四个标志位相关的函数

代码实现

Serial.c
第一步开启时钟

第二步,GPIO初始化

选择引脚模式,TX引脚是USART外设控制的输出脚。所以要选复用推挽输出,RX引脚是USART外设数据输入脚,所以要选择输入模式,输入模式并不分什么普通输入复用输入,一根线只能有一个输出,但可以有多个输入,所以对于输入脚,外设和GPIO都可以同时用。一般RX配置是浮空输入或者上拉输入,因为串口波形空闲状态是高电平所以不使用下拉输入

引脚模式如果不清楚的话,还是看一下手册GPIO那一节有个推荐的配置表,可以参考一下。

我们第一个程序只需要数据发送,所以只初始化TX就行。

第三步配置USART

定义结构体,列出结构体成员,选择每个成员的取值,调用USART_Init函数初始化USART。

第一个成员是波特率,写9600,写完之后,这个USART_Init函数内部会自动算好9600对应的分频系数。然后写到BRR寄存器,计算步骤上节讲过。

😎😎技巧:查找每个成员的取值时,可以把成员变量名复制放到等号右边,按CTRL+ALT+空格联想,就能看到取值的列表,如果没有那可能这个成员的取值是要自己写的,可以右键跳转到定义看看。

第二个成员是硬件流控制,它的取值列表

None不使用流控,只用CTS,只用RTS,或者CTS、RTS都使用

我们不使用流控,所以选择None。

第三个成员是串口模式,取值可以选择TX发送模式和RX接收模式。如果你既需要发送,又需要接收,那就用或符号把TX和RX或起来。我们这个程序只需要发送功能,所以就选择TX这一个参数就行。

第四个成员是校验位,取值是

检验位可以选择NO无校验,Odd奇校验,Even偶校验,我们不需要校验,所以选择NO。

第五个成员是停止位,取值可以是

我们选择1位停止位.

第六个成员是字长,可以选择八位或九位,我们不需要校验,所以字长就选择八位。

到这里,我们结构体参数的初始化就完成了。

第四步开关控制

发送数据的函数

接下来我们来写一个发送数据的函数Serial_SendByte,调用这个函数,就可以从TX引脚发送一个字节数据。

在这里面我们需要调用串口的USART_SendData函数,参数第一个给USART1,第二个给Byte。

这个函数的内部是这样的,把我们传的Byte给它的Data

Data与上01FF就是把无关的高位清零。然后直接赋值给DR寄存器,因为这是写入DR,所以数据最终通向TDR发送数据寄存器,TDR再传递给发送移位寄存器,最后一位一位地把数据移出到TX引脚,完成数据的发送。

调用函数,Byte变量就写入到TDR了。

写完之后,我们还需要等待一下,等TDR的数据转移到移位寄存器了,我们才能放心。要不然如果数据还在TDR进行等待,我们再写入数据,就会产生数据覆盖。所以在发送之后,我们还需要等待一下标志位,在这里调用USART_GetFlagStatus函数,这个函数的第二个参数可以选择

我们需要使用这个TXE发送数据寄存器空标志位复制。然后我们要等待TXE置1。

然后是标志位是否需要手动清除的问题,这个可以看一下手册

所以说这里标志位置1之后,不需要手动清零。当我们下一次再写入Data时,这个标志位会自动清零。

那这样我们这个Serial_SendByte函数就完成了,

如果需要测试这个发送部分的功能,则主函数里可以写这样写

调用这个函数之后,TX引脚就会产生一个0x41对应的波形。这个波形可以发送给其他支持串口的模块,也可以通过USB转串口模块发送到电脑端。

我们本节主要是和电脑通信,所以是在电脑端接收数据。

测试的时候别忘记将以上函数放在头文件中声明一下。

那我们编译下载后,我们按一下复位键。这时可以看到串口模块的接收指灯闪了一下,说明有波形发过来了。

那在电脑端我们需要打开串口助手软件,来查看接收到的数据。

串口助手软件领取链接

链接:https://pan.baidu.com/s/1ASVoP_LLIPUQ91fJ4dz6vw

提取码:hm48

UART.c

#include "stm32f10x.h"                  // Device header
#include "Uart.h"void UART_Init(void)
{//RCC使能外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //RCC使能UART时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //RCC使能GPIOA时钟//配置GPIOGPIO_InitTypeDef gpioInitStructure;gpioInitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//GPIO为复用推挽输出模式gpioInitStructure.GPIO_Pin = GPIO_Pin_9;			//GPIOA_Pin9为UART1_TxgpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpioInitStructure);//配置UARTUSART_InitTypeDef USART_InitStruct;USART_StructInit(&USART_InitStruct);USART_InitStruct.USART_BaudRate = 9600;USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//不使用UART硬件流控USART_InitStruct.USART_Mode = USART_Mode_Tx;		//UART使能Tx发送USART_InitStruct.USART_Parity = USART_Parity_No;	//UART无奇偶校验USART_InitStruct.USART_StopBits = USART_StopBits_1;	//1位停止位USART_InitStruct.USART_WordLength = USART_WordLength_8b;USART_Init(USART1, &USART_InitStruct);//使能UARTUSART_Cmd(USART1, ENABLE);
}void Serial_SendByte(uint8_t data)
{USART_SendData(USART1, data);while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待UART TXE标志位//写入之后等待TXE标志位置1,表示数据从TDR寄存器转移到了发送移位寄存器//如果不等待TDR数据移动到发送移位寄存器,下次写时就会覆盖上一次数据//该标志位在写DR寄存器时自动清除
}

UART.h

#ifndef __UAART_H__
#define __UAART_H__void UART_Init(void);
void Serial_SendByte(uint8_t data);#endif

Main.c

#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Countersensor.h"
#include "Encoder.h"
#include "Timer.h"
#include "AD.h"
#include "Delay.h"
#include "MyDMA.h"
#include "UART.h"extern uint16_t Num;int main(int argc, char *argv[])
{OLED_Init();OLED_ShowString(1, 1, "UART");UART_Init();Serial_SendByte(0x41);Serial_SendByte(0x55);while(1)                                                                                 {}return 1;
}

注意:串口助手上的串口号要和设备管理器上显示的串口号一致。并且串口助手上的参数也必须和我们代码中配置的一致。

串口助手上的参数配置好后,选择打开串口,然后串口助手就准备就绪,此时在USB转串口模块上按一下复位键,就能在串口助手上的接收区看到数据了。

使用淘宝购买的19块钱的24MHz逻辑分析仪抓下报文:

逻辑分析抓取到的STM32 UART发送 0x41 的波形,字长8位,无奇偶校验位,1个停止位

逻辑分析抓取到的STM32 UART发送  0x41, 0x55 两个字符的波形,字长8位,无奇偶校验位,1个停止位

使用9位字长,1个奇校验位,1个停止位。 逻辑分析抓取到的STM32 UART发送  0x41,0x40, 0x55 三个字符的波形,字长8位,无奇偶校验位,1个停止位

	//配置UARTUSART_InitTypeDef USART_InitStruct;USART_StructInit(&USART_InitStruct);USART_InitStruct.USART_BaudRate = 9600;USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//不使用UART硬件流控USART_InitStruct.USART_Mode = USART_Mode_Tx;		//UART使能Tx发送//USART_InitStruct.USART_Parity = USART_Parity_No;	//UART无奇偶校验USART_InitStruct.USART_Parity = USART_Parity_Odd;	//UART奇校验USART_InitStruct.USART_StopBits = USART_StopBits_1;	//1位停止位//USART_InitStruct.USART_WordLength = USART_WordLength_8b;USART_InitStruct.USART_WordLength = USART_WordLength_9b;USART_Init(USART1, &USART_InitStruct);

这就是发送一个字节数据的现象。

然后大家注意到下面这里有一个接收模式。目前选择的是HEX模式,也就是以原始数据的形式显示。发送41显示就是41本身。

如果我们想显示一下字符串怎么办?那就可以选择文本模式,这样就是以字符的形式

这里补充说明一下数据模式的解释

数据模式

HEX模式/十六进制模式/二进制模式(这些描述都是一个意思):以原始数据的形式显示

文本模式/字符模式(这些描述都是一个意思):以原始数据编码后的形式显示(对应ASCII码表上的符号)

 下面这个表展示的就是ASCII码字符集,比如0x41这个数据对应的就是大写字母A

字符集的第一个字符,原始数据是0x00,对应字符是空字符,也就是保留位,不映射任何字符。一般这个0经常作为字符串的结束标志位,字符串遇到数据0x00之后,就代表字符串结束了。

随着计算机的发展,全球互相通信。为了防止不同国家编码的不兼容现象,我们可以把所有国家的字符全部收录到一个统一的字符集,这就是Unicode字符集。Unicode最常用的传输形式是UTF,有关字符编码的内容,大家可以自己再去网上搜一搜。如果编码不匹配,就会出现非常烦人的乱码,这个得注意一下。

下面这个图描述的是字符和数据在发送和接收的转换关系

比如最上面发送0x41数据,发送的线路传输的就是0x41。接收方如果以原始数据形式显示,就是0x41。如果以字符显示,就是走下面这一路,通过字符集译码,找到字符,然后显示字符’A’。

在发送方也可以直接发送字符,比如发送字符A,这时它就会先从字符集找到A的数据进行编码,发现A对应的数据是0x41。最终在线路中传输的必须是十六进制数0x41。然后接收方可以选择查看原始数据0x41,也可进行译码得到字符A。这就是字符和数据在发送接收过程中经历的变化。

接下来我们再封装一些函数模块,这些函数大家之后用串口肯定会经常用的,光有一个发送字节函数满足不了需求。

接下来写的函数其实都是对SendByte的封装。

发送数组的函数

首先是发送一个数组,我们定义一个发送数组的函数Serial_SendArray

然后在主函数中这样调用

发送结果

发送字符串的函数

接下来写一个发送字符串的函数Serial_SendString,然后由于字符串自带一个结束标志位,所以就不需要再传递长度参数了。

在里面执行逻辑和发送字符是非常类似的先定义变量由int i。再赋i等于零,这里循环条件就可以用结束标志位来判断了string i。

在主函数里这样调用

现象

如果想要换行可以在后面加上\r\n来执行换行命令

这样就能在每次打印之后就会执行一次换行命令了

发送数字的函数

接下来我们继续分装发送数字的函数Serial_SendNumber。在函数里面,我们需要把number的个位、十位、百位等等以十进制拆分开,然后转换成字符数字对应的数据,依次发送出去。

怎么以十进制拆开?

比如有个数字是12345,

取万位,就是12345/1000%10=1,

取千位,就是12345/1000%10=2,

取百位,就是12345/100%10=3,

取十位,就是12345/10%10=4,

取个位,就是12345/1%10=5。

总结下来,取某一位,就是数字除以十的x次方,再对此取余。除以十的x次方,就是把这一位的右边去掉,对十取余就是把左边去掉。这就是拆分数字的思路。

所以我们先需要写一个次方函数,

现在就可以写发送数字的函数了,这个函数默认是发送十进制数字,然后接收是接收到字符形式的数字

在主函数里这样调用

结果

程序源码

Serial.c

#include "stm32f10x.h"                  // Device header
#include "Uart.h"void UART_Init(void)
{//RCC使能外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //RCC使能UART时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //RCC使能GPIOA时钟//配置GPIOGPIO_InitTypeDef gpioInitStructure;gpioInitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//GPIO为复用推挽输出模式gpioInitStructure.GPIO_Pin = GPIO_Pin_9;			//GPIOA_Pin9为UART1_TxgpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpioInitStructure);//配置UARTUSART_InitTypeDef USART_InitStruct;USART_StructInit(&USART_InitStruct);USART_InitStruct.USART_BaudRate = 9600;USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//不使用UART硬件流控USART_InitStruct.USART_Mode = USART_Mode_Tx;		//UART使能Tx发送USART_InitStruct.USART_Parity = USART_Parity_No;	//UART无奇偶校验//USART_InitStruct.USART_Parity = USART_Parity_Odd;	//UART无奇偶校验USART_InitStruct.USART_StopBits = USART_StopBits_1;	//1位停止位USART_InitStruct.USART_WordLength = USART_WordLength_8b;//USART_InitStruct.USART_WordLength = USART_WordLength_9b;USART_Init(USART1, &USART_InitStruct);//使能UARTUSART_Cmd(USART1, ENABLE);
}void Serial_SendByte(uint8_t data)
{USART_SendData(USART1, data);while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待UART TXE标志位//写入之后等待TXE标志位置1,表示数据从TDR寄存器转移到了发送移位寄存器//如果不等待TDR数据移动到发送移位寄存器,下次写时就会覆盖上一次数据//该标志位在写DR寄存器时自动清除
}void Serial_SendArry(uint8_t data[], uint8_t length)
{for(int i=0; i<length; i++){Serial_SendByte(data[i]);}
}void Serial_SendString(char *str)
{if(str){while(*str != '\0'){Serial_SendByte(*str++);}}
}uint32_t Serial_Power(uint32_t x, uint32_t y)
{uint32_t ret = 1;while(y > 0){ret *= x;y--;}return ret;
}void Serial_SendNumber(uint32_t number, uint16_t length)
{int i = 0;uint8_t data;for(i=0; i<length; i++){data = number/Serial_Power(10, length - i -1)%10 + '0';Serial_SendByte(data);}
}

main.c

#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Countersensor.h"
#include "Encoder.h"
#include "Timer.h"
#include "AD.h"
#include "Delay.h"
#include "MyDMA.h"
#include "UART.h"extern uint16_t Num;int main(int argc, char *argv[])
{OLED_Init();OLED_ShowString(1, 1, "UART");UART_Init();//Serial_SendByte(0x41);//Serial_SendByte(0x40);//Serial_SendByte(0x55);uint8_t array[4] = {0x41,0x42, 0x43, 0x44};char string[]= "ABCD";//Serial_SendArry(array, 4);Serial_SendString("hello world\r\n");Serial_SendString("test STM32 Serial printf 2024/10/31\r\n");Serial_SendNumber(49875, 5);Serial_SendString("\r\n");Serial_SendNumber(98765341, 8);Serial_SendString("\r\n");while(1)                                                                                 {}return 1;
}

printf函数的移植方法

最后介绍一下printf函数的移植方法。

第一种方法

使用printf之前,我们需要打开工程选项,把这个勾选上

MicroLIB是Keil为嵌入式平台优化的一个精简库,我们等会要用的printf函数,就可以用这个MicroLIB,所以先勾上这个。

然后我们还需要对printf进行重定向,将printf函数打印的东西输出到串口。

🐞🐞因为printf函数默认是输出到屏幕,我们单片机没有屏幕,所以要进行重定向,步骤就是在串口模块里最开始加上#include <stdio.h>

之后在最底下重写fputc函数

那重定向fputc跟printf有什么关系?

这是因为这个fputc是printf函数的底层,printf函数在打印的时候,就是不断调用fputc函数一个个打印的,我们把fputc函数重定向到了串口,那printf自然就输出到串口了。

这样printf就移植好了,我们到主函数试一下,这里直接写printf

结果

接下来再介绍两种printf函数的移植方法。刚才这一种方法,printf只能有一个,你重定向到串口1了,那串口2再用就没有了。如果多个串口都想用printf怎么办?

第二种方法

这时就可以用sprintf,sprintf可以把格式化字符输出到一个字符串里。所以这里可以先定义一个字符串,长度给100,然后sprintf第一个参数是打印输出的位置,我们指定打印到string,之后就跟printf一样了。

目前这个格式化的字符串在string里,最后需要再来一个Serial_SendString把字符串通过串口发送出去,这样就完成了。

这里就是在内存里创建一块char类型的string数组,然后赋值,再通过之前定义好的Serial_SendString函数通过USART1发送String。

因为sprintf可以指定打印位置,不涉及重定向的东西,所以每个串口都可以使用sprintf进行格式化打印。

现象是一样的

最后再介绍一种方法。

第三种方法

sprintf每次都得先定义字符串,再打印到字符串,再发送字符串,太麻烦了。我们要是能分装一下这个过程就再好不过了。

所以第三种方法就是封装sprintf。由于printf这类函数比较特殊,它支持可变的参数,像我们之前写的函数,参数的个数都是固定的,可变现参数,这个执行起来比较复杂。

首先在串口模块里先添加头文件#include <stdarg.h>

然后在最后对sprintf函数进行分装

这里面出现了很多没见过的函数,如果没学过这种用法,可能比较难理解,这个也没关系,知道这样移植就行了。如果学过了,那其实是基本操作。也可以学学这种可变参数的用法,自己写其他函数的时候,也可以用。

在主函数里调用

这样也可以实现printf的功能

以上就是printf函数的移植方法,最常见的是第一种。如果你有多个printf的需求,可以了解一下后两种方法。

显示汉字的操作方法

最后再讲一个显示汉字的操作方法

第一种方法

目前我们这个汉字编码格式选的是UTF8,所以最终发送到串口,汉字会以UTF8的方式编码,最终串口助手也得选择UTF8才能解码正确。

先说一下UTF8不乱码的方案,比如这里写个字符串,你好世界。

不过这样直接写汉字,编译器有时候会报错,这里需要打开工程选项,这里写上--no-multibyte-chars,需要给编译器输入一个这样的参数。

ok这样编译下载看看在串口助手这里目前是乱码

编码方式要选择UTF-8这样就不会乱码了

这是UTF8的解决方案,但是UTF8可能有些软件兼容性不好,所以第二种方式就是切换为GB2312编码,这是汉字的编码方式。

2.🚢第二个代码:串口的发送+接收

接线图:

接线图和上一个是一样的。

然后初始化这里加上接收的部分。

首先是GPIO口,我们要使用RX的引脚。在引脚定义表里,我们知道USART1的RX,复用在了PA10引脚

所以这里需要再初始化一下PA10。

引脚模式可以选择浮空输入或上拉输入,我们就选择上拉输入模式。

下面这些参数大部分不用改,只需要改一下模式,这个参数后面加上一个或串口模式Rx。

如果你只需要接收,那就把前面这个TX去掉就行了。

对串口接收来说,可以使用查询和中断两种方法。如果使用查询,那初始化就结束了。如果使用中断,那还需要在这里开启中断配置NVIC。像我们最开始演示的那个现象,使用查询的方法就可以完成。

这里先演示一下查询,再演示一下中断

查询的流程是在主函数里不断判断RXNE标志位,如果置1了,就说明收到数据了,那再调用receive Data读取DR寄存器,这样就行了。

直接在主函数演示,就不再分装了。

目前接收到的一个字节,数据就已经在RxData里了。

接下来我们可以进行显示。然后还有一个清除标志位的问题

🐞🐞当RDR移位寄存器中的数据被转移到USART_DR寄存器中,该位被硬件置位。如果USART_CR1寄存器中的RXNEIE为1则产生中断。🐞🐞对USART_DR的读操作可以将该位清零

 这里DR可以自动清零标志位,所以这里读完DR就不需要我们再清除标志位了。

UART.c

#include "stm32f10x.h"                  // Device header
#include "Uart.h"
#include <stdio.h>
#include <stdarg.h>void UART_Init(void)
{//RCC使能外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //RCC使能UART时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //RCC使能GPIOA时钟//配置GPIOGPIO_InitTypeDef gpioInitStructure;gpioInitStructure.GPIO_Mode = GPIO_Mode_IPU;		//GPIO为复用推挽输出模式gpioInitStructure.GPIO_Pin = GPIO_Pin_9;			//GPIOA_Pin9为UART1_TxgpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpioInitStructure);gpioInitStructure.GPIO_Mode = GPIO_Mode_IPU;		//GPIO为上拉输入gpioInitStructure.GPIO_Pin = GPIO_Pin_10;			//GPIOA_Pin9为UART1_RxgpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpioInitStructure);//配置UARTUSART_InitTypeDef USART_InitStruct;USART_StructInit(&USART_InitStruct);USART_InitStruct.USART_BaudRate = 9600;USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//不使用UART硬件流控USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;		//UART使能Tx发送USART_InitStruct.USART_Parity = USART_Parity_No;	//UART无奇偶校验USART_InitStruct.USART_StopBits = USART_StopBits_1;	//1位停止位USART_InitStruct.USART_WordLength = USART_WordLength_8b;USART_Init(USART1, &USART_InitStruct);//使能UARTUSART_Cmd(USART1, ENABLE);
}void Serial_SendByte(uint8_t data)
{USART_SendData(USART1, data);while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待UART TXE标志位//写入之后等待TXE标志位置1,表示数据从TDR寄存器转移到了发送移位寄存器//如果不等待TDR数据移动到发送移位寄存器,下次写时就会覆盖上一次数据//该标志位在写DR寄存器时自动清除
}void Serial_SendArry(uint8_t data[], uint8_t length)
{for(int i=0; i<length; i++){Serial_SendByte(data[i]);}
}void Serial_SendString(char *str)
{if(str){while(*str != '\0'){Serial_SendByte(*str++);}}
}uint32_t Serial_Power(uint32_t x, uint32_t y)
{uint32_t ret = 1;while(y > 0){ret *= x;y--;}return ret;
}void Serial_SendNumber(uint32_t number, uint16_t length)
{int i = 0;uint8_t data;for(i=0; i<length; i++){data = number/Serial_Power(10, length - i -1)%10 + '0';Serial_SendByte(data);}
}/* 重写fputc()函数,重定向printf()到串口**/
int fputc(int ch, FILE *f)
{Serial_SendByte(ch);return ch;
}void Serial_Printf(char *format, ...)
{char buf[128];va_list arg;va_start(arg, format);vsprintf(buf, format, arg);va_end(arg);Serial_SendString(buf);
}

main.c

#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Countersensor.h"
#include "Encoder.h"
#include "Timer.h"
#include "AD.h"
#include "Delay.h"
#include "MyDMA.h"
#include "UART.h"
#include <stdio.h>extern uint16_t Num;int main(int argc, char *argv[])
{OLED_Init();OLED_ShowString(1, 1, "UART:");UART_Init();while(1)                                                                                 {if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET){uint8_t data = USART_ReceiveData(USART1);OLED_ShowString(2, 1, "RX:");OLED_ShowHexNum(2, 4, data, 2);}}return 1;
}

下载看一下现象

我们需要在串口助手发送区里写入数据,发送模式选择hex模式。然后在这里写,比如41。

在hex模式下,这里只能写十六进制数,非法字符都将会被忽略。

点发送,在OLED上可以看到就收到了数据41。

这就是查询方法的串口接收程序现象。如果程序比较简单,查询方法是可以考虑的。那接下来我们再演示一下中断方法的程序。

如何使用中断?

首先在GPIO初始化后面这里要加上开启中断的代码。

在初始化NVIC

到这里RXNE标志位一旦置1了,就会向NVIC申请中断之后,我们可以在中断函数里接收数据。

中断函数的名字要启动文件找一下:

找到名字后写中断函数

🐞🐞进入if之后,那if的最后要不要清除标志位?

🐞🐞如果你读取了DR,就可以自动清除。如果没读取DR,就需要手动清除,我们这里直接给清一下,这个也不影响。

之后在这里面,可以直接读取DR执行一些操作。当然由于这个代码是在模块里不太适合加入过多其他的代码。所以就先在最上面定义两个变量

然后在这个中断函数里面,我们先读取到模块的变量里,读完之后,置一个自己的标志位。

我们也实现一个读后自动清除的功能

下面再写一个获取串口接收的数据

到这里,中断接收和变量的封装就完成了。其实这里就是在中断里把数据进行了一次转存。最终还是要扫描查询这个RxFlag来接收数据的。

对于这种单字节接收来说,可能转存一下意义不大。这里这样写,主要是给大家演示一下中断接收的操作方法。另外也是为我们下节多字节数据包接收做一个铺垫。

UART.c

#include "stm32f10x.h"                  // Device header
#include "Uart.h"
#include <stdio.h>
#include <stdarg.h>void UART_Init(void)
{//RCC使能外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //RCC使能UART时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //RCC使能GPIOA时钟//配置GPIOGPIO_InitTypeDef gpioInitStructure;gpioInitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		//GPIO为复用推挽输出模式gpioInitStructure.GPIO_Pin = GPIO_Pin_9;			//GPIOA_Pin9为UART1_TxgpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpioInitStructure);gpioInitStructure.GPIO_Mode = GPIO_Mode_IPU;		//GPIO为上拉输入gpioInitStructure.GPIO_Pin = GPIO_Pin_10;			//GPIOA_Pin9为UART1_RxgpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpioInitStructure);//配置UARTUSART_InitTypeDef USART_InitStruct;USART_StructInit(&USART_InitStruct);USART_InitStruct.USART_BaudRate = 9600;USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//不使用UART硬件流控USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;		//UART使能Tx发送USART_InitStruct.USART_Parity = USART_Parity_No;	//UART无奇偶校验USART_InitStruct.USART_StopBits = USART_StopBits_1;	//1位停止位USART_InitStruct.USART_WordLength = USART_WordLength_8b;USART_Init(USART1, &USART_InitStruct);//使能UART中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//NVIC配置UART中断优先级NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;		//使能UART1_IRQ中断NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStruct);//使能UARTUSART_Cmd(USART1, ENABLE);
}void Serial_SendByte(uint8_t data)
{USART_SendData(USART1, data);while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待UART TXE标志位//写入之后等待TXE标志位置1,表示数据从TDR寄存器转移到了发送移位寄存器//如果不等待TDR数据移动到发送移位寄存器,下次写时就会覆盖上一次数据//该标志位在写DR寄存器时自动清除
}void Serial_SendArry(uint8_t data[], uint8_t length)
{for(int i=0; i<length; i++){Serial_SendByte(data[i]);}
}void Serial_SendString(char *str)
{if(str){while(*str != '\0'){Serial_SendByte(*str++);}}
}uint32_t Serial_Power(uint32_t x, uint32_t y)
{uint32_t ret = 1;while(y > 0){ret *= x;y--;}return ret;
}void Serial_SendNumber(uint32_t number, uint16_t length)
{int i = 0;uint8_t data;for(i=0; i<length; i++){data = number/Serial_Power(10, length - i -1)%10 + '0';Serial_SendByte(data);}
}/* 重写fputc()函数,重定向printf()到串口**/
int fputc(int ch, FILE *f)
{Serial_SendByte(ch);return ch;
}void Serial_Printf(char *format, ...)
{char buf[128];va_list arg;va_start(arg, format);vsprintf(buf, format, arg);va_end(arg);Serial_SendString(buf);
}volatile uint8_t Seial_RxFlag = 0;
volatile uint8_t Seial_Data = 0;//中断服务函数
void USART1_IRQHandler()
{Seial_RxFlag = 1;Seial_Data = USART_ReceiveData(USART1);//清除UART RXNE 标志位USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}uint8_t Serial_GetRxFlag(void)
{uint8_t ret = 0;if(Seial_RxFlag){ret = 1;Seial_RxFlag = 0;}return ret;
}uint8_t Serial_GetRxData(void)
{return Seial_Data;
}

main.c

#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Countersensor.h"
#include "Encoder.h"
#include "Timer.h"
#include "AD.h"
#include "Delay.h"
#include "MyDMA.h"
#include "UART.h"
#include <stdio.h>extern uint16_t Num;int main(int argc, char *argv[])
{OLED_Init();OLED_ShowString(1, 1, "UART:");UART_Init();printf("Hello World\r\n");OLED_ShowString(2, 1, "RX:");while(1)                                                                                 {if(Serial_GetRxFlag() == 1){uint8_t data = Serial_GetRxData();Serial_SendByte(data);//OLED_ShowHexNum(2, 4, data, 2);}}return 1;
}

实验现象

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

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

相关文章

Linux笔记--基础入门

文章目录 Linux基础知识点文件目录*磁盘分区**基础命令*Linux运行级别关机重启手册alias别名ntsysv系统服务管理程序 Linux常用命令命令分类命令行格式选项参数 命令行辅助操作 真常用命令()help命令&#xff1a;帮助指令man手册页manual page绝对路径与相对路径绝对路径&#…

11月1日星期五今日早报简报微语报早读

11月1日星期五&#xff0c;农历十月初一&#xff0c;早报#微语早读。 1、六大行今日起实施存量房贷利率新机制。 2、谷歌被俄罗斯罚款35位数&#xff0c;罚款远超全球GDP。 3、山西吕梁&#xff1a;女性35岁前登记结婚&#xff0c;给予1500元奖励。 4、我国人均每日上网时间…

Pandas DataFrame学习补充

1. 从字典创建&#xff1a;字典的键成为列名&#xff0c;值成为列数据。 import pandas as pd# 通过字典创建 DataFrame df pd.DataFrame({Column1: [1, 2, 3], Column2: [4, 5, 6]}) 2. 从列表的列表创建&#xff1a;外层列表代表行&#xff0c;内层列表代表列。 df pd.Da…

<项目代码>YOLOv8 煤矸石识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

推荐一款功能强大的文字处理工具:Atlantis Word Processor

Atlantis word proCEssor是一款功能强大的文字处理工具。该软件可以让用户放心的去设计文档&#xff0c;并且软件的界面能够按用户的意愿去自定义&#xff0c;比如工具栏、字体选择、排版、打印栏等等&#xff0c;当然还有更多的功能&#xff0c;比如你还可以吧软件界面中的任何…

「虚拟现实中的心理咨询:探索心灵世界的新方法」

内容概要 当我们想到虚拟现实时&#xff0c;很多人会联想到游戏或娱乐&#xff0c;但如今其在心理咨询领域的应用正在逐渐崭露头角。传统的心理咨询方式常常局限在咨询室内&#xff0c;面临着空间和情感隔阂的问题。然而&#xff0c;沉浸式环境的出现&#xff0c;使得治疗者能…

图像修复与重建——几何失真(畸变)的概念

一 几何失真&#xff08;畸变&#xff09;的概念 在实际的成像系统中&#xff0c;图像捕捉介质平面和物体平面之间不可避免地存在有一定的转角和倾斜角。转角对图像的影响是产生图像旋转&#xff0c;倾斜角的影响表现为图像发生投影变形。另外一种情况是由于摄像机系统本身的原…

Spark的集群环境部署

一、Standalone集群 1.1、架构 架构&#xff1a;普通分布式主从架构 主&#xff1a;Master&#xff1a;管理节点&#xff1a;管理从节点、接客、资源管理和任务 调度&#xff0c;等同于YARN中的ResourceManager 从&#xff1a;Worker&#xff1a;计算节点&#xff1a;负责利…

使用 Python 的 BeautifulSoup 与 Flask/Flask-RESTful 集成进行数据爬取和 API 构建

使用 Python 的 BeautifulSoup 与 Flask/Flask-RESTful 集成进行数据爬取和 API 构建 在现代 Web 开发中&#xff0c;许多应用需要从其他网页提取数据并将其呈现为 API 服务。Python 的 BeautifulSoup 是一个流行的 HTML 解析库&#xff0c;用于从网页抓取和解析数据&#xff…

江协科技STM32学习- P28 USART串口数据包

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

Verilog实现的莫尔斯电码发生器

莫尔斯或者摩尔斯电码(Morse Code)&#xff0c;发明于1837年(另有一说是1836年)&#xff0c;通过不同的排列顺序来表达不同的英文字母、数字和标点符号&#xff0c;在这里作一简单处理&#xff0c;仅产生点(Dit)和划(Dah)&#xff0c;时长在0.25秒之内为点&#xff0c;超过为划…

PFC前端电路 -- EMI电路

一、EMI(Electromagnetic Interference)抗干扰电路 在PFC&#xff08;功率因数校正&#xff09;电路中&#xff0c;EMI&#xff08;电磁干扰&#xff09;滤波电路是至关重要的组成部分。EMI滤波电路的主要功能是抑制电磁干扰&#xff0c;以确保电源的电磁兼容性&#xff08;EM…

网关三问:为什么微服务需要网关?什么是微服务网关?网关怎么选型?

文章整体介绍 本文旨在解答关于微服务网关的三个核心问题&#xff1a; 1&#xff09;为什么需要网关&#xff1f;也即在何种场景下应采用微服务网关以优化系统架构&#xff1b; 2&#xff09;什么是微服务网关&#xff1f;主要讲构成微服务网关的关键能力&#xff0c;包括但…

【深度学习】实验 — 动手实现 GPT【三】:LLM架构、LayerNorm、GELU激活函数

【深度学习】实验 — 动手实现 GPT【三】&#xff1a;LLM架构、LayerNorm、GELU激活函数 模型定义编码一个大型语言模型&#xff08;LLM&#xff09;架构 使用层归一化对激活值进行归一化LayerNorm代码实现scale和shift 实现带有 GELU 激活的前馈网络测试 模型定义 编码一个大…

基于vue框架的的考研网上辅导系统ao9z7(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,公告信息,课程分类,考研资料,考研视频,课程信息,院校建议,教师 开题报告内容 基于Vue框架的考研网上辅导系统开题报告 一、研究背景与意义 随着高等教育的普及和就业竞争的加剧&#xff0c;考研已成为众多大学生提升学历、增强就…

GPU 学习笔记二:GPU单机多卡组网和拓扑结构分析(基于A100的单机多卡拓扑结构分析)

文章目录 一、物理拓扑结构A100讲解1.1 单机多卡拓扑结构 二、术语和基础技术介绍2.1 带宽单位2.2 PCIe及PCIe Switch2.3 NVLink2.4 网卡2.5 带宽瓶颈分析2.6 HBM2.7 CPU/GPU 三、其他典型物理拓扑3.1 H100/H800拓扑分析 防止遗忘和后续翻找的麻烦&#xff0c;记录下平时学到和…

Webserver(2.6)有名管道

目录 有名管道有名管道使用有名管道的注意事项读写特性有名管道实现简单版聊天功能拓展&#xff1a;如何解决聊天过程的阻塞 有名管道 可以用在没有关系的进程之间&#xff0c;进行通信 有名管道使用 通过命令创建有名管道 mkfifo 名字 通过函数创建有名管道 int mkfifo …

超分子水凝胶与细胞的互动:现状、难题与未来蓝图

大家好&#xff01;今天来了解超分子水凝胶文章——《Using Chemistry To Recreate the Complexity of the Extracellular Matrix: Guidelines for Supramolecular Hydrogel–Cell Interactions》发表于《Journal of the American Chemical Society》。在再生医学的舞台上&…

逗号运算符应用举例

在main.cpp里输入程序如下&#xff1a; #include <iostream> //使能cin(),cout(); #include <iomanip> //使能setbase(),setfill(),setw(),setprecision(),setiosflags()和resetiosflags(); //setbase( char x )是设置输出数字的基数,如输出进制数则用set…

分类算法——决策树 详解

决策树的底层原理 决策树是一种常用的分类和回归算法&#xff0c;其基本原理是通过一系列的简单决策&#xff0c;将数据集划分为多个子集&#xff0c;从而实现分类。决策树的核心思想是通过树形结构表示决策过程&#xff0c;节点代表特征&#xff0c;边代表决策&#xff0c;叶子…