STM32的USART组件支持异步、同步、单线半双工、多处理器、IrDA、LIN、SmartCard等模式,本文介绍的是异步即UART模式。

总线通信有三种模型:轮询、中断和DMA。DMA对我来说是陌生的内容,以后单独开篇细讲。

HAL

HAL把寄存器组组织成组件,组件包含外设的各个寄存器。在USART这里,寄存器不足以描述外设的所有状态,HAL用handle来包装组件。一个handle包含指向组件的指针、初始化参数、状态、与其他组件的链接(如DMA)和内部状态等。

图源ST官方MOOC,打开之前注意调低音量。

USART的初始化除了USART本身的寄存器以外,还要设置GPIO的复用功能,这两项任务分别在stm32f4xx_hal_uart.c中的HAL_UART_Initstm32f4xx_hal_msp.cHAL_UART_MspInit中完成(MSP意为“MCU Specific Package”)。stm32f4xx_hal_uart.c中也定义了HAL_UART_MspInit,添加了weak属性(提供实现,允许被覆写)。

轮询

轮询是与中断相对的。对于发送,轮询是指写一个字节(或一个packet),等待它发送完,再写下一个字节,直到所有数据被发送完才返回;对于接受,轮询是指等待直到接收到一定长度的数据。轮询相对简单,但是效率很低。

#include "main.h"
#include <string.h> UART_HandleTypeDef huart1; void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
void uart_transmit(const char* string); int main(void)
{
char buffer[2] = {0};
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
uart_transmit("hello\n");
while (1)
{
HAL_StatusTypeDef status = HAL_UART_Receive(&huart1, buffer, 1, 1000);
if (status == HAL_OK)
{
uart_transmit("received: ");
uart_transmit(buffer);
uart_transmit("\n");
}
else
uart_transmit("timeout\n");
}
} void uart_transmit(const char* string)
{
HAL_UART_Transmit(&huart1, string, strlen(string), 1000);
} static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
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();
}
} // ...

HAL的UART接收只能指定数据长度而不能指定终止符。在轮询模式下,可以设置数据长度为1,即每次读取一个字节,判断它是否为终止符。

中断

在中断模式下,函数立即返回,数据在中断中发送或接收。在发送或接收完成后,相应的回调函数会被调用。

#include "main.h"
#include <stdbool.h>
UART_HandleTypeDef huart1;
volatile bool finished = false;
char buffer[3] = {0}; void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
void uart_transmit(const char* string);
void uart_transmit_it(const char* string); int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
uart_transmit_it("hello\n");
const char* info = finished ? "already finished\n" : "still transmitting\n";
while (!finished)
;
finished = false;
uart_transmit_it(info);
uart_transmit_it(info);
while (!finished)
;
while (1)
{
finished = false;
HAL_UART_Receive_IT(&huart1, buffer, 2);
while (!finished)
;
uart_transmit("received: ");
uart_transmit(buffer);
uart_transmit("\n");
}
} void uart_transmit(const char* string)
{
HAL_UART_Transmit(&huart1, string, strlen(string), 1000);
} void uart_transmit_it(const char* string)
{
HAL_UART_Transmit_IT(&huart1, string, strlen(string));
} void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart == &huart1)
{
finished = true;
}
} void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart == &huart1)
{
finished = true;
}
} // ...

串口输出still transmitting,说明HAL_UART_Transmit_IT确实是发送完成前就返回的;still transmitting只出现一次,因为第二次调用时第一次的发送还没结束。

读了HAL的源码,我发现中断发送的数据是拷贝指针的,也就是浅拷贝的,需要保证发送期间该地址上的数据有效。比如,如果一个函数把局部变量数组作为参数传给HAL_UART_Transmit_IT,未等待发送完成便返回,那么发送的数据将会是错误的,甚至导致程序行为未定义。

如果给单片机发送了多余所需量的数据,程序会崩溃,我没有debug出问题在哪。

缓冲区

这样的接收连差强人意都算不上,我的终极目标是实现scanf那样的接收函数。中断发送只能缓冲一次和浅拷贝等问题也相当愚蠢,我想顺便把发送也改造成printf。改造的工具是用循环队列实现的缓冲区,这个我在AVR单片机教程中还煞有其事地写过,正好可以作为现在的练习。

queue.h

