打印
[STM32F4]

【STM32F469I试用】+4.STM32CubeMX配置串口通信(轮询,中断,DM...

[复制链接]
14498|18
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 liuyuliuyuliuyu 于 2015-12-22 10:11 编辑

       开发环境:MDK5.14 和STM32CubeMX4.11。
       在上一个帖子STM32CubeMX按键控制流水灯https://bbs.21ic.com/forum.html?mo ... peid%26typeid%3D350的基础上,继续学习串口通信。
       使用STM32CubeMX配置串口通信,需要用到HAL库。HAL库中实现串口通信有三种方式:轮询、中断和DMA和串口通信相关的初始化部分通过STM32CubeMX软件配置,具体过程在下面的例子中说明。
       轮询模式:为堵塞模式,使用超时管理机制。
发送函数
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
参数:
huart: 指向串口结构体(通过STM32CubeMX软件配置串口时会生成,包含串口通信相关的信息)的指针。
pData: 指向发送数据块的指针
Size: 发送数据的数量
Timeout: 超时周期
返回值:HAL statusHAL_OK HAL_ERRORHAL_BUSY HAL_TIMEOUT
接收函数
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
参数:
huart: 指向串口结构体的指针。
pData: 指向接收数据块的指针
Size: 接收数据的数量
Timeout: 超时周期
返回值:HAL statusHAL_OK HAL_ERRORHAL_BUSY HAL_TIMEOUT
必须在指定的时间内接收到指定数量的数据才会返回HAL_OK
      中断模式: 非堵塞模式。和UART相关的中断:发生完成中断,结束中断和错误中断。
发送函数用于开启中断发送
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
参数:
huart: 指向串口结构体的指针。
pData: 指向发送数据块的指针
Size: 发送数据的数量
返回值:HAL statusHAL_OK HAL_ERRORHAL_BUSY
接收函数用于开启中断接收
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
参数:
huart: 指向串口结构体的指针。
pData: 指向接收数据块的指针
Size: 接收数据的数量
返回值:HAL statusHAL_OK HAL_ERRORHAL_BUSY HAL_TIMEOUT
必须接收到指定数量的数据才会触发中断。
相关的回调函数:
HAL_UART_TxCpltCallback():发送完成后,通过中断处理函数调用。
HAL_UART_RxCpltCallback():接收完成后,通过中断处理函数调用。
HAL_UART_ErrorCallback():传输过程中出现错误时,通过中断处理函数调用。
可以在回调函数里定制自己的代码。
      DMA模式:非堵塞模式
发送函数用于开启DMA发送
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
参数:
huart: 指向串口结构体的指针。
pData: 指向发送数据块的指针
Size: 发送数据的数量
返回值:HAL statusHAL_OK HAL_ERRORHAL_BUSY
接收函数用于开启DMA接收
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
参数:
huart: 指向串口结构体的指针。
pData: 指向接收数据块的指针
Size: 接收数据的数量
返回值:HAL statusHAL_OK HAL_ERRORHAL_BUSY HAL_TIMEOUT
必须接收到指定数量的数据才会完成一次DMA传输。
相关的回调函数(使用回调函数需要开启串口中断):
HAL_UART_TxHalfCpltCallback():一半数据(half transfer)发送完成后,通过中断处理函数调用。
HAL_UART_TxCpltCallback():发送完成后,通过中断处理函数调用。
HAL_UART_RxHalfCpltCallback():一半数据(half transfer)接收完成后,通过中断处理函数调用。
HAL_UART_RxCpltCallback():接收完成后,通过中断处理函数调用。
HAL_UART_ErrorCallback():传输过程中出现错误时,通过中断处理函数调用。
可以在回调函数里定制自己的代码。
     暂停DMA传输:
HAL_StatusTypeDef HAL_UART_DMAPause(UART_HandleTypeDef *huart)
    恢复DMA传输:
HAL_StatusTypeDef HAL_UART_DMAResume(UART_HandleTypeDef *huart)
     停止DMA传输:
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
参数:huart: 指向串口结构体的指针。
返回值:HAL statusHAL_OK

     下面通过简单的例子说明:
      发板的USART3使用的是PB10PB11引脚,但是使能USART3的时候默认引脚是PC10PC11。因此需要先配置PB10PB11引脚,在使能USART。相关的电路图如下:

   轮询模式:
在STM32CubeMX软件中,分别配置PB10和PB11为串口收发,接着使能USART3,最后配置串口通信相关的参数:
生成MDK工程并打开,在main.c文件中,定义全局变量用于收发:
uint8_t aTxBuffer[] = "** UART__ComPolling ** \r\n";
uint8_t aRxBuffer[32];
在主函数的while循环之前,发送一个字符串,如果发送失败,点亮LED:
HAL_GPIO_WritePin(GPIOG,GPIO_PIN_6,GPIO_PIN_SET);
        HAL_GPIO_WritePin(GPIOD,GPIO_PIN_4,GPIO_PIN_SET);
        HAL_GPIO_WritePin(GPIOD,GPIO_PIN_5,GPIO_PIN_SET);
        HAL_GPIO_WritePin(GPIOK,GPIO_PIN_3,GPIO_PIN_SET);
        if(HAL_UART_Transmit(&huart3,(uint8_t *)aTxBuffer,32,5000)!= HAL_OK)
        {
                        HAL_GPIO_WritePin(GPIOD,GPIO_PIN_4,GPIO_PIN_RESET);
        }
在while循环中,等待接收到指定数量的字符,然后将接收的字符发送出去,超时时间设置为1秒(单位为ms):
while(HAL_UART_Receive(&huart3, (uint8_t *)aRxBuffer, 5, 1000) != HAL_OK)
                {
                        HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_5);
                }
                if(HAL_UART_Transmit(&huart3,(uint8_t *)aRxBuffer,5,1000) == HAL_OK)
                {
                        HAL_GPIO_TogglePin(GPIOG,GPIO_PIN_6);
                }
                HAL_Delay(1000);
