目录
- 1、环境说明
- 2、实现不定长数据接收需要做哪些事?
- 2.1、数据的接收与缓存
- 2.2、数据帧的结束判断
- 2.3、数据帧的长度计算
- 3、RS485串口实现不定长数据发送
- 4、代码实现
- 结语:
1、环境说明
单片机型号;Cortex-M4架构,AT32F437
说明:实现只是一个思路,只要了解到单片机存在这些功能,对照着官方手册和例程,实现并不难。
2、实现不定长数据接收需要做哪些事?
- 数据的接收与缓存
- 数据帧的结束判断
- 数据帧的长度计算
解决以上几点,基本上就可以实现不定长数据的接收了。下面将依次给出应对措施。
2.1、数据的接收与缓存
数据的接收通常是通过单片机的usart–>dt(数据寄存器)来完成的,每次能够存储一个字节。单片机可以通过定时查询、接收中断、DMA等方式进行数据的缓存工作。其中DMA作为效率最高的方式,是本次讲解的重点。
什么是DMA?在每一个32位单片机的手册里应该都会有说明
DMA是单片机集成在芯片内部的一个数据搬运工,它可以代替单片机对数据进行传输、存储。举一个简单的例子:
要发一个快递,我们需要填写快递的大致体积大小、从哪里发走、在哪里收货。DMA就像快递员,我们只需要负责填写信息,等待快递员上门取货、发货就可以了,我们只需考虑要填的信息和签收工作。
DMA不仅仅是串口能用,它能满足单片机绝大多数的数据传输工作,比如支持定时器、ADC、DAC、SDIO、I²S、SPI、I²C、DVP、QSPI和USART。无需了解太多,我认为嵌入式工程师对于这一类的工具能做到快速上手使用即可。
因此我们可以派出DMA对usart->dt中存储的数据进行一个接收和缓存工作。我们只需要知道什么时候数据都送到了,然后取出处理即可。
2.2、数据帧的结束判断
在串口通信中,通常我们对一帧数据的结束判定是在一定时间后,没有下一个数据到来。这一段时间在单片机内认为是一个空闲时刻。如果串口接收到一个数据后,隔了一个空闲时刻仍然没有数据继续到达,则串口的状态寄存器就会对空闲位进行一个置起。当单片机触发该中断后,就可以认为本次串口接收已经结束。在2.1我们已经让DMA对数据进行接收缓存了,因此我们就可以直接去DMA中取出这段数据。
2.3、数据帧的长度计算
在串口通信中,数据的长度是一个非常重要的信息,它可以帮助我们对数据帧进行解析。那具体要怎么知道呢?
在2.1中我们知道,在使用DMA时,需要预先填写一些信息。比如“快递”的大致体积。比如DMA最大支持是65536个字节,我们预估本次接收最大可能会用到x个字节。实际我们接收到了y个字节。那么DMA的长度寄存器就会记录下本次接收剩余的一个容量z。那么我们实际上接收到的数据长度y=x-z(x<=65536)。
3、RS485串口实现不定长数据发送
熟悉RS485的朋友都知道,RS485是一种半双工通信,发送和接收必须分时进行,因此对于收发转换控制的就非常重要,既要保证数据能够全部接收到,也必须保证数据能够全部发出,否则会造成数据的丢包。因此我们必须知道我们何时结束数据的发送,将RS485由发送切换到接收状态,切换早了会导致数据发送不完整,晚了会导致数据接收不及时不完整。
在单片机的串口状态寄存器里存在一个发送数据完成寄存器,当数据发送结束时,会将此位置起,产生一个发送结束中断。因此在此中断产生后,我们就可以安心切换至接收状态了。
4、代码实现
#include "rs485.h"
#include "stdio.h"
#include "comm_protocol.h"
/*
*********************************************************************************************************
* 与FreeRTOS相关的一些变量
*********************************************************************************************************
*/
TaskHandle_t rs485_handler;/*RS485数据处理任务句柄*/
SemaphoreHandle_t BinarySemphore;/*创建用于串口中断与任务同步的信号量*/
/*
*********************************************************************************************************
* 该文件内调用的全局变量
*********************************************************************************************************
*/
static uint8_t Uart1RecBuffer[MaxNumOfUart1RecBuffer]; /* 接收缓冲区*/
static uint8_t Uart1SendBuffer[MaxNumOfUart1SendBuffer]; /* 发送缓冲区*/
//数据接收标志位,
volatile uint8_t usart1_rx_dma_status = 0;
volatile uint8_t usart1_tx_dma_status=1;
/*
*********************************************************************************************************
* 部分函数声明
*********************************************************************************************************
*/
void TestProtocol(stCommBufBlock *BufBlockPtr);
/*
*********************************************************************************************************
* 驱动配置(外部比如MAIN.C , APP_HOOKS.c可能引用该全局变量)
*********************************************************************************************************
*/
#define PRINT_UART USART1
//RS485 1 用
stCommBSPInfo RS485BSPInfo1 = {
USART1, //使用串口1
19200, /* 通讯波特率默认值19200*/
0, /* 485通讯地址,在系统初始化的时候,会重新赋值*/
DMA1_CHANNEL2, //串口1接收数据使用的DMA地址
DMA1MUX_CHANNEL2,
DMAMUX_DMAREQ_ID_USART1_RX, /* 通讯模块接收的源号*/
Uart1RecBuffer, /* 接收缓存指针*/
MaxNumOfUart1RecBuffer, /* 接收缓存大小*/
DMA1_CHANNEL1,
DMA1MUX_CHANNEL1,
DMAMUX_DMAREQ_ID_USART1_TX, /* 通讯模块发送 的源号*/
Uart1SendBuffer, /* 发送缓存指针*/
MaxNumOfUart1SendBuffer, /* 发送缓存大小*/
0, /* 接收计数器*/
5, /* 接收间隔,单位5ms*/
5, /* 发送间隔,单位5ms ,RS485用*/
TestProtocol, /* 通信协议函数指针*/
BSPTYPE_485, /* 接口选项,比如RS232,485,SPI等*/
GPIO_PINS_11, /* 控制485芯片的IO 序号0~31,232 不用,RS485 用*/
GPIOA,
0,
};
void usartConfiguration(void) {
gpio_init_type gpio_init_struct;
/* enable the usart1 and gpio clock */
crm_periph_clock_enable(CRM_USART1_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
gpio_default_para_init(&gpio_init_struct);
/* configure the usart2 tx, rx pin */
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
gpio_init_struct.gpio_pins = GPIO_PINS_9 | GPIO_PINS_10;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOA, &gpio_init_struct);
gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE9, GPIO_MUX_7);
gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE10, GPIO_MUX_7);
/* config usart nvic interrupt */
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
nvic_irq_enable(USART1_IRQn, 2, 0);
/* configure usart1 param */
usart_init(USART1, RS485BSPInfo1.BaudRate, USART_DATA_8BITS, USART_STOP_1_BIT);
usart_transmitter_enable(USART1, TRUE);
usart_receiver_enable(USART1, TRUE);
/* enable usart1 and usart2 and usart3 interrupt */
usart_enable(USART1, TRUE);
usart_dma_transmitter_enable(USART1, TRUE);
usart_dma_receiver_enable(USART1, TRUE);
usart_interrupt_enable(USART1, USART_IDLE_INT, TRUE);//使能空闲中断
usart_interrupt_enable(USART1, USART_TDC_INT, TRUE); //使能发送完成中断
}
//初始化DMA的时钟
void dmaInit(void) {
/* enable dma1 clock */
crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE);
dmamux_enable(DMA1, TRUE);/*一旦开启,就不要关闭,*/
//配置通道1,用于串口1的发送
dma_reset(RS485BSPInfo1.Send_Dma_Chn);
dmamux_init(RS485BSPInfo1.Send_Dmamux_Chn, RS485BSPInfo1.CommSendSlotNumber);
//配置通道2,用于串口1的接收
dma_reset(RS485BSPInfo1.Rec_Dma_Chn);
dmamux_init(RS485BSPInfo1.Rec_Dmamux_Chn, RS485BSPInfo1.CommRecSlotNumber);
}
//PA11 PD3 PD11
void usartCtrlIOInit(void) {
gpio_init_type gpio_init_struct;
/* enable the usart1 and gpio clock */
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
gpio_default_para_init(&gpio_init_struct);
/* configure the usart2 tx, rx pin */
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins = GPIO_PINS_11;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOA, &gpio_init_struct);
gpio_init_struct.gpio_pins = GPIO_PINS_3;
//默认接收状态
gpio_bits_set(RS485BSPInfo1.gpio_x, RS485BSPInfo1.IoNumber);
}
//DMA最大传输量为65536
//用于串口接收和发送
void dmaUsartTXorRX(dma_dir_type dir, uint8_t* buf, uint16_t size, dma_channel_type *dmax_channely, usart_type* usart_x) {
dma_init_type dma_init_struct;
dma_reset(dmax_channely);//重置此通道
if (dir == SEND) {
if (usart_x == USART1) {
while(!usart1_tx_dma_status){}
usart1_tx_dma_status=0;
gpio_bits_reset(GPIOA, GPIO_PINS_11);
}
}
/* dma1 channel1 for usart2 tx configuration */
dma_default_para_init(&dma_init_struct);
dma_init_struct.buffer_size = size;
dma_init_struct.direction = dir;
dma_init_struct.memory_base_addr = (uint32_t)buf;
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
dma_init_struct.memory_inc_enable = TRUE;
dma_init_struct.peripheral_base_addr = (uint32_t)&usart_x->dt;
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;
dma_init_struct.peripheral_inc_enable = FALSE;
dma_init_struct.priority = DMA_PRIORITY_MEDIUM;
dma_init_struct.loop_mode_enable = FALSE;
dma_init(dmax_channely, &dma_init_struct);
dma_channel_enable(dmax_channely, TRUE); //一旦开启,就会立刻开始传输,不受MCU控制
}
/*
*********************************************************************************************************
* TestProtocol()
*
* 描述: 测试用例协议
*
* 参数: BufBlockPtr-- 接收数据信息块
*
* 附注: 无
*
*********************************************************************************************************
*/
void TestProtocol(stCommBufBlock *BufBlockPtr)
{
CPU_INT32U i;
// 以同样的字节数将接收到的数据返回
BufBlockPtr->SendByteNum = BufBlockPtr->RecByteNum;
// 将返回的数据都扩大两倍
for (i = 0; i < BufBlockPtr->SendByteNum; i++)
{
*(BufBlockPtr->SendBufPtr + i) = *(BufBlockPtr->RecBufPtr + i);
}
}
/*
*********************************************************************************************************
* CommProcess()
*
* 描述: 串口(232,485)通讯进程,改为任务函数,当有数据完成接收后,及时处理该函数
*
* 参数: CBinfo--通信端口配置信息
*
* 附注: 无
*
*********************************************************************************************************
*/
void CommProcess(stCommBSPInfo *CBinfo) {
stCommBufBlock BufBlock;
uint16_t sendnum;
//先关闭相应的DMA接收
dma_channel_enable(CBinfo->Rec_Dma_Chn, FALSE);
//调用通信协议,RecCount 为接收数据个数参数,CBinfo.RecBuff 为接收数组地址
CBinfo->RecCntTemp = CBinfo->RecBuffLength - dma_data_number_get(CBinfo->Rec_Dma_Chn); //根据接收的数据剩余量得出
BufBlock.RecByteNum = CBinfo->RecCntTemp;
BufBlock.RecBufPtr = CBinfo->RecBuff;
BufBlock.SendBufPtr = CBinfo->SendBuff;
BufBlock.Address = CBinfo->Address;
//跳转至CBinfo.ProtocolFunPtr 指向的通信协议函数
//该函数处理BufBlock.RecBufPtr 所指向的BufBlock.RecByteNum个字节的数据,
//然后将处理出来的BufBlock.SendByteNum个字节的数据存放在BufBlock.SendBufPtr
//所指向的地方
(*CBinfo->ProtocolFunPtr)(&BufBlock);
//防止要发送的数据个数超过SENDBUF最大值
if (BufBlock.SendByteNum <= CBinfo->SendBuffLength) {
sendnum = BufBlock.SendByteNum;
} else {
sendnum = CBinfo->SendBuffLength;
}
//如果地址不对,就不会有数据需要发送
if (sendnum > 0) {
dmaUsartTXorRX(SEND, BufBlock.SendBufPtr, sendnum, CBinfo->Send_Dma_Chn, CBinfo->usart_x);
}
}
//建立一个任务,用于处理通信进程
void communication_485_task(void *pvParameters) {
BaseType_t semaphore485 = pdFALSE;
dmaInit();
dmaUsartTXorRX(RECEIVE, RS485BSPInfo1.RecBuff, RS485BSPInfo1.RecBuffLength, USART1RX, USART1); //开启DMA接收
while (1) {
//还需要判断当前协议。
//等待信号量
if (BinarySemphore != NULL) {
//获取信号量
semaphore485 = xSemaphoreTake(BinarySemphore, portMAX_DELAY);
if (semaphore485 == pdTRUE) { //获取信号量成功
if (usart1_rx_dma_status == 1) { //串口1,数据接收完成
usart1_rx_dma_status = 0;
CommProcess(&RS485BSPInfo1);//进入通信进程
dmaUsartTXorRX(RECEIVE, RS485BSPInfo1.RecBuff, RS485BSPInfo1.RecBuffLength, USART1RX, USART1); //开启DMA接收
}
}
}
}
}
}
void USART1_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (usart_flag_get(USART1, USART_IDLEF_FLAG) != RESET) { //发生了空闲中断
//一帧数据接收结束
usart1_rx_dma_status = 1;
usart_flag_clear(USART1, USART_IDLEF_FLAG);
xSemaphoreGiveFromISR(BinarySemphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
if (usart_flag_get(USART1, USART_TDC_FLAG) != RESET) { //发送完成中断
//一帧数据接收结束
gpio_bits_set(GPIOA, GPIO_PINS_11);
usart1_tx_dma_status=1;
usart_flag_clear(USART1, USART_TDC_FLAG);
}
}
/*end file*/
.h文件
#ifndef _RS485_H_
#define _RS485_H_
#include "config.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
/*
*********************************************************************************************************
* 接口定义
*********************************************************************************************************
*/
#define BSPTYPE_485 2
/*
*********************************************************************************************************
* 与RS485接口相关定义
*********************************************************************************************************
*/
#define SEND DMA_DIR_MEMORY_TO_PERIPHERAL //发送
#define RECEIVE DMA_DIR_PERIPHERAL_TO_MEMORY //接收
#define USART1TX DMA1_CHANNEL1 //串口1发送
#define USART1RX DMA1_CHANNEL2 //串口1接收
#define MaxNumOfUart1RecBuffer 128
#define MaxNumOfUart1SendBuffer 256
/*
*********************************************************************************************************
* 自定义数据结构
*********************************************************************************************************
*/
typedef struct
{
uint32_t Address; /* 该端口地址*/
uint32_t RecByteNum; /* 接收数组字节个数*/
uint8_t *RecBufPtr; /* 接收指针指向缓冲数组*/
uint32_t SendByteNum; /* 发送数组字节个数*/
uint8_t *SendBufPtr; /* 发送指针指向缓冲数组*/
}stCommBufBlock;
typedef void (*tCommProtocolFunPtr)(stCommBufBlock*); /* 通信协议函数指针*/
typedef struct
{
usart_type* usart_x; /* 串口外设 */
uint32_t BaudRate; /* 通讯波特率*/
uint32_t Address; /* 通讯波地址*/
dma_channel_type * Rec_Dma_Chn; /* 用于串口接收的DMA外设 */
dmamux_channel_type* Rec_Dmamux_Chn; /* 用于串口接收的DMA通道号 */
dmamux_requst_id_sel_type CommRecSlotNumber; /* 用于串口接收的接收的源号*/
uint8_t *RecBuff; /* 接收缓存指针*/
uint16_t RecBuffLength; /* 接收缓存大小,由接收最大值减去当前DMA剩余接收量得到 */
dma_channel_type* Send_Dma_Chn; /* 用于串口发送的DMA外设 */
dmamux_channel_type* Send_Dmamux_Chn; /* 用于串口发送的DMA通道号 */
dmamux_requst_id_sel_type CommSendSlotNumber; /* 用于串口发送的接收的源号*/
uint8_t *SendBuff; /* 发送缓存指针*/
uint16_t SendBuffLength; /* 发送缓存大小,由协议给出*/
uint32_t RecCntTemp; /* 接收计数器,现在使用的是串口空闲中断来判断帧数据是否结束,该变量可以省去*/
uint32_t RecTimeInterval; /* 接收间隔,单位5ms*/
uint32_t SendTimeInterval; /* 发送间隔,单位5ms,RS485用*/
tCommProtocolFunPtr ProtocolFunPtr; /* 通信协议函数指针*/
uint32_t BSPType; /* 接口选项,比如RS232,485,SPI等*/
uint32_t IoNumber; /* 控制485芯片的IO 序号0~31*/
gpio_type* gpio_x; /*PORT地址*/
uint8_t CommCnt;
}stCommBSPInfo;
extern TaskHandle_t rs485_handler;
extern SemaphoreHandle_t BinarySemphore;/*创建用于串口中断与任务同步的信号量*/
extern stCommBSPInfo RS485BSPInfo1;
extern volatile uint8_t usart1_tx_dma_status;
void communication_485_task(void *pvParameters);
void usartConfiguration(void);
void dmaInit(void);
void usartCtrlIOInit(void);
void dmaUsartTXorRX(dma_dir_type dir,uint8_t* buf,uint16_t size,dma_channel_type *dmax_channely,usart_type* usart_x);
#endif
结语:
首先此篇文章仅作为对RS485不定长数据的接收与发送的一种探讨,方式不止一种,本文是我的一种愚见罢了。其次,单片机开发并不难,我们只是要要了解到有这么一种方式可以实现想要的功能,那么就基本离成功不远了,耐心看手册耐心调试程序,没有不能完成的事情的。