qintian0303 发表于 2022-9-14 15:15

【AT-START-WB415测评】综合测试

本帖最后由 qintian0303 于 2022-9-14 15:15 编辑

概述 非常感谢21ic论坛和雅特力组织这次活动,让我们又一次认识了以为新朋友,很快就收到了开发板,不过只有一个裸板,还因为运输问题出现了部分引脚的的损坏,不过不影响本次测试,大力的将它复原就可以了。开发板收到后就对上电欣赏了一下内置的程序,简单的LED操作,这些不重要,重要的还是要先了解一下开发板的资源,开展系列测试计划。
资源分析开发板板载AT-Link下载工具;使用的是AT32F403CCT6作为Link的主控芯片,M4核,最高150 MHz工作频率,不过只能分频到144;芯片默认是FLASH启动;射频天线器件包含一个板载PCB天线和进行专业性能测试的SMA接口;板载的LED分别是红、黄、绿各一个,供电指示灯个人建议用绿色的更好;用户按键使用了不同颜色的按键作为区分,不过真正可以进行功能按键的只有一个;SPI,IIC,串口等接口都有,种类齐全不过接口数量少;
需求分析
由以上资源的所支撑的,我们初步对测试内容和资源进行分析和分配,基本原则如下:SPI接口的常用器件有存储器(例如W25Q系列),支持SPI的TFT等,本次测试使用支持SPI通信的TFT,有利于整个测试过程的状态展示;IIC接口一般是各种传感器使用,本次选用AHT20温湿度传感器进行展示;串口可以通过PC测试,同时该开发板所使用的的芯片中的内置蓝牙同样是使用串口通信;确定这些基本测试后并结合板载的LED和按键,基本就能满足本次测试需要。初步的需求分析思维导图:
具体实现根据思维导图我们对于具体的需求进行实际的分析。首先作为这里的唯一输入对象,按键的识别其实是最主要的,其次是显示功能,然后根据各个界面去实现对应的功能,当然在这些操作之前,给单片机上一下发条还是非常必须的(时钟配置);1)时钟配置时钟配置离不开芯片的时钟树,可以清晰直观的进行各部时钟配置的选择;
其实在头文件中就有头文件中 EXTERNSystemInit就是执行的的时钟配置,在system_at32wb415.c的SystemInit如下:
由上图可以看到是直接进行的寄存器赋值,这样其实是不适合我们直接修改操作的,不易理解还容易出错,所以在这之外雅特力在main中又进行了一遍时钟配置system_clock_config();可以通过修改里面的寄存器配置。AT32WB415CCU7-7目前不考虑功耗,由于USB的存在必须能够通过PLLCLK分频得到48M,所以PLLCLK在150M的限值下最大可以设计成144,HEXT通过18倍频后得到PLLCLK。
2)按键配置按键作为目前开发板的唯一输入,准确快速获取其状态是非常有必要的,本次采用外部中断+定时器的方式来进行按键状态的判断,注意所有引脚都可以被配置为外部中断。定时器配置:基本定时器是用定时器10,注意分频的计算,Period对应定时ms;void Tmr10Base_init(uint16_t Period)
{
/* enable tmr10 clock */
crm_periph_clock_enable(CRM_TMR10_PERIPH_CLOCK, TRUE);

/* tmr1 configuration */
/* time base configuration */
/* systemclock/14400/10000 = 1hz */
tmr_base_init(TMR10, Period * 10 - 1, 14400 - 1);
tmr_cnt_dir_set(TMR10, TMR_COUNT_UP);

/* overflow interrupt enable */
tmr_interrupt_enable(TMR10, TMR_OVF_INT, TRUE);

/* tmr10 overflow interrupt nvic init */
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
nvic_irq_enable(TMR1_OVF_TMR10_IRQn, 0, 2);

/* enable tmr10 */
tmr_counter_enable(TMR10, TRUE);
}外部中断配置:按键对应的IO为PA0void port_EXIT_init(void)
{
exint_init_type exint_init_struct;

crm_periph_clock_enable(CRM_IOMUX_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);

gpio_exint_line_config(GPIO_PORT_SOURCE_GPIOA, GPIO_PINS_SOURCE0);

exint_default_para_init(&exint_init_struct);
exint_init_struct.line_enable = TRUE;
exint_init_struct.line_mode = EXINT_LINE_INTERRUPUT;
exint_init_struct.line_select = EXINT_LINE_0;
exint_init_struct.line_polarity = EXINT_TRIGGER_RISING_EDGE;
exint_init(&exint_init_struct);

nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
nvic_irq_enable(EXINT0_IRQn, 1, 0);
}到这里只是一个基本功能的配置,为了实现不阻塞,所有的操作都是对标志位的修改,为此建立一个结构体;typedef struct
{
uint8_t EXIT_flag; //中断触发标志
uint8_t EXIT_Data; //中断触发防抖计时
uint8_t Press_flag; //按键触发标志
uint8_t Press_Data; //按键类型判断计时
uint8_t ShortPress_flag; //短按触发标志
uint8_t LongPress_flag; //长按触发标志
}key;
key key_Ok;在外部中断中只进行EXIT_flag的复位,最主要是通过定时器中进行防抖和按键识别:if(key_Ok.EXIT_flag == 1)//防抖判断3)tft驱动首先了解一下本次使用的tft,1.54吋,IPS高清显示,分辨率240*240,驱动芯片为ST7789,支持MCU和SPI两种通信方式,本次通过SPI方式驱动液晶屏,相对来说分辨率不低,液晶屏SPI通信时只能作为从机接收数据,所以只用到了MOSI和SCK,CS和RS通过软件控制,复位和背光控制通过软件控制。初始化代码:#define CS_OUT0       gpio_bits_reset(GPIOA,GPIO_PINS_4)
#define CS_OUT1       gpio_bits_set(GPIOA,GPIO_PINS_4)

