why does my OLED display with the SH1106 driver have a horizontal offset? - i2c

I have a Nucleo F401RE and want to use my display with it. I have those two I2C functions:
void sh1106_sendCMD(uint8_t cmd)
{
uint8_t reg_cmd[2];
reg_cmd[0] = 0x00;
reg_cmd[1] = cmd;
HAL_I2C_Master_Transmit(&hi2c1, OLED_I2C_ADDRESS, reg_cmd, 2, HAL_MAX_DELAY);
}
void sh1106_sendData(uint8_t data)
{
uint8_t reg_data[2];
reg_data[0] = 0x40; // write data
reg_data[1] = data;
HAL_I2C_Master_Transmit(&hi2c1, OLED_I2C_ADDRESS, reg_data, 2, HAL_MAX_DELAY);
}
Then I have an init function to initialize the Display
void sh1106_init(void)
{
// Initialize the display
sh1106_sendCMD(0xAE); // Set display off
sh1106_sendCMD(0xD5); // Set display clock divide ratio/oscillator frequency
sh1106_sendCMD(0x80); // Set display clock divide ratio/oscillator frequency
sh1106_sendCMD(0xA8); // Set multiplex ratio
sh1106_sendCMD(0x3F); // 1/64 duty
sh1106_sendCMD(0xD3); // Set display offset
sh1106_sendCMD(0x00); // No offset
sh1106_sendCMD(0x40); // Set start line address
sh1106_sendCMD(0xA1); // Set segment re-map
sh1106_sendCMD(0xC8); // Set COM output scan direction
sh1106_sendCMD(0xDA); // Set COM pins hardware configuration
sh1106_sendCMD(0x12);
sh1106_sendCMD(0x81); // Set contrast control register
sh1106_sendCMD(0xFF); // Maximum contrast
sh1106_sendCMD(0xA4); // Set display to RAM
sh1106_sendCMD(0xA6); // Set normal display
sh1106_sendCMD(0xD9); // Set pre-charge period
sh1106_sendCMD(0xF1);
sh1106_sendCMD(0xDB); // Set VCOMH deselect level
sh1106_sendCMD(0x40);
sh1106_sendCMD(0x8D); // Set charge pump enable/disable
sh1106_sendCMD(0x14); // Enable charge pump
sh1106_sendCMD(0xAF); // Set display on
}
When I then try to set the pixel (x=0, y=0), nothing happens, but when I set the pixel (x=2, y=0) the pixel (x=0, y=0) turns on. Somehow I have a horizontal offset of -2.
I set the pixel like this:
sh1106_sendCMD(0xB0 | 0); // Set the page address
sh1106_sendCMD(0x02); // Set the lower column address
sh1106_sendCMD(0x10); // Set the higher column address
sh1106_sendData(0x01);

You probably have a 128 by 64 pixel display. The SH1106 however supports up to 132 by 64 pixel. So there are 4 unused pixel columns.
The easiest solution is to always add 2 to all x-coordinates.
If you feel more adventurous, you can configure the SH1106 accordingly. Given the limited information about your display, I can only guess. I could be:
sh1106_sendCMD(0x42);
replacing:
sh1106_sendCMD(0x40);
See the SH1106 datasheet for more information.

Related

Unreset GPIO Pins While System Resetting

Is it possible to unreset some GPIO pins while using NVIC_SystemReset function at STM32 ?
Thanks in advance for your answers
Best Regards
I try to reach NVIC_SystemReset function. But not clear inside of this function.
Also this project is running on KEIL
Looks like it is not possible, NVIC_SystemReset issues a general reset of all subsystems.
But probably, instead of system reset, you can just reset all peripheral expect one you need keep working, using peripheral reset registers in Reset and Clock Control module (RCC): RCC_AHB1RSTR, RCC_AHB2RSTR, RCC_APB1RSTR, RCC_APB1RSTR.
Example:
// Issue reset of SPI2, SPI3, I2C1, I2C2, I2C3 on APB1 bus
RCC->APB1RSTR = RCC_APB1RSTR_I2C3RST | RCC_APB1RSTR_I2C2RST | RCC_APB1RSTR_I2C1RST
| RCC_APB1RSTR_SPI3RST | RCC_APB1RSTR_SPI2RST;
__DMB(); // Data memory barrier
RCC->APB1RSTR = 0; // deassert all reset signals
See detailed information in RCC registers description in Reference Manual for your MCU.
You may also need to disable all interrupts in NVIC:
for (int i = 0 ; i < 8 ; i++) NVIC->ICER[i] = 0xFFFFFFFFUL; // disable all interrupts
for (int i = 0 ; i < 8 ; i++) NVIC->ICPR[i] = 0xFFFFFFFFUL; // clear all pending flags
if you want to restart the program, you can reload stack pointer to its top value, located at offset 0 of the flash memory and jump to the start address which is stored at offset 4. Note: flash memory is addressed starting from 0x08000000 address in the address space.
uint32_t stack_top = *((volatile uint32_t*)FLASH_BASE);
uint32_t entry_point = *((volatile uint32_t*)(FLASH_BASE + 4));
__set_MSP(stack_top); // set stack top
__ASM volatile ("BX %0" : : "r" (entry_point | 1) ); // jump to the entry point with bit 1 set

