NB-IOT实验案例

通过对《NB-IOT基础知识》的阅读,可以对NB-IOT及其开发环境有一定的初步了解,接下来介绍7个实验来了解NB-IOT模快的STM32实验。

实验环境

这7个实验的环境相同:

  1. 硬件:一个液晶扩展板、一个NB-IOT核心模块、一个 ST-Link 仿真器、1 根mini USB线、1 台 PC 机。(这七个实验不需要传感器。)
  2. 软件:Windows 7/XP及以上、MDK 集成开发环境。

实验概览

1. LED 流水灯实验
2. 按键输入实验
3. 外部中断实验
5. SysTick定时器实验
5. 串口通讯实验
6. ADC实验
7. OLED屏显示实验

实验一:流水灯实验

实验原理

NB-IOT模组

​ 通过查阅电路手册,发现控制两个LED 灯D1、D2的MCU引脚分别为PB2和PB3,

NB-IOT模组

在STM32CubeMX上进一步配饰IO属性的时候,将PB2、PB3的引脚标签为 LED0 和LED1,此时MCU 引脚与 LED 灯引脚的对应关系如下表所示:

NB-IOT模组

观察上表可以得出要想控制 LED 灯的亮灭,只需控制MCU的PB2、PB3引脚即可。

LED灯模块电路图

即要想让 LED 灯点亮,使 MCU的引脚输出低电平即可。

实验代码
LED 灯状态的设置
/** 控制LED灯亮灭的宏,设置 LED 灯的相应引脚为高电平或低电平状态,从而实现控制 LED 的亮和灭。
* LED低电平亮,设置ON=0,OFF=1
* 若LED高电平亮,把宏设置成ON=1 ,OFF=0 即可
*/
#define ON GPIO_PIN_RESET
#define OFF GPIO_PIN_SET
/* 带参宏,可以像内联函数一样使用 */
#define LED1(a) HAL_GPIO_WritePin(LED1_GPIO_PORT,LED1_PIN,a)
#define LED2(a) HAL_GPIO_WritePin(LED2_GPIO_PORT,LED2_PIN,a)
GPIO 引脚初始化

对所用到的 GPIO 引脚进行初始化,并将 LED 的默认状态设置为低电平即熄灭的状态。

void LED_GPIO_Config(void)
{
    /*定义一个GPIO_InitTypeDef类型的结构体*/
    GPIO_InitTypeDef GPIO_InitStruct;
    /*开启LED相关的GPIO外设时钟*/
    LED1_GPIO_CLK_ENABLE();
    LED2_GPIO_CLK_ENABLE();
    /*选择要控制的GPIO引脚*/
    GPIO_InitStruct.Pin = LED1_PIN;
    /*设置引脚的输出类型为推挽输出*/
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    /*设置引脚为上拉模式*/
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    /*设置引脚速率为高速 */
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    /*调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO*/
    HAL_GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStruct);
    /*选择要控制的GPIO引脚*/
    GPIO_InitStruct.Pin = LED2_PIN;
    HAL_GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStruct);
}
系统时钟初始化
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    /**
    根据指定参数初始化RCC振荡器在RCC_OscInitTypeDef结构中。
    */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL6;
    if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
    	Error_Handler();
    }
    /** 
    初始化MCU、AHB和APB总线时钟
    */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
    | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    {
    	Error_Handler();
    }
}
主函数
int main(void)
{
    HAL_Init();
    /* 配置系统时钟 */
    SystemClock_Config();
    /* LED 端口初始化 */
    LED_GPIO_Config();
    /* 死循环, while 循环中进行 LED 灯状态的设置,使其实现 LED 灯呈现流水灯的现象。*/
    while(1)
    {
        LED1(ON); // 亮
        HAL_Delay(500);
        LED1(OFF); // 灭
        HAL_Delay(500);
        LED2(ON); // 亮
        HAL_Delay(500);
        LED2(OFF); // 灭
        HAL_Delay(500);
	}
}
实验结果

