STM32 DMA USART串口发送解析

发布者:HeavenlyJoy444最新更新时间:2019-06-29 来源: eefocus关键字:STM32  DMA  USART  串口发送 手机看文章 扫描二维码
随时随地手机看文章

数据传输时要从支持那些相关的标准?传输的速度?什么时候开始?什么时候结束?传输的内容?怎样防止通信出错?数据量大的时候怎么弄?硬件怎么连接出发,当然对于stm32还要熟悉库函数的功能

具起来rs232和485电平的区别硬件外围芯片,波特率(反映传一位的时间),起始位和停止位,数据宽度,校验,硬件流控制,相应连接电脑时的接口怎么样的。配置,使用函数,中断,查询并结合通信协议才算了解了串口使用。

以上是基础,当然stm很多相关复用功能,支持同步单向通信和半双工单线通信,支持局部互联网、智能卡协议和红外数据组织相关规范,以及调制解调器操作,运行多处理器通信。同时可以使用DMA方式进行高速数据通信。注意Print函数时间问题,尝试通过DMA解决。

特点:全双工,异步,分数波特率发生器好处是速度快,位数8或9为,可配置1或2停止位,Lin协议,可提供同步时钟功能。

 

硬件上

一般2个脚,RX和TX;同步模式需要SCLK时钟脚,红外IRDA需要irDA_RDI脚作为数据输入和irDA_TDO输出。

奇偶校验通过usart_cr1     pce位配置校验(发送生成奇偶位,接受时进行校验)

LIN局域互联网模式:通过设置USART_CR2中LINEN位配置,使用的时候需要外加专门的收发器才可以

同步模式:通过设置USART_CR2中CLKEN位配置

智能卡模式:  通过设置USART_CR3中SCEN位配置   

DMA、硬件流控制作专门研究。

 

中断有哪些事件?

发送数据寄存器空   发送完成  接受数据可读  奇偶校验错数据溢出  CTS标志 空闲标志 断开标志 噪声标志

遗憾是没有留有接受缓冲区,用查询容易数据丢失

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

流程是时钟配置---管脚配置(如重映射,管脚模式)----串口配置----中断配置-----相应中断-----打开串口

上面是一些基础知识点,下面从实际运用来了解串口功能

比较简单些的应用吧:对usart进行初始化的工作

void COM1_Init( void)
{

//首先要初始化结构体:少不了对于的引脚,忘不了usart,更牵挂着中断的配置结构体,定义空间来被涂鸦

 GPIO_InitTypeDef GPIO_InitStructure;           
 USART_InitTypeDef USART_InitStructure;
 NVIC_InitTypeDef NVIC_InitStructure; 
 

//下面是对GPIO进行涂鸦
 
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

 

            
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;                                       //选择管脚位
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                              //模式复用推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(GPIOA, &GPIO_InitStructure);
    
 
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;                                      //选择管脚位
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;                           //模式为输入
 GPIO_Init(GPIOA, &GPIO_InitStructure);
 
 USART_InitStructure.USART_BaudRate = 115200;                                    //波特率
 USART_InitStructure.USART_WordLength = USART_WordLength_8b;                     //8数据位
 USART_InitStructure.USART_StopBits = USART_StopBits_1;                          //1停止位
 USART_InitStructure.USART_Parity = USART_Parity_No;                             //无奇偶校验
 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无数据流控制
 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                 //收发模式

 
 USART_Init(USART1, &USART_InitStructure);
 
 //使能串口中断,并设置优先级
 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = USART1_IRQn_Priority;
 NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
 NVIC_Init(&NVIC_InitStructure); //将结构体丢到配置函数,即写入到对应寄存器

 
    //USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
   
 //所有的工作都做好了,最后别忘了打开串口
 USART_Cmd(USART1, ENABLE);
}

 

//USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);下面是中断源

#define USART_IT_PE                          ((uint16_t)0x0028)
#define USART_IT_TXE                         ((uint16_t)0x0727)
#define USART_IT_TC                          ((uint16_t)0x0626)
#define USART_IT_RXNE                        ((uint16_t)0x0525)
#define USART_IT_IDLE                        ((uint16_t)0x0424)
#define USART_IT_LBD                         ((uint16_t)0x0846)
#define USART_IT_CTS                         ((uint16_t)0x096A)
#define USART_IT_ERR                         ((uint16_t)0x0060)
#define USART_IT_ORE                         ((uint16_t)0x0360)
#define USART_IT_NE                          ((uint16_t)0x0260)
#define USART_IT_FE                          ((uint16_t)0x0160)

 --------------------------------------------------------------------------------------

以上是初始化配置,下面还要构成最小的运用,就举例输出函数吧

void PrintUart1(const u8 *Str)
{
 while(*Str)
 {
  USART_SendData(USART1, (unsigned char)*Str++);
  while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
 }
}

发送字符是通过查询字符串的状态来来不断的发送下一个数据。

接受数据是通过中断来实现的,把接受的数据放入缓冲区,来实现。有包含协议以后细讲。

 

想玩电脑串口传输数据,通过printf()来直接在电脑窗口显示是不是很爽?在usart.c函数中加入

//不使用半主机模式
#if 1 //如果没有这段,则需要在target选项中选择使用USE microLIB
#pragma import(__use_no_semihosting)
struct __FILE 

 int handle; 
}; 
FILE __stdout; 

