目录
前言
一、DMA及各外设介绍
二、实验现象
串口发送
LCD显示
三、代码部分
1.adc部分
adc.c
adc.h
2.串口部分
serial.c
serial.h
3.DMA配置
dma.c
dma.h
4.main函数
main.c
总结
前言
基于stm32f103zet6,将串口收发不定长数据和adc采集通过使用dma搬运数据,提高单片机cpu的运行效率,加深对dma和adc功能的学习。
一、DMA及各外设介绍
可以参考之前文章,这里只贴出相关代码。
【32单片机学习】(6)STM32串口+DMA收发不定长数据_dma 不定长数据的收发_Godox_user的博客-CSDN博客
二、实验现象
串口发送
LCD显示
三、代码部分
1.adc部分
adc.c
这里使用的是ADC1的通道16,stm32内部自带的温度传感器;
ADC3的通道6,在GPIOF_PIN_8上接的光敏传感器。
由于是ADC1和ADC3两个不同组ADC,所以要分开单独进行测量,当ADC1中有多个ADC设备时,可以打开连续扫描模式。
#include "adc.h"
#include "delay.h"
#include "serial.h"uint16_t AD_Value1[1],AD_Value2[1];void Adc_Init(void)
{ADC_InitTypeDef ADC_InitStructure;ADC_InitTypeDef ADC_InitStructure1; GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF | RCC_APB2Periph_ADC1| RCC_APB2Periph_ADC3, ENABLE ); //使能ADC1通道时钟RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14MGPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚GPIO_Init(GPIOF, &GPIO_InitStructure); ADC_DeInit(ADC1); //复位ADC1 ADC_DeInit(ADC3); //复位ADC1 ADC_TempSensorVrefintCmd(ENABLE); //开启内部温度传感器ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //模数转换工作在单次转换模式ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器 ADC_InitStructure1.ADC_Mode = ADC_Mode_Independent; //ADC工作模式:ADC1和ADC2工作在独立模式ADC_InitStructure1.ADC_ScanConvMode = ENABLE; //模数转换工作在单通道模式ADC_InitStructure1.ADC_ContinuousConvMode = ENABLE; //模数转换工作在单次转换模式ADC_InitStructure1.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动ADC_InitStructure1.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐ADC_InitStructure1.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目 ADC_Init(ADC3, &ADC_InitStructure1); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器 ADC_RegularChannelConfig(ADC1,ADC_Channel_16,1,ADC_SampleTime_55Cycles5);ADC_RegularChannelConfig(ADC3,ADC_Channel_6,1,ADC_SampleTime_55Cycles5);DMA_Cmd(DMA1_Channel1,ENABLE); DMA_Cmd(DMA2_Channel5,ENABLE); ADC_Cmd(ADC1,ENABLE); //使能指定的ADC1ADC_Cmd(ADC3,ENABLE); //使能指定的ADC1ADC_DMACmd(ADC1,ENABLE);ADC_DMACmd(ADC3,ENABLE);ADC_ResetCalibration(ADC1); //使能复位校准while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束 ADC_StartCalibration(ADC1); //开启AD校准 while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束 ADC_SoftwareStartConvCmd(ADC1, ENABLE);ADC_ResetCalibration(ADC3); //使能复位校准 while(ADC_GetResetCalibrationStatus(ADC3)); //等待复位校准结束ADC_StartCalibration(ADC3); //开启AD校准while(ADC_GetCalibrationStatus(ADC3)); //等待校准结束ADC_SoftwareStartConvCmd(ADC3, ENABLE);
}short Get_Temprate(void) //获取内部温度传感器温度值
{u32 adcx=0;short result=0;double temperate=0;adcx=AD_Value1[0]; //读取通道16,20次取平均//USART1_printf(" temp:%d\r\n",adcx);temperate=(float)adcx*(3.3/4096); //电压值 temperate=(1.43-temperate)/0.0043+25; //转换为温度值 result=temperate*=100; //扩大100倍.return result;
}//读取Light Sens的值
//0~100:0,最暗;100,最亮
u8 Lsens_Get_Val(void)
{u32 temp_val=0;temp_val=AD_Value2[0];//得到平均值 //USART1_printf(" light:%d\r\n",temp_val);if(temp_val>4000)temp_val=4000;return (u8)(100-(temp_val/40));
}
adc.h
#ifndef __ADC_H__
#define __ADC_H__
#include "sys.h"extern uint16_t AD_Value1[1],AD_Value2[1];void Adc_Init(void);
uint16_t Get_ADValue1(u8 ch);
uint16_t Get_ADValue2(u8 ch);
short Get_Temprate(void);
u8 Lsens_Get_Val(void);#endif
2.串口部分
串口部分使用了串口1,使用串口接收的空闲中断,利用双缓存区的方法对DMA接收到的数据进行转运,防止数据产生覆盖和缓存空间的溢出。通过可变参串口打印函数,使其可以收发不定长数据,将printf只能用于单串口的局限性消除。
serial.c
#include "serial.h"uint8_t USART1_TX_BUF[USART1_MAX_TX_LEN]; //发送缓冲,最大USART1_MAX_TX_LEN字节
uint8_t u1rxbuf[USART1_MAX_RX_LEN]; //发送数据缓冲区1
uint8_t u2rxbuf[USART1_MAX_RX_LEN]; //发送数据缓冲区2
uint8_t witchbuf=0; //标记当前使用的是哪个缓冲区,0:使用u1rxbuf;1:使用u2rxbuf
uint8_t USART1_TX_FLAG; //USART1发送标志,启动发送时置1
uint8_t USART1_RX_FLAG; //USART1接收标志,启动接收时置1void Serial_Init(u32 bound)
{USART_InitTypeDef USART_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;//开启串口1和外设GPIO时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//初始化串口的GPIO口GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//发送TXD GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//接收RXDGPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化串口结构体USART_InitStructure.USART_BaudRate=bound;//串口波特率USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode=USART_Mode_Tx| USART_Mode_Rx;//收发模式USART_InitStructure.USART_Parity=USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_StopBits=USART_StopBits_1;//一个停止位USART_InitStructure.USART_WordLength=USART_WordLength_8b;//字长为8位数据格式USART_Init(USART1,&USART_InitStructure);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 8;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);//DMA1通道5 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn; //NVIC通道设置NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器//DMA1通道4 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn; //NVIC通道设置NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);//开启串口接收中断USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启串口空闲中断USART_ClearFlag(USART1,USART_FLAG_TC);//清除USART1标志位USART_Cmd(USART1,ENABLE);//使能串口1
}void Serial_SendByte(uint8_t Byte)
{USART_SendData(USART1, Byte);while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}void Serial_SendArray(uint8_t *Array, uint16_t Length)
{uint16_t i;for (i = 0; i < Length; i ++){Serial_SendByte(Array[i]);}
}void Serial_SendString(char *String)
{uint8_t i;for (i = 0; String[i] != '\0'; i ++){Serial_SendByte(String[i]);}
}uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{uint32_t Result = 1;while (Y --){Result *= X;}return Result;
}void Serial_SendNumber(uint32_t Number, uint8_t Length)
{uint8_t i;for (i = 0; i < Length; i ++){Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');}
}int fputc(int ch, FILE *f)
{Serial_SendByte(ch);return ch;
}//可变参串口打印函数
void USART1_printf(char *format, ...)
{va_list arg_ptr; //实例化可变长参数列表while(USART1_TX_FLAG); //等待上一次发送完成(USART1_TX_FLAG为1即还在发送数据)va_start(arg_ptr, format); //初始化可变参数列表,设置format为可变长列表的起始点(第一个元素)// USART1_MAX_TX_LEN+1可接受的最大字符数(非字节数,UNICODE一个字符两个字节), 防止产生数组越界vsnprintf((char*)USART1_TX_BUF, USART1_MAX_TX_LEN+1, format, arg_ptr); //从USART1_TX_BUF的首地址开始拼合,拼合format内容;USART1_MAX_TX_LEN+1限制长度,防止产生数组越界va_end(arg_ptr); //注意必须关闭DMA_USART1_Tx_Data(USART1_TX_BUF,strlen((const char*)USART1_TX_BUF)); //发送USART1_TX_BUF内容
}//串口通过DMA 发送数据
//buffer-->发送数据地址
//size-->发送的字节大小
void DMA_USART1_Tx_Data(u8 *buffer, u32 size)
{while(USART1_TX_FLAG); //等待上一次发送完成(USART1_TX_FLAG为1即还在发送数据)USART1_TX_FLAG=1; //USART1发送标志(启动发送)DMA1_Channel4->CMAR = (uint32_t)buffer; //设置要发送的数据地址DMA1_Channel4->CNDTR = size; //设置要发送的字节数目DMA_Cmd(DMA1_Channel4, ENABLE); //开始DMA发送
}//DMA1通道4中断,判断上次数据串口是否已经发送完成
void DMA1_Channel4_IRQHandler(void)
{if(DMA_GetITStatus(DMA1_IT_TC4)!= RESET) //DMA接收完成标志{DMA_ClearITPendingBit(DMA1_IT_TC4); //清除中断标志 USART_ClearFlag(USART1,USART_FLAG_TC); //清除串口1的标志位DMA_Cmd(DMA1_Channel4, DISABLE ); //关闭USART1 TX DMA1 所指示的通道USART1_TX_FLAG=0; //USART1发送标志(关闭)}
}//串口1中断函数
void USART1_IRQHandler(void)
{uint8_t *p;uint8_t USART1_RX_LEN = 0; //接收数据长度if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) //串口1空闲中断{USART_ReceiveData(USART1); //清除串口1空闲中断IDLE标志位USART_ClearFlag(USART1,USART_FLAG_TC); //清除USART1标志位DMA_Cmd(DMA1_Channel5, DISABLE ); //关闭USART1 TX DMA1 所指示的通道USART1_RX_LEN = USART1_MAX_RX_LEN - DMA1_Channel5->CNDTR; //获得接收到的字节数if(witchbuf) //之前用的u1rxbuf,切换为u2rxbuf{p=u2rxbuf; //先保存前一次数据地址再切换缓冲区DMA1_Channel5->CMAR=(u32)u1rxbuf; //切换为u2rxbuf缓冲区地址witchbuf=0; //下一次切换为u1rxbuf}else //之前用的u1rxbuf,切换为u1rxbuf{p=u1rxbuf; //先保存前一次数据地址再切换缓冲区DMA1_Channel5->CMAR=(u32)u2rxbuf; //切换为u1rxbuf缓冲区地址witchbuf=1; //下一次切换为u1rxbuf}DMA1_Channel5->CNDTR = USART1_MAX_RX_LEN; //DMA通道的DMA缓存的大小DMA_Cmd(DMA1_Channel5, ENABLE); //使能USART1 TX DMA1 所指示的通道//******************数据处理******************// DMA_USART1_Tx_Data(p,USART1_RX_LEN);}
}
serial.h
#ifndef __SERIAL_H
#define __SERIAL_H#include "sys.h"
#include <stdio.h>
#include <stdarg.h>
#define USART1_MAX_TX_LEN 256
#define USART1_MAX_RX_LEN 256
extern uint8_t USART1_TX_FLAG; //USART1发送标志,启动发送时置1
extern uint8_t USART1_RX_FLAG; //USART1接收标志,启动接收时置1
extern size_t strlen(const char *);
extern uint8_t USART1_TX_BUF[USART1_MAX_TX_LEN]; //发送缓冲,最大USART1_MAX_TX_LEN字节
extern uint8_t u1rxbuf[USART1_MAX_RX_LEN]; //发送数据缓冲区1
extern uint8_t u2rxbuf[USART1_MAX_RX_LEN]; //发送数据缓冲区2
void Serial_Init(u32 bound);
void USART1_printf(char *format, ...);
void DMA_USART1_Tx_Data(u8 *buffer, u32 size);void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
uint32_t Serial_Pow(uint32_t X, uint32_t Y);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
int fputc(int ch, FILE *f);#endif
3.DMA配置
dma部分用到DMA1通道1、4、5分别作内部温度、串口发送、串口接收的数据转运;DMA2通道5用于光敏传感器数据转运,转运ADC是将数据从ADC的数据寄存器(ADCX->DR)转运到SRAM的数组AD_Value的数组中,转运串口是将接收的数据从串口的数据寄存器(USARTX->DR)转运到USARTX_TX_BUF的数据接收缓冲区中。
dma.c
#include "dma.h"
#include "adc.h"void MyDMA_Init(void)
{DMA_InitTypeDef DMA_InitStructure;DMA_InitTypeDef DMA_InitStructure1;DMA_InitTypeDef DMA_InitStructure2;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);DMA_DeInit(DMA1_Channel1);//内部温度传感器DMA_InitStructure1.DMA_MemoryBaseAddr=(uint32_t)AD_Value1;DMA_InitStructure1.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;DMA_InitStructure1.DMA_MemoryInc=DMA_MemoryInc_Disable;DMA_InitStructure1.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;DMA_InitStructure1.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;DMA_InitStructure1.DMA_PeripheralInc=DMA_PeripheralInc_Disable;DMA_InitStructure1.DMA_BufferSize=1;DMA_InitStructure1.DMA_DIR=DMA_DIR_PeripheralSRC;DMA_InitStructure1.DMA_M2M=DMA_M2M_Disable;DMA_InitStructure1.DMA_Mode=DMA_Mode_Circular;DMA_InitStructure1.DMA_Priority=DMA_Priority_High;DMA_Init(DMA1_Channel1,&DMA_InitStructure1); DMA_DeInit(DMA2_Channel5);//PF8DMA_InitStructure2.DMA_MemoryBaseAddr=(uint32_t)AD_Value2;DMA_InitStructure2.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;DMA_InitStructure2.DMA_MemoryInc=DMA_MemoryInc_Disable;DMA_InitStructure2.DMA_PeripheralBaseAddr=(uint32_t)&ADC3->DR;DMA_InitStructure2.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;DMA_InitStructure2.DMA_PeripheralInc=DMA_PeripheralInc_Disable;DMA_InitStructure2.DMA_BufferSize=1;DMA_InitStructure2.DMA_DIR=DMA_DIR_PeripheralSRC;DMA_InitStructure2.DMA_M2M=DMA_M2M_Disable;DMA_InitStructure2.DMA_Mode=DMA_Mode_Circular;DMA_InitStructure2.DMA_Priority=DMA_Priority_High;DMA_Init(DMA2_Channel5,&DMA_InitStructure2); DMA_DeInit(DMA1_Channel4); //tx 发送数据DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART1_TX_BUF; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = USART1_MAX_TX_LEN; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;DMA_Init(DMA1_Channel4,&DMA_InitStructure); DMA_DeInit(DMA1_Channel5); DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)u1rxbuf; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = USART1_MAX_RX_LEN; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;DMA_InitStructure.DMA_Priority=DMA_Priority_Medium; DMA_Init(DMA1_Channel5,&DMA_InitStructure); DMA_ITConfig(DMA1_Channel5,DMA_IT_TC,ENABLE); //开USART2 Rx DMA中断DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); //开USART2 Tx DMA中断DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE); //开USART2 Rx DMA中断DMA_ITConfig(DMA2_Channel5,DMA_IT_TC,ENABLE); //开USART2 Tx DMA中断DMA_Cmd(DMA1_Channel5,ENABLE); //使DMA通道5停止工作DMA_Cmd(DMA1_Channel4,DISABLE); //使DMA通道4停止工作USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); //开启串口DMA发送USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); //开启串口DMA接收}
dma.h
#ifndef __DMA_H
#define __DMA_H#include "sys.h"
#include "serial.h"void MyDMA_Init(void);#endif
4.main函数
main.c
#include "delay.h"
#include "lcd.h"
#include "dma.h"
#include "serial.h"
#include "adc.h"int main(void)
{ short temp=0;u8 adcx;delay_init();Serial_Init(115200);MyDMA_Init();LCD_Init();Adc_Init();POINT_COLOR=RED;//设置字体为红色 LCD_ShowString(30,140,200,16,16,"TEMPERATE: 00.00C");LCD_ShowString(30,156,200,16,16,"LSENS_VAL:"); ADC_SoftwareStartConvCmd(ADC1, ENABLE);ADC_SoftwareStartConvCmd(ADC3, ENABLE);while(1){temp=Get_Temprate(); //得到温度值 if(temp<0){temp=-temp;LCD_ShowString(30+10*8,140,16,16,16,"-"); //显示负号}else LCD_ShowString(30+10*8,140,16,16,16," "); //无符号 LCD_ShowxNum(30+11*8,140,temp/100,2,16,0); //显示整数部分LCD_ShowxNum(30+14*8,140,temp%100,2,16, 0X80); //显示小数部分USART1_printf(" TEMPERATE:%d.%d\r\n",temp/100,temp%100);adcx=Lsens_Get_Val();LCD_ShowxNum(30+10*8,156,adcx,3,16,0);//显示ADC的值 USART1_printf(" LSENS_VAL:%d\r\n",adcx);delay_ms(250);}
}
总结
以上就是今天要讲的内容,本文是笔者在stm32学习中将adc采集和串口外设接收的数据通过DMA进行转运,进一步理解DMA的配置和转运数据的机制,加深对个外设的理解