stm32之串口/蓝牙控制led灯

news2024/11/15 21:09:00

该文章记录学习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 200

uint16_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(文档里有说明)要清0
  CLEAR_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);
#else
  tmpreg = (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);
  }
#else
  huart->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.coloradmin.cn/o/1033480.html

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

相关文章

计算机组成与设计硬软件接口学习2

并行处理器&#xff1a;从客户端到云 任务级并行或进程级并行&#xff1a;通过同时运行独立的多个程序来使用多处理器 并行处理程序&#xff1a;同时在多个处理器上运行的单个程序 通过增加硬件的方式&#xff0c;将取指令和指令译码实现并行&#xff0c;一次性取出多条指令…

MQTT 协议概要

01 MQTT协议 MQTT&#xff08;消息队列遥测传输&#xff09; 是基于 TCP/IP 协议栈而构建的支持在各方之间异步通信的消息协议。MQTT在空间和时间上将消息发送者与接收者分离&#xff0c;因此可以在不可靠的网络环境中进行扩展。虽然叫做消息队列遥测传输&#xff0c;但它与消息…

[RF学习记录][ssh library][execute Command】关键字的返回值

有时候需要判断通过ssh在远程机器上执行的命令是否正常&#xff0c;使用关键字Execute Command可以在远程机器上运行命令&#xff0c;但是默认不加任何参数的话&#xff0c;没有看到范返回值&#xff0c;而这个关键字是带了几个参数的&#xff0c;简单的试验了下这几个参数&…

点云从入门到精通技术详解100篇-单期点云的高斯曲率定位桥梁潜在损伤技术研究

目录 前言 国内外研究现状 三维激光扫描对桥梁损伤检测的研究现状 基于点云高斯曲率损伤检测的研究现状 柱体偏差检测技术研究现状 存在的问题 法向量约束高斯曲率的 TLS 桥面潜在损伤区域探测 2.1 高斯曲率探伤的基本理论 2.2 点云拓扑关系建立的方法比较 2.2.1 KD-…

机器学习第十三课--主成分分析PCA

一.高维数据 除了图片、文本数据&#xff0c;我们在实际工作中也会面临更多高维的数据。比如在评分卡模型构建过程中&#xff0c;我们通常会试着衍生出很多的特征&#xff0c;最后就得到上千维、甚至上完维特征;在广告点击率预测应用中&#xff0c;拥有几个亿特征也是常见的事…

【数学建模】2023华为杯研究生数学建模F题思路详解

强对流降水临近预报 我国地域辽阔,自然条件复杂,因此灾害性天气种类繁多,地区差异大。其中,雷雨大风、冰雹、龙卷、短时强降水等强对流天气是造成经济损失、危害生命安全最严重的一类灾害性天气[1]。以2022年为例,我国强对流天气引发风雹灾害造成的死亡失踪人数和直接经济…

git:一、GIT介绍+安装+全局配置+基础操作

版本管理系统&#xff08;SVN和Git&#xff09;&#xff1a; 集中式版本控制系统&#xff08;SVN&#xff09; SVN是集中式版本控制系统&#xff0c;版本库是集中放在中央服务器的. 工作流程如下: 1.从中央服务器远程仓库下载代码 2.修改后将代码提交到中央服务器远程仓库…

基于微信小程序的电影院订票系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言运行环境说明用户微信小程序端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考论文参考源码获取 前言 &#x1f497;博主介绍&…

原生js的animate()方法详解

1.介绍 Element 接口的 animate() 方法是创建一个新的 Animation 的便捷方法&#xff0c;将它应用于元素&#xff0c;然后运行动画。它将返回一个新建的 Animation 对象实例。 同时通过Element.getAnimations() 方法可获取元素所有的Animation实例。 2.语法 Element.animate…

读高性能MySQL(第4版)笔记14_备份与恢复(中)

1. 在线备份 2. 离线备份 2.1. 关闭MySQL做备份是最简单、最安全的 2.2. 所有获取一致性副本的方法中最好的 2.3. 损坏或不一致的风险最小 2.4. 根本不用关心InnoDB缓冲池中的脏页或其他缓存 2.5. 不需要担心数据在尝试备份的过程中被修改 2.5.1. 服务器不对应用提供访问…

Redis淘汰策略-架构案例2020(三十六)

上篇案例回顾&#xff1a; 解释器&#xff0c;管道过滤&#xff0c;隐式调用优缺点&#xff1f; 解释器 则是独立的语法规则&#xff0c;可以通过解释器来解析&#xff0c;可扩展性很高&#xff0c;灵活性强。 管道过滤则是侧重于数据的输入和输出&#xff0c;上一个模块的数…

stm32之看门狗

STM32 有两个看门狗&#xff0c;独立看门狗和窗口看门狗&#xff0c;独立看门狗又称宠物狗&#xff0c;窗 口看门狗又称警犬。可用来检测和解决由软件错误引起的故障。两个看门狗的原理都是当计数器达到给定的超时值时&#xff0c;产生系统复位&#xff0c;对于窗口型看门狗同…

2023.9.20 简单了解 HTTP协议 及 Fiddle 安装使用

目录 HTTP 协议基本概念 Fiddle 下载 HTTP 请求格式 HTTP 响应格式 HTTP 协议基本概念 应用层使用最广泛的协议浏览器 基于 HTTP协议 获取网站是 浏览器 和 服务器 之间的交互桥梁HTTP协议 基于传输层的 TCP协议 实现HTTP 全称为 HyperText Transfer Protocol&#xff0c;中…

Java IO流实现文件复制

目录 前言 文件复制底层逻辑 代码实现 ​编辑 重点&#xff01;&#xff01;&#xff01; 完整代码 改善思考 前言 Windows文件复制时我们是使用Ctrl C复制Ctrl V粘贴&#xff0c;上一篇文章Java基础入门对存储文件的相关操作 我们学习了Java IO流对文件的读写操作&…

数据结构--排序(1)

文章目录 排序概念直接插入排序希尔排序冒泡排序堆排序选择排序验证不同排序的运行时间 排序概念 排序指的是通过某一特征关键字&#xff08;如信息量大小&#xff0c;首字母等&#xff09;来对一连串的数据进行重新排列的操作&#xff0c;实现递增或者递减的数据排序。 稳定…

深度学习自学笔记二:逻辑回归和梯度下降法

目录 一、逻辑回归 二、逻辑回归的代价函数 三、梯度下降法 一、逻辑回归 逻辑回归是一种常用的二分类算法&#xff0c;用于将输入数据映射到一个概率输出&#xff0c;表示为属于某个类别的概率。它基于线性回归模型&#xff0c;并使用了sigmoid函数作为激活函数。 假设我们…

Git_06_创建分支/查看分支

创建分支 # 创建分支的同时&#xff0c;切换到该分支上 > git checkout -b 分支名称 # > git push origin 分支名称查看分支 # 查看本地分支 > git branch # 查看远程分支 > git branch -r # 查看所有分支 > git branch -a删除分支 # 删除本地分支 > git …

app一键加固加签名脚本 百度加固 window版本

echo off setlocal enabledelayedexpansionset KEYSTORE_PATH*.jks set KEYSTORE_PASSWORD* set KEY_ALIAS* set KEY_PASSWORD*set OUTPUT_DIR%cd%\out set UNSIGNED_DIR%cd%\unsignedREM 设置加固工具的路径和密钥 set APKPROTECT_PATH"apkprotect" set AKEY替换成你…

Postman 全局配置接口路径变量等

Postman 全局配置接口路径变量等 一、简介 这里主要是介绍通过配置postman接口测试工具&#xff0c;简化每次新增模块等接口时修改url的繁琐过程&#xff0c;方便以后查阅&#xff01;&#xff01;&#xff01; 二、全局变量设置 1、新增测试环境 新增测试环境 2、接口集合设…

上N下P三极管推挽电路----》交越失真问题的解决

交越失真现象&#xff1a; 波形失真原因&#xff1a; 三极管的导通条件是Vbe > 0.7V&#xff0c;在正弦波驱动波形介于-0.7v ~ 0.7v之间时&#xff0c;上N管子截止、下P管子也截止&#xff0c;此时波形失真。 (关于三极管的伏安特性曲线、米勒效应&#xff0c;请自行百度) …