程序运行成功后,将可以看到 LED 灯有规律进行亮灭

res1

实验二:按键输入实验

实验原理

​ 方向按键垂直向下键对应一个MCU引脚,当有按键被按下时,该引脚就会有高低电平的变化,当按键按下时,此引脚为低电平,当按键未按下时,此引脚为高电平。

MCU引脚与按键引脚的对应关系如下表所示:

LED灯模块电路图

参照上表可以发现实验中所用引脚为PA5,对应为按键K4按键的垂直向下按。当垂直向下按下按键时,LED亮灭状态会改变。

LED灯模块电路图

​ 上图为机械抖动的波动图,由于按键是机械期间,按下或者松开时会有固定的机械抖动,尽管这个抖动的时间很短一般为10~15ms,且不同按键抖动不同,但对应单片机来说,很轻松就能检测到,因为单片机的时钟是 us 级别。由于抖动的存在,实际上只进行一次按键操作,也有可能会执行多次按键结果,所以要进行按键去抖操作。

按键去抖分为硬件去抖和软件去抖:

硬件去抖:最简单的就是按键两端并联电容,容量根据实验而定。

  • 优点:稳定。

  • 缺点:实现方便而且不增加硬件成本。

软件去抖:通过软件编写算法,对于物理开关等硬件设备产生的抖动信号进行稳定化处理,以保证系统的正常运行。

  • 优点:实现方便而且不增加硬件成本,容易调试,

  • 缺点:使用软件去抖会增加系统的复杂性,增大系统的开销。

​ 软件去抖应用最为广泛,它的原理为检测到按键按下后进行 10~15ms 延时,用于跳过这个抖动区域延时后再检测按键状态,如果没有按下表明是抖动或者干扰造成,如果仍旧按下,可以认为是有效按下,并进行对应的操作。同样按键释放后也要进行去抖延时,延时后检测按键是否有效松开。

使用软件去抖的流程为:
首先检测按键是否按下,如果没有则退出,如按下调用延时去抖,延时过后重新检测按键状态,如果没有按下,说明是抖动或其他干扰误触发,放弃该结果,退出,反之,如果仍然按下,表明按键确实被按下,此时可以处理按键对应的程序。

实验代码
检测引脚相关的宏定义
//引脚定义
/*******************************************************/
#define KEY_PIN GPIO_PIN_5
#define KEY_GPIO_PORT GPIOA
#define KEY_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
/*******************************************************/
按键 GPIO 初始化函数
void Key_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	/*开启按键 GPIO 口的时钟*/
	KEY_GPIO_CLK_ENABLE();
	/*选择按键的引脚*/
	GPIO_InitStructure.Pin = KEY_PIN;
	/*设置引脚为输入模式*/
	GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
	/*设置引脚不上拉也不下拉*/
	GPIO_InitStructure.Pull = GPIO_NOPULL;
	/*使用上面的结构体初始化按键*/
	HAL_GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStructure);
}
按键状态检测
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{
	/*检测是否有按键按下 */
	if(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON )
	{
		HAL_Delay(10); //延时消抖
		if(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON )
	{
	/*等待按键释放 */
	while(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON);
		return KEY_ON;
	}
	else
		return KEY_OFF;
	}
	else
		return KEY_OFF;
}
主函数
int main(void)
{
	/* 重置所有外围设备,初始化Flash接口和Systick */
	HAL_Init();
	/* 配置系统时钟 */
	SystemClock_Config();
	/* LED 端口初始化 */
	LED_GPIO_Config();
	/*初始化按键*/
	Key_GPIO_Config();
	/* 轮询按键状态,若按键按下则反转 LED */
	while(1)
	{
		if(Key_Scan(KEY_GPIO_PORT, KEY_PIN) == KEY_ON)
		{
			/*LED1 反转*/
			LED1_TOGGLE;
		}
	}
}
实验结果

按下方向按键的垂直向下键可以控制LED 灯亮灭

res1

实验三:外部中断实验

