stm32之串口/蓝牙控制led灯

该文章记录学习stm32串口遇到的一些问题,完整代码地址。

一、项目描述

通过串口或蓝牙发送指令来控制led灯。

  • open ------> led 亮
  • close ------> led 灭
  • 其它  -------> 反馈给串口或蓝牙错误指令

二、项目用到的模块

  1. stm32 串口1,PA9(TX), PA10(RX)
  2. HC01 蓝牙模块,PA9(TX), PA10(RX)
  3. led灯,  PB8

三、USART1关键配置说明

四、代码说明

main.c中主要代码如下:

#define UART1_REC_LEN 200uint16_t UART1_RX_STA=0;
uint8_t buf=0;
// 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
uint8_t UART1_RX_Buffer[UART1_REC_LEN];void SystemClock_Config(void);// 接收中断
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {if(huart->Instance != USART1) {return;}// 数据接收完成if((UART1_RX_STA & 0x8000) != 0) {HAL_UART_Receive_IT(&huart1, &buf, 1);return;}// 接收到回车之后判断后续的是不是换行,如果是换行,数据接收完成,但是还要开启一下中断if(UART1_RX_STA&0x4000) {UART1_RX_STA= (buf == 0x0a) ? (UART1_RX_STA| 0x8000) : 0;} else {// 接收到回车,将高第二位置1,否则继续接收数据if(buf == 0x0d) {UART1_RX_STA |= 0x4000;} else {UART1_RX_Buffer[UART1_RX_STA&0x3fff] = buf;UART1_RX_STA ++;if(UART1_RX_STA > UART1_REC_LEN - 1) {UART1_RX_STA = 0;}}}HAL_UART_Receive_IT(&huart1, &buf, 1);
}int fputc(int ch, FILE *file) {unsigned char temp[1] = {ch};HAL_UART_Transmit(&huart1, temp, 1, 0xfff);return ch;
}int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();HAL_UART_Receive_IT(&huart1, &buf, 1);while (1){if(UART1_RX_STA & 0x8000){printf("收到数据:");if(UART1_RX_Buffer[0] == '\0') continue;if(!strcmp((const char *)UART1_RX_Buffer, "open")) {HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);} else if(!strcmp((const char *)UART1_RX_Buffer, "close")){HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);} else {printf("error code");}HAL_UART_Transmit(&huart1, UART1_RX_Buffer, UART1_RX_STA & 0x3fff, 0xffff);while(huart1.gState != HAL_UART_STATE_READY);UART1_RX_STA = 0;} else {printf("hello heart\r\n");}HAL_Delay(1000);}
}
4.1、重定向printf打印功能到串口

重写fputc函数就可以重定向printf,里面就是调用了HAL库中串口的发送函数。

int fputc(int ch, FILE *file) {unsigned char temp[1] = {ch};HAL_UART_Transmit(&huart1, temp, 1, 0xfff);return ch;
}

注意:这里需要在keil 选中Use Micro LIB,否则会不成功。

4.2、开启接收中断

初始化完成之后和接收中断函数调用完成之后,需要重新调用接收中断函数

HAL_UART_Receive_IT(&huart1, &buf, 1);
4.3、接收中断函数

这里定义一个uint16_t类型的UART1_RX_STA变量,其中

  • 最高位表示是否数据完成
  • 次高位表示是否接收了回车
  • 其它位表示接收的字节数

注意点:

  • 中断函数最好不要有延时函数,可能会造成数据接收异常
  • 中断函数最好不要打印函数,也可能导致时间过长,造成数据异常
  • 双串口调试(正好板子上引出了两组USART1引脚)
  • 串口和蓝牙同时连接时,最好只用一个发,要不然可能有影响
4.4、串口初始化函数
void MX_USART1_UART_Init(void)
{huart1.Instance = USART1;huart1.Init.BaudRate = 9600;huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_NONE;huart1.Init.Mode = UART_MODE_TX_RX;huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart1.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart1) != HAL_OK){Error_Handler();}
}

Instance类型USART_TypeDef,主要是串口包含的寄存器,USART1是一个宏,绑定串口1的物理地址,方式和之前介绍的GPIO类似。

另外USART1 是挂载到APB2总线上,其它串口挂载到APB1总线上。

typedef struct
{__IO uint32_t SR;         /*!< USART Status register,                   Address offset: 0x00 */__IO uint32_t DR;         /*!< USART Data register,                     Address offset: 0x04 */__IO uint32_t BRR;        /*!< USART Baud rate register,                Address offset: 0x08 */__IO uint32_t CR1;        /*!< USART Control register 1,                Address offset: 0x0C */__IO uint32_t CR2;        /*!< USART Control register 2,                Address offset: 0x10 */__IO uint32_t CR3;        /*!< USART Control register 3,                Address offset: 0x14 */__IO uint32_t GTPR;       /*!< USART Guard time and prescaler register, Address offset: 0x18 */
} USART_TypeDef;

 huart1 的类型是UART_HandleTypeDef,定义如下

