• 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中主要代码如下:

    1. #define UART1_REC_LEN 200
    2. uint16_t UART1_RX_STA=0;
    3. uint8_t buf=0;
    4. // 接收缓冲, 串口接收到的数据放在这个数组里,最大UART1_REC_LEN个字节
    5. uint8_t UART1_RX_Buffer[UART1_REC_LEN];
    6. void SystemClock_Config(void);
    7. // 接收中断
    8. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    9. if(huart->Instance != USART1) {
    10. return;
    11. }
    12. // 数据接收完成
    13. if((UART1_RX_STA & 0x8000) != 0) {
    14. HAL_UART_Receive_IT(&huart1, &buf, 1);
    15. return;
    16. }
    17. // 接收到回车之后判断后续的是不是换行,如果是换行,数据接收完成,但是还要开启一下中断
    18. if(UART1_RX_STA&0x4000) {
    19. UART1_RX_STA= (buf == 0x0a) ? (UART1_RX_STA| 0x8000) : 0;
    20. } else {
    21. // 接收到回车,将高第二位置1,否则继续接收数据
    22. if(buf == 0x0d) {
    23. UART1_RX_STA |= 0x4000;
    24. } else {
    25. UART1_RX_Buffer[UART1_RX_STA&0x3fff] = buf;
    26. UART1_RX_STA ++;
    27. if(UART1_RX_STA > UART1_REC_LEN - 1) {
    28. UART1_RX_STA = 0;
    29. }
    30. }
    31. }
    32. HAL_UART_Receive_IT(&huart1, &buf, 1);
    33. }
    34. int fputc(int ch, FILE *file) {
    35. unsigned char temp[1] = {ch};
    36. HAL_UART_Transmit(&huart1, temp, 1, 0xfff);
    37. return ch;
    38. }
    39. int main(void)
    40. {
    41. HAL_Init();
    42. SystemClock_Config();
    43. MX_GPIO_Init();
    44. MX_USART1_UART_Init();
    45. HAL_UART_Receive_IT(&huart1, &buf, 1);
    46. while (1)
    47. {
    48. if(UART1_RX_STA & 0x8000)
    49. {
    50. printf("收到数据:");
    51. if(UART1_RX_Buffer[0] == '\0') continue;
    52. if(!strcmp((const char *)UART1_RX_Buffer, "open")) {
    53. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
    54. } else if(!strcmp((const char *)UART1_RX_Buffer, "close")){
    55. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
    56. } else {
    57. printf("error code");
    58. }
    59. HAL_UART_Transmit(&huart1, UART1_RX_Buffer, UART1_RX_STA & 0x3fff, 0xffff);
    60. while(huart1.gState != HAL_UART_STATE_READY);
    61. UART1_RX_STA = 0;
    62. } else {
    63. printf("hello heart\r\n");
    64. }
    65. HAL_Delay(1000);
    66. }
    67. }
    4.1、重定向printf打印功能到串口

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

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

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

    4.2、开启接收中断

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

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

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

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

    注意点:

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

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

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

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

     huart1 的类型是UART_HandleTypeDef,定义如下

    1. typedef struct __UART_HandleTypeDef
    2. {
    3. USART_TypeDef *Instance;
    4. UART_InitTypeDef Init;
    5. const uint8_t *pTxBuffPtr;
    6. uint16_t TxXferSize;
    7. __IO uint16_t TxXferCount;
    8. uint8_t *pRxBuffPtr;
    9. uint16_t RxXferSize;
    10. __IO uint16_t RxXferCount;
    11. __IO HAL_UART_RxTypeTypeDef ReceptionType;
    12. __IO HAL_UART_RxEventTypeTypeDef RxEventType;
    13. DMA_HandleTypeDef *hdmatx;
    14. DMA_HandleTypeDef *hdmarx;
    15. HAL_LockTypeDef Lock;
    16. __IO HAL_UART_StateTypeDef gState;
    17. __IO HAL_UART_StateTypeDef RxState;
    18. __IO uint32_t ErrorCode;
    19. #if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    20. void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart);
    21. *******省略代码*******
    22. #endif /* USE_HAL_UART_REGISTER_CALLBACKS */
    23. } UART_HandleTypeDef;

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

    1. typedef struct
    2. {
    3. uint32_t BaudRate;
    4. uint32_t WordLength;
    5. uint32_t StopBits;
    6. uint32_t Parity;
    7. uint32_t Mode;
    8. uint32_t HwFlowCtl;
    9. uint32_t OverSampling;
    10. } UART_InitTypeDef;

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

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

    4.4.1、HAL_UART_Init

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

    1. HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
    2. {
    3. //判断 是否地址无效
    4. if (huart == NULL)
    5. {
    6. return HAL_ERROR;
    7. }
    8. // 流控制位有效或无效时的操作
    9. if (huart->Init.HwFlowCtl != UART_HWCONTROL_NONE)
    10. {
    11. // 流控制器只适用于串口1 2 3
    12. assert_param(IS_UART_HWFLOW_INSTANCE(huart->Instance));
    13. assert_param(IS_UART_HARDWARE_FLOW_CONTROL(huart->Init.HwFlowCtl));
    14. }
    15. else
    16. {
    17. assert_param(IS_UART_INSTANCE(huart->Instance));
    18. }
    19. assert_param(IS_UART_WORD_LENGTH(huart->Init.WordLength));
    20. #if defined(USART_CR1_OVER8)
    21. assert_param(IS_UART_OVERSAMPLING(huart->Init.OverSampling));
    22. #endif /* USART_CR1_OVER8 */
    23. if (huart->gState == HAL_UART_STATE_RESET)
    24. {
    25. // 先解锁再操作
    26. huart->Lock = HAL_UNLOCKED;
    27. // 这里没有定义回调,只会走else
    28. #if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    29. UART_InitCallbacksToDefault(huart);
    30. if (huart->MspInitCallback == NULL)
    31. {
    32. huart->MspInitCallback = HAL_UART_MspInit;
    33. }
    34. huart->MspInitCallback(huart);
    35. #else
    36. //初始化GPIO,串口优先级等
    37. HAL_UART_MspInit(huart);
    38. #endif /* (USE_HAL_UART_REGISTER_CALLBACKS) */
    39. }
    40. huart->gState = HAL_UART_STATE_BUSY;
    41. // 先关闭串口,再操作,最后打开串口
    42. __HAL_UART_DISABLE(huart);
    43. UART_SetConfig(huart);
    44. /* In asynchronous mode, the following bits must be kept cleared:
    45. - LINEN and CLKEN bits in the USART_CR2 register,
    46. - SCEN, HDSEL and IREN bits in the USART_CR3 register.*/
    47. //异步模式下,USART_CR2的LINEN(局域网模式)和CLKEN(时钟使能)要清0
    48. // USART_CR3的SCEN, HDSE, IREN ,STOP(文档里有说明)要清0
    49. CLEAR_BIT(huart->Instance->CR2, (USART_CR2_LINEN | USART_CR2_CLKEN));
    50. CLEAR_BIT(huart->Instance->CR3, (USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN));
    51. __HAL_UART_ENABLE(huart);
    52. // 设置串口的初始化状态
    53. huart->ErrorCode = HAL_UART_ERROR_NONE;
    54. huart->gState = HAL_UART_STATE_READY;
    55. huart->RxState = HAL_UART_STATE_READY;
    56. huart->RxEventType = HAL_UART_RXEVENT_TC;
    57. return HAL_OK;
    58. }

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

    1. static void UART_SetConfig(UART_HandleTypeDef *huart)
    2. {
    3. uint32_t tmpreg;
    4. uint32_t pclk;
    5. // 设置停止位
    6. MODIFY_REG(huart->Instance->CR2, USART_CR2_STOP, huart->Init.StopBits);
    7. // 根据Init.WordLength 配置USART1_CR1寄存器中各位
    8. #if defined(USART_CR1_OVER8)
    9. tmpreg = (uint32_t)huart->Init.WordLength | huart->Init.Parity | huart->Init.Mode | huart->Init.OverSampling;
    10. MODIFY_REG(huart->Instance->CR1,
    11. (uint32_t)(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | USART_CR1_RE | USART_CR1_OVER8),
    12. tmpreg);
    13. #else
    14. tmpreg = (uint32_t)huart->Init.WordLength | huart->Init.Parity | huart->Init.Mode;
    15. MODIFY_REG(huart->Instance->CR1,
    16. (uint32_t)(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | USART_CR1_RE),
    17. tmpreg);
    18. #endif /* USART_CR1_OVER8 */
    19. // 配置USART1_CR3寄存器中的RTSE(RTS使能),CTSE(CTS使能)
    20. MODIFY_REG(huart->Instance->CR3, (USART_CR3_RTSE | USART_CR3_CTSE), huart->Init.HwFlowCtl);
    21. // 配置时钟
    22. if(huart->Instance == USART1)
    23. {
    24. pclk = HAL_RCC_GetPCLK2Freq();
    25. }
    26. else
    27. {
    28. pclk = HAL_RCC_GetPCLK1Freq();
    29. }
    30. // 配置波特率
    31. #if defined(USART_CR1_OVER8)
    32. if (huart->Init.OverSampling == UART_OVERSAMPLING_8)
    33. {
    34. huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate);
    35. }
    36. else
    37. {
    38. huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate);
    39. }
    40. #else
    41. huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate);
    42. #endif /* USART_CR1_OVER8 */
    43. }

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

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

    五、效果图

  • 相关阅读:
    基于互联网的招聘信息统计与分析(作业)
    谷歌浏览器书签找回
    Kotlin 中注解 @JvmOverloads 的作用
    提升工作效率!如何巧用 Ansible 实现自动化运维?
    MybatisX快速开发插件模版扩展
    测试人必备的Linux常用命令大全...【全网最全面整理】
    计算机网络-网络层(ARP协议,DHCP协议,ICMP协议)
    java计算机毕业设计在线专业培养方案系统源码+mysql数据库+系统+lw文档+部署
    【Java UI】HarmonyOs如何集成ButterKnife
    Golang:pongo2类似Django的模板引擎
  • 原文地址:https://blog.csdn.net/TSC1235/article/details/132957136