在stm32cubemx中使用串口发送功能时,需要实现串口重映射:通过fputc函数实现
#include<stdio.h>
#include<string.h>
//将此函数添加到对应的uart.c函数中
int fputc(int ch,FILE* f)
{
HAL_UART_Transmit(&huart1,(uint8_t*)&ch,1,0xffff);//以串口轮询方式发送函数
//函数入口参数:UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout
return (ch);
}
使用fputc函数时需要包含标准输入输出函数的头文件#include<stdio.h>#include<string.h>,同时编译器选项勾选use MicroLIB。此时在使用printf函数时会自动调用这个函数,通过串口发送数据。
类似的在在stm32中也可以实现scanf()函数功能:添加fgetc(),注意:在使用scanf时只能接收不带空格的字符串,如果用户需要接收一个带空格的字符串需要先逐个字节的接收在接收过程中不断判断是否接收到'\r'和'\n'两个字符来确定字符串是否接收完成。调用scanf函数读取串口数据时需要以空格或回车作为输入的结束。
int fgetc(FILE *f)
{
uint8_t ch;
HAL_UART_Receive(&huart1,(uint8_t *)&ch,1,0xffff);
return ch;
}
在stm32cubemx中的串口功能时,有三种方法:
- 使用阻塞式发送和接收。
- 使用中断式发送和接收。
- 使用DMA发送和接收。
1.使用阻塞式发送和接收(即轮询的方式)
轮询发送函数:HAL_UART_Transmit(&huart1,(uint8_t*)&ch,1,0xffff);
轮询接收函数:HAL_UART_Receive(&huart1,(uint8_t *)&ch,1,0xffff);
/*此段函数放入主循环内*/
if(scanf("%s",RxBuffer)==1)//读到数据
{
if(strcmp((const char *)RxBuffer,"open")==0)
{
printf("OPEN LED\r\n");
}
else if(strcmp((const char *)RxBuffer,"close")==0)
{
printf("CLOSE LED\r\n");
}
else
{
printf("Input error,Please input again:\r\n");
}
}
2.使用中断式接收
首先需要在主函数初始化内加入此函数
HAL_UART_Receive_IT(&huart1,(uint8_t *)RxBuffer,5);
然后需要在uart.c中添加对应的变量:
volatile uint8_t RrxFlag=0;//定义全局变量接收标志
uint8_t RxBuffer[100];
在usercode处添加如下代码:
/* USER CODE BEGIN 1 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart ->Instance == USART1 )
{
//置位接收标志,表示接收到一次数据;
RrxFlag=1;
HAL_UART_Receive_IT(&huart1,(uint8_t *)RxBuffer,5);//需要明确接收数据的长度
}
}
/* USER CODE END 1 */
//串口接收中断回调函数先根据串口句柄的外设成员instance(外设实例)判断是哪一个串口发生的本次接收中断然后执行中断处理任务,置位数据接收标志,同时使能接收中断准备下一次的数据接收
在主函数while循环里添加
if(RrxFlag==1)
{
RrxFlag=0;
//printf("success\r\n");//调试是否标志位置一
HAL_UART_Transmit(&huart1,(uint8_t *)RxBuffer,5,5);
/*通过串口发送函数 将存储在RxBuffer数组中的前五位数据发送出去*/
printf("\r\n");//与C语言里的printf函数功能一样
for(uint8_t i=0;i<5;i++)
{
printf("%c",RxBuffer[i]);
}
}
3. 使用DMA接收
采用中断方式进行串口通信时,每发送或接收一个字节都会产生中断通知CPU进行处理。当通信速率较高大于115200时,大约每隔86微秒就产生一次串口中断。如果通信的数据量较大,那么CPU将会频繁进入中断中进行数据处理导致其他操作无法及时执行。因此在实际的工程应用中,当串口通讯速率较高大于115200时,通信数据量较大时建议采用DMA方式。使用DMA方式进行串口通信时只需要在启动DMA传输前,设置DMA数据流通道存储器的首地址传入数据的数量等参数。数据传输过程中不需要CPU的干预,DMA控制器会自动将串口接收的数据写入存储器或将存储器中的数据通过串口发送。接收或发送完成指定数量的数据后,才产生DMA中断通知CPU进行后续处理。
HAL库中与DMA方式相关的常用接口函数有四个:
- HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart,uint8_t *pData,uint16_t Size),在DMA方式下发送指定量数据。完成指定数量的数据发送后可以触发DMA中断,在中断中将调用发送中断回调函数HAL_UART_TxCallback()进行后续处理。
- HAL_UART_Receive_DMA(UART_HandleTypeDef *huart,uint8_t *pData,uint16_t Size),完成指定数量的数据接收后,可以触发DMA中断,在中断中将调用接收中断回调函数HAL_UART_RxCallback()进行后续处理。
- _HAL_DMA_GET_COUNTER(_HANDLE_)获取DMA数据流中未传输数据的项数。该函数是宏函数进行宏替换,不发生函数调用。宏参数handle指向DMA句柄的指针(即dma句柄的地址)。
- HAL_UART_DMAStop(UART_HandleTypeDef *huart)停止串口DMA方式的数据传输该函数将同时停止DMA方式下的串口接收数据流和串口发送数据流。
利用空闲中断和DMA实现不定长数据的接收
由于HAL库中没有实现对idle中断的处理需要用户自行实现。要实现idle中断需要在串口初始化之后,调用接口函数__HAL_UART_ENABLE_IT()使能idle中断,然后在STM32f10x_it.c文件的串口中断服务程序USART1_IRQHandler()中添加对idle中断的判断。当接收完一帧数据后,线路将处于空闲中断状态,触发空闲中断在串口1的中断服务程序中置位数据接收标志RX_flag。主程序在while循环中不断判断检测RX_flag标志,为真则暂停DMA传输HAL_UART_DMAStop(&huart1)调用接口函数_HAL_DMA_GET_COUNTER(),获取实际接收的数据项数,然后采取轮询方式将数据原样传回PC。
- mode
mode用于设置DMA工作模式分为normal和circular两种模式在normal模式下当DMA传输结束时及指定数量的数据已经完成传输将不再产生DMA操作如果需要再次传输数据需要在关闭DMA数据流的情况下重新启动DMA传输这种方式相对于单次传输方式。
在circular模式下当前的DMA传输结束时传输数据的数量将自动重载为初值数据缓存重新指向首地址只要有数据就会继续传输DNA这种方式相对于连续传输方式。
circular模式主要用于连续大批量的数据传输如,ad转换中的连续转换模式而这里的不定长数据接收任务无法预测一帧数据的字节数,当利用idle中断接收完一针数据后需要暂停接收DMA传输获取该帧数据的字节数然后重新启动DMA传输因此选择normal模式。
- Increment Address
用于设置地址递增,外设作为单一设备地址固定,因此地址模式设置为不递增,而存储器包括多个存储单元。完成一个数据传输后地址应自动递增,指向下一个待传输数据的地址,因此设置为自动增加。
- Data Width
用于设置数据宽度有byte字节half word两字节和word四字节3种选择,由于串口一次只能收发一字节因此选择byte.
- Use FIFO
用于设置是否使能FIFO,用于数据的缓存一般不选择使能。
程序段配置:
//在stm32f1xx_it.c中添加
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!= RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除IDLE标志
RxFlag=1;//置位接收标志位
}
/* USER CODE END USART1_IRQn 1 */
}
//在main函数中添加
//初始化
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//使能IDLE中断(空闲中断标志)
HAL_UART_Receive_DMA(&huart1,(uint8_t *)RxBuffer,LENGTH);//启动DMA接收
//while中
if(RxFlag==1)
{
RxFlag=0;
HAL_UART_DMAStop(&huart1);//停止串口DMA传输
RxCount=LENGTH-__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//发生空闲中断时,已接收数据的像素等于数据总量减去DMA数据流中剩余未接收的数据项数
HAL_UART_Transmit(&huart1,(uint8_t *)RxBuffer,RxCount,100);
for(uint8_t i=0;i<RxCount;i++)//清除数据缓存
{
RxBuffer[i]=0;
}
RxCount=0;
HAL_UART_Receive_DMA(&huart1,(uint8_t *)RxBuffer,LENGTH);//重启dma传输
}
实现简单的通信协议
起始符 | 设备地址 | 功能代码 | 数据 | 校验 | 结束符 |
1个字符 | 2个字符 | 1个字符 | N个字符 | 2个字符 | 1个字符 |
帧头 | 设备代码 | 功能代码 | 帧尾 |
0xAA | 一个字符 | 一个字符 | 0XBB |
- 帧头0xAA,表示数据开始。
- 设备代码0x01,表示控制设备。
- 功能代码0x00表示关闭,0x01表示开启。
- 帧尾0xBB,表示数据结束。
程序代码
/* USER CODE BEGIN PD */
#define LENGTH 4 // 数据缓冲区长度的宏定义
/* USER CODE END PD */
/* USER CODE BEGIN PV */
uint8_t RxBuffer[LENGTH]; // 数据缓冲区
volatile uint8_t RxFlag = 0; // 数据接收标志: 0表示接收未完成, 1表示接收完成
uint8_t ErrFlag = 0; // 指令错误标志: 0表示指令正确, 1表示指令错误
/* USER CODE END PV */
int main(void)
{
/* USER CODE BEGIN 2 */
// 发送提示信息
printf("****** Communication Protocol ******\r\n");
printf("Please enter instruction:\r\n");
printf("Head->0xAA Device->0x01 Operation->0x00/0x01 Tail->0xBB...\r\n");
HAL_UART_Receive_IT(&huart1, (uint8_t*)RxBuffer, LENGTH); // 使能接收中断
/* USER CODE END 2 */
while (1)
{
/* USER CODE BEGIN 3 */
if (RxFlag)
{
RxFlag = 0; // 清除标志
// 通信协议解析
if (RxBuffer[0] == 0xAA && RxBuffer[3] == 0xBB) // 判断帧头帧尾
{
if (RxBuffer[1] == 0x01) // 判断设备码
{
if (RxBuffer[2] == 0x00) // 判断功能码
{
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
printf("LD2 is close!\r\n");
}
else if (RxBuffer[2] != 0x01) // 判断功能码
{
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
printf("LD2 is open!\r\n");
}
else // 功能码错误
{
ErrFlag = 1;
}
}
else // 设备码错误
{
ErrFlag = 1;
}
}
else // 帧头帧尾错误
{
ErrFlag = 1;
}
if (ErrFlag) // 发送错误提示信息
{
printf("Communication Error! Please send again!\r\n");
}
// 清除接收缓冲区和错误标志,准备下一次接收
ErrFlag = 0;
RxBuffer[0] = 0;
RxBuffer[1] = 0;
RxBuffer[2] = 0;
RxBuffer[3] = 0;
}
}
/* USER CODE END 3 */
}
}
因为使用串口中断接收所以与上面的中断接收配置一致。