stm32 DMA

硬件平台:STM32F103ZET6; 

开发环境:KEIL 4;

先说说应用通讯模式,串口终端的工作方式和迪文屏差不多,终端被动接受MCU发的指令,终端会偶尔主动发送一些数据给MCU(像迪文屏的触摸信息上传)。

串口DMA发送:

发送数据的流程:

前台程序中有数据要发送,则需要做如下几件事

1.      在数据发送缓冲区内放好要发送的数据,说明:此数据缓冲区的首地址必须要在DMA初始化的时候写入到DMA配置中去。

2.      将数据缓冲区内要发送的数据字节数赋值给发送DMA通道,(串口发送DMA和串口接收DAM不是同一个DMA通道)

3.      开启DMA,一旦开启,则DMA开始发送数据,说明一下:在KEIL调试好的时候,DMA和调试是不同步的,即不管Keil 是什么状态,DMA总是发送数据。

4.      等待发送完成标志位,即下面的终端服务函数中的第3点设置的标志位。或者根据自己的实际情况来定,是否要一直等待这个标志位,也可以通过状态机的方式来循环查询也可以。或者其他方式。

判断数据发送完成:

启动DMA并发送完后,产生DMA发送完成中断,在中断函数中做如下几件事:

1. 清DMA发送完成中断标志位

2. 关闭串口发送DMA通道

3. 给前台程序设置一个软件标志位,说明数据已经发送完毕

串口DMA接收:

接收数据的流程:

串口接收DMA在初始化的时候就处于开启状态,一直等待数据的到来,在软件上无需做任何事情,只要在初始化配置的时候设置好配置就可以了。

判断数据数据接收完成:

       这里判断接收完成是通过串口空闲中断的方式实现,即当串口数据流停止后,就会产生IDLE中断。这个中断里面做如下几件事:

1.      关闭串口接收DMA通道,2点原因:1.防止后面又有数据接收到,产生干扰。2.便于DMA的重新配置赋值,下面第4点。

2.      清除DMA 所有标志位

3.      从DMA寄存器中获取接收到的数据字节数

4.      重新设置DMA下次要接收的数据字节数,注意,这里是给DMA寄存器重新设置接收的计数值,这个数量只能大于或者等于可能接收的字节数,否则当DMA接收计数器递减到0的时候,又会重载这个计数值,重新循环递减计数,所以接收缓冲区的数据则会被覆盖丢失。

5.  开启DMA通道,等待下一次的数据接收,注意,对DMA的相关寄存器配置写入,如第4条的写入计数值,必须要在关闭DMA的条件进行,否则操作无效。