typedef struct __UART_HandleTypeDef
{USART_TypeDef                 *Instance;        UART_InitTypeDef              Init;             const uint8_t                 *pTxBuffPtr;      uint16_t                      TxXferSize;      __IO uint16_t                 TxXferCount;      uint8_t                       *pRxBuffPtr;     uint16_t                      RxXferSize;       __IO uint16_t                 RxXferCount;      __IO HAL_UART_RxTypeTypeDef ReceptionType;      __IO HAL_UART_RxEventTypeTypeDef RxEventType;   DMA_HandleTypeDef             *hdmatx;          DMA_HandleTypeDef             *hdmarx;          HAL_LockTypeDef               Lock;             __IO HAL_UART_StateTypeDef    gState;                                                           __IO HAL_UART_StateTypeDef    RxState;          __IO uint32_t                 ErrorCode;        #if (USE_HAL_UART_REGISTER_CALLBACKS == 1)void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart);        *******省略代码*******
#endif  /* USE_HAL_UART_REGISTER_CALLBACKS */} UART_HandleTypeDef;

 Init类型如下,主要是设置数据传输的配置。

typedef struct
{uint32_t BaudRate;                 uint32_t WordLength;               uint32_t StopBits;                 uint32_t Parity;                   uint32_t Mode;                      uint32_t HwFlowCtl;                 uint32_t OverSampling;              
} UART_InitTypeDef;

Init 主要配置波特率,字长,停止位等等。

其实HAL库中的模块配置都很相似,包括GPIO, 定时器等。

4.4.1、HAL_UART_Init

主要操作如下,代码已经添加了注释

HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
{//判断 是否地址无效if (huart == NULL){return HAL_ERROR;}// 流控制位有效或无效时的操作if (huart->Init.HwFlowCtl != UART_HWCONTROL_NONE){// 流控制器只适用于串口1 2 3 assert_param(IS_UART_HWFLOW_INSTANCE(huart->Instance));assert_param(IS_UART_HARDWARE_FLOW_CONTROL(huart->Init.HwFlowCtl));}else{assert_param(IS_UART_INSTANCE(huart->Instance));}assert_param(IS_UART_WORD_LENGTH(huart->Init.WordLength));
#if defined(USART_CR1_OVER8)assert_param(IS_UART_OVERSAMPLING(huart->Init.OverSampling));
#endif /* USART_CR1_OVER8 */if (huart->gState == HAL_UART_STATE_RESET){// 先解锁再操作huart->Lock = HAL_UNLOCKED;// 这里没有定义回调,只会走else
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)UART_InitCallbacksToDefault(huart);if (huart->MspInitCallback == NULL){huart->MspInitCallback = HAL_UART_MspInit;}huart->MspInitCallback(huart);
#else//初始化GPIO,串口优先级等HAL_UART_MspInit(huart);
#endif /* (USE_HAL_UART_REGISTER_CALLBACKS) */}huart->gState = HAL_UART_STATE_BUSY;// 先关闭串口,再操作,最后打开串口__HAL_UART_DISABLE(huart);UART_SetConfig(huart);/* In asynchronous mode, the following bits must be kept cleared:- LINEN and CLKEN bits in the USART_CR2 register,- SCEN, HDSEL and IREN  bits in the USART_CR3 register.*///异步模式下,USART_CR2的LINEN(局域网模式)和CLKEN(时钟使能)要清0// USART_CR3的SCEN, HDSE, IREN ,STOP(文档里有说明)要清0CLEAR_BIT(huart->Instance->CR2, (USART_CR2_LINEN | USART_CR2_CLKEN));CLEAR_BIT(huart->Instance->CR3, (USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN));__HAL_UART_ENABLE(huart);// 设置串口的初始化状态huart->ErrorCode = HAL_UART_ERROR_NONE;huart->gState = HAL_UART_STATE_READY;huart->RxState = HAL_UART_STATE_READY;huart->RxEventType = HAL_UART_RXEVENT_TC;return HAL_OK;
}

UART_SetConfig 是串口的主要寄存器配置,核心代码如下