#define TFT_RS_reset            gpio_bits_reset(GPIOA,GPIO_PINS_5)
#define TFT_RS_set            gpio_bits_set(GPIOA,GPIO_PINS_5)
#define TFT_RESET_reset         gpio_bits_reset(GPIOA,GPIO_PINS_3)
#define TFT_RESET_set         gpio_bits_set(GPIOA,GPIO_PINS_3)
#define TFT_BL_SET            gpio_bits_set(GPIOA,GPIO_PINS_2)
#define TFT_BL_RESET            gpio_bits_reset(GPIOA,GPIO_PINS_2)
void SPI_IOInit(void)
{
gpio_init_type gpio_initstructure;
spi_init_type spi_init_struct;

crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);

/* software cs, pa4 as a general io to control flash cs */
gpio_initstructure.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
gpio_initstructure.gpio_pull         = GPIO_PULL_UP;
gpio_initstructure.gpio_mode         = GPIO_MODE_OUTPUT;
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_initstructure.gpio_pins         = GPIO_PINS_4|GPIO_PINS_5|GPIO_PINS_3;
gpio_init(GPIOA, &gpio_initstructure);

gpio_initstructure.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
gpio_initstructure.gpio_mode         = GPIO_MODE_OUTPUT;
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_initstructure.gpio_pull         = GPIO_PULL_DOWN;
gpio_initstructure.gpio_pins         = GPIO_PINS_2;
gpio_init(GPIOA, &gpio_initstructure);

CS_OUT0;
TFT_RS_reset;
TFT_RESET_reset;
TFT_BL_RESET;
/* sck */
gpio_initstructure.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
gpio_initstructure.gpio_pull         = GPIO_PULL_UP;
gpio_initstructure.gpio_mode         = GPIO_MODE_MUX;
gpio_initstructure.gpio_pins         = GPIO_PINS_13;
gpio_init(GPIOB, &gpio_initstructure);
/* mosi */
gpio_initstructure.gpio_pull         = GPIO_PULL_DOWN;
gpio_initstructure.gpio_mode         = GPIO_MODE_MUX;
gpio_initstructure.gpio_pins         = GPIO_PINS_15;
gpio_init(GPIOB, &gpio_initstructure);

crm_periph_clock_enable(CRM_SPI2_PERIPH_CLOCK, TRUE);
spi_default_para_init(&spi_init_struct);
spi_init_struct.transmission_mode   = SPI_TRANSMIT_FULL_DUPLEX;
spi_init_struct.master_slave_mode   = SPI_MODE_MASTER;
spi_init_struct.mclk_freq_division    = SPI_MCLK_DIV_8;
spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB;
spi_init_struct.frame_bit_num         = SPI_FRAME_8BIT;
spi_init_struct.clock_polarity      = SPI_CLOCK_POLARITY_HIGH;
spi_init_struct.clock_phase         = SPI_CLOCK_PHASE_2EDGE;
spi_init_struct.cs_mode_selection   = SPI_CS_SOFTWARE_MODE;
spi_init(SPI2, &spi_init_struct);
spi_enable(SPI2, TRUE);
}

void SPI_Send_Data(uint8_t Data)
{

CS_OUT0;                                                            //选中该从机
delay_us(1);

spi_i2s_data_transmit(SPI2, Data);
while(spi_i2s_flag_get(SPI2, SPI_I2S_TDBE_FLAG) == RESET);

delay_us(1);
CS_OUT1;                                                            

}注意CS_OUT1;的前面加的delay_us(1);是不可少的,可能上升太快导致通信异常。
4)界面设计界面的控制通过按键的短按和长按进行来实现的,例如界面菜单的切换通过短按实现,具体进入和退出界面通过长按来实现,这时候就需要对按键状态进行判断处理和执行界面切换和刷新操作;界面的分配:#define         Holle_Interface               1                               //开机欢迎界面
#define         Menu_Interface                2                               //菜单界面
#define         LEDTest_Interface             3                               //LED测试界面
#define         THP_Interface               4                               //温湿度大气压力显示界面
#define         RTCMenu_Interface             5                               //RTC菜单测试界面
#define         RTCshow_Interface             6                               //RTC实时显示界面
#define         UART_Interface                7                               //串口测试界面


