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->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;              //串口使能  
 
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); } }

 

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

 

 调试界面

 

posted @ 2023-01-24 08:57  XGZ21  阅读(1081)  评论(0编辑  收藏  举报