static void UART_SetConfig(UART_HandleTypeDef *huart)
{uint32_t tmpreg;uint32_t pclk;// 设置停止位MODIFY_REG(huart->Instance->CR2, USART_CR2_STOP, huart->Init.StopBits);// 根据Init.WordLength 配置USART1_CR1寄存器中各位#if defined(USART_CR1_OVER8)tmpreg = (uint32_t)huart->Init.WordLength | huart->Init.Parity | huart->Init.Mode | huart->Init.OverSampling;MODIFY_REG(huart->Instance->CR1,(uint32_t)(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | USART_CR1_RE | USART_CR1_OVER8),tmpreg);
#elsetmpreg = (uint32_t)huart->Init.WordLength | huart->Init.Parity | huart->Init.Mode;MODIFY_REG(huart->Instance->CR1,(uint32_t)(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | USART_CR1_RE),tmpreg);
#endif /* USART_CR1_OVER8 */// 配置USART1_CR3寄存器中的RTSE(RTS使能),CTSE(CTS使能)MODIFY_REG(huart->Instance->CR3, (USART_CR3_RTSE | USART_CR3_CTSE), huart->Init.HwFlowCtl);// 配置时钟if(huart->Instance == USART1){pclk = HAL_RCC_GetPCLK2Freq();}else{pclk = HAL_RCC_GetPCLK1Freq();}// 配置波特率
#if defined(USART_CR1_OVER8)if (huart->Init.OverSampling == UART_OVERSAMPLING_8){huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate);}else{huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate);}
#elsehuart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate);
#endif /* USART_CR1_OVER8 */
}

pclk的获取,这里大致分析下它的实现

uint32_t HAL_RCC_GetPCLK2Freq(void)
{// HAL_RCC_GetHCLKFreq 最终获取的是系统核心时钟16000000hz// CFGR是时钟配置寄存器,这里要配置的是PPRE2,在11-13位,APB预分频(APB2)// RCC_CFGR_PPRE2 = 11 1000 0000 0000 正好对应11-13位// RCC_CFGR_PPRE2_Pos = 8// const uint8_t APBPrescTable[8U] =  {0, 0, 0, 0, 1, 2, 3, 4};// APBPrescTable[(RCC->CFGR & RCC_CFGR_PPRE2) >> RCC_CFGR_PPRE2_Pos] 这个操作是获取分频系数// 获取之后将 16000000hz 相除return (HAL_RCC_GetHCLKFreq() >> APBPrescTable[(RCC->CFGR & RCC_CFGR_PPRE2) >> RCC_CFGR_PPRE2_Pos]);
}

五、效果图

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

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

相关文章

udp的简单整理

最近思考udp处理的一些细节&#xff0c;根据公开课&#xff0c;反复思考&#xff0c;终于有所理解&#xff0c;做整理备用。 0&#xff1a;简单汇总 1&#xff1a;udp是基于报文传输的&#xff0c;接收方收取数据时要一次性读完。 2&#xff1a;借助udp进行发包&#xff0c;…

JavaWeb-JavaScript

JavaWeb-JavaScript 什么是JavaScript Web标准 Web标准也称为网页标准&#xff0c;由一系列的标准组成&#xff0c;大部分由W3C ( World Wide Web Consortium&#xff0c;万维网联盟&#xff09;负责制定。三个组成部分&#xff1a; HTML&#xff1a;负责网页的结构&#xf…

32 随机链表的复制

随机链表的复制 题解1 哈希表题解2 回溯哈希哈希思路精简 题解3 优化迭代 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点…

OR54 字符串中找出连续最长的数字串

目录 一、题目 二、解答 &#xff08;一&#xff09;问题一&#xff1a;在记录完一组连续字符串后&#xff0c;没有注意判别紧随其后的非数字字符 &#xff08;二&#xff09;问题二&#xff1a;越界访问 &#xff08;三&#xff09;正确 一、题目 字符串中找出连续最长的…

设计模式再探——原型模式

目录 一、背景介绍二、思路&方案三、过程1.原型模式简介2.原型模式的类图3.原型模式代码4.原型模式深度剖析5.原型模式与spring 四、总结五、升华 一、背景介绍 最近在做业务实现的时候&#xff0c;为了通过提升机器来降低开发人员的难度和要求&#xff0c;于是在架构设计…

数据标准化

1、均值方差标准化(Z-Score标准化) 计算过程&#xff1a; 对每个属性/每列分别进行一下操作&#xff0c;将数据按属性/按列减去其均值&#xff0c;并除以其方差&#xff0c;最终使每个属性/每列的所有数据都聚集在均值为0&#xff0c;方差为1附近。 公式&#xff1a;(x-mean(x…

电子信息工程专业课复习知识点总结:(五)通信原理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 第一章通信系统概述——通信系统的构成、各部分性质、性能指标1.通信系统的组成&#xff1f;2.通信系统的分类&#xff1f;3.调制、解调是什么&#xff1f;有什么用…

