小成嵌入式系统作业5分享
第一部分
1、编写UART_2串口发送程序时,初始化需要设置哪些参数?
主要参考了04-Software\CH06\UART-STM32L431-ADDR-20210103的main.c文件
(1)时钟使能
启用了UART_2和相应的GPIO端口的时钟使能。这一步是通过设置对应的时钟使能寄存器来完成的,具体为:
*RCC_APB1 |= (0x1UL << 17U); // UART2时钟使能
*RCC_AHB2 |= (0x1UL << 0U); // GPIOA时钟使能
(2)引脚配置
将GPIO端口设置为串口功能,即复用功能。这一步涉及将特定引脚设置为USART_TX和USART_RX模式。具体的引脚配置过程如下:
//将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)));
(3) 波特率设置
根据要求的波特率配置USART的波特率寄存器。具体的波特率配置过程如下:
// 根据时钟源的不同,计算波特率寄存器的值,然后设置到波特率寄存器中
if (*uart_cr1 & (0x1UL << 15) == (0x1UL << 15))
usartdiv = (uint16_t)((SystemCoreClock / 115200) * 2);
else
usartdiv = (uint16_t)(SystemCoreClock / 115200);
*uart_brr = usartdiv;
(4)控制寄存器配置
设置控制寄存器,包括启用串口、设置发送和接收使能位等。具体的控制寄存器配置过程如下:
//暂时禁用UART功能,控制寄存器1的第0位对应的是UE—USART使能位。
//此位清零后,USART预分频器和输出将立即停止,并丢弃所有当前操作。
*uart_cr1 &= ~(0x1UL);
//暂时关闭串口发送与接收功能,控制寄存器1的发送器使能位(D3)、接收器使能位(D2)
*uart_cr1 &= ~((0x1UL<<3U)|(0x1UL<<2U));
//初始化控制寄存器和中断状态寄存器、清标志位
//关中断
*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);
(5)中断使能
如果需要使用串口接收中断,则需要相应地使能中断。具体的中断使能过程如下:
uart_enable_re_int(UART_User); // 使能UART_User模块接收中断功能
2、假设速度为115200,系统时钟为72MHz,波特率寄存器BRR中的值应该是多少?
根据公式可以得到:
①过采样位为0,系数为16:usartdiv=72000000/115200=625,即寄存器BRR的值为625
②过采样位为1,系数为8:usartdiv=2*72000000/115200=1250,,即寄存器BRR的值为1250
3、中断向量表在哪个文件中?表中有多少项?给出部分截图
中断向量表定义在03_MCU\startup\startup_stm32l431rctx.s文件中
表中有99项:239-141+1=99
4、以下是中断源使能函数,假设中断源为TIM6,将函数实例化(写出各项具体数值)。
中断请求IRQ号定义在03_MCU\startup\stm32l431xx.h文件中
通过查询可以得知,中断源TIM6的IRQ号为54
所以在中断源使能函数中:
当IRQn=54,((uint32_t)IRQn) >> 5UL=0b00110110>>5=0x1
因此,NVIC->ISER[(((uint32_t)IRQn) >> 5UL)]=NVIC->ISER[1]
((uint32_t)IRQn) & 0x1FUL=(0b00110110)&(0x00011111)=0b00010110=22
因此,(uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL))=0x1<<22,即第22位1,其余位为0
综上所述:
该函数将ISER[1]第22位设置为1,即允许中断源TIM6中断(也可以用IRQ号54/32=1…22得出)
5、假设将UART_2和TIM6交换其在中断向量表中的位置和IRQ号,UART_2可以正常中断吗?
如果在中断向量表中交换了UART_2和TIM6的位置及其IRQ号,理论上UART_2仍然有可能正常处理中断,前提是其新的IRQ号没有被其他设备使用。这是因为中断向量表是处理器用于识别和响应中断请求的关键数据结构,其中每个中断源都被分配了一个唯一的中断号和对应的中断服务程序(ISR)。当一个硬件设备发出中断请求时,处理器会查询这个表格,以确定应当执行哪个ISR来处理该中断。
如果你在软件中错误地配置了中断向量表,例如将UART_2的中断处理函数地址放在了TIM6的中断向量位置,那么当UART_2产生中断信号时,由于中断向量表中的地址不正确,微控制器将跳转到错误的处理函数或无效地址,导致程序运行错误或系统崩溃。因此,只要UART_2的新IRQ号被正确地配置到了其ISR,并且这个IRQ号没有被其他中断源占用,UART_2就能够按预期响应中断。
总之,虽然理论上通过软件修改中断向量表的配置可以实现中断源的交换,但这种操作需要非常小心,以确保每个中断号都正确地指向了相应的ISR,并且没有发生IRQ号的冲突。
第二部分
1、实现UART_2串口的接收程序
当收到字符时:
①在电脑的输出窗口显示下一个字符,如收到A显示B,
②亮灯:收到字符G,亮绿灯;收到字符R,亮红灯;收到字符B,亮蓝灯;收到其他字符,不亮灯。
(1)用构件调用方式实现
这个比较简单,亮灯的操作之前已经实现过了,因此只需要找到uart.c这一文件,然后调用里面的函数来实现其余功能就ok了
核心代码:
//(2)======主循环部分(开头)========================================
for(;;) //for(;;)(开头)
{
//(2.1)读取接到的一个字节
ch=uart_re1(UART_User,&flag); //调用接收一个字节的函数,清接收中断位
//(2.3)根据flag判断是否真正收到一个字节的数据
if(flag)
{
switch (ch) {
case 'G':
gpio_set(LIGHT_GREEN, 0); // 亮绿灯
gpio_set(LIGHT_RED, 1); // 灭红灯
gpio_set(LIGHT_BLUE, 1); // 灭蓝灯
uart_send1(UART_User, ch + 1);
uart_send_string(UART_User,(uint8_t *)" czy(构件调用)");
break;
case 'R':
gpio_set(LIGHT_GREEN, 1); // 灭绿灯
gpio_set(LIGHT_RED, 0); // 亮红灯
gpio_set(LIGHT_BLUE, 1); // 灭蓝灯
uart_send1(UART_User, ch + 1);
uart_send_string(UART_User,(uint8_t *)" czy(构件调用)");
break;
case 'B':
gpio_set(LIGHT_GREEN, 1); // 灭绿灯
gpio_set(LIGHT_RED, 1); // 灭红灯
gpio_set(LIGHT_BLUE, 0); // 亮蓝灯
uart_send1(UART_User, ch + 1);
uart_send_string(UART_User,(uint8_t *)" czy(构件调用)");
break;
default:
gpio_set(LIGHT_GREEN, 1); // 灭绿灯
gpio_set(LIGHT_RED, 1); // 灭红灯
gpio_set(LIGHT_BLUE, 1); // 灭蓝灯
uart_send1(UART_User, ch + 1);
uart_send_string(UART_User,(uint8_t *)" czy(构件调用)");
break;
}
}
} //for(;;)结尾
结果(我除了输出下一个字符外,还设置了输出我的姓名缩写czy):
输入G,输出H,亮绿灯
输入R,输出S,亮红灯
输入B,输出C,亮蓝灯
输入A,输出B,不亮灯
(2)UART部分用直接地址方式实现(即不调用uart.c中的函数,其他部分如GPIO、中断设置可调用函数)。
由于不能够调用函数,因此需要将用到的函数改成用直接地址的方式实现
核心代码:
// 主循环
for(;;)
{
// 检查接收缓冲区是否满,并接收字符
if (USART2->ISR & USART_ISR_RXNE_Msk)
{
ch = USART2->RDR; // 从UART2数据寄存器读取接收到的字节
flag = 1; // 设置flag以指示已接收到数据
}
if (flag) {
switch (ch) {
case 'G':
gpio_set(LIGHT_GREEN, 0); // 亮绿灯
gpio_set(LIGHT_RED, 1); // 灭红灯
gpio_set(LIGHT_BLUE, 1); // 灭蓝灯
break;
case 'R':
gpio_set(LIGHT_GREEN, 1); // 灭绿灯
gpio_set(LIGHT_RED, 0); // 亮红灯
gpio_set(LIGHT_BLUE, 1); // 灭蓝灯
break;
case 'B':
gpio_set(LIGHT_GREEN, 1); // 灭绿灯
gpio_set(LIGHT_RED, 1); // 灭红灯
gpio_set(LIGHT_BLUE, 0); // 亮蓝灯
break;
default:
gpio_set(LIGHT_GREEN, 1); // 灭绿灯
gpio_set(LIGHT_RED, 1); // 灭红灯
gpio_set(LIGHT_BLUE, 1); // 灭蓝灯
break;
}
// 发送下一个字符
uint32_t t;
for (t = 0; t < 0xFBBB; t++) {
if (USART2->ISR & USART_ISR_TXE_Msk) {
USART2->TDR = ch + 1; // 发送下一个字符
break;
}
}
// 发送字符串 " czy(直接地址)"
const char* msg = " czy(直接地址)";
for (int i = 0; i < 14; i++) {
for (t = 0; t < 0xFBBB; t++) {
if (USART2->ISR & USART_ISR_TXE_Msk) {
USART2->TDR = msg[i]; // 逐个字符发送 " czy(直接地址)"
break;
}
}
}
flag = 0; // 处理完毕后重置flag
}
}
输入G,输出H,亮绿灯
输入R,输出S,亮红灯
输入B,输出C,亮蓝灯
输入A,输出B,不亮灯
作者:成小橙_106