实验原理
  • 什么是中断?

​ 中断是指CPU对系统发生的某个事件做出的一种反应,CPU暂停正在执行的程序,保留现场后自动地转去执行相应的处理程序,处理完该事件后再返回断点继续执行被“打断”的程序。

  • 什么是外部中断?

​ 它是由CPU外部的输入设备(如按钮、开关、传感器等)所引起的中断。当外部事件发生时,例如按下按钮、达到特定时间间隔等,外部中断装置将向CPU发送中断信号,迫使CPU暂停正在执行的程序,转而去处理中断事件。一旦中断处理完毕,CPU将返回到被中断的程序处,继续执行下去。

​ 举个例子加深理解:假设您正在专心致志地阅读一本小说,突然听到客厅的电话铃声,会暂时放下手中的小说,去接电话,当与电话那头的人交谈完毕后,会挂断电话并回到小说中。在这个过程中,您通过中断处理机制,完成了从小说阅读到电话接听,再到回到小说阅读的操作。在这个例子中,阅读一本小说是当前执行的主程序,听到电话铃声就是外部中断,它打断了您正在进行的小说阅读活动,听到客厅的电话铃声是中断申请,暂时放下手中的小说,去接电话是对中断的响应和处理,在接完电话后,能够回到小说阅读的进程中,这就是中断处理机制的作用。

  • 什么是中断优先级?

​ 中断优先级是计算机系统中各个中断源的优先级顺序。在同时发生多个中断的情况下,系统会首先处理优先级最高的中断事件。STM32 中的中断优先级可以分为:抢占式优先级和响应优先级,每个中断源都需要被指定这两种优先级。

  • 抢占式优先级和响应优先级的区别:

​ 抢占优先级:抢占优先级高的中断可以打断正在执行的抢占优先级低的中断。
​ 响应优先级:抢占优先级相同,响应优先级高的中断不能打断响应优先级低的中断。
​ 还有一种情况就是当两个或者多个中断的抢占式优先级和响应优先级相同时,那么就遵循自然优先级,看中断向量表的中断排序,数值越小,优先级越高。

MCU引脚与按键引脚的对应关系与实验二相同!!!

实验代码
按键和 EXTI 宏定义
//引脚定义
/*******************************************************/
#define KEY_INT_GPIO_PORT GPIOA
#define KEY_INT_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE();
#define KEY_INT_GPIO_PIN GPIO_PIN_5
#define KEY_INT_EXTI_IRQ EXTI9_5_IRQn
#define KEY_IRQHandler EXTI9_5_IRQHandler
/*******************************************************/
EXTI 中断配置
void EXTI_Key_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	/*开启按键 GPIO 口的时钟*/
	KEY_INT_GPIO_CLK_ENABLE();
	/* 选择按键的引脚 */
	GPIO_InitStructure.Pin = KEY_INT_GPIO_PIN;
	/* 设置引脚为输入模式 */
	GPIO_InitStructure.Mode = GPIO_MODE_IT_RISING;
	/* 设置引脚不上拉也不下拉 */
	GPIO_InitStructure.Pull = GPIO_NOPULL;
	/* 使用上面的结构体初始化按键 */
	HAL_GPIO_Init(KEY_INT_GPIO_PORT, &GPIO_InitStructure);
	/* 配置 EXTI 中断源 到 key 引脚、配置中断优先级*/
	HAL_NVIC_SetPriority(KEY_INT_EXTI_IRQ, 0, 0);
	/* 使能中断 */
	HAL_NVIC_EnableIRQ(KEY_INT_EXTI_IRQ);
}
EXTI 中断服务函数
void KEY_IRQHandler(void)
{
	//确保是否产生了 EXTI Line 中断
	if(__HAL_GPIO_EXTI_GET_IT(KEY_INT_GPIO_PIN) != RESET)
	{
		// LED1 取反
		LED1_TOGGLE;
		//清除中断标志位
		__HAL_GPIO_EXTI_CLEAR_IT(KEY_INT_GPIO_PIN);
	}
}
主函数
int main(void)
{
	/* 重置所有外围设备,初始化Flash接口和Systick */
	HAL_Init();
	/* 配置系统时钟 */
	SystemClock_Config();
	/* LED 端口初始化 */
	LED_GPIO_Config();
	/* 初始化 EXTI 中断,按下按键会触发中断,
	* 触发中断会进入 stm32f7xx_it.c 文件中的函数
	* KEY_IRQHandler 处理中断,反转 LED 灯。
	*/
	EXTI_Key_Config();
	/* 等待中断,由于使用中断方式,MCU 不用轮询按键 */
	while(1)
	{
	}
}
实验结果