#define         InterfaceOpen               1                               //其他参数显示界面
#define         InterfaceClose                0                               //其他参数阈值设置界面
typedef struct
{
uint8_t ing; //正在显示的界面
uint8_t Request; //要求显示的界面
}show;      
externshow    Menu,Menu_unit,LEDTest_unit,RTCMenu_unit;
按键状态判断及处理:void App_KEY_Judge(void)
{
if(key_Ok.ShortPress_flag == 1)//短按OK键
{
    switch(Menu.ing)
    {
      case Holle_Interface:
      Menu.Request = Menu_Interface;
      Menu_unit.Request = 1;
      break;
      
      case Menu_Interface:
      Menu_unit.Request = PARA_Cyclic(1,5,Menu_unit.Request,1);
      break;
      
      case LEDTest_Interface:
      LEDTest_unit.Request = PARA_Cyclic(1,3,LEDTest_unit.Request,1);
      LED.Mode_Dis = LEDTest_unit.Request;
      break;
      
      case RTCMenu_Interface:
      RTCMenu_unit.Request = PARA_Cyclic(1,4,RTCMenu_unit.Request,1);
      break;
      
      case UART_Interface:
      USART2_sendbits("AT32WB415 USART Test \r\n");
      break;
      
      default:
      break;

    }
    key_Ok.ShortPress_flag = 0;
}

if(key_Ok.LongPress_flag == 1)//长按OK键
{
   
    switch(Menu.ing)
    {
      case Holle_Interface:
      Menu.Request = Menu_Interface;
      Menu_unit.Request = 1;
      break;
      
      case Menu_Interface:
      if(Menu_unit.Request == 1)
      {
          Menu.Request = LEDTest_Interface;
          LEDTest_unit.Request = 1;
          LED.Mode_Dis = LEDTest_unit.Request;
      }
      else if(Menu_unit.Request == 2)
      {
          Menu.Request = THP_Interface;
      }
      else if(Menu_unit.Request == 3)
      {
          Menu.Request = UART_Interface;
      }
      else if(Menu_unit.Request == 5)
      {
          Menu.Request = RTCMenu_Interface;
          RTCMenu_unit.Request = 1;
      }
      
      Menu_unit.Request = 0;
      MenuInterface_unit_cut(Menu_unit.ing,Menu_unit.Request);
      Menu_unit.ing = 0;
      break;
      
      case LEDTest_Interface:
      Menu.Request = Menu_Interface;
      Menu_unit.Request = 1;
      
      LEDTest_unit.Request = 0;
      LEDTestface_unit_cut(LEDTest_unit.ing,LEDTest_unit.Request);
      LEDTest_unit.ing = 0;
      LED.Mode_Dis = 0;
      
      break;
      
      case THP_Interface:
      Menu.Request = Menu_Interface;
      Menu_unit.Request = 2;
         
      break;
      
      case RTCMenu_Interface:
      if(RTCMenu_unit.Request == 1)
      {
          Menu.Request = RTCshow_Interface;
          RTCMenu_unit.ing = 0;
      }
      else if(RTCMenu_unit.Request == 2)
      {
//          Menu.Request = THP_Interface;
      }
      else if(RTCMenu_unit.Request == 4)//返回上一级界面
      {
          Menu.Request = Menu_Interface;;
          Menu_unit.Request = 5;
         
          RTCMenu_unit.Request = 0;
          RTCMenuface_unit_cut(RTCMenu_unit.ing,RTCMenu_unit.Request);
          RTCMenu_unit.ing = 0;
      }
         
      break;
      
      case RTCshow_Interface:
      Menu.Request = RTCMenu_Interface;
      
      RTCMenu_unit.ing = 0;
      RTCMenu_unit.Request = 1;
         
      break;
      
      case UART_Interface:
      Menu.Request = Menu_Interface;
      Menu_unit.Request = 3;
         
      break;
      
      default:
      break;

    }

    key_Ok.LongPress_flag = 0;
}

}界面静态切换:if(Menu.ing != Menu.Request)                              //静态界面切换
{
    setup(Menu.Request);
}
void setup(uint8_t FaceNum)
{
switch(Menu.ing)
{
    case Holle_Interface:
      HolleInterface(InterfaceClose);
      break;
      
    case Menu_Interface:
      MenuInterface(InterfaceClose);
      break;
      
    case LEDTest_Interface:
      LEDTestface(InterfaceClose);
      break;
      
    case THP_Interface:
      THPInterface(InterfaceClose);
      break;
      
    case RTCMenu_Interface:
      RTCMenuface(InterfaceClose);
      break;
      
    case RTCshow_Interface:
      RTCShowface(InterfaceClose);
      break;
      
    case UART_Interface:
      USARTInterface(InterfaceClose);
      break;
      
    default:
      break;
      
}

switch(FaceNum)
{
    case Holle_Interface:
      HolleInterface(InterfaceOpen);
      break;
      
    case Menu_Interface:
      MenuInterface(InterfaceOpen);
      break;
      
    case LEDTest_Interface:
      LEDTestface(InterfaceOpen);
      break;
      
    case THP_Interface:
      THPInterface(InterfaceOpen);
      break;
      
    case RTCMenu_Interface:
      RTCMenuface(InterfaceOpen);
      break;
      
    case RTCshow_Interface:
      RTCShowface(InterfaceOpen);
      break;
      
    case UART_Interface:
      USARTInterface(InterfaceOpen);
      break;

    default:
      break;
      
}

Menu.ing = FaceNum;
   

}5)接下来就是各个分界面功能的实现,其一:LED操作根据需求分析中的解析,其涉及基本定时器、PWM操作,模式切换可以在App_KEY_Judge(void)中进行。LED对应IO口初始化:void LED_GPIO_init(void)
{
gpio_init_type gpio_init_struct;

crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);/* enable the led clock */
gpio_default_para_init(&gpio_init_struct);/* set default parameter */

/* configure the led gpio */
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type= GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins = GPIO_PINS_7|GPIO_PINS_8|GPIO_PINS_9;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOB, &gpio_init_struct);
}IO口操作宏定义:/************************************宏定义************************************/
#define LEDRed_on               gpio_bits_reset(GPIOB,GPIO_PINS_7);
#define LEDRed_off            gpio_bits_set(GPIOB,GPIO_PINS_7);
#define LEDRed_Toggle         gpio_bits_TogglePin(GPIOB,GPIO_PINS_7);

#define LEDYellow_on            gpio_bits_reset(GPIOB,GPIO_PINS_8);
#define LEDYellow_off         gpio_bits_set(GPIOB,GPIO_PINS_8);
#define LEDYellow_Toggle      gpio_bits_TogglePin(GPIOB,GPIO_PINS_8);

#define LEDGreen_on             gpio_bits_reset(GPIOB,GPIO_PINS_9);
#define LEDGreen_off            gpio_bits_set(GPIOB,GPIO_PINS_9);
#define LEDGreen_Toggle         gpio_bits_TogglePin(GPIOB,GPIO_PINS_9);定时器4PWM输出配置:void Tmr4PWM_init(uint16_t Period)
{
/* tmr4 clock enable */
crm_periph_clock_enable(CRM_TMR4_PERIPH_CLOCK, TRUE);
/* gpioa gpiob clock enable */
crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);

