用的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); } }
运行结果,两个串口都收发正常:
调试界面
标签:GCC,1CR1,tx,NVIC,Proteus,串口,usart2,Channel From: https://www.cnblogs.com/xgz21/p/17065785.html