小e开发板用户代码入口user_init函数分析

乐鑫WIFI 专栏收录该内容
10 篇文章 1 订阅

          外话:各位手头上没有esp8266的开发板注意了,目前开发快正在进行“免费开发板活动”,本人就是在他们那里申请到的开发板,开发快提供的不单单是开发板,还有他们强大的云端服务功能,实现了微信、云平台等物联网所需要的所有功能,其功能齐全,代码简洁,对于想用wifi作为产品和互联网沟通的桥梁的我们来说,确实是一个很好的选择,另外他们还有其他的模块,比如2G通信和GPS目前论坛活动地址:http://bbs.kaifakuai.com/forum.php?mod=viewthread&tid=981&extra=page%3D1,开发快官网地址:http://www.kaifakuai.com/                        》》》》》》写于2017年1月12日


本文摘录于本人帖子:http://bbs.elecfans.com/forum.php?mod=viewthread&tid=1100361&extra=

关于user_init的具体内容和一个简单的程序编译下载等流程请看本人之前的文章:http://bbs.elecfans.com/forum.ph ... &tid=1098080&extra=


       入口函数的第一句话就是调用串口打印函数:os_printf("software version:%s\n", SOFTWARE_VERSION);这里能够在串口中看到相应的信息那就说明在前面的系统初始化代码中一定已经包含了串口初始化的代码。我们看看这个函数,在source insight里可以看到有两个文件定义了这个函数:

       至于最终需要的是哪个文件,我们在esp_common.h文件中看到这样的包含语句:
#include "et_types.h"
#include "c_types.h"
#include "esp_libc.h"
#include "esp_misc.h"
#include "esp_wifi.h"
#include "esp_softap.h"
#include "esp_sta.h"
#include "esp_system.h"
#include "esp_ timer.h"
#include "esp_ssc.h"
#include "esp_spiffs.h"
#include "esp8266/esp8266.h"
#include "smartconfig.h"
#include "spi_flash.h"
        说明这里使用的文件是esp_libc.h,那os_printf函数的定义如下:
/* NOTE: don't use printf_opt in irq handler, for test */
#define os_printf(fmt, ...) do {    \
        static const char flash_str[] ICACHE_RODATA_ATTR STORE_ATTR = fmt;  \
        printf(flash_str, ##__VA_ARGS__);   \
    } while(0)
     其中ICACHE_RODATA_ATTR定义如下:
#define ICACHE_RODATA_ATTR  __attribute__((section(".irom.text")))
       那么 这里flash_str这个数组就只是一个存储在.irom.text(代码区)的一个特殊数组,那么这个函数的第一句话就是把打印格式本分到.irom.text区,最终的flash_str=fmt,
      有意思的是printf(flash_str, ##__VA_ARGS__); 这句话,flash_str=fmt,所以printf(flash_str, ##__VA_ARGS__);就是printf(fmt, ##__VA_ARGS__);而__VA_ARGS__= ...,关于__VA_ARGS__请看http://blog.sina.com.cn/s/blog_661314940100qmfg.html
      那到这里就可以知道 os_printf(fmt, ...)等同于 printf(fmt, ...),只是多了一个备份格式的操作而已,另外那既然printf函数是打印到串口的,那么必定要重写int fputc(int ch, FILE *f)函数,但是在source insight中怎么到找不到这个函数的定义,那么 fputc函数只能够藏在一个地方:share\SDK\lib下面的.a文件中,这个是唯一一个有可能再次有代码的地方,逼不得已呀,这里直接把share\SDK\lib里的所有.a文件的API(函数)给打印出来,如下:
esp8266@esp8266-VirtualBox:~$ cd ~/Share/SDK/lib
esp8266@esp8266-VirtualBox:~/Share/SDK/lib$ ls
libairkiss.a  libfac.a       libmesh.a      libpp.a           libwpa.a
libcirom.a    libfreertos.a  libminic.a     libpwm.a          libwps.a
libcrypto.a   libgcc.a       libmirom.a     libsmartconfig.a
libespconn.a  libjson.a      libnet80211.a  libspiffs.a
libespnow.a   liblwip.a      libnopoll.a    libssc.a
libetilink.a  libmain.a      libphy.a       libssl.a
esp8266@esp8266-VirtualBox:~/Share/SDK/lib$ nm *.a

libairkiss.a:

airkiss.o:
0000060c T airkiss_change_channel
00000000 t __airkiss_crc8
00000598 T airkiss_get_result
00000028 T airkiss_init
0000007c T airkiss_recv
00000020 T airkiss_version
00000018 r CRC8_ARRAY
.................................................................................
          这里的打印信息太多,保存到如下文件:
库文件函数说明.zip(58.17 KB, 下载次数: 0)
           这个压缩包里面的《库文件函数说明.txt》就是所有的库函数信息
            这里我们可以查到fputc函数:
lib_a-fputc.o:
00000034 T fputc
00000000 T _fputc_r
         U _impure_ptr
         U _putc_r
         U __sinit
          说明fputc函数确实被定义了,只不过被封了起来而已!
        继续分析user_init函数:
    if(get_fac_norm_mode(&result) != SPI_FLASH_RESULT_OK)
    {
        os_printf("get_fac_norm_mode error, NORMAL mode\n");
    }
   
    if(result == FAC_MODE)
    {
        os_printf("run in factory mode\n");
        uart_init_new_uart1(BIT_RATE_115200);
        UART_SetPrintPort(UART1);
        uart_init_new(BIT_RATE_115200, result);
        return;
    }
  os_printf("run in normal mode\n");
     这里先从flash获取现在的运行模型,如果运行模型是出厂模式,result == FAC_MODE那么到这里之后设置完波特率就会退出程序,所谓的工厂模式也就是使芯片运行到一个状态:这个状态下可以设置云连接参数,比如下面之后分析到的appkey和uid等,这些都是使用云平台的凭证,就像登录云平台的帐号等等,只有在工厂模式下才能够设置这些参数。下来:
//if define IR_DEMO, ir rx or tx test
#ifdef IR_DEMO
    struct station_config config;
   
    memset(&config, 0, sizeof(config));
    wifi_set_opmode(STATION_MODE);
    wifi_station_set_config_current(&config);
   
//if define IR_RX, ir rx test
#ifdef IR_RX            
    ir_rx_init();

  //ir tx test
#else                     
    ir_tx_init(0);
    xTaskCreate(ir_tx_key, "ir_tx_key", 256, NULL, 2, NULL);
#endif

#else  
   如果当前的例程是IR_DEMO红外例程,将调用上面的程序,这里首先设置WiFi为station模型,然后如果定义IR_RX 红外接收模型,这里将会调用 ir_tx_init函数来初始化红外接收功能,如果没有定义为接受模型就会调用 ir_tx_init函数来初始化红外发送功能,不管是接收还是发送都会调用xTaskCreate来创建一个任务,这里我们分析上面提到的这三个函数, ir_rx_init:
ring_buf_init(&ir_rx_ring_buf, ir_rx_buf, sizeof(ir_rx_buf));
ir_rx_gpio_init();
xTaskCreate(ir_rx_task, "ir_rx_task", 256, NULL, 2, NULL);
     函数ring_buf_init初始化ir_rx_ring_buf这个缓存结构体,如下:
typedef struct
{
    et_uint8 * p_o;                /**< Original pointer */
    et_uint8 * volatile p_r;        /**< Read pointer */
    et_uint8 * volatile p_w;        /**< Write pointer */
    volatile et_int32 fill_cnt;    /**< Number of filled slots */
    et_int32    size;                /**< Buffer size */
}ring_buf_t;
    函数ir_rx_gpio_init就是初始化接收红外的关键:
LOCAL void ir_rx_gpio_init()
{
    //Select pin as gpio
    PIN_FUNC_SELECT(IR_GPIO_IN_MUX, IR_GPIO_IN_FUNC);
    //set GPIO as input
    GPIO_DIS_OUTPUT(IR_GPIO_IN_NUM);
    PIN_PULLUP_DIS(IR_GPIO_IN_MUX);
    //clear interrupt status
    GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(IR_GPIO_IN_NUM));
    //set negitive edge interrupt
    gpio_pin_intr_state_set(IR_GPIO_IN_NUM, GPIO_PIN_INTR_NEGEDGE);
    //register interrupt handler
    ETS_GPIO_INTR_ATTACH(ir_rx_intr_handler, NULL);
    //enable gpio interrupt
    ETS_GPIO_INTR_ENABLE();
}
    其中LOCAL代表static,这个函数的功能是初始化gpio5这个IO口,并且使能该IO口的中断。函数ir_rx_init的最后内容是创建一个任务名是"ir_rx_task"回调函数是ir_rx_task,任务缓存区是256,任务优先级是2,这里让我们看看这个任务回调函数:
void ir_rx_task(void *pvParameters)
{
    et_uint8 ir_data;

    while(1)
    {
        if(ir_rx_ring_buf.fill_cnt > 0)
        {
            ring_buf_get(&ir_rx_ring_buf, &ir_data, 1);
            os_printf("ir_data = %02xh\n", ir_data);
        }
        delay_ms(500);
    }
}
     注意这里的任务回调函数是一个死循环,不会退出,那任务的调度就交给了操作系统,这里的回调函数的主要内容就是判断是否红外有接收到数据,然后通过串口打印出来,但是至于这个接收的数据的填充工作就应该在gpio5这个管脚中断上了, 这里我们看看这个管脚的中断:
LOCAL void ir_rx_intr_handler(void *para)
{
    et_int32  status;

    status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
    if((status >> IR_GPIO_IN_NUM) & BIT0)
    {
        ir_intr_handler();
    }
    else
    {
        os_printf("gpio num mismached\n");
        GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, status);
    }
}其中 ir_intr_handler函数经历了一些列的时序判断调用了这个语句:
ring_buf_put(&ir_rx_ring_buf, (ir_cmd >> 16) & 0xff);
这里吧数据填充到ir_rx_ring_buf数组中
   再回到user_init函数继续往下分析,如果不是红外接收模式,那就是红外发送模式,红外发送模式下调用ir_tx_init函数来初始化红外发送:
void ir_tx_init(et_uint8 req)
{
    hw_timer_init(req);
       hw_timer_set_func(ir_tx_handler);
}因为产生红外通许波形需要一个定时器,所以这里初始化一个定时器,这里传送的参数是0,所以这里的定时器初始化如下:RTC_REG_WRITE(FRC1_CTRL_ADDRESS,  FRC1_ENABLE_TIMER | DIVDED_BY_16 | TM_EDGE_INT);没有自动重装载的功能,所以定时器中断的时候必须要重装定时器数据,这也达到了产生不一样的占空比的功能。这里还注册了定时器中断处理函数ir_tx_handler,在这个函数主要的功能就是参数红外波形,这个叫做NEC时序的东西。因为红外发送口在GPIO14,和很多模块共用,所以这里我们看关键的一句WRITE_PERI_REG(IR_GPIO_OUT_MUX, (READ_PERI_REG(IR_GPIO_OUT_MUX)&0xfffffe0f)| (0x1<<4) );(在gen_carrier_clk函数中),这里我们知道他操作了红外发射管脚。红外发射程序的最后一步是设置一个任务名为"ir_tx_key",回调函数是ir_tx_key,缓存也是256,优先等级为2.
      上面所有关于红外的内容本例程都没有使用到,这里我们继续分析要使用的例程,接下来的内容如下:
    key_gpio_t key;
    struct station_config config;
    struct ip_info info;
   
    // show logo
    user_show_logo();     //通过OLED显示logo
    这里除了定义唯一有用的语句就是 user_show_logo,该函数如下:
user_show_logo()
{   
    extern et_uchar BMP1[];
    et_uint32 len = 1024;    // BMP1 member
   
    i2c_master_gpio_init(); // I2C init
    OLED_init();             // OLED init
    OLED_clear();

    // show logo
    OLED_show_bmp(0, 0, 128, 8, BMP1, len);
}

    这里初始化了OLED_init,然后清屏,最后显示logoOLED_show_bmp(0, 0, 128, 8, BMP1, len);

 接下来是:
    if (RETURN_OK != user_get_work_mode(&work_mode))
    {
        os_printf("get work mode fail !!!\n");
        return;
    }这里调用了 user_get_work_mode函数,其实这个函数就是判断拨码开关的状态,如下:
user_get_work_mode(et_uint32 *mode)
{
    et_uint32 adc = system_adc_read();
    if (adc > 1024)
    {
        os_printf("The adc value=%u is invalid !!!\n", adc);
        return RETURN_ERR;
    }

#ifdef USER_PRINT_DEBUG
    os_printf("get adc value=%u is success !!!\n", adc);
#endif

    // ADC turn into work mode
    if(adc < 100)
    {
        *mode = WORK_MODE_DEFAULT;
    }
    else if (adc < 350)
    {
        *mode = WORK_MODE_AUDIO;
    }
    else if(adc < 550)
    {
        *mode = WORK_MODE_RGB;
    }
    else if(adc < 750)
    {
        *mode = WORK_MODE_BAROMETRIC;
    }
    else if(adc < 1000)
    {
        *mode = WORK_MODE_OLED;
    }
    else
    {
        *mode = WORK_MODE_BUTT;
    }
    return RETURN_OK;
}这里结合下面的原理图就知道上面的含义了:当ADC的值在750到1000之间的时候工作在OLED的模式:

    下来的内容是初始化工作模式,比如工作在OLED模式就初始化OLED等:
    if (RETURN_OK != user_init_work_mode(work_mode, result))
    {
        os_printf("init work mode fail !!!\n");
        return;
    }这里看看user_init_work_mode函数:
user_init_work_mode(et_uint32 mode, et_uchar fac_norm_mode)
{
    if (WORK_MODE_BUTT <= mode)
    {
        os_printf("The work mode=%u is invalid !!!\n", mode);
        return RETURN_ERR;
    }

//#ifdef USER_PRINT_DEBUG
    os_printf("get work mode=%s is success !!!\n", user_get_mode_str(mode));
//#endif

    switch (mode)
    {
        case WORK_MODE_DEFAULT:
            DHT11_init();
            break;
            
        case WORK_MODE_AUDIO:
            DHT11_init();
            audio_init();
            break;

        case WORK_MODE_RGB:
            RGB_light_init();        // RGB init
            DHT11_init();
            break;
        
        case WORK_MODE_BAROMETRIC:
            i2c_master_gpio_init();    // BAROMETRIC init
            DHT11_init();            // temperature init
            break;
               
        case WORK_MODE_OLED:
            i2c_master_gpio_init(); // I2C init
            OLED_init();             // OLED init
            OLED_clear();
            DHT11_init();
            OLED_show_chn(0, 3, 16);
            OLED_show_chn(16, 3, 17);
            OLED_show_chn(32, 3, 18);
            OLED_show_chn(48, 3, 19);
            OLED_show_chn(64, 3, 20);
            OLED_show_chn(80, 3, 21);
            OLED_show_chn(96, 3, 22);
            OLED_show_chn(112, 3, 23);
            break;
            
        default:        
            break;
    }

    return RETURN_OK;
}接下来的内容就开始涉及wifi操作了:
//wifi event handle
    wifi_set_event_handler_cb(et_wifi_event_cb);
   这里初始化wifi事件回调函数et_wifi_event_cb,有关所有的wifi事件的相应函数都在这里,比如:
void et_wifi_event_cb(System_Event_t *event)
{
    switch(event->event_id)
    {
        case EVENT_STAMODE_SCAN_DONE://ESP8266 station finish scanning AP
            
            break;
        case EVENT_STAMODE_CONNECTED://ESP8266 station connected to AP
            
            os_printf("et connect to ssid %s, channel %d\n", event->event_info.connected.ssid, event->event_info.connected.channel);
            break;
    这里的case EVENT_STAMODE_CONNECTED分支就是开发板连接到wifi的时候回调的函数。接下来:
    memset(&key, 0, sizeof(key_gpio_t));
    key.key_gpio_pin = AIRKISS_KEY_IO_PIN;
    key.key_num = AIRKISS_KEY_IO_NUM;

    airkiss_key_init(&key);
    wifi_set_opmode(STATION_MODE);

    wifi_reconnect_start_flag = 0;

    xTaskCreate(airkiss_key_poll_task, "smartconfig_task", 256, NULL, 2, NULL);
    这里初始化了AIRKISS_KEY这个按键(GPIO0),但是这个按键在原理图上叫做BOOT,这里应该写错名字了,反正原理图和开发板和程序有点出入,然后这里设置WiFi模式为sattion模式,最后设置一个按键监视的任务airkiss_key_poll_task,这个任务监视按键airkiss的状态,应该是根据这个按键的状态做出相应的响应。按照实验的现象这里应该就是连接wifi等,这里目前不深究。接下来:
    wifi_led_init();
    memset(&config, 0, sizeof(struct station_config));
    if(wifi_station_get_config_default(&config) == true)
    {
        os_printf("ssid=%s\n", config.ssid);
        wifi_station_set_config_current(&config);
        //for static ip set
        /*wifi_station_dhcpc_stop();
        IP4_ADDR(&info.ip, 192, 168, 1, 43);
        IP4_ADDR(&info.gw, 192, 168, 1, 1);
        IP4_ADDR(&info.netmask, 255, 255, 255, 0);
        wifi_set_ip_info(STATION_IF, &info);*/
    }
   这里配置WiFi指示灯之后进行了WiFi的配置config,这里好像没有进行什么实质性的工作,然后:
    os_timer_disarm(&test_timer);
       os_timer_setfn(&test_timer, (os_timer_func_t *)user_esp_platform_check_ip, NULL);
       os_timer_arm(&test_timer, 1000, 0);
    这里设置了一个定时器test_timer,先要把这个定时器卸载掉,然后初始化定时器的回调函数user_esp_platform_check_ip,最后再装载这个定时器,user_esp_platform_check_ip函数的主要内容是打印wifi目前的状态信息。还有一句比较重要的语句:xTaskCreate(et_user_main, "et_user_main", 1024, NULL, 2, NULL);当配置上IP之后将使能这个任务


到了这里基本上user_init函数分析完毕。
这里为了方便再次上传代码:
share.zip(8.61 MB, 下载次数: 0)

  • 1
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值