_sys_exit(int x) 

 x = x; 
}
#endif

int fputc(int ch, FILE *f)
{
  USART_SendData(USART1, (unsigned char)ch);
  while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
 return ch;
}

上面数据来源可以通过串口,通过usb,通过无线等等。。。

printf函数有个缺陷,就是花费的时间太多了(毫秒级),总不至于让CPU等几个毫秒就来显示串口吧,那再好的CPU也就费了,那肿么办?可以用DMA!!直接让其它硬件来传这些粗糙的工作。CPU玩重点的其它的活!

先接着讲好串口接受,再说这个DMA,在固件库里面有个文件是专门用来放中断处理函数的

里面有个函数
void USART1_IRQHandler(void)
{
 static u8 UartBuf[UART_BUF_LEN];//串口缓冲
 if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
 { 
  temp=USART_ReceiveData(USART1);

..............下面就是一些处理,可以用状态机模式来玩,直到填充好串口缓冲数据,并校验正确,不多说

  }
}

上面是典型的中断处理函数,结合状态机功能就强大了。讲到操作系统还要进一步的学会运用。

---------------------------------------------------------------------------------------------

通过上面的学习相信对基本的串口操作有了比较深入的理解了,前面提到printf太慢,这里需要一些改进。

考虑到真正执行串口输出(用DMA其实在几毫秒后完成)比执行完pruntf(立即完成)这个时间点晚个几毫秒对实际应用来说没有任何影响,因此CPU可以在嘀嗒中断中指挥DMA模块完成串口输出的任务。(其实对系统还是有些影响的宏观讲,串口输出的数据其实很少,在大河中放入一杯水,几乎可以忽略不计的)
再解决怎么分配:
定义两个全局缓存区
其中一个缓存区以循环队列的形式组织,每次执行fputc时向其队尾加入一个元素。
另一个缓存区直接以数组的形式组织,作为DMA的源地址。
在嘀嗒中断中按下列顺序完成对DMA的操作
(1)判断循环队列是否为空,如果为空说明当前没有字符串需要通过串口输出直接跳至(6)
(2)判断DMA是否正在工作,如果DMA正在工作说明上次分配的任何没干完直接跳至(6)
(3)从循环队列出队N个字符到数组缓存
(4)告诉DMA本次需传输的字节数N
(5)命令DMA开始传输
(6)结束操作
补充:
1.N的确定方法:若循环队列中元素的个数大于或等于数组缓存区的长度(固定值),则将数组缓存区的
长度赋给N,若循环队列中元素的个数小于数组缓存区的长度则将循环队列元素的个数赋给N
2.循环队列开辟得越大,能缓存的字符串就越多,因此是越大越好.
3.数组缓存区并不是开辟的越大越好,这个值可以做如下的计算得出,假设波特率为115200嘀嗒中断
的周期为2毫秒则在这2毫秒时间内理论上最多可传115200*0.002/10=23个字节。因此把数组缓存区
的大小定到比23稍小一点即可,比如定为20.
代码可正常运行。经测试使用新方案printf一个包含了二十个字符的字符串只需要25微秒的CPU耗时,
而老方案则需要1.76毫秒的CPU耗时。从而可以放心的使用printf调试一些时序要求较高的函数了,
另外因为执行时间段从而printf被重入的概率大大减小。如果需要彻底防止printf被重入的话,可在调用printf之前关中断,在printf执行完之后开中断,代价也仅是可能发生的几十微秒的中断延时而已。

.........................................................................................

下面讲一讲我对DMA的理解

stm32   DMA有8通道,0---7

既然DMA传输的是数据,当然有数据的宽度了,这需要配置。另外还要有地址吧,从哪个地址开始传,还有传到哪个地址,需要配置。还有传输普通的就直接传完就好了,如果要高速不断循环传输,这也可以配置。还有从ROM直接快速的加载到RAM(反过来不可以)也可以的。

之上就是一些基本需要配置的工作

 DMA_DeInit(DMA1_Channel4); 

 上面这句是给DMA配置通道,根据ST提供的资料,STM3210Fx中DMA包含7个通道 

DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&((USART_TypeDef *)USART1)->DR);
上面是设置外设地址

DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) USART_DMA_BUF;

上面这句很显然是DMA要连接在Memory中变量的地址,上面设置存储器地址;

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;

上面的这句是设置DMA的传输方向,就如前面我所说的,从存储器到外设,也可以从外设到存储器,:把DMA_DIR_PeripheralSRC改成DMA_DIR_PeripheralDST即可。

DMA_InitStructure.DMA_BufferSize = 0;

上面的这句是设置DMA在传输时缓冲区的长度,前面有定义过了buffer的起始地址:为了安全性和可靠性,一般需要给buffer定义一个储存片区,这个参数的单位有三种类型:Byte、HalfWord、word,我设置的2个half-word(见下面的设置);32位的MCU中1个half-word占16 bits。

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

上面的这句是设置DMA的外设递增模式,如果DMA选用的通道(CHx)有多个外设连接,需要使用外设递增模式:DMA_PeripheralInc_Enable;我的例子选用DMA_PeripheralInc_Disable

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

[1] [2] [3]
关键字:STM32  DMA  USART  串口发送 引用地址:STM32 DMA USART串口发送解析

上一篇:STM32HAL----USB升级固件(DFU)
下一篇:STM32外拓RAM

小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved