From 0fb6fb32d1b012b3d0196f1fa0e372f9dbd274aa Mon Sep 17 00:00:00 2001 From: Derek Jamison Date: Tue, 21 Mar 2023 10:08:36 -0400 Subject: [PATCH] Initial code for i2c_demo --- gpio/i2c_demo/README.md | 51 ++++++ gpio/i2c_demo/application.fam | 10 ++ gpio/i2c_demo/i2c_demo.png | Bin 0 -> 1816 bytes gpio/i2c_demo/i2c_demo_app.c | 329 ++++++++++++++++++++++++++++++++++ 4 files changed, 390 insertions(+) create mode 100644 gpio/i2c_demo/README.md create mode 100644 gpio/i2c_demo/application.fam create mode 100644 gpio/i2c_demo/i2c_demo.png create mode 100644 gpio/i2c_demo/i2c_demo_app.c diff --git a/gpio/i2c_demo/README.md b/gpio/i2c_demo/README.md new file mode 100644 index 0000000..2812506 --- /dev/null +++ b/gpio/i2c_demo/README.md @@ -0,0 +1,51 @@ +# I2C DEMO +## Introduction +This is a basic demonstration of reading/writing I2C protocol. +For this demo, we connect a I2C device to pins: + - 3V3 (3V3, pin 9) = VCC + - GND (GND, pin 18) = GND + - SCL (C0, pin 15) = SCL + - SDA (C1, pin 16) = SDA + + +## Installation Directions +This project is intended to be overlayed on top of an existing firmware repo. +- Clone, Build & Deploy an existing flipper zero firmware repo. See this [tutorial](/firmware/updating/README.md) for updating firmware. +- Copy the "i2c_demo" [folder](..) to the \applications\plugins\i2c_demo folder in your firmware. +- Build & deploy the firmware. See this [tutorial](/firmware/updating/README.md) for updating firmware. +- NOTE: You can also extract the i2c_demo.FAP from resources.tar file and use qFlipper to copy the file to the SD Card/apps/Gpio folder. + + +## Running the updated firmware +These directions assume you are starting at the flipper desktop. If not, please press the back button until you are at the desktop. + +- Press the OK button on the flipper to pull up the main menu. +- Choose "Applications" from the menu. +- Choose "GPIO" from the sub-menu. +- Choose "I2C Demo" + +- The flipper should say "I2C NOT FOUND" if no I2C devices are connected. + +- NOTE: If your I2C device already has pull-up resistors, then you do not need to add them to your SCL and SDA lines. + +- Connect an I2C device (like a BH1750) to pins 9 (3V3), 15 (SCL), 16 (SDA), 18 (GND). +- The message should change to "FOUND I2C DEVICE" +- The next line should have the address of the I2C device. +- If the device is a BH1750 then you should also see "WRITE/READ SUCCESS" and a value that changes with the brightness on the sensor. + +- Press the BACK button to exit. + + +## How it works +- application.fam + - specifies the name of our application. + - specifies the entry point for our application. + - specifies we use the GUI. + - specifies our icon is the i2c_demo.png file. + - specifies our application can be found in the "GPIO" category. + +- i2c_demo.png + - The icon for our application that shows up in the "GPIO" folder. + +- i2c_demo_app.c + - This is the demo application that uses I2C. \ No newline at end of file diff --git a/gpio/i2c_demo/application.fam b/gpio/i2c_demo/application.fam new file mode 100644 index 0000000..517b5ba --- /dev/null +++ b/gpio/i2c_demo/application.fam @@ -0,0 +1,10 @@ +App( + appid="I2c_Demo", + name="I2C Demo", + apptype=FlipperAppType.EXTERNAL, + entry_point="i2c_demo_app", + requires=["gui"], + stack_size=2 * 1024, + fap_icon="i2c_demo.png", + fap_category="GPIO", +) diff --git a/gpio/i2c_demo/i2c_demo.png b/gpio/i2c_demo/i2c_demo.png new file mode 100644 index 0000000000000000000000000000000000000000..f893f75e6fa6e869cc2c153d91a569c1ccead892 GIT binary patch literal 1816 zcma)6eM}Q)7(XO3fuPyOCK9q-4xBn{?|M*>3$=l^LN^X8-|Ry?dT;4T+q>OeY552- z!sb^L*k%SuHW@=VG%RDX%qa+Nj))4&SeDE!V%#4ti*XW|&V_}&*Oreu_R-{e@B2Kz z=lA}e=Y8%uV9CvjiinLs5G2Z+ZOnsjWgW396#jpHN8f}XA+?;rU@;pEs72&$oC6@p z#_^*q>dr>-)Air}tf|Nr-E{-rjh0X3degUD~eOjospP>3h&W`^>2) zJfuEJHAojFhB04FbrF|iLh5FcHh-6eht6C+qQ5mTIhDWJeEPYfuKi;eM@IkrxVyA( z!=;nP?W0%o&ipZV?xStHg`u&#_YKz?xIuYOW$4xyPCOjs+uwcc=J|b})UB`Q*1W#c zgcqK<|BC(PhBv1VK0Ud2!fii0QC@TBC2_Rp`RhY zSLKr8{UiYzBao~jC&(P{LKRx3oOjDQ3@Y$R172>C+$9AH084=@9$cd)6tO<2VCQ9C zvh%`%m>~RuJ19uzZ2!Qoc!Hp?X3H~s8R1bNP5(R8>0FR4$)+lpaG-uclIMw~f@%Ta zc|hXbq7|5`fJ=_YP^AErfp_p?KI{UZ!!!Y#h4TVOiIH;xmjufS9mG;k=_8(n!uZK( zo`qaV7`kYOvm!7mbeNQvm5G{7h7?U&N{Wh9!$XxyQD!Go0djz=Lbm%4T(lN*Y>-h& z#Nk$MywJtL)X4xv$)ZDtrIPqOb?M*j@GdACb}GxrjFFL{V3L4+rXf`%Q9x*^WRlWo z6Nxm6fT{byJj>at{|oBtZWR8~VGK=E*_8wVEWalMd@2qqt+1WlzFVj4j@BtPMTzj*fO6J zzN`RWFD)a$Z~uP+{Mh9L1Qm6ufOK5>a+g*m9bc}1e%z8T-j{^m3dafD!MOR^r0f>v z8*ss)Cc^2pVh +#include + +#include +#include + +#include +#include + +typedef enum { + I2cDemoStateNotFound, + I2cDemoStateFound, + I2cDemoStateWriteSuccess, + I2cDemoStateReadSuccess, + I2cDemoStateWriteReadSuccess, +} I2cDemoState; + +typedef enum { + DemoEventTypeTick, + DemoEventTypeKey, + // You can add additional events here. +} DemoEventType; + +typedef struct { + DemoEventType type; // The reason for this event. + InputEvent input; // This data is specific to keypress data. + // You can add additional data that is helpful for your events. +} DemoEvent; + +typedef struct { + FuriString* buffer; + // You can add additional state here. + int address; + I2cDemoState state; + uint16_t value; +} DemoData; + +typedef struct { + FuriMessageQueue* queue; // Message queue (DemoEvent items to process). + FuriMutex* mutex; // Used to provide thread safe access to data. + DemoData* data; // Data accessed by multiple threads (acquire the mutex before accessing!) +} DemoContext; + +// Invoked when input (button press) is detected. We queue a message and then return to the caller. +static void input_callback(InputEvent* input_event, FuriMessageQueue* queue) { + furi_assert(queue); + DemoEvent event = {.type = DemoEventTypeKey, .input = *input_event}; + furi_message_queue_put(queue, &event, FuriWaitForever); +} + +// Invoked by the timer on every tick. We queue a message and then return to the caller. +static void tick_callback(void* ctx) { + furi_assert(ctx); + FuriMessageQueue* queue = ctx; + DemoEvent event = {.type = DemoEventTypeTick}; + // It's OK to loose this event if system overloaded (so we don't pass a wait value for 3rd parameter.) + furi_message_queue_put(queue, &event, 0); +} + +// Invoked by the draw callback to render the screen. We render our UI on the callback thread. +static void render_callback(Canvas* canvas, void* ctx) { + // Attempt to aquire context, so we can read the data. + DemoContext* demo_context = ctx; + if(furi_mutex_acquire(demo_context->mutex, 200) != FuriStatusOk) { + return; + } + + DemoData* data = demo_context->data; + + canvas_set_font(canvas, FontPrimary); + if(data->address) { + canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, "FOUND I2C DEVICE"); + furi_string_printf(data->buffer, "Address 0x%02x", (data->address)); + canvas_draw_str_aligned( + canvas, 64, 30, AlignCenter, AlignCenter, furi_string_get_cstr(data->buffer)); + + if(data->state == I2cDemoStateWriteSuccess) { + canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "WRITE SUCCESS"); + } else if(data->state == I2cDemoStateReadSuccess) { + canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "READ SUCCESS"); + } else if(data->state == I2cDemoStateFound) { + canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "FOUND DEVICE"); + } else if(data->state == I2cDemoStateWriteReadSuccess) { + canvas_draw_str_aligned( + canvas, 64, 40, AlignCenter, AlignCenter, "WRITE/READ SUCCESS"); + } + furi_string_printf(data->buffer, "value %d", (data->value)); + canvas_draw_str_aligned( + canvas, 64, 50, AlignCenter, AlignCenter, furi_string_get_cstr(data->buffer)); + } else { + canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, "I2C NOT FOUND"); + canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignCenter, "pin15=SDA. pin16=SCL"); + canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "pin9=VCC. pin18=GND"); + } + + // Release the context, so other threads can update the data. + furi_mutex_release(demo_context->mutex); +} + +void demo_i2c_call() { + uint8_t addr = 0x46; + uint8_t reg = 0x20; + uint8_t value8 = 0; + uint16_t value16 = 0; + uint8_t buffer[3] = {0x20, 0, 0}; + uint32_t timeout = 100; + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + + // Typically you use one of the following methods... + furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, addr, timeout); + furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr, buffer, 1, timeout); + furi_hal_i2c_rx(&furi_hal_i2c_handle_external, addr, buffer, 1, timeout); + furi_hal_i2c_trx(&furi_hal_i2c_handle_external, addr, buffer, 1, buffer, 2, timeout); + + // or one of these helper methods... + furi_hal_i2c_write_reg_8(&furi_hal_i2c_handle_external, addr, reg, value8, timeout); + furi_hal_i2c_write_reg_16(&furi_hal_i2c_handle_external, addr, reg, value16, timeout); + furi_hal_i2c_read_reg_8(&furi_hal_i2c_handle_external, addr, reg, &value8, timeout); + furi_hal_i2c_read_reg_16(&furi_hal_i2c_handle_external, addr, reg, &value16, timeout); + + furi_hal_i2c_release(&furi_hal_i2c_handle_external); +} + +uint8_t demo_i2c_find_device() { + uint8_t addr = 0; + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + + for(uint8_t try_addr = 0; try_addr != 0xff; try_addr++) { + if(furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, try_addr, 5)) { + addr = try_addr; + break; + } + } + + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + return addr; +} + +bool demo_i2c_init_bh1750(uint8_t addr) { + bool result = false; + uint8_t buffer[1]; + buffer[0] = 0x1; // write a 0x1 to init a BH1750 device. + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + result = furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr, buffer, 1, 100); + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + + return result; +} + +bool demo_i2c_write_one_time_h_res_mode_bh1750(uint8_t addr) { + bool result = false; + uint8_t buffer[1] = { + 0x20}; // write a 0x20 for "One Time H-Resolution Mode" from BH1750 device. + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + + if(furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr, buffer, 1, 100)) { + result = true; + } + + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + return result; +} + +bool demo_i2c_read_one_time_h_res_mode_bh1750(uint8_t addr, uint16_t* value) { + bool result = false; + uint8_t buffer[2]; + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + + // Read 2 bytes from BH1750 device. + if(furi_hal_i2c_rx(&furi_hal_i2c_handle_external, addr, buffer, 2, 100)) { + *value = (buffer[0] << 8) | buffer[1]; + result = true; + } + + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + return result; +} + +bool demo_i2c_write_read_bh1750(uint8_t addr, uint16_t* value) { + bool result = false; + uint8_t buffer[2] = { + 0x20, 0}; // write a 0x20 for "One Time H-Resolution Mode" from BH1750 device. + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + + if(furi_hal_i2c_trx(&furi_hal_i2c_handle_external, addr, buffer, 1, buffer, 2, 100)) { + *value = (buffer[0] << 8) | buffer[1]; + result = true; + } + + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + return result; +} + +bool demo_i2c_read_reg_bh1750(uint8_t addr, uint16_t* value) { + bool result = false; + + furi_hal_i2c_acquire(&furi_hal_i2c_handle_external); + + if(furi_hal_i2c_read_reg_16(&furi_hal_i2c_handle_external, addr, 0x20, value, 100)) { + result = true; + } + + furi_hal_i2c_release(&furi_hal_i2c_handle_external); + return result; +} + +// Our main loop invokes this method after acquiring the mutex, so we can safely access the protected data. +static void update_i2c_status(void* ctx) { + DemoContext* demo_context = ctx; + DemoData* data = demo_context->data; + + uint8_t addr = 0; + + addr = demo_i2c_find_device(); + if(addr) { + data->state = I2cDemoStateFound; + + if(demo_i2c_init_bh1750(addr)) { + data->state = I2cDemoStateWriteSuccess; + + if(demo_i2c_write_one_time_h_res_mode_bh1750(addr)) { + data->state = I2cDemoStateWriteSuccess; + + if(demo_i2c_read_one_time_h_res_mode_bh1750(addr, &data->value)) { + data->state = I2cDemoStateReadSuccess; + + if(demo_i2c_write_read_bh1750(addr, &data->value)) { + data->state = I2cDemoStateWriteReadSuccess; + } + } + } + } + } + + data->address = addr; +} + +int32_t i2c_demo_app(void* p) { + UNUSED(p); + + // Configure our initial data. + DemoContext* demo_context = malloc(sizeof(DemoContext)); + demo_context->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + demo_context->data = malloc(sizeof(DemoData)); + demo_context->data->buffer = furi_string_alloc(); + demo_context->data->address = 0; + demo_context->data->state = I2cDemoStateNotFound; + demo_context->data->value = 0; + + // Queue for events (tick or input) + demo_context->queue = furi_message_queue_alloc(8, sizeof(DemoEvent)); + + // Set ViewPort callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, demo_context); + view_port_input_callback_set(view_port, input_callback, demo_context->queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + // Update the screen fairly frequently (every 1000 milliseconds = 1 second.) + FuriTimer* timer = furi_timer_alloc(tick_callback, FuriTimerTypePeriodic, demo_context->queue); + furi_timer_start(timer, 1000); + + demo_i2c_call(); + + // Main loop + DemoEvent event; + bool processing = true; + do { + if(furi_message_queue_get(demo_context->queue, &event, FuriWaitForever) == FuriStatusOk) { + switch(event.type) { + case DemoEventTypeKey: + // Short press of back button exits the program. + if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) { + processing = false; + } + break; + case DemoEventTypeTick: + // Every timer tick we update the i2c status. + furi_mutex_acquire(demo_context->mutex, FuriWaitForever); + update_i2c_status(demo_context); + furi_mutex_release(demo_context->mutex); + break; + default: + break; + } + + // Send signal to update the screen (callback will get invoked at some point later.) + view_port_update(view_port); + } else { + // We had an issue getting message from the queue, so exit application. + processing = false; + } + } while(processing); + + // Free resources + furi_timer_free(timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_record_close(RECORD_GUI); + furi_message_queue_free(demo_context->queue); + furi_mutex_free(demo_context->mutex); + furi_string_free(demo_context->data->buffer); + free(demo_context->data); + free(demo_context); + + return 0; +} \ No newline at end of file