P2 NBIOT(DMA+空闲中断)
1.相关概念解释
1.1串口透传
- 平常拿到某个模组之后我们往往通过USB-TTL芯片将模组与电脑相连,进而对模组状态进行检测或者控制模组
- 串口透传则是将模组与单片机相连,之后再通过USB-TTL将单片机与电脑相连,通过单片机对模组进行控制,即单片机相当于中间商
- 具体做法是:模组与单片机之间通过串口2通信,单片机与电脑通过串口1通信,在单片机内部通过DMA+空闲中断对数据进行转发
1.2空闲中断
- 空闲中断就是每接收到一条完整的数据就会置位空闲标志位,我们只需要判断空闲标志位是否置一就能知道是不是接收到了一条完整的数据
- 使用空闲中断可减小进入中断的次数,进而减小CPU负担
1.3DMA
- DMA(Direct Memory Access:直接内存存取)是一种可以大大减轻CPU工作量的数据转移方式
- 好比CPU是BOSS,DMA是秘书,某天公司收到了一大批货物,在没有秘书的情况下需要BOSS停下手中的工作亲自去将货物搬到仓库,而有了秘书后,BOSS只需要告诉秘书有货物到了,让秘书去搬运货物,而BOSS则继续处理自己的事情,当秘书搬完后再通知BOSS即可
- DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,从内存的某一区域传输到内存的另一区域
- 当用户将参数设置好,主要涉及源地址,目标地址,传输数据量这三个,DMA控制器就会启动数据传输,传输的终点就是剩余传输数据量为0(循环传输不是这样的),换句话说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输。
2.通过cubemx使用DMA+空闲中断
2.1配置cubemx
- 选择芯片
- 设置调试方式
- 配置时钟
- 配置串口1
- 配置串口2
- 配置控制引脚
- 设置时钟
- 配置输出选项
- 输出工程
- 至此,基本外设已经配置完毕,下面就开始愉快地写代码
2.2软件设置
2.2.1串口初始化
- 开启串口2接收中断与空闲接收中断
- 清除中断,因为stm32默认上电会产生一个空闲中断,所以需要清除之
- 打开串口DMA接收
void EnableUsart_IT(void)
{
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);//开启串口2接收中断
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);//开启串口2空闲接收中断
__HAL_UART_CLEAR_IDLEFLAG(&huart2); //清除串口2空闲中断标志位
HAL_UART_Receive_DMA(&huart2,Usart2type.Usart2DMARecBuffer,USART2_DMA_REC_SIE); //使能DMA接收
}
2.2.2串口中断处理函数
- 判断是否为空闲中断
- 清除中断标志位
- 停止DMA接收
- 获取DMA接收数据的长度
- 串口回调函数,在其中将DMA接收的BUFF数据拷贝到RecBUFF里面
- 开启DMA接收
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
uint32_t temp;
if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) == SET) //如果串口2触发空闲中断
{
__HAL_UART_CLEAR_IDLEFLAG(&huart2); //清除串口2空闲中断标志位
HAL_UART_DMAStop(&huart2); //关闭DMA
//读取一下两个寄存器方可清除DMA接收中断
temp = huart2.Instance->SR; //清除SR状态寄存器(F0系列为ISR)
temp = huart2.Instance->DR; //读取DR数据寄存器(F0系列为RDR)用来清除中断
temp = hdma_usart2_rx.Instance->CNDTR;//获取DMA中未传输的数据个数
Usart2type.UsartDMARecLen = USART2_DMA_REC_SIE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
HAL_UART_AbortCpltCallback(&huart2); //串口接收回调函数
}
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
HAL_UART_Receive_DMA(&huart2,Usart2type.Usart2DMARecBuffer,USART2_DMA_REC_SIE); //重新打开DMA接收
/* USER CODE END USART2_IRQn 1 */
}
2.2.3串口回调函数
- 判断是否为串口2
- 是否有未读数据
有未读数据则从地址0+old_len开始获取DMA接收区的数据
无则从地址0开始获取DMA接收区的数据
- 清空DMA接收区
- 置位数据包接收标志位
/*
功能:串口回调函数,用来搬移数据
*/
void HAL_UART_AbortCpltCallback(UART_HandleTypeDef *huart)
{
//printf("*******in callback*******\r\n");
if(huart->Instance == USART2)
{
if(Usart2type.UsartRecLen>0)
{
memcpy(&Usart2type.Usart2RecBuffer[Usart2type.UsartRecLen],Usart2type.Usart2DMARecBuffer,Usart2type.UsartDMARecLen); //转存到待处理区域
Usart2type.UsartRecLen += Usart2type.UsartDMARecLen;
}
else
{
memcpy(Usart2type.Usart2RecBuffer,Usart2type.Usart2DMARecBuffer,Usart2type.UsartDMARecLen); //转存到待处理区域
Usart2type.UsartRecLen = Usart2type.UsartDMARecLen;
}
printf("%s\r\n", Usart2type.Usart2DMARecBuffer);
memset(Usart2type.Usart2DMARecBuffer, 0x00, Usart2type.UsartDMARecLen); //先清空DMA缓冲区
Usart2type.UsartRecFlag = 1; //置位标记,表示串口2接收到数据
}
}
2.2.4主函数测试
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
EnableUsart_IT();
printf("System init OK!!\r\n");
//模组开机
HAL_GPIO_WritePin(NB_PWK_GPIO_Port, NB_PWK_Pin, GPIO_PIN_SET);
HAL_Delay(2500);
HAL_GPIO_WritePin(NB_PWK_GPIO_Port, NB_PWK_Pin, GPIO_PIN_RESET);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
memset(Usart2type.Usart2RecBuffer, 0x00, USART2_REC_SIE);
HAL_UART_Transmit(&huart2, (uint8_t*)"ATI\r\n", strlen("ATI\r\n"), 0xff);
printf("ATI\r\n");
HAL_Delay(2000);
if(Usart2type.UsartRecFlag == 1)
{
//printf("Usart2 rec is:%s\r\n", Usart2type.Usart2RecBuffer);
Usart2type.UsartRecFlag = 0;
Usart2type.UsartRecLen = 0;
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
- 接线
VCC------stm32 V3
或
VCC------cp2102 3V3