一、作业1
1、编写UART_2串口发送程序时,初始化需要设置哪些参数
设置GPIOA口,UART2时钟,然后设置对应端口的复用。
配置波特率,过采样因子。开启uart功能,启动串口接收、发送功能。
//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寄存器应赋的值
//变量赋值
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_RED,GPIO_OUTPUT,LIGHT_OFF);
gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF);
gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF);
//使能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);
2、假设速度115200,系统时钟72MHz,波特率寄存器BRR值
通过这个公式来计算波特率寄存器BRR值
- 当过采样因子为8:USARTDIV=2*72MHz / 115200 = 1250
- 当过采样因子为16:USARTDIV=72MHz / 115200 = 625
3、中断向量表在哪个文件中?表中有多少项?
在 startup_stm32l431rctx.s 文件内,具体路径\03_MCU\startup\startup_stm32l431rctx.s
4、以下是中断源使能函数,假设中断源为TIM6,将函数实例化(写出各项具体数值)
寻找中断源TIM6的中断IRQ号为54
IRQn = 54;
((uint32_t)IRQn) >> 5UL //即 54 右移 5位,值为1,即NVIC->ISER[1]
((uint32_t)IRQn) & 0x1FUL //得到值为22
因此将ISER[1] 的第22位置为1
5、假设将UART_2和TIM6交换其在向量表中的位置和IRQ号,UART_2是否可以正常中断
可以发现数据已经是发送成功了,只是没有接收到我们想要的结果
现在在irs.c文件添加TIM6的中断处理函数
可以发现已经可以回显字符。首先可以得到的是UART_2交换后其实是可以正常中断的,但是当我们改变了中断向量表位置和IRQ号后,其执行的对应中断函数也发生了改变,变成了TIM6的中断处理函数。
二、作业2(同实验)
实现UART_2串口的接收程序,当收到字符时:在电脑的输出窗口显示下一个字符,如收到A显示B;亮灯:收到字符G,亮绿灯;收到字符R,亮红灯;收到字符B,亮蓝灯;收到其他字符,不亮灯。
实现方式:
(1) 用构件调用方式实现;
main.c文件
#define GLOBLE_VAR
#include "includes.h" //包含总头文件
int main(void)
{
//(1.2)【不变】关总中断
DISABLE_INTERRUPTS;
//初始化串口模块
uart_init(UART_User,115200);
//接受中断使能
uart_enable_re_int(UART_User);
//(1.5)用户外设模块初始化
//初始化灯
gpio_init(LIGHT_RED,GPIO_OUTPUT,LIGHT_OFF);
gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF);
gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF);
//(1.7)【不变】开总中断
ENABLE_INTERRUPTS;
printf("lxy -- 32106200054");
}
isr.c文件
//=====================================================================
//文件名称:isr.c(中断处理程序源文件)
//框架提供:SD-ARM(sumcu.suda.edu.cn)
//版本更新:20170801-20191020
//功能描述:提供中断处理程序编程框架
//移植规则:【固定】
//=====================================================================
#include "includes.h"
UART_User_Handler(void)
{
uint8_t ch;
uint8_t flag;
//关中断
DISABLE_INTERRUPTS;
//读取一个字节
ch = uart_re1(UART_User, &flag);
//判断是否接收到一个字节
if(flag)
{
if(ch == 'G')
{
//发送下一个字符
uart_send1(UART_User, (ch+1));
//
uart_send_string(UART_User,(uint8_t*)"LXY 亮绿灯");
gpio_set(LIGHT_RED, LIGHT_OFF);
gpio_set(LIGHT_BLUE, LIGHT_OFF);
gpio_set(LIGHT_GREEN, LIGHT_ON);
}
else if(ch == 'R')
{
//发送下一个字符
uart_send1(UART_User, (ch+1));
//
uart_send_string(UART_User,(uint8_t*)"LXY 亮红灯");
gpio_set(LIGHT_GREEN, LIGHT_OFF);
gpio_set(LIGHT_BLUE, LIGHT_OFF);
gpio_set(LIGHT_RED, LIGHT_ON);
}
else if(ch == 'B')
{
//发送下一个字符
uart_send1(UART_User, (ch+1));
//
uart_send_string(UART_User,(uint8_t*)" LXY 亮蓝灯");
gpio_set(LIGHT_RED, LIGHT_OFF);
gpio_set(LIGHT_GREEN, LIGHT_OFF);
gpio_set(LIGHT_BLUE, LIGHT_ON);
}
else
{
//发送下一个字符
uart_send1(UART_User, (ch+1));
//不亮灯
gpio_set(LIGHT_RED, LIGHT_OFF);
gpio_set(LIGHT_GREEN, LIGHT_OFF);
gpio_set(LIGHT_BLUE, LIGHT_OFF);
}
}
//开中断
ENABLE_INTERRUPTS;
}
调用构件方式还是非常方便快捷的。
实验的结果
2) UART部分用直接地址方式实现(即不调用uart.c中的函数,其他部分如GPIO、中断设置可调用函数)。
main.c文件
#define GLOBLE_VAR
#include "includes.h" //包含总头文件
//----------------------------------------------------------------------
//声明使用到的内部函数
//main.c使用的内部函数声明处
//----------------------------------------------------------------------
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
int main(void)
{
//(1)======启动部分(开头)==========================================
//(1.1)声明main函数使用的局部变量
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寄存器应赋的值
//变量赋值
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_RED,GPIO_OUTPUT,LIGHT_OFF);
gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF);
gpio_init(LIGHT_BLUE,GPIO_OUTPUT,LIGHT_OFF);
//使能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);
//开放uart接收中断
*uart_cr1 |= (0x1UL<<5U);
//开中断控制器IRQ中断
NVIC_EnableIRQ(USART2_IRQn);
//(1.7)【不变】开总中断
ENABLE_INTERRUPTS;
printf("lxy -- 32106200054\n");
}
isr.c文件
//=====================================================================
//文件名称:isr.c(中断处理程序源文件)
//框架提供:SD-ARM(sumcu.suda.edu.cn)
//版本更新:20170801-20191020
//功能描述:提供中断处理程序编程框架
//移植规则:【固定】
//=====================================================================
#include "includes.h"
#define GLOBLE_VAR
volatile uint32_t* uart_isr=(volatile uint32_t*)0x4000441CUL; // UART中断和状态寄存器基地址
volatile uint32_t* uart_tdr=(volatile uint32_t*)0x40004428UL; //UART发送数据寄存器
volatile uint32_t* uart_rdr=(volatile uint32_t*)0x40004424UL; //UART接收数据寄存器
void User_SysFun(uint8_t ch);
//声明一个发送字符串的函数
void Uart_Send_String(uint8_t* sendMsg);
uint8_t Uart_Send1(uint8_t ch);
void USART2_IRQHandler(void)
{
uint8_t ch;
uint8_t flag;
uint32_t t;
uint8_t* sendMsg = "";
//关中断
DISABLE_INTERRUPTS;
//读取一个字节
//ch = uart_re1(UART_User, &flag);
for(t = 0; t < 0xFBBB; t++)//查询指定次数
{
//判断接收缓冲区是否满
if((*uart_isr) & (1<<5U))
{
ch = *uart_rdr;
flag = 1;
*uart_isr &= ~(1<<5U);//第五位清零
break;
}
}
if(t >= 0xFBBB)
{
ch = 0xFF;
flag = 0; //未收到数据
}
//判断是否接收到一个字节
if(flag)
{
//发送下一个字符
//uart_send1(UART_User, (ch+1));
Uart_Send_String("刘信扬");
Uart_Send1(ch+1);
//不亮灯
gpio_set(LIGHT_RED, LIGHT_OFF);
gpio_set(LIGHT_GREEN, LIGHT_OFF);
gpio_set(LIGHT_BLUE, LIGHT_OFF);
if(ch == 'G')
{
//uart_send_string(UART_User,(uint8_t*)"LXY 亮绿灯");
Uart_Send_String((uint8_t*)"亮绿灯");
gpio_set(LIGHT_RED, LIGHT_OFF);
gpio_set(LIGHT_BLUE, LIGHT_OFF);
gpio_set(LIGHT_GREEN, LIGHT_ON);
}
else if(ch == 'R')
{
//uart_send_string(UART_User,(uint8_t*)"LXY 亮红灯");
Uart_Send_String((uint8_t*)"亮红灯");
gpio_set(LIGHT_GREEN, LIGHT_OFF);
gpio_set(LIGHT_BLUE, LIGHT_OFF);
gpio_set(LIGHT_RED, LIGHT_ON);
}
else if(ch == 'B')
{
//uart_send_string(UART_User,(uint8_t*)" LXY 亮蓝灯");
Uart_Send_String((uint8_t*)"亮蓝灯");
gpio_set(LIGHT_RED, LIGHT_OFF);
gpio_set(LIGHT_GREEN, LIGHT_OFF);
gpio_set(LIGHT_BLUE, LIGHT_ON);
}
}
//开中断
ENABLE_INTERRUPTS;
}
//定义
void Uart_Send_String(uint8_t* sendMsg)
{
uint16_t i = 0;
uint8_t *p = (uint8_t *)sendMsg; //定义指针指向要发送字符串首地址
for(i = 0; p[i] != '\0'; i++) //遍历字符串里的字符
{
//发送指针对应的字符
if(!Uart_Send1(p[i]))
{
break;
}
}
}
uint8_t Uart_Send1(uint8_t ch)
{
uint32_t t;
for (t = 0; t < 0xFBBB; t++)//查询指定次数
{
//发送缓冲区为空则发送数据
if ((*uart_isr) & (1<<7U))
{
*uart_tdr = ch;
break;
}
}
if (t >= 0xFBBB)
return 0; //发送超时,发送失败
else
return 1;
}
//内部函数
void User_SysFun(uint8_t ch)
{
//(1)收到的一个字节参与组帧
if(gcRecvLen == 0) gcRecvLen =useremuart_frame(ch,(uint8_t*)gcRecvBuf);
//(2)字节进入组帧后,判断gcRecvLen=0?若为0,表示组帧尚未完成,
// 下次收到一个字节,再继续组帧
if(gcRecvLen == 0) goto User_SysFun_Exit;
//(3)至此,gcRecvLen≠0,表示组帧完成,gcRecvLen为帧的长度,校验序列号后(与
// 根据Flash中倒数一扇区开始的16字节进行比较)
// gcRecvBuf[16]进行跳转
if(strncmp((char *)(gcRecvBuf),(char *)((MCU_SECTOR_NUM-1)*MCU_SECTORSIZE+
MCU_FLASH_ADDR_START),16) != 0)
{
gcRecvLen = 0; //恢复接收状态
goto User_SysFun_Exit;
}
//(4)至此,不仅收到完整帧,且序号比较也一致, 根据命令字节gcRecvBuf[16]进行跳转
//若为User串口程序更新命令,则进行程序更新
switch(gcRecvBuf[16]) //帧标识
{
case 0:
SYSTEM_FUNCTION((uint8_t *)(gcRecvBuf+17));
gcRecvLen = 0; //恢复接收状态
break;
default:
break;
}
User_SysFun_Exit:
return;
}
实验结果: