完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
扫一扫,分享给好友
|
|
相关推荐
1个回答
|
|
串口通信介绍
UART串口通信,使用三线即可进行最基本的数据收发传送: 在数据线上的 Timing 遵循标准的串口通信协议,由起始位,数据,校验位,停止位组成,数据传输 LSB -> MSB: 板载 USART 资源介绍 当然,由于电平不一样,使用 RS232 标准进行串口数据传送,需要增加 MAX3232 进行电平转换,再接PC: 单板上的 T1IN 和 R1OUT 接到了 STM32 芯片的 USART1 的 TXD/RXD 管脚,故单板上使用了 USART1 来作为 RS232 和 PC 机进行数据传送: USART 初始化配置 既然确定了使用了芯片上的 USART1,要正确使用该功能,需要进行如下配置: 1. 开启 USART1 时钟源,开启 GPIOA 组时钟源(因为使用 UASRT1之前,对 PA9/PA10 需要对管脚进行配置) 2. 复位 USART1 模块(使用之前,应当首先对该模块进行复位) 3. 配置管脚功能的 Remap 4. 配置 PA9/PA10 管脚,PA9 为 TXD,配置成为推挽输出,PA10 配置成为浮空输入 5. 配置串口的波特率(9600),数据长度(8bit),停止位(1bit),校验位(无),以及是否开启流控(无) 注意:波特率的配置,遵循一组计算公式(公式复杂),详见 STM32 的芯片手册 6. 配置 NVIC 控制器 7. 配置 USART1 的中断类型(即,工作过程中,会来些什么中断) 使能 USART1 功能 void SK_UartInit(void) { GPIO_InitTypeDef stGpioInit; USART_InitTypeDef stUsartInit; NVIC_InitTypeDef stNVIC; /* Step1: Open USART1 And GPIOA Clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); USART_DeInit(USART1); /* Step2: Config The Pin Remap */ // According to the hardware diagram, just use UART1 On PA9 And PA10 in default GPIO_PinRemapConfig(GPIO_Remap_USART1, DISABLE); /* Step3: Config RXD/TXD Mode */ // PA9 As TXD stGpioInit.GPIO_Pin = GPIO_Pin_9; stGpioInit.GPIO_Speed = GPIO_Speed_50MHz; stGpioInit.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &stGpioInit); // PA10 As RXD stGpioInit.GPIO_Pin = GPIO_Pin_10; stGpioInit.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &stGpioInit); /* Step4: Reset USART1 before use it */ USART_DeInit(USART1); /* Step5: Configure the UART Basic Settings */ stUsartInit.USART_BaudRate = 9600; stUsartInit.USART_WordLength = USART_WordLength_8b; stUsartInit.USART_StopBits = USART_StopBits_1; stUsartInit.USART_Parity = USART_Parity_No; stUsartInit.USART_HardwareFlowControl = USART_HardwareFlowControl_None; stUsartInit.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &stUsartInit); // Configure RX Interrupt stNVIC.NVIC_IRQChannel = USART1_IRQn; stNVIC.NVIC_IRQChannelPreemptionPriority= 3 ; stNVIC.NVIC_IRQChannelSubPriority = 3; stNVIC.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&stNVIC); #ifndef USART_USE_DMA USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); #endif USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); // Enable Usart1 USART_Cmd(USART1, ENABLE); } 在配置过程中,只打开了 RX 接收中断 TX 发送数据时刻,只需 polling 是否发送完成,在进行下一次的数据发送即可。 值得注意的是,数据收发都是使用了同一个DR寄存器,不同时刻,由硬件来进行区分。 USART 数据 TX 发送(Polling) STM32 USART 数据发送是通过往 USART 的 DR 寄存器写值完成的,DR 寄存器支持每次写 1 Byte 的数据,每次写完数据后,硬件会将 DR 寄存器的值送到移位寄存器中,将数据发送出去。软件需要 polling 硬件的 TC (Transfer Complete)标志位,待 1 Byte 数据发送完成后,再次进行下一个数据的发送: void SK_UsartSendChar(uint8_t ch) { while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET); USART_SendData(USART1, ch); } void SK_UsartSendData(uint8_t *buf, uint32_t len) { uint32_t i = 0; for (i = 0; i < len; i++) SK_UsartSendChar(buf); } USART 数据 RX 接收(IRQ) 对于数据接收,使用 Polling 显然不是一种好办法,用中断的方式进行数据接收,接收的数据带简单的自定义格式:帧头+长度+Data的方式: typedef struct { volatile uint32_t hdr_found; volatile uint32_t data_len; volatile uint32_t data_ready; }SK_USART_RX_CTL_t; SK_USART_RX_CTL_t g_stUsartRxCtl; uint8_t g_SK_UsartRxDataBuf[SK_USART_RX_BUF_LEN] = {0}; /* * Protocol: 1st. Frame start at 0x5A * 2nd. Second is data length * 3rd. Data */ /* void USART1_IRQHandler(void) { uint8_t rx_data = 0; static uint8_t cnt = 0; if (USART_GetITStatus(USART1, USART_IT_RXNE)) { rx_data = USART_ReceiveData(USART1); } if (SK_USART_RX_FRM_HEADER == rx_data && !g_stUsartRxCtl.hdr_found) { g_stUsartRxCtl.hdr_found++; return; } if (g_stUsartRxCtl.hdr_found && g_stUsartRxCtl.data_len == 0) { g_stUsartRxCtl.data_len = rx_data; if (g_stUsartRxCtl.data_len > SK_USART_RX_BUF_LEN) g_stUsartRxCtl.data_len = SK_USART_RX_BUF_LEN; return; } if (cnt < g_stUsartRxCtl.data_len) { g_SK_UsartRxDataBuf[cnt++] = rx_data; if (g_stUsartRxCtl.data_len == cnt) { g_stUsartRxCtl.data_ready = 1; cnt = 0; SK_SetLedStatus(SK_LED_1, SK_LED_ON); delay_ms(2000); SK_SetLedStatus(SK_LED_1, SK_LED_OFF); SK_UsartSendData(g_SK_UsartRxDataBuf, g_stUsartRxCtl.data_len); } } } USART 数据 RX 接收(IRQ + DMA) 虽然可以使用 IRQ 的方式进行一个 Byte 一个 Byte 的数据接收(中断),但您不觉得这会让 CPU 累死么?看着都费劲。 好嘛,STM32 UASRT 数据接收又不带 FIFO,不过没关系,用 DMA 放飞 CPU 吧!! STM32 的 DMA1 支持 7个通道: 如上图所示:Ch4 用作 USART1_TX,Ch5 用作 USART1_RX。让 CPU 在歇一会。 转存失败重新上传取消 USART DMA 配置 DMA 指的是 (Direct Memory Access)直接内存存取,不经过 CPU。STM32 的 DMA 支持外设到内存,内存到外设,以及内存到内存。只要咱们告诉 DMA 控制器,从什么地方去取外设数据,数据有多少,数据宽度是多少,以及将数据放置到内存的什么地方,它便可以带你飞。 当然,DMA 也支持中断的配置,能够配置成为数据传送一半的时候来中断,or,数据传送完来中断,从各方面解决了您的烦恼。让您无忧无虑进行数据传送。 回到正题上来,配置 USART DMA要有入下几个步骤: 1. 开启 DMA1 时钟(这不废话么) 2. 配置 NVIC,并使能(也是废话) 3. 复位 DMA1 的 Ch4/Ch5 4. 设定外设地址为 USART1 的 DR 寄存器,即数据寄存器 5. 设置接收数据的内存地址(本地的一个缓存 RX BUF指针) 6. 设置数据方向为 USART1 的 DR 寄存器 ----> 内存 7. 设置 DMA 传输的数据大小(最大 65536) 8. 设置关闭外设地址自动增加 9. 设置启用缓存 BUF 地址自动增加 (数据传来后,自动存在本地 RX BUF 并指针递增) 10. 配置外设传输数据宽度为 8bit (USART DR 寄存器就 8bit) 11. 配置本地缓存数据宽度为 8bit 12. 配置 DMA 传输模式为 one shot(即传输完一次后,就停止了,也可以配置成为循环模式) 13. 设置 DMA CH15 的优先级 14. 关闭 memory to memory(废话,使用的是外设到内存的数据传输) 15. 开启 DMA1 CH15 16. 使能数据传输完成的 DMA1 中断 17. 在 USART 寄存器中,开启 RX DMA 请求(此项是在 USART 寄存器中进行配置) 好啦,此刻配置基本完成: void SK_UsartDmaInit(void) { DMA_InitTypeDef stDMA_InitStructCh4; DMA_InitTypeDef stDMA_InitStructCh5; /// Step 1 : Open the DMA1 Clock RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // NVIC Config DMA_NVIC_Config(); /// Step 2 : Reset DMA1_CH4(For USART1 TX) and DMA1_CH5(For USART1 RX) DMA_DeInit(DMA1_Channel4); // Configure the USART1 TX DMA Transfer for DMA CH4 // Configure the Peripheral address stDMA_InitStructCh4.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; // Configure the TX Data Buffer address stDMA_InitStructCh4.DMA_MemoryBaseAddr = (uint32_t)g_SK_UsartTxDataBuf; // Configure the data direct: Memory to Peripheral stDMA_InitStructCh4.DMA_DIR = DMA_DIR_PeripheralDST; // Configure the data Len stDMA_InitStructCh4.DMA_BufferSize = SK_USART_TX_BUF_LEN; // Configure the Peripheral Address auto add (disable) stDMA_InitStructCh4.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // Configure the Memory Address auto add (enable) stDMA_InitStructCh4.DMA_MemoryInc = DMA_MemoryInc_Enable; // Configure the Peripheral Data Size = 1 byte stDMA_InitStructCh4.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // Configure the Memory Data Size = 1 byte stDMA_InitStructCh4.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // Configure Normal mode stDMA_InitStructCh4.DMA_Mode = DMA_Mode_Normal; // Configure Priority as medium stDMA_InitStructCh4.DMA_Priority = DMA_Priority_Medium; // Disable memory to memory stDMA_InitStructCh4.DMA_M2M = DMA_M2M_Disable; // Config DMA1 CH4 DMA_Init(DMA1_Channel4, &stDMA_InitStructCh4); // Enable Ch4 DMA_Cmd(DMA1_Channel4, ENABLE); // Enable IRQ when transfer finished DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE); /// Step 3: Reset DMA1_CH5(For USART1 RX) DMA_DeInit(DMA1_Channel5); // Configure the USART1 RX DMA for DMA CH5 // Configure the Peripheral address stDMA_InitStructCh5.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; // Configure the TX Data Buffer address stDMA_InitStructCh5.DMA_MemoryBaseAddr = (uint32_t)g_SK_UsartRxDataBuf; // Configure the data direct: Memory to Peripheral stDMA_InitStructCh5.DMA_DIR = DMA_DIR_PeripheralSRC; // Configure the data Len stDMA_InitStructCh5.DMA_BufferSize = SK_USART_RX_BUF_LEN; // Configure the Peripheral Address auto add (disable) stDMA_InitStructCh5.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // Configure the Memory Address auto add (enable) stDMA_InitStructCh5.DMA_MemoryInc = DMA_MemoryInc_Enable; // Configure the Peripheral Data Size = 1 byte stDMA_InitStructCh5.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // Configure the Memory Data Size = 1 byte stDMA_InitStructCh5.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // Configure Normal mode stDMA_InitStructCh5.DMA_Mode = DMA_Mode_Normal; // Configure Priority as medium stDMA_InitStructCh5.DMA_Priority = DMA_Priority_Medium; // Disable memory to memory stDMA_InitStructCh5.DMA_M2M = DMA_M2M_Disable; // Config DMA1 CH5 DMA_Init(DMA1_Channel5, &stDMA_InitStructCh5); // Enable Ch5 DMA_Cmd(DMA1_Channel5, ENABLE); DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE); } void DMA_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; /* Configure one bit for preemption priority */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn; NVIC_Init(&NVIC_InitStructure); } 接收到来自串口的数据时,会直接将数据灌到我们的 uint8_t g_SK_UsartRxDataBuf[SK_USART_RX_BUF_LEN]中。如果要接收任意长度的数据怎么办呢? USART DMA 接收任意长度数据 不得不说,USART 的 IDLE 中断在其中起了很大的作用,根据描述,可以知道,此中断产生的条件是,完整接收完一组数据后,总线空闲时刻产生的中断。 比如说给单片机一次发来1个字节,或者一次发来8个字节,这些一次发来的数据,就称为一帧数据,也可以叫做一包数据。当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。 在 USART IRQ Handler 中去查看标志位,来判断此中断时哪类中断,需要注意的是,在中断函数里面,需要把对应的位清0,否则会影响下一次数据的接收。比如RXNE接收数据中断,只要把接收到的一个字节读出来,就会清除这个中断。针对IDLE中断,中断标志清除方法是“先读SR寄存器,再读DR寄存器”。 好嘞,有了这个玩意,加上 DMA,便可以做一些事情了,首先在初始化串口的时候,使能 IDLE 中断(因为使用了 DMA传送,所以别再用串口数据接收中断): #ifndef USART_USE_DMA USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);#endif USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); 只要 RX 的本地 buffer 开的足够大,每次 DMA 接收来的数据全部存放到 g_SK_UsartRxDataBuf 中。此刻就会来 USART 的 IDLE 中断。好,此刻数据就收完了。此刻本地的 Buffer 还没爆,赶紧做数据处理吧。 还记得在配置 DMA 的时候,我们写了一个数据传送的最大值到寄存器么?这个值是在数据传送过程中,每次传送一个数据,自动减1,也就是在进行数据的倒计数,如果这个值配置的足够大,在来 USART IDLE 中断的时刻,读该寄存器,我们就可以知道此时传送了多少数据了。若继续进行数据传送,数据传到我们配置的这个值(或者一半)的话,就会产生数据传送(传送一半)完成中断。 所以,一种解决方案是,在进行 DMA 接收数据后,通过读取该寄存器,得到此次接收到的数据长度,我们就知道BUFFER中的数据接收了多少。再在重新对这个 DMA 的数据传送长度进行编程,便可以进行下一次传送了(需要先关闭 DMA,写该寄存器,在开启) uint32_t Usart1_Rec_Cnt = 0; void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_IDLE)) { uint8_t clr = 0; clr = USART1->SR; clr = USART1->DR; Usart1_Rec_Cnt = DMA_GetCurrDataCounter(DMA1_Channel5); SK_SetLedStatus(SK_LED_1, SK_LED_ON); delay_ms(2000); SK_SetLedStatus(SK_LED_1, SK_LED_OFF); } } void DMA1_Channel4_IRQHandler(void) { DMA_ClearITPendingBit(DMA1_IT_TC4); SK_SetLedStatus(SK_LED_2, SK_LED_ON); } void DMA1_Channel5_IRQHandler(void) { DMA_ClearITPendingBit(DMA1_IT_TC5); SK_SetLedStatus(SK_LED_2, SK_LED_OFF); } |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1260 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1267 浏览 1 评论
668 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
498 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1257 浏览 2 评论
1699浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
393浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
363浏览 3评论
350浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
325浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-9-13 05:14 , Processed in 0.925025 second(s), Total 79, Slave 62 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191