#技术资源# #申请开发板# #申请原创# 手上目前有一块N32G401F8S7-1,一共20个引脚。用这个芯片简单制作了一个空调控制面板,用到了一些常用的外设驱动。N32G401这块芯片性价比是可以的,不过由于是新出的芯片,目前很多网络上对这块芯片的资料并不多,更多的是借鉴官方的操作手册以及例程,所以,本人将自己在制作空调控制面板过程中的一些经验以及驱动代码(自己造的轮子)分享出来,希望各位大佬能够多多指导。(N32G401F8S7-1只有20个引脚有点不够用,所以参加活动也是希望能获得一块板子,这样可以对N32G401有更多的深入应用)
首先简单概括一下配置的驱动:定时器,串口,看门狗,中断,GPIO。
配置 LCD 屏幕驱动(HT1621驱动)
配置DS18B20温度传感器(one_wire 协议,需外接上拉电阻)
配置 USART,用串口传输数据
配置按键 KEY,五个按键使用中断的方式分别控制不同功能
配置 Delay,Iwdg
芯片使用的是内置的HSI高速时钟,(没有外接晶振)
内置HSI时钟是8MHZ,有±1%的误差,可以使用PLL进行倍频,如果引脚够用建议使用外接晶振
DS18B20对时序要求比较高,需要1us分辨率的延迟,(国民技术官方的例程里有好几种延迟)
对时序要求高的外设,建议使用系统时钟进行延迟
1.配置LCD屏幕驱动(HT1621)
先看数据手册,确保地址无误,如果不确定的话可以用for循环遍历所有地址,再根据实际配置地址
数码管显示数字由于是两位地址确定一个数码管,所以写了两个数组,一个对应高位,一个对应低位
每个HT1621的对应的地址都不一样,务必根据自己实际地址配置,不然显示肯定出错
以下是代码,ht1621和背光引脚的引脚初始化,需要注意的是:我用N32G401的内部8MHZ晶振HSI,然后通过PLL倍频将芯片时钟设定为64MHZ 所以定时器的预分频值为640-1。 我给背光引脚配置了一个高级定时器,配置PWM模式来控制亮度 /**
* [url=home.php?mod=space&uid=139335]@name[/url] LCD_BLK_Value
* [url=home.php?mod=space&uid=42490]@function[/url] LCD背光值(0~100)
* @param u8 set
* [url=home.php?mod=space&uid=266161]@return[/url] none
*/
void LCD_BLK_Value(u8 set)
{
TIM_Compare2_Set(TIM1, set);
}
以下是HT1621以及背光的GPIO配置。
/**
* @description: 屏幕初始化
* @param none
* [url=home.php?mod=space&uid=266161]@return[/url] none
*/
void LCD_Init(void)
{
RCC_AHB_Peripheral_Clock_Enable(RCC_AHB_PERIPH_GPIOA);
RCC_AHB_Peripheral_Clock_Enable(RCC_AHB_PERIPH_GPIOB);
GPIO_InitType GPIO_InitStructure;
GPIO_Structure_Initialize(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_5 | GPIO_PIN_7;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUT_PP;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_SLEW_RATE_FAST;
GPIO_Peripheral_Initialize(GPIOA, &GPIO_InitStructure);
GPIO_Structure_Initialize(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_1;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUT_PP;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_SLEW_RATE_FAST;
GPIO_Peripheral_Initialize(GPIOB, &GPIO_InitStructure);
LCD_CS_1();
LCD_DATA_1();
LCD_WR_1();
HT1621_WriteCommand(HT1621_SYS_EN); // 打开系统振荡器
HT1621_WriteCommand(HT1621_BIAS); // BIAS 1/3 4个公共口
HT1621_WriteCommand(HT1621_RC256); // 使用RC_256K系统时钟源,片内RC振荡器
HT1621_WriteCommand(HT1621_WDT_DIS);
HT1621_WriteCommand(HT1621_LCD_ON);
LCD_BLK_Init();
LCD_BLK_Value(LCD_BLK_VALUE); // 开启背光
HT1621_Set_LCD(1); // 屏幕显示
}
/**
* [url=home.php?mod=space&uid=139335]@name[/url] LCD_BLK_Init
* [url=home.php?mod=space&uid=42490]@function[/url] LCD背光初始化
* @param none
* [url=home.php?mod=space&uid=266161]@return[/url] none
*/
void LCD_BLK_Init(void)
{
RCC_AHB_Peripheral_Clock_Enable(RCC_AHB_PERIPH_GPIOA);
RCC_APB2_Peripheral_Clock_Enable(RCC_APB2_PERIPH_TIM1);
RCC_APB2_Peripheral_Clock_Enable(RCC_APB2_PERIPH_AFIO);
GPIO_InitType GPIO_InitStructure;
GPIO_Structure_Initialize(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_9;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.GPIO_Current = GPIO_DS_2MA;
GPIO_InitStructure.GPIO_Alternate = GPIO_AF3_TIM1;
GPIO_Peripheral_Initialize(GPIOA, &GPIO_InitStructure);
TIM_TimeBaseInitType TIM_TimeBaseInitStructure;
TIM_Base_Struct_Initialize(&TIM_TimeBaseInitStructure);
TIM_TimeBaseInitStructure.Period = 100 - 1;
TIM_TimeBaseInitStructure.Prescaler = 640 - 1;
TIM_TimeBaseInitStructure.RepetCnt = 0;
TIM_TimeBaseInitStructure.CntMode = TIM_CNT_MODE_UP;
TIM_TimeBaseInitStructure.ClkDiv = TIM_CLK_DIV1;
TIM_Base_Initialize(TIM1, &TIM_TimeBaseInitStructure);
TIM_On(TIM1);
OCInitType TIM_OCInitStructure;
TIM_Output_Channel_Struct_Initialize(&TIM_OCInitStructure);
TIM_OCInitStructure.OcMode = TIM_OCMODE_PWM1;
TIM_OCInitStructure.OcNPolarity = TIM_OCN_POLARITY_LOW;
TIM_OCInitStructure.OutputState = TIM_OUTPUT_STATE_ENABLE;
TIM_OCInitStructure.Pulse = 0;
TIM_OCInitStructure.OcNIdleState = TIM_OCN_IDLE_STATE_SET;
TIM_Output_Channel2_Initialize(TIM1, &TIM_OCInitStructure);
TIM_PWM_Output_Enable(TIM1);
}
2.配置DS18B20温度传感器(one_wire 协议,需外接上拉电阻)
需要了解并掌握DS18B20传输时序,由于对时序要求比较高,务必确保延迟函数的准确度
以下是配置代码: /**
* @description: 初始化DS18B20的IO口 DQ 同时检测DS的存在
* @param none
* @return 返回1:不存在
* @return 返回0:存在
*/
u8 DS18B20_Init(void)
{
RCC_AHB_Peripheral_Clock_Enable(RCC_AHB_PERIPH_GPIOA);
GPIO_InitType GPIO_InitStructure;
GPIO_InitStructure.Pin = DS18B20_PIN; // 推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUT_PP;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_SLEW_RATE_FAST;
GPIO_Peripheral_Initialize(DS18B20_PORT, &GPIO_InitStructure);
GPIO_Pins_Set(DS18B20_PORT, DS18B20_PIN); // 拉高
DS18B20_Rst();
return DS18B20_Check();
}
/**
* @description: IO口输出初始化
* @param none
* @return none
*/
void DS18B20_IO_OUT(void)
{
GPIO_InitType GPIO_InitStructure;
GPIO_InitStructure.Pin = DS18B20_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUT_PP;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_SLEW_RATE_FAST;
GPIO_Peripheral_Initialize(DS18B20_PORT, &GPIO_InitStructure);
}
/**
* @description: IO口输入初始化
* @param none
* @return none
*/
void DS18B20_IO_IN(void)
{
GPIO_InitType GPIO_InitStructure;
GPIO_InitStructure.Pin = DS18B20_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_INPUT;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_SLEW_RATE_FAST;
GPIO_Peripheral_Initialize(DS18B20_PORT, &GPIO_InitStructure);
}
在配置的过程中,我发现使用STM32大多都是通过bit-band操作寄存器,我查了官方的例程,也有相关例程,由于篇幅有限,就不再这里拓展,以后有机会单开一篇来讲讲 DS18B20用的是one-wire通讯协议,所以可以像这样配置操作总线传输数据: #define DS18B20_DQ_IN GPIO_Input_Pin_Data_Get(DS18B20_PORT, DS18B20_PIN)
#define DS18B20_DQ_OUT_1 GPIO_Pins_Set(DS18B20_PORT, DS18B20_PIN)
#define DS18B20_DQ_OUT_0 GPIO_Pins_Reset(DS18B20_PORT, DS18B20_PIN)
3.配置按键 KEY,五个按键使用中断的方式分别控制不同功能
/**
* [url=home.php?mod=space&uid=139335]@name[/url] KEY_init
* [url=home.php?mod=space&uid=42490]@function[/url] 初始化按键,启用中断
* @param none
* @return none
*/
void KEY_init(void)
{
RCC_AHB_Peripheral_Clock_Enable(RCC_AHB_PERIPH_GPIOA); // 按键全部使用GPIOA
RCC_APB2_Peripheral_Clock_Enable(RCC_APB2_PERIPH_AFIO);
RCC_APB1_Peripheral_Clock_Enable(RCC_APB1_PERIPH_TIM6);
GPIO_InitType GPIO_InitStructure;
GPIO_Structure_Initialize(&GPIO_InitStructure);
GPIO_InitStructure.Pin = KEY6_GPIO_PIN | KEY7_GPIO_PIN | KEY8_GPIO_PIN | KEY9_GPIO_PIN | KEY10_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_INPUT; // 输入
GPIO_InitStructure.GPIO_Pull = GPIO_PULL_UP;
GPIO_Peripheral_Initialize(GPIOA, &GPIO_InitStructure);
GPIO_EXTI_Line_Set(EXTI_LINE_SOURCE5, AFIO_EXTI_PA0);
GPIO_EXTI_Line_Set(EXTI_LINE_SOURCE6, AFIO_EXTI_PA1);
GPIO_EXTI_Line_Set(EXTI_LINE_SOURCE7, AFIO_EXTI_PA2);
GPIO_EXTI_Line_Set(EXTI_LINE_SOURCE8, AFIO_EXTI_PA3);
GPIO_EXTI_Line_Set(EXTI_LINE_SOURCE9, AFIO_EXTI_PA4);
/* Configure key EXTI line */
EXTI_InitType EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_LINE5;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Peripheral_Initializes(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line = EXTI_LINE6;
EXTI_Peripheral_Initializes(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line = EXTI_LINE7;
EXTI_Peripheral_Initializes(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line = EXTI_LINE8;
EXTI_Peripheral_Initializes(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line = EXTI_LINE9;
EXTI_Peripheral_Initializes(&EXTI_InitStructure);
/* Set key input interrupt priority */
NVIC_InitType NVIC_InitStructure;
NVIC_Priority_Group_Set(NVIC_PER3_SUB1_PRIORITYGROUP);
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_SUB_PRIORITY_3;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_PER_PRIORITY_3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Initializes(&NVIC_InitStructure);
}
4.配置USART,IWDG,DELAY 以下是官方例程中给的两种中断方式,根据实际需求选择使用
#include "DELAY.h"
/**
* @brief SysTick_Config 函数用于配置 SysTick 定时器
* @param ticks: SysTick 定时器的计数值
* @return 0 表示配置成功,1 表示配置失败(计数值过大)
*/
static uint32_t DBG_SysTick_Config(uint32_t ticks)
{
if (ticks > SysTick_LOAD_RELOAD_Msk)
return (1); /* 重新加载值不合理 */
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* 设置重新加载寄存器 */
NVIC_SetPriority(SysTick_IRQn, (1 << __NVIC_PRIO_BITS) - 1); /* 设置 Cortex-M0 系统中断的优先级 */
SysTick->VAL = 0; /* 加载 SysTick 计数器的初始值 */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_ENABLE_Msk; /* 启用 SysTick IRQ 和 SysTick 定时器 */
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; /* 禁用 SysTick 中断 */
SysTick->VAL = 0;
return (0); /* 配置成功 */
}
/**
* @brief SysTick_Delay_Us 函数用于提供微秒级延时
* @param us: 延时的微秒数
* @return 无
*/
void SysTick_Delay_Us(__IO uint32_t us)
{
volatile uint32_t i;
RCC_ClocksType RCC_Clocks;
RCC_Clocks_Frequencies_Value_Get(&RCC_Clocks); /* 获取系统时钟频率 */
DBG_SysTick_Config(RCC_Clocks.SysclkFreq / 1000000); /* 配置 SysTick 定时器为微秒分辨率 */
for (i = 0; i < us; i++)
{
/* 当计数器值减至0时,CRTL 寄存器的第16位将被设置为1 */
/* 当设置为1时,读取此位将其清零 */
while (!(SysTick->CTRL & (1 << 16)))
;
}
/* 关闭 SysTick 定时器 */
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
/**
* @brief SysTick_Delay_Ms 函数用于提供毫秒级延时
* @param ms: 延时的毫秒数
* @return 无
*/
void SysTick_Delay_Ms(__IO uint32_t ms)
{
volatile uint32_t i;
RCC_ClocksType RCC_Clocks;
RCC_Clocks_Frequencies_Value_Get(&RCC_Clocks); /* 获取系统时钟频率 */
DBG_SysTick_Config(RCC_Clocks.SysclkFreq / 1000); /* 配置 SysTick 定时器为毫秒分辨率 */
for (i = 0; i < ms; i++)
{
/* 当计数器值减至0时,CRTL 寄存器的第16位将被设置为1 */
/* 当设置为1时,读取此位将其清零 */
while (!(SysTick->CTRL & (1 << 16)))
;
}
/* 关闭 SysTick 定时器 */
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
void systick_delay_us(u32 nus)
{
u32 temp;
SysTick_Clock_Source_Set(SYSTICK_CLKSOURCE_HCLK); // select system clock
SysTick->LOAD = nus * (SystemClockFrequency / 1000000); // time relode
SysTick->VAL = 0x00; // clear timer value
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // Start countdown
do
{
temp = SysTick->CTRL;
} while (temp & 0x01 && !(temp & (1 << 16))); // wait for the time reach
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // close the count
SysTick->VAL = 0X00; // clear timer value
}
/**
*\*\name systick_delay_ms.
*\*\fun ms delay program function.
*\*\param nms
*\*\return none
**/
void systick_delay_ms(u16 nms)
{
u32 temp;
SysTick_Clock_Source_Set(SYSTICK_CLKSOURCE_HCLK); // select system clock
SysTick->LOAD = (u32)nms * ((SystemClockFrequency / 1000000) * 1000); // time relode(SysTick->LOAD is 24bit)
SysTick->VAL = 0x00; // clear timer value
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // Start countdown
do
{
temp = SysTick->CTRL;
} while (temp & 0x01 && !(temp & (1 << 16))); // wait for the time reach
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // close the count
SysTick->VAL = 0X00; // clear timer value
}
配置USART:
/**
* @name USART2_Init
* @function USART2初始化 逻辑"0"发送 逻辑"1"接收
* @param none
* @return none
*/
void USART2_Init(void)
{
RCC_APB1_Peripheral_Clock_Enable(RCC_APB1_PERIPH_USART2);
RCC_AHB_Peripheral_Clock_Enable(RCC_AHB_PERIPH_GPIOD);
RCC_APB2_Peripheral_Clock_Enable(RCC_APB2_PERIPH_AFIO);
GPIO_InitType GPIO_InitStructure;
/* 配置USARTy Tx为复用推挽输出 */
GPIO_InitStructure.Pin = GPIO_PIN_15;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.GPIO_Alternate = GPIO_AF4_USART2;
GPIO_Peripheral_Initialize(GPIOD, &GPIO_InitStructure);
/* 配置USARTy Rx为复用推挽输入和上拉输入 */
GPIO_InitStructure.Pin = GPIO_PIN_14;
GPIO_InitStructure.GPIO_Alternate = GPIO_AF4_USART2;
GPIO_Peripheral_Initialize(GPIOD, &GPIO_InitStructure);
USART_InitType USART2_InitStructure;
USART2_InitStructure.BaudRate = 9600;
USART2_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
USART2_InitStructure.WordLength = USART_WL_8B;
USART2_InitStructure.Parity = USART_PE_NO;
USART2_InitStructure.StopBits = USART_STPB_1;
USART2_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
USART_Initializes(USART2, &USART2_InitStructure);
NVIC_InitType NVIC_InitStructure;
NVIC_Priority_Group_Set(NVIC_PER2_SUB2_PRIORITYGROUP);
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Initializes(&NVIC_InitStructure);
USART_Interrput_Enable(USART2, USART_INT_RXDNE);
USART_Interrput_Enable(USART2, USART_INT_TXDE);
USART_Enable(USART2);
}
配置IWDG:
__IO uint32_t LsiFreq = 40000; // 内部低速时钟频率,初始值设定为 40000
void IWDG_Config(IWDG_CONFIG_PRESCALER IWDG_prescaler, uint16_t reload_value)
{
/* 由于 LSI 频率变化,超时时间可能有所不同 */
/* 关闭 IWDG_PREDIV 和 IWDG_RELV 寄存器的写保护 */
IWDG_Write_Protection_Disable();
/* IWDG 计数器时钟预分频设置 */
IWDG_Prescaler_Division_Set(IWDG_prescaler);
/* 设置 IWDG 重装载值 */
/* 设置计数器重装载值以获取 x 秒的 IWDG 超时。
计数器重装载值时间 = x(秒) / IWDG 计数器时钟周期
= x(秒) / (LSI/IWDG_prescaler)
*/
IWDG_Counter_Reload(reload_value);
/* 使用 IWDG_PREDIV 的值重新加载计数器到 IWDG_RELV 寄存器,以防止复位 */
IWDG_Key_Reload();
/* 启用 IWDG(LSI 振荡器将由硬件启用) */
IWDG_Enable();
}
void IWDG_Feed(void)
{
/* 将重装载寄存器的值写入计数器 */
IWDG_Key_Reload();
}
本文由于篇幅原因仅分享所有的驱动配置,各个硬件外设通讯协议以及时序都能在网上找到资源,只需要将驱动配置完成即可使用。 欢迎官方以及各位工程师帮忙指出不足之处~
|