打开主菜单

谷雨文档中心 β

更改

NBDK-L4:基础实验教程

删除6,808字节2019年1月24日 (四) 16:58
硬件设计
''<span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span>''
=== 硬件设计 ===
选择STM32L4引脚PC6用来捕获红外传感器HS0038的DATA引脚输出的PWM波。选择STM32L4引脚PA9和PA10作为串口,当我们将拨码开关SW1拨到USB一端时,此串口通过CH340芯片转成USB接口,用于向电脑上打印一些调试信息。[[文件:NBDK-SCH-UART-USB.png|边框|居中|无框|707x707像素]]
''<span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span><span class="tlid-translation-gender-indicator translation-gender-indicator"></span>''
=== 实验准备 ===
# 使用miniUSB线及10pin排线,通过Jlink仿真器连接PC端和开发板。
# 使用miniUSB线,连接PC与开发板USB接口。
# 将SW1拨到DBG端,SW2拨到MCU。将SW1拨到USB端,SW2拨到MCU。# 使用Keil打开基础实验 0910-红外线接收实验工程。串口打印实验工程。# 使用Xshell打开Jlink虚拟出的COM口使用Xshell打开miniUSB虚拟出的COM口
# 下载程序,并完成功能测试。
=== 实验验证 ===
下载完成后,我们按下遥控器上的任意按键,可以看到LCD上将显示如下,irBtnVal代表的是键值,irBtnCnt代表是按键被按下的次数,irBtnInfo代表按键的图标或者定义。下载完成后,我们打开miniUSB虚拟出的COM口,可以看到串口周期性的打印计数值。[[文件:NBDK-XSHELL-UARTPRINTF.png|边框|居中|无框|759x759像素]]
=== 源码详解 ===
此文件位于“09-红外线接收实验\Inc”路径中,主要用途是选择使能此例程使用到的库文件。
此例程我们主要给大家展示STM32L4的I2C功能,所以我们宏定义中打开I2C相关的。此例程我们主要给大家展示STM32L4的串口功能,所以我们宏定义中打开UART相关的。<syntaxhighlight lang="c" line="1" start="103">
// 使能的宏
#define HAL_MODULE_ENABLED // 芯片
#define HAL_GPIO_MODULE_ENABLED // GPIO
#define HAL_DMA_MODULE_ENABLED // DMA
#define HAL_UART_MODULE_ENABLED // UART
#define HAL_DMA_MODULE_ENABLED // DMA
#define HAL_ADC_MODULE_ENABLED // ADC
#define HAL_DAC_MODULE_ENABLED // DAC
#define HAL_I2C_MODULE_ENABLED // I2C
</syntaxhighlight>
main函数,我们的例程由此处开始执行,首先调用HAL_Init()函数初始化我们的模块,接着调用SystemClock_Config()函数初始化此例程用到的时钟,具体有哪些时钟被初始化,在gyu_util.c部分有详细说明。
接下来我们初始化LCD的SPI控制引脚,LCD背光引脚,并且初始化LCD的图形控制界面。 然后我们在LCD上打印固定的遥控器按键显示格式,也就是"irBtnVal"、"irBtnCnt"、"irBtnInfo"这几个字符串。 接下来我们初始化我们此实验的重点功能,也就是TIM3定时器。接下来我们初始化串口UASRT1。
在while()循环中,我们轮询遥控器的按键信息,一旦有遥控器按下,则在LCD的对应位置,打印按键的信息。循环中,我们每隔100ms通过格式化输出"TimeCount = xx:xx:xx"。
<syntaxhighlight lang="c++" line="1" start="34">
int main(void)
{
irInfo_t irkey uint32_t hour = {0,; uint32_t minute = 0}; uint32_t second = 0; 
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
// 重置所有外设、flash界面以及系统时钟
// 配置系统时钟(包含振荡器、系统时钟、总线时钟等等)
SystemClock_Config();
// LCD SPI初始化 LCD_GPIO_Init(); // LCD IO控制引脚(例如背光) MX_SPI1_Init(); // LCD SPI控制引脚 // 图形界面初始化 GUI_Init(); // GUI界面初始化 GUI_Clear(); // 清屏 GUI_SetColor(GUI_Crimson); // 红色字体 GUI_DispStringAt("irBtnVal:",24,24); // 打印字符串"irBtnVal:"到位置X->24,Y->24 GUI_DispStringAt("irBtnCnt:",24,72); // 打印字符串"irBtnCnt:"到位置X->24,Y->96 GUI_DispStringAt("irBtnInfo:",24,120); // 打印字符串"irBtnCnt:"到位置X->24,Y->120 // 初始化TIM3初始化串口USART1 MX_TIM3_InitMX_USART1_UART_Init();
//
while(1)
{
irkey // 模拟时钟计时,这边的1s实际只是100ms HAL_Delay(100); // 100ms延时 printf("TimeCount = IRBNT_POLL(%02d:%02d:%02d\r\n",hour,minute,second); // 格式化输出"TimeCount = xx:xx:xx"  // 轮训获取IR按键信息时分秒计数 second++; if(irkey.irBtnValsecond == 60) { second = 0; minute++; } if(minute == 60) { minute = 0; hour++; } if(hour == 24) // 如果按键信息存在
{
GUI_DispHexAt(irkey.irBtnVal,144,24,2); // 打印按键值 GUI_DispHexAt(irkey.irBtnCnt,144,72,2); // 打印按键计数 // 打印按键图标或名称 GUI_GotoXY(144,120); // 指的光标位置 GUI_ClearArea(); // 清除指定位置数据 switch(irkey.irBtnVal) // 判断按键值,打印相应图标或名称 { case REMOTE_BTN_SWITCH: GUI_DispString("'switch'"); break; case REMOTE_BTN_MENU: GUI_DispString("'menu'"); break; case REMOTE_BTN_MUTE: GUI_DispString("'mute'"); break; case REMOTE_BTN_MODE: GUI_DispString("'mode'"); break; case REMOTE_BTN_PLUS: GUI_DispString("'+'"); break; case REMOTE_BTN_RETURN: GUI_DispString("'return'"); break; case REMOTE_BTN_REWIND: GUI_DispString("'|<<'"); break; case REMOTE_BTN_PAUSE: GUI_DispString("'>||'"); break; case REMOTE_BTN_FASTFORWARD: GUI_DispString("'>>|'"); break; case REMOTE_BTN_0: GUI_DispString("'hour = 0'"); break; case REMOTE_BTN_LESS: GUI_DispString("'-'"); break; case REMOTE_BTN_OK: GUI_DispString("'OK'"); break; case REMOTE_BTN_1: GUI_DispString("'1'"); break; case REMOTE_BTN_2: GUI_DispString("'2'"); break; case REMOTE_BTN_3: GUI_DispString("'3'"); break; case REMOTE_BTN_4: GUI_DispString("'4'"); break; case REMOTE_BTN_5: GUI_DispString("'5'"); break; case REMOTE_BTN_6: GUI_DispString("'6'"); break; case REMOTE_BTN_7: GUI_DispString("'7'"); break; case REMOTE_BTN_8: GUI_DispString("'8'"); break; case REMOTE_BTN_9: GUI_DispString("'9'"); break; }
}
}
</syntaxhighlight>
==== gyu_irc gyu_usartc ====
初始化定时器TIM3,首先配置TIM3的时钟为1MHz(也就是1us),我们设置它向上自动装载,并且设置自动装载值为10000,通过计算可以知道装满一次需要10ms。
最后使能TIM3的中断,并且开始捕获TIM3的通道1(也就是PC6引脚)。
<syntaxhighlight lang="c++" line="1" start="5937">void MX_TIM3_InitMX_USART1_UART_Init(void)
{
htim3// 配置串口参数 huart1.Instance = TIM3USART1; // 通用定时器3UART寄存器基础地址,定义为USART1的 htim3huart1.Init.Prescaler BaudRate = 80-1115200; // TIM3 80预分频器(APB2总线),80MHz 串口波特率为115200 huart1.Init.WordLength = UART_WORDLENGTH_8B; // 80 = 1MHz(1us)串口数据位为8位 htim3huart1.Init.CounterMode StopBits = TIM_COUNTERMODE_UPUART_STOPBITS_1; // 向上计数器串口停止位为1位 htim3huart1.Init.Period Parity = 10000UART_PARITY_NONE; // 自动装载值设为10000,装满一次 10000 * 1us = 10ms串口无校验位 htim3huart1.Init.ClockDivision Mode = TIM_CLOCKDIVISION_DIV1UART_MODE_TX_RX; // 不分频串口模式,TX和RX作用 htim3huart1.Init.AutoReloadPreload HwFlowCtl = TIM_AUTORELOAD_PRELOAD_ENABLEUART_HWCONTROL_NONE; // 自动加载使能串口无流控制 if (HAL_TIM_IC_Init(&htim3) !huart1.Init.OverSampling = HAL_OK) UART_OVERSAMPLING_16; // 初始化TIM3,出错则进入错误处理函数16位过采样 {huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; _Error_Handler(__FILE__, __LINE__);// 1位过采样禁能 }huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; // 没有串口高级功能初始化
// 初始化TIM3输入捕获参数 TIM_IC_InitTypeDef sConfigIC; // 定义输入捕获结构体(IC:Input capture) sConfigIC.ICPolarity = TIM_ICPOLARITY_BOTHEDGE; // 上升沿下降沿都捕获 sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; // 配置为TI1 sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; // 不分频 sConfigIC.ICFilter = 0x03; // IC1F=0011 8个定时器时钟周期滤波 // 初始化TIM3 CH1通道,出错则进入错误处理函数串口初始化 if (HAL_TIM_IC_ConfigChannelHAL_UART_Init(&htim3, &sConfigIC, TIM_CHANNEL_1huart1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__); // 如果初始化失败,进入错误处理任务
}
HAL_TIM_Base_Start_IT(&htim3); // 使能更新中断(也就是TIM_IT_UPDATE) HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1); // 开始捕获TIM3 CH1
}
</syntaxhighlight>使能GPIOC以及TIM3的时钟,并且配置PC6为TIM3的通道1。<syntaxhighlight lang="c++" line="1" start="9667">void HAL_TIM_IC_MspInitHAL_UART_MspInit(TIM_HandleTypeDef UART_HandleTypeDef*htimuartHandle)
{
GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_TIM3_CLK_ENABLE(); // 使能TIM3时钟定义GPIO结构体 __HAL_RCC_GPIOC_CLK_ENABLE()GPIO_InitTypeDef GPIO_InitStruct; // 开启GPIOC时钟
GPIO_Initure.Pin = GPIO_PIN_6; // PC6判断选择的是否为USART1 GPIO_Initure.Mode if(uartHandle->Instance== GPIO_MODE_AF_PP; // 推挽输出USART1) GPIO_Initure.Pull = GPIO_PULLUP; { // 上拉使能GPIOA引脚时钟(因为选择的TX和RX分别为PA9和PA10) GPIO_Initure.Speed = GPIO_SPEED_HIGH __HAL_RCC_GPIOA_CLK_ENABLE(); // 高速模式 GPIO_Initure.Alternate = GPIO_AF2_TIM3; // PC6配置为TIM3通道1使能USART1时钟 HAL_GPIO_Init __HAL_RCC_USART1_CLK_ENABLE(GPIOC, &GPIO_Initure);
HAL_NVIC_SetPriority(TIM3_IRQn, 10, 0) // GPIO配置 GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10; // 选择USART1的TX和RX引脚(TX:PA9,RX:PA10) GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 推挽输出 GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;// 引脚频率5-80MHz GPIO_InitStruct.Alternate = GPIO_AF7_USART1; // 设置TIM3中断优先级配置为USART1 HAL_NVIC_EnableIRQ HAL_GPIO_Init(TIM3_IRQnGPIOA, &GPIO_InitStruct); // 使能TIM3中断初始化引脚 }
}
</syntaxhighlight>定时器周期中断回调函数,TIM3的自动装载值装满一次,进入一次此回调(本工程配置的参数是10ms进入一次)。
如果是接收到引导码之后,第一次进入此函数,那么我们使能记录遥控器按键值的标志位,也就是代表接收到了一次NEC数据(不管数据对错)。
如果进入的次数少于11次,则继续增加计数,当计数值等于11时(也就是从接收到引导码已经过去至少110ms时),我们认为一次NEC的数据获取已经完成,此时清除周期回调的计数值,并且删除引导码标志位(下次再进到这个函数时,只有新的引导码数据到来,才会进行新的数据处理)。<syntaxhighlight lang="c++" line="1" start="12299">void HAL_TIM_PeriodElapsedCallback#define PUTCHAR_PROTOTYPE int fputc(TIM_HandleTypeDef int ch, FILE *htimf){ if(htim->Instance == TIM3) { if(irStatus & IR_STATUS_BootCode) // 如果接收到引导码 { irStatus &= ~IR_STATUS_Rising; // 删除上升沿标记(以防止本次出错,确保下次采集流程正确) if(tim3Cnt == 0) // 如果是第一次进入(计数为0) { irStatus |= IR_STATUS_BtnInfo; // 记录已经获取到IR按键信号(遥控器按键值) } if((tim3Cnt & 0X0F) < 11) // 进入回调少于11次 { tim3Cnt++; // 计数值自加 } else // 超过11次,代表一次采集超时(不论是否成功) { irStatus &= ~IR_STATUS_BootCode;// 删除引导码标记 tim3Cnt = 0; // 清除计数值 } } }}</syntaxhighlight>处理TIM3 CH1(PC6引脚)捕获的数据,这边要记得初始化中我们使能了上升沿下降沿都捕获数据。 当有边沿捕获到来,我们判断此时PC6引脚的电平。
如果此时是高电平,则代表刚刚的是上升沿,此时我们使能上升沿标志位,并且清空定时器计数值。 如果此时是低电平,且上升沿标识位被置位,则我们根据获取到的计数值(也就是上一次高电平的持续时间)来判断本段PWM波代表的含义。<syntaxhighlight lang="c++" line="1" start="155">void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)PUTCHAR_PROTOTYPE
{
// 判断是否为TIM3 CH1捕获产生的回调配置格式化输出到串口USART1 ifHAL_UART_Transmit((htim->Instance == TIM3) && (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)) { ifhuart1, (HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_6uint8_t *)) // 获取PC6引脚电平,如果是高电平,则代表是上升沿捕获 { __HAL_TIM_SET_COUNTER(&htim3ch, 0); // 清除TIM3定时器计数值 irStatus |= IR_STATUS_Rising; // 标记上升沿捕获 } else //如果是低电平,则代表是下降沿触发 { tim3Val = HAL_TIM_ReadCapturedValue(&htim31, TIM_CHANNEL_10xFFFF); // 读取TIM3 CH1定时器计数值 if(irStatus & IR_STATUS_Rising) // 存在上升沿标记,我们比较定时器计数值 { if(tim3Val > 260 && tim3Val < 860) // 高电平持续560us代表bit 0,范围560us±300us { irRecData <<= 1; // 左移一位 irRecData |= 0; // bit位赋值0 } else if(tim3Val > 1380 && tim3Val < 1980) // 高电平持续1680us代表bit 1,范围1680us±300us { irRecData <<= 1; // 左移一位 irRecData |= 1; // bit位赋值1 } else if(tim3Val > 2200 && tim3Val < 2800) // 高电平持续2500us代表本次按键结束,范围2500us±300us { irCnt++; // 按键次数新增1 tim3Cnt = 0; // 清除计数值 } else if(tim3Val > 4200 && tim3Val < 4800) // 高电平持续4500us代表新的按键,范围4500us±300us { irStatus |= IR_STATUS_BootCode; // 标记引导码 irCnt = 0return ch; // 有新的按键到来,清除按键计数 } } irStatus &= ~IR_STATUS_Rising; // 清除上升沿标记 } }
}
</syntaxhighlight>轮询当前的按键信息。如果按键值标识存在,则代表有按键被按下,接着判断地址码以及数据数据正确,并将最终的按键数据添加到irinfo中留给应用层调用。<syntaxhighlight lang="c++" line="1" start="206">
irInfo_t IRBNT_POLL(void)
{
irInfo_t irinfo = {0,0}; // 定义按键信息结构体
uint8_t bcode, dcode; // 定义引导码正反编码
uint8_t bvalue, dvalue; // 定义按键值正反编码
if(irStatus & IR_STATUS_BtnInfo) // 如果获取到按键值
{
bcode = irRecData >> 24; // 地址码
dcode = (irRecData >> 16) & 0xff; // 地址码反编码
if((bcode == (uint8_t)~dcode) && bcode == REMOTE_DEVICE_ID) // 判断地址码是否正确 { bvalue = irRecData >> 8; // 按键值 dvalue = irRecData; // 按键值反编码 if(bvalue == (uint8_t)~dvalue) // 判断按键值是否正确 { irinfo.irBtnVal = bvalue; // 将按键值赋给irinfo.irBtnVal } } irinfo.irBtnCnt = irCnt; // 将按键此处赋给irinfo.irBtnCnt }  return irinfo; // 返回按键信息}</syntaxhighlight>
[[分类:NB-IOT]]
[[分类:NBDK-L4]]
[[分类:教程]]
__强显目录__
510
个编辑