【数据结构--排序】堆排序

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

在北京多有钱能称为富

背景 首先声明&#xff0c;此讨论仅限个人的观点&#xff0c;因为我本身不富嘛&#xff0c;所以想法应该非常局限。 举个栗子 富二代问我朋友&#xff0c;100~1000w之间&#xff0c;推荐一款车&#xff1f; 一开始听到这个问题的时候&#xff0c;有被唬住&#xff0c;觉得预…

XXE 漏洞及案例实战

文章目录 XXE 漏洞1. 基础概念1.1 XML基础概念1.2 XML与HTML的主要差异1.3 xml示例 2. 演示案例2.1 pikachu靶场XML2.1.1 文件读取2.1.2 内网探针或者攻击内网应用&#xff08;触发漏洞地址&#xff09;2.1.4 RCE2.1.5 引入外部实体DTD2.1.6 无回显读取文件 3. XXE 绕过3.1 dat…

Nitrux 3.0 正式发布并全面上市

导读乌里-埃雷拉&#xff08;Uri Herrera&#xff09;近日宣布 Nitrux 3.0 正式发布并全面上市&#xff0c;它是基于 Debian、无 systemd、不可变的 GNU/Linux 发行版的最新安装媒体&#xff0c;利用了 KDE 软件。 Nitrux 3.0 由带有 Liquorix 味道的 Linux 6.4.12 内核提供支持…

QT-day4

画一个时钟 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPaintEvent> #include <QDebug> #include <QPainter> #include <QTimer> #include <QTime>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } Q…

记一次逆向某医院挂号软件的经历

背景 最近家里娃需要挂专家号的儿保&#xff0c;奈何专家号实在过于抢手&#xff0c;身为程序员的我也没有其他的社会资源渠道可以去弄个号&#xff0c;只能发挥自己的技术力量来解决这个问题了。 出师不利 首先把应用安装到我已经 Root 过的 Pixel 3 上面&#xff0c;点击应…

关于Pandas数据分析

pandas的数据加载与预处理 数据清洗&#xff1a;洗掉脏数据 整理分析&#xff1a;字不如表 数据展现&#xff1a;表不如图 环境搭建 pythonjupyter anaconda Jupyter Notebook Jupyter Notebook可以在网页页面中直接编写代码和运行代码, 代码的运行结果也会直接在代码块下显示…

【 Ubuntu】systemd服务创建、启用、状态查询、自启等

要在 Ubuntu 启动后执行一个守护脚本&#xff0c;您可以使用 Shell 脚本编写一个 systemd 服务单元。systemd 是 Ubuntu 中常用的服务管理工具&#xff0c;可以在系统启动时自动启动和管理服务。 下面是一个示例的守护脚本和 systemd 服务单元的步骤&#xff1a; 创建守护脚本…

Spring之依赖注入源码解析

基于Autowired的依赖注入底层原理 基于Resource注解底层工作流程图&#xff1a; 1 Spring中到底有几种依赖注入的方式&#xff1f; 首先分两种&#xff1a; 手动注入 自动注入 1.1 手动注入 在XML中定义Bean时&#xff0c;就是手动注入&#xff0c;因为是程序员手动给某…

LeetCode 75-02:字符串的最大公因子

前置知识&#xff1a;使用欧几里得算法求出最大公约数 func gcdOfStrings(str1 string, str2 string) string {if str1str2 ! str2str1 {return ""}return str1[:gcd(len(str1), len(str2))] }func gcd(a, b int)int{if b 0{return a}return gcd(b, a%b) }

车载软件架构 —— AUTOSAR Vector SIP包(二)

车载软件架构 —— AUTOSAR Vector SIP包(二) 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 没有人关注你。也无需有人关注你。你必须承认自己的价值,你不能站在他人的角度来反对自己。人生在…

Android Kotlin 基础详解

1,基础语法 1.1 可变变量与不可变变量 可以多次赋值的变量是可变变量&#xff0c;用关键字var表示&#xff1a; var <标识符> : <类型> <初始化值> 注意&#xff0c;在kotlin中成员变量不会赋默认值&#xff0c;不像java一样&#xff0c;必须手动添加默…

83、SpringBoot --- 下载和安装 MSYS2、 Redis

★ 下载和安装MSYS2&#xff08;作用&#xff1a;可在Windows模拟一个Linux的编译环境&#xff09; 得到Redis的编译环境——在Linux平台上&#xff0c;这一步可以省略。&#xff08;1&#xff09;登录MSYS2官网&#xff08;http://repo.msys2.org/distrib/ &#xff09;下载M…