按下方向按键的垂直向下键可以控制LED 灯亮灭。

res1

实验四:SysTick 定时器实验

实验原理
  • 什么是SysTick ?

​ SysTick是一个属于CM3 内核中的一个外设,内嵌在NVIC 中的定时器模块,它可以用来实现时间的计数和定时器功能。

通俗的讲一个沙漏,当你向下倒沙子时,你可以计算它的倒计时时间。SysTick就像这个沙漏一样,它有一个计数器,可以从一个初始值开始倒数,直到计数器归零。你可以设置SysTick的时钟源,例如使用系统时钟(SYSCLK,一般我们设置系统时钟 SYSCLK 等于 72M)或其他外设时钟。当你设置了计数器的初始值后,你可以让它开始倒数。当计数器归零时,会触发一个中断或产生一个定时器溢出事件。

通过使用SysTick可以实现定时器功能:即可以设置计数器的初始值,使其倒数到0时触发中断或产生定时器溢出事件。可以使用SysTick来创建定时器,例如在特定的时间间隔内执行某个任务。

  • SysTick 共有三个寄存器

SysTick寄存器

在使用SysTick 产生定时的时候,只需要配置前三个寄存器,最后一个校准寄存器不需要使用。

实验代码
SysTick 配置库函数
STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
    // 不可能的重装载值,超出范围
    if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) 
    {
    	return (1UL);
    }
    // 设置重装载寄存器
    SysTick->LOAD = (uint32_t)(ticks - 1UL);
    // 设置中断优先级
    NVIC_SetPriority (SysTick_IRQn, (1UL << NVIC_PRIO_BITS) - 1UL);
    // 设置当前数值寄存器
    SysTick->VAL = 0UL;
    // 设置系统定时器的时钟源为 AHBCLK=72M
    // 使能系统定时器中断
    // 使能定时器
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
    SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
    return (0UL);
}
配置 SysTick 中断优先级
// 设置系统定时器中断优先级
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
SysTick 初始化函数
void SysTick_Init(void)
{
	/* SystemFrequency / 1000 1ms 中断一次
 	 * SystemFrequency / 100000
   		10us 中断一次
     * SystemFrequency / 1000000 1us 中断一次
     */
     if (HAL_SYSTICK_Config(SystemCoreClock / 100000))
     {
         /* 捕获错误 */
         while (1);
     }
}
SysTick 定时函数
void Delay_us(__IO u32 nTime)
{
    TimingDelay = nTime;
    while(TimingDelay != 0);
}
SysTick 中断服务函数
void SysTick_Handler(void)
{
	HAL_IncTick();
	TimingDelay_Decrement();
}
// 上面的中断复位函数调用了另外一个函数TimingDelay_Decrement(),原型如下:
/*
void TimingDelay_Decrement(void)
{
    if (TimingDelay != 0x00)
    {
    	TimingDelay--;
    }
}
*/
主函数
int main(void)
{
	/* 重置所有外围设备,初始化Flash接口和Systick */
	HAL_Init();
	/* 配置系统时钟 */
	SystemClock_Config();
	/* LED 端口初始化 */
	LED_GPIO_Config();
	/* 配置 SysTick 为 10us 中断一次,时间到后触发定时中断,
	*进入 stm32f7xx_it.c 文件的 SysTick_Handler 处理,通过数中断次数计时
	*/
	SysTick_Init();
	while(1)
	{
		LED1_TOGGLE;
		Delay_us(100000); // 10000 * 10us = 1000ms
		LED2_TOGGLE;
		Delay_us(100000); // 10000 * 10us = 1000ms
	}
}
实验结果