编译下载,使用串口调试助手,调试过程如下:
中断模式:
在上面配置的基础上,使能串口中断,并配置优先级,也可以改变优先级分组:
在生成的MDK工程中,定义全局变量用于收发:
uint8_t aTxBuffer[] = "** UART__IT ** \r\n";
uint8_t aRxBuffer[32];
uint8_t Rx_flag = 0;
通过Rx_flag控制发送接收的数据。在主函数的while循环之前开启发送和接收中断:
HAL_GPIO_WritePin(GPIOG,GPIO_PIN_6,GPIO_PIN_SET);
        HAL_GPIO_WritePin(GPIOD,GPIO_PIN_4,GPIO_PIN_SET);
        HAL_GPIO_WritePin(GPIOD,GPIO_PIN_5,GPIO_PIN_SET);
        HAL_GPIO_WritePin(GPIOK,GPIO_PIN_3,GPIO_PIN_SET);
   
        HAL_UART_Transmit_IT(&huart3,(uint8_t *)aTxBuffer,32);
        HAL_UART_Receive_IT(&huart3,(uint8_t *)aRxBuffer,12);
在while循环中将接收的数据发送出去:
if(Rx_flag == 1)
                {
                        HAL_UART_Transmit_IT(&huart3,(uint8_t *)aRxBuffer,32);
                        Rx_flag = 0;
                }
在usart.c文件中声明外部变量:
extern uint8_t aRxBuffer[32];
extern uint8_t Rx_flag;
加入收发回调函数,在接收回调函数里将Rx_flag置1控制发送,并在此开始中断接收:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *UartHandle)
{
  HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_4);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
        Rx_flag = 1;
        HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_5);
        HAL_UART_Receive_IT(&huart3,(uint8_t *)aRxBuffer,12);
}
编译下载,使用串口调试助手,调试过程如下:
DMA模式:
在上面配置的基础上,使能DMA,并为发送和接收选择DMA通道:
在生成的MDK工程中定义如下全局变量用于收发:
uint8_t aTxBuffer[] = "** UART__DMA ** \r\n";
uint8_t aRxBuffer[32];
uint8_t Rx_flag = 0;
通过Rx_flag控制发送接收的数据。在主函数的while循环之前开启DMA发送和接收:

HAL_GPIO_WritePin(GPIOG,GPIO_PIN_6,GPIO_PIN_SET);
        HAL_GPIO_WritePin(GPIOD,GPIO_PIN_4,GPIO_PIN_SET);
        HAL_GPIO_WritePin(GPIOD,GPIO_PIN_5,GPIO_PIN_SET);
        HAL_GPIO_WritePin(GPIOK,GPIO_PIN_3,GPIO_PIN_SET);
        HAL_UART_Transmit_DMA(&huart3,(uint8_t *)aTxBuffer,32);
        HAL_UART_Receive_DMA(&huart3,(uint8_t *)aRxBuffer,12);
在while循环中将接收的数据发送出去:
if(Rx_flag == 1)
                {
                        HAL_UART_Transmit_DMA(&huart3,(uint8_t *)aRxBuffer,32);
                        Rx_flag = 0;
                }
                if(Rx_flag == 2)
                {
                        HAL_GPIO_TogglePin(GPIOG,GPIO_PIN_6);
                        Rx_flag = 0;
                }
在usart.c文件中声明外部变量:
extern uint8_t aRxBuffer[32];
extern uint8_t Rx_flag;

加入收发回调函数,在接收回调函数里将Rx_flag置1控制发送,并在此开始中断接收,通过控制LED简单的验证发送和接收一半数据的回调函数:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *UartHandle)
{
  HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_4);
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
        Rx_flag = 1;
        HAL_UART_Receive_DMA(&huart3,(uint8_t *)aRxBuffer,12);
}
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart)
{
        HAL_GPIO_TogglePin(GPIOK,GPIO_PIN_3);
}
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
        Rx_flag = 2;
}
编译下载,使用串口调试助手,调试过程如下:

最后,关于串口重定向可以参考一下代码,需要包含stdio.h,可以替换成中断或DMA模式,这样就可以使用printf和scanf函数:
int fputc(int ch,FILE *fp)
{
        HAL_UART_Transmit(&huart3,(uint8_t *)&ch,1,5000);
        return ch;
}
int fgetc(FILE *fp)
{
        uint8_t ch;
        HAL_UART_Receive(&huart3,(uint8_t *)ch,1,5000);
        return ch;
}

评分
参与人数 1威望 +3 收起 理由
ttl_web + 3 赞一个!
沙发
apple163| | 2016-1-4 14:48 | 只看该作者
请教楼主,使用CUBE MX生成代码,UART只要使用DMA就一定默认开启中断吗?我的测试就是你若配置了DMA给UART,就无法关闭中断。及其的苦恼

使用特权

评论回复
板凳
liuyuliuyuliuyu|  楼主 | 2016-1-5 11:13 | 只看该作者
apple163 发表于 2016-1-4 14:48
请教楼主,使用CUBE MX生成代码,UART只要使用DMA就一定默认开启中断吗?我的测试就是你若配置了DMA给UART ...

我试了,确实不能关,如果关的话可以在程序里把相关的中断使能代码注释掉。还有我不确定的是,如果开中断的话,会有什么问题

使用特权

评论回复
地板
apple163| | 2016-1-5 12:27 | 只看该作者
liuyuliuyuliuyu 发表于 2016-1-5 11:13
我试了,确实不能关,如果关的话可以在程序里把相关的中断使能代码注释掉。还有我不确定的是,如果开中断 ...

开中断比较简单,没有什么问题,但是个人不喜欢开中断,如果一个UART就开两个中断,那么若干模块用下来要开好多中断了,我写程序即不喜欢阻塞模式,又不喜欢开中断,因此我需要DMA做FIFO,然后查询的方式获得数据,NXP自己就带FIFO,STM32F103系列不带,所以只能借助DMA,开中断本来就是为了高速响应事件,然而有些事情真的没有那么高的实时性需求,然后让整个程序充满了各种中断,这样的架构我自认为不好驾驭。通常我只开一个systimer,或者再有特别需求另议。

使用特权

评论回复
5
jjjkkk00| | 2016-4-6 13:45 | 只看该作者
mark

使用特权

评论回复
6
Larm1| | 2016-4-15 13:21 | 只看该作者
好东西值得学习...

使用特权

评论回复
7
凌云展翅| | 2016-4-16 20:45 | 只看该作者
向楼主学习。

使用特权

评论回复
8
xhdzwzj| | 2016-4-27 12:06 | 只看该作者
谢谢楼主分享

使用特权

评论回复
9
598330983| | 2016-5-23 19:33 | 只看该作者
非常感谢楼主的讲解,让我知道了回调的用法,太感谢了。

使用特权

评论回复
10
lijianying1992| | 2016-6-1 18:27 | 只看该作者
谢谢楼主分享。我想请问下,HAL_UART_Transmit_IT() 这个函数里面使能发送中断吗?

使用特权

评论回复
11
liuyuliuyuliuyu|  楼主 | 2016-6-2 08:43 | 只看该作者
lijianying1992 发表于 2016-6-1 18:27
谢谢楼主分享。我想请问下,HAL_UART_Transmit_IT() 这个函数里面使能发送中断吗? ...

使能,相关代码:
  /* Enable the UART Parity Error Interrupt */
    __HAL_UART_ENABLE_IT(huart, UART_IT_PE);

    /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
    __HAL_UART_ENABLE_IT(huart, UART_IT_ERR);

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    /* Enable the UART Transmit data register empty Interrupt */
    __HAL_UART_ENABLE_IT(huart, UART_IT_TXE);

使用特权

评论回复
12
hou221| | 2016-6-30 22:09 | 只看该作者
正在学习STM32,受用了

使用特权

评论回复
13
suxilong| | 2016-7-6 08:54 | 只看该作者
请问楼主, 如果是跑RTOS 的话,

添加UART 外设的具备步骤 是怎样的?

另外 楼主用的 图形工具生成 软件框架。。。。

请问知不知道  IAR 如何实现?  用的是 官网的demo 例程,
但例程中 没有提及如何 添加外设~~~

使用特权

评论回复
14
jp2013| | 2016-8-24 11:15 | 只看该作者
楼主你好,我三种方式都使用过了,我的前两种当发的长度超过一定数值后就发生丢字节,DMA没有问题,我用的STM32F411,结合cube生成的工程,函数应该是一样的,你能帮我看看嘛。
我轮询部分的代码:
while(1){
                while(HAL_UART_Receive(&huart6, (uint8_t *)rx_buffer, 1, 10) != HAL_OK)
                {}
                        HAL_UART_Transmit(&huart6,(uint8_t *)rx_buffer,1,10);
        }
非常感谢!

使用特权

评论回复
15
一般首席| | 2016-12-20 18:09 | 只看该作者
学习了

使用特权

评论回复
16
zhuomuniao110| | 2016-12-20 22:23 | 只看该作者
好详细的教程,收了,这三种方式确实很常用,而套路其实不复杂,掌握透了就OK。

使用特权

评论回复
17
奇缘时间| | 2017-9-21 21:21 | 只看该作者
Hi 楼主
    请问cube mx生成的函数HAL_SPI_TransmitReceive_IT这个函数是不是表示同时开启发送与接收中断,若调用回调函数HAL_SPI_TxRxCpltCallback,是不是指发送与接收触发中断后,都会各自调用一次HAL_SPI_TxRxCpltCallback中的程序?

使用特权

评论回复
18
bitshiyan| | 2017-12-13 11:27 | 只看该作者
正在学习STM32,受用了

使用特权

评论回复
19
mingxie| | 2018-3-19 23:59 | 只看该作者
向楼主学习领教了!!!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

6

主题

184

帖子

3

粉丝