STM32(8)-DMA+ 직렬 포트는 듀얼 개발 보드 데이터 전송 및 수신을 실현합니다.

Jiangke University의 비디오와 CSDN의 거물 블로그를 공부하여 DMA에 대한 이해를 아래에 기록했습니다.

1. 메모리, 레지스터

메모리의 경우 이전 기사에서 ROM과 RAM, 특히 SRAM, 플래시 및 주변 레지스터에 대해 썼는데 여기서 Jiang Keda는 이해에 도움이 되는 지식 포인트를 언급했습니다.
먼저 예를 들어 16진수를 정의합니다:
uint8_t a=0x66
그런 다음 컴파일 후 SRAM에서 a에 대한 주소 공간이 열립니다. 스램입니다.
const 키워드를 사용하면 변수가 상수가 되고
const uint8_t a=0x66
이면 이제 a가 플래시에 저장되고 주소는 0x080000FF가 될 수 있습니다. 플래시 스토리지는 읽기 전용이므로 상수를 변경할 수 없으므로 읽기 전용 속성입니다. 따라서 일부 글꼴 또는 조회 테이블의 경우 const를 추가하여 플래시에 저장하여 SRAM 공간을 해제할 수 있습니다.

또한 주변기기 레지스터의 주소를 조회하고자 하는 경우 먼저 데이터 시트에서 메모리 이미지를 조회하여 USART2의 시작 주소를 찾은 다음 USART2 장에서 특정 레지스터 이미지를 찾으면 됩니다. 오프셋, 시작 주소 + 오프셋 = 특정 레지스터 주소. 코드에서 찾으려면:
예: USART3의 DATAR 데이터 레지스터 주소를 찾으려면
여기에 이미지 설명 삽입
USART2의 시작 주소와 오프셋을 찾아야 합니다. 아래 그림과 같이
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
USART3의 기본 주소는 APB1 주변 장치의 기본 주소 + 오프셋 0x4800입니다.
여기에 이미지 설명 삽입
또한 플래시, SRAM 및 버스 주변 장치의 기본 주소를 볼 수 있으며 이는 APB1 주변 장치의 기본 주소가 0x40000000임을 나타냅니다. 그러면 USART3 주소의 기본 주소를 알 수 있습니다. USART3의 오프셋은 무엇입니까? 여기에 영리한 방법이 사용됩니다. 즉, 구조 변수가 오프셋을 참조합니다.
여기에 이미지 설명 삽입
이때 USART의 구조체 멤버 변수는 주소에서 실제 레지스터와 같은 순서로 구조체의 각 멤버는 각 실제 레지스터에 매핑될 뿐 실제로 지정된 구조체 멤버의 주소는 주소와 일치합니다. 해당 주변 레지스터의. 이것은 오프셋 문제를 해결합니다. &USART3->DATAR는 USART3의 구조 포인터를 지정하고 DATAR 멤버를 가리키는 것은 오프셋 주소를 추가하는 것입니다.

임베디드
시스템에서 일반적으로 사용되는 데이터 유형에는 uint8_t, uint16_t, uint32_t 등이 있습니다. 비트 수가 적어 메모리 공간을 절약할 수 있기 때문입니다. 데이터 전송 중에 실제 필요에 따라 적절한 데이터 유형을 선택하여 전송할 수 있습니다.
uint8_t: 8비트 부호 없는 정수를 나타내고 값 범위는 0~255
uint16_t: 16비트 부호 없는 정수를 나타내고 값 범위는 0~65535
uint32_t: 32비트 부호 없는 정수를 나타내며 값 범위는 0~4294967295 .
마찬가지로 int8_t는 부호 있는 8비트 정수 유형이며 값 범위는 -128~127입니다.
u8과 uint8_t의 기능은 동일하며 둘 다 부호 없는 8비트 정수를 나타내는 데 사용됩니다. 그러나 u8은 일반적으로 일부 특정 시나리오(예: 임베디드 컴파일러, uint8_t는 C 언어의 내장 유형)에서 사용자 지정 유형입니다. 즉, 예를 들어 보유한 데이터가 255를 초과하지 않는
경우 배열을 uint8_t data[100]으로 설정할 수 있어 메모리 공간을 크게 절약할 수 있습니다.동시에 예를 들어 직렬 포트는 1바이트 단위로 데이터를 송수신하고 8비트 정수를 설정하는 것도 도움이 됩니다. 직렬 포트의 기능.

두, 특정 코드

여기에서 이중 개발 보드를 사용합니다. 하나는 STM32F103RCT6 개발 보드이고 다른 하나는 Qinheng CH32V307 개발 보드입니다.실현할 기능은 다음과 같습니다: STM32는 DMA+직렬 포트를 사용하는 발신자로서 온도 및 습도 값을 생성하는 두 가지 기능을 생성합니다
. 어레이에 저장되고 어레이의 데이터는 직렬 포트 + DMA를 통해 수신 측으로 전송됩니다 .
DMA+직렬 포트를 사용하는 수신기로서 CH32V307은 수신된 배열의 데이터를 구별하고 직렬 포트 디버깅 도우미로 출력합니다 .