gpio_init_type gpio_init_struct;
tmr_output_config_type tmr_oc_init_structure;

gpio_default_para_init(&gpio_init_struct);
gpio_init_struct.gpio_pins = GPIO_PINS_7 | GPIO_PINS_8 | GPIO_PINS_9;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init(GPIOB, &gpio_init_struct);

/* tmr4 configuration */
/* time base configuration */
/* systemclock/14400/10000 = 1hz */
tmr_base_init(TMR4, 1000000/Period - 1, 144 - 1);
tmr_cnt_dir_set(TMR4, TMR_COUNT_UP);
tmr_clock_source_div_set(TMR4, TMR_CLOCK_DIV1);

tmr_output_default_para_init(&tmr_oc_init_structure);
tmr_oc_init_structure.oc_mode = TMR_OUTPUT_CONTROL_PWM_MODE_A;
tmr_oc_init_structure.oc_idle_state = TRUE;
tmr_oc_init_structure.oc_polarity = TMR_OUTPUT_ACTIVE_LOW;
tmr_oc_init_structure.oc_output_state = TRUE;

tmr_output_channel_config(TMR4, TMR_SELECT_CHANNEL_2, &tmr_oc_init_structure);
tmr_channel_value_set(TMR4, TMR_SELECT_CHANNEL_2, 0);
tmr_output_channel_buffer_enable(TMR4, TMR_SELECT_CHANNEL_2, TRUE);

tmr_output_channel_config(TMR4, TMR_SELECT_CHANNEL_3, &tmr_oc_init_structure);
tmr_channel_value_set(TMR4, TMR_SELECT_CHANNEL_3, 0);
tmr_output_channel_buffer_enable(TMR4, TMR_SELECT_CHANNEL_3, TRUE);

tmr_output_channel_config(TMR4, TMR_SELECT_CHANNEL_4, &tmr_oc_init_structure);
tmr_channel_value_set(TMR4, TMR_SELECT_CHANNEL_4, 0);
tmr_output_channel_buffer_enable(TMR4, TMR_SELECT_CHANNEL_4, TRUE);

tmr_period_buffer_enable(TMR4, TRUE);

/* tmr enable counter */
tmr_counter_enable(TMR4, TRUE);
}定义吸灯的频率设置为2s,并定义了LED控制结构体:typedef struct
{
uint8_t Mode_Dis; //LED显示模式
uint8_t Mode_OidDis; //LED上一次显示模式
uint8_t PWMcnt; //LED控制模式
uint8_t state; //LED过程
uint8_t UpFlag; //更新标志
uint8_t Upcnt; //更新计时计数
}LED_states;

if(LED.UpFlag == 0)
    {
      if(LED.Mode_Dis == 0 || LED.Mode_Dis == 1)
      {
      LED.Upcnt++;
      if(LED.Upcnt%100 == 0)
      {
          LED.state++;
          LED.state %= 3;
          LED.Upcnt = 0;
          LED.UpFlag = 1;
      }
      }
      else if(LED.Mode_Dis == 2)
      {
//      PWMcnt = Tmr4PWM/10*2;
      LED.Upcnt++;
      if(LED.Upcnt%200 == 0)
      {
          LED.state++;
          LED.state %= 3;
          LED.Upcnt = 0;
          LED.UpFlag = 1;
      }
      if(LED.Upcnt <= 100)
      {
          LED.PWMcnt = LED.Upcnt;
      }
      if(LED.Upcnt <= 200 && LED.Upcnt > 100)
      {
          LED.PWMcnt = 200 - LED.Upcnt;
      }
      }
    }LED执行函数:void App_LED(void)
{
if(LED.Mode_OidDis != LED.Mode_Dis)
{
    LED.Upcnt = 0;
    LED.state = 0;
    LED.UpFlag = 1;
   
    if(LED.Mode_OidDis == 2)
    {
      tmr_counter_enable(TMR4, FALSE);
      LED_GPIO_init();
      
    }
    else
    {
      LEDRed_off;
      LEDYellow_off;
      LEDGreen_off;
    }

    switch(LED.Mode_Dis)
    {
      case 0:
      LEDRed_on;
      LEDYellow_on;
      LEDGreen_on;
      break;
      
      case 1:
      LEDRed_on;
      break;
      
      case 2:
      Tmr4PWM_init(Tmr4PWM);
      LED.Upcnt = 0;
      break;

      default:
      break;
    }
    LED.Mode_OidDis = LED.Mode_Dis;
}

if(LED.UpFlag == 1)
{
    if(LED.Mode_Dis == 0)
    {
      LEDRed_Toggle;
      LEDYellow_Toggle;
      LEDGreen_Toggle;
    }
    else if(LED.Mode_Dis == 1)
    {
      switch(LED.state)
      {
      case 0:
          LEDRed_on;
          LEDGreen_off;
          break;
         
      case 1:
          LEDRed_Toggle;
          LEDYellow_Toggle;
          break;
         
      case 2:
          LEDYellow_Toggle;
          LEDGreen_Toggle;
          break;
         
      default:
          break;
      }
    }
    else if(LED.Mode_Dis == 2)
    {
      switch(LED.state)
      {
      case 0:
          tmr_channel_value_set(TMR4, TMR_SELECT_CHANNEL_2, LED.PWMcnt*50);
          break;
         
      case 1:
          tmr_channel_value_set(TMR4, TMR_SELECT_CHANNEL_3, LED.PWMcnt*50);
          break;
         
      case 2:
          tmr_channel_value_set(TMR4, TMR_SELECT_CHANNEL_4, LED.PWMcnt*50);
          break;
         
      default:
          break;
      }
    }
    LED.UpFlag = 0;
}
}6)AHT20数据获取接AHT界面设计其实模拟IIC就是两个引脚的IO状态来模拟时序,通过IO口的高低电平及上升下降沿来完美模拟时序,本开发板对应的IIC接口是PB7和PB6,注意PB7同时是红灯的控制引脚,通过IO口模拟正确读取了数据,不过使用硬件IIC是并没有成功,一直得不到从机的回复,考虑到速度问题,就没有继续进行硬件IIC的实现,也希望大佬可以指导一下雅特力的硬件IIC。模拟IIC配置:#define IIC1_RCU_GPIOB_clock    crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE)
#define IIC1_SDA_INITOut      IIC1_SDA_GPIO_OutConfig()
#define IIC1_SDA_INITIn         IIC1_SDA_GPIO_InConfig()
#define IIC1_SDA_SET            gpio_bits_set(GPIOB,GPIO_PINS_7)                  
#define IIC1_SDA_RESET          gpio_bits_reset(GPIOB,GPIO_PINS_7)
#define IIC1_ReadSDA            gpio_input_data_bit_read(GPIOB, GPIO_PINS_7)

