技术标签: stm32
传统串口接受与发送:
串口接受一个很长的帧,接受帧时依靠串口中断每次只能传输8位,传一个帧要进入好多次中断,每次进入中断都要判断是否接收完毕。
DMA串口接收与发送:
1,电脑通过串口1给STM32F407芯片发送数据(不定长,按照645格式来),芯片根据串口接收中断接收到数据后,通过DMA将数据存储在内存。当检测到数据接受完毕,产生接收完成标志位置位。当407检测到这个中断标志位后从TX端向电脑发送这段数据。
任务拆分:
1.检测到key0按下,由TX发送已经存在存储器的数据到电脑(用DMA存储器->
外设):
2.实现RX通过串口中断能正常接受电脑发送的帧,检测到key0按下,由TX发送到电脑用(用DMA存储器->
外设):
3.实现RX通过串口(事件)能正常接受电脑发送的帧(DMA:外设->内存),检测到key0按下,由TX发送到电脑用DMA(存储器->
外设):
设置关注博文即可访问,程序发布到GitHUB
DMA(Direct Memory Access)直接存储器访问,当外设到存储器、存储器到外设、存储器到存储器有传输任务时,可以在无需任何 CPU 操作的情况下通过 DMA 快速移动数据。这样节省的 CPU 资源可供其它操作使用。
注:DMA1 控制器 AHB 外设端口与 DMA2 控制器的情况不同,不连接到总线矩阵,因此,仅 DMA2 数据流能够执行存储器到存储器的传输。关于总线矩阵的相关内容见文末链接。
因为如果使用串口中断接收一帧很长很长的数据,每传8位都要打断一次CPU,CPU需要不停的进入中断做处理,严重影响CPU的工作效率。如果能通过DMA实现在接收完一帧数据之后再通知CPU处理这些数据,那岂不好处大大滴!
DMA+IDLE中断是一个常规的串口DMA接收方案,但是也有其局限性,如果数据帧的字节超时时间大于1个字节,那就会导致接收不完整的情况,所以建议在实现DMA接收的时候加入多字节超时的机制.
警告:要关闭连接到 DMA 数据流请求的外设,必须首先关闭外设连接的数据流请求的外设,必须首先关闭外设连接的 DMA 数据流,然后等待 EN 位 = 0 。只有这样才能安全地禁止外设。
接收定长数据:
使用DMA传输完成中断,比较简单
接收不定长数据:
方法1:用DMA + 串口空闲中断 :利用串口空闲标志位产生中断,在中断中处理接收的数据
方法2:用DMA+ 定时器:当产生&&&&&*******(((((((
串口空闲中断的功能先简单了解一下:注意,空闲中断是接受数据后出现一个Byte的高电平(空闲)状态,就会触发空闲中断,而且清除中断的方式很奇怪,需要通过先读SR寄存器再读DR寄存器来清除该标志,库函数的USART_ClearITPendingBit里头都没有IDLE参数的呦。
程序的大致流程:在dma.h文件中编写DMA初始化配置过程和DMA中断函数,在usart.c文件中编写串口1初始化函数和串口中断函数,在usart1初始化过程中调用DMA接收中断配置函数,串口1的RX接收到串口调试助手发的一帧数据后,进入串口1中断函数进行数据处理,完事儿重新开启DMA传输后退出中断。(下面的程序实现了串口1的DMA收发,仅展示关键步骤)
dma.h文件中DMA初始化及DMA中断:
/**********************************************************
* @brief 串口接收端DMA配置函数(从外设->存储器模式/8位数据宽度/存储器增量模式 )
* @param DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
* chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
* par:外设地址
* mar:存储器地址
* ndtr:数据传输量
***********************************************************/
void u2DMA_RX_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
{
DMA_InitTypeDef DMA_InitStructure;
if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); //DMA2时钟使能
}else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); //DMA1时钟使能
}
DMA_DeInit(DMA_Streamx);
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){
} //等待DMA可配置
/*配置 DMA Stream*/
DMA_InitStructure.DMA_Channel = chx; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = par; //DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = mar; //DMA存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; //外设到存储器
DMA_InitStructure.DMA_BufferSize = ndtr; //数据传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据长度:8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //正常模式,即满了就不在接收了,而不是循环存储
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //高优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存储器突发单次传输???
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输???
DMA_Init(DMA_Streamx, &DMA_InitStructure); //初始化DMA Stream
DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_TCIF5); //清除传输完成中断
//开启DMA错误和接收完成中断 +(使能接收完成中断是为了防止一次性接收过长数据)
DMA_ITConfig(DMA2_Stream5,DMA_IT_TE|DMA_IT_TC, ENABLE);
NVIC_Configuration(2,2); //NVIC 配置
DMA_Cmd (DMA2_Stream5,ENABLE); //使能DMA
}
/**********************************************************
* @brief 串口发送端DMA配置函数(从存储器->外设模式/8位数据宽度/存储器增量模式 )
* @param DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
* chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
* par:外设地址
* mar:存储器地址
* ndtr:数据传输量
***********************************************************/
void u2DMA_TX_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
{
DMA_InitTypeDef DMA_InitStructure;
if((u32)DMA_Streamx>(u32)DMA2) //得到当前stream是属于DMA2还是DMA1
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); //DMA2时钟使能
}else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); //DMA1时钟使能
}
DMA_DeInit(DMA_Streamx);
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){
} //等待DMA可配置
/*配置 DMA Stream*/
DMA_InitStructure.DMA_Channel = chx; //通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = par; //DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = mar; //DMA存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //存储器到外设模式
DMA_InitStructure.DMA_BufferSize = ndtr; //数据传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据长度:8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //正常模式,即满了就不在接收了,而不是循环存储
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //中等优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输
DMA_Init(DMA_Streamx, &DMA_InitStructure); //初始化DMA Stream
}
/************************************************************
* @brief DMA2_Stream5的所有中断,防止接收到的帧过长无法触发串口空闲中断
* @param
***********************************************************/
void DMA2_Stream5_IRQHandler(void)
{
if(DMA_GetFlagStatus(DMA2_Stream5,DMA_FLAG_TCIF5) != RESET)
{
DMA_Cmd(DMA2_Stream5, DISABLE); //关闭DMA,防止处理其间有数据
Rx_DMA_receive_size = RECEIVE_BUF - DMA_GetCurrDataCounter(DMA2_Stream5);
if(Rx_DMA_receive_size !=0)
{
// Receive_DataPack();/*处理接收数据函数*/;
}
DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//清除DMA2_Steam7传输完成标志
while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE){
} //确保DMA可以被设置
DMA_SetCurrDataCounter(DMA2_Stream5, RECEIVE_BUF);
DMA_Cmd(DMA2_Stream5, ENABLE); //打开DMA
}
}
usart.c文件中串口1初始化函数和串口中断函数
#define RECEIVE_BUF_SIZE 100 //定义最大接收字
extern u8 Rx_DMA_receive[]; //100bytes buffer
/************************************************************
* @brief 串口 1 初始化函数
* @param bound:波特率
***********************************************************/
void uart_init(u32 bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//开启GPIO与串口时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1
//串口1的GPIO配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9,GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
//USART1初始化设置
USART_InitStructure.USART_BaudRate = bound; //波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_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); //初始化串口1
#if USE_USART_DMA_RX
//Usart1 开启中断 + NVIC 配置
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //开启串口空闲中断
NVIC_Configuration(1,1); //中断优先级配置
USART_DMACmd(USART1,USART_DMAReq_Rx, ENABLE); //使能的DMA收
//DMA2,STEAM5,CH4,外设为串口1,存储器为SendBuff,方向为外设到存储器,长度为:RECEIVE_BUF_SIZE.
u2DMA_RX_Config(DMA2_Stream5,DMA_Channel_4,(u32)&USART1->DR,(u32)Rx_DMA_receive,100);
#else
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接收中断
NVIC_Configuration(3,3);
#endif
#if USE_USART_DMA_TX
USART_DMACmd(USART1,USART_DMAReq_Tx, ENABLE); //使能的DMA发
//DMA2,STEAM7,CH4,外设为串口1,存储器为Rxbuf,方向为存储器到外设,长度为:RECEIVE_BUF_SIZE.
u2DMA_TX_Config(DMA2_Stream7,DMA_Channel_4,(u32)&USART1->DR,(u32)Rxbuf,0);
#endif
USART_Cmd(USART1, ENABLE); //使能串口1,要放最后
}
u8 dum;
u8 Rx_DMA_receive[RECEIVE_BUF_SIZE]; //100bytes
u8 Rx_DMA_receive_size = 0;
/************************************************************
* @brief 串口 1 中断函数
* @param
***********************************************************/
void USART1_IRQHandler(void)
{
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
#if USE_USART_DMA_RX
/*空闲中断 */
if(USART_GetITStatus(USART1,USART_IT_IDLE) != RESET)
{
dum = USART1->SR;
dum = USART1->DR; //读取SR DR数据可清除“空闲中断”标志
Rx_DMA_receive_size = RECEIVE_BUF_SIZE - DMA_GetCurrDataCounter(DMA2_Stream5); //本帧数据长度=DMA准备的接收数据长度-DMA已接收数据长度
if( 0 != Rx_DMA_receive_size)
{
Receive_DataPack();/*DMA接收模式时处理接收数据函数*/
}
DMA_Cmd(DMA2_Stream5,DISABLE); //暂停DMA传输
// 清除DMA中断标志位
DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//清除DMA2_Steam7传输完成标志
while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE){
} //确保DMA可以被设置
DMA_SetCurrDataCounter(DMA2_Stream5,RECEIVE_BUF_SIZE); //设置DMA数据传输量
DMA_Cmd(DMA2_Stream5, ENABLE); //开启DMA传输
}
#endif
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
其中USE_USART_DMA_RX
与USE_USART_DMA_TX
两个宏定义放在.h文件作为决定是否开启串口通道DMA功能的开关。这种串口接收方式存在很大的隐患,如果发送端设备发送一帧数据并不是一次性发完的而是分多次发送(期间间隔几毫秒),就会导致收到的数据不完整,如何解决呢?看下面的方法。
好文链接:
文章浏览阅读3.3k次,点赞4次,收藏9次。首先,我们先来看一下效果_scratch母亲节作品
文章浏览阅读686次。package mainimport ( "awesomeProject1/src/github.com/hyperledger/fabric/core/chaincode/shim" "awesomeProject1/src/github.com/hyperledger/fabric/protos/peer" "bytes" "encoding/json" "..._开源投票系统 github
文章浏览阅读3.9k次。需要再Terminal里面输入tensorboard --logdir logs才能打开Tensorboard# tensorboard --logdir=logs --port=6007 是将端口号改为了6007#一般用到一下几个指令:# 一、打开图片显示图片# writer.add_images("input",imgs,step) 第一个是标签,第二个是图像,注意图像必须是numpy或者tentor类型的,而且必须是通道数在前面,比如RGB三通道的就要是3*w*h#如果通道数在后面,则._tensorboard代码
文章浏览阅读1.2k次。reverse的音标英 [rɪˈvɜːs]美 [rɪˈvɜːrs]reverse的用法v. 颠倒;彻底转变;使完全相反;撤销,废除(决定、法律等);使反转;使次序颠倒n. 相反的情况(或事物);后面;背面;反面;倒挡adj. 相反的;反面的;反向的;背面的;后面的第三人称单数: reverses 复数: reverses 现在分词: reversing 过去式: reversed 过去分词: re..._reverseorder
文章浏览阅读1w次。import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;public class Demo {public static void main(String[] args) throws ParseException {//获..._创建一个指定时间用什么语句
文章浏览阅读1.6k次。Sublime Text 2和3的对比相比于2,Sublime Text 3就秒启动一项,就压倒性地胜利了。因此在之后的叙述中都以Sublime Text 3为主角。并且3一直在不断的完善更新,具体的差异可参看Sublime Blog.简单的说:ST3支持在项目目录里面寻找变量 提供了对标签页更好地支持(更多的命令和快捷键) 加快了程序运行的速度 更新了API,使用Pytho..._folder_exclude_patterns
文章浏览阅读60次。go-mysqlA pure go library to handle MySQL network protocol and replication.ReplicationReplication package handles MySQL replication protocol like python-mysql-replication.You can use it as a MySQL sla..._replication.newbinlogsyncer
文章浏览阅读236次。C/C++头文件一览 C、传统 C++#include //设定插入点#include //字符处理#include //定义错误码#include //浮点数处理#include //文件输入/输出#include //参数化输入/输出#include
文章浏览阅读307次。在PHP中读取二进制文件2012-10-30[1715]technologyphp次阅读很多时候,数据并不是用文本的方式保存的,这就需要将二进制数据读取出来,还原成我们需要的格式。PHP在二进制处理方面也提供了强大的支持。任务下面以读取并分析一个PNG图像的文件头为例,讲解如何使用PHP读取和分析二进制文件。涉及函数PNG格式简介为了完成任务,下面简单介绍一下PNG文件格式。PNG是一种无损压缩的..._php 查找字符串中的二进制
文章浏览阅读1.4w次。kettle报错couldn't convert string [1970-01-01 00:00:00] to a date using format [yyyy/MM/dd HH:mm:ss.SSS]1.报错如下2019/01/08 12:04:18 - 替换NULL值.0 - ERROR (version 8.1.0.0-365, build 8.1.0.0-365 from 2018-..._kettle导出date报错
文章浏览阅读922次。串口通信(Serial Communication),是指外设和计算机间通过数据信号线、地线等按位进行传输数据的一种通信方式,属于串行通信方式。串口是一种接口标准,它规定了接口的电气标准,没有规定接口插件电缆以及使用的协议。(1)接口标准串口通信的接口标准有很多,有 RS-232C、RS-232、RS-422A、RS-485 等。常用的就是 RS-232 和 RS-485。RS-232 其实是 R..._多串口电脑有什么用途
文章浏览阅读2.5w次,点赞7次,收藏18次。转载请注明出处:http://blog.csdn.net/forevercbb/article/details/51037833 由于Google不再支持Eclipse之后,转向Google的亲儿子——AndroidStudio是必然的,但是AndroidStudio的配置以及使用都比Eclipse复杂,不熟悉的情况下经常会遇到一些莫名其妙的问题,导致应用无法正常编译。代码出现的bug可以根据错_run configuration app is not supported in the current project. cannot obtain