AHL(金葫芦)微控制器嵌入式系统学习笔记 | Week 5
一、前言
本周的内容和上一周有点相似,但是涉及到发送数据触发亮灯与灭灯,还是挺有意思的,继续往下看吧!
二、本周学习内容
(1)编写UART_2串口发送程序时,初始化需要设置哪些参数?
(2)假设速度为115200,系统时钟为72MHz,波特率寄存器BRR中的值应该是多少?
(3)中断向量表在哪个文件中?表中有多少项?
(4)以下是中断源使能函数,假设中断源为TIM6,将函数实例化(写出各项具体数值)
(5)假设将UART_2和TIM6交换其在中断向量表中的位置和IRQ号,UART_2可以正常中断吗?
实现UART_2串口的接收程序,收到字符时,进行如下操作:
①在电脑输出窗口显示下一个字符,如收到A显示B;
②亮灯:收到字符R,亮红灯;收到字符G,亮绿灯;收到字符B,亮蓝灯;收到其他字符,不亮灯。
实现方式:
采用构件调用方式和UART部分用直接地址方式(即不调用uart.c中的函数,其他部分如GPIO、中断设置可调用函数)
本周用到的工程文件如下:
三、实验过程-part1
(1)编写UART_2串口发送程序时,初始化需要设置哪些参数?
①波特率
具体设置如下:
// 根据时钟源的不同,计算波特率寄存器的值,然后设置到波特率寄存器中
if (*uart_cr1 & (0x1UL << 15) == (0x1UL << 15))
usartdiv = (uint16_t)((SystemCoreClock / 115200) * 2);
else
usartdiv = (uint16_t)(SystemCoreClock / 115200);
*uart_brr = usartdiv;
②时钟使能
启用了UART_2和相应的GPIO端口的时钟使能。
*RCC_APB1 |= (0x1UL << 17U); // UART2时钟使能
*RCC_AHB2 |= (0x1UL << 0U); // GPIOA时钟使能
③引脚配置
将GPIO端口设置为串口功能,即复用功能。
//将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_enable_re_int(UART_User); // 使能UART_User模块接收中断功能
⑤控制寄存器
设置控制寄存器,包括启用串口、设置发送和接收使能位等。
//暂时禁用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);
(2)假设速度为115200,系统时钟为72MHz,波特率寄存器BRR中的值应该是多少?
由图示公式可以得到:
①过采样位为1,系数为8:usartdiv=2*72000000/115200=1250,即寄存器BRR的值为1250
②过采样位为0,系数为16:usartdiv=72000000/115200=625,即寄存器BRR的值为625
(3)中断向量表在哪个文件中?表中有多少项?
在“03_MCU”目录下的“startup_stm32l431rctx.s”文件中找到g_pfnVectors,共有239-141+1=99
(4)以下是中断源使能函数,假设中断源为TIM6,将函数实例化(写出各项具体数值)
中断请求IRQ号定义在03_MCU\startup\stm32l431xx.h文件中
通过查询,可以得到中断源TIM6的IRQ号为54。
通过IRQ号比去54/32=1…22得出,即,将该函数将ISER[1]第22位设置为1,即允许中断源TIM6中断。
(5)假设将UART_2和TIM6交换其在中断向量表中的位置和IRQ号,UART_2可以正常中断吗?
如果在中断向量表中交换了UART_2和TIM6的位置及其IRQ号,理论上UART_2仍然有可能正常处理中断,前提是其新的IRQ号没有被其他设备使用。只要UART_2的新IRQ号被正确地配置到了其ISR,并且这个IRQ号没有被其他中断源占用,UART_2就能够按预期响应中断。
四、实验过程-part2
(1)构件调用实现亮/灭灯
构建调用和UART部分直接地址实现亮灭灯的实验结果一样,仅代码不同,下面展示实验结果和源码
实验结果
main.c源码
#define GLOBLE_VAR
#include "includes.h" //包含总头文件//———————————————————————-
//声明使用到的内部函数
//main.c使用的内部函数声明处//———————————————————————-
//主函数,一般情况下可以认为程序从此开始运行(实际上有启动过程,参见书稿)
int main(void)
{
//(1)======启动部分(开头)==========================================
//(1.1)声明main函数使用的局部变量
// uint32_t mMainLoopCount; //主循环次数变量
// uint8_t mFlag; //灯的状态标志
// uint8_t mTest;
// uint32_t mLightCount; //灯的状态切换次数//(1.2)【不变】关总中断
DISABLE_INTERRUPTS;//(1.3)给主函数使用的局部变量赋初值
// mMainLoopCount=0; //主循环次数变量
// mFlag='A'; //灯的状态标志
// mLightCount=0; //灯的闪烁次数
// mTest='T';
//(1.4)给全局变量赋初值//(1.5)用户外设模块初始化
gpio_init(LIGHT_RED,GPIO_OUTPUT,1); //初始化红灯
gpio_init(LIGHT_GREEN,GPIO_OUTPUT,1); //初始化绿灯
gpio_init(LIGHT_BLUE,GPIO_OUTPUT,1); //初始化蓝灯uart_init(UART_2,115200); //初始化串口模块
//(1.6)使能模块中断
uart_enable_re_int(UART_2); //使能UART_USER模块接收中断功能
//(1.7)【不变】开总中断
ENABLE_INTERRUPTS;
}
isr.c源码
#include "includes.h"
//======================================================================
//程序名称:UART_User_Handler
//触发条件:UART_User串口收到一个字节触发
//======================================================================
void UART_User_Handler(void)
{
//【1】声明局部变量
uint8_t ch;
uint8_t flag;
//【2】关总中断
DISABLE_INTERRUPTS;
//【3】读取接到的一个字节
ch=uart_re1(UART_2,&flag); //调用接收一个字节的函数,清接收中断位
//【4】根据flag判断是否真正收到一个字节的数据
if(flag==1) //有数据
{
//ch=ch+2;
uart_send1(UART_2,ch+1); //回发接收到的字节
uart_send_string(UART_2, " hello! \t");
if(ch=='R')
{
//gpio_set(LIGHT_RED,1);
gpio_set(LIGHT_GREEN,1);
gpio_set(LIGHT_BLUE,1);
gpio_set(LIGHT_RED,0);
}else if(ch=='G')
{
gpio_set(LIGHT_RED,1);
//gpio_set(LIGHT_GREEN,1);
gpio_set(LIGHT_BLUE,1);
gpio_set(LIGHT_GREEN,0);
}else if(ch=='B')
{ gpio_set(LIGHT_RED,1);
gpio_set(LIGHT_GREEN,1);
//gpio_set(LIGHT_BLUE,1);
gpio_set(LIGHT_BLUE,0);
}else
{
gpio_set(LIGHT_RED,1);
gpio_set(LIGHT_GREEN,1);
gpio_set(LIGHT_BLUE,1);
}
}
//【5】开总中断
ENABLE_INTERRUPTS;
}
(2)UART部分直接地址实现亮/灭灯
该部分的实验结果和构建调用的实验结果一致,下面仅放源码(此处的实现方式把isr.c中的代码搬到main.c中了,其实放哪里都可以,能编译通过即可)。
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,1); //初始化红灯
gpio_init(LIGHT_GREEN,GPIO_OUTPUT,1); //初始化绿灯
gpio_init(LIGHT_BLUE,GPIO_OUTPUT,1); //初始化蓝灯
//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); //使能UART_User模块接收中断功能
//(1.7)【不变】开总中断
ENABLE_INTERRUPTS;
//(1)======启动部分(结尾)==========================================
//(2)======主循环部分(开头)========================================
for(;;)
{
//【1】声明局部变量
uint8_t ch;
uint8_t flag;
//【2】关总中断
DISABLE_INTERRUPTS;
//【3】读取接到的一个字节
// 检查接收缓冲区是否满,并接收字符
if (USART2->ISR & USART_ISR_RXNE_Msk)
{
ch = USART2->RDR; // 从UART2数据寄存器读取接收到的字节
flag = 1; // 设置flag以指示已接收到数据
}
//【4】根据flag判断是否真正收到一个字节的数据
if(flag==1) //有数据
{
//ch=ch+2;
//uart_send1(UART_2,ch+1); //回发接收到的字节
//uart_send_string(UART_2, " hello! \t");
if(ch=='R')
{
//gpio_set(LIGHT_RED,1);
gpio_set(LIGHT_GREEN,1);
gpio_set(LIGHT_BLUE,1);
gpio_set(LIGHT_RED,0);
}else if(ch=='G')
{
gpio_set(LIGHT_RED,1);
//gpio_set(LIGHT_GREEN,1);
gpio_set(LIGHT_BLUE,1);
gpio_set(LIGHT_GREEN,0);
}else if(ch=='B')
{ gpio_set(LIGHT_RED,1);
gpio_set(LIGHT_GREEN,1);
//gpio_set(LIGHT_BLUE,1);
gpio_set(LIGHT_BLUE,0);
}else
{
gpio_set(LIGHT_RED,1);
gpio_set(LIGHT_GREEN,1);
gpio_set(LIGHT_BLUE,1);
}
// 发送下一个字符
uint32_t t;
for (t = 0; t < 0xFBBB; t++) {
if (USART2->ISR & USART_ISR_TXE_Msk) {
USART2->TDR = ch + 1; // 发送下一个字符
break;
}
}
// 发送字符串 " (直接地址)"
const char* msg = " hello!";
for (int i = 0; i < 14; i++) {
for (t = 0; t < 0xFBBB; t++) {
if (USART2->ISR & USART_ISR_TXE_Msk) {
USART2->TDR = msg[i];
break;
}
}
}
flag = 0; // 处理完毕后重置flag
}
}
//【5】开总中断
ENABLE_INTERRUPTS;
//(2)======主循环部分(结尾)========================================
}
本周的学习任务就是这样了,如内容不正确或有疑问,欢迎评论区留言讨论,我们下周再见!(如有侵权,请联系作者删除)
作者:开飞机的舒克515