LED 以 1s 的频率闪烁

实验五:串口通讯实验

实验原理
  • 什么是串口?

​ 串口是一种数据传输方式,它使用串行通信协议将数据逐位传输,即一位一位地发送信息。本实验我们通过USB 转串口芯片CP2102 来与电脑的上位机进行通信。

  • 什么是串口通讯?

​ 由于STM32系列的微控制器集成了串口接口,可以与各种外部设备或通信模块进行数据传输和通信,而STM32的串口通讯是指通过STM32系列微控制器上的串口接口进行数据传输的方式。

本次实验在“串口调试助手”输入指令,让硬件根据这些指令执行一些任务,根据数据内容控制 LED 灯的亮灭。

实验代码
GPIO 和 USART 宏定义
//串口波特率
#define DEBUG_USART_BAUDRATE 115200
//引脚定义
/*******************************************************/
#define DEBUG_USART USART1
#define DEBUG_USART_CLK_ENABLE() __HAL_RCC_USART1_CLK_ENABLE();
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define DEBUG_USART_RX_PIN GPIO_PIN_10
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define DEBUG_USART_TX_PIN GPIO_PIN_9
#define DEBUG_USART_IRQHandler USART1_IRQHandler
#define DEBUG_USART_IRQ
USART1_IRQn
/************************************************************/
USART 初始化配置
void DEBUG_USART_Config(void)
{
	UartHandle.Instance = DEBUG_USART;
	UartHandle.Init.BaudRate = DEBUG_USART_BAUDRATE;
	UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
	UartHandle.Init.StopBits = UART_STOPBITS_1;
	UartHandle.Init.Parity = UART_PARITY_NONE;
	UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
	UartHandle.Init.Mode = UART_MODE_TX_RX;
	HAL_UART_Init(&UartHandle);
}

void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    DEBUG_USART_CLK_ENABLE();
    DEBUG_USART_RX_GPIO_CLK_ENABLE();
    DEBUG_USART_TX_GPIO_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PA9 ------> USART1_TX
    PA10 ------> USART1_RX
    */
    /* 配置 Tx 引脚为复用功能 */
    GPIO_InitStruct.Pin = DEBUG_USART_TX_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStruct);
    /* 配置 Rx 引脚为复用功能 */
    GPIO_InitStruct.Pin = DEBUG_USART_RX_PIN;
    GPIO_InitStruct.Mode=GPIO_MODE_AF_INPUT; //模式要设置为复用输入模式!
    HAL_GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStruct);
    // HAL_NVIC_SetPriority(DEBUG_USART_IRQ ,0,1);
    //抢占优先级 0,子优先级 1
    // HAL_NVIC_EnableIRQ(DEBUG_USART_IRQ );
    //使能 USART1 中断通道
}
重定向 printf 和 scanf 函数
//重定向 c 库函数 printf 到串口 DEBUG_USART,重定向后可使用 printf 函数
int fputc(int ch, FILE *f)
{
	/* 发送一个字节数据到串口 DEBUG_USART */
	HAL_UART_Transmit(&UartHandle, (uint8_t *)&ch, 1, 1000);
	return (ch);
}
//重定向 c 库函数 scanf 到串口 DEBUG_USART,重写向后可使用 scanf、getchar 等函数
int fgetc(FILE *f)
{
	int ch;
	HAL_UART_Receive(&UartHandle, (uint8_t *)&ch, 1, 1000);
	return (ch);
}
输出提示信息
static void Show_Message(void)
{
	printf("\r\n 这是一个通过串口通信指令控制 LED 灯实验 \r\n");
	printf("硬件接到指令后控制 LED 灯亮灭,指令对应如下:\r\n");
	printf(" 指令 ------ LED 灯序号 \r\n");
	printf(" 1 ------ LED1 \r\n");
	printf(" 2 ------ LED2 \r\n");
}
主函数
int main(void)
{
    char ch;
    /* 重置所有外围设备,初始化Flash接口和Systick */
    HAL_Init();
    /* 配置系统时钟 */
    SystemClock_Config();
    /* LED 端口初始化 */
    LED_GPIO_Config();
    /*初始化 USART 配置模式为 115200 8-N-1,中断接收*/
    DEBUG_USART_Config();
    /* 打印指令输入提示信息 */
    Show_Message();
    while(1)
    {
        /* 获取字符指令 */
        ch=getchar();
        printf("接收到字符:%c\r\n",ch);
        /* 根据字符指令控制 LED 灯亮灭 */
        switch(ch)
        {
            case '1':
            LED1_TOGGLE;
            break;
            case '2':
            LED2_TOGGLE;
            break;
            default:
            /* 如果不是指定指令字符,打印提示信息 */
            Show_Message();
            break;
        }
	}
}
实验结果

在“字符串输入框”中输入命令1或2,点击“发送”按钮,观察NB-IOT模块上的两颗LED会相应的亮灭

实验六:ADC实验

实验原理
  • 什么是ADC?

​ ADC是指模拟数字转换器,它是一种用于将模拟信号转换为数字信号的电子器件。ADC的作用是将连续变化的模拟信号转换为离散的数字信号,以便于数字信号处理和处理器的运算。

​ ADC通常由一个采样保持电路和一个量化器组成。采样保持电路在特定时间间隔内对模拟信号进行采样,并将采样值保持一段时间,以便量化器进行数字化处理。量化器则将采样值转换为数字信号,它将连续的模拟信号转换为离散的数字信号。

​ ADC的应用范围非常广泛,包括音频处理、图像处理、通信系统、控制系统、医疗设备等领域。在数字信号处理中,ADC是实现模拟世界和数字世界转换的关键器件之一。

方向按键的检测引脚通过连接至 STM32 芯片的 ADC 通道引脚。当我们拨动方向按键不同方向时,其检测引脚的电压也会随之改变,电压变化范围为 0~3.3V,亦是硬件默认的 ADC 电压采集范围。

  • 硬件设计:

打开“串口调试助手”,拨动方向按键不同方向传输打印输出不同的电压值信息。

