Summary
This week I have been working with my friend Hassane El Khoury on his hobby project which has the SSD1306 in it. [if you want to know more about the project, leave a comment and maybe he will answer the question]. I have been helping him get a number of things going including this display which I have written about before. This article describes the integration of the SSD1306 OLED Display into his project with the PSoC 6 & FreeRTOS.
Firmware Architecture
Hassane started his career as an Automotive System Designer, then he worked as an Applications Engineer, so he has “mad” skills in this area. He is building his project with a PSoC 6 and is heavily using FreeRTOS (which is new for him). He has been distracted with some other thing the last few years, so I have been serving as a consultant on the latest PSoC stuff to smooth the learning curve. In this case PSoC6, FreeRTOS and the SSD1306 OLED Display. I have only used the SSD1306 OLED Display with PSoC 4 and WICED, but I ASSUMED (incorrectly assumed… which I will talk about in the next article) that there would be no problems making it work in that environment.
Hassane is all about reuse and he had copied the design of this section of his project from my article on using queues with FreeRTOS. It looks like this.
- There is a task that manages the I2C and talks directly to the SCB hardware and the I2C devices
- There is a task that manages the OLED
Originally the OLED task was talking directly to the I2C via the SCB (the red line on the picture). Remember from the article on the U8G2 library, the hardware abstraction layer looks like this. Basically it
- Sends an I2C Start at the start of the U8G2 transaction
- Sends an I2C Stop at the end of the tranaction
- Send the number of bytes called for.
uint8_t u8x8_I2C_HW(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { uint8_t *data; switch(msg) { case U8X8_MSG_BYTE_SEND: data = (uint8_t *)arg_ptr; while( arg_int > 0 ) { Cy_SCB_I2C_MasterWriteByte(I2Cm1_HW, *data, I2C_TIMEOUT, &I2Cm1_context); data++; arg_int--; } break; case U8X8_MSG_BYTE_START_TRANSFER: Cy_SCB_I2C_MasterSendStart(I2Cm1_HW, u8x8_GetI2CAddress(u8x8)>>1, CY_SCB_I2C_WRITE_XFER, I2C_TIMEOUT, &I2Cm1_context); break; case U8X8_MSG_BYTE_END_TRANSFER: Cy_SCB_I2C_MasterSendStop(I2Cm1_HW, I2C_TIMEOUT, &I2Cm1_context); break; default: return 0; } return 1; }
The problem with this design is that it takes over the I2C hardware and requires exclusive control. Moreover, it leaves you program hung while the transaction is happening. To solve this problem, there are three options
- “Assign” the SCB block to the exclusive use of this task (makes it hard to share with other I2C devices)
- Create a mutex for the SCB. This will work, but it will not allow the task to “queue” transactions and move on
- Create a task to manage the SCB with a queue (the option that Hassane chose)
The I2C message queue holds complete transactions. Each transaction (which he called I2Cm_transaction_t) looks like this:
typedef enum I2Cm_transactionMethod { I2CM_READ, I2CM_WRITE } I2Cm_transactionMethod_t; typedef enum I2Cm_registerAddresType { I2CM_NONE, I2CM_BIT8, I2CM_BIT16 } i2cm_registerAddressType_t; typedef struct I2Cm_transaction { I2Cm_transactionMethod_t I2Cm_method; // I2CM_READ or I2CM_WRITE uint8_t I2Cm_address; // The I2C Address of the slave i2cm_registerAddressType_t I2Cm_regType; // I2CM_8BIT or I2CM_16BIT uint16_t I2Cm_register; // The register in the slave uint8_t *I2Cm_bytes; // A pointer to the data to be written (or a place to save the data) uint32_t *I2Cm_bytesProcessed; // A return value with the number of bytes written uint32_t I2Cm_byteNum; // How many bytes are in the request SemaphoreHandle_t I2Cm_doneSemaphore; // If you want a semaphore flagging that the transaction is done } I2Cm_transaction_t;
In order to fix the the HAL you just sets up a transaction at the start of the task:
memset(&OLEDtransaction,0,sizeof(OLEDtransaction)); OLEDtransaction.I2Cm_method = I2CM_WRITE; OLEDtransaction.I2Cm_address = OLED_ADDRESS_1; OLEDtransaction.I2Cm_regType = I2CM_NONE; OLEDtransaction.I2Cm_bytes = buff; OLEDtransaction.I2Cm_bytesProcessed = &numProcessed; OLEDtransaction.I2Cm_doneSemaphore = xSemaphoreCreateBinary();
Then queue up the bytes into a buffer, and flush the buffer when the “U8X8_MSG_BYTE_END_TRANSFER” message comes
uint8_t u8x8_I2C_HW(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { uint8_t *data; switch(msg) { case U8X8_MSG_BYTE_SEND: data = (uint8_t *)arg_ptr; while( arg_int > 0 ) { buff[buffCount] = *data; buffCount += 1; CY_ASSERT(buffCount < BUFFSIZE); data++; arg_int--; } break; case U8X8_MSG_BYTE_START_TRANSFER: buffCount = 0; break; case U8X8_MSG_BYTE_END_TRANSFER: OLEDtransaction.I2Cm_byteNum = buffCount; I2Cm1RunTransaction(&OLEDtransaction); break; default: return 0; } return 1; }
SSD1306 OLED Display
Now, the short block of test code works perfect:
u8x8_Setup(&MyI2Cu8x8, u8x8_d_ssd1306_128x32_univision, u8x8_cad_ssd13xx_i2c, u8x8_I2C_HW, u8x8_gpio_and_delay_PSoC6); u8x8_InitDisplay(&MyI2Cu8x8); u8x8_SetPowerSave(&MyI2Cu8x8,0); u8x8_ClearDisplay(&MyI2Cu8x8); u8x8_SetFont(&MyI2Cu8x8,u8x8_font_amstrad_cpc_extended_f); u8x8_DrawString(&MyI2Cu8x8,0,0," Killer Mustang "); u8x8_DrawString(&MyI2Cu8x8,0,1," Test OLED "); u8x8_DrawString(&MyI2Cu8x8,0,2,"________________");
And I get a nice output (I wonder what a “Killer Mustang” is?)
In the next article Ill talk about the SS1306 and a problem that I created for myself.
4 Comments
Is the full code for the OLED and FreeRTOS integration available on Git?
I thought I uploaded it to github… but apparently not.
Ill see if I can find it.
Alan
Hi Alan,
Did you manage to find the code to push up to GitHub?
I really am looking for a robust I2C task handler to use with the OLED and have it share the I2C with other tasks
Thanks
William
Yes… all of that code came from the referenced P4.
It is available here:
git@github.com:iotexpert/PSoC-FreeRTOS-Examples.git