串口初始化:
void bsp_InitUsart(void)
{
//2个结构体 IO和串口
GPIO_InitTypeDef GPIO_InitStructure;//IO初始化结构体
USART_InitTypeDef USART_InitStructure;//串口初始化结构体
NVIC_InitTypeDef NVIC_InitStructure;//中断初始化结构体
//开时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//开启GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//开启USART1时钟
//io口初始化——引脚复用初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //AF是引脚复用模式,即除了输入输出外的其他功能都需设置
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉电阻
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);//IO口复用配置
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);//IO口复用配置
//串口初始化
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据长度8位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位一位
USART_InitStructure.USART_Parity = USART_Parity_No; //无校验位 若有校验位,则长度应选择9位
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 发送以及接收模式
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流, 一般选无
//串口中断初始化
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//中断源
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能NVIC总中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级为0(最高)
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//次优先级为1
NVIC_Init(&NVIC_InitStructure);
//使能串口
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
//清楚标志,防止出现BUG
USART_ClearFlag(USART1, USART_FLAG_TC);
}
串口中断服务函数:
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1 ,USART_IT_TXE) == SET) //发送数据寄存器空(表示上次发送完成,可以继续进行下次发送),发送中断
{
USART_SendData(USART1, TXBUF[tx++]);
if (tx == 10)//发完一个数组的数据
{
USART_ITConfig(USART1,USART_IT_TXE,DISABLE); //关闭发送中断
}
}
else if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET) //接收寄存器空(表示上次接收完成,可以继续进行下一次接受)
{
rev = USART_ReceiveData(USART1);
USART_SendData(USART1, rev);//发送收到的数据(验证数据收到)
while(USART_GetITStatus(USART1,USART_IT_RXNE) == RESET);
}
}
主函数部分:
int main(void)
{
bsp_Init();
bsp_LedOn(1);
while(1)
{
if(bsp_CheckTimer(0))//每500ms
{
tx = 0;
USART_ITConfig(USART1,USART_IT_TXE,ENABLE);//使能
while(tx < 10);
}
keyvalue = bsp_GetKey();
if (keyvalue > 0)
{
switch (keyvalue)
{
case KEY_1_DOWN://发送一次那10个字符
{
tx = 0;
USART_ITConfig(USART1,USART_IT_TXE,ENABLE);//发送中断使能
}
break;
case KEY_2_DOWN: //不停发送 那十个字符 (利用定时器,每500ms发送一次)
{
bsp_StartAutoTimer(0, 500);//开启定时器
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
}
break;
case KEY_3_DOWN://停止发送
{
bsp_StopTimer(0);//关闭定时器
}
break;
case KEY_4_DOWN://接收
{
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//接收使能中断
}
break;
default:
break;
}
}
}
}
四个按键分别对应四个不同的功能。
DMA
DMA(Direct Memory Access,直接存储区访问)为实现数据高速在外设寄存器与存储器之间或者存储器与存储器之间传输提供了高效的方法。之所以称之为高效,是因为DMA 传输实现高速数据移动过程无需任何 CPU 操作控制。
从硬件层次上来说, DMA 控制器是独立于 Cortex-M4 内核的,有点类似 GPIO、USART 外设一般,只是 DMA 的功能是可以快速移动内存数据。
STM32F4xx 系列的 DMA 支持外设到存储器传输、存储器到外设传输和存储器到存储器传输三种传输模式。
这里的外设一般指外设的数据寄存器,比如 ADC、 SPI、 I2C、USART、TIM、 DCMI 等等外设的数据寄存器,
存储器一般是指片内 SRAM、外部存储器、片内 Flash 等等。
外设到存储器传输就是把外设数据寄存器内容转移到指定的内存空间。比如进行 ADC采集时我们可以利用 DMA 传输把 AD 转换数据转移到我们定义的存储区中,这样对于多通道采集、采样频率高、连续输出数据的 AD 采集是非常高效的处理方法。
存储区到外设传输就是把特定存储区内容转移至外设的数据寄存器中,这种多用于外设的发送通信。
存储器到存储器传输就是把一个指定的存储区内容拷贝到另一个存储区空间。功能类似于 C 语言内存拷贝函数 memcpy,利用 DMA 传输可以达到更高的传输效率,特别是DMA 传输是不占用 CPU 的,可以节省很多 CPU 资源。
以下是关于DMA_Init结构体的定义
typedef struct
{
uint32_t DMA_Channel; //通道选择
uint32_t DMA_PeripheralBaseAddr; //外设地址
uint32_t DMA_Memory0BaseAddr; //存储器 0 地址
uint32_t DMA_DIR; //传输方向
uint32_t DMA_BufferSize; //数据数目
uint32_t DMA_PeripheralInc; //外设递增
uint32_t DMA_MemoryInc; //存储器递增
uint32_t DMA_PeripheralDataSize; //外设数据宽度
uint32_t DMA_MemoryDataSize; //存储器数据宽度
uint32_t DMA_Mode; //模式选择
uint32_t DMA_Priority; //优先级
uint32_t DMA_FIFOMode; //FIFO 模式
uint32_t DMA_FIFOThreshold; //FIFO 阈值
uint32_t DMA_MemoryBurst; //存储器突发传输
uint32_t DMA_PeripheralBurst; //外设突发传输
} DMA_InitTypeDef;
DMA_Channel: DMA 请求通道选择,可选通道 0 至通道 7,每个外设对应固定的通道,具体设置值需要查表
DMA_PeripheralBaseAddr: 外设地址,设定 DMA_SxPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储区地址。这里我们定义为串口地址 (uint32_t) (&(USART1->DR))。
DMA_Memory0BaseAddr:存储器 0 地址,设定 DMA_SxM0AR 寄存器值;一般设置为我们自定义存储区的首地址,用来存放串口的数据的地址
DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设以及存储器到存储器,这里选择 DMA_DIR_MemoryToPeripheral(发送表示存储器到外设)
DMA_BufferSize:设定待传输数据数目,这里设置为一次dma串口发送的数据数DMA_PeripheralInc:如果配置为 DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定 DMA_SxCR 寄存器的 PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。
DMA_MemoryInc:如果配置为 DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定 DMA_SxCR 寄存器的 MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以使能存储器地址自动递增功能。
DMA_PeripheralDataSize: 外设数据宽度,可选字节(8 位)、半字(16 位)和字(32 位)这里选择8位
DMA_MemoryDataSize:存储器数据宽度,可选字节(8 位)、半字(16 位)和字(32位),这里选择8位。
DMA_Mode : DMA 传 输 模 式 选 择 , 可 选 一 次 传 输 或 者 循 环 传 输,这里选择一次传输
DMA_Priority:软件设置数据流的优先级,有 4 个可选优先级分别为非常高、高、中和低,这里设置为DMA_Priority_High
DMA_FIFOMode: FIFO 模式使能,如果设置为 DMA_FIFOMode_Enable 表示使能FIFO 模式功能,这里设置使能
DMA_FIFOThreshold: FIFO 阈值选择,可选 4 种状态分别为 FIFO 容量的 1/4、1/2、 3/4 和满
DMA_MemoryBurst:存储器突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式
DMA_PeripheralBurst:外设突发模式选择,可选单次模式、 4 节拍的增量突发模式、 8 节拍的增量突发模式或 16 节拍的增量突发模式
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory ; /* 设置从外设到内存,这里和TX的配置有所不同。
DMA初始化流程:
- 在串口初始化过程中开启DMA时钟最后使能DMA
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
2. 完成对DMA结构体变量的初始化
DMA_InitTypeDef DMA_InitStructure;
该结构体成员较多,需要重点关注以下几个成员
- FIFOMode
- MemoryBurst
- PeripheralBurst
3. 直接调用一次发送和接收DMA传输 实现串口DMA
4. 利用DMA标志查询 完成情况
while (DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7)==RESET);
DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF7);
while (DMA_GetFlagStatus(DMA2_Stream2,DMA_FLAG_TCIF2)==RESET);
/* 清除DMA传输完成标志 */
DMA_ClearFlag(DMA2_Stream2,DMA_FLAG_TCIF2);
/*
*********************************************************************************************************
* 函 数 名: USART_DMA_RxConfig
* 功能说明: 串口的DMA接受配置
* 形 参:BufferDST 目标地址指针
* BufferSize 缓冲区大小
* 返 回 值: 无
*********************************************************************************************************
*/
void USART_DMA_RxConfig(void)
{
DMA_InitTypeDef DMA_InitStructure;
/*
使能前检测DMA是否被禁止。
请注意,在使用相同数据流的多次传输时,这一步很有用:使能,然后禁止,然后使能
..... 在这种情况下,只有在数据传输结束的时候,DMA的禁止才有效,在确保使能位被
硬件清除之前做确认是不可能的,如果DMA传输只做一次,这一步可以忽略。
*/
DMA_Cmd(DMA2_Stream2, DISABLE);
while (DMA_GetCmdStatus(DMA2_Stream2) != DISABLE);
DMA_InitStructure.DMA_BufferSize = DMABUFFSIZE; /* 配置DMA大小 */
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; /* 在这个程序里面使能或者禁止都可以的 */
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; /* 设置阀值 */ //指定FIFO阈值水平
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single ; /* 设置内存为单字节突发模式 */
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;/* 设置内存数据的位宽是字节 */
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /* 使能地址自增 */
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; /* 设置DMA是正常模式 */
DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t) (&(USART1->DR)) ; /* 设置外设地址 */
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; /* 设置外设为单字节突发模式 */
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; /* 设置外设数据的位宽是字节 */
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; /* 禁止外设地址自增 */
DMA_InitStructure.DMA_Priority = DMA_Priority_High; /* 设置优先级 */
/* 配置 RX DMA */
DMA_InitStructure.DMA_Channel = DMA_Channel_4 ; /* 配置接收通道 */
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory ; /* 设置从外设到内存 */
DMA_InitStructure.DMA_Memory0BaseAddr =(uint32_t)aRxBuff ; /* 设置内存地址 */
DMA_Init(DMA2_Stream2,&DMA_InitStructure);
/* 使能 DMA USART TX Stream */
DMA_Cmd(DMA2_Stream2, ENABLE);
/*
检测DMA Stream 是否被正确的使能.如果DMA的参数配置错误了,那么DMA Stream
的使能位会被硬件清除掉,从而使得传输停止比如FIFO的阀值配置错误
*/
while (DMA_GetCmdStatus(DMA2_Stream2) != ENABLE)
{
}
}
void USART1_IRQHandler(void)
{
uint8_t i;
uint16_t data;
if(USART_GetITStatus(USART1 ,USART_IT_IDLE) == SET) //
{
DMA_Cmd(DMA2_Stream2, DISABLE); //关闭DMA,防止处理其间有数据
//data = USART1->SR;
data = USART1->DR;
USART_ClearITPendingBit(USART1, USART_IT_IDLE); //清除中断标志
uiUsart1RevLen = DMABUFFSIZE-DMA_GetCurrDataCounter(DMA2_Stream2); //算出接本帧数据长度
switch(uiUsart1RevLen)
{
case 4:
if (aRxBuff[3] == 1) // 发送AA 01 02 00 AA 01 02 00
// 无意义 无意义 LED2 灭(01亮)
{
bsp_LedOn(aRxBuff[2]);
}
else
{
bsp_LedOff(aRxBuff[2]);
}
break;
case 3:
break;
default:
break;
}
//////////////////////////////////////////////////////
DMA_ClearFlag(DMA2_Stream2,DMA_FLAG_TCIF2 | DMA_FLAG_FEIF2 | DMA_FLAG_DMEIF2 | DMA_FLAG_TEIF2 | DMA_FLAG_HTIF2);//清除DMA2_Steam2传输完成标志
DMA_SetCurrDataCounter(DMA2_Stream2,DMABUFFSIZE);
DMA_Cmd(DMA2_Stream2, ENABLE);
}
}
int main(void)
{
// uint8_t i;
uint8_t ucKeyValue =0;
bsp_Init(); /* 硬件初始化 */
PrintfLogo(); /* 串口打印结果 */
PCout(1) =1;
PCout(2) =0;
PCout(3) =1;
PCout(4) =0;
while (1)
{
ucKeyValue = bsp_GetKey();
if(ucKeyValue != KEY_NONE )
{
switch(ucKeyValue)
{
case KEY_1_UP: //发送数据 发一次
printf("KEY1 DOWN\r\n");
break;
case KEY_2_UP : //发送数据 定时发送 1000ms
printf("KEY2 DOWN\r\n");
break;
case KEY_3_UP : //停止发送
printf("KEY3 DOWN\r\n");
break;
case KEY_4_UP : //接收到15个数据 就发送这15个数据
printf("KEY4 DOWN\r\n");
break;
default:
break;
}
}
}
}
本次实验是用接收到的数据控制LED的亮灭。