#ifndef QUEUE_H
#define QUEUE_H #include <stdint.h>
#include <stdbool.h>
#include <stdlib.h> #ifdef __cplusplus
extern "C"
{
#endif typedef struct
{
uint16_t mask;
uint16_t head;
uint16_t tail;
queue_element_t data[0];
} queue_t; static inline queue_t* queue_create(uint16_t _size)
{
if (_size & (_size - 1))
_size = 256;
queue_t* q = malloc(sizeof(queue_t) + _size * sizeof(queue_element_t));
if (q)
{
q->mask = _size - 1;
q->head = q->tail = 0;
}
return q;
} static inline bool queue_empty(const volatile queue_t* _queue)
{
return _queue->head == _queue->tail;
} static inline bool queue_full(const volatile queue_t* _queue)
{
return ((_queue->tail + 1) & _queue->mask) == _queue->head;
} static inline uint16_t queue_size(const volatile queue_t* _queue)
{
return (_queue->tail - _queue->head) & _queue->mask;
} static inline uint16_t queue_capacity(const volatile queue_t* _queue)
{
return _queue->mask;
} static inline queue_element_t queue_peek(const volatile queue_t* _queue)
{
return _queue->data[_queue->head];
} static inline void queue_push(volatile queue_t* _queue, const queue_element_t _ele)
{
_queue->data[_queue->tail] = _ele;
_queue->tail = (_queue->tail + 1) & _queue->mask;
} static inline void queue_pop(volatile queue_t* _queue)
{
_queue->head = (_queue->head + 1) & _queue->mask;
} #ifdef __cplusplus
}
#endif #endif

inline遇到了点问题,原来C和C++中的inline是不一样的!改成static inline就好了。有空再去深究这个问题。

main.c

#include "main.h"
#include <string.h>
#include "cmsis_gcc.h"
typedef char queue_element_t;
#include "queue.h"
UART_HandleTypeDef huart1;
queue_t* tx_buffer;
queue_t* rx_buffer; void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
static void usart1_init_0();
static void usart1_init_2();
static void usart1_transmit(const char* string);
static void usart1_receive(char* dest, char delim); int main(void)
{
char buffer[80];
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
usart1_transmit("hello\n");
while (1)
{
usart1_receive(buffer, '\n');
usart1_transmit("received: ");
usart1_transmit(buffer);
usart1_transmit("\n");
}
} void usart1_init_0()
{
tx_buffer = queue_create(1024);
rx_buffer = queue_create(1024);
} void usart1_init_2()
{
USART1->CR1 |= USART_CR1_RXNEIE & UART_IT_MASK;
} void usart1_transmit(const char* string)
{
uint16_t capacity = queue_capacity(tx_buffer);
uint16_t size = strlen(string);
bool ok = false;
while (1)
{
__disable_irq();
ok = capacity - queue_size(tx_buffer) >= size;
if (ok)
break;
__enable_irq();
__NOP();
}
for (uint16_t i = 0; i != size; ++i)
queue_push(tx_buffer, string[i]);
USART1->CR1 |= USART_CR1_TXEIE & UART_IT_MASK;
__enable_irq();
} void usart1_receive(char* dest, char delim)
{
while (1)
{
bool ok = false;
while (1)
{
__disable_irq();
ok = !queue_empty(rx_buffer);
if (ok)
break;
__enable_irq();
__NOP();
}
char c = queue_peek(rx_buffer);
queue_pop(rx_buffer);
__enable_irq();
if (c == delim)
break;
*dest++ = c;
}
*dest = '\0';
} void usart1_transmit_handler()
{
USART1->DR = queue_peek(tx_buffer);
queue_pop(tx_buffer);
if (queue_empty(tx_buffer))
USART1->CR1 &= ~USART_CR1_TXEIE & UART_IT_MASK;
} void usart1_receive_handler()
{
queue_push(rx_buffer, USART1->DR);
} void USART1_IRQHandler(void)
{
uint32_t isrflags = USART1->SR;
uint32_t cr1its = USART1->CR1;
uint32_t errorflags = 0x00U;
errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
if (errorflags == RESET)
{
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
usart1_receive_handler();
return;
}
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
usart1_transmit_handler();
return;
}
} HAL_UART_IRQHandler(&huart1);
} static void MX_USART1_UART_Init(void)
{
usart1_init_0(); huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
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();
} usart1_init_2();
}

中断的调用流程是:USART1中断请求调用USART1_IRQHandler(这个名字在startup_stm32f407vetx.s中定义),由STM32CubeMX生成的USART1_IRQHandler调用HAL_UART_IRQHandler,里面进行各种判断和处理,在合适的时机调用HAL_UART_TxCpltCallback等。我在USART1_IRQHandler中插入了一些代码,把TXERXNE两种中断拦截了下来,其余还是丢给HAL_UART_IRQHandler处理(Chain of Responsibility设计模式?)。

queue上的操作不是原子的,主函数与中断共享需要加锁。__disable_irq关闭全局中断,__enable_irq开启全局中断。ARM说在开中断之后Cortex-M3/4还可能执行2条指令才响应中断,而在汇编代码中cpsie后第二句就是cpsid,所以我在__enable_irq后加一句__NOP空指令,以保证中断请求能被响应。

printfscanf只有一步之遥了,但我想把它放到下一篇。20pin的ST-LINK/V2已经在路上了。

