This is the 2nd blog in a series of 5 blogs for the At The Core Design Challenge. It describes in more detail, my idea and describes the Functional and Technical designs that I will use to implement my idea.
Since the PSoC6 kit does not contain any connectivity features, I cannot implement a part of my idea. I had thought that I would publish the data to a Cloud service, but for now I'll be just sending the data to the PC via a UART connection. I will ponder using a UART connection to my PSoC62S2 Wi-Fi BT Pioneer kit, in another phase of the this project.
I plan to run firmware on both cores and communicate between cores using the Hardware implemented inter-process communication capabilities of the PSoC6 . I will implementing an IPC pipe to send message between cores. Here is my functional design showing the tech I'm going to use in my implementation. These blocks are broken out into test examples that I will use to unit test each block in blocg#4 Then in blog # 5, I will tie all these blocks together into my final design. and implementation of my idea.
I will implement barebones firmware on the M0+ core.
- an IPC endpoint will be used to receive and send messages.
- the Temp and ALS onboard sensors will be used to gather data to be sent over the IPC connection.
A FreeRtos based firmware will be implemented on the M4 core.
- The FreeRTOS scheduler will be used to run task.
- The buttons will be used to select which task to run.
- Task1 -will send a ALS message to get ALS data.
- Task2 -will send a Thermistor message to get the Temperature.
- A UART is connected to the PC running Tera-Term. Will be used to display the data coming from the messages sent from M0+ core.
- an IPC endpoint will be used to get and send messages.
Basic DATA FLOW
- Press button
- From the M4 firmware
- Send message to M0 core over pipe
- The message contains which sensor data will be used based on which button is pressed.
- From M0 core firmware
- get the data from sensor requested in the message.
- Send sensor data to M4 core in a message
- Send sensor data via UART to the terminal software on PC
- Display sensor data on terminal software
I will be implementing my idea all on the PSoC6 kit, with no external attached peripherals or sensors. My goal will be to learn and experiment with a few embedded concepts that I've be interested in. I will be using:
- Inter-Process Communication IPC
- FreeRTOS, scheduler
- SENSORS
- Ambient Light Sensor ALS
- Temp
- UART
IPC PIPES, FreeRTOS,Thermistor, Ambient Light Sensor (ALS), and Buttons
I used a few examples on FreeRTOS Scheduler, Thermistor, Ambient Light Sensor (ALS),and IPC PIPES on the Infineon Github repo to get familiar with these new concepts.
Let break those down in the next couple of sections
IPC Inter Process Communication
I decided to implement Pipes to communicate between the cores for my design. I will use the PSoC 6 Peripheral Driver Library (PDL) IPC (Inter Process Communication). This section contains my notes and my design of the implementation of pipes for my Idea.
IPC PDL
- The inter-processor communication (IPC) driver provides a safe and reliable method to transfer data between CPUs
- Hardware locking ensures that only one device can acquire and transfer data at a time.
- To use the IPC driver, either cy_ipc_pipe.h or cy_ipc_sema.h must be included. Alternatively, cy_pdl.h can be included to access all functions and declarations in the PDL.
- The API has three parts: Driver-level (DRV) API, Pipe-level (PIPE) API, and Semaphore-level (SEMA) API.
- The (DRV) is the bottom layer of the Library.
- The (PIPE) and (SEMA) API's sit over the( DRV) API
- NOTE: Firmware does not need to use the DRV API and can implement IPC functionality entirely with the PIPE and SEMA APIs.
The Pipe is the key element in the PDL design. The functional requirements for pipes, includes the following:
- A pipe is typically a full-duplex communication channel between CPU cores.
- A pipe allows a single conduit to transfer messages or data to and from multiple processes or CPUs.
- A pipe has two endpoints, one on each core.
- Each endpoint contains a dedicated IPC channel and an interrupt.
- IPC channels 0-7 and IPC interrupts 0-7 are reserved for system use.
- The pipe also contains the number of clients it supports, and for each client a callback function.
- The pipe can service a number of clients, each with a separate callback function, on either endpoint.
- The number of clients a pipe supports is the sum of each endpoint's clients.
- This design enables any number of processes on the sending core to put arbitrary data into a single pipe.
- The first element of that data is the client ID of the client that should handle the data.
- An interrupt notifies the receiving core that data is available.
- The receiving core parses the data to identify the client, and then dispatches the event to the appropriate client via the client callback function.
- An interrupt notifies the sending core that the receiver is finished.
- In this way, a single pipe can manage arbitrary data transfers between cores with data flowing in either direction.
Driver-level (DRV) API
The IPC implementation on the PSoC6 is a hardware-based communication channel with a locking mechanism that allows only one processor to access it at a time. The Driver-level API manages the IPC registers to implement communication between processors. I use the PIPE layer in my design the the DRV events are described here for clarity.
The communication process involves:
On The SENDER:
1. ACQUIRE - Acquiring a channel,
2. DATA - Putting data
3. NOTIFY Receiver by Generating an interrupt
On The RECEIVER:
4. DATA - identifies the sender and retrieves the data
5. RELEASE - generates a release event, which generates another interrupt. on the sender
The PIPE and SEMA layers of the API can be used to implement communication between cores, in applications, with data transfer limited to a single 32-bit value that can be a pointer to a data structure of any size and complexity.
I did try the Semaphore {SEMA example), which is very well explained and is supported on MTD3.X. I will be using the Pipe-level (PIPE) API for my design.
I will continue with my notes on the PIPE layer, since I'll be using it in my design.
Pipe-level (PIPE) API
What is a PIPE?
- A pipe is a communication channel between two endpoints.
- PSoC 6 devices support 16 IPC channels and 16 IPC interrupts, with some reserved for system use.
- Full duplex pipes use two IPC channels, one per endpoint, while one-directional pipes use a single IPC channel.
- A pipe can support multiple clients with an array of callback functions.
- The first element of any data sent using a pipe must contain a client ID number.
- When a message is sent, the receiver's interrupt handler calls the associated callback function.
- After the callback function is returned, it invokes the release callback function defined by the sender
PIPE layer description:
- PSoC 6 devices support 16 IPC channels and 16 IPC interrupts, each numbered 0-15.
- 8 IPC channels and 8 IPC interrupts are reserved (0-7) for PSoC62.
- A full duplex pipe uses two IPC channels, one per endpoint.
- An endpoint specifies an IPC channel and an IPC interrupt to process a message.
- IPC Interrupts are not directly associated with the IPC channels, so any channel can use any interrupt.
- Any IPC channel can trigger 0, 1 or all the IPC interrupts at once, depending on the Notify or Release masks used.
- A one-directional pipe uses a single IPC channel where one processor is always the sender and the other is always the receiver.
- A pipe supports an arbitrary number of clients with an array of callback functions, one per client.
- After a pipe is configured and initialized, the application calls Cy_IPC_Pipe_RegisterCallback() once per client to register each client's callback function.
- Multiple clients can use the same callback function, and the endpoints in a pipe share the callback array.
- Use Cy_IPC_Pipe_SendMessage() to send data, specifying both the "to" and "from" endpoints and a callback function to be used when the data transfer is complete.
- The data is a 32-bit void pointer, with the first element being a 32-bit unsigned word containing a client ID number.
- When a message is sent, the receiving endpoint's interrupt handler is called.
- The ISR retrieves the client ID from the data and calls the associated callback function using Cy_IPC_Pipe_ExecCallback().
- The user-supplied callback function handles the data in whatever way is appropriate based on the application logic.
- After the callback function is returned by the receiver, it invokes the release callback function defined by the sender of the message.
A basic one direction sender/receiver example
There are many ways to configure and use pipes depending on specific needs. The next example provided is a basic one direction sender/receiver example
Endpoint 1 (Sender):
#include "cy_ipc_drv.h" #define PIPE_CHANNEL 1 #define PIPE_INTERRUPT 1 #define CLIENT_ID 0 uint32_t data[] = {CLIENT_ID, 42, 84}; // The first element of the data must be the client ID void send_data(void) { // Initialize the pipe and register the callback function for the receiver Cy_IPC_Pipe_RegisterCallback(PIPE_CHANNEL, PIPE_INTERRUPT, CLIENT_ID, receive_data); // Send the data to endpoint 2 Cy_IPC_Pipe_SendMessage(PIPE_CHANNEL, PIPE_INTERRUPT, (void*) data, sizeof(data)); } void receive_data(uint32_t *data, uint32_t len, void *arg) { // Process the received data uint32_t client_id = *data++; // The first element of the data is the client ID uint32_t value1 = *data++; uint32_t value2 = *data++; // Invoke the release callback function to signal to endpoint 2 that the data has been processed Cy_IPC_Pipe_ReleaseCallback(PIPE_CHANNEL, client_id); }
Endpoint 2 (Receiver):
#include "cy_ipc_drv.h" #define PIPE_CHANNEL 1 #define PIPE_INTERRUPT 1 void receive_data(uint32_t *data, uint32_t len, void *arg) { // Process the received data uint32_t client_id = *data++; // The first element of the data is the client ID uint32_t value1 = *data++; uint32_t value2 = *data++; // Invoke the release callback function to signal to endpoint 1 that the data has been processed Cy_IPC_Pipe_ReleaseCallback(PIPE_CHANNEL, client_id); } void setup_pipe(void) { // Initialize the pipe and register the callback function for the receiver Cy_IPC_Pipe_RegisterCallback(PIPE_CHANNEL, PIPE_INTERRUPT, 0, receive_data); }
The key functions to used are:
- Cy_IPC_Pipe_RegisterCallback() is a key function to register the callback functions for each endpoint.
- Cy_IPC_Pipe_SendMessage() and Cy_IPC_Pipe_ReleaseCallback() are key functions to send and release data between the endpoints.
PSoC 6 MCU: Dual-CPU IPC pipes Example
Here is the link to the example on the Infineon repo: GitHub - Infineon/mtb-example-psoc6-dual-cpu-ipc-pipes
The example demonstrates the use of the IPC driver to create a message pipe that enables the communication between two CPUs. The pipe is used to send messages between CPUs. This IPC pipes example interested me and is what I had in mind for my IPC implementation. In order to experiment with it I had to import it into Modustoolbox first. I had to use MTB 2.4 and had troubles running dual versions of MTB . And I was advised to migrate the application to MTD 3.0 which I did as an exercise in blog#3.
In this example, the CM0+ CPU generates 32-bit random numbers periodically using the crypto block. The CM4 CPU receives the random numbers from the CM0+ CPU through IPC pipes. The CM4 CPU prints the random numbers to a terminal emulator. It also controls when the CM0+ CPU starts and stops creating random numbers.
The README.MD file contains a detailed implementation section, But what follows is my notes on the Design and implementation of the firmware example after porting it to ModusToolbox 3.0
This code example involves using two CPUs, CM4 and CM0+, in the PSoC 6 MCU.
- CM4 is responsible for initializing the system and CM0+ waits for an IPC command to initialize.
- There are four types of IPC commands: INIT, START, STOP, and STATUS
IPC_CMD_INIT
: CM0+ waits for this command to initializeIPC_CMD_START
: CM0+ waits for this command to enable a watchdog timer and the Crypto blockIPC_CMD_STOP
: CM0+ waits for this command to disable a watchdog timer and the Crypto blockIPC_CMD_STATUS
: CM4 waits for this command to print a number to the terminal
- CM0+ uses a watchdog timer to wake itself up periodically and generates a 32-bit random number using the crypto block.
- CM4 detects button presses and releases, and instructs CM0+ to start or stop creating random numbers accordingly.
- CM4 receives messages from CM0+, parses them, and prints the random number to the terminal.
Flowchart -- courtesy of the README,md file. This was an inspiration for my design.
Operation- courtesy of the README,md file.
- Connect the board to your PC using the provided USB cable through the KitProg3 USB connector.
- Open a terminal program and select the KitProg3 COM port. Set the serial port parameters to 8N1 and 115200 baud.
- Program the board using Eclipse IDE for ModusToolbox software
- Select the application project in the Project Explorer.
- In the Quick Panel, scroll down, and click <Application Name> Program (KitProg3_MiniProg4).
- Here is what the output should look like Terminal output on program startup
FreeRTOS
OVERVIEW of FreeRTOS.
FreeRTOS is a class of RTOS that is designed to be small enough to run on a microcontroller - although its use is not limited to microcontroller applications. It provides the core real time scheduling functionality, inter-task communication, timing and synchronization primitives only. This means it is more accurately described as a real time kernel, or real time executive. Additional functionality, such as a command console interface, or networking stacks, can then be included with add-on components..
Differences between BARE-METAL an d RTOS Programming
Bare-metal programming and Real-Time Operating Systems (RTOS) are two different approaches to software development for embedded systems.
Bare-metal programming refers to writing code directly for the hardware without any additional layers or abstractions. This means that the programmer has full control over the system and can optimize the code for the specific hardware platform. However, this also means that the programmer is responsible for managing all aspects of the system, including timing, scheduling, and resource allocation. Barebones programming can be a good choice for small and simple embedded systems where real-time requirements are not critical.
On the other hand, an RTOS is a software layer that provides a set of services for managing tasks, scheduling, synchronization, and communication between software components. An RTOS provides a higher level of abstraction than barebones programming, which simplifies the development process and makes the software more portable. An RTOS can also provide real-time guarantees, which means that tasks are scheduled and executed in a deterministic and predictable manner. This makes RTOS a better choice for systems with complex requirements, where tasks need to execute in a timely and predictable manner.
Here are some of the key differences between barebones programming and an RTOS:
Control: Bare-metal programming provides full control over the system, while an RTOS provides a more abstracted layer of control.
Complexity: Bare-metal programming can be more complex, as the programmer is responsible for managing all aspects of the system. An RTOS simplifies the development process by providing a set of services for managing tasks, scheduling, synchronization, and communication.
Execution: Bare-metal programming uses a loop to execute tasks sequentially. RTOS provides time slicing of task. Time slicing is a technique used to enable multiple tasks or processes to run concurrently on a single processor. It involves dividing the processor's time into small slices, typically a few milliseconds long, and allocating each slice to a different task. The operating system switches between tasks rapidly, giving the impression that they are all running simultaneously.
Portability: Barebones programming is tightly coupled to the hardware platform, making it less portable. An RTOS provides a more abstracted layer, making it easier to port software to different hardware platforms.
Real-time guarantees: An RTOS can provide real-time guarantees, ensuring that tasks are scheduled and executed in a deterministic and predictable manner. Barebones programming does not provide this level of guarantee.
Overall, the choice between barebones programming and an RTOS depends on the specific requirements of the embedded system. Simple systems with no real-time requirements may be better suited for barebones programming, while complex systems with real-time requirements may benefit from using an RTOS.
To see how Free RTOS is used using ModusToolbox 3.0 and the PSoC-62S4, I used the following example GitHub - Infineon/mtb-example-psoc6-low-power-capsense-freertos from the Infineon Github repo.
This code example showcases the development of a low-power CAPSENSE design utilizing the PSoC 6 MCU. It includes a 5-segment CAPSENSE slider and a ganged sensor, which detects touch position and displays it through the serial terminal. The CAPSENSE middleware library is used to develop this example.
This example, is far more complex and is great at demonstrating Low Power CAPSENSE, 5-segment CAPSENSE slider, and a ganged sensor, which detects touch position and displays it through the serial terminal.. However, I am more interested in learning how to use the FreeRTOS task, schedular and task priority level for us in my idea. The README.md file for this example gives a great design and implementation notes for using this example.
I read thru and ran the example to understand it. I needed a far more simple example of showing basic FreeRTOS functionality in ModusToolbox so I came up with the following simple example.
FreeRTOS Simple Example
Here's a simple example that creates two tasks that print messages to the console:
#include "FreeRTOS.h" #include "task.h" void task1(void* pvParameters) { const char* message = "Task 1 is running\n"; for (;;) { printf(message); vTaskDelay(pdMS_TO_TICKS(1000)); // delay for 1 second } } void task2(void* pvParameters) { const char* message = "Task 2 is running\n"; for (;;) { printf(message); vTaskDelay(pdMS_TO_TICKS(500)); // delay for 500 milliseconds } } int main(void) { xTaskCreate(task1, "Task 1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); xTaskCreate(task2, "Task 2", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); vTaskStartScheduler(); for (;;); return 0; }
This code defines two tasks, "task1" and "task2", which both print a message to the console and then wait for a period of time before repeating. The tasks are created using the "xTaskCreate" function, which takes several parameters, including the task function, a name for the task, a stack size, and a priority. The tasks are started using the "vTaskStartScheduler" function, which begins the FreeRTOS scheduler and switches between the tasks as needed. The two tasks should run concurrently, printing messages to the console at different intervals.
Another simple Example using 3 Task.
here's an example of a FreeRTOS implementation with 3 tasks, implemented in ModusToolbox 3.0:
Functional Spec: In this example, we will implement a FreeRTOS project with 3 tasks using ModusToolbox 3.0. The three tasks will be a producer task that generates random numbers, a consumer task that consumes these numbers and performs some operations, and a LED task that toggles an LED at a fixed interval. The goal of this project is to demonstrate how to create and manage multiple tasks using FreeRTOS in ModusToolbox 3.0.
Implementation:
- Create a new FreeRTOS project in ModusToolbox 3.0 using the PSoC 6 MCU template.
- Add the following code to the main.c file to define the three tasks:
/* Producer task to generate random numbers */ void vProducerTask(void *pvParameters) { while(1) { int randNum = rand() % 100; // Generate a random number between 0 and 99 xQueueSend(xQueue, &randNum, portMAX_DELAY); // Send the number to the queue vTaskDelay(500 / portTICK_PERIOD_MS); // Delay for 500 ms } } /* Consumer task to consume the random numbers and perform some operations */ void vConsumerTask(void *pvParameters) { while(1) { int randNum; xQueueReceive(xQueue, &randNum, portMAX_DELAY); // Receive the number from the queue printf("Received number: %d\n", randNum); // Print the number // Perform some operations with the number randNum *= 2; printf("Processed number: %d\n", randNum); // Print the processed number } } /* LED task to toggle an LED at a fixed interval */ void vLedTask(void *pvParameters) { while(1) { // Toggle the LED Cy_GPIO_Inv(LED_PORT, LED_PIN); vTaskDelay(1000 / portTICK_PERIOD_MS); // Delay for 1000 ms } }
- Add the following code to the main function to initialize the queue and create the three tasks:
int main(void) { // Initialize the hardware Cy_SysEnableCM4(CY_CORTEX_M4_APPL_ADDR); Cy_SysEnableCM0p(CY_CORTEX_M0P_APPL_ADDR); __enable_irq(); Cy_GPIO_Pin_Init(LED_PORT, LED_PIN, &led_config); xQueue = xQueueCreate(10, sizeof(int)); // Create the queue // Create the tasks xTaskCreate(vProducerTask, "Producer", 1000, NULL, 1, NULL); xTaskCreate(vConsumerTask, "Consumer", 1000, NULL, 2, NULL); xTaskCreate(vLedTask, "LED", 1000, NULL, 3, NULL); // Start the FreeRTOS scheduler vTaskStartScheduler(); for(;;); }
- Build and program the project to the development board.
- Observe the behavior of the three tasks:
- The producer task generates random numbers and sends them to the queue every 500 ms.
- The consumer task receives the numbers from
Ambient Light &Thermistor Sensors
The Infineon PSoC62S4 kit comes with an Ambient Light Sensor (ALS) and a Thermistor Sensor. These sensors can be used to measure the light and temperature levels in the environment and provide feedback to the microcontroller for further processing.
Here are some basics about the Ambient Light and Thermistor Sensors on the Infineon PSoC62S4:
- Ambient Light Sensor (ALS): The ALS on the Infineon PSoC62S4 is based on a photodiode that converts light into an electrical signal. The sensor can detect light levels in the range of 0.01 lux to 64,000 lux, depending on the sensitivity setting. The sensor output is an analog voltage that can be read by the microcontroller's ADC.
- Thermistor Sensor: The thermistor sensor on the Infineon PSoC62S4 is a temperature-sensitive resistor that changes its resistance in response to changes in temperature. The sensor can measure temperatures in the range of -40°C to +125°C, depending on the specific thermistor used. The sensor output is an analog voltage that can be read by the microcontroller's ADC.
PSoC6 MCU: SAR ADC low-power sensing - thermistor and ALS
To see how Free RTOS is used using ModusToolbox 3.0 and the PSoC-62S4, I used the following example on the Infineon repo: GitHub - Infineon/mtb-example-psoc6-low-power-sar-adc-thermistor-als
The following code illustrates how to perform low-power sensing of a thermistor and ambient light sensor (ALS) by utilizing the SAR ADC of the PSoC 6 MCU. It's important to note that this code is only applicable to devices equipped with an SAR ADC that can function in System deepsleep mode.
This example, is far more complex and is great at demonstrating Low Power Sensing, Deepsleep, and utilizing the SAR ADC of the PSoC6. However, I am more interested in learning how to perform sensing of the onboard thermistor and ambient light sensor (ALS) by NOT utilizing the SAR ADC on the PSoC6, if that is even possible. The SAR ADC on the PSoC6 MCU is an integrated Successive Approximation Register (SAR) analog-to-digital converter (ADC) that can convert analog signals into digital data with high accuracy and low power consumption. I could not find any examples that did not use the SAR ADC.
I read thru the README.md file and ran the example to understand it.
I needed a far more simple example of showing basic Ambient Light &Thermistor Sensors functionality in ModusToolbox so I came up with the following simple examples for ALS and Thermistor sensors.
a simple C code example that demonstrates how to read the value of the Ambient Light Sensor (ALS) and the Thermistor Sensor using the Infineon PSoC62S4 microcontroller and ModusToolbox 3.0:
#include "cy_pdl.h" #include "cyhal.h" #include "cybsp.h" int main(void) { /* Initialize the board support package (BSP) */ cybsp_init(); /* Initialize the ADC channels for the ALS and Thermistor sensors */ cyhal_adc_channel_t als_channel = CYBSP_AIO_2; cyhal_adc_channel_t therm_channel = CYBSP_AIO_1; cyhal_adc_init(&cy_adc_0); cyhal_adc_channel_init(&cy_adc_0, als_channel, NULL); cyhal_adc_channel_init(&cy_adc_0, therm_channel, NULL); while(1) { /* Read the value of the ALS sensor */ uint32_t als_raw_value = 0; cyhal_adc_convert(&cy_adc_0, als_channel, &als_raw_value); float als_voltage = cyhal_adc_counts_to_volts(&cy_adc_0, als_channel, als_raw_value); /* Convert the voltage to a lux value */ float als_lux = als_voltage * 2000.0f; /* Read the value of the Thermistor sensor */ uint32_t therm_raw_value = 0; cyhal_adc_convert(&cy_adc_0, therm_channel, &therm_raw_value); float therm_voltage = cyhal_adc_counts_to_volts(&cy_adc_0, therm_channel, therm_raw_value); /* Convert the voltage to a temperature value in Celsius */ float therm_temp = (therm_voltage - 0.75f) / 0.01f; /* Print the sensor values to the serial terminal */ printf("ALS Lux: %f, Therm Temp: %f\n", als_lux, therm_temp); /* Delay for a short period of time before taking another reading */ cyhal_system_delay_ms(1000); } }
This code initializes the ADC channels for the ALS and Thermistor sensors, reads the raw sensor values, converts them to voltage values, and then converts the voltage values to meaningful sensor readings in lux and Celsius degrees. The sensor values are then printed to the serial terminal and the program waits for a short period of time before taking another reading.
Note that this code assumes that the ALS sensor is connected to Pin 2 (CYBSP_AIO_2) and the Thermistor sensor is connected to Pin 1 (CYBSP_AIO_1) on the Infineon PSoC62S4 development board. You may need to modify the code if your sensors are connected to different pins or if you are using a different development board.
The next 2 examples demonstrate the separate ADC implementations of the Ambient Light & Thermistor Sensors functionality in ModusToolbox
AMBIENT LIGHT SENSOR (ALS)
To assign the pin for the ambient light sensor in ModusToolbox 3.0, you can follow these steps:
- Open your PSoC 62 project in ModusToolbox 3.0 and navigate to the "Design" view.
- Select the PSoC 62 device in the "Device Configurations" tab.
- In the "Pinout & Configuration" tab, find the pin you want to use for the ambient light sensor input.
- Click on the pin to open the "Pin Configuration" dialog box.
- In the "General" tab of the "Pin Configuration" dialog box, select "Analog" as the pin's function.
- In the "Analog" tab, select "ADC" as the analog function.
- Choose the appropriate ADC channel for your sensor (for example, ADC0 or ADC1) and configure any additional settings as needed.
- Click "OK" to save the pin configuration.
Once you have assigned the pin for the ambient light sensor, you can use ModusToolbox 3.0's code generation tools to generate the code needed to read the sensor data from the ADC channel. This will typically involve configuring the ADC channel with the appropriate settings and calling the appropriate ADC APIs to read the sensor data.
-
First, follow the steps I mentioned earlier to assign an analog input pin to the ADC function in the "Pinout & Configuration" tab of your PSoC 62 project.
-
Next, open the ModusToolbox IDE and create a new project for your PSoC 62 device.
-
In the "Code Examples" tab of the ModusToolbox IDE, search for "ADC" and select the "ADC_SingleEnded" example.
-
This example project demonstrates how to read data from a single-ended ADC channel using interrupts. Open the main.c file and modify the code as follows:
#include "cy_pdl.h" #include "cyhal.h" #include "cybsp.h" #include "stdio.h" #define SAMPLES_PER_SECOND 10 // Define the ADC channel to use #define ADC_CHANNEL CYBSP_AIO_0 // Replace this with the pin you assigned earlier static cyhal_adc_t adc_obj; static cyhal_adc_channel_t adc_channel_obj; static cyhal_gpio_t led_pin; static volatile bool conversion_complete = false; void adc_callback(const void *callback_arg, cyhal_adc_event_t event, uint32_t event_data) { if (event == CYHAL_ADC_EVENT_SCAN_COMPLETE) { conversion_complete = true; } } int main(void) { cy_rslt_t result; // Initialize the board support package and ADC driver result = cybsp_init(); CY_ASSERT(result == CY_RSLT_SUCCESS); result = cyhal_adc_init(&adc_obj); CY_ASSERT(result == CY_RSLT_SUCCESS); // Configure the ADC channel result = cyhal_adc_channel_init(&adc_channel_obj, ADC_CHANNEL, NULL); CY_ASSERT(result == CY_RSLT_SUCCESS); // Configure the LED pin for output result = cyhal_gpio_init(CYBSP_LED_RGB_RED, CYHAL_GPIO_DIR_OUTPUT, CYHAL_GPIO_DRIVE_STRONG, false); CY_ASSERT(result == CY_RSLT_SUCCESS); led_pin = CYBSP_LED_RGB_RED; // Start the ADC conversion result = cyhal_adc_start_scan(&adc_obj); CY_ASSERT(result == CY_RSLT_SUCCESS); while (1) { // Wait for the conversion to complete while (!conversion_complete) { cyhal_system_delay_ms(1000/SAMPLES_PER_SECOND); } // Read the ADC value and print it to the console uint16_t result; cyhal_adc_read(&adc_channel_obj, &result); printf("ADC value: %u\n", result); // Toggle the LED based on the ADC value if (result > 2048) { cyhal_gpio_write(led_pin, CYHAL_GPIO_VALUE_HIGH); } else { cyhal_gpio_write(led_pin, CYHAL_GPIO_VALUE_LOW); } // Reset the conversion complete flag conversion_complete = false; } }
In this modified example, we have replaced the ADC channel with the pin you assigned earlier, and we are using the ADC to read the analog voltage value from that pin. The program prints the ADC value to the console and toggles an LED based on the ADC value.
- Build and program the project to your PSoC 62 device, and you should see the ADC value and LED behavior change as you vary the input voltage on the analog input pin you assigned.
Thermistor
The pin assignment for the Thermistor will depend on the specific board and hardware configuration being used. Typically, the Thermistor will be connected to an analog input pin on the microcontroller, and the pin assignment may vary depending on the board design and pin mapping.
You will need to consult the documentation or schematic for your specific board or hardware design to determine the pin assignment for the Thermistor on the PSoC62S4 in ModusToolbox 3.0.
To assign a pin for the Thermistor in ModusToolbox 3.0, you can use the Pin Configurator tool that is included in the IDE. Here are the general steps you can follow:
-
Open your project in ModusToolbox 3.0.
-
Navigate to the "Design Wide Resources" tab in the "ModusToolbox IDE" perspective.
-
Click on the "Pin Configurator" button to launch the Pin Configurator tool.
-
Select the PSoC62S4 device in the "Device Selector" panel on the left-hand side of the Pin Configurator window.
-
Find the pin you want to use for the Thermistor in the "Pin Table" panel.
-
Double-click on the pin to open the "Pin Configuration" window.
-
In the "Pin Configuration" window, select "Analog Input" as the pin function.
-
Click "OK" to close the "Pin Configuration" window.
-
Repeat steps 5-8 for any other pins you want to use for the Thermistor.
-
Save the pin configuration changes.
Once you have assigned the pins for the Thermistor using the Pin Configurator tool, you can access and configure them in your code using the appropriate APIs provided by the PSoC62S4 device driver libraries.
Here is a simple example using the pin assigned for the Thermistor in ModusToolbox 3.0:
#include "cy_pdl.h" #include "cyhal.h" int main(void) { cy_rslt_t result; cyhal_adc_channel_t thermistor_channel; uint16_t adc_value; // Initialize the ADC result = cyhal_adc_init(&thermistor_channel, P6_0, NULL); if (result != CY_RSLT_SUCCESS) { // Handle initialization error } while(1) { // Read the ADC value result = cyhal_adc_read(&thermistor_channel, &adc_value); if (result != CY_RSLT_SUCCESS) { // Handle ADC read error } // Convert the ADC value to temperature float temperature = convert_to_temperature(adc_value); // Use the temperature value in your application // ... // Wait for some time before taking another reading cyhal_system_delay_ms(500); } }
In this example, we initialize an ADC channel using the pin assigned for the Thermistor (in this case, pin P6_0). We then continuously read the ADC value from the channel, convert it to a temperature value, and use the temperature value in our application. The convert_to_temperature
function would need to be implemented separately based on the characteristics of the Thermistor being used. Finally, we wait for some time before taking another reading.
Note that this is a very basic example and you would need to add error handling, calibration, and other functionality as appropriate for your application.
BUTTONS - USER or CAPSENSE?
Introduction
USER buttons are typically physical buttons that are integrated into the hardware of a device. These buttons can be directly controlled by the device's firmware or software. In contrast, CAPSENSE buttons use a capacitive sensing mechanism to detect the presence of a finger or other conductive material.
When deciding which type of button to use, it depends on the specific requirements of the project. Some factors to consider include the size and shape of the button, the required level of sensitivity, and the overall cost of implementation.
In general, USER buttons are simpler and less expensive to implement than CAPSENSE buttons. However, CAPSENSE buttons offer several advantages, such as being more durable and less prone to wear and tear over time. They can also provide more precise and reliable input detection, as well as allowing for more creative button shapes and layouts.
Ultimately, the choice between USER and CAPSENSE buttons will depend on the specific needs and constraints of the project, as well as the trade-offs between cost, functionality, and design considerations. I decided to use the Capsense buttons for my implementation To learn how to use them in my design I used the following example on the Infineon repo:
CAPSENSE :PSoC 6 MCU: CAPSENSE buttons and slider
on the repo: GitHub - Infineon/mtb-example-psoc6-capsense-buttons-slider
This example uses a 5-segment CapSense slider and two CapSense buttons to control an LED. I am only interested in the 2 CAPSENSE buttons, but this will help me under stand the concept. I will use this example as a guide to create a minimal test with the 2 Capsense buttons
Here's a data flow list for testing the application using the CAPSENSE button and slider:
- Start the application and ensure that the LED is initially OFF.
- Touch CAPSENSE button 1 (BTN1) to turn the LED OFF. The application should detect the touch event and turn the LED off.
- Touch CAPSENSE button 0 (BTN0) to turn the LED ON. The application should detect the touch event and turn the LED on.
- Touch the slider at different positions to change the brightness. The application should detect the slider position and adjust the brightness of the LED accordingly.
- Observe the LED brightness at each slider position and ensure that it is changing smoothly.
- Repeat steps 2-5 multiple times to ensure that the application is working correctly.
- Stop the application once testing is complete.
This data flow outlines the steps needed to test the application's functionality using the CAPSENSE button and slider. It ensures that the LED responds correctly to touch events and that the slider can adjust the LED's brightness. By following these steps, you can verify that the application is working as expected and that there are no issues with the CAPSENSE button or slider.
CONCLUSIONS
- This blog discusses the Design of my idea for the At The Core Design Challenge using the PSoC6 kit.
- run firmware on both cores and communicate between cores using the Hardware implemented inter-process communication capabilities of the PSoC6.
- implement an IPC pipe to send messages between cores.
- The functional and technical designs are described in detail, and the data flow is explained.
- A functional diagram is describe
- Data Flow of the design is described
- embedded concepts, including Inter-Process Communication IPC, FreeRTOS, scheduler, sensors, Ambient Light Sensor ALS, Temp, and UART. Are examined for consideration for inclusion in my design
- explains the implementation of IPC pipes using the PSoC 6 Peripheral Driver Library (PDL) IPC, which provides a safe and reliable method to transfer data between CPUs.
- explains the FreeRTOS implementation using the PSoC6.
- FreeRTOS is a real-time kernel that provides core real-time scheduling functionality, inter-task communication, timing, and synchronization primitives.
- Bare-metal programming is writing code directly for the hardware without any additional layers, while RTOS provides a set of services for managing tasks, scheduling, synchronization, and communication between software components.
- FreeRTOS simplifies the development process and makes the software more portable. An RTOS can also provide real-time guarantees, ensuring that tasks are scheduled and executed in a deterministic and predictable manner.
- The choice between barebones programming and an RTOS depends on the specific requirements of the embedded system.
- A simple example of FreeRTOS creates two tasks that print messages to the console, while a more complex example involves the development of a low-power CAPSENSE design utilizing the PSoCTm 6 MCU, with 5-segment CAPSENSE slider, and a ganged sensor.
- Another example demonstrates how to create and manage multiple tasks using FreeRTOS in ModusToolbox 3.0, including a producer task that generates random numbers, a consumer task that consumes these numbers and performs some operations, and an LED task that toggles an LED at a fixed interval.
- Explains the implementation of the ALS and Thermistor sensors on the PSoC6
- The Infineon PSoC62S4 has an Ambient Light Sensor (ALS) based on a photodiode that can detect light levels from 0.01 lux to 64,000 lux, and a Thermistor Sensor that can measure temperatures from -40°C to +125°C. These sensors output analog voltage that can be read by the microcontroller's ADC.
- The PSoC6 MCU utilizes the SAR ADC for low-power sensing of these sensors,
- To assign a pin for the ambient light sensor, you can follow some steps to configure the pin and generate the code needed to read the sensor data from the ADC channel. Similarly, you can read data from an analog input pin using the ADC in ModusToolbox 3.0.
- Finally, Buttons are explained.
- Ultimately, the choice between USER and CAPSENSE buttons will depend on the specific needs and constraints of the project, as well as the trade-offs between cost, functionality, and design considerations.
- I decided to go with CAPSENSE. I ran and analyzed the CAPSENSE :PSoC 6 MCU: CAPSENSE buttons and slider example,
- I will use this example as a guide to create a minimal test with the 2 Capsense buttons
Now onto the next blog. I decided to devote it to migrating an example MTB 2.x to version 3.x
RESOURCES
GitHub - Infineon/mtb-example-psoc6-low-power-sar-adc-thermistor-als
This code example demonstrates low-power sensing of a thermistor and ambient light sensor (ALS) using the SAR ADC of the PSoC 6 MCU. This code example is supported only for devices that have an SAR ADC capable of operating in System deepsleep mode.
GitHub - Infineon/mtb-example-psoc6-dual-cpu-ipc-pipes
This example demonstrates how to use the inter-processor communication (IPC) driver to implement a message pipe in PSoC 6 MCU. The pipe is used to send messages between CPUs.
GitHub - Infineon/mtb-example-psoc6-low-power-capsense-freertos:
This code example demonstrates how to create a low-power CAPSENSE design using PSoC 6 MCU. This example features a 5-segment CAPSENSE slider and a ganged sensor, and displays the detected touch position over the serial terminal. This example uses the CAPSENSE middleware library.
GitHub - Infineon/mtb-example-psoc6-capsense-buttons-slider:
Uses a 5-segment CapSense slider and two CapSense buttons to control an LED.
BLOG#1-Introduction |
Blog#2--Design |
Blog#3- Migrating an example MTB 2.x to version 3.x |
BLOG#4-Testing |
BLOG#5-Implementation and Challenge Conclusion |
Top Comments