实验代码
ADC 宏定义
// ADC GPIO 宏定义
#define RHEOSTAT_ADC_GPIO_PORT GPIOA //改为 PA5
#define RHEOSTAT_ADC_GPIO_PIN GPIO_PIN_5
#define RHEOSTAT_ADC_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
// ADC 序号宏定义
#define RHEOSTAT_ADC ADC1
#define RHEOSTAT_ADC_CLK_ENABLE() __HAL_RCC_ADC1_CLK_ENABLE();
#define RHEOSTAT_ADC_CHANNEL ADC_CHANNEL_5 //通道 5
// ADC 中断宏定义
#define Rheostat_ADC_IRQ ADC1_IRQn
#define Rheostat_ADC_INT_FUNCTION ADC1_IRQHandler
ADC GPIO 初始化函数
static void Rheostat_ADC_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RHEOSTAT_ADC_CLK_ENABLE();
    // 使能 GPIO 时钟
    RHEOSTAT_ADC_GPIO_CLK_ENABLE();
    // 配置 IO
    GPIO_InitStructure.Pin = RHEOSTAT_ADC_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStructure.Pull = GPIO_NOPULL ; //不上拉不下拉
    HAL_GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure);
}
配置 ADC 工作模式
static void Rheostat_ADC_Mode_Config(void)
{
    RCC_PeriphCLKInitTypeDef ADC_CLKInit;
    // 开启ADC时钟
    //ADC外设时钟
    ADC_CLKInit.PeriphClockSelection=RCC_PERIPHCLK_ADC;
    //分频因子6时钟为72M/6=12MHz
    ADC_CLKInit.AdcClockSelection=RCC_ADCPCLK2_DIV6;
    //设置ADC时钟
    HAL_RCCEx_PeriphCLKConfig(&ADC_CLKInit);
    ADC_Handle.Instance=RHEOSTAT_ADC;
    //右对齐
    ADC_Handle.Init.DataAlign=ADC_DATAALIGN_RIGHT;
    //非扫描模式
    ADC_Handle.Init.ScanConvMode=DISABLE;
    //连续转换
    ADC_Handle.Init.ContinuousConvMode=ENABLE;
    //1个转换在规则序列中 也就是只转换规则序列1
    ADC_Handle.Init.NbrOfConversion=1;
    //禁止不连续采样模式
    ADC_Handle.Init.DiscontinuousConvMode=DISABLE;
    //不连续采样通道数为0
    ADC_Handle.Init.NbrOfDiscConversion=0;
    //软件触发
    ADC_Handle.Init.ExternalTrigConv=ADC_SOFTWARE_START;
    //初始化
    HAL_ADC_Init(&ADC_Handle);
    //------------------------------
    ADC_Config.Channel = RHEOSTAT_ADC_CHANNEL;
    ADC_Config.Rank = 1;
    // 采样时间间隔
    ADC_Config.SamplingTime = ADC_SAMPLETIME_55CYCLES_5 ;
    // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期
    HAL_ADC_ConfigChannel(&ADC_Handle, &ADC_Config);
    HAL_ADC_Start_IT(&ADC_Handle);
}
ADC 中断配置
// 配置中断优先级
static void Rheostat_ADC_NVIC_Config(void)
{
    HAL_NVIC_SetPriority(Rheostat_ADC_IRQ, 0, 0);
    HAL_NVIC_EnableIRQ(Rheostat_ADC_IRQ);
}
中断服务函数
void ADC1_IRQHandler(void)
{
	HAL_ADC_IRQHandler(&ADC_Handle);
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
	/* 获取结果 */
	ADC_ConvertedValue = HAL_ADC_GetValue(AdcHandle);
}
主函数
int main(void)
{
    /* 初始化 */
    HAL_Init();
    /* 配置时钟系统 */
    SystemClock_Config();
    /* LED 端口初始化 */
    LED_GPIO_Config();
    /*初始化USART 配置模式为 115200 8-N-1,中断接收*/
    DEBUG_USART_Config();
    Rheostat_Init();
    while (1)
    {
        ADC_Vol =(float) ADC_ConvertedValue/4096*(float)3.3; // 读取转换的AD值
        printf("\r\n The current AD value = 0x%04X \r\n", ADC_ConvertedValue);
        printf("\r\n The current AD value = %f V \r\n",ADC_Vol);
        Delay(0x8fffff);
    }
}
实验结果

当我们拨动方向按键不同方向时,其检测引脚的电压也会随之改变,电压变化范围为 0~3.3V

实验七:OLED屏显示实验

实验原理

OLED由IIC链接到控制器进行控制,IIC是由数据线 SDA 和时钟线 SCL 构成的串行总线,可发送和接收数据,在 MCU 与被控 IC 之间、IC 与 IC 之间进行双向传送。

  • 硬件设计

MCU 引脚与 OLED引脚的对应关系如下表所示:

MCU引脚号按键引脚说明按键编号
PA11SDAP_12
PA12SCLP_13
PA8使能OLED的电源EXTVDD_3V3P_13

驱动 OLED 模块,不停的显示 ASCII码字符。

