赞
踩
参考:串口的结构体 重定向printf串口发送stm32等博文
作者:点灯小哥
发布时间: 2021-03-06 21:46:33
网址:https://blog.csdn.net/weixin_46016743/article/details/114458698
串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,其通讯协议可分层为协议层和物理层。
串口通讯的物理层的主要标准是RS-232标准,其规定了信号的用途、通讯接口及信号的电平标准,其通讯结构如下:
在设备内部信号是以TTL电平标准传输的,设备之间是通过RS-232电平标准传输的,而且TTL电平需要经过电平转换芯片才能转化为RS-232电平,RS-232电平转TTL电平也是如此。
根据使用的电平标准不同,串口通讯可分为 RS-232标准及TTL标准,具体标准如下:
在电子电路中常使用TTL的电平标准,但其抗干扰能力较弱,为了增加串口的通讯距离及抗干扰能力,使用RS-232电平标准在设备之间传输信息,经常使用MAX232芯片对TTL电平及RS-232电平进行相互转换。
USART(通用同步异步收发器)是一个串行通信设备,可以灵活地与外部设备进行全双工数据交换。
UART,它是在 USART 基础上裁剪掉了同步通信功能,只有异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART。
USART 在 STM32 应用最多莫过于“打印”程序信息,一般在硬件设计时都会预留一USART 通信接口连接电脑,用于在调试程序是可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、如果出错哪具体哪里出错等等。
起始位、数据位(8位或者9位)、奇偶校验位(第9位)、起始停止位(1,15,2位)、波特率设置
校验方式 :
奇偶校验需要一位校验位,即使用串口通信的方式2或方式3(8位数据位+1位校验位)。
奇校验(odd parity):让传输的数据(包含校验位)中1的个数为奇数。
即:如果传输字节中1的个数是偶数(不包含校验位),则校验位为“1”,奇数相反。
比特(bit):每秒传输的二进制位
波特(byte):每秒传输的码源个数(串口常用),一个二进制位表示一个码源(0V——0;3.3V——1)
注:这俩本质上其实是一样的
单工:数据传输只支持数据在一个方向上传输;如:打印机
半双工:允许数据在两个方向上传输。但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;它不需要独立的接收端和发送端,两者可以合并一起使用一个端口。如:对讲机,spi
全双工:允许数据同时在两个方向上传输。因此,全双工通信是两个单工通信方式的结合,需要独立的接收端和发送端。如:spi,usart
1.数据包
串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备得RXD接口,在协议层中规定了数据包的内容,具体包括起始位、主体数据(8位或9位)、校验位以及停止位,通讯的双方必须将数据包的格式约定一致才能正常收发数据。
2.波特率
由于异步通信中没有时钟信号,所以接收双方要约定好波特率,即每秒传输的码元个数,以便对信号进行解码,常见的波特率有4800、9600、115200等。STM32中波特率的设置通过串口初始化结构体来实现。
3.起始和停止信号
数据包的首尾分别是起始位和停止位,数据包的起始信号由一个逻辑0的数据位表示,停止位信号可由0.5、1、1.5、2个逻辑1的数据位表示,双方需约定一致。STM32中起始和停止信号的设置也是通过串口初始化结构体来实现。
4.有效数据
有效数据规定了主题数据的长度,一般为8或9位,其在STM32中也是通过串口初始化结构体来实现的。
5.数据校验
在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验(odd)、偶校验(even)、 0 校验(space)、 1 校验(mark)以及无(noparity)。这些也都可以在串口初始化结构体中实现的。
下面是最小板原理图,UART4和UART5并没有引出来。
串口标志位,用于说明串口发送接收状态,是否都发/收完了
新建一个usart文件夹,里面新建usart.c和usart.h两个文件,将usart.c添加到工程里面(user一栏下),编译一下usart.h会自动添加进工程。
usart.h
#include "stm32f10x.h"
void Usart_Init(void);
usart.c
#include "stm32f10x.h" // Device header #include "usart.h" void Usart_Init(void) { //2. 配置GPIO的结构体 GPIO_InitTypeDef GpioInitStructure; //初始化GPIO结构体命名 //3. 配置USART的结构体(另外一个结构体是带时钟的串口) USART_InitTypeDef UsartInitStructure;//初始化USART结构体命名 //1. 时钟使能: GPIOA的时钟,引脚复用(成串口)的时钟,串口的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//开启APB2总线复用时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //2.1 配置PA9 TX(输出) GpioInitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出(因为复用成串口) GpioInitStructure.GPIO_Pin = GPIO_Pin_9; GpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz;//任意选择,影响不大 GPIO_Init(GPIOA,&GpioInitStructure); //2.2 配置PA10 RX(接收) GpioInitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 GpioInitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_Init(GPIOA,&GpioInitStructure); //3.配置串口结构体 这是不带时钟的结构体 还有一个是带时钟的结构体 UsartInitStructure.USART_BaudRate = 115200; //波特率 UsartInitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流(没有使用) UsartInitStructure.USART_Mode = USART_Mode_Rx| USART_Mode_Tx;//模式(输入输出都选择) UsartInitStructure.USART_Parity = USART_Parity_No; //校验位(不用) UsartInitStructure.USART_StopBits = USART_StopBits_1; //停止位(1位) UsartInitStructure.USART_WordLength = USART_WordLength_8b;//有效字节长度(8位) //串口1 USART_Init(USART1, &UsartInitStructure); USART_Cmd(USART1, ENABLE);//打开串口 比配置GPIO多这一步 }
main.c
#include "stm32f10x.h" #include "usart.h"//头文件是单独创建的文件 main函数找不到 要去手工添加路径(点击魔术棒...) void delay(uint16_t time) { uint16_t i = 0; while(time--) { i=12000; while(i--); } } int main(void) { Usart_Init(); while(1) { USART_SendData(USART1, 'O'); //发送一个字符 //数据寄存器空标志位 RESET状态说明上面字符发送成功了! while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //USART_GetFlagStatus是判断标志位 USART_SendData(USART1, 'K'); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, '\n'); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //右键USART_GetFlagStatus去goto找到RESET delay(1000); } }
上一节串口只能一个一个发送字符,可以自己写一个串口发送字符串函数,然后main函数调用即可。
usart.c
//发送字符函数 (自己定义的 注意形参要和固件库里面串口发送字符的形参要一样) //调用这个函数不用每次都去判断标志位 void USARTSendByte(USART_TypeDef* USARTx, uint16_t Data)//main函数调用这个函数的方法:USARTSendByte(USART1, 'A'); { USART_SendData( USARTx, Data);//固件库本身的串口发送函数 //判断数据寄存器是否为空 字符标志位 while( USART_GetFlagStatus( USARTx, USART_FLAG_TXE) == RESET);//USART_GetFlagStatus是判断标志位 USART_FLAG_TXE 去usart.h FLAG找 } //发送字符串函数(自己定义的) void USARTSendString( USART_TypeDef* USARTx, char *str) { uint16_t i = 0; do{ USARTSendByte(USART1,*(str+i)); i++; }while(*(str+i) != '\0');//字符串结束标识 //字符串标志位 上面是字符标志位 while( USART_GetFlagStatus( USARTx, USART_FLAG_TC) == RESET);//USART_GetFlagStatus是判断标志位 USART_FLAG_TC(这是判断字符串) 去usart.h FLAG找 } //main函数里这样调用 USARTSendByte( USART1, 'O'); USARTSendByte( USART1, 'K'); USARTSendString( USART1, "你好STM32");
在C语言标准库中,printf()拥有十分强大的输出能力,可以输出各种类型的数据,整型、浮点型、8进制、16进制、换行符,缩进符等等。
printf()是把数据输出到屏幕,但是ARM芯片中没有屏幕,我们设想将printf()打印到串口,这样我们就可以通过printf()和串口实时的观察ARM芯片内部的工作情况,运行结果。
先来认识一个关键字:__weak
weak的字面意思就是“微弱”的意思,其主要作用就是可以重新定义重名函数或变量而编译时不报错。笔者最开始注意到这个关键字是在使用 STM32 HAL 库的时候注意到的,比如这张图片所示:
在上图我们可以看到左边的 HAL_MspInit 函数前面用 __weak 进行修饰,而图片右边又定义了 HAL_MspInit函数,这时整个工程就定义了两个 HAL_MspInit 函数,声明可以有多个,但是定义只能存在一个,因为 __weak的存在,所以不会报错,并且真正起作用的函数是没有用 __weak 修饰的函数。
printf函数其实就是调用了fputc,我们来重写fputc达到重定向printf的目的。
实现过程:在usart.c 的后面增加两个函数
usart.c
int fputc(int ch, FILE *f)//(串口发送)重写fputc,供printf调用
{
USARTSendByte( USART1, (uint8_t)ch);
while( USART_GetFlagStatus( USART1, USART_FLAG_TXE) == RESET);
return (ch);
}
int fgetc(FILE *f)//(串口接收)重写fgetc
{ //标志位选择接收
while( USART_GetFlagStatus( USART1, USART_FLAG_RXNE) == RESET);
return (int) USART_ReceiveData(USART1);
}
usart.h
#include "stm32f10x.h"
#include <stdio.h>//标准输入输出printf
void Usart_Init(void);
void USARTSendByte(USART_TypeDef* USARTx, uint16_t Data);
void USARTSendString( USART_TypeDef* USARTx, char *str);
int fputc(int ch, FILE *f);
注意:在使用printf等C语言标准库函数要包含头文件stdio.h,并且勾选Target中的use MicroLIB使用标准库。
main.c
#include "stm32f10x.h" // Device header #include "usart.h" void delay(uint16_t time) { uint16_t i = 0; while(time--) { i=12000; while(i--); } } int main(void) { Usart_Init(); printf("你好STM32");//printf调用重写的fputc 这样单片机就可以通过串口发送字符串"你好STM32" putchar('X'); //putchar调用重写的fputc 这样单片机就可以通过串口发送字符'X' while(1) { } }
参考:串口 stm32 实现中断接收 打开板子上的led灯
作者:点灯小哥
发布时间: 2021-03-07 11:55:35
网址:https://blog.csdn.net/weixin_46016743/article/details/114481125
注:板子上的LED灯看电路图连接的是PC13引脚,前面博文led.c文件里配置好了。
NVIC_InitTypeDef Nvic_init; //misc.h
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC中断组
//3.配置NVIC中断控制器 中断源(通道)选择串口1
Nvic_init.NVIC_IRQChannel = USART1_IRQn;
Nvic_init.NVIC_IRQChannelCmd = ENABLE; //使能 找到FunctionalState字眼 右键goto
Nvic_init.NVIC_IRQChannelPreemptionPriority = 1; //因为只配置了一个中断 不考虑优先级 所以只有1个
Nvic_init.NVIC_IRQChannelSubPriority = 1;//抢占优先级与子优先级
NVIC_Init(&NvicInitStructure);
#include "stm32f10x.h" // Device header #include "usart.h" //#include "stdio.h" void Usart_Init(void) { //2. 配置GPIO的结构体 GPIO_InitTypeDef GpioInitStructure; //初始化GPIO结构体命名 USART_InitTypeDef UsartInitStructure;//初始化USART结构体命名 NVIC_InitTypeDef NvicInitStructure; //加入NVIC NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVIC中断分组 目前一个中断随便配置就好了 //1. 配置时钟:GPIO的时钟,引脚复用的时钟,串口的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //2.1 配置PA9 TX GpioInitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出 GpioInitStructure.GPIO_Pin = GPIO_Pin_9; GpioInitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GpioInitStructure); //2.2 配置PA10 RX GpioInitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 GpioInitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_Init(GPIOA,&GpioInitStructure); //3.配置串口结构体 UsartInitStructure.USART_BaudRate = 115200; //波特率 UsartInitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流 UsartInitStructure.USART_Mode = USART_Mode_Rx| USART_Mode_Tx; //模式 UsartInitStructure.USART_Parity = USART_Parity_No; //校验位 UsartInitStructure.USART_StopBits = USART_StopBits_1; //停止位 UsartInitStructure.USART_WordLength = USART_WordLength_8b; //字节长度 USART_Init(USART1, &UsartInitStructure); //串口中断配置函数 //接收数据寄存器非空标志位 作为串口发生中断的标志 USART_ITConfig( USART1, USART_IT_RXNE, ENABLE ); USART_Cmd(USART1, ENABLE);//打开串口 比配置GPIO多这一步 //配置NVIC中断控制器 中断源(通道)选择串口1 NvicInitStructure.NVIC_IRQChannel = USART1_IRQn; NvicInitStructure.NVIC_IRQChannelPreemptionPriority = 1;//抢占优先级 NvicInitStructure.NVIC_IRQChannelSubPriority = 1;//子优先级 NvicInitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NvicInitStructure); } //发送字符 void USARTSendByte(USART_TypeDef* USARTx, uint16_t Data) { USART_SendData( USARTx, Data); while( USART_GetFlagStatus( USARTx, USART_FLAG_TXE) == RESET);//USART_GetFlagStatus是判断标志位 USART_FLAG_TXE 去usart.h FLAG找 } //发送字符串 void USARTSendString( USART_TypeDef* USARTx, char *str) { uint16_t i = 0; do{ USARTSendByte(USART1,*(str+i)); i++; }while(*(str+i) != '\0'); while( USART_GetFlagStatus( USARTx, USART_FLAG_TC) == RESET);//USART_GetFlagStatus是判断标志位 USART_FLAG_TC(这是判断字符串) 去usart.h FLAG找 }
#include "stm32f10x.h" // Device header #include "usart.h" #include "led.h" #include "exti.h" void delay(uint16_t time) { uint16_t i = 0; while(time--) { i=12000; while(i--); } } int main(void) { Usart_Init(); LED_Init(); GPIO_SetBits( GPIOC, GPIO_Pin_13);//初始化C13电平为高电平 灯不亮 while(1) { } } //4. 中断服务函数(在启动头文件里 有weak标志 属于重定向函数) void USART1_IRQHandler(void) { char temp; //获得串口中断标志位 接收数据寄存器非空标志位 if( USART_GetITStatus( USART1, USART_IT_RXNE) != RESET) //发生了中断 开始接收数据 { temp = USART_ReceiveData( USART1); if(temp == 'O') { GPIO_ResetBits( GPIOC, GPIO_Pin_13);//串口助手发送过来字符'O' 开灯 USARTSendString( USART1, "LED IS OK");//调用自己写的串口发送字符串函数 } if(temp == 'C') { GPIO_SetBits( GPIOC, GPIO_Pin_13);//关灯 USARTSendString( USART1, "LED IS DOWN"); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。