From 2ba5bfb2f33a9b33e88cba103d5f6d477c509829 Mon Sep 17 00:00:00 2001 From: thomasabishop Date: Mon, 12 Jan 2026 18:12:53 +0000 Subject: [PATCH] more C notes --- zk/Create_C_header_file.md | 48 +++++ ...ent_loop_and_ESP32_program_architecture.md | 187 ++++++++++++++++++ zk/Static_in_C.md | 12 ++ zk/Structs_in_C.md | 40 ++++ zk/Typedefs_in_C.md | 22 +++ 5 files changed, 309 insertions(+) create mode 100644 zk/Create_C_header_file.md create mode 100644 zk/RTOS_event_loop_and_ESP32_program_architecture.md create mode 100644 zk/Static_in_C.md create mode 100644 zk/Structs_in_C.md create mode 100644 zk/Typedefs_in_C.md diff --git a/zk/Create_C_header_file.md b/zk/Create_C_header_file.md new file mode 100644 index 0000000..14a65e4 --- /dev/null +++ b/zk/Create_C_header_file.md @@ -0,0 +1,48 @@ +--- +tags: + - C +--- + +In order to create a file that you can import into other C files you need to +create a header file. + +> Remember that this isn't really a module since `.h` files and `#includes` are +> just text substition + +In fact you create a `.h` header file which links to a `.c` file which actually +contains the code you want to import. + +Let's say we want to create a "module" that handles connecting to WiFi: + +```c +// wifi.h + +#ifndef WIFI_H +#define WIFI_H + +void wifi_connect(void); + +#endif +``` + +In the `.h` file we just define the function signature and put checks (so it +doesn't get redefined). + +Then the implementation file: + +```c +// main/wifi.c + +// Add any header files or auxiliary functions that the main (wifi_connect) +// function will require here + +void wifi_connect(void) {} +``` + +Then in your main entrypoint: + +```c +// main/main.c +#include "wifi.h" +wifi_connect() +``` diff --git a/zk/RTOS_event_loop_and_ESP32_program_architecture.md b/zk/RTOS_event_loop_and_ESP32_program_architecture.md new file mode 100644 index 0000000..acaaca3 --- /dev/null +++ b/zk/RTOS_event_loop_and_ESP32_program_architecture.md @@ -0,0 +1,187 @@ +--- +tags: + - C + - ESP32 +--- + +"RTOS" stands for "real time operating system". It is category of OSs designed +for embedded systems that need predicatable, time-sensitive responses. + +"Free RTOS" is the particular implementation of RTOS on the ESP32. + +It is **event-driven** because it runs multiple tasks at once rather than +running a single program from top to bottom. (In fact, as with all OSs, it is +running through task scheduling very rapidly.) + +![RTOS event architecture](../img/rtos-event-architecture.png) + +## Event handlers + +Typically, certain events are created by the ESP32 device which you then hook +into and run functions against. + +You then register **handlers** for events that you care about. + +## Event groups + +You use **event groups** to efficiently manage the different events and prevent +other events being blocked. + +An event group is just a number with bits that you can set and clear basically +like a switch. + +These are predefined as `BIT0, BIT1, ...` in +[ESP-IDF](./Programming_ESP32_with_Expressif_IoT_Development_Framework.md) as +[macros](/zk/Macros_in_C.md) for [bitwise operators](/zk/Bitwise_operators.md). + +Your event handler function can update an event group when the thing it is +concerned with happens. Like toggling a switch. + +This allows other processes to act in response and further looped logic to +appply. + +## Walkthrough example: connecting to WiFi + +> Below is a simplified example of using the event loop to connect to WiFi + +### Event group + +First we define our event group bits in order to sequence and track the state: + +```c +#define WIFI_CONNECTED_BIT BIT0 +#define WIFI_FAIL_BIT BIT1 +``` + +These macros rename the `BIT0` and `BIT1` event group bits that ESP-IDF provides +to more meaningful names focused on what we are trying to achieve: + +- connect to WiFi +- track when/if this connection fails + +### Events to target + +The ESP32 WiFi driver publishes a `WIFI_EVENT` when WiFi-related processes +occur. `WIFI_EVENT` is the broad category (called the "event base"), which +includes the following more specific events: + +- `WIFI_EVENT_STA_START` + - WiFi on device started in station mode (ready to connect) +- `WIFI-EVENT_STA_CONNECTED` + - Device has connected to router +- `WIFI_EVENT_STA_DISCONNECTED` + - Device has disconnected from router + +Another event base is `IP_EVENT` with the specific events: + +- `IP_EVENT_STA_GOT_IP` + - Device has been assigned an IP by [DHCP](./DHCP.md) server +- `IP_EVENT_STA_LOST_IP` + +### Register event handler + +The following code registers a handler for the `WIFI_EVENT` event base, in the +form of a custom function `wifi_event_handler`: + +```c +esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler) +``` + +The second parameter captures all events in the `WIFI_EVENT` category. The third +parameter is a pointer to the function that runs when the event fires +(`wifi_event_handler`): + +```c +static void wifi_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) { + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + esp_wifi_connect(); + } else if (event_base == WIFI_EVENT && + event_id == WIFI_EVENT_STA_DISCONNECTED) { + if (s_retry_num < MAX_RETRY) { + esp_wifi_connect(); + s_retry_num++; + ESP_LOGI(TAG, "Retrying connection..."); + } else { + xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); + } + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ESP_LOGI(TAG, "Connected! Got IP"); + s_retry_num = 0; + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + } +} +``` + +In essence: + +- If WiFi connection event is triggered attempt to connect +- If WiFi disconnection event is triggered try to reconnect within retry params, + if no luck, set wifi connection event group to error +- If IP is assigned set wifi connection event group to success + +> `esp_wifi_connect` is the built in WiFi connection logic from ESP-IDF from +> `esp-wifi.h` header file + +### Main program logic + +We have a main wrapper function `wifi_connect` that manages calls to the event +handler function and which established the event group. This would run once at +setup: + +```c +void wifi_connect(void) { + s_wifi_event_group = xEventGroupCreate(); + + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_netif_create_default_wifi_sta(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + esp_event_handler_instance_t instance_any_id; + esp_event_handler_instance_t instance_got_ip; + ESP_ERROR_CHECK(esp_event_handler_instance_register( + WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, + &instance_any_id)); + ESP_ERROR_CHECK(esp_event_handler_instance_register( + IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, + &instance_got_ip)); + + wifi_config_t wifi_config = { + .sta = + { + .ssid = WIFI_SSID, + .password = WIFI_PASS, + }, + }; + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + + // Wait for connection + EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, + WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, + pdFALSE, pdFALSE, portMAX_DELAY); + + if (bits & WIFI_CONNECTED_BIT) { + ESP_LOGI(TAG, "WiFi connected successfully"); + } else { + ESP_LOGE(TAG, "WiFi connection failed"); + } +} +``` + +Lots going on but basically: + +- Register the WiFi event group +- Register the event handler and pass in the events we are interested in +- Store the `ssid` and `password` in the internal state of the device (these + don't need to be handed to the actual `esp_wifi_connect()` function - they are + remembered once set) +- Log success based on the event group `WIFI_CONNECTED_BIT` + +> Note that we register two event handlers but pass the same function +> (`wifi_event_handler`) because it handles both the `WIFI` and `IP` event +> bases. diff --git a/zk/Static_in_C.md b/zk/Static_in_C.md new file mode 100644 index 0000000..56d1ed9 --- /dev/null +++ b/zk/Static_in_C.md @@ -0,0 +1,12 @@ +--- +tags: + - C +--- + +`static` signals that the variable is private to the file it appears in. It +cannot be accessed from outside of it. + +The opposite to a global variable. + +Using `static` prevents naming conflicts if you have another file with the same +variable name. diff --git a/zk/Structs_in_C.md b/zk/Structs_in_C.md new file mode 100644 index 0000000..9f24c68 --- /dev/null +++ b/zk/Structs_in_C.md @@ -0,0 +1,40 @@ +--- +tags: + - C +--- + +A `struct` is a custom, user-defined type, in constrast to the primitive types +(`int`, `char`, `float` etc). They are known as **compound types**. + +## Usage + +Define the custom type: + +```c +struct Person { + int age; + float height; +} +``` + +Then to apply the type to a variable: + +```c +struct Person thomas +``` + +And define the sub-properties: + +```c +thomas.age = 37 +thomas.height = 5.8 +``` + +Alternatively do all together: + +```c +struct Person thomas = { + .age = 37, + .height = 5.8 +} +``` diff --git a/zk/Typedefs_in_C.md b/zk/Typedefs_in_C.md new file mode 100644 index 0000000..ecc91c2 --- /dev/null +++ b/zk/Typedefs_in_C.md @@ -0,0 +1,22 @@ +--- +tags: + - C +--- + +A `typedef` is an alias for an existing type. It can make complex types easier +to write and provides abstraction. + +The syntax: + +```c +typedef existing_type new_name; +``` + +For example: + +```c +typedef unsigned int uint_t +``` + +> The convention is to append `_t` to the variable name to make it easier to +> understand that it is a typedef later in the execution.