实验代码
IIC 硬件相关宏定义
#define OLED_SDA_PORT GPIOA
#define OLED_SDA_PIN GPIO_PIN_11
#define OLED_SCL_PORT GPIOA
#define OLED_SCL_PIN GPIO_PIN_12
#define VCC_3V3_PORT GPIOA
#define VCC_3V3_PIN GPIO_PIN_8
初始化 IIC 的 GPIO
void OLED_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	__HAL_RCC_GPIOA_CLK_ENABLE();
	// Init the GPIO pins
	GPIO_InitStruct.Pin = VCC_3V3_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(VCC_3V3_PORT, &GPIO_InitStruct);
	GpioWrite(VCC_3V3_PORT, VCC_3V3_PIN, GPIO_PIN_SET);
	DelayMs(100);
	// Init the GPIO pins
	GPIO_InitStruct.Pin = OLED_SDA_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(OLED_SDA_PORT, &GPIO_InitStruct);
	GPIO_InitStruct.Pin = OLED_SCL_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(OLED_SCL_PORT, &GPIO_InitStruct);
	DelayMs(10);
	ugOled9616int();
}
初始化OLED
static void OLED_Init()
{
	Set_Display_On_Off(0x00);
	// Display Off (0x00/0x01)
	Set_Display_Clock(0xA2);
	// Set Clock as 120 Frames/Sec
	Set_Multiplex_Ratio(0x0F);
	// 1/16 Duty (0x0F~0x3F)
	Set_Display_Offset(0x00);
	// Shift Mapping RAM Counter (0x00~0x3F)
	Set_Start_Line(0x00);
	// Set Mapping RAM Display Start Line (0x00~0x3F)
	Set_Charge_Pump(0x04);
	// Enable Embedded DC/DC Converter (0x00/0x04)
	Set_Power_Save(0x05);
	// Set Low Power Save Mode
	Set_Addressing_Mode(0x02);
	// Set Page Addressing Mode (0x00/0x01/0x02)
	Set_Segment_Remap(0x01);
	// Set SEG/Column Mapping (0x00/0x01)
	Set_Common_Remap(0x08);
	// Set COM/Row Scan Direction (0x00/0x08)
	Set_Common_Config(0x00);
	// Set Sequential Configuration (0x00/0x10)
	Set_Contrast_Control(Brightness); // Set SEG Output Current
	Set_Precharge_Period(0xD2);
	// Set Pre-Charge as 13 Clocks & Discharge as
	2 Clock
	Set_VCOMH(0x20);
	// Set VCOM Deselect Level
	Set_Entire_Display(0x00);
	// Disable Entire Display On (0x00/0x01)
	Set_Inverse_Display(0x00);
	// Disable Inverse Display On (0x00/0x01)
	FillRam(0x00);
	// Clear Screen
	Set_Display_On_Off(0x01);
	// Display On (0x00/0x01)
}
产生 IIC 起始信号
static void I2C_Start()
{
	OLED_SDA_LOW();
	uDelay(1);
	OLED_SCL_HIGH();
	uDelay(1);
	OLED_SCL_LOW();
	uDelay(1);
}
产生停止信号
static void I2C_Stop()
{
	OLED_SCL_HIGH();
	uDelay(5);
	OLED_SDA_LOW();
	uDelay(5);
	OLED_SDA_HIGH();
	uDelay(5);
}
发送一个字节数据
static void IIC_O( OledDataType mcmd )
{
	OledDataType length = 8;
	// Send Command
	while(length--)
	{
		if(mcmd & 0x80)
		{
			OLED_SDA_HIGH();
		}
		else
		{
			OLED_SDA_LOW();
		}
		uDelay(1);
		OLED_SCL_HIGH();
		uDelay(1);
		OLED_SCL_LOW();
		uDelay(1);
		mcmd = mcmd << 1;
	}
}
主函数
int main(void)
{
	/* 初始化 */
	HAL_Init();
	/* 配置时钟系统*/
	SystemClock_Config();
	/* LED 端口初始化 */
	LED_GPIO_Config();
	OLED_GPIO_Config(); //OLED 相关引脚定义放在 main.h 文件中
	HAL_Delay(10);
	LcdPutScDispRtoL((void*)"ABCDEFG1234567890", 1, 600); //液晶显示
	/* Infinite loop */
	while(1)
	{
	}
}
实验结果

OLED模块从右至左滚动显示字符"ABCDEFG1234567890"

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值