1、编写UART_2串口发送程序时,初始化需要设置哪些参数?
//uart寄存器相关地址
volatile uint32_t* RCC_AHB2; //GPIO的A口时钟使能寄存器地址
volatile uint32_t* RCC_APB1; //UART的2口时钟使能寄存器地址
volatile uint32_t* gpio_ptr; //GPIO的A口基地址
volatile uint32_t* uart_ptr; //uart2端口的基地址
volatile uint32_t* gpio_mode; //引脚模式寄存器地址=口基地址
volatile uint32_t* gpio_afrl; //GPIO复用功能低位寄存器
volatile uint32_t* uart_brr; //UART波特率寄存器地址
volatile uint32_t* uart_isr; // UART中断和状态寄存器基地址
volatile uint32_t* uart_cr1; //UART控制寄存器1基地址
volatile uint32_t* uart_cr2; // UART控制寄存器2基地址
volatile uint32_t* uart_cr3; // UART控制寄存器3基地址
volatile uint32_t* uart_tdr; // UART发送数据寄存器
uint16_t usartdiv; //BRR寄存器应赋的值
//添加此寄存器地址
volatile uint32_t* uart_rdr;
//变量赋值
uart_rdr=0x40004424UL;
RCC_APB1=0x40021058UL; //UART时钟使能寄存器地址
RCC_AHB2=0x4002104CUL; //GPIO的A口时钟使能寄存器地址
gpio_ptr=0x48000000UL; //GPIOA端口的基地址
uart_ptr=0x40004400UL; //UART2端口的基地址
gpio_mode=0x48000000UL; //引脚模式寄存器地址=口基地址
gpio_afrl=0x48000020UL; // GPIO复用功能低位寄存器
uart_cr1=0x40004400UL; //UART控制寄存器1基地址
uart_brr=0x4000440CUL; // UART波特率寄存器地址
uart_isr=0x4000441CUL; // UART中断和状态寄存器基地址
uart_tdr=0x40004428UL; //UART发送数据寄存器
uart_cr2=0x40004404UL; // UART控制寄存器2基地址
uart_cr3=0x40004408UL; //UART控制寄存器3基地址
gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON); //初始化蓝灯
//uart_init(UART_User,115200)
//使能GPIOA和UART2的时钟
*RCC_APB1|=(0x1UL<<17U); //UART2时钟使能
*RCC_AHB2 |=(0x1UL<<0U); //GPIOA时钟使能
//将GPIO端口设置为复用功能
//首先将D7、D6、D5、D4清零
*gpio_mode &= ~((0x3UL<<4U)|(0x3UL<<6U));
//然后将D7、D6、D5、D4设为1010,设置PTA2、PTA3为复用功能串行功能。
*gpio_mode |=((0x2UL<<4U)|(0x2UL<<6U));
//选择引脚的端口复用功能
//首先将D15~D8清零
*gpio_afrl &= ~((0xFUL<<8U)|(0xFUL<<12U));
//然后将D15~D8设置为01110111,分别将PTA3、PTA2引脚设置为USART2_RX、USART2_TX
*gpio_afrl=(((0x1UL<<8U)|(0x2UL<<8U)|(0x4UL<<8U))|((0x1UL<<12U)
|(0x2UL<<12U)|(0x4UL<<12U)));
//暂时禁用UART功能,控制寄存器1的第0位对应的是UE—USART使能位。
//此位清零后,USART预分频器和输出将立即停止,并丢弃所有当前操作。
*uart_cr1 &= ~(0x1UL);
//暂时关闭串口发送与接收功能,控制寄存器1的发送器使能位(D3)、接收器使能位(D2)
*uart_cr1 &= ~((0x1UL<<3U)|(0x1UL<<2U));
//配置波特率
if(*uart_cr1&(0x1UL<<15) == (0x1UL<<15))
usartdiv = (uint16_t)((SystemCoreClock/115200)*2);
else
usartdiv = (uint16_t)((SystemCoreClock/115200));
*uart_brr = usartdiv;
//初始化控制寄存器和中断状态寄存器、清标志位
//关中断
*uart_isr = 0x0UL;
//将控制寄存器2的两个使能位清零。D14—LIN模式使能位、D11—时钟使能位
*uart_cr2 &= ~((0x1UL<<14U)|(0x1UL<<11U));
//将控制寄存器3的三个使能位清零。D5 (SCEN) —smartcard模式使能位、
//D3 (HDSEL) —半双工选择位、D1 (IREN) —IrDA 模式使能位
*uart_cr3 &= ~((0x1UL<<5U) | (0x1UL<<3U) |(0x1UL<<1U))
//启动串口发送与接收功能
*uart_cr1 |= ((0x1UL<<3U)|(0x1UL<<2U))
//开启UART功能
*uart_cr1 |= (0x1UL<<0U);
//(1.6)使能模块中断
uart_enable_re_int(UART_User); ENABLE_INTERRUPTS;
2、假设速度为115200,系统时钟为72MHz,波特率寄存器BRR中的值应该是多少?
通过代码
if(*uart_cr1&(0x1UL<<15) == (0x1UL<<15))
usartdiv = (uint16_t)((SystemCoreClock/115200)*2);
else
usartdiv = (uint16_t)((SystemCoreClock/115200));
*uart_brr = usartdiv有两种情况,可能为1250,可能为625.
3、中断向量表在哪个文件中?表中有多少项?给出部分截图。
一共有98项。
4、以下是中断源使能函数,假设中断源为TIM6,将函数实例化(写出各项具体数值)。
利用函数__STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn)实例化。
各项具体数值:
刚刚看到,传入的IRQn将是54,通过函数的if语句,结合其左右移操作等,得到的返回值:
return((uint32_t)(((NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] & (1UL << (((uint32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL));
将会是0x00400000。
5、假设将UART_2和TIM6交换其在中断向量表中的位置和IRQ号, UART_2可以正常中断吗?
理论上来说,更换位置和IRQ号,并不会影响其中断的正常,中断向量号通过初始化从篇后,映射到IRQ中,只要对应的IRQ号对应的中断处理程序,没问题就不会出错。
- 程序源码
我将实验步骤也放在这里:
准备导入工程:
(1) 用构件调用方式实现;
说明:
对涉及的函数进行说明:
Uart_init 传入串口号及波特率 初始化串口
使用串口前,我们将使用该函数进行对串口模块的初始化。
Uart_send1 对指定的串口发送1字节数据
Uart_send_string 向指定的串口发送字符串。
gpio_init 初始化灯的参数。
uart_enable_re_int 使某个模块接收中断功能。
特殊说明:
在程序中将会使用 UART_User 此宏,代表用户串口 UART_2
以及UART_USER_handler 指的是我们即将实现的MZL_USART2_IRQHANDLET
这里在前面加上我名字的前缀。
我将urat2串口的中断处理程序也放在了main.c文件中(即实现收到字符打印下一个字符或亮灯的函数操作)。Main函数进行各种初始化。
Main.c代码:
#define GLOBLE_VAR
#include "includes.h" //包含总头文件
void MZL_USART2_IRQHandler(void)
{
uint8_t ch;
uint8_t flag;
gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON);
gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_ON);
gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_ON);
flag =0;
DISABLE_INTERRUPTS;
ch=uart_re1(UART_User,&flag); //flag将由cpu赋值 若flag的值不为0 即有数据
if(flag)
{
if(ch=='R')
{
gpio_set(LIGHT_RED,LIGHT_ON);
gpio_set(LIGHT_GREEN,LIGHT_OFF);
gpio_set(LIGHT_BLUE,LIGHT_OFF);
uart_send_string(UART_User,(uint8_t *)"receive the character:");
uart_send1(UART_User,ch);
uart_send_string(UART_User,(uint8_t *)",the Red light is on :");
uart_send1(UART_User,ch+1);
uart_send_string(UART_User,(uint8_t *)" ");
}
else if(ch=='G')
{
gpio_set(LIGHT_RED,LIGHT_OFF);
gpio_set(LIGHT_GREEN,LIGHT_ON);
gpio_set(LIGHT_BLUE,LIGHT_OFF);
uart_send_string(UART_User,(uint8_t *)"receive the character:");
uart_send1(UART_User,ch);
uart_send_string(UART_User,(uint8_t *)",the Green light is on :");
uart_send1(UART_User,ch+1);
uart_send_string(UART_User,(uint8_t *)" ");
}
else if(ch=='B')
{
gpio_set(LIGHT_RED,LIGHT_OFF);
gpio_set(LIGHT_GREEN,LIGHT_OFF);
gpio_set(LIGHT_BLUE,LIGHT_ON);
uart_send_string(UART_User,(uint8_t *)"receive the character:");
uart_send1(UART_User,ch);
uart_send_string(UART_User,(uint8_t *)",the Blue light is on :");
uart_send1(UART_User,ch+1);
uart_send_string(UART_User,(uint8_t *)" ");
}
else
{
gpio_set(LIGHT_RED,LIGHT_OFF);
gpio_set(LIGHT_GREEN,LIGHT_OFF);
gpio_set(LIGHT_BLUE,LIGHT_OFF);
uart_send_string(UART_User,(uint8_t *)"had received the character:");
uart_send1(UART_User,ch);
uart_send_string(UART_User,(uint8_t *)"the next character is :");
uart_send1(UART_User,ch+1);
uart_send_string(UART_User,(uint8_t *)" ");
}
}
ENABLE_INTERRUPTS; //开总中断
}
int main(void)
{
uint32_t mMainLoopCount =0 ;
DISABLE_INTERRUPTS;//为保证以下的初始化操作不被打断 暂时关闭中断
uart_init(UART_User,115200);
uart_enable_re_int(UART_User); //使能UART_User模块接收中断功能
ENABLE_INTERRUPTS;
printf("红发阿龙的实验:\n Enter the next character , I will display the next character!!!!!!!!!\n");
while(mMainLoopCount<=200000000){
mMainLoopCount++;
if(mMainLoopCount==200000000)mMainLoopCount=0;
}
}
五、运行结果
编译工程:
进行串口更新:
接下来打开串口工具,发送对应的数据到串口中,发起中断,使得对应的中断处理程序进行反应。
输入字符没反应,后面才知道,是因为这里选择错了。
回到串口更新看芯片链接的是哪个串口。
是COM12;
(2) UART部分用直接地址方式实现(即不调用uart.c中的函数,其他部分如GPIO、中断设置可调用函数)。
由于main.c文件里,并没有UART发送数据寄存器,所以要添加。
由于我们进行的是直接地址方式实现,我们在中断处理程序里,不能调用uart.c等文件的函数,所以要获得我们的对应的串口地址如UART发送数据寄存器,UART_接收数据寄存器等地址。
另外,在USART2_IRQHandler函数里,地址使用uint8_t的目的是,选择此指针为8位的跳跃力。
并且,通过usar_isr寄存器进行判断,第五位检测是否有数据,若有数据,根据该字符进行对应的操作,并回发该字符的下一个字符,即ch+1。
更新成功。
发送字符操作:
代码:
Mian.c:
#define GLOBLE_VAR
#include "includes.h" //包含总头文件
int main(void)
{
uint8_t mTest;
uint32_t mCount;
//uart寄存器相关地址
volatile uint32_t* RCC_AHB2; //GPIO的A口时钟使能寄存器地址
volatile uint32_t* RCC_APB1; //UART的2口时钟使能寄存器地址
volatile uint32_t* gpio_ptr; //GPIO的A口基地址
volatile uint32_t* uart_ptr; //uart2端口的基地址
volatile uint32_t* gpio_mode; //引脚模式寄存器地址=口基地址
volatile uint32_t* gpio_afrl; //GPIO复用功能低位寄存器
volatile uint32_t* uart_brr; //UART波特率寄存器地址
volatile uint32_t* uart_isr; // UART中断和状态寄存器基地址
volatile uint32_t* uart_cr1; //UART控制寄存器1基地址
volatile uint32_t* uart_cr2; // UART控制寄存器2基地址
volatile uint32_t* uart_cr3; // UART控制寄存器3基地址
volatile uint32_t* uart_tdr; // UART发送数据寄存器
uint16_t usartdiv; //BRR寄存器应赋的值
//添加此寄存器地址
volatile uint32_t* uart_rdr;
//变量赋值
uart_rdr=0x40004424UL;
RCC_APB1=0x40021058UL; //UART时钟使能寄存器地址
RCC_AHB2=0x4002104CUL; //GPIO的A口时钟使能寄存器地址
gpio_ptr=0x48000000UL; //GPIOA端口的基地址
uart_ptr=0x40004400UL; //UART2端口的基地址
gpio_mode=0x48000000UL; //引脚模式寄存器地址=口基地址
gpio_afrl=0x48000020UL; // GPIO复用功能低位寄存器
uart_cr1=0x40004400UL; //UART控制寄存器1基地址
uart_brr=0x4000440CUL; // UART波特率寄存器地址
uart_isr=0x4000441CUL; // UART中断和状态寄存器基地址
uart_tdr=0x40004428UL; //UART发送数据寄存器
uart_cr2=0x40004404UL; // UART控制寄存器2基地址
uart_cr3=0x40004408UL; //UART控制寄存器3基地址
//(1.2)【不变】关总中断
DISABLE_INTERRUPTS;
//(1.3)给主函数使用的局部变量赋初值
mCount=0;
//(1.4)给全局变量赋初值
//(1.5)用户外设模块初始化
gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON); //初始化蓝灯
//uart_init(UART_User,115200);
//使能GPIOA和UART2的时钟
*RCC_APB1|=(0x1UL<<17U); //UART2时钟使能
*RCC_AHB2 |=(0x1UL<<0U); //GPIOA时钟使能
//将GPIO端口设置为复用功能
//首先将D7、D6、D5、D4清零
*gpio_mode &= ~((0x3UL<<4U)|(0x3UL<<6U));
//然后将D7、D6、D5、D4设为1010,设置PTA2、PTA3为复用功能串行功能。
*gpio_mode |=((0x2UL<<4U)|(0x2UL<<6U));
//选择引脚的端口复用功能
//首先将D15~D8清零
*gpio_afrl &= ~((0xFUL<<8U)|(0xFUL<<12U));
//然后将D15~D8设置为01110111,分别将PTA3、PTA2引脚设置为USART2_RX、USART2_TX
*gpio_afrl=(((0x1UL<<8U)|(0x2UL<<8U)|(0x4UL<<8U))|((0x1UL<<12U)
|(0x2UL<<12U)|(0x4UL<<12U)));
//暂时禁用UART功能,控制寄存器1的第0位对应的是UE—USART使能位。
//此位清零后,USART预分频器和输出将立即停止,并丢弃所有当前操作。
*uart_cr1 &= ~(0x1UL);
//暂时关闭串口发送与接收功能,控制寄存器1的发送器使能位(D3)、接收器使能位(D2)
*uart_cr1 &= ~((0x1UL<<3U)|(0x1UL<<2U));
//配置波特率
if(*uart_cr1&(0x1UL<<15) == (0x1UL<<15))
usartdiv = (uint16_t)((SystemCoreClock/115200)*2);
else
usartdiv = (uint16_t)((SystemCoreClock/115200));
*uart_brr = usartdiv;
//初始化控制寄存器和中断状态寄存器、清标志位
//关中断
*uart_isr = 0x0UL;
//将控制寄存器2的两个使能位清零。D14—LIN模式使能位、D11—时钟使能位
*uart_cr2 &= ~((0x1UL<<14U)|(0x1UL<<11U));
//将控制寄存器3的三个使能位清零。D5 (SCEN) —smartcard模式使能位、
//D3 (HDSEL) —半双工选择位、D1 (IREN) —IrDA 模式使能位
*uart_cr3 &= ~((0x1UL<<5U) | (0x1UL<<3U) |(0x1UL<<1U));
//启动串口发送与接收功能
*uart_cr1 |= ((0x1UL<<3U)|(0x1UL<<2U));
//开启UART功能
*uart_cr1 |= (0x1UL<<0U);
//(1.6)使能模块中断
uart_enable_re_int(UART_User); ENABLE_INTERRUPTS;
printf("MZL 实验二 !!!") ;
while(1);
}
Isr.c文件:
#include "includes.h"
//内部函数
void User_SysFun(uint8_t ch)
{
if(gcRecvLen == 0) gcRecvLen =useremuart_frame(ch,(uint8_t*)gcRecvBuf);
if(gcRecvLen == 0) goto User_SysFun_Exit;
if(strncmp((char *)(gcRecvBuf),(char *)((MCU_SECTOR_NUM-1)*MCU_SECTORSIZE+
MCU_FLASH_ADDR_START),16) != 0)
{
gcRecvLen = 0; //恢复接收状态
goto User_SysFun_Exit;
}
switch(gcRecvBuf[16]) //帧标识
{
case 0:
SYSTEM_FUNCTION((uint8_t *)(gcRecvBuf+17));
gcRecvLen = 0; //恢复接收状态
break;
default:
break;
}
User_SysFun_Exit:
return;
}
void USART2_IRQHandler(void)
{
uint8_t ch;
uint8_t flag;
volatile uint8_t *uart_isr=0x4000441CUL;
volatile uint8_t *uart_rdr=0x40004424UL;
volatile uint8_t *uart_tdr=0x40004428UL;
gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_ON);
gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_ON);
gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_ON); //这里里一共 uint8_t 目的是选择此指针为8位的跳跃力
DISABLE_INTERRUPTS;
//接收一个字节的数据
if (*uart_isr & (0x1UL<<5UL))
{
ch = *uart_rdr;
flag=1;
}
if(flag) //有数据
{
if(ch=='R')
{
gpio_set(LIGHT_RED,LIGHT_ON);
gpio_set(LIGHT_GREEN,LIGHT_OFF);
gpio_set(LIGHT_BLUE,LIGHT_OFF);
}
else if(ch=='G')
{
gpio_set(LIGHT_RED,LIGHT_OFF);
gpio_set(LIGHT_GREEN,LIGHT_ON);
gpio_set(LIGHT_BLUE,LIGHT_OFF);
}
else if(ch=='B')
{
gpio_set(LIGHT_RED,LIGHT_OFF);
gpio_set(LIGHT_GREEN,LIGHT_OFF);
gpio_set(LIGHT_BLUE,LIGHT_ON);
}
else
{
gpio_set(LIGHT_RED,LIGHT_OFF);
gpio_set(LIGHT_GREEN,LIGHT_OFF);
gpio_set(LIGHT_BLUE,LIGHT_OFF);
}
*uart_tdr = ch+1; //发送下一个字符
}
ENABLE_INTERRUPTS;
}
- 分析思考
(1)此实验给我的感悟就是,嵌入式和操作系统内核的编写都属于难度比较大的开发,由于牵涉到很多串口,底层的原理,所以不像机器学习还有算法等,只需要懂原理还有思想即可,但嵌入式开发等,都需要巨大的知识的储备,如各种调用约定,地址的设定,当我点开看到整个工程文件,我深刻理解到这是一个完整的微型操作系统,而本实验仅仅是编写了其中一个中断处理程序,由于上学期我学过如何编写操作系统内核,即操作系统真相还原,故理解起来还比较轻松,所以学习课程的一大益处即是,能够让你的学习能力进一步提升,而不仅仅只是知识层面的提升。
(2)知识要素:
1.本文件中的中断处理函数调用的均是相关设备封装好的具体构件,在更换芯片
时,只需保证设备的构件接口一致,即可保证本文件的相关中断处理函数不做任何
更改,从而达到芯片无关性的要求。
这个在原工程文件就有的一段注释我觉得是精华,我们开发的一个很重要的一个思考的地方即 -- 移植性,如果你对一个硬件就要开发一种软件,可想而知,世界上该有多少软件,而这里由于封装的问题,使得我们如果更换别的芯片时,只需要保证构建结构一致即可,达到了芯片无关性。这让我想起了操作系统的一个重要思想,即设备无关性。