STM32学习笔记——USART的更多相关文章

  1. STM32学习笔记——USART串口

    转载自:http://www.cnblogs.com/microxiami/p/3752715.html 一.USART简介 通用同步异步收发器(USART)提供了一种灵活的方法与使用工业标准NRZ异 ...

  2. STM32学习笔记——USART串口(向原子哥和火哥学习)

    一.USART简介 通用同步异步收发器(USART)提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换.USART利用分数波特率发生器提供宽范围的波特率选择. S ...

  3. STM32学习笔记——OLED屏

    STM32学习笔记--OLED屏 OLED屏的特点: 1.  模块有单色和双色可选,单色为纯蓝色,双色为黄蓝双色(本人选用双色): 2.  显示尺寸为0.96寸 3.  分辨率为128*64 4.   ...

  4. STM32学习笔记——点亮LED

    STM32学习笔记——点亮LED 本人学习STM32是直接通过操作stm32的寄存器,使用的开发板是野火ISO-V2版本: 先简单的介绍一下stm32的GPIO: stm32的GPIO有多种模式: 1 ...

  5. stm32学习笔记----双串口同时打开时的printf()问题

    stm32学习笔记----双串口同时打开时的printf()问题 最近因为要使用串口2外接PN532芯片实现通信,另一方面,要使用串口1来将一些提示信息输出到上位机,于是重定义了printf(),使其 ...

  6. stm32学习笔记——外部中断的使用

    stm32学习笔记——外部中断的使用 基本概念 stm32中,每一个GPIO都可以触发一个外部中断,但是,GPIO的中断是以组为一个单位的,同组间的外部中断同一时间只能使用一个.比如说,PA0,PB0 ...

  7. STM32学习笔记(四)——串口控制LED(中断方式)

    目录: 一.时钟使能,包括GPIO的时钟和串口的时钟使能 二.设置引脚复用映射 三.GPIO的初始化配置,注意要设置为复用模式 四.串口参数初始化配置 五.中断分组和中断优先级配置 六.设置串口中断类 ...

  8. STM32学习笔记-NVIC中断知识点

    STM32学习笔记-NVIC中断知识点总结 中断优先级设置步骤 1. 系统运行后先设置中断优先级分组 函数:void NVIC_PriorityGroupConfig(uint32_tNVIC_Pri ...

  9. STM32学习笔记之一(初窥STM32)

    怎么做好学习笔记? 答:自我感知-->学习知识-->归纳总结-->自我升华(真正属于自己的知识是,抛开书本,运用时,你还能记得的思想) 自我感知--看到知识概念,先自我感觉那应该是个 ...

随机推荐

  1. python机器学习的常用算法

    Python机器学习 学习意味着通过学习或经验获得知识或技能.基于此,我们可以定义机器学习(ML)如下 - 它可以被定义为计算机科学领域,更具体地说是人工智能的应用,其为计算机系统提供了学习数据和从经 ...

  2. Windows 切换 working directory

    用函数 _chdir() 例如用计划任务启动,pwd 是 system32 使用相对路径的地方会出错. 在 main 函数刚启动的时候转换一下 working directory 可解.

  3. Taro 2.2 全面插件化,支持拓展和定制个性化功能

    自 2.2 开始,Taro 引入了插件化机制,允许开发者通过编写插件的方式来为 Taro 拓展更多功能或者为自身业务定制个性化功能,欢迎大家进行尝试,共同讨论~ 当前版本 2.2.1 官方插件 Tar ...

  4. Asp.Net Core 3.1学习-依赖注入、服务生命周期(6)

    1.前言 面向对象设计(OOD)里有一个重要的思想就是依赖倒置原则(DIP),并由该原则牵引出依赖注入(DI).控制反转(IOC)及其容器等概念.在学习Core依赖注入.服务生命周期之前,下面让我们先 ...

  5. SpringBoot应用操作Rabbitmq(topic交换器高级操作)

    一.topic交换器为主题交换器,可以根据路由key模糊匹配 实现模型图 二.实战 1.引入maven <dependency> <groupId>org.springfram ...

  6. 【DNS域名解析命令】host

    host - DNS lookup utility host命令是常用的分析域名查询工具,可以用来测试域名系统工作是否正常. 语法: host [-aCdlnrsTwv] [-c class] [-N ...

  7. 记一次Pinpoint监控工具部署过程

    环境:Centos 7.4 X64IP:192.168.1.11 1.配置环境,先安装jdk 到Oracle官网下载安装JDK https://www.oracle.com/technetwork/j ...

  8. for-loop 与 json.Unmarshal 性能分析概要

    原文地址:for-loop 与 json.Unmarshal 性能分析概要 前言 在项目中,常常会遇到循环交换赋值的数据处理场景,尤其是 RPC,数据交互格式要转为 Protobuf,赋值是无法避免的 ...

  9. stl的stack在开发中的应用

    栈有后进先出特点,我们可以用它来暂时保存数据,在画板开发中,我用到了栈来保存用户的每一步操作,当用户点击撤销时可以把图像从栈里面取出,然后恢复.浏览器的前进和后退也是这个原理,只是它保存的是网页罢了. ...

  10. Native Boot 从一个 VHD 引导系统的相关说明

    Native Boot 是 Windows 7 和 Windows Server 2008 R2 提供的一个新的功能,它允许从一个 VHD 文件引导一个操作系统,但是需要注意的是目前的 Windows ...