说明一下,STM32的IDLE的中断在串口无数据接收的情况下,是不会一直产生的,产生的条件是这样的,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一断接收的数据断流,没有接收到数据,即产生IDLE中断。

 
  1. USART 和 DMA 硬件初始化配置

  2.  
  3. /*--- LumModule Usart Config ---------------------------------------*/

  4.  
  5. #define LUMMOD_UART USART3

  6.  
  7. #define LUMMOD_UART_GPIO GPIOC

  8.  
  9. #define LUMMOD_UART_CLK RCC_APB1Periph_USART3

  10.  
  11. #define LUMMOD_UART_GPIO_CLK RCC_APB2Periph_GPIOC

  12.  
  13. #define LUMMOD_UART_RxPin GPIO_Pin_11

  14.  
  15. #define LUMMOD_UART_TxPin GPIO_Pin_10

  16.  
  17. #define LUMMOD_UART_IRQn USART3_IRQn

  18.  
  19. #define LUMMOD_UART_DR_Base (USART3_BASE + 0x4) //0x40013804

  20.  
  21.  
  22. #define LUMMOD_UART_Tx_DMA_Channel DMA1_Channel2

  23.  
  24. #define LUMMOD_UART_Tx_DMA_FLAG DMA1_FLAG_GL2//DMA1_FLAG_TC2 | DMA1_FLAG_TE2

  25.  
  26. #define LUMMOD_UART_Tx_DMA_IRQ DMA1_Channel2_IRQn

  27.  
  28. #define LUMMOD_UART_Rx_DMA_Channel DMA1_Channel3

  29.  
  30. #define LUMMOD_UART_Rx_DMA_FLAG DMA1_FLAG_GL3//DMA1_FLAG_TC3 | DMA1_FLAG_TE3

  31.  
  32. #define LUMMOD_UART_Rx_DMA_IRQ DMA1_Channel3_IRQn

  33.  
  34.  
  35. void Uart_Init(void)

  36.  
  37. {

  38.  
  39. NVIC_InitTypeDef NVIC_InitStructure;

  40.  
  41. GPIO_InitTypeDef GPIO_InitStructure;

  42.  
  43. USART_InitTypeDef USART_InitStructure;

  44.  
  45. /* System Clocks Configuration */

  46.  
  47. //= System Clocks Configuration ==============================//

  48.  
  49.  
  50. /* Enable GPIO clock */

  51.  
  52. RCC_APB2PeriphClockCmd(LUMMOD_UART_GPIO_CLK , ENABLE ); // 开启串口所在IO端口的时钟

  53.  
  54. /* Enable USART Clock */

  55.  
  56. RCC_APB1PeriphClockCmd(LUMMOD_UART_CLK, ENABLE); // 开始串口时钟

  57.  
  58.  
  59. //=NVIC_Configuration======================================//

  60.  
  61.  
  62. /* Configure the NVIC Preemption Priority Bits */

  63.  
  64. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);

  65.  
  66. /* Enable the DMA Interrupt */

  67.  
  68. NVIC_InitStructure.NVIC_IRQChannel = LUMMOD_UART_Tx_DMA_IRQ; // 发送DMA通道的中断配置

  69.  
  70. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 优先级设置

  71.  
  72. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;

  73.  
  74. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  75.  
  76. NVIC_Init(&NVIC_InitStructure);

  77.  
  78.  
  79. /* Enable the USART Interrupt */

  80.  
  81. NVIC_InitStructure.NVIC_IRQChannel = LUMMOD_UART_IRQn; // 串口中断配置

  82.  
  83. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;

  84.  
  85. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

  86.  
  87. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  88.  
  89. NVIC_Init(&NVIC_InitStructure);

  90.  
  91.  
  92. //=GPIO_Configuration====================================================//

  93.  
  94. GPIO_PinRemapConfig(GPIO_PartialRemap_USART3, ENABLE); // 我这里没有用默认IO口,所以进行了重新映射,这个可以根据自己的硬件情况配置选择

  95.  
  96. /* Configure USART3 Rx as input floating */

  97.  
  98. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 串口接收IO口的设置

  99.  
  100. GPIO_InitStructure.GPIO_Pin = LUMMOD_UART_RxPin;

  101.  
  102. GPIO_Init(LUMMOD_UART_GPIO, &GPIO_InitStructure);

  103.  
  104. /* Configure USART3 Tx as alternate function push-pull */

  105.  
  106. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 串口发送IO口的设置

  107.  
  108. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 这里设置成复用形式的推挽输出

  109.  
  110. GPIO_InitStructure.GPIO_Pin = LUMMOD_UART_TxPin;

  111.  
  112. GPIO_Init(LUMMOD_UART_GPIO, &GPIO_InitStructure);

  113.  
  114. DMA_Uart_Init(); // 串口 DMA 配置

  115.  
  116.  
  117. /* USART Format configuration ------------------------------------------------------*/

  118.  
  119. USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 串口格式配置

  120.  
  121. USART_InitStructure.USART_StopBits = USART_StopBits_1;

  122.  
  123. USART_InitStructure.USART_Parity = USART_Parity_No;

  124.  
  125. USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

  126.  
  127. USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

  128.  
  129.  
  130.  
  131. /* Configure USART3 */

  132.  
  133. USART_InitStructure.USART_BaudRate = 115200; // 波特率设置

  134.  
  135. USART_Init(LUMMOD_UART, &USART_InitStructure);

  136.  
  137. /* Enable USART3 Receive and Transmit interrupts */

  138.  
  139. USART_ITConfig(LUMMOD_UART, USART_IT_IDLE, ENABLE); // 开启 串口空闲IDEL 中断

  140.  
  141.  
  142. /* Enable the USART3 */

  143.  
  144. USART_Cmd(LUMMOD_UART, ENABLE); // 开启串口

  145.  
  146. /* Enable USARTy DMA TX request */

  147.  
  148. USART_DMACmd(LUMMOD_UART, USART_DMAReq_Tx, ENABLE); // 开启串口DMA发送

  149.  
  150. USART_DMACmd(LUMMOD_UART, USART_DMAReq_Rx, ENABLE); // 开启串口DMA接收

  151.  
  152. }

  153.  
  154.  
  155. void DMA_Uart_Init(void)

  156.  
  157. {

  158.  
  159. DMA_InitTypeDef DMA_InitStructure;

  160.  
  161.  
  162.  
  163. /* DMA clock enable */

  164.  
  165. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 开启DMA1时钟

  166.  
  167. //=DMA_Configuration===============================================//

  168.  
  169. /*--- LUMMOD_UART_Tx_DMA_Channel DMA Config ---*/

  170.  
  171.  
  172.  
  173. DMA_Cmd(LUMMOD_UART_Tx_DMA_Channel, DISABLE); // 关DMA通道

  174.  
  175. DMA_DeInit(LUMMOD_UART_Tx_DMA_Channel); // 恢复缺省值

  176.  
  177. DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&LUMMOD_UART->DR);// 设置串口发送数据寄存器

  178.  
  179. DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)LumMod_Tx_Buf; // 设置发送缓冲区首地址

  180.  
  181. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 设置外设位目标,内存缓冲区 ->外设寄存器

  182.  
  183. DMA_InitStructure.DMA_BufferSize = LUMMOD_TX_BSIZE; // 需要发送的字节数,这里其实可以设置为0,因为在实际要发送的时候,会重新设置次值

  184.  
  185. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不做增加调整,调整不调整是DMA自动实现的

  186.  
  187. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存缓冲区地址增加调整

  188.  
  189. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据宽度8位,1个字节

  190.  
  191. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据宽度8位,1个字节

  192.  
  193. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 单次传输模式

  194.  
  195. DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 优先级设置

  196.  
  197. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 关闭内存到内存的DMA模式

  198.  
  199. DMA_Init(LUMMOD_UART_Tx_DMA_Channel, &DMA_InitStructure); // 写入配置

  200.  
  201. DMA_ClearFlag(LUMMOD_UART_Tx_DMA_FLAG); // 清除DMA所有标志

  202.  
  203. DMA_Cmd(LUMMOD_UART_Tx_DMA_Channel, DISABLE); // 关闭DMA

  204.  
  205. DMA_ITConfig(LUMMOD_UART_Tx_DMA_Channel, DMA_IT_TC, ENABLE); // 开启发送DMA通道中断

  206.  
  207. /*--- LUMMOD_UART_Rx_DMA_Channel DMA Config ---*/

  208.  
  209.  
  210.  
  211. DMA_Cmd(LUMMOD_UART_Rx_DMA_Channel, DISABLE); // 关DMA通道

  212.  
  213. DMA_DeInit(LUMMOD_UART_Rx_DMA_Channel); // 恢复缺省值

  214.  
  215. DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&LUMMOD_UART->DR);// 设置串口接收数据寄存器

  216.  
  217. DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)LumMod_Rx_Buf; // 设置接收缓冲区首地址

  218.  
  219. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 设置外设为数据源,外设寄存器 -> 内存缓冲区

  220.  
  221. DMA_InitStructure.DMA_BufferSize = LUMMOD_RX_BSIZE; // 需要最大可能接收到的字节数

  222.  
  223. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不做增加调整,调整不调整是DMA自动实现的

  224.  
  225. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存缓冲区地址增加调整

  226.  
  227. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据宽度8位,1个字节

  228.  
  229. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存数据宽度8位,1个字节

  230.  
  231. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 单次传输模式

  232.  
  233. DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; // 优先级设置

  234.  
  235. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 关闭内存到内存的DMA模式

  236.  
  237. DMA_Init(LUMMOD_UART_Rx_DMA_Channel, &DMA_InitStructure); // 写入配置

  238.  
  239. DMA_ClearFlag(LUMMOD_UART_Rx_DMA_FLAG); // 清除DMA所有标志

  240.  
  241. DMA_Cmd(LUMMOD_UART_Rx_DMA_Channel, ENABLE); // 开启接收DMA通道,等待接收数据

  242.  
  243.  
  244. }

  245.  
  246. void BSP_Init(void)

  247.  
  248. {

  249.  
  250. Uart_Init();

  251.  
  252. }

  253.  
  254. //============================================================//

  255.  
  256. DMA 发送应用源码

  257.  
  258. void DMA1_Channel2_IRQHandler(void)

  259.  
  260. {

  261.  
  262. if(DMA_GetITStatus(DMA1_FLAG_TC2))

  263.  
  264. {

  265.  
  266. LumMod_Uart_DAM_Tx_Over();

  267.  
  268. }

  269.  
  270. }

  271.  
  272. void LumMod_Uart_DAM_Tx_Over(void)

  273.  
  274. {

  275.  
  276. DMA_ClearFlag(LUMMOD_UART_Tx_DMA_FLAG); // 清除标志

  277.  
  278. DMA_Cmd(LUMMOD_UART_Tx_DMA_Channel, DISABLE); // 关闭DMA通道

  279.  
  280. OSMboxPost(mbLumModule_Tx, (void*)1); // 设置标志位,这里我用的是UCOSII ,可以根据自己的需求进行修改

  281.  
  282. }

  283.  
  284.  
  285.  
  286. void LumMod_Cmd_WriteParam( uint8 sample_num, uint8 *psz_param )

  287.  
  288. {

  289.  
  290. uint8 err;

  291.  
  292. uint8 LumMod_Tx_Index ;

  293.  
  294.  
  295.  
  296. LumMod_Tx_Index = 0;

  297.  
  298. LumMod_Tx_Buf[LumMod_Tx_Index++] = 1;

  299.  
  300. LumMod_Tx_Buf[LumMod_Tx_Index++] = 2;

  301.  
  302. LumMod_Tx_Buf[LumMod_Tx_Index++] = 3;

  303.  
  304. LumMod_Tx_Buf[LumMod_Tx_Index++] = 4;

  305.  
  306. LumMod_Tx_Buf[LumMod_Tx_Index++] = 5;

  307.  
  308. LumMod_Tx_Buf[LumMod_Tx_Index++] = 6;

  309.  
  310. LumMod_Tx_Buf[LumMod_Tx_Index++] = 7;

  311.  
  312. LumMod_Tx_Buf[LumMod_Tx_Index++] = 8;

  313.  
  314.  
  315. LumMod_Uart_Start_DMA_Tx( LumMod_Tx_Index );

  316.  
  317. OSMboxPend(mbLumModule_Tx, 0, &err);

  318.  
  319. }

  320.  
  321.  
  322. void LumMod_Uart_Start_DMA_Tx(uint16_t size)

  323. {

  324.  
  325. LUMMOD_UART_Tx_DMA_Channel->CNDTR = (uint16_t)size; // 设置要发送的字节数目

  326.  
  327. DMA_Cmd(LUMMOD_UART_Tx_DMA_Channel, ENABLE); //开始DMA发送

  328.  
  329. }

  330.  
  331. //============================================================//

  332.  
  333. DMA 接收应用源码

  334.  
  335. void USART3_IRQHandler(void)

  336.  
  337. {

  338.  
  339. if(USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) // 空闲中断

  340.  
  341. {

  342.  
  343. LumMod_Uart_DMA_Rx_Data();

  344.  
  345. USART_ReceiveData( USART3 ); // Clear IDLE interrupt flag bit

  346.  
  347. }

  348.  
  349. }

  350.  
  351. void LumMod_Uart_DMA_Rx_Data(void)

  352.  
  353. {

  354.  
  355. DMA_Cmd(LUMMOD_UART_Rx_DMA_Channel, DISABLE); // 关闭DMA ,防止干扰

  356.  
  357. DMA_ClearFlag( LUMMOD_UART_Rx_DMA_FLAG ); // 清DMA标志位

  358.  
  359. LumMod_Rx_Data.index = LUMMOD_RX_BSIZE - DMA_GetCurrDataCounter(LUMMOD_UART_Rx_DMA_Channel); //获得接收到的字节数

  360.  
  361. LUMMOD_UART_Rx_DMA_Channel->CNDTR = LUMMOD_RX_BSIZE; // 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目

  362.  
  363. DMA_Cmd(LUMMOD_UART_Rx_DMA_Channel, ENABLE); /* DMA 开启,等待数据。注意,如果中断发送数据帧的速率很快,MCU来不及处理此次接收到的数据,中断又发来数据的话,这里不能开启,否则数据会被覆盖。有2种方式解决。

  364.  
  365. }

    1. 在重新开启接收DMA通道之前,将LumMod_Rx_Buf缓冲区里面的数据复制到另外一个数组中,然后再开启DMA,然后马上处理复制出来的数据。

    2. 建立双缓冲,在LumMod_Uart_DMA_Rx_Data函数中,重新配置DMA_MemoryBaseAddr 的缓冲区地址,那么下次接收到的数据就会保存到新的缓冲区中,不至于被覆盖。*/

    OSMboxPost(mbLumModule_Rx,  LumMod_Rx_Buf); // 发送接收到新数据标志,供前台程序查询

猜你喜欢

转载自blog.csdn.net/guiwukejiBGG/article/details/84196939