1、准备开发板
在前面我们已经讲完了基础部分的例程,接下来我们正式进入网络通信部分,在此之前,我们需要做些准备,来适配我们的网络通信模块。现在市场上最常用的解决方案就是WIFI和NB,还有在某些要求数据量大、传输速度快的场景会用到的4G,所以我们下面会主要着重以上三个解决方案进行。
开发板功能区分布图
开发板俯视图
网络通信模块对应的串口
由上面的原理图,我们可以知道小熊派的网络通信模块的接口在LPUART1串口上,这个似乎是STM32L系列特有的,它可以通过AT_Switch进行PC和MCU的切换,当你想要用PC调试时,将拨码拨到PC端,然后就可以用网络调试助手进行调试,反之,就是MCU发送AT指令,从而控制网络通信模块。
2、STM32CubeMX生成代码
搜索并选择芯片型号
配置系统时钟
配置时钟树
STM32L4的最高主频可达到80M,最后使HCLK = 80Mhz
即可:
配置GPIO引脚
修改引脚的用户标签(相当于取另一个新名字)
配置串口
配置TIM
计时器更新中断频率计算F:
F = ( arr + 1 )*( psc + 1 ) / 80M 当设置arr = 79,psc = 999时,F = 1000 Hz = 1 K Hz
3、在MDK中编写代码
新建sys.h文件
#ifndef _SYS_H
#define _SYS_H
#include "main.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stdarg.h"
#include "stdbool.h"
#include "stm32l4xx.h"
#include "usart.h"
//0,不支持os
//1,支持os
#define SYSTEM_SUPPORT_OS 0 //定义系统文件夹是否支持OS
#define delay_ms(Delay) HAL_Delay(Delay)
void ClearRAM(u8* ram,u32 n);
void USART_SendChar( USART_TypeDef *huart, uint8_t ch );
void USART_SendBuf( USART_TypeDef *huart, uint8_t *str,uint32_t strlen );
void USART_SendString( USART_TypeDef *huart, uint8_t *str);
void UsartPrintf(USART_TypeDef *huart, char *fmt,...);
新建sys.c文件
//清空字节数组
//ram: 待清空的数组
//n: 待清空数组的长度
void ClearRAM(uint8_t* ram,uint32_t n)
{
u32 i;
for (i = 0;i < n;i++)
{
ram[i] = 0x00;
}
}
//串口发送字符
//huart: 串口结构体句柄
//ch: 待发送的字符
void USART_SendChar( USART_TypeDef *huart, uint8_t ch )
{
while((huart->ISR&0X40)==0);
huart->TDR = (uint8_t)ch;
}
//串口发送指定长度的字节数组
//huart: 串口结构体句柄
//str: 待发送的字节数组
//strlen: 字节数组长度
void USART_SendBuf( USART_TypeDef *huart, uint8_t *str,uint32_t strlen )
{
unsigned int k = 0;
do
{
USART_SendChar(huart,*(str + (k++)));
}while( k < strlen );
}
//串口发送字符串数组
//huart: 串口结构体句柄
//str: 待发送的字符串数组
void USART_SendString( USART_TypeDef *huart, uint8_t *str)
{
unsigned int k = 0;
do
{
USART_SendChar(huart,*(str + (k++)));
}while(*(str + (k)) != '\0');
}
//串口发送长度的字符串
//huart: 串口结构体句柄
//str: 待发送的字符串数组
void UsartPrintf(USART_TypeDef *huart, char *fmt,...)
{
unsigned char UsartPrintfBuf[200];
va_list ap;
unsigned char *pStr = UsartPrintfBuf;
//RTOS_ENTER_CRITICAL(); //写入多个数据时,进入临界段,放置打断出错
va_start(ap, fmt);
vsprintf((char *)UsartPrintfBuf, fmt, ap); //格式化
va_end(ap);
while(*pStr != 0)
{
USART_SendChar(huart,*pStr++);
}
//RTOS_EXIT_CRITICAL();
}
在tim.c下用户区添加以下代码
/* USER CODE BEGIN 0 */
uint32_t time2Count;
/* USER CODE END 0 */
//定时器中断使能
void TIM_Interupt_Enable(void)
{
__HAL_TIM_ENABLE_IT(&htim2,TIM_IT_UPDATE); //使能定时器更新中断
HAL_TIM_Base_Start(&htim2); //启动定时器
}
//定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)
{
if(__HAL_TIM_GET_IT_SOURCE(htim,TIM_IT_UPDATE) != RESET) //如果触发了更新中断
{
if(time2Count++ >= 4294967295UL) //防止数值溢出
{
time2Count = 0;
}
__HAL_TIM_CLEAR_IT(htim,TIM_IT_UPDATE); //清除更新中断标志位
}
}
}
在gpio.h中编写以下代码
/* USER CODE BEGIN Private defines */
typedef enum
{
LED_OFF = 0,
LED_ON,
LED_Toggle
} LED_ENUM;
#define LED_Set(status) status != LED_Toggle ? HAL_GPIO_WritePin( LED_GPIO_Port, LED_Pin, status != LED_ON ? GPIO_PIN_RESET : GPIO_PIN_SET): HAL_GPIO_TogglePin( LED_GPIO_Port, LED_Pin); //利用define可以美化和简短我们的代码,知识点就用到了三目运算符的嵌套判断。
/* USER CODE END Private defines */
在usart.h下的用户代码区编写以下代码
/* USER CODE BEGIN Includes */
#if defined ( __CC_ARM )
#pragma anon_unions
#endif
/* USER CODE END Includes */
/* USER CODE BEGIN Private defines */
#define RX_BUF_MAX_LEN 1024 //最大接收缓存字节数
typedef struct
{
unsigned short dataLenPre; //上一次的长度数据,用于比较
union{
unsigned short InfAll;
struct
{
unsigned short dataLen : 15; //接收数据长度
unsigned short finishFlag : 1; //接收完成标志
}InfBit;
};
unsigned char rxBuf[RX_BUF_MAX_LEN]; //接收缓存
} USART_INFO_STRUCT;
#define REC_OK 1 //接收完成标志
#define REC_WAIT 0 //接收未完成标志
extern USART_INFO_STRUCT usart1Info;
extern USART_INFO_STRUCT lpuart1Info;
/* USER CODE END Private defines */
/* USER CODE BEGIN Prototypes */
void USART_Interupt_Enable(void);
void USER_UartDMAHandler(UART_HandleTypeDef* huart,USART_INFO_STRUCT* usartInfo);
/* USER CODE END Prototypes */
在usart.c下的用户代码区编写以下代码
/* USER CODE BEGIN 0 */
#include <stdio.h>
USART_INFO_STRUCT usart1Info;
USART_INFO_STRUCT lpuart1Info;
int fputc(int ch, FILE *stream)
{
/* 堵塞判断串口是否发送完成 */
while((USART1->ISR & 0X40) == 0);
/* 串口发送完成,将该字符发送 */
USART1->TDR = (uint8_t) ch;
return ch;
}
/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
void USART_Interupt_Enable(void)
{
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //空闲中断使能
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE); //接收中断使能
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除串口空闲中断标志位
__HAL_UART_ENABLE_IT(&hlpuart1,UART_IT_IDLE); //空闲中断使能
__HAL_UART_ENABLE_IT(&hlpuart1,UART_IT_RXNE); //接收中断使能
__HAL_UART_CLEAR_IDLEFLAG(&hlpuart1); //清除串口空闲中断标志位
}
void USER_UartDMAHandler(UART_HandleTypeDef* huart,USART_INFO_STRUCT* usartInfo)
{
uint32_t ucCh;
if(huart->Instance == USART1)
{
if(__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET) //触发空闲中断
{
__HAL_UART_CLEAR_IDLEFLAG(huart); //清除串口2空闲中断标志位
HAL_UART_DMAStop(huart); //关闭DMA
ucCh = huart->Instance->ISR; //清除SR状态寄存器
ucCh = huart->Instance->RDR; //读取DR数据寄存器 用来清除中断
ucCh = hdma_usart1_rx.Instance->CNDTR; //获取DMA中未传输的数据个数
usartInfo->InfBit.dataLen = RX_BUF_MAX_LEN - ucCh; //总计数减去未传输的数据个数,得到已经接收的数据个数
usartInfo->InfBit.finishFlag = REC_OK; //标志接收成功
}
HAL_UART_Receive_DMA(huart,usartInfo->rxBuf,RX_BUF_MAX_LEN); //使能DMA接收到buf1
}
if(huart->Instance == LPUART1)
{
if(__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET) //触发空闲中断
{
__HAL_UART_CLEAR_IDLEFLAG(huart); //清除串口2空闲中断标志位
HAL_UART_DMAStop(huart); //关闭DMA
ucCh = huart->Instance->ISR; //清除SR状态寄存器
ucCh = huart->Instance->RDR; //读取DR数据寄存器 用来清除中断
ucCh = hdma_lpuart_rx.Instance->CNDTR; //获取DMA中未传输的数据个数
usartInfo->InfBit.dataLen = RX_BUF_MAX_LEN - ucCh; //总计数减去未传输的数据个数,得到已经接收的数据个数
usartInfo->InfBit.finishFlag = REC_OK; //标志接收成功
}
HAL_UART_Receive_DMA(huart,usartInfo->rxBuf,RX_BUF_MAX_LEN); //使能DMA接收到buf1
}
}
/* USER CODE END 1 */
在stm32l4xx_it.c中的USART1_IRQHandler函数的用户代码区编写以下代码
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
USER_UartDMAHandler(&huart1,&usart1Info); //用户中断代码
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
/**
* @brief This function handles LPUART1 global interrupt.
*/
void LPUART1_IRQHandler(void)
{
/* USER CODE BEGIN LPUART1_IRQn 0 */
USER_UartDMAHandler(&hlpuart1,&lpuart1Info); //用户中断代码
/* USER CODE END LPUART1_IRQn 0 */
HAL_UART_IRQHandler(&hlpuart1);
/* USER CODE BEGIN LPUART1_IRQn 1 */
/* USER CODE END LPUART1_IRQn 1 */
}