STM32F401 Proteus 仿真 串口两种发送方式 编译用GCC ,寄存器配置方式

Posted xgz21

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F401 Proteus 仿真 串口两种发送方式 编译用GCC ,寄存器配置方式相关的知识,希望对你有一定的参考价值。

用的proteus 8.9 中文版,STM32F401可能是支持的最复杂的MCU了吧,就用这个做实验了。

编译器用GCC,在proteus中安装调试都很方便,编程实验用寄存器配置方式,因为仅仅是学习,简单直接,方便调试。

先在网上找一些中文资料,401的中文资料感觉不如103的多,就结合103的一起看。

在前几天学的STM32F103的基础上改改即可。

还是主要学一下串口,因为串口是最常见最通用的接口,并且涉及其他常用的时钟,中断等。

测试工程的建立过程和103一样:

先画个原理图,用虚拟示波器配合激励信号调试时钟,用虚拟中断调试串口。

双击MUC编辑固件,选GCC创建软件框架,和103有点不同,入口是一个汇编程序,而之前103的模板都是C

调试还是从时钟,GPIO开始,401的PLL工作正常,之前103的是不正常的。

接下来是中断,定时器,串口。

proteus中支持的这款401的管脚比较多,做实验很方便。

串口1配置用TC发送,TC是发送完中断,软件清标志,这里用查询TC标志的方式发送,不用缓冲区,简单直接

串口2配置用TXE发送,TXE是TDR空中断,这个只要空就中断,而不是变空才触发中断,也就是空了后要及时关闭中断,否则一直会中断。

原理图如下:

 

测试程序就一个main.c其他的proteus自动生成,就看看,不用改。

串口1 查询TC标志发送

串口2 用TXE中断,环形缓冲队列发送

如果用TC中断,缓冲区发,则第一个发送需要软件触发

/* Main.c file generated by New Project wizard
 *
 * Created:   2023-1-24
 * Processor: STM32F401VE
 * Compiler:  GCC for ARM
    配置时钟
    配置  GPIOD    
    配置通用定时器
   配置中断串口:USART1 单个字节查询TC状态发送。如果用中断缓冲队列,需要第一个发送触发,
                         USART2 用TXE中断,TXEIE开关控制,环形队列缓冲发送,
 */

#include <stm32f4xx.h>

#define u8 unsigned char 
#define u16 unsigned short
#define u32 unsigned int

void delay(int k)

int i;
for(i = 0; i<k; i++);



//
int XGZ_NVIC_PriorityGroupConfig(u8 NVIC_Group)

u32 temp;
temp = SCB->AIRCR;//读取先前的设置
temp&=0X0000F8FF; //清空先前分组

switch(NVIC_Group)

case 0:
    temp|= (0b111<<8); 
break;
case 1:
    temp|= (0b110<<8); 
break;
case 2:
    temp|= (0b101<<8); 
break;
case 3:
    temp|= (0b100<<8); 
break;
case 4:
    temp|= (0b011<<8); 
break;
default: 
     return -1;  //组号不能大于4
break;


temp|=0X05FA0000; //写入钥匙
SCB->AIRCR=temp; //设置分组
return 1;


//抢占优先级,响应优先级, 中断号,      
void XGZ_NVIC_Init(u8 NVIC_PreemptionPriority,u8 NVIC_SubPriority,u8 NVIC_Channel)

u32 temp;

temp = SCB->AIRCR;//读取先前的设置
temp&=0X00000700; //读取先前分组
NVIC->IP[NVIC_Channel]&=0x0F; //高4位清0
switch(temp)

case 0X00000700: //0
    NVIC->IP[NVIC_Channel]|= (NVIC_SubPriority&0xf)<<4;
break;
case 0X00000600: //1
    NVIC->IP[NVIC_Channel]|= (NVIC_PreemptionPriority&0x1)<<7;
    NVIC->IP[NVIC_Channel]|= (NVIC_SubPriority&0x7)<<4;
break;
case 0X00000500: //2
    NVIC->IP[NVIC_Channel]|= (NVIC_PreemptionPriority&0x3)<<6;
    NVIC->IP[NVIC_Channel]|= (NVIC_SubPriority&0x3)<<4;
break;
case 0X00000400: //3
    NVIC->IP[NVIC_Channel]|= (NVIC_PreemptionPriority&0x7)<<5;
    NVIC->IP[NVIC_Channel]|= (NVIC_SubPriority&0x1)<<4;
break;
case 0X00000300: //4
    NVIC->IP[NVIC_Channel]|= (NVIC_PreemptionPriority&0xF)<<4;