#define IIC1_SCL_INITOut      IIC1_SCL_GPIO_OutConfig()
#define IIC1_SCL_SET            gpio_bits_set(GPIOB,GPIO_PINS_6)                  
#define IIC1_SCL_RESET          gpio_bits_reset(GPIOB,GPIO_PINS_6)

void IIC1_IOInit(void)
{
gpio_init_type gpio_init_struct;
IIC1_RCU_GPIOB_clock;

gpio_default_para_init(&gpio_init_struct);

gpio_init_struct.gpio_drive_strength= GPIO_DRIVE_STRENGTH_MAXIMUM;
gpio_init_struct.gpio_out_type      = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode            = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins            = GPIO_PINS_6|GPIO_PINS_7;
gpio_init_struct.gpio_pull            = GPIO_PULL_UP;
gpio_init(GPIOB, &gpio_init_struct);

IIC1_SDA_SET;
IIC1_SCL_SET;

}

void IIC1_SDA_GPIO_OutConfig(void)
{
gpio_init_type gpio_init_struct;
IIC1_RCU_GPIOB_clock;

gpio_default_para_init(&gpio_init_struct);
gpio_init_struct.gpio_drive_strength= GPIO_DRIVE_STRENGTH_MAXIMUM;
gpio_init_struct.gpio_out_type      = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode            = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins            = GPIO_PINS_7;
gpio_init_struct.gpio_pull            = GPIO_PULL_UP;
gpio_init(GPIOB, &gpio_init_struct);
}

void IIC1_SDA_GPIO_InConfig(void)
{
gpio_init_type gpio_init_struct;
IIC1_RCU_GPIOB_clock;

gpio_default_para_init(&gpio_init_struct);
gpio_init_struct.gpio_mode            = GPIO_MODE_INPUT;
gpio_init_struct.gpio_pins            = GPIO_PINS_7;
gpio_init_struct.gpio_pull            = GPIO_PULL_NONE;
gpio_init(GPIOB, &gpio_init_struct);
}

void IIC1_SCL_GPIO_OutConfig(void)
{
gpio_init_type gpio_init_struct;
IIC1_RCU_GPIOB_clock;

gpio_default_para_init(&gpio_init_struct);
gpio_init_struct.gpio_drive_strength= GPIO_DRIVE_STRENGTH_MAXIMUM;
gpio_init_struct.gpio_out_type      = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode            = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins            = GPIO_PINS_6;
gpio_init_struct.gpio_pull            = GPIO_PULL_UP;
gpio_init(GPIOB, &gpio_init_struct);
}

void IIC1_IIC_Init(void)
{
IIC1_SDA_INITOut;
//IIC1_IIC_Stop();
IIC1_SDA_RESET;
IIC1_SCL_RESET;
}

void IIC1_IIC_Start(void)
{
IIC1_SDA_SET;
delay_us(8);
IIC1_SCL_SET;
delay_us(14);
IIC1_SDA_RESET;
delay_us(14);
IIC1_SCL_RESET;
delay_us(14);
}

void IIC1_IIC_Stop(void)
{
IIC1_SDA_RESET;
delay_us(14);
IIC1_SCL_SET;
delay_us(8);
IIC1_SDA_SET;
delay_us(14);
}void IIC1_SendACK(uint8_t ack)
{
IIC1_SDA_INITOut;
if(ack == 0)
IIC1_SDA_RESET;                                                            //写应答信号
else
IIC1_SDA_SET;
delay_us(14);
IIC1_SCL_SET;                                                                //拉高时钟线
delay_us(14);                                                         //延时
IIC1_SCL_RESET;                                                            //拉低时钟线
delay_us(14);                                                         //延时   
}

uint8_t IIC1_RecvACK(void)
{

uint8_t RecvACK;
   
IIC1_SDA_INITIn;                                                    //SDA接口为输入
delay_us(8);
IIC1_SCL_SET;                                                                //拉高时钟线
delay_us(8);                                                         //延时
RecvACK = IIC1_ReadSDA;                                                      //读应答信号
IIC1_SCL_RESET;                                                            //拉低时钟线
delay_us(8);                                                         //延时
IIC1_SDA_INITOut;
   
return RecvACK;
   
}