LIS3DH sensor do not generate interrupt on pin

I config a LIS3DH sensor to generate interrupt when acceleration in any direction exceeds a certain threshold. I read INT1_SRC register and when I shake my board, IA bit is set and reset again. From this I realized that interrupt has been generated but I can not capture it on pin. I also check INT1 pin with oscilloscope but no signal.
Do any one know where would be the problem?
my configuration for sensor:
/* High-pass filter enabled on interrupt activity 1 */
lis3dh_high_pass_int_conf_set(&dev_ctx, LIS3DH_ON_INT1_GEN);
/* Enable HP filter for wake-up event detection.*/
/* Use this setting to remove gravity on data output */
lis3dh_high_pass_on_outputs_set(&dev_ctx, PROPERTY_ENABLE);
/* Enable AOI1 on int1 pin */
lis3dh_pin_int1_config_get(&dev_ctx, &ctrl_reg3);
ctrl_reg3.i1_ia1 = PROPERTY_ENABLE;
lis3dh_pin_int1_config_set(&dev_ctx, &ctrl_reg3);
/* Interrupt 1 pin latched */
lis3dh_int1_pin_notification_mode_set(&dev_ctx, LIS3DH_INT1_LATCHED);
/* Set full scale to 2 g */
lis3dh_full_scale_set(&dev_ctx, LIS3DH_2g);
/* Set interrupt threshold to 0x10 -> 250 */
lis3dh_int1_gen_threshold_set(&dev_ctx, 0x05);
/* Set no time duration */
lis3dh_int1_gen_duration_set(&dev_ctx, 0);
/* Dummy read to force the HP filter to current acceleration value. */
lis3dh_filter_reference_get(&dev_ctx, &dummy);
/* Configure wake-up interrupt event on all axis */
lis3dh_int1_gen_conf_get(&dev_ctx, &int1_cfg);
int1_cfg.zhie = PROPERTY_ENABLE;
int1_cfg.yhie = PROPERTY_ENABLE;
int1_cfg.xhie = PROPERTY_ENABLE;
int1_cfg.aoi = PROPERTY_DISABLE;
lis3dh_int1_gen_conf_set(&dev_ctx, &int1_cfg);
/* Set device in HR mode */
lis3dh_operating_mode_set(&dev_ctx, LIS3DH_HR_12bit);
/* Set Output Data Rate to 100 Hz */
lis3dh_data_rate_set(&dev_ctx, LIS3DH_ODR_100Hz);

STM32 Use DMA to generate bit pattern on GPIO PIN

I am trying to generate a bit pattern on a GPIO pin. I have set-up the DMA engine to transfer from an array of GPIO pin states to the GPIO BSRR register
Here is the code I am using to configure the DMA
hdma_tim16_ch1_up.Instance = DMA1_Channel3;
hdma_tim16_ch1_up.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_tim16_ch1_up.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim16_ch1_up.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim16_ch1_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_tim16_ch1_up.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_tim16_ch1_up.Init.Mode = DMA_NORMAL;
hdma_tim16_ch1_up.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_tim16_ch1_up) != HAL_OK)
{
Error_Handler();
}
/* Several peripheral DMA handle pointers point to the same DMA handle.
Be aware that there is only one channel to perform all the requested DMAs. */
__HAL_LINKDMA(tim_baseHandle,hdma[TIM_DMA_ID_CC1],hdma_tim16_ch1_up);
__HAL_LINKDMA(tim_baseHandle,hdma[TIM_DMA_ID_UPDATE],hdma_tim16_ch1_up);
Here is the code I use to setup the transfer:
uint32_t outputbuffer[] = {
0x0000100,0x01000000,
0x0000100,0x01000000,
0x0000100,0x01000000,
0x0000100,0x01000000,
0x0000100,0x01000000,
0x0000100,0x01000000,
0x0000100,0x01000000
/* ... */
};
if (HAL_DMA_Start_IT(htim16.hdma[TIM_DMA_ID_UPDATE], (uint32_t)outputbuffer, (uint32_t)&GPIOG->BSRR, 14) != HAL_OK)
{
/* Return error status */
return HAL_ERROR;
}
__HAL_TIM_ENABLE_DMA(&htim16,TIM_DMA_UPDATE);
HAL_TIM_Base_Start_IT(&htim16);
I am expecting to see every time the counter overflows, the DMA transfers 32 bits from the array and increments to the next array position until the DMA CNDTR register reads 0.
I set up a GPIO pin to toggle every time the timer over flows and I setup an alternating bit pattern in the array. I would expect the two GPIO pins to be similar in their output shape but I get one longer pulse on the line connected to the DMA. Any tips would be greatly appreciated
configure TIM2 as input capture direct mode (TIM2_CH1)
configure TIM2 DMA direction "memory to peripheral"
configure TIM2 data width Half word / Half word
configure GPIO pins as GPIO_OUTPUT, for example 16 pins GPIOD0..GPIOD15
copy and paste HAL_TIM_IC_Start_DMA() function from HAL library and give it a new name MY_TIM_IC_Start_DMA()
find HAL_DMA_Start_IT() function call in MY_TIM_IC_Start_DMA()
replace (uint32_t)&htim->Instance->CCR1 with (uint32_t)&GPIOD->ODR
if (HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC1], (uint32_t)&GPIOD->ODR, (uint32_t)pData, Length) != HAL_OK)
Now you can start DMA to GPIO transfer by calling
MY_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_1,(uint32_t*)gpioBuffer,GPIO_BUFFER_SIZE);
Actual transfer must be triggered by providing pulses on TIM2_CH1 input pin (for example, by using output compare pin from other timer channel). Those pulses originally was used to save Timer2 CCR1 register values to DMA buffer. Code was tweaked to transfer DMA buffer value to GPIOD ODR register.
For GPIO to Memory transfer change TIM2 DMA direction to "peripheral to memory", configure GPIO pins as GPIO_INPUT and use GPIOD->IDR instead of ODR in HAL_DMA_Start_IT parameters in modified MY_TIM_IC_Start_DMA() function.

two wire ADC, SPI read

I use Arduino UNO (Atmega328) and a 12bit ADC component (AD7893ANZ-10), datasheet available on https://www.analog.com/media/en/technical-documentation/data-sheets/AD7893.pdf
The problem:
I tried already few different codes but the read value is wrong always, even when the SDATA pin of ADC is not connected to MISO pin of Arduino, I get the same values (See figure1 here). I simulated it in proteus(See figure2 here) and used the virtual serial monitor in proteus. The MOSI and SS pin of Arduino are not connected but I set SS pin in code to LOW and HIGH to fullfill libraries requirements. More information about the timing of ADC is added as comments into the code below. Or availabe in the datasheet. I would be thanksfull if you take a look on it due I cant figure out what I did wrong.
PS: The ADC has just to pins to communicate with SPI: 1.SDATA(slaveout) and 2.SCLK. The pin CONVST on ADC is to initiate a conversion.
#include <SPI.h>
//source of code https://www.gammon.com.au/spi
void setup() {
Serial.begin (115200);
pinMode(7, OUTPUT); // this pin is connected to convst on adc to initiate conversion
// Put SCK, MOSI, SS pins into output mode (introductions from source)
// also put SCK, MOSI into LOW state, and SS into HIGH state.
// Then put SPI hardware into Master mode and turn SPI on
pinMode(SCK, OUTPUT);
pinMode(MOSI, OUTPUT);
pinMode(SS, OUTPUT);
digitalWrite(SS, HIGH);
digitalWrite(SCK, LOW);
digitalWrite(MOSI, LOW);
SPCR = (1<<MSTR);
SPI.begin ();
SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE1)); // set the clk frequency; take the MSB first;
//mode1 for CPOL=0 and CPHA=1 according to datasheet p.9 "CPOL bit set to a logic zero and its CPHA bit set to a logic one."
}
int transferAndWait (const byte what) //methode to read data
{
int a = SPI.transfer(what); //send zero(MOSI not connected)and read the first 8 bits and save
Serial.println (a); // show the value, in serial monitor -1
a =(a << 8); //shift the saved first 8 bits to left
Serial.println (a); // show the value, in serial monitor 255
a = a | SPI.transfer(what); //read and save the last 8 bits
Serial.println (a); // show the value, in serial monitor -256
delayMicroseconds (10);
return a;
}
void loop() {
int k;
digitalWrite(7, HIGH); //set pin high to initiate the conversion
delayMicroseconds(9); //the conversion time needed, actually 6 mikroseconds
digitalWrite(SS, LOW); // enable Slave Select to get the library working
k = transferAndWait (0); //call the method
delayMicroseconds(1);
digitalWrite(7, LOW);
digitalWrite(SS, HIGH); //disable chip select
delay(2000); //delay just to keep the overview on serial monitor
Serial.println (k); // show the value, in serial monitor -1
}
First the the return variable should be an unsigned int instead of a signed int.
Also the CONVST should only be low for a short period as conversion is started afterwards. See Timing sequence.

Would my solution work for 8-bit bus addressing using BSRR and BRR?

I have set an 8-bit bus on (PD0:PD7) on the stm32 MCU to send addresses to another chip (0:255). I am interested if a function like below would work for fast change in addresses. I could not find an example directly showing register to be equal to integer so I want to confirm it would work. I need a function to which I will give an integer value for the address (0:255) and it will set the 8 pins of the bus with this value:
void chipbus(uint16_t bus8){
GPIOD->regs->BSRR = bus8; // set all the '1' in bus8 to high
GPIOD->regs->BRR = 255-bus8; // 255-bus8 inverts the 8 bits
// BRR to set the new '1' to low
}
If this solution works, I am curious also if I change the bus to ports PD5:PD12 would my function work as:
void chipbus(uint16_t bus8){
GPIOD->regs->BSRR = bus8*32; // set all '1' in bus8 to high
// multiply by 32 to shift 5 bits/pins
GPIOD->regs->BRR = (255-bus8)*32; // 255-bus8 inverts the 8 bits
// BRR to set the new '1' to low
}
Thank you!
Yes, both should work. However, a more recognisable but equivalent formulation would be:
void chipbus(uint16_t bus8) {
GPIOD->regs->BSRR = bus8; // set all the '1' in bus8 to high
GPIOD->regs->BRR = (~bus8) & 0xFF; // inverts the 8 bits // BRR to set the new '1' to low
}
void chipbus(uint16_t bus8) {
GPIOD->regs->BSRR = bus8<<5; // set all '1' in bus8 to high, shift 5 bits
GPIOD->regs->BRR = ((~bus8)&0xFF)<<5; // inverts the 8 bits
}
But, there is an even faster way. BSRR is a 32bit register than can both set and reset. You can combine the two write accesses into one:
void chipbus(uint16_t bus8) {
GPIOD->regs->BSRR =
(bus8<<5) | (((~bus8) & 0xFF) << (16+5));
}
Happy bit-fiddling!
Yes, it'd definitely work. However, as others have noted, it's advisable to set the outputs in a single operation.
Taking advantage of the full 32 bit BSRR register, it can be done without inverting the data bits:
GPIOD->regs->BSRR = bus8 | (0xFF << 16);
or
GPIOD->regs->BSRR = (bus8 << 5) | (0xFF << (16 + 5));
because BSRR has the functionality to set some bits and reset some others in a single write operation, and when both the set and reset bits for a particular pin is set, the output becomes 1.
I wouldn't recommend using this approach. Writing to BSRR and BRR as two separate steps means that the bus will transition through an unintended state where some bits are still set from the previous value.
Instead, consider writing directly to the GPIO output data register (ODR). If you need to preserve the original value of the upper bits in the port, you can do that on the CPU side:
GPIOD->regs->ODR = (GPIOD->regs->ODR & 0xff00) | (bus8 & 0x00ff);