break;
default: 
     return;  //会不会没有分组号
break;



NVIC->ISER[NVIC_Channel/32]|=(1<<NVIC_Channel%32);  //中断使能,ISER[8] 每一位是一个中断,103的头文件中42个中断
//NVIC->ICER[NVIC_Channel/32]|=(1<<NVIC_Channel%32);  //中断清除使能,因为寄存器是写1有效
//NVIC->ISPR[NVIC_Channel/32]|=(1<<NVIC_Channel%32);  //中断挂起,
//NVIC->ICPR[NVIC_Channel/32]|=(1<<NVIC_Channel%32);  //中断清除挂起,
//NVIC->IABR[NVIC_Channel/32] == 1;//中断激活标志,只读,表示中断正在执行


void clk4_init(void)


RCC->CR|=RCC_CR_HSION;

//配置时钟 
RCC->CR&=~(1<<24);    //关闭主PLL
// PLLQ 外设48M分频系数:0111 7分频率;PLLSRC :1 HSE 作为源; PLLP:PLLCLK分频系数 00  2分频; PLLN:倍频系数 192-432;PLLM  VCCO主分频系数8
// [HSI] -PLLM -PLLN -PLLP -[PLLCLK]-        [8 ] / 8 X 336 / 2 =168
RCC->PLLCFGR=(7<<24)|(1<<22)|(0<<16)|(336<<6)|(8<<0);
RCC->CR|=1<<24;            //打开主PLL

RCC->CFGR|=(4<<13)|(5<<10)|(0<<4);//PPRE2  100 :APB2 2分频 ; PPRE1 101:APB1 4分频;    HPRE:0XXX: HCLK 不分频; HCLK =168M = FCLK

RCC->CFGR|=2<<0;           //选择主PLL作为系统时钟      168M
//RCC->CFGR|=1<<0;        //选择主HSE作为系统时钟     
//RCC->CFGR|=0<<0;        //选择主HSi作为系统时钟    



//4个寄存器配置

void gpio4_init(void)

     
     RCC->AHB1ENR |=0b11111;   //GPIO  ABCDE全打开

     GPIOD->MODER =0xFFFF0000;  //  高8位输出,低8位输入
     GPIOD->OTYPER =0;               //输出推挽
     GPIOD->OSPEEDR = 0xAAAAAAAA;  // 10输出50M
     GPIOD->PUPDR = 0x55555555;   // 端口上拉, 00浮空,01上拉,10下拉
     GPIOD->ODR = 0xFFFFFFFF; //输出 1
   


void sendstring(u8 chan, char* s);

int t3cnt=0;
int t3cnt10s=0;

void TIM3_IRQHandler(void)

if(TIM3->SR&0X0001) 


    t3cnt++;
    if(t3cnt>1)
    
        GPIOD->ODR |= (1<<10);   
        t3cnt = 0;
    
    else
    
        GPIOD->ODR&=~(1<<10);
    

    t3cnt10s++;
    if(t3cnt10s>1000)
    
        t3cnt10s = 0;
        sendstring(2, "This is TIM3 INT 1000 test.\\r\\n");
    


TIM3->SR&=~(1<<0);//清除中断标志位



void TIM3_Int_Init(u16 arr,u16 psc)

RCC->APB1ENR|=1<<1; //TIM3 时钟使能
TIM3->ARR=arr; //设定计数器自动重装值
TIM3->PSC=psc; //预分频器 
TIM3->DIER|=0x01; //允许更新中断

//TIM3->CR1&=~((1<<4)); //向上和下计数,应该都是0 和 装载值之差

TIM3->CR1|=(1<<7)|(1<<0); //自动装载?, 使能定时器 3,

XGZ_NVIC_Init(1,3,TIM3_IRQn);//抢占 1,子优先级 3,