uint8_t IIC1_SendByte(uint8_t dat)
{
uint8_t datsendbit;
uint8_t RecvACK;
   
for (uint8_t i=0; i<8; i++)                                                   //8位计数器
{
    datsendbit = ((dat & 0x80) >> 7);
    dat <<= 1;                                                                  //移出数据的最高位
    if(datsendbit == 1)
    {
      IIC1_SDA_SET;
    }
    if(datsendbit == 0)
    {
      IIC1_SDA_RESET;
    }                                                                           //送数据口
    delay_us(8);
    IIC1_SCL_SET;                                                               //拉高时钟线
    delay_us(8);                                                                //延时
    IIC1_SCL_RESET;                                                             //拉低时钟线
    delay_us(8);
}
RecvACK = IIC1_RecvACK();
return RecvACK;
}

uint8_t IIC1_RecvByte(void)
{   
uint8_t RecvDat = 0;

IIC1_SDA_INITIn;
for (uint8_t i=0; i<8; i++)                                                   //8位计数器
{
    RecvDat <<= 1;
    IIC1_SCL_SET;                                                               //拉高时钟线
    delay_us(10);                                                               //延时
    RecvDat |= (IIC1_ReadSDA);
    IIC1_SCL_RESET;                                                             //拉低时钟线
    delay_us(10);                                                               //延时   
}
IIC1_SDA_INITOut;
return RecvDat;
      
}

void IIC1_SendBytes(uint8_t len,uint8_t* dat,uint8_t dev_addr)
{
uint8_t ack = 0;
IIC1_IIC_Start();
IIC1_SendByte(dev_addr<<1);

for(uint8_t i = 0; i < len; i++)
{
    ack = IIC1_SendByte(*(dat+i));
}
IIC1_IIC_Stop();
}

void IIC1_RecvBytes(uint8_t len,uint8_t* dat,uint8_t dev_addr)
{
IIC1_IIC_Start();
IIC1_SendByte((dev_addr<<1)+1);

for(uint8_t i = 0; i < len; i++)
{
    *(dat+i) = IIC1_RecvByte();
   
    if(i == (len - 1))
    {
      IIC1_SendACK(1);
    }
    else
    {
      IIC1_SendACK(0);
    }
}
IIC1_IIC_Stop();
}AHT20的驱动函数在文章末尾,具体的驱动就不赘述了,定时采集传感器数据并刷新显示;7)ERTC的实现雅特力开发板的RTC又名ERTC,其作用是提供日历管理,因为其计数逻辑作用在电池供电域,因此只要电池供电域有电,ERTC 就不会受到系统复位以及VDD掉电影响。不过开发板是不带电池域单独供电的。ERTC的ck_b用于更新日历,开发板包含外部32.768kHz时钟,首选通过LEXT经过分频器A 和分频器B获得,ck_b=LEXT/(divA+1)/(divB+1),例如32.768K的时钟通过A分频器127、B分频器255,获得1Hz频率。上电复位后所有ERTC 寄存器都处于写保护状态,在进行擦写前一定要先解除写保护,这部分和多数MCU的配置过程基本是相同的,解除写保护——写配置——写保护,部分寄存器需要进入初始化模式才能更改。ERTC的时间寄存器和日期寄存器就是需要经常访问的寄存器,更新日历就是直接更新这两个寄存器,这一点与GD32F303的单片机略有不同,GD实际上是一个64位的寄存器,只存一个自增数,具体的时间设定还需要自己去进行设计,这里可以直接访问时间相关寄存器,更像一个外部的实时时钟模式,直接获取年月日时分秒,不过ERTC的可统计时间范围没有给出,润年机制方面也没有什么介绍,寄存器中的年份只有十位和各位。ERTC初始化:void ertc_config(void)
{
/* allow access to ertc */
pwc_battery_powered_domain_access(TRUE);
/* reset ertc domain */
crm_battery_powered_domain_reset(TRUE);
crm_battery_powered_domain_reset(FALSE);
/* enable the lext osc */
crm_clock_source_enable(CRM_CLOCK_SOURCE_LEXT, TRUE);
/* wait till lext is ready */
while(crm_flag_get(CRM_LEXT_STABLE_FLAG) == RESET)
{
}
/* select the ertc clock source */
crm_ertc_clock_select(CRM_ERTC_CLOCK_LEXT);
/* enable the ertc clock */
crm_ertc_clock_enable(TRUE);

/* deinitializes the ertc registers */
ertc_reset();
/* wait for ertc apb registers update */
ertc_wait_update();

/* configure the ertc divider */
/* ertc second(1hz) = ertc_clk / (div_a + 1) * (div_b + 1) */
ertc_divider_set(127, 255);
/* configure the ertc hour mode */
ertc_hour_mode_set(ERTC_HOUR_MODE_24);

eRTC_set.year   = 22;
eRTC_set.month    = 8;
eRTC_set.day      = 18;
eRTC_set.hour   = 8;
eRTC_set.min      = 0;
eRTC_set.sec      = 0;
eRTC_set.week   = 4;
Set_Time(&eRTC_set);
}应用函数:void Set_Time(ertc_time_type* time)
{
/* set date*/
ertc_date_set(time->year,time->month,time->day,time->week);
/* set time*/
ertc_time_set(time->hour,time->min,time->sec,ERTC_AM);
}

uint32_t bpr_reg_get(uint8_t index)
{
if(index >= ERTC_BPR_DT_NUMBER)
{
    index = 0;
}
return ertc_bpr_data_read(bpr_addr_tab);
}

void bpr_reg_write(uint8_t index,uint32_t DT_data)
{
ertc_bpr_data_write(bpr_addr_tab,DT_data);
}这里比较遗憾的是按键的限制导致不太好设计设置时间的部分,不过这部分在串口通信中得到了解决。8)串口通信串口1对应的引脚与IIC冲突,所以对PC的通信只能使用串口2了,串口3是对蓝牙的通信,固定死的。串口2配置:void USART2_init(void)
{
gpio_init_type gpio_init_struct;
/* enable the usart2 and gpio clock */
crm_periph_clock_enable(CRM_USART2_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);

/* enable iomux clock */
crm_periph_clock_enable(CRM_IOMUX_PERIPH_CLOCK, TRUE);

gpio_default_para_init(&gpio_init_struct);

/* configure the usart2 tx pin */
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type= GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
gpio_init_struct.gpio_pins = GPIO_PINS_2;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOA, &gpio_init_struct);

