OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 13994|回复: 8

USART3用DMA 中断方式发送,为什么发送完一帧数据后就死机?

[复制链接]

14

主题

75

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
248
金钱
248
注册时间
2011-10-16
在线时间
31 小时
发表于 2012-2-27 22:34:10 | 显示全部楼层 |阅读模式

我这个调试版本的程序意图是这样的:串口3 接收方式采用普通的中断来接收串口调试助手发送来的数据,接收到有用的数据后,就采用DMA方式回送一帧数据到串口调试助手,DMA发送完后要产生一个TC的中断,因为我要在这个中断的服务程序里设置一下RS485芯片的数据传输方向,必须要设置这个方向,(要不然会收不到数据,只能发送数据,芯片功能是这样)。 发送数据的前后时间段 ,我一直要控制一个小直流马达转动,还要一直进行AD转换来获取这个直流马达的转动位置,但是现在调试的时候,串口通过DMA发送一帧数据后,就死机了,不知道怎么回事,好像卡在DMA_Cmd(DMA1_Channel2, ENABLE);这句这里了,因为我在这条语句后面加了一些语句来验证,发现这条语句后面的程序都没有执行?

问题的现象就是:只能响应一次,响应完后就死机。

程序是照原子哥寄过来的光盘里的改的,原子哥的是DMA polling  发送,我的只是改成DMA interrupt 发送就不行了  大家帮忙看下吧,

相关程序如下:

char SendBuff[]="hello";

void Init_usart3(void);

void Init_DMA(void);

int main(void)
{   

 SystemInit();
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3 , ENABLE);
 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

 Init_usart3();

 Init_DMA();

 while(1)

{

    if(flag) //串口3接收中断里SET该变量 ,即收到一个上位机发来的数据,就启动一次DMA传输,
 
   {
           flag = 0;
            USART_DMACmd(USART3, USART_DMAReq_Tx, ENABLE);

            DMA_Cmd(DMA1_Channel2, ENABLE);

 

     }

}

 

void USART3_IRQHandler(void) //串口3中断服务程序
{
 if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)  //接收中断
 {
        temp =USART_ReceiveData(USART3);
         flag=1;
 }
}

 

void DMA1_Channel2_IRQHandler(void) //DMA1通道2传输完成中断
{
  if(DMA_GetITStatus(DMA1_IT_TC2) != RESET)  //

  {
    DMA_ClearITPendingBit(DMA1_IT_TC2);
    USART_DMACmd(USART3, USART_DMAReq_Tx, DISABLE) ;
  }

 

void DMA_Configuration(void)
 {
 /* USART1 TX DMA4 Channel Config */
 DMA_InitTypeDef DMA_InitStructure;

 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //使能时钟
// delay_us(2);
 DMA_DeInit(DMA1_Channel2);   //将DMA的通道1寄存器重设为缺省值
 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART3->DR;  //DMA外设ADC基地址
 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;  //DMA内存基地址
 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //外设作为数据传输的目的地
 DMA_InitStructure.DMA_BufferSize =7;// sizeof(SendBuff);  //DMA通道的DMA缓存的大小
 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通道 x拥有中优先级
 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输
 DMA_Init(DMA1_Channel2, &DMA_InitStructure);  //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
 
 DMA_ITConfig(DMA1_Channel2,DMA_IT_TC,ENABLE);//添加中断配置
 
 }
void NVIC_DMA_Configuration(void)
{
 NVIC_InitTypeDef NVIC_InitStructure;
 
 NVIC_InitStructure.NVIC_IRQChannel =DMA1_Channel2_IRQn;  //USART1中断
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   //IRQ通道使能
 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器USART1
}

void Init_DMA(void)
{
  DMA_Configuration();
  NVIC_DMA_Configuration();
}

 


void GPIO_Configuration(void)
{
 GPIO_InitTypeDef GPIO_InitStructure;
 GPIO_StructInit(&GPIO_InitStructure);
 GPIO_InitStructure.GPIO_Pin= GPIO_Pin_10;
 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP;
 GPIO_Init(GPIOB, &GPIO_InitStructure);

 GPIO_InitStructure.GPIO_Pin= GPIO_Pin_11;
 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN_FLOATING;
 GPIO_Init(GPIOB,&GPIO_InitStructure);

 GPIO_InitStructure.GPIO_Pin=RS485_ENABLE_PIN;
 GPIO_InitStructure.GPIO_Mode= GPIO_Mode_Out_PP ;
 GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
 GPIO_Init(RS485_ENABLE_PORT, &GPIO_InitStructure);

}

void NVIC_Configuration(void)
{
 NVIC_InitTypeDef NVIC_InitStructure;

 /* Configure the NVIC Preemption Priority Bits */ 
 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //设置优先级分组:先占优先级0位,从优先级4位
 
 //设置向量表的位置和偏移
 #ifdef  VECT_TAB_RAM     
  /* Set the Vector Table base location at 0x20000000 */
  NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);   //向量表位于RAM
 #else  /* VECT_TAB_FLASH  */
  /* Set the Vector Table base location at 0x08000000 */
  NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);   //向量表位于FLASH
 #endif

 /* Enable the USARTy Interrupt */
 NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;  //USART1中断
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   //IRQ通道使能
 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器USART1

}
void USART_Configuration(void)
{
 USART_InitTypeDef  USART_InitStructure;
 USART_StructInit(&USART_InitStructure);
 USART_InitStructure.USART_BaudRate= 9600;
 USART_InitStructure.USART_WordLength =USART_WordLength_8b;
 USART_InitStructure.USART_StopBits= USART_StopBits_1;
 USART_InitStructure.USART_Parity =USART_Parity_No;
 USART_InitStructure.USART_Mode= USART_Mode_Rx|USART_Mode_Tx;
 USART_InitStructure.USART_HardwareFlowControl =
 USART_HardwareFlowControl_None;

 USART_Init(USART3, &USART_InitStructure);

 USART_ITConfig(USART3,USART_IT_RXNE, ENABLE);

 USART_Cmd(USART3, ENABLE);
 USART_DMACmd(USART3, USART_DMAReq_Tx, ENABLE);
 
}