1. STM32(발신자) DMA 구성

(1) 발신자로서 데이터는 자연스럽게 메모리에서 직렬 포트 데이터 레지스터로 전송됩니다 배열을 정의하고 배열의 요소로 각각 온도 및 습도 값을 반환하는 두 가지 함수를 작성합니다.

uint8_t data[2];
uint8_t get_temperature(){
    
    
	data[0]=rand() % 126 ;
    return data[0];// 限定温度在-40到85摄氏度之间
}
uint8_t get_humi(){
    
    
    data[1]=rand() % 101; // 限定湿度在0到100%之间
	return data[1];
}

(2) DMA 구성, 방향은 메모리에서 주변 레지스터로

여기서는 직렬 포트 2가 사용됩니다. 데이터 시트를 확인하고 USART2의 TX 기능이 DMA1의 채널 7에 해당하므로 구성할 때 주의하십시오.

여기에 이미지 설명 삽입
먼저 직렬 포트 2를 정상적으로 구성하고 전송 속도는 9600입니다.
아래와 같이 코드 쇼:

void Init_USART2(){
    
    
  
	GPIO_InitTypeDef GPIO_InitStructure;//声明一个结构体对象
	USART_InitTypeDef  USART_InitStructure;
	//NVIC_InitTypeDef NVIC_InitStructure;
	
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//USART2挂载APB1总线
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIOA挂载APB2总线
  //对于哪个应用挂载哪个APB总线,可以根据代码自动补全功能快捷判断

	//TX端口-PA2
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;//这个对象的成员变量GPIO_Pin取值为pin2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//模式为复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//50MHZ速度
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//RX端口-PA3
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//这个对象的成员变量GPIO_Pin取值为pin3
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//模式为浮空输入模式
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	USART_InitStructure.USART_BaudRate = 9600;
	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;//1位停止位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//八位数据位
	USART_Init(USART2,&USART_InitStructure);
	//USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);//开启串口2的中断接收
	USART_Cmd(USART2,ENABLE);
	
}

(3) DMA 초기화 구성

void USART2_DMA_Tx_Configuration(void)
{
    
    
	DMA_InitTypeDef  DMA_InitStructure;
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2 , ENABLE);						//DMA2时钟使能
	DMA_DeInit(DMA1_Channel7);
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR;		//DMA外设地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART2_DMA_TX_Buffer;	//发送缓存指针
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;						//传输方向,从内存到外设
    DMA_InitStructure.DMA_BufferSize = USART2_DMA_TX_BUFFER_MAX_LENGTH;		//传输长度
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;		//外设递增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;				//内存递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//外设数据宽度:BYTE
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;			//内存数据宽度:BYTE
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;							//循环模式:否//(注:DMA_Mode_Normal为正常模式,DMA_Mode_Circular为循环模式)
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; 				//优先级:高
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 							//内存:内存(都)
	DMA_Init(DMA1_Channel7 , &DMA_InitStructure);							//初始化DMA1_Channel4
	//DMA_ClearFlag(DMA1_FLAG_GL4);
	DMA_ClearFlag(DMA1_FLAG_GL7);
	DMA_Cmd(DMA1_Channel7 , DISABLE); 										//禁用DMA通道传输
	USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);                          //开启串口DMA发送
}

(4) DMA는 전송 기능을 켭니다.

void DMA_send(){
    
    
  //开启计数器,在传输过程中,DMA控制器会持续地递减该计数器的值,直到计数器为0,表示数据传输完成。
  int len = sizeof(data);
  memcpy(USART2_DMA_TX_Buffer, (uint8_t*)data, len);
  DMA_SetCurrDataCounter(DMA1_Channel7,USART2_DMA_TX_BUFFER_MAX_LENGTH);
  DMA_Cmd(DMA1_Channel7, ENABLE);//开启DMA传输
  while(DMA_GetFlagStatus(DMA1_FLAG_TC7) != SET);
 
  DMA_Cmd(DMA1_Channel7, DISABLE);//关闭DMA传输
  DMA_ClearFlag(DMA1_FLAG_TC7);

}

(5) 메인 프로그램

 int main(void)
 {
    
    	
	 delay_init();	    //延时函数初始化	  
	 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	 uart_init(9600);
	 Init_USART2();
	 USART2_DMA_Tx_Configuration();
	 printf("666");
	while(1)
	{
    
       get_humi();
		get_temperature();
		DMA_send();
		delay_ms(1000);
	}
 }

2. CH32V307(수신기)은 일반 직렬 포트 인터럽트 수신을 채택합니다.

먼저 일반적인 직렬 포트 수신 방법을 사용하십시오.

void USART2_IRQHandler(void)
{
    
    
    u8 Res;
    int i;
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
        {
    
    
      static uint8_t idx=0;//当前接收到的字节数
      static uint8_t* ptr=(uint8_t*)res_data;//将数据转化为字节数组  
      Res=USART_ReceiveData(USART2);//读取接收到的字节
      if(idx<data_length*sizeof(int)){
    
    //如果数据还未接受完
          ptr[idx++]=Res;//将接收到的数据存储到数组中
      }
      if (idx==data_length*sizeof(int)) {
    
    //如果数据接收完毕
          idx=0;
        for ( i = 0; i < data_length;i++) {
    
    
            res_data[i]=*((int*)(ptr+i*sizeof(int)));
        }
    }
      USART_ClearITPendingBit(USART2, USART_IT_RXNE); // 清除接收中断标志位
        }

}