void Usart1_Config(u32 brr)

    float div=0;
    u32 div_m=0;//存放整数部分
    u32 div_f=0;//存放小数部分
    //开时钟 PA USART1
    RCC->AHB1ENR |= (1<<0);
    RCC->APB2ENR |= (1<<4);

    //配置工作模式 p9 p10 复用模式做 usart1
    GPIOA->AFR[1] &=~ (0xff<<4);
    GPIOA->AFR[1] |= (0x7<<4);
    GPIOA->AFR[1] |= (0x7<<8);
    //PA9 复用推挽输出
     GPIOA->MODER  &=~ (3<<18);
    GPIOA->MODER |= (2<<18);
    GPIOA->OTYPER &=~ (1<<9);
    GPIOA->OSPEEDR &=~ (3<<18);
    GPIOA->OSPEEDR |= (2<<18);
    GPIOA->PUPDR &=~ (3<<18);
    //pa10 
    GPIOA->MODER &=~ (3<<20);
    GPIOA->MODER |= (2<<20);
    GPIOA->PUPDR &=~ (3<<20);
    //配置串口
    USART1->CR1 &=~ (1<<15);// over8   16位采样
    //USART1->CR1 |= (1<<15);// 8位采样
    USART1->CR1 &=~ (1<<12);//字长1+8+n
    USART1->CR1 &=~ (1<<10);    //禁止奇偶校验
    USART1->CR2 &=~ (3<<12); //停止位
   
    
  //波特率uart2 在AHB2上 时钟2分频
    div = 168000000.0/2/16/brr;   // FCLK /(8 X(2-0) )/ USARTDIV ;  OVER8=0 16倍过采样
    div_m = (u32)div;                //浮点整数位 (12bit)
    div_f = (div-div_m)*16;       //浮点小数位(4bit)
    USART1->BRR = (div_m<<4)|div_f;
   
    USART1->CR1|=1<<2;              //串口接收使能
    USART1->CR1|=1<<3;              //串口发送使能  
    USART1->CR1|=1<<5;                //接收缓冲区非空中断使能    
   // USART1->CR1|=1<<6;                //TCIE   发送完成中断    
   // USART1->CR1|=1<<7;                //TXEIE   USART_SR 变空中断    
    USART1->CR1|=1<<13;              //串口使能  

    XGZ_NVIC_Init(3,3,USART1_IRQn);// 抢占3,响应3,


void usart2_init(u32 pclk1,u32 bound)
       
    
    float div=0;
    u32 div_m=0;//存放整数部分
    u32 div_f=0;//存放小数部分
         
    RCC->AHB1ENR |= (1<<0);
    RCC->APB1ENR |= (1<<17); //uart2 clk

    GPIOA->AFR[0] &=~ (0xff<<8);
    GPIOA->AFR[0] |= (0x7<<8);
    GPIOA->AFR[0] |= (0x7<<12);
    //PA2 复用推挽输出
    GPIOA->MODER  &=~ (3<<4);
    GPIOA->MODER |= (2<<4);
    GPIOA->OTYPER &=~ (1<<2);
    GPIOA->OSPEEDR &=~ (3<<4);
    GPIOA->OSPEEDR |= (2<<4);
    GPIOA->PUPDR &=~ (3<<4);
    //pa3 
    GPIOA->MODER &=~ (3<<6);
    GPIOA->MODER |= (2<<6);
    GPIOA->PUPDR &=~ (3<<4);

    //配置串口
    USART2->CR1 &=~ (1<<15);// over8   16位采样
    //USART2->CR1 |= (1<<15);// 8位采样
    USART2->CR1 &=~ (1<<12);//字长1+8+n
    USART2->CR1 &=~ (1<<10);    //禁止奇偶校验
    USART2->CR2 &=~ (3<<12); //停止位
   
    //波特率 uart2 在AHB1上 时钟4分频
   
    div = (float)(pclk1*1000000)/4/16/bound;  
   // div = 168000000.0/4/16/bound;   // FCLK /(8 X(2-0) )/ USARTDIV ;  OVER8=0 16倍过采样
    div_m = (u32)div;                //浮点整数位 (12bit)
    div_f = (div-div_m)*16;       //浮点小数位(4bit)
    USART2->BRR = (div_m<<4)|div_f;
           
    //波特率设置
     //USART2->BRR=mantissa;             // 波特率设置     
    
    USART2->CR1|=1<<2;              //串口接收使能
    USART2->CR1|=1<<3;              //串口发送使能  
    USART2->CR1|=1<<5;                //接收缓冲区非空中断使能    
    //USART2->CR1|=1<<6;                //TCIE   发送完成中断,没数据时不会中断,平时不用关闭    
   // USART2->CR1|=1<<7;                //TXEIE   USART_TDR 是空中断, 没数据会一直中断,只有发数据时才能打开    
    USART2->CR1|=1<<13;              //串口使能  
    
    //MY_NVIC_Init(0,0,USART3_IRQn,2);//组2,优先级0,0,最高优先级 
     XGZ_NVIC_Init(3,3,USART2_IRQn);// 抢占3,响应3,
    



void USART1_IRQHandler(void)

    unsigned char res;
   

    if(USART1->SR&(1<<5))  //RXNE
    
         res=USART1->DR;

         USART1->DR=res;
         while((USART1->SR&0X40)==0);//等待发送结束
      
    

    if(USART1->SR&(1<<6)) //TC
    
         USART1->SR&=~(1<<6);   //软件清除TC标志
     

    if(USART1->SR&(1<<7)) //TXE   
     
          USART1->CR1&=~(1<<7);   //关闭
        