void Init_usart3(void)
{
 NVIC_Configuration();
 GPIO_Configuration();
 USART_Configuration();
}

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2012-2-27 23:48:23 | 显示全部楼层
你的DMA 中断服务函数在哪里?
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

14

主题

75

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
248
金钱
248
注册时间
2011-10-16
在线时间
31 小时
 楼主| 发表于 2012-2-28 23:03:29 | 显示全部楼层
回复【2楼】正点原子:
你的DMA 中断服务函数在哪里?
---------------------------------
DMA中断服务函数有,我发帖的时候 贴错了,
但是这个问题我已经解决, 只能发送一次的原因是,发送完后,我没有重新设置 下次传输的传输字节数,查了Reference manual ,这个寄存器在每次发送完后就归零了,下次发送的时候要用发送的字节数来填充他,我就是因为没有填充他所以发生错误了 !!!

但是现在有个新问题,用USART3 的 DMA方式连续发送时, DMA的TC中断产生后,串口却还没有将该帧数据全部传送出去,
因为我的USART3外部接了个RS485的芯片,该芯片的发送接收每次工作的时候需要设置,
我在DMA TC中断服务程序里就使能RS485芯片的接收功能,所以每次有2哥数据传不出去, 
妈的,不知道有没有什么好的解决办法,让我既能发送完数据,又能及时打开RS485的接收功能,
我在DMA中断里 延时2MS才能发送出去,但是我这个项目实时性要求很高,怕这里留下隐患。
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2012-2-28 23:20:47 | 显示全部楼层
波特率设置高一点吧.
应该不会有这种问题吧,TC中断,就表示发送缓冲区空了吧?
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

14

主题

75

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
248
金钱
248
注册时间
2011-10-16
在线时间
31 小时
 楼主| 发表于 2012-3-1 21:40:04 | 显示全部楼层
回复【4楼】正点原子:
波特率设置高一点吧.
应该不会有这种问题吧,TC中断,就表示发送缓冲区空了吧?
---------------------------------
TC中断 是DMA传输缓冲完成了 不是串口发送完成了 ,并且波特率不能改,要符合一个国际标准,那不然就和别的厂商的通信不了了,郁闷啊
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2012-3-1 22:18:17 | 显示全部楼层
回复【5楼】okyihu:
---------------------------------
那你要检测串口是否发送完成的标志.再去做下一步处理.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

