基于LL库STM32F103的串口DMA发送

        看了几篇CSDN上前辈写的文章受益匪浅,其中有LL库通过串口+DMA方式发送数据_jacklondonjia的博客-CSDN博客提到的“发送完成中断,应该使用uart的TC标志,而不应该是DMA的发送完成标志TCIF。前者,表示数据全部传输到了uart的输出线上(所有的Data+1个空闲byte时间),后者,仅仅表示数据全部传给了Uart的DR。DR到外设线上是有一段缓存区的。”还有一个严谨的STM32串口DMA发送&接收(1.5Mbps波特率)机制_一个严谨的串口dma_Acuity.的博客-CSDN博客提到的“注意发送状态标识,必须先置为“发送状态”,然后启动DMA 传输。如果步骤反过来,在传输数据量少时,DMA传输时间短,“DMA_IT_TC”中断可能比“发送状态标识置位”先执行,导致程序误判DMA一直处理发送状态(发送标识无法被清除)。”

        本文就细节进一步阐述,避免出现按照教程搬运库函数后仍然无法实现功能的情况出现。

首先在cubeMX中配置系统时钟使用外部晶振:然后配置调试接口,我用的是STLink:

配置USART1,开启全局中断:串口参数设置为115200-8-1:

配置DMA:

        注意DMA模式为normal,如果设置为连续模式则一旦开启DMA数据不断传输,如果想把DMA的数据拷贝到缓冲区时DMA传输是不会停止的,开启normal的好处是DMA每初始化一次开启一次,数据传输到串口时用户可以设置传输完成中断进行处理然后再开启DMA。

        生成初始化代码后我们在keil的串口初始化代码中添加:

        前三行分别为设置DMA的源地址也就是自定义的缓冲区地址、DMA的目标地址也就是串口的数据寄存器和每次发送的数据长度也就是缓冲区长度。后四行为关于串口完成中断的设置。

        设置是否有效果呢?我们调试看一下寄存器,对DMA1和USART1进行watch,本来想对LL_DMA_CHANNEL直接进行watch发现不行,那就只好在watch界面手动敲入在datasheet中查找到的地址——((DMA_Channel_TypeDef *)(0x40020044UL))。界面如下:

        执行调试断点达到自己加的七行代码前,可以看到经过cubeMX自动生成的USART和DMA代码后,寄存器的值变为:

其中USART中的SR定义为:

可以看到如果初始化成功,那么TXE和TC位都是1,其中TC位定义为:

        前两行明确说明了TXE被置1时TC位在串口传输完成时会硬件置1,并且在CR1寄存器中的TCIE为1时会发生中断,因此当不进中断时应该尤其检查SR和CR1寄存器。而CR1寄存器的值在初始化后为0x0000200C,根据手册:

可知UE、TE、RE位为1,其定义如下:

        也就是开启了USART的发送和接收功能(本文重点讲发送),而关键的TCIE位此时并未置1。BRR寄存器为USART波特率数据位等参数的设置,此处不展开。

        DMA中CCR寄存器的定义为:

     初始化后该寄存器的值为0x00000090,根据手册可知MINC和DIR的值为1,而这两位的定义为:

        因为内存的地址是我们自定义的缓冲区地址,如果不设置为自增长的话DMA传送的会一直是首个字节,因此MINC位为1。而DIR位说明了我们设置DMA传输方向是由内存向外设也就是由缓冲区向串口搬运数据。

        初始化正常后我们运行到自己添加的前三行代码后:

        可以看到CNDTR、CPAR和CMAR寄存器都发生了变化,根据手册可以知道这三个寄存器分别代表DMA发送长度、目标地址和源地址,如果地址设置不正确那这里的值也必然是错的。我们可以看到0x40013804与手册中写的USART的数据寄存器DR的地址是一致的,0x20000008与我们自定义的缓冲区地址也是一致的,说明设置正确。

        接着运行到LL_USART_ClearFlag_TC(USART1),可以看到SR寄存器发生了变化:

        根据前面SR寄存器的定义可知,TC位被成功清0,否则当CR1的TCIE被置1时会直接进入中断而不是等串口传输完才进。

        接着运行完LL_USART_EnableIT_TC(USART1),可以看到CR1寄存器发生了变化:

        根据CR1寄存器的定义可知,TCIE被置1,因此如果前面不清除标志位的话直接使能传输完成中断会直接进入中断,且这两行代码的顺序不能反。

        接着运行完LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_4),可以看到CCR寄存器值由0x00000090变为了0x00000091,即DMA1的发送通道被使能:

接着运行完我们手动添加的七行代码的最后一行即LL_USART_EnableDMAReq_TX(USART1):

        可以看到SR的TC位被置1,CR3寄存器值变为0x00000080,由手册可知:

        DMA开始传输。

        ISR寄存器值变为0x00007000,由手册可知:

        即HTIF半满标志、TCIF全满标志和GIF全局中断标志全置1,不过这里我们没有用到DMA的中断,而是串口中断,原因见开头引用的文章。

        CNDTR寄存器值变为了0,可知DMA发送区长度变为了0,即将缓冲区的数据全部发送到了串口。

        这七条代码执行完成后按前面的分析肯定会进中断,调试接着进行一步验证一下是不是呢:

        果然进了中断,在中断中除了清除传输完成标志位和关闭通道外,最关键的就是这个tx_flag全局变量,这个变量我在main中是这么用的:

        什么意思呢,就是让DMA进中断的时候不要进行数据的拷贝,也就是传输完成的时候缓冲区中的数据不要有变化,否则会引起数据错乱,这点很关键,在开头引用的一篇文章中指出。而while中的函数MX_USART1_UART_ReInit()是我另外写的函数,内容就是那七行代码,因为初始化的时候配置了DMA是normal模式,因此每次传输完重新配置一下寄存器。

        最后来看看效果:

        密密麻麻一胡片,总之就是没丢的数据也没乱,都是自增1的。还有别的需求在这个的基础上改就好了。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要使用串行方式驱动AD7606,您需要使用STM32F103的USART或SPI接口。以下是一些基本步骤: 1. 配置USART或SPI接口的GPIO引脚,以使其与AD7606连接。 2. 配置USART或SPI接口的参数,如波特率、数据位、停止位等。 3. 初始化USART或SPI接口,并启用相关中断(如果需要)。 4. 在主循环中,发送命令给AD7606,以读取转换数据。 5. 从USART或SPI接口接收AD7606的响应,并解析转换数据。 下面是一个使用STM32F103LL驱动AD7606的示例代码,其中使用SPI接口: ``` #include "stm32f1xx_ll_spi.h" #include "stm32f1xx_ll_gpio.h" #define SPI_CS_PIN LL_GPIO_PIN_4 #define SPI_CS_PORT GPIOA #define SPI_CHANNEL LL_SPI_CHANNEL_2 #define SPI_INSTANCE SPI2 void ad7606_init(void) { // Configure SPI GPIO pins LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = SPI_CS_PIN; GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT; GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH; LL_GPIO_Init(SPI_CS_PORT, &GPIO_InitStruct); // Configure SPI interface LL_SPI_InitTypeDef SPI_InitStruct = {0}; SPI_InitStruct.Mode = LL_SPI_MODE_MASTER; SPI_InitStruct.Direction = LL_SPI_DIRECTION_2LINES; SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_16BIT; SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_HIGH; SPI_InitStruct.ClockPhase = LL_SPI_PHASE_2EDGE; SPI_InitStruct.NSS = LL_SPI_NSS_SOFT; SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV2; SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST; LL_SPI_Init(SPI_INSTANCE, &SPI_InitStruct); // Enable SPI interface LL_SPI_Enable(SPI_INSTANCE); } void ad7606_read(uint16_t* data) { // Send command to start AD conversion uint16_t cmd = 0x8400; LL_GPIO_ResetOutputPin(SPI_CS_PORT, SPI_CS_PIN); LL_SPI_TransmitData16(SPI_INSTANCE, cmd); // Wait for conversion to complete while (LL_SPI_IsActiveFlag_BSY(SPI_INSTANCE)); // Read conversion data for (int i = 0; i < 8; i++) { LL_SPI_TransmitData16(SPI_INSTANCE, 0xFFFF); while (!LL_SPI_IsActiveFlag_RXNE(SPI_INSTANCE)); data[i] = LL_SPI_ReceiveData16(SPI_INSTANCE); } LL_GPIO_SetOutputPin(SPI_CS_PORT, SPI_CS_PIN); } ``` 请注意,这只是一个简单的示例代码,您需要根据自己的需求进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值