int usart2_rx_p1 = 0;
int usart2_rx_p2 = 0;
int usart2_rx_overflow = 0;

int usart2_tx_p1 = 0;
int usart2_tx_p2 = 0;
int usart2_tx_overflow = 0;

#define USART_BUF_RXMAX  64
char usart2_rxbuf[USART_BUF_RXMAX]=0;

#define USART_BUF_TXMAX  64
char usart2_txbuf[USART_BUF_TXMAX]=0;

void USART2_IRQHandler(void)

    unsigned char res;

    if(USART2->SR&(1<<5))  //RXNE
    
         res=USART2->DR;

         USART2->DR=res;
         while((USART2->SR&0X40)==0);//等待发送结束
      
    

    if(USART2->SR&(1<<6)) //TC
    
         USART2->SR&=~(1<<6);   //软件清除TC状态标志
     
    
    if(USART2->SR&(1<<7)) //TXE   
    

         if(usart2_tx_p2 == usart2_tx_p1)  // 队列空 ,关闭TXEIE 中断
        
           
              USART2->CR1&=~(1<<7);                //TXEIE   USART_TDR 是空中断, 没数据会一直中断,只有发数据时才能打开    
        
        else
        
               USART2->DR=usart2_txbuf[usart2_tx_p1] ;
               usart2_tx_p1++;
               if(usart2_tx_p1 == USART_BUF_TXMAX)
               
                      usart2_tx_p1 = 0;       
               
         
         
     
       


void fputc(char c)

    while((USART1->SR & (1<<6)) == 0);
    USART1->DR = c;
   



void fputc2(char c)

    
    usart2_txbuf[usart2_tx_p2]  = c;
    usart2_tx_p2++;
    
    if(usart2_tx_p2 == USART_BUF_TXMAX)
    
         usart2_tx_p2 = 0;
    

    if(usart2_tx_p2 == usart2_tx_p1)
    
           usart2_tx_overflow = 1;  //overflow
     

     USART2->CR1|=1<<7;                //TXEIE   USART_TDR 是空中断, 没数据会一直中断,只有发数据时才能打开    
     


void sendstring(u8 chan, char* s)

    while(*s)

  
switch(chan)

case 1:
fputc(*s++);
break;
case 2:
fputc2(*s++);
break;
default:
fputc(*s++);
break;


   


EXTI0_IRQHandler()

   fputc2(\'1\');
   EXTI->PR|= 1<<0;  //挂起寄存器,写入1 清除


int main (void)
  
 
clk4_init();
XGZ_NVIC_PriorityGroupConfig(2); //为系统配置中断分组2

gpio4_init();

Usart1_Config(9600);

usart2_init(168,9600);

TIM3_Int_Init(1000, 6-1);  // 1k:  6M/2 x2 APB1 2分频 , TMR时钟源是分频APB的2倍,ABP1不分频的话直接用

 RCC->APB2ENR |= (1<<2)|(1<<0);
//GPIOA->CRL &=0xFFFFFFF0;
//GPIOA->CRL |=0x00000008;

 GPIOA->MODER &=~ (3<<0);
GPIOA->MODER |= (2<<0);
GPIOA->PUPDR &=~ (3<<0);

GPIOA->ODR |=1<<0;
   

NVIC->ISER[0] |= 1<<6;     //
EXTI->IMR|= 1<<0;        //0 线 中断使能
EXTI->FTSR= 1<<0;       // 下降沿触发使能
//AFIO->EXTICR[0] |=0;   //PA0

sendstring(1, "This is uart1 test.");
sendstring(2, "This is uart2 test.");
 while (1)

if(GPIOD->IDR & 0x01)

GPIOD->ODR |= (1<<8)|(1<<9);   
delay(100);

GPIOD->ODR &= ~((1<<8)|(1<<9));   
delay(100);




    



 

 运行结果,两个串口都收发正常:

 

 调试界面

 

以上是关于STM32F401 Proteus 仿真 串口两种发送方式 编译用GCC ,寄存器配置方式的主要内容,如果未能解决你的问题,请参考以下文章

11-CubeMx+Keil+Proteus仿真STM32 -串口单字节通信

STM32CubeMX-HAL库开发笔记-基于Proteus仿真

关于proteus中仿真STM32F103芯片的注意事项

基于STM32的超声波测距proteus仿真 HC-SR04(仿真+源码)

开源电路STM32F401RCT6开发板

stm32的温湿度采集Proteus仿真