/* configure the usart2 rx pin */
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type= GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
gpio_init_struct.gpio_pins = GPIO_PINS_3;
gpio_init_struct.gpio_pull = GPIO_PULL_UP;
gpio_init(GPIOA, &gpio_init_struct);

/* config usart nvic interrupt */
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
nvic_irq_enable(USART2_IRQn, 0, 0);

/* configure usart2 param */
usart_init(USART2, 115200, USART_DATA_8BITS, USART_STOP_1_BIT);
usart_transmitter_enable(USART2, TRUE);
usart_receiver_enable(USART2, TRUE);

/* enable usart2 interrupt */
usart_interrupt_enable(USART2, USART_RDBF_INT, TRUE);
usart_enable(USART2, TRUE);


}采用的方式是中断接受数据,非中断方式发送数据,发送和接收处理函数如下://******************************************************************************
//* 函数名称: USART_sendbit(TxBuffer)
//* 函数描述: 串口发送一个字节
//* 输入参数:
//* 参数描述:TxBuffer:存储发送的一个字节
//* 输出参数: 无
//* 返回值    : 无
//******************************************************************************   
void USART2_sendbit(uint8_t aaTx)
{
usart_data_transmit(USART2,aaTx);
while((usart_flag_get(USART2, USART_TDBE_FLAG) == RESET));
}

//******************************************************************************
//* 函数名称: USART_sendbit(TxBuffer)
//* 函数描述: 串口发送多个字节
//* 输入参数:
//* 参数描述:TxBuffer:存储发送的一个字节
//* 输出参数: 无
//* 返回值    : 无
//******************************************************************************   
void USART2_sendbits(uint8_t *HzpAscii)
{
while(*HzpAscii != 0)
{
    usart_data_transmit(USART2,*HzpAscii);
    while((usart_flag_get(USART2, USART_TDBE_FLAG) == RESET));
    HzpAscii += 1;
}

}
//******************************************************************************
// 函数名称: USART2_deal
// 函数描述: 串口中断处理
// 输入参数:
// 参数描述: 无
// 输出参数: 无
// 返回值    : 无
//******************************************************************************
void USART2_deal(uint8_t USART_data)
{

uint8_t check = 0;

if(USART_data == 0xA5 && Com_Data.ing_step == 0)
{
    Com_Data.STX_1 = 0xA5;
    Com_Data.ing_step = 1;
}
else if(USART_data == 0x5A && Com_Data.ing_step == 1)
{
    Com_Data.STX_2 = 0x5A;
    Com_Data.ing_step = 2;
}
else if(Com_Data.ing_step == 2)
{
    Com_Data.Length = USART_data;
    Com_Data.ing_step = 3;
}
else if(Com_Data.ing_step == 3)
{
    RecePackBuf = USART_data;
    Com_Data.ReturnCnt ++;
    if(Com_Data.Length == Com_Data.ReturnCnt)
    {
      Com_Data.CHECKSUM_1 = RecePackBuf;
      for(uint8_t i=0;i<Com_Data.Length-1;i++)
      {
      check += RecePackBuf;
      }
      if(Com_Data.CHECKSUM_1 = check)//校验成功
      {
      line_** = 2;
      Com_Data.CMD = RecePackBuf;
      }
      else
      {
      memset(RecePackBuf, 0,ARRAYNUM(RecePackBuf));//清空接收包
      memset(&Com_Data,0,sizeof(Com_Data));//清空结构体
      }
    }
}
else
{
    memset(RecePackBuf, 0,ARRAYNUM(RecePackBuf));//清空接收包
    memset(&Com_Data,0,sizeof(Com_Data));//清空结构体
}
}对于接收具体处理,这边采用的是通信协议的方式进行甄别,实现方式千千万,只取适合自己的那一瓢,这时候就可以把设置时间放到了这里,同时也可以控制LED的显示,只要正确解析就可以。9)蓝牙通信其实所谓蓝牙通信实现的功能就是串口通信的无线化,其透传模式功能实现的就是串口通信的延伸,命令模式就是对蓝牙模块的配置。本开发板通过串口3和蓝牙模块的耦合实现芯片自带的蓝牙功能。最开始测试想把开发板作为主机进行扫描的,不过通过对资料的分析,这个工程有点大,本次测试提供的资料里只有简单的AT指令,所以改变目标,实现基本透传功能。串口3使用的是空闲中断+DMA的方式进行的,这样可以减少串口中断的次数,蓝牙切换为透传模式也是非常方便的,发送"AT+TPMODE1\r\n"就可以。串口三初始化:void USART3_init(void)
{
gpio_init_type gpio_init_struct;

crm_periph_clock_enable(CRM_USART3_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_IOMUX_PERIPH_CLOCK, TRUE);

gpio_pin_remap_config(USART3_GMUX_0010, TRUE);
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type= GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
gpio_init_struct.gpio_pins = GPIO_PINS_7;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOA, &gpio_init_struct);

gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type= GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
gpio_init_struct.gpio_pins = GPIO_PINS_6;
gpio_init_struct.gpio_pull = GPIO_PULL_UP;
gpio_init(GPIOA, &gpio_init_struct);

usart_init(USART3, 115200, USART_DATA_8BITS, USART_STOP_1_BIT);
usart_transmitter_enable(USART3, TRUE);
usart_receiver_enable(USART3, TRUE);
usart_dma_receiver_enable(USART3, TRUE);

USART3_DMA1_config();

nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
nvic_irq_enable(USART3_IRQn, 0, 0);
/* Enable the USARTx Interrupt */
usart_interrupt_enable(USART3, USART_IDLE_INT, TRUE);
//usart_interrupt_enable(USART3, USART_RDBF_INT, TRUE);
usart_enable(USART3, TRUE);

}DMA初始化:void USART3_DMA1_config(void)
{
dma_init_type dma_init_struct;

/* enable dma1 clock */
crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE);

/* dma1 channel3 for usart1 tx configuration */
dma_reset(DMA1_CHANNEL3);
dma_default_para_init(&dma_init_struct);
dma_init_struct.direction = DMA_DIR_PERIPHERAL_TO_MEMORY;//外设到内存
dma_init_struct.memory_base_addr = (uint32_t)USART3RecePackBuf;//内存接收基地址
dma_init_struct.memory_inc_enable = TRUE;//内存地址递增
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;//8位数据
dma_init_struct.buffer_size = ARRAYNUM(USART3RecePackBuf);

dma_init_struct.peripheral_base_addr = (uint32_t)&USART3->dt;//外设地址
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;//外设数据长度
dma_init_struct.peripheral_inc_enable = FALSE;//外设地址不增加
dma_init_struct.priority = DMA_PRIORITY_MEDIUM;//最高DMA通道
dma_init_struct.loop_mode_enable = TRUE;
dma_init(DMA1_CHANNEL3, &dma_init_struct);

/* config flexible dma for usart3 Rx */
dma_flexible_config(DMA1, FLEX_CHANNEL3, DMA_FLEXIBLE_UART3_RX);

dma_channel_enable(DMA1_CHANNEL3, TRUE); /* usart1 tx begin dma transmitting */
}
这里我们将DMA初始化单独拎出来,是为了实现串口处理函数中对DMA重新进行配置。串口处理函数:void USART3_DMA_deal(void)
{
uint8_t ReturnCnt;
uint8_t check = 0;
dma_channel_enable(DMA1_CHANNEL3, FALSE);                                        /* 关闭DMA传输 */

ReturnCnt = ARRAYNUM(USART3RecePackBuf)-dma_data_number_get(DMA1_CHANNEL3);
memcpy(BLEPackBuf,USART3RecePackBuf,ReturnCnt);
memset(USART3RecePackBuf, 0,ReturnCnt);//清空接收包

//usart_data_transmit(USART0, RecePackBuf);
//while((usart_flag_get(USART0, USART_FLAG_TBE) == RESET));
if(BLEPackBuf == 0xA5 && BLEPackBuf == 0x5A)
{
    Com_Data.Length   = BLEPackBuf;
    Com_Data.CHECKSUM_1 = BLEPackBuf;
   
    for(uint8_t i=0;i<Com_Data.Length-1;i++)
    {
      check += BLEPackBuf;
    }
    if(Com_Data.CHECKSUM_1 = check)//校验成功
    {
      line_** = 3;
      memcpy(RecePackBuf,BLEPackBuf+3,ReturnCnt-4);
      Com_Data.CMD = RecePackBuf;
      memset(BLEPackBuf, 0,Com_Data.Length+3);//清空接收包
    }
    else
    {
      memset(BLEPackBuf, 0,ARRAYNUM(BLEPackBuf));//清空接收包
      memset(&Com_Data,0,sizeof(Com_Data));//清空结构体
    }
   
   
}

USART3_DMA1_config();
}这里一共用到了三个数组,实现对接收数据的隔离及缓存,我们在进入空闲中断是需要关闭DMA通道,避免这个是后继续接收数据,重新调用USART3_DMA1_config();而不是简单的开启DMA通道的目的是使下一组数据依然从0开始,而不是接着上一个数据存储。通过memcpy(RecePackBuf,BLEPackBuf+3,ReturnCnt-4);使数据与串口解析数据进行融合,共用同一个解析程序。10)总结AT32WB415是一个非常优秀全面的芯片,是一个面向低功耗消费电子的一款产品,基本可以满足一个产品从控制到显示及通信等多方面要求。不过也有一些不足,例如内部RTC的润年识别机制应该是没有,蓝牙方面控制资料略有不足,建议添加全面的蓝牙设计指南,当然我们对于蓝牙的深层理解还是不够的,还需要继续努力,全面的开发设计资料也是我们所需要的,希望可以补足全面蓝牙设计资料。再次感谢论坛和雅特力的这次活动,希望有机会能实际使用该产品进行设计,也希望国产芯片能大踏步发展!










muyichuan2012 发表于 2022-9-14 17:48

本帖最后由 muyichuan2012 于 2022-9-14 17:51 编辑

感谢楼主分享详细过程,以下信息作为补充
1 关于ERTC ,以下链接有使用指南
另外,ERTC是有闰年处理机制的,硬件自动处理的。
https://www.arterytek.com/download/APNOTE/AN0047_AT32_ERTC_Application_Note_ZH_V2.0.0.pdf

2 “不过只能分频到144M”
可以的,可使用时钟配置工具进行配置,然后生成相关code.
链接如下:
https://www.arterytek.com/downlo ... uration_V3.0.03.zip






骑着蜗牛狂奔O 发表于 2022-9-15 13:57

闰年28、29天、月份30、31天,ERTC计数器内部已经处理好了,所以不需要关心,年份设置成4的倍数就是润年,例如0、4、8,你可以设置这些年份,把时间设置成2月,然后就可以观察2月份的天数

xu@xupt 发表于 2022-12-11 00:20

很详细的教程,感谢分享
页: [1]
查看完整版本: 【AT-START-WB415测评】综合测试