이 방법은 비교적 일반적이며 CPU 리소스를 차지합니다.예를 들어 100바이트의 데이터를 보내면 CPU는 자주 100개의 인터럽트를 입력합니다. 전송된.

3. CH32V307(수신기) DMA 구성

또한 CH32V307의 직렬 포트 2를 사용하여 DMA 수신을 구성합니다.

(1) 수신기로서 자연 데이터는 직렬 포트 데이터 레지스터에서 메모리로 전송되어야 하므로 DMA 구성을 변경해야 합니다.

//DMA1的通道6对应USART2的RX
void DMA_RX_init(){
    
    
    DMA_InitTypeDef  DMA_InitStructure;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE);                     //DMA2时钟使能

    DMA_DeInit(DMA1_Channel6);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DATAR;       //DMA外设地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART2_RxBuf;    //发送缓存指针
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                        //传输方向,从外设到内存
    DMA_InitStructure.DMA_BufferSize = USART_MAX_LEN;       //传输长度
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;      //外设递增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;               //内存递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;   //外设数据宽度:BYTE
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;           //内存数据宽度:BYTE
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                         //循环模式:否//(注:DMA_Mode_Normal为正常模式,DMA_Mode_Circular为循环模式)
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;               //优先级:高
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                          //内存:内存(都)
    DMA_Init(DMA1_Channel6 , &DMA_InitStructure);                           //初始化DMA1_Channel4
        //DMA_ClearFlag(DMA1_FLAG_GL4);
    //DMA_ClearFlag(DMA1_FLAG_GL6);
    DMA_Cmd(DMA1_Channel6 , DISABLE);                                       //禁用DMA通道传输
    USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);                          //开启串口DMA接收
    USART_Cmd(USART2, ENABLE);      //使能串口
}

(2) DMA 시작 프로그램

void USART2_Server(){
    
    

        uint16_t i,len;
       // len = USART_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel6);    // 获取接收到的数据长度 单位为字节
   //DMA_SetCurrDataCounter(DMA1_Channel6,USART_MAX_LEN); // 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目
   DMA_Cmd(DMA1_Channel6 , ENABLE);
   DMA_SetCurrDataCounter(DMA1_Channel6,USART_MAX_LEN); // 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目
        //USART_ReceiveData(USART2);                                      // 清除空闲中断标志位(接收函数有清标志位的作用)
     //printf("data=%d\r\n",USART_ReceiveData(USART2));
    // printf("data1=%d\r\n",USART2_RxBuf[0]);
        //DMA_Cmd(DMA1_Channel6, DISABLE);                                // 关闭DMA1_Channel6不再接收数据

        while(DMA_GetFlagStatus(DMA1_FLAG_TC6)==RESET);
        DMA_Cmd(DMA1_Channel6, DISABLE);
        DMA_ClearFlag(DMA1_FLAG_TC6);                                   // 清DMA1_Channel6接收完成标志位
        //DMA_SetCurrDataCounter(DMA1_Channel6,USART_MAX_LEN);            // 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目
        //for (i = 0; i < len; ++i) {                                     // 把接收到的数据转移到发送数组
          //  res_data[i] = USART2_RxBuf[i];
         //   printf("res%d=%d\r\n",i,res_data[i]);
        }

(3) 메인 프로그램

extern uint8_t USART2_RxBuf[USART_MAX_LEN];   //接收缓存
extern uint8_t res_data[2];
int main(void)
{
    
    
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	SystemCoreClockUpdate();
	Delay_Init();
	USART_Printf_Init(115200);
	printf("SystemClk:%d\r\n", SystemCoreClock);
	//printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );
	printf("RTC Test\r\n");
   // USART3_INIT(9600);
    uart2_init(9600);
    DMA_RX_init();
    printf(" Test\r\n");
 	while(1)
    {
    
    
 	    USART2_Server();
 	    printf("res_data[0]=%d\r\n",USART2_RxBuf[0]);
 	    printf("res_data[1]=%d\r\n",USART2_RxBuf[1]);
 	    Delay_Ms(1000);
    }
}

3. 결과:

여기에 이미지 설명 삽입
송신측에서 임의의 데이터를 생성하면 데이터가 메모리에서 직렬 포트 2의 데이터 레지스터로 전송되어 수신측으로 전송됩니다. 수신단의 데이터 레지스터는 DMA에 의해 지정된 메모리로 전송됩니다. 그림과 같이 실험 결과가 정확합니다.

요약하다

DMA는 정말 유용합니다. 하지만 아직 DMA+ 인터럽트를 사용하지 않았으므로 나중에 시도할 수 있습니다.

추천

출처blog.csdn.net/qq_53092944/article/details/130673554