打印
[活动专区]

【N32G401征文】空调控制面板以及常见外设驱动配置

[复制链接]
243|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Wayneso|  楼主 | 2024-1-11 14:48 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
#技术资源# #申请开发板# #申请原创#        手上目前有一块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();
}

本文由于篇幅原因仅分享所有的驱动配置,各个硬件外设通讯协议以及时序都能在网上找到资源,只需要将驱动配置完成即可使用。
欢迎官方以及各位工程师帮忙指出不足之处~




使用特权

评论回复
沙发
Wayneso|  楼主 | 2024-1-11 15:22 | 只看该作者
本来是打算使用SPI驱动的液晶屏幕,驱动都差不多写好了,后来发现HT1621更简易和便宜,所以换成了断码屏。
有机会再写一篇SPI+DMA驱动LCD屏幕

使用特权

评论回复
板凳
jobszheng| | 2024-1-11 15:44 | 只看该作者
我们当年低成本方案也是使用的HT1621与断码屏组合的方式。超级好用,代码也简洁

使用特权

评论回复
地板
Wayneso|  楼主 | 2024-1-11 16:17 | 只看该作者
jobszheng 发表于 2024-1-11 15:44
我们当年低成本方案也是使用的HT1621与断码屏组合的方式。超级好用,代码也简洁 ...

确实好用hhh,对一些显示固定面板首选HT1621+断码屏

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2

主题

7

帖子

0

粉丝