3

主题

20

帖子

0

精华

初级会员

Rank: 2

积分
52
金钱
52
注册时间
2014-11-1
在线时间
0 小时
发表于 2014-11-6 04:49:55 | 显示全部楼层
最近看到好多人问这个问题:DMA在配置为Normal模式时只能进入一次中断,随后就擦擦了。其实你要看英文资料你就会发现"DMA传输通道配置为Normal模式时也就是单次传输,这时DMA通道是被锁定的,就是说执行完一次读写之后不管你CPU发多少request(例如:USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);),DMA都不会响应,如果重新启动DMA进行数据传输,那么首先要先将DMA关闭"关闭之后怎么操作呢?这才是重点:
其实加载一个新的数据ST的库里面有一个专门的函数,好多网友说的其实也是对的,就是没有把具体的操作方式晒出来,说白了就是修改要传送数据的数量,软件监控就会发现,DMA是属于减计数,发送读取完成之后计数器的值会自动减为0,所以你要发送(以发送为例子)新的数据,就必须重新配置其数据的数目或者长度。具体的如下:
第一步:DMA_Cmd(DMA1_ChannelX,DISABLE);//关闭DMA
第二步:DMA_SetCurrDataCounter(DMA1_Channel4,SENDBUFFER_SIZE);//这是关键,这个函数就是ST库里面用来重新设置当前数据计数器值的。
第三步:DMA_Cmd(DMA1_Channel4,ENABLE);//重新启动DMA
               USART_DMACmd(USART1, USART_DMAReq_Xx, ENABLE);//向DMA发送request请求。
第二步的函数原型如下:
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber)
{
  /* Check the parameters */
  assert_param(IS_DMA_ALL_PERIPH(DMAy_Channelx));
  /* Write to DMAy Channelx CNDTR */
  DMAy_Channelx->CNDTR = DataNumber;  
}
就这么简单。下面附上代码大家学习。
实验代码的原理是:1;DMA 循环扫描ADC1通道1(C1)上的电压信号。2:将12位的AD转换原始数据同过DMA发到串口发送数据寄存器,通过串口发送给PC机,为什么不用扫描模式呢?像温度这些也没必要那么快的发,消耗资源,还有就是要给上位机给够充裕的数据处理时间,我的数据比较海,每秒钟发送一次也就差不多了。
DMA本身比较高效,但是个人感觉不是特别的舒服,受控程度不高,尤其是串口在接受数据时候,那就相当麻烦了各种异常标志要去清理。说实话ST没有一个全面的库函数说明,目前比较少起码,我和德国日本的高科技产品打交道比较多,之前做视觉,像德国小工厂出的视觉系统,介绍资料2000多页,里面对硬件和每一个函数都有非常详细的解释和讲义,ST对其函数库的说明是一塌糊涂,再加上本身是他们工程师写的库,库里面嵌套比较多,比较混乱,就上面的那个函数软体,要是在PDF里面有详细的解释说明,我相信可能不会有这么多人问这个问题。国际惯例,先贴上调试截图:


唉呀妈呀大半夜的不小心差点把“苍老师的”图传上来了,大家见谅。

DriveWheel_ADC_DMA.rar

3.37 MB, 下载次数: 549

回复 支持 反对

使用道具 举报

22

主题

83

帖子

0

精华

初级会员

Rank: 2

积分
195
金钱
195
注册时间
2012-6-14
在线时间
0 小时
发表于 2014-11-12 17:43:49 | 显示全部楼层
回复【7楼】drivewheel:
---------------------------------
你好,我也遇到过同样问题,串口接收完成PC机下发的数据后,下位机通过数据后,然后通过DMA向PC机发送要得到的数据!!先按照楼主思维看看我的code!
越是自由的环境中越要自律
回复 支持 反对

使用道具 举报

3

主题

20

帖子

0

精华

初级会员

Rank: 2

积分
52
金钱
52
注册时间
2014-11-1
在线时间
0 小时
发表于 2014-11-12 20:52:36 | 显示全部楼层
恩恩,我那个就是DMA采集ADC1的信号,通过DMA发给串口,看看上面我提到的那个函数!!!!!!3布搞定,先停止再赋值,然后再启动DMA
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-29 06:14

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表